sashite-epin
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
Or add it manually to Cargo.toml:
[]
= "1"
Cargo features
serde(off by default) — implementsSerialize/DeserializeforIdentifier, (de)serializing it as its canonical token string (e.g."+K^'"). It also turns onsashite-pin's matching feature, and keeps the crateno_std.
[]
= { = "1", = ["serde"] }
Usage
Parsing and decoding
use ;
let king: Identifier = "+K^'".parse?; // via FromStr
let rook = parse?; // via the inherent method
assert_eq!;
assert_eq!;
assert_eq!;
assert!;
assert!;
assert!;
assert!;
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 Identifier;
let king = parse?;
assert_eq!;
assert!;
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 Identifier;
let king = parse?;
assert_eq!;
assert_eq!;
// Compose with PIN's own transformations through `with_pin`.
let flipped = king.with_pin;
assert_eq!;
Building from typed components
Construction is infallible: every PIN identifier is a valid EPIN core.
use Identifier;
use Identifier as Pin;
let derived = new;
assert_eq!;
Validation
use Identifier;
assert!;
assert!; // 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 (A–Z) |
+ / - 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 ; // re-exported from sashite-pin
use 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_stdand allocation-free. Parsing borrows the input bytes; anIdentifieris a 5-byteCopyvalue andEncodedEpinkeeps 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 tosashite-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 thenative/derive/with_pintransforms areconst fn. (Parsing composes PIN's string/byte API and is notconst.)- Error chaining. A failure in the PIN core is wrapped in
ParseError::Pin, whosesource()returns the underlyingsashite_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
- Game Protocol — the conceptual foundation
- PIN Specification v1.0.0 — the core EPIN builds on
- EPIN Specification v1.0.0 — the normative document
- EPIN Examples — context-driven modeling patterns
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.