pgn2 0.4.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +50 -0
  3. data/.github/workflows/publish.yml +75 -0
  4. data/.github/workflows/release.yml +103 -0
  5. data/.gitignore +2 -1
  6. data/.rubocop.yml +38 -0
  7. data/CHANGELOG.md +84 -0
  8. data/README.md +122 -4
  9. data/Rakefile +20 -0
  10. data/TODO.md +9 -0
  11. data/bench/.keep +0 -0
  12. data/bench/IMPROVEMENTS.md +143 -0
  13. data/bench/baseline_moves.pre-optimization.txt +22 -0
  14. data/bench/baseline_moves.pre-quickwins.txt +23 -0
  15. data/bench/baseline_moves.txt +22 -0
  16. data/bench/baseline_parse.pre-optimization.txt +25 -0
  17. data/bench/baseline_parse.pre-quickwins.txt +26 -0
  18. data/bench/baseline_parse.racc.txt +25 -0
  19. data/bench/baseline_parse.txt +25 -0
  20. data/bench/profile_moves.rb +53 -0
  21. data/bench/profile_parse.rb +44 -0
  22. data/docs/superpowers/plans/2026-08-12-efficiency-optimizations.md +573 -0
  23. data/docs/superpowers/plans/2026-08-12-efficiency-tests-and-profiling.md +1091 -0
  24. data/docs/superpowers/plans/2026-08-12-to-pgn-serialization.md +162 -0
  25. data/docs/superpowers/plans/2026-08-13-whittle-to-racc-migration.md +130 -0
  26. data/docs/superpowers/specs/2026-08-12-to-pgn-serialization-design.md +217 -0
  27. data/docs/superpowers/specs/2026-08-13-pgn-performance-quick-wins-design.md +227 -0
  28. data/lib/pgn/board.rb +33 -15
  29. data/lib/pgn/fen.rb +16 -8
  30. data/lib/pgn/game.rb +23 -3
  31. data/lib/pgn/lexer.rb +223 -0
  32. data/lib/pgn/move.rb +12 -5
  33. data/lib/pgn/move_calculator.rb +27 -21
  34. data/lib/pgn/parser.rb +13 -203
  35. data/lib/pgn/pgn_parser.rb +393 -0
  36. data/lib/pgn/pgn_parser.y +140 -0
  37. data/lib/pgn/position.rb +3 -2
  38. data/lib/pgn/serializer.rb +141 -0
  39. data/lib/pgn/version.rb +1 -1
  40. data/lib/pgn.rb +3 -0
  41. data/pgn2.gemspec +12 -2
  42. data/spec/board_spec.rb +111 -0
  43. data/spec/fen_spec.rb +25 -0
  44. data/spec/game_spec.rb +74 -0
  45. data/spec/lexer_spec.rb +153 -0
  46. data/spec/move_calculator_spec.rb +226 -0
  47. data/spec/move_spec.rb +136 -0
  48. data/spec/parser_explicit_spec.rb +210 -0
  49. data/spec/parser_spec.rb +6 -23
  50. data/spec/pgn_files/doublequotes.pgn +21 -0
  51. data/spec/pgn_files/specialcharacters.pgn +79 -0
  52. data/spec/position_spec.rb +73 -0
  53. data/spec/serializer_spec.rb +89 -0
  54. data/spec/spec_helper.rb +0 -1
  55. metadata +103 -15
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+
3
+ describe PGN::Serializer do
4
+ describe '#to_s' do
5
+ it 'serializes a simple game with tags and result' do
6
+ game = PGN::Game.new(%w[e4 e5], { 'White' => 'A', 'Black' => 'B' }, '1-0')
7
+ expected = "[White \"A\"]\n[Black \"B\"]\n\n1. e4 e5 1-0\n"
8
+ expect(PGN::Serializer.new(game).to_s).to eq(expected)
9
+ end
10
+
11
+ it 'synthesizes a Result tag when there are no tags' do
12
+ game = PGN::Game.new(%w[e4 e5])
13
+ expected = "[Result \"*\"]\n\n1. e4 e5 *\n"
14
+ expect(PGN::Serializer.new(game).to_s).to eq(expected)
15
+ end
16
+
17
+ it 'serializes an empty game as just the result' do
18
+ game = PGN::Game.new([], nil, '*')
19
+ expected = "[Result \"*\"]\n\n*\n"
20
+ expect(PGN::Serializer.new(game).to_s).to eq(expected)
21
+ end
22
+
23
+ it 'emits * when there is no result' do
24
+ game = PGN::Game.new(%w[e4 e5], { 'White' => 'A' })
25
+ expect(PGN::Serializer.new(game).to_s).to end_with("1. e4 e5 *\n")
26
+ end
27
+
28
+ it 'escapes backslashes and quotes in tag values' do
29
+ game = PGN::Game.new(%w[e4], { 'White' => 'A "B" \\C' }, '1-0')
30
+ expect(PGN::Serializer.new(game).to_s).to start_with("[White \"A \\\"B\\\" \\\\C\"]\n")
31
+ end
32
+
33
+ it 'serializes castling as O-O / O-O-O' do
34
+ game = PGN::Game.new(%w[O-O O-O-O])
35
+ expect(PGN::Serializer.new(game).to_s).to eq("[Result \"*\"]\n\n1. O-O O-O-O *\n")
36
+ end
37
+
38
+ it 'emits a single annotation after the notation' do
39
+ moves = [PGN::MoveText.new('e4', ['$4'])]
40
+ game = PGN::Game.new(moves, { 'White' => 'A' }, '1-0')
41
+ expect(PGN::Serializer.new(game).to_s).to eq("[White \"A\"]\n\n1. e4 $4 1-0\n")
42
+ end
43
+
44
+ it 'emits symbolic annotations verbatim' do
45
+ moves = [PGN::MoveText.new('e4', ['??'])]
46
+ game = PGN::Game.new(moves, { 'White' => 'A' }, '1-0')
47
+ expect(PGN::Serializer.new(game).to_s).to eq("[White \"A\"]\n\n1. e4 ?? 1-0\n")
48
+ end
49
+
50
+ it 'emits multiple annotations in order' do
51
+ moves = [PGN::MoveText.new('d4'), PGN::MoveText.new('d5', ['$2', '$11'])]
52
+ game = PGN::Game.new(moves, { 'White' => 'A' }, '1/2-1/2')
53
+ expect(PGN::Serializer.new(game).to_s).to eq("[White \"A\"]\n\n1. d4 d5 $2 $11 1/2-1/2\n")
54
+ end
55
+
56
+ it 'wraps move comments in braces with spaces' do
57
+ moves = [PGN::MoveText.new('e4', nil, 'good move')]
58
+ game = PGN::Game.new(moves, { 'White' => 'A' }, '1-0')
59
+ expect(PGN::Serializer.new(game).to_s).to eq("[White \"A\"]\n\n1. e4 { good move } 1-0\n")
60
+ end
61
+
62
+ it 'reproduces the variations.pgn movetext shape, including 2... after variations' do
63
+ game = PGN.parse(File.read('./spec/pgn_files/variations.pgn')).first
64
+ expected = "[Black \"Petrov\"]\n[White \"Somebody\"]\n\n" \
65
+ "1. e4 e5 2. Nf3 { comment } " \
66
+ "(2. f4 exf4 { final variation }) " \
67
+ "(2. Nc3 { other } 2... d5 (2... f5) 3. exd5) " \
68
+ "2... Nf6 *\n"
69
+ expect(PGN::Serializer.new(game).to_s).to eq(expected)
70
+ end
71
+
72
+ it 'emits a game comment as the first movetext token' do
73
+ game = PGN::Game.new([], nil, '*', nil, 'game comment')
74
+ expect(PGN::Serializer.new(game).to_s).to eq("[Result \"*\"]\n\n{ game comment } *\n")
75
+ end
76
+
77
+ it 'numbers the first move 1... when starting from a FEN with black to move' do
78
+ fen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b KQkq - 0 1'
79
+ game = PGN::Game.new(%w[e5], { 'FEN' => fen }, '*')
80
+ expected = "[FEN \"#{fen}\"]\n\n1... e5 *\n"
81
+ expect(PGN::Serializer.new(game).to_s).to eq(expected)
82
+ end
83
+
84
+ it 'serializes -- moves verbatim and alternates color/fullmove' do
85
+ game = PGN::Game.new(%w[-- e4])
86
+ expect(PGN::Serializer.new(game).to_s).to eq("[Result \"*\"]\n\n1. -- e4 *\n")
87
+ end
88
+ end
89
+ end
data/spec/spec_helper.rb CHANGED
@@ -7,7 +7,6 @@ require 'pgn'
7
7
  #
8
8
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
9
9
  RSpec.configure do |config|
10
- config.treat_symbols_as_metadata_keys_with_true_values = true
11
10
  config.run_all_when_everything_filtered = true
12
11
  config.filter_run :focus
13
12
 
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: 0.4.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stacey Touset
@@ -9,16 +9,30 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-04-01 00:00:00.000000000 Z
12
+ date: 2026-08-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: whittle
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '2.3'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '2.3'
28
+ - !ruby/object:Gem::Dependency
29
+ name: pry
16
30
  requirement: !ruby/object:Gem::Requirement
17
31
  requirements:
18
32
  - - ">="
19
33
  - !ruby/object:Gem::Version
20
34
  version: '0'
21
- type: :runtime
35
+ type: :development
22
36
  prerelease: false
23
37
  version_requirements: !ruby/object:Gem::Requirement
24
38
  requirements:
@@ -26,21 +40,21 @@ dependencies:
26
40
  - !ruby/object:Gem::Version
27
41
  version: '0'
28
42
  - !ruby/object:Gem::Dependency
29
- name: bundler
43
+ name: rake
30
44
  requirement: !ruby/object:Gem::Requirement
31
45
  requirements:
32
- - - "~>"
46
+ - - ">="
33
47
  - !ruby/object:Gem::Version
34
- version: '2.3'
48
+ version: '0'
35
49
  type: :development
36
50
  prerelease: false
37
51
  version_requirements: !ruby/object:Gem::Requirement
38
52
  requirements:
39
- - - "~>"
53
+ - - ">="
40
54
  - !ruby/object:Gem::Version
41
- version: '2.3'
55
+ version: '0'
42
56
  - !ruby/object:Gem::Dependency
43
- name: pry
57
+ name: rspec
44
58
  requirement: !ruby/object:Gem::Requirement
45
59
  requirements:
46
60
  - - ">="
@@ -54,7 +68,7 @@ dependencies:
54
68
  - !ruby/object:Gem::Version
55
69
  version: '0'
56
70
  - !ruby/object:Gem::Dependency
57
- name: rake
71
+ name: racc
58
72
  requirement: !ruby/object:Gem::Requirement
59
73
  requirements:
60
74
  - - ">="
@@ -68,7 +82,7 @@ dependencies:
68
82
  - !ruby/object:Gem::Version
69
83
  version: '0'
70
84
  - !ruby/object:Gem::Dependency
71
- name: rspec
85
+ name: benchmark-ips
72
86
  requirement: !ruby/object:Gem::Requirement
73
87
  requirements:
74
88
  - - ">="
@@ -81,6 +95,34 @@ dependencies:
81
95
  - - ">="
82
96
  - !ruby/object:Gem::Version
83
97
  version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: memory_profiler
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: rubocop
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '1.60'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '1.60'
84
126
  description: A PGN parser and FEN generator for Ruby
85
127
  email:
86
128
  - stacey@touset.org
@@ -89,45 +131,83 @@ executables: []
89
131
  extensions: []
90
132
  extra_rdoc_files: []
91
133
  files:
134
+ - ".github/workflows/ci.yml"
135
+ - ".github/workflows/publish.yml"
136
+ - ".github/workflows/release.yml"
92
137
  - ".gitignore"
93
138
  - ".rspec"
139
+ - ".rubocop.yml"
140
+ - CHANGELOG.md
94
141
  - Gemfile
95
142
  - LICENSE.txt
96
143
  - README.md
97
144
  - Rakefile
98
145
  - TODO.md
146
+ - bench/.keep
147
+ - bench/IMPROVEMENTS.md
148
+ - bench/baseline_moves.pre-optimization.txt
149
+ - bench/baseline_moves.pre-quickwins.txt
150
+ - bench/baseline_moves.txt
151
+ - bench/baseline_parse.pre-optimization.txt
152
+ - bench/baseline_parse.pre-quickwins.txt
153
+ - bench/baseline_parse.racc.txt
154
+ - bench/baseline_parse.txt
155
+ - bench/profile_moves.rb
156
+ - bench/profile_parse.rb
157
+ - docs/superpowers/plans/2026-08-12-efficiency-optimizations.md
158
+ - docs/superpowers/plans/2026-08-12-efficiency-tests-and-profiling.md
159
+ - docs/superpowers/plans/2026-08-12-to-pgn-serialization.md
160
+ - docs/superpowers/plans/2026-08-13-whittle-to-racc-migration.md
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
99
163
  - examples/immortal_game.pgn
100
164
  - lib/pgn.rb
101
165
  - lib/pgn/board.rb
102
166
  - lib/pgn/fen.rb
103
167
  - lib/pgn/game.rb
168
+ - lib/pgn/lexer.rb
104
169
  - lib/pgn/move.rb
105
170
  - lib/pgn/move_calculator.rb
106
171
  - lib/pgn/parser.rb
172
+ - lib/pgn/pgn_parser.rb
173
+ - lib/pgn/pgn_parser.y
107
174
  - lib/pgn/position.rb
175
+ - lib/pgn/serializer.rb
108
176
  - lib/pgn/version.rb
109
177
  - pgn2.gemspec
178
+ - spec/board_spec.rb
110
179
  - spec/fen_spec.rb
111
180
  - spec/game_spec.rb
181
+ - spec/lexer_spec.rb
182
+ - spec/move_calculator_spec.rb
183
+ - spec/move_spec.rb
184
+ - spec/parser_explicit_spec.rb
112
185
  - spec/parser_spec.rb
113
186
  - spec/pgn_files/alternate_castling.pgn
114
187
  - spec/pgn_files/annotations.pgn
115
188
  - spec/pgn_files/comments.pgn
189
+ - spec/pgn_files/doublequotes.pgn
116
190
  - spec/pgn_files/empty_variation_move.pgn
117
191
  - spec/pgn_files/fen.pgn
118
192
  - spec/pgn_files/multiline_comments.pgn
119
193
  - spec/pgn_files/nested_comments.pgn
120
194
  - spec/pgn_files/no_moves.pgn
195
+ - spec/pgn_files/specialcharacters.pgn
121
196
  - spec/pgn_files/test.pgn
122
197
  - spec/pgn_files/two_annotations.pgn
123
198
  - spec/pgn_files/two_games.pgn
124
199
  - spec/pgn_files/variations.pgn
125
200
  - spec/position_spec.rb
201
+ - spec/serializer_spec.rb
126
202
  - spec/spec_helper.rb
127
203
  homepage: https://github.com/muriloime/pgn
128
204
  licenses:
129
205
  - MIT
130
- metadata: {}
206
+ metadata:
207
+ homepage_uri: https://github.com/muriloime/pgn
208
+ source_code_uri: https://github.com/muriloime/pgn
209
+ bug_tracker_uri: https://github.com/muriloime/pgn/issues
210
+ changelog_uri: https://github.com/muriloime/pgn/blob/main/CHANGELOG.md
131
211
  post_install_message:
132
212
  rdoc_options: []
133
213
  require_paths:
@@ -136,32 +216,40 @@ required_ruby_version: !ruby/object:Gem::Requirement
136
216
  requirements:
137
217
  - - ">="
138
218
  - !ruby/object:Gem::Version
139
- version: '0'
219
+ version: '3.0'
140
220
  required_rubygems_version: !ruby/object:Gem::Requirement
141
221
  requirements:
142
222
  - - ">="
143
223
  - !ruby/object:Gem::Version
144
224
  version: '0'
145
225
  requirements: []
146
- rubygems_version: 3.2.32
226
+ rubygems_version: 3.5.22
147
227
  signing_key:
148
228
  specification_version: 4
149
229
  summary: A PGN parser for Ruby
150
230
  test_files:
231
+ - spec/board_spec.rb
151
232
  - spec/fen_spec.rb
152
233
  - spec/game_spec.rb
234
+ - spec/lexer_spec.rb
235
+ - spec/move_calculator_spec.rb
236
+ - spec/move_spec.rb
237
+ - spec/parser_explicit_spec.rb
153
238
  - spec/parser_spec.rb
154
239
  - spec/pgn_files/alternate_castling.pgn
155
240
  - spec/pgn_files/annotations.pgn
156
241
  - spec/pgn_files/comments.pgn
242
+ - spec/pgn_files/doublequotes.pgn
157
243
  - spec/pgn_files/empty_variation_move.pgn
158
244
  - spec/pgn_files/fen.pgn
159
245
  - spec/pgn_files/multiline_comments.pgn
160
246
  - spec/pgn_files/nested_comments.pgn
161
247
  - spec/pgn_files/no_moves.pgn
248
+ - spec/pgn_files/specialcharacters.pgn
162
249
  - spec/pgn_files/test.pgn
163
250
  - spec/pgn_files/two_annotations.pgn
164
251
  - spec/pgn_files/two_games.pgn
165
252
  - spec/pgn_files/variations.pgn
166
253
  - spec/position_spec.rb
254
+ - spec/serializer_spec.rb
167
255
  - spec/spec_helper.rb