pgn2 1.0.0 → 1.1.0
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.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +103 -0
- data/.gitignore +2 -1
- data/CHANGELOG.md +32 -0
- data/README.md +33 -15
- data/TODO.md +9 -0
- data/bench/IMPROVEMENTS.md +68 -0
- data/bench/baseline_moves.pre-quickwins.txt +23 -0
- data/bench/baseline_moves.txt +6 -6
- data/bench/baseline_parse.pre-quickwins.txt +26 -0
- data/bench/baseline_parse.txt +6 -6
- data/docs/superpowers/specs/2026-08-13-pgn-performance-quick-wins-design.md +227 -0
- data/lib/pgn/game.rb +15 -4
- data/lib/pgn/lexer.rb +60 -38
- data/lib/pgn/move.rb +5 -2
- data/lib/pgn/move_calculator.rb +9 -4
- data/lib/pgn/parser.rb +5 -15
- data/lib/pgn/pgn_parser.rb +79 -78
- data/lib/pgn/pgn_parser.y +20 -22
- data/lib/pgn/position.rb +3 -2
- data/lib/pgn/version.rb +1 -1
- data/spec/parser_spec.rb +6 -38
- metadata +5 -1
data/lib/pgn/pgn_parser.rb
CHANGED
|
@@ -8,7 +8,7 @@ require 'racc/parser.rb'
|
|
|
8
8
|
module PGN
|
|
9
9
|
class PgnParser < Racc::Parser
|
|
10
10
|
|
|
11
|
-
module_eval(<<'...end pgn_parser.y/module_eval...', 'pgn_parser.y',
|
|
11
|
+
module_eval(<<'...end pgn_parser.y/module_eval...', 'pgn_parser.y', 97)
|
|
12
12
|
|
|
13
13
|
def parse(input)
|
|
14
14
|
@lexer = PGN::Lexer.new(input)
|
|
@@ -19,29 +19,21 @@ module_eval(<<'...end pgn_parser.y/module_eval...', 'pgn_parser.y', 91)
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def next_token
|
|
22
|
-
|
|
23
|
-
return [false, false] unless
|
|
24
|
-
|
|
22
|
+
pair = @lexer.next_token_pair
|
|
23
|
+
return [false, false] unless pair
|
|
24
|
+
type, value = pair
|
|
25
|
+
[translate_type(type), value]
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
private
|
|
28
29
|
|
|
30
|
+
# Punctuation tokens are racc'd by literal; everything else is racc'd by
|
|
31
|
+
# the upcased lexer symbol (e.g. :san_move -> :SAN_MOVE), so there's a
|
|
32
|
+
# single source of truth for the token vocabulary: PGN::Lexer's rules.
|
|
33
|
+
LITERAL_TOKENS = { lbracket: '[', rbracket: ']', lparen: '(', rparen: ')' }.freeze
|
|
34
|
+
|
|
29
35
|
def translate_type(sym)
|
|
30
|
-
|
|
31
|
-
when :string then :STRING
|
|
32
|
-
when :comment then :COMMENT
|
|
33
|
-
when :game_termination then :GAME_TERMINATION
|
|
34
|
-
when :san_move then :SAN_MOVE
|
|
35
|
-
when :nag then :NAG
|
|
36
|
-
when :move_number then :MOVE_NUMBER
|
|
37
|
-
when :tag_name then :TAG_NAME
|
|
38
|
-
when :lbracket then '['
|
|
39
|
-
when :rbracket then ']'
|
|
40
|
-
when :lparen then '('
|
|
41
|
-
when :rparen then ')'
|
|
42
|
-
else
|
|
43
|
-
raise "PGN::Lexer produced an unknown token type: #{sym.inspect}"
|
|
44
|
-
end
|
|
36
|
+
LITERAL_TOKENS[sym] || sym.to_s.upcase.to_sym
|
|
45
37
|
end
|
|
46
38
|
|
|
47
39
|
# Slice the verbatim raw PGN text per game out of the original input using
|
|
@@ -65,42 +57,42 @@ module_eval(<<'...end pgn_parser.y/module_eval...', 'pgn_parser.y', 91)
|
|
|
65
57
|
##### State transition tables begin ###
|
|
66
58
|
|
|
67
59
|
racc_action_table = [
|
|
68
|
-
2, 16, 7, 17,
|
|
69
|
-
|
|
70
|
-
21,
|
|
60
|
+
2, 16, 7, 17, 23, 14, 6, 25, 11, 6,
|
|
61
|
+
32, 16, 12, 17, 30, 14, 18, 31, 21, 26,
|
|
62
|
+
21, 25, 31 ]
|
|
71
63
|
|
|
72
64
|
racc_action_check = [
|
|
73
|
-
1,
|
|
74
|
-
|
|
75
|
-
20,
|
|
65
|
+
1, 28, 2, 28, 17, 28, 5, 17, 6, 1,
|
|
66
|
+
28, 9, 9, 9, 24, 9, 11, 24, 15, 18,
|
|
67
|
+
20, 23, 29 ]
|
|
76
68
|
|
|
77
69
|
racc_action_pointer = [
|
|
78
70
|
nil, 0, 2, nil, nil, -3, 0, nil, nil, 8,
|
|
79
71
|
nil, 14, nil, nil, nil, 7, nil, 1, 9, nil,
|
|
80
|
-
9, nil, 15, 11, nil, nil, nil, -2, 16,
|
|
81
|
-
nil, nil ]
|
|
72
|
+
9, nil, nil, 15, 11, nil, nil, nil, -2, 16,
|
|
73
|
+
nil, nil, nil ]
|
|
82
74
|
|
|
83
75
|
racc_action_default = [
|
|
84
|
-
-1, -
|
|
85
|
-
-5, -
|
|
86
|
-
-
|
|
87
|
-
-
|
|
76
|
+
-1, -25, -25, -2, -8, -4, -25, 33, -3, -25,
|
|
77
|
+
-5, -25, -7, -9, -10, -11, -13, -14, -25, -12,
|
|
78
|
+
-22, -8, -15, -16, -17, -20, -6, -23, -25, -19,
|
|
79
|
+
-18, -21, -24 ]
|
|
88
80
|
|
|
89
81
|
racc_goto_table = [
|
|
90
|
-
9,
|
|
91
|
-
|
|
82
|
+
9, 24, 4, 19, 1, 3, 10, 29, 27, 8,
|
|
83
|
+
22, nil, nil, nil, nil, nil, nil, 28 ]
|
|
92
84
|
|
|
93
85
|
racc_goto_check = [
|
|
94
|
-
6, 9,
|
|
95
|
-
|
|
86
|
+
6, 11, 3, 9, 1, 2, 3, 11, 9, 4,
|
|
87
|
+
10, nil, nil, nil, nil, nil, nil, 6 ]
|
|
96
88
|
|
|
97
89
|
racc_goto_pointer = [
|
|
98
|
-
nil,
|
|
99
|
-
-
|
|
90
|
+
nil, 4, 4, 1, 5, nil, -4, nil, nil, -12,
|
|
91
|
+
-7, -16, nil ]
|
|
100
92
|
|
|
101
93
|
racc_goto_default = [
|
|
102
94
|
nil, nil, nil, nil, nil, 5, nil, 13, 15, nil,
|
|
103
|
-
nil, 20 ]
|
|
95
|
+
nil, nil, 20 ]
|
|
104
96
|
|
|
105
97
|
racc_reduce_table = [
|
|
106
98
|
0, 0, :racc_error,
|
|
@@ -119,18 +111,19 @@ racc_reduce_table = [
|
|
|
119
111
|
1, 20, :_reduce_13,
|
|
120
112
|
1, 21, :_reduce_14,
|
|
121
113
|
2, 21, :_reduce_15,
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
114
|
+
1, 23, :_reduce_16,
|
|
115
|
+
1, 23, :_reduce_17,
|
|
116
|
+
2, 23, :_reduce_18,
|
|
117
|
+
2, 23, :_reduce_19,
|
|
118
|
+
1, 24, :_reduce_20,
|
|
119
|
+
2, 24, :_reduce_21,
|
|
120
|
+
1, 22, :_reduce_22,
|
|
121
|
+
2, 22, :_reduce_23,
|
|
122
|
+
3, 25, :_reduce_24 ]
|
|
130
123
|
|
|
131
|
-
racc_reduce_n =
|
|
124
|
+
racc_reduce_n = 25
|
|
132
125
|
|
|
133
|
-
racc_shift_n =
|
|
126
|
+
racc_shift_n = 33
|
|
134
127
|
|
|
135
128
|
racc_token_table = {
|
|
136
129
|
false => 0,
|
|
@@ -192,6 +185,7 @@ Racc_token_to_s_table = [
|
|
|
192
185
|
"element",
|
|
193
186
|
"san_move_annotated",
|
|
194
187
|
"variation_list",
|
|
188
|
+
"move_trailer",
|
|
195
189
|
"annotation_list",
|
|
196
190
|
"variation" ]
|
|
197
191
|
Ractor.make_shareable(Racc_token_to_s_table) if defined?(Ractor)
|
|
@@ -202,21 +196,21 @@ Racc_debug_parser = false
|
|
|
202
196
|
|
|
203
197
|
# reduce 0 omitted
|
|
204
198
|
|
|
205
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
199
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 9)
|
|
206
200
|
def _reduce_1(val, _values, result)
|
|
207
201
|
result = []
|
|
208
202
|
result
|
|
209
203
|
end
|
|
210
204
|
.,.,
|
|
211
205
|
|
|
212
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
206
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 10)
|
|
213
207
|
def _reduce_2(val, _values, result)
|
|
214
208
|
result = val[0] << val[1]
|
|
215
209
|
result
|
|
216
210
|
end
|
|
217
211
|
.,.,
|
|
218
212
|
|
|
219
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
213
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 15)
|
|
220
214
|
def _reduce_3(val, _values, result)
|
|
221
215
|
result = val[1].pop
|
|
222
216
|
result = {
|
|
@@ -231,14 +225,14 @@ module_eval(<<'.,.,', 'pgn_parser.y', 13)
|
|
|
231
225
|
end
|
|
232
226
|
.,.,
|
|
233
227
|
|
|
234
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
228
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 26)
|
|
235
229
|
def _reduce_4(val, _values, result)
|
|
236
230
|
result = val[0]
|
|
237
231
|
result
|
|
238
232
|
end
|
|
239
233
|
.,.,
|
|
240
234
|
|
|
241
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
235
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 29)
|
|
242
236
|
def _reduce_5(val, _values, result)
|
|
243
237
|
# Right-recursive with section.merge(pair) reproduces the legacy
|
|
244
238
|
# whittle parser's tag semantics exactly: reverse source insertion
|
|
@@ -250,28 +244,28 @@ module_eval(<<'.,.,', 'pgn_parser.y', 27)
|
|
|
250
244
|
end
|
|
251
245
|
.,.,
|
|
252
246
|
|
|
253
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
247
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 37)
|
|
254
248
|
def _reduce_6(val, _values, result)
|
|
255
249
|
result = { val[1] => val[2][1...-1] }
|
|
256
250
|
result
|
|
257
251
|
end
|
|
258
252
|
.,.,
|
|
259
253
|
|
|
260
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
254
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 40)
|
|
261
255
|
def _reduce_7(val, _values, result)
|
|
262
256
|
result = val[0] << val[1]
|
|
263
257
|
result
|
|
264
258
|
end
|
|
265
259
|
.,.,
|
|
266
260
|
|
|
267
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
261
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 43)
|
|
268
262
|
def _reduce_8(val, _values, result)
|
|
269
263
|
result = []
|
|
270
264
|
result
|
|
271
265
|
end
|
|
272
266
|
.,.,
|
|
273
267
|
|
|
274
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
268
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 46)
|
|
275
269
|
def _reduce_9(val, _values, result)
|
|
276
270
|
result = val[1].nil? ? val[0] : val[0] << val[1]
|
|
277
271
|
|
|
@@ -279,7 +273,7 @@ module_eval(<<'.,.,', 'pgn_parser.y', 44)
|
|
|
279
273
|
end
|
|
280
274
|
.,.,
|
|
281
275
|
|
|
282
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
276
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 50)
|
|
283
277
|
def _reduce_10(val, _values, result)
|
|
284
278
|
result = nil
|
|
285
279
|
result
|
|
@@ -288,7 +282,7 @@ module_eval(<<'.,.,', 'pgn_parser.y', 48)
|
|
|
288
282
|
|
|
289
283
|
# reduce 11 omitted
|
|
290
284
|
|
|
291
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
285
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 54)
|
|
292
286
|
def _reduce_12(val, _values, result)
|
|
293
287
|
result = val[0]
|
|
294
288
|
result.variations = val[1]
|
|
@@ -298,7 +292,7 @@ module_eval(<<'.,.,', 'pgn_parser.y', 52)
|
|
|
298
292
|
end
|
|
299
293
|
.,.,
|
|
300
294
|
|
|
301
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
295
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 60)
|
|
302
296
|
def _reduce_13(val, _values, result)
|
|
303
297
|
# A standalone comment (not attached to a move) becomes the game
|
|
304
298
|
# comment; the last such comment in the game wins.
|
|
@@ -309,64 +303,71 @@ module_eval(<<'.,.,', 'pgn_parser.y', 58)
|
|
|
309
303
|
end
|
|
310
304
|
.,.,
|
|
311
305
|
|
|
312
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
306
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 67)
|
|
313
307
|
def _reduce_14(val, _values, result)
|
|
314
308
|
result = MoveText.new(val[0])
|
|
315
309
|
result
|
|
316
310
|
end
|
|
317
311
|
.,.,
|
|
318
312
|
|
|
319
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
313
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 68)
|
|
320
314
|
def _reduce_15(val, _values, result)
|
|
321
|
-
result = MoveText.new(val[0],
|
|
315
|
+
result = MoveText.new(val[0], *val[1])
|
|
322
316
|
result
|
|
323
317
|
end
|
|
324
318
|
.,.,
|
|
325
319
|
|
|
326
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
320
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 72)
|
|
327
321
|
def _reduce_16(val, _values, result)
|
|
328
|
-
result =
|
|
322
|
+
result = [nil, val[0]]
|
|
329
323
|
result
|
|
330
324
|
end
|
|
331
325
|
.,.,
|
|
332
326
|
|
|
333
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
327
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 73)
|
|
334
328
|
def _reduce_17(val, _values, result)
|
|
335
|
-
result =
|
|
329
|
+
result = [val[0], nil]
|
|
336
330
|
result
|
|
337
331
|
end
|
|
338
332
|
.,.,
|
|
339
333
|
|
|
340
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
334
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 74)
|
|
341
335
|
def _reduce_18(val, _values, result)
|
|
342
|
-
result =
|
|
336
|
+
result = [val[0], val[1]]
|
|
343
337
|
result
|
|
344
338
|
end
|
|
345
339
|
.,.,
|
|
346
340
|
|
|
347
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
341
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 75)
|
|
348
342
|
def _reduce_19(val, _values, result)
|
|
349
|
-
result = [val[0]]
|
|
343
|
+
result = [val[1], val[0]]
|
|
350
344
|
result
|
|
351
345
|
end
|
|
352
346
|
.,.,
|
|
353
347
|
|
|
354
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
348
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 78)
|
|
355
349
|
def _reduce_20(val, _values, result)
|
|
356
|
-
result = val[0]
|
|
350
|
+
result = [val[0]]
|
|
357
351
|
result
|
|
358
352
|
end
|
|
359
353
|
.,.,
|
|
360
354
|
|
|
361
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
355
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 79)
|
|
362
356
|
def _reduce_21(val, _values, result)
|
|
363
|
-
result =
|
|
357
|
+
result = val[0] << val[1]
|
|
364
358
|
result
|
|
365
359
|
end
|
|
366
360
|
.,.,
|
|
367
361
|
|
|
368
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
362
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 82)
|
|
369
363
|
def _reduce_22(val, _values, result)
|
|
364
|
+
result = [val[0]]
|
|
365
|
+
result
|
|
366
|
+
end
|
|
367
|
+
.,.,
|
|
368
|
+
|
|
369
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 85)
|
|
370
|
+
def _reduce_23(val, _values, result)
|
|
370
371
|
# Right-recursive prepend reproduces the legacy whittle parser's
|
|
371
372
|
# variation-order reversal on every parse. This keeps parsed-game
|
|
372
373
|
# serialization byte-compatible with the legacy behavior; the quirk
|
|
@@ -377,8 +378,8 @@ module_eval(<<'.,.,', 'pgn_parser.y', 79)
|
|
|
377
378
|
end
|
|
378
379
|
.,.,
|
|
379
380
|
|
|
380
|
-
module_eval(<<'.,.,', 'pgn_parser.y',
|
|
381
|
-
def
|
|
381
|
+
module_eval(<<'.,.,', 'pgn_parser.y', 93)
|
|
382
|
+
def _reduce_24(val, _values, result)
|
|
382
383
|
result = val[1]
|
|
383
384
|
result
|
|
384
385
|
end
|
data/lib/pgn/pgn_parser.y
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
class PGN::PgnParser
|
|
2
4
|
|
|
3
5
|
token STRING COMMENT GAME_TERMINATION SAN_MOVE NAG MOVE_NUMBER TAG_NAME
|
|
@@ -64,10 +66,14 @@ rule
|
|
|
64
66
|
|
|
65
67
|
san_move_annotated:
|
|
66
68
|
SAN_MOVE { result = MoveText.new(val[0]) }
|
|
67
|
-
| SAN_MOVE
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
| SAN_MOVE move_trailer { result = MoveText.new(val[0], *val[1]) }
|
|
70
|
+
|
|
71
|
+
# [annotation_list, comment], in whichever order they followed the move.
|
|
72
|
+
move_trailer:
|
|
73
|
+
COMMENT { result = [nil, val[0]] }
|
|
74
|
+
| annotation_list { result = [val[0], nil] }
|
|
75
|
+
| annotation_list COMMENT { result = [val[0], val[1]] }
|
|
76
|
+
| COMMENT annotation_list { result = [val[1], val[0]] }
|
|
71
77
|
|
|
72
78
|
annotation_list:
|
|
73
79
|
NAG { result = [val[0]] }
|
|
@@ -98,29 +104,21 @@ rule
|
|
|
98
104
|
end
|
|
99
105
|
|
|
100
106
|
def next_token
|
|
101
|
-
|
|
102
|
-
return [false, false] unless
|
|
103
|
-
|
|
107
|
+
pair = @lexer.next_token_pair
|
|
108
|
+
return [false, false] unless pair
|
|
109
|
+
type, value = pair
|
|
110
|
+
[translate_type(type), value]
|
|
104
111
|
end
|
|
105
112
|
|
|
106
113
|
private
|
|
107
114
|
|
|
115
|
+
# Punctuation tokens are racc'd by literal; everything else is racc'd by
|
|
116
|
+
# the upcased lexer symbol (e.g. :san_move -> :SAN_MOVE), so there's a
|
|
117
|
+
# single source of truth for the token vocabulary: PGN::Lexer's rules.
|
|
118
|
+
LITERAL_TOKENS = { lbracket: '[', rbracket: ']', lparen: '(', rparen: ')' }.freeze
|
|
119
|
+
|
|
108
120
|
def translate_type(sym)
|
|
109
|
-
|
|
110
|
-
when :string then :STRING
|
|
111
|
-
when :comment then :COMMENT
|
|
112
|
-
when :game_termination then :GAME_TERMINATION
|
|
113
|
-
when :san_move then :SAN_MOVE
|
|
114
|
-
when :nag then :NAG
|
|
115
|
-
when :move_number then :MOVE_NUMBER
|
|
116
|
-
when :tag_name then :TAG_NAME
|
|
117
|
-
when :lbracket then '['
|
|
118
|
-
when :rbracket then ']'
|
|
119
|
-
when :lparen then '('
|
|
120
|
-
when :rparen then ')'
|
|
121
|
-
else
|
|
122
|
-
raise "PGN::Lexer produced an unknown token type: #{sym.inspect}"
|
|
123
|
-
end
|
|
121
|
+
LITERAL_TOKENS[sym] || sym.to_s.upcase.to_sym
|
|
124
122
|
end
|
|
125
123
|
|
|
126
124
|
# Slice the verbatim raw PGN text per game out of the original input using
|
data/lib/pgn/position.rb
CHANGED
|
@@ -81,7 +81,8 @@ module PGN
|
|
|
81
81
|
move = PGN::Move.new(str, player)
|
|
82
82
|
calculator = PGN::MoveCalculator.new(board, move)
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
restrictions = calculator.castling_restrictions
|
|
85
|
+
new_castling = restrictions.empty? ? castling : castling - restrictions
|
|
85
86
|
new_halfmove = if calculator.increment_halfmove?
|
|
86
87
|
halfmove + 1
|
|
87
88
|
else
|
|
@@ -106,7 +107,7 @@ module PGN
|
|
|
106
107
|
# @return [Symbol] the next player to move
|
|
107
108
|
#
|
|
108
109
|
def next_player
|
|
109
|
-
|
|
110
|
+
player == :white ? :black : :white
|
|
110
111
|
end
|
|
111
112
|
|
|
112
113
|
def inspect
|
data/lib/pgn/version.rb
CHANGED
data/spec/parser_spec.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'spec_helper'
|
|
2
4
|
require 'pry'
|
|
3
5
|
|
|
@@ -25,7 +27,7 @@ describe PGN do
|
|
|
25
27
|
games.each do |game|
|
|
26
28
|
expect(game.tags['White']).to eq 'Fool'
|
|
27
29
|
expect(game.result).to eq '0-1'
|
|
28
|
-
expect(game.moves.last).to eq
|
|
30
|
+
expect(game.moves.last).to eq 'Qh4#'
|
|
29
31
|
end
|
|
30
32
|
end
|
|
31
33
|
|
|
@@ -39,7 +41,7 @@ describe PGN do
|
|
|
39
41
|
game = games.first
|
|
40
42
|
expect(game.tags['White']).to eq 'Scholar'
|
|
41
43
|
expect(game.result).to eq '1-0'
|
|
42
|
-
expect(game.moves.last).to eq
|
|
44
|
+
expect(game.moves.last).to eq 'Qxf7#'
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
it 'should handle multiline comments' do
|
|
@@ -47,7 +49,7 @@ describe PGN do
|
|
|
47
49
|
game = games.first
|
|
48
50
|
expect(game.tags['White']).to eq 'Scholar'
|
|
49
51
|
expect(game.result).to eq '1-0'
|
|
50
|
-
expect(game.moves.last).to eq
|
|
52
|
+
expect(game.moves.last).to eq 'Qxf7#'
|
|
51
53
|
end
|
|
52
54
|
|
|
53
55
|
it 'should handle nested comments' do
|
|
@@ -57,12 +59,6 @@ describe PGN do
|
|
|
57
59
|
expect(game.moves.last).to eq 'Nf6'
|
|
58
60
|
end
|
|
59
61
|
|
|
60
|
-
it 'handles two annotations' do
|
|
61
|
-
games = PGN.parse(File.read('./spec/pgn_files/two_annotations.pgn'))
|
|
62
|
-
game = games.first
|
|
63
|
-
expect(game.moves[1].annotation).to eq ['$2', '$11']
|
|
64
|
-
end
|
|
65
|
-
|
|
66
62
|
it 'returns empty array when no variations' do
|
|
67
63
|
games = PGN.parse(File.read('./spec/pgn_files/variations.pgn'))
|
|
68
64
|
game = games.first
|
|
@@ -84,13 +80,6 @@ describe PGN do
|
|
|
84
80
|
expect(game.moves[-2].variations.first).to eq %w[f4 exf4]
|
|
85
81
|
end
|
|
86
82
|
|
|
87
|
-
it 'should handle empty variation moves' do
|
|
88
|
-
games = PGN.parse(File.read('./spec/pgn_files/empty_variation_move.pgn'))
|
|
89
|
-
game = games.first
|
|
90
|
-
expect(game.result).to eq '*'
|
|
91
|
-
expect(game.moves.last).to eq 'Ng5'
|
|
92
|
-
end
|
|
93
|
-
|
|
94
83
|
it 'should handle complex files' do
|
|
95
84
|
games = PGN.parse(File.read('./spec/pgn_files/test.pgn'))
|
|
96
85
|
game = games.first
|
|
@@ -103,7 +92,7 @@ describe PGN do
|
|
|
103
92
|
expect(game.moves[35].variations.size).to eq 1
|
|
104
93
|
variation = game.moves[35].variations[0]
|
|
105
94
|
expect(variation.size).to eq 2
|
|
106
|
-
expect(variation[0]).to eq
|
|
95
|
+
expect(variation[0]).to eq 'exf3'
|
|
107
96
|
end
|
|
108
97
|
|
|
109
98
|
it 'should handle files with starting position' do
|
|
@@ -133,26 +122,5 @@ describe PGN do
|
|
|
133
122
|
expect { game.positions }.not_to raise_error
|
|
134
123
|
expect(game.moves).not_to be_empty
|
|
135
124
|
end
|
|
136
|
-
|
|
137
|
-
it 'gets game comment' do
|
|
138
|
-
games = PGN.parse(File.read('./spec/pgn_files/no_moves.pgn'))
|
|
139
|
-
game = games.last
|
|
140
|
-
expect(game.comment).to eq('{game comment}')
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
it 'tag_values with double quotes' do
|
|
144
|
-
games = PGN.parse(File.read('./spec/pgn_files/doublequotes.pgn'))
|
|
145
|
-
game = games.last
|
|
146
|
-
expect(game.tags['Event']).to eq('IRT BLITZ "Sub Zonal"')
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
it 'set encoding' do
|
|
150
|
-
file = File.read('./spec/pgn_files/specialcharacters.pgn')
|
|
151
|
-
games = PGN.parse(file, Encoding::UTF_8)
|
|
152
|
-
game1 = games.first
|
|
153
|
-
game2 = games.last
|
|
154
|
-
expect(game1.tags['WhiteTeam']).to eq('NARIÑO')
|
|
155
|
-
expect(game2.tags['Site']).to eq('HOTEL CAFEIRA Calle 18 Nro. 5 – 38 Centro de Pereira – Risaralda')
|
|
156
|
-
end
|
|
157
125
|
end
|
|
158
126
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pgn2
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stacey Touset
|
|
@@ -133,6 +133,7 @@ extra_rdoc_files: []
|
|
|
133
133
|
files:
|
|
134
134
|
- ".github/workflows/ci.yml"
|
|
135
135
|
- ".github/workflows/publish.yml"
|
|
136
|
+
- ".github/workflows/release.yml"
|
|
136
137
|
- ".gitignore"
|
|
137
138
|
- ".rspec"
|
|
138
139
|
- ".rubocop.yml"
|
|
@@ -145,8 +146,10 @@ files:
|
|
|
145
146
|
- bench/.keep
|
|
146
147
|
- bench/IMPROVEMENTS.md
|
|
147
148
|
- bench/baseline_moves.pre-optimization.txt
|
|
149
|
+
- bench/baseline_moves.pre-quickwins.txt
|
|
148
150
|
- bench/baseline_moves.txt
|
|
149
151
|
- bench/baseline_parse.pre-optimization.txt
|
|
152
|
+
- bench/baseline_parse.pre-quickwins.txt
|
|
150
153
|
- bench/baseline_parse.racc.txt
|
|
151
154
|
- bench/baseline_parse.txt
|
|
152
155
|
- bench/profile_moves.rb
|
|
@@ -156,6 +159,7 @@ files:
|
|
|
156
159
|
- docs/superpowers/plans/2026-08-12-to-pgn-serialization.md
|
|
157
160
|
- docs/superpowers/plans/2026-08-13-whittle-to-racc-migration.md
|
|
158
161
|
- docs/superpowers/specs/2026-08-12-to-pgn-serialization-design.md
|
|
162
|
+
- docs/superpowers/specs/2026-08-13-pgn-performance-quick-wins-design.md
|
|
159
163
|
- examples/immortal_game.pgn
|
|
160
164
|
- lib/pgn.rb
|
|
161
165
|
- lib/pgn/board.rb
|