ADR-0021 implementation: per-command usage templates in parse errors
New `dsl::usage` module: registry pairing each command's
entry-keyword with a `parse.usage.*` catalog key.
`matched_entry()` resolves the entry keyword from the
consumed token prefix; multi-entry families (add, drop,
show) return all matching keys.
Catalog: new `parse.usage.<command>` keys (one per command),
`parse.token.{keyword,punct,...}` vocabulary (one per
Keyword/Punct variant + token-class labels + LexError
kinds), and `parse.available_commands` for the no-prefix
fallback. Catalog grows ~60 entries.
Validator: extended KEYS_AND_PLACEHOLDERS; new completeness
test asserts every Keyword and Punct variant has its
`parse.token.*` entry.
`app::dispatch_dsl` rewritten to compose three blocks per
ADR-0021 §2: caret + structural/custom error + usage block
(or available-commands fallback per §5). Caret math fixed
to use original-input byte position rather than
trimmed-input position (the lexer no longer trims before
lexing). Three pre-existing app tests adjusted to look
across all error lines instead of `output.back()` (the
usage block is now the last line).
`dsl::usage::matched_entry` uses `<=` rather than `<` for
position comparison so custom errors raised by `try_map`
(whose span starts at the first consumed token) still
resolve to the entry keyword.
Tests: 668 passing, 0 failing, 1 ignored (650 baseline →
+18: 8 usage + 1 token-vocab completeness + 9 new
integration tests in tests/parse_error_pedagogy.rs
covering create/add/drop/show/frobulate/update/insert
cases). Clippy clean.
This commit is contained in:
@@ -263,6 +263,95 @@ parse:
|
||||
# Default for the `ParseError::Empty` variant — surfaces as
|
||||
# `{detail}` inside the wrapper.
|
||||
empty: "empty input"
|
||||
# No-prefix fallback (ADR-0021 §5): when the parse fails
|
||||
# before any keyword is consumed, the renderer lists every
|
||||
# command-entry keyword instead of attempting a per-command
|
||||
# usage block. `{commands}` is an oxford-joined list of
|
||||
# command-keyword renderings (each from
|
||||
# `parse.token.keyword.*`).
|
||||
available_commands: "available commands: {commands}"
|
||||
# Per-command usage templates (ADR-0021 §1). Rendered under a
|
||||
# "usage:" prefix when a parse fails after consuming a
|
||||
# known command-entry keyword. The bracket convention `[...]`
|
||||
# marks optional parts; angle-bracket `<...>` marks
|
||||
# placeholders. ADR-0009's surface conventions apply.
|
||||
usage:
|
||||
create_table: "create table <Name> with pk [<col>:<type>[, ...]]"
|
||||
drop_table: "drop table <Name>"
|
||||
drop_column: "drop column [from] [table] <Table>: <Name>"
|
||||
drop_relationship: |-
|
||||
drop relationship <Name>
|
||||
drop relationship from <Parent>.<col> to <Child>.<col>
|
||||
add_column: "add column [to] [table] <Table>: <Name> (<Type>)"
|
||||
add_relationship: |-
|
||||
add 1:n relationship [as <Name>]
|
||||
from <Parent>.<col> to <Child>.<col>
|
||||
[on delete <action>] [on update <action>]
|
||||
[--create-fk]
|
||||
rename_column: "rename column [in] [table] <Table>: <Old> to <New>"
|
||||
change_column: |-
|
||||
change column [in] [table] <Table>: <Name> (<Type>)
|
||||
[--force-conversion | --dont-convert]
|
||||
show_data: "show data <Table>"
|
||||
show_table: "show table <Table>"
|
||||
insert: "insert into <Table> [(<col>[, ...])] [values] (<value>[, ...])"
|
||||
update: "update <Table> set <col>=<value>[, ...] (where <col>=<value> | --all-rows)"
|
||||
delete: "delete from <Table> (where <col>=<value> | --all-rows)"
|
||||
replay: "replay <path> | replay '<path with spaces>'"
|
||||
# Single-token vocabulary the renderer uses to translate
|
||||
# chumsky's expected-set patterns. One key per Keyword variant
|
||||
# (validated against `Keyword::ALL`), one per Punct variant,
|
||||
# one per token-class label, one per LexError kind.
|
||||
token:
|
||||
keyword:
|
||||
create: "`create`"
|
||||
drop: "`drop`"
|
||||
add: "`add`"
|
||||
rename: "`rename`"
|
||||
change: "`change`"
|
||||
show: "`show`"
|
||||
insert: "`insert`"
|
||||
update: "`update`"
|
||||
delete: "`delete`"
|
||||
replay: "`replay`"
|
||||
table: "`table`"
|
||||
column: "`column`"
|
||||
data: "`data`"
|
||||
relationship: "`relationship`"
|
||||
pk: "`pk`"
|
||||
with: "`with`"
|
||||
from: "`from`"
|
||||
to: "`to`"
|
||||
into: "`into`"
|
||||
as: "`as`"
|
||||
in: "`in`"
|
||||
on: "`on`"
|
||||
set: "`set`"
|
||||
where: "`where`"
|
||||
values: "`values`"
|
||||
"null": "`null`"
|
||||
"true": "`true`"
|
||||
"false": "`false`"
|
||||
cascade: "`cascade`"
|
||||
restrict: "`restrict`"
|
||||
action: "`action`"
|
||||
"no": "`no`"
|
||||
punct:
|
||||
colon: "`:`"
|
||||
open_paren: "`(`"
|
||||
close_paren: "`)`"
|
||||
comma: "`,`"
|
||||
equals: "`=`"
|
||||
dot: "`.`"
|
||||
identifier: "identifier"
|
||||
number: "number"
|
||||
string_literal: "string literal"
|
||||
flag: "flag (--name)"
|
||||
end_of_input: "end of input"
|
||||
error:
|
||||
unterminated_string: "unterminated string literal"
|
||||
unknown_char: "unrecognised character `{found}`"
|
||||
bad_flag: "malformed flag (bare `--`)"
|
||||
|
||||
# ---- Project lifecycle event notes -----------------------------------
|
||||
project:
|
||||
|
||||
Reference in New Issue
Block a user