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.runwithout--filereads.peppermintin the current directory and uses itsmainstring field.run --args ...passes the remaining values to@argv/@argv:N.initcreatesREADME.md,app.ppm,tests/test.ppm, and.peppermint.listreads a simple project store. SetPEPPERMINT_STORE=/path/to/storeto override the default$HOME/.peppermint/store.jsonlocation.versionprintsPeppermint Version 0.0.1[platform].bfis a separate Brainfuck runner. With--compile=true, it also writes the original Long-language-style output todist/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 := expressionconstant bindings.let name = expressionmutable bindings.name = expressionassignment 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 produce1for true and0for 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.