From e5cfb136bf7fd1352eff1bd87a458aa4ff748537 Mon Sep 17 00:00:00 2001 From: adam Date: Mon, 29 May 2006 03:48:08 -0400 Subject: [PATCH] tentative checkpoint ROLL THIS BACK BUT INCLUDES CRUCIAL FIX darcs-hash:20060529074808-5007d-be65fd987295ef194773dbf20d0fe87af05fc760.gz --- Makefile | 8 ++ TODO | 6 +- src/edu/berkeley/sbp/GSS.java | 140 +++++++++++++++++++----- src/edu/berkeley/sbp/Parser.java | 44 +++++++- src/edu/berkeley/sbp/Sequence.java | 13 ++- src/edu/berkeley/sbp/chr/CharRange.java | 4 +- src/edu/berkeley/sbp/chr/CharTopology.java | 1 + src/edu/berkeley/sbp/misc/MetaGrammar.java | 6 +- src/edu/berkeley/sbp/misc/MetaGrammarTree.java | 24 ++++ src/edu/berkeley/sbp/misc/RegressionTests.java | 39 ++++--- src/edu/berkeley/sbp/util/GraphViz.java | 52 ++++++--- src/edu/berkeley/sbp/util/IntPairMap.java | 3 +- tests/loop.tc | 23 ++++ tests/regression.tc | 24 ++++ 14 files changed, 312 insertions(+), 75 deletions(-) create mode 100644 tests/loop.tc diff --git a/Makefile b/Makefile index 1da960a..4ce382c 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,13 @@ javatest: edu.berkeley.sbp.jar tests/testcase.g \ tests/java.tc +loop: edu.berkeley.sbp.jar + $(java) -cp $< edu.berkeley.sbp.misc.RegressionTests \ + -graph \ + tests/meta.g \ + tests/testcase.g \ + tests/loop.tc + boot: edu.berkeley.sbp.jar cd src; \ $(java) -cp ../$< \ @@ -72,3 +79,4 @@ upload: darcs dist echo '' > index.html rsync -are ssh --progress --verbose --delete ./ argus.cs.berkeley.edu:public_html/sbp/ + diff --git a/TODO b/TODO index d7afed2..1e53bb8 100644 --- a/TODO +++ b/TODO @@ -18,11 +18,7 @@ create( _:{...}, c[] ) = { create(.,c), create(.,c), ... } create( $c:{...} ) = - - clean up the visualization (?) - - - I still don't like Atom.Infer and Atom.Invert... - - - better ambiguity debugging tools + - better ambiguity debugging tools / visualization - ParseFailed, GSS, Walk, Parser, Sequence, Forest diff --git a/src/edu/berkeley/sbp/GSS.java b/src/edu/berkeley/sbp/GSS.java index 743cfd3..80fbd5c 100644 --- a/src/edu/berkeley/sbp/GSS.java +++ b/src/edu/berkeley/sbp/GSS.java @@ -11,6 +11,7 @@ import java.lang.reflect.*; class GSS { public static int count = 0; + public GSS() { } private Phase.Node[] reducing_list = null; @@ -26,8 +27,9 @@ class GSS { public Forest.Ref finalResult; /** corresponds to a positions between tokens the input stream; same as Tomita's U_i's */ - class Phase implements Invokable.Node>, IntegerMappable { + class Phase implements Invokable.Node>, IntegerMappable, GraphViz.ToGraphViz, Iterable { + public Iterator iterator() { return hash.iterator(); } public void invoke(State st, Forest result, Node n) { good |= next.newNode(n, result, st, false); } @@ -147,8 +149,8 @@ class GSS { } } private boolean newNode2(Node p, Node parent, Forest pending, State state, boolean fromEmptyReduction) { - p.holder.merge(pending); - if (p.parents().contains(parent)) return true; + //if (p.parents().contains(parent)) return true; + p.merge(parent, pending); p.parents().add(parent, true); if (p!=parent && !fromEmptyReduction && reducing) p.performReductions(parent); return true; @@ -216,7 +218,7 @@ class GSS { /** perform all shift operations, adding promoted nodes to next */ public void shift(Phase next, Forest result) throws ParseFailed { // this massively improves GC performance - if (prev!=null) { + if (prev!=null && parser.helpgc) { prev.hash = null; prev.singularReductions = null; } @@ -227,7 +229,8 @@ class GSS { for(Phase.Node n : hash.values()) { if (token == null && n.state.isAccepting()) { if (finalResult==null) finalResult = new Forest.Ref(); - finalResult.merge(n.holder); + for(Object f : n.results()) + finalResult.merge((Forest)f); } if (token == null) continue; n.state.invokeShifts(token, this, result, n); @@ -268,9 +271,27 @@ class GSS { // Node ///////////////////////////////////////////////////////////////////////////////// /** a node in the GSS */ - final class Node extends FastSet implements Invokable, IntegerMappable { + final class Node implements Invokable, IntegerMappable, GraphViz.ToGraphViz { + public FastSet set = new FastSet(); + + public GraphViz.Node toGraphViz(GraphViz gv) { + if (gv.hasNode(this)) return gv.createNode(this); + GraphViz.Node n = gv.createNode(this); + n.label = ""+state.toStringx(); + n.shape = "rectangle"; + n.fill = "green"; + //GraphViz.Node f = pending().toGraphViz(gv); + //n.add(f); + for(Forest result : results()) n.edge(result, ""); + for(Node parent : parents()) n.edge(parent, ""); + ((GraphViz.Group)phase().toGraphViz(gv)).add(n); + return n; + } + public boolean isTransparent() { return false; } + public boolean isHidden() { return false; } + - private Forest.Ref holder = null; + //private Forest.Ref holder = new Forest.Ref(); private boolean allqueued = false; /** what state this node is in */ @@ -278,9 +299,28 @@ class GSS { /** which Phase this Node belongs to (node that Node is also a non-static inner class of Phase) */ public Phase phase() { return Phase.this; } - public Forest.Ref holder() { return holder==null ? (holder = new Forest.Ref()) : holder; } - public Forest pending() { return Phase.this.closed ? holder().resolve() : holder; } - public FastSet parents() { return this; } + //private Forest pending() { return !Phase.this.closed ? holder : holder.resolve(); } + private HashMap resultMap = new HashMap(); + public void merge(Node parent, Forest result) { + //holder.merge(result); + Forest.Ref f = (Forest.Ref)resultMap.get(parent); + if (f==null) { f = new Forest.Ref(); resultMap.put(parent, f); } + f.merge(result); + set.add(parent, true); + } + //public Iterable childrenFor(Forest result) { return resultMap.getAll(result); } + public Iterable results() { return resultMap.values(); } + private Forest pending(Node n) { + //return !Phase.this.closed ? holder : holder.resolve(); + /* + for(Forest f : resultMap) + if (resultMap.contains(f, n)) + return f; + return null; + */ + return resultMap.get(n); + } + public FastSet parents() { return set; } public void performReductions() { if (allqueued) return; @@ -295,6 +335,7 @@ class GSS { public void performEmptyReductions() { state.invokeReductions(token, this, null, null); } public final void invoke(Position r, Node n, Node n2) { + //if (r.owner().lame) return; if (n==null || n2==null || r.pos==0) { if (r.pos==0) { if (n==null) n = this; @@ -303,38 +344,70 @@ class GSS { if (n==null) return; Forest[] holder = new Forest[r.pos]; if (r.pos==0) n.finish(r, r.zero(), n.phase(), holder); - else n.reduce(r, r.pos-1, n.phase(), holder); + else n.reduce(r, r.pos-1, n.phase(), holder, null, n.pending(n)); } else { Forest[] holder = new Forest[r.pos]; if (r.pos<=0) throw new Error("called wrong form of reduce()"); int pos = r.pos-1; - n.reduce(r, pos, n.phase(), holder, n2); + n.reduce(r, pos, n.phase(), holder, n2, n.pending(n)); } } - public void reduce(Position r, int pos, Phase target, Forest[] holder) { reduce(r, pos, target, holder, null); } + /* + public void reduce(Position r, int pos, Phase target, Forest[] holder) { + reduce(r, pos, target, holder, null); } public void reduce(Position r, int pos, Phase target, Forest[] holder, Node only) { + reduce(r, pos, target, holder, only, this.pending()); + } + */ + public void reduce(Position r, int pos, Phase target, Forest[] holder, Node only, Forest pending) { Forest old = holder[pos]; - holder[pos] = this.pending(); + holder[pos] = pending; if (pos==0) { - System.arraycopy(holder, 0, r.holder, 0, holder.length); - for(int i=0; i { /** implement this method to create the output forest corresponding to a lone shifted input token */ public abstract Forest shiftToken(Tok t, Input.Location loc); + public boolean helpgc = true; + + public String toString() { return pt.toString(); } + /** parse input, using the table pt to drive the parser */ public Forest parse(Input input) throws IOException, ParseFailed { GSS gss = new GSS(); @@ -24,11 +28,21 @@ public abstract class Parser { GSS.Phase current = gss.new Phase(null, this, null, input.next(), loc, null); current.newNode(null, Forest.leaf(null, null, null), pt.start, true); int count = 1; - for(;;) { + for(int idx=0;;idx++) { loc = input.getLocation(); current.reduce(); Forest forest = current.token==null ? null : shiftToken((Tok)current.token, loc); GSS.Phase next = gss.new Phase(current, this, current, input.next(), loc, forest); + if (!helpgc) { + FileOutputStream fos = new FileOutputStream("out-"+idx+".dot"); + PrintWriter p = new PrintWriter(new OutputStreamWriter(fos)); + GraphViz gv = new GraphViz(); + for(Object n : next) + ((GSS.Phase.Node)n).toGraphViz(gv); + gv.dump(p); + p.flush(); + p.close(); + } count = next.size(); if (current.isDone()) return (Forest)gss.finalResult; current = next; @@ -40,6 +54,26 @@ public abstract class Parser { /** an SLR(1) parse table which may contain conflicts */ static class Table extends Walk.Cache { + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("parse table"); + for(State state : all_states.values()) { + sb.append(" " + state + "\n"); + for(Topology t : state.shifts) { + sb.append(" shift \""+ + new edu.berkeley.sbp.chr.CharTopology((IntegerTopology)t)+"\" => "); + for(State st : state.shifts.getAll(t)) + sb.append(st.idx+" "); + sb.append("\n"); + } + for(Topology t : state.reductions) + sb.append(" reduce \""+ + new edu.berkeley.sbp.chr.CharTopology((IntegerTopology)t)+"\" => " + + state.reductions.getAll(t) + "\n"); + } + return sb.toString(); + } + public final Walk.Cache cache = this; private void walk(Element e, HashSet hs) { @@ -230,6 +264,14 @@ public abstract class Parser { } } + public String toStringx() { + StringBuffer st = new StringBuffer(); + for(Position p : this) { + if (st.length() > 0) st.append("\n"); + st.append(p); + } + return st.toString(); + } public String toString() { StringBuffer ret = new StringBuffer(); ret.append("state["+idx+"]: "); diff --git a/src/edu/berkeley/sbp/Sequence.java b/src/edu/berkeley/sbp/Sequence.java index a75bafa..b8e4263 100644 --- a/src/edu/berkeley/sbp/Sequence.java +++ b/src/edu/berkeley/sbp/Sequence.java @@ -18,6 +18,7 @@ public abstract class Sequence extends Element implements Iterable { for(Sequence s : needs) { ret.needs.add(s); s.needed.add(ret); } for(Sequence s : hates) { ret.hates.add(s); s.hated.add(ret); } ret.follow = follow; + ret.lame = lame; return ret; } @@ -38,7 +39,8 @@ public abstract class Sequence extends Element implements Iterable { * after matching the sequence, create the specified output tree * @param tag the tag for the output tree * @param e the elements to match - * @param drops only elements of e whose corresponding boolean in drops is false will be included in the output tree + * @param drops only elements of e whose corresponding boolean in drops + * is false will be included in the output tree **/ public static Sequence rewritingSequence(Object tag, Element[] e, Object[] labs, boolean[] drops) { return new RewritingSequence(tag, e, labs, drops); } @@ -49,7 +51,8 @@ public abstract class Sequence extends Element implements Iterable { public final Topology follow() { return follow==null ? null : Atom.toAtom(follow); } Topology toAtom() { - if (elements.length!=1) throw new RuntimeException("cannot invoke toAtom() on a Sequence with " + elements.length + " elements: " + this); + if (elements.length!=1) + throw new RuntimeException("cannot invoke toAtom() on a Sequence with " + elements.length + " elements: " + this); return Atom.toAtom(elements[0]); } @@ -59,9 +62,9 @@ public abstract class Sequence extends Element implements Iterable { protected final Element[] elements; final HashSet needed = new HashSet(); - final HashSet hated = new HashSet(); - final HashSet needs = new HashSet(); - final HashSet hates = new HashSet(); + final HashSet hated = new HashSet(); + final HashSet needs = new HashSet(); + final HashSet hates = new HashSet(); public boolean lame = false; final Position firstp; diff --git a/src/edu/berkeley/sbp/chr/CharRange.java b/src/edu/berkeley/sbp/chr/CharRange.java index 123e6ca..8952536 100644 --- a/src/edu/berkeley/sbp/chr/CharRange.java +++ b/src/edu/berkeley/sbp/chr/CharRange.java @@ -12,9 +12,9 @@ public class CharRange extends Atom { public CharRange(char a) { this(a,a); } public CharRange(char a, char b) { this(new CharTopology(a, b)); } - public CharRange(Topology t) { this.t = t; } + public CharRange(CharTopology t) { this.t = t; } - private Topology t; + private CharTopology t; public Topology top() { return t; } public static final char left = (char)9998; diff --git a/src/edu/berkeley/sbp/chr/CharTopology.java b/src/edu/berkeley/sbp/chr/CharTopology.java index d812855..8445eb3 100644 --- a/src/edu/berkeley/sbp/chr/CharTopology.java +++ b/src/edu/berkeley/sbp/chr/CharTopology.java @@ -7,6 +7,7 @@ public class CharTopology extends IntegerTopology implements Functor< public CharTopology() { super(null); } public CharTopology(Range.Set r) { super(null, r); } + public CharTopology(Topology it) { this(((IntegerTopology)it.unwrap()).getRanges()); } public CharTopology(char a, char b) { super(null, a, b); } public Integer invoke(Character c) { return (int)c.charValue(); } diff --git a/src/edu/berkeley/sbp/misc/MetaGrammar.java b/src/edu/berkeley/sbp/misc/MetaGrammar.java index 53c9bed..4d82952 100644 --- a/src/edu/berkeley/sbp/misc/MetaGrammar.java +++ b/src/edu/berkeley/sbp/misc/MetaGrammar.java @@ -20,8 +20,8 @@ public class MetaGrammar extends StringWalker { private static Element set(Range.Set r) { return CharRange.set(r); } private static Element string(String s) { return CharRange.string(s); } - private static Atom infer(Element e) { return infer(Atom.toAtom(e)); } - private static Atom infer(Topology t) { return new CharRange((Topology)t); } + private static Atom infer(Element e) { return infer((Topology)Atom.toAtom(e)); } + private static Atom infer(Topology t) { return new CharRange(new CharTopology(t)); } private MetaGrammar() { } @@ -357,7 +357,7 @@ public class MetaGrammar extends StringWalker { public MetaClause element; public MetaInvert(Tree t, MetaConjunct c) { this.element = make(t, c); } public String toString() { return "~"+element; } - public Element build(BuildContext bc) { return infer(Atom.toAtom(element.build(bc)).complement()); } + public Element build(BuildContext bc) { return infer((Topology)Atom.toAtom(element.build(bc)).complement()); } } } diff --git a/src/edu/berkeley/sbp/misc/MetaGrammarTree.java b/src/edu/berkeley/sbp/misc/MetaGrammarTree.java index 7dd8cbe..60d0da8 100644 --- a/src/edu/berkeley/sbp/misc/MetaGrammarTree.java +++ b/src/edu/berkeley/sbp/misc/MetaGrammarTree.java @@ -48,6 +48,18 @@ public class MetaGrammarTree { + + + + + + + + + + + + // DO NOT EDIT STUFF BELOW: IT IS AUTOMATICALLY GENERATED new edu.berkeley.sbp.Tree(null, "grammar", new edu.berkeley.sbp.Tree[] { new edu.berkeley.sbp.Tree(null, null, new edu.berkeley.sbp.Tree[] { new edu.berkeley.sbp.Tree(null, "=", new edu.berkeley.sbp.Tree[] { new edu.berkeley.sbp.Tree(null, null, new edu.berkeley.sbp.Tree[] { new edu.berkeley.sbp.Tree(null, "G", new edu.berkeley.sbp.Tree[] { }), new edu.berkeley.sbp.Tree(null, "r", new edu.berkeley.sbp.Tree[] { }), @@ -572,3 +584,15 @@ new edu.berkeley.sbp.Tree(null, "grammar", new edu.berkeley.sbp.Tree[] { new edu + + + + + + + + + + + + diff --git a/src/edu/berkeley/sbp/misc/RegressionTests.java b/src/edu/berkeley/sbp/misc/RegressionTests.java index 353866d..533cb89 100644 --- a/src/edu/berkeley/sbp/misc/RegressionTests.java +++ b/src/edu/berkeley/sbp/misc/RegressionTests.java @@ -11,10 +11,17 @@ import edu.berkeley.sbp.util.*; public class RegressionTests { public static boolean yes = false; + public static boolean graph = false; public static void main(String[] s) throws Exception { try { boolean profile = false; + if (s[0].equals("-graph")) { + graph = true; + String[] s2 = new String[s.length-1]; + System.arraycopy(s, 1, s2, 0, s2.length); + s = s2; + } if (s[0].equals("-profile")) { profile = true; String[] s2 = new String[s.length-1]; @@ -28,7 +35,7 @@ public class RegressionTests { System.err.println("parsing " + s[0]); Tree res = new CharParser(MetaGrammar.make()).parse(new FileInputStream(s[0])).expand1(); //System.out.println(mg); - Union meta = MetaGrammar.make(); + Union meta = MetaGrammar.make(res, "s"); System.err.println("parsing " + s[1]); SequenceInputStream sis = new SequenceInputStream(new FileInputStream(s[0]), new FileInputStream(s[1])); res = new CharParser(meta).parse(sis).expand1(); @@ -52,11 +59,6 @@ public class RegressionTests { System.exit(0); } System.err.println("expanding..."); - GraphViz gv = new GraphViz(); - r2.toGraphViz(gv); - FileOutputStream fox = new FileOutputStream("out.dot"); - gv.dump(fox); - fox.close(); TestCase[] expanded = (TestCase[])new TestCaseBuilder().walk(r2.expand1()); System.err.println("executing..."); @@ -91,27 +93,28 @@ public class RegressionTests { return ret; } public boolean execute() throws Exception { - if (jav) { - Forest tree = new CharParser(grammar).parse(new StringReader(input)); - FileOutputStream fos = new FileOutputStream("/Users/megacz/Desktop/out.dot"); - PrintWriter p = new PrintWriter(new OutputStreamWriter(fos)); - GraphViz gv = new GraphViz(); - tree.toGraphViz(gv); - gv.dump(p); - p.flush(); - p.close(); - return true; - } Forest res = null; ParseFailed pfe = null; + CharParser parser = new CharParser(grammar); + parser.helpgc = false; try { res = tib ? /*new CharParser(grammar).parse(new Tib(input))*/ null - : new CharParser(grammar).parse(new StringReader(input)); + : parser.parse(new StringReader(input)); } catch (ParseFailed pf) { pfe = pf; } //ystem.out.println("res=="+res); + if (graph) { + FileOutputStream fos = new FileOutputStream("out.dot"); + PrintWriter p = new PrintWriter(new OutputStreamWriter(fos)); + GraphViz gv = new GraphViz(); + res.toGraphViz(gv); + gv.dump(p); + p.flush(); + p.close(); + System.out.println(parser); + } Collection> results = res==null ? new HashSet>() : res.expand(false); System.out.print("\r"); if (results == null || (results.size() == 0 && (output!=null && output.length > 0))) { diff --git a/src/edu/berkeley/sbp/util/GraphViz.java b/src/edu/berkeley/sbp/util/GraphViz.java index d5f1bcf..b3132d7 100644 --- a/src/edu/berkeley/sbp/util/GraphViz.java +++ b/src/edu/berkeley/sbp/util/GraphViz.java @@ -11,18 +11,39 @@ public class GraphViz { IdentityHashMap ihm = new IdentityHashMap(); HashMap groups = new HashMap(); - public class Group { + public class Group extends Node { + private final int idx = master_idx++; + public boolean cluster = false; + public boolean primary = true; public Group() { } public void add(Node n) { groups.put(n, this); } + public String name() { return cluster?("cluster_"+idx):("subgraph_"+idx); } + public boolean simple() { return false; } + public void dump(PrintWriter pw, IdentityHashMap done) { + Group g = this; + if (done.get(g)!=null) return; + done.put(g,g); + pw.println(" subgraph "+name()+" { rank=same;\n"); + pw.println(" label=\""+StringUtil.escapify(label.toString(), "\\\"\r\n")+"\";\n"); + pw.println(" color="+g.color+";\n"); + pw.println(" shape="+g.shape+";\n"); + for(Node n : groups.keySet()) + if (groups.get(n)==g) + n.dump(pw, done); + pw.println(" }\n"); + } } private static int master_idx=0; + public class Node { private final int idx = master_idx++; public String label; public String comment; public boolean directed = false; public String color="black"; + public String fill="white"; + public String shape="ellipse"; public ArrayList edges = new ArrayList(); public ArrayList labels = new ArrayList(); public ArrayList inbound = new ArrayList(); @@ -62,7 +83,9 @@ public class GraphViz { if (n.inbound.size() > 1) { simple = false; break; } return simple; } - public void dump(PrintWriter pw) { + public void dump(PrintWriter pw, IdentityHashMap done) { + if (done.get(this)!=null) return; + done.put(this, this); if (inbound.size() > 0) { boolean good = false; for(Node n : inbound) @@ -113,6 +136,14 @@ public class GraphViz { return n; } + public Group createGroup(ToGraphViz o) { + Group n = (Group)ihm.get(o); + if (n!=null) return n; + n = new Group(); + ihm.put(o, n); + return n; + } + public static interface ToGraphViz { public Node toGraphViz(GraphViz gv); public boolean isTransparent(); @@ -126,19 +157,14 @@ public class GraphViz { public void dump(OutputStream os) { dump(new PrintWriter(new OutputStreamWriter(os))); } public void dump(PrintWriter pw) { IdentityHashMap done = new IdentityHashMap(); - pw.println("digraph G { rankdir=LR; ordering=out; \n"); - for(Group g : groups.values()) { - pw.println(" { rank=same;\n"); - for(Node n : groups.keySet()) - if (groups.get(n)==g) { - done.put(n,n); - n.dump(pw); - } - pw.println(" }\n"); - } + pw.println("digraph G { rankdir=LR; ordering=out; compound=true; \n"); + for(Group g : groups.values()) + if (g.primary) + g.dump(pw, done); for(Node n : ihm.values()) { if (done.get(n)!=null) continue; - n.dump(pw); + if (n instanceof Group) continue; + n.dump(pw, done); } for(Node n : ihm.values()) n.edges(pw); pw.println("}\n"); diff --git a/src/edu/berkeley/sbp/util/IntPairMap.java b/src/edu/berkeley/sbp/util/IntPairMap.java index db12004..a45c6c3 100644 --- a/src/edu/berkeley/sbp/util/IntPairMap.java +++ b/src/edu/berkeley/sbp/util/IntPairMap.java @@ -3,7 +3,7 @@ import java.util.*; // FEATURE: make this faster (plenty of ways: quadradic probing hash table is one) /** a sparse mapping from pairs of int's to V's */ -public final class IntPairMap { +public final class IntPairMap implements Iterable { private final HashMap hm = new HashMap(); @@ -16,4 +16,5 @@ public final class IntPairMap { public Iterable values() { return hm.values(); } public int size() { return hm.size(); } public void toArray(V[] v) { hm.values().toArray(v); } + public Iterator iterator() { return hm.values().iterator(); } } diff --git a/tests/loop.tc b/tests/loop.tc new file mode 100644 index 0000000..c7b6590 --- /dev/null +++ b/tests/loop.tc @@ -0,0 +1,23 @@ +testcase { + input "if (bar!) baz!;"; + output "IfThen:{id:{x:{b x:{a r}}} id:{x:{b x:{a z}}}}"; + + s = Expr ";" + Expr = IfThen + | IfThenElse + | id:: id "!" + id = [a-z] | x:: [a-z] id + IfThen = IfThen:: + "if" "(" Expr ")" + Expr + /ws + IfThenElse = IfThenElse:: + "if" "(" Expr ")" + NotIfThenExpr + "else" + Expr + /ws + NotIfThenExpr = (Expr & [a-z]+) + SpaceIfThen = (~[])*// !IfThen + ws = [\n ]** +} \ No newline at end of file diff --git a/tests/regression.tc b/tests/regression.tc index 73fe509..1c47dc4 100644 --- a/tests/regression.tc +++ b/tests/regression.tc @@ -389,3 +389,27 @@ testcase { c = ("c":: x "2" x "1")* x = [123] } + +testcase { + input "if (bar!) baz!;"; + output "IfThen:{id:{x:{b x:{a r}}} id:{x:{b x:{a z}}}}"; + + s = Expr ";" + Expr = IfThen + | IfThenElse + | id:: id "!" + id = [a-z] | x:: [a-z] id + IfThen = IfThen:: + "if" "(" Expr ")" + Expr + /ws + IfThenElse = IfThenElse:: + "if" "(" Expr ")" + NotIfThenExpr + "else" + Expr + /ws + NotIfThenExpr = (Expr & [a-z]+) + SpaceIfThen = (~[])*// !IfThen + ws = [\n ]** +} \ No newline at end of file -- 1.7.10.4