ADR-0024 Phase F (full) step 5: walker-driven completion
Replaces the `ParseError::Invalid::expected: Vec<String>`
round-trip with structured `Expectation`s direct from the walker
(ADR-0024 §architecture). The completion engine no longer parses
formatted strings back into types — `Expectation::Ident { source,
role }`, `Expectation::Word`, `Expectation::Literal`,
`Expectation::Flag`, `Expectation::NumberLit`, and
`Expectation::StringLit` are consumed as enum variants.
New helper:
- `walker::expected_at_input(source) -> Vec<Expectation>`
consolidates the empty-input case (returns every CommandNode
entry word), unknown-command-word case (also entry words), and
walker-engaged case (Incomplete / Mismatch expectations) in one
place. ValidationFailed and Match resolve to empty.
`completion.rs` refactor:
- `expected_at(leading)` wraps the walker helper; replaces the
legacy string-based `expected_set`.
- Keyword candidates: filter `Expectation::Word(w)` /
`Expectation::Literal(s)` to alphabetic-only literals (no
more string-parsing / `strip_backticks`).
- Type names: detect `Expectation::Ident { source:
IdentSource::Types }` directly (replaces the `TYPE_SLOT_LABEL`
magic string).
- Flag candidates: read `Expectation::Flag(body)` and format
as `--{body}` (replaces backticked-string matching).
- Composite-literal candidates: match against
`Expectation::Literal("1")` (replaces the backticked-string
`` `1` ``).
- Schema identifiers: `Expectation::Ident { source, .. }`
filtered by `source.completes_from_schema()`.
- `is_value_literal_signature` checks for `Expectation::Word`
values "null"/"true"/"false" and `Expectation::NumberLit` +
`Expectation::StringLit` variants (replaces backticked-string
matching).
- `invalid_ident_at_cursor` and `typing_name_at_cursor` adopt
the same path.
The `typing_name_at_cursor` probe (substitute placeholder and
re-parse) still goes through `parse_command` because the probe
specifically wants the *post-name* expected set — `parse_command`
+ the string `expected` field carries that today. A future
follow-up could thread the structured probe through `walker`,
but the value-add is marginal.
`COMPOSITE_CANDIDATES` opener key changes from `` `1` `` (the
backticked-string the chumsky parser produced) to bare `"1"`
(the Expectation::Literal payload).
Touched modules: `dsl/walker/mod.rs` (new export),
`src/completion.rs` (refactor).
Tests: 806 passing, 0 failing, 1 ignored — every existing
completion test passes unchanged, proving the structured path
is behaviour-preserving. Clippy clean.
This commit is contained in:
@@ -30,6 +30,49 @@ use crate::dsl::walker::outcome::{
|
||||
pub use context::ColumnInfo;
|
||||
pub use highlight::highlight_runs;
|
||||
|
||||
/// What the grammar would accept at the end of `source`
|
||||
/// (ADR-0024 §architecture, Phase F walker-driven completion).
|
||||
///
|
||||
/// Empty / whitespace-only input yields every command-entry word
|
||||
/// as `Expectation::Word(primary)`. Otherwise the walker is
|
||||
/// driven to `EndOfInput`; if the input completes a command,
|
||||
/// the result is empty; if it fails or is incomplete, the
|
||||
/// walker's expected-set surfaces verbatim — `Ident { source,
|
||||
/// role }` carries its `IdentSource` (so the completion engine
|
||||
/// can schema-look-up without a string round-trip), `Word` /
|
||||
/// `Literal` carry their primary literal, etc.
|
||||
///
|
||||
/// Inputs whose first token is not a registered entry word
|
||||
/// fall back to listing every entry word — matches the
|
||||
/// synthetic "unknown command" expectation set the parser
|
||||
/// produces.
|
||||
#[must_use]
|
||||
pub fn expected_at_input(source: &str) -> Vec<outcome::Expectation> {
|
||||
use crate::dsl::grammar::REGISTRY;
|
||||
|
||||
if source.trim().is_empty() {
|
||||
return REGISTRY
|
||||
.iter()
|
||||
.map(|c| outcome::Expectation::Word(c.entry.primary))
|
||||
.collect();
|
||||
}
|
||||
let mut ctx = context::WalkContext::new();
|
||||
let (result, _cmd) = walk(source, outcome::WalkBound::EndOfInput, &mut ctx);
|
||||
match result.map(|r| r.outcome) {
|
||||
Some(outcome::WalkOutcome::Match { .. }) => Vec::new(),
|
||||
Some(outcome::WalkOutcome::Incomplete { expected, .. }) => expected,
|
||||
Some(outcome::WalkOutcome::Mismatch { expected, .. }) => expected,
|
||||
Some(outcome::WalkOutcome::ValidationFailed { .. }) => Vec::new(),
|
||||
// Walker didn't engage (unknown entry word): the
|
||||
// completion engine should still surface the available
|
||||
// entry words so the user can recover.
|
||||
None => REGISTRY
|
||||
.iter()
|
||||
.map(|c| outcome::Expectation::Word(c.entry.primary))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Public walk entry. `bound` is `EndOfInput` for parse;
|
||||
/// `Position(cursor)` for completion / hint (Phase A: not yet
|
||||
/// wired).
|
||||
|
||||
Reference in New Issue
Block a user