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