diff --git a/Cargo.lock b/Cargo.lock index b5c38d3..209efbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,7 +66,7 @@ dependencies = [ [[package]] name = "density_function_lang" -version = "3.1.2" +version = "3.2.0" dependencies = [ "clap", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index c5dffdc..81daeeb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "density_function_lang" -version = "3.1.2" +version = "3.2.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/example/test/main.test b/example/test/main.test new file mode 100644 index 0000000..f3aae58 --- /dev/null +++ b/example/test/main.test @@ -0,0 +1,35 @@ +template loop_for_index(start, end, step, f) { + loop_for_index_impl(start, end, step, start, f, false) +} + +template loop_for_index_impl(start, end, step, current, f, result) { + if (current == end || (start + step > start && current >= end) || (start + step < start && current <= end)) { + result + } else { + loop_for_index_impl(start, end, step, builtin.static(current + step), f, f(current)) + } +} + +template iteration(i) { + i // builtin.error("Iteration", i) +} + +export loop_for_index_test = loop_for_index(0, 20, 1, iteration); + +template array_fold(array, start_acc, f) { + array_fold_impl(array, start_acc, f, 0) +} + +template array_fold_impl(array, acc, f, index) { + if (index >= array.length) + acc + else { + array_fold_impl(array, f(acc, builtin.static(array[index])), f, builtin.static(index + 1)) + } +} + +template array_fold_iteration(acc, element) { + builtin.static(acc + element) +} + +export array_fold_test = array_fold([7, 3, 5, 1, 2, 8, 10, 9, 6, 4], 0, array_fold_iteration); diff --git a/example/test/target/array_fold_test.json b/example/test/target/array_fold_test.json new file mode 100644 index 0000000..c3f407c --- /dev/null +++ b/example/test/target/array_fold_test.json @@ -0,0 +1 @@ +55 diff --git a/example/test/target/loop_for_index_test.json b/example/test/target/loop_for_index_test.json new file mode 100644 index 0000000..d6b2404 --- /dev/null +++ b/example/test/target/loop_for_index_test.json @@ -0,0 +1 @@ +19 diff --git a/src/compiler/ast.rs b/src/compiler/ast.rs index 00f8b96..a4ccef9 100644 --- a/src/compiler/ast.rs +++ b/src/compiler/ast.rs @@ -116,6 +116,11 @@ pub enum Expr { receiver: Box, name: Token, }, + Index { + receiver: Box, + operator: Token, + index: Box, + }, BuiltinFunctionCall { name: Token, args: Vec, @@ -144,6 +149,7 @@ impl Debug for Expr { .collect::>().join(", ")) }, Expr::Member { receiver, name } => write!(f, "({:?}.{})", receiver, name.source()), + Expr::Index { receiver, index, .. } => write!(f, "({:?}[{:?}])", receiver, index), Expr::BuiltinFunctionCall { name, args } => { write!(f, "(builtin.{}({}))", name.source(), args.iter() .map(|expr| format!("{:?}", expr)) @@ -188,7 +194,17 @@ pub struct Template { pub file_path: Rc>, } -#[derive(Debug)] +impl PartialEq for Template { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + && self.receiver == other.receiver + && self.args == other.args + && self.expr == other.expr + && self.file_path == other.file_path + } +} + +#[derive(Debug, PartialEq)] pub struct Module { pub name: String, pub sub_modules: Vec>>, diff --git a/src/compiler/compiler.rs b/src/compiler/compiler.rs index 5196535..2691a56 100644 --- a/src/compiler/compiler.rs +++ b/src/compiler/compiler.rs @@ -3,6 +3,7 @@ use std::path::{Path, PathBuf}; use std::rc::Rc; use crate::compiler::ast::{Expr, JsonElement, Module, ExportFunction, Decl, Template, TemplateExpr, JsonElementType, CompiledExport}; use crate::compiler::lexer::{Token, TokenPos}; +use crate::Config; struct TemplateScope { pub args: Vec<(String, JsonElement)>, @@ -17,6 +18,8 @@ struct TemplateCall { pub path: Rc>, pub line: i32, pub column: i32, + + pub tailrec_index: u32, } pub struct Compiler { @@ -31,11 +34,11 @@ pub struct Compiler { path: Rc>, target_dir: PathBuf, had_error: bool, panic_mode: bool, - verbose: bool, + config: Rc, } impl Compiler { - pub fn new(path: PathBuf, target_dir: PathBuf, verbose: bool) -> Compiler { + pub fn new(path: PathBuf, target_dir: PathBuf, config: Rc) -> Compiler { Compiler { current_module: vec![], top_level_module: Rc::new(RefCell::new(Module { name: String::from(""), @@ -50,7 +53,7 @@ impl Compiler { path: Rc::new(RefCell::new(path)), target_dir, had_error: false, panic_mode: false, - verbose, + config, } } @@ -205,7 +208,7 @@ impl Compiler { } }; - let mut compiler = Compiler::new(path, self.target_dir.to_owned(), self.verbose); + let mut compiler = Compiler::new(path, self.target_dir.to_owned(), Rc::clone(&self.config)); compiler.outer_top_level_module = Some(Rc::clone(&self.top_level_module)); compiler.outer_current_module = Some(self.current_module.iter().map(|module| Rc::clone(module)).collect()); compiler.compile(declarations); @@ -366,6 +369,13 @@ impl Compiler { let compiled_receiver = self.compile_expr(*receiver, static_expr); self.evaluate_member(compiled_receiver, name, static_expr) }, + Expr::Index { receiver, operator, index } => { + let compiled_receiver = self.compile_expr(*receiver, static_expr); + let compiled_index = self.compile_expr(*index, static_expr); + + let pos = *operator.start(); + self.evaluate_template(Expr::Identifier(operator), pos, vec![compiled_receiver, compiled_index], static_expr) + }, Expr::BuiltinFunctionCall { name, args } => { self.evaluate_builtin_call(name, args, static_expr) }, @@ -380,19 +390,19 @@ impl Compiler { } } - fn evaluate_template(&mut self, callee: Expr, token_pos: TokenPos, args: Vec, static_expr: bool) -> JsonElement { + fn begin_evaluate_template(&mut self, callee: Expr, token_pos: TokenPos, args: &Vec, static_expr: bool) -> Result<(Option, Rc>), JsonElement> { let (receiver, name) = match callee { Expr::Member { receiver, name} => (Some(self.compile_expr(*receiver, static_expr)), name), Expr::Identifier(name) => (None, name), _ => { self.error_at(token_pos, "Cannot call non-identifier expression", true); - return JsonElement::Error; + return Err(JsonElement::Error); }, }; if static_expr { if let Some(result) = self.evaluate_static_operator(&name, &args) { - return result; + return Err(result); } } @@ -400,9 +410,14 @@ impl Compiler { Some(template) => template, None => { self.error_at(*name.start(), &format!("Unresolved function call: {}", name.source()), false); - return JsonElement::Error; + return Err(JsonElement::Error); } }; + + Ok((receiver, template)) + } + + fn begin_compile_template(&mut self, template: Rc>, receiver: Option, token_pos: TokenPos, args: &Vec) -> (TemplateExpr, Vec>>, Rc>) { let template_borrow = template.borrow(); let mut scope_args = vec![]; @@ -422,7 +437,7 @@ impl Compiler { self.template_scopes.push(TemplateScope { args: scope_args }); - if self.verbose { + if self.config.verbose { self.template_call_stack.push(TemplateCall { template: Rc::clone(&template), name: template_borrow.name.to_owned(), @@ -430,6 +445,7 @@ impl Compiler { arg_count: template_borrow.args.len() as i32, path: Rc::clone(&self.path), line: token_pos.line, column: token_pos.column, + tailrec_index: 0, }); } @@ -443,12 +459,57 @@ impl Compiler { std::mem::swap(&mut self.current_module, &mut template_current_modules); std::mem::swap(&mut self.path, &mut template_file_path); - let expr = self.compile_template_expr(template_expr.clone(), static_expr); - if self.verbose { self.template_call_stack.pop(); } + (template_expr, template_current_modules, template_file_path) + } + + fn begin_compile_tailrec_template(&mut self, template: Rc>, receiver: Option, args: &Vec) -> TemplateExpr { + let template_borrow = template.borrow(); + + let mut scope_args = vec![]; + // eprintln!("name: {}; template receiver: {}; provided receiver: {}; template args: {:?}; provided args: {:?}", + // &template_borrow.name, template_borrow.receiver, receiver.is_some(), &template_borrow.args, &args); + + if let Some(receiver) = receiver { + scope_args.push((String::from("this"), receiver)); + } + + for i in 0..template_borrow.args.len() { + let name = template_borrow.args[i].clone(); + let element = args[i].clone(); + + scope_args.push((name, element)); + } + + *self.template_scopes.last_mut().expect("Internal compiler error: No template scope for tailrec template") = + TemplateScope { args: scope_args }; + + if self.config.verbose { + self.template_call_stack.last_mut().expect("Internal compiler error: No template call stack frame for tailrec template").tailrec_index += 1; + } + + let template_expr = template_borrow.expr.clone(); + drop(template_borrow); + + template_expr + } + + fn end_compile_template(&mut self, mut template_current_modules: Vec>>, mut template_file_path: Rc>) { + if self.config.verbose { self.template_call_stack.pop(); } self.template_scopes.pop(); std::mem::swap(&mut self.current_module, &mut template_current_modules); std::mem::swap(&mut self.path, &mut template_file_path); + } + + fn evaluate_template(&mut self, callee: Expr, token_pos: TokenPos, args: Vec, static_expr: bool) -> JsonElement { + let (receiver, template) = match self.begin_evaluate_template(callee, token_pos, &args, static_expr) { + Ok(result) => result, + Err(result) => return result, + }; + let (template_expr, template_current_modules, template_file_path) = self.begin_compile_template(Rc::clone(&template), receiver, token_pos, &args); + + let expr = self.compile_template_expr(template_expr.clone(), Some(template), static_expr); + self.end_compile_template(template_current_modules, template_file_path); expr } @@ -545,6 +606,23 @@ impl Compiler { "&&" => binary_logic_op!(args, &&), "||" => binary_logic_op!(args, ||), + + "[" => match &args[0] { + JsonElement::Array(array) => match &args[1] { + JsonElement::ConstantInt(index) => { + if *index < 0 || *index as usize > array.len() { + self.error_at_with_context(*name.start(), "Array index out of bounds", vec![ + ("Length", JsonElement::ConstantInt(array.len() as i32)), + ("Index", JsonElement::ConstantInt(*index)) + ], false) + } + + return Some(array[*index as usize].clone()) + }, + _ => {}, + }, + _ => {}, + }, _ => {}, } } @@ -559,29 +637,61 @@ impl Compiler { ], false); } - fn compile_template_expr(&mut self, expr: TemplateExpr, static_expr: bool) -> JsonElement { - match expr { - TemplateExpr::Block { expressions, last } => { - for expr in expressions { - self.compile_template_expr(expr, static_expr); - } + fn compile_template_expr(&mut self, mut expr: TemplateExpr, template: Option>>, static_expr: bool) -> JsonElement { + loop { + match expr { + TemplateExpr::Block { expressions, last } => { + for expr in expressions { + self.compile_template_expr(expr, None, static_expr); + } - self.compile_template_expr(*last, static_expr) - }, - TemplateExpr::If { token, condition, then, otherwise } => { - let compiled_condition = self.compile_expr(condition, true); - - match compiled_condition { - JsonElement::ConstantBoolean(value) => - self.compile_template_expr(if value { *then } else { *otherwise }, static_expr), - element => { - self.error_at_with_context(*token.end(), "Condition of 'if' expression must be a boolean value", - vec![("Context", element)], true); - JsonElement::Error - }, + expr = *last; + // self.compile_template_expr(*last, static_expr) + }, + TemplateExpr::If { token, condition, then, otherwise } => { + let compiled_condition = self.compile_expr(condition, true); + + match compiled_condition { + JsonElement::ConstantBoolean(value) => { + expr = if value { *then } else { *otherwise }; + // self.compile_template_expr(if value { *then } else { *otherwise }, static_expr) + }, + element => { + self.error_at_with_context(*token.end(), "Condition of 'if' expression must be a boolean value", + vec![("Context", element)], true); + return JsonElement::Error; + }, + } + }, + TemplateExpr::Simple(simple_expr) => { + if let Some(outer_template) = &template { + match simple_expr { + Expr::FunctionCall { callee, token, args } => { + let compiled_args = args.into_iter().map(|arg| self.compile_expr(arg, static_expr)).collect(); + + let (receiver, template) = match self.begin_evaluate_template(*callee, *token.start(), &compiled_args, static_expr) { + Ok(result) => result, + Err(result) => return result, + }; + + if &template == outer_template { + expr = self.begin_compile_tailrec_template(template, receiver, &compiled_args); + continue; + } else { + let (template_expr, template_current_modules, template_file_path) = self.begin_compile_template(Rc::clone(&template), receiver, *token.start(), &compiled_args); + let expr = self.compile_template_expr(template_expr.clone(), Some(template), static_expr); + + self.end_compile_template(template_current_modules, template_file_path); + return expr + } + }, + _ => return self.compile_expr(simple_expr, static_expr), + } + } else { + return self.compile_expr(simple_expr, static_expr) + } } - }, - TemplateExpr::Simple(expr) => self.compile_expr(expr, static_expr), + } } } @@ -593,6 +703,14 @@ impl Compiler { } } + if let Some((_, arg)) = self.template_scopes.last().and_then(|scope| scope.args.iter() + .find(|(arg_name, _)| arg_name == name.source())) { + match arg { + JsonElement::Template(template) => return Some(Rc::clone(template)), + _ => {}, + } + } + let mut module_index: isize = self.current_module.len() as isize - 1; while module_index >= -1 { @@ -732,6 +850,22 @@ impl Compiler { receiver => { if static_expr && name.source() == "type" { return JsonElement::Type(JsonElementType::from(receiver)); + } else if static_expr && name.source() == "length" { + match receiver { + JsonElement::Array(array) => return JsonElement::ConstantInt(array.len() as i32), + _ => {}, + } + } + + if static_expr { + match receiver { + JsonElement::Object(fields) => { + if let Some((_, field)) = fields.iter().find(|(key, _)| name.source() == key) { + return field.clone(); + } + }, + _ => {}, + } } self.error_at(*name.start(), "Tried to get a member of non-module value", true); @@ -775,11 +909,11 @@ impl Compiler { eprintln!(" {}: {:?}", *name, context_element); } - if self.verbose && !context.is_empty() && !self.template_call_stack.is_empty() { + if self.config.verbose && !context.is_empty() && !self.template_call_stack.is_empty() { eprintln!(); } - if self.verbose { + if self.config.verbose { for template_call in self.template_call_stack.iter().rev() { let mut args = vec![]; @@ -803,6 +937,14 @@ impl Compiler { } else { None } } else { None }).unwrap_or_else(|| String::new()); + if template_call.tailrec_index > 0 { + if template_call.tailrec_index == 1 { + eprint!(" ... 1 tail recursion call\n"); + } else { + eprint!(" ... {} tail recursion calls\n", template_call.tailrec_index); + } + } + eprintln!(" at [{}:{}:{}] {}{}({})", template_call.path.borrow().to_string_lossy(), template_call.line, template_call.column, template_path, &template_call.name, args.join(", ")); } diff --git a/src/compiler/parser.rs b/src/compiler/parser.rs index 1e21f8e..8431520 100644 --- a/src/compiler/parser.rs +++ b/src/compiler/parser.rs @@ -6,7 +6,7 @@ use crate::compiler::lexer::{Lexer, LexerError, Token, TokenPos, TokenType}; use crate::compiler::ast::{Expr, JsonElementType, Decl, TemplateExpr}; lazy_static! { - static ref ALLOWED_TEMPLATE_NAME_TYPES: [TokenType; 15] = [ + static ref ALLOWED_TEMPLATE_NAME_TYPES: [TokenType; 16] = [ TokenType::Identifier, TokenType::Plus, TokenType::Minus, TokenType::Multiply, TokenType::Divide, TokenType::Equal, TokenType::NotEqual, @@ -14,6 +14,7 @@ lazy_static! { TokenType::Less, TokenType::LessEqual, TokenType::And, TokenType::ShortcircuitAnd, TokenType::Or, TokenType::ShortcircuitOr, + TokenType::SquareBracketLeft, // [] operator ]; } @@ -93,6 +94,10 @@ impl<'source> Parser<'source> { self.expect_any(&*ALLOWED_TEMPLATE_NAME_TYPES, "Expected name after 'template'"); let name = self.previous.clone(); + if name.token_type() == TokenType::SquareBracketLeft { + self.expect(TokenType::SquareBracketRight, "Expected ']' after '[' in template name"); + } + self.expect(TokenType::ParenthesisLeft, "Expected '(' after template name"); let mut arguments = vec![]; let mut this = None; @@ -370,6 +375,13 @@ impl<'source> Parser<'source> { let name = self.previous.clone(); expr = Expr::Member { receiver: Box::new(expr), name } + } else if self.matches(TokenType::SquareBracketLeft) { + let token = self.previous.clone(); + + let index = self.parse_expression(); + self.expect(TokenType::SquareBracketRight, "Expected ']' after index expression"); + + expr = Expr::Index { receiver: Box::new(expr), operator: token, index: Box::new(index) } } else { break; } diff --git a/src/compiler/writer.rs b/src/compiler/writer.rs index 25423b9..b4c71d5 100644 --- a/src/compiler/writer.rs +++ b/src/compiler/writer.rs @@ -62,11 +62,11 @@ impl JsonWriter { let field_count = fields.len(); for (i, (name, field)) in fields.into_iter().enumerate() { - write!(out, "\"{}\": ", name)?; + write!(out, "\"{}\":", name)?; self.write_element(field, out)?; if i < field_count - 1 { - write!(out, ", ")?; + write!(out, ",")?; } } @@ -106,7 +106,7 @@ impl JsonWriter { self.write_element(element, out)?; if i < field_count - 1 { - write!(out, ", ")?; + write!(out, ",")?; } } diff --git a/src/lib.rs b/src/lib.rs index 7e1179f..8b43789 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,11 @@ pub struct Config { #[clap(short, long, help = "Print verbose log output")] pub verbose: bool, + + #[clap(long, help = "Disable pretty printing")] + pub no_pretty_print: bool, + #[clap(long, default_value = " ", help = "Indentation string for pretty printing")] + pub indentation: String, } pub fn parse(path: &Path) -> Result>, std::io::Error> { @@ -38,13 +43,13 @@ pub fn parse(path: &Path) -> Result>, std::io::Error> { Ok(Some(statements)) } -pub fn compile(path: PathBuf, target_dir: PathBuf, verbose: bool) -> Result>>, Compiler)>, std::io::Error> { +pub fn compile(path: PathBuf, target_dir: PathBuf, config: Rc) -> Result>>, Compiler)>, std::io::Error> { let statements = match parse(&path)? { Some(result) => result, None => return Ok(None), }; - let mut compiler = Compiler::new(path.to_owned(), target_dir.to_owned(), verbose); + let mut compiler = Compiler::new(path.to_owned(), target_dir.to_owned(), config); compiler.compile(statements); let functions = compiler.collect_exports(); @@ -56,15 +61,15 @@ pub fn compile(path: PathBuf, target_dir: PathBuf, verbose: bool) -> Result Result<(), std::io::Error> { - let config: Config = Config::parse(); + let config: Rc = Rc::new(Config::parse()); - let (functions, _) = match compile(config.input.to_owned(), config.target_dir.to_owned(), config.verbose)? { + let (functions, _) = match compile(config.input.to_owned(), config.target_dir.to_owned(), Rc::clone(&config))? { Some(result) => result, None => return Err(std::io::Error::from(std::io::ErrorKind::InvalidData)), }; std::fs::create_dir_all(&config.target_dir)?; - let mut writer = JsonWriter::new(String::from(" "), true); + let mut writer = JsonWriter::new(config.indentation.to_owned(), !config.no_pretty_print); for function in functions { let function = &*function.borrow();