Skip to content

Commit 05afdc3

Browse files
committed
docs: Update CLI documentation and improve LLVM backend
- Update README and CLI book chapter with LLVM backend usage - Improve ast_builder with better node handling - Refactor CLI backend module for cleaner code - Update LLVM AOT backend with improved error handling
1 parent 5c9f551 commit 05afdc3

9 files changed

Lines changed: 168 additions & 87 deletions

File tree

README.md

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,36 +40,39 @@ The Zyntax command-line interface provides a unified compilation toolchain with
4040
# Build the CLI
4141
cargo build --release
4242

43-
# Compile and run a program
44-
zyntax compile input.json -o myprogram --run
43+
# Compile and run a program with JIT
44+
zyntax compile input.json --jit
4545

4646
# Multiple input formats supported
47-
zyntax compile program.zbc --format zbc -o output
48-
zyntax compile source.zyn --format zyn -o output --jit
47+
zyntax compile program.zbc --format hir-bytecode -o output
48+
zyntax compile --source code.calc --grammar calc.zyn --format zyn --jit
4949
```
5050

5151
### CLI Features
5252

5353
- **Dual-Format Support**: Compile from JSON TypedAST or ZBC bytecode
54-
- **JIT Execution**: Run programs directly with `--run` flag
54+
- **JIT Execution**: Run programs directly with `--jit` flag
5555
- **Multiple Backends**: Choose Cranelift (fast) or LLVM (optimized)
5656
- **Format Auto-Detection**: Automatically detects input format from file extension
5757
- **Rich Diagnostics**: Clear error messages with source location tracking
5858

5959
### Usage Examples
6060

6161
```bash
62-
# Compile JSON TypedAST to executable
62+
# Compile JSON TypedAST to executable (AOT)
6363
zyntax compile program.json -o myapp
6464

65-
# Compile and run immediately
66-
zyntax compile program.json --run
65+
# JIT compile and run immediately
66+
zyntax compile program.json --jit
6767

6868
# Compile ZBC bytecode format
6969
zyntax compile program.zbc -o myapp
7070

71-
# Use specific backend
71+
# Use LLVM backend for maximum optimization
7272
zyntax compile program.json --backend llvm -o myapp
73+
74+
# JIT with LLVM backend
75+
zyntax compile program.json --backend llvm --jit
7376
```
7477

7578
---
@@ -192,11 +195,14 @@ WHITESPACE = _{ " " | "\t" | "\n" | "\r" }
192195
### CLI Usage
193196

194197
```bash
195-
# Compile and run with grammar
196-
zyntax compile --source input.calc --grammar calc.zyn --format zyn --run
198+
# JIT compile and run with grammar
199+
zyntax compile --source input.calc --grammar calc.zyn --format zyn --jit
200+
201+
# AOT compile to executable
202+
zyntax compile --source code.mylang --grammar mylang.zyn --format zyn -o output
197203

198-
# Verbose mode shows compilation steps
199-
zyntax compile -v --source code.mylang --grammar mylang.zyn --format zyn -o output
204+
# Use LLVM backend for maximum optimization
205+
zyntax compile --source code.mylang --grammar mylang.zyn --backend llvm -o output
200206

201207
# Interactive REPL mode
202208
zyntax repl --grammar calc.zyn

book/03-using-the-cli.md

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ zyntax compile --grammar <grammar.zyn> --source <file> [OPTIONS]
4242

4343
### Compile and Run (JIT)
4444

45-
The most common workflow - compile and execute immediately:
45+
The most common workflow - JIT compile and execute immediately:
4646

4747
```bash
4848
zyntax compile --grammar crates/zyn_peg/grammars/zig.zyn \
4949
--source examples/hello.zig \
50-
--run
50+
--jit
5151
```
5252

5353
Output:
@@ -62,12 +62,21 @@ result: main() returned: 42
6262
| `--grammar` | `-g` | ZynPEG grammar file (.zyn) | Required |
6363
| `--source` | `-s` | Source file to compile | Required |
6464
| `--output` | `-o` | Output file path | None |
65-
| `--backend` | `-b` | Backend: `jit` or `llvm` | `jit` |
65+
| `--backend` | `-b` | Backend: `cranelift` or `llvm` | `cranelift` |
6666
| `--opt-level` | `-O` | Optimization: 0-3 | `2` |
67-
| `--run` | | Execute after compilation (JIT only) | Off |
67+
| `--jit` | | JIT compile and execute immediately | Off |
6868
| `--verbose` | `-v` | Show detailed output | Off |
6969
| `--format` | `-f` | Input format (see below) | `auto` |
7070

71+
### Backends
72+
73+
Zyntax supports two compilation backends:
74+
75+
| Backend | Description | Use Case |
76+
|---------|-------------|----------|
77+
| `cranelift` | Fast compilation, good code quality | Development, JIT |
78+
| `llvm` | Maximum optimization, slower compilation | Production, AOT |
79+
7180
### Input Formats
7281

7382
The `--format` option controls how input is interpreted:
@@ -81,38 +90,45 @@ The `--format` option controls how input is interpreted:
8190

8291
### Examples
8392

84-
#### Compile and Run
93+
#### JIT Compile and Run
8594

8695
```bash
87-
# Simple execution
88-
zyntax compile -g zig.zyn -s test.zig --run
96+
# Simple execution with Cranelift (fast)
97+
zyntax compile -g zig.zyn -s test.zig --jit
8998

9099
# With verbose output
91-
zyntax compile -g zig.zyn -s test.zig --run -v
100+
zyntax compile -g zig.zyn -s test.zig --jit -v
101+
102+
# JIT with LLVM backend (maximum optimization)
103+
zyntax compile -g zig.zyn -s test.zig --backend llvm --jit
92104
```
93105

94-
#### Compile to Object File (LLVM)
106+
#### AOT Compile to Executable
95107

96108
```bash
97-
zyntax compile -g zig.zyn -s main.zig -b llvm -o main.o
109+
# Compile to native executable with Cranelift
110+
zyntax compile -g zig.zyn -s main.zig -o main
111+
112+
# Compile with LLVM for maximum optimization
113+
zyntax compile -g zig.zyn -s main.zig --backend llvm -o main
98114
```
99115

100116
#### Optimization Levels
101117

102118
```bash
103119
# No optimization (fastest compile)
104-
zyntax compile -g zig.zyn -s test.zig -O0 --run
120+
zyntax compile -g zig.zyn -s test.zig -O0 --jit
105121

106122
# Maximum optimization
107-
zyntax compile -g zig.zyn -s test.zig -O3 --run
123+
zyntax compile -g zig.zyn -s test.zig -O3 --jit
108124
```
109125

110126
#### Compile TypedAST JSON
111127

112128
If you have pre-built TypedAST:
113129

114130
```bash
115-
zyntax compile program.json --run
131+
zyntax compile program.json --jit
116132
```
117133

118134
## The `repl` Command
@@ -130,7 +146,7 @@ zyntax repl --grammar crates/zyn_peg/grammars/zig.zyn
130146
| Option | Short | Description | Default |
131147
|--------|-------|-------------|---------|
132148
| `--grammar` | `-g` | ZynPEG grammar file | Required |
133-
| `--backend` | `-b` | Backend (JIT only) | `jit` |
149+
| `--backend` | `-b` | Backend: `cranelift` or `llvm` | `cranelift` |
134150
| `--opt-level` | `-O` | Optimization: 0-3 | `0` |
135151
| `--verbose` | `-v` | Show parse details | Off |
136152

@@ -223,7 +239,7 @@ When developing a grammar:
223239
zyntax repl -g mygrammar.zyn
224240

225241
# Test a complete program
226-
zyntax compile -g mygrammar.zyn -s test.mylang --run
242+
zyntax compile -g mygrammar.zyn -s test.mylang --jit
227243
```
228244

229245
### Testing Workflow
@@ -239,7 +255,7 @@ fn main() i32 {
239255
EOF
240256

241257
# Run and verify
242-
zyntax compile -g zig.zyn -s /tmp/test_arithmetic.zig --run
258+
zyntax compile -g zig.zyn -s /tmp/test_arithmetic.zig --jit
243259
# Expected: result: main() returned: 14
244260
```
245261

@@ -253,7 +269,7 @@ GRAMMAR="crates/zyn_peg/grammars/zig.zyn"
253269

254270
for file in tests/*.zig; do
255271
echo "Testing: $file"
256-
zyntax compile -g "$GRAMMAR" -s "$file" --run
272+
zyntax compile -g "$GRAMMAR" -s "$file" --jit
257273
echo "---"
258274
done
259275
```
@@ -264,7 +280,7 @@ When something doesn't work:
264280

265281
```bash
266282
# Step 1: Check parsing with verbose
267-
zyntax compile -g mygrammar.zyn -s broken.mylang -v --run
283+
zyntax compile -g mygrammar.zyn -s broken.mylang -v --jit
268284

269285
# Step 2: Simplify the input
270286
echo "1 + 2" | zyntax repl -g mygrammar.zyn -v
@@ -319,7 +335,7 @@ zyntax repl -g mygrammar.zyn
319335
When parsing fails unexpectedly:
320336

321337
```bash
322-
zyntax compile -g grammar.zyn -s file.src -v --run
338+
zyntax compile -g grammar.zyn -s file.src -v --jit
323339
```
324340

325341
### 3. Create Minimal Test Cases
@@ -360,7 +376,7 @@ FAIL=0
360376

361377
for test in tests/*.mylang; do
362378
expected="${test%.mylang}.expected"
363-
result=$(zyntax compile -g "$GRAMMAR" -s "$test" --run 2>&1)
379+
result=$(zyntax compile -g "$GRAMMAR" -s "$test" --jit 2>&1)
364380

365381
if diff -q <(echo "$result") "$expected" > /dev/null; then
366382
echo "$test"
@@ -383,10 +399,10 @@ echo "Passed: $PASS, Failed: $FAIL"
383399

384400
```bash
385401
# Debug logging
386-
RUST_LOG=debug zyntax compile -g zig.zyn -s test.zig --run
402+
RUST_LOG=debug zyntax compile -g zig.zyn -s test.zig --jit
387403

388404
# Full backtrace on errors
389-
RUST_BACKTRACE=1 zyntax compile -g zig.zyn -s test.zig --run
405+
RUST_BACKTRACE=1 zyntax compile -g zig.zyn -s test.zig --jit
390406
```
391407

392408
## Next Steps

crates/zyn_peg/generated/ast_builder.rs

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,14 +1062,65 @@ impl AstBuilderContext {
10621062
.iter()
10631063
.find(|p| p.as_rule() == Rule::expr)
10641064
.cloned();
1065+
// Build the actual assignment as a Binary expression with Assign operator
1066+
let target = child_assign_target
1067+
.as_ref()
1068+
.and_then(|p| self.build_assign_target(p.clone()).ok())
1069+
.unwrap_or_default();
1070+
let value = child_expr
1071+
.as_ref()
1072+
.and_then(|p| self.build_expr(p.clone()).ok())
1073+
.unwrap_or_default();
1074+
// Check for compound assignment operators (+=, -=, etc.)
1075+
let assign_op_str = child_assign_op.as_ref().map(|p| p.as_str()).unwrap_or("=");
1076+
let assign_expr = if assign_op_str == "=" {
1077+
// Simple assignment: target = value
1078+
typed_node(
1079+
TypedExpression::Binary(TypedBinary {
1080+
op: BinaryOp::Assign,
1081+
left: Box::new(target),
1082+
right: Box::new(value),
1083+
}),
1084+
Type::Never,
1085+
span,
1086+
)
1087+
} else {
1088+
// Compound assignment: target op= value => target = target op value
1089+
let op = match assign_op_str {
1090+
"+=" => BinaryOp::Add,
1091+
"-=" => BinaryOp::Sub,
1092+
"*=" => BinaryOp::Mul,
1093+
"/=" => BinaryOp::Div,
1094+
"%=" => BinaryOp::Mod,
1095+
"&=" => BinaryOp::BitAnd,
1096+
"|=" => BinaryOp::BitOr,
1097+
"^=" => BinaryOp::BitXor,
1098+
"<<=" => BinaryOp::Shl,
1099+
">>=" => BinaryOp::Shr,
1100+
_ => BinaryOp::Assign, // Fallback
1101+
};
1102+
let compound_value = typed_node(
1103+
TypedExpression::Binary(TypedBinary {
1104+
op,
1105+
left: Box::new(target.clone()),
1106+
right: Box::new(value),
1107+
}),
1108+
Type::Never,
1109+
span,
1110+
);
1111+
typed_node(
1112+
TypedExpression::Binary(TypedBinary {
1113+
op: BinaryOp::Assign,
1114+
left: Box::new(target),
1115+
right: Box::new(compound_value),
1116+
}),
1117+
Type::Never,
1118+
span,
1119+
)
1120+
};
10651121
Ok({
10661122
typed_node(
1067-
TypedStatement::Expression(Box::new(
1068-
child_expr
1069-
.as_ref()
1070-
.and_then(|p| self.build_expr(p.clone()).ok())
1071-
.unwrap_or_default(),
1072-
)),
1123+
TypedStatement::Expression(Box::new(assign_expr)),
10731124
Type::Never,
10741125
span,
10751126
)

crates/zyntax_cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ rustyline = "14.0"
4848
dirs = "5.0"
4949

5050
# LLVM backend (optional)
51-
inkwell = { version = "0.4", features = ["llvm17-0"], optional = true }
51+
inkwell = { version = "0.7.1", features = ["llvm21-1"], optional = true }
5252

5353
[features]
5454
default = []

crates/zyntax_cli/src/backends/llvm_aot.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ pub fn compile_llvm(
4848

4949
if verbose {
5050
info!("Generated {} bytes of LLVM IR", llvm_ir.len());
51+
debug!("LLVM IR:\n{}", llvm_ir);
52+
// Also write LLVM IR to file for debugging
53+
let ir_path = output_path.with_extension("ll");
54+
std::fs::write(&ir_path, &llvm_ir).unwrap_or_else(|e| {
55+
error!("Failed to write LLVM IR: {}", e);
56+
});
57+
info!("Wrote LLVM IR to {:?}", ir_path);
5158
}
5259

5360
// Get target triple for the host machine
@@ -197,36 +204,36 @@ pub fn compile_and_run_llvm(
197204
.get_function_pointer(main_id)
198205
.ok_or("Failed to get main function pointer")?;
199206

200-
if verbose {
201-
debug!("Executing main() at {:?}...", fn_ptr);
202-
}
207+
debug!("Got main function pointer: {:p}", fn_ptr);
208+
debug!("About to execute main()...");
203209

204210
// Execute main function
211+
// IMPORTANT: Use extern "C" calling convention to match LLVM's default
205212
let result = unsafe {
206213
if main_fn.signature.returns.is_empty() {
207-
let f: fn() = std::mem::transmute(fn_ptr);
214+
let f: extern "C" fn() = std::mem::transmute(fn_ptr);
208215
f();
209216
0
210217
} else {
211218
match &main_fn.signature.returns[0] {
212219
zyntax_compiler::hir::HirType::I32 => {
213-
let f: fn() -> i32 = std::mem::transmute(fn_ptr);
220+
let f: extern "C" fn() -> i32 = std::mem::transmute(fn_ptr);
214221
f() as i64
215222
}
216223
zyntax_compiler::hir::HirType::I64 => {
217-
let f: fn() -> i64 = std::mem::transmute(fn_ptr);
224+
let f: extern "C" fn() -> i64 = std::mem::transmute(fn_ptr);
218225
f()
219226
}
220227
zyntax_compiler::hir::HirType::F32 => {
221-
let f: fn() -> f32 = std::mem::transmute(fn_ptr);
228+
let f: extern "C" fn() -> f32 = std::mem::transmute(fn_ptr);
222229
f() as i64
223230
}
224231
zyntax_compiler::hir::HirType::F64 => {
225-
let f: fn() -> f64 = std::mem::transmute(fn_ptr);
232+
let f: extern "C" fn() -> f64 = std::mem::transmute(fn_ptr);
226233
f() as i64
227234
}
228235
zyntax_compiler::hir::HirType::Void => {
229-
let f: fn() = std::mem::transmute(fn_ptr);
236+
let f: extern "C" fn() = std::mem::transmute(fn_ptr);
230237
f();
231238
0
232239
}

0 commit comments

Comments
 (0)