1 module pegged.dynamic.peg;
2 
3 import std.array: join;
4 import std.conv: to;
5 
6 import std.stdio;
7 
8 import pegged.peg;
9 
10 alias ParseTree delegate(ParseTree) Dynamic;
11 
12 string getName(D)(D rule)
13 {
14     return callDynamic(rule, ParseTree()).name;
15 }
16 
17 ParseTree callDynamic(D)(D d, string s)
18 {
19     static if (is(typeof(d) : ParseTree delegate(ParseTree)) || is(typeof(d) : ParseTree function(ParseTree)))
20         return d(ParseTree("",false,[], s));
21     else static if (is(typeof(d) : ParseTree delegate(ParseTree) delegate()) || is(typeof(d) : ParseTree function(ParseTree) delegate()))
22         return d()(ParseTree("",false,[], s));
23     else static if (is(typeof(d) : ParseTree delegate(string)) || is(typeof(d) : ParseTree function(string)))
24         return d(s);
25     else static if (is(typeof(d) : ParseTree delegate(string) delegate()) || is(typeof(d) : ParseTree function(string) delegate()))
26         return d()(s);
27     else
28         static assert(false, "Bad callDynamic, with type " ~ D.stringof);
29 }
30 
31 ParseTree callDynamic(D)(D d, ParseTree p)
32 {
33     static if (is(typeof(d) : ParseTree delegate(ParseTree)) || is(typeof(d) : ParseTree function(ParseTree)))
34         return d(p);
35     else static if (is(typeof(d) : ParseTree delegate(ParseTree) delegate()) || is(typeof(d) : ParseTree function(ParseTree) delegate()))
36         return d()(p);
37     else static if (is(typeof(d) : ParseTree delegate(string)) || is(typeof(d) : ParseTree function(string)))
38         return d(p.input[p.end..$]);
39     else static if (is(typeof(d) : ParseTree delegate(string) delegate()) || is(typeof(d) : ParseTree function(string) delegate()))
40         return d()(p.input[p.end..$]);
41     else
42         static assert(false, "Bad callDynamic, with type " ~ D.stringof);
43 }
44 
45 Dynamic fail()
46 {
47     return (ParseTree p)
48     {
49         return ParseTree("fail", false, ["fail"], p.input, p.end, p.end);
50     };
51 }
52 
53 Dynamic eoi()
54 {
55     return (ParseTree p)
56     {
57         if (p.end == p.input.length)
58             return ParseTree("eoi", true, [], p.input, p.end, p.end);
59         else
60             return ParseTree("eoi", false, ["end of input"], p.input, p.end, p.end);
61     };
62 }
63 
64 Dynamic eps()
65 {
66     return (ParseTree p)
67     {
68         return ParseTree("eps", true, [""], p.input, p.end, p.end);
69     };
70 }
71 
72 Dynamic any()
73 {
74     return(ParseTree p)
75     {
76         if (p.end < p.input.length)
77             return ParseTree("any", true, [p.input[p.end..p.end+1]], p.input, p.end, p.end+1);
78         else
79             return ParseTree("any", false, ["any char"], p.input, p.end, p.end);
80     };
81 }
82 
83 Dynamic literal(string s)
84 {
85     return (ParseTree p)
86     {
87         if (p.end+s.length <= p.input.length && p.input[p.end..p.end+s.length] == s)
88             return ParseTree("literal!(\"" ~ s ~ "\")", true, [s], p.input, p.end, p.end+s.length);
89         else
90             return ParseTree("literal!(\"" ~ s ~ "\")", false, [`"` ~ s ~ `"`], p.input, p.end, p.end);
91 
92     };
93 }
94 
95 Dynamic charRange(dchar begin, dchar end)
96 {
97     return (ParseTree p)
98     {
99         string longName = "a char between '"~to!string(begin)~"' and '"~to!string(end)~"'";
100         if (p.end < p.input.length && p.input[p.end] >= begin && p.input[p.end] <= end)
101             return ParseTree("charRange!(" ~ to!string(begin) ~ ", " ~ to!string(end) ~ ")", true, [p.input[p.end..p.end+1]], p.input, p.end, p.end+1);
102         else
103             return ParseTree("charRange!(" ~ to!string(begin) ~ ", " ~ to!string(end) ~ ")", false, [longName], p.input, p.end, p.end);
104     };
105 
106 }
107 
108 Dynamic wrapAround(B, M, A)(B before, M middle, A after)
109 {
110     return(ParseTree p)
111     {
112         ParseTree temp = callDynamic(before,p);
113         if (!temp.successful)
114             return temp;
115 
116         ParseTree result = callDynamic(middle,temp);
117         if (!result.successful)
118             return result;
119         result.begin = temp.begin;
120 
121         temp = callDynamic(after,result);
122         if (!temp.successful)
123             return temp;
124 
125         result.end = temp.end;
126         return result;
127     };
128 }
129 
130 Dynamic zeroOrMore(D)(D d)
131 {
132     return (ParseTree p)
133     {
134         string name = "zeroOrMore!(" ~ getName(d) ~ ")";
135         ParseTree result = ParseTree(name, true, [], p.input, p.end, p.end);
136         ParseTree temp = callDynamic(d,result);
137         while(temp.successful
138             && (temp.begin < temp.end // To avoid infinite loops on epsilon-matching rules
139             || temp.name.startsWith("discard!(")))
140         {
141             result.matches ~= temp.matches;
142             result.children ~= temp;
143             result.end = temp.end;
144             temp = callDynamic(d, result);
145         }
146         result.successful = true;
147         return result;
148     };
149 }
150 
151 Dynamic oneOrMore(D)(D d)
152 {
153     return(ParseTree p)
154     {
155         string name = "oneOrMore!(" ~ getName(d) ~ ")";
156         ParseTree result = ParseTree(name, false, [], p.input, p.end, p.end);
157         ParseTree temp = callDynamic(d, result);
158 
159         if (!temp.successful)
160         {
161             result.matches = temp.matches;
162             result.children = [temp];
163             result.end = temp.end;
164         }
165         else
166         {
167             while(  temp.successful
168                 && (temp.begin < temp.end // To avoid infinite loops on epsilon-matching rules
169             || temp.name.startsWith("discard!(")))
170             {
171                 result.matches ~= temp.matches;
172                 result.children ~= temp;
173                 result.end = temp.end;
174                 temp = callDynamic(d, result);
175             }
176             result.successful = true;
177         }
178         return result;
179     };
180 }
181 
182 Dynamic option(D)(D d)
183 {
184     return (ParseTree p)
185     {
186         string name = "option!(" ~ getName(d) ~ ")";
187         ParseTree result = callDynamic(d, p);
188         if (result.successful)
189             return ParseTree(name, true, result.matches, result.input, result.begin, result.end, [result]);
190         else
191             return ParseTree(name, true, [], p.input, p.end, p.end, null);
192     };
193 }
194 
195 Dynamic and(T...)(T rules) if (T.length)
196 {
197     return (ParseTree p)
198     {
199         bool keepNode(ParseTree node)
200         {
201             return    node.name.startsWith("keep!(")
202                 || (  !node.name.startsWith("discard!(")
203                    //&& !node.name.startsWith("drop!(")
204                    && node.matches !is null
205                    //&& node.begin != node.end
206                    );
207         }
208 
209 
210         string name = "and!(" ~ ")";
211 
212         ParseTree result = ParseTree(name, false, [], p.input, p.end, p.end, []);
213 
214         foreach(i,r; rules)
215         {
216             ParseTree temp = callDynamic(r, result);
217 
218             result.end = temp.end;
219             if (temp.successful)
220             {
221                 if (keepNode(temp))
222                 {
223                     result.matches ~= temp.matches;
224                     if (temp.name.startsWith("drop!("))
225                     {}
226                     else if (temp.name.startsWith("propagate!("))
227                         result.children ~= temp.children;
228                     else
229                         result.children ~= temp;
230                 }
231             }
232             else
233             {
234                 result.children ~= temp;// add the failed node, to indicate which failed
235                 if (temp.matches.length > 0)
236                     result.matches ~= temp.matches[$-1];
237                 return result; // and end the parsing attempt right there
238             }
239         }
240         result.successful = true;
241         return result;
242     };
243 }
244 
245 Dynamic or(T...)(T rules)
246 {
247     return (ParseTree p)
248     {
249         // error-management
250         ParseTree longestFail = ParseTree("or", false, [], p.input, p.end, 0);
251         string[] errorStrings;
252         size_t errorStringChars;
253         string orErrorString;
254 
255         ParseTree[rules.length] results;
256         string[rules.length] names;
257         size_t[rules.length] failedLength;
258         size_t maxFailedLength;
259 
260         // Real 'or' loop
261         foreach(i,r; rules)
262         {
263             ParseTree temp = callDynamic(r, p);
264             if (temp.successful)
265             {
266 				temp.children = [temp];
267                 temp.name = "or";
268                 return temp;
269             }
270             else
271             {
272                 enum errName = " (" ~")";
273                 failedLength[i] = temp.end;
274                 if (temp.end >= longestFail.end)
275                 {
276                     maxFailedLength = temp.end;
277                     longestFail = temp;
278                     names[i] = errName;
279                     results[i] = temp;
280 
281                     if (temp.end == longestFail.end)
282                         errorStringChars += temp.matches[$-1].length + errName.length + 4;
283                     else
284                         errorStringChars = temp.matches[$-1].length + errName.length + 4;
285                 }
286                 // Else, this error parsed less input than another one: we discard it.
287             }
288         }
289 
290 
291         // All subrules failed, we will take the longest match as the result
292         // If more than one node failed at the same (farthest) position, we concatenate their error messages
293 
294 
295         char[] errString;// = new char[](errorStringChars);
296         errString.length = errorStringChars;
297         uint start = 0;
298         foreach(i; 0..rules.length)
299         {
300             if (failedLength[i] == maxFailedLength)
301             {
302                 auto temp = results[i];
303                 auto len = temp.matches[$-1].length;
304                 auto nlen = names[i].length;
305                 errString[start .. start+len] = temp.matches[$-1][];
306                 errString[start+len .. start+len+names[i].length] = names[i][];
307                 errString[start+len+nlen .. start+len+nlen+4] = " or ";
308                 start += len + names[i].length + 4;
309             }
310         }
311         orErrorString = cast(string)(errString[0..$-4]);
312 
313         longestFail.matches = longestFail.matches[0..$-1]  // discarding longestFail error message
314                             ~ [orErrorString];             // and replacing it by the new, concatenated one.
315         longestFail.name = "or";
316         longestFail.begin = p.end;
317         return longestFail;
318     };
319 }
320 
321 Dynamic posLookahead(D)(D d)
322 {
323     return (ParseTree p)
324     {
325         string name = "posLookahead!(" ~ getName(d) ~ ")";
326         ParseTree temp = callDynamic(d,p);
327         if (temp.successful)
328             return ParseTree(name, temp.successful, [], p.input, p.end, p.end);
329         else
330             return ParseTree(name, temp.successful, [temp.matches[$-1]], p.input, p.end, p.end);
331     };
332 }
333 
334 Dynamic negLookahead(D)(D d)
335 {
336     return (ParseTree p)
337     {
338         string name = "negLookahead!(" ~ getName(d) ~ ")";
339         ParseTree temp = callDynamic(d,p);
340         if (temp.successful)
341             return ParseTree(name, false, ["anything but \"" ~ p.input[temp.begin..temp.end] ~ "\""], p.input, p.end, p.end);
342         else
343             return ParseTree(name, true, [], p.input, p.end, p.end);
344     };
345 }
346 
347 Dynamic named(D)(D d, string name)
348 {
349     return (ParseTree p)
350     {
351         ParseTree result = callDynamic(d,p);
352         result.name = name;
353         return result;
354     };
355 }
356 
357 Dynamic action(D, A)(D d, A act)
358 {
359     return (ParseTree p)
360     {
361         return callDynamic(act, callDynamic(d,p));
362     };
363 }
364 
365 Dynamic fuse(D)(D d)
366 {
367     return(ParseTree p)
368     {
369         p = callDynamic(d,p);
370         if (p.successful)
371         {
372             if (p.matches.length != 0)
373                 p.matches = [std.array.join(p.matches)];
374 
375             p.children = null; // also discard children
376         }
377         return p;
378     };
379 }
380 
381 Dynamic discardChildren(D)(D d)
382 {
383     return (ParseTree p)
384     {
385         p = callDynamic(d,p);
386         p.children = null;
387         return p;
388     };
389 }
390 
391 Dynamic discardMatches(D)(D d)
392 {
393     return (ParseTree p)
394     {
395         p = callDynamic(d,p);
396         if (p.successful)
397             p.matches = null;
398         return p;
399     };
400 }
401 
402 Dynamic discard(D)(D d)
403 {
404     return (ParseTree p)
405     {
406         ParseTree result = callDynamic(d,p);
407         result.name = "discard!(" ~ getName(d) ~ ")";
408         //result.begin = result.end;
409         result.children = null;
410         if (result.successful)
411             result.matches = null;//to keep error messages, if any
412 
413         return result;
414     };
415 }
416 
417 Dynamic drop(D)(D d)
418 {
419     return (ParseTree p)
420     {
421         ParseTree result = callDynamic(d,p);
422         result.children = null;
423         if (result.successful)
424             result.name = "drop!(" ~ getName(d) ~ ")";
425         return result;
426     };
427 }
428 
429 Dynamic propagate(D)(D d)
430 {
431     return (ParseTree p)
432     {
433         ParseTree result = callDynamic(d,p);
434         if (result.successful)
435             result.name = "propagate!(" ~ getName(d) ~ ")";
436         return result;
437     };
438 }
439 
440 Dynamic keep(D)(D d)
441 {
442     return (ParseTree p)
443     {
444         ParseTree result = callDynamic(d,p);
445         if (result.successful)
446         {
447             result.children = [result];
448             result.name = "keep!(" ~ getName(d) ~ ")";
449         }
450         return result;
451     };
452 }