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 }