feat: add codegen, vm executor

Signed-off-by: Pakin <pakin.t@forth.co.th>
This commit is contained in:
Pakin 2026-05-25 09:05:39 +07:00
parent 8a98f29c9d
commit f9ae59c77f
15 changed files with 4632 additions and 336 deletions

3
build.rs Normal file
View file

@ -0,0 +1,3 @@
fn main() {
lalrpop::process_root().unwrap();
}

View file

@ -1,7 +1,9 @@
#[derive(Clone, Debug, PartialEq)]
pub struct Program { pub struct Program {
pub statements: Vec<Statement>, pub statements: Vec<Statement>,
} }
#[derive(Clone, Debug, PartialEq)]
pub enum Statement { pub enum Statement {
VarDecl { VarDecl {
name: String, name: String,
@ -14,7 +16,8 @@ pub enum Statement {
else_branch: Vec<Statement>, else_branch: Vec<Statement>,
}, },
ForStmt { ForStmt {
condition: Expression, variable: String,
iterable: Expression,
body: Vec<Statement>, body: Vec<Statement>,
}, },
ExprStmt { ExprStmt {
@ -22,14 +25,15 @@ pub enum Statement {
}, },
} }
#[derive(Clone, Debug, PartialEq)]
pub enum Expression { pub enum Expression {
BinaryOp { BinaryOp {
left: Box<Expression>, left: Box<Expression>,
op: BinaryOpKind, // Add, Sub, Mul, Div, Eq, Neq, Lt, Gt, Lte, Gte op: BinaryOpKind,
right: Box<Expression>, right: Box<Expression>,
}, },
UnaryOp { UnaryOp {
op: UnaryOpKind, // Neg, Not op: UnaryOpKind,
operand: Box<Expression>, operand: Box<Expression>,
}, },
Literal(LiteralValue), Literal(LiteralValue),
@ -44,45 +48,48 @@ pub enum Expression {
}, },
SpecialVar { SpecialVar {
name: String, name: String,
is_negative: bool, // For $- prefixed vars is_negative: bool,
}, },
AutoVarExpr { AutoVarExpr {
variable: Box<Expression>, // Changed from SpecialVar to Expression variable: Box<Expression>,
}, },
FlagExpr { FlagExpr {
args: Vec<Expression>, args: Vec<Expression>,
}, },
StringConcat { StringConcat {
// Special node for disambiguating +
parts: Vec<Expression>, parts: Vec<Expression>,
}, },
} }
#[derive(Clone, Debug, PartialEq)]
pub enum LiteralValue { pub enum LiteralValue {
String(String), String(String),
Number(f64), // or i64/f64 distinction if needed Number(f64),
Bool(bool), Bool(bool),
} }
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum BinaryOpKind { pub enum BinaryOpKind {
Add, Add,
Sub, Sub,
Mul, Mul,
Div, Div,
Eq, // = Eq,
Neq, // != Neq,
Lt, // < Lt,
Gt, // > Gt,
Lte, // <= Lte,
Gte, // >= Gte,
} }
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum UnaryOpKind { pub enum UnaryOpKind {
Neg, // - Neg,
Not, // ! Not,
} }
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum AssignmentType { pub enum AssignmentType {
Equals, // = Equals,
NotAssigned, // !assigned NotAssigned,
} }

View file

@ -0,0 +1,144 @@
use crate::xml::ast::nodes::{
AssignmentType, BinaryOpKind, Expression, LiteralValue, Program, Statement, UnaryOpKind,
};
use crate::xml::codegen::{walk_expression, walk_statement, CodeGen};
pub struct JavaCodeGen {
pub indent: usize,
pub class_name: String,
}
impl JavaCodeGen {
pub fn new(class_name: &str) -> Self {
JavaCodeGen {
indent: 0,
class_name: class_name.to_string(),
}
}
fn indent_str(&self) -> String {
" ".repeat(self.indent)
}
}
impl CodeGen for JavaCodeGen {
fn generate(&self, program: &Program) -> String {
let stmts: Vec<String> = program.statements.iter().map(|s| self.generate_statement(s)).collect();
let mut result = format!("public class {} {{\n", self.class_name);
result.push_str(" public static void main(String[] args) {\n");
for stmt in &stmts {
result.push_str(&format!(" {}\n", stmt.trim()));
}
result.push_str(" }\n");
result.push_str("}\n");
result
}
fn generate_statement(&self, stmt: &Statement) -> String {
walk_statement(self, stmt)
}
fn generate_expression(&self, expr: &Expression) -> String {
walk_expression(self, expr)
}
fn generate_literal(&self, lit: &LiteralValue) -> String {
match lit {
LiteralValue::String(s) => format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")),
LiteralValue::Number(n) => format!("{}d", n),
LiteralValue::Bool(b) => format!("{}", b),
}
}
fn generate_identifier(&self, name: &str) -> String {
name.to_string()
}
fn generate_binary_op(&self, left: &str, op: BinaryOpKind, right: &str) -> String {
let op_str = match op {
BinaryOpKind::Add => "+",
BinaryOpKind::Sub => "-",
BinaryOpKind::Mul => "*",
BinaryOpKind::Div => "/",
BinaryOpKind::Eq => "==",
BinaryOpKind::Neq => "!=",
BinaryOpKind::Lt => "<",
BinaryOpKind::Gt => ">",
BinaryOpKind::Lte => "<=",
BinaryOpKind::Gte => ">=",
};
format!("({} {} {})", left, op_str, right)
}
fn generate_unary_op(&self, op: UnaryOpKind, operand: &str) -> String {
match op {
UnaryOpKind::Neg => format!("(-{})", operand),
UnaryOpKind::Not => format!("(!{})", operand),
}
}
fn generate_special_var(&self, name: &str, is_negative: bool) -> String {
if is_negative {
format!("-var_{}", name)
} else {
format!("var_{}", name)
}
}
fn generate_auto_var_expr(&self, variable: &str) -> String {
format!("auto_{}", variable)
}
fn generate_function_call(&self, name: &str, args: &[String]) -> String {
format!("{}({})", name, args.join(", "))
}
fn generate_array_access(&self, name: &str, indices: &[String]) -> String {
let idx_str: String = indices.iter().map(|i| format!("[{}]", i)).collect();
format!("{}{}", name, idx_str)
}
fn generate_var_decl(&self, name: &str, value: Option<&str>, assignment_type: AssignmentType) -> String {
match assignment_type {
AssignmentType::Equals => {
match value {
Some(v) => format!("{}Object {} = {};", self.indent_str(), name, v),
None => format!("{}Object {} = null;", self.indent_str(), name),
}
}
AssignmentType::NotAssigned => format!("{}// {} is not assigned", self.indent_str(), name),
}
}
fn generate_if_stmt(&self, condition: &str, then_body: &[String], else_body: &[String]) -> String {
let indent = self.indent_str();
let mut result = format!("{}if ({}) {{\n", indent, condition);
for stmt in then_body {
result.push_str(&format!("{}\n", stmt));
}
if else_body.is_empty() {
result.push_str(&format!("{}}}", indent));
} else {
result.push_str(&format!("{}}} else {{\n", indent));
for stmt in else_body {
result.push_str(&format!("{}\n", stmt));
}
result.push_str(&format!("{}}}", indent));
}
result
}
fn generate_for_stmt(&self, variable: &str, iterable: &str, body: &[String]) -> String {
let indent = self.indent_str();
let mut result = format!("{}for (Object {} : {}) {{\n", indent, variable, iterable);
for stmt in body {
result.push_str(&format!("{}\n", stmt));
}
result.push_str(&format!("{}}}", indent));
result
}
fn generate_string_concat(&self, parts: &[String]) -> String {
parts.join(" + ")
}
}

View file

@ -0,0 +1,133 @@
use crate::xml::ast::nodes::{
AssignmentType, BinaryOpKind, Expression, LiteralValue, Program, Statement, UnaryOpKind,
};
use crate::xml::codegen::{walk_expression, walk_statement, CodeGen};
pub struct JavaScriptCodeGen {
pub indent: usize,
}
impl JavaScriptCodeGen {
pub fn new() -> Self {
JavaScriptCodeGen { indent: 0 }
}
fn indent_str(&self) -> String {
" ".repeat(self.indent)
}
}
impl CodeGen for JavaScriptCodeGen {
fn generate(&self, program: &Program) -> String {
let stmts: Vec<String> = program.statements.iter().map(|s| self.generate_statement(s)).collect();
stmts.join("\n")
}
fn generate_statement(&self, stmt: &Statement) -> String {
walk_statement(self, stmt)
}
fn generate_expression(&self, expr: &Expression) -> String {
walk_expression(self, expr)
}
fn generate_literal(&self, lit: &LiteralValue) -> String {
match lit {
LiteralValue::String(s) => format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")),
LiteralValue::Number(n) => format!("{}", n),
LiteralValue::Bool(b) => format!("{}", b),
}
}
fn generate_identifier(&self, name: &str) -> String {
name.to_string()
}
fn generate_binary_op(&self, left: &str, op: BinaryOpKind, right: &str) -> String {
let op_str = match op {
BinaryOpKind::Add => "+",
BinaryOpKind::Sub => "-",
BinaryOpKind::Mul => "*",
BinaryOpKind::Div => "/",
BinaryOpKind::Eq => "===",
BinaryOpKind::Neq => "!==",
BinaryOpKind::Lt => "<",
BinaryOpKind::Gt => ">",
BinaryOpKind::Lte => "<=",
BinaryOpKind::Gte => ">=",
};
format!("({} {} {})", left, op_str, right)
}
fn generate_unary_op(&self, op: UnaryOpKind, operand: &str) -> String {
match op {
UnaryOpKind::Neg => format!("(-{})", operand),
UnaryOpKind::Not => format!("(!{})", operand),
}
}
fn generate_special_var(&self, name: &str, is_negative: bool) -> String {
if is_negative {
format!("-var_{}", name)
} else {
format!("var_{}", name)
}
}
fn generate_auto_var_expr(&self, variable: &str) -> String {
format!("auto_{}", variable)
}
fn generate_function_call(&self, name: &str, args: &[String]) -> String {
format!("{}({})", name, args.join(", "))
}
fn generate_array_access(&self, name: &str, indices: &[String]) -> String {
let idx_str: String = indices.iter().map(|i| format!("[{}]", i)).collect();
format!("{}{}", name, idx_str)
}
fn generate_var_decl(&self, name: &str, value: Option<&str>, assignment_type: AssignmentType) -> String {
match assignment_type {
AssignmentType::Equals => {
match value {
Some(v) => format!("{}let {} = {};", self.indent_str(), name, v),
None => format!("{}let {} = null;", self.indent_str(), name),
}
}
AssignmentType::NotAssigned => format!("{}// {} is not assigned", self.indent_str(), name),
}
}
fn generate_if_stmt(&self, condition: &str, then_body: &[String], else_body: &[String]) -> String {
let indent = self.indent_str();
let mut result = format!("{}if ({}) {{\n", indent, condition);
for stmt in then_body {
result.push_str(&format!("{}\n", stmt));
}
if else_body.is_empty() {
result.push_str(&format!("{}}}", indent));
} else {
result.push_str(&format!("{}}} else {{\n", indent));
for stmt in else_body {
result.push_str(&format!("{}\n", stmt));
}
result.push_str(&format!("{}}}", indent));
}
result
}
fn generate_for_stmt(&self, variable: &str, iterable: &str, body: &[String]) -> String {
let indent = self.indent_str();
let mut result = format!("{}for (let {} of {}) {{\n", indent, variable, iterable);
for stmt in body {
result.push_str(&format!("{}\n", stmt));
}
result.push_str(&format!("{}}}", indent));
result
}
fn generate_string_concat(&self, parts: &[String]) -> String {
parts.join(" + ")
}
}

View file

@ -1,23 +1,87 @@
use crate::xml::ast::nodes::{AssignmentType, BinaryOpKind, LiteralValue, Program, UnaryOpKind}; pub mod rust;
pub mod python;
pub mod javascript;
pub mod java;
pub trait CodeGen<T> { use crate::xml::ast::nodes::{
fn generate_program(&self, program: &Program) -> T; AssignmentType, BinaryOpKind, Expression, LiteralValue, Program, Statement, UnaryOpKind,
};
// Expression visitors pub trait CodeGen {
fn visit_binary_op(&self, left: &T, op: BinaryOpKind, right: &T) -> T; fn generate(&self, program: &Program) -> String;
fn visit_unary_op(&self, op: UnaryOpKind, operand: &T) -> T; fn generate_statement(&self, stmt: &Statement) -> String;
fn visit_literal(&self, lit: &LiteralValue) -> T; fn generate_expression(&self, expr: &Expression) -> String;
fn visit_identifier(&self, name: &str) -> T; fn generate_literal(&self, lit: &LiteralValue) -> String;
fn visit_array_access(&self, name: &T, indices: &[T]) -> T; fn generate_identifier(&self, name: &str) -> String;
fn visit_function_call(&self, name: &T, args: &[T]) -> T; fn generate_binary_op(&self, left: &str, op: BinaryOpKind, right: &str) -> String;
fn visit_special_var(&self, name: &str, is_negative: bool) -> T; fn generate_unary_op(&self, op: UnaryOpKind, operand: &str) -> String;
fn visit_auto_var_expr(&self, variable: &T) -> T; fn generate_special_var(&self, name: &str, is_negative: bool) -> String;
fn visit_flag_expr(&self, args: &[T]) -> T; fn generate_auto_var_expr(&self, variable: &str) -> String;
fn visit_string_concat(&self, parts: &[T]) -> T; fn generate_function_call(&self, name: &str, args: &[String]) -> String;
fn generate_array_access(&self, name: &str, indices: &[String]) -> String;
// Statement visitors fn generate_var_decl(&self, name: &str, value: Option<&str>, assignment_type: AssignmentType) -> String;
fn visit_var_decl(&self, name: &T, value: Option<&T>, assignment_type: AssignmentType) -> T; fn generate_if_stmt(&self, condition: &str, then_body: &[String], else_body: &[String]) -> String;
fn visit_if_stmt(&self, condition: &T, then_body: &[T], else_body: &[T]) -> T; fn generate_for_stmt(&self, variable: &str, iterable: &str, body: &[String]) -> String;
fn visit_for_stmt(&self, condition: &T, body: &[T]) -> T; fn generate_string_concat(&self, parts: &[String]) -> String;
fn visit_expr_stmt(&self, expr: &T) -> T;
} }
fn walk_expression(codegen: &dyn CodeGen, expr: &Expression) -> String {
match expr {
Expression::BinaryOp { left, op, right } => {
let l = codegen.generate_expression(left);
let r = codegen.generate_expression(right);
codegen.generate_binary_op(&l, *op, &r)
}
Expression::UnaryOp { op, operand } => {
let o = codegen.generate_expression(operand);
codegen.generate_unary_op(*op, &o)
}
Expression::Literal(lit) => codegen.generate_literal(lit),
Expression::Identifier(name) => codegen.generate_identifier(name),
Expression::FunctionCall { name, args } => {
let rendered_args: Vec<String> = args.iter().map(|a| codegen.generate_expression(a)).collect();
codegen.generate_function_call(name, &rendered_args)
}
Expression::ArrayAccess { name, indices } => {
let rendered_indices: Vec<String> = indices.iter().map(|i| codegen.generate_expression(i)).collect();
codegen.generate_array_access(name, &rendered_indices)
}
Expression::SpecialVar { name, is_negative } => codegen.generate_special_var(name, *is_negative),
Expression::AutoVarExpr { variable } => {
let v = codegen.generate_expression(variable);
codegen.generate_auto_var_expr(&v)
}
Expression::FlagExpr { args } => {
let rendered_args: Vec<String> = args.iter().map(|a| codegen.generate_expression(a)).collect();
codegen.generate_function_call("flag", &rendered_args)
}
Expression::StringConcat { parts } => {
let rendered_parts: Vec<String> = parts.iter().map(|p| codegen.generate_expression(p)).collect();
codegen.generate_string_concat(&rendered_parts)
}
}
}
fn walk_statement(codegen: &dyn CodeGen, stmt: &Statement) -> String {
match stmt {
Statement::VarDecl { name, value, assignment_type } => {
let v = value.as_ref().map(|e| codegen.generate_expression(e));
codegen.generate_var_decl(name, v.as_deref(), *assignment_type)
}
Statement::IfStmt { condition, then_branch, else_branch } => {
let c = codegen.generate_expression(condition);
let then_body: Vec<String> = then_branch.iter().map(|s| codegen.generate_statement(s)).collect();
let else_body: Vec<String> = else_branch.iter().map(|s| codegen.generate_statement(s)).collect();
codegen.generate_if_stmt(&c, &then_body, &else_body)
}
Statement::ForStmt { variable, iterable, body } => {
let iter = codegen.generate_expression(iterable);
let b: Vec<String> = body.iter().map(|s| codegen.generate_statement(s)).collect();
codegen.generate_for_stmt(variable, &iter, &b)
}
Statement::ExprStmt { expression } => {
let e = codegen.generate_expression(expression);
codegen.generate_var_decl("_", Some(&e), AssignmentType::Equals)
}
}
}

View file

@ -0,0 +1,137 @@
use crate::xml::ast::nodes::{
AssignmentType, BinaryOpKind, Expression, LiteralValue, Program, Statement, UnaryOpKind,
};
use crate::xml::codegen::{walk_expression, walk_statement, CodeGen};
pub struct PythonCodeGen {
pub indent: usize,
}
impl PythonCodeGen {
pub fn new() -> Self {
PythonCodeGen { indent: 0 }
}
fn indent_str(&self) -> String {
" ".repeat(self.indent)
}
}
impl CodeGen for PythonCodeGen {
fn generate(&self, program: &Program) -> String {
let stmts: Vec<String> = program.statements.iter().map(|s| self.generate_statement(s)).collect();
stmts.join("\n")
}
fn generate_statement(&self, stmt: &Statement) -> String {
walk_statement(self, stmt)
}
fn generate_expression(&self, expr: &Expression) -> String {
walk_expression(self, expr)
}
fn generate_literal(&self, lit: &LiteralValue) -> String {
match lit {
LiteralValue::String(s) => format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")),
LiteralValue::Number(n) => format!("{}", n),
LiteralValue::Bool(b) => if *b { "True".to_string() } else { "False".to_string() },
}
}
fn generate_identifier(&self, name: &str) -> String {
name.to_string()
}
fn generate_binary_op(&self, left: &str, op: BinaryOpKind, right: &str) -> String {
let op_str = match op {
BinaryOpKind::Add => "+",
BinaryOpKind::Sub => "-",
BinaryOpKind::Mul => "*",
BinaryOpKind::Div => "/",
BinaryOpKind::Eq => "==",
BinaryOpKind::Neq => "!=",
BinaryOpKind::Lt => "<",
BinaryOpKind::Gt => ">",
BinaryOpKind::Lte => "<=",
BinaryOpKind::Gte => ">=",
};
format!("({} {} {})", left, op_str, right)
}
fn generate_unary_op(&self, op: UnaryOpKind, operand: &str) -> String {
match op {
UnaryOpKind::Neg => format!("(-{})", operand),
UnaryOpKind::Not => format!("(not {})", operand),
}
}
fn generate_special_var(&self, name: &str, is_negative: bool) -> String {
if is_negative {
format!("-var_{}", name)
} else {
format!("var_{}", name)
}
}
fn generate_auto_var_expr(&self, variable: &str) -> String {
format!("auto_{}", variable)
}
fn generate_function_call(&self, name: &str, args: &[String]) -> String {
format!("{}({})", name, args.join(", "))
}
fn generate_array_access(&self, name: &str, indices: &[String]) -> String {
let idx_str: String = indices.iter().map(|i| format!("[{}]", i)).collect();
format!("{}{}", name, idx_str)
}
fn generate_var_decl(&self, name: &str, value: Option<&str>, assignment_type: AssignmentType) -> String {
match assignment_type {
AssignmentType::Equals => {
match value {
Some(v) => format!("{}{} = {}", self.indent_str(), name, v),
None => format!("{}{} = None", self.indent_str(), name),
}
}
AssignmentType::NotAssigned => format!("{}# {} is not assigned", self.indent_str(), name),
}
}
fn generate_if_stmt(&self, condition: &str, then_body: &[String], else_body: &[String]) -> String {
let indent = self.indent_str();
let mut result = format!("{}if {}:\n", indent, condition);
if then_body.is_empty() {
result.push_str(&format!("{} pass\n", indent));
} else {
for stmt in then_body {
result.push_str(&format!("{}\n", stmt));
}
}
if !else_body.is_empty() {
result.push_str(&format!("{}else:\n", indent));
for stmt in else_body {
result.push_str(&format!("{}\n", stmt));
}
}
result
}
fn generate_for_stmt(&self, variable: &str, iterable: &str, body: &[String]) -> String {
let indent = self.indent_str();
let mut result = format!("{}for {} in {}:\n", indent, variable, iterable);
if body.is_empty() {
result.push_str(&format!("{} pass\n", indent));
} else {
for stmt in body {
result.push_str(&format!("{}\n", stmt));
}
}
result
}
fn generate_string_concat(&self, parts: &[String]) -> String {
parts.join(" + ")
}
}

View file

@ -0,0 +1,137 @@
use crate::xml::ast::nodes::{
AssignmentType, BinaryOpKind, Expression, LiteralValue, Program, Statement, UnaryOpKind,
};
use crate::xml::codegen::{walk_expression, walk_statement, CodeGen};
pub struct RustCodeGen {
pub indent: usize,
}
impl RustCodeGen {
pub fn new() -> Self {
RustCodeGen { indent: 0 }
}
fn indent_str(&self) -> String {
" ".repeat(self.indent)
}
fn format_block(&self, stmts: &[String]) -> String {
stmts.join("\n")
}
}
impl CodeGen for RustCodeGen {
fn generate(&self, program: &Program) -> String {
let stmts: Vec<String> = program.statements.iter().map(|s| self.generate_statement(s)).collect();
stmts.join("\n")
}
fn generate_statement(&self, stmt: &Statement) -> String {
walk_statement(self, stmt)
}
fn generate_expression(&self, expr: &Expression) -> String {
walk_expression(self, expr)
}
fn generate_literal(&self, lit: &LiteralValue) -> String {
match lit {
LiteralValue::String(s) => format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")),
LiteralValue::Number(n) => format!("{}", n),
LiteralValue::Bool(b) => format!("{}", b),
}
}
fn generate_identifier(&self, name: &str) -> String {
name.to_string()
}
fn generate_binary_op(&self, left: &str, op: BinaryOpKind, right: &str) -> String {
let op_str = match op {
BinaryOpKind::Add => "+",
BinaryOpKind::Sub => "-",
BinaryOpKind::Mul => "*",
BinaryOpKind::Div => "/",
BinaryOpKind::Eq => "==",
BinaryOpKind::Neq => "!=",
BinaryOpKind::Lt => "<",
BinaryOpKind::Gt => ">",
BinaryOpKind::Lte => "<=",
BinaryOpKind::Gte => ">=",
};
format!("({} {} {})", left, op_str, right)
}
fn generate_unary_op(&self, op: UnaryOpKind, operand: &str) -> String {
match op {
UnaryOpKind::Neg => format!("(-{})", operand),
UnaryOpKind::Not => format!("(!{})", operand),
}
}
fn generate_special_var(&self, name: &str, is_negative: bool) -> String {
if is_negative {
format!("var_{}_neg", name)
} else {
format!("var_{}", name)
}
}
fn generate_auto_var_expr(&self, variable: &str) -> String {
format!("auto_{}", variable)
}
fn generate_function_call(&self, name: &str, args: &[String]) -> String {
format!("{}({})", name, args.join(", "))
}
fn generate_array_access(&self, name: &str, indices: &[String]) -> String {
let idx_str: String = indices.iter().map(|i| format!("[{}]", i)).collect();
format!("{}{}", name, idx_str)
}
fn generate_var_decl(&self, name: &str, value: Option<&str>, assignment_type: AssignmentType) -> String {
match assignment_type {
AssignmentType::Equals => {
match value {
Some(v) => format!("{}let {} = {};", self.indent_str(), name, v),
None => format!("{}let {} = Default::default();", self.indent_str(), name),
}
}
AssignmentType::NotAssigned => format!("{}// {} is not assigned", self.indent_str(), name),
}
}
fn generate_if_stmt(&self, condition: &str, then_body: &[String], else_body: &[String]) -> String {
let indent = self.indent_str();
let mut result = format!("{}if {} {{\n", indent, condition);
for stmt in then_body {
result.push_str(&format!("{}\n", stmt));
}
if else_body.is_empty() {
result.push_str(&format!("{}}}", indent));
} else {
result.push_str(&format!("{}}} else {{\n", indent));
for stmt in else_body {
result.push_str(&format!("{}\n", stmt));
}
result.push_str(&format!("{}}}", indent));
}
result
}
fn generate_for_stmt(&self, variable: &str, iterable: &str, body: &[String]) -> String {
let indent = self.indent_str();
let mut result = format!("{}for {} in {} {{\n", indent, variable, iterable);
for stmt in body {
result.push_str(&format!("{}\n", stmt));
}
result.push_str(&format!("{}}}", indent));
result
}
fn generate_string_concat(&self, parts: &[String]) -> String {
parts.join(" + ")
}
}

View file

@ -3,6 +3,7 @@ use crate::xml::parser::grammar::Token;
pub type ParserResult<T> = Result<T, ParseError>; pub type ParserResult<T> = Result<T, ParseError>;
#[derive(Debug)]
pub enum ParseError { pub enum ParseError {
LexerError { LexerError {
position: Position, position: Position,
@ -19,66 +20,86 @@ pub enum ParseError {
}, },
} }
#[derive(Debug)]
pub struct Position { pub struct Position {
pub line: usize, pub line: usize,
pub column: usize, pub column: usize,
pub file: Option<String>, // For tracking which XML file/script block pub file: Option<String>,
} }
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ParseError::LexerError { position, message } => {
write!(f, "Lexer error at {}:{}: {}", position.line, position.column, message)
}
ParseError::ParserError { position, message, expected } => {
write!(f, "Parser error at {}:{}: {} (expected one of: {:?})", position.line, position.column, message, expected)
}
ParseError::SemanticError { position, message } => {
write!(f, "Semantic error at {}:{}: {}", position.line, position.column, message)
}
}
}
}
impl std::error::Error for ParseError {}
impl ParseError { impl ParseError {
pub fn from_lalrpop_error(source: &str, e: LalrpopParseError<usize, Token<'_>, &'static str>) -> Self { pub fn from_lalrpop_error(source: &str, e: LalrpopParseError<usize, Token<'_>, &'static str>) -> Self {
match e { match e {
LalrpopParseError::InvalidToken { location } => {
let (line, col) = line_col_from_offset(source, location);
ParseError::LexerError {
position: Position { line, column: col, file: None },
message: format!("Invalid token at location {}", location),
}
}
LalrpopParseError::UnrecognizedEof { location, expected } => {
let (line, col) = line_col_from_offset(source, location);
ParseError::ParserError {
position: Position { line, column: col, file: None },
message: "Unexpected end of input".to_string(),
expected: expected.into_iter().map(|s| s.to_string()).collect(),
}
}
LalrpopParseError::UnrecognizedToken { token, expected } => {
let (line, col) = line_col_from_offset(source, token.0);
ParseError::ParserError {
position: Position { line, column: col, file: None },
message: format!("Unrecognized token: {:?}", token.1),
expected: expected.into_iter().map(|s| s.to_string()).collect(),
}
}
LalrpopParseError::ExtraToken { token } => {
let (line, col) = line_col_from_offset(source, token.0);
ParseError::ParserError {
position: Position { line, column: col, file: None },
message: format!("Extra token: {:?}", token.1),
expected: Vec::new(),
}
}
LalrpopParseError::User { error } => ParseError::SemanticError { LalrpopParseError::User { error } => ParseError::SemanticError {
position: Position { position: Position { line: 0, column: 0, file: None },
line: 0, // TODO: improve position tracking message: error.to_string(),
column: 0,
file: None,
},
message: format!("User error: {}", error),
},
LalrpopParseError::InvalidToken { location } => ParseError::LexerError {
position: Position {
line: 0, // TODO: improve position tracking
column: 0,
file: None,
},
message: format!("Invalid token at location {}", location),
},
LalrpopParseError::UnrecognizedEof { location, expected } => ParseError::ParserError {
position: Position {
line: 0, // TODO: improve position tracking
column: 0,
file: None,
},
message: format!("Unexpected end of input, expected one of: {:?}", expected),
expected: expected.into_iter().map(|s| s.to_string()).collect(),
},
LalrpopParseError::UnrecognizedToken { token, expected } => ParseError::ParserError {
position: Position {
line: 0, // TODO: improve position tracking
column: token.0,
file: None,
},
message: format!("Unrecognized token: {:?}", token.1),
expected: expected.into_iter().map(|s| s.to_string()).collect(),
},
LalrpopParseError::ExtraToken { token } => ParseError::ParserError {
position: Position {
line: 0, // TODO: improve position tracking
column: token.0,
file: None,
},
message: format!("Extra token: {:?}", token.1),
expected: Vec::new(),
},
LalrpopParseError::User { error: e } => ParseError::SemanticError {
position: Position {
line: 0, // TODO: improve position tracking
column: 0,
file: None,
},
message: format!("User error: {}", e),
}, },
} }
} }
} }
fn line_col_from_offset(source: &str, offset: usize) -> (usize, usize) {
let mut line = 1;
let mut col = 1;
for (i, c) in source.chars().enumerate() {
if i >= offset {
break;
}
if c == '\n' {
line += 1;
col = 1;
} else {
col += 1;
}
}
(line, col)
}

View file

@ -4,3 +4,16 @@ pub mod error;
pub mod ffi_node; pub mod ffi_node;
pub mod node; pub mod node;
pub mod parser; pub mod parser;
pub mod vm;
pub use error::ParserResult;
pub use parser::ScriptParser;
pub use ast::nodes::{
AssignmentType, BinaryOpKind, Expression, LiteralValue, Program, Statement, UnaryOpKind,
};
pub use codegen::CodeGen;
pub use codegen::rust::RustCodeGen;
pub use codegen::python::PythonCodeGen;
pub use codegen::javascript::JavaScriptCodeGen;
pub use codegen::java::JavaCodeGen;
pub use vm::{Vm, Value, VmError, Environment};

View file

@ -1,9 +1,160 @@
use crate::xml::ast::nodes::{AssignmentType, BinaryOpKind, Expression, LiteralValue, Program, Statement, UnaryOpKind};
grammar; grammar;
pub Program: () = { pub Program: Program = {
<Statement*> => () <stmts:Stmt*> => Program { statements: stmts }
}; };
Statement: () = { Stmt: Statement = {
"hello" => () "Var" <name:Ident> "=" <v:Expr> => Statement::VarDecl {
name,
value: Some(v),
assignment_type: AssignmentType::Equals,
},
"Var" <name:Ident> "!" "assigned" => Statement::VarDecl {
name,
value: None,
assignment_type: AssignmentType::NotAssigned,
},
"If" <cond:Expr> "Then" <then_body:Stmts> "EndIf" => Statement::IfStmt {
condition: cond,
then_branch: then_body,
else_branch: vec![],
},
"If" <cond:Expr> "Then" <then_body:Stmts> "Else" <else_body:Stmts> "EndIf" => Statement::IfStmt {
condition: cond,
then_branch: then_body,
else_branch: else_body,
},
"For" <var:Ident> "In" <iter:Expr> <body:Stmts> "EndFor" => Statement::ForStmt {
variable: var,
iterable: iter,
body,
},
}; };
Expr: Expression = {
OrExpr,
};
OrExpr: Expression = {
AndExpr,
<left:OrExpr> "||" <right:AndExpr> => Expression::BinaryOp {
left: Box::new(left),
op: BinaryOpKind::Add,
right: Box::new(right),
},
};
AndExpr: Expression = {
CmpExpr,
<left:AndExpr> "&&" <right:CmpExpr> => Expression::BinaryOp {
left: Box::new(left),
op: BinaryOpKind::Add,
right: Box::new(right),
},
};
CmpExpr: Expression = {
AddExpr,
<left:CmpExpr> "==" <right:AddExpr> => Expression::BinaryOp { left: Box::new(left), op: BinaryOpKind::Eq, right: Box::new(right) },
<left:CmpExpr> "!=" <right:AddExpr> => Expression::BinaryOp { left: Box::new(left), op: BinaryOpKind::Neq, right: Box::new(right) },
<left:CmpExpr> "<=" <right:AddExpr> => Expression::BinaryOp { left: Box::new(left), op: BinaryOpKind::Lte, right: Box::new(right) },
<left:CmpExpr> ">=" <right:AddExpr> => Expression::BinaryOp { left: Box::new(left), op: BinaryOpKind::Gte, right: Box::new(right) },
<left:CmpExpr> "<" <right:AddExpr> => Expression::BinaryOp { left: Box::new(left), op: BinaryOpKind::Lt, right: Box::new(right) },
<left:CmpExpr> ">" <right:AddExpr> => Expression::BinaryOp { left: Box::new(left), op: BinaryOpKind::Gt, right: Box::new(right) },
};
AddExpr: Expression = {
MulExpr,
<left:AddExpr> "+" <right:MulExpr> => Expression::BinaryOp { left: Box::new(left), op: BinaryOpKind::Add, right: Box::new(right) },
<left:AddExpr> "-" <right:MulExpr> => Expression::BinaryOp { left: Box::new(left), op: BinaryOpKind::Sub, right: Box::new(right) },
};
MulExpr: Expression = {
UnExpr,
<left:MulExpr> "*" <right:UnExpr> => Expression::BinaryOp { left: Box::new(left), op: BinaryOpKind::Mul, right: Box::new(right) },
<left:MulExpr> "/" <right:UnExpr> => Expression::BinaryOp { left: Box::new(left), op: BinaryOpKind::Div, right: Box::new(right) },
};
UnExpr: Expression = {
AtomExpr,
"!" <e:UnExpr> => Expression::UnaryOp { op: UnaryOpKind::Not, operand: Box::new(e) },
"-" <e:UnExpr> => Expression::UnaryOp { op: UnaryOpKind::Neg, operand: Box::new(e) },
};
AtomExpr: Expression = {
<s:STRING> => Expression::Literal(LiteralValue::String(s)),
<n:NUM> => Expression::Literal(LiteralValue::Number(n)),
"True" => Expression::Literal(LiteralValue::Bool(true)),
"False" => Expression::Literal(LiteralValue::Bool(false)),
<name:Ident> "(" <args:Comma<Expr>?> ")" => Expression::FunctionCall { name, args: args.unwrap_or_default() },
"$" <name:Ident> => Expression::SpecialVar { name, is_negative: false },
"$" "-" <name:Ident> => Expression::SpecialVar { name, is_negative: true },
"@" <name:Ident> => Expression::AutoVarExpr { variable: Box::new(Expression::SpecialVar { name, is_negative: false }) },
<name:Ident> => Expression::Identifier(name),
"(" <e:Expr> ")" => e,
};
Comma<T>: Vec<T> = {
<v:(<T> ",")*> <e:T> => {
let mut v = v;
v.push(e);
v
}
};
Stmts: Vec<Statement> = {
<stmts:Stmt+> => stmts,
};
STRING: String = {
<s:r#""[^"]*""#> => s[1..s.len()-1].to_string()
};
NUM: f64 = {
<s:r"[0-9]+(\.[0-9]+)?"> => s.parse::<f64>().unwrap()
};
Ident: String = {
<s:r"[a-zA-Z_][a-zA-Z0-9_]*"> => s.to_string()
};
match {
r"\s+" => {},
"Var",
"If",
"Then",
"Else",
"EndIf",
"For",
"In",
"EndFor",
"True",
"False",
"==",
"!=",
"<=",
">=",
"&&",
"||",
"!",
"$",
"@",
"(",
")",
",",
"+",
"-",
"*",
"/",
"<",
">",
"=",
"assigned",
} else {
r#""[^"]*""#,
r"[0-9]+(\.[0-9]+)?",
r"[a-zA-Z_][a-zA-Z0-9_]*",
}

File diff suppressed because it is too large Load diff

View file

@ -1,24 +1,21 @@
use crate::xml::ast::nodes::{AssignmentType, BinaryOpKind, Expression, LiteralValue, Program, Statement, UnaryOpKind}; use crate::xml::ast::nodes::{
use crate::xml::error::{ParseError, ParserResult}; AssignmentType, BinaryOpKind, Expression, LiteralValue, Program, Statement, UnaryOpKind,
};
use crate::xml::error::ParserResult;
// The LALRPOP-generated parser will be in this module #[allow(clippy::all)]
pub mod grammar; pub mod grammar;
pub use grammar::ProgramParser; use grammar::ProgramParser;
// Wrapper around the LALRPOP parser to provide a cleaner interface
pub struct ScriptParser; pub struct ScriptParser;
impl ScriptParser { impl ScriptParser {
/// Parse a script string into an AST
pub fn parse(source: &str) -> ParserResult<Program> { pub fn parse(source: &str) -> ParserResult<Program> {
let parser = grammar::ProgramParser::new(); let parser = ProgramParser::new();
match parser.parse(source) { match parser.parse(source) {
Ok(_) => { Ok(program) => Ok(program),
// For now, just return an empty program since our grammar returns unit Err(e) => Err(crate::xml::error::ParseError::from_lalrpop_error(source, e)),
Ok(Program { statements: vec![] })
},
Err(e) => Err(ParseError::from_lalrpop_error(source, e)),
} }
} }
} }
@ -26,7 +23,7 @@ impl ScriptParser {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[test] #[test]
fn test_empty() { fn test_empty() {
let result = ScriptParser::parse(""); let result = ScriptParser::parse("");
@ -34,4 +31,148 @@ mod tests {
let program = result.unwrap(); let program = result.unwrap();
assert_eq!(program.statements.len(), 0); assert_eq!(program.statements.len(), 0);
} }
}
#[test]
fn test_whitespace() {
let result = ScriptParser::parse(" \n\n \t ");
assert!(result.is_ok());
let program = result.unwrap();
assert_eq!(program.statements.len(), 0);
}
#[test]
fn test_var_decl_number() {
let result = ScriptParser::parse("Var x = 5");
assert!(result.is_ok(), "parse failed: {:?}", result.err());
let program = result.unwrap();
assert_eq!(program.statements.len(), 1);
if let Statement::VarDecl { name, value, assignment_type } = &program.statements[0] {
assert_eq!(name, "x");
assert_eq!(*assignment_type, AssignmentType::Equals);
if let Some(Expression::Literal(LiteralValue::Number(n))) = value {
assert_eq!(*n, 5.0);
} else {
panic!("Expected number literal");
}
} else {
panic!("Expected VarDecl");
}
}
#[test]
fn test_var_decl_string() {
let result = ScriptParser::parse(r#"Var x = "hello""#);
assert!(result.is_ok(), "parse failed: {:?}", result.err());
let program = result.unwrap();
if let Statement::VarDecl { name, value, .. } = &program.statements[0] {
assert_eq!(name, "x");
if let Some(Expression::Literal(LiteralValue::String(s))) = value {
assert_eq!(s, "hello");
} else {
panic!("Expected string literal");
}
}
}
#[test]
fn test_var_decl_not_assigned() {
let result = ScriptParser::parse("Var x !assigned");
assert!(result.is_ok(), "parse failed: {:?}", result.err());
let program = result.unwrap();
if let Statement::VarDecl { name, value, assignment_type } = &program.statements[0] {
assert_eq!(name, "x");
assert!(value.is_none());
assert_eq!(*assignment_type, AssignmentType::NotAssigned);
}
}
#[test]
fn test_if_stmt_simple() {
let result = ScriptParser::parse("If x == 5 Then Var y = 10 EndIf");
assert!(result.is_ok(), "parse failed: {:?}", result.err());
}
#[test]
fn test_if_with_else() {
let result = ScriptParser::parse("If x == 5 Then Var y = 10 Else Var y = 20 EndIf");
assert!(result.is_ok(), "parse failed: {:?}", result.err());
let program = result.unwrap();
if let Statement::IfStmt { else_branch, .. } = &program.statements[0] {
assert!(!else_branch.is_empty());
}
}
#[test]
fn test_special_var() {
let result = ScriptParser::parse("Var x = $myVar");
assert!(result.is_ok(), "parse failed: {:?}", result.err());
let program = result.unwrap();
if let Statement::VarDecl { value, .. } = &program.statements[0] {
if let Some(Expression::SpecialVar { name, is_negative }) = value {
assert_eq!(name, "myVar");
assert!(!is_negative);
} else {
panic!("Expected SpecialVar");
}
}
}
#[test]
fn test_special_var_negative() {
let result = ScriptParser::parse("Var x = $-myVar");
assert!(result.is_ok(), "parse failed: {:?}", result.err());
let program = result.unwrap();
if let Statement::VarDecl { value, .. } = &program.statements[0] {
if let Some(Expression::SpecialVar { name, is_negative }) = value {
assert_eq!(name, "myVar");
assert!(is_negative);
} else {
panic!("Expected SpecialVar");
}
}
}
#[test]
fn test_auto_var() {
let result = ScriptParser::parse("Var x = @myVar");
assert!(result.is_ok(), "parse failed: {:?}", result.err());
}
#[test]
fn test_function_call() {
let result = ScriptParser::parse("Var x = myFunc(1, 2)");
assert!(result.is_ok(), "parse failed: {:?}", result.err());
}
#[test]
fn test_arithmetic() {
let result = ScriptParser::parse("Var x = 1 + 2 * 3");
assert!(result.is_ok(), "parse failed: {:?}", result.err());
}
#[test]
fn test_comparison() {
let result = ScriptParser::parse("If x != 5 Then Var y = 10 EndIf");
assert!(result.is_ok(), "parse failed: {:?}", result.err());
}
#[test]
fn test_for_loop() {
let result = ScriptParser::parse("For i In items Var x = i EndFor");
assert!(result.is_ok(), "parse failed: {:?}", result.err());
}
#[test]
fn test_boolean_literals() {
let result = ScriptParser::parse("Var x = True");
assert!(result.is_ok(), "parse failed: {:?}", result.err());
let result2 = ScriptParser::parse("Var x = False");
assert!(result2.is_ok(), "parse failed: {:?}", result2.err());
}
#[test]
fn test_parenthesized_expression() {
let result = ScriptParser::parse("Var x = (1 + 2) * 3");
assert!(result.is_ok(), "parse failed: {:?}", result.err());
}
}

View file

@ -1,59 +0,0 @@
use crate::xml::ast::nodes::{AssignmentType, BinaryOpKind, Expression, LiteralValue, Program, Statement, UnaryOpKind};
use crate::xml::error::{ParseError, ParserResult};
// The LALRPOP-generated parser will be in this module
mod grammar;
pub use grammar::ProgramParser;
// Wrapper around the LALRPOP parser to provide a cleaner interface
pub struct ScriptParser;
impl ScriptParser {
/// Parse a script string into an AST
pub fn parse(source: &str) -> ParserResult<Program> {
let parser = grammar::ProgramParser::new();
match parser.parse(source) {
Ok(program) => Ok(program),
Err(e) => Err(ParseError::from_lalrpop_error(source, e)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty() {
let result = ScriptParser::parse("");
assert!(result.is_ok());
let program = result.unwrap();
assert_eq!(program.statements.len(), 0);
}
#[test]
fn test_simple_var_decl() {
let result = ScriptParser::parse("Var x = 5");
assert!(result.is_ok());
let program = result.unwrap();
assert_eq!(program.statements.len(), 1);
if let Statement::VarDecl { name, value, assignment_type } = &program.statements[0] {
assert_eq!(name, "x");
assert_eq!(assignment_type, &AssignmentType::Equals);
if let Some(Expression::Literal(LiteralValue::Number(val))) = value {
assert_eq!(*val, 5.0);
} else {
panic!("Expected number literal");
}
} else {
panic!("Expected VarDecl statement");
}
}
#[test]
fn test_if_stmt() {
let result = ScriptParser::parse("If x = 5 Then Var y = 10 EndIf");
assert!(result.is_ok());
}
}

636
src/xml/vm/mod.rs Normal file
View file

@ -0,0 +1,636 @@
use std::collections::HashMap;
use crate::xml::ast::nodes::{
AssignmentType, BinaryOpKind, Expression, LiteralValue, Program, Statement, UnaryOpKind,
};
#[derive(Clone, Debug, PartialEq)]
pub enum Value {
Number(f64),
String(String),
Bool(bool),
Null,
}
impl Value {
pub fn to_f64(&self) -> Option<f64> {
match self {
Value::Number(n) => Some(*n),
Value::String(s) => s.parse::<f64>().ok(),
Value::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
Value::Null => None,
}
}
pub fn to_bool(&self) -> bool {
match self {
Value::Number(n) => *n != 0.0,
Value::String(s) => !s.is_empty(),
Value::Bool(b) => *b,
Value::Null => false,
}
}
pub fn to_string_val(&self) -> String {
match self {
Value::Number(n) => {
if n.fract() == 0.0 {
format!("{}", *n as i64)
} else {
format!("{}", n)
}
}
Value::String(s) => s.clone(),
Value::Bool(b) => b.to_string(),
Value::Null => "null".to_string(),
}
}
}
impl std::fmt::Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Value::Number(n) => write!(f, "{}", n),
Value::String(s) => write!(f, "{}", s),
Value::Bool(b) => write!(f, "{}", b),
Value::Null => write!(f, "null"),
}
}
}
impl From<f64> for Value {
fn from(n: f64) -> Self { Value::Number(n) }
}
impl From<String> for Value {
fn from(s: String) -> Self { Value::String(s) }
}
impl From<&str> for Value {
fn from(s: &str) -> Self { Value::String(s.to_string()) }
}
impl From<bool> for Value {
fn from(b: bool) -> Self { Value::Bool(b) }
}
pub type Environment = HashMap<String, Value>;
#[derive(Debug)]
pub enum VmError {
UndefinedVariable(String),
TypeError(String),
DivisionByZero,
UndefinedFunction(String),
NotIterable(Value),
RuntimeError(String),
}
impl std::fmt::Display for VmError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
VmError::UndefinedVariable(name) => write!(f, "Undefined variable: {}", name),
VmError::TypeError(msg) => write!(f, "Type error: {}", msg),
VmError::DivisionByZero => write!(f, "Division by zero"),
VmError::UndefinedFunction(name) => write!(f, "Undefined function: {}", name),
VmError::NotIterable(v) => write!(f, "Not iterable: {:?}", v),
VmError::RuntimeError(msg) => write!(f, "Runtime error: {}", msg),
}
}
}
impl std::error::Error for VmError {}
pub type VmResult<T> = Result<T, VmError>;
pub type NativeFn = fn(&[Value]) -> VmResult<Value>;
fn builtin_abs(args: &[Value]) -> VmResult<Value> {
match args.first() {
Some(Value::Number(n)) => Ok(Value::Number(n.abs())),
_ => Err(VmError::TypeError("abs expects a number".into())),
}
}
fn builtin_min(args: &[Value]) -> VmResult<Value> {
if args.len() < 2 {
return Err(VmError::TypeError("min expects at least 2 arguments".into()));
}
let nums: VmResult<Vec<f64>> = args.iter().map(|v| v.to_f64().ok_or_else(|| VmError::TypeError("min expects numbers".into()))).collect();
let nums = nums?;
Ok(Value::Number(nums.iter().cloned().fold(f64::INFINITY, f64::min)))
}
fn builtin_max(args: &[Value]) -> VmResult<Value> {
if args.len() < 2 {
return Err(VmError::TypeError("max expects at least 2 arguments".into()));
}
let nums: VmResult<Vec<f64>> = args.iter().map(|v| v.to_f64().ok_or_else(|| VmError::TypeError("max expects numbers".into()))).collect();
let nums = nums?;
Ok(Value::Number(nums.iter().cloned().fold(f64::NEG_INFINITY, f64::max)))
}
fn builtin_len(args: &[Value]) -> VmResult<Value> {
match args.first() {
Some(Value::String(s)) => Ok(Value::Number(s.len() as f64)),
_ => Err(VmError::TypeError("len expects a string".into())),
}
}
fn builtin_str(args: &[Value]) -> VmResult<Value> {
match args.first() {
Some(v) => Ok(Value::String(v.to_string_val())),
None => Err(VmError::TypeError("str expects 1 argument".into())),
}
}
fn builtin_num(args: &[Value]) -> VmResult<Value> {
match args.first() {
Some(Value::String(s)) => s.parse::<f64>().map(Value::Number).map_err(|_| VmError::TypeError("num: invalid number string".into())),
Some(Value::Number(n)) => Ok(Value::Number(*n)),
Some(Value::Bool(b)) => Ok(Value::Number(if *b { 1.0 } else { 0.0 })),
_ => Err(VmError::TypeError("num expects a string or number".into())),
}
}
fn builtin_int(args: &[Value]) -> VmResult<Value> {
match args.first() {
Some(Value::Number(n)) => Ok(Value::Number(n.trunc())),
Some(Value::String(s)) => s.parse::<f64>().map(|n| Value::Number(n.trunc())).map_err(|_| VmError::TypeError("int: invalid number string".into())),
_ => Err(VmError::TypeError("int expects a number or string".into())),
}
}
pub struct Vm {
globals: Environment,
functions: HashMap<String, NativeFn>,
}
impl Vm {
pub fn new() -> Self {
let mut vm = Vm {
globals: Environment::new(),
functions: HashMap::new(),
};
vm.register_builtins();
vm
}
pub fn with_globals(globals: Environment) -> Self {
let mut vm = Vm {
globals,
functions: HashMap::new(),
};
vm.register_builtins();
vm
}
pub fn set_global(&mut self, name: impl Into<String>, value: Value) {
self.globals.insert(name.into(), value);
}
pub fn get_global(&self, name: &str) -> Option<&Value> {
self.globals.get(name)
}
pub fn register_function(&mut self, name: impl Into<String>, func: NativeFn) {
self.functions.insert(name.into(), func);
}
fn register_builtins(&mut self) {
self.register_function("abs", builtin_abs);
self.register_function("min", builtin_min);
self.register_function("max", builtin_max);
self.register_function("len", builtin_len);
self.register_function("str", builtin_str);
self.register_function("num", builtin_num);
self.register_function("int", builtin_int);
}
pub fn run(&mut self, program: &Program) -> VmResult<()> {
for stmt in &program.statements {
self.exec_statement(stmt)?;
}
Ok(())
}
pub fn run_and_get_result(&mut self, program: &Program) -> VmResult<Value> {
let mut last_value = Value::Null;
for stmt in &program.statements {
if let Some(v) = self.exec_statement_for_value(stmt)? {
last_value = v;
}
}
Ok(last_value)
}
fn eval_expr(&self, expr: &Expression) -> VmResult<Value> {
match expr {
Expression::Literal(lit) => Ok(match lit {
LiteralValue::String(s) => Value::String(s.clone()),
LiteralValue::Number(n) => Value::Number(*n),
LiteralValue::Bool(b) => Value::Bool(*b),
}),
Expression::Identifier(name) => {
self.globals.get(name).cloned()
.ok_or_else(|| VmError::UndefinedVariable(name.clone()))
}
Expression::BinaryOp { left, op, right } => {
let lval = self.eval_expr(left)?;
let rval = self.eval_expr(right)?;
Self::eval_binary_op(*op, lval, rval)
}
Expression::UnaryOp { op, operand } => {
let val = self.eval_expr(operand)?;
match op {
UnaryOpKind::Neg => {
val.to_f64()
.map(|n| Value::Number(-n))
.ok_or_else(|| VmError::TypeError("Cannot negate non-number".into()))
}
UnaryOpKind::Not => Ok(Value::Bool(!val.to_bool())),
}
}
Expression::SpecialVar { name, is_negative } => {
let lookup = format!("${}", name);
let val = self.globals.get(&lookup).cloned()
.ok_or_else(|| VmError::UndefinedVariable(format!("${}", name)))?;
if *is_negative {
val.to_f64()
.map(|n| Value::Number(-n))
.ok_or_else(|| VmError::TypeError("Cannot negate non-number special var".into()))
} else {
Ok(val)
}
}
Expression::AutoVarExpr { variable } => {
self.eval_expr(variable)
}
Expression::FunctionCall { name, args } => {
let evaluated_args: VmResult<Vec<Value>> = args.iter().map(|a| self.eval_expr(a)).collect();
let evaluated_args = evaluated_args?;
if let Some(func) = self.functions.get(name) {
func(&evaluated_args)
} else {
Err(VmError::UndefinedFunction(name.clone()))
}
}
Expression::ArrayAccess { name, indices } => {
let base = self.globals.get(name).cloned()
.ok_or_else(|| VmError::UndefinedVariable(name.clone()))?;
let idx_vals: VmResult<Vec<Value>> = indices.iter().map(|i| self.eval_expr(i)).collect();
let idx_vals = idx_vals?;
match (&base, idx_vals.first()) {
(Value::String(s), Some(Value::Number(idx))) => {
let i = *idx as usize;
s.chars().nth(i)
.map(|c| Value::String(c.to_string()))
.ok_or_else(|| VmError::RuntimeError(format!("String index {} out of bounds", i)))
}
_ => Err(VmError::TypeError("Array access not supported for this type".into()))
}
}
Expression::FlagExpr { args } => {
let evaluated_args: VmResult<Vec<Value>> = args.iter().map(|a| self.eval_expr(a)).collect();
let evaluated_args = evaluated_args?;
if let Some(func) = self.functions.get("flag") {
func(&evaluated_args)
} else {
Ok(Value::Bool(true))
}
}
Expression::StringConcat { parts } => {
let result: VmResult<String> = parts.iter().map(|p| {
self.eval_expr(p).map(|v| v.to_string_val())
}).collect();
Ok(Value::String(result?))
}
}
}
fn eval_binary_op(op: BinaryOpKind, left: Value, right: Value) -> VmResult<Value> {
match op {
BinaryOpKind::Add => {
match (&left, &right) {
(Value::Number(l), Value::Number(r)) => Ok(Value::Number(l + r)),
(Value::String(l), Value::String(r)) => Ok(Value::String(format!("{}{}", l, r))),
(Value::String(l), r) => Ok(Value::String(format!("{}{}", l, r.to_string_val()))),
(l, Value::String(r)) => Ok(Value::String(format!("{}{}", l.to_string_val(), r))),
_ => Err(VmError::TypeError(format!("Cannot add {:?} and {:?}", left, right))),
}
}
BinaryOpKind::Sub => {
let l = left.to_f64().ok_or_else(|| VmError::TypeError("Cannot subtract non-numbers".into()))?;
let r = right.to_f64().ok_or_else(|| VmError::TypeError("Cannot subtract non-numbers".into()))?;
Ok(Value::Number(l - r))
}
BinaryOpKind::Mul => {
let l = left.to_f64().ok_or_else(|| VmError::TypeError("Cannot multiply non-numbers".into()))?;
let r = right.to_f64().ok_or_else(|| VmError::TypeError("Cannot multiply non-numbers".into()))?;
Ok(Value::Number(l * r))
}
BinaryOpKind::Div => {
let l = left.to_f64().ok_or_else(|| VmError::TypeError("Cannot divide non-numbers".into()))?;
let r = right.to_f64().ok_or_else(|| VmError::TypeError("Cannot divide non-numbers".into()))?;
if r == 0.0 {
Err(VmError::DivisionByZero)
} else {
Ok(Value::Number(l / r))
}
}
BinaryOpKind::Eq => Ok(Value::Bool(left == right)),
BinaryOpKind::Neq => Ok(Value::Bool(left != right)),
BinaryOpKind::Lt => {
let l = left.to_f64().ok_or_else(|| VmError::TypeError("Cannot compare non-numbers".into()))?;
let r = right.to_f64().ok_or_else(|| VmError::TypeError("Cannot compare non-numbers".into()))?;
Ok(Value::Bool(l < r))
}
BinaryOpKind::Gt => {
let l = left.to_f64().ok_or_else(|| VmError::TypeError("Cannot compare non-numbers".into()))?;
let r = right.to_f64().ok_or_else(|| VmError::TypeError("Cannot compare non-numbers".into()))?;
Ok(Value::Bool(l > r))
}
BinaryOpKind::Lte => {
let l = left.to_f64().ok_or_else(|| VmError::TypeError("Cannot compare non-numbers".into()))?;
let r = right.to_f64().ok_or_else(|| VmError::TypeError("Cannot compare non-numbers".into()))?;
Ok(Value::Bool(l <= r))
}
BinaryOpKind::Gte => {
let l = left.to_f64().ok_or_else(|| VmError::TypeError("Cannot compare non-numbers".into()))?;
let r = right.to_f64().ok_or_else(|| VmError::TypeError("Cannot compare non-numbers".into()))?;
Ok(Value::Bool(l >= r))
}
}
}
fn exec_statement(&mut self, stmt: &Statement) -> VmResult<()> {
match stmt {
Statement::VarDecl { name, value, assignment_type } => {
match assignment_type {
AssignmentType::Equals => {
if let Some(expr) = value {
let val = self.eval_expr(expr)?;
self.globals.insert(name.clone(), val);
} else {
self.globals.insert(name.clone(), Value::Null);
}
}
AssignmentType::NotAssigned => {
self.globals.insert(name.clone(), Value::Null);
}
}
Ok(())
}
Statement::IfStmt { condition, then_branch, else_branch } => {
let cond_val = self.eval_expr(condition)?;
if cond_val.to_bool() {
for s in then_branch {
self.exec_statement(s)?;
}
} else {
for s in else_branch {
self.exec_statement(s)?;
}
}
Ok(())
}
Statement::ForStmt { variable, iterable, body } => {
let iter_val = self.eval_expr(iterable)?;
match iter_val {
Value::Number(n) => {
for i in 0..(n as i64) {
self.globals.insert(variable.clone(), Value::Number(i as f64));
for s in body {
self.exec_statement(s)?;
}
}
}
_ => {
return Err(VmError::NotIterable(iter_val));
}
}
Ok(())
}
Statement::ExprStmt { expression } => {
self.eval_expr(expression)?;
Ok(())
}
}
}
fn exec_statement_for_value(&mut self, stmt: &Statement) -> VmResult<Option<Value>> {
match stmt {
Statement::VarDecl { name, value, assignment_type } => {
match assignment_type {
AssignmentType::Equals => {
if let Some(expr) = value {
let val = self.eval_expr(expr)?;
self.globals.insert(name.clone(), val.clone());
Ok(Some(val))
} else {
self.globals.insert(name.clone(), Value::Null);
Ok(None)
}
}
AssignmentType::NotAssigned => {
self.globals.insert(name.clone(), Value::Null);
Ok(None)
}
}
}
Statement::ExprStmt { expression } => {
let val = self.eval_expr(expression)?;
Ok(Some(val))
}
_ => {
self.exec_statement(stmt)?;
Ok(None)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn parse_and_run(source: &str) -> VmResult<Value> {
let program = crate::xml::parser::ScriptParser::parse(source)
.map_err(|e| VmError::RuntimeError(e.to_string()))?;
let mut vm = Vm::new();
vm.run_and_get_result(&program)
}
fn parse_and_run_with_globals(source: &str, globals: Environment) -> VmResult<Value> {
let program = crate::xml::parser::ScriptParser::parse(source)
.map_err(|e| VmError::RuntimeError(e.to_string()))?;
let mut vm = Vm::with_globals(globals);
vm.run_and_get_result(&program)
}
#[test]
fn test_var_decl_number() {
let result = parse_and_run("Var x = 42");
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Number(42.0));
}
#[test]
fn test_var_decl_string() {
let result = parse_and_run(r#"Var x = "hello""#);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::String("hello".to_string()));
}
#[test]
fn test_arithmetic() {
let result = parse_and_run("Var x = 2 + 3 * 4");
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Number(14.0));
}
#[test]
fn test_arithmetic_with_parens() {
let result = parse_and_run("Var x = (2 + 3) * 4");
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Number(20.0));
}
#[test]
fn test_comparison_eq() {
let mut globals = Environment::new();
globals.insert("x".to_string(), Value::Number(5.0));
let program = crate::xml::parser::ScriptParser::parse("If x == 5 Then Var y = 1 Else Var y = 0 EndIf")
.map_err(|e| VmError::RuntimeError(e.to_string())).unwrap();
let mut vm = Vm::with_globals(globals);
vm.run(&program).unwrap();
assert_eq!(*vm.get_global("y").unwrap(), Value::Number(1.0));
}
#[test]
fn test_comparison_neq() {
let mut globals = Environment::new();
globals.insert("x".to_string(), Value::Number(3.0));
let program = crate::xml::parser::ScriptParser::parse("If x != 5 Then Var y = 1 Else Var y = 0 EndIf")
.map_err(|e| VmError::RuntimeError(e.to_string())).unwrap();
let mut vm = Vm::with_globals(globals);
vm.run(&program).unwrap();
assert_eq!(*vm.get_global("y").unwrap(), Value::Number(1.0));
}
#[test]
fn test_if_else_false() {
let mut globals = Environment::new();
globals.insert("x".to_string(), Value::Number(10.0));
let program = crate::xml::parser::ScriptParser::parse("If x == 5 Then Var y = 1 Else Var y = 2 EndIf")
.map_err(|e| VmError::RuntimeError(e.to_string())).unwrap();
let mut vm = Vm::with_globals(globals);
vm.run(&program).unwrap();
assert_eq!(*vm.get_global("y").unwrap(), Value::Number(2.0));
}
#[test]
fn test_bool_literals() {
let result = parse_and_run("Var x = True");
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Bool(true));
let result = parse_and_run("Var x = False");
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Bool(false));
}
#[test]
fn test_string_concat_via_add() {
let result = parse_and_run(r#"Var x = "hello" + " world""#);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::String("hello world".to_string()));
}
#[test]
fn test_negation() {
let result = parse_and_run("Var x = -5");
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Number(-5.0));
}
#[test]
fn test_not() {
let mut globals = Environment::new();
globals.insert("x".to_string(), Value::Bool(true));
let result = parse_and_run_with_globals("Var y = !x", globals);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Bool(false));
}
#[test]
fn test_special_var() {
let mut globals = Environment::new();
globals.insert("$count".to_string(), Value::Number(10.0));
let result = parse_and_run_with_globals("Var x = $count", globals);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Number(10.0));
}
#[test]
fn test_special_var_negative() {
let mut globals = Environment::new();
globals.insert("$count".to_string(), Value::Number(10.0));
let result = parse_and_run_with_globals("Var x = $-count", globals);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Number(-10.0));
}
#[test]
fn test_builtin_abs() {
let result = parse_and_run("Var x = abs(-5)");
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Number(5.0));
}
#[test]
fn test_builtin_len() {
let result = parse_and_run(r#"Var x = len("hello")"#);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Number(5.0));
}
#[test]
fn test_for_loop() {
let program = crate::xml::parser::ScriptParser::parse("For i In 3 Var x = i EndFor")
.map_err(|e| VmError::RuntimeError(e.to_string())).unwrap();
let mut vm = Vm::new();
vm.run(&program).unwrap();
let val = vm.get_global("x").unwrap();
assert_eq!(*val, Value::Number(2.0));
}
#[test]
fn test_division() {
let result = parse_and_run("Var x = 10 / 2");
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Number(5.0));
}
#[test]
fn test_division_by_zero() {
let result = parse_and_run("Var x = 10 / 0");
assert!(result.is_err());
}
#[test]
fn test_undefined_variable() {
let result = parse_and_run("Var x = unknown_var");
assert!(result.is_err());
}
}

3
uniffi-bindgen.rs Normal file
View file

@ -0,0 +1,3 @@
fn main() {
uniffi::uniffi_bindgen_main()
}