1 /* 2 * Copyright (c) 2014 Nonki Takahashi. All rights reserved. 3 * 4 * History: 5 * 0.2 2014-01-22 Created. 6 */ 7 8 /** 9 * @fileOverview SGFParser - SGF Parser Object 10 * @version 0.2 11 * @author EBNFParser written by Nonki Takahashi 12 */ 13 14 /** 15 * Constant for convert move alphabet to number 16 * @constant 17 */ 18 const ALPHA = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRWTUVWXYZ"; 19 20 /** 21 * SGF Parser Object 22 * @class Represents SGF Parser Object 23 * @this {SGFParser} 24 * @param {String} src source to parser 25 * @param {Rec} rec record object 26 * @property {String} buf source buffer 27 * @property {Number} ptr source buffer pointer 28 * @property {Object} gapFree array of gap free id 29 * @property {String} name name of the parser 30 * @property {String} ver version of the parser 31 * @property {Boolean} ignoreCase true if ignore case 32 * @since 0.2 33 */ 34 SGFParser = function(src, rec) { 35 this.rec = rec; 36 // inherit the methods of class Lex 37 Lex.call(this, src); 38 }; 39 SGFParser.prototype = new Lex(); 40 41 /** 42 * Collection = GameTree, {GameTree}. 43 * @return {String} code generated if matched, null if not matched 44 * @since 0.2 45 */ 46 SGFParser.prototype.Collection = function() { 47 var match = []; 48 var save = this.ptr; 49 var n = -1; 50 this.sp(); 51 match[++n] = this.GameTree(); 52 if (match[n]) { 53 this.sp(); 54 while (match[n]) { 55 this.sp(); 56 match[++n] = this.GameTree(); 57 } 58 match[++n] = true; 59 } 60 if (!match[n]) { 61 this.ptr = save; 62 return null; 63 } 64 return this.run(n, match, "Collection"); 65 }; 66 67 /** 68 * GameTree = '(', Sequence, {GameTree}, ')'. 69 * @return {String} code generated if matched, null if not matched 70 * @since 0.2 71 */ 72 SGFParser.prototype.GameTree = function() { 73 var match = []; 74 var save = this.ptr; 75 var n = -1; 76 this.sp(); 77 match[++n] = this.text('('); 78 if (match[n]) { 79 this.sp(); 80 match[++n] = this.Sequence(); 81 } 82 if (match[n]) { 83 this.sp(); 84 while (match[n]) { 85 this.sp(); 86 match[++n] = this.GameTree(); 87 } 88 match[++n] = true; 89 } 90 if (match[n]) { 91 this.sp(); 92 match[++n] = this.text(')'); 93 } 94 if (!match[n]) { 95 this.ptr = save; 96 return null; 97 } 98 return this.run(n, match, "GameTree"); 99 }; 100 101 /** 102 * Sequence = Node, {Node}. 103 * @return {String} code generated if matched, null if not matched 104 * @since 0.2 105 */ 106 SGFParser.prototype.Sequence = function() { 107 var match = []; 108 var save = this.ptr; 109 var n = -1; 110 this.sp(); 111 match[++n] = this.Node(); 112 if (match[n]) { 113 this.sp(); 114 while (match[n]) { 115 this.sp(); 116 match[++n] = this.Node(); 117 } 118 match[++n] = true; 119 } 120 if (!match[n]) { 121 this.ptr = save; 122 return null; 123 } 124 return this.run(n, match, "Sequence"); 125 }; 126 127 /** 128 * Node = ';', {Property}. 129 * @return {String} code generated if matched, null if not matched 130 * @since 0.2 131 */ 132 SGFParser.prototype.Node = function() { 133 var match = []; 134 var save = this.ptr; 135 var n = -1; 136 this.sp(); 137 match[++n] = this.text(';'); 138 if (match[n]) { 139 this.sp(); 140 while (match[n]) { 141 this.sp(); 142 match[++n] = this.Property(); 143 } 144 match[++n] = true; 145 } 146 if (!match[n]) { 147 this.ptr = save; 148 return null; 149 } 150 return this.run(n, match, "Node"); 151 }; 152 153 /** 154 * Property = Game | Size | Player | Handy | Komi | BW | Other. 155 * @return {String} code generated if matched, null if not matched 156 * @since 0.2 157 */ 158 SGFParser.prototype.Property = function() { 159 var match = []; 160 var save = this.ptr; 161 var n = -1; 162 this.sp(); 163 match[++n] = this.Game(); 164 if (!match[n]) { 165 n--; 166 this.sp(); 167 match[++n] = this.Size(); 168 } 169 if (!match[n]) { 170 n--; 171 this.sp(); 172 match[++n] = this.Player(); 173 } 174 if (!match[n]) { 175 n--; 176 this.sp(); 177 match[++n] = this.Handy(); 178 } 179 if (!match[n]) { 180 n--; 181 this.sp(); 182 match[++n] = this.Komi(); 183 } 184 if (!match[n]) { 185 n--; 186 this.sp(); 187 match[++n] = this.BW(); 188 } 189 if (!match[n]) { 190 n--; 191 this.sp(); 192 match[++n] = this.Other(); 193 } 194 if (!match[n]) { 195 this.ptr = save; 196 return null; 197 } 198 return this.run(n, match, "Property"); 199 }; 200 201 /** 202 * Game = 'GM[1]'. 203 * @return {String} code generated if matched, null if not matched 204 * @since 0.2 205 */ 206 SGFParser.prototype.Game = function() { 207 var match = []; 208 var save = this.ptr; 209 var n = -1; 210 this.sp(); 211 match[++n] = this.text('GM[1]'); 212 if (!match[n]) { 213 this.ptr = save; 214 return null; 215 } 216 return this.run(n, match, "Game"); 217 }; 218 219 /** 220 * Size = 'SZ[', Number, ']'. 221 * @return {String} code generated if matched, null if not matched 222 * @since 0.2 223 */ 224 SGFParser.prototype.Size = function() { 225 var match = []; 226 var save = this.ptr; 227 var n = -1; 228 this.sp(); 229 match[++n] = this.text('SZ['); 230 if (match[n]) { 231 this.sp(); 232 match[++n] = this.Number(); 233 } 234 if (match[n]) { 235 this.sp(); 236 match[++n] = this.text(']'); 237 } 238 if (!match[n]) { 239 this.ptr = save; 240 return null; 241 } 242 return this.run(n, match, "Size"); 243 }; 244 245 /** 246 * Player = ('PB'|'PW'), '[', SimpleText, ']'. 247 * @return {String} code generated if matched, null if not matched 248 * @since 0.2 249 */ 250 SGFParser.prototype.Player = function() { 251 var match = []; 252 var save = this.ptr; 253 var n = -1; 254 this.sp(); 255 this.sp(); 256 match[++n] = this.text('PB'); 257 if (!match[n]) { 258 n--; 259 this.sp(); 260 match[++n] = this.text('PW'); 261 } 262 if (match[n]) { 263 this.sp(); 264 match[++n] = this.text('['); 265 } 266 if (match[n]) { 267 this.sp(); 268 match[++n] = this.SimpleText(); 269 } 270 if (match[n]) { 271 this.sp(); 272 match[++n] = this.text(']'); 273 } 274 if (!match[n]) { 275 this.ptr = save; 276 return null; 277 } 278 return this.run(n, match, "Player"); 279 }; 280 281 /** 282 * Handy = 'HA[', Number, ']'. 283 * @return {String} code generated if matched, null if not matched 284 * @since 0.2 285 */ 286 SGFParser.prototype.Handy = function() { 287 var match = []; 288 var save = this.ptr; 289 var n = -1; 290 this.sp(); 291 match[++n] = this.text('HA['); 292 if (match[n]) { 293 this.sp(); 294 match[++n] = this.Number(); 295 } 296 if (match[n]) { 297 this.sp(); 298 match[++n] = this.text(']'); 299 } 300 if (!match[n]) { 301 this.ptr = save; 302 return null; 303 } 304 return this.run(n, match, "Handy"); 305 }; 306 307 /** 308 * Komi = 'KM[', Real, ']'. 309 * @return {String} code generated if matched, null if not matched 310 * @since 0.2 311 */ 312 SGFParser.prototype.Komi = function() { 313 var match = []; 314 var save = this.ptr; 315 var n = -1; 316 this.sp(); 317 match[++n] = this.text('KM['); 318 if (match[n]) { 319 this.sp(); 320 match[++n] = this.Real(); 321 } 322 if (match[n]) { 323 this.sp(); 324 match[++n] = this.text(']'); 325 } 326 if (!match[n]) { 327 this.ptr = save; 328 return null; 329 } 330 return this.run(n, match, "Komi"); 331 }; 332 333 /** 334 * BW = Color, '[', [Move], ']'. 335 * @return {String} code generated if matched, null if not matched 336 * @since 0.2 337 */ 338 SGFParser.prototype.BW = function() { 339 var match = []; 340 var save = this.ptr; 341 var n = -1; 342 this.sp(); 343 match[++n] = this.Color(); 344 if (match[n]) { 345 this.sp(); 346 match[++n] = this.text('['); 347 } 348 if (match[n]) { 349 this.sp(); 350 this.sp(); 351 match[++n] = this.Move(); 352 match[++n] = true; 353 } 354 if (match[n]) { 355 this.sp(); 356 match[++n] = this.text(']'); 357 } 358 if (!match[n]) { 359 this.ptr = save; 360 return null; 361 } 362 return this.run(n, match, "BW"); 363 }; 364 365 /** 366 * Other = PropIdent, PropValue, {PropValue}. 367 * @return {String} code generated if matched, null if not matched 368 * @since 0.2 369 */ 370 SGFParser.prototype.Other = function() { 371 var match = []; 372 var save = this.ptr; 373 var n = -1; 374 this.sp(); 375 match[++n] = this.PropIdent(); 376 if (match[n]) { 377 this.sp(); 378 match[++n] = this.PropValue(); 379 } 380 if (match[n]) { 381 this.sp(); 382 while (match[n]) { 383 this.sp(); 384 match[++n] = this.PropValue(); 385 } 386 match[++n] = true; 387 } 388 if (!match[n]) { 389 this.ptr = save; 390 return null; 391 } 392 return this.run(n, match, "Other"); 393 }; 394 395 /** 396 * PropIdent = upper, {upper}. 397 * @return {String} code generated if matched, null if not matched 398 * @since 0.2 399 */ 400 SGFParser.prototype.PropIdent = function() { 401 var match = []; 402 var save = this.ptr; 403 var n = -1; 404 this.sp(); 405 match[++n] = this.upper(); 406 if (match[n]) { 407 while (match[n]) { 408 match[++n] = this.upper(); 409 } 410 match[++n] = true; 411 } 412 if (!match[n]) { 413 this.ptr = save; 414 return null; 415 } 416 return this.run(n, match, "PropIdent"); 417 }; 418 419 /** 420 * PropValue = '[', CValueType, ']'. 421 * @return {String} code generated if matched, null if not matched 422 * @since 0.2 423 */ 424 SGFParser.prototype.PropValue = function() { 425 var match = []; 426 var save = this.ptr; 427 var n = -1; 428 this.sp(); 429 match[++n] = this.text('['); 430 if (match[n]) { 431 this.sp(); 432 match[++n] = this.CValueType(); 433 } 434 if (match[n]) { 435 this.sp(); 436 match[++n] = this.text(']'); 437 } 438 if (!match[n]) { 439 this.ptr = save; 440 return null; 441 } 442 return this.run(n, match, "PropValue"); 443 }; 444 445 /** 446 * CValueType = ValueType | Compose. 447 * @return {String} code generated if matched, null if not matched 448 * @since 0.2 449 */ 450 SGFParser.prototype.CValueType = function() { 451 var match = []; 452 var save = this.ptr; 453 var n = -1; 454 this.sp(); 455 match[++n] = this.ValueType(); 456 if (!match[n]) { 457 n--; 458 this.sp(); 459 match[++n] = this.Compose(); 460 } 461 if (!match[n]) { 462 this.ptr = save; 463 return null; 464 } 465 return this.run(n, match, "CValueType"); 466 }; 467 468 /** 469 * ValueType = Text | None | Number | Real | Double | Color | SimpleText | Move. 470 * @return {String} code generated if matched, null if not matched 471 * @since 0.2 472 */ 473 SGFParser.prototype.ValueType = function() { 474 var match = []; 475 var save = this.ptr; 476 var n = -1; 477 this.sp(); 478 match[++n] = this.Text(); 479 if (!match[n]) { 480 n--; 481 this.sp(); 482 match[++n] = this.None(); 483 } 484 if (!match[n]) { 485 n--; 486 this.sp(); 487 match[++n] = this.Number(); 488 } 489 if (!match[n]) { 490 n--; 491 this.sp(); 492 match[++n] = this.Real(); 493 } 494 if (!match[n]) { 495 n--; 496 this.sp(); 497 match[++n] = this.Double(); 498 } 499 if (!match[n]) { 500 n--; 501 this.sp(); 502 match[++n] = this.Color(); 503 } 504 if (!match[n]) { 505 n--; 506 this.sp(); 507 match[++n] = this.SimpleText(); 508 } 509 if (!match[n]) { 510 n--; 511 this.sp(); 512 match[++n] = this.Move(); 513 } 514 if (!match[n]) { 515 this.ptr = save; 516 return null; 517 } 518 return this.run(n, match, "ValueType"); 519 }; 520 521 /** 522 * None = [sp]. 523 * @return {String} code generated if matched, null if not matched 524 * @since 0.2 525 */ 526 SGFParser.prototype.None = function() { 527 var match = []; 528 var save = this.ptr; 529 var n = -1; 530 this.sp(); 531 this.sp(); 532 match[++n] = this.sp(); 533 match[++n] = true; 534 if (!match[n]) { 535 this.ptr = save; 536 return null; 537 } 538 return this.run(n, match, "None"); 539 }; 540 541 /** 542 * Number = ['+' | '-'], digit, {digit}. 543 * @return {String} code generated if matched, null if not matched 544 * @since 0.2 545 */ 546 SGFParser.prototype.Number = function() { 547 var match = []; 548 var save = this.ptr; 549 var n = -1; 550 this.sp(); 551 match[++n] = this.text('+'); 552 if (!match[n]) { 553 n--; 554 match[++n] = this.text('-'); 555 } 556 match[++n] = true; 557 if (match[n]) { 558 match[++n] = this.digit(); 559 } 560 if (match[n]) { 561 while (match[n]) { 562 match[++n] = this.digit(); 563 } 564 match[++n] = true; 565 } 566 if (!match[n]) { 567 this.ptr = save; 568 return null; 569 } 570 return this.run(n, match, "Number"); 571 }; 572 573 /** 574 * Real = Number, ['.', digit, {digit}]. 575 * @return {String} code generated if matched, null if not matched 576 * @since 0.2 577 */ 578 SGFParser.prototype.Real = function() { 579 var match = []; 580 var save = this.ptr; 581 var n = -1; 582 this.sp(); 583 match[++n] = this.Number(); 584 if (match[n]) { 585 match[++n] = this.text('.'); 586 if (match[n]) { 587 match[++n] = this.digit(); 588 } 589 if (match[n]) { 590 while (match[n]) { 591 match[++n] = this.digit(); 592 } 593 match[++n] = true; 594 } 595 match[++n] = true; 596 } 597 if (!match[n]) { 598 this.ptr = save; 599 return null; 600 } 601 return this.run(n, match, "Real"); 602 }; 603 604 /** 605 * Double = '1' | '2'. 606 * @return {String} code generated if matched, null if not matched 607 * @since 0.2 608 */ 609 SGFParser.prototype.Double = function() { 610 var match = []; 611 var save = this.ptr; 612 var n = -1; 613 this.sp(); 614 match[++n] = this.text('1'); 615 if (!match[n]) { 616 n--; 617 this.sp(); 618 match[++n] = this.text('2'); 619 } 620 if (!match[n]) { 621 this.ptr = save; 622 return null; 623 } 624 return this.run(n, match, "Double"); 625 }; 626 627 /** 628 * Color = 'B' | 'W'. 629 * @return {String} code generated if matched, null if not matched 630 * @since 0.2 631 */ 632 SGFParser.prototype.Color = function() { 633 var match = []; 634 var save = this.ptr; 635 var n = -1; 636 this.sp(); 637 match[++n] = this.text('B'); 638 if (!match[n]) { 639 n--; 640 this.sp(); 641 match[++n] = this.text('W'); 642 } 643 if (!match[n]) { 644 this.ptr = save; 645 return null; 646 } 647 return this.run(n, match, "Color"); 648 }; 649 650 /** 651 * Move = (lower | upper), (lower | upper). 652 * @return {String} code generated if matched, null if not matched 653 * @since 0.2 654 */ 655 SGFParser.prototype.Move = function() { 656 var match = []; 657 var save = this.ptr; 658 var n = -1; 659 this.sp(); 660 match[++n] = this.lower(); 661 if (!match[n]) { 662 n--; 663 match[++n] = this.upper(); 664 } 665 if (match[n]) { 666 match[++n] = this.lower(); 667 if (!match[n]) { 668 n--; 669 match[++n] = this.upper(); 670 } 671 } 672 if (!match[n]) { 673 this.ptr = save; 674 return null; 675 } 676 return this.run(n, match, "Move"); 677 }; 678 679 /** 680 * Compose = ValueType, ':', ValueType. 681 * @return {String} code generated if matched, null if not matched 682 * @since 0.2 683 */ 684 SGFParser.prototype.Compose = function() { 685 var match = []; 686 var save = this.ptr; 687 var n = -1; 688 this.sp(); 689 match[++n] = this.ValueType(); 690 if (match[n]) { 691 this.sp(); 692 match[++n] = this.text(':'); 693 } 694 if (match[n]) { 695 this.sp(); 696 match[++n] = this.ValueType(); 697 } 698 if (!match[n]) { 699 this.ptr = save; 700 return null; 701 } 702 return this.run(n, match, "Compose"); 703 }; 704 705 /** 706 * Text - Formatted Text 707 * @return {String} code generated if matched, null if not matched 708 * @since 0.2 709 */ 710 SGFParser.prototype.Text = function() { 711 var match = []; 712 var text = this.beforeDelim("]\t\\"); 713 var top = this.top(); 714 while (top != "]") { 715 if (top == "\\") { 716 // escape character 717 this.ptr++; 718 top = this.top(); 719 if (top != "\n") 720 text += top; 721 this.ptr++; 722 } else if (top == "\t") { 723 // convert tab to space 724 text += this.sp(); 725 } 726 text += this.beforeDelim("]\t\\"); 727 top = this.top(); 728 } 729 match[0] = text; 730 return this.run(1, match, "Text"); 731 }; 732 733 /** 734 * SimpleText - Simple Text 735 * @return {String} code generated if matched, null if not matched 736 * @since 0.2 737 */ 738 SGFParser.prototype.SimpleText = function() { 739 var match = []; 740 var text = this.beforeDelim("]\n\t\\"); 741 var top = this.top(); 742 while (top != "]") { 743 if (top == "\\") { 744 // escape character 745 this.ptr++; 746 top = this.top(); 747 if (top != "\n") 748 text += top; 749 this.ptr++; 750 } else if (top == "\t" || top == "\n") { 751 // convert newline and tab to space 752 text += this.sp(); 753 } 754 text += this.beforeDelim("]\n\t\\"); 755 top = this.top(); 756 } 757 match[0] = text; 758 return this.run(1, match, "SimpleText"); 759 }; 760 761 /** 762 * Parser Runner for SGF Syntax 763 * @param {String} n number of match 764 * @param {Object} match array of parsed result 765 * @param {String} parser caller method name 766 * @return {String} generated code 767 * @since 0.2 768 */ 769 SGFParser.prototype.run = function(n, match, parser) { 770 var code = ""; 771 if (parser == "Collection") { 772 code = true; 773 } else if (parser == "GameTree") { 774 code = true; 775 } else if (parser == "Node") { 776 code = true; 777 } else if (parser == "Property") { 778 code = true; 779 } else if (parser == "Game") { 780 code = true; 781 } else if (parser == "Size") { 782 this.rec.ro = parseInt(match[1]); 783 code = true; 784 } else if (parser == "Player") { 785 if (match[0] == "PB") { 786 this.rec.players[P_BLACK] = match[2]; 787 } else if (match[0] == "PW") { 788 this.rec.players[P_WHITE] = match[2]; 789 } 790 code = true; 791 } else if (parser == "Handy") { 792 code = true; 793 } else if (parser == "Komi") { 794 code = true; 795 } else if (parser == "BW") { 796 this.rec.move(match[2]); 797 code = true; 798 } else if (parser == "Other") { 799 code = true; 800 } else if (parser == "PropIdent") { 801 code = true; 802 } else if (parser == "PropValue") { 803 code = true; 804 } else if (parser == "CValueType") { 805 code = true; 806 } else if (parser == "ValueType") { 807 code = true; 808 } else if (parser == "None") { 809 code = match[0]; 810 } else if (parser == "Number") { 811 var sign; 812 switch (match[0]) { 813 case '-' : 814 sign = -1; 815 break; 816 case '+' : 817 default : 818 sign = 1; 819 } 820 var num = 0; 821 for (var i = 2; i <= n; i++) { 822 if (match[i] !== true && match[i] !== null) 823 num = num * 10 + parseInt(match[i]); 824 } 825 code = (sign * num).toString(); 826 } else if (parser == "Real") { 827 code = match[0]; 828 code += "."; 829 for (var i = 2; i <= n; i++) { 830 if (match[i] !== true && match[i] !== null) 831 code += match[i]; 832 } 833 } else if (parser == "Double") { 834 code = true; 835 } else if (parser == "Color") { 836 code = true; 837 } else if (parser == "Move") { 838 var col = ALPHA.indexOf(match[0]) + 1; 839 var row = ALPHA.indexOf(match[1]) + 1; 840 code = new Move(col, row); 841 } else if (parser == "Compose") { 842 code = true; 843 } else if (parser == "Text") { 844 code = match[0]; 845 } else if (parser == "SimpleText") { 846 code = match[0]; 847 } 848 return code; 849 }; 850