name: monkeycpp-interpreter description: >- Guides implementation of the Monkey-style C++ interpreter in lang2 (lexer, parser, AST, objects, environment, evaluator, REPL). Use when adding language features, fixing parser/eval bugs, or changing the REPL; use when the user mentions monkeycpp, lang2, or this interpreter codebase.
Monkey-style C++ interpreter (lang2)
Architecture
Data flow: Lexer (token.h, lexer.*) → Parser (parser.*, ast.h) → Evaluator (evaluator.*) using Environment (environment.*) and Value (object.*). Builtins: builtins.*. Entry: main.cpp (REPL or argv[1] file).
| Layer | Responsibility |
|---|---|
Token / Lexer |
Keywords, literals, [ ] % <= >=, etc. |
ast.h |
Arrays (ArrayLiteralExpr), index (IndexExpressionExpr), ThisExpressionExpr, … |
Parser |
Pratt loop: calls (, index [, member ., assign =; precedence INDEX before MEMBER. |
Value (object.*) |
Kind includes Array, Builtin; arrays use shared_ptr<vector<Value>>. |
Environment |
tryGet (optional), get (throws), set, assign. |
builtins.* |
len, puts, first, last, rest, push; builtinMap() + evalIdentifier fallback after tryGet. |
Evaluator |
applyFunction returns last block expression value (like lang1); callValue handles Builtin. |
Project conventions
- C++17, sources under
src/, include path-I src(or CMaketarget_include_directoriesonsrc). - Match existing style: minimal comments, straightforward
dynamic_castfor AST dispatch,std::shared_ptrfor env and heap objects,std::unique_ptrfor AST owned byProgram. - Do not add unrelated refactors or new docs unless asked.
Critical invariants
AST lifetime
FunctionObject/ClassObjectmethods holdBlockStatement*(and similar) into the parse tree. The REPL keepsstd::vector<std::unique_ptr<Program>> program_storageinmain.cppso closures survive across lines. Any new code path that stores AST pointers must keep the owningProgramalive for as long as those pointers are used.thisin methodsapplyFunction(..., this_binding)setsthison the function env. Member access requiresValue::Kind::Instance.Parser pitfalls (already fixed once; preserve behavior)
new Name(arg): After(, decide empty vs args usingcurTokenIs(RPAREN), notpeekTokenIsonly—otherwise a single arg is skipped whenpeekis already).- Empty
()inparseFunctionParameters: do not advance past)twice; parent usesexpectPeek(LBRACE)for method body. - Class body: parse methods while
curTokenIs(FUNCTION), then expect closing}; stray;after}is handled byparseProgramskipping leading;.
How to add a language feature
Work in order; compile after each step.
token.h/token.cpp
NewTokenTypeandToken::typeNameentry.lexer.cpp
Single-char tokens innextToken;lookupIdentfor new keywords.ast.h
New node types; statement vs expression; ownership (unique_ptrfor child AST).parser.h/parser.cpp- New statement: branch in
parseStatement. - New prefix: branch in
parsePrefixExpression. - New infix: extend
parseExpressionloop /peekPrecedence/tokenToPrecedence(e.g.[for index = between CALL and MEMBER or as specified).
- New statement: branch in
object.h/object.cpp
ExtendValue::Kindandinspect()if a new runtime type is needed (e.g. array for indexing).evaluator.cpp
Newdynamic_castbranches inevalStatement/evalExpression; updatecallValueif new callable type.main.cpp
If REPL should print new expression forms, adjust the single-statement print rule (currently skips printing null).
Build
c++ -std=c++17 -Wall -Wextra -o monkeycpp src/main.cpp src/token.cpp src/lexer.cpp src/parser.cpp src/object.cpp src/environment.cpp src/builtins.cpp src/evaluator.cpp -I src
Or CMake: cmake -B build -S . && cmake --build build (add new .cpp files to CMakeLists.txt).
File map
| File | Role |
|---|---|
src/main.cpp |
REPL loop, program_storage, parse errors, runtime errors |
src/lexer.* |
Scanning |
src/parser.* |
Parsing |
src/ast.h |
AST definitions only |
src/object.* |
Runtime values |
src/environment.* |
Scopes |
src/evaluator.* |
Execution |
src/builtins.* |
Builtin implementations and registry |
CMakeLists.txt |
Executable sources list |