1 module ctpg.parsers; 2 3 import std.algorithm : count; 4 import std.array : empty; 5 import std.conv : to; 6 import std.exception : assertThrown; 7 import std.traits : Unqual, CommonType, isArray, isSomeString, isSomeChar; 8 import std.range : ElementEncodingType, isRandomAccessRange; 9 10 import ctpg : parse; 11 12 import ctpg.for_unittest; 13 import ctpg.parse_result : ParseResult, getParseResultType; 14 import ctpg.parser_kind : ParserKind, ParserKinds; 15 import ctpg.input : Input; 16 import ctpg.caller : Caller; 17 import ctpg.none : None; 18 import ctpg.macro_ : MAKE_RESULT; 19 import ctpg.unsupported_input_type_exception : UnsupportedInputTypeException; 20 21 import combinators = ctpg.combinators; 22 23 import selflinoin : makeCompilationErrorMessage; 24 25 import compile_time_unittest : enableCompileTimeUnittest; 26 mixin enableCompileTimeUnittest; 27 28 29 template success() 30 { 31 template build(alias kind, SrcType) 32 { 33 mixin MAKE_RESULT!q{ None }; 34 35 Result parse(Input!SrcType input, in Caller caller) 36 { 37 Result result; 38 39 result.match = true; 40 result.nextInput = input; 41 42 return result; 43 } 44 } 45 } 46 47 debug(ctpg) unittest 48 { 49 foreach(conv; convs) foreach(kind; ParserKinds) 50 { 51 with(parse!(success!(), kind)(conv!"hoge")) 52 { 53 assert(match == true); 54 assert(nextInput.source == conv!"hoge"); 55 assert(nextInput.position == 0); 56 assert(nextInput.line == 0); 57 } 58 59 with(parse!(success!(), kind)(conv!"")) 60 { 61 assert(match == true); 62 assert(nextInput.source == conv!""); 63 assert(nextInput.position == 0); 64 assert(nextInput.line == 0); 65 } 66 } 67 } 68 69 70 template eof() 71 { 72 template build(alias kind, SrcType) 73 { 74 mixin MAKE_RESULT!q{ None }; 75 76 Result parse(Input!SrcType input, in Caller caller) 77 { 78 Result result; 79 80 if(input.source.empty) 81 { 82 result.match = true; 83 result.nextInput = input; 84 } 85 else 86 { 87 static if(kind.hasError) 88 { 89 result.error.msg = "EOF expected"; 90 result.error.position = input.position; 91 } 92 } 93 94 return result; 95 } 96 } 97 } 98 99 debug(ctpg) unittest 100 { 101 foreach(conv; convs) foreach(kind; ParserKinds) 102 { 103 with(parse!(eof!(), kind)(conv!"")) 104 { 105 assert(match == true); 106 assert(nextInput.source == conv!""); 107 assert(nextInput.position == 0); 108 assert(nextInput.line == 0); 109 } 110 111 with(parse!(eof!(), kind)(conv!"hoge")) 112 { 113 assert(match == false); 114 static if(kind.hasError) assert(error.msg == "EOF expected"); 115 static if(kind.hasError) assert(error.position == 0); 116 } 117 118 assertThrown!UnsupportedInputTypeException(parse!(string_!"hoge", kind)([0, 1])); 119 } 120 } 121 122 123 template spaces() 124 { 125 template build(alias kind, SrcType) 126 { 127 mixin MAKE_RESULT!q{ None }; 128 129 Result parse(Input!SrcType input, in Caller caller) 130 { 131 return combinators.none! 132 ( 133 combinators.more0! 134 ( 135 combinators.choice! 136 ( 137 string_!" ", 138 string_!"\n", 139 string_!"\t", 140 string_!"\r", 141 string_!"\f" 142 ) 143 ) 144 ).build!(kind, SrcType).parse(input, caller); 145 } 146 } 147 } 148 149 alias skip = spaces; 150 151 debug(ctpg) unittest 152 { 153 foreach(conv; convs) foreach(kind; ParserKinds) 154 { 155 with(parse!(spaces!())(conv!" \n\t\r\f")) 156 { 157 assert(match == true); 158 assert(nextInput.source == conv!""); 159 } 160 161 with(parse!(spaces!())(conv!" \n\ta\r\f")) 162 { 163 assert(match == true); 164 assert(nextInput.source == conv!"a\r\f"); 165 } 166 167 with(parse!(spaces!())(conv!"")) 168 { 169 assert(match == true); 170 assert(nextInput.source == conv!""); 171 } 172 } 173 } 174 175 176 template string_(alias string str) 177 { 178 template build(alias kind, SrcType) 179 { 180 static if(is(Unqual!(ElementEncodingType!SrcType) == char)) enum adaptedStr = str; 181 static if(is(Unqual!(ElementEncodingType!SrcType) == wchar)) enum adaptedStr = cast(wstring)str; 182 static if(is(Unqual!(ElementEncodingType!SrcType) == dchar)) enum adaptedStr = cast(dstring)str; 183 184 enum lines = str.count('\n'); 185 enum errorMsg = "'" ~ str ~ "' expected"; 186 187 mixin MAKE_RESULT!q{ string }; 188 189 static if(isSomeString!SrcType) 190 { 191 Result parse(Input!SrcType input, in Caller caller) 192 { 193 Result result; 194 195 if(input.source.length >= adaptedStr.length && input.source[0..adaptedStr.length] == adaptedStr) 196 { 197 result.match = true; 198 result.nextInput.source = input.source[str.length..$]; 199 result.nextInput.position = input.position + adaptedStr.length; 200 result.nextInput.line = input.line + lines; 201 202 static if(kind.hasValue) 203 { 204 result.value = str; 205 } 206 } 207 else 208 { 209 static if(kind.hasError) 210 { 211 result.error.msg = errorMsg; 212 result.error.position = input.position; 213 } 214 } 215 216 return result; 217 } 218 } 219 else static if(isSomeChar!(ElementEncodingType!SrcType)) 220 { 221 Result parse(Input!SrcType input, in Caller caller) 222 { 223 Result result; 224 225 foreach(c; adaptedStr) 226 { 227 if(input.source.empty || c != input.source.front) 228 { 229 static if(kind.hasError) 230 { 231 result.error.msg = errorMsg; 232 result.error.position = input.position; 233 } 234 235 return result; 236 } 237 else 238 { 239 input.source.popFront(); 240 } 241 } 242 243 result.match = true; 244 result.nextInput.source = input.source; 245 result.nextInput.position = input.position + adaptedStr.length; 246 result.nextInput.line = input.line + lines; 247 248 static if(kind.hasValue) 249 { 250 result.value = str; 251 } 252 253 return result; 254 } 255 } 256 else 257 { 258 Result parse(Input!SrcType input, in Caller caller) 259 { 260 throw new UnsupportedInputTypeException("Input should be some string or a range whose elemement type is some char"); 261 } 262 } 263 } 264 } 265 266 debug(ctpg) unittest 267 { 268 foreach(conv; convs) foreach(kind; ParserKinds) 269 { 270 with(parse!(string_!"hoge", kind)(conv!"hogehoge")) 271 { 272 assert(match == true); 273 static if(kind.hasValue) assert(value == "hoge"); 274 assert(nextInput.source == conv!"hoge"); 275 assert(nextInput.position == 4); 276 assert(nextInput.line == 0); 277 } 278 279 with(parse!(string_!"\n\nhoge", kind)(conv!"\n\nhogehoge")) 280 { 281 assert(match == true); 282 static if(kind.hasValue) assert(value == "\n\nhoge"); 283 assert(nextInput.source == conv!"hoge"); 284 assert(nextInput.position == 6); 285 assert(nextInput.line == 2); 286 } 287 288 with(parse!(string_!"hoge", kind)(conv!"fuga")) 289 { 290 assert(match == false); 291 static if(kind.hasError) assert(error.msg == "'hoge' expected"); 292 static if(kind.hasError) assert(error.position == 0); 293 } 294 295 assertThrown!UnsupportedInputTypeException(parse!(string_!"hoge", kind)([0, 1])); 296 } 297 } 298 299 300 template charRange(dchar begin, dchar end, size_t line = 0, string file = "") 301 { 302 static if(begin > end) 303 { 304 pragma(msg, makeCompilationErrorMessage("Error: Invalid char range", file, line)); 305 static assert(false); 306 } 307 308 template build(alias kind, SrcType) 309 { 310 dchar decode(ref SrcType input, ref size_t advance) 311 { 312 dchar result; 313 314 static if(isArray!SrcType || isRandomAccessRange!SrcType) 315 { 316 static if(is(Unqual!(ElementEncodingType!SrcType) == char)) 317 { 318 if(!(input[0] & 0b_1000_0000)) 319 { 320 result = input[0]; 321 input = input[1..$]; 322 advance = 1; 323 } 324 else if(!(input[0] & 0b_0010_0000)) 325 { 326 result = ((input[0] & 0b_0001_1111) << 6) | (input[1] & 0b_0011_1111); 327 input = input[2..$]; 328 advance = 2; 329 } 330 else if(!(input[0] & 0b_0001_0000)) 331 { 332 result = ((input[0] & 0b_0000_1111) << 12) | ((input[1] & 0b_0011_1111) << 6) | (input[2] & 0b_0011_1111); 333 input = input[3..$]; 334 advance = 3; 335 } 336 else 337 { 338 result = ((input[0] & 0b_0000_0111) << 18) | ((input[1] & 0b_0011_1111) << 12) | ((input[2] & 0b_0011_1111) << 6) | (input[3] & 0b_0011_1111); 339 input = input[4..$]; 340 advance = 4; 341 } 342 } 343 else static if(is(Unqual!(ElementEncodingType!SrcType) == wchar)) 344 { 345 if(input[0] <= 0xD7FF || (0xE000 <= input[0] && input[0] < 0xFFFF)) 346 { 347 result = input[0]; 348 input = input[1..$]; 349 advance = 1; 350 } 351 else 352 { 353 result = (input[0] & 0b_0000_0011_1111_1111) * 0x400 + (input[1] & 0b_0000_0011_1111_1111) + 0x10000; 354 input = input[2..$]; 355 advance = 2; 356 } 357 } 358 else static if(is(Unqual!(ElementEncodingType!SrcType) == dchar)) 359 { 360 result = input[0]; 361 input = input[1..$]; 362 advance = 1; 363 } 364 } 365 else 366 { 367 static if(is(Unqual!(ElementEncodingType!SrcType) == char)) 368 { 369 if(!(input.front & 0b_1000_0000)) 370 { 371 result = input.front; 372 input.popFront(); 373 advance = 1; 374 } 375 else if(!(input.front & 0b_0010_0000)) 376 { 377 result = input.front & 0b_0001_1111; 378 result <<= 6; 379 input.popFront(); 380 result |= input.front & 0b_0011_1111; 381 input.popFront(); 382 advance = 2; 383 } 384 else if(!(input.front & 0b_0001_0000)) 385 { 386 result = input.front & 0b_0000_1111; 387 result <<= 6; 388 input.popFront(); 389 result |= input.front & 0b_0011_1111; 390 result <<= 6; 391 input.popFront(); 392 result |= input.front & 0b_0011_1111; 393 input.popFront; 394 advance = 3; 395 } 396 else 397 { 398 result = input.front & 0b_0000_0111; 399 result <<= 6; 400 input.popFront(); 401 result |= input.front & 0b_0011_1111; 402 result <<= 6; 403 input.popFront(); 404 result |= input.front & 0b_0011_1111; 405 result <<= 6; 406 input.popFront(); 407 result |= input.front & 0b_0011_1111; 408 input.popFront(); 409 advance = 4; 410 } 411 } 412 else static if(is(Unqual!(ElementEncodingType!SrcType) == wchar)) 413 { 414 if(input.front <= 0xD7FF || (0xE000 <= input.front && input.front < 0xFFFF)) 415 { 416 result = input.front; 417 input.popFront(); 418 advance = 1; 419 } 420 else 421 { 422 result = (input.front & 0b_0000_0011_1111_1111) * 0x400; 423 input.popFront(); 424 result += (input.front & 0b_0000_0011_1111_1111) + 0x10000; 425 input.popFront(); 426 advance = 2; 427 } 428 } 429 else static if(is(Unqual!(ElementEncodingType!SrcType) == dchar)) 430 { 431 result = input.front; 432 input.popFront(); 433 advance = 1; 434 } 435 } 436 437 return result; 438 } 439 440 static if(begin == dchar.min && end == dchar.max) 441 { 442 enum errorMsg = "any char expected"; 443 } 444 else 445 { 446 enum errorMsg = "'" ~ begin.to!string() ~ " ~ " ~ end.to!string() ~ "' expected"; 447 } 448 449 mixin MAKE_RESULT!q{ dchar }; 450 451 static if(isSomeChar!(ElementEncodingType!SrcType)) 452 { 453 Result parse(Input!SrcType input, in Caller caller) 454 { 455 Result result; 456 457 if(input.source.empty) 458 { 459 static if(kind.hasError) 460 { 461 result.error.msg = errorMsg; 462 result.error.position = input.position; 463 } 464 } 465 else 466 { 467 size_t advance; 468 dchar c = decode(input.source, advance); 469 470 if(begin <= c && c <= end) 471 { 472 result.match = true; 473 result.nextInput.source = input.source; 474 result.nextInput.position = input.position + advance; 475 result.nextInput.line = input.line + (c == '\n' ? 1 : 0); 476 477 static if(kind.hasValue) 478 { 479 result.value = c; 480 } 481 } 482 else 483 { 484 static if(kind.hasError) 485 { 486 result.error.msg = errorMsg; 487 result.error.position = input.position; 488 } 489 } 490 } 491 492 return result; 493 } 494 } 495 else 496 { 497 Result parse(Input!SrcType input, in Caller caller) 498 { 499 throw new UnsupportedInputTypeException("Input should be some string or a range whose elemement type is some char"); 500 } 501 } 502 } 503 } 504 505 template anyChar(size_t line = 0, string file = "") 506 { 507 alias anyChar = charRange!(dchar.min, dchar.max, line, file); 508 } 509 510 debug(ctpg) unittest 511 { 512 foreach(conv; convs) foreach(kind; ParserKinds) 513 { 514 with(parse!(charRange!('a', 'z'), kind)(conv!"hoge")) 515 { 516 assert(match == true); 517 static if(kind.hasValue) assert(value == 'h'); 518 assert(nextInput.source == conv!"oge"); 519 assert(nextInput.position == 1); 520 assert(nextInput.line == 0); 521 } 522 523 with(parse!(charRange!(dchar.min, dchar.max), kind)(conv!"\nhoge")) 524 { 525 assert(match == true); 526 static if(kind.hasValue) assert(value == '\n'); 527 assert(nextInput.source == conv!"hoge"); 528 assert(nextInput.position == 1); 529 assert(nextInput.line == 1); 530 } 531 532 with(parse!(charRange!('a', 'z'), kind)(conv!"")) 533 { 534 assert(match == false); 535 static if(kind.hasError) assert(error.msg == "'a ~ z' expected"); 536 static if(kind.hasError) assert(error.position == 0); 537 } 538 539 with(parse!(charRange!('a', 'z'), kind)(conv!"鬱")) 540 { 541 assert(match == false); 542 static if(kind.hasError) assert(error.msg == "'a ~ z' expected"); 543 static if(kind.hasError) assert(error.position == 0); 544 } 545 546 assertThrown!UnsupportedInputTypeException(parse!(charRange!('a', 'z'), kind)([1, 0])); 547 548 debug(ctpgCompilesTest) 549 { 550 static assert(!__traits(compiles, parse!(charRange!('z', 'a'), kind)(conv!""))); 551 } 552 } 553 } 554 555 template getCallerLine() 556 { 557 template build(alias kind, SrcType) 558 { 559 mixin MAKE_RESULT!q{ size_t }; 560 561 Result parse(Input!SrcType input, in Caller caller) 562 { 563 Result result; 564 565 result.match = true; 566 result.nextInput = input; 567 568 static if(kind.hasValue) 569 { 570 result.value = caller.line; 571 } 572 573 return result; 574 } 575 } 576 } 577 578 debug(ctpg) unittest 579 { 580 foreach(conv; convs) foreach(kind; ParserKinds) 581 { 582 with(parse!(getCallerLine!(), kind)(conv!"")) 583 { 584 assert(match == true); 585 assert(nextInput.source == conv!""); 586 assert(nextInput.position == 0); 587 assert(nextInput.line == 0); 588 static if(kind.hasValue) assert(value == __LINE__ - 6); 589 } 590 } 591 } 592 593 594 template getCallerFile() 595 { 596 template build(alias kind, SrcType) 597 { 598 mixin MAKE_RESULT!q{ string }; 599 600 Result parse(Input!SrcType input, in Caller caller) 601 { 602 Result result; 603 604 result.match = true; 605 result.nextInput = input; 606 607 static if(kind.hasValue) 608 { 609 result.value = caller.file; 610 } 611 612 return result; 613 } 614 } 615 } 616 617 debug(ctpg) unittest 618 { 619 foreach(conv; convs) foreach(kind; ParserKinds) 620 { 621 with(parse!(getCallerFile!(), kind)(conv!"")) 622 { 623 assert(match == true); 624 assert(nextInput.source == conv!""); 625 assert(nextInput.position == 0); 626 assert(nextInput.line == 0); 627 static if(kind.hasValue) assert(value == __FILE__); 628 } 629 } 630 } 631 632 633 template getLine() 634 { 635 template build(alias kind, SrcType) 636 { 637 mixin MAKE_RESULT!q{ size_t }; 638 639 Result parse(Input!SrcType input, in Caller caller) 640 { 641 Result result; 642 643 result.match = true; 644 result.nextInput = input; 645 646 static if(kind.hasValue) 647 { 648 result.value = input.line; 649 } 650 651 return result; 652 } 653 } 654 } 655 656 debug(ctpg) unittest 657 { 658 foreach(conv; convs) foreach(kind; ParserKinds) 659 { 660 with(parse!(getLine!(), kind)(conv!"")) 661 { 662 assert(match == true); 663 assert(nextInput.source == conv!""); 664 assert(nextInput.position == 0); 665 assert(nextInput.line == 0); 666 static if(kind.hasValue) assert(value == 0); 667 } 668 } 669 }