improvement: re-use the same context for multiple file compiles.

This commit is contained in:
James Harton 2022-08-29 19:09:31 +12:00
parent c12177947e
commit eefe3a606e
7 changed files with 138 additions and 52 deletions

View file

@ -1,13 +1,14 @@
use super::{Error, Function, Notice, NoticeKind, NoticeLevel, Type, TypeVariable}; use super::{Error, Function, Notice, NoticeKind, NoticeLevel, Type, TypeVariable};
use crate::source::Source; use crate::source::Source;
use crate::span::Span; use crate::span::Span;
use crate::unique_id::UniqueIdGenerator;
use outrun_common::{Stack, StackError}; use outrun_common::{Stack, StackError};
use std::cell::RefCell; use std::cell::RefCell;
use std::sync::Arc; use std::sync::Arc;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Context { pub struct Context {
pub source: Arc<Source>, pub source: Option<Arc<Source>>,
pub types: Vec<Arc<Type>>, pub types: Vec<Arc<Type>>,
pub functions: Vec<Arc<Function>>, pub functions: Vec<Arc<Function>>,
pub docs: Stack<String>, pub docs: Stack<String>,
@ -15,24 +16,29 @@ pub struct Context {
pub current_type: Stack<Arc<Type>>, pub current_type: Stack<Arc<Type>>,
pub self_type: Stack<Arc<Type>>, pub self_type: Stack<Arc<Type>>,
pub current_function: Stack<Arc<Function>>, pub current_function: Stack<Arc<Function>>,
type_id: u64, type_id: UniqueIdGenerator,
} }
impl Context { impl Context {
pub fn new(source: Arc<Source>) -> Context { pub fn new() -> Context {
bootstrap(Context { bootstrap(Context {
source, source: None,
types: Vec::new(), types: Vec::new(),
functions: Vec::new(), functions: Vec::new(),
docs: Stack::new(), docs: Stack::new(),
notices: Vec::new(), notices: Vec::new(),
type_id: 0, type_id: UniqueIdGenerator::new(),
current_type: Stack::new(), current_type: Stack::new(),
self_type: Stack::new(), self_type: Stack::new(),
current_function: Stack::new(), current_function: Stack::new(),
}) })
} }
pub fn with_source(mut self, source: Arc<Source>) -> Self {
self.source = Some(source);
self
}
pub fn find_named_type<T: ToString>(&self, name: T) -> Result<Arc<Type>, Error> { pub fn find_named_type<T: ToString>(&self, name: T) -> Result<Arc<Type>, Error> {
let name = name.to_string(); let name = name.to_string();
@ -59,8 +65,7 @@ impl Context {
} }
pub fn unbound_type(&mut self) -> Result<Arc<Type>, Error> { pub fn unbound_type(&mut self) -> Result<Arc<Type>, Error> {
let type_id = self.type_id; let type_id = self.type_id.next();
self.type_id += 1;
let r#type = RefCell::new(TypeVariable::Unbound { id: type_id }); let r#type = RefCell::new(TypeVariable::Unbound { id: type_id });
let r#type = Arc::new(Type::Variable { r#type, span: None }); let r#type = Arc::new(Type::Variable { r#type, span: None });
self.types.push(r#type.clone()); self.types.push(r#type.clone());
@ -108,31 +113,47 @@ impl Context {
.ok_or_else(|| StackError::Underflow.into()) .ok_or_else(|| StackError::Underflow.into())
} }
pub fn info(&mut self, span: Option<Span>, kind: NoticeKind) { pub fn info(&mut self, span: Option<Span>, kind: NoticeKind) -> Result<(), Error> {
self.notices.push(Notice { let source = self.assert_has_source()?;
source: self.source.clone(),
let notice = Notice {
source: source,
level: NoticeLevel::Info, level: NoticeLevel::Info,
kind, kind,
span, span,
}) };
self.notices.push(notice);
Ok(())
} }
pub fn warning(&mut self, span: Option<Span>, kind: NoticeKind) { pub fn warning(&mut self, span: Option<Span>, kind: NoticeKind) -> Result<(), Error> {
self.notices.push(Notice { let source = self.assert_has_source()?;
source: self.source.clone(),
let notice = Notice {
source: source,
level: NoticeLevel::Warning, level: NoticeLevel::Warning,
kind, kind,
span, span,
}) };
self.notices.push(notice);
Ok(())
} }
pub fn error(&mut self, span: Option<Span>, kind: NoticeKind) { pub fn error(&mut self, span: Option<Span>, kind: NoticeKind) -> Result<(), Error> {
self.notices.push(Notice { let source = self.assert_has_source()?;
source: self.source.clone(),
let notice = Notice {
source: source,
level: NoticeLevel::Error, level: NoticeLevel::Error,
kind, kind,
span, span,
}) };
self.notices.push(notice);
Ok(())
} }
pub fn search_type<F>(&self, predicate: F) -> Option<Arc<Type>> pub fn search_type<F>(&self, predicate: F) -> Option<Arc<Type>>
@ -141,6 +162,17 @@ impl Context {
{ {
self.types.iter().find(|t| predicate((*t).clone())).cloned() self.types.iter().find(|t| predicate((*t).clone())).cloned()
} }
fn assert_has_source(&self) -> Result<Arc<Source>, 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 { fn bootstrap(mut module: Context) -> Context {

View file

@ -24,6 +24,8 @@ pub enum Error {
}, },
#[error("{0}")] #[error("{0}")]
Stack(#[from] StackError), Stack(#[from] StackError),
#[error("Internal compiler error: {0}")]
InternalError(String, Option<Span>),
} }
impl Error { impl Error {
@ -33,6 +35,7 @@ impl Error {
Error::UnknownType(_) => None, Error::UnknownType(_) => None,
Error::MissingProperty { span, .. } => Some(*span), Error::MissingProperty { span, .. } => Some(*span),
Error::Stack(_) => None, Error::Stack(_) => None,
Error::InternalError(_, span) => *span,
} }
} }
} }

View file

@ -17,7 +17,7 @@ pub fn link_type_references(mut context: Context) -> Result<Context, Error> {
if let Some(target) = maybe_type { if let Some(target) = maybe_type {
ty.link_to(target); ty.link_to(target);
} else { } else {
context.error(ty.span(), NoticeKind::CannotResolveType(target_name)); context.error(ty.span(), NoticeKind::CannotResolveType(target_name))?;
} }
} }

View file

@ -81,7 +81,7 @@ fn eval_type_union(mut context: Context, node: Node) -> Result<(Context, Arc<Typ
} else if name.starts_with("Self.") { } else if name.starts_with("Self.") {
let (mut context, parent) = self_type(context)?; let (mut context, parent) = self_type(context)?;
if parent.is_named() { if parent.is_named() {
let name = parent.to_string(); let name = name.replace("Self", &parent.to_string());
let ty = context.reference_type(name, Some(node.span))?; let ty = context.reference_type(name, Some(node.span))?;
Ok((context, ty)) Ok((context, ty))
} else { } else {
@ -236,7 +236,7 @@ fn visit_atom(mut context: Context, node: Node) -> Result<(Context, Expression),
context.info( context.info(
Some(node.span), Some(node.span),
NoticeKind::UnconventionalAtom(value.clone(), suggestion), NoticeKind::UnconventionalAtom(value.clone(), suggestion),
) )?;
} }
let value = ExpressionValue::Atom(value); let value = ExpressionValue::Atom(value);
@ -347,7 +347,7 @@ fn visit_function_statement(mut context: Context, node: Node) -> Result<Context,
context.info( context.info(
Some(node.span), Some(node.span),
NoticeKind::UnconventionalFunctionName(name.clone(), suggestion), NoticeKind::UnconventionalFunctionName(name.clone(), suggestion),
); )?;
} }
let associated_type = context.current_type.peek().cloned()?; let associated_type = context.current_type.peek().cloned()?;
@ -361,7 +361,7 @@ fn visit_function_statement(mut context: Context, node: Node) -> Result<Context,
context.info( context.info(
Some(node.span), Some(node.span),
NoticeKind::UnconventionalKeyword(key.clone(), suggestion), NoticeKind::UnconventionalKeyword(key.clone(), suggestion),
); )?;
} }
argument_names.push(key); argument_names.push(key);
@ -447,7 +447,7 @@ fn visit_function_statement(mut context: Context, node: Node) -> Result<Context,
context.warning( context.warning(
Some(span), Some(span),
NoticeKind::UnexpectedFunctionProperty(key.to_string()), NoticeKind::UnexpectedFunctionProperty(key.to_string()),
); )?;
} }
Ok(context) Ok(context)
@ -467,7 +467,7 @@ fn visit_get_local(mut context: Context, node: Node) -> Result<(Context, Express
context.info( context.info(
Some(span), Some(span),
NoticeKind::UnconventionalVariableName(local.clone(), suggestion), NoticeKind::UnconventionalVariableName(local.clone(), suggestion),
) )?;
} }
let r#type = context.unbound_type()?; let r#type = context.unbound_type()?;
@ -534,7 +534,7 @@ fn visit_impl(mut context: Context, node: Node) -> Result<(Context, Arc<Type>),
context.warning( context.warning(
Some(node.span), Some(node.span),
NoticeKind::UnexpectedImplProperty(key.to_string()), NoticeKind::UnexpectedImplProperty(key.to_string()),
); )?;
} }
Ok((context, ty)) Ok((context, ty))
@ -556,7 +556,7 @@ fn visit_index(context: Context, node: Node) -> Result<(Context, Expression), Er
context.info( context.info(
Some(node.span), Some(node.span),
NoticeKind::UnconventionalFieldName(field_name.clone(), suggestion), NoticeKind::UnconventionalFieldName(field_name.clone(), suggestion),
); )?;
} }
let value = ExpressionValue::Index { let value = ExpressionValue::Index {
@ -644,7 +644,7 @@ fn visit_let_expression(mut context: Context, node: Node) -> Result<(Context, Ex
context.info( context.info(
Some(span), Some(span),
NoticeKind::UnconventionalVariableName(name.clone(), suggestion), NoticeKind::UnconventionalVariableName(name.clone(), suggestion),
); )?;
} }
let expression_type; let expression_type;
@ -792,7 +792,7 @@ fn visit_protocol(mut context: Context, node: Node) -> Result<(Context, Arc<Type
context.warning( context.warning(
Some(node.span), Some(node.span),
NoticeKind::UnexpectedProtocolProperty(key.to_string()), NoticeKind::UnexpectedProtocolProperty(key.to_string()),
); )?;
} }
Ok((context, ty)) Ok((context, ty))
@ -882,7 +882,7 @@ fn visit_struct(mut context: Context, node: Node) -> Result<(Context, Arc<Type>)
context.info( context.info(
Some(node.span), Some(node.span),
NoticeKind::UnconventionalFieldName(key.clone(), suggestion), NoticeKind::UnconventionalFieldName(key.clone(), suggestion),
); )?;
} }
let (new_context, value) = eval_type_union(context, value.clone())?; 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<Type>)
context.warning( context.warning(
Some(node.span), Some(node.span),
NoticeKind::UnexpectedStructProperty(key.to_string()), NoticeKind::UnexpectedStructProperty(key.to_string()),
); )?;
} }
Ok((context, r#type)) Ok((context, r#type))
@ -988,7 +988,7 @@ fn visit_use_statement(mut context: Context, node: Node) -> Result<Context, Erro
context.warning( context.warning(
Some(node.span), Some(node.span),
NoticeKind::UnexpectedUseProperty(key.to_string()), NoticeKind::UnexpectedUseProperty(key.to_string()),
); )?;
} }
if let Some(alias) = props.get("as") { if let Some(alias) = props.get("as") {
@ -1013,7 +1013,7 @@ fn typename_to_string(context: &mut Context, node: &Node) -> Result<String, Erro
context.info( context.info(
Some(node.span), Some(node.span),
NoticeKind::UnconventionalTypeName(value.clone(), suggestion), NoticeKind::UnconventionalTypeName(value.clone(), suggestion),
); )?;
} }
Ok(value) Ok(value)

View file

@ -19,6 +19,7 @@ mod grammar;
mod ir; mod ir;
mod source; mod source;
mod span; mod span;
mod unique_id;
use std::sync::Arc; use std::sync::Arc;
@ -27,10 +28,10 @@ pub use ir::Context;
pub use ir::Type; pub use ir::Type;
pub use source::Source; pub use source::Source;
pub fn compile(source: Arc<Source>) -> Result<Context, Error> { pub fn compile(context: Context, source: Arc<Source>) -> Result<Context, Error> {
let ast = grammar::parse(source.clone(), None).map_err(|e| (source.clone(), e))?; 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::visit_nodes(context, ast).map_err(|e| (source.clone(), e))?;
let context = ir::improve(context).map_err(|e| (source.clone(), e))?; let context = ir::improve(context).map_err(|e| (source.clone(), e))?;

View file

@ -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<AtomicU64>,
}
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);
}

View file

@ -6,23 +6,22 @@ use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
pub fn init() -> Result<Vec<Context>, Error> { pub fn init() -> Result<Context, Error> {
let path = Path::new(env!("CARGO_MANIFEST_DIR")); let path = Path::new(env!("CARGO_MANIFEST_DIR"));
let path = path.join("lib"); let path = path.join("lib");
let path = path.as_path(); let path = path.as_path();
let files = collect_files(path, Vec::new())?; let files = collect_files(path, Vec::new())?;
let mut results = Vec::new(); let mut context = Context::new();
for path in files.iter().take(1) { for path in files.iter().take(1) {
let source = Source::from_file(path)?; let source = Source::from_file(path)?;
let source = Arc::new(source); let source = Arc::new(source);
let context = outrun_compiler::compile(source)?; context = outrun_compiler::compile(context, source)?;
results.push(context);
} }
Ok(results) Ok(context)
} }
fn collect_files(path: &Path, mut files: Vec<PathBuf>) -> Result<Vec<PathBuf>, Error> { fn collect_files(path: &Path, mut files: Vec<PathBuf>) -> Result<Vec<PathBuf>, Error> {
@ -52,21 +51,19 @@ mod test {
#[test] #[test]
fn test_init() -> Result<()> { fn test_init() -> Result<()> {
let results = init()?; let context = init()?;
// println!("results: {:#?}", results); // println!("results: {:#?}", results);
for context in results { let unbound_types: Vec<Arc<Type>> = context
let unbound_types: Vec<Arc<Type>> = context .types
.types .iter()
.iter() .filter(|t| t.is_unbound())
.filter(|t| t.is_unbound()) .cloned()
.cloned() .collect();
.collect(); println!("unbound_types: {:#?}", unbound_types);
println!("unbound_types: {:#?}", unbound_types);
assert_eq!(unbound_types.len(), 0); assert_eq!(unbound_types.len(), 0);
}
Ok(()) Ok(())
} }