feat: add codegen, vm executor
Signed-off-by: Pakin <pakin.t@forth.co.th>
This commit is contained in:
parent
8a98f29c9d
commit
f9ae59c77f
15 changed files with 4632 additions and 336 deletions
3
build.rs
Normal file
3
build.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
lalrpop::process_root().unwrap();
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
@ -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(" + ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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(" + ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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(" + ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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(" + ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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};
|
||||||
|
|
@ -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
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
636
src/xml/vm/mod.rs
Normal 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
3
uniffi-bindgen.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
uniffi::uniffi_bindgen_main()
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue