asciiunits
PARG grammar, checksummed artifact and conformance corpus for the
unitsml(…) expression content of UnitsML
(the AsciiUnits unit-expression syntax).
This repository ships no runtime code. It ships the contract:
-
grammars/unitsml.parg— the grammar source -
artifacts/unitsml.json— the compiled artifact (parsanol-tree/v2, checksummed) -
tables/— the terminal vocabularies (embedded in the artifact at compile time; loaded from disk at parse time, see below) -
corpora/unitsml/— conformance corpus, oracle-frozen against the unitsml-ruby Parslet parser
Runtime repositories (Ruby, Rust, TypeScript) load the artifact via their Parsanol runtime and map parse trees to their own object models. In Ruby:
require "parsanol"
artifact = Parsanol::PARG::Artifact.load("artifacts/unitsml.json", tables_dir: "tables")
artifact.parse("expression", "mm*s^-2")The tables_dir must point at this repository’s tables/ — the artifact
records the tables in its manifest and resolves them at load time, so a
runtime should vendor (or install) this checkout as a unit.
What the grammar covers
Exactly the expression syntax Unitsml::Parse accepts — transcribed rule
for rule, in the same alternative order (PEG ordered choice makes order
load-bearing):
-
unit tokens from UnitsDB (
m,Hz,cal_th,ton-force (2000 lb), …), prefixed units (mm,mmol), the literal1 -
registered prefixed symbols (
dB,mbar,kgf,cSt, …) tried after normal composition — decibel parses as an atom (bel_B), not deci-bel -
exponents:
^2,^-2,^(1/2)— a single power per unit, no chained powers -
multiplication/division:
*,/,//, implicit multiplication by whitespace (m s), and direct fence/sqrt adjacency ((m)(s),sqrt(m)s) -
sqrt(…)— includingsqrt(sqrt(m)) -
fencing
(…)— powers do not attach to fenced or sqrt forms ((cm)^3is rejected) -
prefix-only forms at top level (
k-) -
dimension expressions (
dim_L*dim_M^2) with the same operators -
every other quirk of the original — the oracle corpus pins them
Artifact tree contract
The artifact carries a single entry expression. Captures mirror the
Parslet .as() names of the original (values are slices: value + offset
+ length):
| Capture | Meaning |
|---|---|
|
Terminal token |
|
Number text (optional leading |
|
Exponent subtree ( |
|
Operator: |
|
|
|
Fenced |
|
The right-recursive continuation of a chain |
|
The two operands of an implicit multiplication (under |
|
Fence characters captured by the named int-exp rules |
Post-parse semantics (sqrt → 0.5 powers, / and // inverting exponents)
belong to the runtime adapter, not the grammar — the grammar records
structure only, exactly as the Parslet original does.
Installation
The gem ships the artifact, tables, grammar source and conformance corpus:
gem install asciiunitsrequire "asciiunits"
artifact = Asciiunits.load_artifact
artifact.parse("expression", "mm*s^-2")Rebuilding
The unitsml gem (build-time only) supplies the terminal selection and the
Parslet oracle; parsanol compiles:
bundle install
rake tables:gen # regenerate terminal tables from the unitsml gem
rake oracle:regen # re-baseline accept/reject against the parslet parser
rake artifact:compile # compile with determinism check + checksums manifest
rake contracts:regen # replay artifact over the corpus, enforce parity
rake ci # everything green before committingA data change in UnitsDB (new unit symbols, renamed prefixes) means:
rake tables:gen && rake artifact:compile && rake contracts:regen, review
the corpus diff, commit. Consumers pick the change up by updating their
vendored artifact.
Verification status
-
Oracle: the unitsml-ruby Parslet parser (parg-parser branch, including the registered prefixed-symbol support), frozen in
corpora/unitsml/expected-kinds.json -
Corpus: 143 inputs (80 accept / 63 reject), kind parity green
-
Differential probes at authoring time: 572 + 172 generated inputs, 0 accept/reject disagreements against the oracle
-
Engines: native (Rust) and
mode: :rubytrees verified identical -
Compile: byte-identical across recompiles (
rake artifact:compile)