ADR-0024 Phase F (full) step 3: delete legacy parser modules

Removes the last consumers of `dsl::lexer`, `dsl::keyword`, and
`dsl::ident_slot`, then deletes the modules.

- `Theme::token_color(&TokenKind)` deleted along with its test;
  `Theme::highlight_class_color(HighlightClass)` is the sole
  highlight-colour mapper (the walker's `per_byte_class` feeds
  it directly).
- `IdentSource` (`dsl::grammar`) absorbs the schema-list /
  expected-label / round-trip semantics that previously lived
  on `IdentSlot`. Adds `completes_from_schema`, `expected_label`,
  and `from_expected_label` methods. The walker's
  `Expectation::Ident { source }` and the schema-lookup request
  on the database worker now share one enum.
- `SchemaCache::for_slot(IdentSlot)` → `for_source(IdentSource)`.
- `Database::list_names_for` and the `Request::ListNamesFor`
  worker variant take `IdentSource`. Internal tables and column
  / relationship lookups dispatch on the same enum.
- `InvalidIdent.slot: IdentSlot` → `InvalidIdent.source: IdentSource`.
  The `invalid_ident_at_cursor` rendering branch in
  `input_render.rs::ambient_hint` updates accordingly.
- Completion's keyword filter (`Keyword::from_word`) becomes
  "backticked items whose payload is all ASCII alphabetic" —
  punct and digit literals still surface through their own
  candidate sources (composite-literal, flag, schema-ident);
  the alphabetic filter excludes them from the keyword bucket.
- `friendly::keys::tests::keyword_and_punct_have_complete_token_vocabulary`
  is dropped. It cross-checked `Keyword::ALL` / `Punct::ALL`
  against catalog entries; both enums are gone. The
  `parse.token.keyword.*` / `parse.token.punct.*` catalog
  entries themselves survive for one more commit (catalog
  cleanup, ADR-0024 §cleanup-pass); the
  `keys_validate_against_catalog` test still pins them.
- Modules deleted: `src/dsl/lexer.rs`, `src/dsl/keyword.rs`,
  `src/dsl/ident_slot.rs`.

Tests: 806 passing, 0 failing, 1 ignored. The drop from 852
reflects the removed module-internal tests (~32 lexer, 7
keyword, 4 ident_slot, 1 theme token_color, 1 friendly keys
keyword/punct), and is the expected outcome.

Clippy clean with `nursery` lints + `-D warnings`.
This commit is contained in:
claude@clouddev1
2026-05-15 08:33:59 +00:00
parent a41400e532
commit 266b4c2ef4
11 changed files with 153 additions and 1236 deletions
+33 -28
View File
@@ -14,8 +14,7 @@
//! The cycling memo (`LastCompletion` on `App`) lives in
//! `app.rs`; this module owns the candidate computation.
use crate::dsl::ident_slot::IdentSlot;
use crate::dsl::keyword::Keyword;
use crate::dsl::grammar::IdentSource;
use crate::dsl::types::Type;
use crate::dsl::{ParseError, parse_command};
@@ -53,15 +52,15 @@ pub struct SchemaCache {
impl SchemaCache {
/// Lookup the candidate list for an identifier slot.
/// `NewName` always returns `&[]` — the user invents
/// these names.
/// Sources that don't read from the schema (`NewName`,
/// `Types`, `Free`) return `&[]`.
#[must_use]
pub fn for_slot(&self, slot: IdentSlot) -> &[String] {
match slot {
IdentSlot::NewName => &[],
IdentSlot::TableName => &self.tables,
IdentSlot::Column => &self.columns,
IdentSlot::RelationshipName => &self.relationships,
pub fn for_source(&self, source: IdentSource) -> &[String] {
match source {
IdentSource::Tables => &self.tables,
IdentSource::Columns => &self.columns,
IdentSource::Relationships => &self.relationships,
IdentSource::NewName | IdentSource::Types | IdentSource::Free => &[],
}
}
}
@@ -110,7 +109,7 @@ pub struct Completion {
/// bare keywords (excluding punctuation and descriptive
/// labels per ADR-0022 §10).
/// - **Schema identifiers**: when the parser's expected-set
/// includes an `IdentSlot::expected_label()`, the matching
/// includes an `IdentSource::expected_label()`, the matching
/// schema list from `cache` is added (skipping the `NewName`
/// slot — the user invents those).
///
@@ -172,7 +171,13 @@ pub fn candidates_at_cursor(
let mut keywords: Vec<String> = expected
.iter()
.filter_map(|item| strip_backticks(item))
.filter_map(|name| Keyword::from_word(name).map(|_| name.to_string()))
// Backticked items are walker `Expectation::Word`s or
// `Expectation::Literal`s. Keywords are the
// alphabetic-only ones; punct (`,`, `=`) and digit
// literals (`1`) live in the same expected-set but
// surface through other candidate sources.
.filter(|name| !name.is_empty() && name.chars().all(|c| c.is_ascii_alphabetic()))
.map(str::to_string)
.filter(|name| matches_prefix(name))
.collect();
let mut seen_kw = std::collections::HashSet::new();
@@ -245,8 +250,8 @@ pub fn candidates_at_cursor(
// matching known-set slot. `NewName` slots return `&[]`.
let mut identifiers: Vec<String> = expected
.iter()
.filter_map(|item| IdentSlot::from_expected_label(item))
.flat_map(|slot| cache.for_slot(slot).iter().cloned())
.filter_map(|item| IdentSource::from_expected_label(item))
.flat_map(|source| cache.for_source(source).iter().cloned())
.filter(|name| matches_prefix(name))
.collect();
identifiers.sort();
@@ -365,7 +370,7 @@ pub struct InvalidIdent {
/// The text the user typed in the slot.
pub found: String,
/// Which known-set slot this position expected.
pub slot: IdentSlot,
pub source: IdentSource,
}
/// "User is typing a name" cursor state (round-3 follow-up).
@@ -408,8 +413,8 @@ pub fn typing_name_at_cursor(input: &str, cursor: usize) -> Option<TypingName> {
let expected = expected_set(leading);
let is_new_name_slot = expected
.iter()
.filter_map(|item| IdentSlot::from_expected_label(item))
.any(|slot| slot == IdentSlot::NewName);
.filter_map(|item| IdentSource::from_expected_label(item))
.any(|source| source == IdentSource::NewName);
if !is_new_name_slot {
return None;
}
@@ -485,34 +490,34 @@ pub fn invalid_ident_at_cursor(
return None;
}
// Find every known-set slot in the expected list.
let slots: Vec<IdentSlot> = expected
let sources: Vec<IdentSource> = expected
.iter()
.filter_map(|item| IdentSlot::from_expected_label(item))
.filter(|slot| slot.completes_from_schema())
.filter_map(|item| IdentSource::from_expected_label(item))
.filter(|s| s.completes_from_schema())
.collect();
if slots.is_empty() {
if sources.is_empty() {
return None;
}
let lowered = partial.to_lowercase();
// If any schema entry across the matching slots matches
// the prefix, the partial is not "invalid" — it's an
// in-progress lookup.
let any_match = slots
let any_match = sources
.iter()
.flat_map(|slot| cache.for_slot(*slot))
.flat_map(|s| cache.for_source(*s))
.any(|name| name.to_lowercase().starts_with(&lowered));
if any_match {
return None;
}
// Pick the first slot kind for the diagnostic — when
// Pick the first source kind for the diagnostic — when
// multiple are expected (e.g. `drop relationship …`
// expects RelationshipName *or* the `from` keyword;
// here only the schema slot survives the filter) we
// expects Relationships *or* the `from` keyword;
// here only the schema source survives the filter) we
// surface the first.
Some(InvalidIdent {
range: (start, cursor),
found: partial.to_string(),
slot: slots[0],
source: sources[0],
})
}
@@ -1123,7 +1128,7 @@ mod tests {
.expect("should be invalid");
assert_eq!(invalid.range, (10, 15));
assert_eq!(invalid.found, "Custp");
assert_eq!(invalid.slot, IdentSlot::TableName);
assert_eq!(invalid.source, IdentSource::Tables);
}
#[test]