use super::ast::{
BinaryOp, ContractNode, Expression, MethodNode, ParamNode, PrimitiveTypeName,
PropertyNode, SourceLocation, Statement, TypeNode, UnaryOp, Visibility,
};
use super::diagnostic::Diagnostic;
use super::parser::ParseResult;
#[derive(Debug, Clone, PartialEq)]
enum TokenType {
Use, Struct, Impl, Fn, Pub, Let, Mut, If, Else, For, Return, In,
True, False, Self_,
AssertMacro, AssertEqMacro,
Ident(String), Number(String), HexString(String),
HashBracket, LParen, RParen, LBrace, RBrace, LBracket, RBracket,
Semi, Comma, Dot, Colon, ColonColon, Arrow,
Plus, Minus, Star, Slash, Percent,
EqEq, BangEq, Lt, LtEq, Gt, GtEq,
AmpAmp, PipePipe,
Amp, Pipe, Caret, Tilde, Bang,
Eq, PlusEq, MinusEq,
Eof,
}
#[derive(Debug, Clone)]
struct Token {
typ: TokenType,
line: usize,
col: usize,
}
fn tokenize(source: &str) -> Vec<Token> {
let chars: Vec<char> = source.chars().collect();
let mut tokens = Vec::new();
let mut pos = 0;
let mut line = 1usize;
let mut col = 1usize;
while pos < chars.len() {
let ch = chars[pos];
let l = line;
let c = col;
if ch.is_whitespace() {
if ch == '\n' { line += 1; col = 1; } else { col += 1; }
pos += 1;
continue;
}
if ch == '/' && pos + 1 < chars.len() && chars[pos + 1] == '/' {
while pos < chars.len() && chars[pos] != '\n' { pos += 1; }
continue;
}
if ch == '/' && pos + 1 < chars.len() && chars[pos + 1] == '*' {
pos += 2; col += 2;
while pos + 1 < chars.len() {
if chars[pos] == '\n' { line += 1; col = 1; }
if chars[pos] == '*' && chars[pos + 1] == '/' { pos += 2; col += 2; break; }
pos += 1; col += 1;
}
continue;
}
if ch == '#' && pos + 1 < chars.len() && chars[pos + 1] == '[' {
tokens.push(Token { typ: TokenType::HashBracket, line: l, col: c });
pos += 2; col += 2;
continue;
}
if pos + 1 < chars.len() {
let two = format!("{}{}", ch, chars[pos + 1]);
let tok = match two.as_str() {
"::" => Some(TokenType::ColonColon),
"->" => Some(TokenType::Arrow),
"==" => Some(TokenType::EqEq),
"!=" => Some(TokenType::BangEq),
"<=" => Some(TokenType::LtEq),
">=" => Some(TokenType::GtEq),
"&&" => Some(TokenType::AmpAmp),
"||" => Some(TokenType::PipePipe),
"+=" => Some(TokenType::PlusEq),
"-=" => Some(TokenType::MinusEq),
_ => None,
};
if let Some(t) = tok {
tokens.push(Token { typ: t, line: l, col: c });
pos += 2; col += 2;
continue;
}
}
let single = match ch {
'(' => Some(TokenType::LParen),
')' => Some(TokenType::RParen),
'{' => Some(TokenType::LBrace),
'}' => Some(TokenType::RBrace),
'[' => Some(TokenType::LBracket),
']' => Some(TokenType::RBracket),
';' => Some(TokenType::Semi),
',' => Some(TokenType::Comma),
'.' => Some(TokenType::Dot),
':' => Some(TokenType::Colon),
'+' => Some(TokenType::Plus),
'-' => Some(TokenType::Minus),
'*' => Some(TokenType::Star),
'/' => Some(TokenType::Slash),
'%' => Some(TokenType::Percent),
'<' => Some(TokenType::Lt),
'>' => Some(TokenType::Gt),
'&' => Some(TokenType::Amp),
'|' => Some(TokenType::Pipe),
'^' => Some(TokenType::Caret),
'~' => Some(TokenType::Tilde),
'!' => Some(TokenType::Bang),
'=' => Some(TokenType::Eq),
_ => None,
};
if let Some(t) = single {
tokens.push(Token { typ: t, line: l, col: c });
pos += 1; col += 1;
continue;
}
if ch == '0' && pos + 1 < chars.len() && chars[pos + 1] == 'x' {
let mut val = String::new();
pos += 2; col += 2;
while pos < chars.len() && chars[pos].is_ascii_hexdigit() {
val.push(chars[pos]);
pos += 1; col += 1;
}
tokens.push(Token { typ: TokenType::HexString(val), line: l, col: c });
continue;
}
if ch.is_ascii_digit() {
let mut val = String::new();
while pos < chars.len() && (chars[pos].is_ascii_digit() || chars[pos] == '_') {
if chars[pos] != '_' { val.push(chars[pos]); }
pos += 1; col += 1;
}
tokens.push(Token { typ: TokenType::Number(val), line: l, col: c });
continue;
}
if ch.is_alphabetic() || ch == '_' {
let mut val = String::new();
while pos < chars.len() && (chars[pos].is_alphanumeric() || chars[pos] == '_') {
val.push(chars[pos]);
pos += 1; col += 1;
}
if (val == "assert" || val == "assert_eq") && pos < chars.len() && chars[pos] == '!' {
pos += 1; col += 1;
let tok = if val == "assert" { TokenType::AssertMacro } else { TokenType::AssertEqMacro };
tokens.push(Token { typ: tok, line: l, col: c });
continue;
}
let tok = match val.as_str() {
"use" => TokenType::Use,
"struct" => TokenType::Struct,
"impl" => TokenType::Impl,
"fn" => TokenType::Fn,
"pub" => TokenType::Pub,
"let" => TokenType::Let,
"mut" => TokenType::Mut,
"if" => TokenType::If,
"else" => TokenType::Else,
"for" => TokenType::For,
"return" => TokenType::Return,
"in" => TokenType::In,
"true" => TokenType::True,
"false" => TokenType::False,
"self" => TokenType::Self_,
_ => TokenType::Ident(val),
};
tokens.push(Token { typ: tok, line: l, col: c });
continue;
}
if ch == '"' {
let mut val = String::new();
pos += 1; col += 1;
while pos < chars.len() && chars[pos] != '"' {
val.push(chars[pos]);
pos += 1; col += 1;
}
if pos < chars.len() { pos += 1; col += 1; } tokens.push(Token { typ: TokenType::HexString(val), line: l, col: c });
continue;
}
pos += 1; col += 1;
}
tokens.push(Token { typ: TokenType::Eof, line, col });
tokens
}
struct RustDslParser {
tokens: Vec<Token>,
pos: usize,
file: String,
errors: Vec<Diagnostic>,
}
impl RustDslParser {
fn new(tokens: Vec<Token>, file: String) -> Self {
Self { tokens, pos: 0, file, errors: Vec::new() }
}
fn current(&self) -> &Token {
self.tokens.get(self.pos).unwrap_or(self.tokens.last().unwrap())
}
fn advance_clone(&mut self) -> Token {
let t = self.current().clone();
if self.pos < self.tokens.len() - 1 { self.pos += 1; }
t
}
fn expect(&mut self, expected: &TokenType) {
if std::mem::discriminant(&self.current().typ) != std::mem::discriminant(expected) {
self.errors.push(Diagnostic::error(format!("Expected {:?}, got {:?} at {}:{}", expected, self.current().typ, self.current().line, self.current().col), None));
}
self.advance_clone();
}
fn match_tok(&mut self, expected: &TokenType) -> bool {
if std::mem::discriminant(&self.current().typ) == std::mem::discriminant(expected) {
self.advance_clone();
true
} else {
false
}
}
fn loc(&self) -> SourceLocation {
SourceLocation { file: self.file.clone(), line: self.current().line, column: self.current().col }
}
fn parse(mut self) -> ParseResult {
while matches!(self.current().typ, TokenType::Use) {
while !matches!(self.current().typ, TokenType::Semi | TokenType::Eof) { self.advance_clone(); }
if matches!(self.current().typ, TokenType::Semi) { self.advance_clone(); }
}
let mut properties: Vec<PropertyNode> = Vec::new();
let mut contract_name = String::new();
let mut parent_class = "SmartContract".to_string();
let mut methods: Vec<MethodNode> = Vec::new();
while !matches!(self.current().typ, TokenType::Eof) {
if matches!(self.current().typ, TokenType::HashBracket) {
let attr = self.parse_attribute();
if attr == "runar::contract" || attr == "runar::stateful_contract" {
if attr == "runar::stateful_contract" {
parent_class = "StatefulSmartContract".to_string();
}
if matches!(self.current().typ, TokenType::Pub) { self.advance_clone(); }
self.expect(&TokenType::Struct);
if let TokenType::Ident(name) = self.current().typ.clone() {
contract_name = name;
self.advance_clone();
}
self.expect(&TokenType::LBrace);
while !matches!(self.current().typ, TokenType::RBrace | TokenType::Eof) {
let mut readonly = false;
if matches!(self.current().typ, TokenType::HashBracket) {
let field_attr = self.parse_attribute();
if field_attr == "readonly" { readonly = true; }
}
let loc = self.loc();
if let TokenType::Ident(field_name) = self.current().typ.clone() {
self.advance_clone();
self.expect(&TokenType::Colon);
let field_type = self.parse_rust_type();
self.match_tok(&TokenType::Comma);
if !readonly {
}
let camel_name = snake_to_camel(&field_name);
if camel_name != "txPreimage" {
properties.push(PropertyNode {
name: camel_name,
prop_type: field_type,
readonly,
initializer: None,
source_location: loc,
});
}
} else {
self.advance_clone();
}
}
self.expect(&TokenType::RBrace);
} else if attr.starts_with("runar::methods") {
if matches!(self.current().typ, TokenType::Impl) { self.advance_clone(); }
if let TokenType::Ident(_) = self.current().typ { self.advance_clone(); }
self.expect(&TokenType::LBrace);
while !matches!(self.current().typ, TokenType::RBrace | TokenType::Eof) {
let mut visibility = Visibility::Private;
if matches!(self.current().typ, TokenType::HashBracket) {
let method_attr = self.parse_attribute();
if method_attr == "public" { visibility = Visibility::Public; }
}
if matches!(self.current().typ, TokenType::Pub) {
self.advance_clone();
visibility = Visibility::Public;
}
methods.push(self.parse_function(visibility));
}
self.expect(&TokenType::RBrace);
} else {
continue;
}
} else {
self.advance_clone();
}
}
if properties.iter().any(|p| !p.readonly) {
parent_class = "StatefulSmartContract".to_string();
}
if contract_name.is_empty() {
self.errors.push(Diagnostic::error("No Rúnar contract struct found", None));
return ParseResult { contract: None, errors: self.errors };
}
let mut final_methods = Vec::new();
for m in methods {
if m.name == "init" && m.params.is_empty() {
for stmt in &m.body {
if let Statement::Assignment { target, value, .. } = stmt {
if let Expression::PropertyAccess { property } = target {
for p in properties.iter_mut() {
if p.name == *property {
p.initializer = Some(value.clone());
break;
}
}
}
}
}
} else {
final_methods.push(m);
}
}
let methods = final_methods;
let uninit_props: Vec<&PropertyNode> = properties.iter()
.filter(|p| p.initializer.is_none())
.collect();
let loc = SourceLocation { file: self.file.clone(), line: 1, column: 1 };
let super_args: Vec<Expression> = uninit_props.iter()
.map(|p| Expression::Identifier { name: p.name.clone() })
.collect();
let super_call = Statement::ExpressionStatement {
expression: Expression::CallExpr {
callee: Box::new(Expression::Identifier { name: "super".to_string() }),
args: super_args,
},
source_location: loc.clone(),
};
let mut ctor_body = vec![super_call];
for p in &uninit_props {
ctor_body.push(Statement::Assignment {
target: Expression::PropertyAccess { property: p.name.clone() },
value: Expression::Identifier { name: p.name.clone() },
source_location: loc.clone(),
});
}
let constructor = MethodNode {
name: "constructor".to_string(),
params: uninit_props.iter().map(|p| ParamNode {
name: p.name.clone(),
param_type: p.prop_type.clone(),
}).collect(),
body: ctor_body,
visibility: Visibility::Public,
source_location: loc,
};
let contract = ContractNode {
name: contract_name,
parent_class,
properties,
constructor,
methods,
source_file: self.file.clone(),
};
ParseResult { contract: Some(contract), errors: self.errors }
}
fn parse_attribute(&mut self) -> String {
self.advance_clone(); let mut attr = String::new();
let mut depth = 1;
while depth > 0 && !matches!(self.current().typ, TokenType::Eof) {
match &self.current().typ {
TokenType::LBracket => { depth += 1; self.advance_clone(); }
TokenType::RBracket => {
depth -= 1;
if depth == 0 { self.advance_clone(); break; }
self.advance_clone();
}
TokenType::Ident(name) => { attr.push_str(name); self.advance_clone(); }
TokenType::ColonColon => { attr.push_str("::"); self.advance_clone(); }
TokenType::LParen => { attr.push('('); self.advance_clone(); }
TokenType::RParen => { attr.push(')'); self.advance_clone(); }
_ => { self.advance_clone(); }
}
}
attr
}
fn parse_rust_type(&mut self) -> TypeNode {
if let TokenType::Ident(name) = self.current().typ.clone() {
self.advance_clone();
let mapped = map_rust_type(&name);
if let Some(prim) = PrimitiveTypeName::from_str(&mapped) {
TypeNode::Primitive(prim)
} else {
TypeNode::Custom(mapped)
}
} else {
self.advance_clone();
TypeNode::Custom("unknown".to_string())
}
}
fn parse_function(&mut self, visibility: Visibility) -> MethodNode {
let loc = self.loc();
self.expect(&TokenType::Fn);
let raw_name = if let TokenType::Ident(name) = self.current().typ.clone() {
self.advance_clone();
name
} else {
self.advance_clone();
"unknown".to_string()
};
let name = snake_to_camel(&raw_name);
self.expect(&TokenType::LParen);
let mut params: Vec<ParamNode> = Vec::new();
while !matches!(self.current().typ, TokenType::RParen | TokenType::Eof) {
if matches!(self.current().typ, TokenType::Amp) {
self.advance_clone();
if matches!(self.current().typ, TokenType::Mut) { self.advance_clone(); }
if matches!(self.current().typ, TokenType::Self_) {
self.advance_clone();
if matches!(self.current().typ, TokenType::Comma) { self.advance_clone(); }
continue;
}
}
if matches!(self.current().typ, TokenType::Self_) {
self.advance_clone();
if matches!(self.current().typ, TokenType::Comma) { self.advance_clone(); }
continue;
}
if let TokenType::Ident(param_name) = self.current().typ.clone() {
self.advance_clone();
self.expect(&TokenType::Colon);
if matches!(self.current().typ, TokenType::Amp) {
self.advance_clone();
if matches!(self.current().typ, TokenType::Mut) { self.advance_clone(); }
}
let param_type = self.parse_rust_type();
params.push(ParamNode {
name: snake_to_camel(¶m_name),
param_type,
});
} else {
self.advance_clone();
}
if matches!(self.current().typ, TokenType::Comma) { self.advance_clone(); }
}
self.expect(&TokenType::RParen);
if matches!(self.current().typ, TokenType::Arrow) {
self.advance_clone();
self.parse_rust_type();
}
self.expect(&TokenType::LBrace);
let mut body: Vec<Statement> = Vec::new();
while !matches!(self.current().typ, TokenType::RBrace | TokenType::Eof) {
if let Some(stmt) = self.parse_statement() {
body.push(stmt);
}
}
self.expect(&TokenType::RBrace);
MethodNode { name, params, body, visibility, source_location: loc }
}
fn parse_statement(&mut self) -> Option<Statement> {
let loc = self.loc();
if matches!(self.current().typ, TokenType::AssertMacro) {
self.advance_clone();
self.expect(&TokenType::LParen);
let expr = self.parse_expression();
self.expect(&TokenType::RParen);
self.match_tok(&TokenType::Semi);
return Some(Statement::ExpressionStatement {
expression: Expression::CallExpr {
callee: Box::new(Expression::Identifier { name: "assert".to_string() }),
args: vec![expr],
},
source_location: loc,
});
}
if matches!(self.current().typ, TokenType::AssertEqMacro) {
self.advance_clone();
self.expect(&TokenType::LParen);
let left = self.parse_expression();
self.expect(&TokenType::Comma);
let right = self.parse_expression();
self.expect(&TokenType::RParen);
self.match_tok(&TokenType::Semi);
return Some(Statement::ExpressionStatement {
expression: Expression::CallExpr {
callee: Box::new(Expression::Identifier { name: "assert".to_string() }),
args: vec![Expression::BinaryExpr {
op: BinaryOp::StrictEq,
left: Box::new(left),
right: Box::new(right),
}],
},
source_location: loc,
});
}
if matches!(self.current().typ, TokenType::Let) {
self.advance_clone();
let mutable = self.match_tok(&TokenType::Mut);
let var_name = if let TokenType::Ident(name) = self.current().typ.clone() {
self.advance_clone();
snake_to_camel(&name)
} else {
self.advance_clone();
"unknown".to_string()
};
let var_type = if matches!(self.current().typ, TokenType::Colon) {
self.advance_clone();
if matches!(self.current().typ, TokenType::Amp) { self.advance_clone(); }
if matches!(self.current().typ, TokenType::Mut) { self.advance_clone(); }
Some(self.parse_rust_type())
} else {
None
};
self.expect(&TokenType::Eq);
let init = self.parse_expression();
self.match_tok(&TokenType::Semi);
return Some(Statement::VariableDecl {
name: var_name, var_type, mutable, init, source_location: loc,
});
}
if matches!(self.current().typ, TokenType::If) {
self.advance_clone();
let condition = self.parse_expression();
self.expect(&TokenType::LBrace);
let mut then_branch = Vec::new();
while !matches!(self.current().typ, TokenType::RBrace | TokenType::Eof) {
if let Some(s) = self.parse_statement() { then_branch.push(s); }
}
self.expect(&TokenType::RBrace);
let else_branch = if matches!(self.current().typ, TokenType::Else) {
self.advance_clone();
self.expect(&TokenType::LBrace);
let mut eb = Vec::new();
while !matches!(self.current().typ, TokenType::RBrace | TokenType::Eof) {
if let Some(s) = self.parse_statement() { eb.push(s); }
}
self.expect(&TokenType::RBrace);
Some(eb)
} else {
None
};
return Some(Statement::IfStatement { condition, then_branch, else_branch, source_location: loc });
}
if matches!(self.current().typ, TokenType::Return) {
self.advance_clone();
let value = if !matches!(self.current().typ, TokenType::Semi | TokenType::RBrace) {
Some(self.parse_expression())
} else {
None
};
self.match_tok(&TokenType::Semi);
return Some(Statement::ReturnStatement { value, source_location: loc });
}
let expr = self.parse_expression();
if matches!(self.current().typ, TokenType::Eq) {
self.advance_clone();
let value = self.parse_expression();
self.match_tok(&TokenType::Semi);
return Some(Statement::Assignment {
target: self.convert_self_access(expr),
value,
source_location: loc,
});
}
if matches!(self.current().typ, TokenType::PlusEq) {
self.advance_clone();
let rhs = self.parse_expression();
self.match_tok(&TokenType::Semi);
let target = self.convert_self_access(expr.clone());
return Some(Statement::Assignment {
target: target.clone(),
value: Expression::BinaryExpr { op: BinaryOp::Add, left: Box::new(target), right: Box::new(rhs) },
source_location: loc,
});
}
if matches!(self.current().typ, TokenType::MinusEq) {
self.advance_clone();
let rhs = self.parse_expression();
self.match_tok(&TokenType::Semi);
let target = self.convert_self_access(expr.clone());
return Some(Statement::Assignment {
target: target.clone(),
value: Expression::BinaryExpr { op: BinaryOp::Sub, left: Box::new(target), right: Box::new(rhs) },
source_location: loc,
});
}
let had_semi = self.match_tok(&TokenType::Semi);
if !had_semi && matches!(self.current().typ, TokenType::RBrace) {
return Some(Statement::ReturnStatement { value: Some(expr), source_location: loc });
}
Some(Statement::ExpressionStatement { expression: expr, source_location: loc })
}
fn convert_self_access(&self, expr: Expression) -> Expression {
if let Expression::MemberExpr { ref object, ref property } = expr {
if let Expression::Identifier { ref name } = **object {
if name == "self" {
return Expression::PropertyAccess { property: snake_to_camel(property) };
}
}
}
expr
}
fn parse_expression(&mut self) -> Expression { self.parse_or() }
fn parse_or(&mut self) -> Expression {
let mut left = self.parse_and();
while matches!(self.current().typ, TokenType::PipePipe) {
self.advance_clone();
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 matches!(self.current().typ, TokenType::AmpAmp) {
self.advance_clone();
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 matches!(self.current().typ, TokenType::Pipe) {
self.advance_clone();
left = Expression::BinaryExpr { op: BinaryOp::BitOr, left: Box::new(left), right: Box::new(self.parse_bit_xor()) };
}
left
}
fn parse_bit_xor(&mut self) -> Expression {
let mut left = self.parse_bit_and();
while matches!(self.current().typ, TokenType::Caret) {
self.advance_clone();
left = Expression::BinaryExpr { op: BinaryOp::BitXor, left: Box::new(left), right: Box::new(self.parse_bit_and()) };
}
left
}
fn parse_bit_and(&mut self) -> Expression {
let mut left = self.parse_equality();
while matches!(self.current().typ, TokenType::Amp) {
self.advance_clone();
left = Expression::BinaryExpr { op: BinaryOp::BitAnd, left: Box::new(left), right: Box::new(self.parse_equality()) };
}
left
}
fn parse_equality(&mut self) -> Expression {
let mut left = self.parse_comparison();
loop {
let op = match self.current().typ {
TokenType::EqEq => BinaryOp::StrictEq,
TokenType::BangEq => BinaryOp::StrictNe,
_ => break,
};
self.advance_clone();
left = Expression::BinaryExpr { op, left: Box::new(left), right: Box::new(self.parse_comparison()) };
}
left
}
fn parse_comparison(&mut self) -> Expression {
let mut left = self.parse_add_sub();
loop {
let op = match self.current().typ {
TokenType::Lt => BinaryOp::Lt,
TokenType::LtEq => BinaryOp::Le,
TokenType::Gt => BinaryOp::Gt,
TokenType::GtEq => BinaryOp::Ge,
_ => break,
};
self.advance_clone();
left = Expression::BinaryExpr { op, left: Box::new(left), right: Box::new(self.parse_add_sub()) };
}
left
}
fn parse_add_sub(&mut self) -> Expression {
let mut left = self.parse_mul_div();
loop {
let op = match self.current().typ {
TokenType::Plus => BinaryOp::Add,
TokenType::Minus => BinaryOp::Sub,
_ => break,
};
self.advance_clone();
left = Expression::BinaryExpr { op, left: Box::new(left), right: Box::new(self.parse_mul_div()) };
}
left
}
fn parse_mul_div(&mut self) -> Expression {
let mut left = self.parse_unary();
loop {
let op = match self.current().typ {
TokenType::Star => BinaryOp::Mul,
TokenType::Slash => BinaryOp::Div,
TokenType::Percent => BinaryOp::Mod,
_ => break,
};
self.advance_clone();
left = Expression::BinaryExpr { op, left: Box::new(left), right: Box::new(self.parse_unary()) };
}
left
}
fn parse_unary(&mut self) -> Expression {
match self.current().typ {
TokenType::Bang => { self.advance_clone(); Expression::UnaryExpr { op: UnaryOp::Not, operand: Box::new(self.parse_unary()) } }
TokenType::Minus => { self.advance_clone(); Expression::UnaryExpr { op: UnaryOp::Neg, operand: Box::new(self.parse_unary()) } }
TokenType::Tilde => { self.advance_clone(); Expression::UnaryExpr { op: UnaryOp::BitNot, operand: Box::new(self.parse_unary()) } }
TokenType::Amp => {
self.advance_clone();
if matches!(self.current().typ, TokenType::Mut) { self.advance_clone(); }
self.parse_postfix()
}
_ => self.parse_postfix(),
}
}
fn parse_postfix(&mut self) -> Expression {
let mut expr = self.parse_primary();
loop {
if matches!(self.current().typ, TokenType::LParen) {
self.advance_clone();
let mut args = Vec::new();
while !matches!(self.current().typ, TokenType::RParen | TokenType::Eof) {
args.push(self.parse_expression());
if matches!(self.current().typ, TokenType::Comma) { self.advance_clone(); }
}
self.expect(&TokenType::RParen);
expr = Expression::CallExpr { callee: Box::new(expr), args };
} else if matches!(self.current().typ, TokenType::Dot) {
self.advance_clone();
let prop = if let TokenType::Ident(name) = self.current().typ.clone() {
self.advance_clone();
snake_to_camel(&name)
} else {
self.advance_clone();
"unknown".to_string()
};
if let Expression::Identifier { ref name } = expr {
if name == "self" {
expr = Expression::PropertyAccess { property: prop };
continue;
}
}
expr = Expression::MemberExpr { object: Box::new(expr), property: prop };
} else if matches!(self.current().typ, TokenType::ColonColon) {
self.advance_clone();
if let TokenType::Ident(name) = self.current().typ.clone() {
self.advance_clone();
expr = Expression::Identifier { name: snake_to_camel(&name) };
}
} else if matches!(self.current().typ, TokenType::LBracket) {
self.advance_clone();
let index = self.parse_expression();
self.expect(&TokenType::RBracket);
expr = Expression::IndexAccess { object: Box::new(expr), index: Box::new(index) };
} else {
break;
}
}
expr
}
fn parse_primary(&mut self) -> Expression {
match self.current().typ.clone() {
TokenType::Number(val) => {
self.advance_clone();
let n: i64 = val.parse().unwrap_or(0);
Expression::BigIntLiteral { value: n }
}
TokenType::HexString(val) => {
self.advance_clone();
Expression::ByteStringLiteral { value: val }
}
TokenType::True => { self.advance_clone(); Expression::BoolLiteral { value: true } }
TokenType::False => { self.advance_clone(); Expression::BoolLiteral { value: false } }
TokenType::Self_ => {
self.advance_clone();
Expression::Identifier { name: "self".to_string() }
}
TokenType::LParen => {
self.advance_clone();
let expr = self.parse_expression();
self.expect(&TokenType::RParen);
expr
}
TokenType::Ident(name) => {
self.advance_clone();
let mapped = map_rust_builtin(&name);
Expression::Identifier { name: mapped }
}
_ => {
let tok = self.current().clone();
self.advance_clone();
self.errors.push(Diagnostic::error(format!(
"unsupported token '{:?}' at {}:{} — not valid in Rúnar contract",
tok.typ, tok.line, tok.col), None));
Expression::Identifier { name: "unknown".to_string() }
}
}
}
}
fn snake_to_camel(name: &str) -> String {
let parts: Vec<&str> = name.split('_').collect();
if parts.len() <= 1 {
return name.to_string();
}
let mut result = parts[0].to_string();
for part in &parts[1..] {
if !part.is_empty() {
let mut chars = part.chars();
if let Some(first) = chars.next() {
result.push(first.to_uppercase().next().unwrap());
result.extend(chars);
}
}
}
result
}
fn map_rust_type(name: &str) -> String {
match name {
"Bigint" | "Int" | "i64" | "u64" | "i128" | "u128" => "bigint".to_string(),
"Bool" | "bool" => "boolean".to_string(),
_ => name.to_string(),
}
}
fn map_rust_builtin(name: &str) -> String {
match name {
"bool_cast" => return "bool".to_string(),
"verify_wots" => return "verifyWOTS".to_string(),
"verify_slh_dsa_sha2_128s" => return "verifySLHDSA_SHA2_128s".to_string(),
"verify_slh_dsa_sha2_128f" => return "verifySLHDSA_SHA2_128f".to_string(),
"verify_slh_dsa_sha2_192s" => return "verifySLHDSA_SHA2_192s".to_string(),
"verify_slh_dsa_sha2_192f" => return "verifySLHDSA_SHA2_192f".to_string(),
"verify_slh_dsa_sha2_256s" => return "verifySLHDSA_SHA2_256s".to_string(),
"verify_slh_dsa_sha2_256f" => return "verifySLHDSA_SHA2_256f".to_string(),
"bin_2_num" => return "bin2num".to_string(),
"int_2_str" => return "int2str".to_string(),
"to_byte_string" => return "toByteString".to_string(),
_ => {}
}
let camel = snake_to_camel(name);
match camel.as_str() {
"hash160" => "hash160".to_string(),
"hash256" => "hash256".to_string(),
"sha256" => "sha256".to_string(),
"ripemd160" => "ripemd160".to_string(),
"checkSig" => "checkSig".to_string(),
"checkMultiSig" => "checkMultiSig".to_string(),
"checkPreimage" => "checkPreimage".to_string(),
"verifyRabinSig" => "verifyRabinSig".to_string(),
"num2bin" => "num2bin".to_string(),
"bin2num" => "bin2num".to_string(),
"int2str" => "int2str".to_string(),
"extractLocktime" => "extractLocktime".to_string(),
"extractOutputHash" => "extractOutputHash".to_string(),
"extractVersion" => "extractVersion".to_string(),
"extractHashPrevouts" => "extractHashPrevouts".to_string(),
"extractHashSequence" => "extractHashSequence".to_string(),
"extractOutpoint" => "extractOutpoint".to_string(),
"extractInputIndex" => "extractInputIndex".to_string(),
"extractScriptCode" => "extractScriptCode".to_string(),
"extractAmount" => "extractAmount".to_string(),
"extractSequence" => "extractSequence".to_string(),
"extractOutputs" => "extractOutputs".to_string(),
"extractSigHashType" => "extractSigHashType".to_string(),
"addOutput" => "addOutput".to_string(),
"reverseBytes" => "reverseBytes".to_string(),
"toByteString" => "toByteString".to_string(),
_ => camel,
}
}
pub fn parse_rust_dsl(source: &str, file_name: Option<&str>) -> ParseResult {
let file = file_name.unwrap_or("contract.runar.rs").to_string();
let tokens = tokenize(source);
let parser = RustDslParser::new(tokens, file);
parser.parse()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_basic_rust_contract() {
let source = r#"
use runar::prelude::*;
#[runar::contract]
pub struct P2PKH {
pub_key_hash: Addr,
}
#[runar::methods]
impl P2PKH {
#[public]
fn unlock(&self, sig: Sig, pub_key: PubKey) {
assert!(hash160(pub_key) == self.pub_key_hash);
assert!(check_sig(sig, pub_key));
}
}
"#;
let result = parse_rust_dsl(source, Some("P2PKH.runar.rs"));
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let contract = result.contract.unwrap();
assert_eq!(contract.name, "P2PKH");
assert_eq!(contract.properties.len(), 1);
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_snake_to_camel_conversion() {
assert_eq!(snake_to_camel("pub_key_hash"), "pubKeyHash");
assert_eq!(snake_to_camel("check_sig"), "checkSig");
assert_eq!(snake_to_camel("already"), "already");
assert_eq!(snake_to_camel("a_b_c"), "aBC");
assert_eq!(snake_to_camel("hello_world"), "helloWorld");
}
#[test]
fn test_type_mapping_works() {
assert_eq!(map_rust_type("i64"), "bigint");
assert_eq!(map_rust_type("u64"), "bigint");
assert_eq!(map_rust_type("i128"), "bigint");
assert_eq!(map_rust_type("u128"), "bigint");
assert_eq!(map_rust_type("Bigint"), "bigint");
assert_eq!(map_rust_type("bool"), "boolean");
assert_eq!(map_rust_type("Bool"), "boolean");
assert_eq!(map_rust_type("PubKey"), "PubKey");
assert_eq!(map_rust_type("Sig"), "Sig");
assert_eq!(map_rust_type("Addr"), "Addr");
}
#[test]
fn test_public_attribute_makes_method_public() {
let source = r#"
use runar::prelude::*;
#[runar::contract]
pub struct Test {
#[readonly]
x: bigint,
}
#[runar::methods]
impl Test {
#[public]
fn public_method(&self, v: i64) {
assert!(v == self.x);
}
fn private_method(&self, v: i64) -> i64 {
return v + 1;
}
}
"#;
let result = parse_rust_dsl(source, Some("Test.runar.rs"));
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let contract = result.contract.unwrap();
assert_eq!(contract.methods.len(), 2);
assert_eq!(contract.methods[0].name, "publicMethod");
assert_eq!(contract.methods[0].visibility, Visibility::Public);
assert_eq!(contract.methods[1].name, "privateMethod");
assert_eq!(contract.methods[1].visibility, Visibility::Private);
}
#[test]
fn test_contract_name_extracted_correctly() {
let source = r#"
use runar::prelude::*;
#[runar::contract]
pub struct MyFancyContract {
value: bigint,
}
#[runar::methods]
impl MyFancyContract {
#[public]
fn check(&self, v: i64) {
assert!(v == self.value);
}
}
"#;
let result = parse_rust_dsl(source, Some("MyFancyContract.runar.rs"));
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let contract = result.contract.unwrap();
assert_eq!(contract.name, "MyFancyContract");
}
#[test]
fn test_property_names_are_camel_cased() {
let source = r#"
use runar::prelude::*;
#[runar::contract]
pub struct Test {
pub_key_hash: Addr,
my_value: bigint,
}
#[runar::methods]
impl Test {
#[public]
fn check(&self, v: i64) {
assert!(v == self.my_value);
}
}
"#;
let result = parse_rust_dsl(source, Some("Test.runar.rs"));
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let contract = result.contract.unwrap();
assert_eq!(contract.properties[0].name, "pubKeyHash");
assert_eq!(contract.properties[1].name, "myValue");
}
#[test]
fn test_stateful_contract_attribute() {
let source = r#"
use runar::prelude::*;
#[runar::stateful_contract]
pub struct Counter {
count: bigint,
}
#[runar::methods]
impl Counter {
#[public]
fn increment(&mut self) {
self.count = self.count + 1;
}
}
"#;
let result = parse_rust_dsl(source, Some("Counter.runar.rs"));
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let contract = result.contract.unwrap();
assert_eq!(contract.name, "Counter");
assert_eq!(contract.parent_class, "StatefulSmartContract");
}
#[test]
fn test_constructor_auto_generated() {
let source = r#"
use runar::prelude::*;
#[runar::contract]
pub struct Test {
a: bigint,
b: PubKey,
}
#[runar::methods]
impl Test {
#[public]
fn check(&self) {
assert!(self.a > 0);
}
}
"#;
let result = parse_rust_dsl(source, None);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let contract = result.contract.unwrap();
assert_eq!(contract.constructor.params.len(), 2);
assert_eq!(contract.constructor.body.len(), 3);
}
#[test]
fn test_assert_eq_macro_maps_to_assert_strict_eq() {
let source = r#"
use runar::prelude::*;
#[runar::contract]
pub struct Test {
#[readonly]
x: bigint,
}
#[runar::methods]
impl Test {
#[public]
fn check(&self, v: i64) {
assert_eq!(self.x, v);
}
}
"#;
let result = parse_rust_dsl(source, Some("Test.runar.rs"));
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let contract = result.contract.unwrap();
let body = &contract.methods[0].body;
assert_eq!(body.len(), 1);
if let Statement::ExpressionStatement { expression, .. } = &body[0] {
if let Expression::CallExpr { callee, args } = expression {
if let Expression::Identifier { name } = callee.as_ref() {
assert_eq!(name, "assert");
}
if let Expression::BinaryExpr { op, .. } = &args[0] {
assert_eq!(*op, BinaryOp::StrictEq);
} else {
panic!("Expected BinaryExpr inside assert_eq!, got {:?}", args[0]);
}
}
}
}
#[test]
fn test_int_type_maps_to_bigint() {
assert_eq!(map_rust_type("Int"), "bigint");
}
#[test]
fn test_bool_cast_maps_to_bool() {
assert_eq!(map_rust_builtin("bool_cast"), "bool");
}
#[test]
fn test_verify_wots_mapping() {
assert_eq!(map_rust_builtin("verify_wots"), "verifyWOTS");
}
#[test]
fn test_verify_slh_dsa_mappings() {
assert_eq!(map_rust_builtin("verify_slh_dsa_sha2_128s"), "verifySLHDSA_SHA2_128s");
assert_eq!(map_rust_builtin("verify_slh_dsa_sha2_128f"), "verifySLHDSA_SHA2_128f");
assert_eq!(map_rust_builtin("verify_slh_dsa_sha2_192s"), "verifySLHDSA_SHA2_192s");
assert_eq!(map_rust_builtin("verify_slh_dsa_sha2_192f"), "verifySLHDSA_SHA2_192f");
assert_eq!(map_rust_builtin("verify_slh_dsa_sha2_256s"), "verifySLHDSA_SHA2_256s");
assert_eq!(map_rust_builtin("verify_slh_dsa_sha2_256f"), "verifySLHDSA_SHA2_256f");
}
#[test]
fn test_extract_builtin_mappings() {
assert_eq!(map_rust_builtin("extract_version"), "extractVersion");
assert_eq!(map_rust_builtin("extract_hash_prevouts"), "extractHashPrevouts");
assert_eq!(map_rust_builtin("extract_hash_sequence"), "extractHashSequence");
assert_eq!(map_rust_builtin("extract_outpoint"), "extractOutpoint");
assert_eq!(map_rust_builtin("extract_input_index"), "extractInputIndex");
assert_eq!(map_rust_builtin("extract_script_code"), "extractScriptCode");
assert_eq!(map_rust_builtin("extract_amount"), "extractAmount");
assert_eq!(map_rust_builtin("extract_sequence"), "extractSequence");
assert_eq!(map_rust_builtin("extract_output_hash"), "extractOutputHash");
assert_eq!(map_rust_builtin("extract_outputs"), "extractOutputs");
assert_eq!(map_rust_builtin("extract_locktime"), "extractLocktime");
assert_eq!(map_rust_builtin("extract_sig_hash_type"), "extractSigHashType");
}
#[test]
fn test_byte_operation_builtin_mappings() {
assert_eq!(map_rust_builtin("reverse_bytes"), "reverseBytes");
assert_eq!(map_rust_builtin("bin2num"), "bin2num");
assert_eq!(map_rust_builtin("bin_2_num"), "bin2num");
assert_eq!(map_rust_builtin("int2str"), "int2str");
assert_eq!(map_rust_builtin("int_2_str"), "int2str");
assert_eq!(map_rust_builtin("to_byte_string"), "toByteString");
assert_eq!(map_rust_builtin("add_output"), "addOutput");
}
#[test]
fn test_implicit_return_in_method() {
let source = r#"
use runar::prelude::*;
#[runar::contract]
pub struct Foo { #[readonly] pub x: Bigint }
#[runar::methods(Foo)]
impl Foo {
fn compute(&self, a: Bigint, b: Bigint) -> Bigint {
let sum = a + b;
sum
}
#[public]
pub fn check(&self) {
assert!(self.x > 0);
}
}
"#;
let result = parse_rust_dsl(source, Some("Foo.runar.rs"));
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let contract = result.contract.unwrap();
let compute = contract.methods.iter().find(|m| m.name == "compute").unwrap();
assert_eq!(compute.body.len(), 2, "expected 2 statements (let + return)");
match &compute.body[1] {
Statement::ReturnStatement { value: Some(_), .. } => {}
other => panic!("expected ReturnStatement, got {:?}", other),
}
}
}