aboutsummaryrefslogtreecommitdiff
path: root/forest-examples.md
diff options
context:
space:
mode:
Diffstat (limited to 'forest-examples.md')
-rw-r--r--forest-examples.md298
1 files changed, 298 insertions, 0 deletions
diff --git a/forest-examples.md b/forest-examples.md
new file mode 100644
index 0000000..e22edef
--- /dev/null
+++ b/forest-examples.md
@@ -0,0 +1,298 @@
+# Reference derivation forests (hand-verified)
+
+These are ground-truth cases for comparing against the parser's forest
+output. Every case was derived by hand from the grammar, so treat a
+mismatch as a bug in the parser (or in the oracle), never as a reason
+to edit this file silently.
+
+## Notation
+
+Trees are S-expressions: `(Symbol child child ...)`. Terminals are
+bare tokens. An empty derivation is written `eps`.
+
+A packed forest is a set of nodes `Symbol[start,end]`. Each node has
+one or more packed alternatives; each alternative is a sequence of
+children, where a child is a node or a terminal. Ambiguity = a node
+with more than one alternative. A node is identified by (symbol,
+start, end), and identical nodes must be shared.
+
+The tree set represented by a forest is obtained by choosing one
+alternative per node, recursively, from the root node `S[0,n]`.
+
+---
+
+## Case 1: ambiguous, no epsilon
+
+Grammar:
+
+```
+S -> S S | a
+```
+
+Input: `aaa` (tokens `a a a`, n = 3)
+
+Packed forest (6 nodes, 5 packed alternatives in total):
+
+```
+S[0,3]: { S[0,1] S[1,3] } | { S[0,2] S[2,3] }
+S[0,2]: { S[0,1] S[1,2] }
+S[1,3]: { S[1,2] S[2,3] }
+S[0,1]: { a }
+S[1,2]: { a }
+S[2,3]: { a }
+```
+
+Tree set (exactly 2 trees):
+
+```
+(S (S a) (S (S a) (S a)))
+(S (S (S a) (S a)) (S a))
+```
+
+Tree counts by input length (binary trees with n leaves =
+Catalan(n-1)):
+
+| n | trees |
+|---|-------|
+| 1 | 1 |
+| 2 | 1 |
+| 3 | 2 |
+| 4 | 5 |
+| 5 | 14 |
+
+Forest size should stay polynomial: for n = 20 the forest must NOT
+enumerate the 1,767,263,190 trees. Number of nodes is at most O(n^2),
+packed alternatives at most O(n^3).
+
+---
+
+## Case 2: ambiguous expression grammar
+
+Grammar:
+
+```
+E -> E + E | n
+```
+
+Input: `n + n + n`
+
+Packed forest:
+
+```
+E[0,5]: { E[0,1] + E[2,5] } | { E[0,3] + E[4,5] }
+E[0,3]: { E[0,1] + E[2,3] }
+E[2,5]: { E[2,3] + E[4,5] }
+E[0,1]: { n }
+E[2,3]: { n }
+E[4,5]: { n }
+```
+
+Here positions are token indices: `n`=0..1, `+`=1..2, `n`=2..3, `+`=3..4, `n`=4..5.
+
+Tree set (exactly 2 trees):
+
+```
+(E (E n) + (E (E n) + (E n)))
+(E (E (E n) + (E n)) + (E n))
+```
+
+---
+
+## Case 3: nullable nonterminals
+
+Grammar:
+
+```
+S -> A A
+A -> a | eps
+```
+
+Input: `a` (n = 1)
+
+Packed forest:
+
+```
+S[0,1]: { A[0,1] A[1,1] } | { A[0,0] A[0,1] }
+A[0,1]: { a }
+A[1,1]: { eps }
+A[0,0]: { eps }
+```
+
+Tree set (exactly 2 trees; the two are distinct because the `a` is
+consumed by a different `A`):
+
+```
+(S (A a) (A eps))
+(S (A eps) (A a))
+```
+
+Input: `` (empty, n = 0). Exactly 1 tree:
+
+```
+(S (A eps) (A eps))
+```
+
+Watch out: the empty input must give exactly ONE tree, not two. Both
+`A`s derive eps in only one way each.
+
+---
+
+## Case 4: right recursion with epsilon, unambiguous
+
+Grammar:
+
+```
+S -> a S | eps
+```
+
+Input: `a a`
+
+Packed forest (no ambiguity, every node has exactly one alternative):
+
+```
+S[0,2]: { a S[1,2] }
+S[1,2]: { a S[2,2] }
+S[2,2]: { eps }
+```
+
+Tree set (exactly 1 tree):
+
+```
+(S a (S a (S eps)))
+```
+
+---
+
+## Case 5: cyclic grammars (infinite ambiguity), represented by forest cycles
+
+Policy for this repo: **infinitely many derivations are represented by
+a cycle in the forest.** The forest stays finite; its tree set is
+infinite. Nothing is cut or pruned.
+
+Consequences for reading the notation:
+
+- A node's alternative may contain the node itself, or a chain of
+ nodes leading back to it.
+- The tree set is obtained by choosing one alternative **per
+ occurrence** of a node in the tree being built, not once per node
+ globally. This matters here: the same node appears several times on
+ one root-to-leaf path, and each occurrence may choose differently.
+- Since every child sequence covers its parent's span exactly, a cycle
+ can only pass through nodes with the **same span** `[start, end]`,
+ and only through derivations that consume nothing extra (unit rules,
+ or rules whose other symbols are nullable).
+
+Because the tree set is infinite, compare **bounded** tree
+sets. Define the height of a tree as the maximum number of nonterminal
+nodes on any root-to-leaf path. Let `trees_h` be the set of trees with
+height <= h. The parser's forest, unpacked with bound h, must equal
+the oracle's `trees_h` for every h tested.
+
+### Case 5a: self-loop
+
+Grammar:
+
+```
+S -> S | a
+```
+
+Input: `a`
+
+Forest (one node, two alternatives, one of them cyclic):
+
+```
+S[0,1]: { S[0,1] } | { a }
+```
+
+Bounded tree sets (exactly h trees for each h):
+
+```
+h = 1: (S a)
+h = 2: (S a) (S (S a))
+h = 3: (S a) (S (S a)) (S (S (S a)))
+h = 4: (S a) (S (S a)) (S (S (S a))) (S (S (S (S a))))
+```
+
+### Case 5b: nullable self-loop
+
+Grammar:
+
+```
+A -> A | eps
+```
+
+Input: `` (empty, n = 0)
+
+Forest:
+
+```
+A[0,0]: { A[0,0] } | { eps }
+```
+
+Bounded tree sets (exactly h trees for each h):
+
+```
+h = 1: (A eps)
+h = 2: (A eps) (A (A eps))
+h = 3: (A eps) (A (A eps)) (A (A (A eps)))
+```
+
+### Case 5c: two-node cycle
+
+Grammar:
+
+```
+S -> A | a
+A -> S
+```
+
+Input: `a`
+
+Forest (a cycle through two nodes with the same span):
+
+```
+S[0,1]: { A[0,1] } | { a }
+A[0,1]: { S[0,1] }
+```
+
+Bounded tree sets (each trip around the cycle adds 2 to the height):
+
+```
+h = 1: (S a)
+h = 2: (S a)
+h = 3: (S a) (S (A (S a)))
+h = 4: (S a) (S (A (S a)))
+h = 5: (S a) (S (A (S a))) (S (A (S (A (S a)))))
+```
+
+### Case 5d: cycle combined with ambiguity
+
+Grammar:
+
+```
+S -> S S | S | a
+```
+
+Input: `a a`
+
+Do not hand-write the expected forest for this one. It is the stress
+test: the oracle (bounded by h) is the ground truth. Checks to make:
+
+- The forest is finite and its node count is polynomial in n.
+- Every cycle passes only through nodes with identical span (see
+ invariants in `CLAUDE.md`).
+- For h = 1, 2, 3, 4 the unpacked tree set equals the oracle's
+ `trees_h`.
+- Nodes with span `[0,2]` are shared: `S[0,2]` must exist exactly
+ once.
+
+---
+
+## Suggested extra cases to add once the above pass
+
+- Left recursion: `S -> S a | a`, input `aaaa` (1 tree).
+- Hidden left recursion: `S -> A S a | a ; A -> eps`.
+- Nullable in the middle: `S -> a A b ; A -> eps | A c`.
+- Highly ambiguous with epsilon: `S -> S S | a | eps`, input `aa` (`S
+ -> S S` with `S -> eps` is cyclic, so this exercises Case 5
+ behavior; compare bounded tree sets only).