Peppermint C89 port

This repository root is a clean native C89 implementation of the behavior that is implemented in the original TypeScript Peppermint tree, with no TypeScript, Node.js, Beef, or third-party runtime dependency.

Original project: https://github.com/lainq/peppermint

Build

make
./peppermint smoke.ppm hello

The build uses -std=c89 -pedantic -Wall -Wextra -Wconversion -Wshadow. The language core is ISO C89; the project-oriented CLI commands use the usual POSIX file APIs (mkdir, stat, getcwd) so init/run/list can create and discover files as the TypeScript CLI does.

CLI

Supported commands:

peppermint run [--file=FILE] [--args ARGS...]
peppermint init --name=NAME [--author=AUTHOR] [--license=LICENSE]
peppermint list
peppermint version
peppermint bf --file=FILE [--compile=true]
peppermint FILE.ppm [ARGS...]

Notes:

  • peppermint FILE.ppm [ARGS...] preserves the first C89 port's direct-file behavior.
  • run --file=... executes a Peppermint source file.
  • run without --file reads .peppermint in the current directory and uses its main string field.
  • run --args ... passes the remaining values to @argv / @argv:N.
  • init creates README.md, app.ppm, tests/test.ppm, and .peppermint.
  • list reads a simple project store. Set PEPPERMINT_STORE=/path/to/store to override the default $HOME/.peppermint/store.json location.
  • version prints Peppermint Version 0.0.1[platform].
  • bf is a separate Brainfuck runner. With --compile=true, it also writes the original Long-language-style output to dist/test.long. The compiler follows the upstream static conversion pass: it records cell values observed at . instructions and ignores Brainfuck loop/control execution while compiling.

Language behavior

Implemented:

  • let name := expression constant bindings.
  • let name = expression mutable bindings.
  • name = expression assignment to existing mutable bindings.
  • integer and floating-point literals, including unary minus.
  • quoted strings with \n, \r, \t, and escaped-character handling.
  • +, -, *, /, and % with normal arithmetic precedence.
  • string concatenation with + when both operands are strings.
  • relational operators tokenized by the TypeScript lexer: ==, !=, >=, <=, >, and <. They produce 1 for true and 0 for false.
  • parenthesized expressions.
  • # comments and line-oriented programs.
  • @argv, @argv:N, @env:NAME, and @exit.
  • robust file/line diagnostics for syntax and runtime errors.

Upstream gaps and compatibility notes

The TypeScript parser in the upstream tree is incomplete: it tokenizes assignment (=), constants (:=), and relational operators, but only executes arithmetic builtins in practice and does not build a real AST. This C89 port therefore implements the practical behavior those tokens imply instead of claiming byte-for-byte parser parity. Unsupported higher-level semantics such as functions, blocks, imports, conditionals, loops, objects, and a module system are not present in the TypeScript implementation and are intentionally not fabricated here.

The TypeScript project store was a JSON file under the compiled CLI directory. A single native executable has no such package directory, so the C89 CLI uses a portable tab-separated project store at $HOME/.peppermint/store.json (or PEPPERMINT_STORE) while keeping the same user-visible init/list workflow.

Tests

./test.sh

The test script rebuilds with strict C89 flags, then checks legacy direct-file execution, command dispatch, expressions, relational operators, mutable vs. constant assignment, builtins, project init/config/list/run behavior, version output, and the Brainfuck runner/compiler command including static Long output semantics.

S
Description
No description provided
Readme
57 KiB
Languages
C 92.1%
Shell 7.5%
Makefile 0.4%