use crate::{SyntaxKind, SyntaxNode, SyntaxToken};
pub trait AstNode: Sized {
fn can_cast(kind: SyntaxKind) -> bool;
fn cast(node: SyntaxNode) -> Option<Self>;
fn syntax(&self) -> &SyntaxNode;
}
macro_rules! ast_node {
($(#[$meta:meta])* $name:ident, $kind:ident) => {
$(#[$meta])*
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct $name(SyntaxNode);
impl AstNode for $name {
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::$kind
}
fn cast(node: SyntaxNode) -> Option<Self> {
Self::can_cast(node.kind()).then(|| Self(node))
}
fn syntax(&self) -> &SyntaxNode {
&self.0
}
}
};
}
fn child<N: AstNode>(node: &SyntaxNode) -> Option<N> {
node.children().find_map(N::cast)
}
fn children<N: AstNode>(node: &SyntaxNode) -> impl Iterator<Item = N> + use<N> {
node.children().filter_map(N::cast)
}
fn tokens(node: &SyntaxNode) -> impl Iterator<Item = SyntaxToken> + use<> {
node.children_with_tokens()
.filter_map(|element| element.into_token())
.filter(|token| !token.kind().is_trivia())
}
fn name_after(node: &SyntaxNode, skip: &[&str]) -> Option<String> {
tokens(node)
.filter(|t| matches!(t.kind(), SyntaxKind::Ident | SyntaxKind::DelimitedIdent))
.find(|t| !skip.contains(&t.text()))
.map(|t| name_text(&t))
}
pub fn name_text(token: &SyntaxToken) -> String {
match token.kind() {
SyntaxKind::DelimitedIdent => unescape(token.text()),
_ => token.text().to_owned(),
}
}
pub fn unescape(text: &str) -> String {
let inner = if text.starts_with("\"\"\"") && text.len() >= 6 {
return text[3..text.len() - 3].to_owned();
} else if text.len() >= 2 {
&text[1..text.len() - 1]
} else {
text
};
let mut out = String::with_capacity(inner.len());
let mut chars = inner.chars();
while let Some(c) = chars.next() {
if c != '\\' {
out.push(c);
continue;
}
match chars.next() {
Some('n') => out.push('\n'),
Some('r') => out.push('\r'),
Some('t') => out.push('\t'),
Some('f') => out.push('\u{c}'),
Some('u') => {
let hex: String = chars.by_ref().take(4).collect();
match u32::from_str_radix(&hex, 16).ok().and_then(char::from_u32) {
Some(decoded) => out.push(decoded),
None => {
out.push_str("\\u");
out.push_str(&hex);
}
}
}
Some(other) => out.push(other),
None => out.push('\\'),
}
}
out
}
ast_node!(
SourceFile,
SourceFile
);
impl SourceFile {
pub fn metadata(&self) -> impl Iterator<Item = Metadata> + use<> {
children(&self.0)
}
pub fn concept_maps(&self) -> impl Iterator<Item = ConceptMap> + use<> {
children(&self.0)
}
pub fn map_declaration(&self) -> Option<MapDeclaration> {
child(&self.0)
}
pub fn uses(&self) -> impl Iterator<Item = UsesDeclaration> + use<> {
children(&self.0)
}
pub fn imports(&self) -> impl Iterator<Item = ImportsDeclaration> + use<> {
children(&self.0)
}
pub fn constants(&self) -> impl Iterator<Item = ConstantDeclaration> + use<> {
children(&self.0)
}
pub fn groups(&self) -> impl Iterator<Item = Group> + use<> {
children(&self.0)
}
}
ast_node!(
Metadata,
Metadata
);
impl Metadata {
pub fn key(&self) -> Option<QualifiedName> {
child(&self.0)
}
pub fn value(&self) -> Option<Literal> {
child(&self.0)
}
}
ast_node!(
Literal,
Literal
);
impl Literal {
pub fn token(&self) -> Option<SyntaxToken> {
tokens(&self.0).next()
}
pub fn value(&self) -> Option<String> {
let token = self.token()?;
Some(match token.kind() {
SyntaxKind::SingleQuotedString
| SyntaxKind::DoubleQuotedString
| SyntaxKind::TripleQuotedString
| SyntaxKind::DelimitedIdent => unescape(token.text()),
_ => token.text().to_owned(),
})
}
pub fn kind(&self) -> Option<SyntaxKind> {
self.token().map(|t| t.kind())
}
}
ast_node!(
ConceptMap,
ConceptMap
);
impl ConceptMap {
pub fn url(&self) -> Option<Literal> {
child(&self.0)
}
pub fn prefixes(&self) -> impl Iterator<Item = ConceptMapPrefix> + use<> {
children(&self.0)
}
pub fn entries(&self) -> impl Iterator<Item = ConceptMapEntry> + use<> {
children(&self.0)
}
pub fn unmapped(&self) -> impl Iterator<Item = ConceptMapUnmapped> + use<> {
children(&self.0)
}
}
ast_node!(
ConceptMapUnmapped,
ConceptMapUnmapped
);
impl ConceptMapUnmapped {
pub fn prefix(&self) -> Option<String> {
name_after(&self.0, &["unmapped", "for"])
}
pub fn mode(&self) -> Option<String> {
tokens(&self.0)
.filter(|t| t.kind() == SyntaxKind::Ident)
.last()
.map(|t| t.text().to_owned())
}
}
ast_node!(
ConceptMapPrefix,
ConceptMapPrefix
);
impl ConceptMapPrefix {
pub fn name(&self) -> Option<String> {
name_after(&self.0, &["prefix"])
}
pub fn url(&self) -> Option<Literal> {
child(&self.0)
}
}
ast_node!(
ConceptMapEntry,
ConceptMapEntry
);
impl ConceptMapEntry {
pub fn source(&self) -> Option<ConceptMapCode> {
children(&self.0).next()
}
pub fn target(&self) -> Option<ConceptMapCode> {
children(&self.0).nth(1)
}
pub fn relation(&self) -> Option<String> {
self.0
.children()
.find(|n| n.kind() == SyntaxKind::ConceptMapRelation)
.map(|n| tokens(&n).map(|t| t.text().to_owned()).collect())
}
}
ast_node!(
ConceptMapCode,
ConceptMapCode
);
impl ConceptMapCode {
pub fn prefix(&self) -> Option<String> {
tokens(&self.0).next().map(|t| t.text().to_owned())
}
pub fn code(&self) -> Option<String> {
let token = tokens(&self.0).nth(2)?;
Some(match token.kind() {
SyntaxKind::Ident | SyntaxKind::Integer | SyntaxKind::Decimal => {
token.text().to_owned()
}
_ => unescape(token.text()),
})
}
}
ast_node!(
MapDeclaration,
MapDeclaration
);
impl MapDeclaration {
pub fn url(&self) -> Option<Literal> {
children(&self.0).next()
}
pub fn name(&self) -> Option<Literal> {
children(&self.0).nth(1)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ModelMode {
Source,
Queried,
Target,
Produced,
}
impl ModelMode {
pub fn as_str(self) -> &'static str {
match self {
ModelMode::Source => "source",
ModelMode::Queried => "queried",
ModelMode::Target => "target",
ModelMode::Produced => "produced",
}
}
}
ast_node!(
UsesDeclaration,
UsesDeclaration
);
impl UsesDeclaration {
pub fn url(&self) -> Option<Literal> {
child(&self.0)
}
pub fn alias(&self) -> Option<String> {
let alias = self
.0
.children()
.find(|n| n.kind() == SyntaxKind::UsesAlias)?;
name_after(&alias, &["alias"])
}
pub fn mode(&self) -> Option<ModelMode> {
let mut words = tokens(&self.0).map(|t| t.text().to_owned());
words.find(|w| w == "as")?;
match words.next()?.as_str() {
"source" => Some(ModelMode::Source),
"queried" => Some(ModelMode::Queried),
"target" => Some(ModelMode::Target),
"produced" => Some(ModelMode::Produced),
_ => None,
}
}
}
ast_node!(
ImportsDeclaration,
ImportsDeclaration
);
impl ImportsDeclaration {
pub fn url(&self) -> Option<Literal> {
child(&self.0)
}
}
ast_node!(
ConstantDeclaration,
ConstantDeclaration
);
impl ConstantDeclaration {
pub fn name(&self) -> Option<String> {
name_after(&self.0, &["let"])
}
pub fn expression(&self) -> Option<Expr> {
child(&self.0)
}
}
ast_node!(
Group,
Group
);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GroupTypeMode {
Types,
TypeAndTypes,
}
impl Group {
pub fn name(&self) -> Option<String> {
name_after(&self.0, &["group"])
}
pub fn parameters(&self) -> impl Iterator<Item = Parameter> + use<> {
child::<ParameterList>(&self.0)
.into_iter()
.flat_map(|list| children::<Parameter>(&list.0).collect::<Vec<_>>())
}
pub fn extends(&self) -> Option<String> {
let clause = self
.0
.children()
.find(|n| n.kind() == SyntaxKind::ExtendsClause)?;
name_after(&clause, &["extends"])
}
pub fn type_mode(&self) -> Option<GroupTypeMode> {
let mode = self
.0
.children()
.find(|n| n.kind() == SyntaxKind::TypeMode)?;
let text: String = tokens(&mode).map(|t| t.text().to_owned()).collect();
match text.as_str() {
"<<types>>" => Some(GroupTypeMode::Types),
"<<type+>>" => Some(GroupTypeMode::TypeAndTypes),
_ => None,
}
}
pub fn body(&self) -> Option<RuleBlock> {
child(&self.0)
}
pub fn rules(&self) -> impl Iterator<Item = Rule> + use<> {
self.body()
.into_iter()
.flat_map(|block| block.rules().collect::<Vec<_>>())
}
}
ast_node!(ParameterList, ParameterList);
ast_node!(
Parameter,
Parameter
);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ParameterMode {
Source,
Target,
}
impl Parameter {
pub fn mode(&self) -> Option<ParameterMode> {
match tokens(&self.0).next()?.text() {
"source" => Some(ParameterMode::Source),
"target" => Some(ParameterMode::Target),
_ => None,
}
}
pub fn name(&self) -> Option<String> {
tokens(&self.0)
.nth(1)
.filter(|t| matches!(t.kind(), SyntaxKind::Ident | SyntaxKind::DelimitedIdent))
.map(|t| name_text(&t))
}
pub fn type_annotation(&self) -> Option<TypeAnnotation> {
child(&self.0)
}
}
ast_node!(
TypeAnnotation,
TypeAnnotation
);
impl TypeAnnotation {
pub fn type_name(&self) -> Option<String> {
if let Some(name) = child::<QualifiedName>(&self.0) {
return Some(name.text());
}
child::<Literal>(&self.0).and_then(|l| l.value())
}
}
ast_node!(
RuleBlock,
RuleBlock
);
impl RuleBlock {
pub fn rules(&self) -> impl Iterator<Item = Rule> + use<> {
children(&self.0)
}
}
ast_node!(
Rule,
Rule
);
impl Rule {
pub fn sources(&self) -> impl Iterator<Item = Source> + use<> {
self.0
.children()
.find(|n| n.kind() == SyntaxKind::SourceList)
.into_iter()
.flat_map(|list| children::<Source>(&list).collect::<Vec<_>>())
}
pub fn targets(&self) -> impl Iterator<Item = Target> + use<> {
self.0
.children()
.find(|n| n.kind() == SyntaxKind::TargetList)
.into_iter()
.flat_map(|list| children::<Target>(&list).collect::<Vec<_>>())
}
pub fn dependent(&self) -> Option<DependentClause> {
child(&self.0)
}
pub fn identity_fields(&self) -> Vec<String> {
self.0
.children()
.find(|n| n.kind() == SyntaxKind::IdentityList)
.map(|list| {
tokens(&list)
.filter(|t| matches!(t.kind(), SyntaxKind::Ident | SyntaxKind::DelimitedIdent))
.map(|t| name_text(&t))
.collect()
})
.unwrap_or_default()
}
pub fn name(&self) -> Option<String> {
let name = self
.0
.children()
.find(|n| n.kind() == SyntaxKind::RuleName)?;
tokens(&name).next().map(|t| unescape(t.text()))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ListMode {
First,
NotFirst,
Last,
NotLast,
OnlyOne,
Single,
Collate,
Share(String),
}
impl ListMode {
fn from_word(word: &str) -> Option<Self> {
Some(match word {
"first" => ListMode::First,
"not_first" => ListMode::NotFirst,
"last" => ListMode::Last,
"not_last" => ListMode::NotLast,
"only_one" => ListMode::OnlyOne,
"single" => ListMode::Single,
"collate" => ListMode::Collate,
_ => return None,
})
}
}
ast_node!(
Source,
Source
);
impl Source {
pub fn path(&self) -> Option<QualifiedName> {
child(&self.0)
}
pub fn type_annotation(&self) -> Option<TypeAnnotation> {
child(&self.0)
}
pub fn cardinality(&self) -> Option<(String, String)> {
let node = self
.0
.children()
.find(|n| n.kind() == SyntaxKind::Cardinality)?;
let mut parts = tokens(&node);
let min = parts.next()?.text().to_owned();
parts.next()?;
let max = parts.next()?.text().to_owned();
Some((min, max))
}
pub fn default_value(&self) -> Option<DefaultClause> {
child(&self.0)
}
pub fn list_mode(&self) -> Option<ListMode> {
let node = self
.0
.children()
.find(|n| n.kind() == SyntaxKind::SourceListMode)?;
ListMode::from_word(tokens(&node).next()?.text())
}
pub fn alias(&self) -> Option<String> {
child::<Alias>(&self.0).and_then(|a| a.name())
}
pub fn where_clause(&self) -> Option<Expr> {
self.clause(SyntaxKind::WhereClause)
}
pub fn check_clause(&self) -> Option<Expr> {
self.clause(SyntaxKind::CheckClause)
}
pub fn log_clause(&self) -> Option<Expr> {
self.clause(SyntaxKind::LogClause)
}
fn clause(&self, kind: SyntaxKind) -> Option<Expr> {
let node = self.0.children().find(|n| n.kind() == kind)?;
child(&node)
}
}
ast_node!(
DefaultClause,
DefaultClause
);
impl DefaultClause {
pub fn expression(&self) -> Option<Expr> {
child(&self.0)
}
}
ast_node!(
Alias,
Alias
);
impl Alias {
pub fn name(&self) -> Option<String> {
name_after(&self.0, &["as"])
}
}
ast_node!(
Target,
Target
);
impl Target {
pub fn path(&self) -> Option<QualifiedName> {
child(&self.0)
}
pub fn transform(&self) -> Option<TransformValue> {
if let Some(transform) = self
.0
.children()
.find(|n| n.kind() == SyntaxKind::Transform)
{
return transform.children().find_map(TransformValue::cast);
}
self.0
.children()
.filter(|n| matches!(n.kind(), SyntaxKind::Invocation | SyntaxKind::ParenExpr))
.find_map(TransformValue::cast)
}
pub fn alias(&self) -> Option<String> {
child::<Alias>(&self.0).and_then(|a| a.name())
}
pub fn list_mode(&self) -> Option<ListMode> {
let node = self
.0
.children()
.find(|n| n.kind() == SyntaxKind::TargetListMode)?;
let mut words = tokens(&node);
let word = words.next()?;
if word.text() == "share" {
return Some(ListMode::Share(name_text(&words.next()?)));
}
ListMode::from_word(word.text())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TransformValue {
Literal(Literal),
Name(QualifiedName),
Invocation(Invocation),
Expression(Expr),
}
impl TransformValue {
fn cast(node: SyntaxNode) -> Option<Self> {
Some(match node.kind() {
SyntaxKind::Literal => TransformValue::Literal(Literal(node)),
SyntaxKind::QualifiedName => TransformValue::Name(QualifiedName(node)),
SyntaxKind::Invocation => TransformValue::Invocation(Invocation(node)),
SyntaxKind::ParenExpr => TransformValue::Expression(Expr(node)),
_ => return None,
})
}
pub fn syntax(&self) -> &SyntaxNode {
match self {
TransformValue::Literal(n) => n.syntax(),
TransformValue::Name(n) => n.syntax(),
TransformValue::Invocation(n) => n.syntax(),
TransformValue::Expression(n) => n.syntax(),
}
}
}
ast_node!(
Invocation,
Invocation
);
impl Invocation {
pub fn name(&self) -> Option<String> {
tokens(&self.0).next().map(|t| name_text(&t))
}
pub fn arguments(&self) -> impl Iterator<Item = Expr> + use<> {
child::<ArgumentList>(&self.0)
.into_iter()
.flat_map(|list| children::<Expr>(&list.0).collect::<Vec<_>>())
}
}
ast_node!(ArgumentList, ArgumentList);
ast_node!(
DependentClause,
DependentClause
);
impl DependentClause {
pub fn invocations(&self) -> impl Iterator<Item = Invocation> + use<> {
children(&self.0)
}
pub fn rules(&self) -> Option<RuleBlock> {
child(&self.0)
}
}
ast_node!(
QualifiedName,
QualifiedName
);
impl QualifiedName {
pub fn segments(&self) -> Vec<String> {
let mut segments: Vec<String> = Vec::new();
let mut choice = false;
for token in tokens(&self.0) {
match token.kind() {
SyntaxKind::Ident | SyntaxKind::DelimitedIdent if choice => {}
SyntaxKind::Ident | SyntaxKind::DelimitedIdent => segments.push(name_text(&token)),
SyntaxKind::LBracket => choice = true,
SyntaxKind::RBracket => {
choice = false;
if let Some(last) = segments.last_mut() {
last.push_str("[x]");
}
}
_ => {}
}
}
segments
}
pub fn text(&self) -> String {
self.segments().join(".")
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Expr(SyntaxNode);
impl AstNode for Expr {
fn can_cast(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::Literal
| SyntaxKind::Quantity
| SyntaxKind::NameRef
| SyntaxKind::ExternalConstant
| SyntaxKind::ParenExpr
| SyntaxKind::MemberExpr
| SyntaxKind::CallExpr
| SyntaxKind::IndexExpr
| SyntaxKind::PrefixExpr
| SyntaxKind::BinaryExpr
| SyntaxKind::TypeExpr
| SyntaxKind::NullLiteral
)
}
fn cast(node: SyntaxNode) -> Option<Self> {
Self::can_cast(node.kind()).then(|| Self(node))
}
fn syntax(&self) -> &SyntaxNode {
&self.0
}
}
impl Expr {
pub fn unparenthesized(&self) -> Expr {
let mut expr = self.clone();
while expr.0.kind() == SyntaxKind::ParenExpr {
match child::<Expr>(&expr.0) {
Some(inner) => expr = inner,
None => break,
}
}
expr
}
pub fn canonical_text(&self) -> String {
crate::expression_text(&self.0)
}
pub fn text(&self) -> String {
self.0.text().to_string().trim().to_owned()
}
}