@echecs/pgn 1.0.0 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/grammar.ne CHANGED
@@ -5,43 +5,66 @@
5
5
  # ----- pgn ----- #
6
6
  DATABASE -> GAME:+ {% id %}
7
7
 
8
- GAME -> TAGS __ MOVES __ result __ {%
9
- (d) => ({ meta: d[0], moves: d[2], result: d[4] })
8
+ # TAGS contains whitespace at the end
9
+ GAME -> TAGS MOVE_SECTION __ result __ {%
10
+ (d) => ({ meta: d[0], moves: d[1], result: d[3] })
10
11
  %}
11
12
  # ----- /pgn ---- #
12
13
 
13
14
  # ----- tags ----- #
14
- TAGS -> TAG:+ {% (d) => d[0].reduce((acc, item) => ({ ...acc, ...item }), {}) %}
15
+ TAGS -> (TAG __):+ {% (d) => d[0].map(d0 => d0[0]).reduce((acc, item) => ({ ...acc, ...item }), {}) %}
15
16
 
16
- TAG -> "[" NAME __ VALUE "]" __ {% (d) => ({ [d[1]]: d[3] }) %}
17
+ TAG -> "[" NAME __ VALUE "]" {% (d) => ({ [d[1]]: d[3] }) %}
17
18
 
18
19
  NAME -> string
19
20
 
20
21
  VALUE -> dqstring {% id %}
21
- # ----- /tags ---- #
22
+ # ----- /tags ----- #
22
23
 
23
24
 
24
25
  # ----- moves ----- #
25
- MOVES -> MOVE:* HALFMOVE:? {%
26
+ MOVE_SECTION ->
27
+ MOVE (_ MOVE):* (_ HM_WHITE):? {%
28
+ (d) => {
29
+ const moves = [d[0]];
30
+
31
+ if (d[1].length > 0) {
32
+ const d1 = d[1];
33
+
34
+ moves.push(...d1.map(d => d[1]));
35
+ }
36
+
37
+ if (d[2]) {
38
+ const d2 = d[2];
39
+
40
+ moves.push(d2[1]);
41
+ }
42
+
43
+ return moves;
44
+ }
45
+ %}
46
+ | HM_WHITE {% d => [d[0]] %}
47
+
48
+ MOVES -> (_ MOVE):* (_ HM_WHITE):? {%
26
49
  (d) => {
27
50
  const moves = [];
28
- const assign = (number, ...move) => {
29
- moves[number - 1] = move;
30
- };
31
51
 
32
- d[0].map(dd0 => assign(...dd0));
52
+ if (d[0]) {
53
+ d[0].map(d0 => d0[1]).map(d01 => moves.push(d01));
54
+ }
33
55
 
34
56
  if (d[1]) {
35
- assign(...d[1]);
57
+ moves.push(d[1][1]);
36
58
  }
37
59
 
38
60
  return moves;
39
61
  }
40
62
  %}
41
63
 
42
- HALFMOVE -> NUMBER _ SAN (_ VARIATION):? _ {%
64
+ # Half Move White #
65
+ HM_WHITE -> NUMBER _ SAN (_ VARIANT):* {%
43
66
  (d) => {
44
- const move = [d[0], {...d[2], ...(d[3] && { variation: d[3][1] }), }];
67
+ const move = [d[0], {...d[2], ...(d[3].length > 0 && { variants: d[3].map(d3 => d3[1]) }), }];
45
68
 
46
69
  if (move[1].castling) {
47
70
  move[1].to = move[1].castling === 'K' ? 'g1' : 'c1';
@@ -52,128 +75,156 @@ HALFMOVE -> NUMBER _ SAN (_ VARIATION):? _ {%
52
75
  }
53
76
  %}
54
77
 
55
- MOVE -> NUMBER _ SAN __ SAN (_ VARIATION):? _ {%
78
+ HM_BLACK -> _ NUMBER continuation _ SAN (_ VARIANT):* {%
56
79
  (d) => {
57
- const move = [d[0], d[2], d[4]];
58
-
59
- if (move[1].castling) {
60
- move[1].to = move[1].castling === 'K' ? 'g1' : 'c1';
61
- move[1].castling = true;
62
- }
80
+ const move = [d[1], undefined, {...d[4], ...(d[5].length > 0 && { variants: d[5].map(d5 => d5[1]) }) }];
63
81
 
64
82
  if (move[2].castling) {
65
83
  move[2].to = move[2].castling === 'K' ? 'g8' : 'c8';
66
84
  move[2].castling = true;
67
85
  }
68
86
 
69
- if (d[5]) {
70
- const variant = d[5][1];
71
- const isWhite = variant[0][0] !== undefined;
72
-
73
- move[isWhite ? 1 : 2].variation = d[5][1];
74
- }
75
-
76
87
  return move;
77
88
  }
78
89
  %}
79
90
 
80
- VARIATION ->
81
- "(" MOVES ")" {%
82
- (d) => [...d[1].values()].filter(Boolean)
83
- %}
84
- | "(" MOVES_BLACK ")" {%
85
- (d) => [...d[1].values()].filter(Boolean)
86
- %}
91
+ MOVE ->
92
+ NUMBER _ SAN __ SAN (_ VARIANT):* {%
93
+ (d) => {
94
+ const move = [d[0], d[2], d[4]];
87
95
 
88
- MOVES_BLACK -> HALFMOVE_BLACK MOVES:? {%
89
- (d) => {
90
- const moves = d[1] || [];
96
+ if (move[1].castling) {
97
+ move[1].to = move[1].castling === 'K' ? 'g1' : 'c1';
98
+ move[1].castling = true;
99
+ }
91
100
 
92
- const assign = (number, ...move) => {
93
- moves[number - 1] = move;
94
- };
101
+ if (move[2].castling) {
102
+ move[2].to = move[2].castling === 'K' ? 'g8' : 'c8';
103
+ move[2].castling = true;
104
+ }
95
105
 
96
- if (d[0]) {
97
- assign(...d[0]);
98
- }
106
+ if (d[5]) {
107
+ const variants = d[5].map(d5 => d5[1]);
99
108
 
100
- return moves;
101
- }
102
- %}
109
+ const white = variants.filter(variant => variant[0][1]);
110
+ const black = variants.filter(variant => !variant[0][1]);
103
111
 
104
- HALFMOVE_BLACK -> NUMBER continuation _ SAN (_ VARIATION):? _ {%
105
- (d) => {
106
- const move = [
107
- d[0],
108
- undefined,
109
- {...d[3], ...(d[4] && { variation: d[4][1] }), }
110
- ];
112
+ if (white.length > 0) {
113
+ move[1].variants = white;
114
+ }
111
115
 
112
- if (move[2].castling) {
113
- move[2].to = move[2].castling === 'K' ? 'g8' : 'c8';
114
- move[2].castling = true;
116
+ if (black.length > 0) {
117
+ move[2].variants = black;
118
+ }
119
+ }
120
+
121
+ return move;
115
122
  }
123
+ %}
124
+ | HM_WHITE __ HM_BLACK {%
125
+ d => [d[0][0], d[0][1], d[2][2]]
126
+ %}
116
127
 
117
- return move;
118
- }
119
- %}
128
+ NUMBER -> unsigned_int ".":? {% (d) => d[0] %}
120
129
 
121
- NUMBER -> unsigned_int ".":* {% (d) => d[0] %}
130
+ VARIANT ->
131
+ "(" MOVES _ ")" {%
132
+ (d) => d[1]
133
+ %}
134
+ | "(" HM_BLACK MOVES _ ")" {%
135
+ (d) => [d[1], ...d[2]]
136
+ %}
137
+
138
+ # ----- /moves ----- #
139
+
140
+ # ----- san ----- #
122
141
 
123
142
  SAN ->
124
- piece:? DISAMBIGUATION:? capture:? file rank promotion:? (_ annotation):* (_ COMMENT):? {%
143
+ piece:? DISAMBIGUATION:? capture:? file rank PROMOTION:? (_ SUFFIX):? (_ COMMENT):* {%
144
+ (d) => {
145
+ const comments = d[7].map(d7 => d7[1]).filter(Boolean);
146
+
147
+ return ({
148
+ ...(d[6] && d[6][1]),
149
+ ...(comments.length > 0 && { comment: comments.reduce((acc, item) => `${acc} ${item}`, '') }),
150
+ ...(d[2] && { capture: true }),
151
+ ...(d[1] && { from: d[1][0] }),
152
+ piece: d[0] ? d[0][0] : 'P',
153
+ ...(d[5] && { promotion: d[5] }),
154
+ to: `${d[3]}${d[4]}`,
155
+ });
156
+ }
157
+ %}
158
+ | castling (_ SUFFIX):* (_ COMMENT):* {%
159
+ (d) => {
160
+ const comments = d[2].map(d2 => d2[1]).filter(Boolean);
161
+
162
+ return ({
163
+ ...(d[1]),
164
+ ...(comments.length > 0 && { comment: comments.reduce((acc, item) => `${acc} ${item}`, '') }),
165
+ castling: d[0],
166
+ piece: 'K'
167
+ })
168
+ }
169
+ %}
170
+
171
+ DISAMBIGUATION ->
172
+ file {% id %}
173
+ | rank {% id %}
174
+
175
+ PROMOTION -> "=" [QBNR] {% (d) => d[1] %}
176
+
177
+ SUFFIX ->
178
+ (check | checkmate) annotation:? (_ NAG):* {%
179
+ (d) => ({
180
+ ...(d[0] && { [d[0]]: true }),
181
+ ...((d[1] || d[2].length > 0) && { annotations: [...(d[1] ? [d[1]] : []), ...d[2].map(d2 => d2[1])] }),
182
+ })
183
+ %}
184
+ | (check | checkmate):? annotation (_ NAG):* {%
125
185
  (d) => ({
126
- ...(d[6].length > 0 && { annotations: d[6].map(([_, a]) => a) }),
127
- ...(d[7] && { comment: d[7][1] }),
128
- ...(d[1] && { from: d[1][0] }),
129
- piece: d[0] ? d[0][0] : 'P',
130
- ...(d[5] && { promotion: d[5] }),
131
- to: `${d[3]}${d[4]}`,
186
+ ...(d[0] && { [d[0]]: true }),
187
+ ...((d[1] || d[2].length > 0) && { annotations: [...(d[1] ? [d[1]] : []), ...d[2].map(d2 => d2[1])] }),
132
188
  })
133
189
  %}
134
- | castling (_ annotation):* (_ COMMENT):? {%
190
+ | (check | checkmate):? annotation:? (_ NAG):+ {%
135
191
  (d) => ({
136
- ...(d[1].length > 0 && { annotations: d[1].map(([_, a]) => a) }),
137
- ...(d[2] && { comment: d[2][1] }),
138
- castling: d[0],
139
- piece: 'K'
192
+ ...(d[0] && { [d[0]]: true }),
193
+ ...((d[1] || d[2].length > 0) && { annotations: [...(d[1] ? [d[1]] : []), ...d[2].map(d2 => d2[1])] }),
140
194
  })
141
195
  %}
142
196
 
143
- DISAMBIGUATION ->
144
- file {% id %}
145
- | rank {% id %}
197
+ # Numeric Annotation Glyph
198
+ NAG -> "$" ("25" [0-5] | "2" [0-4] [0-9] | "1" [0-9] [0-9] | [1-9] [0-9] | [0-9]) {%
199
+ (d) => d[1]
200
+ %}
146
201
 
147
- COMMENT -> bstring
202
+ COMMENT -> bstring | ";" [^\n]:*
148
203
 
149
- # ----- /moves ---- #
204
+ # ----- /san ----- #
150
205
 
151
206
  # types
152
207
  annotation ->
153
208
  "!!" {% id %}
154
209
  | "!" {% id %}
155
210
  | "!?" {% id %}
156
- | "#" {% () => 'checkmate' %}
157
- | "$1" {% () => '!' %}
158
- | "$2" {% () => '?' %}
159
- | "$3" {% () => '!!' %}
160
- | "$4" {% () => '!?' %}
161
- | "$5" {% () => '?!' %}
162
- | "$6" {% () => '??' %}
163
- | "+" {% () => 'check' %}
211
+ | "?!" {% id %}
212
+ | "?" {% id %}
213
+ | "??" {% id %}
164
214
  | "+-" {% id %}
165
215
  | "-+" {% id %}
166
216
  | "=" {% id %}
167
217
  | "?!" {% id %}
168
218
  | "?" {% id %}
169
219
  | "??" {% id %}
170
- | "N" {% id %}
171
220
  | "±" {% id %}
172
221
  | "∓" {% id %}
173
222
  | "∞" {% id %}
174
223
  | "⨀" {% id %}
175
224
  | "⩱" {% id %}
176
225
  | "⩲" {% id %}
226
+ check -> "+" {% () => 'check' %}
227
+ checkmate -> "#" {% () => 'checkmate' %}
177
228
  bstring -> "{" [^}]:* "}" {% (d) => d[1].join('') %}
178
229
  capture -> "x"
179
230
  castling ->
@@ -182,7 +233,6 @@ castling ->
182
233
  continuation -> "..."
183
234
  file -> [a-h]
184
235
  piece -> [KQBNR]
185
- promotion -> "=" [QBNR] {% (d) => d[1] %}
186
236
  rank -> [1-8]
187
237
  result ->
188
238
  "1-0" {% () => 1 %}
package/src/index.ts ADDED
@@ -0,0 +1,59 @@
1
+ import { Grammar, Parser } from 'nearley';
2
+
3
+ import grammar from './grammar';
4
+
5
+ type File = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h';
6
+ type Piece = 'B' | 'K' | 'N' | 'P' | 'Q' | 'R';
7
+ type Rank = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8';
8
+ type Result = '1-0' | '0-1' | '1/2-1/2' | '?';
9
+
10
+ type Square = `${File}${Rank}`;
11
+
12
+ type HalfMove = [number, undefined, Move];
13
+ type VariantMove = Move | HalfMove;
14
+ type VariantMoves = VariantMove[];
15
+
16
+ type Move = {
17
+ annotations?: string[];
18
+ capture?: boolean;
19
+ castling?: boolean;
20
+ check?: boolean;
21
+ checkmate?: boolean;
22
+ comment?: string;
23
+ from?: File | Rank;
24
+ piece: Piece;
25
+ promotion?: Piece;
26
+ to: Square;
27
+ variants?: VariantMoves[];
28
+ };
29
+
30
+ type Meta = {
31
+ Result: Result;
32
+ [key: string]: string;
33
+ };
34
+
35
+ type Moves = [number, Move] | [number, Move, Move];
36
+
37
+ type PGN = {
38
+ meta: Meta;
39
+ moves: Moves;
40
+ result: Result;
41
+ };
42
+
43
+ function tokenize(input: string): PGN[] {
44
+ const parser = new Parser(Grammar.fromCompiled(grammar));
45
+
46
+ parser.feed(input);
47
+
48
+ if (parser.results.length > 1) {
49
+ throw new Error(`@echecs/parser: Ambiguous syntax. Found ${parser.results.length} results`);
50
+ }
51
+
52
+ return parser.results[0] as PGN[];
53
+ }
54
+
55
+ export default function parse(input: string): PGN[] {
56
+ const games = input.replace(/[\r\uFEFF]/g, '');
57
+
58
+ return tokenize(games);
59
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "declaration": true,
5
+ "esModuleInterop": true,
6
+ "forceConsistentCasingInFileNames": true,
7
+ "module": "ESNext",
8
+ "moduleResolution": "node",
9
+ "outDir": "dist/",
10
+ "removeComments": true,
11
+ "resolveJsonModule": true,
12
+ "strict": true,
13
+ "target": "ESNext"
14
+ }
15
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "declaration": true,
5
+ "esModuleInterop": true,
6
+ "forceConsistentCasingInFileNames": true,
7
+ "module": "ESNext",
8
+ "moduleResolution": "node",
9
+ "outDir": "dist/",
10
+ "removeComments": true,
11
+ "resolveJsonModule": true,
12
+ "strict": true,
13
+ "target": "ESNext"
14
+ },
15
+ "files": ["src/index.ts"]
16
+ }
package/dist/cjs/pgn.js DELETED
@@ -1,25 +0,0 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
6
-
7
- var nearley = _interopDefault(require('nearley'));
8
- var grammar = _interopDefault(require('./grammar.js'));
9
-
10
- const {
11
- Grammar,
12
- Parser
13
- } = nearley;
14
-
15
-
16
-
17
- var src = function parse(string) {
18
- const parser = new Parser(Grammar.fromCompiled(grammar));
19
- string = string.replace(/\r/g, '');
20
- parser.feed(string);
21
- return parser.results[0];
22
- };
23
-
24
- exports.default = src;
25
- //# sourceMappingURL=pgn.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pgn.js","sources":["../../src/index.js"],"sourcesContent":["const { Grammar, Parser } = require('nearley');\n\nconst grammar = require('./grammar');\n\nmodule.exports = function parse(string) {\n const parser = new Parser(Grammar.fromCompiled(grammar));\n\n string = string.replace(/\\r/g, '');\n\n parser.feed(string);\n\n return parser.results[0];\n};\n"],"names":["Grammar","Parser","require","module","parse","string","parser","fromCompiled","grammar","replace","feed","results"],"mappings":";;;;;;;;;AAAA,MAAM;AAAEA,EAAAA,OAAF;AAAWC,EAAAA;AAAX,IAAsBC,OAA5B;;;;AAIAC,OAAA,GAAiB,SAASC,KAAT,CAAeC,MAAf,EAAuB;AACtC,QAAMC,MAAM,GAAG,IAAIL,MAAJ,CAAWD,OAAO,CAACO,YAAR,CAAqBC,OAArB,CAAX,CAAf;AAEAH,EAAAA,MAAM,GAAGA,MAAM,CAACI,OAAP,CAAe,KAAf,EAAsB,EAAtB,CAAT;AAEAH,EAAAA,MAAM,CAACI,IAAP,CAAYL,MAAZ;AAEA,SAAOC,MAAM,CAACK,OAAP,CAAe,CAAf,CAAP;AACD,CARD;;;;"}
package/dist/es/pgn.js DELETED
@@ -1,19 +0,0 @@
1
- import nearley from 'nearley';
2
- import grammar from './grammar.js';
3
-
4
- const {
5
- Grammar,
6
- Parser
7
- } = nearley;
8
-
9
-
10
-
11
- var src = function parse(string) {
12
- const parser = new Parser(Grammar.fromCompiled(grammar));
13
- string = string.replace(/\r/g, '');
14
- parser.feed(string);
15
- return parser.results[0];
16
- };
17
-
18
- export default src;
19
- //# sourceMappingURL=pgn.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pgn.js","sources":["../../src/index.js"],"sourcesContent":["const { Grammar, Parser } = require('nearley');\n\nconst grammar = require('./grammar');\n\nmodule.exports = function parse(string) {\n const parser = new Parser(Grammar.fromCompiled(grammar));\n\n string = string.replace(/\\r/g, '');\n\n parser.feed(string);\n\n return parser.results[0];\n};\n"],"names":["Grammar","Parser","require","module","parse","string","parser","fromCompiled","grammar","replace","feed","results"],"mappings":";;;AAAA,MAAM;AAAEA,EAAAA,OAAF;AAAWC,EAAAA;AAAX,IAAsBC,OAA5B;;;;AAIAC,OAAA,GAAiB,SAASC,KAAT,CAAeC,MAAf,EAAuB;AACtC,QAAMC,MAAM,GAAG,IAAIL,MAAJ,CAAWD,OAAO,CAACO,YAAR,CAAqBC,OAArB,CAAX,CAAf;AAEAH,EAAAA,MAAM,GAAGA,MAAM,CAACI,OAAP,CAAe,KAAf,EAAsB,EAAtB,CAAT;AAEAH,EAAAA,MAAM,CAACI,IAAP,CAAYL,MAAZ;AAEA,SAAOC,MAAM,CAACK,OAAP,CAAe,CAAf,CAAP;AACD,CARD;;;;"}
package/index.js DELETED
@@ -1 +0,0 @@
1
- module.exports = require('./dist/cjs/pgn.js');
package/index.module.js DELETED
@@ -1 +0,0 @@
1
- module.exports = require('./dist/es/pgn.js');
package/src/index.js DELETED
@@ -1,13 +0,0 @@
1
- const { Grammar, Parser } = require('nearley');
2
-
3
- const grammar = require('./grammar');
4
-
5
- module.exports = function parse(string) {
6
- const parser = new Parser(Grammar.fromCompiled(grammar));
7
-
8
- string = string.replace(/\r/g, '');
9
-
10
- parser.feed(string);
11
-
12
- return parser.results[0];
13
- };