Skip to content

Commit d9f7e0f

Browse files
committed
feat: add backwards-compatible JSON action syntax to ZynPEG 2.0 parser
The new ZynPEG 2.0 grammar parser now supports both: 1. New Rust-like action syntax with named bindings 2. Legacy JSON command syntax for backwards compatibility Changes: - Add LegacyJson variant to ActionIR for JSON action blocks - Detect JSON syntax in parse_action() when first token is quoted string - Add parse_json_block_content() to capture raw JSON content - Handle LegacyJson in action code generator - Fix operator parsing to include @%^&| chars for $@ mapping - Add tests for ImagePipe grammar (23 rules) and ZynML grammar (135 rules) Both grammars now parse successfully with the new parser.
1 parent dc4e9a5 commit d9f7e0f

3 files changed

Lines changed: 155 additions & 2 deletions

File tree

crates/zyn_peg/src/codegen/action_gen.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,14 @@ impl ActionGenerator {
8888
}
8989
self.line("}");
9090
}
91+
92+
ActionIR::LegacyJson { return_type, json_content } => {
93+
// For legacy JSON actions, we generate a comment and placeholder
94+
// The actual execution uses the runtime interpreter
95+
self.line(&format!("// Legacy JSON action for {}", return_type));
96+
self.line(&format!("// JSON: {{{}}}", json_content.trim()));
97+
self.line("unimplemented!(\"Legacy JSON actions require runtime interpreter\")");
98+
}
9199
}
92100
}
93101

crates/zyn_peg/src/grammar/ir.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,21 @@ pub enum ActionIR {
223223
then_action: Box<ActionIR>,
224224
else_action: Option<Box<ActionIR>>,
225225
},
226+
227+
/// Legacy JSON command syntax (for backwards compatibility)
228+
/// ```zyn
229+
/// -> TypedStatement {
230+
/// "commands": [
231+
/// { "define": "let_stmt", "args": { "name": "$1", ... }}
232+
/// ]
233+
/// }
234+
/// ```
235+
LegacyJson {
236+
/// The return type (e.g., "TypedStatement")
237+
return_type: String,
238+
/// Raw JSON content as string
239+
json_content: String,
240+
},
226241
}
227242

228243
/// Expression IR for action code

crates/zyn_peg/src/grammar/parser.rs

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,17 @@ impl<'a> GrammarParser<'a> {
539539
self.expect_char('{')?;
540540
self.skip_ws_and_comments();
541541

542+
// Check if this is legacy JSON syntax (starts with quoted string key)
543+
if self.peek_char() == Some('"') {
544+
// Legacy JSON action - capture everything until matching closing brace
545+
let json_content = self.parse_json_block_content()?;
546+
self.expect_char('}')?;
547+
return Ok((ActionIR::LegacyJson {
548+
return_type: return_type.clone(),
549+
json_content,
550+
}, return_type));
551+
}
552+
542553
let mut fields = Vec::new();
543554

544555
while self.peek_char() != Some('}') {
@@ -565,6 +576,60 @@ impl<'a> GrammarParser<'a> {
565576
}, return_type))
566577
}
567578

579+
/// Parse the content of a JSON action block (legacy format)
580+
/// This captures everything between { and the matching }
581+
fn parse_json_block_content(&mut self) -> ParseResult<String> {
582+
let start = self.pos;
583+
let mut depth = 0;
584+
585+
loop {
586+
match self.peek_char() {
587+
Some('{') => {
588+
depth += 1;
589+
self.advance();
590+
}
591+
Some('}') => {
592+
if depth == 0 {
593+
// Found the closing brace of the outer block
594+
let content = self.input[start..self.pos].to_string();
595+
return Ok(content);
596+
}
597+
depth -= 1;
598+
self.advance();
599+
}
600+
Some('"') => {
601+
// Skip string literals (they might contain braces)
602+
self.advance();
603+
while let Some(c) = self.peek_char() {
604+
if c == '"' {
605+
self.advance();
606+
break;
607+
}
608+
if c == '\\' {
609+
self.advance();
610+
self.advance(); // Skip escaped char
611+
} else {
612+
self.advance();
613+
}
614+
}
615+
}
616+
Some('[') => {
617+
// Array - just advance
618+
self.advance();
619+
}
620+
Some(']') => {
621+
self.advance();
622+
}
623+
Some(_) => {
624+
self.advance();
625+
}
626+
None => {
627+
return Err(self.error("Unexpected EOF in JSON block"));
628+
}
629+
}
630+
}
631+
}
632+
568633
/// Parse a type path like TypedExpression::Binary
569634
fn parse_type_path(&mut self) -> ParseResult<String> {
570635
let mut path = self.parse_identifier()?;
@@ -1041,9 +1106,9 @@ impl<'a> GrammarParser<'a> {
10411106

10421107
// Rest is identifier-like or operator chars
10431108
if name.starts_with('$') {
1044-
// Operator: can include things like *, +, etc.
1109+
// Operator: can include things like *, +, @, etc.
10451110
while let Some(c) = self.peek_char() {
1046-
if c.is_ascii_alphanumeric() || "*+-/<>=!".contains(c) {
1111+
if c.is_ascii_alphanumeric() || "*+-/<>=!@%^&|".contains(c) {
10471112
name.push(c);
10481113
self.advance();
10491114
} else {
@@ -1390,4 +1455,69 @@ mod tests {
13901455
_ => panic!("Expected Repeat pattern"),
13911456
}
13921457
}
1458+
1459+
#[test]
1460+
fn test_parse_imagepipe_grammar() {
1461+
let input = include_str!("../../../../examples/imagepipe/imagepipe.zyn");
1462+
1463+
match parse_grammar(input) {
1464+
Ok(grammar) => {
1465+
println!("Successfully parsed ImagePipe grammar!");
1466+
println!("Language: {} v{}", grammar.metadata.name, grammar.metadata.version);
1467+
println!("Rules: {}", grammar.rules.len());
1468+
for (name, _rule) in &grammar.rules {
1469+
println!(" - {}", name);
1470+
}
1471+
1472+
// Verify key metadata
1473+
assert_eq!(grammar.metadata.name, "ImagePipe");
1474+
assert_eq!(grammar.metadata.version, "1.0");
1475+
1476+
// Verify key rules exist
1477+
assert!(grammar.rules.contains_key("program"));
1478+
assert!(grammar.rules.contains_key("statement"));
1479+
assert!(grammar.rules.contains_key("load_stmt"));
1480+
assert!(grammar.rules.contains_key("save_stmt"));
1481+
assert!(grammar.rules.contains_key("identifier"));
1482+
}
1483+
Err(e) => {
1484+
panic!("Failed to parse ImagePipe grammar: {:?}", e);
1485+
}
1486+
}
1487+
}
1488+
1489+
#[test]
1490+
fn test_parse_zynml_grammar() {
1491+
let input = include_str!("../../../zynml/ml.zyn");
1492+
1493+
match parse_grammar(input) {
1494+
Ok(grammar) => {
1495+
println!("Successfully parsed ZynML grammar!");
1496+
println!("Language: {} v{}", grammar.metadata.name, grammar.metadata.version);
1497+
println!("Rules: {}", grammar.rules.len());
1498+
1499+
// Verify key metadata
1500+
assert_eq!(grammar.metadata.name, "ZynML");
1501+
assert_eq!(grammar.metadata.version, "1.0");
1502+
1503+
// Verify key rules exist
1504+
assert!(grammar.rules.contains_key("program"));
1505+
assert!(grammar.rules.contains_key("fn_def"));
1506+
assert!(grammar.rules.contains_key("expr"));
1507+
assert!(grammar.rules.contains_key("statement"));
1508+
assert!(grammar.rules.contains_key("identifier"));
1509+
1510+
// Print a few rule names
1511+
let mut rule_names: Vec<_> = grammar.rules.keys().collect();
1512+
rule_names.sort();
1513+
println!("Sample rules:");
1514+
for name in rule_names.iter().take(20) {
1515+
println!(" - {}", name);
1516+
}
1517+
}
1518+
Err(e) => {
1519+
panic!("Failed to parse ZynML grammar: {:?}", e);
1520+
}
1521+
}
1522+
}
13931523
}

0 commit comments

Comments
 (0)