diff --git a/src/ast.rs b/src/ast.rs index 17374f5..28a19c9 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -2,12 +2,23 @@ use std::{fmt, path::PathBuf}; use crate::parser::Span; -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Script { pub imports: Vec, pub global_vars: Vec, pub functions: Vec, pub data: Vec, + pub includes: Vec +} + +impl Script { + pub fn merge(&mut self, mut other: Script) { + self.imports.append(&mut other.imports); + self.global_vars.append(&mut other.global_vars); + self.functions.append(&mut other.functions); + self.data.append(&mut other.data); + assert!(other.includes.is_empty()); + } } #[derive(Debug)] @@ -16,6 +27,13 @@ pub enum TopLevelItem { GlobalVar(GlobalVar), Function(Function), Data(Data), + Include(Include) +} + +#[derive(Debug)] +pub struct Include { + pub span: Span, + pub path: String, } #[derive(Debug)] diff --git a/src/lib.rs b/src/lib.rs index 24912f2..4fb801e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,16 +24,41 @@ impl Options { pub fn compile_file>(path: P, options: Options) -> Result> { let path = path.as_ref(); + let mut script = ast::Script::default(); let mut sources = Sources::new(); - let id = sources.add(path)?; - - let mut script = match parser::parse(&sources, id) { - Ok(script) => script, - Err(_) => bail!("Parse failed"), - }; - includes::resolve_includes(&mut script, path)?; + let mut pending_files = vec![(path.to_path_buf(), None)]; + while let Some((path, span)) = pending_files.pop() { + match sources.add(&path) { + Ok((id, true)) => { + let mut new_script = match parser::parse(&sources, id) { + Ok(script) => script, + Err(_) => bail!("Parse failed"), + }; + + includes::resolve_includes(&mut new_script, &path)?; + + for include in std::mem::take(&mut new_script.includes) { + let mut path = path.parent().expect("Script path has no parent").to_path_buf(); + path.push(include.path); + pending_files.push((path, Some(include.span))); + } + + script.merge(new_script); + } + Ok((_, false)) => (), // already parsed this include + Err(err) => { + if let Some(span) = span { + let _ = typecheck::report_error(&err.to_string(), &span, &sources); + } else { + eprintln!("Failed to load script {}: {}", path.display(), err); + } + bail!("Parse failed"); + } + } + } + constfold::fold_script(&mut script); if typecheck::tc_script(&mut script, &sources).is_err() { diff --git a/src/parser.rs b/src/parser.rs index f77c477..77fa050 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -24,11 +24,11 @@ impl Sources { Sources(Vec::new()) } - pub fn add(&mut self, path: &Path) -> Result { + pub fn add(&mut self, path: &Path) -> Result<(usize, bool)> { let canonical = path.canonicalize()?; for (index, source) in self.0.iter().enumerate() { if source.path.canonicalize()? == canonical { - return Ok(index); + return Ok((index, false)); } } let mut source = String::new(); @@ -37,7 +37,7 @@ impl Sources { source: ariadne::Source::from(source), path: path.to_path_buf(), }); - Ok(self.0.len() - 1) + Ok((self.0.len() - 1, true)) } } @@ -970,7 +970,12 @@ fn script_parser() -> impl Parser + Clo }) .boxed(); - import.or(function).or(global).or(data).boxed() + let include = + just(Token::Ident("include".to_string())).ignore_then(string.clone().map_with_span( + |path, span| ast::TopLevelItem::Include(ast::Include { span, path }), + )); + + import.or(function).or(global).or(data).or(include).boxed() }; top_level_item.repeated().then_ignore(end()).map(|items| { @@ -979,6 +984,7 @@ fn script_parser() -> impl Parser + Clo global_vars: Vec::new(), functions: Vec::new(), data: Vec::new(), + includes: Vec::new(), }; for item in items { match item { @@ -986,6 +992,7 @@ fn script_parser() -> impl Parser + Clo ast::TopLevelItem::GlobalVar(v) => script.global_vars.push(v), ast::TopLevelItem::Function(f) => script.functions.push(f), ast::TopLevelItem::Data(d) => script.data.push(d), + ast::TopLevelItem::Include(i) => script.includes.push(i), } } script diff --git a/src/typecheck.rs b/src/typecheck.rs index 6c8a37f..5a0c564 100644 --- a/src/typecheck.rs +++ b/src/typecheck.rs @@ -357,7 +357,7 @@ fn type_mismatch( Err(()) } -fn report_error(msg: &str, span: &Span, sources: &Sources) -> Result<()> { +pub fn report_error(msg: &str, span: &Span, sources: &Sources) -> Result<()> { Report::build(ReportKind::Error, span.0, span.1.start) .with_message(msg) .with_label(