1 module gamma.grammar.GrammarBuilder;
2 
3 import gamma.grammar.hyper.GeneratedNonterminal;
4 import gamma.grammar.Alternative;
5 import gamma.grammar.Grammar;
6 import gamma.grammar.Nonterminal;
7 import gamma.grammar.Rule;
8 import gamma.grammar.Symbol;
9 import gamma.grammar.SymbolNode;
10 import gamma.grammar.Terminal;
11 import std.range;
12 
13 version (unittest) import gamma.grammar.Node;
14 version (unittest) import gamma.util.Position;
15 
16 public struct GrammarBuilder
17 {
18     private Nonterminal[string] nonterminalMap;
19 
20     private Nonterminal[] nonterminals;
21 
22     private Terminal[string] terminalMap;
23 
24     private Terminal[] terminals;
25 
26     private Alternative[][] alternativesMap;
27 
28     private bool[Nonterminal] undefinedNonterminals;
29 
30     public Nonterminal buildNonterminal(string representation)
31     {
32         Nonterminal nonterminal = this.nonterminalMap.get(representation, null);
33 
34         if (nonterminal is null)
35         {
36             const index = this.nonterminals.length;
37 
38             nonterminal = new Nonterminal(representation, index);
39             this.nonterminalMap[representation] = nonterminal;
40             this.nonterminals ~= nonterminal;
41             this.alternativesMap ~= null;
42 
43             this.undefinedNonterminals[nonterminal] = true;
44         }
45         return nonterminal;
46     }
47 
48     /**
49      * TODO: ?
50      */
51     public GeneratedNonterminal buildGeneratedNonterminal()
52     {
53         import std.exception : enforce;
54         import std.format : format;
55 
56         const index = this.nonterminals.length;
57         auto nonterminal = new GeneratedNonterminal(index);
58         Nonterminal userNont = this.nonterminalMap.get(nonterminal.toString, null);
59 
60         enforce(userNont is null,
61                 format!"generated nonterminal name already defined by the user: %s"(nonterminal));
62 
63         this.nonterminalMap[nonterminal.toString] = nonterminal;
64         this.nonterminals ~= nonterminal;
65         this.alternativesMap ~= null;
66 
67         this.undefinedNonterminals[nonterminal] = true;
68 
69         return nonterminal;
70     }
71 
72     public Terminal buildTerminal(string representation)
73     {
74         Terminal terminal = this.terminalMap.get(representation, null);
75 
76         if (terminal is null)
77         {
78             const index = this.terminals.length;
79 
80             terminal = new Terminal(representation, index);
81             this.terminalMap[representation] = terminal;
82             this.terminals ~= terminal;
83         }
84         return terminal;
85     }
86 
87     public void add(Alternative alternative)
88     in(cast(Nonterminal) alternative.lhs.symbol)
89     {
90         Nonterminal lhs = cast(Nonterminal) alternative.lhs.symbol;
91         const index = lhs.index;
92 
93         this.undefinedNonterminals.remove(lhs);
94 
95         this.alternativesMap[index] ~= alternative;
96     }
97 
98     /**
99      * Returns true if and only if for all nonterminals N on the right hand side
100      * exists at least one alternative where the nonterminal N is on the left hand side.
101      *
102      * @return true if the grammar is well defined; false otherwise
103      */
104     public bool grammarIsWellDefined()
105     {
106         // TODO: hyper grammar contains undefined nonterminals,
107         // because a complete rule is added for any EBNF expression
108         return this.undefinedNonterminals.empty || true;
109     }
110 
111     @("build well-formed grammar")
112     unittest
113     {
114         with (TestGrammarBuilder())
115         {
116             rule("A: B");
117             rule("B:");
118 
119             assert(grammarIsWellDefined);
120         }
121     }
122 
123     /**
124      * Marks all positions for which the grammar is not well defined.
125      * If the grammar is well defined no positions are marked.
126      */
127     public void markErrors()
128     {
129         import std.format : format;
130 
131         if (this.undefinedNonterminals.empty)
132             return;
133         foreach (alternatives; alternativesMap)
134         {
135             foreach (alternative; alternatives)
136             {
137                 foreach (node; alternative.rhs)
138                 {
139                     if (cast(SymbolNode) node) // FIXME: else
140                     {
141                         Symbol symbol = (cast(SymbolNode) node).symbol;
142 
143                         if (cast(Nonterminal) symbol && cast(Nonterminal) symbol in this.undefinedNonterminals)
144                         {
145                             node.position.markError(format!"%s is undefined (not on left hand side)"(symbol));
146                         }
147                     }
148                 }
149             }
150         }
151     }
152 
153     @("mark position as having an error")
154     unittest
155     {
156         class MarkedPosition : Position
157         {
158             bool errorHasBeenMarked = false;
159 
160             public void markError(string message)
161             {
162                 errorHasBeenMarked = true;
163             }
164         }
165 
166         with (GrammarBuilder())
167         {
168             Position position;
169             MarkedPosition errorPos = new MarkedPosition;
170             Nonterminal A = buildNonterminal("A");
171             Nonterminal B = buildNonterminal("B");
172             Node[] rhs = [new SymbolNode(B, errorPos)];
173 
174             add(new Alternative(new SymbolNode(A, position), rhs , position));
175 
176             markErrors;
177 
178             assert(errorPos.errorHasBeenMarked);
179         }
180     }
181 
182     /**
183      * Returns the collected grammar rules with the given nonterminal as start symbol.
184      * Returns null if the grammar is not well defined.
185      *
186      * @param startSymbol
187      *            the nonterminal which is used as start symbol for the returned grammar
188      * @return the collected grammar
189      */
190     public Grammar getGrammar(Nonterminal startSymbol = null)
191     {
192         if (!grammarIsWellDefined)
193             return null;
194 
195         Rule[] rules;
196 
197         foreach (alternatives; alternativesMap)
198             if (!alternatives.empty)
199                 rules ~= new Rule(alternatives);
200         return new Grammar(this.nonterminals, this.terminals, rules, startSymbol);
201     }
202 }
203 
204 version (unittest):
205 
206 import std.algorithm;
207 
208 struct TestGrammarBuilder
209 {
210     public GrammarBuilder builder;
211 
212     private Nonterminal startSymbol;
213 
214     alias builder this;
215 
216     public Grammar grammar()
217     in (startSymbol !is null)
218     {
219         return getGrammar(startSymbol);
220     }
221 
222     /**
223      * Rule:
224      *     Nonterminal ":" { Nonterminal | terminal } { "|" { Nonterminal | terminal } }.
225      */
226     public void rule(string line)
227     {
228         import gamma.grammar.Node : Node;
229         import std.array : split;
230         import std..string : strip;
231 
232         Position position;
233 
234         if (auto result = line.findSplit(":"))
235         {
236             auto lhs = symbolNode(result[0].strip);
237 
238             if (startSymbol is null)
239                 startSymbol = cast(Nonterminal) lhs.symbol;
240 
241             if (result[2].empty)
242                 add(new Alternative(lhs, [], position));
243             foreach (parts; result[2].split("|"))
244             {
245                 auto rhs = parts.split
246                     .map!strip
247                     .map!(representation => cast(Node) symbolNode(representation))
248                     .array;
249 
250                 add(new Alternative(lhs, rhs, position));
251             }
252         }
253     }
254 
255     private SymbolNode symbolNode(string representation)
256     out (symbolNode; symbolNode !is null)
257     {
258         Position position;
259 
260         return new SymbolNode(symbol(representation), position);
261     }
262 
263     public Symbol symbol(string representation)
264     out (symbol; symbol !is null)
265     {
266         import std.uni;
267 
268         if (representation.front.isUpper)
269             return buildNonterminal(representation);
270         else
271             return buildTerminal(representation);
272     }
273 }