f38bd8e11e25f837ae6e2425ddf25fd5d253615c
[sbp.git] / src / edu / berkeley / sbp / meta / GrammarBuilder.java
1 // Copyright 2006 all rights reserved; see LICENSE file for BSD-style license
2
3 package edu.berkeley.sbp.meta;
4 import edu.berkeley.sbp.util.*;
5 import edu.berkeley.sbp.*;
6 import edu.berkeley.sbp.chr.*;
7 import edu.berkeley.sbp.misc.*;
8 import java.util.*;
9 import java.lang.annotation.*;
10 import java.lang.reflect.*;
11 import java.io.*;
12
13 // FIXME: deal with question-marks
14
15 // FIXME: put back in associativity: ^")"
16 //            A = A "->" (A)
17 //        means that the FIRST "A" cannot match the SAME sequence in
18 //        which the (A) occurs
19 //        -- and add a test case
20
21 // FIXME: make it so that we can have multi-result nonterminals
22 //        so long as they always appear under double-colons?
23 //        auto-insert the unwrap?
24
25 // FIXME: put associativity back in
26
27 // FIXME: "flat" sequences (no subtrees contain "::"s?)
28
29 // freezing problem is related to comments in grammar files
30
31 /** The java classes typically used to represent a parsed grammar AST; each inner class is a type of AST node. */
32 public class GrammarBuilder {
33
34     /**
35      *  Create a grammar from a parse tree and binding resolver
36      * 
37      *  @param t   a tree produced by parsing a grammar using the metagrammar
38      *  @param s   the name of the "start symbol"
39      *  @param gbr a GrammarBindingResolver that resolves grammatical reductions into tree-node-heads
40      */
41     public static Union buildFromAST(Tree grammarAST, String startingNonterminal, File[] includes) {
42         return new GrammarBuilder(includes, "").buildGrammar(grammarAST, startingNonterminal);
43     }
44
45     public static Object illegalTag = ""; // this is the tag that should never appear in the non-dropped output FIXME
46
47     private final String prefix;
48     private final File[] includes;
49
50     //public GrammarBuilder(String path) { this(path, ""); }
51     public GrammarBuilder(File[] includes, String prefix) {
52         this.prefix = prefix;
53         this.includes = includes;
54     }
55
56     public Union buildGrammar(Tree t, String rootNonTerminal) {
57         return ((GrammarBuilder.GrammarNode)walk(t)).build(rootNonTerminal);
58     }
59
60     private ElementNode epsilon = new LiteralNode("");
61
62     public Object[] walkChildren(Tree t) {
63         Object[] ret = new Object[t.size()];
64         for(int i=0; i<ret.length; i++) {
65             ret[i] = walk(t.child(i));
66             if (ret[i] instanceof Object[])
67                 ret[i] = Reflection.lub((Object[])ret[i]);
68         }
69         return Reflection.lub(ret);
70     }
71     public String stringifyChildren(Tree t) {
72         StringBuffer sb = new StringBuffer();
73         for(int i=0; i<t.size(); i++) {
74             sb.append(t.child(i).head());
75             sb.append(stringifyChildren(t.child(i)));
76         }
77         return sb.toString();
78     }
79     public String unescape(Tree t) {
80         StringBuffer sb = new StringBuffer();
81         for(int i=0; i<t.size(); i++)
82             sb.append(t.child(i).head()+stringifyChildren(t.child(i)));
83         return sb.toString();
84     }
85
86     public Object walk(Tree t) {
87         String head = (String)t.head();
88         if (head.indexOf('.') != 0)
89             while(head.indexOf('.') != -1)
90                 head = head.substring(head.indexOf('.')+1);
91         if (head==null) throw new RuntimeException("head is null: " + t);
92         if (head.equals("|")) return walkChildren(t);
93         if (head.equals("RHS")) return walkChildren(t);
94         if (head.equals("Grammar")) return new GrammarNode(walkChildren(t));
95         if (head.equals("(")) return new UnionNode((Seq[][])walkChildren(t.child(0)));
96         if (head.equals("Word")) return stringifyChildren(t);
97         if (head.equals("Elements")) return seq2((ElementNode[])Reflection.rebuild(walkChildren(t), ElementNode[].class));
98         if (head.equals("NonTerminalReference")) return new ReferenceNode(stringifyChildren(t.child(0)));
99         //if (head.equals(")")) return new ReferenceNode(stringifyChildren(t.child(0)));
100         if (head.equals("{")) return new XTree((Seq)walk(t.child(0)));
101         if (head.equals("::")) return tag((String)walk(t.child(0)), (Seq)walk(t.child(1)));
102         if (head.equals("++")) return plusmax((ElementNode)walk(t.child(0)));
103         if (head.equals("...")) return star(new TildeNode(new AtomNode()));
104         if (head.equals("+")) return plus((ElementNode)walk(t.child(0)));
105         if (head.equals("++/")) return plusmaxfollow((ElementNode)walk(t.child(0)), (ElementNode)walk(t.child(1)));
106         if (head.equals("+/")) return plusfollow((ElementNode)walk(t.child(0)), (ElementNode)walk(t.child(1)));
107         if (head.equals("**")) return starmax((ElementNode)walk(t.child(0)));
108         if (head.equals("*")) return star((ElementNode)walk(t.child(0)));
109         if (head.equals("**/")) return starmaxfollow((ElementNode)walk(t.child(0)), (ElementNode)walk(t.child(1)));
110         if (head.equals("*/")) return starfollow((ElementNode)walk(t.child(0)), (ElementNode)walk(t.child(1)));
111         if (head.equals("?")) return question((ElementNode)walk(t.child(0)));
112         if (head.equals("!")) return new DropNode((ElementNode)walk(t.child(0)));
113         if (head.equals("^")) return new LiteralNode((String)walk(t.child(0)), true);
114         if (head.equals("`")) {
115             ElementNode ret = (ElementNode)walk(t.child(0));
116             ret.lifted = true;
117             return ret;
118         }
119         if (head.equals("Quoted")) return stringifyChildren(t);
120         if (head.equals("Literal")) return new LiteralNode((String)walk(t.child(0)));
121         if (head.equals("->")) return arrow((Seq)walk(t.child(0)), (ElementNode)walk(t.child(1)));
122         if (head.equals("DropNT")) return new NonTerminalNode((String)walk(t.child(0)), (Seq[][])walkChildren(t.child(1)), false, null, true);
123         if (head.equals("=") && t.size()==2) return new NonTerminalNode((String)walk(t.child(0)), (Seq[][])walk(t.child(1)), true, null, false);
124         if (head.equals("=")) return new NonTerminalNode((String)walk(t.child(0)), (Seq[][])walk(t.child(2)), true, (String)walk(t.child(1)), false);
125         if (head.equals("&")) return and2((Seq)walk(t.child(0)), (Seq)walk(t.child(1)));
126         if (head.equals("&~")) return andnot2((Seq)walk(t.child(0)), (Seq)walk(t.child(1)));
127         if (head.equals("/")) return ((Seq)walk(t.child(0))).separate((ElementNode)walk(t.child(1)));
128         if (head.equals("()")) return epsilon;
129         if (head.equals("[")) return new AtomNode((AtomNodeRange[])Reflection.rebuild(walkChildren(t), AtomNodeRange[].class));
130         if (head.equals("\\{")) return new DropNode(new AtomNode(new AtomNodeRange(CharAtom.left, CharAtom.left)));
131         if (head.equals("\\}")) return new DropNode(new AtomNode(new AtomNodeRange(CharAtom.right, CharAtom.right)));
132         if (head.equals("~")) return new TildeNode((ElementNode)walk(t.child(0)));
133         if (head.equals("Range") && t.size()==1) return new AtomNodeRange(unescape(t).charAt(0));
134         if (head.equals("Range")) return new AtomNodeRange(unescape(t).charAt(0), unescape(t).charAt(1));
135         if (head.equals("\"\"")) return "";
136         if (head.equals("\n")) return "\n";
137         if (head.equals("\r")) return "\r";
138         if (head.equals("grammar.Grammar")) return walkChildren(t);
139         if (head.equals("SubGrammar")) return GrammarBuilder.buildFromAST(t.child(0), "s", includes);
140         if (head.equals("NonTerminal"))
141             return new NonTerminalNode((String)walk(t.child(0)),
142                                        (Seq[][])walkChildren(t.child(1)), false, null, false);
143         if (head.equals("Colons")) {
144             String tag = (String)walk(t.child(0));
145             Seq[][] seqs = (Seq[][])walk(t.child(1));
146             for(Seq[] seq : seqs)
147                 for(int i=0; i<seq.length; i++)
148                     seq[i] = tag(tag, seq[i]);
149             return new NonTerminalNode(tag, seqs, false, null, false);
150         }
151         if (head.equals("TestCase"))
152             return new RegressionTests.TestCase((String)walk(t.child(0)),
153                                                 (String)walk(t.child(1)),
154                                                 (String[])Reflection.coerce(walkChildren(t.child(2)), String[].class),
155                                                 (Union)walk(t.child(3)),
156                                                 false,
157                                                 false);
158         if (head.equals("#import")) {
159             String fileName = (String)stringifyChildren(t.child(0));
160             for(File f : includes) {
161                 File file = new File(f.getAbsolutePath()+File.separatorChar+fileName);
162                 if (!file.exists()) continue;
163                 try {
164                     String newPrefix = t.size()<2 ? "" : ((String)walk(t.child(1))+".");
165                     FileInputStream fis = new FileInputStream(file);
166                     Tree tr = new CharParser(MetaGrammar.newInstance()).parse(fis).expand1();
167                     return (GrammarNode)new GrammarBuilder(includes, newPrefix).walk(tr);
168                 } catch (Exception e) {
169                     throw new RuntimeException("while parsing " + file, e);
170                 }
171             }
172             throw new RuntimeException("unable to find #include file \""+fileName+"\"");
173         }
174         throw new RuntimeException("unknown head: \"" + head + "\" => " + (head.equals("...")));
175     }
176     
177     /** A grammar (a set of nonterminals) */
178     public class GrammarNode extends HashMap<String,NonTerminalNode> {
179         public GrammarNode(NonTerminalNode[] nonterminals) {
180             for(NonTerminalNode nt : nonterminals) {
181                 if (nt==null) continue;
182                 if (this.get(nt.name)!=null)
183                     throw new RuntimeException("duplicate definition of nonterminal \""+nt.name+"\"");
184                 this.put(nt.name, nt);
185             }
186         }
187         public  GrammarNode(Object[] nt) { add(nt); }
188         private void add(Object o) {
189             if (o==null) return;
190             else if (o instanceof Object[]) for(Object o2 : (Object[])o) add(o2);
191             else if (o instanceof NonTerminalNode) {
192                 NonTerminalNode nt = (NonTerminalNode)o;
193                 if (this.get(nt.name)!=null)
194                     throw new RuntimeException("duplicate definition of nonterminal \""+nt.name+"\"");
195                 this.put(nt.name, nt);
196             }
197             else if (o instanceof GrammarNode)
198                 for(NonTerminalNode n : ((GrammarNode)o).values())
199                     add(n);
200         }
201         public String toString() {
202             String ret = "[ ";
203             for(NonTerminalNode nt : values()) ret += nt + ", ";
204             return ret + " ]";
205         }
206         public Union build(String rootNonterminal) {
207             Context cx = new Context(this);
208             Union u = null;
209             for(GrammarBuilder.NonTerminalNode nt : values())
210                 if (nt.name.equals(rootNonterminal))
211                     return (Union)cx.get(nt.name);
212             return null;
213         }
214     }
215
216     public class UnionNode extends ElementNode {
217         public Seq[][] sequences;
218         public String  sep = null;
219         public boolean rep;
220         public UnionNode(Seq[][] sequences) { this(sequences, false, null); }
221         public UnionNode(Seq[][] sequences, boolean rep, String sep) {
222             this.sequences = sequences;
223             this.rep = rep;
224             this.sep = sep;
225         }
226         public Atom toAtom(Context cx) {
227             Atom ret = null;
228             for(Seq[] ss : sequences)
229                 for(Seq s : ss)
230                     ret = ret==null ? s.toAtom(cx) : (Atom)ret.union(s.toAtom(cx));
231             return ret;
232         }
233         public Element build(Context cx, NonTerminalNode cnt, boolean dropall) {
234             return buildIntoPreallocatedUnion(cx, cnt, dropall, new Union(null, false)); }
235         public Element buildIntoPreallocatedUnion(Context cx, NonTerminalNode cnt, boolean dropall, Union u) {
236             Union urep = null;
237             if (rep) {
238                 urep = new Union(null, false);
239                 urep.add(Sequence.create(new Element[0], cnt.name));
240                 urep.add(sep==null
241                          ? Sequence.create(new Element[] { u }, 0)
242                          : Sequence.create(new Element[] { cx.get(sep), u }, 1));
243             }
244             HashSet<Sequence> bad2 = new HashSet<Sequence>();
245             for(int i=0; i<sequences.length; i++) {
246                 Seq[] group = sequences[i];
247                 Union u2 = new Union(null, false);
248                 if (sequences.length==1) u2 = u;
249                 for(int j=0; j<group.length; j++)
250                     if (!rep)
251                         group[j].build(cx, u2, cnt, dropall);
252                     else {
253                         Union u3 = new Union(null, false);
254                         group[j].build(cx, u3, cnt, dropall);
255                         Sequence s = Sequence.create(cnt.name,
256                                                      new Element[] { u3, urep },
257                                                      new boolean[] { false, false },
258                                                      true);
259                         u2.add(s);
260                     }
261                 if (sequences.length==1) break;
262                 Sequence seq = Sequence.create(u2);
263                 for(Sequence s : bad2) seq = seq.andnot(s);
264                 u.add(seq);
265                 bad2.add(Sequence.create(u2));
266             }
267             return u;
268         }
269     }
270
271     public class NonTerminalNode extends UnionNode {
272         public boolean alwaysDrop;
273         public String  name = null;
274         public boolean drop(Context cx) { return alwaysDrop; }
275         public NonTerminalNode(String name, Seq[][] sequences, boolean rep, String sep, boolean alwaysDrop) {
276             super(sequences, rep, sep==null?null:(prefix + sep));
277             this.name = prefix + name;
278             this.alwaysDrop = alwaysDrop;
279         }
280         public Element build(Context cx, NonTerminalNode cnt, boolean dropall) { return cx.get(name); }
281     }
282
283     public class Seq {
284         public boolean alwaysDrop = false;
285         public boolean drop(Context cx) { return alwaysDrop; }
286         HashSet<Seq> and = new HashSet<Seq>();
287         HashSet<Seq> not = new HashSet<Seq>();
288         ElementNode[] elements;
289         ElementNode follow;
290         String tag = null;
291         public Seq(ElementNode e) { this(new ElementNode[] { e }); }
292         public Seq(ElementNode[] elements) {
293             this.elements = elements;
294             for(int i=0; i<elements.length; i++)
295                 if (elements[i]==null)
296                     throw new RuntimeException();
297         }
298         public Atom toAtom(Context cx) {
299             if (elements.length != 1)
300                 throw new Error("you attempted to use ->, **, ++, or a similar character-class"+
301                                 " operator on a [potentially] multicharacter production");
302             return elements[0].toAtom(cx);
303         }
304         public Seq tag(String tag) { this.tag = tag; return this; }
305         public Seq follow(ElementNode follow) { this.follow = follow; return this; }
306         public Seq and(Seq s) { and.add(s); return this; }
307         public Seq andnot(Seq s) { not.add(s); return this; }
308         public Seq dup() {
309             Seq ret = new Seq(elements);
310             ret.and.addAll(and);
311             ret.not.addAll(not);
312             ret.follow = follow;
313             ret.tag = tag;
314             return ret;
315         }
316         public Seq separate(ElementNode sep) {
317             ElementNode[] elements = new ElementNode[this.elements.length * 2 - 1];
318             for(int i=0; i<this.elements.length; i++) {
319                 elements[i*2] = this.elements[i];
320                 if (i<this.elements.length-1)
321                     elements[i*2+1] = new DropNode(sep);
322             }
323             this.elements = elements;
324             return this;
325         }
326         public Sequence build(Context cx, Union u, NonTerminalNode cnt, boolean dropall) {
327             Sequence ret = build0(cx, cnt, dropall);
328             for(Seq s : and) ret = ret.and(s.build(cx, null, cnt, true));
329             for(Seq s : not) ret = ret.andnot(s.build(cx, null, cnt, true));
330             if (u!=null) u.add(ret);
331             return ret;
332         }
333         public Sequence build0(Context cx, NonTerminalNode cnt, boolean dropall) {
334             boolean[] drops = new boolean[elements.length];
335             Element[] els = new Element[elements.length];
336             dropall |= drop(cx);
337             for(int i=0; i<elements.length; i++) {
338                 if (dropall) drops[i] = true;
339                 else         drops[i] = elements[i].drop(cx);
340                 if (elements[i].getOwnerTag() != null)
341                     tag = elements[i].getOwnerTag();
342             }
343             Sequence ret = null;
344             int idx = -1;
345             boolean multiNonDrop = false;
346             for(int i=0; i<drops.length; i++)
347                 if (!drops[i])
348                     if (idx==-1) idx = i;
349                     else multiNonDrop = true;
350             for(int i=0; i<elements.length; i++) {
351                 if (!multiNonDrop && i==idx && tag!=null && elements[i] instanceof RepeatNode) {
352                     els[i] = ((RepeatNode)elements[i]).build(cx, cnt, dropall, tag);
353                     tag = null;
354                 } else
355                     els[i] = elements[i].build(cx, cnt, dropall);
356             }
357             if (tag==null && multiNonDrop)
358                 throw new RuntimeException("multiple non-dropped elements in sequence: " + Sequence.create(els, ""));
359             boolean lift = false;
360             if (elements.length > 0 && elements[0].lifted)
361                 lift = true;
362             if (!multiNonDrop) {
363                 if (idx == -1) 
364                     ret = tag==null
365                         ? Sequence.create(els, illegalTag)
366                         : Sequence.createLeft(tag, els, drops, lift);
367                 else if (tag==null) ret = Sequence.create(els, idx);
368                 else ret = Sequence.createLeft(tag, els, drops, lift);
369             }
370             if (multiNonDrop)
371                 ret = Sequence.createLeft(tag, els, drops, lift);
372             if (this.follow != null)
373                 ret = ret.followedBy(this.follow.toAtom(cx));
374             return ret;
375         }
376     }
377
378     public class ReferenceNode extends ElementNode {
379         public String nonTerminal;
380         public ReferenceNode() { }
381         public NonTerminalNode resolve(Context cx) {
382             NonTerminalNode ret = cx.grammar.get(nonTerminal);
383             if (ret==null) throw new RuntimeException("undefined nonterminal: " + nonTerminal);
384             return ret;
385         }
386         public ReferenceNode(String nonTerminal) { this.nonTerminal = prefix + nonTerminal; }
387         public Atom toAtom(Context cx) { return cx.grammar.get(nonTerminal).toAtom(cx); }
388         public boolean drop(Context cx) { return resolve(cx).drop(cx); }
389         public Element build(Context cx, NonTerminalNode cnt, boolean dropall) {
390             if (!this.nonTerminal.startsWith(prefix)) nonTerminal = prefix + nonTerminal;
391             Element ret = cx.get(nonTerminal);
392             if (ret == null) throw new RuntimeException("unknown nonterminal \""+nonTerminal+"\"");
393             return ret;
394         }
395     }
396
397     public class LiteralNode extends ElementNode {
398         private String string;
399         private final String thePrefix = prefix;
400         private boolean caret;
401         public LiteralNode(String string) { this(string, false); }
402         public LiteralNode(String string, boolean caret) {
403             this.string = string;
404             this.caret = caret;
405         }
406         public String getOwnerTag() { return caret ? thePrefix+string : super.getOwnerTag(); }
407         public String toString() { return "\""+string+"\""; }
408         public boolean drop(Context cx) { return true; }
409         public Atom toAtom(Context cx) {
410             if (string.length()!=1) return super.toAtom(cx);
411             edu.berkeley.sbp.util.Range.Set set = new edu.berkeley.sbp.util.Range.Set();
412             set.add(string.charAt(0), string.charAt(0));
413             return CharAtom.set(set);
414         }
415         public Element build(Context cx, NonTerminalNode cnt, boolean dropall) { return CharAtom.string(string); }
416     }
417
418     public class AtomNode extends ElementNode {
419         AtomNodeRange[] ranges;
420         public AtomNode() { this(new AtomNodeRange[0]); }
421         public AtomNode(AtomNodeRange[] ranges) { this.ranges = ranges; }
422         public AtomNode(AtomNodeRange range) { this.ranges = new AtomNodeRange[] { range }; }
423         public Element build(Context cx, NonTerminalNode cnt, boolean dropall) { return toAtom(cx); }
424         public Atom toAtom(Context cx) {
425             edu.berkeley.sbp.util.Range.Set set = new edu.berkeley.sbp.util.Range.Set();
426             for(AtomNodeRange r : ranges) set.add(r.first, r.last);
427             return CharAtom.set(set);
428         }
429     }
430     public class AtomNodeRange {
431         public char first;
432         public char last;
433         public AtomNodeRange(char only) { first = only; last = only; }
434         public AtomNodeRange(char first, char last) { this.first = first; this.last = last; }
435     }
436
437     public class RepeatNode extends ElementNode {
438         public ElementNode e, sep;
439         public final boolean zero, many, max;
440         public RepeatNode(ElementNode e, ElementNode sep, boolean zero, boolean many, boolean max) {
441             this.e = e; this.sep = sep; this.zero = zero; this.many = many; this.max = max;
442         }
443         public Atom toAtom(Context cx) { return sep==null ? e.toAtom(cx) : super.toAtom(cx); }
444         public boolean drop(Context cx) { return e.drop(cx); }
445         public Element build(Context cx, NonTerminalNode cnt, boolean dropall) {
446             Element ret = build(cx, cnt, dropall, illegalTag);
447             String must = "must be tagged unless they appear within a dropped expression or their contents are dropped: ";
448             if (!dropall && !drop(cx) && !e.drop(cx))
449                 if (!many)      throw new RuntimeException("options (?) " + must + ret);
450                 else if (zero)  throw new RuntimeException("zero-or-more repetitions (*) " + must + ret);
451                 else            throw new RuntimeException("one-or-more repetitions (+) " + must + ret);
452             return ret;
453         }
454         public Element build(Context cx, NonTerminalNode cnt, boolean dropall, Object repeatTag) {
455             return (!max)
456                 ? Repeat.repeat(e.build(cx, null, dropall), zero, many, sep==null ? null : sep.build(cx, null, dropall), repeatTag)
457                 : sep==null
458                 ? Repeat.repeatMaximal(e.toAtom(cx), zero, many, repeatTag)
459                 : Repeat.repeatMaximal(e.build(cx, null, dropall), zero, many, sep.toAtom(cx), repeatTag);
460         }
461     }
462
463     public abstract class ElementNode {
464         public boolean lifted = false;
465         public String getOwnerTag() { return null; }
466         public boolean drop(Context cx) { return false; }
467         public Atom toAtom(Context cx) { throw new Error("can't convert a " + this.getClass().getName() + " to an atom: " + this); }
468         public abstract Element build(Context cx, NonTerminalNode cnt, boolean dropall);
469     }
470
471     public abstract class ElementNodeWrapper extends ElementNode {
472         protected ElementNode _e;
473         public ElementNodeWrapper(ElementNode e) { this._e = e; }
474         public String getOwnerTag() { return _e.getOwnerTag(); }
475         public boolean drop(Context cx) { return _e.drop(cx); }
476         public Atom toAtom(Context cx) { return _e.toAtom(cx); }
477         public Element build(Context cx, NonTerminalNode cnt, boolean dropall) { return _e.build(cx, cnt, dropall); }
478     }
479
480     public class TildeNode extends ElementNodeWrapper {
481         public TildeNode(ElementNode e) { super(e); }
482         public Atom toAtom(Context cx) { return (Atom)((Topology<Character>)_e.toAtom(cx).complement()); }
483         public Element build(Context cx, NonTerminalNode cnt, boolean dropall) { return toAtom(cx); }
484     }
485
486     public class DropNode extends ElementNodeWrapper {
487         public DropNode(ElementNode e) { super(e); }
488         public boolean drop(Context cx) { return true; }
489     }
490
491     public    Seq  and2(Seq s,        Seq a)   { a.alwaysDrop = true;  return s.and(a); }
492     public   Seq  andnot2(Seq s,     Seq a)   { a.alwaysDrop = true; return s.andnot(a); }
493     public   Seq  arrow(Seq s, ElementNode e) { return s.follow(e); }
494     public   Seq  tag(String tagname, Seq s)  { return s.tag(tagname); }
495     public Seq  seq(ElementNode[] elements)               { return new Seq(elements); }
496     public   Seq  seq2(ElementNode[] elements)               { return new Seq(elements); }
497     public   ElementNode plusmax(final ElementNode e)                         { return new RepeatNode(e, null, false, true, true); }
498     public    ElementNode plus(final ElementNode e)                            { return new RepeatNode(e, null, false, true, false); }
499     public  ElementNode plusmaxfollow(final ElementNode e, final ElementNode sep)     { return new RepeatNode(e, sep,  false, true, true); }
500     public   ElementNode plusfollow(final ElementNode e, final ElementNode sep)        { return new RepeatNode(e, sep,  false, true, false); }
501     public   ElementNode starmax(final ElementNode e)                         { return new RepeatNode(e, null, true,  true, true); }
502     public    ElementNode star(final ElementNode e)                            { return new RepeatNode(e, null, true,  true, false); }
503     public  ElementNode starmaxfollow(final ElementNode e, final ElementNode sep)     { return new RepeatNode(e, sep,  true,  true, true); }
504     public   ElementNode starfollow(final ElementNode e, final ElementNode sep)        { return new RepeatNode(e, sep,  true,  true, false); }
505     public    ElementNode question(final ElementNode e)                        { return new RepeatNode(e, null, true,  false, false); }
506
507     //////////////////////////////////////////////////////////////////////////////
508
509     public class Context {
510         public HashMap<String,Union> map = new HashMap<String,Union>();
511         public GrammarNode grammar;
512         public Context(Tree t) { }
513         public Context(GrammarNode g) { this.grammar = g; }
514         public Union build() {
515             Union ret = null;
516             for(NonTerminalNode nt : grammar.values()) {
517                 Union u = get(nt.name);
518                 if ("s".equals(nt.name))
519                     ret = u;
520             }
521             return ret;
522         }
523         public Union peek(String name) { return map.get(name); }
524         public void  put(String name, Union u) { map.put(name, u); }
525         public Union get(String name) {
526             Union ret = map.get(name);
527             if (ret != null) return ret;
528             NonTerminalNode nt = grammar.get(name);
529             if (nt==null) {
530                 throw new Error("warning could not find " + name);
531             } else {
532                 ret = new Union(name, false);
533                 map.put(name, ret);
534                 nt.buildIntoPreallocatedUnion(this, nt, nt.drop(this), ret);
535             }
536             return ret;
537         }
538
539     }
540
541     public class XTree extends ElementNode {
542         public Seq body;
543         public XTree(Seq seq) { this.body = seq; }
544         public Element build(Context cx, NonTerminalNode cnt, boolean dropall) {
545             Union u = new Union(null, false);
546             Sequence s = body.build(cx, u, null, dropall);
547             Union u2 = new Union(null, false);
548             u2.add(Sequence.create(new Element[] {
549                 CharAtom.leftBrace,
550                 u,
551                 CharAtom.rightBrace
552             }, 1));
553             return u2;
554         }
555     }
556 }