sashite-sin 1.0.0

Style Identifier Notation (SIN): a compact, ASCII-only, no_std token encoding a player's side and style in abstract strategy board games.
Documentation
# sashite-sin

[![Crates.io](https://img.shields.io/crates/v/sashite-sin.svg)](https://crates.io/crates/sashite-sin)
[![Docs.rs](https://docs.rs/sashite-sin/badge.svg)](https://docs.rs/sashite-sin)
[![CI](https://github.com/sashite/sin.rs/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/sashite/sin.rs/actions/workflows/ci.yml)
[![License](https://img.shields.io/crates/l/sashite-sin.svg)](https://github.com/sashite/sin.rs/blob/main/LICENSE)

> **SIN** (Style Identifier Notation) implementation for Rust.

## Overview

This crate implements the [SIN Specification v1.0.0](https://sashite.dev/specs/sin/1.0.0/).

SIN is a compact, ASCII-only token format that encodes a **player identity** at
the level of notation: the tuple (player side, player style). A single letter
carries both — its case encodes the side, and the letter itself is the
player-style abbreviation.

```text
<abbr>      e.g.  W   c   J   s
```

Because a player's side and style are both fixed for the duration of a match, a
SIN token is a stable player identifier. SIN standardizes only the *encoding*:
which letter denotes which style is left to the rule system — see the
[Game Protocol](https://sashite.dev/game-protocol/) and [Glossary](https://sashite.dev/glossary/).

### Implementation constraints

| Property            | Value                | Rationale                                                  |
|---------------------|----------------------|------------------------------------------------------------|
| Token length        | 1 byte               | `^[A-Za-z]$` per the specification                         |
| Closed domain       | 52 tokens            | 26 letters × 2 sides                                       |
| `Identifier` size   | 2 bytes, `Copy`      | stored inline; parsing and encoding never allocate         |
| Dependencies        | none required        | zero by default; `serde` is an optional, `no_std` add-on   |
| `unsafe`            | forbidden            | the crate is built under a forbid-`unsafe` lint policy     |
| MSRV                | 1.81                 | for `core::error::Error` without a `std` feature           |

## Installation

```sh
cargo add sashite-sin
```

Or add it manually to `Cargo.toml`:

```toml
[dependencies]
sashite-sin = "1"
```

## Cargo features

- **`serde`** *(off by default)* — implements `Serialize` / `Deserialize` for
  `Identifier`, (de)serializing it as its canonical token string (e.g. `"W"`).
  Enabling it keeps the crate `no_std`.

```toml
[dependencies]
sashite-sin = { version = "1", features = ["serde"] }
```

## Usage

### Parsing

```rust
use sashite_sin::{Identifier, Side};

let western: Identifier = "W".parse()?;      // via FromStr
let chinese = Identifier::parse("c")?;       // via the inherent method

assert_eq!(western.letter().as_char(), 'W');
assert_eq!(western.side(), Side::First);

assert_eq!(chinese.side(), Side::Second);
```

### Building from typed components

Construction is **infallible**: because each component type is valid by
construction, every combination denotes a valid token.

```rust
use sashite_sin::{Identifier, Letter, Side};

let japanese = Identifier::new(Letter::try_from_char('J')?, Side::Second);
assert_eq!(japanese.encode().as_str(), "j");
```

### Encoding and formatting

`encode` returns an allocation-free, fixed-buffer string view that dereferences
to `str`; `to_char` returns the single character; `Display` writes the same
canonical form.

```rust
use sashite_sin::Identifier;

let id = Identifier::parse("W")?;
assert_eq!(id.encode().as_str(), "W");
assert_eq!(id.to_char(), 'W');
assert_eq!(id.to_string(), "W");             // requires `alloc`/`std`
```

### Validation

```rust
use sashite_sin::Identifier;

assert!(Identifier::is_valid("S"));
assert!(!Identifier::is_valid("WW"));         // a token is exactly one letter
```

### Transformations and queries

Every transformation returns a new value (the type is `Copy`, so this is cheap):

```rust
use sashite_sin::{Identifier, Letter};

let western = Identifier::parse("W")?;
assert_eq!(western.flipped().encode().as_str(), "w");
assert_eq!(
    western.with_letter(Letter::try_from_char('C')?).encode().as_str(),
    "C",
);

assert!(western.is_first());
```

## Token format

The grammar (EBNF) is:

```ebnf
sin  ::= abbr ;
abbr ::= "A"…"Z" | "a"…"z" ;
```

A token maps to exactly two attributes:

| Component   | Encodes      | Values                                    |
|-------------|--------------|-------------------------------------------|
| letter case | side         | uppercase → `First`, lowercase → `Second` |
| letter      | player style | a single-letter abbreviation (`A``Z`)    |

Letters are not reserved: the mapping from abbreviation to full style name is
defined entirely by the rule system. See the
[examples page](https://sashite.dev/specs/sin/1.0.0/examples/) for the
conventional mappings (Western, Chinese, Japanese, Siamese).

## Design and guarantees

- **`no_std` and allocation-free.** Parsing borrows the input bytes; an
  `Identifier` is a 2-byte `Copy` value and `EncodedSin` keeps the single output
  byte in a fixed inline buffer. Nothing touches the heap.
- **No `unsafe`, no regex engine.** The parser matches raw bytes directly,
  eliminating ReDoS as an attack vector.
- **Bounded, panic-free parsing.** Inputs longer than one byte are rejected on a
  structural length check before any byte is inspected, and the public parsing
  API returns a `Result` rather than panicking.
- **`const`-friendly.** Construction, parsing, validation, the accessors, and
  the transformations are all `const fn`, so identifiers can be built and
  checked at compile time.
- **Total component construction.** With valid-by-construction component types,
  building an identifier from its parts cannot fail.

### Performance

The hot paths are tiny: parsing is a length check followed by a single byte
classification, and encoding writes one byte — expect single-digit nanoseconds
per call. Run `cargo bench` (see `benches/parse.rs`) for figures on your own
hardware.

## Related specifications

- [Game Protocol]https://sashite.dev/game-protocol/ — the conceptual foundation
- [SIN Specification v1.0.0]https://sashite.dev/specs/sin/1.0.0/ — the normative document
- [SIN Examples]https://sashite.dev/specs/sin/1.0.0/examples/ — sample style mappings

Reference implementations in other languages are maintained by Sashité:
[Elixir](https://github.com/sashite/sin.ex),
[Ruby](https://github.com/sashite/sin.rb).

If a library's behavior appears to conflict with the specification, the
specification is normative.

## License

Available as open source under the terms of the [Apache License 2.0](https://opensource.org/licenses/Apache-2.0).