From 7810052c569ef3a171472a9e87f413890889575b Mon Sep 17 00:00:00 2001 From: Garrit Franke <32395585+garritfra@users.noreply.github.com> Date: Mon, 22 Feb 2021 21:54:31 +0100 Subject: [PATCH] Modules (#17) * chore: rename program -> module * feat: add module builder * feat: append standard library * chore: fix clippy warnings * chore: fix formatting * feat: imports * chore: fix formatting * feat: resolve path deltas to entrypoint * chore: fix formatting * fix: path resolver * chore: refactor stdlib * docs: document modules * docs: add "unreleased" section to changelog * docs: add modules as unreleased bullet in changelog * feat: resolve nested modules * fix: clean up file resolvement * chore: fix clippy lints --- CHANGELOG.md | 1 + docs/SUMMARY.md | 3 +- docs/modules/SUMMARY.md | 66 +++++++++++ lib/array.sb | 27 +++++ lib/assert.sb | 6 + lib/io.sb | 10 ++ lib/os.sb | 4 + lib/stdio.sb | 50 -------- lib/stdlib.sb | 4 + src/builder/mod.rs | 132 ++++++++++++++++++++++ src/command/build.rs | 41 +------ src/generator/c.rs | 2 +- src/generator/js.rs | 2 +- src/generator/llvm.rs | 6 +- src/generator/mod.rs | 4 +- src/generator/x86.rs | 10 +- src/lexer/mod.rs | 2 + src/main.rs | 1 + src/parser/infer.rs | 2 +- src/parser/mod.rs | 6 +- src/parser/node_type.rs | 17 +-- src/parser/parser.rs | 8 +- src/parser/rules.rs | 37 ++++-- src/parser/tests.rs | 106 ++++++++--------- src/tests/test_examples.rs | 5 + tests/importable_module/foo/bar.sb | 5 + tests/importable_module/foo/baz/module.sb | 3 + tests/importable_module/module.sb | 6 + tests/imports.sb | 5 + 29 files changed, 397 insertions(+), 174 deletions(-) create mode 100644 docs/modules/SUMMARY.md create mode 100644 lib/array.sb create mode 100644 lib/assert.sb create mode 100644 lib/io.sb create mode 100644 lib/os.sb delete mode 100644 lib/stdio.sb create mode 100644 lib/stdlib.sb create mode 100644 src/builder/mod.rs create mode 100644 tests/importable_module/foo/bar.sb create mode 100644 tests/importable_module/foo/baz/module.sb create mode 100644 tests/importable_module/module.sb create mode 100644 tests/imports.sb diff --git a/CHANGELOG.md b/CHANGELOG.md index 43b7011..782a8e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ **Features** - Match statements (#15) +- Modules and imports (#17) ## v0.4.0 (2021-02-20) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 0eca749..b718d39 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -9,7 +9,8 @@ - [Functions](./concepts/functions.md) - [Comments](./concepts/comments.md) - [Control Flow](./concepts/control-flow.md) - - [Structured data](./concepts/structured-data.md) + - [Structured Data](./concepts/structured-data.md) +- [Modules and Imports](./modules/SUMMARY.md) - [Developer Resources](./developers/SUMMARY.md) - [Contributing to Sabre](./developers/contributing.md) - [Compiler Backends](./developers/backends.md) diff --git a/docs/modules/SUMMARY.md b/docs/modules/SUMMARY.md new file mode 100644 index 0000000..fefa0fe --- /dev/null +++ b/docs/modules/SUMMARY.md @@ -0,0 +1,66 @@ +# Modules and Imports + +Projects naturally grow over time, and digging through 10.000 lines of code in a single file can be cumbersome. By grouping related functionality and separating code with distinct features, you’ll clarify where to find code that implements a particular feature and where to go to change how a feature works. + +The programs we've written so far have been in one file. As a project grows, you can organize code by splitting it into multiple modules with a clear name. + +In Sabre, every file is also a module. Let's take a look at a project structure and identify its modules. + +``` +. +├── foo +│   ├── bar.sb +│   └── baz +│   └── module.sb +├── main.sb +└── some_logic.sb +``` + +As per convention, the entrypoint for this project is the `main.sb` file in the root directory. + +There is a child-module called `some_logic` at the same directory-level. + +Below it, there is a directory called `foo`, containing the submodule `bar`. To address the `bar` module from our entrypoint, we'd import the following: + +``` +import "foo/bar" +``` + +> **Note**: File extensions in imports are optional. Importing `foo/bar.sb` would yield the same result as importing `foo/bar`. + +## Module entrypoints + +In the `foo` directory, there is another directory called `baz`, containing a single file named `module.sb`. This file is treated as a special file, since it serves as the entrypoint for that module. So, instead of importing the file explicitely: + +``` +// main.sb +import "foo/baz/module" +``` + +we can simply import the module containing this file, and Sabre will import the contained `module.sb` instead. + +``` +// main.sb +import "foo/baz" +``` + +## Using imported modules + +To use code defined in a separate module, we first need to import it. This is usually done at the top of the file, but it technically doesn't make a difference where in the document the import is defined. Once the module is imported, we can use the code inside it, as if it were in the current file. + +Let's say we have a module named `math.sb` in the same directory as out `main.sb`, and it defines the function `add(x: int, y: int): int`. To call it in our `main.sb`, we'd do the following: + +``` +import "math" + +fn main() { + println(add(1, 2)) +} +``` + +If we run `main.sb`, we should see the expected output. Sabre has imported the `add` function from the `math` module. + +``` +$ sabre run main.sb +3 +``` diff --git a/lib/array.sb b/lib/array.sb new file mode 100644 index 0000000..9a7e76d --- /dev/null +++ b/lib/array.sb @@ -0,0 +1,27 @@ +// Prints the size of an array +fn len(arr: int[]): int { + let c: int = 0 + while arr[c] { + c += 1 + } + + return c +} + +// Reverses an array +// TODO: fix me! +fn rev(arr: int[]): int[] { + + let l: int = len(arr) + let new_arr: int[] = [] + + let i: int = 0 + let j: int = l + while i < l { + new_arr[i] = arr[j] + i = i - 1 + j = j - 1 + } + + return new_arr +} \ No newline at end of file diff --git a/lib/assert.sb b/lib/assert.sb new file mode 100644 index 0000000..6a60191 --- /dev/null +++ b/lib/assert.sb @@ -0,0 +1,6 @@ +fn assert(condition: bool) { + if condition == false { + println("Assertion failed") + exit(1) + } +} \ No newline at end of file diff --git a/lib/io.sb b/lib/io.sb new file mode 100644 index 0000000..db32fea --- /dev/null +++ b/lib/io.sb @@ -0,0 +1,10 @@ +// Raw wrapper around _printf builtin function. +// Writes the given content to stdout +fn print(arg: string) { + _printf(arg) +} + +// Like print(), but with an extra newline ('\n') character +fn println(msg: string) { + print(msg + "\n") +} diff --git a/lib/os.sb b/lib/os.sb new file mode 100644 index 0000000..ba07443 --- /dev/null +++ b/lib/os.sb @@ -0,0 +1,4 @@ +// Exit the program immediately +fn exit(code: int) { + _exit(code) +} \ No newline at end of file diff --git a/lib/stdio.sb b/lib/stdio.sb deleted file mode 100644 index ce40fc6..0000000 --- a/lib/stdio.sb +++ /dev/null @@ -1,50 +0,0 @@ -// Raw wrapper around _printf builtin function. -// Writes the given content to stdout -fn print(arg: string) { - _printf(arg) -} - -// Like print(), but with an extra newline ('\n') character -fn println(msg: string) { - print(msg + "\n") -} - -// Exit the program immediately -fn exit(code: int) { - _exit(code) -} - -fn assert(condition: bool) { - if condition == false { - println("Assertion failed") - exit(1) - } -} - -// Prints the size of an array -fn len(arr: int[]): int { - let c: int = 0 - while arr[c] { - c += 1 - } - - return c -} - -// Reverses an array -// TODO: fix me! -fn rev(arr: int[]): int[] { - - let l: int = len(arr) - let new_arr: int[] = [] - - let i: int = 0 - let j: int = l - while i < l { - new_arr[i] = arr[j] - i = i - 1 - j = j - 1 - } - - return new_arr -} \ No newline at end of file diff --git a/lib/stdlib.sb b/lib/stdlib.sb new file mode 100644 index 0000000..7a60eea --- /dev/null +++ b/lib/stdlib.sb @@ -0,0 +1,4 @@ +import "array" +import "assert" +import "io" +import "os" \ No newline at end of file diff --git a/src/builder/mod.rs b/src/builder/mod.rs new file mode 100644 index 0000000..444edc3 --- /dev/null +++ b/src/builder/mod.rs @@ -0,0 +1,132 @@ +use crate::generator; +use crate::lexer; +use crate::parser; +use crate::Lib; +use crate::PathBuf; +use parser::node_type::Module; +use std::env; +/** + * Copyright 2021 Garrit Franke + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +use std::fs::File; +use std::io::Read; +use std::io::Write; + +pub struct Builder { + in_file: PathBuf, + modules: Vec, +} + +impl Builder { + pub fn new(entrypoint: PathBuf) -> Self { + Self { + in_file: entrypoint, + modules: Vec::new(), + } + } + + fn get_base_path(&self) -> Result { + Ok(self + .in_file + .parent() + .ok_or("File does not have a parent")? + .to_path_buf()) + } + + pub fn build(&mut self) -> Result<(), String> { + let in_file = self.in_file.clone(); + // Resolve path deltas between working directory and entrypoint + let base_directory = self.get_base_path()?; + if let Ok(resolved_delta) = in_file.strip_prefix(&base_directory) { + // TODO: This error could probably be handled better + let _ = env::set_current_dir(base_directory); + self.in_file = resolved_delta.to_path_buf(); + } + self.build_module(self.in_file.clone())?; + + // Append standard library + self.build_stdlib(); + Ok(()) + } + + fn build_module(&mut self, file_path: PathBuf) -> Result { + // TODO: This method can probably cleaned up quite a bit + + // In case the module is a directory, we have to append the filename of the entrypoint + let resolved_file_path = if file_path.is_dir() { + file_path.join("module.sb") + } else { + file_path + }; + let mut file = File::open(&resolved_file_path) + .map_err(|_| format!("Could not open file: {}", resolved_file_path.display()))?; + let mut contents = String::new(); + + file.read_to_string(&mut contents) + .expect("Could not read file"); + let tokens = lexer::tokenize(&contents); + let module = parser::parse( + tokens, + Some(contents), + resolved_file_path.display().to_string(), + )?; + for import in &module.imports { + // Build module relative to the current file + let mut import_path = resolved_file_path + .parent() + .unwrap() + .join(PathBuf::from(import)); + + if import_path.is_dir() { + import_path = import_path.join("module.sb"); + } else if !import_path.ends_with(".sb") { + import_path.set_extension("sb"); + } + + self.build_module(import_path)?; + } + self.modules.push(module.clone()); + Ok(module) + } + + pub(crate) fn generate(&mut self, out_file: PathBuf) -> Result<(), String> { + let mut mod_iter = self.modules.iter(); + + // TODO: We shouldn't clone here + let mut condensed = mod_iter.next().ok_or("No module specified")?.clone(); + for module in mod_iter { + condensed.merge_with(module.clone()); + } + let output = generator::generate(condensed); + let mut file = std::fs::File::create(out_file).expect("create failed"); + file.write_all(output.as_bytes()).expect("write failed"); + file.flush().map_err(|_| "Could not flush file".into()) + } + + fn build_stdlib(&mut self) { + let assets = Lib::iter(); + + for file in assets { + let stdlib_raw = + Lib::get(&file).expect("Standard library not found. This should not occur."); + let stblib_str = + std::str::from_utf8(&stdlib_raw).expect("Could not interpret standard library."); + let stdlib_tokens = lexer::tokenize(&stblib_str); + let module = parser::parse(stdlib_tokens, Some(stblib_str.into()), file.to_string()) + .expect("Could not parse stdlib"); + self.modules.push(module); + } + } +} diff --git a/src/command/build.rs b/src/command/build.rs index baea9cf..1035152 100644 --- a/src/command/build.rs +++ b/src/command/build.rs @@ -13,44 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -use crate::generator; -use crate::lexer; -use crate::parser; -use crate::Lib; -use std::fs::File; -use std::io::Read; -use std::io::Write; +use crate::builder; use std::path::Path; pub fn build(in_file: &Path, out_file: &Path) -> Result<(), String> { - let mut file = File::open(in_file).expect("Could not open file"); - let mut contents = String::new(); - - file.read_to_string(&mut contents) - .expect("Could not read file"); - let tokens = lexer::tokenize(&contents); - let mut program = parser::parse(tokens, Some(contents))?; - - // C Backend currently does not support stdlib yet, since not all features are implemented - if cfg!(feature = "backend_node") { - let stdlib = build_stdlib(); - program.merge_with(stdlib); - } - - let output = generator::generate(program); - let mut file = std::fs::File::create(out_file).expect("create failed"); - file.write_all(output.as_bytes()).expect("write failed"); - file.flush().expect("Could not flush file"); - - Ok(()) -} - -fn build_stdlib() -> parser::node_type::Program { - let stdlib_raw = - Lib::get("stdio.sb").expect("Standard library not found. This should not occur."); - let stblib_str = - std::str::from_utf8(&stdlib_raw).expect("Could not interpret standard library."); - let stdlib_tokens = lexer::tokenize(&stblib_str); - - parser::parse(stdlib_tokens, Some(stblib_str.into())).expect("Could not parse stdlib") + let mut b = builder::Builder::new(in_file.to_path_buf()); + b.build()?; + b.generate(out_file.to_path_buf()) } diff --git a/src/generator/c.rs b/src/generator/c.rs index 70097e1..2069623 100644 --- a/src/generator/c.rs +++ b/src/generator/c.rs @@ -20,7 +20,7 @@ use crate::util::Either; pub struct CGenerator; impl Generator for CGenerator { - fn generate(prog: Program) -> String { + fn generate(prog: Module) -> String { let mut code = String::new(); let raw_builtins = diff --git a/src/generator/js.rs b/src/generator/js.rs index 5c37a88..1652502 100644 --- a/src/generator/js.rs +++ b/src/generator/js.rs @@ -20,7 +20,7 @@ use std::collections::HashMap; pub struct JsGenerator; impl Generator for JsGenerator { - fn generate(prog: Program) -> String { + fn generate(prog: Module) -> String { let mut code = String::new(); let raw_builtins = diff --git a/src/generator/llvm.rs b/src/generator/llvm.rs index 8083225..cc8e743 100644 --- a/src/generator/llvm.rs +++ b/src/generator/llvm.rs @@ -16,16 +16,16 @@ use crate::generator::Generator; use crate::parser::node_type::*; use inkwell::context::Context; -use inkwell::module::Module; +use inkwell::module; use inkwell::types::*; pub struct LLVMGenerator<'ctx> { ctx: &'ctx Context, - module: Module<'ctx>, + module: module::Module<'ctx>, } impl<'ctx> Generator for LLVMGenerator<'ctx> { - fn generate(prog: Program) -> String { + fn generate(prog: Module) -> String { let ctx = Context::create(); let module = ctx.create_module("main"); let mut generator = LLVMGenerator { ctx: &ctx, module }; diff --git a/src/generator/mod.rs b/src/generator/mod.rs index e4061e1..43358ac 100644 --- a/src/generator/mod.rs +++ b/src/generator/mod.rs @@ -26,13 +26,13 @@ mod tests; pub mod x86; pub trait Generator { - fn generate(prog: Program) -> String; + fn generate(prog: Module) -> String; } // Since we're using multiple features, // "unreachable" statements are okay #[allow(unreachable_code)] -pub fn generate(prog: Program) -> String { +pub fn generate(prog: Module) -> String { #[cfg(feature = "backend_llvm")] return llvm::LLVMGenerator::generate(prog); #[cfg(feature = "backend_c")] diff --git a/src/generator/x86.rs b/src/generator/x86.rs index c44346c..ff91a10 100644 --- a/src/generator/x86.rs +++ b/src/generator/x86.rs @@ -14,7 +14,7 @@ * limitations under the License. */ use crate::generator::Generator; -use crate::parser::node_type::{Function, Program, Statement}; +use crate::parser::node_type::{Function, Module, Statement}; struct Assembly { asm: Vec, @@ -45,7 +45,7 @@ impl Assembly { pub struct X86Generator; impl Generator for X86Generator { - fn generate(prog: Program) -> String { + fn generate(prog: Module) -> String { Self::new().gen_program(prog).build() } } @@ -55,12 +55,14 @@ impl X86Generator { X86Generator {} } - fn gen_program(&mut self, prog: Program) -> Assembly { + fn gen_program(&mut self, prog: Module) -> Assembly { let mut asm = Assembly::new(); - let Program { + let Module { func, globals, structs: _, + path: _, + imports: _, } = prog; asm.add(".intel_syntax noprefix"); diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index 73b3836..e5ec7fe 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -148,6 +148,7 @@ pub enum Keyword { Struct, New, Match, + Import, Unknown, } @@ -376,6 +377,7 @@ impl Cursor<'_> { c if c == "struct" => Keyword::Struct, c if c == "new" => Keyword::New, c if c == "match" => Keyword::Match, + c if c == "import" => Keyword::Import, _ => Keyword::Unknown, } } diff --git a/src/main.rs b/src/main.rs index 13570db..70d5278 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,7 @@ extern crate tempfile; use std::path::PathBuf; use structopt::StructOpt; +mod builder; mod command; mod generator; mod lexer; diff --git a/src/parser/infer.rs b/src/parser/infer.rs index 294709c..677beb2 100644 --- a/src/parser/infer.rs +++ b/src/parser/infer.rs @@ -19,7 +19,7 @@ use super::node_type::*; /// /// TODO: Global symbol table is passed around randomly. /// This could probably be cleaned up. -pub(super) fn infer(program: &mut Program) { +pub(super) fn infer(program: &mut Module) { let table = &program.get_symbol_table(); // TODO: Fix aweful nesting for func in &mut program.func { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6e70a39..99113c9 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -20,11 +20,11 @@ pub mod node_type; mod parser; mod rules; use crate::lexer::Token; -use node_type::Program; +use node_type::Module; #[cfg(test)] mod tests; -pub fn parse(tokens: Vec, raw: Option) -> Result { - let mut parser = parser::Parser::new(tokens, raw); +pub fn parse(tokens: Vec, raw: Option, path: String) -> Result { + let mut parser = parser::Parser::new(tokens, raw, path); parser.parse() } diff --git a/src/parser/node_type.rs b/src/parser/node_type.rs index 7a992e2..4347ce2 100644 --- a/src/parser/node_type.rs +++ b/src/parser/node_type.rs @@ -1,3 +1,6 @@ +use crate::lexer::*; +use core::convert::TryFrom; +use std::collections::HashMap; /** * Copyright 2020 Garrit Franke * @@ -13,22 +16,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -use crate::lexer::*; -use core::convert::TryFrom; -use std::collections::HashMap; +use std::collections::HashSet; /// Table that contains all symbol and its types pub type SymbolTable = HashMap>; -#[derive(Debug)] -pub struct Program { +#[derive(Debug, Clone)] +pub struct Module { + pub path: String, + pub imports: HashSet, pub func: Vec, pub structs: Vec, pub globals: Vec, } -impl Program { - pub fn merge_with(&mut self, mut other: Program) { +impl Module { + pub fn merge_with(&mut self, mut other: Module) { self.func.append(&mut other.func); self.globals.append(&mut other.globals) } diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 486ece5..5467181 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -24,6 +24,7 @@ use std::iter::Peekable; use std::vec::IntoIter; pub struct Parser { + pub path: String, tokens: Peekable>, peeked: Vec, current: Option, @@ -32,12 +33,13 @@ pub struct Parser { } impl Parser { - pub fn new(tokens: Vec, raw: Option) -> Parser { + pub fn new(tokens: Vec, raw: Option, file_name: String) -> Parser { let tokens_without_whitespace: Vec = tokens .into_iter() .filter(|token| token.kind != TokenKind::Whitespace && token.kind != TokenKind::Comment) .collect(); Parser { + path: file_name, tokens: tokens_without_whitespace.into_iter().peekable(), peeked: vec![], current: None, @@ -46,8 +48,8 @@ impl Parser { } } - pub fn parse(&mut self) -> Result { - let mut program = self.parse_program()?; + pub fn parse(&mut self) -> Result { + let mut program = self.parse_module()?; // infer types infer(&mut program); diff --git a/src/parser/rules.rs b/src/parser/rules.rs index 95aeafb..0aab785 100644 --- a/src/parser/rules.rs +++ b/src/parser/rules.rs @@ -1,3 +1,9 @@ +use super::node_type::Statement; +use super::node_type::*; +use super::parser::Parser; +use crate::lexer::Keyword; +use crate::lexer::{TokenKind, Value}; +use std::collections::HashMap; /** * Copyright 2020 Garrit Franke * @@ -13,24 +19,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -use super::node_type::Statement; -use super::node_type::*; -use super::parser::Parser; -use crate::lexer::Keyword; -use crate::lexer::{TokenKind, Value}; -use std::collections::HashMap; +use std::collections::HashSet; use std::convert::TryFrom; impl Parser { - pub fn parse_program(&mut self) -> Result { + pub fn parse_module(&mut self) -> Result { let mut functions = Vec::new(); let mut structs = Vec::new(); + let mut imports = HashSet::new(); let globals = Vec::new(); while self.has_more() { let next = self.peek()?; match next.kind { TokenKind::Keyword(Keyword::Function) => functions.push(self.parse_function()?), + TokenKind::Keyword(Keyword::Import) => { + imports.insert(self.parse_import()?); + } TokenKind::Keyword(Keyword::Struct) => { structs.push(self.parse_struct_definition()?) } @@ -38,10 +43,14 @@ impl Parser { } } - Ok(Program { + // TODO: Populate imports + + Ok(Module { func: functions, structs, globals, + path: self.path.clone(), + imports, }) } @@ -139,6 +148,18 @@ impl Parser { }) } + fn parse_import(&mut self) -> Result { + self.match_keyword(Keyword::Import)?; + let import_path_token = self.match_token(TokenKind::Literal(Value::Str))?; + + // Remove leading and trailing string tokens + let mut chars = import_path_token.raw.chars(); + chars.next(); + chars.next_back(); + + Ok(chars.collect()) + } + fn parse_type(&mut self) -> Result { self.match_token(TokenKind::Colon)?; let next = self.peek()?; diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 614639c..e0291a9 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -21,7 +21,7 @@ use crate::parser::parse; fn test_parse_empty_function() { let raw = "fn main() {}"; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -33,7 +33,7 @@ fn test_parse_function_with_return() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -45,7 +45,7 @@ fn test_parse_redundant_semicolon() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_err()) } @@ -55,7 +55,7 @@ fn test_parse_no_function_context() { let x = 1 "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_err()) } @@ -73,7 +73,7 @@ fn test_parse_multiple_functions() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -86,7 +86,7 @@ fn test_parse_variable_declaration() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -100,7 +100,7 @@ fn test_parse_variable_reassignment() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -114,7 +114,7 @@ fn test_parse_variable_declaration_added() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -126,7 +126,7 @@ fn test_parse_function_with_args() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -142,7 +142,7 @@ fn test_parse_function_call() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -158,7 +158,7 @@ fn test_parse_return_function_call() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -174,7 +174,7 @@ fn test_parse_function_call_multiple_arguments() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -190,7 +190,7 @@ fn test_parse_nexted_function_call() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -202,7 +202,7 @@ fn test_parse_basic_ops() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -214,7 +214,7 @@ fn test_parse_compound_ops() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -226,7 +226,7 @@ fn test_parse_compound_ops_with_function_call() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -238,7 +238,7 @@ fn test_parse_compound_ops_with_strings() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -250,7 +250,7 @@ fn test_parse_compound_ops_with_identifier() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -262,7 +262,7 @@ fn test_parse_compound_ops_with_identifier_first() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -274,7 +274,7 @@ fn test_parse_compound_ops_return() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -288,7 +288,7 @@ fn test_parse_basic_conditional() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -303,7 +303,7 @@ fn test_parse_basic_conditional_with_multiple_statements() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -320,7 +320,7 @@ fn test_parse_conditional_else_if_branch() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -339,7 +339,7 @@ fn test_parse_conditional_multiple_else_if_branch_branches() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -356,7 +356,7 @@ fn test_parse_conditional_else_branch() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -377,7 +377,7 @@ fn test_parse_conditional_elseif_else_branch() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -390,7 +390,7 @@ fn test_int_array() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -402,7 +402,7 @@ fn test_string_array() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -417,7 +417,7 @@ fn test_basic_while_loop() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -432,7 +432,7 @@ fn test_while_loop_boolean_expression() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -449,7 +449,7 @@ fn test_boolean_arithmetic() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -468,7 +468,7 @@ fn test_array_access_in_loop() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -482,7 +482,7 @@ fn test_array_access_standalone() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -497,7 +497,7 @@ fn test_array_access_assignment() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -513,7 +513,7 @@ fn test_array_access_in_if() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -526,7 +526,7 @@ fn test_uninitialized_variables() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -538,7 +538,7 @@ fn test_function_call_math() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -550,7 +550,7 @@ fn test_function_multiple_args() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -562,7 +562,7 @@ fn test_array_position_assignment() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -576,7 +576,7 @@ fn test_typed_declare() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()) } @@ -588,7 +588,7 @@ fn test_no_function_args_without_type() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_err()) } @@ -600,7 +600,7 @@ fn test_function_with_return_type() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()); assert_eq!(tree.unwrap().func[0].ret_type, Some(Type::Int)); } @@ -617,7 +617,7 @@ fn test_booleans_in_function_call() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()); } @@ -637,7 +637,7 @@ fn test_late_initializing_variable() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()); } @@ -653,7 +653,7 @@ fn test_simple_for_loop() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()); } @@ -671,7 +671,7 @@ fn test_nested_for_loop() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()); } @@ -689,7 +689,7 @@ fn test_nested_array() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()); } @@ -702,7 +702,7 @@ fn test_simple_nested_expression() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()); } @@ -722,7 +722,7 @@ fn test_continue() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()); } @@ -742,7 +742,7 @@ fn test_break() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()); } @@ -761,7 +761,7 @@ fn test_complex_nested_expressions() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()); } @@ -773,7 +773,7 @@ fn test_array_as_argument() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()); } @@ -795,6 +795,6 @@ fn test_struct_initialization() { } "; let tokens = tokenize(raw); - let tree = parse(tokens, Some(raw.to_string())); + let tree = parse(tokens, Some(raw.to_string()), "".into()); assert!(tree.is_ok()); } diff --git a/src/tests/test_examples.rs b/src/tests/test_examples.rs index ee33056..c4ebdba 100644 --- a/src/tests/test_examples.rs +++ b/src/tests/test_examples.rs @@ -36,6 +36,11 @@ fn test_directory(dir_in: &str) -> Result<(), Error> { for ex in examples { let example = ex?; let in_file = dir.join(dir_in).join(example.file_name()); + + // We don't want to build submodules, since they don't run without a main function + if in_file.is_dir() { + continue; + } let out_file = dir.join(&dir_out).join( example .file_name() diff --git a/tests/importable_module/foo/bar.sb b/tests/importable_module/foo/bar.sb new file mode 100644 index 0000000..f226c46 --- /dev/null +++ b/tests/importable_module/foo/bar.sb @@ -0,0 +1,5 @@ +import "baz" + +fn nested_module() { + println("A deeply nested function was called!") +} \ No newline at end of file diff --git a/tests/importable_module/foo/baz/module.sb b/tests/importable_module/foo/baz/module.sb new file mode 100644 index 0000000..9602aab --- /dev/null +++ b/tests/importable_module/foo/baz/module.sb @@ -0,0 +1,3 @@ +fn baz() { + println("Baz was called") +} \ No newline at end of file diff --git a/tests/importable_module/module.sb b/tests/importable_module/module.sb new file mode 100644 index 0000000..5ab00a6 --- /dev/null +++ b/tests/importable_module/module.sb @@ -0,0 +1,6 @@ +import "foo/bar" + +fn external_function() { + println("I was called!!") + nested_module() +} \ No newline at end of file diff --git a/tests/imports.sb b/tests/imports.sb new file mode 100644 index 0000000..d5c8fb1 --- /dev/null +++ b/tests/imports.sb @@ -0,0 +1,5 @@ +import "importable_module" + +fn main() { + external_function() +} \ No newline at end of file