docs(adr): fold the user's answers on ASCII ids, virtual categories and scripts into ADR 007 (improvise-cq1q.1)
- Id alphanumerics are ASCII and include -: slugify maps everything
outside a-z, 0-9 and - to _, collapses _, trims _ and - at both ends,
prefixes n before a digit; the id syntax, examples and the suffix /
hyphenated-slug collision rule follow.
- A file never declares a virtual category and refers to one by its id.
- Only CSV import and the .improv loaders read display names in input
text; command-language text, from any source, names things by id.
- The formula line is shown before and after; the dimension spelling and
the [_measure] tag follow from the rules, so that question is deleted.
- The Opus review of 2dfe0886: each measure is keyed by one handle, and
the shared-display case moves to an open question; views join the
things a formula may name.
Open questions are now three: a formula and a stored measure with the
same display, where stored measures are declared, and whether interactive
typing keeps a display stage.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
committed by
Edward Langley
parent
2dfe088668
commit
8405f5c18b
@@ -19,15 +19,16 @@ amendment is approved:
|
||||
| clause | accepted rule | the amendment proposes |
|
||||
|---|---|---|
|
||||
| 1 | groups have no id | a group has an id and a display, and its id behaves like every other id |
|
||||
| 1 | `_index`, `_dim`, `_measure` are the only ids beginning with `_` | a leading `_` is reserved for virtual ids in general, including the virtual views `_records` and `_drill`; virtual ids are never written to `.improv` |
|
||||
| 1 | `_index`, `_dim`, `_measure` are the only ids beginning with `_` | a leading `_` is reserved for virtual ids in general, including the virtual views `_records` and `_drill`; a file never declares a virtual category and refers to one by its id (`_measure`) |
|
||||
| 2 | `CellKey` pairs are `(CategoryId, ItemId)` | `(CategoryId, ItemHandle)` |
|
||||
| 2, 5 | a collapsed entry and a group reference name a group by name | by its id |
|
||||
| 4 | casefold; a run of U+0020 spaces becomes one `_`; strip characters outside `a`–`z`, `0`–`9`, `-`, `_`; remove a leading `_` | casefold; every non-alphanumeric character becomes `_`; runs of `_` collapse; leading and trailing `_` are removed; `n` is prepended to a slug that starts with a digit |
|
||||
| 4 | casefold; a run of U+0020 spaces becomes one `_`; strip characters outside `a`–`z`, `0`–`9`, `-`, `_`; remove a leading `_` | casefold; every character outside `a`–`z`, `0`–`9` and `-` becomes `_`; runs of `_` collapse; leading and trailing `_` and `-` are removed; `n` is prepended to a slug that starts with a digit |
|
||||
| 5 | a hand-written id uses the alphabet `a`–`z`, `0`–`9`, `-`, `_` and may begin with `_` only if reserved | a hand-written id obeys every syntax rule of a generated id |
|
||||
| 6 | display names typed into minibuffers are turned into ids at the input edge | only CSV import and the `.improv` loaders turn display names in input text into ids; command-language text, from any source, names things by id |
|
||||
| 12 | a typed name new to a formula edit is resolved against the document at commit | the client resolves it, ids first, and the wire carries only id-form text |
|
||||
| 12 | the regression test commits the **unedited** text after the concurrent renames | the test edits a non-name token and commits, because a client may pass unedited text as a no-op without resolving any name |
|
||||
|
||||
Four questions are open (last section).
|
||||
Three questions are open (last section).
|
||||
|
||||
## Context
|
||||
|
||||
@@ -64,7 +65,8 @@ human name moves to a new `display` field.
|
||||
existing thing. A reference to the stable identity cannot glitch when something is
|
||||
renamed or replaced.
|
||||
- **Display names are for people.** They appear on screen, are typed into
|
||||
minibuffers, and are turned into ids at the input edge.
|
||||
minibuffers, and are turned into ids at the input edge. **[Amendment]** Clause 6 narrows
|
||||
which readers turn display text into ids.
|
||||
|
||||
### 1. What carries an identity
|
||||
|
||||
@@ -96,15 +98,21 @@ have ids `_records` and `_drill`, displayed `_Records` and `_Drill`; they are ad
|
||||
on the wire (`SwitchView`), and a slugified `_Drill` would otherwise be `drill` and
|
||||
could collide with a user's view. Slugify never produces a leading `_` (clause 4), so
|
||||
the reservation needs no check in the grammar.
|
||||
- **[Amendment] Virtual ids are lower case and are never written to `.improv`.** The
|
||||
code, the wire, `ViewState` and the browser client spell the virtual categories
|
||||
`_Index`, `_Dim` and `_Measure` today, and move to the lower-case ids in the re-key
|
||||
step (clause 11). How the file records what it records today under `_Measure`,
|
||||
`_Index` and `_Dim` is open question 2.
|
||||
- **[Amendment] Virtual ids are lower case.** The code, the wire, `ViewState` and the
|
||||
browser client spell the virtual categories `_Index`, `_Dim` and `_Measure` today,
|
||||
and move to the lower-case ids in the re-key step (clause 11).
|
||||
- **[Amendment] A file never declares a virtual category; it refers to one by its
|
||||
id.** The virtual categories exist in every document, so they are not required in
|
||||
the file. Where a line refers to one, it spells the virtual id:
|
||||
`_measure: column`, `_index: none`, `region=east, _measure=revenue = 1200`. An
|
||||
old-version file's `_Measure` resolves through its display at load, like every name
|
||||
in an old file (clause 5), and the first save writes `_measure`. Where the file
|
||||
declares the stored measures themselves (`Revenue`, `Cost`) is open question 2.
|
||||
- **[Amendment] Records mode's synthetic positions are values, not ids.** The row
|
||||
coordinate under `_index` and the column coordinate under `_dim` (a column index,
|
||||
gotcha 1) are values of those virtual categories. They are never persisted or typed,
|
||||
take no part in id uniqueness, and cannot collide with a user's item.
|
||||
coordinate under `_index` is a value of that virtual category. So, by the same
|
||||
reasoning, is the column coordinate under `_dim` (a column index, gotcha 1). They are
|
||||
never persisted or typed, take no part in id uniqueness, and cannot collide with a
|
||||
user's item.
|
||||
|
||||
**Formulas and measures.** **[Amendment]** A measure is the normal formula target
|
||||
(`profit = revenue - cost`). A dimension (a category other than `_Measure`) is a rare
|
||||
@@ -112,21 +120,23 @@ one, for a derived dimension.
|
||||
- **A `_Measure` target has no item of its own**, and its handle is its formula's
|
||||
`FormulaId`. So the category panel's `_Measure` rows use
|
||||
`ItemHandle::{Item(ItemId), FormulaTarget(FormulaId)}` (clause 6).
|
||||
- **[Amendment] A formula is a kind of measure, so a `_Measure` formula's id is the
|
||||
measure's id.** A measure has an id and a display, as everything else does.
|
||||
`Profit = Revenue - Cost` is one thing, `profit`: another formula refers to it as
|
||||
`profit`, each Profit cell's `CellKey` holds `profit` under `_measure`, and
|
||||
`ReplaceFormula` and `RemoveFormula` address `profit`. A formula that derives a
|
||||
measure already stored under that display takes the stored measure's id, so the
|
||||
column has one id, not two. Removing the formula removes a measure that exists only
|
||||
through it; a measure that also holds stored data stays, as a stored measure.
|
||||
- **[Amendment] A formula is a kind of measure, so there is no distinction between a
|
||||
`_Measure` formula's id and its measure's id.** A measure has an id and a display,
|
||||
as everything else does. `Profit = Revenue - Cost` is one thing, `profit`: another
|
||||
formula refers to it as `profit`, each Profit cell's `CellKey` holds `profit` under
|
||||
`_measure`, and `ReplaceFormula` and `RemoveFormula` address `profit`.
|
||||
- **[Amendment] Each measure's cells are keyed by one handle.** A stored measure is
|
||||
keyed `ItemHandle::Item(id)`. A measure that exists only through its formula is
|
||||
keyed `ItemHandle::FormulaTarget(id)`. The id is the measure's either way, and no
|
||||
column is keyed by both. What happens when a formula's measure and a stored measure
|
||||
share a display, and what removing that formula does, is open question 1.
|
||||
- **[Amendment] A target outside `_Measure` is a real item**, with its own `ItemId`,
|
||||
and its formula has a separate `FormulaId`. A formula whose generated id would
|
||||
collide takes a suffix like any other generated id (clause 4): a formula `Tax` in
|
||||
`CustomCat` gets item id `tax`, then formula id `tax-<n>`.
|
||||
- **[Amendment] Formulas may refer to anything by id.** A function in a formula may
|
||||
accept any id-bearing thing (a category, an item, a measure, a formula, a group),
|
||||
referred to by its id. Whether a given function takes the thing itself or its value
|
||||
accept any id-bearing thing (a category, an item, a measure, a formula, a view, a
|
||||
group), referred to by its id. Whether a given function takes the thing itself or its value
|
||||
(an item's display, say) is part of that function's signature. Global uniqueness
|
||||
makes every such reference unambiguous, and a function may read any part of a record,
|
||||
so nothing further is needed here.
|
||||
@@ -196,27 +206,27 @@ thing's id never depends on how it was made. An id is lower case.
|
||||
1. **Casefold, with Unicode full case folding**, so `Straße` → `strasse`. Rust's
|
||||
`str::to_lowercase` is not enough (`ß` stays `ß`). The folding table or crate must
|
||||
pass the `wasm` gate, because `improvise-core` is in the browser bundle.
|
||||
2. **Replace every character that is not alphanumeric with `_`.** That includes
|
||||
spaces, tabs and every other whitespace, punctuation and `-`. Whether
|
||||
"alphanumeric" means ASCII only is open question 1. Whichever it decides, the slug
|
||||
ends up in `a`–`z`, `0`–`9` and `_`: a Unicode reading needs a further step that
|
||||
drops or transliterates what is outside that alphabet, and question 1 picks it.
|
||||
2. **Replace every character outside `a`–`z`, `0`–`9` and `-` with `_`.** The id's
|
||||
alphanumerics are ASCII and include `-`. Every other character, including spaces,
|
||||
tabs and other whitespace, punctuation and every non-ASCII letter, becomes `_`.
|
||||
3. **Collapse each run of `_` to one `_`**, whether the underscores came from step 2
|
||||
or were in the display.
|
||||
4. **Remove every leading and trailing `_`.** A leading `_` is reserved for virtual
|
||||
ids (clause 1), and a trailing one is dropped.
|
||||
4. **Remove every leading and trailing `_` and `-`**, repeating until neither end
|
||||
holds either. A leading `_` is reserved for virtual ids (clause 1), a trailing `_`
|
||||
is dropped, and no id begins or ends with `-`.
|
||||
5. **If the slug starts with a digit, prepend `n`.** An id is never purely numeric
|
||||
and never starts with a digit, so `2024` becomes `n2024`.
|
||||
6. **If nothing is left, use `<kind>-<n>`**, such as `category-1` or `item-4`. `n`
|
||||
comes from the shared counter below.
|
||||
comes from the shared counter below. A display of only `-`, only `_` or only
|
||||
non-ASCII letters ends here, because `-` alone is not an id.
|
||||
|
||||
So a generated id is a lower-case letter followed by letters, digits and single
|
||||
underscores, never ending in `_`, plus, only when it collided, a `-<n>` suffix; or
|
||||
`<kind>-<n>`. The syntax of an id is therefore a slug,
|
||||
`[a-z][a-z0-9]*(_[a-z0-9]+)*`, optionally followed by `-` and a number; a `-` anywhere
|
||||
else never occurs.
|
||||
**The syntax of an id** is therefore: a lower-case ASCII letter, then any of `a`–`z`,
|
||||
`0`–`9`, `-` and single underscores, never ending in `_` or `-`:
|
||||
`[a-z]([a-z0-9-]|_(?=[a-z0-9-]))*`, not ending in `-`. Slugify's output already has
|
||||
that shape, so slugifying an id gives the same id back. The collision suffix `-<n>`
|
||||
(below) keeps the shape too, because it ends in a digit.
|
||||
|
||||
Examples under the amendment, with "alphanumeric" read as ASCII:
|
||||
Examples under the amendment:
|
||||
|
||||
| display | id |
|
||||
|---|---|
|
||||
@@ -224,24 +234,40 @@ Examples under the amendment, with "alphanumeric" read as ASCII:
|
||||
| `Time Period`, `Time Period`, `Time\tPeriod`, `Time \u{200B} Period` | `time_period` |
|
||||
| `Coastal_East` | `coastal_east` |
|
||||
| `a_ b`, `a__b` | `a_b` |
|
||||
| `Q1-2024`, `Q1 - 2024` | `q1_2024` |
|
||||
| `Q1-2024` | `q1-2024` |
|
||||
| `Q1 - 2024` | `q1_-_2024` |
|
||||
| `East-1` | `east-1` |
|
||||
| `East `, `East_`, `-East-` | `east` |
|
||||
| `_Draft` | `draft` |
|
||||
| `Region(code)` | `region_code` |
|
||||
| `Café` | `caf` |
|
||||
| `Crème Brûlée` | `cr_me_br_l_e` |
|
||||
| `Straße` | `strasse` |
|
||||
| `2024`, `_2024` | `n2024` |
|
||||
| `2024 Budget` | `n2024_budget` |
|
||||
| `2024-Q1` | `n2024-q1` |
|
||||
| `Ü`, `-`, `%` | `<kind>-<n>` |
|
||||
|
||||
Under the accepted steps, the ones that differ are `Q1-2024` → `q1-2024`,
|
||||
`Q1 - 2024` → `q1_-_2024`, `East ` → `east_`, `a_ b` → `a__b`, `Region(code)` →
|
||||
`regioncode`, `2024` → `2024` and `-` → `-`.
|
||||
Under the accepted steps, the ones that differ are `East ` → `east_`, `a_ b` →
|
||||
`a__b`, `Time\tPeriod` → `timeperiod`, `Time \u{200B} Period` → `time__period`,
|
||||
`-East-` → `-east-`,
|
||||
`Region(code)` → `regioncode`, `Crème Brûlée` → `crme_brle`, `2024` → `2024`,
|
||||
`2024-Q1` → `2024-q1` and `-` → `-`.
|
||||
|
||||
**Collisions.** `Time Period` and `Time_Period` slug alike, and whichever comes later
|
||||
takes a suffix. **[Amendment]** So do `East`, `East ` and `East_`, and `Q1-2024` and
|
||||
`Q1 2024`, which slug alike only under the amendment's steps. `East` in `Region` and
|
||||
`East` in `Direction` also slug alike, and ids are global, so the second is `east-<n>`.
|
||||
takes a suffix. **[Amendment]** So do `East`, `East ` and `East_`, which slug alike
|
||||
only under the amendment's steps. `East` in `Region` and `East` in `Direction` also
|
||||
slug alike, and ids are global, so the second is `east-<n>`.
|
||||
|
||||
**[Amendment] A suffix and a hyphenated slug can look alike.** The display `East-1`
|
||||
slugs to `east-1`, which is also what the counter's suffix may make of `east`. They
|
||||
never collide, because every id a document holds is taken, whoever made it:
|
||||
- if `east-1` is already held (as a slug or as a suffix), a display `East-1` that slugs
|
||||
to it is a collision, and becomes `east-1-<n>`;
|
||||
- if the counter's candidate `east-<n>` is already held, the counter advances until a
|
||||
candidate is free.
|
||||
|
||||
So an id says nothing about how it was made: `east-1` may be a suffixed `east` or the
|
||||
slug of `East-1`.
|
||||
- **Any colliding generated id gets a suffix**, whatever kind of thing holds the
|
||||
slug: on import, when an id-less file loads, and when the host creates something in
|
||||
a session. An id that does not collide stays bare.
|
||||
@@ -267,11 +293,9 @@ every data key and reference in that file spells it. So:
|
||||
- **two identical hand-written ids in one file are refused at load**, even when they
|
||||
declare different kinds of thing. Renaming one would corrupt the data section, and a
|
||||
file with a duplicate cannot be resaved safely. The error names both lines.
|
||||
- **[Amendment] A hand-written id obeys every syntax rule of a generated id**: a slug
|
||||
(lower case, starting with a letter, single underscores, not ending in `_`),
|
||||
optionally followed by the `-<n>` suffix. So `east-1` and `item-4`, which every save
|
||||
writes, load back; `-east`, `east-`, `2024` and `_draft` do not. One that breaks the
|
||||
syntax is refused at load (clause 5).
|
||||
- **[Amendment] A hand-written id obeys every syntax rule of a generated id** (the
|
||||
syntax above). So `east-1`, `item-4` and `q1_-_2024` load; `-east`, `east-`, `2024`,
|
||||
`a__b` and `_draft` are refused at load (clause 5).
|
||||
|
||||
**Reuse across a save and reload is accepted.** Retired ids are not persisted. So
|
||||
remove `East`, save, reload and add `East`, and the new item gets `east` again. An
|
||||
@@ -286,9 +310,9 @@ promised (improvise-8tk5).
|
||||
**Built code.** `crates/improvise-core/src/model/id.rs` (`slugify`, `IdAllocator`)
|
||||
implements the accepted steps, and `context/code/core.md` §20 describes them. Once
|
||||
the amendment is approved, a follow-up must:
|
||||
- replace every non-alphanumeric character, not only spaces, with `_`, and collapse
|
||||
`_` runs;
|
||||
- trim a trailing `_`, and prepend `n` before a leading digit;
|
||||
- replace every character outside `a`–`z`, `0`–`9` and `-`, not only spaces, with
|
||||
`_` instead of stripping it, and collapse `_` runs;
|
||||
- trim a trailing `_` and both ends' `-`, and prepend `n` before a leading digit;
|
||||
- make `claim` refuse every hand-written id a generated one could not be;
|
||||
- add the virtual views to `VIRTUAL_IDS`, and `IdKind::Group`.
|
||||
|
||||
@@ -312,16 +336,17 @@ grammar spells it **with ranges**, because the generator drops `ASCII_ALPHANUMER
|
||||
`id_tag`, so it has no id. The error names the line;
|
||||
- a hand-written id beginning with `_`, other than a reserved one;
|
||||
- **[Amendment]** a hand-written id that breaks any syntax rule of a generated id
|
||||
(clause 4). Virtual ids are never written to a file, so no hand-written id begins
|
||||
with `_`. The grammar's `id_token` stays permissive, and the loader refuses, so the
|
||||
error can name the line.
|
||||
(clause 4). A file never declares a virtual category, so no declared id begins with
|
||||
`_`; a reference may still spell a virtual id such as `_measure`. The grammar's
|
||||
`id_token` stays permissive, and the loader refuses, so the error can name the line.
|
||||
|
||||
**Inside formula text an id may still need quoting.** The formula grammar's
|
||||
`bare_ident` must begin with a letter or `_`, and keywords are guarded (gotcha 34). So
|
||||
the writer spells an id in formula text through `quote::quote_name`. An id equal to a
|
||||
keyword (`sum`, `if`, `where`, `is`) is written `|sum|`, and under the accepted rules
|
||||
an id beginning with a digit (`2024`) is written `|2024|`. **[Amendment]** No id begins
|
||||
with a digit, so only keyword ids need quoting.
|
||||
with a digit, so only keyword ids need quoting. A `-` inside an id needs none, because
|
||||
the formula grammar's bare identifier already admits `-` after its first character.
|
||||
|
||||
```pest
|
||||
id_token = @{ ('a'..'z' | '0'..'9' | "_" | "-")+ }
|
||||
@@ -366,16 +391,39 @@ lose its axis line to the view's number format, and the new-version arm puts
|
||||
- a view whose number format is each axis keyword round-trips in a document where
|
||||
that view also holds a category `format` on an axis.
|
||||
|
||||
**[Amendment] A formula line** is
|
||||
`- <target>(<formula id>) = <id-form expression> [<category id>]`:
|
||||
- **The parenthesised id is the formula's `FormulaId`**, between the target and the
|
||||
`=`. For a measure formula it is also the measure's id.
|
||||
- **For a measure formula, `<target>` is the measure's display**, held on the
|
||||
`Formula`: `- Profit(profit) = revenue - cost`. How a dimension formula's line spells
|
||||
its target, and whether the `[_measure]` tag is written, is open question 3.
|
||||
**[Amendment] A formula line** declares the formula: its target, its id, and its
|
||||
expression over ids. Today's `web/demo.improv` and the format's own dimension example
|
||||
are written:
|
||||
|
||||
```
|
||||
## Formulas
|
||||
- Profit = Revenue - Cost
|
||||
- Tax = Revenue * 0.1 [CustomCat]
|
||||
```
|
||||
|
||||
Under the amendment the same two formulas are written:
|
||||
|
||||
```
|
||||
## Formulas
|
||||
- Profit(profit) = revenue - cost
|
||||
- tax(tax-1) = revenue * 0.1 [customcat]
|
||||
```
|
||||
|
||||
- **A measure formula** (no category tag) is the normal case. The formula is the
|
||||
measure (clause 1), so the line is the measure's only declaration: `Profit` is its
|
||||
display, and `(profit)` is the id it shares with its formula. No `[_measure]` tag is
|
||||
written, because a file does not spell a virtual category where it is not needed
|
||||
(clause 1); a line without a tag targets `_measure`.
|
||||
- **A dimension formula** (a `[<category id>]` tag) targets a real item, already
|
||||
declared with its display in its category's section (`## Category: CustomCat(customcat)`
|
||||
lists `- Tax(tax)`). So the line names the target by its item id, `tax`, and
|
||||
`(tax-1)` is the formula's own id. The display is not repeated, because a display
|
||||
lives in one place.
|
||||
- **So the loader can always tell the two apart**: with a tag the target is an item
|
||||
id; without one it is a measure's display.
|
||||
- **The expression is over ids**, spelled through `quote::quote_name`. A reference to
|
||||
a formula target is the id text of its `ItemHandle`: the item id outside `_Measure`,
|
||||
the formula's (and measure's) id for a `_Measure` target.
|
||||
the measure's (and its formula's) id for a `_Measure` target.
|
||||
- **A `WHERE`/`IS` value follows the same rule**. It is the id text of an
|
||||
`ItemHandle` in the filtered category, and the formula panel shows it as a display.
|
||||
- **A display in the target position that looks like `Name(x)` is pipe-quoted**, as in
|
||||
@@ -434,13 +482,17 @@ display that would otherwise read as display plus id:
|
||||
is the group's id, and a new group is created by its display under the same
|
||||
create-by-display rule;
|
||||
- `AddFormula { target_category: CategoryId, raw }`.
|
||||
- **[Amendment] Only a user interface accepts a display name as a reference**, and it
|
||||
converts the name to an id as early as it can. The TUI and browser clients are user
|
||||
interfaces, and so is the headless command line. The wire never carries a display as
|
||||
a reference.
|
||||
- **[Amendment] Scripts refer to things by id.** A script that names something by its
|
||||
display fails. A script gets a display only through a dedicated function such as
|
||||
`display_for(id)`. Which headless entry points count as scripts is open question 4.
|
||||
- **[Amendment] Only two readers turn display names in input text into ids: CSV
|
||||
import and the `.improv` loaders.** They read text people wrote, where names are
|
||||
all there is. The wire never carries a display as a reference.
|
||||
- **[Amendment] Command-language text names things by id.** Anything that uses the
|
||||
command language as a scripting language is a script, wherever the text comes from
|
||||
(a script file, `improvise cmd`, another program). A script that names something by
|
||||
its display fails. A script gets a display only through a dedicated function such as
|
||||
`display_for(id)`.
|
||||
- **[Amendment] Interactive typing prefers ids and previews displays** (clause 12).
|
||||
Whether an interactive input still resolves typed text that matches no id by
|
||||
display is open question 3.
|
||||
- **A frame cannot address a thing it creates**, because it does not know the id the
|
||||
host will assign. A create-then-act compound becomes **one command**:
|
||||
`create-and-switch-view` sends `[CreateView, SwitchView]` today and becomes
|
||||
@@ -610,8 +662,9 @@ answers for those.
|
||||
preferred wherever there is ambiguity, now and once an autocomplete exists.
|
||||
- A typed name equal to an id names that id's thing, even where it is also another
|
||||
thing's display.
|
||||
- Otherwise a display that exactly one thing in scope has names that thing.
|
||||
- A display matching several things is refused, and the refusal lists their ids.
|
||||
- Otherwise a display that exactly one thing in scope has names that thing, and a
|
||||
display matching several things is refused with a list of their ids. Whether this
|
||||
display stage stays is open question 3.
|
||||
- A name that resolves to nothing is refused, and the refusal names it.
|
||||
- Ids are matched exactly, with no case folding.
|
||||
- **Typing prefers ids and previews displays.** A view-layer autocomplete turns typed
|
||||
@@ -647,7 +700,8 @@ without resolving any name (improvise-hexb).
|
||||
(`east`, `east-1`), type `east` into an editor, in text not captured at open:
|
||||
- it must bind A, and the test must fail under displays-first;
|
||||
- `east-1` must bind B;
|
||||
- a display that is nobody's id must still resolve by display.
|
||||
- a display that is nobody's id must still resolve by display (pending open
|
||||
question 3).
|
||||
|
||||
A fixture where no display equals an id proves nothing.
|
||||
|
||||
@@ -715,86 +769,73 @@ three things:
|
||||
|
||||
These are open, and nothing above depends on an answer except where it says so.
|
||||
|
||||
### 1. Does "alphanumeric" mean ASCII?
|
||||
### 1. A formula and a stored measure with the same display: one measure, and what does removal do?
|
||||
|
||||
**The question.** Slugify replaces every non-alphanumeric character with `_`. Unicode
|
||||
counts `é`, `ü` and `日` as alphanumeric, but an id's alphabet is `a`–`z`, `0`–`9`, `_`
|
||||
(and `-`).
|
||||
**The question.** A document stores a `Profit` measure: an item of `_measure` with id
|
||||
`profit`, display `Profit`, holding data. The user then adds the formula
|
||||
`Profit = Revenue - Cost`. A formula is a kind of measure with one id (clause 1), and
|
||||
displays are unique within their scope (clause 2), so this cannot simply make a second
|
||||
`Profit`. Today `measure_item_names` merges the two into one column.
|
||||
|
||||
**Why it matters.** It decides which handle keys the column (`Item(profit)` or
|
||||
`FormulaTarget(profit)`, clause 1), what `RemoveFormula` leaves behind, and whether
|
||||
documents that rely on today's merge keep loading.
|
||||
|
||||
**Options.**
|
||||
- **(a) ASCII.** A non-ASCII letter becomes `_`: `Café` → `caf`, `Ü` → `<kind>-<n>`,
|
||||
`Crème Brûlée` → `cr_me_br_l_e`.
|
||||
- **(b) Unicode, then drop what is outside the alphabet.** A non-ASCII letter
|
||||
disappears without splitting the word: `Café` → `caf`, `Crème Brûlée` →
|
||||
`crme_brle`.
|
||||
- **(c) Unicode, then transliterate** (`é` → `e`). `Café` → `cafe`, `Crème Brûlée` →
|
||||
`creme_brulee`. This needs a transliteration table that passes the `wasm` gate.
|
||||
- **(a) The formula attaches to the stored measure.** One measure, id `profit`, keyed
|
||||
`Item(profit)`; the formula computes it. Removing the formula leaves the stored
|
||||
measure and its data.
|
||||
- **(b) Refuse it.** Adding a formula whose display is a stored measure's is refused,
|
||||
as any duplicate display is. Existing files that hold both must be repaired at load.
|
||||
- **(c) The formula replaces the stored measure.** Its stored data is dropped or kept
|
||||
hidden.
|
||||
|
||||
**Why it matters.** It decides every id generated from a non-English display.
|
||||
**Author's recommendation:** (a). It keeps today's behaviour, so existing documents
|
||||
load unchanged, and a measure stays one thing with one id.
|
||||
|
||||
**Author's recommendation:** (a): it is the simplest reading of "non-alphanumeric
|
||||
characters are replaced", and it needs no table.
|
||||
### 2. Where does a file declare the stored measures?
|
||||
|
||||
### 2. How does a file record the virtual categories, if virtual ids are never written?
|
||||
**The question.** A file never declares a virtual category (clause 1). But the stored
|
||||
measures, such as `Revenue` and `Cost`, are items of `_measure`, and each needs a
|
||||
declaration that carries its display and id. Today they are listed under
|
||||
`## Category: _Measure`:
|
||||
|
||||
**The question.** Virtual ids are never written to `.improv` (clause 1). But today's
|
||||
files record three things under the virtual categories (`web/demo.improv` has all of
|
||||
them):
|
||||
- the virtual categories' axis placements, such as `_Measure: column` and
|
||||
`_Index: none`;
|
||||
- the stored measures, under `## Category: _Measure`;
|
||||
- each data key's measure, as `_Measure=Revenue`.
|
||||
```
|
||||
## Category: _Measure
|
||||
- Revenue, Cost
|
||||
```
|
||||
|
||||
**Why it matters.** Every document with data has measures, so every file needs some
|
||||
way to say which measure a cell holds and where `_Measure` sits.
|
||||
**Why it matters.** Without a declaration a measure has no display or id in the file,
|
||||
and a new-version file refuses any undeclared id (clause 5).
|
||||
|
||||
**Options.**
|
||||
- **(a) Reserved keywords.** The file keeps fixed spellings for the virtual categories
|
||||
(`_Measure`, `_Index`, `_Dim`, or new lower-case keywords). They are part of the
|
||||
format, not ids, so no id is written.
|
||||
- **(b) Write the virtual ids** (`_measure: column`, `_measure=revenue`), an exception
|
||||
to "never written".
|
||||
- **(c) Leave the virtual categories implicit.** Their axis placements take defaults,
|
||||
the measures section has no category header, and a data key's measure is written
|
||||
without its category.
|
||||
- **(a) Keep a section for the virtual category's items only**, spelled by its id:
|
||||
`## Category: _measure` with `- Revenue(revenue), Cost(cost)`. The section declares
|
||||
items, not the category.
|
||||
- **(b) A dedicated section**, such as `## Measures`, with the same item lines.
|
||||
- **(c) Declare a measure where it is first used**, in a data key. A data key holds
|
||||
only an id, so the display would be lost.
|
||||
|
||||
**Author's recommendation:** (a). The file needs these facts, and a keyword says so
|
||||
without turning a virtual id into file content.
|
||||
**Author's recommendation:** (b). It names what the section holds, and keeps the
|
||||
virtual category out of the file entirely.
|
||||
|
||||
### 3. How does a dimension formula's line spell its target, and is the `[_measure]` tag written?
|
||||
### 3. Does interactive typing still resolve a display name that matches no id?
|
||||
|
||||
**The question.** A measure formula's line is `- Profit(profit) = revenue - cost`: the
|
||||
measure's display, then its id (clause 5). A dimension formula (the rare case) has an
|
||||
item id and a separate formula id. Its line may be:
|
||||
- **(a)** `- tax(tax-1) = … [customcat]`: the item id in the target position. The
|
||||
target position then holds a display for a measure and an id for a dimension.
|
||||
- **(b)** `- Tax(tax-1)(tax) = … [customcat]`: display, formula id, item id. The target
|
||||
position always holds a display.
|
||||
**The question.** Only CSV import and the `.improv` loaders turn display names in input
|
||||
text into ids (clause 6). Interactive typing (the TUI's minibuffers and `:` line, the
|
||||
formula editor, the browser) prefers ids and shows the display as a preview
|
||||
(clause 12). Clause 12's resolver also has a display stage: typed text that matches no
|
||||
id but is exactly one thing's display names that thing. Does that stage stay?
|
||||
|
||||
Today's writer omits the category tag for a `_Measure` formula. Should the new writer
|
||||
keep omitting `[_measure]`?
|
||||
|
||||
**Why it matters.** It fixes the formula-line grammar and its generator strategy.
|
||||
|
||||
**Author's recommendation:** (b), so one position never holds two kinds of thing;
|
||||
keep omitting `[_measure]`, which also keeps the virtual id out of the file
|
||||
(question 2).
|
||||
|
||||
### 4. Which headless entry points are scripts?
|
||||
|
||||
**The question.** User interfaces accept display names, and scripts refer to things by
|
||||
id only (clause 6). The headless front ends are `improvise cmd '<line>'`, which runs
|
||||
one or more command lines given on the command line, and `improvise script <file>`,
|
||||
which runs a file of them.
|
||||
|
||||
**Why it matters.** It decides whether `improvise cmd 'add-item Region East'`-style
|
||||
invocations that name existing things by display keep working.
|
||||
**Why it matters.** Without it, a person typing `Revenue` in the formula editor must
|
||||
type or pick the id `revenue`. With it, a typed display still works, and interactive
|
||||
input is a third place that reads display names.
|
||||
|
||||
**Options.**
|
||||
- **(a)** `improvise cmd` is a user interface and resolves displays; a script file is
|
||||
a script.
|
||||
- **(b)** Both are scripts: only the TUI and browser clients resolve displays.
|
||||
- **(a) Keep the display stage** for interactive typing only. Command-language text
|
||||
from scripts never gets it.
|
||||
- **(b) Ids only.** Interactive inputs match ids; displays appear only as previews and
|
||||
autocomplete suggestions, which insert the id.
|
||||
|
||||
**Author's recommendation:** (a). A person types `improvise cmd` lines as they type
|
||||
`:` lines in the TUI; a script file is a program that should not break when something
|
||||
is renamed.
|
||||
**Recommendation:** none. "Prefer ids" leaves room for (a); "only import and the
|
||||
loaders read display names" points to (b).
|
||||
|
||||
@@ -1715,10 +1715,12 @@ to `east_`. Only *leading* underscores are removed (step 4).
|
||||
|
||||
**ADR 007's proposed amendment changes these rules, and this code predates it.**
|
||||
The amendment (PR #326) is not yet approved. Its clause 4:
|
||||
- every non-alphanumeric character becomes `_` (not only U+0020, and `-` too),
|
||||
so `Q1-2024` slugs to `q1_2024` and `Region(code)` to `region_code`;
|
||||
- every character outside `a`–`z`, `0`–`9` and `-` becomes `_` instead of being
|
||||
stripped (not only U+0020), so `Region(code)` slugs to `region_code`, while
|
||||
`Q1-2024` keeps its `-`;
|
||||
- runs of `_` collapse, so `a_ b` and `a__b` both slug to `a_b`;
|
||||
- leading and trailing `_` are removed, so `East ` → `east` and `-East-` → `east`;
|
||||
- leading and trailing `_` and `-` are removed, so `East ` → `east`, `-East-` →
|
||||
`east`, and `-` alone takes `<kind>-<n>`;
|
||||
- `n` is prepended to a slug that starts with a digit, so `2024` → `n2024`;
|
||||
- a hand-written id must obey every syntax rule of a generated one.
|
||||
|
||||
@@ -1751,8 +1753,8 @@ the alphabet (non-empty, `a`–`z`, `0`–`9`, `-`, `_`), the reserved leading
|
||||
`_` (only `_index`, `_dim` and `_measure` may carry one, per clause 1, and
|
||||
the match is exact: `_indexer` and `_measure-1` are Reserved, since "no other
|
||||
id may start with `_`"), and
|
||||
duplicates. It accepts `-`, `-x`, `x-` and `2024`, all of which the amendment
|
||||
refuses (a hand-written id obeys the syntax of a generated one). It never renames.
|
||||
duplicates. It accepts `-`, `-x`, `x-`, `a__b` and `2024`, all of which the
|
||||
amendment refuses (a hand-written id obeys the syntax of a generated one). It never renames.
|
||||
Refusing is the loader's job, and the loader
|
||||
must name both lines of a duplicate; `ClaimError` carries only the id, so the
|
||||
loader keeps the line numbers. The virtual views `_records` and `_drill` are
|
||||
|
||||
Reference in New Issue
Block a user