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 }