Walker: node-attached HintMode via Node::Hinted (ADR-0024 §HintMode-per-node)
Replaces the hint resolver's signature-matching (does the expected set
contain all five literal forms? an Ident{NewName}?) with a grammar-
declared annotation. New Node::Hinted { mode, inner } wrapper; the
walker records the mode in WalkContext::pending_hint_mode on entry and
clears it on any successful match (cursor moved past the slot — this
also undoes the leak where a failed Hinted branch of a Choice would
otherwise strand a stale mode). The resolver reads pending_hint_mode
directly.
Value-literal fallback slots carry ProseOnly; NewName ident slots carry
ForceProse. hint_mode_at_input_inner now delegates to
hint_resolution_at_input — one resolution path, no duplicated logic.
No behaviour change; the typing-surface matrix guards it.
This commit is contained in:
+17
-10
@@ -9,7 +9,8 @@
|
||||
|
||||
use crate::dsl::command::{AppCommand, Command, MessagesValue, ModeValue};
|
||||
use crate::dsl::grammar::{
|
||||
CommandNode, IdentSource, IdentValidator, Node, ValidationError, Word,
|
||||
CommandNode, HintMode, IdentSource, IdentValidator, Node, ValidationError,
|
||||
Word,
|
||||
};
|
||||
use crate::dsl::walker::outcome::{MatchedKind, MatchedPath};
|
||||
|
||||
@@ -43,17 +44,23 @@ const UNKNOWN_MESSAGES_VALIDATOR: IdentValidator = validate_unknown_messages;
|
||||
|
||||
const SAVE_AS_WORD: Node = Node::Word(Word::keyword("as"));
|
||||
|
||||
const IMPORT_TARGET_IDENT: Node = Node::Ident {
|
||||
source: IdentSource::NewName,
|
||||
role: "target",
|
||||
validator: None,
|
||||
highlight_override: None,
|
||||
writes_table: false,
|
||||
writes_column: false,
|
||||
writes_user_listed_column: false,
|
||||
};
|
||||
const IMPORT_TARGET: Node = Node::Hinted {
|
||||
mode: HintMode::ForceProse("hint.ambient_typing_name"),
|
||||
inner: &IMPORT_TARGET_IDENT,
|
||||
};
|
||||
|
||||
const IMPORT_AS_TARGET: Node = Node::Seq(&[
|
||||
Node::Word(Word::keyword("as")),
|
||||
Node::Ident {
|
||||
source: IdentSource::NewName,
|
||||
role: "target",
|
||||
validator: None,
|
||||
highlight_override: None,
|
||||
writes_table: false,
|
||||
writes_column: false,
|
||||
writes_user_listed_column: false,
|
||||
},
|
||||
IMPORT_TARGET,
|
||||
]);
|
||||
const IMPORT_AS_TARGET_OPT: Node = Node::Optional(&IMPORT_AS_TARGET);
|
||||
|
||||
|
||||
+10
-2
@@ -18,7 +18,7 @@
|
||||
|
||||
use crate::dsl::command::{Command, RowFilter};
|
||||
use crate::dsl::grammar::{
|
||||
CommandNode, IdentSource, Node, ValidationError, Word,
|
||||
CommandNode, HintMode, IdentSource, Node, ValidationError, Word,
|
||||
shared::{column_value_list, current_column_value},
|
||||
};
|
||||
use crate::dsl::value::Value;
|
||||
@@ -61,7 +61,15 @@ const VALUE_LITERAL_CHOICES: &[Node] = &[
|
||||
Node::NumberLit { validator: None },
|
||||
Node::StringLit,
|
||||
];
|
||||
const VALUE_LITERAL: Node = Node::Choice(VALUE_LITERAL_CHOICES);
|
||||
const VALUE_LITERAL_INNER: Node = Node::Choice(VALUE_LITERAL_CHOICES);
|
||||
/// Value-literal slot with the `ProseOnly` HintMode
|
||||
/// (ADR-0024 §HintMode-per-node) — the hint resolver surfaces
|
||||
/// the generic "Type a value: …" prose rather than the
|
||||
/// misleading `null`/`true`/`false` candidate trio.
|
||||
const VALUE_LITERAL: Node = Node::Hinted {
|
||||
mode: HintMode::ProseOnly("hint.value_literal_slot"),
|
||||
inner: &VALUE_LITERAL_INNER,
|
||||
};
|
||||
|
||||
// =================================================================
|
||||
// show — `show (data|table) <T>`
|
||||
|
||||
+52
-22
@@ -14,9 +14,15 @@
|
||||
use crate::dsl::action::ReferentialAction;
|
||||
use crate::dsl::command::{ChangeColumnMode, ColumnSpec, Command, RelationshipSelector};
|
||||
use crate::dsl::grammar::{
|
||||
CommandNode, IdentSource, Node, ValidationError, Word,
|
||||
CommandNode, HintMode, IdentSource, Node, ValidationError, Word,
|
||||
shared::{REFERENTIAL_CLAUSES, TYPE_SLOT, TYPE_VALIDATOR},
|
||||
};
|
||||
|
||||
/// `HintMode` annotation shared by every `NewName` ident slot:
|
||||
/// the user is inventing a name, so the hint panel forces the
|
||||
/// "Type a name [then …]" prose rather than offering schema
|
||||
/// candidates (ADR-0024 §HintMode-per-node).
|
||||
const NEW_NAME_HINT: HintMode = HintMode::ForceProse("hint.ambient_typing_name");
|
||||
use crate::dsl::types::Type;
|
||||
use crate::dsl::walker::outcome::{MatchedKind, MatchedPath};
|
||||
|
||||
@@ -24,7 +30,7 @@ use crate::dsl::walker::outcome::{MatchedKind, MatchedPath};
|
||||
// Building blocks
|
||||
// =================================================================
|
||||
|
||||
const TABLE_NAME_NEW: Node = Node::Ident {
|
||||
const TABLE_NAME_NEW_IDENT: Node = Node::Ident {
|
||||
source: IdentSource::NewName,
|
||||
role: "table_name",
|
||||
validator: None,
|
||||
@@ -33,6 +39,10 @@ const TABLE_NAME_NEW: Node = Node::Ident {
|
||||
writes_column: false,
|
||||
writes_user_listed_column: false,
|
||||
};
|
||||
const TABLE_NAME_NEW: Node = Node::Hinted {
|
||||
mode: NEW_NAME_HINT,
|
||||
inner: &TABLE_NAME_NEW_IDENT,
|
||||
};
|
||||
|
||||
// `writes_table: true` so that the column-name slots that
|
||||
// follow the table name in `drop column` / `rename column` /
|
||||
@@ -61,7 +71,7 @@ const COLUMN_NAME: Node = Node::Ident {
|
||||
writes_user_listed_column: false,
|
||||
};
|
||||
|
||||
const COLUMN_NAME_NEW: Node = Node::Ident {
|
||||
const COLUMN_NAME_NEW_IDENT: Node = Node::Ident {
|
||||
source: IdentSource::NewName,
|
||||
role: "column_name",
|
||||
validator: None,
|
||||
@@ -70,6 +80,10 @@ const COLUMN_NAME_NEW: Node = Node::Ident {
|
||||
writes_column: false,
|
||||
writes_user_listed_column: false,
|
||||
};
|
||||
const COLUMN_NAME_NEW: Node = Node::Hinted {
|
||||
mode: NEW_NAME_HINT,
|
||||
inner: &COLUMN_NAME_NEW_IDENT,
|
||||
};
|
||||
|
||||
const RELATIONSHIP_NAME: Node = Node::Ident {
|
||||
source: IdentSource::Relationships,
|
||||
@@ -81,7 +95,7 @@ const RELATIONSHIP_NAME: Node = Node::Ident {
|
||||
writes_user_listed_column: false,
|
||||
};
|
||||
|
||||
const RELATIONSHIP_NAME_NEW: Node = Node::Ident {
|
||||
const RELATIONSHIP_NAME_NEW_IDENT: Node = Node::Ident {
|
||||
source: IdentSource::NewName,
|
||||
role: "relationship_name",
|
||||
validator: None,
|
||||
@@ -90,6 +104,10 @@ const RELATIONSHIP_NAME_NEW: Node = Node::Ident {
|
||||
writes_column: false,
|
||||
writes_user_listed_column: false,
|
||||
};
|
||||
const RELATIONSHIP_NAME_NEW: Node = Node::Hinted {
|
||||
mode: NEW_NAME_HINT,
|
||||
inner: &RELATIONSHIP_NAME_NEW_IDENT,
|
||||
};
|
||||
|
||||
// `[to]` and `[table]` connectives.
|
||||
const TO_OPT: Node = Node::Optional(&Node::Word(Word::keyword("to")));
|
||||
@@ -308,6 +326,20 @@ const ADD_SHAPE: Node = Node::Choice(ADD_CHOICES);
|
||||
// rename_column — `rename column [in] [table] <T> : <col> to <new>`
|
||||
// =================================================================
|
||||
|
||||
const NEW_COLUMN_NAME_IDENT: Node = Node::Ident {
|
||||
source: IdentSource::NewName,
|
||||
role: "new_column_name",
|
||||
validator: None,
|
||||
highlight_override: None,
|
||||
writes_table: false,
|
||||
writes_column: false,
|
||||
writes_user_listed_column: false,
|
||||
};
|
||||
const NEW_COLUMN_NAME: Node = Node::Hinted {
|
||||
mode: NEW_NAME_HINT,
|
||||
inner: &NEW_COLUMN_NAME_IDENT,
|
||||
};
|
||||
|
||||
const RENAME_COLUMN_NODES: &[Node] = &[
|
||||
Node::Word(Word::keyword("column")),
|
||||
IN_OPT,
|
||||
@@ -316,15 +348,7 @@ const RENAME_COLUMN_NODES: &[Node] = &[
|
||||
Node::Punct(':'),
|
||||
COLUMN_NAME,
|
||||
Node::Word(Word::keyword("to")),
|
||||
Node::Ident {
|
||||
source: IdentSource::NewName,
|
||||
role: "new_column_name",
|
||||
validator: None,
|
||||
highlight_override: None,
|
||||
writes_table: false,
|
||||
writes_column: false,
|
||||
writes_user_listed_column: false,
|
||||
},
|
||||
NEW_COLUMN_NAME,
|
||||
];
|
||||
const RENAME_COLUMN: Node = Node::Seq(RENAME_COLUMN_NODES);
|
||||
|
||||
@@ -650,16 +674,22 @@ pub static CHANGE: CommandNode = CommandNode {
|
||||
// (Phase C)
|
||||
// =================================================================
|
||||
|
||||
const COL_NAME_IDENT: Node = Node::Ident {
|
||||
source: IdentSource::NewName,
|
||||
role: "col_name",
|
||||
validator: None,
|
||||
highlight_override: None,
|
||||
writes_table: false,
|
||||
writes_column: false,
|
||||
writes_user_listed_column: false,
|
||||
};
|
||||
const COL_NAME: Node = Node::Hinted {
|
||||
mode: NEW_NAME_HINT,
|
||||
inner: &COL_NAME_IDENT,
|
||||
};
|
||||
|
||||
const COL_SPEC_NODES: &[Node] = &[
|
||||
Node::Ident {
|
||||
source: IdentSource::NewName,
|
||||
role: "col_name",
|
||||
validator: None,
|
||||
highlight_override: None,
|
||||
writes_table: false,
|
||||
writes_column: false,
|
||||
writes_user_listed_column: false,
|
||||
},
|
||||
COL_NAME,
|
||||
Node::Punct(':'),
|
||||
Node::Ident {
|
||||
source: IdentSource::Types,
|
||||
|
||||
@@ -308,6 +308,24 @@ pub enum Node {
|
||||
column_name: Option<&'static str>,
|
||||
inner: &'static Self,
|
||||
},
|
||||
/// Annotates `inner` with a hint-panel `HintMode` (ADR-0024
|
||||
/// §HintMode-per-node). On entry the walker records `mode`
|
||||
/// in `WalkContext::pending_hint_mode`; on a successful
|
||||
/// inner match the record clears (so positions past the
|
||||
/// slot don't carry stale hint state). Transparent to
|
||||
/// matching, highlighting and the expected-set otherwise —
|
||||
/// it walks `inner` and returns its result verbatim.
|
||||
///
|
||||
/// This is the node-attached replacement for the hint
|
||||
/// resolver's earlier signature-matching: the grammar tree
|
||||
/// declares the hint mode at the slot, the walker
|
||||
/// propagates it, the resolver reads it. Used by the
|
||||
/// value-literal fallback slot (`ProseOnly`) and `NewName`
|
||||
/// ident slots (`ForceProse`).
|
||||
Hinted {
|
||||
mode: HintMode,
|
||||
inner: &'static Self,
|
||||
},
|
||||
}
|
||||
|
||||
/// Top-level entry record. One per command. The `entry` keyword
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
|
||||
use crate::completion::TableColumn;
|
||||
use crate::dsl::grammar::{
|
||||
IdentSource, IdentValidator, Node, NumberValidator, ValidationError, Word,
|
||||
HintMode, IdentSource, IdentValidator, Node, NumberValidator,
|
||||
ValidationError, Word,
|
||||
};
|
||||
use crate::dsl::types::Type;
|
||||
use crate::dsl::walker::context::WalkContext;
|
||||
@@ -347,7 +348,16 @@ const FALLBACK_VALUE_LITERAL_CHOICES: &[Node] = &[
|
||||
Node::NumberLit { validator: None },
|
||||
Node::StringLit,
|
||||
];
|
||||
const FALLBACK_VALUE_LITERAL: Node = Node::Choice(FALLBACK_VALUE_LITERAL_CHOICES);
|
||||
const FALLBACK_VALUE_LITERAL_INNER: Node = Node::Choice(FALLBACK_VALUE_LITERAL_CHOICES);
|
||||
/// The schemaless value-literal slot. The `ProseOnly` HintMode
|
||||
/// (ADR-0024 §HintMode-per-node) tells the hint resolver to
|
||||
/// surface the generic "Type a value: number, 'text', …" prose
|
||||
/// here rather than the misleading `null`/`true`/`false`
|
||||
/// candidate trio.
|
||||
const FALLBACK_VALUE_LITERAL: Node = Node::Hinted {
|
||||
mode: HintMode::ProseOnly("hint.value_literal_slot"),
|
||||
inner: &FALLBACK_VALUE_LITERAL_INNER,
|
||||
};
|
||||
|
||||
const FALLBACK_VALUE_LIST: Node = Node::Repeated {
|
||||
inner: &FALLBACK_VALUE_LITERAL,
|
||||
@@ -434,10 +444,3 @@ pub fn column_value_list(ctx: &WalkContext) -> Node {
|
||||
}
|
||||
Node::Seq(Box::leak(children.into_boxed_slice()))
|
||||
}
|
||||
|
||||
// The HintMode / NumberValidator imports are part of the Phase D
|
||||
// typed-slot toolkit even though only NumberValidator is used by
|
||||
// the explicit validators above; surface HintMode so future
|
||||
// per-type prose annotations can attach without re-importing.
|
||||
#[allow(dead_code)]
|
||||
const _USES_HINT_MODE: Option<crate::dsl::grammar::HintMode> = None;
|
||||
|
||||
Reference in New Issue
Block a user