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