ADR-0011 — 3,5 is a point, 2,5 is a number: the decimal comma and the command grammar

Status: accepted · 2026-08-08 · relates to ADR-0009

Context

The plan names this trap explicitly and asks for an ADR and a test:

Named trap: decimal-comma locales conflict with cadCommands.ts's 5,0 coordinate grammar — resolve in an ADR and test it.

The conflict is total. 3,5 is the point (3, 5) to an anglophone drafter and the single number 3.5 to most of the rest of the world, and both are valid input to this grammar. No parser can tell them apart from the text alone.

What makes it worth a decision rather than a guess is the failure mode. There is no error. WALL 0,0 3,5 draws a wall, it is simply the wrong wall, and a drafter has no reason to suspect the parser. Compare the polar branch of the same function, which has carried a comment since extraction about Number("") being 0: "a sloppy split would silently draw a wrong wall… a drafter would never notice." Same class of bug, one character over.

Decision

Ambiguity is resolved by an explicit separator, never by locale.

1. Where there is one number, a comma is a decimal separator

No competing reading exists, so accepting it costs nothing and refusing it makes the tool feel broken to most of its potential users. This applies to:

2. Where there are two, a comma stays the separator and ; is the way out

; is not invented here: it is the separator decimal-comma locales already use in CSV files and in spreadsheet formulas, so the population that needs it is the population that already knows it.

3. Locale is never consulted by a parser

Two reasons, and the second decides it.

  1. Muscle memory outranks number formatting. AutoCAD's command grammar is ,-separated with a . decimal in every locale it ships in. A drafter's hands already know this.
  2. A locale-dependent parser makes a saved macro mean different things to different people. Command invocations are serialisable by design — that is what makes macros, the audit log, replay and the eventual CRDT path work. A recorded WALL 0,0 3,5 that draws one wall for a colleague in Berlin and a different one for a colleague in Boston is a data-corruption bug wearing an i18n hat, and it would surface as "the macro is broken" long after the cause.

Formatting for display is fully locale-aware — Translator.number() uses Intl.NumberFormat, so a German UI shows 12.480 and 41 % with its non-breaking space. Display is localised; the grammar is a formal language.

Consequences

A bug this found

Writing the tests surfaced a real defect in the pre-existing cartesian branch. Number("") is 0, so WALL 0,0 5, resolved to the point (5, 0) — a point the drafter never typed, silently, with no error. The polar branch had guarded against exactly this since extraction; the cartesian branch beside it never got the guard. coordinateParts now rejects an empty component in either form, and WALL 0,0,0 5,0,3 still parses, because x,y,z with z dropped is documented as valid.

What was rejected