use crate::diagnostic::Diagnostic;
use crate::source::SourceStore;
use crate::span::{NodeId, SourceId, Span};
use crate::syntax_kind::SyntaxKind;
use crate::tables::{CstEdgeKind, CstNodeRecord, CstTables};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MessageMode {
#[default]
Simple,
Complex,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SemanticMessageKind {
#[default]
Pattern,
Select,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SemanticRef {
pub node: NodeId,
pub span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DeclarationKind {
Input,
Local,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ExpressionKind {
Literal,
Variable,
Function,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MarkupKind {
Open,
Standalone,
Close,
}
#[derive(Debug, Clone, Copy)]
pub struct DeclarationRecord {
pub semantic_ref: SemanticRef,
pub kind: DeclarationKind,
pub variable: Option<SemanticRef>,
}
#[derive(Debug, Clone, Copy)]
pub struct ReferenceRecord {
pub semantic_ref: SemanticRef,
pub name_span: Span,
}
#[derive(Debug, Clone, Copy)]
pub struct PatternRecord {
pub semantic_ref: SemanticRef,
pub is_quoted: bool,
}
#[derive(Debug, Clone, Copy)]
pub struct ExpressionRecord {
pub semantic_ref: SemanticRef,
pub kind: ExpressionKind,
}
#[derive(Debug, Clone, Copy)]
pub struct MarkupRecord {
pub semantic_ref: SemanticRef,
pub kind: MarkupKind,
}
#[derive(Debug, Clone, Copy)]
pub struct LiteralRecord {
pub semantic_ref: SemanticRef,
pub is_quoted: bool,
}
#[derive(Debug, Clone, Copy)]
pub struct FunctionRecord {
pub semantic_ref: SemanticRef,
pub identifier_span: Span,
}
#[derive(Debug, Clone, Copy)]
pub struct OptionRecord {
pub semantic_ref: SemanticRef,
pub identifier_span: Span,
}
#[derive(Debug, Clone, Copy)]
pub struct AttributeRecord {
pub semantic_ref: SemanticRef,
pub identifier_span: Span,
}
#[derive(Debug, Clone, Copy)]
pub struct SelectorRecord {
pub semantic_ref: SemanticRef,
pub variable: Option<SemanticRef>,
}
#[derive(Debug, Clone, Copy)]
pub struct VariantRecord {
pub semantic_ref: SemanticRef,
pub key_count: u32,
pub has_catch_all: bool,
}
#[derive(Debug, Default, Clone)]
pub struct SemanticModel {
pub mode: MessageMode,
pub kind: SemanticMessageKind,
pub declarations: Vec<DeclarationRecord>,
pub references: Vec<ReferenceRecord>,
pub patterns: Vec<PatternRecord>,
pub expressions: Vec<ExpressionRecord>,
pub markups: Vec<MarkupRecord>,
pub literals: Vec<LiteralRecord>,
pub functions: Vec<FunctionRecord>,
pub options: Vec<OptionRecord>,
pub attributes: Vec<AttributeRecord>,
pub selectors: Vec<SelectorRecord>,
pub variants: Vec<VariantRecord>,
pub diagnostics: Vec<Diagnostic>,
}
#[derive(Debug, Clone, Copy)]
pub struct SemanticView<'a> {
pub(crate) model: &'a SemanticModel,
#[allow(dead_code)]
pub(crate) tables: &'a CstTables,
}
impl<'a> SemanticView<'a> {
pub fn new(model: &'a SemanticModel, tables: &'a CstTables) -> Self {
Self { model, tables }
}
pub fn mode(&self) -> MessageMode {
self.model.mode
}
pub fn kind(&self) -> SemanticMessageKind {
self.model.kind
}
pub fn declarations(&self) -> &[DeclarationRecord] {
&self.model.declarations
}
pub fn references(&self) -> &[ReferenceRecord] {
&self.model.references
}
pub fn patterns(&self) -> &[PatternRecord] {
&self.model.patterns
}
pub fn expressions(&self) -> &[ExpressionRecord] {
&self.model.expressions
}
pub fn variants(&self) -> &[VariantRecord] {
&self.model.variants
}
}
#[allow(dead_code)]
pub fn lower(sources: &SourceStore, source_id: SourceId, tables: &CstTables) -> SemanticModel {
let mut model = SemanticModel::default();
lower_into(sources, source_id, tables, &mut model);
model
}
pub fn lower_into(
sources: &SourceStore,
source_id: SourceId,
tables: &CstTables,
model: &mut SemanticModel,
) {
let _ = (sources, source_id);
reset_model(model);
let Some(root_id) = tables.root_id() else {
return;
};
let root_rec = tables.node_at(root_id);
for (_, message_rec) in iter_node_children(tables, root_rec) {
let kind = message_rec.kind;
if kind == SyntaxKind::SimpleMessage as u16 {
model.mode = MessageMode::Simple;
model.kind = SemanticMessageKind::Pattern;
lower_message_children(tables, message_rec, model);
} else if kind == SyntaxKind::ComplexMessage as u16 {
model.mode = MessageMode::Complex;
model.kind = SemanticMessageKind::Pattern; lower_message_children(tables, message_rec, model);
}
}
}
fn reset_model(model: &mut SemanticModel) {
model.mode = MessageMode::default();
model.kind = SemanticMessageKind::default();
model.declarations.clear();
model.references.clear();
model.patterns.clear();
model.expressions.clear();
model.markups.clear();
model.literals.clear();
model.functions.clear();
model.options.clear();
model.attributes.clear();
model.selectors.clear();
model.variants.clear();
model.diagnostics.clear();
}
#[inline]
fn span_of(rec: &CstNodeRecord) -> Span {
Span::new(rec.span_start, rec.span_end)
}
#[inline]
fn semantic_ref_for(id: NodeId, rec: &CstNodeRecord) -> SemanticRef {
SemanticRef {
node: id,
span: span_of(rec),
}
}
#[inline]
fn iter_node_children<'a>(
tables: &'a CstTables,
rec: &'a CstNodeRecord,
) -> impl Iterator<Item = (NodeId, &'a CstNodeRecord)> + 'a {
tables.edges_for(rec).iter().filter_map(move |edge| {
if edge.kind == CstEdgeKind::Node as u16 {
let id = NodeId::new(edge.ref_id);
Some((id, tables.node_at(id)))
} else {
None
}
})
}
#[inline]
fn first_direct_node_child<'a>(
tables: &'a CstTables,
rec: &'a CstNodeRecord,
kind: SyntaxKind,
) -> Option<(NodeId, &'a CstNodeRecord)> {
let needle = kind as u16;
for edge in tables.edges_for(rec) {
if edge.kind == CstEdgeKind::Node as u16 {
let id = NodeId::new(edge.ref_id);
let r = tables.node_at(id);
if r.kind == needle {
return Some((id, r));
}
}
}
None
}
#[inline]
fn first_direct_child_span(tables: &CstTables, rec: &CstNodeRecord, kind: SyntaxKind) -> Span {
first_direct_node_child(tables, rec, kind)
.map(|(_, r)| span_of(r))
.unwrap_or_default()
}
fn lower_message_children(tables: &CstTables, node_rec: &CstNodeRecord, model: &mut SemanticModel) {
for (n_id, n_rec) in iter_node_children(tables, node_rec) {
let kind = n_rec.kind;
if kind == SyntaxKind::Pattern as u16 {
collect_pattern(tables, n_id, n_rec, model, false);
} else if kind == SyntaxKind::QuotedPattern as u16 {
model.patterns.push(PatternRecord {
semantic_ref: semantic_ref_for(n_id, n_rec),
is_quoted: true,
});
for (inner_id, inner_rec) in iter_node_children(tables, n_rec) {
if inner_rec.kind == SyntaxKind::Pattern as u16 {
collect_pattern(tables, inner_id, inner_rec, model, true);
}
}
} else if kind == SyntaxKind::InputDeclaration as u16 {
let placeholder = first_direct_node_child(tables, n_rec, SyntaxKind::Placeholder);
let declared_var: Option<(NodeId, SemanticRef)> = placeholder.and_then(|(_, p_rec)| {
let (_, var_expr_rec) =
first_direct_node_child(tables, p_rec, SyntaxKind::VariableExpression)?;
let (var_id, var_rec) =
first_direct_node_child(tables, var_expr_rec, SyntaxKind::Variable)?;
Some((var_id, semantic_ref_for(var_id, var_rec)))
});
model.declarations.push(DeclarationRecord {
semantic_ref: semantic_ref_for(n_id, n_rec),
kind: DeclarationKind::Input,
variable: declared_var.map(|(_, sref)| sref),
});
if let Some((_, p_rec)) = placeholder {
let skip = declared_var.map(|(id, _)| id);
collect_placeholder(tables, p_rec, model, skip);
}
} else if kind == SyntaxKind::LocalDeclaration as u16 {
let declared_var: Option<(NodeId, SemanticRef)> =
first_direct_node_child(tables, n_rec, SyntaxKind::Variable)
.map(|(id, r)| (id, semantic_ref_for(id, r)));
model.declarations.push(DeclarationRecord {
semantic_ref: semantic_ref_for(n_id, n_rec),
kind: DeclarationKind::Local,
variable: declared_var.map(|(_, sref)| sref),
});
let skip = declared_var.map(|(id, _)| id);
walk_expression_subtree(tables, n_rec, model, skip);
} else if kind == SyntaxKind::ComplexBody as u16 {
for (b_id, b_rec) in iter_node_children(tables, n_rec) {
let b_kind = b_rec.kind;
if b_kind == SyntaxKind::QuotedPattern as u16 {
model.kind = SemanticMessageKind::Pattern;
model.patterns.push(PatternRecord {
semantic_ref: semantic_ref_for(b_id, b_rec),
is_quoted: true,
});
for (inner_id, inner_rec) in iter_node_children(tables, b_rec) {
if inner_rec.kind == SyntaxKind::Pattern as u16 {
collect_pattern(tables, inner_id, inner_rec, model, true);
}
}
} else if b_kind == SyntaxKind::Matcher as u16 {
model.kind = SemanticMessageKind::Select;
collect_matcher(tables, b_rec, model);
}
}
}
}
}
fn collect_pattern(
tables: &CstTables,
node_id: NodeId,
node_rec: &CstNodeRecord,
model: &mut SemanticModel,
is_quoted: bool,
) {
model.patterns.push(PatternRecord {
semantic_ref: semantic_ref_for(node_id, node_rec),
is_quoted,
});
for (_, n_rec) in iter_node_children(tables, node_rec) {
if n_rec.kind == SyntaxKind::Placeholder as u16 {
collect_placeholder(tables, n_rec, model, None);
}
}
}
fn collect_placeholder(
tables: &CstTables,
node_rec: &CstNodeRecord,
model: &mut SemanticModel,
skip_var: Option<NodeId>,
) {
for (n_id, n_rec) in iter_node_children(tables, node_rec) {
let kind = n_rec.kind;
let expr_kind = if kind == SyntaxKind::VariableExpression as u16 {
Some(ExpressionKind::Variable)
} else if kind == SyntaxKind::LiteralExpression as u16 {
Some(ExpressionKind::Literal)
} else if kind == SyntaxKind::FunctionExpression as u16 {
Some(ExpressionKind::Function)
} else {
None
};
if let Some(ek) = expr_kind {
model.expressions.push(ExpressionRecord {
semantic_ref: semantic_ref_for(n_id, n_rec),
kind: ek,
});
walk_expression_subtree(tables, n_rec, model, skip_var);
} else if kind == SyntaxKind::Markup as u16 {
let markup_kind = detect_markup_kind(tables, n_rec);
model.markups.push(MarkupRecord {
semantic_ref: semantic_ref_for(n_id, n_rec),
kind: markup_kind,
});
walk_expression_subtree(tables, n_rec, model, skip_var);
}
}
}
fn detect_markup_kind(tables: &CstTables, node_rec: &CstNodeRecord) -> MarkupKind {
let mut saw_hash = false;
let mut saw_slash = false;
let hash = SyntaxKind::HashToken as u16;
let slash = SyntaxKind::SlashToken as u16;
for edge in tables.edges_for(node_rec) {
if edge.kind == CstEdgeKind::Token as u16 {
let tok_kind = tables.token_at(crate::span::TokenId::new(edge.ref_id)).kind;
if tok_kind == hash {
saw_hash = true;
} else if tok_kind == slash {
saw_slash = true;
}
}
}
match (saw_hash, saw_slash) {
(true, true) => MarkupKind::Standalone,
(false, true) => MarkupKind::Close,
_ => MarkupKind::Open,
}
}
fn collect_matcher(tables: &CstTables, node_rec: &CstNodeRecord, model: &mut SemanticModel) {
for (n_id, n_rec) in iter_node_children(tables, node_rec) {
let kind = n_rec.kind;
if kind == SyntaxKind::Selector as u16 {
let var = first_direct_node_child(tables, n_rec, SyntaxKind::Variable)
.map(|(id, r)| semantic_ref_for(id, r));
model.selectors.push(SelectorRecord {
semantic_ref: semantic_ref_for(n_id, n_rec),
variable: var,
});
if let Some(v) = var {
model.references.push(ReferenceRecord {
semantic_ref: v,
name_span: v.span,
});
}
} else if kind == SyntaxKind::Variant as u16 {
let mut key_count = 0u32;
let mut has_catch_all = false;
for (k_id, k_rec) in iter_node_children(tables, n_rec) {
let k_kind = k_rec.kind;
if k_kind == SyntaxKind::CatchAllKey as u16 {
has_catch_all = true;
key_count += 1;
} else if k_kind == SyntaxKind::VariantKey as u16 {
key_count += 1;
for (l_id, l_rec) in iter_node_children(tables, k_rec) {
let l_kind = l_rec.kind;
if l_kind == SyntaxKind::QuotedLiteral as u16
|| l_kind == SyntaxKind::UnquotedLiteral as u16
{
model.literals.push(LiteralRecord {
semantic_ref: semantic_ref_for(l_id, l_rec),
is_quoted: l_kind == SyntaxKind::QuotedLiteral as u16,
});
}
}
} else if k_kind == SyntaxKind::QuotedPattern as u16 {
model.patterns.push(PatternRecord {
semantic_ref: semantic_ref_for(k_id, k_rec),
is_quoted: true,
});
for (inner_id, inner_rec) in iter_node_children(tables, k_rec) {
if inner_rec.kind == SyntaxKind::Pattern as u16 {
collect_pattern(tables, inner_id, inner_rec, model, true);
}
}
}
}
model.variants.push(VariantRecord {
semantic_ref: semantic_ref_for(n_id, n_rec),
key_count,
has_catch_all,
});
}
}
}
fn walk_expression_subtree(
tables: &CstTables,
node_rec: &CstNodeRecord,
model: &mut SemanticModel,
skip_var: Option<NodeId>,
) {
for (n_id, n_rec) in iter_node_children(tables, node_rec) {
let kind = n_rec.kind;
if kind == SyntaxKind::Variable as u16 {
if skip_var == Some(n_id) {
continue;
}
let sref = semantic_ref_for(n_id, n_rec);
model.references.push(ReferenceRecord {
semantic_ref: sref,
name_span: sref.span,
});
} else if kind == SyntaxKind::Function as u16 {
let identifier_span = first_direct_child_span(tables, n_rec, SyntaxKind::Identifier);
model.functions.push(FunctionRecord {
semantic_ref: semantic_ref_for(n_id, n_rec),
identifier_span,
});
walk_expression_subtree(tables, n_rec, model, skip_var);
} else if kind == SyntaxKind::Option as u16 {
let identifier_span = first_direct_child_span(tables, n_rec, SyntaxKind::Identifier);
model.options.push(OptionRecord {
semantic_ref: semantic_ref_for(n_id, n_rec),
identifier_span,
});
walk_expression_subtree(tables, n_rec, model, skip_var);
} else if kind == SyntaxKind::Attribute as u16 {
let identifier_span = first_direct_child_span(tables, n_rec, SyntaxKind::Identifier);
model.attributes.push(AttributeRecord {
semantic_ref: semantic_ref_for(n_id, n_rec),
identifier_span,
});
walk_expression_subtree(tables, n_rec, model, skip_var);
} else if kind == SyntaxKind::QuotedLiteral as u16
|| kind == SyntaxKind::UnquotedLiteral as u16
{
model.literals.push(LiteralRecord {
semantic_ref: semantic_ref_for(n_id, n_rec),
is_quoted: kind == SyntaxKind::QuotedLiteral as u16,
});
} else {
walk_expression_subtree(tables, n_rec, model, skip_var);
}
}
}