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 Docs.rs CI License

SIN (Style Identifier Notation) implementation for Rust.

Overview

This crate implements the SIN Specification v1.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.

<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 and 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

cargo add sashite-sin

Or add it manually to Cargo.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.
[dependencies]
sashite-sin = { version = "1", features = ["serde"] }

Usage

Parsing

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.

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.

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

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):

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:

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 (AZ)

Letters are not reserved: the mapping from abbreviation to full style name is defined entirely by the rule system. See the examples page 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

Reference implementations in other languages are maintained by Sashité: Elixir, Ruby.

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.