ワンクリックで
parser-internals
Comprehensive guide to the Chemical recursive descent parser — how Chemical source code is parsed into AST nodes.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Comprehensive guide to the Chemical recursive descent parser — how Chemical source code is parsed into AST nodes.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Comprehensive guide to the Chemical compiler test infrastructure — how tests are organized, written, and executed. Covers the test framework, @test annotation dispatch, test_env and test libraries, and how compiler plugins get tested via lang/tests/build.lab.
Comprehensive deep-dive into the Chemical build pipeline — how chemical.mod is converted to build.lab, how build scripts are JIT-compiled via TinyCC, how jobs are created and executed, and how ASTProcessor orchestrates the parallel compilation passes.
All documentation related to building and running Project or Tests
Comprehensive guide to the Chemical Compiler Binding Interface (CBI) — how compiler plugins are built, registered, and integrated into the compilation pipeline.
Documentation of Syntax and APIs for Chemical Programming Language, A must read before implementing something in chemical programming language.s
Comprehensive guide to Chemical's compiler intrinsic functions and reflection APIs — how GlobalFunctions.cpp provides interpreter-friendly implementations, compile-time reflection, and metadata access.
| name | Parser Internals |
| description | Comprehensive guide to the Chemical recursive descent parser — how Chemical source code is parsed into AST nodes. |
The Chemical parser is a hand-written recursive descent parser. It takes a stream of tokens from the lexer and produces an AST (Abstract Syntax Tree).
Source code → Lexer → Token stream → Parser → AST
| File | Purpose |
|---|---|
parser/Parser.h | Parser class declaration — all parsing method declarations |
parser/Parser.cpp | Main parser implementation — orchestration and entry points |
lexer/Lexer.h | Lexer class — tokenizer |
lexer/Lexer.cpp | Lexer implementation |
lexer/Token.h | Token structure and types |
lexer/TokenType.h | Token type enum |
parser/structures/ | Per-construct parsers (one file per AST construct) |
parser/values/ | Value parsers (expressions, literals, etc.) |
parser/statements/ | Statement parsers |
parser/utils/ | Parsing utilities |
| File | Construct |
|---|---|
parser/structures/Function.cpp | Function declarations |
parser/structures/Struct.cpp | Struct definitions |
parser/structures/Variant.cpp | Variant definitions |
parser/structures/Enum.cpp | Enum definitions |
parser/structures/Interface.cpp | Interface definitions |
parser/structures/ImplDef.cpp | Implementation blocks (impl) |
parser/structures/Namespace.cpp | Namespace declarations |
parser/structures/Block.cpp | Block scopes ({ }) |
parser/structures/IfBlock.cpp | If/else statements |
parser/structures/WhileBlock.cpp | While loops |
parser/structures/DoWhile.cpp | Do-while loops |
parser/structures/ForBlock.cpp | For loops |
parser/structures/Switch.cpp | Switch statements |
parser/structures/TryCatch.cpp | Try/catch blocks |
parser/structures/Union.cpp | Union definitions |
parser/values/StructValue.cpp | Struct literal values |
parser/values/LexValue.cpp | Lexed values (integers, floats, strings) |
parser/values/Expression.cpp | Binary/unary expressions |
parser/utils/Helpers.cpp | Helper functions |
parser/statements/Import.cpp | Import statements |
parser/statements/Export.cpp | Export statements |
parser/statements/VarInitialization.cpp | Variable initialization |
parser/statements/Typealias.cpp | Type alias statements |
parser/statements/AccessChain.cpp | Access chain parsing (a.b.c) |
parser/statements/AnnotationMacro.cpp | Annotation/macro parsing |
The Parser class holds:
class Parser {
Lexer& lexer; // Token source
ASTAllocator& allocator; // Arena allocator for AST nodes
ASTDiagnoser& diagnoser; // Error reporting
Token current; // Current token
Token peeked; // Lookahead token (optional)
bool has_peeked; // Whether peeked token is valid
// Methods:
Token& consume(); // Advance to next token
Token& peek(); // Look at next token without consuming
bool consume_if(TokenType type); // Consume if matches type
bool expect(TokenType type); // Expect specific type or error
// Entry points:
Scope* parse_scope(); // Parse a { } scope
ASTNode* parse_declaration(); // Parse a top-level declaration
Value* parse_expression(); // Parse an expression
Value* parse_value(); // Parse a value
BaseType* parse_type(); // Parse a type
};
The parser uses standard recursive descent with one-token lookahead:
// Example: parsing a function declaration
FunctionDeclaration* Parser::parse_func_decl() {
// 1. Check for 'func' keyword
if(!consume_if(TokenType::FuncKw)) return nullptr;
// 2. Parse function name
auto name = expect_identifier();
// 3. Parse generic parameters (optional)
std::vector<GenericTypeParameter*> generic_params;
if(consume_if(TokenType::LessThan)) {
generic_params = parse_generic_params();
expect(TokenType::GreaterThan);
}
// 4. Parse parameters
expect(TokenType::OpenParen);
auto params = parse_params();
expect(TokenType::CloseParen);
// 5. Parse return type (optional)
BaseType* return_type = nullptr;
if(consume_if(TokenType::Colon)) {
return_type = parse_type();
}
// 6. Parse function body
Scope* body = nullptr;
if(current.type == TokenType::OpenBrace) {
body = parse_scope();
} else if(consume_if(TokenType::EqualsGreater)) {
// Lambda-style: () => expr
body = parse_lambda_body();
}
// 7. Create AST node
return create_func_decl(name, params, return_type, body, generic_params);
}
Expressions are parsed with precedence climbing:
// Precedence levels (lowest to highest):
LogicalOr (||)
LogicalAnd (&&)
BitwiseOr (|)
BitwiseXor (^)
BitwiseAnd (&)
Equality (==, !=)
Comparison (<, >, <=, >=)
Shift (<<, >>)
Additive (+, -)
Multiplicative (*, /, %)
Unary (!, -, ~, &, *, ++, --)
Postfix (), [], ., ?,
Types are parsed with a recursive type parser:
BaseType* Parser::parse_type() {
// Handle pointer types: *int, **int, *mut int
if(consume_if(TokenType::Star)) {
auto mutable_ = consume_if(TokenType::MutKw);
auto inner = parse_type();
return allocator.create<PointerType>(inner, mutable_);
}
// Handle reference types: &int, &mut int
if(consume_if(TokenType::Ampersand)) {
auto mutable_ = consume_if(TokenType::MutKw);
auto inner = parse_type();
return allocator.create<ReferenceType>(inner, mutable_);
}
// Handle array types: [10]int
if(consume_if(TokenType::OpenBracket)) {
auto size = parse_expression();
expect(TokenType::CloseBracket);
auto inner = parse_type();
return allocator.create<ArrayType>(inner, size);
}
// Handle function types: (int) => bool
if(consume_if(TokenType::OpenParen)) {
return parse_function_type();
}
// Handle named types: int, string, MyStruct, GenericType<int>
return parse_named_type();
}
Statement parsing dispatches based on the first token:
| Token | Statement |
|---|---|
var / const | Variable declaration |
if | If statement |
while | While loop |
do | Do-while loop |
for | For loop |
switch | Switch statement |
return | Return statement |
break | Break statement |
continue | Continue statement |
unsafe | Unsafe block |
comptime | Comptime block |
{ | Scope block |
import | Import statement |
export | Export statement |
typealias | Type alias |
throw | Throw statement |
delete | Delete statement |
dealloc | Dealloc statement |
@ | Annotation/macro |
func | Function |
struct | Struct |
variant | Variant |
union | Union |
enum | Enum |
interface | Interface |
impl | Implementation |
namespace | Namespace |
using | Using declaration |
| Other | Expression statement |
The parser uses basic error recovery:
@ Annotation Macro SystemThe @ prefix triggers annotation parsing, which can either:
@extern, @test, @deprecated, @no_mangle, @inline, @noinline, etc.@name that isn't a built-in is forwarded to CBI plugin handlersvoid Parser::parse_annotation() {
consume(); // consume @
auto name = expect_identifier();
// Check built-in annotations
if(name == "extern") { /* handle @extern */ }
else if(name == "test") { /* handle @test */ }
// ... built-in annotations ...
else {
// Forward to CBI plugin
binder.dispatch_annotation(name, ...);
}
}
The lexer is in lexer/Lexer.cpp and produces tokens consumed by the parser:
class Lexer {
InputSource& source; // Source file
Token current; // Current token
SourceLocation location; // Current location
Token next(); // Get next token
Token peek(); // Lookahead
};
| Token Type | Example |
|---|---|
Identifier | foo, Bar, _baz |
IntegerLiteral | 42, 0xFF, 0b1010 |
FloatLiteral | 3.14, 1e10 |
StringLiteral | "hello", "\"escaped\"" |
CharLiteral | 'a', '\n' |
FuncKw | func |
StructKw | struct |
VarKw | var |
ConstKw | const |
OpenParen | ( |
CloseParen | ) |
OpenBrace | { |
CloseBrace | } |
OpenBracket | [ |
CloseBracket | ] |
Semicolon | ; |
Colon | : |
EqualsGreater | => |
Ampersand | & |
Star | * |
Arrow | -> |
Dot | . |
Comma | , |
| ... | ... |
All AST nodes are allocated through ASTAllocator, an arena allocator:
class ASTAllocator {
void* allocate(size_t size); // Arena allocation — no individual free
void reset(); // Reset entire arena
};
This means:
delete ever called| Issue | Cause | Fix |
|---|---|---|
| Ambiguous grammar | Expression vs. declaration confusion | Use consume_if() with careful token checking |
| Left recursion | Direct/indirect left-recursive rules | Rewrite as iteration (e.g., a + b + c) |
| Operator precedence | Wrong binding strength | Use precedence climbing or Pratt parsing |
| Lookahead limit | Need >1 token to disambiguate | Buffer tokens or use backtracking |
| Generic parsing | < vs. less-than ambiguity | Use context tracking to distinguish |