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