diff --git a/outrun-compiler/src/ir/context.rs b/outrun-compiler/src/ir/context.rs index 3c67b56..5b90e67 100644 --- a/outrun-compiler/src/ir/context.rs +++ b/outrun-compiler/src/ir/context.rs @@ -1,13 +1,14 @@ use super::{Error, Function, Notice, NoticeKind, NoticeLevel, Type, TypeVariable}; use crate::source::Source; use crate::span::Span; +use crate::unique_id::UniqueIdGenerator; use outrun_common::{Stack, StackError}; use std::cell::RefCell; use std::sync::Arc; #[derive(Debug, Clone)] pub struct Context { - pub source: Arc, + pub source: Option>, pub types: Vec>, pub functions: Vec>, pub docs: Stack, @@ -15,24 +16,29 @@ pub struct Context { pub current_type: Stack>, pub self_type: Stack>, pub current_function: Stack>, - type_id: u64, + type_id: UniqueIdGenerator, } impl Context { - pub fn new(source: Arc) -> Context { + pub fn new() -> Context { bootstrap(Context { - source, + source: None, types: Vec::new(), functions: Vec::new(), docs: Stack::new(), notices: Vec::new(), - type_id: 0, + type_id: UniqueIdGenerator::new(), current_type: Stack::new(), self_type: Stack::new(), current_function: Stack::new(), }) } + pub fn with_source(mut self, source: Arc) -> Self { + self.source = Some(source); + self + } + pub fn find_named_type(&self, name: T) -> Result, Error> { let name = name.to_string(); @@ -59,8 +65,7 @@ impl Context { } pub fn unbound_type(&mut self) -> Result, Error> { - let type_id = self.type_id; - self.type_id += 1; + let type_id = self.type_id.next(); let r#type = RefCell::new(TypeVariable::Unbound { id: type_id }); let r#type = Arc::new(Type::Variable { r#type, span: None }); self.types.push(r#type.clone()); @@ -108,31 +113,47 @@ impl Context { .ok_or_else(|| StackError::Underflow.into()) } - pub fn info(&mut self, span: Option, kind: NoticeKind) { - self.notices.push(Notice { - source: self.source.clone(), + pub fn info(&mut self, span: Option, kind: NoticeKind) -> Result<(), Error> { + let source = self.assert_has_source()?; + + let notice = Notice { + source: source, level: NoticeLevel::Info, kind, span, - }) + }; + + self.notices.push(notice); + + Ok(()) } - pub fn warning(&mut self, span: Option, kind: NoticeKind) { - self.notices.push(Notice { - source: self.source.clone(), + pub fn warning(&mut self, span: Option, kind: NoticeKind) -> Result<(), Error> { + let source = self.assert_has_source()?; + + let notice = Notice { + source: source, level: NoticeLevel::Warning, kind, span, - }) + }; + + self.notices.push(notice); + Ok(()) } - pub fn error(&mut self, span: Option, kind: NoticeKind) { - self.notices.push(Notice { - source: self.source.clone(), + pub fn error(&mut self, span: Option, kind: NoticeKind) -> Result<(), Error> { + let source = self.assert_has_source()?; + + let notice = Notice { + source: source, level: NoticeLevel::Error, kind, span, - }) + }; + + self.notices.push(notice); + Ok(()) } pub fn search_type(&self, predicate: F) -> Option> @@ -141,6 +162,17 @@ impl Context { { self.types.iter().find(|t| predicate((*t).clone())).cloned() } + + fn assert_has_source(&self) -> Result, Error> { + if let Some(source) = self.source.as_ref() { + return Ok(source.clone()); + } + + Err(Error::InternalError( + "Attempt use context without source".to_string(), + None, + )) + } } fn bootstrap(mut module: Context) -> Context { diff --git a/outrun-compiler/src/ir/error.rs b/outrun-compiler/src/ir/error.rs index 941a81e..c9707e8 100644 --- a/outrun-compiler/src/ir/error.rs +++ b/outrun-compiler/src/ir/error.rs @@ -24,6 +24,8 @@ pub enum Error { }, #[error("{0}")] Stack(#[from] StackError), + #[error("Internal compiler error: {0}")] + InternalError(String, Option), } impl Error { @@ -33,6 +35,7 @@ impl Error { Error::UnknownType(_) => None, Error::MissingProperty { span, .. } => Some(*span), Error::Stack(_) => None, + Error::InternalError(_, span) => *span, } } } diff --git a/outrun-compiler/src/ir/stages.rs b/outrun-compiler/src/ir/stages.rs index ba7d8c9..7198135 100644 --- a/outrun-compiler/src/ir/stages.rs +++ b/outrun-compiler/src/ir/stages.rs @@ -17,7 +17,7 @@ pub fn link_type_references(mut context: Context) -> Result { if let Some(target) = maybe_type { ty.link_to(target); } else { - context.error(ty.span(), NoticeKind::CannotResolveType(target_name)); + context.error(ty.span(), NoticeKind::CannotResolveType(target_name))?; } } diff --git a/outrun-compiler/src/ir/visitor.rs b/outrun-compiler/src/ir/visitor.rs index 1e745f6..fe11e9a 100644 --- a/outrun-compiler/src/ir/visitor.rs +++ b/outrun-compiler/src/ir/visitor.rs @@ -81,7 +81,7 @@ fn eval_type_union(mut context: Context, node: Node) -> Result<(Context, Arc Result<(Context, Expression), context.info( Some(node.span), NoticeKind::UnconventionalAtom(value.clone(), suggestion), - ) + )?; } let value = ExpressionValue::Atom(value); @@ -347,7 +347,7 @@ fn visit_function_statement(mut context: Context, node: Node) -> Result Result Result Result<(Context, Express context.info( Some(span), NoticeKind::UnconventionalVariableName(local.clone(), suggestion), - ) + )?; } let r#type = context.unbound_type()?; @@ -534,7 +534,7 @@ fn visit_impl(mut context: Context, node: Node) -> Result<(Context, Arc), context.warning( Some(node.span), NoticeKind::UnexpectedImplProperty(key.to_string()), - ); + )?; } Ok((context, ty)) @@ -556,7 +556,7 @@ fn visit_index(context: Context, node: Node) -> Result<(Context, Expression), Er context.info( Some(node.span), NoticeKind::UnconventionalFieldName(field_name.clone(), suggestion), - ); + )?; } let value = ExpressionValue::Index { @@ -644,7 +644,7 @@ fn visit_let_expression(mut context: Context, node: Node) -> Result<(Context, Ex context.info( Some(span), NoticeKind::UnconventionalVariableName(name.clone(), suggestion), - ); + )?; } let expression_type; @@ -792,7 +792,7 @@ fn visit_protocol(mut context: Context, node: Node) -> Result<(Context, Arc Result<(Context, Arc) context.info( Some(node.span), NoticeKind::UnconventionalFieldName(key.clone(), suggestion), - ); + )?; } let (new_context, value) = eval_type_union(context, value.clone())?; @@ -921,7 +921,7 @@ fn visit_struct(mut context: Context, node: Node) -> Result<(Context, Arc) context.warning( Some(node.span), NoticeKind::UnexpectedStructProperty(key.to_string()), - ); + )?; } Ok((context, r#type)) @@ -988,7 +988,7 @@ fn visit_use_statement(mut context: Context, node: Node) -> Result Result) -> Result { +pub fn compile(context: Context, source: Arc) -> Result { let ast = grammar::parse(source.clone(), None).map_err(|e| (source.clone(), e))?; - let context = Context::new(source.clone()); + let context = context.with_source(source.clone()); let context = ir::visit_nodes(context, ast).map_err(|e| (source.clone(), e))?; let context = ir::improve(context).map_err(|e| (source.clone(), e))?; diff --git a/outrun-compiler/src/unique_id.rs b/outrun-compiler/src/unique_id.rs new file mode 100644 index 0000000..946a253 --- /dev/null +++ b/outrun-compiler/src/unique_id.rs @@ -0,0 +1,53 @@ +use std::sync::{ + atomic::{AtomicU64, Ordering}, + Arc, +}; + +/// A unique identifier generator. +/// +/// Blatantly stolen from https://github.com/gleam-lang/gleam/blob/main/compiler-core/src/uid.rs + +/// A generator of unique ids. Only one should be used per compilation run to +/// ensure ids do not get reused. +#[derive(Debug, Clone, Default)] +pub struct UniqueIdGenerator { + id: Arc, +} + +impl UniqueIdGenerator { + /// Fork the id generator into a new one that does not share the same + /// internal id. It will increment independently. Not to be used in the same + /// compilation run. + pub fn fork(&self) -> Self { + let current = self.id.load(Ordering::Relaxed); + let id = AtomicU64::new(current); + Self { id: Arc::new(id) } + } + + pub fn new() -> Self { + Self::default() + } + + pub fn next(&self) -> u64 { + self.id.fetch_add(1, Ordering::Relaxed) + } +} + +#[test] +fn id_generation() { + let ids = UniqueIdGenerator::new(); + let ids2 = ids.clone(); + + assert_eq!(ids.next(), 0); + assert_eq!(ids.next(), 1); + assert_eq!(ids.next(), 2); + + // Cloned ones use the same counter + assert_eq!(ids2.next(), 3); + assert_eq!(ids2.next(), 4); + assert_eq!(ids2.next(), 5); + + // The original is updated + assert_eq!(ids.next(), 6); + assert_eq!(ids.next(), 7); +} diff --git a/outrun-core/src/lib.rs b/outrun-core/src/lib.rs index 1f35e08..59a947e 100644 --- a/outrun-core/src/lib.rs +++ b/outrun-core/src/lib.rs @@ -6,23 +6,22 @@ use std::fs; use std::path::{Path, PathBuf}; use std::sync::Arc; -pub fn init() -> Result, Error> { +pub fn init() -> Result { let path = Path::new(env!("CARGO_MANIFEST_DIR")); let path = path.join("lib"); let path = path.as_path(); let files = collect_files(path, Vec::new())?; - let mut results = Vec::new(); + let mut context = Context::new(); for path in files.iter().take(1) { let source = Source::from_file(path)?; let source = Arc::new(source); - let context = outrun_compiler::compile(source)?; - results.push(context); + context = outrun_compiler::compile(context, source)?; } - Ok(results) + Ok(context) } fn collect_files(path: &Path, mut files: Vec) -> Result, Error> { @@ -52,21 +51,19 @@ mod test { #[test] fn test_init() -> Result<()> { - let results = init()?; + let context = init()?; // println!("results: {:#?}", results); - for context in results { - let unbound_types: Vec> = context - .types - .iter() - .filter(|t| t.is_unbound()) - .cloned() - .collect(); - println!("unbound_types: {:#?}", unbound_types); + let unbound_types: Vec> = context + .types + .iter() + .filter(|t| t.is_unbound()) + .cloned() + .collect(); + println!("unbound_types: {:#?}", unbound_types); - assert_eq!(unbound_types.len(), 0); - } + assert_eq!(unbound_types.len(), 0); Ok(()) }