ADR-0024 Phase A: walker framework + app-lifecycle commands

Stand up the unified-grammar tree walker alongside the existing
chumsky parser and migrate the eleven app-lifecycle commands
(quit, help, rebuild, save / save as, new, load, export, import,
mode, messages) end-to-end. The router in parse_tokens consults
the walker first; non-migrated commands still fall through to
chumsky.

Scope:
- src/dsl/grammar/{mod,app}.rs: Node enum (13 kinds), Word /
  IdentSource / HintMode / HighlightClass / ValidationError /
  CommandNode types, REGISTRY of the eleven app commands.
- src/dsl/walker/{mod,driver,context,outcome,lex_helpers}.rs:
  scannerless byte-level walker, per-node-kind dispatch with
  Choice/Seq/Optional backtracking, WalkContext (Phase B-D
  schema fields stubbed), WalkOutcome with Match/Incomplete/
  Mismatch/ValidationFailed.
- src/dsl/parser.rs: try_walker_route() runs first in
  parse_tokens; bridge converts WalkOutcome to ParseError
  preserving catalog wording (mode.unknown / messages.unknown
  surface verbatim via friendly::translate). Legacy
  try_parse_app_path_command deleted; chumsky's bare-keyword
  app branches remain unreachable until Phase F sweep.

Walker design choices worth noting:
- mode <value> / messages <value> use Choice(Word, Word, Ident)
  so known keywords appear in the expected-set; the trailing
  Ident catch-all funnels unknown values into the friendly
  validator that always errors with the catalog wording.
- save / save as is one CommandNode (Optional(Word("as"))) -
  closes the round-5 "save Tab can't offer as" limitation
  structurally.
- Path-bearing UX shipped per ADR-0024: BarePath terminates at
  whitespace; paths with spaces use the (not-yet-wired) quoted
  form. Existing tests pass on the new shape.

Tests:
- 28 new walker-specific tests in dsl::walker::tests covering
  every app-lifecycle command, friendly-error wording for
  mode/messages unknown values, trailing-garbage detection,
  whitespace tolerance, and routing fall-through.
- Total: 805 passed, 0 failed, 1 ignored (was 777 / 1).
- cargo clippy --all-targets -- -D warnings clean.
This commit is contained in:
claude@clouddev1
2026-05-15 06:39:29 +00:00
parent 3e1ff83f26
commit 50b3542050
9 changed files with 1696 additions and 60 deletions
+43
View File
@@ -0,0 +1,43 @@
//! `WalkContext` — per-walk mutable state that flows through the
//! walker (ADR-0024 §WalkContext).
//!
//! Phase A keeps this minimal: app-lifecycle commands have no
//! schema dependency. The `current_table`, `current_table_columns`,
//! and schema-cache pointer become populated as Phase B-D land
//! the schema-aware DDL/data commands.
/// Per-walk state. Cheap to construct; `default()` is the right
/// shape for app-lifecycle commands.
#[derive(Debug, Default)]
pub struct WalkContext {
/// Table whose name an `Ident { source: Tables, writes_table:
/// true }` matched earlier in the walk. Phase B+ writes this.
pub current_table: Option<String>,
/// Columns of `current_table`, resolved against the schema
/// cache when the table identifier matched. Phase D+ uses
/// this to drive the dynamic `column_value_list` sub-grammar.
#[allow(dead_code)]
pub current_table_columns: Option<Vec<ColumnInfo>>,
/// For `set col=…` and `where col=…`, the column whose value
/// is about to be consumed. Phase D+ writes this so the value
/// slot picks the right typed sub-grammar.
#[allow(dead_code)]
pub current_column: Option<ColumnInfo>,
}
impl WalkContext {
pub fn new() -> Self {
Self::default()
}
}
/// Schema info for a single column. Phase D+ populates this from
/// the schema cache; Phase A leaves it unused.
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ColumnInfo {
pub name: String,
pub user_type: crate::dsl::types::Type,
}