/*------------------------------------------------------------------------- EBNF Visualizer Copyright (c) 2005 Stefan Schoergenhumer, Markus Dopler supported by Hanspeter Moessenboeck, University of Linz This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -------------------------------------------------------------------------*/ using System; using System.Collections; using System.Drawing; public class Symbol { public static ArrayList terminals = new ArrayList(); public static ArrayList nonterminals = new ArrayList(); public int typ; // t, nt public string name; // symbol name public Graph graph; // nt: to first node of syntax graph public Symbol(int typ, string name) { if (name.Length == 2 && name[0] == '"') { Console.WriteLine("empty token not allowed"); name = "???"; } this.typ = typ; this.name = name; switch (typ) { case Node.t: terminals.Add(this); break; case Node.nt: nonterminals.Add(this); break; } } public static Symbol Find(string name) { foreach (Symbol s in terminals) if (s.name == name) return s; foreach (Symbol s in nonterminals) if (s.name == name) return s; return null; } public static void terminalToNt(string name) { foreach (Symbol s in terminals) { if (s.name == name) { nonterminals.Add(s); terminals.Remove(s); break; } } } } //--------------------------------------------------------------------- // Syntax graph (class Node, class Graph) //--------------------------------------------------------------------- public class Node { public static ArrayList nodes = new ArrayList(); public static string[] nTyp = {" ", "t ", "nt ", "eps ", "alt ", "iter", "opt ","reru"}; // constants for node kinds public const int t = 1; // terminal symbol public const int nt = 2; // nonterminal symbol public const int eps = 3; // empty public const int alt = 4; // alternative: | public const int iter = 5; // iteration: { } public const int opt = 6; // option: [ ] public const int rerun = 7; // the optimization of: a {a} or a {b a} public const int wrap = 8; // forces line break if found in the outer structure public int n; // node number public int typ; // t, nt, eps, alt, iter, opt, rerun public Node next; // to successor node public Node down; // alt: to next alternative public Node sub; // alt, iter, opt: to first node of substructure public bool up; // true: "next" leads to successor in enclosing structure public Symbol sym; // nt, t: symbol represented by this node public Node itergraph; // rerun: points to the b in "a {b a}", null if "a {a}" public bool firstLevel; // true if the Node is in the first Level public static bool trace=false; public Node(Symbol sym) { this.typ = sym.typ; this.sym = sym; n = nodes.Count; nodes.Add(this); } public Node(int typ, Node sub) { this.typ = typ; n = nodes.Count; nodes.Add(this); this.sub = sub; } //only for searching nt/t nodes public static Node Find(string name) { foreach (Node n in nodes) if (n.sym!=null && n.sym.name == name) return n; return null; } //can change the type of node from t to nt later on public static void terminalToNt(string name) { foreach (Node n in nodes) { if (n.sym!=null && n.sym.name == name) { n.typ=Node.nt; } } } //----------------- for printing ---------------------- static int Ptr(Node p, bool up) { if (p == null) return 0; else if (up) return -p.n; else return p.n; } public static void PrintNodes() { Console.WriteLine("Graph nodes:"); Console.WriteLine("(S...Starting nodes)"); Console.WriteLine("--------------------------------------------"); Console.WriteLine("S n type name next down sub "); Console.WriteLine("--------------------------------------------"); foreach (Node p in nodes) { bool nt=false; foreach (Symbol s in Symbol.nonterminals) { if (s.graph.l.n == p.n) { Console.Write("*"); nt=true; } } if(!nt) Console.Write(" "); Console.Write("{0,4} {1} ", p.n, nTyp[p.typ]); if (p.sym != null) Console.Write("{0,12} ", p.sym.name); else Console.Write(" "); Console.Write("{0,5} ", Ptr(p.next, p.up)); switch (p.typ) { case alt: case iter: case opt: case rerun: Console.Write("{0,5} {1,5}", Ptr(p.down, false), Ptr(p.sub, false)); break; case eps: Console.Write(" "); break; } Console.WriteLine(); } Console.WriteLine(); } //----------------- for drawing ---------------------- /**************default Settings**********************/ private static bool showBorders = false; // show the rectangles around the components private static int defaultComponentArcSize = 16; private static int defaultComponentGapWidth = 32; private static int defaultComponentGapHeight = 10; private static Font defaultCharFont = new Font("Times",12); private static int defaultArrowSize = 3; private static Pen defaultLinePen = new Pen(Color.Black,1); private static int defaultSymbolGapHeight = 0; private static Color defaultCharColor = Color.Black; /**********initialize variables with default settings***********/ private static int componentArcSize = defaultComponentArcSize; // size of the arcs private static int componentGapWidth = defaultComponentGapWidth; // gap between subcomponent size and actual size private static int componentGapHeight = defaultComponentGapHeight; // gap between subcomponent size and actual size private static Font charFont = defaultCharFont; // font of the t and nt symbols private static int arrowSize = defaultArrowSize; // size of the arrows private static Pen linePen = defaultLinePen; // color and thickness of the line private static int symbolGapHeight = defaultSymbolGapHeight; // gap between the line of the symbol and the font private static Color charColor = defaultCharColor; // fontColor of the T and NT symbols private static int fontHeight = (int)defaultCharFont.Height; // needed to make the gap between the symbol and and the font possible private static bool optimizeGraph = true; // enable optimizations? /*****************other variables needed for the drawing********/ public Size size = new Size(0,0); // the required size to draw the node public Size altSize = new Size(0,0); // the required size to draw a construct of alts or the size of the firstcomponent in the special rerun-node (itergraph!=null) public Size iterSize = new Size(0,0); // the size of the second component in the special rerun Node (itergraph!=null) public PointF posBegin = new PointF(0,0); // the point in the left above corner of the component public PointF posLine = new PointF(0,0); // the point of the line of the component public PointF posEnd = new PointF(0,0); // the point in the left down corner of the component private static Size symbolSize = new Size(1,1); // the total size of the current Rule private static int beginningXCoordinate = 50; // where the drawing starts (X) private static int beginningYCoordinate = 40; // where the drawing starts (Y) private static Graphics g = EbnfForm.BitmapGraphics; // the graphics object from the EBNFForm on witch the drawing takes place public static Font CharFont { set { charFont=value; fontHeight=(int)charFont.Height+symbolGapHeight; } get { return charFont; } } public static Color CharColor { set { charColor=value; } get { return charColor; } } public static int ArrowSize { set { arrowSize=value; } get { return arrowSize; } } public static bool OptimizeGraph { set { optimizeGraph=value; } get { return optimizeGraph; } } public static int SymbolGapHeight { set { symbolGapHeight=value; } get { return symbolGapHeight; } } public static int ComponentGapHeight { set { componentGapHeight=value; if(componentGapHeight/2+fontHeight/2maxW) maxW=a.size.Width; a=a.down; } if(n.sub.typ==iter && realHeight!=0) maxH+=(fontHeight+componentGapHeight)/2; maxW += 2*componentGapWidth; maxH += componentGapHeight; n.altSize.Height=maxH; n.altSize.Width=maxW; } if(n.typ==Node.iter&&realHeight!=0) { iterCompensation=(fontHeight+componentGapHeight)/2; } if(n.typ==Node.alt) { if(s.Height t,nt,eps is ok, go to next if(n1.typ==Node.opt|| n1.typ==Node.iter || n1.typ==Node.rerun) { if(!DeepCompare(n1.sub, n2.sub,false)) { if(Node.trace) Console.WriteLine("false: false in subelem of iter,opt or rerun"); return false; } if(n1.typ==Node.rerun && !DeepCompare(n1.itergraph, n2.itergraph, false)) { if(Node.trace) Console.WriteLine("false: itergraph of rerun doesn't match"); return false; } } else if(n1.typ==Node.alt) { Node a1=n1,a2=n2; while(a1!=null) { if(a2==null) { if(Node.trace) Console.WriteLine("false: false in subalt, second node null"); return false; } if(!DeepCompare(a1.sub,a2.sub,false)) { if(Node.trace) Console.WriteLine("false: false in subelem of subalt"); return false; } a1=a1.down; a2=a2.down; } if(a2!=null) { if(Node.trace) Console.WriteLine("false: second alt has more alternatives"); return false; } } if(n1.up) { if(!n2.up) { if(Node.trace) Console.WriteLine("false: second has not finished enclosing structure"); return false; } samelevel=false; } n1=n1.next; n2=n2.next; } if(n1==null && n2!=null) { if(Node.trace) Console.WriteLine("false: first enclosing substructure ended before second"); return false; } return true; } //calls all methods which optimize the graphs public static void Optimize() { foreach(Symbol s in Symbol.nonterminals) { Node.RemoveWrongLinebreaks(s.graph.l,null,s); if(optimizeGraph) Node.RemoveRedundancy(s.graph.l,null,s); //remove redundant iter/opts if(optimizeGraph) Node.RemoveEps(s.graph.l,null,s); //remove eps nodes and redundant eps nodes in alternatives if(optimizeGraph) Node.OptimizeIter(s.graph.l,null,s); } } //removes all unnecessary and wrong linebreaks (wrap-nodes) from the graph public static void RemoveWrongLinebreaks(Node n,Node parent, Symbol s) { bool samelevel=true; Node i=n; while(i!=null && samelevel) { if(i.typ==Node.wrap) { //if in outer structure, just remove multiple wraps if(parent==null) { while(i.next!=null && i.next.typ==Node.wrap) { i.next=i.next.next; } } //if in inner structure remove it else { //if \n is first element of substructure if(n==i) { //parent==null doesn't occur //if \n is the only subelement if(i.up ||i.next==null) { Node eps=new Node(Node.eps, null); parent.sub=eps; eps.up=i.up; eps.next=i.next; n=eps; } else { parent.sub=i.next; n=parent.sub; } } else { //if within substructure Node j=n; while(j.next!=i) j=j.next; j.next=i.next; j.up=i.up; } } } else if(i.typ==Node.opt || i.typ==Node.iter || i.typ==Node.rerun) RemoveWrongLinebreaks(i.sub,i,s); else if(i.typ==Node.alt) { Node a=i; while(a!=null) { RemoveWrongLinebreaks(a.sub,a,s); a=a.down; } } if(i.up) { samelevel=false; } i=i.next; } } private static void RemoveRedundancy(Node n, Node parent, Symbol s) { bool samelevel=true; //next node in same level? Node begin=n; while(n!=null && samelevel) { if(n.typ==Node.alt) { Node a=n; while(a!=null) { RemoveRedundancy(a.sub,a,s); a=a.down; } } else if(n.typ==Node.iter) { while((n.sub.typ==Node.iter || n.sub.typ==Node.opt) && n.sub.up) { //EbnfForm.WriteLine("Rendundant "+Node.nTyp[n.sub.typ]+" Node removed (iter)."); n.sub=n.sub.sub; Node i=n.sub; while(!i.up) { i=i.next; } i.next=n; } RemoveRedundancy(n.sub,n,s); } else if(n.typ==Node.opt) { bool containsIter=false; while((n.sub.typ==Node.opt && (n.sub.up || n.sub.next==null)) || (n.sub.typ==Node.iter && (n.sub.up || n.sub.next==null))) { //if(n.sub.typ==Node.opt || containsIter) EbnfForm.WriteLine("Rendundant "+Node.nTyp[n.sub.typ]+" Node removed (opt)."); if(n.sub.typ==Node.iter) containsIter=true; n.sub=n.sub.sub; } if(containsIter) { Node iter=new Node(Node.iter,n.sub); iter.next=n.next; if(n==begin) { if(parent==null) { s.graph.l=iter; } else { parent.sub=iter; } } else { Node j=begin; while(j.next!=n) { j=j.next; } j.next=iter; } n=iter; //set correct next pointer of last subelement of new iter Node i=iter.sub; while(i.next!=null && !i.up) i=i.next; i.next=iter; } RemoveRedundancy(n.sub,n,s); } if(n.up) samelevel=false; n=n.next; } } private static void RemoveEps(Node n, Node parent, Symbol s) { bool samelevel=true; //next node in same level? Node begin=n; while(n!=null && samelevel) { if(n.typ==Node.eps) { if(n==begin) { if(parent==null) { //if the graph only consists of an eps, let it live if(n.next!=null) { s.graph.l=n.next; begin=n.next; } } //else: at beginning of substructure not required (iter/opt/alt subnodes were already handled) } else { Node i=begin; while(i.next!=n) { i=i.next; } i.next=n.next; i.up=n.up; } } else if(n.typ==Node.iter || n.typ==Node.opt) { if(n.sub.typ==Node.eps && (n.sub.next==null || n.sub.up)) { if(n==begin) { if(parent==null) { //beginning of graph //if graph only consists of this iter/opt, then replace it with an eps node if(n.next==null) { Node eps=new Node(Node.eps, null); s.graph.l=eps; s.graph.r=eps; } else { //remove that node s.graph.l=n.next; begin=n.next; } } //else: at beginning of substructure not required (iter/opt/alt subnodes were already handled) } else { //within substructure Node i=begin; while(i.next!=n) { i=i.next; } if(n.up) i.up=true; i.next=n.next; } } else RemoveEps(n.sub,n,s); } else if(n.typ==Node.alt) { Node a=n; //count number of eps int numOfEps=0; while(a!=null) { //CheckSubAlts(a); if(a.sub.typ==Node.eps && (a.sub.next==null || a.sub.up)) numOfEps++; a=a.down; } a=n; while(numOfEps>1) { if(n!=a && a.sub.typ==Node.eps && (a.sub.next==null || a.sub.up)) { Node i=n; while(i.down!=a) { i=i.down; } i.down=a.down; numOfEps--; } a=a.down; } RemoveSameAlts(n); PutEpsAtBeginningOfAlt(begin,n,parent,s); //optimize subcomponents a=n; while(a!=null) { //if not the left eps node if(!(a.sub.typ==Node.eps && (a.sub.next==null || a.sub.up))) RemoveEps(a.sub,a,s); a=a.down; } } if(n.up) samelevel=false; n=n.next; } } //removes all empty iter/opt nodes in alternatives, as well as multiple eps nodes at the beginning: //they would bug a condition in RemoveEps private static void CheckSubAlts(Node alt) { //remove all empty iter/opts //make sure, that at least one eps Node will exist Node eps=new Node(Node.eps,null); eps.next=alt.sub; alt.sub=eps; Node i=alt.sub; bool samelevel=true; while(i!=null && samelevel) { //if empty iter/opt if((i.typ==Node.iter || i.typ==Node.opt) && i.sub.typ==Node.eps && (i.sub.next==null || i.sub.up)) { //case i==alt.sub not possible Node a=alt.sub; while(a.next!=i) a=a.next; a.next=i.next; } if(i.up) samelevel=false; i=i.next; } i=alt.sub; //remove multiple eps nodes at the beginning if(i.typ==Node.eps) { while(i.next!=null && !i.next.up && i.next.typ==Node.eps) { i.next=i.next.next; } } } private static void RemoveSameAlts(Node alt) { Node a=alt; while(a!=null) { Node i=a.down; while(i!=null) { if(DeepCompare(a.sub,i.sub,false)) { Node n=a; while(n.down!=i) n=n.down; n.down=i.down; } i=i.down; } a=a.down; } } private static void PutEpsAtBeginningOfAlt(Node n,Node alt,Node parent, Symbol s) { Node a=alt; bool containsEps=false; //determine if eps is contained while(a!=null) { //if eps node if(a.sub.typ==Node.eps && (a.sub.next==null || a.sub.up)) containsEps=true; a=a.down; } if(containsEps) { //remove eps node a=alt; while(a!=null) { //if eps node if(a.sub.typ==Node.eps && (a.sub.next==null || a.sub.up)) { //remove eps only if within alternatives if(a!=alt) { Node i=alt; while(i.down!=a) i=i.down; i.down=a.down; } break; //there can be only one eps in the alts because same nodes have already been removed } a=a.down; } //insert eps, if first alt isn't eps if(!(alt.sub.typ==Node.eps && (alt.sub.next==null || alt.sub.up))) { Node eps=new Node(Node.eps,null); eps.next=alt.next; eps.up=true; Node a1= new Node(Node.alt,eps); a1.down=alt; if(alt==n) { if(parent==null) s.graph.l=a1; else parent.sub=a1; } else { Node i=n; while (i.next!=alt) i=i.next; i.next=a1; } a1.next=alt.next; a1.up=alt.up; alt.next=null; } } } //optimizes enclosing structures and recursively its substructures private static void OptimizeIter(Node n, Node parent, Symbol s) { bool samelevel=true; //next node in same level? Node i=n; while(i!=null && samelevel) { if(i.typ==Node.opt) OptimizeIter(i.sub,i,s); else if(i.typ==Node.alt) { Node a=i; while(a!=null) { OptimizeIter(a.sub,a,s); a=a.down; } } else if(i.typ==Node.iter) { //first optimize the iter substructure OptimizeIter(i.sub,i,s); //while loop to DeepCompare from every node until the iter node Node j=n; bool matchFound=false; while(j!=i && !matchFound) { Node k=i.sub; bool samelevel2=true; while(k!=null && samelevel2 && !matchFound) { if(DeepCompare(j,k,true)) { //EbnfForm.WriteLine("Iter node optimized."); matchFound=true; //replace the iter node and the nodes before by the rerun node Node re= new Node(Node.rerun, k); if(j==n) { if(parent==null) { s.graph.l=re; n=re; } else { parent.sub=re; n=re; } } else { Node l=n; while(l.next!=j) l=l.next; l.next=re; } //if a {b a} isolate b if(k!=i.sub) { re.itergraph=i.sub; Node temp=re.itergraph; while(temp.next!=k) temp=temp.next; temp.next=null; } re.next=i.next; re.up=i.up; i=re; } if(k.up) samelevel2=false; k=k.next; } j=j.next; } } if(i.up) samelevel=false; i=i.next; } } } public class Graph { public Node l; // left end of graph = head public Node r; // right end of graph = list of nodes to be linked to successor graph public Size graphSize; public Graph() { l = null; r = null; } public Graph(Node left, Node right) { l = left; r = right; } public Graph(Node p) { l = p; r = p; } public static void MakeFirstAlt(Graph g) { g.l = new Node(Node.alt, g.l); g.l.next = g.r; g.r = g.l; } public static void MakeAlternative(Graph g1, Graph g2) { g2.l = new Node(Node.alt, g2.l); Node p = g1.l; while (p.down != null) p = p.down; p.down = g2.l; p = g1.r; while (p.next != null) p = p.next; p.next = g2.r; } public static void MakeSequence(Graph g1, Graph g2) { if(g1.l==null && g1.r==null) {/*case: g1 is empty */ g1.l=g2.l;g1.r=g2.r; } else { Node p = g1.r.next; g1.r.next = g2.l; // link head node while (p != null) { // link substructure Node q = p.next; p.next = g2.l; p.up = true; p = q; } g1.r = g2.r; } } public static void MakeIteration(Graph g) { g.l = new Node(Node.iter, g.l); Node p = g.r; g.r = g.l; while (p != null) { Node q = p.next; p.next = g.l; p.up = true; p = q; } } public static void MakeOption(Graph g) { g.l = new Node(Node.opt, g.l); g.l.next = g.r; g.r = g.l; } public static void Finish(Graph g) { Node p = g.r; while (p != null) { Node q = p.next; p.next = null; p = q; } } }