The compiler lives in
src/compiler. Its
.bas files implement the compiler itself; its
.bi files describe internal interfaces. The subsystem README (
src/compiler/README.md) has the detailed ownership and lifetime contracts. This page gives a reading route through those modules.
compiler/
driver/ Invocation, options, paths, tools and final linking
platforms/ Policy for the user's compilation target
core/ Compiler environment and source-module lifecycle
diagnostics/ Errors, warnings and source context
lexer/ Source characters, tokens and locations
preprocessor/ Macros, conditional compilation and directives
parser/
declarations/ Variables, types, constants and initializers
expressions/ Precedence, values, calls and member access
procedures/ Signatures, arguments and procedure bodies
statements/ Assignment and compound control flow
intrinsics/ Statements/functions with special BASIC syntax
symbols/ Names, visibility, types, layouts and ABI metadata
ast/
nodes/ Typed operation builders and lowering
optimize/ Tree simplification and vectorization
runtime/ Runtime declarations and typed call builders
backend/ IR interface and backend dispatch
c/ llvm/ gas64/ x86/ debug/
support/ Allocation, containers, strings and numeric helpers
platform/ Compiler-host implementation policies
tooling/ Compiler-derived semantic observations and export
Start at the phase that owns the behavior
- Invocation options, files and tools: src/compiler/driver/fbc.bas and the driver's phase modules.
- Source-module initialization and compilation: src/compiler/core/fb.bas.
- Source text and tokens: src/compiler/lexer/lex.bas, then src/compiler/preprocessor/pp.bas.
- Accepted and rejected syntax: the corresponding parser family, with its positive and negative tests.
- Name lookup and type layout: symbols, with src/compiler/symbols/symb.bi as the shared contract.
- Conversions, cleanup and runtime calls: ast/nodes and the relevant runtime/rtl-*.bas module.
- Emitted instructions and ABI forms: src/compiler/backend/ir.bi, then the selected backend.
- Semantic sidecar facts: tooling and its observation hooks in the owning compiler phase.
The parser is recursive descent. A parser module may describe the common grammar in comments, but its branches, dialect checks and recovery behavior determine what is accepted. An accepted parse is only part of compilation: symbol and AST checks still enforce types, argument modes and lifetime rules.
Trace PRINT from syntax to runtime
For a string-valued
PRINT argument, the useful path is:
- src/compiler/parser/intrinsics/parser-quirk-file.bas implements cPrintStmt(). It parses the destination, expressions and formatting separators and asks the runtime lowering layer to build calls.
- src/compiler/runtime/rtl-print.bas describes printing entry points and chooses a typed operation for the expression. The string family includes the runtime symbol fb_PrintString.
- The resulting AST call passes through the selected backend. That backend emits the target's call sequence rather than printing anything itself.
- At program execution, src/rtlib/console/io_print.c implements fb_PrintString(). It handles the string descriptor, formatting and temporary-string cleanup and dispatches through the runtime's file output machinery.
This separation matters when fixing a bug. Incorrect parsing of a separator belongs in the parser; a wrong parameter declaration belongs in the call builder; an output or temporary-lifetime bug belongs in the runtime. A graphics-mode output path may also involve the runtime hooks described in the
runtime guide.
Keep host policy separate from target code generation
driver/platforms describes how to build programs for supported targets.
platform supplies implementations needed by the machine running the compiler. For example, compiler-host numeric representation is a different question from the target program's calling convention.
The C, LLVM and assembly backends implement a common IR interface, but have different emission and ABI details. Check the affected backend's implementation and interoperability tests before changing parameter layout, aggregate passing, inline assembly or debug emission. A fix in one emitter does not automatically cover the others.
tooling records facts selected by the compiler. Its observers must obtain identities, types and source ranges before the corresponding compiler storage is released. It must not reparse text to invent a competing interpretation. The semantic model contract (
doc/compiler-semantic-model.md) describes the exported format separately from these implementation boundaries.
Adding or moving a compiler module
mk/compiler-sources.mk lists source groups explicitly. Compiler objects and bootstrap emission retain flat module names, so selected modules must have unique basenames. The host policy can replace a generic module by its basename; an unrelated duplicate is an error.
Put a new module with the subsystem that owns its state. Name includes from the compiler source root, preserve the shared interfaces, and update the source group list when adding a directory. Keep driver-private state out of parser and backend modules. Shared compiler contexts are used serially within a process; parallel Make jobs run separate compiler processes.
make compiler-structure-test checks the maintained source map and include contracts. Behavior changes also need a regression in the relevant language, runtime or backend suite. See
tools and tests.
See also