use crate::ir::*;
use crate::traits::{ReadError, Reader};
use tree_sitter::{Node, Parser, Tree};
pub static LUA_READER: LuaReader = LuaReader;
pub struct LuaReader;
impl Reader for LuaReader {
fn language(&self) -> &'static str {
"lua"
}
fn extensions(&self) -> &'static [&'static str] {
&["lua"]
}
fn read(&self, source: &str) -> Result<Program, ReadError> {
read_lua(source)
}
}
pub fn read_lua(source: &str) -> Result<Program, ReadError> {
let language = normalize_languages::parsers::grammar_loader()
.get("lua")
.map_err(|e| ReadError::Parse(format!("load lua grammar: {e}")))?;
let mut parser = Parser::new();
parser
.set_language(&language)
.map_err(|err| ReadError::Parse(err.to_string()))?;
let tree = parser
.parse(source, None)
.ok_or_else(|| ReadError::Parse("failed to parse".into()))?;
let ctx = ReadContext::new(source);
ctx.read_program(&tree)
}
struct ReadContext<'a> {
source: &'a str,
}
impl<'a> ReadContext<'a> {
fn new(source: &'a str) -> Self {
Self { source }
}
fn node_text(&self, node: Node) -> &str {
node.utf8_text(self.source.as_bytes()).unwrap_or("")
}
fn read_program(&self, tree: &Tree) -> Result<Program, ReadError> {
let root = tree.root_node();
if root.has_error() {
return Err(ReadError::Parse("syntax error in source".into()));
}
let mut statements = Vec::new();
let mut cursor = root.walk();
for child in root.children(&mut cursor) {
if child.is_named()
&& let Some(stmt) = self.read_stmt(child)?
{
statements.push(stmt);
}
}
Ok(Program::new(statements))
}
fn read_stmt(&self, node: Node) -> Result<Option<Stmt>, ReadError> {
match node.kind() {
"goto_statement" | "label_statement" => Ok(None),
"comment" => {
let raw = self.node_text(node);
let span = Span::from_ts(node.start_position(), node.end_position());
let stmt = if let Some(inner) = raw.strip_prefix("--[[") {
let content = inner.strip_suffix("]]").unwrap_or(inner).trim();
Stmt::comment_block(content)
} else if let Some(inner) = raw.strip_prefix("---") {
Stmt::comment_line(inner.trim())
} else if let Some(inner) = raw.strip_prefix("--") {
Stmt::comment_line(inner.trim())
} else {
Stmt::comment_line(raw.trim())
};
Ok(Some(stmt.with_span(span)))
}
"variable_declaration" => self.read_local_variable_declaration(node).map(Some),
"assignment_statement" => self.read_assignment_statement(node).map(Some),
"if_statement" => self.read_if_statement(node).map(Some),
"while_statement" => self.read_while_statement(node).map(Some),
"repeat_statement" => self.read_repeat_statement(node).map(Some),
"for_statement" => self.read_for_statement(node).map(Some),
"do_statement" => self.read_do_statement(node).map(Some),
"return_statement" => self.read_return_statement(node).map(Some),
"break_statement" => Ok(Some(Stmt::break_stmt())),
"function_declaration" => self.read_function_declaration(node).map(Some),
"local_function_declaration" => self.read_local_function_declaration(node).map(Some),
"function_call" => {
let expr = self.read_function_call(node)?;
Ok(Some(Stmt::expr(expr)))
}
_ => {
let expr = self.read_expr(node)?;
Ok(Some(Stmt::expr(expr)))
}
}
}
fn read_expr(&self, node: Node) -> Result<Expr, ReadError> {
match node.kind() {
"number" => self.read_number(node),
"string" => self.read_string(node),
"true" => Ok(Expr::bool(true)),
"false" => Ok(Expr::bool(false)),
"nil" => Ok(Expr::null()),
"identifier" => Ok(Expr::ident(self.node_text(node))),
"binary_expression" => self.read_binary_expr(node),
"unary_expression" => self.read_unary_expr(node),
"parenthesized_expression" => self.read_parenthesized(node),
"function_call" => self.read_function_call(node),
"dot_index_expression" => self.read_dot_index(node),
"bracket_index_expression" => self.read_bracket_index(node),
"table_constructor" => self.read_table(node),
"function_definition" => self.read_function_expr(node),
"method_index_expression" => self.read_method_index(node),
"vararg_expression" => Ok(Expr::ident("...")),
kind => Err(ReadError::Unsupported(format!(
"expression type '{}': {}",
kind,
self.node_text(node)
))),
}
}
fn read_number(&self, node: Node) -> Result<Expr, ReadError> {
let text = self.node_text(node);
let value: f64 = text
.parse()
.map_err(|_| ReadError::Parse(format!("invalid number: {}", text)))?;
Ok(Expr::number(value))
}
fn read_string(&self, node: Node) -> Result<Expr, ReadError> {
let text = self.node_text(node);
let inner = if text.starts_with("[[") {
text.strip_prefix("[[")
.and_then(|s| s.strip_suffix("]]"))
.unwrap_or(text)
} else if text.starts_with("[=[") {
let start = 1; let end = text
.rfind(']')
.ok_or_else(|| ReadError::Parse("unterminated long string".into()))?;
let equals_count = text[start..].chars().take_while(|c| *c == '=').count();
let actual_start = start + equals_count + 1;
let actual_end = end - equals_count;
&text[actual_start..actual_end]
} else if text.starts_with('"') || text.starts_with('\'') {
&text[1..text.len() - 1]
} else {
text
};
let unescaped = inner
.replace("\\n", "\n")
.replace("\\t", "\t")
.replace("\\r", "\r")
.replace("\\\"", "\"")
.replace("\\'", "'")
.replace("\\\\", "\\");
Ok(Expr::string(unescaped))
}
fn read_binary_expr(&self, node: Node) -> Result<Expr, ReadError> {
let left = node
.child_by_field_name("left")
.ok_or_else(|| ReadError::Parse("binary_expression missing left".into()))?;
let right = node
.child_by_field_name("right")
.ok_or_else(|| ReadError::Parse("binary_expression missing right".into()))?;
let mut operator_text = None;
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if !child.is_named() {
let text = self.node_text(child).trim();
if !text.is_empty() {
operator_text = Some(text);
break;
}
}
}
let op_text = operator_text
.ok_or_else(|| ReadError::Parse("binary_expression missing operator".into()))?;
let left_expr = self.read_expr(left)?;
let right_expr = self.read_expr(right)?;
let op = match op_text {
"+" => BinaryOp::Add,
"-" => BinaryOp::Sub,
"*" => BinaryOp::Mul,
"/" => BinaryOp::Div,
"%" => BinaryOp::Mod,
"//" => BinaryOp::Div,
".." => BinaryOp::Concat,
"==" => BinaryOp::Eq,
"~=" => BinaryOp::Ne,
"<" => BinaryOp::Lt,
">" => BinaryOp::Gt,
"<=" => BinaryOp::Le,
">=" => BinaryOp::Ge,
"and" => BinaryOp::And,
"or" => BinaryOp::Or,
"^" => {
return Ok(Expr::call(
Expr::member(Expr::ident("math"), "pow"),
vec![left_expr, right_expr],
));
}
_ => {
return Err(ReadError::Unsupported(format!("operator '{}'", op_text)));
}
};
Ok(Expr::binary(left_expr, op, right_expr))
}
fn read_unary_expr(&self, node: Node) -> Result<Expr, ReadError> {
let mut operator_text = None;
let mut operand_node = None;
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if !child.is_named() {
let text = self.node_text(child).trim();
if !text.is_empty() && operator_text.is_none() {
operator_text = Some(text);
}
} else if operand_node.is_none() {
operand_node = Some(child);
}
}
let op_text = operator_text
.ok_or_else(|| ReadError::Parse("unary_expression missing operator".into()))?;
let operand = operand_node
.ok_or_else(|| ReadError::Parse("unary_expression missing operand".into()))?;
let arg_expr = self.read_expr(operand)?;
let op = match op_text {
"not" => UnaryOp::Not,
"-" => UnaryOp::Neg,
"#" => {
return Ok(Expr::call(Expr::ident("len"), vec![arg_expr]));
}
_ => {
return Err(ReadError::Unsupported(format!(
"unary operator '{}'",
op_text
)));
}
};
Ok(Expr::unary(op, arg_expr))
}
fn read_parenthesized(&self, node: Node) -> Result<Expr, ReadError> {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.is_named() {
return self.read_expr(child);
}
}
Err(ReadError::Parse("empty parenthesized expression".into()))
}
fn read_function_call(&self, node: Node) -> Result<Expr, ReadError> {
let name = node
.child_by_field_name("name")
.ok_or_else(|| ReadError::Parse("function_call missing name".into()))?;
let arguments = node.child_by_field_name("arguments");
let (callee, implicit_self) = if name.kind() == "method_index_expression" {
let table = name
.child_by_field_name("table")
.ok_or_else(|| ReadError::Parse("method_index_expression missing table".into()))?;
let method = name
.child_by_field_name("method")
.ok_or_else(|| ReadError::Parse("method_index_expression missing method".into()))?;
let table_expr = self.read_expr(table)?;
let method_name = self.node_text(method);
let callee = Expr::member(table_expr.clone(), method_name);
(callee, Some(table_expr))
} else {
(self.read_expr(name)?, None)
};
let mut args = if let Some(args_node) = arguments {
self.read_arguments(args_node)?
} else {
let mut args = Vec::new();
let mut cursor = node.walk();
let mut past_name = false;
for child in node.children(&mut cursor) {
if child.id() == name.id() {
past_name = true;
continue;
}
if past_name && child.is_named() {
args.push(self.read_expr(child)?);
break;
}
}
args
};
if let Some(self_expr) = implicit_self {
args.insert(0, self_expr);
}
Ok(Expr::call(callee, args))
}
fn read_arguments(&self, node: Node) -> Result<Vec<Expr>, ReadError> {
let mut args = Vec::new();
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.is_named() {
args.push(self.read_expr(child)?);
}
}
Ok(args)
}
fn read_dot_index(&self, node: Node) -> Result<Expr, ReadError> {
let table = node
.child_by_field_name("table")
.ok_or_else(|| ReadError::Parse("dot_index_expression missing table".into()))?;
let field = node
.child_by_field_name("field")
.ok_or_else(|| ReadError::Parse("dot_index_expression missing field".into()))?;
let table_expr = self.read_expr(table)?;
let field_name = self.node_text(field);
Ok(Expr::member(table_expr, field_name))
}
fn read_bracket_index(&self, node: Node) -> Result<Expr, ReadError> {
let table = node
.child_by_field_name("table")
.ok_or_else(|| ReadError::Parse("bracket_index_expression missing table".into()))?;
let index = node
.child_by_field_name("field")
.ok_or_else(|| ReadError::Parse("bracket_index_expression missing field".into()))?;
let table_expr = self.read_expr(table)?;
let index_expr = self.read_expr(index)?;
Ok(Expr::index(table_expr, index_expr))
}
fn read_method_index(&self, node: Node) -> Result<Expr, ReadError> {
let table = node
.child_by_field_name("table")
.ok_or_else(|| ReadError::Parse("method_index_expression missing table".into()))?;
let method = node
.child_by_field_name("method")
.ok_or_else(|| ReadError::Parse("method_index_expression missing method".into()))?;
let table_expr = self.read_expr(table)?;
let method_name = self.node_text(method);
Ok(Expr::member(table_expr, method_name))
}
fn read_table(&self, node: Node) -> Result<Expr, ReadError> {
let mut pairs: Vec<(String, Expr)> = Vec::new();
let mut array_index = 1;
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
match child.kind() {
"field" => {
let name_node = child.child_by_field_name("name");
let value = child
.child_by_field_name("value")
.ok_or_else(|| ReadError::Parse("field missing value".into()))?;
let key = match name_node {
Some(n) if n.kind() == "identifier" => {
self.node_text(n).to_string()
}
Some(n) if n.kind() == "string" => {
match self.read_string(n)? {
Expr::Literal(crate::ir::Literal::String(s)) => s,
_ => self.node_text(n).to_string(),
}
}
Some(n) => {
self.node_text(n).to_string()
}
None => {
let k = array_index.to_string();
array_index += 1;
k
}
};
pairs.push((key, self.read_expr(value)?));
}
_ if child.is_named() => {
let key = array_index.to_string();
array_index += 1;
pairs.push((key, self.read_expr(child)?));
}
_ => {}
}
}
Ok(Expr::object(pairs))
}
fn read_function_expr(&self, node: Node) -> Result<Expr, ReadError> {
let mut params = Vec::new();
if let Some(params_node) = node.child_by_field_name("parameters") {
self.collect_params(params_node, &mut params);
}
let body_node = node
.child_by_field_name("body")
.ok_or_else(|| ReadError::Parse("function_definition missing body".into()))?;
let body = self.read_block_stmts(body_node)?;
Ok(Expr::Function(Box::new(Function::anonymous(params, body))))
}
fn collect_params(&self, params: Node, out: &mut Vec<Param>) {
let mut cursor = params.walk();
for child in params.children(&mut cursor) {
if child.kind() == "identifier" {
out.push(Param::new(self.node_text(child)));
} else if child.kind() == "vararg_expression" {
out.push(Param::new("..."));
}
}
}
fn read_local_variable_declaration(&self, node: Node) -> Result<Stmt, ReadError> {
let mut names = Vec::new();
let mut values = Vec::new();
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "assignment_statement" {
let mut assign_cursor = child.walk();
for assign_child in child.children(&mut assign_cursor) {
if assign_child.kind() == "variable_list" {
let mut inner_cursor = assign_child.walk();
for name_node in assign_child.children(&mut inner_cursor) {
if name_node.kind() == "identifier" {
names.push(self.node_text(name_node).to_string());
}
}
} else if assign_child.kind() == "expression_list" {
let mut inner_cursor = assign_child.walk();
for val_node in assign_child.children(&mut inner_cursor) {
if val_node.is_named() {
values.push(self.read_expr(val_node)?);
}
}
}
}
} else if child.kind() == "variable_list" {
let mut inner_cursor = child.walk();
for name_node in child.children(&mut inner_cursor) {
if name_node.kind() == "identifier" {
names.push(self.node_text(name_node).to_string());
}
}
} else if child.kind() == "expression_list" {
let mut inner_cursor = child.walk();
for val_node in child.children(&mut inner_cursor) {
if val_node.is_named() {
values.push(self.read_expr(val_node)?);
}
}
}
}
if names.len() == 1 {
let init = values.into_iter().next();
return Ok(Stmt::Let {
name: names.remove(0),
init,
mutable: true,
type_annotation: None,
span: None,
});
}
let mut stmts = Vec::new();
for (i, name) in names.into_iter().enumerate() {
let init = values.get(i).cloned();
stmts.push(Stmt::Let {
name,
init,
mutable: true,
type_annotation: None,
span: None,
});
}
Ok(Stmt::block(stmts))
}
fn read_assignment_statement(&self, node: Node) -> Result<Stmt, ReadError> {
let mut targets = Vec::new();
let mut values = Vec::new();
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "variable_list" {
let mut inner_cursor = child.walk();
for target_node in child.children(&mut inner_cursor) {
if target_node.is_named() {
targets.push(self.read_expr(target_node)?);
}
}
} else if child.kind() == "expression_list" {
let mut inner_cursor = child.walk();
for val_node in child.children(&mut inner_cursor) {
if val_node.is_named() {
values.push(self.read_expr(val_node)?);
}
}
}
}
if targets.len() == 1 && values.len() == 1 {
return Ok(Stmt::expr(Expr::assign(
targets.remove(0),
values.remove(0),
)));
}
let mut stmts = Vec::new();
for (target, value) in targets.into_iter().zip(values.into_iter()) {
stmts.push(Stmt::expr(Expr::assign(target, value)));
}
Ok(Stmt::block(stmts))
}
fn read_if_statement(&self, node: Node) -> Result<Stmt, ReadError> {
let condition = node
.child_by_field_name("condition")
.ok_or_else(|| ReadError::Parse("if_statement missing condition".into()))?;
let consequence = node
.child_by_field_name("consequence")
.ok_or_else(|| ReadError::Parse("if_statement missing consequence".into()))?;
let cond_expr = self.read_expr(condition)?;
let then_stmts = self.read_block_stmts(consequence)?;
let then_stmt = Stmt::block(then_stmts);
let mut else_stmt: Option<Stmt> = None;
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
match child.kind() {
"elseif_statement" => {
else_stmt = Some(self.read_elseif_statement(child)?);
break; }
"else_statement" => {
let else_body = child
.child_by_field_name("body")
.ok_or_else(|| ReadError::Parse("else_statement missing body".into()))?;
let stmts = self.read_block_stmts(else_body)?;
else_stmt = Some(Stmt::block(stmts));
break;
}
_ => {}
}
}
Ok(Stmt::if_stmt(cond_expr, then_stmt, else_stmt))
}
fn read_elseif_statement(&self, node: Node) -> Result<Stmt, ReadError> {
let condition = node
.child_by_field_name("condition")
.ok_or_else(|| ReadError::Parse("elseif_statement missing condition".into()))?;
let consequence = node
.child_by_field_name("consequence")
.ok_or_else(|| ReadError::Parse("elseif_statement missing consequence".into()))?;
let cond_expr = self.read_expr(condition)?;
let then_stmts = self.read_block_stmts(consequence)?;
let then_stmt = Stmt::block(then_stmts);
let mut else_stmt: Option<Stmt> = None;
if let Some(sibling) = node.next_sibling() {
match sibling.kind() {
"elseif_statement" => {
else_stmt = Some(self.read_elseif_statement(sibling)?);
}
"else_statement" => {
let else_body = sibling
.child_by_field_name("body")
.ok_or_else(|| ReadError::Parse("else_statement missing body".into()))?;
let stmts = self.read_block_stmts(else_body)?;
else_stmt = Some(Stmt::block(stmts));
}
_ => {}
}
}
Ok(Stmt::if_stmt(cond_expr, then_stmt, else_stmt))
}
fn read_while_statement(&self, node: Node) -> Result<Stmt, ReadError> {
let condition = node
.child_by_field_name("condition")
.ok_or_else(|| ReadError::Parse("while_statement missing condition".into()))?;
let body = node
.child_by_field_name("body")
.ok_or_else(|| ReadError::Parse("while_statement missing body".into()))?;
let cond_expr = self.read_expr(condition)?;
let body_stmts = self.read_block_stmts(body)?;
Ok(Stmt::while_loop(cond_expr, Stmt::block(body_stmts)))
}
fn read_repeat_statement(&self, node: Node) -> Result<Stmt, ReadError> {
let body = node
.child_by_field_name("body")
.ok_or_else(|| ReadError::Parse("repeat_statement missing body".into()))?;
let condition = node
.child_by_field_name("condition")
.ok_or_else(|| ReadError::Parse("repeat_statement missing condition".into()))?;
let body_stmts = self.read_block_stmts(body)?;
let cond_expr = self.read_expr(condition)?;
let break_if = Stmt::if_stmt(cond_expr, Stmt::break_stmt(), None);
let mut loop_body = body_stmts;
loop_body.push(break_if);
Ok(Stmt::while_loop(Expr::bool(true), Stmt::block(loop_body)))
}
fn read_for_statement(&self, node: Node) -> Result<Stmt, ReadError> {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "for_generic_clause" {
return self.read_for_generic(node, child);
} else if child.kind() == "for_numeric_clause" {
return self.read_for_numeric(node, child);
}
}
Err(ReadError::Parse("for_statement missing clause".into()))
}
fn read_for_numeric(&self, node: Node, clause: Node) -> Result<Stmt, ReadError> {
let name = clause
.child_by_field_name("name")
.ok_or_else(|| ReadError::Parse("for_numeric_clause missing name".into()))?;
let start = clause
.child_by_field_name("start")
.ok_or_else(|| ReadError::Parse("for_numeric_clause missing start".into()))?;
let finish = clause
.child_by_field_name("end")
.ok_or_else(|| ReadError::Parse("for_numeric_clause missing end".into()))?;
let var_name = self.node_text(name).to_string();
let start_expr = self.read_expr(start)?;
let finish_expr = self.read_expr(finish)?;
let step_expr = clause
.child_by_field_name("step")
.map(|n| self.read_expr(n))
.transpose()?;
let body_node = node
.child_by_field_name("body")
.ok_or_else(|| ReadError::Parse("for_statement missing body".into()))?;
let body_stmts = self.read_block_stmts(body_node)?;
let step = step_expr.unwrap_or_else(|| Expr::number(1.0));
let init = Stmt::let_decl(var_name.clone(), Some(start_expr));
let test = Expr::binary(
Expr::ident(var_name.clone()),
BinaryOp::Le,
finish_expr.clone(),
);
let update = Expr::assign(
Expr::ident(var_name.clone()),
Expr::binary(Expr::ident(var_name), BinaryOp::Add, step),
);
Ok(Stmt::for_loop(
Some(init),
Some(test),
Some(update),
Stmt::block(body_stmts),
))
}
fn read_for_generic(&self, node: Node, clause: Node) -> Result<Stmt, ReadError> {
let mut var_names = Vec::new();
let mut iterator = None;
let mut cursor = clause.walk();
for child in clause.children(&mut cursor) {
if child.kind() == "variable_list" {
let mut inner_cursor = child.walk();
for name_node in child.children(&mut inner_cursor) {
if name_node.kind() == "identifier" {
var_names.push(self.node_text(name_node).to_string());
}
}
} else if child.kind() == "expression_list" {
let mut inner_cursor = child.walk();
for expr_node in child.children(&mut inner_cursor) {
if expr_node.is_named() && iterator.is_none() {
iterator = Some(self.read_expr(expr_node)?);
}
}
}
}
let iter_expr = iterator
.ok_or_else(|| ReadError::Parse("for_generic_clause missing iterator".into()))?;
let body_node = node
.child_by_field_name("body")
.ok_or_else(|| ReadError::Parse("for_statement missing body".into()))?;
let body_stmts = self.read_block_stmts(body_node)?;
let var_name = if var_names.is_empty() {
"_".to_string()
} else {
var_names.join(", ")
};
Ok(Stmt::for_in(var_name, iter_expr, Stmt::block(body_stmts)))
}
fn read_do_statement(&self, node: Node) -> Result<Stmt, ReadError> {
let body = node
.child_by_field_name("body")
.ok_or_else(|| ReadError::Parse("do_statement missing body".into()))?;
let stmts = self.read_block_stmts(body)?;
Ok(Stmt::block(stmts))
}
fn read_return_statement(&self, node: Node) -> Result<Stmt, ReadError> {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "expression_list" {
let mut values = Vec::new();
let mut inner_cursor = child.walk();
for expr_node in child.children(&mut inner_cursor) {
if expr_node.is_named() {
values.push(self.read_expr(expr_node)?);
}
}
return match values.len() {
0 => Ok(Stmt::return_stmt(None)),
1 => Ok(Stmt::return_stmt(Some(values.remove(0)))),
_ => Ok(Stmt::return_stmt(Some(Expr::array(values)))),
};
} else if child.is_named() && child.kind() != "return" {
let value = self.read_expr(child)?;
return Ok(Stmt::return_stmt(Some(value)));
}
}
Ok(Stmt::return_stmt(None))
}
fn read_function_declaration(&self, node: Node) -> Result<Stmt, ReadError> {
let name = node
.child_by_field_name("name")
.ok_or_else(|| ReadError::Parse("function_declaration missing name".into()))?;
let name_str = self.node_text(name).to_string();
let mut params = Vec::new();
if let Some(params_node) = node.child_by_field_name("parameters") {
self.collect_params(params_node, &mut params);
}
let body_node = node
.child_by_field_name("body")
.ok_or_else(|| ReadError::Parse("function_declaration missing body".into()))?;
let body = self.read_block_stmts(body_node)?;
Ok(Stmt::function(Function::new(name_str, params, body)))
}
fn read_local_function_declaration(&self, node: Node) -> Result<Stmt, ReadError> {
self.read_function_declaration(node)
}
fn read_block_stmts(&self, node: Node) -> Result<Vec<Stmt>, ReadError> {
let mut statements = Vec::new();
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.is_named()
&& let Some(stmt) = self.read_stmt(child)?
{
statements.push(stmt);
}
}
Ok(statements)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_assignment() -> Result<(), ReadError> {
let program = read_lua("local x = 42")?;
assert_eq!(program.body.len(), 1);
match &program.body[0] {
Stmt::Let { name, init, .. } => {
assert_eq!(name, "x");
assert!(init.is_some());
}
_ => panic!("expected Let"),
}
Ok(())
}
#[test]
fn test_binary_expr() -> Result<(), ReadError> {
let program = read_lua("local x = 1 + 2")?;
match &program.body[0] {
Stmt::Let {
init: Some(Expr::Binary { op, .. }),
..
} => {
assert_eq!(op, &BinaryOp::Add);
}
_ => panic!("expected Binary"),
}
Ok(())
}
#[test]
fn test_function_call() -> Result<(), ReadError> {
let program = read_lua("print('hello')")?;
match &program.body[0] {
Stmt::Expr(Expr::Call { callee, args, .. }) => {
assert_eq!(args.len(), 1);
match callee.as_ref() {
Expr::Ident(name) => assert_eq!(name, "print"),
_ => panic!("expected Ident"),
}
}
_ => panic!("expected Call"),
}
Ok(())
}
#[test]
fn test_function_declaration() -> Result<(), ReadError> {
let program = read_lua("function add(a, b) return a + b end")?;
match &program.body[0] {
Stmt::Function(f) => {
assert_eq!(f.name, "add");
assert_eq!(f.params.len(), 2);
assert_eq!(f.params[0].name, "a");
assert_eq!(f.params[1].name, "b");
}
_ => panic!("expected Function"),
}
Ok(())
}
#[test]
fn test_if_statement() -> Result<(), ReadError> {
let program = read_lua("if x > 0 then return 1 else return 0 end")?;
match &program.body[0] {
Stmt::If {
test, alternate, ..
} => {
assert!(matches!(
test,
Expr::Binary {
op: BinaryOp::Gt,
..
}
));
assert!(alternate.is_some());
}
_ => panic!("expected If"),
}
Ok(())
}
#[test]
fn test_table_constructor() -> Result<(), ReadError> {
let program = read_lua("local t = { a = 1, b = 2 }")?;
match &program.body[0] {
Stmt::Let {
init: Some(Expr::Object(pairs)),
..
} => {
assert_eq!(pairs.len(), 2);
}
_ => panic!("expected Object"),
}
Ok(())
}
#[test]
fn test_varargs_expression() -> Result<(), ReadError> {
let program = read_lua("function foo(...) return ... end")?;
match &program.body[0] {
Stmt::Function(f) => {
assert_eq!(f.params.len(), 1);
assert_eq!(f.params[0].name, "...");
match &f.body[0] {
Stmt::Return(Some(Expr::Ident(name))) => {
assert_eq!(name, "...");
}
_ => panic!("expected Return with vararg ident"),
}
}
_ => panic!("expected Function"),
}
Ok(())
}
#[test]
fn test_multiple_return() -> Result<(), ReadError> {
let program = read_lua("function swap(a, b) return b, a end")?;
match &program.body[0] {
Stmt::Function(f) => match &f.body[0] {
Stmt::Return(Some(Expr::Array(items))) => {
assert_eq!(items.len(), 2);
}
_ => panic!("expected Return with array"),
},
_ => panic!("expected Function"),
}
Ok(())
}
#[test]
fn test_repeat_until() -> Result<(), ReadError> {
let program = read_lua("repeat x = x + 1 until x > 10")?;
match &program.body[0] {
Stmt::While { test, body, .. } => {
assert!(matches!(test, Expr::Literal(Literal::Bool(true))));
match body.as_ref() {
Stmt::Block(stmts) => {
assert!(stmts.len() >= 2); }
_ => panic!("expected Block body"),
}
}
_ => panic!("expected While"),
}
Ok(())
}
#[test]
fn test_goto_label_skipped() -> Result<(), ReadError> {
let program = read_lua("::start::\nprint('hello')\ngoto start")?;
assert_eq!(program.body.len(), 1);
match &program.body[0] {
Stmt::Expr(Expr::Call { .. }) => {}
_ => panic!("expected Call"),
}
Ok(())
}
#[test]
fn test_nested_function_calls() -> Result<(), ReadError> {
let program = read_lua("local x = f(g(h(42)))")?;
match &program.body[0] {
Stmt::Let {
init: Some(Expr::Call { callee, args, .. }),
..
} => {
match callee.as_ref() {
Expr::Ident(name) => assert_eq!(name, "f"),
_ => panic!("expected Ident callee"),
}
assert_eq!(args.len(), 1);
match &args[0] {
Expr::Call { callee: inner, .. } => match inner.as_ref() {
Expr::Ident(name) => assert_eq!(name, "g"),
_ => panic!("expected Ident inner callee"),
},
_ => panic!("expected nested Call"),
}
}
_ => panic!("expected Let with Call"),
}
Ok(())
}
#[test]
fn test_multi_return_local() -> Result<(), ReadError> {
let program = read_lua("local a, b = f()")?;
match &program.body[0] {
Stmt::Block(stmts) => {
assert_eq!(stmts.len(), 2);
match &stmts[0] {
Stmt::Let { name, .. } => assert_eq!(name, "a"),
_ => panic!("expected Let a"),
}
match &stmts[1] {
Stmt::Let { name, .. } => assert_eq!(name, "b"),
_ => panic!("expected Let b"),
}
}
_ => panic!("expected Block for multi-variable declaration"),
}
Ok(())
}
#[test]
fn test_unicode_string() -> Result<(), ReadError> {
let program = read_lua(r#"local s = "こんにちは""#)?;
match &program.body[0] {
Stmt::Let {
init: Some(Expr::Literal(Literal::String(s))),
..
} => {
assert_eq!(s, "こんにちは");
}
_ => panic!("expected Let with String"),
}
Ok(())
}
#[test]
fn test_long_string() -> Result<(), ReadError> {
let program = read_lua("local s = [[hello world]]")?;
match &program.body[0] {
Stmt::Let {
init: Some(Expr::Literal(Literal::String(s))),
..
} => {
assert_eq!(s, "hello world");
}
_ => panic!("expected Let with String"),
}
Ok(())
}
#[test]
fn test_numeric_for_with_step() -> Result<(), ReadError> {
let program = read_lua("for i = 1, 10, 2 do print(i) end")?;
match &program.body[0] {
Stmt::For { init, update, .. } => {
match init.as_ref().map(|b| b.as_ref()) {
Some(Stmt::Let { name, .. }) => assert_eq!(name, "i"),
_ => panic!("expected Let init"),
}
match update {
Some(Expr::Assign { value, .. }) => match value.as_ref() {
Expr::Binary { op, right, .. } => {
assert_eq!(op, &BinaryOp::Add);
match right.as_ref() {
Expr::Literal(Literal::Number(n)) => assert_eq!(*n, 2.0),
_ => panic!("expected step = 2"),
}
}
_ => panic!("expected Binary update"),
},
_ => panic!("expected Assign update"),
}
}
_ => panic!("expected For"),
}
Ok(())
}
#[test]
fn test_numeric_for_default_step() -> Result<(), ReadError> {
let program = read_lua("for i = 1, 10 do print(i) end")?;
match &program.body[0] {
Stmt::For { update, .. } => match update {
Some(Expr::Assign { value, .. }) => match value.as_ref() {
Expr::Binary { right, .. } => match right.as_ref() {
Expr::Literal(Literal::Number(n)) => assert_eq!(*n, 1.0),
_ => panic!("expected step = 1"),
},
_ => panic!("expected Binary"),
},
_ => panic!("expected Assign update"),
},
_ => panic!("expected For"),
}
Ok(())
}
#[test]
fn test_generic_for_multi_var() -> Result<(), ReadError> {
let program = read_lua("for k, v in pairs(t) do print(k) end")?;
match &program.body[0] {
Stmt::ForIn { variable, .. } => {
assert_eq!(variable, "k, v");
}
_ => panic!("expected ForIn"),
}
Ok(())
}
#[test]
fn test_method_call_desugars_self() -> Result<(), ReadError> {
let program = read_lua("obj:method(42)")?;
match &program.body[0] {
Stmt::Expr(Expr::Call { callee, args, .. }) => {
match callee.as_ref() {
Expr::Member {
object,
property,
computed: false,
..
} => {
match object.as_ref() {
Expr::Ident(name) => assert_eq!(name, "obj"),
_ => panic!("expected Ident object"),
}
match property.as_ref() {
Expr::Literal(Literal::String(s)) => assert_eq!(s, "method"),
_ => panic!("expected String property"),
}
}
_ => panic!("expected Member callee"),
}
assert_eq!(args.len(), 2);
match &args[0] {
Expr::Ident(name) => assert_eq!(name, "obj"),
_ => panic!("expected Ident as first (self) arg"),
}
}
_ => panic!("expected Expr Call"),
}
Ok(())
}
#[test]
fn test_metamethod_table_keys() -> Result<(), ReadError> {
let program = read_lua("local mt = { __index = function(self, k) return nil end }")?;
match &program.body[0] {
Stmt::Let {
init: Some(Expr::Object(pairs)),
..
} => {
assert_eq!(pairs.len(), 1);
assert_eq!(pairs[0].0, "__index");
assert!(matches!(pairs[0].1, Expr::Function(_)));
}
_ => panic!("expected Let with Object"),
}
Ok(())
}
#[test]
fn test_table_computed_string_key() -> Result<(), ReadError> {
let program = read_lua(r#"local t = { ["hello"] = 42 }"#)?;
match &program.body[0] {
Stmt::Let {
init: Some(Expr::Object(pairs)),
..
} => {
assert_eq!(pairs.len(), 1);
assert_eq!(pairs[0].0, "hello");
}
_ => panic!("expected Let with Object"),
}
Ok(())
}
#[test]
fn test_complex_control_flow() -> Result<(), ReadError> {
let src = r#"
local function classify(x)
if x > 100 then
return "big"
elseif x > 10 then
return "medium"
else
return "small"
end
end
"#;
let program = read_lua(src)?;
match &program.body[0] {
Stmt::Function(f) => {
assert_eq!(f.name, "classify");
match &f.body[0] {
Stmt::If {
test,
alternate: Some(alt),
..
} => {
assert!(matches!(
test,
Expr::Binary {
op: BinaryOp::Gt,
..
}
));
match alt.as_ref() {
Stmt::If {
alternate: Some(_), ..
} => {}
_ => panic!("expected nested If (elseif)"),
}
}
_ => panic!("expected If"),
}
}
_ => panic!("expected Function"),
}
Ok(())
}
#[test]
fn test_line_comment_preserved() -> Result<(), ReadError> {
let program = read_lua("-- This is a comment\nlocal x = 1")?;
assert_eq!(program.body.len(), 2);
match &program.body[0] {
Stmt::Comment { text, block, .. } => {
assert_eq!(text, "This is a comment");
assert!(!block);
}
_ => panic!("expected Comment"),
}
Ok(())
}
#[test]
fn test_luadoc_comment_preserved() -> Result<(), ReadError> {
let program = read_lua("--- Adds two numbers\nlocal function add(a, b) return a + b end")?;
assert_eq!(program.body.len(), 2);
match &program.body[0] {
Stmt::Comment { text, block, .. } => {
assert!(text.contains("Adds two numbers"));
assert!(!block);
}
_ => panic!("expected Comment"),
}
Ok(())
}
#[test]
fn test_block_comment_preserved() -> Result<(), ReadError> {
let program = read_lua("--[[ block comment ]]\nlocal x = 1")?;
assert_eq!(program.body.len(), 2);
match &program.body[0] {
Stmt::Comment { text, block, .. } => {
assert!(text.contains("block comment"));
assert!(*block);
}
_ => panic!("expected Comment"),
}
Ok(())
}
}