Skip to content

Repository files navigation

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().expect("a valid EPIN token"); // via FromStr
let rook = Identifier::parse("r'").expect("a valid EPIN token");    // inherent

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^'").expect("a valid EPIN token");
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^'").expect("a valid EPIN token");
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").expect("a valid PIN token"), 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. An Identifier borrows nothing — it is a self-contained 5-byte Copy value — and EncodedEpin keeps the ≤ 4 output bytes in a fixed inline buffer. Nothing touches the heap, and no output outlives its input.
  • Panic-free. The library contains no indexing, no fallible arithmetic and no panicking call: a clippy census with indexing_slicing, arithmetic_side_effects, panic, unwrap_used and expect_used enabled reports zero sites. That matters in a crate meant for bare-metal targets, where a panic is not a nice failure mode.
  • String-like formatting. Identifier, EncodedEpin and ParseError all render through core::fmt::Formatter::pad, so width, fill, alignment and precision work as they do for str.
  • Comparable encodings. EncodedEpin implements PartialEq, Eq, Hash, PartialOrd and Ord, as well as comparison against str. Its order is the token text's, which is deliberately not the attribute order Identifier derives — -K < +K by identifier, +K < -K by text.
  • 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. parse, is_valid and encode are not: they compose PIN's string/byte API, and the pieces they need are not yet const at the 1.81 MSRV. The crate docs spell out which blocker applies to which.
  • 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.

About

EPIN (Extended Piece Identifier Notation) implementation for Rust extending PIN with style derivation markers.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages