1 /**
2 Dynamic grammars:
3     - no CT parsing (this one will stay)
4     - no memoization (could be added)
5     - slightly less handy parameterized rules (could be better)
6     - no semantic actions (drat, this one is the worse)
7     - no calling from other grammars?
8 
9 Advantages:
10     - fully runtime configurable: change rules, add rules, delete rules.
11 */
12 module pegged.dynamic.grammar;
13 
14 import std.algorithm : startsWith;
15 import std.array;
16 import std.stdio;
17 
18 import pegged.peg;
19 import pegged.parser;
20 import pegged.dynamic.peg;
21 
22 struct ParameterizedRule
23 {
24     size_t numArgs;
25     Dynamic delegate(Dynamic[]) code;
26 
27     Dynamic opCall(D...)(D rules)
28     {
29         Dynamic[] args;
30         foreach(i,rule; rules)
31         {
32             static if (is(typeof(rule) == Dynamic))
33                 args ~= rule;
34             else
35                 args ~= rule(); // Dynamic delegate()
36         }
37 
38         if(rules.length == numArgs)
39         {
40             switch(numArgs)
41             {
42                 case 1:
43                     return code([args[0]]);
44                 case 2:
45                     return code([args[0], args[1]]);
46                 case 3:
47                     return code([args[0], args[1], args[2]]);
48                 case 4:
49                     return code([args[0], args[1], args[2], args[3]]);
50                 case 5:
51                     return code([args[0], args[1], args[2], args[3], args[4]]);
52                 case 6:
53                     return code([args[0], args[1], args[2], args[3], args[4], args[5]]);
54                 default:
55                     throw new Exception("Unimplemented parameterized rule with more than 6 arguments.");
56             }
57         }
58         else
59             throw new Exception( "ParameterizedRule called with "
60                                ~ to!string(rules.length)
61                                ~ " args. Waited for "
62                                ~ to!string(numArgs) ~ ".");
63         return code(args);
64     }
65 }
66 
67 ParameterizedRule parameterizedRule(size_t n, Dynamic delegate(Dynamic[] d) code)
68 {
69     ParameterizedRule pr;
70     pr.numArgs = n;
71     pr.code = code;
72     return pr;
73 }
74 
75 struct DynamicGrammar
76 {
77     string grammarName;
78     string startingRule;
79     Dynamic[string] rules;
80     ParameterizedRule[string] paramRules;
81 
82     ParseTree decimateTree(ParseTree p)
83     {
84         if(p.children.length == 0) return p;
85 
86         ParseTree[] filterChildren(ParseTree pt)
87         {
88             ParseTree[] result;
89             foreach(child; pt.children)
90             {
91                 if (  (child.name.startsWith(grammarName) && child.matches.length != 0)
92                    || !child.successful && child.children.length == 0)
93                 {
94                     child.children = filterChildren(child);
95                     result ~= child;
96                 }
97                 else if (child.name.startsWith("keep!(")) // 'keep' node are never discarded.
98                                                // They have only one child, the node to keep
99                 {
100                     result ~= child.children[0];
101                 }
102                 else // discard this node, but see if its children contain nodes to keep
103                 {
104                     result ~= filterChildren(child);
105                 }
106             }
107             return result;
108         }
109         p.children = filterChildren(p);
110         return p;
111     }
112 
113     ParseTree opCall(ParseTree p)
114     {
115         return decimateTree(rules[startingRule](p));
116         /+
117         result.children = [result];
118         result.name = grammarName;
119         return result;
120         +/
121     }
122 
123     ParseTree opCall(string input)
124     {
125         return decimateTree(rules[startingRule](ParseTree(``, false, [], input, 0, 0)));
126         /+
127         result.children = [result];
128         result.name = grammarName;
129         return result;
130         +/
131     }
132 
133     void opIndexAssign(D)(D code, string s)
134     {
135         rules[s]= named(code, grammarName ~ "." ~ s);
136     }
137 
138     Dynamic opIndex(string s)
139     {
140         return rules[s];
141     }
142 }
143 
144 // Helper to insert 'Spacing' before and after Primaries
145 ParseTree spaceArrow(ParseTree input)
146 {
147     ParseTree wrapInSpaces(ParseTree p)
148     {
149         ParseTree spacer =
150         ParseTree("Pegged.Prefix", true, null, null, 0,0, [
151             ParseTree("Pegged.Suffix", true, null, null, 0, 0, [
152                 ParseTree("Pegged.Primary", true, null, null, 0, 0, [
153                     ParseTree("Pegged.RhsName", true, null, null, 0,0, [
154                         ParseTree("Pegged.Identifier", true, ["Spacing"])
155                     ])
156                 ])
157             ])
158         ]);
159         ParseTree result = ParseTree("Pegged.WrapAround", true, p.matches, p.input, p.begin, p.end, p.children);
160         result.children = spacer ~ result.children ~ spacer;
161         return result;
162     }
163     return modify!( p => p.name == "Pegged.Primary",
164                     wrapInSpaces)(input);
165 }
166 
167 Dynamic makeRule(string def, Dynamic[string] context)
168 {
169     ParseTree p = Pegged.decimateTree(Pegged.Definition(def));
170     return makeRule(p, context);
171 }
172 
173 Dynamic makeRule(ParseTree def, Dynamic[string] context)
174 {
175     Dynamic code;
176 
177     Dynamic getDyn(string name)
178     {
179         if (name in context)
180             return context[name];
181         else
182             throw new Exception("Unknown name: " ~ name);
183     }
184 
185     Dynamic ruleFromTree(ParseTree p)
186     {
187         //writeln("rfT: ", p.name, " ", p.matches);
188         //Dynamic result;
189         switch(p.name)
190         {
191             case "Pegged.Expression":
192                 //if (p.children.length > 1) // OR expression
193                 //{
194                     Dynamic[] children;
195                     foreach(seq; p.children)
196                         children ~= ruleFromTree(seq);
197                     return distribute!(or)(children);
198                 //}
199                 //else // One child -> just a sequence, no need for a or!( , )
200                 //{
201                 //    return ruleFromTree(p.children[0]);
202                 //}
203                 //break;
204             case "Pegged.Sequence":
205                 //if (p.children.length > 1) // real sequence
206                 //{
207                     Dynamic[] children;
208                     foreach(seq; p.children)
209                         children ~= ruleFromTree(seq);
210                     return distribute!(pegged.dynamic.peg.and)(children);
211                 /+}
212                 else // One child -> just a Suffix, no need for a and!( , )
213                 {
214                     return ruleFromTree(p.children[0]);
215                 }
216                 +/
217             case "Pegged.Prefix":
218                 ParseTree temp;
219                 foreach_reverse(i,child; p.children[0..$-1]) // transforming a list into a linear tree
220                 {
221                     temp  = p.children[$-1];
222                     p.children[$-1] = child;
223                     p.children[$-1].children = [temp];
224                 }
225                 return ruleFromTree(p.children[$-1]);
226             case "Pegged.Suffix":
227                 foreach(child; p.children[1..$])
228                 {
229                     ParseTree temp = p.children[0];
230                     p.children[0] = child;
231                     p.children[0].children = [temp];
232                 }
233                 return ruleFromTree(p.children[0]);
234             case "Pegged.Primary":
235                 return ruleFromTree(p.children[0]);
236             case "Pegged.RhsName":
237                 return ruleFromTree(p.children[0]);
238             case "Pegged.Identifier":
239                 //writeln("Identifier: ", p.matches[0], " ");//, getDyn(p.matches[0])()(ParseTree()).name);
240                 return getDyn(p.matches[0]);
241             case "Pegged.Literal":
242                 //writeln("Literal: ", p.matches);
243                 if(p.matches.length == 3) // standard case
244                     return literal(p.matches[1]);
245                 else // only two children -> empty literal
246                     return eps();
247             case "Pegged.CharClass":
248                 if (p.children.length > 1)
249                 {
250                     Dynamic[] children;
251                     foreach(seq; p.children)
252                         children ~= ruleFromTree(seq);
253                     return distribute!(pegged.dynamic.peg.or)(children);
254                 }
255                 else // One child -> just a sequence, no need for a or!( , )
256                 {
257                     return ruleFromTree(p.children[0]);
258                 }
259                 //break;
260             case "Pegged.CharRange":
261                 /// Make the generation at the Char level: directly what is needed, be it `` or "" or whatever
262                 if (p.children.length > 1) // a-b range
263                 {
264                     return charRange(p.matches[0].front, p.matches[2].front);
265                 }
266                 else // lone char
267                 {
268                     //result = "pegged.peg.literal!(";
269                     string ch = p.matches[0];
270                     switch (ch)
271                     {
272                         case "\\[":
273                         case "\\]":
274                         case "\\-":
275                             return literal(ch[0..1]);
276                         case "\\\'":
277                             return literal("\'");
278                         case "\\`":
279                             return literal("`");
280                         case "\\":
281                         case "\\\\":
282                             return literal("\\");
283                         case "\"":
284                         case "\\\"":
285                             return literal("\"");
286                         case "\n":
287                         case "\r":
288                         case "\t":
289                             return literal(ch);
290                         default:
291                             return literal(ch);
292                     }
293                 }
294             case "Pegged.Char":
295                 string ch = p.matches[0];
296                 switch (ch)
297                 {
298                     case "\\[":
299                     case "\\]":
300                     case "\\-":
301 
302                     case "\\\'":
303                     case "\\\"":
304                     case "\\`":
305                     case "\\\\":
306                         return literal(ch[1..$]);
307                     case "\n":
308                     case "\r":
309                     case "\t":
310                         return literal(ch);
311                     default:
312                         return literal(ch);
313                 }
314                 //break;
315             case "Pegged.POS":
316                 return posLookahead(ruleFromTree(p.children[0]));
317             case "Pegged.NEG":
318                 return negLookahead(ruleFromTree(p.children[0]));
319             case "Pegged.FUSE":
320                 return fuse(ruleFromTree(p.children[0]));
321             case "Pegged.DISCARD":
322                 return discard(ruleFromTree(p.children[0]));
323             case "Pegged.KEEP":
324                 return keep(ruleFromTree(p.children[0]));
325             case "Pegged.DROP":
326                 return drop(ruleFromTree(p.children[0]));
327             case "Pegged.PROPAGATE":
328                 return propagate(ruleFromTree(p.children[0]));
329             case "Pegged.OPTION":
330                 return option(ruleFromTree(p.children[0]));
331             case "Pegged.ZEROORMORE":
332                 return zeroOrMore(ruleFromTree(p.children[0]));
333             case "Pegged.ONEORMORE":
334                 return oneOrMore(ruleFromTree(p.children[0]));
335             case "Pegged.Action":
336                 Dynamic result = ruleFromTree(p.children[0]);
337                 foreach(act; p.matches[1..$])
338                     result = action(result, getDyn(act));
339                 return result;
340             case "Pegged.ANY":
341                 return any();
342             case "Pegged.WrapAround":
343                 return wrapAround( ruleFromTree(p.children[0])
344                                         , ruleFromTree(p.children[1])
345                                         , ruleFromTree(p.children[2]));
346             default:
347                 throw new Exception("Bad tree: " ~ p.toString());
348         }
349     }
350 
351     switch(def.children[1].children[0].name)
352     {
353         case "Pegged.LEFTARROW":
354             code = ruleFromTree(def.children[2]);
355             break;
356         case "Pegged.FUSEARROW":
357             code = fuse(ruleFromTree(def.children[2]));
358             break;
359         case "Pegged.DISCARDARROW":
360             code = discard(ruleFromTree(def.children[2]));
361             break;
362         case "Pegged.KEEPARROW":
363             code = keep(ruleFromTree(def.children[2]));
364             break;
365         case "Pegged.DROPARROW":
366             code = drop(ruleFromTree(def.children[2]));
367             break;
368         case "Pegged.PROPAGATEARROW":
369             code = propagate(ruleFromTree(def.children[2]));
370             break;
371         case "Pegged.SPACEARROW":
372             ParseTree modified = spaceArrow(def.children[2]);
373             code = ruleFromTree(modified);
374             break;
375         case "Pegged.ACTIONARROW":
376             Dynamic actionResult = ruleFromTree(def.children[2]);
377             foreach(act; def.children[1].matches[1..$])
378                 actionResult = action(actionResult, getDyn(act));
379             code = actionResult;
380             break;
381         default:
382             throw new Exception("Unknow arrow: " ~ def.children[1].children[0].name);
383             //break;
384     }
385 
386     return named(code, def.matches[0]);
387 }
388 
389 DynamicGrammar grammar(string definition, Dynamic[string] context = null)
390 {
391     //writeln("Entering dyn gram");
392     ParseTree defAsParseTree = Pegged(definition);
393     //writeln(defAsParseTree);
394     if (!defAsParseTree.successful)
395     {
396         // To work around a strange bug with ParseTree printing at compile time
397         throw new Exception("Bad grammar input: " ~ defAsParseTree.toString(""));
398     }
399 
400     DynamicGrammar gram;
401     foreach(name, rule; context)
402     {
403         gram.rules[name] = rule;
404     }
405 
406     ParseTree p = defAsParseTree.children[0];
407 
408     string grammarName = p.children[0].matches[0];
409     string shortGrammarName = p.children[0].matches[0];
410     gram.grammarName = shortGrammarName;
411 
412     // Predefined spacing
413     gram.rules["Spacing"] = discard(zeroOrMore(or(literal(" "), literal("\t"), literal("\n"), literal("\r"))));
414 
415     ParseTree[] definitions = p.children[1 .. $];
416 
417     foreach(i,def; definitions)
418     {
419         gram[def.matches[0]] = fail();
420     }
421 
422     foreach(i,def; definitions)
423     {
424         string shortName = def.matches[0];
425 
426         if (i == 0)
427             gram.startingRule = shortName;
428         // prepending the global grammar name, to get a qualified-name rule 'Gram.Rule'
429         def.matches[0] = shortGrammarName ~ "." ~ def.matches[0];
430         gram.rules[shortName] = makeRule(def, gram.rules);
431     }
432 
433     return gram;
434 }
435 
436 Dynamic distribute(alias fun)(Dynamic[] args)
437 {
438     //mixin(makeSwitch(40));
439     switch(args.length)
440     {
441         case 1:
442             return fun(args[0]);
443         case 2:
444             return fun(args[0], args[1]);
445         case 3:
446             return fun(args[0], args[1], args[2]);
447         case 4:
448             return fun(args[0], args[1], args[2], args[3]);
449         case 5:
450             return fun(args[0], args[1], args[2], args[3], args[4]);
451         case 6:
452             return fun(args[0], args[1], args[2], args[3], args[4], args[5]);
453         default:
454             return fun(fun(args[0], args[1], args[2], args[3], args[4], args[5]), distribute!fun(args[6..$]));
455     }
456 }