docs: sync core-parser spec and add unit normalisation

Bring core-parser.md in line with the resolved design in CONTEXT.md and
ADRs 0004/0005, and promote a new decision to normalise ingredient/cookware
units.

- Full sync of the core-parser spec: diagnostics/total-function channel,
  container blocks (ListItemBlock, BlockquoteBlock), RawInline alongside
  RawBlock via position-slicing, the unified WikilinkNode/TransclusionNode
  shape as two typed nodes, and the minimal remark plugin set.
- New ADR 0006: normalise ingredient/cookware units via a known-alias table
  with passthrough, case-insensitive, canonical-only; align Timer matching
  to case-insensitive too.
- SPEC.md: units are normalised (new Units section, alias table); Timer table
  marked case-insensitive.
- CONTEXT.md: add Unit Normalisation entry, resolve the open item.
- fixtures/basic.md: add a blockquote (callout) carrying an annotation and a
  non-canonical unit so the integration test exercises containers and
  normalisation.
- Expand the core test.todo checklists to cover the new behaviour (containers,
  raw fallbacks, diagnostics, unit normalisation, wikilink/transclusion
  variants); still pending, suite stays green.
This commit is contained in:
2026-07-14 21:42:12 -04:00
parent 3ffd2b3f9f
commit 9958b6e6d5
7 changed files with 330 additions and 60 deletions

View File

@@ -26,6 +26,7 @@ The quantity/unit split is **grammar-driven, not last-space-driven**: the parser
If the brace content does not begin with a grammar-valid quantity (e.g. `@stock{a splash}`), the entire content is preserved as the quantity string and the unit is left empty — no annotation is dropped.
The name is trimmed of surrounding whitespace (interior spaces preserved, so `@unsalted butter{}` stays `unsalted butter`), and the brace content is trimmed before grammar-matching.
The unit is the whole remainder after the quantity, so multi-word units work: `@stock{200 fl oz}` → quantity `200`, unit `fl oz`.
The unit is normalised: a unit matching a known alias is rewritten to its canonical abbreviation (case-insensitive, multi-word aware), and any unit not in the table is passed through verbatim. See the [[Unit Normalisation]] entry and ADR 0006.
Valid name characters: any character except `{`, `}`, `@`, `$`, `~`.
## Cookware Annotation
@@ -35,7 +36,7 @@ Syntax: `$name{}`, `$name{quantity}`, or `$name{quantity unit}`
Same `{`-terminated name rule as Ingredient.
The `{}` wrapper is mandatory.
Quantity is optional; `$pan{}` is valid with no quantity.
Quantity and unit follow the same rules as Ingredient: valid formats are integer, decimal, simple fraction, mixed number; unit is free-form; the split is grammar-driven, with non-numeric content preserved wholesale as the quantity string.
Quantity and unit follow the same rules as Ingredient: valid formats are integer, decimal, simple fraction, mixed number; the split is grammar-driven, with non-numeric content preserved wholesale as the quantity string; and the unit is normalised via the same known-alias table (see [[Unit Normalisation]]).
Valid name characters: any character except `{`, `}`, `@`, `$`, `~`.
## Timer Annotation
@@ -50,9 +51,21 @@ In range form, the `-` separator follows the first quantity expression — since
The range `-` must directly abut both numbers: `~10-15 mins` is a range, but `~10 - 15 mins` (spaces around `-`) is plain prose, not a timer.
One or more spaces separate the number (or range) from the unit.
The annotation terminates after the unit word — trailing prose is ignored. `~1 min more` parses as a timer of 1 min; "more" is plain text.
The unit matches only as a complete word (followed by whitespace, punctuation, or end-of-input) and the longest known alias wins (`seconds` before `sec`); `~5 minsx` is not a timer because `minsx` is not a known unit.
The unit matches only as a complete word (followed by whitespace, punctuation, or end-of-input), is matched case-insensitively, and the longest known alias wins (`seconds` before `sec`); `~5 minsx` is not a timer because `minsx` is not a known unit.
Known unit set (aliases → canonical abbreviation): `sec`, `secs`, `second`, `seconds``s`; `min`, `mins`, `minute`, `minutes``min`; `hr`, `hrs`, `hour`, `hours``hr`.
## Unit Normalisation
The Parser normalises Ingredient and Cookware units to a canonical abbreviation from a fixed known-alias table.
A unit whose text matches an alias is rewritten to its canonical form; any unit not in the table is passed through verbatim (original casing and spacing preserved).
Lookup is **case-insensitive**, multi-word aliases (`fluid ounce`) match on the whole unit string, and only the canonical unit is retained — the author's original spelling is not kept.
Single-letter cooking abbreviations (`t`, `T`, `c`) are excluded as ambiguous, and count/descriptive units (`clove`, `pinch`, `to taste`) have no canonical form and pass through unchanged.
Timer units are normalised from their own known set (see [[Timer Annotation]]), also case-insensitively.
Canonical set (aliases → canonical): g, gram, grams → `g`; kg, kilogram, kilograms, kilo, kilos → `kg`; mg, milligram, milligrams → `mg`; oz, ounce, ounces → `oz`; lb, lbs, pound, pounds → `lb`; ml, milliliter, millilitre, milliliters, millilitres → `ml`; l, liter, litre, liters, litres → `l`; tsp, teaspoon, teaspoons → `tsp`; tbsp, tablespoon, tablespoons → `tbsp`; cup, cups → `cup`; fl oz, fluid ounce, fluid ounces → `fl oz`; pt, pint, pints → `pt`; qt, quart, quarts → `qt`; gal, gallon, gallons → `gal`.
The durable definition of this table lives in the core-parser spec and `SPEC.md`; ADR 0006 records the decision.
## Recipe File
A valid `.md` file that may contain any combination of Obsidian Flavored Markdown, YAML frontmatter, and Annotations.
@@ -183,4 +196,7 @@ A raw node exposes only that `value` string; it carries no `kind`/type hint (tha
## Open / Unresolved
- **Unit normalisation** for Ingredient/Cookware — the parser should normalise known aliases to a canonical abbreviation (`grams``g`, `kilograms``kg`); exact alias table and canonical forms are TBD. This is a parser concern, not a format constraint. (Timer unit normalisation is already fully specified above.)
- _(none currently)_
Resolved:
- **Unit normalisation** for Ingredient/Cookware — resolved as a known-alias table with passthrough, case-insensitive, canonical-only. See [[Unit Normalisation]] and ADR 0006.