diff --git a/src/code/ast_visitor.rs b/src/code/ast_visitor.rs new file mode 100644 index 0000000..c004f25 --- /dev/null +++ b/src/code/ast_visitor.rs @@ -0,0 +1,93 @@ +use std::error::Error; + +use ast::*; + +use crate::parser::{ast, Script}; + +use super::builder::Builder; + +#[derive(Debug)] +pub struct VisitError(pub Box); + +pub type Result = std::result::Result; + +pub trait Visitor { + // Expressions + fn visit_reference(&self, ctx: Builder, expr: &MemberExpression) -> Result; + fn visit_call(&self, ctx: Builder, expr: &CallExpression) -> Result; + fn visit_binary(&self, ctx: Builder, expr: &BinaryExpression) -> Result; + fn visit_unary(&self, ctx: Builder, expr: &UnaryExpression) -> Result; + fn visit_wrapped_expression(&self, ctx: Builder, expr: &Expression) -> Result; + fn visit_identifier(&self, ctx: Builder, expr: &Identifier) -> Result; + fn visit_use_expression(&self, ctx: Builder, expr: &Identifier) -> Result; + + // Literals + fn visit_lambda(&self, ctx: Builder, expr: &Lambda) -> Result; + fn visit_tuple(&self, ctx: Builder, expr: &Tuple) -> Result; + fn visit_unit(&self, ctx: Builder) -> Result; + fn visit_number(&self, ctx: Builder, expr: &Number) -> Result; + fn visit_string(&self, ctx: Builder, expr: &StringLiteral) -> Result; + fn visit_table(&self, ctx: Builder, expr: &Table) -> Result; + fn visit_vector(&self, ctx: Builder, expr: &Vector) -> Result; + + // Statements + fn visit_return(&self, ctx: Builder, stmt: &Return) -> Result; + fn visit_class(&self, ctx: Builder, stmt: &Class) -> Result; + fn visit_fn(&self, ctx: Builder, stmt: &Function) -> Result; + fn visit_assignment(&self, ctx: Builder, stmt: &Assignment) -> Result; + fn visit_declaration(&self, ctx: Builder, stmt: &Let) -> Result; + fn visit_expression_statement(&self, ctx: Builder, stmt: &Expression) -> Result; + fn visit_use_statement(&self, ctx: Builder, stmt: &Identifier) -> Result; + + // Looping + fn visit_for(&self, ctx: Builder, expr: &For) -> Result; + fn visit_while(&self, ctx: Builder, expr: &While) -> Result; + fn visit_loop(&self, ctx: Builder, expr: &Loop) -> Result; + + // Conditionals + fn visit_if(&self, ctx: Builder, expr: &If) -> Result; + fn visit_match(&self, ctx: Builder, expr: &Match) -> Result; + + fn visit_block_expression(&self, ctx: Builder, expr: &Do) -> Result; + fn visit_script(&self, ctx: Builder, script: &Script) -> Result; + + // Generically implementable matching patterns: + fn visit_expression(&self, ctx: Builder, expression: &Expression) -> Result { + match expression { + Expression::Lambda(e) => self.visit_lambda(ctx, e), + Expression::Reference(e) => self.visit_reference(ctx, e), + Expression::Call(e) => self.visit_call(ctx, e), + Expression::Tuple(e) => self.visit_tuple(ctx, e), + Expression::Number(e) => self.visit_number(ctx, e), + Expression::String(e) => self.visit_string(ctx, e), + Expression::Unit => self.visit_unit(ctx), + Expression::Binary(e) => self.visit_binary(ctx, e), + Expression::Unary(e) => self.visit_unary(ctx, e), + Expression::Table(e) => self.visit_table(ctx, e), + Expression::Vector(e) => self.visit_vector(ctx, e), + Expression::Tuple1(e) => self.visit_wrapped_expression(ctx, e), + Expression::Identifier(e) => self.visit_identifier(ctx, e), + Expression::Do(e) => self.visit_block_expression(ctx, e), + Expression::Use(e) => self.visit_use_expression(ctx, e), + } + } + fn visit_block(&self, ctx: Builder, script: &Script) -> Result { + script + .statements + .iter() + .fold(Ok(ctx), |ctx, stmt| match stmt { + ast::Statement::If(e) => self.visit_if(ctx?, e), + ast::Statement::For(e) => self.visit_for(ctx?, e), + ast::Statement::Loop(e) => self.visit_loop(ctx?, e), + ast::Statement::While(e) => self.visit_while(ctx?, e), + ast::Statement::Return(e) => self.visit_return(ctx?, e), + ast::Statement::Class(e) => self.visit_class(ctx?, e), + ast::Statement::Function(e) => self.visit_fn(ctx?, e), + ast::Statement::Assignment(e) => self.visit_assignment(ctx?, e), + ast::Statement::Let(e) => self.visit_declaration(ctx?, e), + ast::Statement::Match(e) => self.visit_match(ctx?, e), + ast::Statement::Expression(e) => self.visit_expression_statement(ctx?, e), + ast::Statement::Use(e) => self.visit_use_statement(ctx?, e), + }) + } +} diff --git a/src/code/generator.rs b/src/code/generator.rs deleted file mode 100644 index 99489af..0000000 --- a/src/code/generator.rs +++ /dev/null @@ -1,6 +0,0 @@ -/// TODO -pub trait Generator { - fn gen() { - todo!("Not ready yet.") - } -} diff --git a/src/code/mod.rs b/src/code/mod.rs index cce9646..0472280 100644 --- a/src/code/mod.rs +++ b/src/code/mod.rs @@ -1,10 +1,3 @@ -mod builder; -mod generator; +pub mod ast_visitor; +pub mod builder; pub mod macros; -mod visitor; - -pub use builder::Builder; -pub use generator::Generator; -pub use visitor::{VisitError, Visitor}; - -pub trait BuilderVisitor: Visitor {} diff --git a/src/code/visitor.rs b/src/code/visitor.rs deleted file mode 100644 index 269d716..0000000 --- a/src/code/visitor.rs +++ /dev/null @@ -1,85 +0,0 @@ -use ast::*; - -use crate::parser::{ast, Script}; - -#[derive(Debug)] -pub struct VisitError; - -pub trait Visitor { - // Those need to be implemented explicitly by the user: - fn visit_return(&self, ctx: T, stmt: &Return) -> Result; - fn visit_class(&self, ctx: T, stmt: &Class) -> Result; - fn visit_fn(&self, ctx: T, stmt: &Function) -> Result; - fn visit_assignment(&self, ctx: T, stmt: &Assignment) -> Result; - fn visit_declaration(&self, ctx: T, stmt: &Let) -> Result; - fn visit_expression_statement(&self, ctx: T, stmt: &Expression) -> Result; - fn visit_lambda(&self, ctx: T, expr: &Lambda) -> Result; - fn visit_reference(&self, ctx: T, expr: &MemberExpression) -> Result; - fn visit_call(&self, ctx: T, expr: &CallExpression) -> Result; - fn visit_tuple(&self, ctx: T, expr: &Tuple) -> Result; - fn visit_number(&self, ctx: T, expr: &Number) -> Result; - fn visit_string(&self, ctx: T, expr: &StringLiteral) -> Result; - fn visit_unit(&self, ctx: T) -> Result; - fn visit_binary(&self, ctx: T, expr: &BinaryExpression) -> Result; - fn visit_unary(&self, ctx: T, expr: &UnaryExpression) -> Result; - fn visit_if(&self, ctx: T, expr: &If) -> Result; - fn visit_table(&self, ctx: T, expr: &Table) -> Result; - fn visit_vector(&self, ctx: T, expr: &Vector) -> Result; - fn visit_for(&self, ctx: T, expr: &For) -> Result; - fn visit_while(&self, ctx: T, expr: &While) -> Result; - fn visit_loop(&self, ctx: T, expr: &Loop) -> Result; - fn visit_match(&self, ctx: T, expr: &Match) -> Result; - fn visit_1tuple(&self, ctx: T, expr: &Expression) -> Result; - fn visit_identifier(&self, ctx: T, expr: &Identifier) -> Result; - fn visit_do(&self, ctx: T, expr: &Do) -> Result; - fn visit_use(&self, ctx: T, expr: &Identifier) -> Result; - fn visit_use_statement(&self, ctx: T, expr: &Identifier) -> Result; - fn enter_script(&self, ctx: T, _script: &Script) -> Result { - Ok(ctx) - } - fn exit_script(&self, ctx: T, _script: &Script) -> Result { - Ok(ctx) - } - - // Generically implementable matching patterns: - fn visit_expression(&self, ctx: T, expression: &Expression) -> Result { - match expression { - Expression::Lambda(e) => self.visit_lambda(ctx, e), - Expression::Reference(e) => self.visit_reference(ctx, e), - Expression::Call(e) => self.visit_call(ctx, e), - Expression::Tuple(e) => self.visit_tuple(ctx, e), - Expression::Number(e) => self.visit_number(ctx, e), - Expression::String(e) => self.visit_string(ctx, e), - Expression::Unit => self.visit_unit(ctx), - Expression::Binary(e) => self.visit_binary(ctx, e), - Expression::Unary(e) => self.visit_unary(ctx, e), - Expression::Table(e) => self.visit_table(ctx, e), - Expression::Vector(e) => self.visit_vector(ctx, e), - Expression::Tuple1(e) => self.visit_1tuple(ctx, e), - Expression::Identifier(e) => self.visit_identifier(ctx, e), - Expression::Do(e) => self.visit_do(ctx, e), - Expression::Use(e) => self.visit_use(ctx, e), - } - } - fn visit_script(&self, ctx: T, script: &Script) -> Result { - let ctx = self.enter_script(ctx, script)?; - let ctx = script - .statements - .iter() - .fold(Ok(ctx), |ctx, stmt| match stmt { - ast::Statement::If(e) => self.visit_if(ctx?, e), - ast::Statement::For(e) => self.visit_for(ctx?, e), - ast::Statement::Loop(e) => self.visit_loop(ctx?, e), - ast::Statement::While(e) => self.visit_while(ctx?, e), - ast::Statement::Return(e) => self.visit_return(ctx?, e), - ast::Statement::Class(e) => self.visit_class(ctx?, e), - ast::Statement::Function(e) => self.visit_fn(ctx?, e), - ast::Statement::Assignment(e) => self.visit_assignment(ctx?, e), - ast::Statement::Let(e) => self.visit_declaration(ctx?, e), - ast::Statement::Match(e) => self.visit_match(ctx?, e), - ast::Statement::Use(e) => self.visit_use_statement(ctx?, e), - ast::Statement::Expression(e) => self.visit_expression_statement(ctx?, e), - })?; - self.exit_script(ctx, script) - } -} diff --git a/src/lua/helpers.rs b/src/lua/helpers.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/lua/mod.rs b/src/lua/mod.rs index 9fac48c..8e6e8a0 100644 --- a/src/lua/mod.rs +++ b/src/lua/mod.rs @@ -1,876 +1,2 @@ -use crate::{ - code::{self, VisitError}, - parser::ast::{self}, -}; - -fn escape_string(str: String) -> String { - return str.replace("\n", "\\n"); -} - -fn translate_operator(ctx: code::Builder, op: String) -> code::Builder { - let ctx = ctx.put("__"); - let name = op - .into_bytes() - .iter() - .map(|ch| { - match ch { - b'+' => "plus", - b'-' => "minus", - b'*' => "times", - b'/' => "slash", - b'.' => "dot", - b'|' => "pipe", - b'>' => "greater", - b'<' => "less", - b'=' => "equals", - b'?' => "interrogation", - b'!' => "exclamation", - b'~' => "tilde", - b'%' => "percent", - b'&' => "ampersand", - b'#' => "bang", - b'$' => "dollar", - b'^' => "power", - b':' => "colon", - _ => panic!( - "Error! Unexpected operator {} to be translated as a function!", - ch - ), - } - .to_owned() - }) - .collect::>() - .join("_"); - ctx.put(name) -} - -pub struct LuaEmitter; - -impl LuaEmitter { - pub fn escape_reference( - &self, - ctx: code::Builder, - ident: &ast::Identifier, - ) -> Result { - let ctx = match &ident.0 { - a if a == "then" => ctx.put("['then']"), - ident => ctx.put(".").put(ident.clone()), - }; - Ok(ctx) - } - pub fn generate_member_segment( - &self, - s: &S, - ctx: code::Builder, - elem: &ast::MemberSegment, - ) -> Result - where - S: code::Visitor, - { - match elem { - ast::MemberSegment::Computed(c) => { - let ctx = ctx.put("["); - let ctx = s.visit_expression(ctx, &c)?; - Ok(ctx.put("]")) - } - ast::MemberSegment::IdentifierDynamic(i) => self.escape_reference(ctx, i), - ast::MemberSegment::IdentifierStatic(_) => Err(VisitError), - } - } - pub fn generate_destructured_assignment( - &self, - ctx: code::Builder, - e: &ast::Destructuring, - ) -> Result { - let mut i = 0; - match e.1 { - ast::DestructureOrigin::Tuple => e.0.iter().fold(Ok(ctx), |ctx, elem| { - let ctx = ctx? - .line() - .put(elem.0.clone()) - .put(" = __destructure__") - .put(format!("._{}", i)) - .put(";"); - i += 1; - Ok(ctx) - }), - ast::DestructureOrigin::Array => e.0.iter().fold(Ok(ctx), |ctx, elem| { - let ctx = ctx? - .line() - .put(elem.0.clone()) - .put(" = __destructure__") - .put(format!("[{}]", i)) - .put(";"); - i += 1; - Ok(ctx) - }), - ast::DestructureOrigin::Table => e.0.iter().fold(Ok(ctx), |ctx, elem| { - let ctx = ctx? - .line() - .put(elem.0.clone()) - .put(" = __destructure__.") - .put(elem.0.clone()) - .put(";"); - Ok(ctx) - }), - } - } -} - -impl code::Visitor for LuaEmitter { - fn visit_return( - &self, - ctx: code::Builder, - stmt: &ast::Return, - ) -> Result { - let ctx = ctx.line().put("return "); - let ctx = self.visit_expression(ctx, &stmt.value)?; - Ok(ctx.put(";")) - } - - fn visit_do(&self, ctx: code::Builder, expr: &ast::Do) -> Result { - let ctx = ctx.put("(function(...)").push(); - let ctx = self.visit_script(ctx, &expr.body)?; - let ctx = ctx.pop().unwrap().line().put("end)(...)"); - Ok(ctx) - } - - fn visit_1tuple( - &self, - ctx: code::Builder, - expr: &ast::Expression, - ) -> Result { - let ctx = ctx.put("("); - let ctx = self.visit_expression(ctx, expr)?; - Ok(ctx.put(")")) - } - - fn visit_identifier( - &self, - ctx: code::Builder, - expr: &ast::Identifier, - ) -> Result { - Ok(ctx.put(expr.0.clone())) - } - - fn visit_class( - &self, - ctx: code::Builder, - stmt: &ast::Class, - ) -> Result { - let ctx = ctx - .line() - .put(format!("local {} = {{}};", stmt.name.0.clone())) - .line() - .put(format!("{}.__meta__ = {{}};", stmt.name.0.clone())) - .line() - .put(format!( - "{}.__meta__.__call = function(self, struct)", - stmt.name.0.clone() - )) - .push() - .line() - .put("return setmetatable(struct, self.prototype.__meta__);") - .pop() - .unwrap() - .line() - .put("end;") - .line() - .put(format!("{}.prototype = {{}};", stmt.name.0.clone())) - .line() - .put(format!( - "{}.prototype.__meta__ = {{}};", - stmt.name.0.clone() - )) - .line() - .put(format!( - "{}.prototype.__meta__.__index = {}.prototype;", - stmt.name.0.clone(), - stmt.name.0.clone() - )) - .line() - .put(format!( - "setmetatable({}, {}.__meta__);", - stmt.name.0.clone(), - stmt.name.0.clone() - )); - let ctx = stmt.fields.iter().fold(Ok(ctx), |ctx, field| { - let ctx = ctx?.line(); - let ctx = match field { - ast::ClassField::Method(f) => { - let is_self = if let Some(first) = f.arguments.first() { - first.name.0 == "self" - } else { - false - }; - let level = if is_self { ".prototype." } else { "." }.to_string(); - let ctx = ctx - .put(stmt.name.0.clone()) - .put(level) - .put(f.name.0.clone()) - .put(" = "); - let ctx = self.visit_lambda( - ctx, - &ast::Lambda { - arguments: f.arguments.clone(), - body: ast::ScriptOrExpression::Script(f.body.clone()), - }, - )?; - let ctx = ctx.put(";"); - let ctx = f.decorators.iter().fold(Ok(ctx), |ctx, dec| { - let ctx = ctx?.line(); - let ctx = self.visit_call(ctx, &dec.target)?; - let fn_ref = if is_self { - format!("{}.prototype.{}", stmt.name.0.clone(), f.name.0.clone()) - } else { - format!("{}.{}", stmt.name.0.clone(), f.name.0.clone()) - }; - let ctx = ctx.put(format!( - "({}, \"{}\", {}, \"{}\", {{ is_static = {} }});", - fn_ref, - f.name.0.clone(), - stmt.name.0.clone(), - stmt.name.0.clone(), - !is_self - )); - Ok(ctx) - })?; - ctx - } - ast::ClassField::Let(f) => { - let ctx = match &f.target { - ast::AssignmentTarget::Destructuring(_) => { - panic!("Can't destructure that!") - } - ast::AssignmentTarget::Identifier(e) => ctx.put(format!( - "{}.prototype.{} = ", - stmt.name.0.clone(), - e.0.clone() - )), - }; - let ctx = if let Some(value) = f.value.as_ref() { - self.visit_expression(ctx, value)? - } else { - ctx.put("nil") - }; - ctx.put(";") - } - }; - Ok(ctx) - })?; - let ctx = stmt.decorators.iter().fold(Ok(ctx), |ctx, dec| { - let ctx = ctx?.line(); - let ctx = self.visit_call(ctx, &dec.target)?; - let ctx = ctx.put(format!( - "({}, \"{}\");", - stmt.name.0.clone(), - stmt.name.0.clone() - )); - Ok(ctx) - })?; - Ok(ctx) - } - - fn visit_fn( - &self, - ctx: code::Builder, - stmt: &ast::Function, - ) -> Result { - let ctx = ctx - .line() - .put("local function ") - .put(stmt.name.0.clone()) - .put("("); - let ctx = if let Some(first) = stmt.arguments.first() { - ctx.put(first.name.0.clone()) - } else { - ctx - }; - let ctx = stmt - .arguments - .iter() - .skip(1) - .fold(ctx, |ctx, ident| ctx.put(", ").put(ident.name.0.clone())); - let ctx = if stmt.arguments.len() > 0 { - ctx.put(", ...") - } else { - ctx.put("...") - }; - let ctx = ctx.put(")").push(); - let ctx = if let Some(native) = stmt.native.clone() { - let ctx = ctx.line().put("-- NATIVE CODE"); - match native.iter().find(|(ident, _)| ident.0 == "Lua") { - Some((_, src)) => match src { - ast::StringLiteral::Double(src) => ctx.put(src), - }, - None => ctx.put("error('Native function implementation not found')"), - } - .line() - .put("-- NATIVE CODE") - } else { - self.visit_script(ctx, &stmt.body)? - }; - let ctx = ctx.pop().unwrap().line().put("end"); - let ctx = stmt.decorators.iter().fold(Ok(ctx), |ctx, dec| { - let ctx = ctx?.line(); - let ctx = self.visit_call(ctx, &dec.target)?; - let ctx = ctx.put(format!( - "({}, \"{}\");", - stmt.name.0.clone(), - stmt.name.0.clone() - )); - Ok(ctx) - })?; - Ok(ctx) - } - - fn visit_assignment( - &self, - ctx: code::Builder, - stmt: &ast::Assignment, - ) -> Result { - let segment = self - .visit_reference(ctx.clone_like(), &stmt.target)? - .collect(); - let ctx = ctx.line().put(segment).put(" = "); - let ctx = if let Some(extra) = stmt.extra.as_ref() { - let ast::Assignment { target, value, .. } = stmt.clone(); - self.visit_binary( - ctx, - &ast::BinaryExpression { - left: ast::Expression::Reference(Box::new(target)), - operator: extra.clone(), - right: value, - }, - ) - } else { - self.visit_expression(ctx, &stmt.value) - }?; - let ctx = ctx.put(";"); - Ok(ctx) - } - - fn visit_declaration( - &self, - ctx: code::Builder, - stmt: &ast::Let, - ) -> Result { - let ctx = match &stmt.target { - ast::AssignmentTarget::Destructuring(e) => { - let ctx = ctx.line().put("local "); - let ctx = ctx.put(e.0.get(0).unwrap().0.clone()); - let ctx = - e.0.iter() - .skip(1) - .fold(ctx, |ctx, elem| ctx.put(", ").put(elem.0.clone())); - let ctx = ctx - .put(";") - .line() - .put("do") - .push() - .line() - .put("local __destructure__ = "); - let ctx = self - .visit_expression(ctx, stmt.value.as_ref().unwrap())? - .put(";"); - self.generate_destructured_assignment(ctx, &e)? - .pop() - .unwrap() - .line() - .put("end") - } - ast::AssignmentTarget::Identifier(e) => { - let ctx = ctx.line().put("local ").put(e.0.clone()).put(" = "); - let ctx = self.visit_expression(ctx, stmt.value.as_ref().unwrap())?; - ctx.put(";") - } - }; - Ok(ctx) - } - - fn visit_expression_statement( - &self, - ctx: code::Builder, - stmt: &ast::Expression, - ) -> Result { - Ok(self.visit_expression(ctx.line(), stmt)?.put(";")) - } - - fn visit_lambda( - &self, - ctx: code::Builder, - expr: &ast::Lambda, - ) -> Result { - let ctx = ctx.put("function("); - let ctx = if let Some(first) = expr.arguments.first() { - ctx.put(first.name.0.clone()) - } else { - ctx - }; - let ctx = expr - .arguments - .iter() - .skip(1) - .fold(ctx, |ctx, ident| ctx.put(", ").put(ident.name.0.clone())); - let ctx = if expr.arguments.len() > 0 { - ctx.put(", ...") - } else { - ctx.put("...") - }; - let ctx = ctx.put(")").push(); - let ctx = match &expr.body { - ast::ScriptOrExpression::Script(e) => self.visit_script(ctx, e)?, - ast::ScriptOrExpression::Expression(e) => self - .visit_expression(ctx.line().put("return "), e) - .map(|b| b.put(";"))?, - }; - Ok(ctx.pop().unwrap().line().put("end")) - } - - fn visit_reference( - &self, - ctx: code::Builder, - expr: &ast::MemberExpression, - ) -> Result { - let ctx = self.visit_expression(ctx, &expr.head)?; - let ctx = expr.tail.iter().fold(Ok(ctx), |ctx, elem| { - self.generate_member_segment(self, ctx?, elem) - })?; - Ok(ctx) - } - - fn visit_use( - &self, - ctx: code::Builder, - expr: &ast::Identifier, - ) -> Result { - Ok(ctx.put(format!("__modules__.{}", expr.0.clone()))) - } - - fn visit_use_statement( - &self, - ctx: code::Builder, - expr: &ast::Identifier, - ) -> Result { - let target = expr.0.clone(); - let target2 = expr.0.clone(); - Ok(ctx - .line() - .put(format!("local {target} = __modules__.{target2};"))) - } - - fn enter_script( - &self, - ctx: code::Builder, - _script: &crate::parser::Script, - ) -> Result { - Ok(ctx.line().put("local argv = {...};")) - } - - fn visit_call( - &self, - ctx: code::Builder, - expr: &ast::CallExpression, - ) -> Result { - let ctx = if let Some(callee) = expr.head.callee.clone() { - let ctx = self.visit_expression(ctx, &callee.head)?; - let ctx = callee - .tail - .iter() - .rev() - .skip(1) - .rev() - .fold(Ok(ctx), |ctx, elem| { - let ctx = ctx?; - let ctx = match elem { - ast::MemberSegment::Computed(c) => { - let ctx = ctx.put("["); - let ctx = self.visit_expression(ctx, &c)?; - ctx.put("]") - } - ast::MemberSegment::IdentifierDynamic(c) => ctx.put(".").put(c.0.clone()), - ast::MemberSegment::IdentifierStatic(_) => Err(VisitError)?, - }; - Ok(ctx) - })?; - let ctx = if let Some(last) = callee.tail.last() { - match last { - ast::MemberSegment::Computed(c) => { - let ctx = ctx.put("["); - let ctx = self.visit_expression(ctx, &c)?; - ctx.put("]") - } - ast::MemberSegment::IdentifierDynamic(c) => ctx.put(":").put(c.0.clone()), - ast::MemberSegment::IdentifierStatic(c) => ctx.put(".").put(c.0.clone()), - } - } else { - ctx - }; - ctx - } else { - ctx - }; - let ctx = ctx.put("("); - if expr.head.is_macro { - let ctx = ctx.put(")"); - return Ok(ctx); - } - // If not macro - let ctx = if let Some(first) = expr.head.arguments.first() { - self.visit_expression(ctx, first)? - } else { - ctx - }; - let ctx = expr - .head - .arguments - .iter() - .skip(1) - .fold(Ok(ctx), |ctx, elem| { - let ctx = ctx?.put(", "); - self.visit_expression(ctx, elem) - })?; - let ctx = ctx.put(")"); - let ctx = expr.tail.iter().fold(Ok(ctx), |ctx, elem| match elem { - ast::CallExpressionVariant::Call(c) => self.visit_call( - ctx?, - &ast::CallExpression { - head: c.clone(), - tail: vec![], - }, - ), - ast::CallExpressionVariant::Member(m) => match m { - ast::MemberSegment::Computed(c) => { - let ctx = ctx?.put("["); - let ctx = self.visit_expression(ctx, &c)?; - Ok(ctx.put("]")) - } - ast::MemberSegment::IdentifierStatic(i) => Ok(ctx?.put(".").put(i.0.clone())), - ast::MemberSegment::IdentifierDynamic(i) => Ok(ctx?.put(":").put(i.0.clone())), - }, - })?; - Ok(ctx) - } - - fn visit_tuple( - &self, - ctx: code::Builder, - expr: &ast::Tuple, - ) -> Result { - let ctx = ctx.put("{"); - let ctx = if let Some(first) = expr.0.first().as_ref() { - let ctx = ctx.put(format!("_0 = ")); - self.visit_expression(ctx, first)? - } else { - ctx - }; - let ctx = expr - .0 - .iter() - .skip(1) - .fold(Ok((ctx, 1_u16)), |ctx, value| { - let (ctx, i) = ctx?; - let ctx = ctx.put(format!(", _{} = ", i)); - let ctx = self.visit_expression(ctx, value)?; - Ok((ctx, i + 1)) - })? - .0; - let ctx = ctx.put("}"); - Ok(ctx) - } - - fn visit_number( - &self, - ctx: code::Builder, - expr: &ast::Number, - ) -> Result { - let repr = match expr { - ast::Number::Float(e) => e.to_string(), - ast::Number::Integer(e) => e.to_string(), - }; - Ok(ctx.put(repr)) - } - - fn visit_string( - &self, - ctx: code::Builder, - expr: &ast::StringLiteral, - ) -> Result { - let ctx = match expr { - ast::StringLiteral::Double(s) => ctx.put("\"").put(escape_string(s.clone())).put("\""), - }; - Ok(ctx) - } - - fn visit_unit(&self, ctx: code::Builder) -> Result { - Ok(ctx.put("nil")) - } - - fn visit_binary( - &self, - ctx: code::Builder, - expr: &ast::BinaryExpression, - ) -> Result { - let op = expr.operator.0.as_str(); - // Extra logic (Indirect operation expansion) - if op == "??" { - let ctx = self.visit_expression(ctx, &expr.left)?.put(" == nil and "); - let ctx = self.visit_expression(ctx, &expr.right)?.put(" or "); - return self.visit_expression(ctx, &expr.left); - } else if op == "?:" { - let ctx = self.visit_expression(ctx, &expr.left)?.put(" or "); - return self.visit_expression(ctx, &expr.right); - } - // Translate native operators. - match op { - // Basic math - | "+" - | "-" - | "*" - | "/" - | "%" - | "**" - // Comparison - | ">" - | ">=" - | "<" - | "<=" - | "==" - // Logic - | "not" - | "and" - | "or" - // Logic - | "&" - | "|" - | "<<" - | "<<<" - | ">>" - | ">>>" - => { - let ctx = self.visit_expression(ctx, &expr.left)?.put(" "); - let ctx = ctx.put(op.to_owned()); - self.visit_expression(ctx.put(" "), &expr.right) - } - "++" => { - // Native-to-native operator translation - let ctx = self.visit_expression(ctx, &expr.left)?.put(" "); - let ctx = ctx.put("..".to_owned()); - self.visit_expression(ctx.put(" "), &expr.right) - } - "<>" => { - // Native-to-native operator translation - let ctx = self.visit_expression(ctx, &expr.left)?.put(" "); - let ctx = ctx.put("~=".to_owned()); - self.visit_expression(ctx.put(" "), &expr.right) - } - _ => { - // Direct function translation - let ctx = translate_operator(ctx, op.to_owned()).put("("); - let ctx = self.visit_expression(ctx, &expr.left)?.put(", "); - let ctx = self.visit_expression(ctx, &expr.right)?.put(")"); - Ok(ctx) - } - } - } - - fn visit_unary( - &self, - ctx: code::Builder, - expr: &ast::UnaryExpression, - ) -> Result { - let ctx = match expr.operator.clone().0.as_str() { - "-" => ctx.put("-"), - "not" => ctx.put("not "), - "#?" => ctx.put("#"), - op => todo!("Unary operator {:?} not supported!", op), - }; - let ctx = self.visit_expression(ctx, &expr.expression)?; - Ok(ctx) - } - - fn visit_if( - &self, - ctx: code::Builder, - expr: &ast::If, - ) -> Result { - let ctx = ctx.line().put("if "); - let ctx = self.visit_expression(ctx, &expr.condition)?; - let ctx = ctx.put(" then").push(); - let ctx = self.visit_script(ctx, &expr.body)?; - let ctx = expr.branches.iter().fold(Ok(ctx), |ctx, (c, s)| { - let ctx = ctx?.pop().unwrap().line().put("elseif "); - let ctx = self.visit_expression(ctx, c)?; - let ctx = ctx.put(" then").push(); - let ctx = self.visit_script(ctx, s)?; - Ok(ctx) - })?; - let ctx = if let Some(eb) = expr.else_branch.as_ref() { - let ctx = ctx.pop().unwrap().line().put("else").push(); - self.visit_script(ctx, eb)? - } else { - ctx - }; - let ctx = ctx.pop().unwrap().line().put("end"); - Ok(ctx) - } - - fn visit_table( - &self, - ctx: code::Builder, - expr: &ast::Table, - ) -> Result { - let ctx = ctx.put("{"); - let ctx = if let Some((k, v)) = expr.key_values.first() { - match k { - ast::TableKeyExpression::Identifier(k) => { - let ctx = ctx.put(k.0.clone()).put(" = "); - self.visit_expression(ctx, &v.clone().unwrap()) - } - ast::TableKeyExpression::Expression(k) => { - let ctx = ctx.put("["); - let ctx = self.visit_expression(ctx, &k)?.put("] = "); - self.visit_expression(ctx, &v.clone().unwrap()) - } - ast::TableKeyExpression::Implicit(k) => { - let ctx = ctx.put(k.0.clone()).put(" = ").put(k.0.clone()); - Ok(ctx) - } - }? - } else { - ctx - }; - let ctx = expr - .key_values - .iter() - .skip(1) - .fold(Ok(ctx), |ctx, (k, v)| { - let ctx = ctx?.put(", "); - match k { - ast::TableKeyExpression::Identifier(k) => { - let ctx = ctx.put(k.0.clone()).put(" = "); - self.visit_expression(ctx, &v.clone().unwrap()) - } - ast::TableKeyExpression::Expression(k) => { - let ctx = ctx.put("["); - let ctx = self.visit_expression(ctx, &k)?.put("] = "); - self.visit_expression(ctx, &v.clone().unwrap()) - } - ast::TableKeyExpression::Implicit(k) => { - let ctx = ctx.put(k.0.clone()).put(" = ").put(k.0.clone()); - Ok(ctx) - } - } - })?; - Ok(ctx.put("}")) - } - - fn visit_vector( - &self, - ctx: code::Builder, - expr: &ast::Vector, - ) -> Result { - let ctx = ctx.put("{"); - let ctx = if let Some(first) = expr.expressions.first() { - self.visit_expression(ctx, first)? - } else { - ctx - }; - let ctx = expr.expressions.iter().skip(1).fold(Ok(ctx), |ctx, v| { - let ctx = ctx?.put(", "); - let ctx = self.visit_expression(ctx, v)?; - Ok(ctx) - })?; - Ok(ctx.put("}")) - } - - fn visit_for( - &self, - ctx: code::Builder, - expr: &ast::For, - ) -> Result { - let ctx = match &expr.handler { - ast::AssignmentTarget::Destructuring(e) => { - let ctx = ctx.line().put("for __destructure__ in "); - let ctx = self.visit_expression(ctx, &expr.target)?; - let ctx = ctx.put(" do").push(); - let ctx = self.generate_destructured_assignment(ctx, &e)?; - let ctx = self.visit_script(ctx, &expr.body)?; - ctx.pop().unwrap().line().put("end") - } - ast::AssignmentTarget::Identifier(e) => { - let ctx = ctx.line().put(format!("for {} in ", e.0.clone())); - let ctx = self.visit_expression(ctx, &expr.target)?; - let ctx = ctx.put(" do").push(); - let ctx = self.visit_script(ctx, &expr.body)?; - ctx.pop().unwrap().line().put("end") - } - }; - Ok(ctx) - } - - fn visit_while( - &self, - ctx: code::Builder, - expr: &ast::While, - ) -> Result { - let ctx = match &expr.condition { - ast::ExpressionOrLet::Expression(e) => { - let ctx = ctx.line().put("while "); - let ctx = self.visit_expression(ctx, e)?; - let ctx = ctx.put(" do").push(); - let ctx = self.visit_script(ctx, &expr.body)?; - ctx.pop().unwrap().line().put("end") - } - ast::ExpressionOrLet::Let(e) => { - if let ast::AssignmentTarget::Identifier(id) = &e.target { - let ctx = ctx.line().put("do").push(); - let ctx = self.visit_declaration(ctx, e)?; - let ctx = ctx.line().put(format!("while {} do", id.0.clone())).push(); - let ctx = self.visit_script(ctx, &expr.body)?; - let ctx = self.visit_assignment( - ctx, - &ast::Assignment { - target: ast::MemberExpression { - head: ast::Expression::Identifier(id.clone()), - tail: vec![], - }, - value: e.value.clone().unwrap(), - extra: None, - }, - )?; - ctx.pop() - .unwrap() - .line() - .put("end") - .pop() - .unwrap() - .line() - .put("end") - } else { - panic!("Destructured while-let not supported"); - } - } - }; - Ok(ctx) - } - - fn visit_loop( - &self, - ctx: code::Builder, - expr: &ast::Loop, - ) -> Result { - let ctx = ctx.line().put("while true do").push(); - let ctx = self.visit_script(ctx, &expr.body)?; - let ctx = ctx.pop().unwrap().line().put("end"); - Ok(ctx) - } - - fn visit_match( - &self, - _ctx: code::Builder, - _expr: &ast::Match, - ) -> Result { - todo!("Match code generation not implemented yet.") - } -} +pub mod helpers; +pub mod visitor; diff --git a/src/lua/visitor.rs b/src/lua/visitor.rs new file mode 100644 index 0000000..147f738 --- /dev/null +++ b/src/lua/visitor.rs @@ -0,0 +1,781 @@ +use crate::{ + code::{ + ast_visitor::{Result, VisitError, Visitor}, + builder::Builder, + }, + parser::ast::{self}, +}; + +#[derive(Debug)] +struct BadCode; +impl std::fmt::Display for BadCode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Bad code") + } +} +impl std::error::Error for BadCode { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +} + +fn escape_string(str: String) -> String { + return str.replace("\n", "\\n"); +} + +fn translate_operator(ctx: Builder, op: String) -> Builder { + let ctx = ctx.put("__"); + let name = op + .into_bytes() + .iter() + .map(|ch| { + match ch { + b'+' => "plus", + b'-' => "minus", + b'*' => "times", + b'/' => "slash", + b'.' => "dot", + b'|' => "pipe", + b'>' => "greater", + b'<' => "less", + b'=' => "equals", + b'?' => "interrogation", + b'!' => "exclamation", + b'~' => "tilde", + b'%' => "percent", + b'&' => "ampersand", + b'#' => "bang", + b'$' => "dollar", + b'^' => "power", + b':' => "colon", + _ => panic!( + "Error! Unexpected operator {} to be translated as a function!", + ch + ), + } + .to_owned() + }) + .collect::>() + .join("_"); + ctx.put(name) +} + +pub struct LuaEmitter(); + +impl LuaEmitter { + pub fn escape_reference(&self, ctx: Builder, ident: &ast::Identifier) -> Result { + let ctx = match &ident.0 { + a if a == "then" => ctx.put("['then']"), + ident => ctx.put(".").put(ident.clone()), + }; + Ok(ctx) + } + pub fn generate_member_segment( + &self, + s: &S, + ctx: Builder, + elem: &ast::MemberSegment, + ) -> Result + where + S: Visitor, + { + match elem { + ast::MemberSegment::Computed(c) => { + let ctx = ctx.put("["); + let ctx = s.visit_expression(ctx, &c)?; + Ok(ctx.put("]")) + } + ast::MemberSegment::IdentifierDynamic(i) => self.escape_reference(ctx, i), + ast::MemberSegment::IdentifierStatic(_) => Err(VisitError(Box::new(BadCode))), + } + } + pub fn generate_destructured_assignment(&self, ctx: Builder, e: &ast::Destructuring) -> Result { + let mut i = 0; + match e.1 { + ast::DestructureOrigin::Tuple => e.0.iter().fold(Ok(ctx), |ctx, elem| { + let ctx = ctx? + .line() + .put(elem.0.clone()) + .put(" = __destructure__") + .put(format!("._{}", i)) + .put(";"); + i += 1; + Ok(ctx) + }), + ast::DestructureOrigin::Array => e.0.iter().fold(Ok(ctx), |ctx, elem| { + let ctx = ctx? + .line() + .put(elem.0.clone()) + .put(" = __destructure__") + .put(format!("[{}]", i)) + .put(";"); + i += 1; + Ok(ctx) + }), + ast::DestructureOrigin::Table => e.0.iter().fold(Ok(ctx), |ctx, elem| { + let ctx = ctx? + .line() + .put(elem.0.clone()) + .put(" = __destructure__.") + .put(elem.0.clone()) + .put(";"); + Ok(ctx) + }), + } + } +} + +impl Visitor for LuaEmitter { + fn visit_return(&self, ctx: Builder, stmt: &ast::Return) -> Result { + let ctx = ctx.line().put("return "); + let ctx = self.visit_expression(ctx, &stmt.value)?; + Ok(ctx.put(";")) + } + + fn visit_block_expression(&self, ctx: Builder, expr: &ast::Do) -> Result { + let ctx = ctx.put("(function(...)").push(); + let ctx = self.visit_script(ctx, &expr.body)?; + let ctx = ctx.pop().unwrap().line().put("end)(...)"); + Ok(ctx) + } + + fn visit_wrapped_expression(&self, ctx: Builder, expr: &ast::Expression) -> Result { + let ctx = ctx.put("("); + let ctx = self.visit_expression(ctx, expr)?; + Ok(ctx.put(")")) + } + + fn visit_identifier(&self, ctx: Builder, expr: &ast::Identifier) -> Result { + Ok(ctx.put(expr.0.clone())) + } + + fn visit_class(&self, ctx: Builder, stmt: &ast::Class) -> Result { + let ctx = ctx + .line() + .put(format!("local {} = {{}};", stmt.name.0.clone())) + .line() + .put(format!("{}.__meta__ = {{}};", stmt.name.0.clone())) + .line() + .put(format!( + "{}.__meta__.__call = function(self, struct)", + stmt.name.0.clone() + )) + .push() + .line() + .put("return setmetatable(struct, self.prototype.__meta__);") + .pop() + .unwrap() + .line() + .put("end;") + .line() + .put(format!("{}.prototype = {{}};", stmt.name.0.clone())) + .line() + .put(format!( + "{}.prototype.__meta__ = {{}};", + stmt.name.0.clone() + )) + .line() + .put(format!( + "{}.prototype.__meta__.__index = {}.prototype;", + stmt.name.0.clone(), + stmt.name.0.clone() + )) + .line() + .put(format!( + "setmetatable({}, {}.__meta__);", + stmt.name.0.clone(), + stmt.name.0.clone() + )); + let ctx = stmt.fields.iter().fold(Ok(ctx), |ctx, field| { + let ctx = ctx?.line(); + let ctx = match field { + ast::ClassField::Method(f) => { + let is_self = if let Some(first) = f.arguments.first() { + first.name.0 == "self" + } else { + false + }; + let level = if is_self { ".prototype." } else { "." }.to_string(); + let ctx = ctx + .put(stmt.name.0.clone()) + .put(level) + .put(f.name.0.clone()) + .put(" = "); + let ctx = self.visit_lambda( + ctx, + &ast::Lambda { + arguments: f.arguments.clone(), + body: ast::ScriptOrExpression::Script(f.body.clone()), + }, + )?; + let ctx = ctx.put(";"); + let ctx = f.decorators.iter().fold(Ok(ctx), |ctx, dec| { + let ctx = ctx?.line(); + let ctx = self.visit_call(ctx, &dec.target)?; + let fn_ref = if is_self { + format!("{}.prototype.{}", stmt.name.0.clone(), f.name.0.clone()) + } else { + format!("{}.{}", stmt.name.0.clone(), f.name.0.clone()) + }; + let ctx = ctx.put(format!( + "({}, \"{}\", {}, \"{}\", {{ is_static = {} }});", + fn_ref, + f.name.0.clone(), + stmt.name.0.clone(), + stmt.name.0.clone(), + !is_self + )); + Ok(ctx) + })?; + ctx + } + ast::ClassField::Let(f) => { + let ctx = match &f.target { + ast::AssignmentTarget::Destructuring(_) => { + panic!("Can't destructure that!") + } + ast::AssignmentTarget::Identifier(e) => ctx.put(format!( + "{}.prototype.{} = ", + stmt.name.0.clone(), + e.0.clone() + )), + }; + let ctx = if let Some(value) = f.value.as_ref() { + self.visit_expression(ctx, value)? + } else { + ctx.put("nil") + }; + ctx.put(";") + } + }; + Ok(ctx) + })?; + let ctx = stmt.decorators.iter().fold(Ok(ctx), |ctx, dec| { + let ctx = ctx?.line(); + let ctx = self.visit_call(ctx, &dec.target)?; + let ctx = ctx.put(format!( + "({}, \"{}\");", + stmt.name.0.clone(), + stmt.name.0.clone() + )); + Ok(ctx) + })?; + Ok(ctx) + } + + fn visit_fn(&self, ctx: Builder, stmt: &ast::Function) -> Result { + let ctx = ctx + .line() + .put("local function ") + .put(stmt.name.0.clone()) + .put("("); + let ctx = if let Some(first) = stmt.arguments.first() { + ctx.put(first.name.0.clone()) + } else { + ctx + }; + let ctx = stmt + .arguments + .iter() + .skip(1) + .fold(ctx, |ctx, ident| ctx.put(", ").put(ident.name.0.clone())); + let ctx = if stmt.arguments.len() > 0 { + ctx.put(", ...") + } else { + ctx.put("...") + }; + let ctx = ctx.put(")").push(); + let ctx = if let Some(native) = stmt.native.clone() { + let ctx = ctx.line().put("-- NATIVE CODE"); + match native.iter().find(|(ident, _)| ident.0 == "Lua") { + Some((_, src)) => match src { + ast::StringLiteral::Double(src) => ctx.put(src), + }, + None => ctx.put("error('Native function implementation not found')"), + } + .line() + .put("-- NATIVE CODE") + } else { + self.visit_script(ctx, &stmt.body)? + }; + let ctx = ctx.pop().unwrap().line().put("end"); + let ctx = stmt.decorators.iter().fold(Ok(ctx), |ctx, dec| { + let ctx = ctx?.line(); + let ctx = self.visit_call(ctx, &dec.target)?; + let ctx = ctx.put(format!( + "({}, \"{}\");", + stmt.name.0.clone(), + stmt.name.0.clone() + )); + Ok(ctx) + })?; + Ok(ctx) + } + + fn visit_assignment(&self, ctx: Builder, stmt: &ast::Assignment) -> Result { + let segment = self + .visit_reference(ctx.clone_like(), &stmt.target)? + .collect(); + let ctx = ctx.line().put(segment).put(" = "); + let ctx = if let Some(extra) = stmt.extra.as_ref() { + let ast::Assignment { target, value, .. } = stmt.clone(); + self.visit_binary( + ctx, + &ast::BinaryExpression { + left: ast::Expression::Reference(Box::new(target)), + operator: extra.clone(), + right: value, + }, + ) + } else { + self.visit_expression(ctx, &stmt.value) + }?; + let ctx = ctx.put(";"); + Ok(ctx) + } + + fn visit_declaration(&self, ctx: Builder, stmt: &ast::Let) -> Result { + let ctx = match &stmt.target { + ast::AssignmentTarget::Destructuring(e) => { + let ctx = ctx.line().put("local "); + let ctx = ctx.put(e.0.get(0).unwrap().0.clone()); + let ctx = + e.0.iter() + .skip(1) + .fold(ctx, |ctx, elem| ctx.put(", ").put(elem.0.clone())); + let ctx = ctx + .put(";") + .line() + .put("do") + .push() + .line() + .put("local __destructure__ = "); + let ctx = self + .visit_expression(ctx, stmt.value.as_ref().unwrap())? + .put(";"); + self.generate_destructured_assignment(ctx, &e)? + .pop() + .unwrap() + .line() + .put("end") + } + ast::AssignmentTarget::Identifier(e) => { + let ctx = ctx.line().put("local ").put(e.0.clone()).put(" = "); + let ctx = self.visit_expression(ctx, stmt.value.as_ref().unwrap())?; + ctx.put(";") + } + }; + Ok(ctx) + } + + fn visit_expression_statement(&self, ctx: Builder, stmt: &ast::Expression) -> Result { + Ok(self.visit_expression(ctx.line(), stmt)?.put(";")) + } + + fn visit_lambda(&self, ctx: Builder, expr: &ast::Lambda) -> Result { + let ctx = ctx.put("function("); + let ctx = if let Some(first) = expr.arguments.first() { + ctx.put(first.name.0.clone()) + } else { + ctx + }; + let ctx = expr + .arguments + .iter() + .skip(1) + .fold(ctx, |ctx, ident| ctx.put(", ").put(ident.name.0.clone())); + let ctx = if expr.arguments.len() > 0 { + ctx.put(", ...") + } else { + ctx.put("...") + }; + let ctx = ctx.put(")").push(); + let ctx = match &expr.body { + ast::ScriptOrExpression::Script(e) => self.visit_script(ctx, e)?, + ast::ScriptOrExpression::Expression(e) => self + .visit_expression(ctx.line().put("return "), e) + .map(|b| b.put(";"))?, + }; + Ok(ctx.pop().unwrap().line().put("end")) + } + + fn visit_reference(&self, ctx: Builder, expr: &ast::MemberExpression) -> Result { + let ctx = self.visit_expression(ctx, &expr.head)?; + let ctx = expr.tail.iter().fold(Ok(ctx), |ctx, elem| { + self.generate_member_segment(self, ctx?, elem) + })?; + Ok(ctx) + } + + fn visit_use_expression(&self, ctx: Builder, expr: &ast::Identifier) -> Result { + Ok(ctx.put(format!("__modules__.{}", expr.0.clone()))) + } + + fn visit_use_statement(&self, ctx: Builder, expr: &ast::Identifier) -> Result { + let target = expr.0.clone(); + let target2 = expr.0.clone(); + Ok(ctx + .line() + .put(format!("local {target} = __modules__.{target2};"))) + } + + fn visit_call(&self, ctx: Builder, expr: &ast::CallExpression) -> Result { + let ctx = if let Some(callee) = expr.head.callee.clone() { + let ctx = self.visit_expression(ctx, &callee.head)?; + let ctx = callee + .tail + .iter() + .rev() + .skip(1) + .rev() + .fold(Ok(ctx), |ctx, elem| { + let ctx = ctx?; + let ctx = match elem { + ast::MemberSegment::Computed(c) => { + let ctx = ctx.put("["); + let ctx = self.visit_expression(ctx, &c)?; + ctx.put("]") + } + ast::MemberSegment::IdentifierDynamic(c) => ctx.put(".").put(c.0.clone()), + ast::MemberSegment::IdentifierStatic(_) => { + Err(VisitError(Box::new(BadCode)))? + } + }; + Ok(ctx) + })?; + let ctx = if let Some(last) = callee.tail.last() { + match last { + ast::MemberSegment::Computed(c) => { + let ctx = ctx.put("["); + let ctx = self.visit_expression(ctx, &c)?; + ctx.put("]") + } + ast::MemberSegment::IdentifierDynamic(c) => ctx.put(":").put(c.0.clone()), + ast::MemberSegment::IdentifierStatic(c) => ctx.put(".").put(c.0.clone()), + } + } else { + ctx + }; + ctx + } else { + ctx + }; + let ctx = ctx.put("("); + let ctx = if let Some(first) = expr.head.arguments.first() { + self.visit_expression(ctx, first)? + } else { + ctx + }; + let ctx = expr + .head + .arguments + .iter() + .skip(1) + .fold(Ok(ctx), |ctx, elem| { + let ctx = ctx?.put(", "); + self.visit_expression(ctx, elem) + })?; + let ctx = ctx.put(")"); + let ctx = expr.tail.iter().fold(Ok(ctx), |ctx, elem| match elem { + ast::CallExpressionVariant::Call(c) => self.visit_call( + ctx?, + &ast::CallExpression { + head: c.clone(), + tail: vec![], + }, + ), + ast::CallExpressionVariant::Member(m) => match m { + ast::MemberSegment::Computed(c) => { + let ctx = ctx?.put("["); + let ctx = self.visit_expression(ctx, &c)?; + Ok(ctx.put("]")) + } + ast::MemberSegment::IdentifierStatic(i) => Ok(ctx?.put(".").put(i.0.clone())), + ast::MemberSegment::IdentifierDynamic(i) => Ok(ctx?.put(":").put(i.0.clone())), + }, + })?; + Ok(ctx) + } + + fn visit_tuple(&self, ctx: Builder, expr: &ast::Tuple) -> Result { + let ctx = ctx.put("{"); + let ctx = if let Some(first) = expr.0.first().as_ref() { + let ctx = ctx.put(format!("_0 = ")); + self.visit_expression(ctx, first)? + } else { + ctx + }; + let ctx = expr + .0 + .iter() + .skip(1) + .fold(Ok((ctx, 1_u16)), |ctx, value| { + let (ctx, i) = ctx?; + let ctx = ctx.put(format!(", _{} = ", i)); + let ctx = self.visit_expression(ctx, value)?; + Ok((ctx, i + 1)) + })? + .0; + let ctx = ctx.put("}"); + Ok(ctx) + } + + fn visit_number(&self, ctx: Builder, expr: &ast::Number) -> Result { + let numeric_string = match expr { + ast::Number::Float(e) => e.to_string(), + ast::Number::Integer(e) => e.to_string(), + }; + Ok(ctx.put(numeric_string)) + } + + fn visit_string(&self, ctx: Builder, expr: &ast::StringLiteral) -> Result { + let ctx = match expr { + ast::StringLiteral::Double(s) => ctx.put("\"").put(escape_string(s.clone())).put("\""), + }; + Ok(ctx) + } + + fn visit_unit(&self, ctx: Builder) -> Result { + Ok(ctx.put("nil")) + } + + fn visit_binary(&self, ctx: Builder, expr: &ast::BinaryExpression) -> Result { + let op = expr.operator.0.as_str(); + // Extra logic (Indirect operation expansion) + if op == "??" { + let ctx = self.visit_expression(ctx, &expr.left)?.put(" == nil and "); + let ctx = self.visit_expression(ctx, &expr.right)?.put(" or "); + return self.visit_expression(ctx, &expr.left); + } else if op == "?:" { + let ctx = self.visit_expression(ctx, &expr.left)?.put(" or "); + return self.visit_expression(ctx, &expr.right); + } + // Translate native operators. + match op { + // Basic math + | "+" + | "-" + | "*" + | "/" + | "%" + | "**" + // Comparison + | ">" + | ">=" + | "<" + | "<=" + | "==" + // Logic + | "not" + | "and" + | "or" + // Logic + | "&" + | "|" + | "<<" + | "<<<" + | ">>" + | ">>>" + => { + let ctx = self.visit_expression(ctx, &expr.left)?.put(" "); + let ctx = ctx.put(op.to_owned()); + self.visit_expression(ctx.put(" "), &expr.right) + } + "++" => { + // Native-to-native operator translation + let ctx = self.visit_expression(ctx, &expr.left)?.put(" "); + let ctx = ctx.put("..".to_owned()); + self.visit_expression(ctx.put(" "), &expr.right) + } + "<>" => { + // Native-to-native operator translation + let ctx = self.visit_expression(ctx, &expr.left)?.put(" "); + let ctx = ctx.put("~=".to_owned()); + self.visit_expression(ctx.put(" "), &expr.right) + } + _ => { + // Direct function translation + let ctx = translate_operator(ctx, op.to_owned()).put("("); + let ctx = self.visit_expression(ctx, &expr.left)?.put(", "); + let ctx = self.visit_expression(ctx, &expr.right)?.put(")"); + Ok(ctx) + } + } + } + + fn visit_unary(&self, ctx: Builder, expr: &ast::UnaryExpression) -> Result { + let ctx = match expr.operator.clone().0.as_str() { + "-" => ctx.put("-"), + "not" => ctx.put("not "), + "#?" => ctx.put("#"), + op => todo!("Unary operator {:?} not supported!", op), + }; + let ctx = self.visit_expression(ctx, &expr.expression)?; + Ok(ctx) + } + + fn visit_if(&self, ctx: Builder, expr: &ast::If) -> Result { + let ctx = ctx.line().put("if "); + let ctx = self.visit_expression(ctx, &expr.condition)?; + let ctx = ctx.put(" then").push(); + let ctx = self.visit_script(ctx, &expr.body)?; + let ctx = expr.branches.iter().fold(Ok(ctx), |ctx, (c, s)| { + let ctx = ctx?.pop().unwrap().line().put("elseif "); + let ctx = self.visit_expression(ctx, c)?; + let ctx = ctx.put(" then").push(); + let ctx = self.visit_script(ctx, s)?; + Ok(ctx) + })?; + let ctx = if let Some(eb) = expr.else_branch.as_ref() { + let ctx = ctx.pop().unwrap().line().put("else").push(); + self.visit_script(ctx, eb)? + } else { + ctx + }; + let ctx = ctx.pop().unwrap().line().put("end"); + Ok(ctx) + } + + fn visit_table(&self, ctx: Builder, expr: &ast::Table) -> Result { + let ctx = ctx.put("{"); + let ctx = if let Some((k, v)) = expr.key_values.first() { + match k { + ast::TableKeyExpression::Identifier(k) => { + let ctx = ctx.put(k.0.clone()).put(" = "); + self.visit_expression(ctx, &v.clone().unwrap()) + } + ast::TableKeyExpression::Expression(k) => { + let ctx = ctx.put("["); + let ctx = self.visit_expression(ctx, &k)?.put("] = "); + self.visit_expression(ctx, &v.clone().unwrap()) + } + ast::TableKeyExpression::Implicit(k) => { + let ctx = ctx.put(k.0.clone()).put(" = ").put(k.0.clone()); + Ok(ctx) + } + }? + } else { + ctx + }; + let ctx = expr + .key_values + .iter() + .skip(1) + .fold(Ok(ctx), |ctx, (k, v)| { + let ctx = ctx?.put(", "); + match k { + ast::TableKeyExpression::Identifier(k) => { + let ctx = ctx.put(k.0.clone()).put(" = "); + self.visit_expression(ctx, &v.clone().unwrap()) + } + ast::TableKeyExpression::Expression(k) => { + let ctx = ctx.put("["); + let ctx = self.visit_expression(ctx, &k)?.put("] = "); + self.visit_expression(ctx, &v.clone().unwrap()) + } + ast::TableKeyExpression::Implicit(k) => { + let ctx = ctx.put(k.0.clone()).put(" = ").put(k.0.clone()); + Ok(ctx) + } + } + })?; + Ok(ctx.put("}")) + } + + fn visit_vector(&self, ctx: Builder, expr: &ast::Vector) -> Result { + let ctx = ctx.put("{"); + let ctx = if let Some(first) = expr.expressions.first() { + self.visit_expression(ctx, first)? + } else { + ctx + }; + let ctx = expr.expressions.iter().skip(1).fold(Ok(ctx), |ctx, v| { + let ctx = ctx?.put(", "); + let ctx = self.visit_expression(ctx, v)?; + Ok(ctx) + })?; + Ok(ctx.put("}")) + } + + fn visit_for(&self, ctx: Builder, expr: &ast::For) -> Result { + let ctx = match &expr.handler { + ast::AssignmentTarget::Destructuring(e) => { + let ctx = ctx.line().put("for __destructure__ in "); + let ctx = self.visit_expression(ctx, &expr.target)?; + let ctx = ctx.put(" do").push(); + let ctx = self.generate_destructured_assignment(ctx, &e)?; + let ctx = self.visit_script(ctx, &expr.body)?; + ctx.pop().unwrap().line().put("end") + } + ast::AssignmentTarget::Identifier(e) => { + let ctx = ctx.line().put(format!("for {} in ", e.0.clone())); + let ctx = self.visit_expression(ctx, &expr.target)?; + let ctx = ctx.put(" do").push(); + let ctx = self.visit_script(ctx, &expr.body)?; + ctx.pop().unwrap().line().put("end") + } + }; + Ok(ctx) + } + + fn visit_while(&self, ctx: Builder, expr: &ast::While) -> Result { + let ctx = match &expr.condition { + ast::ExpressionOrLet::Expression(e) => { + let ctx = ctx.line().put("while "); + let ctx = self.visit_expression(ctx, e)?; + let ctx = ctx.put(" do").push(); + let ctx = self.visit_script(ctx, &expr.body)?; + ctx.pop().unwrap().line().put("end") + } + ast::ExpressionOrLet::Let(e) => { + if let ast::AssignmentTarget::Identifier(id) = &e.target { + let ctx = ctx.line().put("do").push(); + let ctx = self.visit_declaration(ctx, e)?; + let ctx = ctx.line().put(format!("while {} do", id.0.clone())).push(); + let ctx = self.visit_script(ctx, &expr.body)?; + let ctx = self.visit_assignment( + ctx, + &ast::Assignment { + target: ast::MemberExpression { + head: ast::Expression::Identifier(id.clone()), + tail: vec![], + }, + value: e.value.clone().unwrap(), + extra: None, + }, + )?; + ctx.pop() + .unwrap() + .line() + .put("end") + .pop() + .unwrap() + .line() + .put("end") + } else { + panic!("Destructured while-let not supported"); + } + } + }; + Ok(ctx) + } + + fn visit_loop(&self, ctx: Builder, expr: &ast::Loop) -> Result { + let ctx = ctx.line().put("while true do").push(); + let ctx = self.visit_script(ctx, &expr.body)?; + let ctx = ctx.pop().unwrap().line().put("end"); + Ok(ctx) + } + + fn visit_match(&self, _ctx: Builder, _expr: &ast::Match) -> Result { + todo!("Match code generation not implemented yet.") + } + + fn visit_script(&self, ctx: Builder, script: &crate::parser::Script) -> Result { + let ctx = ctx + .put("-- Generated by the Saturnus compiler 1.0") + .line() + .put("-- WARNING! Changes may be discarded at any moment!"); + self.visit_block(ctx, script) + } +} diff --git a/src/main.rs b/src/main.rs index 77dbfb1..16e4bbc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,11 @@ use std::{fs::File, io::Write, path::Path}; use clap::Parser; -use code::Visitor; use errors::report_error; use runtime::RuntimeError; +use crate::code::{ast_visitor::Visitor, builder::Builder}; + #[cfg(test)] #[macro_use] extern crate spectral; @@ -72,29 +73,24 @@ struct CompilationOptions { } fn try_run(options: CompilationOptions, input: String, indent: String) -> Result<(), RuntimeError> { - let host = runtime::RuntimeHost::new(indent.clone()); - // TODO: Clean std code injection - let input = if options.args.no_std { - format!("let __modules__ = {{ }};\n{input}") + let header = format!("let __modules__ = {{ }};"); + let header = if options.args.no_std { + header } else { let embed = include_str!("assets/std.saturn"); - format!( - "let __modules__ = {{ - std: {{ - {embed} - }} -}}; -{input}" - ) + format!("{header}\n__modules__.std = {{\n{embed}\n}};") }; + let compiler = lua::visitor::LuaEmitter(); + if options.args.dump_saturnus { println!("{input}"); return Ok(()); } - let script = parser::Script::parse(input).map_err(|err| RuntimeError::ParseError(err))?; + let script = parser::Script::parse(format!("{header}\n{input}")) + .map_err(|err| RuntimeError::ParseError(err))?; let CompilationOptions { args, @@ -104,8 +100,8 @@ fn try_run(options: CompilationOptions, input: String, indent: String) -> Result if args.compile { println!("Compiling {:?}...", in_path); - let output = lua::LuaEmitter - .visit_script(code::Builder::new(indent), &script) + let output = compiler + .visit_script(Builder::new(indent), &script) .map_err(|err| RuntimeError::CompilationError(err))? .collect(); if args.print { @@ -116,6 +112,8 @@ fn try_run(options: CompilationOptions, input: String, indent: String) -> Result out_file.write_all(output.as_bytes()).unwrap(); } } else { + let host: runtime::RuntimeHost = + runtime::RuntimeHost::new(indent.clone(), Box::new(compiler)); host.evaluate(&script)?; } diff --git a/src/parser/ast.rs b/src/parser/ast.rs index 6e48156..866c891 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -94,9 +94,7 @@ pub struct Class { #[derive(Debug, Clone)] pub struct CallSubExpression { pub callee: Option, - // pub static_target: Option, pub arguments: Vec, - pub is_macro: bool, } impl Into for CallSubExpression { fn into(self) -> CallExpressionVariant { @@ -110,6 +108,19 @@ pub enum CallExpressionVariant { Member(MemberSegment), } +// TODO: Implement macros! +#[derive(Debug, Clone)] +pub enum MacroCallArguments { + FunctionLike(Vec), + BlockLike(Script), +} + +#[derive(Debug, Clone)] +pub struct MacroCallExpression { + pub target: Identifier, + pub arguments: MacroCallArguments, +} + #[derive(Debug, Clone)] pub struct CallExpression { pub head: CallSubExpression, diff --git a/src/parser/grammar.rs b/src/parser/grammar.rs index 10d3992..63b84e8 100644 --- a/src/parser/grammar.rs +++ b/src/parser/grammar.rs @@ -93,16 +93,16 @@ peg::parser! { rule call_expression() -> CallExpression = head:( - callee:member_expression() m:"!"? _ arguments:call_arguments() - { CallSubExpression { callee: Some(callee), is_macro: m.is_some(), arguments }.into() } + callee:member_expression() _ arguments:call_arguments() + { CallSubExpression { callee: Some(callee), arguments }.into() } / callee:member_expression() _ arg:table_expression() - { CallSubExpression { callee: Some(callee), is_macro: false, arguments: vec![arg] } } + { CallSubExpression { callee: Some(callee), arguments: vec![arg] } } ) tail:( _ "[" _ prop:expression() _ "]" { MemberSegment::Computed(prop).into() } / _ "." _ prop:identifier() { MemberSegment::IdentifierDynamic(prop).into() } / _ "::" _ prop:identifier() { MemberSegment::IdentifierStatic(prop).into() } - / _ arguments:call_arguments() { CallSubExpression { callee: None, is_macro: false, arguments }.into() } + / _ arguments:call_arguments() { CallSubExpression { callee: None, arguments }.into() } )* { CallExpression { head, tail } } diff --git a/src/runtime.rs b/src/runtime.rs index 920e32c..473f389 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -1,7 +1,8 @@ use crate::{ - code, - code::{VisitError, Visitor}, - lua, + code::{ + ast_visitor::{VisitError, Visitor}, + builder::Builder, + }, parser::Script, }; @@ -26,13 +27,18 @@ pub struct EvaluationOutput { /// This host will take care of evaluating the incoming Saturnus code. pub struct RuntimeHost { host: rlua::Lua, + compiler: Box, indent: String, } impl RuntimeHost { - pub fn new(indent: String) -> RuntimeHost { + pub fn new(indent: String, compiler: Box) -> RuntimeHost { let host = rlua::Lua::new(); - RuntimeHost { host, indent } + RuntimeHost { + host, + indent, + compiler, + } } pub fn run(&self, code: &String) -> Result { @@ -41,12 +47,9 @@ impl RuntimeHost { } pub fn evaluate(&self, script: &Script) -> Result { - let code = lua::LuaEmitter - .visit_script( - code::Builder::new(self.indent.clone()) - .put("-- Compiled by Saturnus compiler, warning: Changes may be discarded!"), - &script, - ) + let code = self + .compiler + .visit_script(Builder::new(self.indent.clone()), &script) .map_err(|err| RuntimeError::CompilationError(err))? .collect(); self.host