diff --git a/crates/syn-solidity/src/kw.rs b/crates/syn-solidity/src/kw.rs index 818214fcb..f47369f41 100644 --- a/crates/syn-solidity/src/kw.rs +++ b/crates/syn-solidity/src/kw.rs @@ -102,4 +102,86 @@ custom_keywords!( new, revert, unchecked, + + // Yul other + switch, + case, + default, + leave, + + // Yul-EVM-builtin opcodes + stop, + add, + sub, + mul, + div, + sdiv, + //mod, + smod, + exp, + not, + lt, + gt, + slt, + sgt, + eq, + iszero, + and, + or, + xor, + byte, + shl, + shr, + sar, + addmod, + mulmod, + signextend, + keccak256, + pop, + mload, + mstore, + mstore8, + sload, + sstore, + msize, + gas, + address, + balance, + selfbalance, + caller, + callvalue, + calldataload, + calldatasize, + calldatacopy, + extcodesize, + extcodecopy, + returndatasize, + returndatacopy, + extcodehash, + create, + create2, + call, + callcode, + delegatecall, + staticcall, + //return, + //revert, + selfdestruct, + invalid, + log0, + log1, + log2, + log3, + log4, + chainid, + origin, + gasprice, + blockhash, + coinbase, + timestamp, + number, + difficulty, + prevrandao, + gaslimit, + basefee, ); diff --git a/crates/syn-solidity/src/lib.rs b/crates/syn-solidity/src/lib.rs index 5c1c6badf..b68008d8d 100644 --- a/crates/syn-solidity/src/lib.rs +++ b/crates/syn-solidity/src/lib.rs @@ -84,7 +84,11 @@ pub mod visit_mut; pub use visit_mut::VisitMut; mod yul; -pub use yul::YulBlock; +pub use yul::{ + WalrusToken, YulBlock, YulCaseBranch, YulEVMBuiltIn, YulExpr, YulFnCall, YulFor, + YulFunctionDef, YulIdent, YulIf, YulPath, YulReturns, YulStmt, YulSwitch, YulSwitchDefault, + YulVarAssign, YulVarDecl, +}; /// Parse a Solidity [`proc_macro::TokenStream`] into a [`File`]. pub fn parse(input: proc_macro::TokenStream) -> Result { diff --git a/crates/syn-solidity/src/yul/expr/fn_call.rs b/crates/syn-solidity/src/yul/expr/fn_call.rs new file mode 100644 index 000000000..3bdb030ed --- /dev/null +++ b/crates/syn-solidity/src/yul/expr/fn_call.rs @@ -0,0 +1,104 @@ +use crate::{utils::DebugPunctuated, Spanned, YulEVMBuiltIn, YulExpr, YulIdent}; + +use proc_macro2::Span; +use std::fmt; +use syn::{ + parenthesized, + parse::{discouraged::Speculative, Parse, ParseStream, Result}, + punctuated::Punctuated, + token::Paren, + Token, +}; + +/// Yul function call. +/// +/// Solidity Reference: +/// +#[derive(Clone)] +pub struct YulFnCall { + pub function_type: FnType, + pub paren_token: Paren, + pub arguments: Punctuated, +} + +impl Parse for YulFnCall { + fn parse(input: ParseStream<'_>) -> Result { + let content; + Ok(Self { + function_type: input.parse()?, + paren_token: parenthesized!(content in input), + arguments: content.parse_terminated(YulExpr::parse, Token![,])?, + }) + } +} + +impl Spanned for YulFnCall { + fn span(&self) -> Span { + let span = self.function_type.span(); + span.join(self.arguments.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.function_type.set_span(span); + self.paren_token = Paren(span); + self.arguments.set_span(span); + } +} + +impl fmt::Debug for YulFnCall { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("YulFnCall") + .field("function_type", &self.function_type) + .field("arguments", DebugPunctuated::new(&self.arguments)) + .finish() + } +} + +/// What type of function is called. +#[derive(Clone)] +pub enum FnType { + /// When calling a self defined function + Custom(YulIdent), + + /// When calling a built in evm opcode + EVMOpcode(YulEVMBuiltIn), +} + +impl Parse for FnType { + fn parse(input: ParseStream<'_>) -> Result { + let speculative_parse = input.fork(); + + if let Ok(evm_builtin) = speculative_parse.parse::() { + input.advance_to(&speculative_parse); + Ok(Self::EVMOpcode(evm_builtin)) + } else { + Ok(Self::Custom(input.parse::()?)) + } + } +} + +impl Spanned for FnType { + fn span(&self) -> Span { + match self { + Self::Custom(custom) => custom.span(), + Self::EVMOpcode(opcode) => opcode.span(), + } + } + + fn set_span(&mut self, span: Span) { + match self { + Self::Custom(custom) => custom.set_span(span), + Self::EVMOpcode(opcode) => opcode.set_span(span), + } + } +} + +impl fmt::Debug for FnType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("FnType::")?; + match self { + Self::Custom(custom) => custom.fmt(f), + Self::EVMOpcode(opcode) => opcode.fmt(f), + } + } +} diff --git a/crates/syn-solidity/src/yul/expr/mod.rs b/crates/syn-solidity/src/yul/expr/mod.rs new file mode 100644 index 000000000..0c4d0c4ef --- /dev/null +++ b/crates/syn-solidity/src/yul/expr/mod.rs @@ -0,0 +1,69 @@ +use crate::{Lit, Spanned, YulPath}; + +use std::fmt; + +use proc_macro2::Span; +use syn::{ + parse::{discouraged::Speculative, Parse, ParseStream, Result}, + token::Paren, +}; + +mod fn_call; +pub use fn_call::YulFnCall; + +/// A Yul expression. +/// +/// Solidity Reference: +/// +#[derive(Clone)] +pub enum YulExpr { + Path(YulPath), + Call(YulFnCall), + Literal(Lit), +} + +impl Parse for YulExpr { + fn parse(input: ParseStream<'_>) -> Result { + if input.peek2(Paren) { + return input.parse().map(Self::Call) + } + + let speculative_parse = input.fork(); + + if let Ok(lit) = speculative_parse.parse::() { + input.advance_to(&speculative_parse); + return Ok(Self::Literal(lit)) + } + + input.parse().map(Self::Path) + } +} + +impl Spanned for YulExpr { + fn span(&self) -> Span { + match self { + Self::Path(path) => path.span(), + Self::Call(call) => call.span(), + Self::Literal(lit) => lit.span(), + } + } + + fn set_span(&mut self, span: Span) { + match self { + Self::Path(path) => path.set_span(span), + Self::Call(call) => call.set_span(span), + Self::Literal(lit) => lit.set_span(span), + } + } +} + +impl fmt::Debug for YulExpr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("YulExpr::")?; + match self { + Self::Path(path) => path.fmt(f), + Self::Call(call) => call.fmt(f), + Self::Literal(lit) => lit.fmt(f), + } + } +} diff --git a/crates/syn-solidity/src/yul/ident/mod.rs b/crates/syn-solidity/src/yul/ident/mod.rs new file mode 100644 index 000000000..03aedd9a3 --- /dev/null +++ b/crates/syn-solidity/src/yul/ident/mod.rs @@ -0,0 +1,126 @@ +use crate::Spanned; + +use proc_macro2::{Ident, Span}; +use quote::ToTokens; +use std::fmt; +use syn::{ + ext::IdentExt, + parse::{Parse, ParseStream}, + Result, +}; + +mod path; +pub use path::YulPath; + +/// A Yul identifier. +/// +/// Solidity Reference: +/// +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct YulIdent(pub Ident); + +impl quote::IdentFragment for YulIdent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } + + fn span(&self) -> Option { + Some(self.0.span()) + } +} + +impl fmt::Display for YulIdent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl fmt::Debug for YulIdent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("YulIdent").field(&self.0).finish() + } +} + +impl> PartialEq for YulIdent { + fn eq(&self, other: &T) -> bool { + self.0 == other + } +} + +impl From for YulIdent { + fn from(value: Ident) -> Self { + Self(value) + } +} + +impl From for Ident { + fn from(value: YulIdent) -> Self { + value.0 + } +} + +impl From<&str> for YulIdent { + fn from(value: &str) -> Self { + Self::new(value) + } +} + +impl Parse for YulIdent { + fn parse(input: ParseStream<'_>) -> Result { + Self::parse_any(input) + } +} + +impl ToTokens for YulIdent { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + self.0.to_tokens(tokens); + } +} + +impl Spanned for YulIdent { + fn span(&self) -> Span { + self.0.span() + } + + fn set_span(&mut self, span: Span) { + self.0.set_span(span); + } +} + +impl YulIdent { + pub fn new(s: &str) -> Self { + Self(Ident::new(s, Span::call_site())) + } + + pub fn new_spanned(s: &str, span: Span) -> Self { + Self(Ident::new(s, span)) + } + + /// Returns the identifier as a string, without the `r#` prefix if present. + pub fn as_string(&self) -> String { + let mut s = self.0.to_string(); + if s.starts_with("r#") { + s = s[2..].to_string(); + } + s + } + + /// Parses any identifier including keywords. + pub fn parse_any(input: ParseStream<'_>) -> Result { + input.call(Ident::parse_any).map(Self) + } + + /// Peeks any identifier including keywords. + pub fn peek_any(input: ParseStream<'_>) -> bool { + input.peek(Ident::peek_any) + } + + pub fn parse_opt(input: ParseStream<'_>) -> Result> { + if Self::peek_any(input) { + input.parse().map(Some) + } else { + Ok(None) + } + } +} diff --git a/crates/syn-solidity/src/yul/ident/path.rs b/crates/syn-solidity/src/yul/ident/path.rs new file mode 100644 index 000000000..2af0abcec --- /dev/null +++ b/crates/syn-solidity/src/yul/ident/path.rs @@ -0,0 +1,39 @@ +use crate::{Spanned, YulIdent}; + +use proc_macro2::Span; +use std::fmt; +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + Result, Token, +}; + +/// In inline assembly, only dot-less identifiers can be declared, but dotted +/// paths can reference declarations made outside the assembly block. +/// +/// Solidity Reference: +/// +#[derive(Clone)] +pub struct YulPath(Punctuated); + +impl Parse for YulPath { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self(Punctuated::parse_separated_nonempty(input)?)) + } +} + +impl Spanned for YulPath { + fn span(&self) -> Span { + crate::utils::join_spans(&self.0) + } + + fn set_span(&mut self, span: Span) { + crate::utils::set_spans(&mut self.0, span) + } +} + +impl fmt::Debug for YulPath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("YulPath").field(&self.0).finish() + } +} diff --git a/crates/syn-solidity/src/yul/mod.rs b/crates/syn-solidity/src/yul/mod.rs index c1265f967..18307120d 100644 --- a/crates/syn-solidity/src/yul/mod.rs +++ b/crates/syn-solidity/src/yul/mod.rs @@ -1,2 +1,14 @@ -mod block; -pub use block::YulBlock; +mod expr; +pub use expr::{YulExpr, YulFnCall}; + +mod stmt; +pub use stmt::{ + WalrusToken, YulBlock, YulCaseBranch, YulFor, YulIf, YulStmt, YulSwitch, YulSwitchDefault, + YulVarAssign, YulVarDecl, +}; + +mod ident; +pub use ident::{YulIdent, YulPath}; + +mod r#type; +pub use r#type::{YulEVMBuiltIn, YulFunctionDef, YulReturns}; diff --git a/crates/syn-solidity/src/yul/stmt/assignment.rs b/crates/syn-solidity/src/yul/stmt/assignment.rs new file mode 100644 index 000000000..06919a45a --- /dev/null +++ b/crates/syn-solidity/src/yul/stmt/assignment.rs @@ -0,0 +1,61 @@ +use crate::{utils::DebugPunctuated, Spanned, WalrusToken, YulExpr, YulPath}; + +use proc_macro2::Span; +use std::fmt; +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + Result, Token, +}; + +/// Yul variable assignment. `x := 0` or `x, y := foo()`. +/// Assigning values to multiple variables requires a function call. +/// +/// Solidity Reference: +/// +#[derive(Clone)] +pub struct YulVarAssign { + pub vars: Punctuated, + pub walrus_token: WalrusToken, + pub assigned_value: YulExpr, +} + +impl Parse for YulVarAssign { + fn parse(input: ParseStream<'_>) -> Result { + let vars = Punctuated::parse_separated_nonempty(input)?; + let walrus_token = input.parse()?; + let assigned_value = input.parse()?; + + if vars.len() > 1 && !matches!(assigned_value, YulExpr::Call(_)) { + return Err(input.error("Multiple variables require a function call for assignment")) + } + + Ok(Self { + vars, + walrus_token, + assigned_value, + }) + } +} + +impl Spanned for YulVarAssign { + fn span(&self) -> Span { + let span = crate::utils::join_spans(&self.vars); + span.join(self.assigned_value.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + crate::utils::set_spans(&mut self.vars, span); + self.walrus_token.set_span(span); + self.assigned_value.set_span(span); + } +} + +impl fmt::Debug for YulVarAssign { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("YulVarAssign") + .field("vars", DebugPunctuated::new(&self.vars)) + .field("assigned_value", &self.assigned_value) + .finish() + } +} diff --git a/crates/syn-solidity/src/yul/block.rs b/crates/syn-solidity/src/yul/stmt/block.rs similarity index 56% rename from crates/syn-solidity/src/yul/block.rs rename to crates/syn-solidity/src/yul/stmt/block.rs index 75b933c31..e1601ae7f 100644 --- a/crates/syn-solidity/src/yul/block.rs +++ b/crates/syn-solidity/src/yul/stmt/block.rs @@ -1,5 +1,6 @@ -use crate::Spanned; -use proc_macro2::{Span, TokenStream}; +use crate::{Spanned, YulStmt}; + +use proc_macro2::Span; use std::fmt; use syn::{ braced, @@ -8,20 +9,14 @@ use syn::{ Result, }; +/// A Yul block contains `YulStmt` between curly braces. +/// +/// Solidity Reference: +/// #[derive(Clone)] pub struct YulBlock { pub brace_token: Brace, - // TODO - pub stmts: TokenStream, -} - -impl fmt::Debug for YulBlock { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("YulBlock") - // .field("stmts", &self.stmts) - .field("stmts", &self.stmts.to_string()) - .finish() - } + pub stmts: Vec, } impl Parse for YulBlock { @@ -29,7 +24,14 @@ impl Parse for YulBlock { let content; Ok(Self { brace_token: braced!(content in input), - stmts: content.parse()?, + stmts: { + let mut stmts = Vec::new(); + while !content.is_empty() { + let stmt: YulStmt = content.parse()?; + stmts.push(stmt); + } + stmts + }, }) } } @@ -43,3 +45,11 @@ impl Spanned for YulBlock { self.brace_token = Brace(span); } } + +impl fmt::Debug for YulBlock { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("YulBlock") + .field("stmt", &self.stmts) + .finish() + } +} diff --git a/crates/syn-solidity/src/yul/stmt/for.rs b/crates/syn-solidity/src/yul/stmt/for.rs new file mode 100644 index 000000000..ec4f638e9 --- /dev/null +++ b/crates/syn-solidity/src/yul/stmt/for.rs @@ -0,0 +1,60 @@ +use crate::{Spanned, YulBlock, YulExpr}; + +use proc_macro2::Span; +use std::fmt; +use syn::{ + parse::{Parse, ParseStream}, + Result, Token, +}; + +/// Yul for loop e.g `for {let i := 0} lt(i,10) {i := add(i,1)} {mstore(i,7)}`. +/// +/// Solidity Reference: +/// +/// breakdown of parts: +#[derive(Clone)] +pub struct YulFor { + for_token: Token![for], + initialization: YulBlock, + condition: YulExpr, + post_iteration: YulBlock, + body: YulBlock, +} + +impl Parse for YulFor { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + for_token: input.parse()?, + initialization: input.parse()?, + condition: input.parse()?, + post_iteration: input.parse()?, + body: input.parse()?, + }) + } +} + +impl Spanned for YulFor { + fn span(&self) -> Span { + let span = self.for_token.span(); + span.join(self.body.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.for_token.set_span(span); + self.initialization.set_span(span); + self.condition.set_span(span); + self.post_iteration.set_span(span); + self.body.set_span(span); + } +} + +impl fmt::Debug for YulFor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("YulFor") + .field("initialization", &self.initialization) + .field("condition", &self.condition) + .field("post_iteration", &self.post_iteration) + .field("body", &self.body) + .finish() + } +} diff --git a/crates/syn-solidity/src/yul/stmt/if.rs b/crates/syn-solidity/src/yul/stmt/if.rs new file mode 100644 index 000000000..61a5de502 --- /dev/null +++ b/crates/syn-solidity/src/yul/stmt/if.rs @@ -0,0 +1,51 @@ +use crate::{Spanned, YulBlock, YulExpr}; + +use proc_macro2::Span; +use std::fmt; +use syn::{ + parse::{Parse, ParseStream}, + Result, Token, +}; + +/// A Yul if statement: `if lt(a, b) { sstore(0, 1) }`. +/// +/// Reference: +/// +#[derive(Clone)] +pub struct YulIf { + pub if_token: Token![if], + pub cond: YulExpr, + pub then_branch: Box, +} + +impl Parse for YulIf { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + if_token: input.parse()?, + cond: input.parse()?, + then_branch: input.parse()?, + }) + } +} + +impl Spanned for YulIf { + fn span(&self) -> Span { + let span = self.if_token.span; + span.join(self.then_branch.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.if_token.set_span(span); + self.cond.set_span(span); + self.then_branch.set_span(span); + } +} + +impl fmt::Debug for YulIf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("YulIf") + .field("cond", &self.cond) + .field("then_branch", &self.then_branch) + .finish() + } +} diff --git a/crates/syn-solidity/src/yul/stmt/mod.rs b/crates/syn-solidity/src/yul/stmt/mod.rs new file mode 100644 index 000000000..c9707ae07 --- /dev/null +++ b/crates/syn-solidity/src/yul/stmt/mod.rs @@ -0,0 +1,154 @@ +use crate::{kw, Spanned, YulFnCall, YulFunctionDef}; + +use std::fmt; + +use proc_macro2::Span; +use syn::{ + parse::{Parse, ParseStream, Result}, + token::{Brace, Paren}, + Token, +}; + +mod r#if; +pub use r#if::YulIf; + +mod block; +pub use block::YulBlock; + +mod var_decl; +pub use var_decl::YulVarDecl; + +mod r#for; +pub use r#for::YulFor; + +mod switch; +pub use switch::{YulCaseBranch, YulSwitch, YulSwitchDefault}; + +mod assignment; +pub use assignment::YulVarAssign; + +mod walrus_token; +pub use walrus_token::WalrusToken; + +/// A Yul statement. +/// +/// Solidity Reference: +/// +#[derive(Clone)] +pub enum YulStmt { + /// A Yul blocked scope: `{ ... }`. + Block(YulBlock), + + /// A variable declaration statement: `let x := 0`. + Decl(YulVarDecl), + + /// A variable assignment statement: `x := 1`. + Assign(YulVarAssign), + + /// A function call statement: `foo(a, b)`. + Call(YulFnCall), + + /// A if statement: `if lt(a, b) { ... }`. + If(YulIf), + + /// A for statement: `for {let i := 0} lt(i,10) {i := add(i,1)} { ... }`. + For(YulFor), + + /// A switch statement: `switch expr case 0 { ... } default { ... }`. + Switch(YulSwitch), + + /// A leave statement: `leave`. + Leave(kw::leave), + + /// A break statement: `break`. + Break(Token![break]), + + /// A continue statement: `continue`. + Continue(Token![continue]), + + /// A function defenition statement: `function f() { ... }`. + FunctionDef(YulFunctionDef), +} + +impl Parse for YulStmt { + fn parse(input: ParseStream<'_>) -> Result { + let _ = input.call(syn::Attribute::parse_outer)?; + + if input.peek(Brace) { + input.parse().map(Self::Block) + } else if input.peek(Token![let]) { + input.parse().map(Self::Decl) + } else if input.peek(Token![if]) { + input.parse().map(Self::If) + } else if input.peek(Token![for]) { + input.parse().map(Self::For) + } else if input.peek(kw::switch) { + input.parse().map(Self::Switch) + } else if input.peek(kw::leave) { + input.parse().map(Self::Leave) + } else if input.peek(Token![break]) { + input.parse().map(Self::Break) + } else if input.peek(Token![continue]) { + input.parse().map(Self::Continue) + } else if input.peek(kw::function) { + input.parse().map(Self::FunctionDef) + } else if input.peek2(Paren) { + input.parse().map(Self::Call) + } else { + input.parse().map(Self::Assign) + } + } +} + +impl Spanned for YulStmt { + fn span(&self) -> Span { + match self { + YulStmt::Block(block) => block.span(), + YulStmt::Decl(decl) => decl.span(), + YulStmt::Assign(assign) => assign.span(), + YulStmt::Call(call) => call.span(), + YulStmt::If(r#if) => r#if.span(), + YulStmt::For(r#for) => r#for.span(), + YulStmt::Switch(switch) => switch.span(), + YulStmt::Leave(leave) => leave.span(), + YulStmt::Break(r#break) => r#break.span(), + YulStmt::Continue(r#continue) => r#continue.span(), + YulStmt::FunctionDef(fn_def) => fn_def.span(), + } + } + + fn set_span(&mut self, span: Span) { + match self { + YulStmt::Block(block) => block.set_span(span), + YulStmt::Decl(decl) => decl.set_span(span), + YulStmt::Assign(assign) => assign.set_span(span), + YulStmt::Call(call) => call.set_span(span), + YulStmt::If(r#if) => r#if.set_span(span), + YulStmt::For(r#for) => r#for.set_span(span), + YulStmt::Switch(switch) => switch.set_span(span), + YulStmt::Leave(leave) => leave.set_span(span), + YulStmt::Break(r#break) => r#break.set_span(span), + YulStmt::Continue(r#continue) => r#continue.set_span(span), + YulStmt::FunctionDef(fn_def) => fn_def.set_span(span), + } + } +} + +impl fmt::Debug for YulStmt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("YulStmt::")?; + match self { + YulStmt::Block(block) => block.fmt(f), + YulStmt::Decl(decl) => decl.fmt(f), + YulStmt::Assign(assign) => assign.fmt(f), + YulStmt::Call(call) => call.fmt(f), + YulStmt::If(r#if) => r#if.fmt(f), + YulStmt::For(r#for) => r#for.fmt(f), + YulStmt::Switch(switch) => switch.fmt(f), + YulStmt::Leave(leave) => leave.fmt(f), + YulStmt::Break(r#break) => r#break.fmt(f), + YulStmt::Continue(r#continue) => r#continue.fmt(f), + YulStmt::FunctionDef(fn_def) => fn_def.fmt(f), + } + } +} diff --git a/crates/syn-solidity/src/yul/stmt/switch.rs b/crates/syn-solidity/src/yul/stmt/switch.rs new file mode 100644 index 000000000..ddeb420be --- /dev/null +++ b/crates/syn-solidity/src/yul/stmt/switch.rs @@ -0,0 +1,162 @@ +use crate::{kw, Lit, Spanned, YulBlock, YulExpr}; + +use proc_macro2::Span; +use std::fmt; +use syn::parse::{Parse, ParseStream, Result}; + +/// A Yul switch statement can consist of only a default-case or one +/// or more non-default cases optionally followed by a default-case. +/// +/// Example switch statement in Yul: +/// +/// ```solidity +/// switch exponent +/// case 0 { result := 1 } +/// case 1 { result := base } +/// default { revert(0, 0) } +/// ``` +/// +/// Solidity Reference: +/// +#[derive(Clone)] +pub struct YulSwitch { + pub switch_token: kw::switch, + pub selector: YulExpr, + pub branches: Vec, + pub default_case: Option, +} + +impl Parse for YulSwitch { + fn parse(input: ParseStream<'_>) -> Result { + let switch_token = input.parse()?; + let selector = input.parse()?; + let branches = { + let mut branches = vec![]; + while input.peek(kw::case) { + branches.push(input.parse()?); + } + branches + }; + let default_case = { + if input.peek(kw::default) { + Some(input.parse()?) + } else { + None + } + }; + + if branches.is_empty() && default_case.is_none() { + return Err(input.error("Must have at least one case or a default case.")) + } + + Ok(Self { + switch_token, + selector, + branches, + default_case, + }) + } +} + +impl Spanned for YulSwitch { + fn span(&self) -> Span { + let span = self.switch_token.span(); + if let Some(default_case) = &self.default_case { + return span.join(default_case.span()).unwrap_or(span) + } + span.join(self.branches.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.switch_token.set_span(span); + self.selector.set_span(span); + self.branches.set_span(span); + self.default_case.set_span(span); + } +} + +impl fmt::Debug for YulSwitch { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("YulSwitch") + .field("selector", &self.selector) + .field("branches", &self.branches) + .field("default_case", &self.default_case) + .finish() + } +} + +/// Represents a non-default case of a Yul switch statement. +#[derive(Clone)] +pub struct YulCaseBranch { + pub case_token: kw::case, + pub constant: Lit, + pub body: YulBlock, +} + +impl Parse for YulCaseBranch { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + case_token: input.parse()?, + constant: input.parse()?, + body: input.parse()?, + }) + } +} + +impl Spanned for YulCaseBranch { + fn span(&self) -> Span { + let span = self.case_token.span(); + span.join(self.body.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.case_token.set_span(span); + self.constant.set_span(span); + self.body.set_span(span); + } +} + +impl fmt::Debug for YulCaseBranch { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("YulCaseBranch") + .field("constant", &self.constant) + .field("body", &self.body) + .finish() + } +} + +/// Represents the default case of a Yul switch statement. +#[derive(Clone)] +pub struct YulSwitchDefault { + pub default_token: kw::default, + pub body: YulBlock, +} + +impl Parse for YulSwitchDefault { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + default_token: input.parse()?, + body: input.parse()?, + }) + } +} + +impl Spanned for YulSwitchDefault { + fn span(&self) -> Span { + let span = self.default_token.span(); + span.join(self.body.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.default_token.set_span(span); + self.body.set_span(span); + } +} + +impl fmt::Debug for YulSwitchDefault { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SwitchDefault") + .field("body", &self.body) + .finish() + } +} diff --git a/crates/syn-solidity/src/yul/stmt/var_decl.rs b/crates/syn-solidity/src/yul/stmt/var_decl.rs new file mode 100644 index 000000000..0c8e08469 --- /dev/null +++ b/crates/syn-solidity/src/yul/stmt/var_decl.rs @@ -0,0 +1,84 @@ +use crate::{utils::DebugPunctuated, Spanned, WalrusToken, YulExpr, YulIdent}; + +use proc_macro2::Span; +use std::fmt; +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + Result, Token, +}; + +/// Declares Yul variables, which may or may not have initial values. E.x. +/// `let x := 0` +/// `let x` +/// `let x, y := foo()` +/// `let x, y, z` +/// +/// Multiple variables can only be initialized via a function call. +/// +/// Solidity Reference: +/// +#[derive(Clone)] +pub struct YulVarDecl { + pub let_token: Token![let], + pub vars: Punctuated, + pub init_value: Option<(WalrusToken, YulExpr)>, +} + +impl Parse for YulVarDecl { + fn parse(input: ParseStream<'_>) -> Result { + let let_token = input.parse()?; + let vars = Punctuated::parse_separated_nonempty(input)?; + let init_value = if input.peek(Token![:]) && input.peek2(Token![=]) { + Some((input.parse()?, input.parse()?)) + } else { + None + }; + + if vars.len() > 1 + && init_value + .as_ref() + .map_or(false, |(_, expr)| !matches!(expr, YulExpr::Call(_))) + { + return Err(input.error("Multiple variables can only be initialized by a function call")) + } + + Ok(Self { + let_token, + vars, + init_value, + }) + } +} + +impl Spanned for YulVarDecl { + fn span(&self) -> Span { + let span = self.let_token.span(); + if let Some((_, expr)) = &self.init_value { + span.join(expr.span()).unwrap_or(span) + } else { + span.join(self.vars.span()).unwrap_or(span) + } + } + + fn set_span(&mut self, span: Span) { + self.let_token.set_span(span); + crate::utils::set_spans(&mut self.vars, span); + if let Some((walrus_token, init_value)) = &mut self.init_value { + walrus_token.set_span(span); + init_value.set_span(span); + } + } +} + +impl fmt::Debug for YulVarDecl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("YulVarDecl") + .field("vars", DebugPunctuated::new(&self.vars)) + .field( + "init_value", + &self.init_value.as_ref().map(|(_, expr)| expr), + ) + .finish() + } +} diff --git a/crates/syn-solidity/src/yul/stmt/walrus_token.rs b/crates/syn-solidity/src/yul/stmt/walrus_token.rs new file mode 100644 index 000000000..2a8ed6c51 --- /dev/null +++ b/crates/syn-solidity/src/yul/stmt/walrus_token.rs @@ -0,0 +1,37 @@ +use crate::Spanned; + +use proc_macro2::Span; +use syn::{ + parse::{Parse, ParseStream}, + Result, Token, +}; + +/// Represents the walrus operator `:=`. +#[derive(Clone, Debug)] +pub struct WalrusToken { + pub colon: Token![:], + pub equals: Token![=], +} + +impl Parse for WalrusToken { + fn parse(input: ParseStream<'_>) -> Result { + let colon = input.parse()?; + let equals = input.parse()?; + + Ok(Self { colon, equals }) + } +} + +impl Spanned for WalrusToken { + fn span(&self) -> Span { + self.colon + .span() + .join(self.equals.span()) + .unwrap_or(self.colon.span()) + } + + fn set_span(&mut self, span: Span) { + self.colon.set_span(span); + self.equals.set_span(span); + } +} diff --git a/crates/syn-solidity/src/yul/type/evm_builtin.rs b/crates/syn-solidity/src/yul/type/evm_builtin.rs new file mode 100644 index 000000000..a0a60195b --- /dev/null +++ b/crates/syn-solidity/src/yul/type/evm_builtin.rs @@ -0,0 +1,124 @@ +use crate::{kw, Spanned}; + +use proc_macro2::Span; +use syn::{ + parse::{Parse, ParseStream}, + Result, Token, +}; + +macro_rules! yul_evm_builtin_enum_builder { + ($( $variant:ident($($token:tt)* ) ),* $(,)?) => { + /// Respresentation of an EVM builtin opcode. + /// + /// Solidity Reference: + /// + #[derive(Clone, Debug)] + pub enum YulEVMBuiltIn { + $( $variant($($token)*), )* + } + + // Generate the parser + impl Parse for YulEVMBuiltIn { + fn parse(input: ParseStream<'_>) -> Result { + let lookahead = input.lookahead1(); + $( + if lookahead.peek($($token)*) { + return input.parse().map(Self::$variant); + } + )* + Err(lookahead.error()) + } + } + + impl Spanned for YulEVMBuiltIn { + fn span(&self) -> Span { + match self { + $( Self::$variant(inner) => inner.span(), )* + } + } + + fn set_span(&mut self, span: proc_macro2::Span) { + match self { + $( Self::$variant(inner) => inner.span = span, )* + } + } + } + }; +} + +yul_evm_builtin_enum_builder!( + Stop(kw::stop), + Add(kw::add), + Sub(kw::sub), + Mul(kw::mul), + Div(kw::div), + Sdiv(kw::sdiv), + Mod(Token![mod]), + Smod(kw::smod), + Exp(kw::exp), + Not(kw::not), + Lt(kw::lt), + Gt(kw::gt), + Slt(kw::slt), + Sgt(kw::sgt), + Eq(kw::eq), + Iszero(kw::iszero), + And(kw::and), + Or(kw::or), + Xor(kw::xor), + Byte(kw::byte), + Shl(kw::shl), + Shr(kw::shr), + Sar(kw::sar), + Addmod(kw::addmod), + Mulmod(kw::mulmod), + Signextend(kw::signextend), + Keccak256(kw::keccak256), + Pop(kw::pop), + Mload(kw::mload), + Mstore(kw::mstore), + Mstore8(kw::mstore8), + Sload(kw::sload), + Sstore(kw::sstore), + Msize(kw::msize), + Gas(kw::gas), + Address(kw::address), + Balance(kw::balance), + Selfbalance(kw::selfbalance), + Caller(kw::caller), + Callvalue(kw::callvalue), + Calldataload(kw::calldataload), + Calldatasize(kw::calldatasize), + Calldatacopy(kw::calldatacopy), + Extcodesize(kw::extcodesize), + Extcodecopy(kw::extcodecopy), + Returndatasize(kw::returndatasize), + Returndatacopy(kw::returndatacopy), + Extcodehash(kw::extcodehash), + Create(kw::create), + Create2(kw::create2), + Call(kw::call), + Callcode(kw::callcode), + Delegatecall(kw::delegatecall), + Staticcall(kw::staticcall), + Return(Token![return]), + Revert(kw::revert), + Selfdestruct(kw::selfdestruct), + Invalid(kw::invalid), + Log0(kw::log0), + Log1(kw::log1), + Log2(kw::log2), + Log3(kw::log3), + Log4(kw::log4), + Chainid(kw::chainid), + Origin(kw::origin), + Gasprice(kw::gasprice), + Blockhash(kw::blockhash), + Coinbase(kw::coinbase), + Timestamp(kw::timestamp), + Number(kw::number), + Difficulty(kw::difficulty), + Prevrandao(kw::prevrandao), + Gaslimit(kw::gaslimit), + Basefee(kw::basefee), +); diff --git a/crates/syn-solidity/src/yul/type/function.rs b/crates/syn-solidity/src/yul/type/function.rs new file mode 100644 index 000000000..3afcbc4c8 --- /dev/null +++ b/crates/syn-solidity/src/yul/type/function.rs @@ -0,0 +1,115 @@ +use crate::{kw, utils::DebugPunctuated, Spanned, YulBlock, YulIdent}; + +use proc_macro2::Span; +use std::fmt; +use syn::{ + parenthesized, + parse::{Parse, ParseStream}, + punctuated::Punctuated, + token::Paren, + Result, Token, +}; + +/// Yul function definition: `function f() -> a, b { ... }`. +/// +/// Solitify Reference: +/// +#[derive(Clone)] +pub struct YulFunctionDef { + pub function_token: kw::function, + pub ident: YulIdent, + pub paren_token: Paren, + pub arguments: Punctuated, + pub returns: Option, + pub body: YulBlock, +} + +impl Parse for YulFunctionDef { + fn parse(input: ParseStream<'_>) -> Result { + let content; + Ok(Self { + function_token: input.parse()?, + ident: input.parse()?, + paren_token: parenthesized!(content in input), + arguments: Punctuated::parse_terminated(&content)?, + returns: input.call(YulReturns::parse_opt)?, + body: input.parse()?, + }) + } +} + +impl Spanned for YulFunctionDef { + fn span(&self) -> Span { + let span = self.function_token.span(); + span.join(self.body.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.function_token.set_span(span); + self.ident.set_span(span); + self.paren_token = Paren(span); + self.arguments.set_span(span); + self.returns.set_span(span); + self.body.set_span(span); + } +} + +impl fmt::Debug for YulFunctionDef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("YulFunctionDef") + .field("ident", &self.ident) + .field("arguments", DebugPunctuated::new(&self.arguments)) + .field("returns", &self.returns) + .field("body", &self.body) + .finish() + } +} + +/// The return attribute of a Yul function definition. +#[derive(Clone)] +pub struct YulReturns { + pub arrow_token: Token![->], + pub returns: Punctuated, +} + +impl YulReturns { + pub fn parse_opt(input: ParseStream<'_>) -> Result> { + if input.peek(Token![->]) { + Ok(Some(Self { + arrow_token: input.parse()?, + returns: Punctuated::parse_separated_nonempty(input)?, + })) + } else { + Ok(None) + } + } +} + +impl Parse for YulReturns { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + arrow_token: input.parse()?, + returns: Punctuated::parse_separated_nonempty(input)?, + }) + } +} + +impl Spanned for YulReturns { + fn span(&self) -> Span { + let span = self.arrow_token.span(); + span.join(self.returns.span()).unwrap_or(span) + } + + fn set_span(&mut self, span: Span) { + self.arrow_token.set_span(span); + self.returns.set_span(span); + } +} + +impl fmt::Debug for YulReturns { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("YulReturns") + .field("returns", DebugPunctuated::new(&self.returns)) + .finish() + } +} diff --git a/crates/syn-solidity/src/yul/type/mod.rs b/crates/syn-solidity/src/yul/type/mod.rs new file mode 100644 index 000000000..7270c4f7b --- /dev/null +++ b/crates/syn-solidity/src/yul/type/mod.rs @@ -0,0 +1,5 @@ +mod evm_builtin; +pub use evm_builtin::YulEVMBuiltIn; + +mod function; +pub use function::{YulFunctionDef, YulReturns}; diff --git a/crates/syn-solidity/tests/contracts.rs b/crates/syn-solidity/tests/contracts.rs index 9887ea89a..e613f0d6e 100644 --- a/crates/syn-solidity/tests/contracts.rs +++ b/crates/syn-solidity/tests/contracts.rs @@ -36,6 +36,7 @@ fn contracts() { for file in files { let path = file.path(); let name = path.file_name().unwrap().to_str().unwrap(); + match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| parse_file(&path))) { Ok(Ok(())) => {} Ok(Err(e)) => {