sashite-pin 4.0.0 → 4.2.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/LICENSE +1 -1
- data/README.md +174 -91
- data/lib/sashite/pin/constants.rb +52 -14
- data/lib/sashite/pin/errors/argument/messages.rb +7 -10
- data/lib/sashite/pin/errors/argument.rb +0 -3
- data/lib/sashite/pin/identifier.rb +222 -316
- data/lib/sashite/pin/parser.rb +154 -113
- data/lib/sashite/pin.rb +68 -59
- data/lib/sashite-pin.rb +0 -11
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 654a422f7d1d03a9b2e474e6f29644c7d8636cc6a7da60100fc49efa87ccda02
|
|
4
|
+
data.tar.gz: c9548c761f14c4dc76acc1376fda12fa64339bbe7d6b2567b49f205113c89b59
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fc265f0049d911133c3de8910ed15911a207fcec239aa97d8c4a9766242f2f5ca53d80802e36c9aafeac525eb79a01ef4f3625738f04622db97b210d8094296c
|
|
7
|
+
data.tar.gz: 5a5254e21d89dfec797ce72c62d1a65689c9db5460a91c13edba6360655483b7dd1c0a024c04ee35e5e35971c30e08518b540086112334da0a662d2ea1f3b432
|
data/LICENSE
CHANGED
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
same "printed page" as the copyright notice for easier
|
|
187
187
|
identification within third-party archives.
|
|
188
188
|
|
|
189
|
-
Copyright 2025 Cyril Kato
|
|
189
|
+
Copyright 2025-2026 Cyril Kato
|
|
190
190
|
|
|
191
191
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
192
|
you may not use this file except in compliance with the License.
|
data/README.md
CHANGED
|
@@ -11,6 +11,18 @@
|
|
|
11
11
|
|
|
12
12
|
This library implements the [PIN Specification v1.0.0](https://sashite.dev/specs/pin/1.0.0/).
|
|
13
13
|
|
|
14
|
+
PIN is a compact, ASCII-only token format encoding a **Piece Identity**: the tuple (**Piece Name**, **Piece Side**, **Piece State**, **Terminal Status**). Case encodes side, an optional `+`/`-` prefix encodes state, and an optional `^` suffix marks terminal pieces.
|
|
15
|
+
|
|
16
|
+
### Implementation Constraints
|
|
17
|
+
|
|
18
|
+
| Constraint | Value | Rationale |
|
|
19
|
+
|------------|-------|-----------|
|
|
20
|
+
| Token length | 1–3 characters | `[+-]?[A-Za-z]\^?` per spec |
|
|
21
|
+
| Character space | 312 tokens | 26 abbreviations × 2 sides × 3 states × 2 terminal |
|
|
22
|
+
| Instance pool | 312 objects | All identifiers are pre-instantiated and frozen |
|
|
23
|
+
|
|
24
|
+
The closed domain of 312 possible values enables a flyweight architecture with zero allocation on the hot path.
|
|
25
|
+
|
|
14
26
|
## Installation
|
|
15
27
|
|
|
16
28
|
```ruby
|
|
@@ -35,7 +47,7 @@ require "sashite/pin"
|
|
|
35
47
|
|
|
36
48
|
# Standard parsing (raises on error)
|
|
37
49
|
pin = Sashite::Pin.parse("K")
|
|
38
|
-
pin.
|
|
50
|
+
pin.abbr # => :K
|
|
39
51
|
pin.side # => :first
|
|
40
52
|
pin.state # => :normal
|
|
41
53
|
pin.terminal? # => false
|
|
@@ -53,57 +65,73 @@ pin = Sashite::Pin.parse("+K^")
|
|
|
53
65
|
pin.state # => :enhanced
|
|
54
66
|
pin.terminal? # => true
|
|
55
67
|
|
|
68
|
+
# Returns a cached instance — no allocation
|
|
69
|
+
Sashite::Pin.parse("+K^").equal?(Sashite::Pin.parse("+K^")) # => true
|
|
70
|
+
|
|
56
71
|
# Invalid input raises ArgumentError
|
|
57
72
|
Sashite::Pin.parse("invalid") # => raises ArgumentError
|
|
58
73
|
```
|
|
59
74
|
|
|
75
|
+
### Safe Parsing (String → Identifier | nil)
|
|
76
|
+
|
|
77
|
+
Parse without raising exceptions. Returns `nil` on invalid input.
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
# Valid input returns an Identifier
|
|
81
|
+
Sashite::Pin.safe_parse("K") # => #<Sashite::Pin::Identifier K>
|
|
82
|
+
Sashite::Pin.safe_parse("+R^") # => #<Sashite::Pin::Identifier +R^>
|
|
83
|
+
|
|
84
|
+
# Invalid input returns nil — no exception allocated
|
|
85
|
+
Sashite::Pin.safe_parse("") # => nil
|
|
86
|
+
Sashite::Pin.safe_parse("invalid") # => nil
|
|
87
|
+
Sashite::Pin.safe_parse(nil) # => nil
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Fetching by Components (Symbol, Symbol, ... → Identifier)
|
|
91
|
+
|
|
92
|
+
Retrieve a cached identifier directly by components, bypassing string parsing entirely.
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
# Direct lookup — no string parsing, no allocation
|
|
96
|
+
Sashite::Pin.fetch(:K, :first) # => #<Sashite::Pin::Identifier K>
|
|
97
|
+
Sashite::Pin.fetch(:R, :second, :enhanced) # => #<Sashite::Pin::Identifier +r>
|
|
98
|
+
Sashite::Pin.fetch(:K, :first, :normal, terminal: true) # => #<Sashite::Pin::Identifier K^>
|
|
99
|
+
|
|
100
|
+
# Same cached instance as parse
|
|
101
|
+
Sashite::Pin.fetch(:K, :first).equal?(Sashite::Pin.parse("K")) # => true
|
|
102
|
+
|
|
103
|
+
# Invalid components raise ArgumentError
|
|
104
|
+
Sashite::Pin.fetch(:KK, :first) # => raises ArgumentError
|
|
105
|
+
Sashite::Pin.fetch(:K, :third) # => raises ArgumentError
|
|
106
|
+
```
|
|
107
|
+
|
|
60
108
|
### Formatting (Identifier → String)
|
|
61
109
|
|
|
62
110
|
Convert an `Identifier` back to a PIN string.
|
|
63
111
|
|
|
64
112
|
```ruby
|
|
65
|
-
|
|
66
|
-
pin
|
|
67
|
-
pin.to_s # => "K"
|
|
68
|
-
|
|
69
|
-
# With attributes
|
|
70
|
-
pin = Sashite::Pin::Identifier.new(:R, :second, :enhanced)
|
|
71
|
-
pin.to_s # => "+r"
|
|
113
|
+
pin = Sashite::Pin.parse("+K^")
|
|
114
|
+
pin.to_s # => "+K^"
|
|
72
115
|
|
|
73
|
-
pin = Sashite::Pin
|
|
74
|
-
pin.to_s # => "
|
|
116
|
+
pin = Sashite::Pin.parse("r")
|
|
117
|
+
pin.to_s # => "r"
|
|
75
118
|
```
|
|
76
119
|
|
|
77
120
|
### Validation
|
|
78
121
|
|
|
79
122
|
```ruby
|
|
80
|
-
# Boolean check
|
|
123
|
+
# Boolean check (never raises)
|
|
124
|
+
# Uses an exception-free code path internally for performance.
|
|
81
125
|
Sashite::Pin.valid?("K") # => true
|
|
82
126
|
Sashite::Pin.valid?("+R") # => true
|
|
83
127
|
Sashite::Pin.valid?("K^") # => true
|
|
84
128
|
Sashite::Pin.valid?("invalid") # => false
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
### Accessing Identifier Data
|
|
88
|
-
|
|
89
|
-
```ruby
|
|
90
|
-
pin = Sashite::Pin.parse("+K^")
|
|
91
|
-
|
|
92
|
-
# Get attributes
|
|
93
|
-
pin.type # => :K
|
|
94
|
-
pin.side # => :first
|
|
95
|
-
pin.state # => :enhanced
|
|
96
|
-
pin.terminal? # => true
|
|
97
|
-
|
|
98
|
-
# Get string components
|
|
99
|
-
pin.letter # => "K"
|
|
100
|
-
pin.prefix # => "+"
|
|
101
|
-
pin.suffix # => "^"
|
|
129
|
+
Sashite::Pin.valid?(nil) # => false
|
|
102
130
|
```
|
|
103
131
|
|
|
104
132
|
### Transformations
|
|
105
133
|
|
|
106
|
-
All transformations return new
|
|
134
|
+
All transformations return cached instances from the flyweight pool — no new object is ever allocated.
|
|
107
135
|
|
|
108
136
|
```ruby
|
|
109
137
|
pin = Sashite::Pin.parse("K")
|
|
@@ -117,14 +145,17 @@ pin.normalize.to_s # => "K"
|
|
|
117
145
|
pin.flip.to_s # => "k"
|
|
118
146
|
|
|
119
147
|
# Terminal transformations
|
|
120
|
-
pin.
|
|
121
|
-
pin.
|
|
148
|
+
pin.terminal.to_s # => "K^"
|
|
149
|
+
pin.non_terminal.to_s # => "K"
|
|
122
150
|
|
|
123
151
|
# Attribute changes
|
|
124
|
-
pin.
|
|
125
|
-
pin.with_side(:second).to_s
|
|
152
|
+
pin.with_abbr(:Q).to_s # => "Q"
|
|
153
|
+
pin.with_side(:second).to_s # => "k"
|
|
126
154
|
pin.with_state(:enhanced).to_s # => "+K"
|
|
127
155
|
pin.with_terminal(true).to_s # => "K^"
|
|
156
|
+
|
|
157
|
+
# Transformations return cached instances
|
|
158
|
+
pin.enhance.equal?(Sashite::Pin.parse("+K")) # => true
|
|
128
159
|
```
|
|
129
160
|
|
|
130
161
|
### Queries
|
|
@@ -146,7 +177,7 @@ pin.terminal? # => true
|
|
|
146
177
|
|
|
147
178
|
# Comparison queries
|
|
148
179
|
other = Sashite::Pin.parse("k")
|
|
149
|
-
pin.
|
|
180
|
+
pin.same_abbr?(other) # => true
|
|
150
181
|
pin.same_side?(other) # => false
|
|
151
182
|
pin.same_state?(other) # => false
|
|
152
183
|
pin.same_terminal?(other) # => false
|
|
@@ -154,27 +185,60 @@ pin.same_terminal?(other) # => false
|
|
|
154
185
|
|
|
155
186
|
## API Reference
|
|
156
187
|
|
|
157
|
-
###
|
|
188
|
+
### Module Methods
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
# Parses a PIN string into a cached Identifier.
|
|
192
|
+
# Returns a pre-instantiated, frozen instance.
|
|
193
|
+
# Raises ArgumentError if the string is not valid.
|
|
194
|
+
#
|
|
195
|
+
# @param string [String] PIN string
|
|
196
|
+
# @return [Identifier]
|
|
197
|
+
# @raise [ArgumentError] if invalid
|
|
198
|
+
def Sashite::Pin.parse(string)
|
|
199
|
+
|
|
200
|
+
# Parses a PIN string without raising.
|
|
201
|
+
# Returns a cached Identifier on success, nil on failure.
|
|
202
|
+
# Never allocates exception objects or captures backtraces.
|
|
203
|
+
#
|
|
204
|
+
# @param string [String] PIN string
|
|
205
|
+
# @return [Identifier, nil]
|
|
206
|
+
def Sashite::Pin.safe_parse(string)
|
|
207
|
+
|
|
208
|
+
# Retrieves a cached Identifier by components.
|
|
209
|
+
# Bypasses string parsing entirely — direct hash lookup.
|
|
210
|
+
# Raises ArgumentError if components are invalid.
|
|
211
|
+
#
|
|
212
|
+
# @param abbr [Symbol] Piece abbreviation (:A through :Z)
|
|
213
|
+
# @param side [Symbol] Piece side (:first or :second)
|
|
214
|
+
# @param state [Symbol] Piece state (:normal, :enhanced, or :diminished)
|
|
215
|
+
# @param terminal [Boolean] Terminal status
|
|
216
|
+
# @return [Identifier]
|
|
217
|
+
# @raise [ArgumentError] if invalid
|
|
218
|
+
def Sashite::Pin.fetch(abbr, side, state = :normal, terminal: false)
|
|
219
|
+
|
|
220
|
+
# Reports whether string is a valid PIN.
|
|
221
|
+
# Never raises; returns false for any invalid input.
|
|
222
|
+
# Uses an exception-free code path internally for performance.
|
|
223
|
+
#
|
|
224
|
+
# @param string [String] PIN string
|
|
225
|
+
# @return [Boolean]
|
|
226
|
+
def Sashite::Pin.valid?(string)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Identifier
|
|
158
230
|
|
|
159
231
|
```ruby
|
|
160
232
|
# Identifier represents a parsed PIN with all attributes.
|
|
233
|
+
# All instances are frozen and pre-instantiated — never construct directly,
|
|
234
|
+
# use Sashite::Pin.parse, .safe_parse, or .fetch instead.
|
|
161
235
|
class Sashite::Pin::Identifier
|
|
162
|
-
#
|
|
163
|
-
# Raises ArgumentError if attributes are invalid.
|
|
236
|
+
# Returns the piece name abbreviation (always uppercase symbol).
|
|
164
237
|
#
|
|
165
|
-
# @
|
|
166
|
-
|
|
167
|
-
# @param state [Symbol] Piece state (:normal, :enhanced, or :diminished)
|
|
168
|
-
# @param terminal [Boolean] Terminal status
|
|
169
|
-
# @return [Identifier]
|
|
170
|
-
def initialize(type, side, state = :normal, terminal: false)
|
|
171
|
-
|
|
172
|
-
# Returns the piece type (always uppercase symbol).
|
|
173
|
-
#
|
|
174
|
-
# @return [Symbol]
|
|
175
|
-
def type
|
|
238
|
+
# @return [Symbol] :A through :Z
|
|
239
|
+
def abbr
|
|
176
240
|
|
|
177
|
-
# Returns the
|
|
241
|
+
# Returns the piece side.
|
|
178
242
|
#
|
|
179
243
|
# @return [Symbol] :first or :second
|
|
180
244
|
def side
|
|
@@ -196,41 +260,12 @@ class Sashite::Pin::Identifier
|
|
|
196
260
|
end
|
|
197
261
|
```
|
|
198
262
|
|
|
199
|
-
### Constants
|
|
200
|
-
|
|
201
|
-
```ruby
|
|
202
|
-
Sashite::Pin::Constants::VALID_TYPES # => [:A, :B, ..., :Z]
|
|
203
|
-
Sashite::Pin::Constants::VALID_SIDES # => [:first, :second]
|
|
204
|
-
Sashite::Pin::Constants::VALID_STATES # => [:normal, :enhanced, :diminished]
|
|
205
|
-
Sashite::Pin::Constants::MAX_STRING_LENGTH # => 3
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
### Parsing
|
|
209
|
-
|
|
210
|
-
```ruby
|
|
211
|
-
# Parses a PIN string into an Identifier.
|
|
212
|
-
# Raises ArgumentError if the string is not valid.
|
|
213
|
-
#
|
|
214
|
-
# @param string [String] PIN string
|
|
215
|
-
# @return [Identifier]
|
|
216
|
-
# @raise [ArgumentError] if invalid
|
|
217
|
-
def Sashite::Pin.parse(string)
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
### Validation
|
|
221
|
-
|
|
222
|
-
```ruby
|
|
223
|
-
# Reports whether string is a valid PIN.
|
|
224
|
-
#
|
|
225
|
-
# @param string [String] PIN string
|
|
226
|
-
# @return [Boolean]
|
|
227
|
-
def Sashite::Pin.valid?(string)
|
|
228
|
-
```
|
|
229
|
-
|
|
230
263
|
### Transformations
|
|
231
264
|
|
|
265
|
+
All transformations return cached `Sashite::Pin::Identifier` instances from the flyweight pool:
|
|
266
|
+
|
|
232
267
|
```ruby
|
|
233
|
-
# State transformations
|
|
268
|
+
# State transformations
|
|
234
269
|
def enhance # => Identifier with :enhanced state
|
|
235
270
|
def diminish # => Identifier with :diminished state
|
|
236
271
|
def normalize # => Identifier with :normal state
|
|
@@ -239,19 +274,50 @@ def normalize # => Identifier with :normal state
|
|
|
239
274
|
def flip # => Identifier with opposite side
|
|
240
275
|
|
|
241
276
|
# Terminal transformations
|
|
242
|
-
def
|
|
243
|
-
def
|
|
277
|
+
def terminal # => Identifier with terminal: true
|
|
278
|
+
def non_terminal # => Identifier with terminal: false
|
|
244
279
|
|
|
245
280
|
# Attribute changes
|
|
246
|
-
def
|
|
247
|
-
def with_side(new_side)
|
|
248
|
-
def with_state(new_state)
|
|
281
|
+
def with_abbr(new_abbr) # => Identifier with different abbreviation
|
|
282
|
+
def with_side(new_side) # => Identifier with different side
|
|
283
|
+
def with_state(new_state) # => Identifier with different state
|
|
249
284
|
def with_terminal(new_terminal) # => Identifier with specified terminal status
|
|
250
285
|
```
|
|
251
286
|
|
|
287
|
+
### Queries
|
|
288
|
+
|
|
289
|
+
```ruby
|
|
290
|
+
# State queries
|
|
291
|
+
def normal? # => Boolean
|
|
292
|
+
def enhanced? # => Boolean
|
|
293
|
+
def diminished? # => Boolean
|
|
294
|
+
|
|
295
|
+
# Side queries
|
|
296
|
+
def first_player? # => Boolean
|
|
297
|
+
def second_player? # => Boolean
|
|
298
|
+
|
|
299
|
+
# Terminal query
|
|
300
|
+
def terminal? # => Boolean
|
|
301
|
+
|
|
302
|
+
# Comparison queries
|
|
303
|
+
def same_abbr?(other) # => Boolean
|
|
304
|
+
def same_side?(other) # => Boolean
|
|
305
|
+
def same_state?(other) # => Boolean
|
|
306
|
+
def same_terminal?(other) # => Boolean
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Constants
|
|
310
|
+
|
|
311
|
+
```ruby
|
|
312
|
+
Sashite::Pin::Constants::VALID_ABBRS # => [:A, :B, ..., :Z]
|
|
313
|
+
Sashite::Pin::Constants::VALID_SIDES # => [:first, :second]
|
|
314
|
+
Sashite::Pin::Constants::VALID_STATES # => [:normal, :enhanced, :diminished]
|
|
315
|
+
Sashite::Pin::Constants::MAX_STRING_LENGTH # => 3
|
|
316
|
+
```
|
|
317
|
+
|
|
252
318
|
### Errors
|
|
253
319
|
|
|
254
|
-
All
|
|
320
|
+
All errors raise `ArgumentError` with descriptive messages:
|
|
255
321
|
|
|
256
322
|
| Message | Cause |
|
|
257
323
|
|---------|-------|
|
|
@@ -263,13 +329,30 @@ All parsing and validation errors raise `ArgumentError` with descriptive message
|
|
|
263
329
|
|
|
264
330
|
## Design Principles
|
|
265
331
|
|
|
266
|
-
- **
|
|
267
|
-
- **
|
|
332
|
+
- **Spec conformance**: Strict adherence to PIN v1.0.0
|
|
333
|
+
- **Flyweight identifiers**: All 312 possible instances are pre-built and frozen; parsing, fetching, and transformations return cached objects with zero allocation
|
|
334
|
+
- **Performance-oriented internals**: Exception-free validation path; exceptions only at the public API boundary
|
|
268
335
|
- **Ruby idioms**: `valid?` predicate, `to_s` conversion, `ArgumentError` for invalid input
|
|
269
|
-
- **Immutable identifiers**:
|
|
270
|
-
- **Transformation methods**: Return new instances for state changes
|
|
336
|
+
- **Immutable identifiers**: All instances are frozen after creation
|
|
271
337
|
- **No dependencies**: Pure Ruby standard library only
|
|
272
338
|
|
|
339
|
+
### Performance Architecture
|
|
340
|
+
|
|
341
|
+
PIN has a closed domain of exactly 312 valid tokens (26 letters × 2 cases × 3 states × 2 terminal). The implementation exploits this constraint through three complementary strategies.
|
|
342
|
+
|
|
343
|
+
**Flyweight instance pool** — All 312 `Identifier` objects are pre-instantiated and frozen at load time. `parse`, `safe_parse`, `fetch`, and all transformation methods return these cached instances via hash lookup. No `Identifier` is ever allocated after the module loads. This makes PIN essentially free to call from EPIN, FEEN, or any other hot loop — every call is a hash lookup returning a pre-existing frozen object.
|
|
344
|
+
|
|
345
|
+
**Dual-path parsing** — Parsing is split into two layers to avoid using exceptions for control flow:
|
|
346
|
+
|
|
347
|
+
- **Validation layer** — `safe_parse` performs all validation and returns the cached `Identifier` on success, or `nil` on failure, without raising, without allocating exception objects, and without capturing backtraces.
|
|
348
|
+
- **Public API layer** — `parse` calls `safe_parse` internally. On failure, it raises `ArgumentError` exactly once, at the boundary. `valid?` calls `safe_parse` and returns a boolean directly, never raising.
|
|
349
|
+
|
|
350
|
+
**Zero-allocation transformations** — Every transformation method (`flip`, `enhance`, `diminish`, `terminal`, `with_abbr`, etc.) computes the target component values and performs a direct lookup into the instance pool. The result is always a cached object — transformations never allocate. Chaining like `pin.enhance.flip.terminal` performs three hash lookups and zero allocations.
|
|
351
|
+
|
|
352
|
+
**Direct component lookup** — `fetch` bypasses string parsing entirely. Given components `(:K, :first, :enhanced, terminal: true)`, it performs a single hash lookup into the instance pool. This is the fastest path for callers that already have structured data (e.g., EPIN's internal construction from parsed components).
|
|
353
|
+
|
|
354
|
+
This architecture ensures that PIN never becomes a bottleneck when called from higher-level parsers like EPIN or FEEN, where it may be invoked hundreds of times per position.
|
|
355
|
+
|
|
273
356
|
## Related Specifications
|
|
274
357
|
|
|
275
358
|
- [Game Protocol](https://sashite.dev/game-protocol/) — Conceptual foundation
|
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
module Sashite
|
|
4
4
|
module Pin
|
|
5
|
-
#
|
|
5
|
+
# Performance-oriented constants for PIN (Piece Identifier Notation).
|
|
6
6
|
#
|
|
7
|
-
#
|
|
7
|
+
# All lookups are O(1) frozen Hashes. Byte-level constants eliminate
|
|
8
|
+
# repeated method calls in the parser hot path.
|
|
8
9
|
module Constants
|
|
9
|
-
#
|
|
10
|
-
|
|
10
|
+
# ====================================================================
|
|
11
|
+
# Domain values
|
|
12
|
+
# ====================================================================
|
|
13
|
+
|
|
14
|
+
# Valid piece name abbreviations (uppercase symbols :A..:Z).
|
|
15
|
+
VALID_ABBRS = %i[A B C D E F G H I J K L M N O P Q R S T U V W X Y Z].freeze
|
|
11
16
|
|
|
12
17
|
# Valid player sides.
|
|
13
18
|
VALID_SIDES = %i[first second].freeze
|
|
@@ -15,21 +20,54 @@ module Sashite
|
|
|
15
20
|
# Valid piece states.
|
|
16
21
|
VALID_STATES = %i[normal enhanced diminished].freeze
|
|
17
22
|
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
|
|
23
|
+
# ====================================================================
|
|
24
|
+
# O(1) validation lookups (frozen Hashes → faster than Array#include?)
|
|
25
|
+
# ====================================================================
|
|
26
|
+
|
|
27
|
+
# { :A => true, :B => true, … :Z => true }
|
|
28
|
+
ABBR_SET = VALID_ABBRS.each_with_object({}) { |a, h| h[a] = true }.freeze
|
|
29
|
+
|
|
30
|
+
# { :first => true, :second => true }
|
|
31
|
+
SIDE_SET = VALID_SIDES.each_with_object({}) { |s, h| h[s] = true }.freeze
|
|
21
32
|
|
|
22
|
-
#
|
|
23
|
-
|
|
33
|
+
# { :normal => true, :enhanced => true, :diminished => true }
|
|
34
|
+
STATE_SET = VALID_STATES.each_with_object({}) { |s, h| h[s] = true }.freeze
|
|
24
35
|
|
|
25
|
-
#
|
|
36
|
+
# ====================================================================
|
|
37
|
+
# String fragments (frozen, reused by Identifier#to_s)
|
|
38
|
+
# ====================================================================
|
|
39
|
+
|
|
40
|
+
ENHANCED_PREFIX = "+"
|
|
26
41
|
DIMINISHED_PREFIX = "-"
|
|
42
|
+
TERMINAL_SUFFIX = "^"
|
|
43
|
+
EMPTY_STRING = ""
|
|
44
|
+
|
|
45
|
+
# ====================================================================
|
|
46
|
+
# Byte constants (used by Parser for zero-allocation character checks)
|
|
47
|
+
# ====================================================================
|
|
48
|
+
|
|
49
|
+
# State modifier bytes
|
|
50
|
+
BYTE_PLUS = 0x2B # '+'
|
|
51
|
+
BYTE_MINUS = 0x2D # '-'
|
|
52
|
+
|
|
53
|
+
# Terminal marker byte
|
|
54
|
+
BYTE_CARET = 0x5E # '^'
|
|
55
|
+
|
|
56
|
+
# ASCII letter ranges
|
|
57
|
+
BYTE_UPPER_A = 0x41 # 'A'
|
|
58
|
+
BYTE_UPPER_Z = 0x5A # 'Z'
|
|
59
|
+
BYTE_LOWER_A = 0x61 # 'a'
|
|
60
|
+
BYTE_LOWER_Z = 0x7A # 'z'
|
|
61
|
+
|
|
62
|
+
# Case conversion offset (lowercase - uppercase = 32)
|
|
63
|
+
CASE_OFFSET = 0x20
|
|
27
64
|
|
|
28
|
-
#
|
|
29
|
-
|
|
65
|
+
# ====================================================================
|
|
66
|
+
# Token length
|
|
67
|
+
# ====================================================================
|
|
30
68
|
|
|
31
|
-
#
|
|
32
|
-
|
|
69
|
+
# Maximum byte length of a valid PIN string: "+K^"
|
|
70
|
+
MAX_BYTE_LENGTH = 3
|
|
33
71
|
end
|
|
34
72
|
end
|
|
35
73
|
end
|
|
@@ -5,21 +5,18 @@ module Sashite
|
|
|
5
5
|
module Errors
|
|
6
6
|
class Argument < ::ArgumentError
|
|
7
7
|
# Centralized error messages for PIN parsing and validation.
|
|
8
|
-
#
|
|
9
|
-
# @example
|
|
10
|
-
# raise Errors::Argument, Messages::EMPTY_INPUT
|
|
11
8
|
module Messages
|
|
12
9
|
# Parsing errors
|
|
13
|
-
EMPTY_INPUT
|
|
14
|
-
INPUT_TOO_LONG
|
|
10
|
+
EMPTY_INPUT = "empty input"
|
|
11
|
+
INPUT_TOO_LONG = "input exceeds 3 characters"
|
|
15
12
|
MUST_CONTAIN_ONE_LETTER = "must contain exactly one letter"
|
|
16
|
-
INVALID_STATE_MODIFIER
|
|
13
|
+
INVALID_STATE_MODIFIER = "invalid state modifier"
|
|
17
14
|
INVALID_TERMINAL_MARKER = "invalid terminal marker"
|
|
18
15
|
|
|
19
|
-
# Validation errors (constructor)
|
|
20
|
-
|
|
21
|
-
INVALID_SIDE
|
|
22
|
-
INVALID_STATE
|
|
16
|
+
# Validation errors (constructor / fetch)
|
|
17
|
+
INVALID_ABBR = "abbr must be a symbol from :A to :Z"
|
|
18
|
+
INVALID_SIDE = "side must be :first or :second"
|
|
19
|
+
INVALID_STATE = "state must be :normal, :enhanced, or :diminished"
|
|
23
20
|
INVALID_TERMINAL = "terminal must be true or false"
|
|
24
21
|
end
|
|
25
22
|
end
|