Migrate the Zig language frontend from direct Rust parser (zyn_parser/zig_builder.rs) to the declarative Zyn grammar format (zig.zyn) that works with the CLI's --format zyn option.
-
Direct Parser (
crates/zyn_parser/src/zig_builder.rs)- ~3,362 lines of hand-written Rust
- Uses
pestfor parsing + manual AST construction - Tightly coupled to
zyntax_typed_asttypes - Well-tested but maintenance-heavy
-
Declarative Grammar (
crates/zyn_peg/grammars/zig.zyn)- ~1,290 lines of grammar + Rust action blocks
- PEG syntax with
-> Type { ... }action blocks - Currently uses embedded Rust code (NOT JSON commands)
- Not integrated with CLI runtime
┌─────────────────────────────────────────────────────────────────┐
│ zig.zyn (Grammar + JSON Commands) │
│ ↓ │
│ ZynPEG Compiler │
│ ↓ │
│ ZpegModule (pest_grammar + rule commands) │
│ ↓ │
│ pest_vm (Runtime Parser) + CommandInterpreter │
│ ↓ │
│ TypedAST JSON → TypedProgram → HIR → Native │
└─────────────────────────────────────────────────────────────────┘
The existing zig.zyn uses Rust action blocks, not JSON commands:
// Current (Rust code - WON'T WORK with runtime)
const_decl = { "const" ~ identifier ~ (":" ~ type_expr)? ~ "=" ~ expr ~ ";" }
-> TypedNode<TypedDeclaration> {
typed_node(
TypedDeclaration::Variable(TypedVariable {
name: intern($2),
...
}),
Type::Never,
span($1, $6),
)
}
Needs to become JSON commands like calc.zyn:
// Target (JSON commands - WORKS with runtime)
const_decl = { "const" ~ identifier ~ (":" ~ type_expr)? ~ "=" ~ expr ~ ";" }
-> TypedDeclaration {
"commands": [
{ "define": "variable", "args": {
"name": "$2",
"mutability": "immutable",
"initializer": "$5"
}}
]
}
Grammar files follow the lang.zyn pattern (e.g., zig.zyn, calc.zyn). The current zig.zyn will be updated in place - we don't create separate files.
The CommandInterpreter supports:
define- Create AST nodesget_child- Get child by index/nameget_text- Get matched textparse_int/parse_float- Parse literalsfold_binary- Binary expression foldingmap_children- Iterate childrenmatch_rule- Conditional dispatchstore/load- Variablesreturn- Return value
But TypedAstBuilder host functions need extension for:
struct_decl/enum_decl/union_declfunction_declwith full paramstype_exprvariants (pointer, optional, error_union)statementvariants (if, while, for, defer, etc.)- Field/variant collection
Goal: Add missing define handlers to TypedAstBuilder
Files to modify:
crates/zyn_peg/src/runtime.rs
New handlers needed:
// In TypedAstBuilder::define_node()
match node_type {
// Declarations
"variable" => { /* const/var decl */ }
"function" => { /* function decl */ }
"struct" => { /* struct decl */ }
"enum" => { /* enum decl */ }
"union" => { /* union decl */ }
// Statements
"if" => { /* if statement */ }
"while" => { /* while loop */ }
"for" => { /* for loop */ }
"return" => { /* return stmt */ }
"break" | "continue" => { /* control flow */ }
"defer" | "errdefer" => { /* defer stmts */ }
"let" => { /* local variable */ }
"expression_stmt" => { /* expr as stmt */ }
// Expressions
"binary" => { /* already exists via fold_binary */ }
"unary" => { /* unary op */ }
"call" => { /* function call */ }
"field_access" => { /* obj.field */ }
"index" => { /* arr[i] */ }
"try" => { /* try expr */ }
"lambda" => { /* closure */ }
"struct_literal" => { /* Point { x: 1 } */ }
"array_literal" => { /* [1, 2, 3] */ }
// Literals (most exist)
"int_literal" => { /* exists */ }
"float_literal" => { /* exists */ }
"string_literal" => { /* exists */ }
"bool_literal" => { /* exists */ }
"null_literal" => { /* add */ }
// Types
"primitive_type" => { /* i32, bool, etc */ }
"pointer_type" => { /* *T */ }
"optional_type" => { /* ?T */ }
"error_union_type" => { /* !T */ }
"array_type" => { /* [N]T */ }
"named_type" => { /* MyStruct */ }
// Patterns
"wildcard_pattern" => { /* _ */ }
"identifier_pattern" => { /* x, mut x */ }
"literal_pattern" => { /* 42 */ }
}Goal: Rewrite zig.zyn action blocks from Rust to JSON
Priority order (start simple, build up):
- Literals - Integer, float, string, bool, null
- Identifiers - Variable references
- Unary expressions - -x, !x, ~x
- Binary expressions - Using
fold_binary - Postfix expressions - call, field, index
- Statements - let, return, expression
- Control flow - if, while, for, break, continue
- Declarations - const, var, fn
- Types - primitives, pointers, optionals, arrays
- Complex - struct, enum, union, patterns
Example conversions:
// Before: Rust action block
integer_literal = @{ "-"? ~ ASCII_DIGIT+ }
-> TypedNode<TypedExpression> {
typed_node(
TypedExpression::Literal(TypedLiteral::Integer(Self::parse_int(pair_str))),
Type::Primitive(PrimitiveType::I32),
span,
)
}
// After: JSON commands
integer_literal = @{ "-"? ~ ASCII_DIGIT+ }
-> TypedExpression {
"get_text": true,
"parse_int": true,
"define": "int_literal",
"args": { "value": "$result" }
}
// Before: Rust
fn_decl = { "fn" ~ identifier ~ "(" ~ fn_params? ~ ")" ~ type_expr ~ block }
-> TypedNode<TypedDeclaration> {
typed_node(
TypedDeclaration::Function(TypedFunction {
name: intern($2),
params: $4.unwrap_or_default(),
return_type: $6,
body: Some($7),
...
}),
Type::Never,
span($1, $7),
)
}
// After: JSON
fn_decl = { "fn" ~ identifier ~ "(" ~ fn_params? ~ ")" ~ type_expr ~ block }
-> TypedDeclaration {
"commands": [
{ "define": "function", "args": {
"name": "$2",
"params": "$4",
"return_type": "$6",
"body": "$7"
}}
]
}
Goal: Verify Zig code compiles via CLI
Test progression:
-
Literal expressions
echo '42' > test.zig zyntax compile --source test.zig --grammar zig.zyn --format zyn --run
-
Simple functions
fn main() i32 { return 42; }
-
Variables and expressions
fn main() i32 { const x = 10; const y = 20; return x + y; }
-
Control flow
fn main() i32 { var sum: i32 = 0; for (i in 0..10) { sum = sum + i; } return sum; }
-
Structs and methods
const Point = struct { x: i32, y: i32, };
-
Full E2E tests
- Port existing
zig_e2e_jittests - Compare output with direct parser
- Port existing
Goal: Remove zig_builder.rs once grammar works
- Add deprecation warnings to
zig_builder.rs - Update documentation to prefer Zyn grammar
- Run parallel tests comparing both paths
- Remove
zig_builder.rswhen confident
- Add
define_variable()- for const/var declarations - Add
define_function()- for function declarations - Add
define_struct()- for struct types - Add
define_enum()- for enum types - Add
define_if()- for if statements - Add
define_while()- for while loops - Add
define_for()- for for loops - Add
define_unary()- for unary expressions - Add
define_call()- for function calls - Add
define_field_access()- for field access - Add
define_index()- for array indexing - Add
define_try()- for try expressions - Add type construction helpers
- Convert literals (int, float, string, bool) to JSON commands
- Convert identifier references
- Convert binary expressions with fold_binary
- Convert unary expressions
- Convert function declarations
- Convert variable declarations
- Convert statements (if, while, for, return)
- Convert type expressions
- Convert struct/enum/union declarations
- Create
tests/zig_grammar_tests.rs - Test literals through CLI
- Test expressions through CLI
- Test functions through CLI
- Test control flow through CLI
- Port
zig_e2e_jittests
- Update README Zig section
- Document Zyn grammar format
- Add migration guide for grammar authors
- Deprecate and remove
zig_builder.rs
| Phase | Effort | Dependencies |
|---|---|---|
| Phase 1: Host Functions | Medium | None |
| Phase 2: Grammar Conversion | Large | Phase 1 |
| Phase 3: Testing | Medium | Phase 2 |
| Phase 4: Cleanup | Small | Phase 3 |
zyntax compile --source test.zig --grammar zig.zyn --format zyn --runworks- All existing Zig E2E tests pass with grammar-based parser
- Performance is comparable (within 2x of direct parser)
zig_builder.rscan be safely removed
- Back up current
zig.zynbefore modifying (or use git) - Can incrementally migrate rules (test one at a time)
- The
calc.zynexample is the template for JSON command syntax - Keep direct parser functional until migration is complete