feat: add debug print, open & events

- change: equality
- update codegen

Signed-off-by: Pakin <pakin.t@forth.co.th>
This commit is contained in:
Pakin 2026-05-25 09:56:51 +07:00
parent f9ae59c77f
commit 41cc051b71
10 changed files with 245 additions and 3506 deletions

View file

@ -20,6 +20,16 @@ pub enum Statement {
iterable: Expression, iterable: Expression,
body: Vec<Statement>, body: Vec<Statement>,
}, },
DebugVar {
name: String,
},
Open {
filename: String,
},
Call {
name: String,
args: Vec<Expression>,
},
ExprStmt { ExprStmt {
expression: Expression, expression: Expression,
}, },
@ -80,6 +90,8 @@ pub enum BinaryOpKind {
Gt, Gt,
Lte, Lte,
Gte, Gte,
And,
Or,
} }
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]

View file

@ -66,6 +66,8 @@ impl CodeGen for JavaCodeGen {
BinaryOpKind::Gt => ">", BinaryOpKind::Gt => ">",
BinaryOpKind::Lte => "<=", BinaryOpKind::Lte => "<=",
BinaryOpKind::Gte => ">=", BinaryOpKind::Gte => ">=",
BinaryOpKind::And => "&&",
BinaryOpKind::Or => "||",
}; };
format!("({} {} {})", left, op_str, right) format!("({} {} {})", left, op_str, right)
} }
@ -141,4 +143,20 @@ impl CodeGen for JavaCodeGen {
fn generate_string_concat(&self, parts: &[String]) -> String { fn generate_string_concat(&self, parts: &[String]) -> String {
parts.join(" + ") parts.join(" + ")
} }
fn generate_debug_var(&self, name: &str) -> String {
format!("{}System.out.println(\"{} = \" + {});", self.indent_str(), name, name)
}
fn generate_open(&self, filename: &str) -> String {
format!("{}// open(\"{}\")", self.indent_str(), filename)
}
fn generate_call(&self, name: &str, args: &[String]) -> String {
format!("{}{}({});", self.indent_str(), name, args.join(", "))
}
fn generate_expr_stmt(&self, expr: &str) -> String {
format!("{}{};", self.indent_str(), expr)
}
} }

View file

@ -55,6 +55,8 @@ impl CodeGen for JavaScriptCodeGen {
BinaryOpKind::Gt => ">", BinaryOpKind::Gt => ">",
BinaryOpKind::Lte => "<=", BinaryOpKind::Lte => "<=",
BinaryOpKind::Gte => ">=", BinaryOpKind::Gte => ">=",
BinaryOpKind::And => "&&",
BinaryOpKind::Or => "||",
}; };
format!("({} {} {})", left, op_str, right) format!("({} {} {})", left, op_str, right)
} }
@ -130,4 +132,20 @@ impl CodeGen for JavaScriptCodeGen {
fn generate_string_concat(&self, parts: &[String]) -> String { fn generate_string_concat(&self, parts: &[String]) -> String {
parts.join(" + ") parts.join(" + ")
} }
fn generate_debug_var(&self, name: &str) -> String {
format!("{}console.log(\"{} =\", {});", self.indent_str(), name, name)
}
fn generate_open(&self, filename: &str) -> String {
format!("{}// open(\"{}\")", self.indent_str(), filename)
}
fn generate_call(&self, name: &str, args: &[String]) -> String {
format!("{}{}({});", self.indent_str(), name, args.join(", "))
}
fn generate_expr_stmt(&self, expr: &str) -> String {
format!("{}{};", self.indent_str(), expr)
}
} }

View file

@ -23,6 +23,10 @@ pub trait CodeGen {
fn generate_if_stmt(&self, condition: &str, then_body: &[String], else_body: &[String]) -> String; fn generate_if_stmt(&self, condition: &str, then_body: &[String], else_body: &[String]) -> String;
fn generate_for_stmt(&self, variable: &str, iterable: &str, body: &[String]) -> String; fn generate_for_stmt(&self, variable: &str, iterable: &str, body: &[String]) -> String;
fn generate_string_concat(&self, parts: &[String]) -> String; fn generate_string_concat(&self, parts: &[String]) -> String;
fn generate_debug_var(&self, name: &str) -> String;
fn generate_open(&self, filename: &str) -> String;
fn generate_call(&self, name: &str, args: &[String]) -> String;
fn generate_expr_stmt(&self, expr: &str) -> String;
} }
fn walk_expression(codegen: &dyn CodeGen, expr: &Expression) -> String { fn walk_expression(codegen: &dyn CodeGen, expr: &Expression) -> String {
@ -81,7 +85,13 @@ fn walk_statement(codegen: &dyn CodeGen, stmt: &Statement) -> String {
} }
Statement::ExprStmt { expression } => { Statement::ExprStmt { expression } => {
let e = codegen.generate_expression(expression); let e = codegen.generate_expression(expression);
codegen.generate_var_decl("_", Some(&e), AssignmentType::Equals) codegen.generate_expr_stmt(&e)
}
Statement::DebugVar { name } => codegen.generate_debug_var(name),
Statement::Open { filename } => codegen.generate_open(filename),
Statement::Call { name, args } => {
let rendered_args: Vec<String> = args.iter().map(|a| codegen.generate_expression(a)).collect();
codegen.generate_call(name, &rendered_args)
} }
} }
} }

View file

@ -55,6 +55,8 @@ impl CodeGen for PythonCodeGen {
BinaryOpKind::Gt => ">", BinaryOpKind::Gt => ">",
BinaryOpKind::Lte => "<=", BinaryOpKind::Lte => "<=",
BinaryOpKind::Gte => ">=", BinaryOpKind::Gte => ">=",
BinaryOpKind::And => "and",
BinaryOpKind::Or => "or",
}; };
format!("({} {} {})", left, op_str, right) format!("({} {} {})", left, op_str, right)
} }
@ -134,4 +136,20 @@ impl CodeGen for PythonCodeGen {
fn generate_string_concat(&self, parts: &[String]) -> String { fn generate_string_concat(&self, parts: &[String]) -> String {
parts.join(" + ") parts.join(" + ")
} }
fn generate_debug_var(&self, name: &str) -> String {
format!("{}print(\"{} =\", {})", self.indent_str(), name, name)
}
fn generate_open(&self, filename: &str) -> String {
format!("{}# open(\"{}\")", self.indent_str(), filename)
}
fn generate_call(&self, name: &str, args: &[String]) -> String {
format!("{}{}({})", self.indent_str(), name, args.join(", "))
}
fn generate_expr_stmt(&self, expr: &str) -> String {
format!("{}{}", self.indent_str(), expr)
}
} }

View file

@ -59,6 +59,8 @@ impl CodeGen for RustCodeGen {
BinaryOpKind::Gt => ">", BinaryOpKind::Gt => ">",
BinaryOpKind::Lte => "<=", BinaryOpKind::Lte => "<=",
BinaryOpKind::Gte => ">=", BinaryOpKind::Gte => ">=",
BinaryOpKind::And => "&&",
BinaryOpKind::Or => "||",
}; };
format!("({} {} {})", left, op_str, right) format!("({} {} {})", left, op_str, right)
} }
@ -134,4 +136,20 @@ impl CodeGen for RustCodeGen {
fn generate_string_concat(&self, parts: &[String]) -> String { fn generate_string_concat(&self, parts: &[String]) -> String {
parts.join(" + ") parts.join(" + ")
} }
fn generate_debug_var(&self, name: &str) -> String {
format!("{}println!(\"{} = {{}}\", {});", self.indent_str(), name, name)
}
fn generate_open(&self, filename: &str) -> String {
format!("{}// open(\"{}\")", self.indent_str(), filename)
}
fn generate_call(&self, name: &str, args: &[String]) -> String {
format!("{}{}({});", self.indent_str(), name, args.join(", "))
}
fn generate_expr_stmt(&self, expr: &str) -> String {
format!("{}{};", self.indent_str(), expr)
}
} }

View file

@ -32,6 +32,8 @@ Stmt: Statement = {
iterable: iter, iterable: iter,
body, body,
}, },
"DEBUGVAR" <name:Ident> => Statement::DebugVar { name },
"Open" <filename:STRING> => Statement::Open { filename },
}; };
Expr: Expression = { Expr: Expression = {
@ -42,7 +44,7 @@ OrExpr: Expression = {
AndExpr, AndExpr,
<left:OrExpr> "||" <right:AndExpr> => Expression::BinaryOp { <left:OrExpr> "||" <right:AndExpr> => Expression::BinaryOp {
left: Box::new(left), left: Box::new(left),
op: BinaryOpKind::Add, op: BinaryOpKind::Or,
right: Box::new(right), right: Box::new(right),
}, },
}; };
@ -51,15 +53,14 @@ AndExpr: Expression = {
CmpExpr, CmpExpr,
<left:AndExpr> "&&" <right:CmpExpr> => Expression::BinaryOp { <left:AndExpr> "&&" <right:CmpExpr> => Expression::BinaryOp {
left: Box::new(left), left: Box::new(left),
op: BinaryOpKind::Add, op: BinaryOpKind::And,
right: Box::new(right), right: Box::new(right),
}, },
}; };
CmpExpr: Expression = { CmpExpr: Expression = {
AddExpr, 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::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::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::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::Lt, right: Box::new(right) },
@ -133,8 +134,8 @@ match {
"EndFor", "EndFor",
"True", "True",
"False", "False",
"==", "DEBUGVAR",
"!=", "Open",
"<=", "<=",
">=", ">=",
"&&", "&&",

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,11 @@
use crate::xml::ast::nodes::{
AssignmentType, BinaryOpKind, Expression, LiteralValue, Program, Statement, UnaryOpKind,
};
use crate::xml::error::ParserResult; use crate::xml::error::ParserResult;
#[allow(clippy::all)] #[allow(clippy::all)]
pub mod grammar; pub mod grammar {
include!(concat!(env!("OUT_DIR"), "/xml/parser/grammar.rs"));
}
pub use crate::xml::ast::nodes::{AssignmentType, BinaryOpKind, Expression, LiteralValue, Program, Statement, UnaryOpKind};
use grammar::ProgramParser; use grammar::ProgramParser;
@ -88,13 +89,13 @@ mod tests {
#[test] #[test]
fn test_if_stmt_simple() { fn test_if_stmt_simple() {
let result = ScriptParser::parse("If x == 5 Then Var y = 10 EndIf"); let result = ScriptParser::parse("If x = 5 Then Var y = 10 EndIf");
assert!(result.is_ok(), "parse failed: {:?}", result.err()); assert!(result.is_ok(), "parse failed: {:?}", result.err());
} }
#[test] #[test]
fn test_if_with_else() { fn test_if_with_else() {
let result = ScriptParser::parse("If x == 5 Then Var y = 10 Else Var y = 20 EndIf"); let result = ScriptParser::parse("If x = 5 Then Var y = 10 Else Var y = 20 EndIf");
assert!(result.is_ok(), "parse failed: {:?}", result.err()); assert!(result.is_ok(), "parse failed: {:?}", result.err());
let program = result.unwrap(); let program = result.unwrap();
if let Statement::IfStmt { else_branch, .. } = &program.statements[0] { if let Statement::IfStmt { else_branch, .. } = &program.statements[0] {
@ -152,10 +153,46 @@ mod tests {
#[test] #[test]
fn test_comparison() { fn test_comparison() {
let result = ScriptParser::parse("If x != 5 Then Var y = 10 EndIf"); let result = ScriptParser::parse("If x = 5 Then Var y = 10 EndIf");
assert!(result.is_ok(), "parse failed: {:?}", result.err()); assert!(result.is_ok(), "parse failed: {:?}", result.err());
} }
#[test]
fn test_logical_and() {
let result = ScriptParser::parse("If x = 5 && y = 3 Then Var z = 1 EndIf");
assert!(result.is_ok(), "parse failed: {:?}", result.err());
}
#[test]
fn test_logical_or() {
let result = ScriptParser::parse("If x = 5 || y = 3 Then Var z = 1 EndIf");
assert!(result.is_ok(), "parse failed: {:?}", result.err());
}
#[test]
fn test_debug_var() {
let result = ScriptParser::parse("DEBUGVAR myVar");
assert!(result.is_ok(), "parse failed: {:?}", result.err());
let program = result.unwrap();
if let Statement::DebugVar { name } = &program.statements[0] {
assert_eq!(name, "myVar");
} else {
panic!("Expected DebugVar statement");
}
}
#[test]
fn test_open() {
let result = ScriptParser::parse(r#"Open "somefile.xml""#);
assert!(result.is_ok(), "parse failed: {:?}", result.err());
let program = result.unwrap();
if let Statement::Open { filename } = &program.statements[0] {
assert_eq!(filename, "somefile.xml");
} else {
panic!("Expected Open statement");
}
}
#[test] #[test]
fn test_for_loop() { fn test_for_loop() {
let result = ScriptParser::parse("For i In items Var x = i EndFor"); let result = ScriptParser::parse("For i In items Var x = i EndFor");

View file

@ -279,7 +279,8 @@ impl Vm {
if let Some(func) = self.functions.get(name) { if let Some(func) = self.functions.get(name) {
func(&evaluated_args) func(&evaluated_args)
} else { } else {
Err(VmError::UndefinedFunction(name.clone())) eprintln!("[WARN] Unknown function: {}, skipping", name);
Ok(Value::Null)
} }
} }
@ -370,6 +371,8 @@ impl Vm {
let r = right.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)) Ok(Value::Bool(l >= r))
} }
BinaryOpKind::And => Ok(Value::Bool(left.to_bool() && right.to_bool())),
BinaryOpKind::Or => Ok(Value::Bool(left.to_bool() || right.to_bool())),
} }
} }
@ -425,6 +428,30 @@ impl Vm {
self.eval_expr(expression)?; self.eval_expr(expression)?;
Ok(()) Ok(())
} }
Statement::DebugVar { name } => {
if let Some(val) = self.globals.get(name) {
println!("{} = {}", name, val);
} else if let Some(val) = self.globals.get(&format!("${}", name)) {
println!("${} = {}", name, val);
} else {
println!("{} = <undefined>", name);
}
Ok(())
}
Statement::Open { filename } => {
eprintln!("[WARN] Open \"{}\" - not yet implemented in VM", filename);
Ok(())
}
Statement::Call { 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 {
eprintln!("[WARN] Unknown function: {}, skipping", name);
}
Ok(())
}
} }
} }
@ -452,6 +479,31 @@ impl Vm {
let val = self.eval_expr(expression)?; let val = self.eval_expr(expression)?;
Ok(Some(val)) Ok(Some(val))
} }
Statement::DebugVar { name } => {
if let Some(val) = self.globals.get(name) {
println!("{} = {}", name, val);
} else if let Some(val) = self.globals.get(&format!("${}", name)) {
println!("${} = {}", name, val);
} else {
println!("{} = <undefined>", name);
}
Ok(None)
}
Statement::Open { filename } => {
eprintln!("[WARN] Open \"{}\" - not yet implemented in VM", filename);
Ok(None)
}
Statement::Call { 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) {
let result = func(&evaluated_args)?;
Ok(Some(result))
} else {
eprintln!("[WARN] Unknown function: {}, skipping", name);
Ok(Some(Value::Null))
}
}
_ => { _ => {
self.exec_statement(stmt)?; self.exec_statement(stmt)?;
Ok(None) Ok(None)
@ -510,7 +562,7 @@ mod tests {
fn test_comparison_eq() { fn test_comparison_eq() {
let mut globals = Environment::new(); let mut globals = Environment::new();
globals.insert("x".to_string(), Value::Number(5.0)); 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") 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(); .map_err(|e| VmError::RuntimeError(e.to_string())).unwrap();
let mut vm = Vm::with_globals(globals); let mut vm = Vm::with_globals(globals);
vm.run(&program).unwrap(); vm.run(&program).unwrap();
@ -518,10 +570,10 @@ mod tests {
} }
#[test] #[test]
fn test_comparison_neq() { fn test_comparison_lt() {
let mut globals = Environment::new(); let mut globals = Environment::new();
globals.insert("x".to_string(), Value::Number(3.0)); 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") 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(); .map_err(|e| VmError::RuntimeError(e.to_string())).unwrap();
let mut vm = Vm::with_globals(globals); let mut vm = Vm::with_globals(globals);
vm.run(&program).unwrap(); vm.run(&program).unwrap();
@ -532,7 +584,7 @@ mod tests {
fn test_if_else_false() { fn test_if_else_false() {
let mut globals = Environment::new(); let mut globals = Environment::new();
globals.insert("x".to_string(), Value::Number(10.0)); 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") 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(); .map_err(|e| VmError::RuntimeError(e.to_string())).unwrap();
let mut vm = Vm::with_globals(globals); let mut vm = Vm::with_globals(globals);
vm.run(&program).unwrap(); vm.run(&program).unwrap();
@ -633,4 +685,46 @@ mod tests {
let result = parse_and_run("Var x = unknown_var"); let result = parse_and_run("Var x = unknown_var");
assert!(result.is_err()); assert!(result.is_err());
} }
#[test]
fn test_unknown_function_returns_null() {
let result = parse_and_run("Var x = unknownFunc(1, 2)");
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Null);
}
#[test]
fn test_logical_and() {
let mut globals = Environment::new();
globals.insert("x".to_string(), Value::Bool(true));
globals.insert("y".to_string(), Value::Bool(false));
let program = crate::xml::parser::ScriptParser::parse("If x && y Then Var z = 1 Else Var z = 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("z").unwrap(), Value::Number(0.0));
}
#[test]
fn test_logical_or() {
let mut globals = Environment::new();
globals.insert("x".to_string(), Value::Bool(true));
globals.insert("y".to_string(), Value::Bool(false));
let program = crate::xml::parser::ScriptParser::parse("If x || y Then Var z = 1 Else Var z = 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("z").unwrap(), Value::Number(1.0));
}
#[test]
fn test_debug_var() {
let mut globals = Environment::new();
globals.insert("myVar".to_string(), Value::Number(42.0));
let program = crate::xml::parser::ScriptParser::parse("DEBUGVAR myVar")
.map_err(|e| VmError::RuntimeError(e.to_string())).unwrap();
let mut vm = Vm::with_globals(globals);
let result = vm.run(&program);
assert!(result.is_ok());
}
} }