1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/// Tests for grammar inheritance at execution time.
///
/// Covers:
/// 1. Single-level inheritance (child extends parent)
/// 2. Multi-level / chain inheritance (C → B → A)
/// 3. Cycle detection (A → B → A does not loop)
/// 4. Sub-grammar call into an inheriting grammar
use rustine::parse_and_run;
// ── 1. Single-level: child inherits parent's skip rule ──────────────
#[test]
fn single_level_inheritance() {
// `base` has a skip rule for whitespace.
// `child(base)` only has a match rule.
// Because child inherits base, whitespace is skipped before "hello" is matched.
let gel = r#"
grammar base:
skip /\s+/
grammar child(base):
match /hello/:
out.create("root")
out.add("greeting", "$0")
"#;
let json = parse_and_run(gel, "child", " hello").expect("single-level inheritance");
assert!(
json.contains("greeting"),
"child should match via inherited skip: {json}"
);
assert!(json.contains("hello"), "captured value: {json}");
}
// ── 2. Multi-level: C(B), B(A) – three-level chain ─────────────────
#[test]
fn multi_level_inheritance() {
// grammar A: skip whitespace
// grammar B(A): match numbers → create "num" node
// grammar C(B): match words → create "word" node
//
// Running C should have A-skip + B-match + C-match available.
let gel = r#"
grammar A:
skip /\s+/
grammar B(A):
match /(\d+)/:
out.create("root")
out.add("num", "$1")
grammar C(B):
match /([a-z]+)/:
out.add("word", "$1")
"#;
// Input: whitespace then a number then whitespace then a word.
// A's skip rule eats whitespace, B's rule matches "42", C's rule matches "abc".
let json = parse_and_run(gel, "C", " 42 abc").expect("multi-level inheritance");
assert!(json.contains("num"), "B-rule should fire via A→B→C chain: {json}");
assert!(json.contains("42"), "number captured: {json}");
assert!(json.contains("word"), "C-rule should fire: {json}");
assert!(json.contains("abc"), "word captured: {json}");
}
// ── 3. Cycle detection: A(B) + B(A) must not loop ───────────────────
#[test]
fn cycle_detection_does_not_hang() {
// Both grammars point at each other. The engine should break the cycle
// and still be able to match.
let gel = r#"
grammar A(B):
match /hello/:
out.create("root")
out.add("msg", "$0")
grammar B(A):
skip /\s+/
"#;
// Even with a cycle, running A should work (it will see its own statements,
// the cycle just prevents revisiting B→A again).
let json = parse_and_run(gel, "A", " hello").expect("cycle must not hang");
assert!(json.contains("msg"), "A should still match: {json}");
}
// ── 4. Sub-grammar call dispatches to inheriting grammar ────────────
#[test]
fn subgrammar_call_with_inheritance() {
// `inner(base)` inherits base-skip and has its own match.
// `main` calls `inner` as a bare grammar name action.
let gel = r#"
grammar main:
match /START/:
inner
grammar base:
skip /\s+/
grammar inner(base):
match /hello/:
out.create("root")
out.add("val", "$0")
do.return()
"#;
let json = parse_and_run(gel, "main", "START hello").expect("sub-grammar with inheritance");
assert!(json.contains("val"), "inner should execute with inherited skip: {json}");
assert!(json.contains("hello"), "captured: {json}");
}
// ── 5. Deep chain (4 levels) ────────────────────────────────────────
#[test]
fn deep_chain_four_levels() {
let gel = r#"
grammar L1:
skip /\s+/
grammar L2(L1):
match /A/:
out.create("root")
out.add("a", "$0")
grammar L3(L2):
match /B/:
out.add("b", "$0")
grammar L4(L3):
match /C/:
out.add("c", "$0")
"#;
let json = parse_and_run(gel, "L4", " A B C").expect("4-level chain");
assert!(json.contains("\"a\""), "L2 rule via L1→L2→L3→L4: {json}");
assert!(json.contains("\"b\""), "L3 rule: {json}");
assert!(json.contains("\"c\""), "L4 rule: {json}");
}