use super::ast::{
BinaryOp, ContractNode, Expression, MethodNode, ParamNode, PrimitiveTypeName, PropertyNode,
SourceLocation, Statement, TypeNode, UnaryOp, Visibility,
};
use super::diagnostic::Diagnostic;
use super::parser::ParseResult;
pub fn parse_solidity(source: &str, file_name: Option<&str>) -> ParseResult {
let file = file_name.unwrap_or("contract.runar.sol");
let mut errors: Vec<Diagnostic> = Vec::new();
let tokens = tokenize(source);
let mut parser = SolParser::new(tokens, file, &mut errors);
let contract = parser.parse_contract();
ParseResult {
contract,
errors,
}
}
#[derive(Debug, Clone, PartialEq)]
enum Token {
Contract,
Is,
Immutable,
Constructor,
Function,
Public,
Private,
If,
Else,
For,
Return,
Require,
Let,
Const,
True,
False,
Ident(String),
NumberLit(i64),
StringLit(String),
Plus,
Minus,
Star,
Slash,
Percent,
EqEq, NotEq, EqEqEq, NotEqEq, Lt,
Le,
Gt,
Ge,
And, Or, BitAnd, BitOr, BitXor, Not, Tilde, Eq, PlusEq, MinusEq, StarEq, SlashEq, PercentEq, PlusPlus, MinusMinus,
LParen,
RParen,
LBrace,
RBrace,
LBracket,
RBracket,
Semicolon,
Comma,
Dot,
Colon,
Question,
Eof,
}
fn tokenize(source: &str) -> Vec<Token> {
let mut tokens = Vec::new();
let chars: Vec<char> = source.chars().collect();
let len = chars.len();
let mut i = 0;
while i < len {
let ch = chars[i];
if ch.is_whitespace() {
i += 1;
continue;
}
if ch == '/' && i + 1 < len && chars[i + 1] == '/' {
while i < len && chars[i] != '\n' {
i += 1;
}
continue;
}
if ch == '/' && i + 1 < len && chars[i + 1] == '*' {
i += 2;
while i + 1 < len && !(chars[i] == '*' && chars[i + 1] == '/') {
i += 1;
}
if i + 1 < len {
i += 2;
}
continue;
}
if ch == '=' && i + 2 < len && chars[i + 1] == '=' && chars[i + 2] == '=' {
tokens.push(Token::EqEqEq);
i += 3;
continue;
}
if ch == '!' && i + 2 < len && chars[i + 1] == '=' && chars[i + 2] == '=' {
tokens.push(Token::NotEqEq);
i += 3;
continue;
}
if ch == '=' && i + 1 < len && chars[i + 1] == '=' {
tokens.push(Token::EqEq);
i += 2;
continue;
}
if ch == '!' && i + 1 < len && chars[i + 1] == '=' {
tokens.push(Token::NotEq);
i += 2;
continue;
}
if ch == '<' && i + 1 < len && chars[i + 1] == '=' {
tokens.push(Token::Le);
i += 2;
continue;
}
if ch == '>' && i + 1 < len && chars[i + 1] == '=' {
tokens.push(Token::Ge);
i += 2;
continue;
}
if ch == '&' && i + 1 < len && chars[i + 1] == '&' {
tokens.push(Token::And);
i += 2;
continue;
}
if ch == '|' && i + 1 < len && chars[i + 1] == '|' {
tokens.push(Token::Or);
i += 2;
continue;
}
if ch == '+' && i + 1 < len && chars[i + 1] == '+' {
tokens.push(Token::PlusPlus);
i += 2;
continue;
}
if ch == '-' && i + 1 < len && chars[i + 1] == '-' {
tokens.push(Token::MinusMinus);
i += 2;
continue;
}
if ch == '+' && i + 1 < len && chars[i + 1] == '=' {
tokens.push(Token::PlusEq);
i += 2;
continue;
}
if ch == '-' && i + 1 < len && chars[i + 1] == '=' {
tokens.push(Token::MinusEq);
i += 2;
continue;
}
if ch == '*' && i + 1 < len && chars[i + 1] == '=' {
tokens.push(Token::StarEq);
i += 2;
continue;
}
if ch == '/' && i + 1 < len && chars[i + 1] == '=' {
tokens.push(Token::SlashEq);
i += 2;
continue;
}
if ch == '%' && i + 1 < len && chars[i + 1] == '=' {
tokens.push(Token::PercentEq);
i += 2;
continue;
}
match ch {
'+' => { tokens.push(Token::Plus); i += 1; continue; }
'-' => { tokens.push(Token::Minus); i += 1; continue; }
'*' => { tokens.push(Token::Star); i += 1; continue; }
'/' => { tokens.push(Token::Slash); i += 1; continue; }
'%' => { tokens.push(Token::Percent); i += 1; continue; }
'<' => { tokens.push(Token::Lt); i += 1; continue; }
'>' => { tokens.push(Token::Gt); i += 1; continue; }
'!' => { tokens.push(Token::Not); i += 1; continue; }
'~' => { tokens.push(Token::Tilde); i += 1; continue; }
'&' => { tokens.push(Token::BitAnd); i += 1; continue; }
'|' => { tokens.push(Token::BitOr); i += 1; continue; }
'^' => { tokens.push(Token::BitXor); i += 1; continue; }
'=' => { tokens.push(Token::Eq); i += 1; continue; }
'(' => { tokens.push(Token::LParen); i += 1; continue; }
')' => { tokens.push(Token::RParen); i += 1; continue; }
'{' => { tokens.push(Token::LBrace); i += 1; continue; }
'}' => { tokens.push(Token::RBrace); i += 1; continue; }
'[' => { tokens.push(Token::LBracket); i += 1; continue; }
']' => { tokens.push(Token::RBracket); i += 1; continue; }
';' => { tokens.push(Token::Semicolon); i += 1; continue; }
',' => { tokens.push(Token::Comma); i += 1; continue; }
'.' => { tokens.push(Token::Dot); i += 1; continue; }
':' => { tokens.push(Token::Colon); i += 1; continue; }
'?' => { tokens.push(Token::Question); i += 1; continue; }
_ => {}
}
if ch == '\'' || ch == '"' {
let quote = ch;
i += 1;
let start = i;
while i < len && chars[i] != quote {
if chars[i] == '\\' {
i += 1; }
i += 1;
}
let val: String = chars[start..i].iter().collect();
tokens.push(Token::StringLit(val));
if i < len {
i += 1;
}
continue;
}
if ch.is_ascii_digit() {
let start = i;
while i < len && (chars[i].is_ascii_digit() || chars[i] == 'n') {
i += 1;
}
let num_str: String = chars[start..i].iter().collect();
let num_str = num_str.trim_end_matches('n');
let val = num_str.parse::<i64>().unwrap_or(0);
tokens.push(Token::NumberLit(val));
continue;
}
if ch.is_ascii_alphabetic() || ch == '_' {
let start = i;
while i < len && (chars[i].is_ascii_alphanumeric() || chars[i] == '_') {
i += 1;
}
let word: String = chars[start..i].iter().collect();
let tok = match word.as_str() {
"contract" => Token::Contract,
"is" => Token::Is,
"immutable" => Token::Immutable,
"constructor" => Token::Constructor,
"function" => Token::Function,
"public" => Token::Public,
"private" => Token::Private,
"if" => Token::If,
"else" => Token::Else,
"for" => Token::For,
"return" => Token::Return,
"require" => Token::Require,
"let" => Token::Let,
"const" => Token::Const,
"true" => Token::True,
"false" => Token::False,
_ => Token::Ident(word),
};
tokens.push(tok);
continue;
}
i += 1;
}
tokens.push(Token::Eof);
tokens
}
struct SolParser<'a> {
tokens: Vec<Token>,
pos: usize,
file: &'a str,
errors: &'a mut Vec<Diagnostic>,
}
impl<'a> SolParser<'a> {
fn new(tokens: Vec<Token>, file: &'a str, errors: &'a mut Vec<Diagnostic>) -> Self {
Self {
tokens,
pos: 0,
file,
errors,
}
}
fn peek(&self) -> &Token {
self.tokens.get(self.pos).unwrap_or(&Token::Eof)
}
fn advance(&mut self) -> Token {
let t = self.tokens.get(self.pos).cloned().unwrap_or(Token::Eof);
self.pos += 1;
t
}
fn expect(&mut self, expected: &Token) -> bool {
if self.peek() == expected {
self.advance();
true
} else {
self.errors.push(Diagnostic::error(format!(
"Expected {:?}, got {:?}",
expected,
self.peek()
), None));
false
}
}
fn expect_ident(&mut self) -> String {
match self.advance() {
Token::Ident(name) => name,
other => {
self.errors
.push(Diagnostic::error(format!("Expected identifier, got {:?}", other), None));
"_error".to_string()
}
}
}
fn loc(&self) -> SourceLocation {
SourceLocation {
file: self.file.to_string(),
line: 1,
column: 0,
}
}
fn parse_contract(&mut self) -> Option<ContractNode> {
while *self.peek() != Token::Contract && *self.peek() != Token::Eof {
self.advance();
}
if *self.peek() == Token::Eof {
self.errors
.push(Diagnostic::error("No 'contract' declaration found", None));
return None;
}
self.expect(&Token::Contract);
let name = self.expect_ident();
let parent_class = if *self.peek() == Token::Is {
self.advance();
self.expect_ident()
} else {
"SmartContract".to_string()
};
self.expect(&Token::LBrace);
let mut properties = Vec::new();
let mut constructor: Option<MethodNode> = None;
let mut methods = Vec::new();
while *self.peek() != Token::RBrace && *self.peek() != Token::Eof {
match self.peek().clone() {
Token::Constructor => {
constructor = Some(self.parse_constructor());
}
Token::Function => {
methods.push(self.parse_function());
}
Token::Immutable => {
properties.push(self.parse_property(true));
}
Token::Ident(_) => {
properties.push(self.parse_property(false));
}
_ => {
self.errors.push(Diagnostic::error(format!(
"Unexpected token in contract body: {:?}",
self.peek()
), None));
self.advance();
}
}
}
self.expect(&Token::RBrace);
let constructor = constructor.unwrap_or_else(|| {
self.errors
.push(Diagnostic::error("Contract must have a constructor", None));
MethodNode {
name: "constructor".to_string(),
params: Vec::new(),
body: Vec::new(),
visibility: Visibility::Public,
source_location: self.loc(),
}
});
Some(ContractNode {
name,
parent_class,
properties,
constructor,
methods,
source_file: self.file.to_string(),
})
}
fn parse_property(&mut self, is_immutable: bool) -> PropertyNode {
if is_immutable {
self.advance(); }
let type_node = self.parse_type();
let name = self.expect_ident();
let initializer = if *self.peek() == Token::Eq {
self.advance(); Some(self.parse_expression())
} else {
None
};
self.expect(&Token::Semicolon);
PropertyNode {
name,
prop_type: type_node,
readonly: is_immutable,
initializer,
source_location: self.loc(),
}
}
fn parse_type(&mut self) -> TypeNode {
let name = self.expect_ident();
if name == "FixedArray" {
if *self.peek() == Token::Lt {
self.advance(); let element = self.parse_type();
self.expect(&Token::Comma);
let length = match self.advance() {
Token::NumberLit(n) => n as usize,
_ => {
self.errors
.push(Diagnostic::error("FixedArray requires numeric length", None));
0
}
};
self.expect(&Token::Gt);
return TypeNode::FixedArray {
element: Box::new(element),
length,
};
}
}
let mapped = match name.as_str() {
"uint256" | "int256" | "uint" | "int" => "bigint",
"bool" => "boolean",
"bytes" => "ByteString",
"address" => "Addr",
_ => &name,
};
if let Some(prim) = PrimitiveTypeName::from_str(mapped) {
TypeNode::Primitive(prim)
} else {
TypeNode::Custom(mapped.to_string())
}
}
fn parse_constructor(&mut self) -> MethodNode {
self.advance(); self.expect(&Token::LParen);
let params = self.parse_param_list();
self.expect(&Token::RParen);
if *self.peek() == Token::Public || *self.peek() == Token::Private {
self.advance();
}
let body = self.parse_block();
MethodNode {
name: "constructor".to_string(),
params,
body,
visibility: Visibility::Public,
source_location: self.loc(),
}
}
fn parse_function(&mut self) -> MethodNode {
self.advance(); let name = self.expect_ident();
self.expect(&Token::LParen);
let params = self.parse_param_list();
self.expect(&Token::RParen);
let visibility = match self.peek() {
Token::Public => {
self.advance();
Visibility::Public
}
Token::Private => {
self.advance();
Visibility::Private
}
_ => Visibility::Public,
};
let body = self.parse_block();
MethodNode {
name,
params,
body,
visibility,
source_location: self.loc(),
}
}
fn parse_param_list(&mut self) -> Vec<ParamNode> {
let mut params = Vec::new();
if *self.peek() == Token::RParen {
return params;
}
params.push(self.parse_param());
while *self.peek() == Token::Comma {
self.advance();
params.push(self.parse_param());
}
params
}
fn parse_param(&mut self) -> ParamNode {
let param_type = self.parse_type();
let name = self.expect_ident();
ParamNode { name, param_type }
}
fn parse_block(&mut self) -> Vec<Statement> {
self.expect(&Token::LBrace);
let mut stmts = Vec::new();
while *self.peek() != Token::RBrace && *self.peek() != Token::Eof {
if let Some(stmt) = self.parse_statement() {
stmts.push(stmt);
}
}
self.expect(&Token::RBrace);
stmts
}
fn parse_statement(&mut self) -> Option<Statement> {
match self.peek().clone() {
Token::Let | Token::Const => Some(self.parse_var_decl()),
Token::If => Some(self.parse_if()),
Token::For => Some(self.parse_for()),
Token::Return => Some(self.parse_return()),
Token::Require => Some(self.parse_require()),
_ => {
let expr = self.parse_expression();
match self.peek() {
Token::Eq => {
self.advance();
let value = self.parse_expression();
self.expect(&Token::Semicolon);
Some(Statement::Assignment {
target: expr,
value,
source_location: self.loc(),
})
}
Token::PlusEq => {
self.advance();
let rhs = self.parse_expression();
let value = Expression::BinaryExpr {
op: BinaryOp::Add,
left: Box::new(expr.clone()),
right: Box::new(rhs),
};
self.expect(&Token::Semicolon);
Some(Statement::Assignment {
target: expr,
value,
source_location: self.loc(),
})
}
Token::MinusEq => {
self.advance();
let rhs = self.parse_expression();
let value = Expression::BinaryExpr {
op: BinaryOp::Sub,
left: Box::new(expr.clone()),
right: Box::new(rhs),
};
self.expect(&Token::Semicolon);
Some(Statement::Assignment {
target: expr,
value,
source_location: self.loc(),
})
}
Token::StarEq => {
self.advance();
let rhs = self.parse_expression();
let value = Expression::BinaryExpr {
op: BinaryOp::Mul,
left: Box::new(expr.clone()),
right: Box::new(rhs),
};
self.expect(&Token::Semicolon);
Some(Statement::Assignment {
target: expr,
value,
source_location: self.loc(),
})
}
Token::SlashEq => {
self.advance();
let rhs = self.parse_expression();
let value = Expression::BinaryExpr {
op: BinaryOp::Div,
left: Box::new(expr.clone()),
right: Box::new(rhs),
};
self.expect(&Token::Semicolon);
Some(Statement::Assignment {
target: expr,
value,
source_location: self.loc(),
})
}
Token::PercentEq => {
self.advance();
let rhs = self.parse_expression();
let value = Expression::BinaryExpr {
op: BinaryOp::Mod,
left: Box::new(expr.clone()),
right: Box::new(rhs),
};
self.expect(&Token::Semicolon);
Some(Statement::Assignment {
target: expr,
value,
source_location: self.loc(),
})
}
_ => {
self.expect(&Token::Semicolon);
Some(Statement::ExpressionStatement {
expression: expr,
source_location: self.loc(),
})
}
}
}
}
}
fn parse_var_decl(&mut self) -> Statement {
let is_const = *self.peek() == Token::Const;
self.advance();
let (name, var_type) = if self.is_type_then_name() {
let type_node = self.parse_type();
let name = self.expect_ident();
(name, Some(type_node))
} else {
let name = self.expect_ident();
let var_type = if *self.peek() == Token::Colon {
self.advance();
Some(self.parse_type())
} else {
None
};
(name, var_type)
};
self.expect(&Token::Eq);
let init = self.parse_expression();
self.expect(&Token::Semicolon);
Statement::VariableDecl {
name,
var_type,
mutable: !is_const,
init,
source_location: self.loc(),
}
}
fn is_type_then_name(&self) -> bool {
matches!(
(self.tokens.get(self.pos), self.tokens.get(self.pos + 1)),
(Some(Token::Ident(_)), Some(Token::Ident(_)))
)
}
fn parse_require(&mut self) -> Statement {
self.advance(); self.expect(&Token::LParen);
let expr = self.parse_expression();
self.expect(&Token::RParen);
self.expect(&Token::Semicolon);
Statement::ExpressionStatement {
expression: Expression::CallExpr {
callee: Box::new(Expression::Identifier {
name: "assert".to_string(),
}),
args: vec![expr],
},
source_location: self.loc(),
}
}
fn parse_if(&mut self) -> Statement {
self.advance(); self.expect(&Token::LParen);
let condition = self.parse_expression();
self.expect(&Token::RParen);
let then_branch = self.parse_block();
let else_branch = if *self.peek() == Token::Else {
self.advance();
if *self.peek() == Token::If {
let nested = self.parse_if();
Some(vec![nested])
} else {
Some(self.parse_block())
}
} else {
None
};
Statement::IfStatement {
condition,
then_branch,
else_branch,
source_location: self.loc(),
}
}
fn parse_for(&mut self) -> Statement {
self.advance(); self.expect(&Token::LParen);
let init = if *self.peek() == Token::Let || *self.peek() == Token::Const {
self.parse_var_decl()
} else {
self.errors
.push(Diagnostic::error("For loop init must be a variable declaration", None));
self.expect(&Token::Semicolon);
Statement::VariableDecl {
name: "_i".to_string(),
var_type: None,
mutable: true,
init: Expression::BigIntLiteral { value: 0 },
source_location: self.loc(),
}
};
let condition = self.parse_expression();
self.expect(&Token::Semicolon);
let update = self.parse_for_update();
self.expect(&Token::RParen);
let body = self.parse_block();
Statement::ForStatement {
init: Box::new(init),
condition,
update: Box::new(update),
body,
source_location: self.loc(),
}
}
fn parse_for_update(&mut self) -> Statement {
let expr = self.parse_expression();
match self.peek() {
Token::Eq => {
self.advance();
let value = self.parse_expression();
Statement::Assignment {
target: expr,
value,
source_location: self.loc(),
}
}
Token::PlusEq => {
self.advance();
let rhs = self.parse_expression();
Statement::Assignment {
target: expr.clone(),
value: Expression::BinaryExpr {
op: BinaryOp::Add,
left: Box::new(expr),
right: Box::new(rhs),
},
source_location: self.loc(),
}
}
Token::MinusEq => {
self.advance();
let rhs = self.parse_expression();
Statement::Assignment {
target: expr.clone(),
value: Expression::BinaryExpr {
op: BinaryOp::Sub,
left: Box::new(expr),
right: Box::new(rhs),
},
source_location: self.loc(),
}
}
_ => Statement::ExpressionStatement {
expression: expr,
source_location: self.loc(),
},
}
}
fn parse_return(&mut self) -> Statement {
self.advance(); if *self.peek() == Token::Semicolon {
self.advance();
return Statement::ReturnStatement {
value: None,
source_location: self.loc(),
};
}
let value = self.parse_expression();
self.expect(&Token::Semicolon);
Statement::ReturnStatement {
value: Some(value),
source_location: self.loc(),
}
}
fn parse_expression(&mut self) -> Expression {
self.parse_ternary()
}
fn parse_ternary(&mut self) -> Expression {
let cond = self.parse_or();
if *self.peek() == Token::Question {
self.advance();
let cons = self.parse_ternary();
self.expect(&Token::Colon);
let alt = self.parse_ternary();
Expression::TernaryExpr {
condition: Box::new(cond),
consequent: Box::new(cons),
alternate: Box::new(alt),
}
} else {
cond
}
}
fn parse_or(&mut self) -> Expression {
let mut left = self.parse_and();
while *self.peek() == Token::Or {
self.advance();
let right = self.parse_and();
left = Expression::BinaryExpr {
op: BinaryOp::Or,
left: Box::new(left),
right: Box::new(right),
};
}
left
}
fn parse_and(&mut self) -> Expression {
let mut left = self.parse_bit_or();
while *self.peek() == Token::And {
self.advance();
let right = self.parse_bit_or();
left = Expression::BinaryExpr {
op: BinaryOp::And,
left: Box::new(left),
right: Box::new(right),
};
}
left
}
fn parse_bit_or(&mut self) -> Expression {
let mut left = self.parse_bit_xor();
while *self.peek() == Token::BitOr {
self.advance();
let right = self.parse_bit_xor();
left = Expression::BinaryExpr {
op: BinaryOp::BitOr,
left: Box::new(left),
right: Box::new(right),
};
}
left
}
fn parse_bit_xor(&mut self) -> Expression {
let mut left = self.parse_bit_and();
while *self.peek() == Token::BitXor {
self.advance();
let right = self.parse_bit_and();
left = Expression::BinaryExpr {
op: BinaryOp::BitXor,
left: Box::new(left),
right: Box::new(right),
};
}
left
}
fn parse_bit_and(&mut self) -> Expression {
let mut left = self.parse_equality();
while *self.peek() == Token::BitAnd {
self.advance();
let right = self.parse_equality();
left = Expression::BinaryExpr {
op: BinaryOp::BitAnd,
left: Box::new(left),
right: Box::new(right),
};
}
left
}
fn parse_equality(&mut self) -> Expression {
let mut left = self.parse_comparison();
loop {
match self.peek() {
Token::EqEq | Token::EqEqEq => {
self.advance();
let right = self.parse_comparison();
left = Expression::BinaryExpr {
op: BinaryOp::StrictEq,
left: Box::new(left),
right: Box::new(right),
};
}
Token::NotEq | Token::NotEqEq => {
self.advance();
let right = self.parse_comparison();
left = Expression::BinaryExpr {
op: BinaryOp::StrictNe,
left: Box::new(left),
right: Box::new(right),
};
}
_ => break,
}
}
left
}
fn parse_comparison(&mut self) -> Expression {
let mut left = self.parse_additive();
loop {
match self.peek() {
Token::Lt => {
self.advance();
let right = self.parse_additive();
left = Expression::BinaryExpr {
op: BinaryOp::Lt,
left: Box::new(left),
right: Box::new(right),
};
}
Token::Le => {
self.advance();
let right = self.parse_additive();
left = Expression::BinaryExpr {
op: BinaryOp::Le,
left: Box::new(left),
right: Box::new(right),
};
}
Token::Gt => {
self.advance();
let right = self.parse_additive();
left = Expression::BinaryExpr {
op: BinaryOp::Gt,
left: Box::new(left),
right: Box::new(right),
};
}
Token::Ge => {
self.advance();
let right = self.parse_additive();
left = Expression::BinaryExpr {
op: BinaryOp::Ge,
left: Box::new(left),
right: Box::new(right),
};
}
_ => break,
}
}
left
}
fn parse_additive(&mut self) -> Expression {
let mut left = self.parse_multiplicative();
loop {
match self.peek() {
Token::Plus => {
self.advance();
let right = self.parse_multiplicative();
left = Expression::BinaryExpr {
op: BinaryOp::Add,
left: Box::new(left),
right: Box::new(right),
};
}
Token::Minus => {
self.advance();
let right = self.parse_multiplicative();
left = Expression::BinaryExpr {
op: BinaryOp::Sub,
left: Box::new(left),
right: Box::new(right),
};
}
_ => break,
}
}
left
}
fn parse_multiplicative(&mut self) -> Expression {
let mut left = self.parse_unary();
loop {
match self.peek() {
Token::Star => {
self.advance();
let right = self.parse_unary();
left = Expression::BinaryExpr {
op: BinaryOp::Mul,
left: Box::new(left),
right: Box::new(right),
};
}
Token::Slash => {
self.advance();
let right = self.parse_unary();
left = Expression::BinaryExpr {
op: BinaryOp::Div,
left: Box::new(left),
right: Box::new(right),
};
}
Token::Percent => {
self.advance();
let right = self.parse_unary();
left = Expression::BinaryExpr {
op: BinaryOp::Mod,
left: Box::new(left),
right: Box::new(right),
};
}
_ => break,
}
}
left
}
fn parse_unary(&mut self) -> Expression {
match self.peek() {
Token::Not => {
self.advance();
let operand = self.parse_unary();
Expression::UnaryExpr {
op: UnaryOp::Not,
operand: Box::new(operand),
}
}
Token::Minus => {
self.advance();
let operand = self.parse_unary();
Expression::UnaryExpr {
op: UnaryOp::Neg,
operand: Box::new(operand),
}
}
Token::Tilde => {
self.advance();
let operand = self.parse_unary();
Expression::UnaryExpr {
op: UnaryOp::BitNot,
operand: Box::new(operand),
}
}
Token::PlusPlus => {
self.advance();
let operand = self.parse_postfix();
Expression::IncrementExpr {
operand: Box::new(operand),
prefix: true,
}
}
Token::MinusMinus => {
self.advance();
let operand = self.parse_postfix();
Expression::DecrementExpr {
operand: Box::new(operand),
prefix: true,
}
}
_ => self.parse_postfix(),
}
}
fn parse_postfix(&mut self) -> Expression {
let mut expr = self.parse_primary();
loop {
match self.peek().clone() {
Token::Dot => {
self.advance();
let prop = self.expect_ident();
if matches!(&expr, Expression::Identifier { name } if name == "this") {
expr = Expression::PropertyAccess { property: prop };
} else {
expr = Expression::MemberExpr {
object: Box::new(expr),
property: prop,
};
}
}
Token::LParen => {
self.advance();
let mut args = Vec::new();
if *self.peek() != Token::RParen {
args.push(self.parse_expression());
while *self.peek() == Token::Comma {
self.advance();
args.push(self.parse_expression());
}
}
self.expect(&Token::RParen);
expr = Expression::CallExpr {
callee: Box::new(expr),
args,
};
}
Token::LBracket => {
self.advance();
let index = self.parse_expression();
self.expect(&Token::RBracket);
expr = Expression::IndexAccess {
object: Box::new(expr),
index: Box::new(index),
};
}
Token::PlusPlus => {
self.advance();
expr = Expression::IncrementExpr {
operand: Box::new(expr),
prefix: false,
};
}
Token::MinusMinus => {
self.advance();
expr = Expression::DecrementExpr {
operand: Box::new(expr),
prefix: false,
};
}
_ => break,
}
}
expr
}
fn parse_primary(&mut self) -> Expression {
match self.advance() {
Token::NumberLit(v) => Expression::BigIntLiteral { value: v },
Token::True => Expression::BoolLiteral { value: true },
Token::False => Expression::BoolLiteral { value: false },
Token::StringLit(v) => Expression::ByteStringLiteral { value: v },
Token::Ident(name) => {
if name == "this" {
Expression::Identifier {
name: "this".to_string(),
}
} else if name == "super" {
Expression::Identifier {
name: "super".to_string(),
}
} else {
Expression::Identifier { name }
}
}
Token::Require => {
self.expect(&Token::LParen);
let arg = self.parse_expression();
self.expect(&Token::RParen);
Expression::CallExpr {
callee: Box::new(Expression::Identifier {
name: "assert".to_string(),
}),
args: vec![arg],
}
}
Token::LParen => {
let expr = self.parse_expression();
self.expect(&Token::RParen);
expr
}
other => {
self.errors
.push(Diagnostic::error(format!("Unexpected token in expression: {:?}", other), None));
Expression::BigIntLiteral { value: 0 }
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_simple_solidity_contract() {
let source = r#"
contract P2PKH is SmartContract {
immutable Addr pubKeyHash;
constructor(Addr pubKeyHash) {
super(pubKeyHash);
this.pubKeyHash = pubKeyHash;
}
function unlock(Sig sig, PubKey pubKey) public {
require(hash160(pubKey) == this.pubKeyHash);
require(checkSig(sig, pubKey));
}
}
"#;
let result = parse_solidity(source, Some("P2PKH.runar.sol"));
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let contract = result.contract.unwrap();
assert_eq!(contract.name, "P2PKH");
assert_eq!(contract.parent_class, "SmartContract");
assert_eq!(contract.properties.len(), 1);
assert_eq!(contract.properties[0].name, "pubKeyHash");
assert!(contract.properties[0].readonly);
assert_eq!(contract.methods.len(), 1);
assert_eq!(contract.methods[0].name, "unlock");
assert_eq!(contract.methods[0].visibility, Visibility::Public);
assert_eq!(contract.methods[0].params.len(), 2);
}
#[test]
fn test_parse_stateful_solidity_contract() {
let source = r#"
contract Counter is StatefulSmartContract {
bigint count;
constructor(bigint count) {
super(count);
this.count = count;
}
function increment() public {
this.count++;
}
function decrement() public {
require(this.count > 0);
this.count--;
}
}
"#;
let result = parse_solidity(source, Some("Counter.runar.sol"));
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let contract = result.contract.unwrap();
assert_eq!(contract.name, "Counter");
assert_eq!(contract.parent_class, "StatefulSmartContract");
assert_eq!(contract.properties.len(), 1);
assert!(!contract.properties[0].readonly);
assert_eq!(contract.methods.len(), 2);
}
#[test]
fn test_eq_maps_to_strict_eq() {
let source = r#"
contract Test is SmartContract {
immutable bigint x;
constructor(bigint x) {
super(x);
this.x = x;
}
function check(bigint y) public {
require(this.x == y);
require(this.x != y);
}
}
"#;
let result = parse_solidity(source, Some("Test.runar.sol"));
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let contract = result.contract.unwrap();
let body = &contract.methods[0].body;
assert_eq!(body.len(), 2);
if let Statement::ExpressionStatement { expression, .. } = &body[0] {
if let Expression::CallExpr { args, .. } = expression {
if let Expression::BinaryExpr { op, .. } = &args[0] {
assert_eq!(*op, BinaryOp::StrictEq);
} else {
panic!("Expected BinaryExpr inside assert");
}
} else {
panic!("Expected CallExpr for assert");
}
}
}
#[test]
fn test_for_loop() {
let source = r#"
contract Loop is SmartContract {
immutable bigint n;
constructor(bigint n) {
super(n);
this.n = n;
}
function run() public {
let bigint sum = 0;
for (let bigint i = 0; i < this.n; i++) {
sum += i;
}
require(sum > 0);
}
}
"#;
let result = parse_solidity(source, Some("Loop.runar.sol"));
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let contract = result.contract.unwrap();
assert_eq!(contract.methods[0].body.len(), 3); }
#[test]
fn test_methods_and_params() {
let source = r#"
contract Adder is SmartContract {
immutable bigint target;
constructor(bigint target) {
super(target);
this.target = target;
}
function verify(bigint a, bigint b) public {
require(a + b == this.target);
}
}
"#;
let result = parse_solidity(source, Some("Adder.runar.sol"));
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let contract = result.contract.unwrap();
assert_eq!(contract.methods.len(), 1, "expected 1 method");
let method = &contract.methods[0];
assert_eq!(method.name, "verify");
assert_eq!(
method.visibility,
Visibility::Public,
"expected public visibility"
);
assert_eq!(method.params.len(), 2, "expected 2 params");
assert_eq!(method.params[0].name, "a");
assert_eq!(method.params[1].name, "b");
}
#[test]
fn test_invalid_syntax_error() {
let source = r#"
contract {
// missing name and parent
}
"#;
let result = parse_solidity(source, Some("bad.runar.sol"));
let is_bad = !result.errors.is_empty() || result.contract.is_none();
assert!(
is_bad,
"expected errors or no contract for invalid Solidity syntax"
);
}
#[test]
fn test_multiple_properties() {
let source = r#"
contract TwoProps is SmartContract {
immutable Addr addr;
immutable PubKey key;
constructor(Addr addr, PubKey key) {
super(addr, key);
this.addr = addr;
this.key = key;
}
function check(bigint x) public {
require(x == 1);
}
}
"#;
let result = parse_solidity(source, Some("TwoProps.runar.sol"));
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let contract = result.contract.unwrap();
assert_eq!(
contract.properties.len(),
2,
"expected 2 properties, got {}",
contract.properties.len()
);
assert_eq!(contract.properties[0].name, "addr");
assert_eq!(contract.properties[1].name, "key");
}
}