feat: add debug print, open & events
- change: equality - update codegen Signed-off-by: Pakin <pakin.t@forth.co.th>
This commit is contained in:
parent
f9ae59c77f
commit
41cc051b71
10 changed files with 245 additions and 3506 deletions
|
|
@ -20,6 +20,16 @@ pub enum Statement {
|
|||
iterable: Expression,
|
||||
body: Vec<Statement>,
|
||||
},
|
||||
DebugVar {
|
||||
name: String,
|
||||
},
|
||||
Open {
|
||||
filename: String,
|
||||
},
|
||||
Call {
|
||||
name: String,
|
||||
args: Vec<Expression>,
|
||||
},
|
||||
ExprStmt {
|
||||
expression: Expression,
|
||||
},
|
||||
|
|
@ -80,6 +90,8 @@ pub enum BinaryOpKind {
|
|||
Gt,
|
||||
Lte,
|
||||
Gte,
|
||||
And,
|
||||
Or,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
|
|
|
|||
|
|
@ -66,6 +66,8 @@ impl CodeGen for JavaCodeGen {
|
|||
BinaryOpKind::Gt => ">",
|
||||
BinaryOpKind::Lte => "<=",
|
||||
BinaryOpKind::Gte => ">=",
|
||||
BinaryOpKind::And => "&&",
|
||||
BinaryOpKind::Or => "||",
|
||||
};
|
||||
format!("({} {} {})", left, op_str, right)
|
||||
}
|
||||
|
|
@ -141,4 +143,20 @@ impl CodeGen for JavaCodeGen {
|
|||
fn generate_string_concat(&self, parts: &[String]) -> String {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -55,6 +55,8 @@ impl CodeGen for JavaScriptCodeGen {
|
|||
BinaryOpKind::Gt => ">",
|
||||
BinaryOpKind::Lte => "<=",
|
||||
BinaryOpKind::Gte => ">=",
|
||||
BinaryOpKind::And => "&&",
|
||||
BinaryOpKind::Or => "||",
|
||||
};
|
||||
format!("({} {} {})", left, op_str, right)
|
||||
}
|
||||
|
|
@ -130,4 +132,20 @@ impl CodeGen for JavaScriptCodeGen {
|
|||
fn generate_string_concat(&self, parts: &[String]) -> String {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,10 @@ pub trait CodeGen {
|
|||
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_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 {
|
||||
|
|
@ -81,7 +85,13 @@ fn walk_statement(codegen: &dyn CodeGen, stmt: &Statement) -> String {
|
|||
}
|
||||
Statement::ExprStmt { 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -55,6 +55,8 @@ impl CodeGen for PythonCodeGen {
|
|||
BinaryOpKind::Gt => ">",
|
||||
BinaryOpKind::Lte => "<=",
|
||||
BinaryOpKind::Gte => ">=",
|
||||
BinaryOpKind::And => "and",
|
||||
BinaryOpKind::Or => "or",
|
||||
};
|
||||
format!("({} {} {})", left, op_str, right)
|
||||
}
|
||||
|
|
@ -134,4 +136,20 @@ impl CodeGen for PythonCodeGen {
|
|||
fn generate_string_concat(&self, parts: &[String]) -> String {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -59,6 +59,8 @@ impl CodeGen for RustCodeGen {
|
|||
BinaryOpKind::Gt => ">",
|
||||
BinaryOpKind::Lte => "<=",
|
||||
BinaryOpKind::Gte => ">=",
|
||||
BinaryOpKind::And => "&&",
|
||||
BinaryOpKind::Or => "||",
|
||||
};
|
||||
format!("({} {} {})", left, op_str, right)
|
||||
}
|
||||
|
|
@ -134,4 +136,20 @@ impl CodeGen for RustCodeGen {
|
|||
fn generate_string_concat(&self, parts: &[String]) -> String {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,8 @@ Stmt: Statement = {
|
|||
iterable: iter,
|
||||
body,
|
||||
},
|
||||
"DEBUGVAR" <name:Ident> => Statement::DebugVar { name },
|
||||
"Open" <filename:STRING> => Statement::Open { filename },
|
||||
};
|
||||
|
||||
Expr: Expression = {
|
||||
|
|
@ -42,7 +44,7 @@ OrExpr: Expression = {
|
|||
AndExpr,
|
||||
<left:OrExpr> "||" <right:AndExpr> => Expression::BinaryOp {
|
||||
left: Box::new(left),
|
||||
op: BinaryOpKind::Add,
|
||||
op: BinaryOpKind::Or,
|
||||
right: Box::new(right),
|
||||
},
|
||||
};
|
||||
|
|
@ -51,15 +53,14 @@ AndExpr: Expression = {
|
|||
CmpExpr,
|
||||
<left:AndExpr> "&&" <right:CmpExpr> => Expression::BinaryOp {
|
||||
left: Box::new(left),
|
||||
op: BinaryOpKind::Add,
|
||||
op: BinaryOpKind::And,
|
||||
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::Eq, 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) },
|
||||
|
|
@ -133,8 +134,8 @@ match {
|
|||
"EndFor",
|
||||
"True",
|
||||
"False",
|
||||
"==",
|
||||
"!=",
|
||||
"DEBUGVAR",
|
||||
"Open",
|
||||
"<=",
|
||||
">=",
|
||||
"&&",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,10 +1,11 @@
|
|||
use crate::xml::ast::nodes::{
|
||||
AssignmentType, BinaryOpKind, Expression, LiteralValue, Program, Statement, UnaryOpKind,
|
||||
};
|
||||
use crate::xml::error::ParserResult;
|
||||
|
||||
#[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;
|
||||
|
||||
|
|
@ -88,13 +89,13 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
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());
|
||||
}
|
||||
|
||||
#[test]
|
||||
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());
|
||||
let program = result.unwrap();
|
||||
if let Statement::IfStmt { else_branch, .. } = &program.statements[0] {
|
||||
|
|
@ -152,10 +153,46 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
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());
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn test_for_loop() {
|
||||
let result = ScriptParser::parse("For i In items Var x = i EndFor");
|
||||
|
|
|
|||
|
|
@ -279,7 +279,8 @@ impl Vm {
|
|||
if let Some(func) = self.functions.get(name) {
|
||||
func(&evaluated_args)
|
||||
} 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()))?;
|
||||
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)?;
|
||||
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)?;
|
||||
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)?;
|
||||
Ok(None)
|
||||
|
|
@ -510,7 +562,7 @@ mod tests {
|
|||
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")
|
||||
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();
|
||||
|
|
@ -518,10 +570,10 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_comparison_neq() {
|
||||
fn test_comparison_lt() {
|
||||
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")
|
||||
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();
|
||||
|
|
@ -532,7 +584,7 @@ mod tests {
|
|||
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")
|
||||
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();
|
||||
|
|
@ -633,4 +685,46 @@ mod tests {
|
|||
let result = parse_and_run("Var x = unknown_var");
|
||||
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());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue