← Contents

Appendix C

Source Map

The vocabulary the book uses — the general compiler terms and this project's own — is in Appendix D.

Where to look for what

If you are changingStart inAlso read
tokenization, quoting, heredocsLexer.cpp, Token.hChapter 4
statement or expression syntaxParser.cpp, Ast.hChapters 5 and 7
a user-declared operatorParser.cpp parseSub, the userInfix_ familyChapter 6
what a value isValue.hChapter 8
string performanceValue.h CowStr, BuiltinsShared.hChapters 9 and 24
the number towerBigInt.cpp, IntOps.h, Value::ratChapter 11
scoping, assignment, bindingInterpreter.cpp lvalue, evalAssignChapter 12
how lists and argument lists are storedValueVec.h RVec, Value.h's ValueListChapter 12
calls and signaturesInterpreter.cpp callCallableRaw, bindParamsChapter 14
return, next, last, whenthe cooperative registers in ExecContextChapter 15
a built-in routineBuiltins.cpp registerBuiltinsChapter 16
a built-in methodthe four methodCall segments, in orderChapters 2 and 16
classes, roles, mixinsInterpreter.cpp ClassDecl handling, Value.hChapter 17
laziness, gatherLazySeqState, seqOp, the gather stackChapter 18
interpreter speedevalBinary, evalIndex, the decided-once fieldsChapter 19
regex syntaxRegex.cpp parseAtomChapter 20
regex matchingRegex.cpp matchNodeChapter 21
grammarsGrammarMatcher, Interpreter::grammarParseChapter 22
alternation rankingLtmNfa.cppChapter 23
UnicodeUnicode.cpp, tools/ucd/, the generatorsChapter 24
the CLI and compile driversmain.cppChapter 25
the native compilerCodegen.cpp, the rt* helpers in Interpreter.hChapters 26 to 28
what a binary keepsSlimScan.cpp, ucd_seam.h, src/stubs/Chapter 29
the parse cacheAstSerial.cppChapter 30
the JavaScript back endsrc/codegen/Js.cpp, JsRuntimeSrc.cpp, src/js-rt/Chapter 31
the browser buildrakujs/rakupp_web.cpp, rakujs/build.sh, raku.jsChapter 32
module loadingInterpreter.cpp loadModule, Parser.cpp scanModuleOpsChapter 33
the installer and the storetools/install.raku, Builtins.cpp .installChapter 34
nqp:: opsParser::makeNqpOp, Interpreter::evalNqpOpChapter 35
NativeCallFfi.cpp, Interpreter::callNativeChapter 36
the extension ABIrakupp_ext.h, ExtApi.cppChapter 37
embedding — Raku inside a hostrakupp.h, EmbedApi.cppChapter 37
threads, the GIL, suppliesInterpreter.h's concurrency sectionChapter 38
lint, highlight, profile, REPLLint.cpp, Highlight.cpp, Profiler.cpp, Repl.cppChapter 39
the undeclared-variable gateDeclCheck.cpp, isSpecialVar in Interpreter.cppChapter 39
the MCP serverMcpServer.cppChapter 39

Rules that are easy to break by accident

Collected here because each has cost real debugging time.

The method-dispatch chain is order-sensitive. The four segments are ordered slices of one function; later arms deliberately catch what earlier ones decline. Moving an arm for readability is a behaviour change.

A decided-once field may hold a fact about the syntax, never a value that can change. The literal cache is the one exception, and a literal is a constant by definition.

Never store an FnRef. It borrows the caller's lambda; storing it dangles.

A pointer is only a valid map key when its target's lifetime is at least the map's. AST nodes are never freed, so a flip-flop state map keyed on one is fine. Regex nodes are freed and their addresses recycled, which is why a cache keyed on one produced automata built for other patterns.

Intern a closed vocabulary, never an open one. The intern table is append-only, so a field that can hold arbitrary runtime data would leak an entry per distinct value.

Do not add a non-const operator[], begin() or data() to CowStr. That is exactly the interface that made copy-on-write non-conforming for std::string.

Nothing in a Value may point at itself. ValueList relocates its buffer with a memcpy, which is sound only while every member survives having its bytes moved without the source being destroyed. A field with an interior pointer, a self-registering handle or an intrusive list node breaks it — and breaks it silently, by producing wrong data rather than by failing to compile. The near-miss is already in the struct: libstdc++'s short std::string points at its own inline buffer, which is why the path is chosen by a run-time probe. Run tools/reloc-probe.cpp after touching the struct.

Add an early exit, never restructure the general path underneath it. The first node specialisation cost the control 5.7% by doing the latter.

A memo is only sound if the thing memoised is a function of the key. A grammar rule that reads a dynamic variable is not a function of (rule, position), which is what the dynDep flag records.

gilPark's window must touch no interpreter state. Only thread-local buffers and syscalls.

Never join a thread while holding the lock the joinee might want.

A derived-data mechanism must degrade to recomputation, never to a guess. Every failure mode in the caching and emitting paths ends in "parse it again".