Even a small language needed four explicit boundaries
For a programming-languages assignment, I implemented a mini imperative language with declarations, assignment, output, repetition, and selection in both Python and C++. Source text is not executed directly; it passes through:
source
-> tokenizer
-> recursive-descent parser + symbol table
-> AST
-> tree-walk evaluator + environment
The tokenizer only separates multi-character comparisons before single-character delimiters. Lexical-validation helpers invoked by the parser, including is_var_name and is_number, enforce identifier and number lengths and reserved words. Parser functions correspond to program, declaration, statement, block, boolean-expression, and arithmetic-expression rules.
Syntax and selected semantic failures are rejected during parsing
The symbol table stores whether a name is a variable or constant and its initial value. Duplicate declarations, undeclared identifiers, and constant reassignment fail before AST execution. The runtime environment begins from that table, while AssignStmt, RepeatStmt, and SelectStmt mutate or read it.
Expressions and statements are represented by nodes with eval and execute rather than one monolithic switch. A BinExpr evaluates two children, and SelectStmt walks one block based on a boolean result. Parsing responsibility and execution responsibility remain separable when adding a construct.
Re-executing the source revealed a contract more important than the diagram
For this audit, I verified that the local file had the same blob hash as public main and ran it directly. Explicit (1 + 2) * 3 prints 9, while assigning to a constant produces Syntax Error!.
The README says the grammar provides conventional precedence, but parse_aexpr handles +, -, and * in one loop:
while self.peek() in ("+", "-", "*"):
op = self.pop()
right = self.parse_term()
expr = BinExpr(op, expr, right)
Consequently, 1 + 2 * 3 is left-associated as (1 + 2) * 3 and evaluates to 9 rather than 7. The current RepeatStmt also breaks when its condition is false, so the README’s until(i > 3) example stops at 1 after the first iteration.
A learning record is stronger when it preserves the mismatch
The current source demonstrates an implemented compiler frontend, but it has no formal test suite and contains documentation-to-runtime mismatches. I do not present it as a production language with proven correctness.
A repair would introduce a separate multiplicative parse layer, reconcile repeat semantics with the original specification, and fix valid and invalid programs as executable fixtures. The most useful lesson from the audit was not the number of AST classes. A language contract is complete only when grammar, parser, and examples say the same thing under tests.