diff --git a/--release b/--release new file mode 100755 index 00000000..f57264ac --- /dev/null +++ b/--release @@ -0,0 +1,17 @@ + +function __15_v0 { + echo "foo + bar" +}; +function __16_v0 { + echo "bar" +} +function __19_v0 { + abc=$1 + test=$2 + echo "$(__16_v0 )"; + echo "${test}" +}; +echo "$(__19_v0 "test" "this")" +echo "$(__19_v0 "hello world" "this")"; +echo "$(__15_v0 )" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 954290c3..6068420a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,8 @@ test*.ab test*.sh +# Flamegraph files +flamegraph.svg + # MacOS **/.DS_Store \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 088e8f82..2964ae6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,9 +41,9 @@ dependencies = [ [[package]] name = "heraclitus-compiler" -version = "1.5.4" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12eb414683f8ccfcdf8a9b64ee8189627c931da539f19a941b4bc0e7d1057223" +checksum = "07fe84cac1d31ea6cca6ea79984b2e5c2f2eabb973a0e26cdeeaef0e0906a26b" dependencies = [ "capitalize", "colored", diff --git a/Cargo.toml b/Cargo.toml index a382225a..1b5c64bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -heraclitus-compiler = "1.5.4" +heraclitus-compiler = "1.5.6" similar-string = "1.4.2" colored = "2.0.0" + +[profile.release] +debug = true \ No newline at end of file diff --git a/README.md b/README.md index becf91e1..c5517033 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,13 @@ Finally in order to build amber build.ab ``` -In order to parse AST with a debug trace run cargo with the following environment variable: +Debugging Amber: ```bash +// Shows the AST AMBER_DEBUG_PARSER=true cargo run +// Shows the time it took to compile each phase +AMBER_DEBUG_TIME=true cargo run + +// Flamegraph is a profiling tool that is used to visualize the time each function took to execute +sudo cargo flamegraph -- ``` diff --git a/meta.txt b/meta.txt new file mode 100644 index 00000000..d61921f4 --- /dev/null +++ b/meta.txt @@ -0,0 +1,2 @@ +ParserMetadata { expr: [Tok[pub 1:1], Tok[fun 1:5], Tok[input 1:9], Tok[ 1:14], Tok[ 1:15], Tok[ 1:17], Tok[$read$ 2:5], Tok[echo 3:5], Tok['$REPLY' 3:10], Tok[ 4:1], Tok[pub 6:1], Tok[fun 6:5], Tok[replace_once 6:9], Tok[ 6:21], Tok[source 6:22], Tok[, 6:28], Tok[pattern 6:30], Tok[, 6:37], Tok[replacement 6:39], Tok[ 6:50], Tok[ 6:52], Tok[echo 7:5], Tok[$echo "\$\{source/ 7:10], Tok[ 7:28], Tok[pattern 7:29], Tok[ 7:36], Tok[/ 7:37], Tok[ 7:38], Tok[replacement 7:39], Tok[ 7:50], Tok[}"$ 7:51], Tok[ 8:1], Tok[pub 10:1], Tok[fun 10:5], Tok[replace 10:9], Tok[ 10:16], Tok[source 10:17], Tok[, 10:23], Tok[pattern 10:25], Tok[, 10:32], Tok[replacement 10:34], Tok[ 10:45], Tok[ 10:47], Tok[echo 11:5], Tok[$echo "\$\{source// 11:10], Tok[ 11:29], Tok[pattern 11:30], Tok[ 11:37], Tok[/ 11:38], Tok[ 11:39], Tok[replacement 11:40], Tok[ 11:51], Tok[}"$ 11:52], Tok[ 12:1], Tok[pub 14:1], Tok[fun 14:5], Tok[replace_regex 14:9], Tok[ 14:22], Tok[source 14:23], Tok[ 14:29], Tok[Text 14:31], Tok[, 14:35], Tok[pattern 14:37], Tok[ 14:44], Tok[Text 14:46], Tok[, 14:50], Tok[replacement 14:52], Tok[ 14:63], Tok[Text 14:65], Tok[ 14:69], Tok[ 14:70], Tok[Text 14:72], Tok[ 14:77], Tok[echo 15:5], Tok[$echo " 15:10], Tok[ 15:17], Tok[source 15:18], Tok[ 15:24], Tok[" | sed -e "s/ 15:25], Tok[ 15:39], Tok[pattern 15:40], Tok[ 15:47], Tok[/ 15:48], Tok[ 15:49], Tok[replacement 15:50], Tok[ 15:61], Tok[/g"$ 15:62], Tok[ 16:1], Tok[pub 18:1], Tok[fun 18:5], Tok[file_read 18:9], Tok[ 18:18], Tok[path 18:19], Tok[ 18:23], Tok[ 18:25], Tok[echo 19:5], Tok[$cat " 19:10], Tok[ 19:16], Tok[path 19:17], Tok[ 19:21], Tok["$ 19:22], Tok[ 20:1], Tok[pub 22:1], Tok[fun 22:5], Tok[file_write 22:9], Tok[ 22:19], Tok[path 22:20], Tok[, 22:24], Tok[content 22:26], Tok[ 22:33], Tok[ 22:35], Tok[$echo " 23:5], Tok[ 23:12], Tok[content 23:13], Tok[ 23:20], Tok[" > " 23:21], Tok[ 23:26], Tok[path 23:27], Tok[ 23:31], Tok["$ 23:32], Tok[ 24:1], Tok[pub 26:1], Tok[fun 26:5], Tok[file_append 26:9], Tok[ 26:20], Tok[path 26:21], Tok[, 26:25], Tok[content 26:27], Tok[ 26:34], Tok[ 26:36], Tok[$echo " 27:5], Tok[ 27:12], Tok[content 27:13], Tok[ 27:20], Tok[" >> " 27:21], Tok[ 27:27], Tok[path 27:28], Tok[ 27:32], Tok["$ 27:33], Tok[ 28:1]], index: 10, path: Some("[standard library]"), code: Some("pub fun input() {\n $read$\n echo '$REPLY'\n}\n\npub fun replace_once(source, pattern, replacement) {\n echo $echo \"\\$\\{source/{pattern}/{replacement}}\"$\n}\n\npub fun replace(source, pattern, replacement) {\n echo $echo \"\\$\\{source//{pattern}/{replacement}}\"$\n}\n\npub fun replace_regex(source: Text, pattern: Text, replacement: Text): Text {\n echo $echo \"{source}\" | sed -e \"s/{pattern}/{replacement}/g\"$\n}\n\npub fun file_read(path) {\n echo $cat \"{path}\"$\n}\n\npub fun file_write(path, content) {\n $echo \"{content}\" > \"{path}\"$\n}\n\npub fun file_append(path, content) {\n $echo \"{content}\" >> \"{path}\"$\n}"), binop_border: None, mem: Memory { scopes: [ScopeUnit { vars: {}, funs: {} }], function_map: FunctionMap { map: {}, current_id: 0 }, variable_id: 0, exports: Exports { values: [] } }, debug: None, trace: [PositionInfo { path: Some("test.ab"), position: Pos(2, 1), row: 2, col: 1, len: 6, data: None }, PositionInfo { path: Some("test4.ab"), position: Pos(1, 1), row: 1, col: 1, len: 6, data: None }], import_history: ImportHistory { imports: ["test.ab", "test4.ab", "[standard library]"], import_graph: [[1], [2], []], import_blocks: [None, None, None], exports: [None, None, None] }, loop_ctx: false, function_ctx: false, messages: [] } +false diff --git a/src/cli/cli_interface.rs b/src/cli/cli_interface.rs index bcd64d55..f4a5bb68 100644 --- a/src/cli/cli_interface.rs +++ b/src/cli/cli_interface.rs @@ -43,7 +43,7 @@ impl CLI { match self.flags.get_flag("-e").unwrap().value.clone() { Some(code) => { match AmberCompiler::new(code, None).compile() { - Ok((code, messages)) => { + Ok((messages, code)) => { messages.iter().for_each(|m| m.show()); AmberCompiler::execute(code, self.flags.get_args()) }, @@ -67,7 +67,7 @@ impl CLI { match self.read_file(input.clone()) { Ok(code) => { match AmberCompiler::new(code, Some(input)).compile() { - Ok((code, messages)) => { + Ok((messages, code)) => { messages.iter().for_each(|m| m.show()); // Save to the output file if self.args.len() >= 3 { diff --git a/src/cli/mod.rs b/src/cli/mod.rs index c69657dc..87d100d9 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,5 +1,2 @@ pub mod flag_registry; pub mod cli_interface; - -#[cfg(test)] -pub mod tests; diff --git a/src/compiler.rs b/src/compiler.rs index 6cfa5074..6a31313d 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -91,10 +91,11 @@ impl AmberCompiler { } pub fn translate(&self, block: Block, meta: ParserMetadata) -> String { - let imports_sorted = meta.import_history.topological_sort(); - let imports_blocks = meta.import_history.import_blocks.clone(); - let _imports = meta.import_history.imports.clone(); - let mut meta = TranslateMetadata::new(&meta); + let imports_sorted = meta.import_cache.topological_sort(); + let imports_blocks = meta.import_cache.files.iter() + .map(|file| file.metadata.as_ref().map(|meta| meta.block.clone())) + .collect::>>(); + let mut meta = TranslateMetadata::new(meta); let mut result = vec![]; let time = Instant::now(); for index in imports_sorted.iter() { @@ -110,10 +111,10 @@ impl AmberCompiler { result.join("\n") } - pub fn compile(&self) -> Result<(String, Vec), Message> { + pub fn compile(&self) -> Result<(Vec, String), Message> { self.tokenize() .and_then(|tokens| self.parse(tokens)) - .map(|(block, meta)| (self.translate(block, meta.clone()), meta.messages)) + .map(|(block, meta)| (meta.messages.clone(), self.translate(block, meta))) } pub fn execute(code: String, flags: &[String]) { @@ -123,7 +124,7 @@ impl AmberCompiler { #[allow(dead_code)] pub fn test_eval(&mut self) -> Result { - self.compile().map_or_else(Err, |(code, _)| { + self.compile().map_or_else(Err, |(_, code)| { let child = Command::new("/bin/bash") .arg("-c").arg::<&str>(code.as_ref()) .output().unwrap(); diff --git a/src/main.rs b/src/main.rs index cbb742b4..f1aaca1c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,9 @@ mod compiler; pub mod cli; use cli::cli_interface::CLI; +#[cfg(test)] +pub mod tests; + fn main() { let mut cli = CLI::new(); cli.run(); diff --git a/src/modules/block.rs b/src/modules/block.rs index 2668f709..ca6d98d6 100644 --- a/src/modules/block.rs +++ b/src/modules/block.rs @@ -30,7 +30,7 @@ impl SyntaxModule for Block { } fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { - meta.mem.push_scope(); + meta.push_scope(); while let Some(token) = meta.get_current_token() { // Handle the end of line or command if ["\n", ";"].contains(&token.word.as_str()) { @@ -55,7 +55,7 @@ impl SyntaxModule for Block { } self.statements.push(statemant); } - meta.mem.pop_scope(); + meta.pop_scope(); Ok(()) } } diff --git a/src/modules/expression/binop/mod.rs b/src/modules/expression/binop/mod.rs index 071a289a..7d129f33 100644 --- a/src/modules/expression/binop/mod.rs +++ b/src/modules/expression/binop/mod.rs @@ -45,7 +45,7 @@ pub fn parse_left_expr(meta: &mut ParserMetadata, module: &mut Expr, op: &str) - } // Check if this binop can actually take place and return a new boundary for the left hand expression -fn binop_left_cut(meta: &mut ParserMetadata, op: &str) -> Result { +pub fn binop_left_cut(meta: &mut ParserMetadata, op: &str) -> Result { let old_index = meta.get_index(); let mut parenthesis = 0; while let Some(token) = meta.get_current_token() { diff --git a/src/modules/expression/expr.rs b/src/modules/expression/expr.rs index 11ae6a78..346f0367 100644 --- a/src/modules/expression/expr.rs +++ b/src/modules/expression/expr.rs @@ -123,16 +123,9 @@ impl SyntaxModule for Expr { } fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { - let statements = self.get_modules(); - for statement in statements { - // Handle comments - if let Some(token) = meta.get_current_token() { - if token.word.starts_with('#') { - meta.increment_index(); - continue - } - } - match self.parse_match(meta, statement) { + let exprs = self.get_modules(); + for expr in exprs { + match self.parse_match(meta, expr) { Ok(()) => return Ok(()), Err(failure) => { if let Failure::Loud(err) = failure { diff --git a/src/modules/function/declaration.rs b/src/modules/function/declaration.rs index dc3ed008..9167b0c7 100644 --- a/src/modules/function/declaration.rs +++ b/src/modules/function/declaration.rs @@ -1,6 +1,7 @@ use heraclitus_compiler::prelude::*; use crate::modules::types::Type; use crate::modules::variable::variable_name_extensions; +use crate::utils::function_interface::FunctionInterface; use crate::utils::metadata::{ParserMetadata, TranslateMetadata}; use crate::translate::module::TranslateModule; use crate::modules::block::Block; @@ -11,7 +12,8 @@ use super::declaration_utils::*; #[derive(Debug, Clone)] pub struct FunctionDeclaration { pub name: String, - pub args: Vec<(String, Type)>, + pub arg_names: Vec, + pub arg_types: Vec, pub returns: Type, pub body: Block, pub id: usize, @@ -20,10 +22,10 @@ pub struct FunctionDeclaration { impl FunctionDeclaration { fn set_args_as_variables(&self, meta: &mut TranslateMetadata) -> Option { - if !self.args.is_empty() { + if !self.arg_names.is_empty() { meta.increase_indent(); let mut result = vec![]; - for (index, (name, _kind)) in self.args.clone().iter().enumerate() { + for (index, name) in self.arg_names.clone().iter().enumerate() { let indent = meta.gen_indent(); result.push(format!("{indent}{name}=${}", index + 1)); } @@ -39,7 +41,8 @@ impl SyntaxModule for FunctionDeclaration { fn new() -> Self { FunctionDeclaration { name: String::new(), - args: vec![], + arg_names: vec![], + arg_types: vec![], returns: Type::Generic, body: Block::new(), id: 0, @@ -67,8 +70,14 @@ impl SyntaxModule for FunctionDeclaration { let name = variable(meta, variable_name_extensions())?; // Optionally parse the argument type match token(meta, ":") { - Ok(_) => self.args.push((name.clone(), parse_type(meta)?)), - Err(_) => self.args.push((name, Type::Generic)) + Ok(_) => { + self.arg_names.push(name.clone()); + self.arg_types.push(parse_type(meta)?); + }, + Err(_) => { + self.arg_names.push(name.clone()); + self.arg_types.push(Type::Generic); + } } match token(meta, ")") { Ok(_) => break, @@ -83,15 +92,19 @@ impl SyntaxModule for FunctionDeclaration { // Parse the body token(meta, "{")?; let (index_begin, index_end) = skip_function_body(meta); + // Create a new context with the function body + let expr = meta.context.expr[index_begin..index_end].to_vec(); + let ctx = meta.context.clone().function_invocation(expr); token(meta, "}")?; // Add the function to the memory - self.id = handle_add_function(meta, tok, FunctionDeclSyntax { + self.id = handle_add_function(meta, tok, FunctionInterface { + id: None, name: self.name.clone(), - args: self.args.clone(), + arg_names: self.arg_names.clone(), + arg_types: self.arg_types.clone(), returns: self.returns.clone(), - body: meta.expr[index_begin..index_end].to_vec(), is_public: self.is_public - })?; + }, ctx)?; Ok(()) }, |pos| { error_pos!(meta, pos, format!("Failed to parse function declaration '{}'", self.name)) @@ -102,7 +115,7 @@ impl SyntaxModule for FunctionDeclaration { impl TranslateModule for FunctionDeclaration { fn translate(&self, meta: &mut TranslateMetadata) -> String { let mut result = vec![]; - let blocks = meta.mem.get_function_instances(self.id).unwrap().to_vec(); + let blocks = meta.fun_cache.get_instances_cloned(self.id).unwrap(); // Translate each one of them for (index, function) in blocks.iter().enumerate() { let name = format!("__{}_v{}", self.id, index); @@ -111,7 +124,7 @@ impl TranslateModule for FunctionDeclaration { if let Some(args) = self.set_args_as_variables(meta) { result.push(args); } - result.push(function.body.translate(meta)); + result.push(function.block.translate(meta)); result.push(meta.gen_indent() + "}"); } // Return the translation diff --git a/src/modules/function/declaration_utils.rs b/src/modules/function/declaration_utils.rs index f9d42476..b34529b1 100644 --- a/src/modules/function/declaration_utils.rs +++ b/src/modules/function/declaration_utils.rs @@ -2,15 +2,8 @@ use heraclitus_compiler::prelude::*; use crate::modules::types::Type; use crate::utils::ParserMetadata; use crate::modules::variable::{handle_identifier_name}; - -#[derive(Debug, Clone)] -pub struct FunctionDeclSyntax { - pub name: String, - pub args: Vec<(String, Type)>, - pub returns: Type, - pub body: Vec, - pub is_public: bool -} +use crate::utils::context::Context; +use crate::utils::function_interface::FunctionInterface; pub fn skip_function_body(meta: &mut ParserMetadata) -> (usize, usize) { let index_begin = meta.get_index(); @@ -31,26 +24,26 @@ pub fn skip_function_body(meta: &mut ParserMetadata) -> (usize, usize) { pub fn handle_existing_function(meta: &mut ParserMetadata, tok: Option) -> Result<(), Failure> { let name = tok.as_ref().unwrap().word.clone(); handle_identifier_name(meta, &name, tok.clone())?; - if meta.mem.get_function(&name).is_some() { + if meta.get_fun_declaration(&name).is_some() { return error!(meta, tok, format!("Function '{}' already exists", name)) } Ok(()) } -pub fn handle_add_function(meta: &mut ParserMetadata, tok: Option, decl: FunctionDeclSyntax) -> Result { - let name = decl.name.clone(); +pub fn handle_add_function(meta: &mut ParserMetadata, tok: Option, fun: FunctionInterface, ctx: Context) -> Result { + let name = fun.name.clone(); handle_identifier_name(meta, &name, tok.clone())?; - let any_generic = decl.args.iter().any(|(_, kind)| kind == &Type::Generic); - let any_typed = decl.args.iter().any(|(_, kind)| kind != &Type::Generic); + let any_generic = fun.arg_types.iter().any(|kind| kind == &Type::Generic); + let any_typed = fun.arg_types.iter().any(|kind| kind != &Type::Generic); // Either all arguments are generic or typed - if any_typed && (any_generic || decl.returns == Type::Generic) { + if any_typed && (any_generic || fun.returns == Type::Generic) { return error!(meta, tok => { message: format!("Function '{}' has a mix of generic and typed arguments", name), comment: "Please decide whether to use generics or types for all arguments" }) } // Try to add the function to the memory - match meta.mem.add_function_declaration(meta.clone(), decl) { + match meta.add_fun_declaration(fun, ctx) { // Return the id of the function Some(id) => Ok(id), // If the function already exists, show an error diff --git a/src/modules/function/invocation.rs b/src/modules/function/invocation.rs index b01fbca0..b47e43f1 100644 --- a/src/modules/function/invocation.rs +++ b/src/modules/function/invocation.rs @@ -55,7 +55,7 @@ impl SyntaxModule for FunctionInvocation { }; } let types = self.args.iter().map(|e| e.get_type()).collect::>(); - (self.kind, self.variant_id) = handle_function_parameters(meta, &self.name, &types, tok)?; + (self.kind, self.variant_id) = handle_function_parameters(meta, self.id, &self.name, &types, tok)?; Ok(()) } } diff --git a/src/modules/function/invocation_utils.rs b/src/modules/function/invocation_utils.rs index d4273d5c..8bb36daa 100644 --- a/src/modules/function/invocation_utils.rs +++ b/src/modules/function/invocation_utils.rs @@ -1,3 +1,4 @@ +use std::mem::swap; use heraclitus_compiler::prelude::*; use similar_string::find_best_similarity; use crate::modules::types::Type; @@ -5,44 +6,44 @@ use crate::utils::ParserMetadata; use crate::modules::block::Block; fn run_function_with_args(meta: &mut ParserMetadata, name: &str, args: &[Type], tok: Option) -> Result { - let function = meta.mem.get_function(name).unwrap().clone(); - let mut block = Block::new(); - // Create a new parser metadata specific for the function parsing context - let mut new_meta = function.meta.clone(); - let function_ctx = new_meta.function_ctx; - new_meta.expr = function.body.clone(); - new_meta.set_index(0); - new_meta.function_ctx = true; - new_meta.mem.set_function_map(meta); - // Check if the function can exist - if function.typed { - if function.args.len() != args.len() { - return error!(meta, tok, format!("Function '{}' expects {} arguments, but {} were given", name, function.args.len(), args.len())) - } - for (index, (arg, kind)) in function.args.iter().enumerate() { + let fun = meta.get_fun_declaration(name).unwrap().clone(); + // Check if there are the correct amount of arguments + if fun.arg_names.len() != args.len() { + // Determine the correct grammar + let txt_arguments = if fun.arg_names.len() == 1 { "argument" } else { "arguments" }; + let txt_given = if args.len() == 1 { "was given" } else { "were given" }; + // Return an error + return error!(meta, tok, format!("Function '{}' expects {} {txt_arguments}, but {} {txt_given}", name, fun.arg_names.len(), args.len())) + } + // Check if the function argument types match + if fun.is_args_typed { + for (index, (arg, kind)) in fun.arg_names.iter().zip(fun.arg_types.iter()).enumerate() { if kind != &args[index] { return error!(meta, tok, format!("Argument '{}' of function '{}' expects type '{}', but '{}' was given", arg, name, kind, args[index])) } } } + let mut ctx = meta.fun_cache.get_context(fun.id).unwrap().clone(); + let mut block = Block::new(); + // Swap the contexts to use the function context + swap(&mut ctx, &mut meta.context); // Create a sub context for new variables - new_meta.mem.push_scope(); - for (kind, (name, _generic)) in args.iter().zip(function.args.iter()) { - new_meta.mem.add_variable(name, kind.clone(), false); + meta.push_scope(); + for (kind, name) in args.iter().zip(fun.arg_names.iter()) { + meta.add_var(name, kind.clone()); } // Parse the function body - syntax(&mut new_meta, &mut block)?; + syntax(meta, &mut block)?; // Pop function body - new_meta.mem.pop_scope(); - new_meta.function_ctx = function_ctx; - // Update function map - meta.mem.set_function_map(&new_meta); + meta.pop_scope(); + // Restore old context + swap(&mut ctx, &mut meta.context); // Persist the new function instance - Ok(meta.mem.add_function_instance(function.id, args, function.returns, block)) + Ok(meta.add_fun_instance(fun.to_interface(), block)) } pub fn handle_function_reference(meta: &mut ParserMetadata, tok: Option, name: &str) -> Result { - match meta.mem.get_function(name) { + match meta.get_fun_declaration(name) { Some(fun_decl) => Ok(fun_decl.id), None => { let message = format!("Function '{}' does not exist", name); @@ -56,14 +57,17 @@ pub fn handle_function_reference(meta: &mut ParserMetadata, tok: Option, } } -pub fn handle_function_parameters(meta: &mut ParserMetadata, name: &str, args: &[Type], tok: Option) -> Result<(Type, usize), Failure> { - let function_unit = meta.mem.get_function(name).unwrap().clone(); - // TODO: Here is a good place to insert trace - Ok((function_unit.returns, run_function_with_args(meta, name, args, tok)?)) +pub fn handle_function_parameters(meta: &mut ParserMetadata, id: usize, name: &str, args: &[Type], tok: Option) -> Result<(Type, usize), Failure> { + let function_unit = meta.get_fun_declaration(name).unwrap().clone(); + // If the function was previously called with the same arguments, return the cached variant + match meta.fun_cache.get_instances(id).unwrap().iter().find(|fun| fun.args == args) { + Some(fun) => Ok((function_unit.returns, fun.variant_id)), + None => Ok((function_unit.returns, run_function_with_args(meta, name, args, tok)?)) + } } fn handle_similar_function(meta: &mut ParserMetadata, name: &str) -> Option { - let vars = Vec::from_iter(meta.mem.get_available_functions()); + let vars = Vec::from_iter(meta.get_fun_names()); if let Some((match_name, score)) = find_best_similarity(name, &vars) { match score >= 0.75 { true => Some(format!("Did you mean '{match_name}'?")), diff --git a/src/modules/imports/import.rs b/src/modules/imports/import.rs index f75217e9..be4cb341 100644 --- a/src/modules/imports/import.rs +++ b/src/modules/imports/import.rs @@ -1,9 +1,10 @@ use std::fs; +use std::mem::swap; use heraclitus_compiler::prelude::*; use crate::compiler::AmberCompiler; use crate::modules::block::Block; use crate::modules::variable::variable_name_extensions; -use crate::utils::exports::{Exports, ExportUnit}; +use crate::utils::context::{Context, FunctionDecl}; use crate::utils::{ParserMetadata, TranslateMetadata}; use crate::translate::module::TranslateModule; use super::import_string::ImportString; @@ -18,53 +19,29 @@ pub struct Import { } impl Import { - fn handle_export(&mut self, meta: &mut ParserMetadata, exports: Exports) -> SyntaxResult { - let exports = exports.get_exports().iter().cloned(); - for (name, alias, tok) in self.export_defs.drain(..) { - let mut found = false; - for export_unit in exports.clone() { - match export_unit { - ExportUnit::Function(mut func) => { - if func.name == name { - found = true; - func.name = alias.unwrap_or_else(|| name.clone()); - if !meta.mem.add_existing_function_declaration(func) { - return error!(meta, tok => { - message: format!("Function '{}' is already defined", name) - }) - } - break - } - } + fn handle_export(&mut self, meta: &mut ParserMetadata, pub_funs: Vec) -> SyntaxResult { + for mut fun in pub_funs { + if !self.is_all { + match self.export_defs.iter().find(|(name, ..)| fun.name == *name) { + Some((_, Some(alias), _)) => fun.name = alias.clone(), + Some((_, None, _)) => (), + None => continue, } } - if !found { - return error!(meta, tok => { - message: format!("Export '{}' not found in module '{}'", &name, self.path.value), - comment: "Exports are case-sensitive" + // This function should not be furher exported + fun.is_public = false; + let name = fun.name.clone(); + if meta.add_fun_declaration_existing(fun).is_none() { + return error!(meta, self.token_import.clone() => { + message: format!("Function '{}' is already defined", name) }) - } - } - if self.is_all { - for export in exports { - match export { - ExportUnit::Function(mut func_decl) => { - let name = func_decl.name.clone(); - func_decl.is_public = false; - if !meta.mem.add_existing_function_declaration(func_decl) { - return error!(meta, self.token_import.clone() => { - message: format!("Function '{}' is already defined", name) - }) - } - } - } } } Ok(()) } fn add_import(&mut self, meta: &mut ParserMetadata, path: &str) -> SyntaxResult { - if meta.import_history.add_import(meta.get_path(), path.to_string()).is_none() { + if meta.import_cache.add_import_entry(meta.get_path(), path.to_string()).is_none() { return error!(meta, self.token_path.clone() => { message: "Circular import detected", comment: "Please remove the circular import" @@ -84,8 +61,9 @@ impl Import { } fn handle_import(&mut self, meta: &mut ParserMetadata, imported_code: String) -> SyntaxResult { - match meta.import_history.get_export(Some(self.path.value.clone())) { - Some(exports) => self.handle_export(meta, exports), + // If the import was already cached, we don't need to recompile it + match meta.import_cache.get_import_pub_funs(Some(self.path.value.clone())) { + Some(pub_funs) => self.handle_export(meta, pub_funs), None => self.handle_compile_code(meta, imported_code) } } @@ -95,38 +73,21 @@ impl Import { Ok(tokens) => { let mut block = Block::new(); // Save snapshot of current file - let code = meta.code.clone(); - let path = meta.path.clone(); - let expr = meta.expr.clone(); - let exports = meta.mem.exports.clone(); - let index = meta.get_index(); - let scopes = meta.mem.scopes.clone(); - // Parse the imported file - meta.push_trace(PositionInfo::from_token(meta, self.token_import.clone())); - meta.path = Some(self.path.value.clone()); - meta.code = Some(imported_code); - meta.expr = tokens; - meta.set_index(0); - meta.mem.scopes = vec![]; + let position = PositionInfo::from_token(meta, self.token_import.clone()); + let mut ctx = Context::new(Some(self.path.value.clone()), tokens) + .file_import(&meta.context.trace, position); + swap(&mut ctx, &mut meta.context); + // Parse imported code syntax(meta, &mut block)?; // Restore snapshot of current file - meta.mem.scopes = scopes; - meta.code = code; - meta.path = path; - meta.expr = expr; - meta.set_index(index); - meta.pop_trace(); - // Finalize importing phase - meta.import_history.add_import_block(Some(self.path.value.clone()), block); - meta.import_history.add_export(Some(self.path.value.clone()), meta.mem.exports.clone()); - self.handle_export(meta, meta.mem.exports.clone())?; - // Restore exports - meta.mem.exports = exports; + swap(&mut ctx, &mut meta.context); + // Persist compiled file to cache + meta.import_cache.add_import_metadata(Some(self.path.value.clone()), block, ctx.pub_funs.clone()); + // Handle exports (add to current file) + self.handle_export(meta, ctx.pub_funs)?; Ok(()) } - Err(err) => { - Err(Failure::Loud(err)) - } + Err(err) => Err(Failure::Loud(err)) } } } @@ -147,7 +108,7 @@ impl SyntaxModule for Import { fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { self.token_import = meta.get_current_token(); token(meta, "import")?; - if meta.mem.get_depth() > 1 { + if !meta.is_global_scope() { return error!(meta, self.token_import.clone(), "Imports must be in the global scope") } match token(meta, "*") { @@ -182,6 +143,7 @@ impl SyntaxModule for Import { } else { self.add_import(meta, &self.path.value.clone())?; self.resolve_import(meta)? + }; self.handle_import(meta, imported_code)?; Ok(()) diff --git a/src/modules/imports/import_string.rs b/src/modules/imports/import_string.rs index 8e171d0e..60546508 100644 --- a/src/modules/imports/import_string.rs +++ b/src/modules/imports/import_string.rs @@ -14,7 +14,7 @@ impl ImportString { return Ok(()) } - let mut path = meta.path.as_ref() + let mut path = meta.context.path.as_ref() .map_or_else(|| Path::new("."), |path| Path::new(path)) .to_path_buf(); path.pop(); diff --git a/src/modules/loops/break_stmt.rs b/src/modules/loops/break_stmt.rs index 96509e72..f475cbe2 100644 --- a/src/modules/loops/break_stmt.rs +++ b/src/modules/loops/break_stmt.rs @@ -16,7 +16,7 @@ impl SyntaxModule for Break { let tok = meta.get_current_token(); token(meta, "break")?; // Detect if the break statement is inside a loop - if !meta.loop_ctx { + if !meta.context.is_loop_ctx { return error!(meta, tok, "Break statement can only be used inside a loop") } Ok(()) diff --git a/src/modules/loops/continue_stmt.rs b/src/modules/loops/continue_stmt.rs index bf7d73c4..d137dcea 100644 --- a/src/modules/loops/continue_stmt.rs +++ b/src/modules/loops/continue_stmt.rs @@ -16,7 +16,7 @@ impl SyntaxModule for Continue { let tok = meta.get_current_token(); token(meta, "continue")?; // Detect if the break statement is inside a loop - if !meta.loop_ctx { + if !meta.context.is_loop_ctx { return error!(meta, tok, "Continue statement can only be used inside a loop") } Ok(()) diff --git a/src/modules/loops/infinite_loop.rs b/src/modules/loops/infinite_loop.rs index 335b2180..25d314f8 100644 --- a/src/modules/loops/infinite_loop.rs +++ b/src/modules/loops/infinite_loop.rs @@ -21,13 +21,13 @@ impl SyntaxModule for InfiniteLoop { token(meta, "loop")?; token(meta, "{")?; // Save loop context state and set it to true - let ctx = meta.loop_ctx; - meta.loop_ctx = true; + let ctx = meta.context.is_loop_ctx; + meta.context.is_loop_ctx = true; // Parse loop syntax(meta, &mut self.block)?; token(meta, "}")?; // Restore loop context state - meta.loop_ctx = ctx; + meta.context.is_loop_ctx = ctx; Ok(()) } } diff --git a/src/modules/main.rs b/src/modules/main.rs index 24fbffe7..a7d0ab04 100644 --- a/src/modules/main.rs +++ b/src/modules/main.rs @@ -28,11 +28,11 @@ impl SyntaxModule for Main { let tok = meta.get_current_token(); token(meta, "main")?; // Main cannot be parsed inside of a block - if meta.mem.get_depth() > 1 { + if !meta.is_global_scope() { return error!(meta, tok, "Main must be in the global scope") } // If this main is included in other file, skip it - if !meta.trace.is_empty() { + if !meta.context.trace.is_empty() { self.is_skipped = true; } context!({ @@ -50,15 +50,15 @@ impl SyntaxModule for Main { } token(meta, "{")?; // Create a new scope for variables - meta.mem.push_scope(); + meta.push_scope(); // Create variables for arg in self.args.iter() { - meta.mem.add_variable(arg, Type::Text, false); + meta.add_var(arg, Type::Text); } // Parse the block syntax(meta, &mut self.block)?; // Remove the scope made for variables - meta.mem.pop_scope(); + meta.pop_scope(); token(meta, "}")?; Ok(()) }, |pos| { diff --git a/src/modules/statement/stmt.rs b/src/modules/statement/stmt.rs index bc90de29..a9fc53f2 100644 --- a/src/modules/statement/stmt.rs +++ b/src/modules/statement/stmt.rs @@ -111,6 +111,13 @@ impl SyntaxModule for Statement { let mut error = None; let statements = self.get_modules(); for statement in statements { + // Handle comments + if let Some(token) = meta.get_current_token() { + if token.word.starts_with('#') { + meta.increment_index(); + continue + } + } // Try to parse the statement match self.parse_match(meta, statement) { Ok(()) => return Ok(()), diff --git a/src/modules/variable/init.rs b/src/modules/variable/init.rs index a12a9ceb..a2956417 100644 --- a/src/modules/variable/init.rs +++ b/src/modules/variable/init.rs @@ -13,9 +13,9 @@ pub struct VariableInit { } impl VariableInit { - fn handle_add_variable(&mut self, meta: &mut ParserMetadata, name: &str, kind: Type, tok: Option, is_global: bool) -> SyntaxResult { + fn handle_add_variable(&mut self, meta: &mut ParserMetadata, name: &str, kind: Type, tok: Option) -> SyntaxResult { handle_identifier_name(meta, name, tok)?; - self.global_id = meta.mem.add_variable(name, kind, is_global); + self.global_id = meta.add_var(name, kind); Ok(()) } } @@ -40,7 +40,7 @@ impl SyntaxModule for VariableInit { token(meta, "=")?; syntax(meta, &mut *self.expr)?; // Add a variable to the memory - self.handle_add_variable(meta, &self.name.clone(), self.expr.get_type(), tok, !meta.function_ctx)?; + self.handle_add_variable(meta, &self.name.clone(), self.expr.get_type(), tok)?; Ok(()) }, |position| { error_pos!(meta, position, format!("Expected '=' after variable name '{}'", self.name)) diff --git a/src/modules/variable/mod.rs b/src/modules/variable/mod.rs index d97d1fbf..fb42345c 100644 --- a/src/modules/variable/mod.rs +++ b/src/modules/variable/mod.rs @@ -17,7 +17,7 @@ pub fn variable_name_keywords() -> Vec<&'static str> { pub fn handle_variable_reference(meta: &mut ParserMetadata, tok: Option, name: &str) -> Result<(Option, Type), Failure> { handle_identifier_name(meta, name, tok.clone())?; - match meta.mem.get_variable(name) { + match meta.get_var(name) { Some(variable_unit) => Ok((variable_unit.global_id, variable_unit.kind.clone())), None => { let message = format!("Variable '{}' does not exist", name); @@ -32,7 +32,7 @@ pub fn handle_variable_reference(meta: &mut ParserMetadata, tok: Option, } fn handle_similar_variable(meta: &mut ParserMetadata, name: &str) -> Option { - let vars = Vec::from_iter(meta.mem.get_available_variables()); + let vars = Vec::from_iter(meta.get_var_names()); if let Some((match_name, score)) = find_best_similarity(name, &vars) { match score >= 0.75 { true => Some(format!("Did you mean '{match_name}'?")), diff --git a/src/tests/mod.rs b/src/tests/mod.rs new file mode 100644 index 00000000..1d7653cf --- /dev/null +++ b/src/tests/mod.rs @@ -0,0 +1,2 @@ +pub mod validity; +pub mod performance; \ No newline at end of file diff --git a/src/tests/performance.rs b/src/tests/performance.rs new file mode 100644 index 00000000..39261d16 --- /dev/null +++ b/src/tests/performance.rs @@ -0,0 +1,155 @@ +use crate::compiler::AmberCompiler; +use std::time::Duration; + +const REPS: u32 = 10; + +// The following tests calculate the time it takes to compile the code and execute by a bash interpreter +macro_rules! test_time_amber { + ($code:expr, $result:expr) => { + { + let mut times = Vec::with_capacity(REPS as usize); + for _ in 0..REPS { + let time = std::time::Instant::now(); + match AmberCompiler::new($code.to_string(), None).test_eval() { + Ok(_) => times.push(time.elapsed()), + Err(err) => panic!("ERROR: {}", err.message.unwrap()) + } + } + let avg = times.iter().fold(Duration::new(0, 0), |acc, x| acc + *x) / REPS; + if avg > $result { + panic!("ERROR: Took too long to execute {}ms", avg.as_millis()); + } + } + }; +} + +#[test] +fn hello_world() { + test_time_amber!("echo 'Hello World'", Duration::from_millis(20)); +} + +#[test] +fn add() { + test_time_amber!("echo 15 + 45", Duration::from_millis(20)); +} + +#[test] +fn sub() { + test_time_amber!("echo 21 - 7", Duration::from_millis(20)); +} + +#[test] +fn mul() { + test_time_amber!("echo 3 * 4", Duration::from_millis(50)); +} + +#[test] +fn div() { + test_time_amber!("echo 21 / 3", Duration::from_millis(50)); +} + +#[test] +fn text() { + test_time_amber!("echo 'Hello World'", Duration::from_millis(20)); +} + +#[test] +fn bool() { + test_time_amber!("echo true", Duration::from_millis(20)); + test_time_amber!("echo false", Duration::from_millis(20)); +} + +#[test] +fn number() { + test_time_amber!("echo 15", Duration::from_millis(20)); +} + +#[test] +fn variable() { + let code = " + let x = 42 + echo x + "; + test_time_amber!(code, Duration::from_millis(20)); +} + +#[test] +fn if_statement() { + let code = " + let x = 42 + if x == 42 { + echo 'Hello World' + } + "; + test_time_amber!(code, Duration::from_millis(50)); +} + +#[test] +fn if_else_statement() { + let code = " + let x = 42 + if x == 42 { + echo 'Hello World' + } else { + echo 'Goodbye World' + } + "; + test_time_amber!(code, Duration::from_millis(100)); +} + +#[test] +fn if_else_if_statement() { + let code = " + let x = 42 + if { + x == 42 => echo 'Hello World' + x == 43 => echo 'Goodbye World' + else => 'XD' + } + "; + test_time_amber!(code, Duration::from_millis(100)); +} + +#[test] +fn function() { + let code = " + fun test() { + echo 'Hello World' + } + echo test() + "; + test_time_amber!(code, Duration::from_millis(50)); +} + +#[test] +fn function_with_args() { + let code = " + fun test(a, b) { + echo a + echo b + } + echo test('Hello', 'World') + echo test(11, 42) + "; + test_time_amber!(code, Duration::from_millis(100)); +} + +#[test] +fn function_with_typed_args() { + let code = " + fun test(a: Num, b: Num) { + echo a + b + } + echo test(11, 42) + "; + test_time_amber!(code, Duration::from_millis(100)); +} + +#[test] +fn import_existing_file() { + let code = " + import * from 'test_files/str/trim.ab' + echo trim(' Hello World ') + "; + test_time_amber!(code, Duration::from_millis(100)); +} diff --git a/src/cli/tests.rs b/src/tests/validity.rs similarity index 92% rename from src/cli/tests.rs rename to src/tests/validity.rs index 5accd725..e0f8405d 100644 --- a/src/cli/tests.rs +++ b/src/tests/validity.rs @@ -12,27 +12,11 @@ macro_rules! test_amber { }; } -macro_rules! test_amber_err { - ($code:expr, $result:expr) => { - { - match AmberCompiler::new($code.to_string(), None).test_eval() { - Ok(result) => panic!("PASSED: Expected error, got {}", result), - Err(err) => assert_eq!(err.message.unwrap(), $result) - } - } - }; -} - #[test] fn hello_world() { test_amber!("echo 'Hello World'", "Hello World"); } -#[test] -fn hello_world_error() { - test_amber_err!("echo Hello World", "Variable 'Hello' does not exist"); -} - #[test] fn add() { test_amber!("echo 15 + 45", "60"); @@ -427,6 +411,7 @@ fn function_with_typed_args() { "; test_amber!(code, "53"); } + #[test] fn function_with_typed_different_args() { let code = " @@ -442,7 +427,7 @@ fn function_with_typed_different_args() { #[test] fn function_with_typed_args_text() { let code = " - fun test(a: Text, b: Text) { + pub fun test(a: Text, b: Text) { echo a + b } echo test('Hello', 'World') @@ -453,7 +438,7 @@ fn function_with_typed_args_text() { #[test] fn import_existing_file() { let code = " - import * from 'tests/str/trim.ab' + import * from 'test_files/str/trim.ab' echo trim(' Hello World ') "; test_amber!(code, "Hello World"); @@ -462,7 +447,7 @@ fn import_existing_file() { #[test] fn import_existing_nested_file() { let code = " - import * from 'tests/is_even.ab' + import * from 'test_files/is_even.ab' echo is_even(10) "; test_amber!(code, "even"); diff --git a/src/translate/mod.rs b/src/translate/mod.rs index 643015e5..26c97eca 100644 --- a/src/translate/mod.rs +++ b/src/translate/mod.rs @@ -7,7 +7,7 @@ pub mod compute; pub fn check_all_blocks(meta: &mut ParserMetadata) -> SyntaxResult { let mut stack = 0; - for token in meta.expr.iter() { + for token in meta.context.expr.iter() { match token.word.as_str() { "{" => stack += 1, "}" => stack -= 1, diff --git a/src/utils/context.rs b/src/utils/context.rs new file mode 100644 index 00000000..409482a6 --- /dev/null +++ b/src/utils/context.rs @@ -0,0 +1,142 @@ +use heraclitus_compiler::prelude::*; +use std::collections::HashMap; +use crate::modules::types::Type; + +use super::function_interface::FunctionInterface; + +#[derive(Clone, Debug)] +pub struct FunctionDecl { + pub name: String, + pub arg_names: Vec, + pub arg_types: Vec, + pub returns: Type, + pub is_args_typed: bool, + pub is_public: bool, + pub id: usize +} + +impl FunctionDecl { + pub fn to_interface(self) -> FunctionInterface { + FunctionInterface { + id: Some(self.id), + name: self.name, + arg_names: self.arg_names, + arg_types: self.arg_types, + returns: self.returns, + is_public: self.is_public + } + } +} + +#[derive(Clone, Debug)] +pub struct VariableDecl { + pub name: String, + pub kind: Type, + pub global_id: Option +} + +#[derive(Clone, Debug)] +pub struct ScopeUnit { + pub vars: HashMap, + pub funs: HashMap +} + +/// Perform methods just on the scope +impl ScopeUnit { + pub fn new() -> ScopeUnit { + ScopeUnit { + vars: HashMap::new(), + funs: HashMap::new() + } + } + + /* Variables */ + + /// Persists a variable declaration in the scope + pub fn add_var(&mut self, name: &str, kind: Type, global_id: Option) { + self.vars.insert(name.to_string(), VariableDecl { + name: name.to_string(), + kind, + global_id + }); + } + + /// Fetches a variable declaration from the scope + pub fn get_var(&self, name: &str) -> Option<&VariableDecl> { + self.vars.get(name) + } + + /// Gets all the variable names in the scope + pub fn get_var_names(&self) -> Vec<&String> { + self.vars.keys().collect() + } + + /* Functions */ + + /// Persists a function declaration in the scope + pub fn add_fun(&mut self, fun: FunctionDecl) -> bool { + let name = fun.name.clone(); + self.funs.insert(name, fun).is_none() + } + + /// Fetches a function declaration from the scope + pub fn get_fun(&self, name: &str) -> Option<&FunctionDecl> { + self.funs.get(name) + } + + /// Gets all the function names in the scope + pub fn get_fun_names(&self) -> Vec<&String> { + self.funs.keys().collect() + } +} + +#[derive(Clone, Debug)] +pub struct Context { + /// The current index in the expression + pub index: usize, + /// The expression to be parsed + pub expr: Vec, + /// The path of the file + pub path: Option, + /// Scopes saved in the context + pub scopes: Vec, + /// A trace of the current position in the file + pub trace: Vec, + /// Determines if the context is in a function + pub is_fun_ctx: bool, + /// Determines if the context is in a loop + pub is_loop_ctx: bool, + /// This is a list of ids of all the public functions in the file + pub pub_funs: Vec +} + +// FIXME: Move the scope related structures to the separate file +impl Context { + pub fn new(path: Option, expr: Vec) -> Self { + Self { + index: 0, + expr, + path, + scopes: vec![], + trace: vec![], + is_fun_ctx: false, + is_loop_ctx: false, + pub_funs: vec![] + } + } + + pub fn function_invocation(mut self, expr: Vec) -> Self { + self.is_fun_ctx = true; + self.index = 0; + self.expr = expr; + self + } + + pub fn file_import(mut self, trace: &Vec, position: PositionInfo) -> Self { + // Initialize the trace + self.trace = trace.clone(); + // Push the position to the trace + self.trace.push(position); + self + } +} \ No newline at end of file diff --git a/src/utils/exports.rs b/src/utils/exports.rs deleted file mode 100644 index 66b8617a..00000000 --- a/src/utils/exports.rs +++ /dev/null @@ -1,30 +0,0 @@ - -use super::memory::FunctionDecl; - -#[derive(Clone, Debug)] -pub enum ExportUnit { - Function(FunctionDecl) -} - -#[derive(Clone, Debug)] -pub struct Exports { - values: Vec -} - -impl Exports { - pub fn new() -> Exports { - Exports { - values: vec![] - } - } - - pub fn get_exports(&self) -> &Vec { - &self.values - } - - pub fn add_function(&mut self, function: FunctionDecl) { - if function.is_public { - self.values.push(ExportUnit::Function(function)) - } - } -} diff --git a/src/utils/function_cache.rs b/src/utils/function_cache.rs new file mode 100644 index 00000000..37cd91df --- /dev/null +++ b/src/utils/function_cache.rs @@ -0,0 +1,64 @@ +use std::collections::HashMap; +use crate::modules::{types::Type, block::Block}; +use super::context::Context; + +#[derive(Clone, Debug)] +/// This is a compiled function instance +pub struct FunctionInstance { + pub variant_id: usize, + pub args: Vec, + pub returns: Type, + pub block: Block +} + +#[derive(Debug)] +/// This is a cached data representing a function +pub struct FunctionCacheEntry { + pub instances: Vec, + pub context: Context +} + +#[derive(Debug)] +// This is a map of all generated functions based on their invocations +pub struct FunctionCache { + pub funs: HashMap, +} + +impl FunctionCache { + pub fn new() -> FunctionCache { + FunctionCache { + funs: HashMap::new(), + } + } + + /// Adds a new function declaration to the cache + pub fn add_declaration(&mut self, id: usize, context: Context) { + self.funs.insert(id, FunctionCacheEntry { + instances: Vec::new(), + context + }); + } + + /// Adds a new function instance to the cache + pub fn add_instance(&mut self, id: usize, mut fun: FunctionInstance) -> usize { + let functions = self.funs.get_mut(&id).expect("Function not found in cache"); + fun.variant_id = functions.instances.len(); + functions.instances.push(fun); + functions.instances.len() - 1 + } + + /// Gets all the function instances of a function declaration + pub fn get_instances_cloned(&self, id: usize) -> Option> { + self.funs.get(&id).map(|f| f.instances.clone()) + } + + /// Gets all the function instances of a function declaration as a reference + pub fn get_instances(&self, id: usize) -> Option<&Vec> { + self.funs.get(&id).map(|f| &f.instances) + } + + /// Gets the context of a function declaration + pub fn get_context(&self, id: usize) -> Option<&Context> { + self.funs.get(&id).map(|f| &f.context) + } +} \ No newline at end of file diff --git a/src/utils/function_interface.rs b/src/utils/function_interface.rs new file mode 100644 index 00000000..7dc111e7 --- /dev/null +++ b/src/utils/function_interface.rs @@ -0,0 +1,38 @@ +use crate::modules::{types::Type, block::Block}; +use super::{context::FunctionDecl, function_cache::FunctionInstance}; + + + +#[derive(Clone, Debug)] +pub struct FunctionInterface { + pub id: Option, + pub name: String, + pub arg_names: Vec, + pub arg_types: Vec, + pub returns: Type, + pub is_public: bool +} + +impl FunctionInterface { + pub fn to_fun_declaration(self, id: usize) -> FunctionDecl { + let is_args_typed = self.arg_types.iter().all(|t| t != &Type::Generic); + FunctionDecl { + name: self.name, + arg_names: self.arg_names, + arg_types: self.arg_types, + returns: self.returns, + is_args_typed, + is_public: self.is_public, + id + } + } + + pub fn to_fun_instance(self, block: Block) -> FunctionInstance { + FunctionInstance { + variant_id: 0, + args: self.arg_types, + returns: self.returns, + block + } + } +} \ No newline at end of file diff --git a/src/utils/function_map.rs b/src/utils/function_map.rs deleted file mode 100644 index 7ed10fe1..00000000 --- a/src/utils/function_map.rs +++ /dev/null @@ -1,44 +0,0 @@ -use std::collections::HashMap; -use crate::modules::{types::Type, block::Block}; - -#[derive(Clone, Debug)] -pub struct FunctionInstance { - pub args: Vec, - pub returns: Type, - pub body: Block -} - -#[derive(Clone, Debug)] -// This is a map of all generated functions based on their invocations -pub struct FunctionMap { - pub map: HashMap>, - pub current_id: usize -} - -impl FunctionMap { - pub fn new() -> FunctionMap { - FunctionMap { - map: HashMap::new(), - current_id: 0 - } - } - - pub fn add_declaration(&mut self) -> usize { - let id = self.current_id; - self.map.insert(id, vec![]); - self.current_id += 1; - id - } - - pub fn add_instance(&mut self, id: usize, function: FunctionInstance) -> usize { - if let Some(functions) = self.map.get_mut(&id) { - let length = functions.len(); - functions.push(function); - length - } else { 0 } - } - - pub fn get(&self, id: usize) -> Option<&Vec> { - self.map.get(&id) - } -} \ No newline at end of file diff --git a/src/utils/import_cache.rs b/src/utils/import_cache.rs new file mode 100644 index 00000000..6249270a --- /dev/null +++ b/src/utils/import_cache.rs @@ -0,0 +1,116 @@ +use crate::modules::block::Block; +use super::context::FunctionDecl; + +#[derive(Debug, Clone)] +pub struct FileMetadata { + pub block: Block, + pub pub_funs: Vec +} + +#[derive(Debug, Clone)] +pub struct FileCache { + pub path: String, + pub metadata: Option +} + +#[derive(Debug, Clone)] +pub struct ImportCache { + /// The paths of the imports (used to be able to resolve imports with topological sort) + pub import_graph: Vec>, + /// Cached imported files (always has the same length as the import graph) + pub files: Vec +} + +impl ImportCache { + + pub fn get_path(optional_path: Option) -> String { + optional_path.unwrap_or_else(|| String::from(".")) + } + + pub fn get_path_id(&self, path: &str) -> Option { + self.files.iter().position(|import| import.path == path) + } + + pub fn new(initial_path: Option) -> Self { + ImportCache { + files: vec![FileCache { + path: Self::get_path(initial_path), + metadata: None + }], + import_graph: vec![vec![]] + } + } + + fn contains_cycle_util(&self, v: usize, visited: &mut Vec, rec_stack: &mut Vec) -> bool { + if !visited[v] { + visited[v] = true; + rec_stack[v] = true; + for i in self.import_graph[v].iter() { + if (!visited[*i] && self.contains_cycle_util(*i, visited, rec_stack)) || rec_stack[*i] { + return true; + } + } + } + rec_stack[v] = false; + false + } + + // Check if graph contains a cycle starting from id + pub fn contains_cycle(&self, id: usize) -> bool { + let mut visited = vec![false; self.files.len()]; + let mut stack = vec![false; self.files.len()]; + self.contains_cycle_util(id, &mut visited, &mut stack) + } + + pub fn add_import_entry(&mut self, src_path: Option, dst_path: String) -> Option { + // Get id of source path + let src_path_id = self.get_path_id(&Self::get_path(src_path)).unwrap(); + // Check if destination path is already in the graph + match self.get_path_id(&dst_path) { + // If so add it to the graph + Some(dst_path_id) => { + self.import_graph[src_path_id].push(dst_path_id); + (!self.contains_cycle(src_path_id)).then(|| dst_path_id) + } + // If not add it to the graph and create a new import entry + None => { + let dst_path_id = self.files.len(); + self.files.push(FileCache { + path: dst_path, + metadata: None + }); + self.import_graph.push(vec![]); + self.import_graph[src_path_id].push(dst_path_id); + Some(dst_path_id) + } + } + } + + pub fn add_import_metadata(&mut self, path: Option, block: Block, pub_funs: Vec) { + let path_id = self.get_path_id(&Self::get_path(path)).unwrap(); + self.files[path_id].metadata = Some(FileMetadata { block, pub_funs }); + } + + pub fn get_import_pub_funs(&mut self, path: Option) -> Option> { + self.get_path_id(&Self::get_path(path)) + .map(|path_id| self.files[path_id].metadata.as_ref().map(|meta| meta.pub_funs.clone())) + .flatten() + } + + fn topological_sort_util(&self, v: usize, visited: &mut Vec, stack: &mut Vec) { + visited[v] = true; + for i in self.import_graph[v].iter() { + if !visited[*i] { + self.topological_sort_util(*i, visited, stack); + } + } + stack.push(v); + } + + pub fn topological_sort(&self) -> Vec { + let mut stack = Vec::new(); + let mut visited = vec![false; self.files.len()]; + self.topological_sort_util(0, &mut visited, &mut stack); + stack + } +} \ No newline at end of file diff --git a/src/utils/import_history.rs b/src/utils/import_history.rs deleted file mode 100644 index f0155e18..00000000 --- a/src/utils/import_history.rs +++ /dev/null @@ -1,112 +0,0 @@ -use crate::modules::block::Block; - -use super::exports::Exports; - -#[derive(Debug, Clone)] -pub struct ImportHistory { - pub imports: Vec, - pub import_graph: Vec>, - // Used to resolve imports in the correct order (topological sort) - pub import_blocks: Vec>, - // Used to resolve problem with - pub exports: Vec> -} - -impl ImportHistory { - - pub fn get_path(optional_path: Option) -> String { - optional_path.unwrap_or_else(|| String::from(".")) - } - - pub fn get_path_id(&self, path: &str) -> Option { - self.imports.iter().position(|import| import == path) - } - - pub fn new(initial_path: Option) -> Self { - ImportHistory { - imports: vec![Self::get_path(initial_path)], - import_graph: vec![vec![]], - import_blocks: vec![None], - exports: vec![None] - } - } - - fn contains_cycle_util(&self, v: usize, visited: &mut Vec, rec_stack: &mut Vec) -> bool { - if !visited[v] { - visited[v] = true; - rec_stack[v] = true; - for i in self.import_graph[v].iter() { - if (!visited[*i] && self.contains_cycle_util(*i, visited, rec_stack)) || rec_stack[*i] { - return true; - } - } - } - rec_stack[v] = false; - false - } - - // Check if graph contains a cycle starting from id - pub fn contains_cycle(&self, id: usize) -> bool { - let mut visited = vec![false; self.imports.len()]; - let mut stack = vec![false; self.imports.len()]; - self.contains_cycle_util(id, &mut visited, &mut stack) - } - - pub fn add_import(&mut self, src: Option, path: String) -> Option { - let src_id = self.get_path_id(&Self::get_path(src)).unwrap(); - match self.get_path_id(&path) { - Some(dst_id) => { - self.import_graph[src_id].push(dst_id); - if self.contains_cycle(src_id) { - None - } else { - Some(dst_id) - } - } - None => { - let dst_id = self.imports.len(); - self.imports.push(path); - self.exports.push(None); - self.import_blocks.push(None); - self.import_graph.push(vec![]); - self.import_graph[src_id].push(dst_id); - Some(dst_id) - } - } - } - - pub fn add_import_block(&mut self, path: Option, block: Block) { - let path_id = self.get_path_id(&Self::get_path(path)).unwrap(); - self.import_blocks[path_id] = Some(block); - } - - pub fn add_export(&mut self, path: Option, exports: Exports) { - let path_id = self.get_path_id(&Self::get_path(path)).unwrap(); - self.exports[path_id] = Some(exports); - } - - pub fn get_export(&mut self, path: Option) -> Option { - if let Some(path_id) = self.get_path_id(&Self::get_path(path)) { - self.exports[path_id].clone() - } else { - None - } - } - - fn topological_sort_util(&self, v: usize, visited: &mut Vec, stack: &mut Vec) { - visited[v] = true; - for i in self.import_graph[v].iter() { - if !visited[*i] { - self.topological_sort_util(*i, visited, stack); - } - } - stack.push(v); - } - - pub fn topological_sort(&self) -> Vec { - let mut stack = Vec::new(); - let mut visited = vec![false; self.imports.len()]; - self.topological_sort_util(0, &mut visited, &mut stack); - stack - } -} \ No newline at end of file diff --git a/src/utils/memory.rs b/src/utils/memory.rs deleted file mode 100644 index 9290ed8b..00000000 --- a/src/utils/memory.rs +++ /dev/null @@ -1,178 +0,0 @@ -use heraclitus_compiler::prelude::*; -use std::collections::{HashMap, BTreeSet}; -use crate::modules::{types::Type, block::Block, function::declaration_utils::FunctionDeclSyntax}; -use super::{function_map::{FunctionMap, FunctionInstance}, exports::Exports, ParserMetadata}; - -#[derive(Clone, Debug)] -pub struct FunctionDecl { - pub name: String, - pub args: Vec<(String, Type)>, - pub returns: Type, - pub body: Vec, - pub meta: ParserMetadata, - pub typed: bool, - pub is_public: bool, - pub id: usize -} - -#[derive(Clone, Debug)] -pub struct VariableDecl { - pub name: String, - pub kind: Type, - pub global_id: Option -} - -#[derive(Clone, Debug)] -pub struct ScopeUnit { - pub vars: HashMap, - pub funs: HashMap -} - -impl ScopeUnit { - fn new() -> ScopeUnit { - ScopeUnit { - vars: HashMap::new(), - funs: HashMap::new() - } - } -} - -#[derive(Clone, Debug)] -pub struct Memory { - pub scopes: Vec, - // Map of all generated functions based on their invocations - pub function_map: FunctionMap, - pub variable_id: usize, - pub exports: Exports -} - -impl Memory { - pub fn new() -> Memory { - Memory { - scopes: vec![], - function_map: FunctionMap::new(), - exports: Exports::new(), - variable_id: 0 - } - } - - pub fn get_depth(&self) -> usize { - self.scopes.len() - } - - pub fn push_scope(&mut self) { - self.scopes.push(ScopeUnit::new()) - } - - pub fn pop_scope(&mut self) -> Option { - self.scopes.pop() - } - - pub fn add_variable(&mut self, name: &str, kind: Type, global: bool) -> Option { - let mut global_id = None; - if global { - global_id = Some(self.variable_id); - self.variable_id += 1; - } - let scope = self.scopes.last_mut().unwrap(); - scope.vars.insert(name.to_string(), VariableDecl { - name: name.to_string(), - kind, - global_id - }); - global_id - } - - pub fn get_variable(&self, name: &str) -> Option<&VariableDecl> { - for scope in self.scopes.iter().rev() { - if let Some(var) = scope.vars.get(name) { - return Some(var); - } - } - None - } - - pub fn get_available_variables(&self) -> BTreeSet<&String> { - let mut set = BTreeSet::new(); - for scope in self.scopes.iter().rev() { - for name in scope.vars.keys() { - set.insert(name); - } - } - set - } - - pub fn add_existing_function_declaration(&mut self, decl: FunctionDecl) -> bool { - let scope = self.scopes.last_mut().unwrap(); - // Add function declaration to the exports - self.exports.add_function(decl.clone()); - // Add function declaration to the scope - let res = scope.funs.insert(decl.name.to_string(), decl); - res.is_none() - } - - pub fn add_function_declaration(&mut self, meta: ParserMetadata, decl: FunctionDeclSyntax) -> Option { - let typed = !decl.args.iter().any(|(_, kind)| kind == &Type::Generic); - let scope = self.scopes.last_mut().unwrap(); - // Add function declaration to the function map - let id = self.function_map.add_declaration(); - // Create a new function declaration - let function_declaration = FunctionDecl { - name: decl.name.to_string(), - args: decl.args.to_vec(), - returns: decl.returns, - is_public: decl.is_public, - body: decl.body, - meta, - typed, - id, - }; - // Add function declaration to the scope - let success = scope.funs.insert(decl.name, function_declaration.clone()); - // Add function declaration to the exports - self.exports.add_function(function_declaration); - // If this is a new function, return its id - if success.is_none() { - Some(id) - } - // If we are having a conflict - else { - None - } - } - - pub fn add_function_instance(&mut self, id: usize, args: &[Type], returns: Type, body: Block) -> usize { - self.function_map.add_instance(id, FunctionInstance { - args: args.to_vec(), - returns, - body - }) - } - - pub fn get_function(&self, name: &str) -> Option<&FunctionDecl> { - for scope in self.scopes.iter().rev() { - if let Some(fun) = scope.funs.get(name) { - return Some(fun); - } - } - None - } - - pub fn get_function_instances(&self, id: usize) -> Option<&Vec> { - self.function_map.get(id) - } - - pub fn set_function_map(&mut self, old_meta: &ParserMetadata) { - self.function_map = old_meta.mem.function_map.clone(); - } - - pub fn get_available_functions(&self) -> BTreeSet<&String> { - let mut set = BTreeSet::new(); - for scope in self.scopes.iter().rev() { - for name in scope.funs.keys() { - set.insert(name); - } - } - set - } -} \ No newline at end of file diff --git a/src/utils/metadata/parser.rs b/src/utils/metadata/parser.rs index 83146aa2..59df6f5a 100644 --- a/src/utils/metadata/parser.rs +++ b/src/utils/metadata/parser.rs @@ -1,65 +1,168 @@ +use std::collections::BTreeSet; + use heraclitus_compiler::prelude::*; -use crate::utils::memory::Memory; -use crate::utils::import_history::ImportHistory; +use crate::modules::block::Block; +use crate::modules::types::Type; +use crate::utils::context::{Context, ScopeUnit, VariableDecl, FunctionDecl}; +use crate::utils::function_interface::FunctionInterface; +use crate::utils::import_cache::ImportCache; +use crate::utils::function_cache::FunctionCache; -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct ParserMetadata { - pub expr: Vec, - index: usize, - pub path: Option, - pub code: Option, + /// Code if the parser is in eval mode + pub eval_code: Option, + /// Determines where the binary operator should end pub binop_border: Option, - pub mem: Memory, - debug: Option, - pub trace: Vec, - pub import_history: ImportHistory, - pub loop_ctx: bool, - pub function_ctx: bool, + /// Used for debugging by Heraclitus + pub debug: Option, + /// Cache of already imported modules + pub import_cache: ImportCache, + /// Cache of already parsed functions + pub fun_cache: FunctionCache, + /// Global function id + pub fun_id: usize, + /// Global variable id + pub var_id: usize, + /// Context of the parser + pub context: Context, + /// List of all failure messages pub messages: Vec } impl ParserMetadata { - pub fn push_trace(&mut self, position: PositionInfo) { - self.trace.push(position); + pub fn add_message(&mut self, message: Message) { + self.messages.push(message); } +} - pub fn pop_trace(&mut self) -> Option { - self.trace.pop() +// Implement context methods +impl ParserMetadata { + /* Scopes */ + + /// Determines if the parser is in the global scope + pub fn is_global_scope(&self) -> bool { + self.context.scopes.len() == 1 } - pub fn add_message(&mut self, message: Message) { - self.messages.push(message); + /// Pushes a new scope to the stack + pub fn push_scope(&mut self) { + self.context.scopes.push(ScopeUnit::new()) + } + + /// Pops the last scope from the stack + pub fn pop_scope(&mut self) -> Option { + self.context.scopes.pop() + } + + /* Variables */ + + /// Generate a new global variable id + pub fn gen_var_id(&mut self) -> usize { + let id = self.var_id; + self.var_id += 1; + id + } + + /// Adds a variable to the current scope + pub fn add_var(&mut self, name: &str, kind: Type) -> Option { + let global_id = self.is_global_scope().then(|| self.gen_var_id()); + let scope = self.context.scopes.last_mut().unwrap(); + scope.add_var(name, kind, global_id); + global_id + } + + /// Gets a variable from the current scope or any parent scope + pub fn get_var(&self, name: &str) -> Option<&VariableDecl> { + self.context.scopes.iter().rev().find_map(|scope| scope.get_var(name)) + } + + /// Gets variable names + pub fn get_var_names(&self) -> BTreeSet<&String> { + self.context.scopes.iter().rev().flat_map(|scope| scope.get_var_names()).collect() + } + + /* Functions */ + + /// Generate a new global function id + pub fn gen_fun_id(&mut self) -> usize { + let id = self.fun_id; + self.fun_id += 1; + id + } + + /// Adds a function declaration to the current scope + pub fn add_fun_declaration(&mut self, fun: FunctionInterface, ctx: Context) -> Option { + let global_id = self.gen_fun_id(); + // Add the function to the public function list + if fun.is_public { + let decl = fun.clone().to_fun_declaration(global_id); + self.context.pub_funs.push(decl); + } + // Add the function to the current scope + let scope = self.context.scopes.last_mut().unwrap(); + scope.add_fun(fun.to_fun_declaration(global_id)).then(|| { + // Add the function to the function cache + self.fun_cache.add_declaration(global_id, ctx); + global_id + }) + } + + /// Adds a function declaration that that was already parsed - this function is probably imported + pub fn add_fun_declaration_existing(&mut self, fun: FunctionDecl) -> Option { + let global_id = self.gen_fun_id(); + // Add the function to the current scope + let scope = self.context.scopes.last_mut().unwrap(); + scope.add_fun(fun).then(|| global_id) + } + + /// Adds a function instance to the cache + /// This function returns the id of the function instance variant + pub fn add_fun_instance(&mut self, fun: FunctionInterface, block: Block) -> usize { + let id = fun.id.expect("Function id is not set"); + self.fun_cache.add_instance(id, fun.to_fun_instance(block)) + } + + /// Gets a function declaration from the current scope or any parent scope + pub fn get_fun_declaration(&self, name: &str) -> Option<&FunctionDecl> { + self.context.scopes.iter().rev().find_map(|scope| scope.get_fun(name)) + } + + /// Gets function names + pub fn get_fun_names(&self) -> BTreeSet<&String> { + self.context.scopes.iter().rev().flat_map(|scope| scope.get_fun_names()).collect() } } impl Metadata for ParserMetadata { fn new(tokens: Vec, path: Option, code: Option) -> Self { ParserMetadata { - expr: tokens, - index: 0, - path: path.clone(), - code, + eval_code: code, binop_border: None, - mem: Memory::new(), debug: None, - trace: Vec::new(), - import_history: ImportHistory::new(path), - loop_ctx: false, - function_ctx: false, - messages: Vec::new(), + import_cache: ImportCache::new(path.clone()), + fun_cache: FunctionCache::new(), + fun_id: 0, + var_id: 0, + context: Context::new(path, tokens), + messages: Vec::new() } } + fn get_trace(&self) -> Vec { + self.context.trace.clone() + } + fn get_index(&self) -> usize { - self.index + self.context.index } fn set_index(&mut self, index: usize) { - self.index = index + self.context.index = index } fn get_token_at(&self, index: usize) -> Option { - self.expr.get(index).cloned() + self.context.expr.get(index).cloned() } fn get_debug(&mut self) -> Option { @@ -71,10 +174,10 @@ impl Metadata for ParserMetadata { } fn get_code(&self) -> Option<&String> { - self.code.as_ref() + self.eval_code.as_ref() } fn get_path(&self) -> Option { - self.path.clone() + self.context.path.clone() } } \ No newline at end of file diff --git a/src/utils/metadata/translate.rs b/src/utils/metadata/translate.rs index 6462d3e7..2e20cb3f 100644 --- a/src/utils/metadata/translate.rs +++ b/src/utils/metadata/translate.rs @@ -1,17 +1,17 @@ -use crate::{translate::compute::ArithType, utils::memory::Memory}; +use crate::{translate::compute::ArithType, utils::function_cache::FunctionCache}; use super::ParserMetadata; pub struct TranslateMetadata { pub arith_module: ArithType, - pub mem: Memory, + pub fun_cache: FunctionCache, pub indent: i64 } impl TranslateMetadata { - pub fn new(meta: &ParserMetadata) -> Self { + pub fn new(meta: ParserMetadata) -> Self { TranslateMetadata { arith_module: ArithType::BcSed, - mem: meta.mem.clone(), + fun_cache: meta.fun_cache, indent: -1 } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index d8061a6d..cb7dcba5 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,6 +1,6 @@ pub mod metadata; -pub mod memory; -pub mod function_map; -pub mod exports; -pub mod import_history; +pub mod context; +pub mod function_cache; +pub mod import_cache; +pub mod function_interface; pub use metadata::*; \ No newline at end of file diff --git a/tests/is_even.ab b/test_files/is_even.ab similarity index 100% rename from tests/is_even.ab rename to test_files/is_even.ab diff --git a/tests/str/trim.ab b/test_files/str/trim.ab similarity index 100% rename from tests/str/trim.ab rename to test_files/str/trim.ab