clean up metagrammar handling of drop and question-mark
[sbp.git] / src / edu / berkeley / sbp / meta / GrammarBuilder.java
index c7ef44a..e6e06ad 100644 (file)
@@ -31,15 +31,26 @@ import java.io.*;
 /** The java classes typically used to represent a parsed grammar AST; each inner class is a type of AST node. */
 public class GrammarBuilder {
 
+    /**
+     *  Create a grammar from a parse tree and binding resolver
+     * 
+     *  @param t   a tree produced by parsing a grammar using the metagrammar
+     *  @param s   the name of the "start symbol"
+     *  @param gbr a GrammarBindingResolver that resolves grammatical reductions into tree-node-heads
+     */
+    public static Union buildFromAST(Tree grammarAST, String startingNonterminal, File[] includes) {
+        return new GrammarBuilder(includes, "").buildGrammar(grammarAST, startingNonterminal);
+    }
+
     public static Object illegalTag = ""; // this is the tag that should never appear in the non-dropped output FIXME
 
     private final String prefix;
-    private final String path;
+    private final File[] includes;
 
     //public GrammarBuilder(String path) { this(path, ""); }
-    public GrammarBuilder(String path, String prefix) {
+    public GrammarBuilder(File[] includes, String prefix) {
         this.prefix = prefix;
-        this.path = path;
+        this.includes = includes;
     }
 
     public Union buildGrammar(Tree t, String rootNonTerminal) {
@@ -100,7 +111,7 @@ public class GrammarBuilder {
         if (head.equals("Quoted")) return stringifyChildren(t);
         if (head.equals("Literal")) return new LiteralNode((String)walk(t.child(0)));
         if (head.equals("->")) return arrow((Seq)walk(t.child(0)), (ElementNode)walk(t.child(1)));
-        if (head.equals("DropNT")) return new NonTerminalNode((String)walk(t.child(0)), (Seq[][])walk(t.child(1)), true, null, true);
+        if (head.equals("DropNT")) return new NonTerminalNode((String)walk(t.child(0)), (Seq[][])walkChildren(t.child(1)), false, null, true);
         if (head.equals("=") && t.size()==2) return new NonTerminalNode((String)walk(t.child(0)), (Seq[][])walk(t.child(1)), true, null, false);
         if (head.equals("=")) return new NonTerminalNode((String)walk(t.child(0)), (Seq[][])walk(t.child(2)), true, (String)walk(t.child(1)), false);
         if (head.equals("&")) return and2((Seq)walk(t.child(0)), (Seq)walk(t.child(1)));
@@ -117,7 +128,7 @@ public class GrammarBuilder {
         if (head.equals("\n")) return "\n";
         if (head.equals("\r")) return "\r";
         if (head.equals("grammar.Grammar")) return walkChildren(t);
-        if (head.equals("SubGrammar")) return Grammar.create(t.child(0), "s");
+        if (head.equals("SubGrammar")) return GrammarBuilder.buildFromAST(t.child(0), "s", includes);
         if (head.equals("NonTerminal"))
             return new NonTerminalNode((String)walk(t.child(0)),
                                        (Seq[][])walkChildren(t.child(1)), false, null, false);
@@ -137,16 +148,21 @@ public class GrammarBuilder {
                                                 false,
                                                 false);
         if (head.equals("#import")) {
-            String fileName = path+(String)stringifyChildren(t.child(0));
-            try {
-                String newPrefix = (String)walk(t.child(1));
-                if (newPrefix.length() > 0) newPrefix += ".";
-                FileInputStream fis = new FileInputStream(fileName);
-                Tree tr = new CharParser(MetaGrammar.newInstance()).parse(fis).expand1();
-                return (GrammarNode)new GrammarBuilder(path, newPrefix).walk(tr);
-            } catch (Exception e) {
-                throw new RuntimeException("while parsing " + fileName, e);
+            String fileName = (String)stringifyChildren(t.child(0));
+            for(File f : includes) {
+                File file = new File(f.getAbsolutePath()+File.separatorChar+fileName);
+                if (!file.exists()) continue;
+                try {
+                    String newPrefix = (String)walk(t.child(1));
+                    if (newPrefix.length() > 0) newPrefix += ".";
+                    FileInputStream fis = new FileInputStream(file);
+                    Tree tr = new CharParser(MetaGrammar.newInstance()).parse(fis).expand1();
+                    return (GrammarNode)new GrammarBuilder(includes, newPrefix).walk(tr);
+                } catch (Exception e) {
+                    throw new RuntimeException("while parsing " + file, e);
+                }
             }
+            throw new RuntimeException("unable to find #include file \""+fileName+"\"");
         }
         throw new RuntimeException("unknown head: " + head + "\n"+t);
     }
@@ -248,7 +264,7 @@ public class GrammarBuilder {
     public class NonTerminalNode extends UnionNode {
         public boolean alwaysDrop;
         public String  name = null;
-        public boolean drop() { return alwaysDrop; }
+        public boolean drop(Context cx) { return alwaysDrop; }
         public NonTerminalNode(String name, Seq[][] sequences, boolean rep, String sep, boolean alwaysDrop) {
             super(sequences, rep, sep==null?null:(prefix + sep));
             this.name = prefix + name;
@@ -259,7 +275,7 @@ public class GrammarBuilder {
 
     public class Seq {
         public boolean alwaysDrop = false;
-        public boolean drop() { return alwaysDrop; }
+        public boolean drop(Context cx) { return alwaysDrop; }
         HashSet<Seq> and = new HashSet<Seq>();
         HashSet<Seq> not = new HashSet<Seq>();
         ElementNode[] elements;
@@ -310,13 +326,10 @@ public class GrammarBuilder {
         public Sequence build0(Context cx, NonTerminalNode cnt, boolean dropall) {
             boolean[] drops = new boolean[elements.length];
             Element[] els = new Element[elements.length];
-            dropall |= drop();
+            dropall |= drop(cx);
             for(int i=0; i<elements.length; i++) {
                 if (dropall) drops[i] = true;
-                else         drops[i] = elements[i].drop();
-                if (elements[i] instanceof ReferenceNode)
-                    if (((ReferenceNode)elements[i]).resolve(cx).drop())
-                        drops[i] = true;
+                else         drops[i] = elements[i].drop(cx);
                 if (elements[i].getOwnerTag() != null)
                     tag = elements[i].getOwnerTag();
             }
@@ -327,8 +340,6 @@ public class GrammarBuilder {
                 if (!drops[i])
                     if (idx==-1) idx = i;
                     else multiNonDrop = true;
-            if (tag==null && multiNonDrop)
-                throw new Error("multiple non-dropped elements in sequence: " + Sequence.create(els, ""));
             for(int i=0; i<elements.length; i++) {
                 if (!multiNonDrop && i==idx && tag!=null && elements[i] instanceof RepeatNode) {
                     els[i] = ((RepeatNode)elements[i]).build(cx, cnt, dropall, tag);
@@ -336,6 +347,8 @@ public class GrammarBuilder {
                 } else
                     els[i] = elements[i].build(cx, cnt, dropall);
             }
+            if (tag==null && multiNonDrop)
+                throw new RuntimeException("multiple non-dropped elements in sequence: " + Sequence.create(els, ""));
             if (!multiNonDrop) {
                 if (idx == -1) 
                     ret = tag==null
@@ -358,6 +371,7 @@ public class GrammarBuilder {
         public NonTerminalNode resolve(Context cx) { return cx.grammar.get(nonTerminal); }
         public ReferenceNode(String nonTerminal) { this.nonTerminal = prefix + nonTerminal; }
         public Atom toAtom(Context cx) { return cx.grammar.get(nonTerminal).toAtom(cx); }
+        public boolean drop(Context cx) { return resolve(cx).drop(cx); }
         public Element build(Context cx, NonTerminalNode cnt, boolean dropall) {
             if (!this.nonTerminal.startsWith(prefix)) nonTerminal = prefix + nonTerminal;
             Element ret = cx.get(nonTerminal);
@@ -377,7 +391,7 @@ public class GrammarBuilder {
         }
         public String getOwnerTag() { return caret ? thePrefix+string : super.getOwnerTag(); }
         public String toString() { return "\""+string+"\""; }
-        public boolean drop() { return true; }
+        public boolean drop(Context cx) { return true; }
         public Atom toAtom(Context cx) {
             if (string.length()!=1) return super.toAtom(cx);
             edu.berkeley.sbp.util.Range.Set set = new edu.berkeley.sbp.util.Range.Set();
@@ -407,16 +421,23 @@ public class GrammarBuilder {
 
     public class RepeatNode extends ElementNode {
         public ElementNode e, sep;
-        public boolean zero, many, max;
+        public final boolean zero, many, max;
         public RepeatNode(ElementNode e, ElementNode sep, boolean zero, boolean many, boolean max) {
-            this.e = e; this.sep = sep; this.zero = zero; this.many = many; this.max = max;}
+            this.e = e; this.sep = sep; this.zero = zero; this.many = many; this.max = max;
+        }
         public Atom toAtom(Context cx) { return sep==null ? e.toAtom(cx) : super.toAtom(cx); }
+        public boolean drop(Context cx) { return e.drop(cx); }
         public Element build(Context cx, NonTerminalNode cnt, boolean dropall) {
-            if (!dropall && !drop() && !e.drop())
-                throw new Error("you need a tag on this repetition: " + build(cx, cnt, dropall, ""));
-            return build(cx, cnt, dropall, illegalTag);
+            Element ret = build(cx, cnt, dropall, illegalTag);
+            String must = "must be tagged unless they appear within a dropped expression or their contents are dropped: ";
+            if (!dropall && !drop(cx) && !e.drop(cx))
+                if (!many)      throw new RuntimeException("options (?) " + must + ret);
+                else if (zero)  throw new RuntimeException("zero-or-more repetitions (*) " + must + ret);
+                else            throw new RuntimeException("one-or-more repetitions (+) " + must + ret);
+            return ret;
         }
         public Element build(Context cx, NonTerminalNode cnt, boolean dropall, Object repeatTag) {
+            if (!many) System.out.println("tag is: " + repeatTag);
             return (!max)
                 ? Repeat.repeat(e.build(cx, null, dropall), zero, many, sep==null ? null : sep.build(cx, null, dropall), repeatTag)
                 : sep==null
@@ -427,7 +448,7 @@ public class GrammarBuilder {
 
     public abstract class ElementNode {
         public String getOwnerTag() { return null; }
-        public boolean drop() { return false; }
+        public boolean drop(Context cx) { return false; }
         public Atom toAtom(Context cx) { throw new Error("can't convert a " + this.getClass().getName() + " to an atom: " + this); }
         public abstract Element build(Context cx, NonTerminalNode cnt, boolean dropall);
     }
@@ -436,7 +457,7 @@ public class GrammarBuilder {
         protected ElementNode _e;
         public ElementNodeWrapper(ElementNode e) { this._e = e; }
         public String getOwnerTag() { return _e.getOwnerTag(); }
-        public boolean drop() { return _e.drop(); }
+        public boolean drop(Context cx) { return _e.drop(cx); }
         public Atom toAtom(Context cx) { return _e.toAtom(cx); }
         public Element build(Context cx, NonTerminalNode cnt, boolean dropall) { return _e.build(cx, cnt, dropall); }
     }
@@ -449,7 +470,7 @@ public class GrammarBuilder {
 
     public class DropNode extends ElementNodeWrapper {
         public DropNode(ElementNode e) { super(e); }
-        public boolean drop() { return true; }
+        public boolean drop(Context cx) { return true; }
     }
 
     public    Seq  and2(Seq s,        Seq a)   { a.alwaysDrop = true;  return s.and(a); }
@@ -466,7 +487,7 @@ public class GrammarBuilder {
     public    ElementNode star(final ElementNode e)                            { return new RepeatNode(e, null, true,  true, false); }
     public  ElementNode starmaxfollow(final ElementNode e, final ElementNode sep)     { return new RepeatNode(e, sep,  true,  true, true); }
     public   ElementNode starfollow(final ElementNode e, final ElementNode sep)        { return new RepeatNode(e, sep,  true,  true, false); }
-    public    ElementNode question(final ElementNode e)                        { return new RepeatNode(e, null, true,  true, false); }
+    public    ElementNode question(final ElementNode e)                        { return new RepeatNode(e, null, true,  false, false); }
 
     //////////////////////////////////////////////////////////////////////////////
 
@@ -495,7 +516,7 @@ public class GrammarBuilder {
             } else {
                 ret = new Union(name, false);
                 map.put(name, ret);
-                nt.buildIntoPreallocatedUnion(this, nt, nt.drop(), ret);
+                nt.buildIntoPreallocatedUnion(this, nt, nt.drop(this), ret);
             }
             return ret;
         }