sashite-epin 1.0.0

Extended Piece Identifier Notation (EPIN): a compact, ASCII-only, no_std token format that extends PIN with a native/derived style marker for abstract strategy board games.
Documentation

sashite-epin

Crates.io Docs.rs CI License

EPIN (Extended Piece Identifier Notation) implementation for Rust.

Overview

This crate implements the EPIN Specification v1.0.0.

EPIN is a strict superset of PIN: it inherits the four PIN attributes (piece name, side, state, terminal status) and adds a single optional trailing marker, the derivation marker ', which flags whether a piece's style is native or derived.

<pin>[']        e.g.  K   r'   +K^   -k^'

EPIN encodes only the flag. What "native" and "derived" mean, and how a concrete style is resolved, is left to the surrounding context — see the Game Protocol and Glossary.

This crate is a thin layer over sashite-pin: an Identifier is a PIN identifier paired with the native/derived flag, and all PIN-level parsing, validation, and encoding is delegated to that crate.

Implementation constraints

Property Value Rationale
Token length 1–4 bytes \A[-+]?[A-Za-z]\^?'?\z per the specification
Closed domain 624 tokens the 312 PIN tokens × 2 style-status flags
Identifier size 5 bytes, Copy a PIN identifier (4 bytes) plus the style-status flag
Dependencies sashite-pin only optional serde; no other runtime dependencies
unsafe forbidden the crate is built under a forbid-unsafe lint policy
MSRV 1.81 inherited from sashite-pin (core::error::Error)

Installation

cargo add sashite-epin

Or add it manually to Cargo.toml:

[dependencies]
sashite-epin = "1"

Cargo features

  • serde (off by default) — implements Serialize / Deserialize for Identifier, (de)serializing it as its canonical token string (e.g. "+K^'"). It also turns on sashite-pin's matching feature, and keeps the crate no_std.
[dependencies]
sashite-epin = { version = "1", features = ["serde"] }

Usage

Parsing and decoding

use sashite_epin::{Identifier, Side, State};

let king: Identifier = "+K^'".parse()?;        // via FromStr
let rook = Identifier::parse("r'")?;           // via the inherent method

assert_eq!(king.letter().as_char(), 'K');
assert_eq!(king.side(), Side::First);
assert_eq!(king.state(), State::Enhanced);
assert!(king.is_terminal());
assert!(king.is_derived());

assert!(rook.is_second());
assert!(rook.is_derived());

The underlying PIN core

pin returns the underlying PIN identifier — the escape hatch to the full PIN API (queries and transformations not re-exposed directly on the EPIN type).

use sashite_epin::Identifier;

let king = Identifier::parse("+K^'")?;
assert_eq!(king.pin().encode().as_str(), "+K^");
assert!(king.pin().is_enhanced());

Style-status transforms

native and derive flip only the style-status flag and are idempotent; with_pin swaps the PIN core while preserving that flag.

use sashite_epin::Identifier;

let king = Identifier::parse("+K^'")?;
assert_eq!(king.native().encode().as_str(), "+K^");
assert_eq!(king.derive().encode().as_str(), "+K^'");

// Compose with PIN's own transformations through `with_pin`.
let flipped = king.with_pin(king.pin().flipped());
assert_eq!(flipped.encode().as_str(), "+k^'");

Building from typed components

Construction is infallible: every PIN identifier is a valid EPIN core.

use sashite_epin::Identifier;
use sashite_pin::Identifier as Pin;

let derived = Identifier::new(Pin::parse("+r")?, true);
assert_eq!(derived.encode().as_str(), "+r'");

Validation

use sashite_epin::Identifier;

assert!(Identifier::is_valid("r'"));
assert!(!Identifier::is_valid("K'^"));         // the marker must be last

Token format

The grammar (EBNF) is:

epin              ::= pin [ derivation-marker ] ;
derivation-marker ::= "'" ;

For the pin production, see the PIN specification. A token maps to the four PIN attributes plus one flag:

Component Encodes Values
letter case side uppercase → First, lowercase → Second
letter piece name a single-letter abbreviation (AZ)
+ / - prefix state Enhanced / Diminished (else Normal)
^ suffix terminal status present → terminal piece
' suffix style status present → derived (else native)

Native vs derived

  • No ' → style status is native
  • With ' → style status is derived

That is the only universal meaning EPIN assigns to '. Resolving a concrete style from this flag is context-defined.

Relationship to PIN

EPIN re-exports the PIN layer, so downstream code needs no separate sashite-pin dependency to name the underlying types:

use sashite_epin::{Letter, Side, State};        // re-exported from sashite-pin
use sashite_epin::sashite_pin::Identifier as Pin; // the PIN identifier itself

Because sashite_pin::Identifier appears in EPIN's public API (as the return type of Identifier::pin), the "1" version requirement relies on PIN's 1.x semver stability.

Design and guarantees

  • no_std and allocation-free. Parsing borrows the input bytes; an Identifier is a 5-byte Copy value and EncodedEpin keeps the ≤ 4 output bytes in a fixed inline buffer. Nothing touches the heap.
  • No unsafe, no regex engine. Parsing detaches an optional trailing ' and hands the core to sashite-pin, which matches raw bytes directly.
  • Single source of truth. All PIN-level parsing, validation, and encoding lives in sashite-pin; EPIN adds only the ' marker, so the two can never diverge.
  • const-friendly construction. new, the accessors, and the native / derive / with_pin transforms are const fn. (Parsing composes PIN's string/byte API and is not const.)
  • Error chaining. A failure in the PIN core is wrapped in ParseError::Pin, whose source() returns the underlying sashite_pin::ParseError.

Performance

The hot paths are a thin layer over sashite-pin's (which run in single-digit nanoseconds): parsing adds one suffix check, and encoding appends at most one byte. Run cargo bench for figures on your machine; see benches/parse.rs.

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.