sashite-cell 2.0.2 → 4.0.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.
data/README.md CHANGED
@@ -1,17 +1,25 @@
1
- # Cell.rb
1
+ # cell.rb
2
2
 
3
3
  [![Version](https://img.shields.io/github/v/tag/sashite/cell.rb?label=Version&logo=github)](https://github.com/sashite/cell.rb/tags)
4
4
  [![Yard documentation](https://img.shields.io/badge/Yard-documentation-blue.svg?logo=github)](https://rubydoc.info/github/sashite/cell.rb/main)
5
- ![Ruby](https://github.com/sashite/cell.rb/actions/workflows/main.yml/badge.svg?branch=main)
6
- [![License](https://img.shields.io/github/license/sashite/cell.rb?label=License&logo=github)](https://github.com/sashite/cell.rb/raw/main/LICENSE.md)
5
+ [![CI](https://github.com/sashite/cell.rb/actions/workflows/ruby.yml/badge.svg?branch=main)](https://github.com/sashite/cell.rb/actions)
6
+ [![License](https://img.shields.io/github/license/sashite/cell.rb)](https://github.com/sashite/cell.rb/blob/main/LICENSE)
7
7
 
8
- > **CELL** (Coordinate Encoding for Layered Locations) support for the Ruby language.
8
+ > **CELL** (Coordinate Encoding for Layered Locations) implementation for Ruby.
9
9
 
10
- ## What is CELL?
10
+ ## Overview
11
11
 
12
- CELL (Coordinate Encoding for Layered Locations) is a standardized format for representing coordinates on multi-dimensional game boards using a cyclical ASCII character system. CELL supports unlimited dimensional coordinate systems through the systematic repetition of three distinct character sets.
12
+ This library implements the [CELL Specification v1.0.0](https://sashite.dev/specs/cell/1.0.0/).
13
13
 
14
- This gem implements the [CELL Specification v1.0.0](https://sashite.dev/specs/cell/1.0.0/), providing a Ruby interface for working with multi-dimensional game coordinates through a clean, functional API.
14
+ ### Implementation Constraints
15
+
16
+ | Constraint | Value | Rationale |
17
+ |------------|-------|-----------|
18
+ | Max dimensions | 3 | Sufficient for 1D, 2D, 3D boards |
19
+ | Max index value | 255 | Fits in 8-bit integer, covers 256×256×256 boards |
20
+ | Max string length | 7 | `"iv256IV"` (max for all dimensions at 255) |
21
+
22
+ These constraints enable bounded memory usage and safe parsing.
15
23
 
16
24
  ## Installation
17
25
 
@@ -26,322 +34,191 @@ Or install manually:
26
34
  gem install sashite-cell
27
35
  ```
28
36
 
29
- ## CELL Format
30
-
31
- CELL uses a cyclical three-character-set system that repeats indefinitely based on dimensional position:
32
-
33
- **Dimension (n % 3 = 1)**: Latin Lowercase Letters
34
- - `a`, `b`, `c`, ..., `z`, `aa`, `ab`, ..., `zz`, `aaa`, ...
37
+ ## Usage
35
38
 
36
- **Dimension (n % 3 = 2)**: Arabic Numerals
37
- - `1`, `2`, `3`, ..., `25`, `26`, ...
38
-
39
- **Dimension (n % 3 = 0)**: Latin Uppercase Letters
40
- - `A`, `B`, `C`, ..., `Z`, `AA`, `AB`, ..., `ZZ`, `AAA`, ...
41
-
42
- ## Basic Usage
43
-
44
- ### Validation
39
+ ### Parsing (String Coordinate)
45
40
 
46
- The primary functionality is validating CELL coordinates:
41
+ Convert a CELL string into a `Coordinate` object.
47
42
 
48
43
  ```ruby
49
44
  require "sashite/cell"
50
45
 
51
- # Check if a string represents a valid CELL coordinate
52
- Sashite::Cell.valid?("a1") # => true (2D coordinate)
53
- Sashite::Cell.valid?("a1A") # => true (3D coordinate)
54
- Sashite::Cell.valid?("e4") # => true (2D coordinate)
55
- Sashite::Cell.valid?("h8Hh8") # => true (5D coordinate)
56
- Sashite::Cell.valid?("*") # => false (not a CELL coordinate)
57
- Sashite::Cell.valid?("a0") # => false (invalid numeral)
58
- Sashite::Cell.valid?("") # => false (empty string)
59
-
60
- # Alias for convenience
61
- Cell = Sashite::Cell
62
- Cell.valid?("a1") # => true
63
- ```
64
-
65
- ### Dimensional Analysis
66
-
67
- ```ruby
68
- # Get the number of dimensions in a coordinate
69
- Sashite::Cell.dimensions("a1") # => 2
70
- Sashite::Cell.dimensions("a1A") # => 3
71
- Sashite::Cell.dimensions("h8Hh8") # => 5
72
- Sashite::Cell.dimensions("foobar") # => 1
73
-
74
- # Parse coordinate into dimensional components
75
- Sashite::Cell.parse("a1A")
76
- # => ["a", "1", "A"]
77
-
78
- Sashite::Cell.parse("h8Hh8")
79
- # => ["h", "8", "H", "h", "8"]
80
-
81
- Sashite::Cell.parse("foobar")
82
- # => ["foobar"]
83
- ```
84
-
85
- ### Coordinate Conversion
86
-
87
- ```ruby
88
- # Convert coordinates to arrays of integers (0-indexed)
89
- Sashite::Cell.to_indices("a1")
90
- # => [0, 0]
91
-
92
- Sashite::Cell.to_indices("e4")
93
- # => [4, 3]
46
+ # Standard parsing (returns Coordinate or raises)
47
+ coord = Sashite::Cell.parse("e4")
48
+ coord.indices # => [4, 3]
49
+ coord.dimensions # => 2
94
50
 
95
- Sashite::Cell.to_indices("a1A")
96
- # => [0, 0, 0]
51
+ # 3D coordinate
52
+ coord = Sashite::Cell.parse("a1A")
53
+ coord.indices # => [0, 0, 0]
97
54
 
98
- # Convert arrays of integers back to CELL coordinates
99
- Sashite::Cell.from_indices(0, 0)
100
- # => "a1"
101
-
102
- Sashite::Cell.from_indices(4, 3)
103
- # => "e4"
104
-
105
- Sashite::Cell.from_indices(0, 0, 0)
106
- # => "a1A"
55
+ # Invalid input raises Sashite::Cell::Errors::Argument
56
+ Sashite::Cell.parse("a0") # => raises Sashite::Cell::Errors::Argument
107
57
  ```
108
58
 
109
- ## Usage Examples
59
+ ### Formatting (Coordinate → String)
110
60
 
111
- ### Chess Board (8x8)
61
+ Convert a `Coordinate` back to a CELL string.
112
62
 
113
63
  ```ruby
114
- # Standard chess notation mapping
115
- chess_squares = %w[a1 b1 c1 d1 e1 f1 g1 h1
116
- a2 b2 c2 d2 e2 f2 g2 h2
117
- a3 b3 c3 d3 e3 f3 g3 h3
118
- a4 b4 c4 d4 e4 f4 g4 h4
119
- a5 b5 c5 d5 e5 f5 g5 h5
120
- a6 b6 c6 d6 e6 f6 g6 h6
121
- a7 b7 c7 d7 e7 f7 g7 h7
122
- a8 b8 c8 d8 e8 f8 g8 h8]
123
-
124
- chess_squares.all? { |square| Sashite::Cell.valid?(square) }
125
- # => true
64
+ # From Coordinate object
65
+ coord = Sashite::Cell::Coordinate.new(4, 3)
66
+ coord.to_s # => "e4"
67
+
68
+ # Direct formatting (convenience)
69
+ Sashite::Cell.format(2, 2, 2) # => "c3C"
126
70
  ```
127
71
 
128
- ### Shōgi Board (9x9)
72
+ ### Validation
129
73
 
130
74
  ```ruby
131
- # CELL coordinates for shōgi positions
132
- shogi_positions = %w[a1 e5 i9] # Left corner, center, right corner
133
- shogi_positions.all? { |pos| Sashite::Cell.valid?(pos) }
134
- # => true
75
+ # Boolean check
76
+ Sashite::Cell.valid?("e4") # => true
135
77
 
136
- # Convert to indices for board representation
137
- Sashite::Cell.to_indices("e5") # => [4, 4] (center of 9x9 board)
78
+ # Detailed error
79
+ Sashite::Cell.validate("a0") # => raises Sashite::Cell::Errors::Argument, "leading zero"
138
80
  ```
139
81
 
140
- ### 3D Tic-Tac-Toe (3x3x3)
82
+ ### Accessing Coordinate Data
141
83
 
142
84
  ```ruby
143
- # Three-dimensional game coordinates
144
- positions_3d = %w[a1A b2B c3C a2B b3C c1A]
145
- positions_3d.all? { |pos| Sashite::Cell.valid?(pos) && Sashite::Cell.dimensions(pos) == 3 }
146
- # => true
147
-
148
- # Winning diagonal across all three dimensions
149
- diagonal_win = %w[a1A b2B c3C]
150
- diagonal_win.map { |pos| Sashite::Cell.to_indices(pos) }
151
- # => [[0,0,0], [1,1,1], [2,2,2]]
152
- ```
153
-
154
- ### Multi-dimensional Coordinates
85
+ coord = Sashite::Cell.parse("e4")
155
86
 
156
- ```ruby
157
- # Higher dimensional coordinates
158
- coord_4d = "a1Aa"
159
- coord_5d = "b2Bb2"
87
+ # Get dimensions count
88
+ coord.dimensions # => 2
160
89
 
161
- Sashite::Cell.dimensions(coord_4d) # => 4
162
- Sashite::Cell.dimensions(coord_5d) # => 5
90
+ # Get indices as array
91
+ coord.indices # => [4, 3]
163
92
 
164
- # Parse into components
165
- Sashite::Cell.parse(coord_4d) # => ["a", "1", "A", "a"]
166
- Sashite::Cell.parse(coord_5d) # => ["b", "2", "B", "b", "2"]
93
+ # Access individual index
94
+ coord.indices[0] # => 4
95
+ coord.indices[1] # => 3
167
96
  ```
168
97
 
169
98
  ## API Reference
170
99
 
171
- ### Module Methods
172
-
173
- #### Validation
174
- - `Sashite::Cell.valid?(string)` - Check if string represents a valid CELL coordinate
175
-
176
- #### Analysis
177
- - `Sashite::Cell.dimensions(string)` - Get number of dimensions
178
- - `Sashite::Cell.parse(string)` - Parse coordinate into dimensional components array
179
-
180
- #### Conversion
181
- - `Sashite::Cell.to_indices(string)` - Convert CELL coordinate to 0-indexed integer array
182
- - `Sashite::Cell.from_indices(*indices)` - Convert splat indices to CELL coordinate
183
-
184
- #### Utilities
185
- - `Sashite::Cell.regex` - Get the validation regular expression
186
-
187
- ### Constants
188
-
189
- - `Sashite::Cell::REGEX` - Regular expression for CELL validation per specification v1.0.0
190
-
191
- ## Properties of CELL
192
-
193
- * **Multi-dimensional**: Supports unlimited dimensional coordinate systems
194
- * **Cyclical**: Uses systematic three-character-set repetition
195
- * **ASCII-based**: Pure ASCII characters for universal compatibility
196
- * **Unambiguous**: Each coordinate maps to exactly one location
197
- * **Scalable**: Extends naturally from 1D to unlimited dimensions
198
- * **Rule-agnostic**: Independent of specific game mechanics
199
-
200
- ## Character Set Details
201
-
202
- ### Latin Lowercase (Dimensions 1, 4, 7, ...)
203
- Single letters: `a` through `z` (positions 0-25)
204
- Double letters: `aa` through `zz` (positions 26-701)
205
- Triple letters: `aaa` through `zzz` (positions 702-18277)
206
- And so on...
207
-
208
- ### Arabic Numerals (Dimensions 2, 5, 8, ...)
209
- Standard decimal notation: `1`, `2`, `3`, ... (1-indexed)
210
- No leading zeros, unlimited range
211
-
212
- ### Latin Uppercase (Dimensions 3, 6, 9, ...)
213
- Single letters: `A` through `Z` (positions 0-25)
214
- Double letters: `AA` through `ZZ` (positions 26-701)
215
- Triple letters: `AAA` through `ZZZ` (positions 702-18277)
216
- And so on...
217
-
218
- ## Integration with DROP
219
-
220
- CELL complements the DROP specification for complete location coverage:
100
+ ### Types
221
101
 
222
102
  ```ruby
223
- # Combined location validation
224
- def valid_game_location?(location)
225
- Sashite::Cell.valid?(location) || Sashite::Drop.reserve?(location)
103
+ # Coordinate represents a parsed CELL coordinate with up to 3 dimensions.
104
+ class Sashite::Cell::Coordinate
105
+ # Creates a Coordinate from 1 to 3 indices.
106
+ # Raises Sashite::Cell::Errors::Argument if no indices provided or more than 3.
107
+ #
108
+ # @param indices [Array<Integer>] 0-indexed coordinate values (0-255)
109
+ # @return [Coordinate]
110
+ def initialize(*indices)
111
+
112
+ # Returns the number of dimensions (1, 2, or 3).
113
+ #
114
+ # @return [Integer]
115
+ def dimensions
116
+
117
+ # Returns the coordinate indices as a frozen array.
118
+ #
119
+ # @return [Array<Integer>]
120
+ def indices
121
+
122
+ # Returns the CELL string representation.
123
+ #
124
+ # @return [String]
125
+ def to_s
226
126
  end
227
-
228
- valid_game_location?("a1") # => true (board position)
229
- valid_game_location?("*") # => true (reserve position)
230
- valid_game_location?("$") # => false (invalid)
231
127
  ```
232
128
 
233
- ## Examples in Different Games
234
-
235
- ### Chess
129
+ ### Constants
236
130
 
237
131
  ```ruby
238
- # Standard algebraic notation positions
239
- start_position = "e2"
240
- end_position = "e4"
241
-
242
- Sashite::Cell.valid?(start_position) # => true
243
- Sashite::Cell.valid?(end_position) # => true
244
-
245
- # Convert to array indices for board representation
246
- Sashite::Cell.to_indices("e4") # => [4, 3]
132
+ Sashite::Cell::Constants::MAX_DIMENSIONS # => 3
133
+ Sashite::Cell::Constants::MAX_INDEX_VALUE # => 255
134
+ Sashite::Cell::Constants::MAX_STRING_LENGTH # => 7
247
135
  ```
248
136
 
249
- ### Go (19x19)
137
+ ### Parsing
250
138
 
251
139
  ```ruby
252
- # Go board positions
253
- corner = "a1" # Corner position
254
- edge = "j1" # Edge position
255
- tengen = "j10" # Center point (tengen) on 19x19 board
256
-
257
- [corner, edge, tengen].all? { |pos| Sashite::Cell.valid?(pos) }
258
- # => true
140
+ # Parses a CELL string into a Coordinate.
141
+ # Raises Sashite::Cell::Errors::Argument if the string is not valid.
142
+ #
143
+ # @param string [String] CELL coordinate string
144
+ # @return [Coordinate]
145
+ # @raise [Sashite::Cell::Errors::Argument] if invalid
146
+ def Sashite::Cell.parse(string)
259
147
  ```
260
148
 
261
- ### Abstract Strategy Games
149
+ ### Formatting
262
150
 
263
151
  ```ruby
264
- # Multi-dimensional abstract games
265
- hypercube_4d = "a1Aa"
266
- tesseract_pos = "h8Hh8"
267
-
268
- # Validate high-dimensional coordinates
269
- Sashite::Cell.valid?(hypercube_4d) # => true
270
- Sashite::Cell.dimensions(tesseract_pos) # => 5
271
-
272
- # Convert for mathematical operations
273
- Sashite::Cell.to_indices(hypercube_4d) # => [0, 0, 0, 0]
152
+ # Formats indices into a CELL string.
153
+ # Convenience method equivalent to Coordinate.new(*indices).to_s.
154
+ #
155
+ # @param indices [Array<Integer>] 0-indexed coordinate values
156
+ # @return [String]
157
+ def Sashite::Cell.format(*indices)
274
158
  ```
275
159
 
276
- ## Specification Compliance
277
-
278
- This implementation strictly follows the [CELL Specification v1.0.0](https://sashite.dev/specs/cell/1.0.0/) and includes:
279
-
280
- - **Exact regex**: Uses the official validation pattern from the specification
281
- - **Complete API**: All methods and behaviors defined in the specification
282
- - **Full test coverage**: Validates against all specification examples
283
- - **Round-trip safety**: Guaranteed coordinate ↔ indices conversion integrity
284
-
285
- ### Specification Examples
286
-
287
- All examples from the CELL specification work correctly:
160
+ ### Validation
288
161
 
289
162
  ```ruby
290
- # Basic Examples from spec
291
- Sashite::Cell.valid?("a") # => true (1D)
292
- Sashite::Cell.valid?("a1") # => true (2D)
293
- Sashite::Cell.valid?("a1A") # => true (3D)
294
- Sashite::Cell.valid?("a1Aa1A") # => true (6D)
295
-
296
- # Extended Alphabet Examples from spec
297
- Sashite::Cell.valid?("aa1AA") # => true
298
- Sashite::Cell.valid?("z26Z") # => true
299
- Sashite::Cell.valid?("abc123XYZ") # => true
300
-
301
- # Invalid Examples from spec
302
- Sashite::Cell.valid?("") # => false
303
- Sashite::Cell.valid?("a0") # => false
304
- Sashite::Cell.valid?("1a") # => false
305
- Sashite::Cell.valid?("a1a") # => false
163
+ # Validates a CELL string.
164
+ # Raises Sashite::Cell::Errors::Argument with descriptive message if invalid.
165
+ #
166
+ # @param string [String] CELL coordinate string
167
+ # @return [nil]
168
+ # @raise [Sashite::Cell::Errors::Argument] if invalid
169
+ def Sashite::Cell.validate(string)
170
+
171
+ # Reports whether string is a valid CELL coordinate.
172
+ #
173
+ # @param string [String] CELL coordinate string
174
+ # @return [Boolean]
175
+ def Sashite::Cell.valid?(string)
306
176
  ```
307
177
 
308
- ## Documentation
178
+ ### Errors
309
179
 
310
- - [Official CELL Specification v1.0.0](https://sashite.dev/specs/cell/1.0.0/)
311
- - [API Documentation](https://rubydoc.info/github/sashite/cell.rb/main)
312
- - [CELL Examples](https://sashite.dev/specs/cell/1.0.0/examples/)
180
+ All parsing and validation errors raise `Sashite::Cell::Errors::Argument` (a subclass of `ArgumentError`) with descriptive messages:
313
181
 
314
- ## Development
182
+ | Message | Cause |
183
+ |---------|-------|
184
+ | `"empty input"` | String length is 0 |
185
+ | `"input exceeds 7 characters"` | String too long |
186
+ | `"must start with lowercase letter"` | Invalid first character |
187
+ | `"unexpected character"` | Character violates the cyclic sequence |
188
+ | `"leading zero"` | Numeric part starts with '0' |
189
+ | `"exceeds 3 dimensions"` | More than 3 dimensions |
190
+ | `"index exceeds 255"` | Decoded value out of range |
315
191
 
316
- ```sh
317
- # Clone the repository
318
- git clone https://github.com/sashite/cell.rb.git
319
- cd cell.rb
320
-
321
- # Install dependencies
322
- bundle install
323
-
324
- # Run tests
325
- ruby test.rb
192
+ ```ruby
193
+ begin
194
+ Sashite::Cell.parse("a0")
195
+ rescue Sashite::Cell::Errors::Argument => e
196
+ puts e.message # => "leading zero"
197
+ end
326
198
 
327
- # Generate documentation
328
- yard doc
199
+ # Also catchable as ArgumentError for compatibility
200
+ begin
201
+ Sashite::Cell.parse("a0")
202
+ rescue ArgumentError => e
203
+ puts e.message # => "leading zero"
204
+ end
329
205
  ```
330
206
 
331
- ## Contributing
207
+ ## Design Principles
332
208
 
333
- 1. Fork the repository
334
- 2. Create a feature branch (`git checkout -b feature/new-feature`)
335
- 3. Add tests for your changes
336
- 4. Ensure all tests pass (`ruby test.rb`)
337
- 5. Commit your changes (`git commit -am 'Add new feature'`)
338
- 6. Push to the branch (`git push origin feature/new-feature`)
339
- 7. Create a Pull Request
209
+ - **Bounded values**: Index validation prevents overflow
210
+ - **Object-oriented**: `Coordinate` class enables methods and encapsulation
211
+ - **Ruby idioms**: `valid?` predicate, `to_s` conversion
212
+ - **Custom error class**: `Errors::Argument` inherits from `ArgumentError` for precise error handling
213
+ - **Immutable coordinates**: Frozen indices array prevents mutation
214
+ - **No dependencies**: Pure Ruby standard library only
340
215
 
341
- ## License
216
+ ## Related Specifications
342
217
 
343
- Available as open source under the [MIT License](https://opensource.org/licenses/MIT).
218
+ - [Game Protocol](https://sashite.dev/game-protocol/) — Conceptual foundation
219
+ - [CELL Specification](https://sashite.dev/specs/cell/1.0.0/) — Official specification
220
+ - [CELL Examples](https://sashite.dev/specs/cell/1.0.0/examples/) — Usage examples
344
221
 
345
- ## About
222
+ ## License
346
223
 
347
- Maintained by [Sashité](https://sashite.com/) — promoting chess variants and sharing the beauty of board game cultures.
224
+ Available as open source under the [Apache License 2.0](https://opensource.org/licenses/Apache-2.0).
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sashite
4
+ module Cell
5
+ module Constants
6
+ # Maximum number of dimensions supported by a CELL coordinate.
7
+ # Sufficient for 1D, 2D, and 3D game boards.
8
+ MAX_DIMENSIONS = 3
9
+
10
+ # Maximum value for a single coordinate index.
11
+ # Fits in an 8-bit unsigned integer (0-255).
12
+ MAX_INDEX_VALUE = 255
13
+
14
+ # Maximum length of a CELL string representation.
15
+ # Corresponds to "iv256IV" (worst case for all dimensions at 255).
16
+ MAX_STRING_LENGTH = 7
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "constants"
4
+ require_relative "errors"
5
+ require_relative "formatter"
6
+
7
+ module Sashite
8
+ module Cell
9
+ # Represents a parsed CELL coordinate with up to 3 dimensions.
10
+ #
11
+ # A Coordinate holds 0-indexed integer values for each dimension
12
+ # and provides conversion to/from CELL string format.
13
+ #
14
+ # @example Creating a coordinate
15
+ # coord = Sashite::Cell::Coordinate.new(4, 3)
16
+ # coord.indices # => [4, 3]
17
+ # coord.dimensions # => 2
18
+ # coord.to_s # => "e4"
19
+ #
20
+ # @example 3D coordinate
21
+ # coord = Sashite::Cell::Coordinate.new(0, 0, 0)
22
+ # coord.to_s # => "a1A"
23
+ class Coordinate
24
+ # Returns the coordinate indices as a frozen array.
25
+ #
26
+ # @return [Array<Integer>] 0-indexed coordinate values
27
+ #
28
+ # @example
29
+ # Sashite::Cell::Coordinate.new(4, 3).indices # => [4, 3]
30
+ attr_reader :indices
31
+
32
+ # Creates a Coordinate from 1 to 3 indices.
33
+ #
34
+ # @param indices [Array<Integer>] 0-indexed coordinate values (0-255 each)
35
+ # @raise [Sashite::Cell::Errors::Argument] if no indices provided, more than 3, or values out of range
36
+ #
37
+ # @example
38
+ # Sashite::Cell::Coordinate.new(4, 3) # 2D coordinate
39
+ # Sashite::Cell::Coordinate.new(0, 0, 0) # 3D coordinate
40
+ def initialize(*indices)
41
+ if indices.empty?
42
+ raise Errors::Argument, Errors::Argument::Messages::NO_INDICES
43
+ end
44
+
45
+ if indices.size > Constants::MAX_DIMENSIONS
46
+ raise Errors::Argument, Errors::Argument::Messages::TOO_MANY_DIMENSIONS
47
+ end
48
+
49
+ indices.each do |index|
50
+ unless index.is_a?(::Integer) && index >= 0 && index <= Constants::MAX_INDEX_VALUE
51
+ raise Errors::Argument, Errors::Argument::Messages::INDEX_OUT_OF_RANGE
52
+ end
53
+ end
54
+
55
+ @indices = indices.freeze
56
+ end
57
+
58
+ # Returns the number of dimensions (1, 2, or 3).
59
+ #
60
+ # @return [Integer] dimension count
61
+ #
62
+ # @example
63
+ # Sashite::Cell::Coordinate.new(4, 3).dimensions # => 2
64
+ def dimensions
65
+ @indices.size
66
+ end
67
+
68
+ # Returns the CELL string representation.
69
+ #
70
+ # @return [String] CELL coordinate string
71
+ #
72
+ # @example
73
+ # Sashite::Cell::Coordinate.new(4, 3).to_s # => "e4"
74
+ def to_s
75
+ Formatter.indices_to_string(@indices)
76
+ end
77
+
78
+ # Checks equality with another Coordinate.
79
+ #
80
+ # @param other [Object] object to compare
81
+ # @return [Boolean] true if equal, false otherwise
82
+ #
83
+ # @example
84
+ # Sashite::Cell::Coordinate.new(4, 3) == Sashite::Cell::Coordinate.new(4, 3) # => true
85
+ def ==(other)
86
+ other.is_a?(Coordinate) && @indices == other.indices
87
+ end
88
+
89
+ alias eql? ==
90
+
91
+ # Returns hash code for use in Hash keys.
92
+ #
93
+ # @return [Integer] hash code
94
+ #
95
+ # @example
96
+ # coord = Sashite::Cell::Coordinate.new(4, 3)
97
+ # hash = { coord => "value" }
98
+ def hash
99
+ @indices.hash
100
+ end
101
+
102
+ # Returns a human-readable representation.
103
+ #
104
+ # @return [String] inspection string
105
+ #
106
+ # @example
107
+ # Sashite::Cell::Coordinate.new(4, 3).inspect # => "#<Sashite::Cell::Coordinate e4>"
108
+ def inspect
109
+ "#<#{self.class} #{self}>"
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sashite
4
+ module Cell
5
+ module Errors
6
+ class Argument < ::ArgumentError
7
+ # Error messages for validation failures.
8
+ # Kept as constants to ensure consistency across the library.
9
+ module Messages
10
+ EMPTY_INPUT = "empty input"
11
+ INPUT_TOO_LONG = "input exceeds 7 characters"
12
+ INVALID_START = "must start with lowercase letter"
13
+ UNEXPECTED_CHARACTER = "unexpected character"
14
+ LEADING_ZERO = "leading zero"
15
+ TOO_MANY_DIMENSIONS = "exceeds 3 dimensions"
16
+ INDEX_OUT_OF_RANGE = "index exceeds 255"
17
+ NO_INDICES = "at least one index required"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "argument/messages"
4
+
5
+ module Sashite
6
+ module Cell
7
+ module Errors
8
+ # Custom error class for CELL parsing and validation failures.
9
+ #
10
+ # Inherits from ArgumentError to maintain semantic meaning
11
+ # while allowing specific rescue of CELL-related errors.
12
+ #
13
+ # @example Rescuing specific CELL errors
14
+ # begin
15
+ # Sashite::Cell.parse("invalid")
16
+ # rescue Sashite::Cell::Errors::Argument => e
17
+ # puts "CELL error: #{e.message}"
18
+ # end
19
+ #
20
+ # @example Rescuing as ArgumentError
21
+ # begin
22
+ # Sashite::Cell.parse("invalid")
23
+ # rescue ArgumentError => e
24
+ # puts "Argument error: #{e.message}"
25
+ # end
26
+ class Argument < ::ArgumentError
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "errors/argument"