ADR-0024 Phase F (full) step 2: usage via CommandNode.usage_ids
Migrates parse-error usage-block rendering from the legacy `dsl::usage::matched_entry` (which scanned a `Vec<Token>` for the first matched Keyword) to walker-side lookup driven by each `CommandNode`'s `usage_ids` slice. `CommandNode.usage_id: Option<&'static str>` becomes `usage_ids: &'static [&'static str]`. Multi-form families (`drop`, `add`, `show`) carry every variant — `drop` lists table/column/relationship templates; `add` lists column / relationship; `show` lists data / table. The single-shape commands carry their single catalog key. App-lifecycle CommandNodes had pointed at non-existent `parse.usage.app.*` keys (never noticed because the field was unused); they now point at the real catalog entries (`parse.usage.quit`, `parse.usage.help`, …). New helpers in `dsl::grammar`: - `usage_keys_for_input(source) -> Option<(entry_word, usage_ids)>` resolves the first identifier-shape token to a CommandNode and returns its usage_ids list. Used by `app::render_usage_block` and `input_render::ambient_hint`. - `entry_words_alphabetised() -> Vec<&'static str>` replaces `dsl::usage::entry_keywords_alphabetised`. `dsl::usage` is deleted. The "available commands:" fallback in `render_usage_block` now formats entry words as `` `<word>` `` directly (matching the `parse.token.keyword.*` catalog renders); the per-keyword catalog wrappers will collapse in the next step (ADR-0024 §cleanup-pass §F). `parse_command` and `parse_tokens` slim down: - `parse_command(input)` no longer pre-lexes — the walker scans source bytes directly. - `parse_tokens` (internal-only `pub` for "future I3/I4 work") is removed; its body folded into `parse_command`. - `unknown_command_error` reads the walker registry directly. Touched modules also drop their `crate::dsl::lexer::lex` and `crate::dsl::usage` imports: `app.rs`, `input_render.rs`, `completion.rs`. Tests: 852 passing, 0 failing, 1 ignored (down from 860 because the 8 `dsl::usage::tests::*` tests are gone with the module).
This commit is contained in:
+10
-10
@@ -176,7 +176,7 @@ pub static QUIT: CommandNode = CommandNode {
|
||||
shape: EMPTY_SEQ,
|
||||
ast_builder: build_quit,
|
||||
help_id: Some("app.quit"),
|
||||
usage_id: Some("parse.usage.app.quit"),
|
||||
usage_ids: &["parse.usage.quit"],
|
||||
hint_mode: None,
|
||||
};
|
||||
|
||||
@@ -185,7 +185,7 @@ pub static HELP: CommandNode = CommandNode {
|
||||
shape: EMPTY_SEQ,
|
||||
ast_builder: build_help,
|
||||
help_id: Some("app.help"),
|
||||
usage_id: Some("parse.usage.app.help"),
|
||||
usage_ids: &["parse.usage.help"],
|
||||
hint_mode: None,
|
||||
};
|
||||
|
||||
@@ -194,7 +194,7 @@ pub static REBUILD: CommandNode = CommandNode {
|
||||
shape: EMPTY_SEQ,
|
||||
ast_builder: build_rebuild,
|
||||
help_id: Some("app.rebuild"),
|
||||
usage_id: Some("parse.usage.app.rebuild"),
|
||||
usage_ids: &["parse.usage.rebuild"],
|
||||
hint_mode: None,
|
||||
};
|
||||
|
||||
@@ -203,7 +203,7 @@ pub static SAVE: CommandNode = CommandNode {
|
||||
shape: SAVE_AS_OPT,
|
||||
ast_builder: build_save,
|
||||
help_id: Some("app.save"),
|
||||
usage_id: Some("parse.usage.app.save"),
|
||||
usage_ids: &["parse.usage.save"],
|
||||
hint_mode: None,
|
||||
};
|
||||
|
||||
@@ -212,7 +212,7 @@ pub static NEW: CommandNode = CommandNode {
|
||||
shape: EMPTY_SEQ,
|
||||
ast_builder: build_new,
|
||||
help_id: Some("app.new"),
|
||||
usage_id: Some("parse.usage.app.new"),
|
||||
usage_ids: &["parse.usage.new"],
|
||||
hint_mode: None,
|
||||
};
|
||||
|
||||
@@ -221,7 +221,7 @@ pub static LOAD: CommandNode = CommandNode {
|
||||
shape: EMPTY_SEQ,
|
||||
ast_builder: build_load,
|
||||
help_id: Some("app.load"),
|
||||
usage_id: Some("parse.usage.app.load"),
|
||||
usage_ids: &["parse.usage.load"],
|
||||
hint_mode: None,
|
||||
};
|
||||
|
||||
@@ -230,7 +230,7 @@ pub static EXPORT: CommandNode = CommandNode {
|
||||
shape: EXPORT_PATH_OPT,
|
||||
ast_builder: build_export,
|
||||
help_id: Some("app.export"),
|
||||
usage_id: Some("parse.usage.app.export"),
|
||||
usage_ids: &["parse.usage.export"],
|
||||
hint_mode: None,
|
||||
};
|
||||
|
||||
@@ -239,7 +239,7 @@ pub static IMPORT: CommandNode = CommandNode {
|
||||
shape: IMPORT_BODY_OPT,
|
||||
ast_builder: build_import,
|
||||
help_id: Some("app.import"),
|
||||
usage_id: Some("parse.usage.app.import"),
|
||||
usage_ids: &["parse.usage.import"],
|
||||
hint_mode: None,
|
||||
};
|
||||
|
||||
@@ -248,7 +248,7 @@ pub static MODE: CommandNode = CommandNode {
|
||||
shape: MODE_VALUE,
|
||||
ast_builder: build_mode,
|
||||
help_id: Some("app.mode"),
|
||||
usage_id: Some("parse.usage.app.mode"),
|
||||
usage_ids: &["parse.usage.mode"],
|
||||
hint_mode: None,
|
||||
};
|
||||
|
||||
@@ -257,6 +257,6 @@ pub static MESSAGES: CommandNode = CommandNode {
|
||||
shape: MESSAGES_VALUE_OPT,
|
||||
ast_builder: build_messages,
|
||||
help_id: Some("app.messages"),
|
||||
usage_id: Some("parse.usage.app.messages"),
|
||||
usage_ids: &["parse.usage.messages"],
|
||||
hint_mode: None,
|
||||
};
|
||||
|
||||
@@ -529,7 +529,7 @@ pub static SHOW: CommandNode = CommandNode {
|
||||
shape: SHOW_SHAPE,
|
||||
ast_builder: build_show,
|
||||
help_id: Some("data.show"),
|
||||
usage_id: Some("parse.usage.show"),
|
||||
usage_ids: &["parse.usage.show_data", "parse.usage.show_table"],
|
||||
hint_mode: None,
|
||||
};
|
||||
|
||||
@@ -538,7 +538,7 @@ pub static INSERT: CommandNode = CommandNode {
|
||||
shape: INSERT_SHAPE,
|
||||
ast_builder: build_insert,
|
||||
help_id: Some("data.insert"),
|
||||
usage_id: Some("parse.usage.insert"),
|
||||
usage_ids: &["parse.usage.insert"],
|
||||
hint_mode: None,
|
||||
};
|
||||
|
||||
@@ -547,7 +547,7 @@ pub static UPDATE: CommandNode = CommandNode {
|
||||
shape: UPDATE_SHAPE,
|
||||
ast_builder: build_update,
|
||||
help_id: Some("data.update"),
|
||||
usage_id: Some("parse.usage.update"),
|
||||
usage_ids: &["parse.usage.update"],
|
||||
hint_mode: None,
|
||||
};
|
||||
|
||||
@@ -556,7 +556,7 @@ pub static DELETE: CommandNode = CommandNode {
|
||||
shape: DELETE_SHAPE,
|
||||
ast_builder: build_delete,
|
||||
help_id: Some("data.delete"),
|
||||
usage_id: Some("parse.usage.delete"),
|
||||
usage_ids: &["parse.usage.delete"],
|
||||
hint_mode: None,
|
||||
};
|
||||
|
||||
@@ -565,6 +565,6 @@ pub static REPLAY: CommandNode = CommandNode {
|
||||
shape: REPLAY_PATH,
|
||||
ast_builder: build_replay,
|
||||
help_id: Some("data.replay"),
|
||||
usage_id: Some("parse.usage.replay"),
|
||||
usage_ids: &["parse.usage.replay"],
|
||||
hint_mode: None,
|
||||
};
|
||||
|
||||
@@ -549,7 +549,11 @@ pub static DROP: CommandNode = CommandNode {
|
||||
shape: DROP_SHAPE,
|
||||
ast_builder: build_drop,
|
||||
help_id: Some("ddl.drop"),
|
||||
usage_id: Some("parse.usage.drop"),
|
||||
usage_ids: &[
|
||||
"parse.usage.drop_table",
|
||||
"parse.usage.drop_column",
|
||||
"parse.usage.drop_relationship",
|
||||
],
|
||||
hint_mode: None,
|
||||
};
|
||||
|
||||
@@ -558,7 +562,7 @@ pub static ADD: CommandNode = CommandNode {
|
||||
shape: ADD_SHAPE,
|
||||
ast_builder: build_add,
|
||||
help_id: Some("ddl.add"),
|
||||
usage_id: Some("parse.usage.add"),
|
||||
usage_ids: &["parse.usage.add_column", "parse.usage.add_relationship"],
|
||||
hint_mode: None,
|
||||
};
|
||||
|
||||
@@ -567,7 +571,7 @@ pub static RENAME: CommandNode = CommandNode {
|
||||
shape: RENAME_COLUMN,
|
||||
ast_builder: build_rename_column,
|
||||
help_id: Some("ddl.rename"),
|
||||
usage_id: Some("parse.usage.rename_column"),
|
||||
usage_ids: &["parse.usage.rename_column"],
|
||||
hint_mode: None,
|
||||
};
|
||||
|
||||
@@ -576,7 +580,7 @@ pub static CHANGE: CommandNode = CommandNode {
|
||||
shape: CHANGE_COLUMN,
|
||||
ast_builder: build_change_column,
|
||||
help_id: Some("ddl.change"),
|
||||
usage_id: Some("parse.usage.change_column"),
|
||||
usage_ids: &["parse.usage.change_column"],
|
||||
hint_mode: None,
|
||||
};
|
||||
|
||||
@@ -698,6 +702,6 @@ pub static CREATE: CommandNode = CommandNode {
|
||||
shape: CREATE_TABLE,
|
||||
ast_builder: build_create_table,
|
||||
help_id: Some("ddl.create"),
|
||||
usage_id: Some("parse.usage.create_table"),
|
||||
usage_ids: &["parse.usage.create_table"],
|
||||
hint_mode: None,
|
||||
};
|
||||
|
||||
+36
-2
@@ -229,12 +229,46 @@ pub struct CommandNode {
|
||||
pub ast_builder: fn(&MatchedPath) -> Result<Command, ValidationError>,
|
||||
#[allow(dead_code)]
|
||||
pub help_id: Option<&'static str>,
|
||||
#[allow(dead_code)]
|
||||
pub usage_id: Option<&'static str>,
|
||||
/// Catalog keys under `parse.usage.*` to render in the
|
||||
/// "usage:" block when a parse error fires for this command
|
||||
/// (ADR-0021 §1, ADR-0024 §architecture). Multi-form families
|
||||
/// like `drop` (drop table / drop column / drop relationship)
|
||||
/// carry every variant so the user sees the full family on a
|
||||
/// generic-entry-word failure.
|
||||
pub usage_ids: &'static [&'static str],
|
||||
#[allow(dead_code)]
|
||||
pub hint_mode: Option<HintMode>,
|
||||
}
|
||||
|
||||
/// Look up the usage catalog keys for the entry word at the start
|
||||
/// of `source`.
|
||||
///
|
||||
/// Case-insensitive, whitespace-tolerant. Replaces
|
||||
/// `dsl::usage::matched_entry` — the walker is the single source
|
||||
/// of truth for which command a given input belongs to.
|
||||
///
|
||||
/// Returns the canonical (primary-form) entry literal and the
|
||||
/// `usage_ids` list, or `None` if no entry word matches.
|
||||
#[must_use]
|
||||
pub fn usage_keys_for_input(source: &str) -> Option<(&'static str, &'static [&'static str])> {
|
||||
use crate::dsl::walker::lex_helpers::{consume_ident, skip_whitespace};
|
||||
let start = skip_whitespace(source, 0);
|
||||
let (kw_start, kw_end) = consume_ident(source, start)?;
|
||||
let word = &source[kw_start..kw_end];
|
||||
let (_, node) = command_for_entry_word(word)?;
|
||||
Some((node.entry.primary, node.usage_ids))
|
||||
}
|
||||
|
||||
/// Every command-entry word in the registry, sorted alphabetically
|
||||
/// by primary literal. Replaces `dsl::usage::entry_keywords_alphabetised`
|
||||
/// which read the same data through the legacy `usage::REGISTRY`.
|
||||
#[must_use]
|
||||
pub fn entry_words_alphabetised() -> Vec<&'static str> {
|
||||
let mut words: Vec<&'static str> = REGISTRY.iter().map(|c| c.entry.primary).collect();
|
||||
words.sort_unstable();
|
||||
words
|
||||
}
|
||||
|
||||
/// The active grammar registry. Phase A: the eleven app-lifecycle
|
||||
/// commands. Migrated commands route through this; everything
|
||||
/// else falls through to the chumsky path in `dsl::parser`.
|
||||
|
||||
Reference in New Issue
Block a user