@echecs/pgn 1.0.0 → 2.1.1

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.
@@ -0,0 +1,11 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: npm
4
+ directory: '/'
5
+ schedule:
6
+ interval: daily
7
+ time: '13:00'
8
+ open-pull-requests-limit: 10
9
+ commit-message:
10
+ prefix: 'chore'
11
+ include: 'scope'
@@ -0,0 +1,34 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ jobs:
9
+ build:
10
+ name: Release
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Checkout code
14
+ uses: actions/checkout@v3
15
+ - name: Setup
16
+ uses: actions/setup-node@v3
17
+ with:
18
+ node-version: node
19
+ - name: Cache NPM Modules
20
+ id: cache-node
21
+ uses: actions/cache@v3
22
+ with:
23
+ path: '**/node_modules'
24
+ key: ${{ runner.os }}-yarn-${{ hashFiles('**/package-lock.json') }}
25
+ - name: Install dependencies
26
+ if: steps.cache-node.outputs.cache-hit != 'true'
27
+ run: npm ci
28
+ - name: Verify
29
+ run: npm run test
30
+ - name: Release to NPM
31
+ uses: JS-DevTools/npm-publish@v1
32
+ with:
33
+ access: public
34
+ token: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env sh
2
+ . "$(dirname -- "$0")/_/husky.sh"
3
+
4
+ npx lint-staged
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+ . "$(dirname "$0")/_/husky.sh"
3
+
4
+ npm run format && npm run lint && npm run test
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Adrian de la Rosa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,19 +1,34 @@
1
1
  # PGN
2
2
 
3
- PGN is part of the project @echecs (chess). It parses a PGN File following the
4
- specification provided in:
5
- [Specification](http://www.saremba.de/chessgml/standards/pgn/pgn-complete.htm)
3
+ **PGN** is part of the **ECHECS** project. **PGN** is a parser of the
4
+ [PGN specification](http://www.saremba.de/chessgml/standards/pgn/pgn-complete.htm).
6
5
 
7
- ## Use
6
+ ## Installation
7
+
8
+ ```bash
9
+ npm install --save-dev @echecs/pgn
10
+ ```
11
+
12
+ ```bash
13
+ yarn add @echecs/pgn
14
+ ```
15
+
16
+ ## Usage
8
17
 
9
18
  We just need to provide with a PGN to the parser. It always return an array
10
19
  because PGN files could contain several games.
11
20
 
12
21
  ```js
13
- const parser = require('@echecs/pgn');
22
+ import { readFileSync } from 'fs';
23
+ import parse from '@echecs/pgn';
24
+
25
+ function readFile(path: string): string {
26
+ const filename = require.resolve(path);
14
27
 
15
- parser(string);
28
+ return readFileSync(filename, 'utf8');
29
+ }
16
30
 
31
+ const pgn = parse(readFile('./games/file.pgn'));
17
32
  // [
18
33
  // {
19
34
  // "meta": {
@@ -0,0 +1,35 @@
1
+ export const Lexer: undefined;
2
+ export const ParserRules: ({
3
+ name: string;
4
+ symbols: (string | RegExp)[];
5
+ postprocess: (d: any) => any;
6
+ } | {
7
+ name: string;
8
+ symbols: (string | {
9
+ literal: string;
10
+ })[];
11
+ postprocess?: undefined;
12
+ } | {
13
+ name: string;
14
+ symbols: (string | RegExp)[];
15
+ postprocess?: undefined;
16
+ } | {
17
+ name: string;
18
+ symbols: (string | {
19
+ literal: string;
20
+ })[];
21
+ postprocess: (d: any) => any;
22
+ } | {
23
+ name: string;
24
+ symbols: (RegExp | {
25
+ literal: string;
26
+ })[];
27
+ postprocess: (d: any) => any;
28
+ } | {
29
+ name: string;
30
+ symbols: (RegExp | {
31
+ literal: string;
32
+ })[];
33
+ postprocess?: undefined;
34
+ })[];
35
+ export const ParserStart: string;
package/dist/grammar.js CHANGED
@@ -1,4 +1,4 @@
1
- // Generated automatically by nearley, version 2.19.2
1
+ // Generated automatically by nearley, version 2.20.1
2
2
  // http://github.com/Hardmath123/nearley
3
3
  (function () {
4
4
  function id(x) { return x[0]; }
@@ -126,41 +126,70 @@ var grammar = {
126
126
  {"name": "DATABASE$ebnf$1", "symbols": ["GAME"]},
127
127
  {"name": "DATABASE$ebnf$1", "symbols": ["DATABASE$ebnf$1", "GAME"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
128
128
  {"name": "DATABASE", "symbols": ["DATABASE$ebnf$1"], "postprocess": id},
129
- {"name": "GAME", "symbols": ["TAGS", "__", "MOVES", "__", "result", "__"], "postprocess":
130
- (d) => ({ meta: d[0], moves: d[2], result: d[4] })
129
+ {"name": "GAME", "symbols": ["TAGS", "MOVE_SECTION", "__", "result", "__"], "postprocess":
130
+ (d) => ({ meta: d[0], moves: d[1], result: d[3] })
131
131
  },
132
- {"name": "TAGS$ebnf$1", "symbols": ["TAG"]},
133
- {"name": "TAGS$ebnf$1", "symbols": ["TAGS$ebnf$1", "TAG"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
134
- {"name": "TAGS", "symbols": ["TAGS$ebnf$1"], "postprocess": (d) => d[0].reduce((acc, item) => ({ ...acc, ...item }), {})},
135
- {"name": "TAG", "symbols": [{"literal":"["}, "NAME", "__", "VALUE", {"literal":"]"}, "__"], "postprocess": (d) => ({ [d[1]]: d[3] })},
132
+ {"name": "TAGS$ebnf$1$subexpression$1", "symbols": ["TAG", "__"]},
133
+ {"name": "TAGS$ebnf$1", "symbols": ["TAGS$ebnf$1$subexpression$1"]},
134
+ {"name": "TAGS$ebnf$1$subexpression$2", "symbols": ["TAG", "__"]},
135
+ {"name": "TAGS$ebnf$1", "symbols": ["TAGS$ebnf$1", "TAGS$ebnf$1$subexpression$2"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
136
+ {"name": "TAGS", "symbols": ["TAGS$ebnf$1"], "postprocess": (d) => d[0].map(d0 => d0[0]).reduce((acc, item) => ({ ...acc, ...item }), {})},
137
+ {"name": "TAG", "symbols": [{"literal":"["}, "NAME", "__", "VALUE", {"literal":"]"}], "postprocess": (d) => ({ [d[1]]: d[3] })},
136
138
  {"name": "NAME", "symbols": ["string"]},
137
139
  {"name": "VALUE", "symbols": ["dqstring"], "postprocess": id},
140
+ {"name": "MOVE_SECTION$ebnf$1", "symbols": []},
141
+ {"name": "MOVE_SECTION$ebnf$1$subexpression$1", "symbols": ["_", "MOVE"]},
142
+ {"name": "MOVE_SECTION$ebnf$1", "symbols": ["MOVE_SECTION$ebnf$1", "MOVE_SECTION$ebnf$1$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
143
+ {"name": "MOVE_SECTION$ebnf$2$subexpression$1", "symbols": ["_", "HM_WHITE"]},
144
+ {"name": "MOVE_SECTION$ebnf$2", "symbols": ["MOVE_SECTION$ebnf$2$subexpression$1"], "postprocess": id},
145
+ {"name": "MOVE_SECTION$ebnf$2", "symbols": [], "postprocess": function(d) {return null;}},
146
+ {"name": "MOVE_SECTION", "symbols": ["MOVE", "MOVE_SECTION$ebnf$1", "MOVE_SECTION$ebnf$2"], "postprocess":
147
+ (d) => {
148
+ const moves = [d[0]];
149
+
150
+ if (d[1].length > 0) {
151
+ const d1 = d[1];
152
+
153
+ moves.push(...d1.map(d => d[1]));
154
+ }
155
+
156
+ if (d[2]) {
157
+ const d2 = d[2];
158
+
159
+ moves.push(d2[1]);
160
+ }
161
+
162
+ return moves;
163
+ }
164
+ },
165
+ {"name": "MOVE_SECTION", "symbols": ["HM_WHITE"], "postprocess": d => [d[0]]},
138
166
  {"name": "MOVES$ebnf$1", "symbols": []},
139
- {"name": "MOVES$ebnf$1", "symbols": ["MOVES$ebnf$1", "MOVE"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
140
- {"name": "MOVES$ebnf$2", "symbols": ["HALFMOVE"], "postprocess": id},
167
+ {"name": "MOVES$ebnf$1$subexpression$1", "symbols": ["_", "MOVE"]},
168
+ {"name": "MOVES$ebnf$1", "symbols": ["MOVES$ebnf$1", "MOVES$ebnf$1$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
169
+ {"name": "MOVES$ebnf$2$subexpression$1", "symbols": ["_", "HM_WHITE"]},
170
+ {"name": "MOVES$ebnf$2", "symbols": ["MOVES$ebnf$2$subexpression$1"], "postprocess": id},
141
171
  {"name": "MOVES$ebnf$2", "symbols": [], "postprocess": function(d) {return null;}},
142
172
  {"name": "MOVES", "symbols": ["MOVES$ebnf$1", "MOVES$ebnf$2"], "postprocess":
143
173
  (d) => {
144
174
  const moves = [];
145
- const assign = (number, ...move) => {
146
- moves[number - 1] = move;
147
- };
148
175
 
149
- d[0].map(dd0 => assign(...dd0));
176
+ if (d[0]) {
177
+ d[0].map(d0 => d0[1]).map(d01 => moves.push(d01));
178
+ }
150
179
 
151
180
  if (d[1]) {
152
- assign(...d[1]);
181
+ moves.push(d[1][1]);
153
182
  }
154
183
 
155
184
  return moves;
156
185
  }
157
186
  },
158
- {"name": "HALFMOVE$ebnf$1$subexpression$1", "symbols": ["_", "VARIATION"]},
159
- {"name": "HALFMOVE$ebnf$1", "symbols": ["HALFMOVE$ebnf$1$subexpression$1"], "postprocess": id},
160
- {"name": "HALFMOVE$ebnf$1", "symbols": [], "postprocess": function(d) {return null;}},
161
- {"name": "HALFMOVE", "symbols": ["NUMBER", "_", "SAN", "HALFMOVE$ebnf$1", "_"], "postprocess":
187
+ {"name": "HM_WHITE$ebnf$1", "symbols": []},
188
+ {"name": "HM_WHITE$ebnf$1$subexpression$1", "symbols": ["_", "VARIANT"]},
189
+ {"name": "HM_WHITE$ebnf$1", "symbols": ["HM_WHITE$ebnf$1", "HM_WHITE$ebnf$1$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
190
+ {"name": "HM_WHITE", "symbols": ["NUMBER", "_", "SAN", "HM_WHITE$ebnf$1"], "postprocess":
162
191
  (d) => {
163
- const move = [d[0], {...d[2], ...(d[3] && { variation: d[3][1] }), }];
192
+ const move = [d[0], {...d[2], ...(d[3].length > 0 && { variants: d[3].map(d3 => d3[1]) }), }];
164
193
 
165
194
  if (move[1].castling) {
166
195
  move[1].to = move[1].castling === 'K' ? 'g1' : 'c1';
@@ -170,155 +199,201 @@ var grammar = {
170
199
  return move;
171
200
  }
172
201
  },
173
- {"name": "MOVE$ebnf$1$subexpression$1", "symbols": ["_", "VARIATION"]},
174
- {"name": "MOVE$ebnf$1", "symbols": ["MOVE$ebnf$1$subexpression$1"], "postprocess": id},
175
- {"name": "MOVE$ebnf$1", "symbols": [], "postprocess": function(d) {return null;}},
176
- {"name": "MOVE", "symbols": ["NUMBER", "_", "SAN", "__", "SAN", "MOVE$ebnf$1", "_"], "postprocess":
202
+ {"name": "HM_BLACK$ebnf$1", "symbols": []},
203
+ {"name": "HM_BLACK$ebnf$1$subexpression$1", "symbols": ["_", "VARIANT"]},
204
+ {"name": "HM_BLACK$ebnf$1", "symbols": ["HM_BLACK$ebnf$1", "HM_BLACK$ebnf$1$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
205
+ {"name": "HM_BLACK", "symbols": ["_", "NUMBER", "continuation", "_", "SAN", "HM_BLACK$ebnf$1"], "postprocess":
177
206
  (d) => {
178
- const move = [d[0], d[2], d[4]];
179
-
180
- if (move[1].castling) {
181
- move[1].to = move[1].castling === 'K' ? 'g1' : 'c1';
182
- move[1].castling = true;
183
- }
207
+ const move = [d[1], undefined, {...d[4], ...(d[5].length > 0 && { variants: d[5].map(d5 => d5[1]) }) }];
184
208
 
185
209
  if (move[2].castling) {
186
210
  move[2].to = move[2].castling === 'K' ? 'g8' : 'c8';
187
211
  move[2].castling = true;
188
212
  }
189
213
 
190
- if (d[5]) {
191
- const variant = d[5][1];
192
- const isWhite = variant[0][0] !== undefined;
193
-
194
- move[isWhite ? 1 : 2].variation = d[5][1];
195
- }
196
-
197
214
  return move;
198
215
  }
199
216
  },
200
- {"name": "VARIATION", "symbols": [{"literal":"("}, "MOVES", {"literal":")"}], "postprocess":
201
- (d) => [...d[1].values()].filter(Boolean)
202
- },
203
- {"name": "VARIATION", "symbols": [{"literal":"("}, "MOVES_BLACK", {"literal":")"}], "postprocess":
204
- (d) => [...d[1].values()].filter(Boolean)
205
- },
206
- {"name": "MOVES_BLACK$ebnf$1", "symbols": ["MOVES"], "postprocess": id},
207
- {"name": "MOVES_BLACK$ebnf$1", "symbols": [], "postprocess": function(d) {return null;}},
208
- {"name": "MOVES_BLACK", "symbols": ["HALFMOVE_BLACK", "MOVES_BLACK$ebnf$1"], "postprocess":
217
+ {"name": "MOVE$ebnf$1", "symbols": []},
218
+ {"name": "MOVE$ebnf$1$subexpression$1", "symbols": ["_", "VARIANT"]},
219
+ {"name": "MOVE$ebnf$1", "symbols": ["MOVE$ebnf$1", "MOVE$ebnf$1$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
220
+ {"name": "MOVE", "symbols": ["NUMBER", "_", "SAN", "__", "SAN", "MOVE$ebnf$1"], "postprocess":
209
221
  (d) => {
210
- const moves = d[1] || [];
211
-
212
- const assign = (number, ...move) => {
213
- moves[number - 1] = move;
214
- };
222
+ const move = [d[0], d[2], d[4]];
215
223
 
216
- if (d[0]) {
217
- assign(...d[0]);
224
+ if (move[1].castling) {
225
+ move[1].to = move[1].castling === 'K' ? 'g1' : 'c1';
226
+ move[1].castling = true;
218
227
  }
219
228
 
220
- return moves;
221
- }
222
- },
223
- {"name": "HALFMOVE_BLACK$ebnf$1$subexpression$1", "symbols": ["_", "VARIATION"]},
224
- {"name": "HALFMOVE_BLACK$ebnf$1", "symbols": ["HALFMOVE_BLACK$ebnf$1$subexpression$1"], "postprocess": id},
225
- {"name": "HALFMOVE_BLACK$ebnf$1", "symbols": [], "postprocess": function(d) {return null;}},
226
- {"name": "HALFMOVE_BLACK", "symbols": ["NUMBER", "continuation", "_", "SAN", "HALFMOVE_BLACK$ebnf$1", "_"], "postprocess":
227
- (d) => {
228
- const move = [
229
- d[0],
230
- undefined,
231
- {...d[3], ...(d[4] && { variation: d[4][1] }), }
232
- ];
233
-
234
229
  if (move[2].castling) {
235
230
  move[2].to = move[2].castling === 'K' ? 'g8' : 'c8';
236
231
  move[2].castling = true;
237
232
  }
238
233
 
234
+ if (d[5]) {
235
+ const variants = d[5].map(d5 => d5[1]);
236
+
237
+ const white = variants.filter(variant => variant[0][1]);
238
+ const black = variants.filter(variant => !variant[0][1]);
239
+
240
+ if (white.length > 0) {
241
+ move[1].variants = white;
242
+ }
243
+
244
+ if (black.length > 0) {
245
+ move[2].variants = black;
246
+ }
247
+ }
248
+
239
249
  return move;
240
250
  }
241
- },
242
- {"name": "NUMBER$ebnf$1", "symbols": []},
243
- {"name": "NUMBER$ebnf$1", "symbols": ["NUMBER$ebnf$1", {"literal":"."}], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
251
+ },
252
+ {"name": "MOVE", "symbols": ["HM_WHITE", "__", "HM_BLACK"], "postprocess":
253
+ d => [d[0][0], d[0][1], d[2][2]]
254
+ },
255
+ {"name": "NUMBER$ebnf$1", "symbols": [{"literal":"."}], "postprocess": id},
256
+ {"name": "NUMBER$ebnf$1", "symbols": [], "postprocess": function(d) {return null;}},
244
257
  {"name": "NUMBER", "symbols": ["unsigned_int", "NUMBER$ebnf$1"], "postprocess": (d) => d[0]},
258
+ {"name": "VARIANT", "symbols": [{"literal":"("}, "MOVES", "_", {"literal":")"}], "postprocess":
259
+ (d) => d[1]
260
+ },
261
+ {"name": "VARIANT", "symbols": [{"literal":"("}, "HM_BLACK", "MOVES", "_", {"literal":")"}], "postprocess":
262
+ (d) => [d[1], ...d[2]]
263
+ },
245
264
  {"name": "SAN$ebnf$1", "symbols": ["piece"], "postprocess": id},
246
265
  {"name": "SAN$ebnf$1", "symbols": [], "postprocess": function(d) {return null;}},
247
266
  {"name": "SAN$ebnf$2", "symbols": ["DISAMBIGUATION"], "postprocess": id},
248
267
  {"name": "SAN$ebnf$2", "symbols": [], "postprocess": function(d) {return null;}},
249
268
  {"name": "SAN$ebnf$3", "symbols": ["capture"], "postprocess": id},
250
269
  {"name": "SAN$ebnf$3", "symbols": [], "postprocess": function(d) {return null;}},
251
- {"name": "SAN$ebnf$4", "symbols": ["promotion"], "postprocess": id},
270
+ {"name": "SAN$ebnf$4", "symbols": ["PROMOTION"], "postprocess": id},
252
271
  {"name": "SAN$ebnf$4", "symbols": [], "postprocess": function(d) {return null;}},
253
- {"name": "SAN$ebnf$5", "symbols": []},
254
- {"name": "SAN$ebnf$5$subexpression$1", "symbols": ["_", "annotation"]},
255
- {"name": "SAN$ebnf$5", "symbols": ["SAN$ebnf$5", "SAN$ebnf$5$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
272
+ {"name": "SAN$ebnf$5$subexpression$1", "symbols": ["_", "SUFFIX"]},
273
+ {"name": "SAN$ebnf$5", "symbols": ["SAN$ebnf$5$subexpression$1"], "postprocess": id},
274
+ {"name": "SAN$ebnf$5", "symbols": [], "postprocess": function(d) {return null;}},
275
+ {"name": "SAN$ebnf$6", "symbols": []},
256
276
  {"name": "SAN$ebnf$6$subexpression$1", "symbols": ["_", "COMMENT"]},
257
- {"name": "SAN$ebnf$6", "symbols": ["SAN$ebnf$6$subexpression$1"], "postprocess": id},
258
- {"name": "SAN$ebnf$6", "symbols": [], "postprocess": function(d) {return null;}},
277
+ {"name": "SAN$ebnf$6", "symbols": ["SAN$ebnf$6", "SAN$ebnf$6$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
259
278
  {"name": "SAN", "symbols": ["SAN$ebnf$1", "SAN$ebnf$2", "SAN$ebnf$3", "file", "rank", "SAN$ebnf$4", "SAN$ebnf$5", "SAN$ebnf$6"], "postprocess":
260
- (d) => ({
261
- ...(d[6].length > 0 && { annotations: d[6].map(([_, a]) => a) }),
262
- ...(d[7] && { comment: d[7][1] }),
263
- ...(d[1] && { from: d[1][0] }),
264
- piece: d[0] ? d[0][0] : 'P',
265
- ...(d[5] && { promotion: d[5] }),
266
- to: `${d[3]}${d[4]}`,
267
- })
279
+ (d) => {
280
+ const comments = d[7].map(d7 => d7[1]).filter(Boolean);
281
+
282
+ return ({
283
+ ...(d[6] && d[6][1]),
284
+ ...(comments.length > 0 && { comment: comments.reduce((acc, item) => `${acc} ${item}`, '') }),
285
+ ...(d[2] && { capture: true }),
286
+ ...(d[1] && { from: d[1][0] }),
287
+ piece: d[0] ? d[0][0] : 'P',
288
+ ...(d[5] && { promotion: d[5] }),
289
+ to: `${d[3]}${d[4]}`,
290
+ });
291
+ }
268
292
  },
269
293
  {"name": "SAN$ebnf$7", "symbols": []},
270
- {"name": "SAN$ebnf$7$subexpression$1", "symbols": ["_", "annotation"]},
294
+ {"name": "SAN$ebnf$7$subexpression$1", "symbols": ["_", "SUFFIX"]},
271
295
  {"name": "SAN$ebnf$7", "symbols": ["SAN$ebnf$7", "SAN$ebnf$7$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
296
+ {"name": "SAN$ebnf$8", "symbols": []},
272
297
  {"name": "SAN$ebnf$8$subexpression$1", "symbols": ["_", "COMMENT"]},
273
- {"name": "SAN$ebnf$8", "symbols": ["SAN$ebnf$8$subexpression$1"], "postprocess": id},
274
- {"name": "SAN$ebnf$8", "symbols": [], "postprocess": function(d) {return null;}},
298
+ {"name": "SAN$ebnf$8", "symbols": ["SAN$ebnf$8", "SAN$ebnf$8$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
275
299
  {"name": "SAN", "symbols": ["castling", "SAN$ebnf$7", "SAN$ebnf$8"], "postprocess":
276
- (d) => ({
277
- ...(d[1].length > 0 && { annotations: d[1].map(([_, a]) => a) }),
278
- ...(d[2] && { comment: d[2][1] }),
279
- castling: d[0],
280
- piece: 'K'
281
- })
300
+ (d) => {
301
+ const comments = d[2].map(d2 => d2[1]).filter(Boolean);
302
+
303
+ return ({
304
+ ...(d[1]),
305
+ ...(comments.length > 0 && { comment: comments.reduce((acc, item) => `${acc} ${item}`, '') }),
306
+ castling: d[0],
307
+ piece: 'K'
308
+ })
309
+ }
282
310
  },
283
311
  {"name": "DISAMBIGUATION", "symbols": ["file"], "postprocess": id},
284
312
  {"name": "DISAMBIGUATION", "symbols": ["rank"], "postprocess": id},
313
+ {"name": "PROMOTION", "symbols": [{"literal":"="}, /[QBNR]/], "postprocess": (d) => d[1]},
314
+ {"name": "SUFFIX$subexpression$1", "symbols": ["check"]},
315
+ {"name": "SUFFIX$subexpression$1", "symbols": ["checkmate"]},
316
+ {"name": "SUFFIX$ebnf$1", "symbols": ["annotation"], "postprocess": id},
317
+ {"name": "SUFFIX$ebnf$1", "symbols": [], "postprocess": function(d) {return null;}},
318
+ {"name": "SUFFIX$ebnf$2", "symbols": []},
319
+ {"name": "SUFFIX$ebnf$2$subexpression$1", "symbols": ["_", "NAG"]},
320
+ {"name": "SUFFIX$ebnf$2", "symbols": ["SUFFIX$ebnf$2", "SUFFIX$ebnf$2$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
321
+ {"name": "SUFFIX", "symbols": ["SUFFIX$subexpression$1", "SUFFIX$ebnf$1", "SUFFIX$ebnf$2"], "postprocess":
322
+ (d) => ({
323
+ ...(d[0] && { [d[0]]: true }),
324
+ ...((d[1] || d[2].length > 0) && { annotations: [...(d[1] ? [d[1]] : []), ...d[2].map(d2 => d2[1])] }),
325
+ })
326
+ },
327
+ {"name": "SUFFIX$ebnf$3$subexpression$1", "symbols": ["check"]},
328
+ {"name": "SUFFIX$ebnf$3$subexpression$1", "symbols": ["checkmate"]},
329
+ {"name": "SUFFIX$ebnf$3", "symbols": ["SUFFIX$ebnf$3$subexpression$1"], "postprocess": id},
330
+ {"name": "SUFFIX$ebnf$3", "symbols": [], "postprocess": function(d) {return null;}},
331
+ {"name": "SUFFIX$ebnf$4", "symbols": []},
332
+ {"name": "SUFFIX$ebnf$4$subexpression$1", "symbols": ["_", "NAG"]},
333
+ {"name": "SUFFIX$ebnf$4", "symbols": ["SUFFIX$ebnf$4", "SUFFIX$ebnf$4$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
334
+ {"name": "SUFFIX", "symbols": ["SUFFIX$ebnf$3", "annotation", "SUFFIX$ebnf$4"], "postprocess":
335
+ (d) => ({
336
+ ...(d[0] && { [d[0]]: true }),
337
+ ...((d[1] || d[2].length > 0) && { annotations: [...(d[1] ? [d[1]] : []), ...d[2].map(d2 => d2[1])] }),
338
+ })
339
+ },
340
+ {"name": "SUFFIX$ebnf$5$subexpression$1", "symbols": ["check"]},
341
+ {"name": "SUFFIX$ebnf$5$subexpression$1", "symbols": ["checkmate"]},
342
+ {"name": "SUFFIX$ebnf$5", "symbols": ["SUFFIX$ebnf$5$subexpression$1"], "postprocess": id},
343
+ {"name": "SUFFIX$ebnf$5", "symbols": [], "postprocess": function(d) {return null;}},
344
+ {"name": "SUFFIX$ebnf$6", "symbols": ["annotation"], "postprocess": id},
345
+ {"name": "SUFFIX$ebnf$6", "symbols": [], "postprocess": function(d) {return null;}},
346
+ {"name": "SUFFIX$ebnf$7$subexpression$1", "symbols": ["_", "NAG"]},
347
+ {"name": "SUFFIX$ebnf$7", "symbols": ["SUFFIX$ebnf$7$subexpression$1"]},
348
+ {"name": "SUFFIX$ebnf$7$subexpression$2", "symbols": ["_", "NAG"]},
349
+ {"name": "SUFFIX$ebnf$7", "symbols": ["SUFFIX$ebnf$7", "SUFFIX$ebnf$7$subexpression$2"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
350
+ {"name": "SUFFIX", "symbols": ["SUFFIX$ebnf$5", "SUFFIX$ebnf$6", "SUFFIX$ebnf$7"], "postprocess":
351
+ (d) => ({
352
+ ...(d[0] && { [d[0]]: true }),
353
+ ...((d[1] || d[2].length > 0) && { annotations: [...(d[1] ? [d[1]] : []), ...d[2].map(d2 => d2[1])] }),
354
+ })
355
+ },
356
+ {"name": "NAG$subexpression$1$string$1", "symbols": [{"literal":"2"}, {"literal":"5"}], "postprocess": function joiner(d) {return d.join('');}},
357
+ {"name": "NAG$subexpression$1", "symbols": ["NAG$subexpression$1$string$1", /[0-5]/]},
358
+ {"name": "NAG$subexpression$1", "symbols": [{"literal":"2"}, /[0-4]/, /[0-9]/]},
359
+ {"name": "NAG$subexpression$1", "symbols": [{"literal":"1"}, /[0-9]/, /[0-9]/]},
360
+ {"name": "NAG$subexpression$1", "symbols": [/[1-9]/, /[0-9]/]},
361
+ {"name": "NAG$subexpression$1", "symbols": [/[0-9]/]},
362
+ {"name": "NAG", "symbols": [{"literal":"$"}, "NAG$subexpression$1"], "postprocess":
363
+ (d) => d[1]
364
+ },
285
365
  {"name": "COMMENT", "symbols": ["bstring"]},
366
+ {"name": "COMMENT$ebnf$1", "symbols": []},
367
+ {"name": "COMMENT$ebnf$1", "symbols": ["COMMENT$ebnf$1", /[^\n]/], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
368
+ {"name": "COMMENT", "symbols": [{"literal":";"}, "COMMENT$ebnf$1"]},
286
369
  {"name": "annotation$string$1", "symbols": [{"literal":"!"}, {"literal":"!"}], "postprocess": function joiner(d) {return d.join('');}},
287
370
  {"name": "annotation", "symbols": ["annotation$string$1"], "postprocess": id},
288
371
  {"name": "annotation", "symbols": [{"literal":"!"}], "postprocess": id},
289
372
  {"name": "annotation$string$2", "symbols": [{"literal":"!"}, {"literal":"?"}], "postprocess": function joiner(d) {return d.join('');}},
290
373
  {"name": "annotation", "symbols": ["annotation$string$2"], "postprocess": id},
291
- {"name": "annotation", "symbols": [{"literal":"#"}], "postprocess": () => 'checkmate'},
292
- {"name": "annotation$string$3", "symbols": [{"literal":"$"}, {"literal":"1"}], "postprocess": function joiner(d) {return d.join('');}},
293
- {"name": "annotation", "symbols": ["annotation$string$3"], "postprocess": () => '!'},
294
- {"name": "annotation$string$4", "symbols": [{"literal":"$"}, {"literal":"2"}], "postprocess": function joiner(d) {return d.join('');}},
295
- {"name": "annotation", "symbols": ["annotation$string$4"], "postprocess": () => '?'},
296
- {"name": "annotation$string$5", "symbols": [{"literal":"$"}, {"literal":"3"}], "postprocess": function joiner(d) {return d.join('');}},
297
- {"name": "annotation", "symbols": ["annotation$string$5"], "postprocess": () => '!!'},
298
- {"name": "annotation$string$6", "symbols": [{"literal":"$"}, {"literal":"4"}], "postprocess": function joiner(d) {return d.join('');}},
299
- {"name": "annotation", "symbols": ["annotation$string$6"], "postprocess": () => '!?'},
300
- {"name": "annotation$string$7", "symbols": [{"literal":"$"}, {"literal":"5"}], "postprocess": function joiner(d) {return d.join('');}},
301
- {"name": "annotation", "symbols": ["annotation$string$7"], "postprocess": () => '?!'},
302
- {"name": "annotation$string$8", "symbols": [{"literal":"$"}, {"literal":"6"}], "postprocess": function joiner(d) {return d.join('');}},
303
- {"name": "annotation", "symbols": ["annotation$string$8"], "postprocess": () => '??'},
304
- {"name": "annotation", "symbols": [{"literal":"+"}], "postprocess": () => 'check'},
305
- {"name": "annotation$string$9", "symbols": [{"literal":"+"}, {"literal":"-"}], "postprocess": function joiner(d) {return d.join('');}},
306
- {"name": "annotation", "symbols": ["annotation$string$9"], "postprocess": id},
307
- {"name": "annotation$string$10", "symbols": [{"literal":"-"}, {"literal":"+"}], "postprocess": function joiner(d) {return d.join('');}},
308
- {"name": "annotation", "symbols": ["annotation$string$10"], "postprocess": id},
374
+ {"name": "annotation$string$3", "symbols": [{"literal":"?"}, {"literal":"!"}], "postprocess": function joiner(d) {return d.join('');}},
375
+ {"name": "annotation", "symbols": ["annotation$string$3"], "postprocess": id},
376
+ {"name": "annotation", "symbols": [{"literal":"?"}], "postprocess": id},
377
+ {"name": "annotation$string$4", "symbols": [{"literal":"?"}, {"literal":"?"}], "postprocess": function joiner(d) {return d.join('');}},
378
+ {"name": "annotation", "symbols": ["annotation$string$4"], "postprocess": id},
379
+ {"name": "annotation$string$5", "symbols": [{"literal":"+"}, {"literal":"-"}], "postprocess": function joiner(d) {return d.join('');}},
380
+ {"name": "annotation", "symbols": ["annotation$string$5"], "postprocess": id},
381
+ {"name": "annotation$string$6", "symbols": [{"literal":"-"}, {"literal":"+"}], "postprocess": function joiner(d) {return d.join('');}},
382
+ {"name": "annotation", "symbols": ["annotation$string$6"], "postprocess": id},
309
383
  {"name": "annotation", "symbols": [{"literal":"="}], "postprocess": id},
310
- {"name": "annotation$string$11", "symbols": [{"literal":"?"}, {"literal":"!"}], "postprocess": function joiner(d) {return d.join('');}},
311
- {"name": "annotation", "symbols": ["annotation$string$11"], "postprocess": id},
384
+ {"name": "annotation$string$7", "symbols": [{"literal":"?"}, {"literal":"!"}], "postprocess": function joiner(d) {return d.join('');}},
385
+ {"name": "annotation", "symbols": ["annotation$string$7"], "postprocess": id},
312
386
  {"name": "annotation", "symbols": [{"literal":"?"}], "postprocess": id},
313
- {"name": "annotation$string$12", "symbols": [{"literal":"?"}, {"literal":"?"}], "postprocess": function joiner(d) {return d.join('');}},
314
- {"name": "annotation", "symbols": ["annotation$string$12"], "postprocess": id},
315
- {"name": "annotation", "symbols": [{"literal":"N"}], "postprocess": id},
387
+ {"name": "annotation$string$8", "symbols": [{"literal":"?"}, {"literal":"?"}], "postprocess": function joiner(d) {return d.join('');}},
388
+ {"name": "annotation", "symbols": ["annotation$string$8"], "postprocess": id},
316
389
  {"name": "annotation", "symbols": [{"literal":"±"}], "postprocess": id},
317
390
  {"name": "annotation", "symbols": [{"literal":"∓"}], "postprocess": id},
318
391
  {"name": "annotation", "symbols": [{"literal":"∞"}], "postprocess": id},
319
392
  {"name": "annotation", "symbols": [{"literal":"⨀"}], "postprocess": id},
320
393
  {"name": "annotation", "symbols": [{"literal":"⩱"}], "postprocess": id},
321
394
  {"name": "annotation", "symbols": [{"literal":"⩲"}], "postprocess": id},
395
+ {"name": "check", "symbols": [{"literal":"+"}], "postprocess": () => 'check'},
396
+ {"name": "checkmate", "symbols": [{"literal":"#"}], "postprocess": () => 'checkmate'},
322
397
  {"name": "bstring$ebnf$1", "symbols": []},
323
398
  {"name": "bstring$ebnf$1", "symbols": ["bstring$ebnf$1", /[^}]/], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
324
399
  {"name": "bstring", "symbols": [{"literal":"{"}, "bstring$ebnf$1", {"literal":"}"}], "postprocess": (d) => d[1].join('')},
@@ -331,7 +406,6 @@ var grammar = {
331
406
  {"name": "continuation", "symbols": ["continuation$string$1"]},
332
407
  {"name": "file", "symbols": [/[a-h]/]},
333
408
  {"name": "piece", "symbols": [/[KQBNR]/]},
334
- {"name": "promotion", "symbols": [{"literal":"="}, /[QBNR]/], "postprocess": (d) => d[1]},
335
409
  {"name": "rank", "symbols": [/[1-8]/]},
336
410
  {"name": "result$string$1", "symbols": [{"literal":"1"}, {"literal":"-"}, {"literal":"0"}], "postprocess": function joiner(d) {return d.join('');}},
337
411
  {"name": "result", "symbols": ["result$string$1"], "postprocess": () => 1},
@@ -0,0 +1,33 @@
1
+ type File = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h';
2
+ type Piece = 'B' | 'K' | 'N' | 'P' | 'Q' | 'R';
3
+ type Rank = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8';
4
+ type Result = '1-0' | '0-1' | '1/2-1/2' | '?';
5
+ type Square = `${File}${Rank}`;
6
+ type HalfMove = [number, undefined, Move];
7
+ type VariantMove = Move | HalfMove;
8
+ type VariantMoves = VariantMove[];
9
+ type Move = {
10
+ annotations?: string[];
11
+ capture?: boolean;
12
+ castling?: boolean;
13
+ check?: boolean;
14
+ checkmate?: boolean;
15
+ comment?: string;
16
+ from?: File | Rank;
17
+ piece: Piece;
18
+ promotion?: Piece;
19
+ to: Square;
20
+ variants?: VariantMoves[];
21
+ };
22
+ type Meta = {
23
+ Result: Result;
24
+ [key: string]: string;
25
+ };
26
+ type Moves = [number, Move] | [number, Move, Move];
27
+ type PGN = {
28
+ meta: Meta;
29
+ moves: Moves;
30
+ result: Result;
31
+ };
32
+ export default function parse(input: string): PGN[];
33
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ import { Grammar, Parser } from 'nearley';
2
+ import grammar from './grammar';
3
+ function tokenize(input) {
4
+ const parser = new Parser(Grammar.fromCompiled(grammar));
5
+ parser.feed(input);
6
+ if (parser.results.length > 1) {
7
+ throw new Error(`@echecs/parser: Ambiguous syntax. Found ${parser.results.length} results`);
8
+ }
9
+ return parser.results[0];
10
+ }
11
+ export default function parse(input) {
12
+ const games = input.replace(/[\r\uFEFF]/g, '');
13
+ return tokenize(games);
14
+ }
package/jest.config.js ADDED
@@ -0,0 +1,5 @@
1
+ /** @type {import('ts-jest').JestConfigWithTsJest} */
2
+ module.exports = {
3
+ preset: 'ts-jest',
4
+ testEnvironment: 'node',
5
+ };