# sin.ex
[](https://hex.pm/packages/sashite_sin)
[](https://hexdocs.pm/sashite_sin)
[](https://github.com/sashite/sin.ex/blob/main/LICENSE)
> **SIN** (Style Identifier Notation) implementation for Elixir.
## Overview
This library implements the [SIN Specification v1.0.0](https://sashite.dev/specs/sin/1.0.0/).
## Installation
Add `sashite_sin` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:sashite_sin, "~> 2.0"}
]
end
```
## Usage
### Parsing (String → Identifier)
Convert a SIN string into an `Identifier` struct.
```elixir
alias Sashite.Sin.Identifier
# Standard parsing (returns {:ok, _} or {:error, _})
{:ok, sin} = Sashite.Sin.parse("C")
sin.style # => :C
sin.side # => :first
# Lowercase indicates second player
{:ok, sin} = Sashite.Sin.parse("c")
sin.style # => :C
sin.side # => :second
# Bang version (raises on error)
sin = Sashite.Sin.parse!("C")
# Invalid input returns error tuple
{:error, :empty_input} = Sashite.Sin.parse("")
{:error, :input_too_long} = Sashite.Sin.parse("CC")
```
### Formatting (Identifier → String)
Convert an `Identifier` back to a SIN string.
```elixir
alias Sashite.Sin.Identifier
# From Identifier struct
sin = Identifier.new(:C, :first)
Identifier.to_string(sin) # => "C"
sin = Identifier.new(:C, :second)
Identifier.to_string(sin) # => "c"
```
### Validation
```elixir
# Boolean check
Sashite.Sin.valid?("C") # => true
Sashite.Sin.valid?("c") # => true
Sashite.Sin.valid?("") # => false
Sashite.Sin.valid?("CC") # => false
Sashite.Sin.valid?("1") # => false
```
### Accessing Identifier Data
```elixir
sin = Sashite.Sin.parse!("C")
# Get attributes (struct fields)
sin.style # => :C
sin.side # => :first
# Get string component
Sashite.Sin.Identifier.letter(sin) # => "C"
```
### Transformations
All transformations return new immutable `Identifier` structs.
```elixir
alias Sashite.Sin.Identifier
sin = Sashite.Sin.parse!("C")
# Side transformation
Identifier.flip(sin) |> Identifier.to_string() # => "c"
# Attribute changes
Identifier.with_style(sin, :S) |> Identifier.to_string() # => "S"
Identifier.with_side(sin, :second) |> Identifier.to_string() # => "c"
```
### Queries
```elixir
alias Sashite.Sin.Identifier
sin = Sashite.Sin.parse!("C")
# Side queries
Identifier.first_player?(sin) # => true
Identifier.second_player?(sin) # => false
# Comparison queries
other = Sashite.Sin.parse!("c")
Identifier.same_style?(sin, other) # => true
Identifier.same_side?(sin, other) # => false
```
## API Reference
### Types
```elixir
# Identifier represents a parsed SIN identifier with style and side.
%Sashite.Sin.Identifier{
style: :A..:Z, # Piece style (always uppercase atom)
side: :first | :second # Player side
}
# Create an Identifier from style and side.
# Raises ArgumentError if attributes are invalid.
Sashite.Sin.Identifier.new(style, side)
```
### Constants
```elixir
Sashite.Sin.Constants.valid_styles() # => [:A, :B, ..., :Z]
Sashite.Sin.Constants.valid_sides() # => [:first, :second]
Sashite.Sin.Constants.max_string_length() # => 1
```
### Parsing
```elixir
# Parses a SIN string into an Identifier.
# Returns {:ok, identifier} or {:error, reason}.
@spec Sashite.Sin.parse(String.t()) :: {:ok, Identifier.t()} | {:error, atom()}
# Parses a SIN string into an Identifier.
# Raises ArgumentError if the string is not valid.
@spec Sashite.Sin.parse!(String.t()) :: Identifier.t()
```
### Validation
```elixir
# Reports whether string is a valid SIN identifier.
@spec Sashite.Sin.valid?(String.t()) :: boolean()
```
### Transformations
All transformations return new `Sashite.Sin.Identifier` structs:
```elixir
# Side transformation
@spec Identifier.flip(Identifier.t()) :: Identifier.t()
# Attribute changes
@spec Identifier.with_style(Identifier.t(), atom()) :: Identifier.t()
@spec Identifier.with_side(Identifier.t(), atom()) :: Identifier.t()
```
### Queries
```elixir
# Side queries
@spec Identifier.first_player?(Identifier.t()) :: boolean()
@spec Identifier.second_player?(Identifier.t()) :: boolean()
# Comparison queries
@spec Identifier.same_style?(Identifier.t(), Identifier.t()) :: boolean()
@spec Identifier.same_side?(Identifier.t(), Identifier.t()) :: boolean()
```
### Errors
Parsing errors are returned as atoms:
| Atom | Cause |
|------|-------|
| `:empty_input` | String length is 0 |
| `:input_too_long` | String exceeds 1 character |
| `:must_be_letter` | Character is not A-Z or a-z |
## Design Principles
- **Bounded values**: Explicit validation of styles and sides
- **Struct-based**: `Identifier` struct enables pattern matching and encapsulation
- **Elixir idioms**: `{:ok, _}` / `{:error, _}` tuples, `parse!` bang variant
- **Immutable data**: All transformations return new structs
- **No dependencies**: Pure Elixir standard library only
## Related Specifications
- [Game Protocol](https://sashite.dev/game-protocol/) — Conceptual foundation
- [SIN Specification](https://sashite.dev/specs/sin/1.0.0/) — Official specification
- [SIN Examples](https://sashite.dev/specs/sin/1.0.0/examples/) — Usage examples
## License
Available as open source under the [Apache License 2.0](https://opensource.org/licenses/Apache-2.0).