diff --git a/reid/examples/cli.rs b/reid/examples/cli.rs index 45a993b..9a8c8fa 100644 --- a/reid/examples/cli.rs +++ b/reid/examples/cli.rs @@ -1,12 +1,14 @@ -use std::{env, fs}; +use std::{env, fs, path::PathBuf}; use reid::compile; fn main() -> Result<(), std::io::Error> { let args: Vec = env::args().collect(); if let Some(filename) = args.get(1) { - let text = fs::read_to_string(filename)?; - let output = match compile(&text) { + let path = PathBuf::from(filename); + + let text = fs::read_to_string(&path)?; + let output = match compile(&text, PathBuf::from(path)) { Ok(t) => t, Err(e) => panic!("{}", e), }; diff --git a/reid/examples/testcodegen.rs b/reid/examples/testcodegen.rs index 9e7aa60..6aa34a5 100644 --- a/reid/examples/testcodegen.rs +++ b/reid/examples/testcodegen.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use reid::mir::{self, *}; use reid_lib::Context; @@ -159,7 +161,9 @@ fn main() { name: "test module".to_owned(), imports: vec![], functions: vec![fibonacci, main], + path: None, }], + base: PathBuf::new(), }; println!("test1"); diff --git a/reid/src/ast/mod.rs b/reid/src/ast/mod.rs index 9c0cea5..d768c39 100644 --- a/reid/src/ast/mod.rs +++ b/reid/src/ast/mod.rs @@ -1,6 +1,8 @@ //! This is the module that contains relevant code to parsing Reid, that is to //! say transforming a Vec of FullTokens into a loose parsed AST that can be //! used for unwrapping syntax sugar, and then be transformed into Reid MIR. +use std::path::PathBuf; + use crate::token_stream::TokenRange; pub mod parse; @@ -166,4 +168,5 @@ pub enum TopLevelStatement { pub struct Module { pub name: String, pub top_level_statements: Vec, + pub path: Option, } diff --git a/reid/src/ast/process.rs b/reid/src/ast/process.rs index 3fd79a3..f8f27ee 100644 --- a/reid/src/ast/process.rs +++ b/reid/src/ast/process.rs @@ -1,18 +1,18 @@ +use std::path::PathBuf; + use crate::{ ast::{self}, mir::{self, NamedVariableRef, StmtKind}, }; impl mir::Context { - pub fn from(modules: Vec) -> mir::Context { - mir::Context { - modules: modules.iter().map(|m| m.process()).collect(), - } + pub fn from(modules: Vec, base: PathBuf) -> mir::Context { + mir::Context { modules, base } } } impl ast::Module { - fn process(&self) -> mir::Module { + pub fn process(&self) -> mir::Module { let mut imports = Vec::new(); let mut functions = Vec::new(); @@ -67,6 +67,7 @@ impl ast::Module { name: self.name.clone(), imports, functions, + path: self.path.clone(), } } } diff --git a/reid/src/lexer.rs b/reid/src/lexer.rs index edd5698..ce7aee7 100644 --- a/reid/src/lexer.rs +++ b/reid/src/lexer.rs @@ -267,7 +267,7 @@ pub fn tokenize>(to_tokenize: T) -> Result, Error Ok(tokens) } -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, Clone)] pub enum Error { #[error("Invalid token '{}' at Ln {}, Col {}", .0, (.1).1, (.1).0)] InvalidToken(char, Position), diff --git a/reid/src/lib.rs b/reid/src/lib.rs index 5173f35..0a5b80e 100644 --- a/reid/src/lib.rs +++ b/reid/src/lib.rs @@ -40,6 +40,8 @@ //! - Loops //! ``` +use std::path::PathBuf; + use mir::{typecheck::TypeCheck, typeinference::TypeInference, typerefs::TypeRefs}; use reid_lib::Context; @@ -53,7 +55,7 @@ mod pad_adapter; mod token_stream; mod util; -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, Clone)] pub enum ReidError { #[error(transparent)] LexerError(#[from] lexer::Error), @@ -63,10 +65,7 @@ pub enum ReidError { TypeCheckErrors(Vec>), } -/// Takes in a bit of source code, parses and compiles it and produces `hello.o` -/// and `hello.asm` from it, which can be linked using `ld` to produce an -/// executable file. -pub fn compile(source: &str) -> Result { +pub fn compile_module(source: &str, path: Option) -> Result { let tokens = lexer::tokenize(source)?; dbg!(&tokens); @@ -83,10 +82,20 @@ pub fn compile(source: &str) -> Result { let ast_module = ast::Module { name: "test".to_owned(), top_level_statements: statements, + path, }; - dbg!(&ast_module); - let mut mir_context = mir::Context::from(vec![ast_module]); + Ok(ast_module.process()) +} + +/// Takes in a bit of source code, parses and compiles it and produces `hello.o` +/// and `hello.asm` from it, which can be linked using `ld` to produce an +/// executable file. +pub fn compile(source: &str, path: PathBuf) -> Result { + let mut mir_context = mir::Context::from( + vec![compile_module(source, Some(path.clone()))?], + path.parent().unwrap().to_owned(), + ); println!("{}", &mir_context); diff --git a/reid/src/mir/imports.rs b/reid/src/mir/imports.rs new file mode 100644 index 0000000..a860a1f --- /dev/null +++ b/reid/src/mir/imports.rs @@ -0,0 +1,120 @@ +use std::{collections::HashMap, convert::Infallible, fs, path::PathBuf}; + +use crate::{compile_module, ReidError}; + +use super::{ + pass::{Pass, PassState}, + types::EqualsIssue, + Context, FunctionDefinition, Import, Module, +}; + +#[derive(thiserror::Error, Debug, Clone)] +pub enum ErrorKind { + #[error("Unable to import inner modules, not yet supported: {0}")] + InnerModulesNotYetSupported(Import), + #[error("No such module: {0}")] + ModuleNotFound(String), + #[error("Error while compiling module {0}: {1}")] + ModuleCompilationError(String, ReidError), + #[error("No such function {0} found in module {1}")] + NoSuchFunctionInModule(String, String), + #[error("Importing function {0}::{1} not possible: {2}")] + FunctionImportIssue(String, String, EqualsIssue), +} + +/// Struct used to implement a type-checking pass that can be performed on the +/// MIR. +pub struct ImportsPass {} + +impl Pass for ImportsPass { + type TError = ErrorKind; + fn context(&mut self, context: &mut Context, mut state: PassState) { + let mut modules = HashMap::::new(); + + for module in context.modules.clone() { + modules.insert(module.name.clone(), module); + } + + let mut modules_to_process: Vec<_> = modules.values().cloned().collect(); + + let iter = modules_to_process.iter_mut(); + + for module in iter { + let mut new_modules = Vec::new(); + + for import in &module.imports { + let Import(path, _) = import; + if path.len() != 2 { + state.ok::<_, Infallible>( + Err(ErrorKind::InnerModulesNotYetSupported(import.clone())), + import.1, + ); + } + + let module_name = unsafe { path.get_unchecked(0) }; + + let imported = if let Some(module) = modules.get(module_name) { + module + } else { + let file_path = PathBuf::from(&context.base.clone()).join(module_name); + + let Ok(source) = fs::read_to_string(&file_path) else { + state.ok::<_, Infallible>( + Err(ErrorKind::ModuleNotFound(module_name.clone())), + import.1, + ); + continue; + }; + + match compile_module(&source, Some(file_path)) { + Ok(m) => { + new_modules.push(m); + new_modules.last().unwrap() + } + Err(err) => { + state.ok::<_, Infallible>( + Err(ErrorKind::ModuleCompilationError(module_name.clone(), err)), + import.1, + ); + continue; + } + } + }; + + let func_name = unsafe { path.get_unchecked(1) }; + + let Some(func) = imported.functions.iter().find(|f| f.name == *func_name) else { + state.ok::<_, Infallible>( + Err(ErrorKind::NoSuchFunctionInModule( + module_name.clone(), + func_name.clone(), + )), + import.1, + ); + continue; + }; + + if let Some(existing) = module.functions.iter().find(|f| f.name == *func_name) { + if let Err(e) = existing.equals_as_imported(func) { + state.ok::<_, Infallible>( + Err(ErrorKind::FunctionImportIssue( + module_name.clone(), + func_name.clone(), + e, + )), + import.1, + ); + } + } + + module.functions.push(FunctionDefinition { + name: func.name.clone(), + is_pub: false, + return_type: func.return_type.clone(), + parameters: func.parameters.clone(), + kind: super::FunctionDefinitionKind::Extern, + }); + } + } + } +} diff --git a/reid/src/mir/mod.rs b/reid/src/mir/mod.rs index 7984b01..18fb9d9 100644 --- a/reid/src/mir/mod.rs +++ b/reid/src/mir/mod.rs @@ -2,9 +2,12 @@ //! Reid. It contains a simplified version of Reid which can be e.g. //! typechecked. +use std::path::PathBuf; + use crate::token_stream::TokenRange; mod display; +pub mod imports; pub mod pass; pub mod typecheck; pub mod typeinference; @@ -205,13 +208,13 @@ pub enum ReturnKind { Soft, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct NamedVariableRef(pub TypeKind, pub String, pub Metadata); -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Import(pub Vec, pub Metadata); -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ExprKind { Variable(NamedVariableRef), Index(Box, TypeKind, u64), @@ -223,21 +226,21 @@ pub enum ExprKind { Block(Block), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Expression(pub ExprKind, pub Metadata); /// Condition, Then, Else -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct IfExpression(pub Box, pub Block, pub Option); -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct FunctionCall { pub name: String, pub return_type: TypeKind, pub parameters: Vec, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct FunctionDefinition { pub name: String, pub is_pub: bool, @@ -246,7 +249,7 @@ pub struct FunctionDefinition { pub kind: FunctionDefinitionKind, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum FunctionDefinitionKind { /// Actual definition block and surrounding signature range Local(Block, Metadata), @@ -269,7 +272,7 @@ impl FunctionDefinition { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Block { /// List of non-returning statements pub statements: Vec, @@ -277,22 +280,22 @@ pub struct Block { pub meta: Metadata, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Statement(pub StmtKind, pub Metadata); -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct IndexedVariableReference { pub kind: IndexedVariableReferenceKind, pub meta: Metadata, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum IndexedVariableReferenceKind { Named(NamedVariableRef), Index(Box, u64), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum StmtKind { /// Variable name++mutability+type, evaluation Let(NamedVariableRef, bool, Expression), @@ -301,14 +304,16 @@ pub enum StmtKind { Expression(Expression), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Module { pub name: String, pub imports: Vec, pub functions: Vec, + pub path: Option, } #[derive(Debug)] pub struct Context { pub modules: Vec, + pub base: PathBuf, } diff --git a/reid/src/mir/pass.rs b/reid/src/mir/pass.rs index ff1e421..ab6e686 100644 --- a/reid/src/mir/pass.rs +++ b/reid/src/mir/pass.rs @@ -191,6 +191,7 @@ impl<'st, 'sc, TError: STDError + Clone> PassState<'st, 'sc, TError> { pub trait Pass { type TError: STDError + Clone; + fn context(&mut self, _context: &mut Context, mut _state: PassState) {} fn module(&mut self, _module: &mut Module, mut _state: PassState) {} fn function( &mut self, @@ -207,6 +208,7 @@ impl Context { pub fn pass(&mut self, pass: &mut T) -> State { let mut state = State::new(); let mut scope = Scope::default(); + pass.context(self, PassState::from(&mut state, &mut scope)); for module in &mut self.modules { module.pass(pass, &mut state, &mut scope); } diff --git a/reid/src/mir/types.rs b/reid/src/mir/types.rs index 713605c..ab2b6da 100644 --- a/reid/src/mir/types.rs +++ b/reid/src/mir/types.rs @@ -180,3 +180,34 @@ impl IndexedVariableReference { } } } + +#[derive(Debug, Clone, Copy, thiserror::Error)] +pub enum EqualsIssue { + #[error("Function is already defined locally at {:?}", (.0).range)] + ExistsLocally(Metadata), + #[error("asd")] + Equals, + #[error("asd")] + ConflictingImports, +} + +impl FunctionDefinition { + pub fn equals_as_imported(&self, other: &FunctionDefinition) -> Result<(), EqualsIssue> { + match &self.kind { + FunctionDefinitionKind::Local(_, metadata) => { + Err(EqualsIssue::ExistsLocally(*metadata)) + } + FunctionDefinitionKind::Extern => { + if self.is_pub == other.is_pub + && self.name == other.name + && self.parameters == other.parameters + && self.return_type == other.return_type + { + Ok(()) + } else { + Err(EqualsIssue::ConflictingImports) + } + } + } + } +} diff --git a/reid/src/token_stream.rs b/reid/src/token_stream.rs index 24add5b..e112d21 100644 --- a/reid/src/token_stream.rs +++ b/reid/src/token_stream.rs @@ -205,7 +205,7 @@ impl std::iter::Sum for TokenRange { } } -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, Clone)] pub enum Error { #[error("Expected {} at Ln {}, Col {}, got {:?}", .0, (.2).1, (.2).0, .1)] Expected(String, Token, Position),