Skip to content

Repository files navigation

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().expect("a valid SIN token"); // via FromStr
let chinese = Identifier::parse("c").expect("a valid SIN token");  // inherent

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 letter = Letter::try_from_char('J').expect("an ASCII letter");
let japanese = Identifier::new(letter, 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").expect("a valid SIN token");
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").expect("a valid SIN token");
assert_eq!(western.flipped().encode().as_str(), "w");
assert_eq!(
    western
        .with_letter(Letter::try_from_char('C').expect("an ASCII letter"))
        .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. An Identifier borrows nothing — it is a self-contained 2-byte Copy value — and EncodedSin keeps the single output byte in a fixed inline buffer. Nothing touches the heap, and no output outlives its input.
  • String-like formatting. Identifier, EncodedSin and ParseError all render through core::fmt::Formatter::pad, so width, fill, alignment and precision work exactly as they do for str.
  • Comparable encodings. EncodedSin implements PartialEq, Eq, Hash, PartialOrd and Ord as well as comparison against str, so an encoding behaves like the &str it stands for — against another encoding or against a string.
  • 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.

About

SIN (Style Identifier Notation) implementation for Rust with immutable style objects.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages