pub use rowan::ast::AstNode;
use rowan::ast::support;
use crate::syntax::{MasmLanguage, SyntaxKind, SyntaxNode, SyntaxToken};
pub trait AstNodeExt: AstNode<Language = MasmLanguage> {
fn describe(&self) -> &'static str;
fn significant_tokens(&self) -> impl Iterator<Item = SyntaxToken>;
}
macro_rules! ast_node {
($(#[$meta:meta])* $name:ident, $kind:path, $description:literal) => {
__ast_node!($(#[$meta])* $name, $kind, $description, significant_tokens);
};
($(#[$meta:meta])* $name:ident, $kind:path, $description:literal, recursive = true) => {
__ast_node!($(#[$meta])* $name, $kind, $description, significant_tokens_recursive);
};
}
macro_rules! __ast_node {
($(#[$meta:meta])* $name:ident, $kind:path, $description:literal, $significant_tokens:ident) => {
$(#[$meta])*
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct $name {
syntax: SyntaxNode,
}
impl AstNode for $name {
type Language = MasmLanguage;
fn can_cast(kind: SyntaxKind) -> bool {
kind == $kind
}
fn cast(syntax: SyntaxNode) -> Option<Self> {
Self::can_cast(syntax.kind()).then_some(Self { syntax })
}
fn syntax(&self) -> &SyntaxNode {
&self.syntax
}
}
impl $name {
#[inline]
pub fn significant_tokens(&self) -> impl Iterator<Item = SyntaxToken> {
<Self as AstNodeExt>::significant_tokens(self)
}
}
impl AstNodeExt for $name {
#[inline]
fn describe(&self) -> &'static str {
$description
}
#[inline]
fn significant_tokens(&self) -> impl Iterator<Item = SyntaxToken> {
$significant_tokens(self.syntax())
}
}
};
}
ast_node!(
#[doc = "The root node for a parsed MASM source file."]
SourceFile,
SyntaxKind::SourceFile,
"source file"
);
ast_node!(
#[doc = "A `#!` documentation line. Lowering decides whether a contiguous group becomes module-level or item-level documentation."]
Doc,
SyntaxKind::Doc,
"doc comment"
);
ast_node!(
#[doc = "A `namespace` item."]
Namespace,
SyntaxKind::Namespace,
"namespace declaration"
);
ast_node!(
#[doc = "An `extern package` item."]
ExternPackage,
SyntaxKind::ExternPackage,
"extern package declaration"
);
ast_node!(
#[doc = "A `mod` or `pub mod` item."]
Submodule,
SyntaxKind::Submodule,
"submodule declaration"
);
ast_node!(
#[doc = "A `use` item."]
Import,
SyntaxKind::Import,
"import"
);
ast_node!(
#[doc = "A braced item import list."]
ImportList,
SyntaxKind::ImportList,
"import list"
);
ast_node!(
#[doc = "An item import specifier."]
ImportSpecifier,
SyntaxKind::ImportSpecifier,
"import specifier"
);
ast_node!(
#[doc = "A `const` item."]
Constant,
SyntaxKind::Constant,
"constant declaration"
);
ast_node!(
#[doc = "A `type` or `enum` item."]
TypeDecl,
SyntaxKind::TypeDecl,
"type declaration"
);
ast_node!(
#[doc = "An `adv_map` item."]
AdviceMap,
SyntaxKind::AdviceMap,
"advice map entry declaration",
recursive = true
);
ast_node!(
#[doc = "A top-level `begin` block."]
BeginBlock,
SyntaxKind::BeginBlock,
"begin"
);
ast_node!(
#[doc = "A `proc` item together with its attributes and body."]
Procedure,
SyntaxKind::Procedure,
"procedure declaration"
);
ast_node!(
#[doc = "An attribute attached to a procedure."]
Attribute,
SyntaxKind::Attribute,
"attribute"
);
ast_node!(
#[doc = "A visibility marker such as `pub`."]
Visibility,
SyntaxKind::Visibility,
"visibility modifier"
);
ast_node!(
#[doc = "A procedure signature node."]
Signature,
SyntaxKind::Signature,
"type signature"
);
ast_node!(
#[doc = "A structured operation body."]
Block,
SyntaxKind::Block,
"block"
);
ast_node!(
#[doc = "An `if.true` or `if.false` structured operation."]
IfOp,
SyntaxKind::IfOp,
"if statement"
);
ast_node!(
#[doc = "A `while.true` structured operation."]
WhileOp,
SyntaxKind::WhileOp,
"while loop"
);
ast_node!(
#[doc = "A `do`..`while`..`end` structured operation (tail-controlled loop)."]
DoWhileOp,
SyntaxKind::DoWhileOp,
"do-while loop"
);
ast_node!(
#[doc = "A `repeat.<count>` structured operation."]
RepeatOp,
SyntaxKind::RepeatOp,
"repeat"
);
ast_node!(
#[doc = "A single instruction line or grouped same-line instruction sequence."]
Instruction,
SyntaxKind::Instruction,
"instruction"
);
ast_node!(
#[doc = "A path-like token group used in imports and invocation targets."]
Path,
SyntaxKind::Path,
"symbol path"
);
ast_node!(
#[doc = "A lossless expression token group."]
Expr,
SyntaxKind::Expr,
"expression"
);
ast_node!(
#[doc = "The body of a `type` or `enum` declaration."]
TypeBody,
SyntaxKind::TypeBody,
"type definition"
);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Item {
Doc(Doc),
Namespace(Namespace),
ExternPackage(ExternPackage),
Submodule(Submodule),
Import(Import),
Constant(Constant),
TypeDecl(TypeDecl),
AdviceMap(AdviceMap),
BeginBlock(BeginBlock),
Procedure(Procedure),
}
impl Item {
pub fn cast(node: SyntaxNode) -> Option<Self> {
match node.kind() {
SyntaxKind::Doc => Doc::cast(node).map(Self::Doc),
SyntaxKind::Namespace => Namespace::cast(node).map(Self::Namespace),
SyntaxKind::ExternPackage => ExternPackage::cast(node).map(Self::ExternPackage),
SyntaxKind::Submodule => Submodule::cast(node).map(Self::Submodule),
SyntaxKind::Import => Import::cast(node).map(Self::Import),
SyntaxKind::Constant => Constant::cast(node).map(Self::Constant),
SyntaxKind::TypeDecl => TypeDecl::cast(node).map(Self::TypeDecl),
SyntaxKind::AdviceMap => AdviceMap::cast(node).map(Self::AdviceMap),
SyntaxKind::BeginBlock => BeginBlock::cast(node).map(Self::BeginBlock),
SyntaxKind::Procedure => Procedure::cast(node).map(Self::Procedure),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Operation {
If(IfOp),
While(WhileOp),
DoWhile(DoWhileOp),
Repeat(RepeatOp),
Instruction(Instruction),
}
impl Operation {
pub fn cast(node: SyntaxNode) -> Option<Self> {
match node.kind() {
SyntaxKind::IfOp => IfOp::cast(node).map(Self::If),
SyntaxKind::WhileOp => WhileOp::cast(node).map(Self::While),
SyntaxKind::DoWhileOp => DoWhileOp::cast(node).map(Self::DoWhile),
SyntaxKind::RepeatOp => RepeatOp::cast(node).map(Self::Repeat),
SyntaxKind::Instruction => Instruction::cast(node).map(Self::Instruction),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ImportKind {
Module,
Items,
}
impl SourceFile {
pub fn items(&self) -> impl Iterator<Item = Item> + '_ {
self.syntax.children().filter_map(Item::cast)
}
}
impl Namespace {
pub fn path(&self) -> Option<Path> {
support::child(&self.syntax)
}
}
impl ExternPackage {
pub fn package_token(&self) -> Option<SyntaxToken> {
token_after_keyword(&self.syntax, "package")
}
}
impl Submodule {
pub fn visibility(&self) -> Option<Visibility> {
support::child(&self.syntax)
}
pub fn name_token(&self) -> Option<SyntaxToken> {
token_after_keyword(&self.syntax, "mod")
}
}
impl Import {
pub fn visibility(&self) -> Option<Visibility> {
support::child(&self.syntax)
}
pub fn kind(&self) -> ImportKind {
if self.import_list().is_some() {
ImportKind::Items
} else {
ImportKind::Module
}
}
pub fn module_path(&self) -> Option<Path> {
support::child(&self.syntax)
}
pub fn path(&self) -> Option<Path> {
(self.kind() == ImportKind::Module).then(|| self.module_path()).flatten()
}
pub fn module_alias_token(&self) -> Option<SyntaxToken> {
if self.kind() != ImportKind::Module {
return None;
}
token_after_contextual_keyword(&self.syntax, "as")
}
pub fn alias_token(&self) -> Option<SyntaxToken> {
self.module_alias_token()
}
pub fn import_list(&self) -> Option<ImportList> {
support::child(&self.syntax)
}
pub fn item_specs(&self) -> impl Iterator<Item = ImportSpecifier> + '_ {
self.syntax.descendants().filter_map(ImportSpecifier::cast)
}
}
impl ImportList {
pub fn specifiers(&self) -> impl Iterator<Item = ImportSpecifier> + '_ {
support::children(&self.syntax)
}
}
impl ImportSpecifier {
pub fn name_token(&self) -> Option<SyntaxToken> {
significant_tokens(&self.syntax).find(|token| is_name_like_token(token.kind()))
}
pub fn alias_token(&self) -> Option<SyntaxToken> {
let name = self.name_token()?;
token_after_contextual_keyword_after(&self.syntax, &name, "as")
}
}
impl Constant {
pub fn visibility(&self) -> Option<Visibility> {
support::child(&self.syntax)
}
pub fn name_token(&self) -> Option<SyntaxToken> {
token_after_keyword(&self.syntax, "const")
}
pub fn expr(&self) -> Option<Expr> {
support::child(&self.syntax)
}
}
impl TypeDecl {
pub fn visibility(&self) -> Option<Visibility> {
support::child(&self.syntax)
}
pub fn keyword_token(&self) -> Option<SyntaxToken> {
self.syntax
.children_with_tokens()
.filter_map(rowan::NodeOrToken::into_token)
.find(|token| {
token.kind() == SyntaxKind::Ident && matches!(token.text(), "type" | "enum")
})
}
pub fn name_token(&self) -> Option<SyntaxToken> {
let keyword = self.keyword_token()?;
next_significant_token(&self.syntax, &keyword)
}
pub fn body(&self) -> Option<TypeBody> {
support::child(&self.syntax)
}
}
impl AdviceMap {
pub fn name_token(&self) -> Option<SyntaxToken> {
token_after_keyword(&self.syntax, "adv_map")
}
pub fn value_expr(&self) -> Option<Expr> {
support::child(&self.syntax)
}
}
impl BeginBlock {
pub fn block(&self) -> Option<Block> {
support::child(&self.syntax)
}
}
impl Procedure {
pub fn attributes(&self) -> impl Iterator<Item = Attribute> + '_ {
support::children(&self.syntax)
}
pub fn visibility(&self) -> Option<Visibility> {
support::child(&self.syntax)
}
pub fn signature(&self) -> Option<Signature> {
support::child(&self.syntax)
}
pub fn block(&self) -> Option<Block> {
support::children(&self.syntax).last()
}
pub fn name_token(&self) -> Option<SyntaxToken> {
token_after_keyword(&self.syntax, "proc")
}
}
impl Block {
pub fn operations(&self) -> impl Iterator<Item = Operation> + '_ {
self.syntax.children().filter_map(Operation::cast)
}
}
impl IfOp {
pub fn then_block(&self) -> Option<Block> {
support::children(&self.syntax).next()
}
pub fn else_block(&self) -> Option<Block> {
support::children(&self.syntax).nth(1)
}
}
impl WhileOp {
pub fn body(&self) -> Option<Block> {
support::child(&self.syntax)
}
}
impl DoWhileOp {
pub fn body(&self) -> Option<Block> {
support::children(&self.syntax).next()
}
pub fn condition(&self) -> Option<Block> {
support::children(&self.syntax).nth(1)
}
}
impl RepeatOp {
pub fn body(&self) -> Option<Block> {
support::child(&self.syntax)
}
}
impl Path {
pub fn segments(&self) -> impl Iterator<Item = SyntaxToken> + '_ {
self.syntax
.children_with_tokens()
.filter_map(rowan::NodeOrToken::into_token)
.filter(|token| {
matches!(
token.kind(),
SyntaxKind::Ident | SyntaxKind::QuotedIdent | SyntaxKind::SpecialIdent
)
})
}
}
fn token_after_keyword(node: &SyntaxNode, keyword: &str) -> Option<SyntaxToken> {
node.children_with_tokens()
.filter_map(rowan::NodeOrToken::into_token)
.skip_while(|token| !(token.kind() == SyntaxKind::Ident && token.text() == keyword))
.skip(1)
.find(|token| !token.kind().is_trivia())
}
fn token_after_contextual_keyword(node: &SyntaxNode, keyword: &str) -> Option<SyntaxToken> {
node.children_with_tokens()
.filter_map(rowan::NodeOrToken::into_token)
.skip_while(|token| !(token.kind() == SyntaxKind::Ident && token.text() == keyword))
.skip(1)
.find(|token| !token.kind().is_trivia())
}
fn token_after_contextual_keyword_after(
node: &SyntaxNode,
start: &SyntaxToken,
keyword: &str,
) -> Option<SyntaxToken> {
node.children_with_tokens()
.filter_map(rowan::NodeOrToken::into_token)
.skip_while(|token| token != start)
.skip(1)
.skip_while(|token| !(token.kind() == SyntaxKind::Ident && token.text() == keyword))
.skip(1)
.find(|token| !token.kind().is_trivia())
}
fn significant_tokens(node: &SyntaxNode) -> impl Iterator<Item = SyntaxToken> {
node.children_with_tokens()
.filter_map(rowan::NodeOrToken::into_token)
.filter(|token| !token.kind().is_trivia())
}
fn significant_tokens_recursive(node: &SyntaxNode) -> impl Iterator<Item = SyntaxToken> {
node.descendants_with_tokens()
.filter_map(rowan::NodeOrToken::into_token)
.filter(|token| !token.kind().is_trivia())
}
fn is_name_like_token(kind: SyntaxKind) -> bool {
matches!(kind, SyntaxKind::Ident | SyntaxKind::QuotedIdent | SyntaxKind::SpecialIdent)
}
fn next_significant_token(node: &SyntaxNode, token: &SyntaxToken) -> Option<SyntaxToken> {
node.children_with_tokens()
.filter_map(rowan::NodeOrToken::into_token)
.skip_while(|t| t != token)
.skip(1)
.find(|token| !token.kind().is_trivia())
}