YUL tree visitor interface (#369)

- Implement the visitor interface. This simplifies working with the AST,
for example transformations into other IRs or collecting and analyzing
various statistics.
- Switch the explorer to use the visitor interface.
- Add the reciprocal function name conversion for function names.
- Some drive-by cosmetic fixes.

---------

Signed-off-by: xermicus <bigcyrill@hotmail.com>
This commit is contained in:
xermicus
2025-08-10 00:08:25 +02:00
committed by GitHub
parent 72f9e4f66e
commit 903cbd7159
24 changed files with 1303 additions and 162 deletions
@@ -15,6 +15,8 @@ use crate::lexer::Lexer;
use crate::parser::error::Error as ParserError;
use crate::parser::identifier::Identifier;
use crate::parser::statement::expression::Expression;
use crate::visitor::AstNode;
use crate::visitor::AstVisitor;
/// The Yul assignment expression statement.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
@@ -184,3 +186,20 @@ where
Ok(())
}
}
impl AstNode for Assignment {
fn accept(&self, ast_visitor: &mut impl AstVisitor) {
ast_visitor.visit_assignment(self);
}
fn visit_children(&self, ast_visitor: &mut impl AstVisitor) {
for binding in &self.bindings {
binding.accept(ast_visitor);
}
self.initializer.accept(ast_visitor);
}
fn location(&self) -> Location {
self.location
}
}
+18
View File
@@ -17,6 +17,8 @@ use crate::parser::error::Error as ParserError;
use crate::parser::statement::assignment::Assignment;
use crate::parser::statement::expression::Expression;
use crate::parser::statement::Statement;
use crate::visitor::AstNode;
use crate::visitor::AstVisitor;
/// The Yul source code block.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
@@ -226,6 +228,22 @@ where
}
}
impl AstNode for Block {
fn accept(&self, ast_visitor: &mut impl AstVisitor) {
ast_visitor.visit_block(self);
}
fn visit_children(&self, ast_visitor: &mut impl AstVisitor) {
for statement in &self.statements {
statement.accept(ast_visitor);
}
}
fn location(&self) -> Location {
self.location
}
}
#[cfg(test)]
mod tests {
use crate::lexer::token::location::Location;
+16
View File
@@ -13,6 +13,8 @@ use crate::lexer::token::Token;
use crate::lexer::Lexer;
use crate::parser::error::Error as ParserError;
use crate::parser::statement::block::Block;
use crate::visitor::AstNode;
use crate::visitor::AstVisitor;
/// The YUL code entity, which is the first block of the object.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
@@ -66,6 +68,20 @@ where
}
}
impl AstNode for Code {
fn accept(&self, ast_visitor: &mut impl AstVisitor) {
ast_visitor.visit_code(self);
}
fn visit_children(&self, ast_visitor: &mut impl AstVisitor) {
self.block.accept(ast_visitor);
}
fn location(&self) -> Location {
self.location
}
}
#[cfg(test)]
mod tests {
use crate::lexer::token::location::Location;
@@ -19,6 +19,8 @@ use crate::lexer::Lexer;
use crate::parser::error::Error as ParserError;
use crate::parser::statement::expression::literal::Literal;
use crate::parser::statement::expression::Expression;
use crate::visitor::AstNode;
use crate::visitor::AstVisitor;
use self::name::Name;
@@ -1021,3 +1023,19 @@ impl FunctionCall {
Ok(arguments.try_into().expect("Always successful"))
}
}
impl AstNode for FunctionCall {
fn accept(&self, ast_visitor: &mut impl AstVisitor) {
ast_visitor.visit_function_call(self);
}
fn visit_children(&self, ast_visitor: &mut impl AstVisitor) {
for argument in &self.arguments {
argument.accept(ast_visitor);
}
}
fn location(&self) -> Location {
self.location
}
}
@@ -1,10 +1,13 @@
//! The function name.
use std::fmt;
use serde::Deserialize;
use serde::Serialize;
/// The function name.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum Name {
/// The user-defined function.
UserDefined(String),
@@ -356,3 +359,130 @@ impl From<&str> for Name {
}
}
}
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Self::Verbatim {
input_size,
output_size,
} = self
{
return write!(f, "verbatim_{input_size}i_{output_size}o");
}
let token = match self {
Self::Add => "add",
Self::Sub => "sub",
Self::Mul => "mul",
Self::Div => "div",
Self::Mod => "mod",
Self::Sdiv => "sdiv",
Self::Smod => "smod",
Self::Lt => "lt",
Self::Gt => "gt",
Self::Eq => "eq",
Self::IsZero => "iszero",
Self::Slt => "slt",
Self::Sgt => "sgt",
Self::Or => "or",
Self::Xor => "xor",
Self::Not => "not",
Self::And => "and",
Self::Shl => "shl",
Self::Shr => "shr",
Self::Sar => "sar",
Self::Byte => "byte",
Self::Pop => "pop",
Self::AddMod => "addmod",
Self::MulMod => "mulmod",
Self::Exp => "exp",
Self::SignExtend => "signextend",
Self::Keccak256 => "keccak256",
Self::MLoad => "mload",
Self::MStore => "mstore",
Self::MStore8 => "mstore8",
Self::MCopy => "mcopy",
Self::SLoad => "sload",
Self::SStore => "sstore",
Self::TLoad => "tload",
Self::TStore => "tstore",
Self::LoadImmutable => "loadimmutable",
Self::SetImmutable => "setimmutable",
Self::CallDataLoad => "calldataload",
Self::CallDataSize => "calldatasize",
Self::CallDataCopy => "calldatacopy",
Self::CodeSize => "codesize",
Self::CodeCopy => "codecopy",
Self::ReturnDataSize => "returndatasize",
Self::ReturnDataCopy => "returndatacopy",
Self::ExtCodeSize => "extcodesize",
Self::ExtCodeHash => "extcodehash",
Self::Return => "return",
Self::Revert => "revert",
Self::Log0 => "log0",
Self::Log1 => "log1",
Self::Log2 => "log2",
Self::Log3 => "log3",
Self::Log4 => "log4",
Self::Call => "call",
Self::DelegateCall => "delegatecall",
Self::StaticCall => "staticcall",
Self::Create => "create",
Self::Create2 => "create2",
Self::DataSize => "datasize",
Self::DataOffset => "dataoffset",
Self::DataCopy => "datacopy",
Self::Stop => "stop",
Self::Invalid => "invalid",
Self::LinkerSymbol => "linkersymbol",
Self::MemoryGuard => "memoryguard",
Self::Address => "address",
Self::Caller => "caller",
Self::CallValue => "callvalue",
Self::Gas => "gas",
Self::Balance => "balance",
Self::SelfBalance => "selfbalance",
Self::GasLimit => "gaslimit",
Self::GasPrice => "gasprice",
Self::Origin => "origin",
Self::ChainId => "chainid",
Self::Timestamp => "timestamp",
Self::Number => "number",
Self::BlockHash => "blockhash",
Self::BlobHash => "blobhash",
Self::Difficulty => "difficulty",
Self::Prevrandao => "prevrandao",
Self::CoinBase => "coinbase",
Self::BaseFee => "basefee",
Self::BlobBaseFee => "blobbasefee",
Self::MSize => "msize",
Self::CallCode => "callcode",
Self::Pc => "pc",
Self::ExtCodeCopy => "extcodecopy",
Self::SelfDestruct => "selfdestruct",
Self::UserDefined(s) => s.as_str(),
Self::Verbatim { .. } => unreachable!(),
};
write!(f, "{token}")
}
}
@@ -18,6 +18,8 @@ use crate::lexer::token::Token;
use crate::lexer::Lexer;
use crate::parser::error::Error as ParserError;
use crate::parser::r#type::Type;
use crate::visitor::AstNode;
use crate::visitor::AstVisitor;
/// Represents a literal in YUL without differentiating its type.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
@@ -221,3 +223,15 @@ impl Literal {
}
}
}
impl AstNode for Literal {
fn accept(&self, ast_visitor: &mut impl AstVisitor) {
ast_visitor.visit_literal(self);
}
fn visit_children(&self, _ast_visitor: &mut impl AstVisitor) {}
fn location(&self) -> Location {
self.location
}
}
@@ -16,6 +16,8 @@ use crate::lexer::token::Token;
use crate::lexer::Lexer;
use crate::parser::error::Error as ParserError;
use crate::parser::identifier::Identifier;
use crate::visitor::AstNode;
use crate::visitor::AstVisitor;
use self::function_call::FunctionCall;
use self::literal::Literal;
@@ -144,3 +146,21 @@ impl Expression {
}
}
}
impl AstNode for Expression {
fn accept(&self, ast_visitor: &mut impl AstVisitor) {
ast_visitor.visit_expression(self);
}
fn visit_children(&self, ast_visitor: &mut impl AstVisitor) {
match self {
Self::FunctionCall(inner) => inner.accept(ast_visitor),
Self::Identifier(inner) => inner.accept(ast_visitor),
Self::Literal(inner) => inner.accept(ast_visitor),
}
}
fn location(&self) -> Location {
self.location()
}
}
@@ -11,6 +11,8 @@ use crate::lexer::token::Token;
use crate::lexer::Lexer;
use crate::parser::statement::block::Block;
use crate::parser::statement::expression::Expression;
use crate::visitor::AstNode;
use crate::visitor::AstVisitor;
/// The Yul for-loop statement.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
@@ -113,3 +115,20 @@ where
Ok(())
}
}
impl AstNode for ForLoop {
fn accept(&self, ast_visitor: &mut impl AstVisitor) {
ast_visitor.visit_for_loop(self);
}
fn visit_children(&self, ast_visitor: &mut impl AstVisitor) {
self.initializer.accept(ast_visitor);
self.condition.accept(ast_visitor);
self.finalizer.accept(ast_visitor);
self.body.accept(ast_visitor);
}
fn location(&self) -> Location {
self.location
}
}
@@ -18,6 +18,8 @@ use crate::parser::error::Error as ParserError;
use crate::parser::identifier::Identifier;
use crate::parser::statement::block::Block;
use crate::parser::statement::expression::function_call::name::Name as FunctionName;
use crate::visitor::AstNode;
use crate::visitor::AstVisitor;
/// The function definition statement.
/// All functions are translated in two steps:
@@ -329,6 +331,28 @@ where
}
}
impl AstNode for FunctionDefinition {
fn accept(&self, ast_visitor: &mut impl AstVisitor) {
ast_visitor.visit_function_definition(self);
}
fn visit_children(&self, ast_visitor: &mut impl AstVisitor) {
for argument in &self.arguments {
argument.accept(ast_visitor);
}
self.body.accept(ast_visitor);
for result in &self.result {
result.accept(ast_visitor);
}
}
fn location(&self) -> Location {
self.location
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
@@ -11,6 +11,8 @@ use crate::lexer::token::Token;
use crate::lexer::Lexer;
use crate::parser::statement::block::Block;
use crate::parser::statement::expression::Expression;
use crate::visitor::AstNode;
use crate::visitor::AstVisitor;
/// The Yul if-conditional statement.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
@@ -82,3 +84,18 @@ where
Ok(())
}
}
impl AstNode for IfConditional {
fn accept(&self, ast_visitor: &mut impl AstVisitor) {
ast_visitor.visit_if_conditional(self);
}
fn visit_children(&self, ast_visitor: &mut impl AstVisitor) {
self.condition.accept(ast_visitor);
self.block.accept(ast_visitor);
}
fn location(&self) -> Location {
self.location
}
}
+30
View File
@@ -23,6 +23,8 @@ use crate::lexer::token::location::Location;
use crate::lexer::token::Token;
use crate::lexer::Lexer;
use crate::parser::error::Error as ParserError;
use crate::visitor::AstNode;
use crate::visitor::AstVisitor;
use self::assignment::Assignment;
use self::block::Block;
@@ -177,3 +179,31 @@ impl Statement {
}
}
}
impl AstNode for Statement {
fn accept(&self, ast_visitor: &mut impl AstVisitor) {
ast_visitor.visit_statement(self);
}
fn visit_children(&self, ast_visitor: &mut impl AstVisitor) {
match self {
Self::Object(inner) => inner.accept(ast_visitor),
Self::Code(inner) => inner.accept(ast_visitor),
Self::Block(inner) => inner.accept(ast_visitor),
Self::Expression(inner) => inner.accept(ast_visitor),
Self::FunctionDefinition(inner) => inner.accept(ast_visitor),
Self::VariableDeclaration(inner) => inner.accept(ast_visitor),
Self::Assignment(inner) => inner.accept(ast_visitor),
Self::IfConditional(inner) => inner.accept(ast_visitor),
Self::Switch(inner) => inner.accept(ast_visitor),
Self::ForLoop(inner) => inner.accept(ast_visitor),
Self::Continue(_location) => {}
Self::Break(_location) => {}
Self::Leave(_location) => {}
}
}
fn location(&self) -> Location {
self.location()
}
}
+20
View File
@@ -17,6 +17,8 @@ use crate::lexer::token::Token;
use crate::lexer::Lexer;
use crate::parser::error::Error as ParserError;
use crate::parser::statement::code::Code;
use crate::visitor::AstNode;
use crate::visitor::AstVisitor;
/// The upper-level YUL object, representing the deploy code.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
@@ -297,6 +299,24 @@ where
}
}
impl AstNode for Object {
fn accept(&self, ast_visitor: &mut impl AstVisitor) {
ast_visitor.visit_object(self);
}
fn visit_children(&self, ast_visitor: &mut impl AstVisitor) {
self.code.accept(ast_visitor);
if let Some(inner_object) = &self.inner_object {
inner_object.accept(ast_visitor);
}
}
fn location(&self) -> Location {
self.location
}
}
#[cfg(test)]
mod tests {
use crate::lexer::token::location::Location;
@@ -13,6 +13,8 @@ use crate::lexer::Lexer;
use crate::parser::error::Error as ParserError;
use crate::parser::statement::block::Block;
use crate::parser::statement::expression::literal::Literal;
use crate::visitor::AstNode;
use crate::visitor::AstVisitor;
/// The Yul switch statement case.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
@@ -61,6 +63,21 @@ impl Case {
}
}
impl AstNode for Case {
fn accept(&self, ast_visitor: &mut impl AstVisitor) {
ast_visitor.visit_case(self);
}
fn visit_children(&self, ast_visitor: &mut impl AstVisitor) {
self.literal.accept(ast_visitor);
self.block.accept(ast_visitor);
}
fn location(&self) -> Location {
self.location
}
}
#[cfg(test)]
mod tests {
use crate::lexer::token::location::Location;
@@ -16,6 +16,8 @@ use crate::lexer::Lexer;
use crate::parser::error::Error as ParserError;
use crate::parser::statement::block::Block;
use crate::parser::statement::expression::Expression;
use crate::visitor::AstNode;
use crate::visitor::AstVisitor;
use self::case::Case;
@@ -179,6 +181,28 @@ where
}
}
impl AstNode for Switch {
fn accept(&self, ast_visitor: &mut impl AstVisitor) {
ast_visitor.visit_switch(self);
}
fn visit_children(&self, ast_visitor: &mut impl AstVisitor) {
self.expression.accept(ast_visitor);
for case in &self.cases {
case.accept(ast_visitor);
}
if let Some(default) = self.default.as_ref() {
default.accept(ast_visitor);
}
}
fn location(&self) -> Location {
self.location
}
}
#[cfg(test)]
mod tests {
use crate::lexer::token::location::Location;
@@ -17,6 +17,8 @@ use crate::parser::error::Error as ParserError;
use crate::parser::identifier::Identifier;
use crate::parser::statement::expression::function_call::name::Name as FunctionName;
use crate::parser::statement::expression::Expression;
use crate::visitor::AstNode;
use crate::visitor::AstVisitor;
/// The Yul variable declaration statement.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
@@ -218,6 +220,26 @@ where
}
}
impl AstNode for VariableDeclaration {
fn accept(&self, ast_visitor: &mut impl AstVisitor) {
ast_visitor.visit_variable_declaration(self);
}
fn visit_children(&self, ast_visitor: &mut impl AstVisitor) {
for binding in &self.bindings {
binding.accept(ast_visitor);
}
if let Some(initializer) = self.expression.as_ref() {
initializer.accept(ast_visitor);
}
}
fn location(&self) -> Location {
self.location
}
}
#[cfg(test)]
mod tests {
use crate::lexer::token::location::Location;