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 }