From 3333dffa1e014aab4c093a10c526882013dc7a4c Mon Sep 17 00:00:00 2001 From: Adam Wick Date: Fri, 27 Jan 2023 21:41:06 -0800 Subject: [PATCH] Add a validation step back. --- examples/basic/test2.ngr | 4 ++ src/backend.rs | 38 +++++++-------- src/bin.rs | 26 ++++++++-- src/syntax.rs | 1 + src/syntax/simplify.rs | 10 ++-- src/syntax/validate.rs | 100 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 152 insertions(+), 27 deletions(-) create mode 100644 examples/basic/test2.ngr create mode 100644 src/syntax/validate.rs diff --git a/examples/basic/test2.ngr b/examples/basic/test2.ngr new file mode 100644 index 0000000..775e24a --- /dev/null +++ b/examples/basic/test2.ngr @@ -0,0 +1,4 @@ +x = 5; +x = 4*x + 3; +print x; +print y; diff --git a/src/backend.rs b/src/backend.rs index 4127a74..049ee8e 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -8,7 +8,7 @@ use cranelift_codegen::isa::LookupError; use cranelift_codegen::settings::{Configurable, SetError}; use cranelift_codegen::{isa, settings, CodegenError}; use cranelift_module::{default_libcall_names, ModuleCompiledFunction, ModuleError}; -use cranelift_object::{ObjectBuilder, ObjectModule, object}; +use cranelift_object::{object, ObjectBuilder, ObjectModule}; use target_lexicon::Triple; use thiserror::Error; @@ -36,24 +36,24 @@ pub enum BackendError { impl From for Diagnostic { fn from(value: BackendError) -> Self { match value { - BackendError::Cranelift(me) => - Diagnostic::error() - .with_message(format!("Internal cranelift error: {}", me)), - BackendError::BuiltinError(me) => - Diagnostic::error() - .with_message(format!("Internal runtime function error: {}", me)), - BackendError::VariableLookupFailure => - Diagnostic::error() - .with_message("Internal variable lookup error!"), - BackendError::CodegenError(me) => - Diagnostic::error() - .with_message(format!("Internal codegen error: {}", me)), - BackendError::SetError(me) => - Diagnostic::error() - .with_message(format!("Internal backend setup error: {}", me)), - BackendError::LookupError(me) => - Diagnostic::error() - .with_message(format!("Internal error: {}", me)), + BackendError::Cranelift(me) => { + Diagnostic::error().with_message(format!("Internal cranelift error: {}", me)) + } + BackendError::BuiltinError(me) => { + Diagnostic::error().with_message(format!("Internal runtime function error: {}", me)) + } + BackendError::VariableLookupFailure => { + Diagnostic::error().with_message("Internal variable lookup error!") + } + BackendError::CodegenError(me) => { + Diagnostic::error().with_message(format!("Internal codegen error: {}", me)) + } + BackendError::SetError(me) => { + Diagnostic::error().with_message(format!("Internal backend setup error: {}", me)) + } + BackendError::LookupError(me) => { + Diagnostic::error().with_message(format!("Internal error: {}", me)) + } } } } diff --git a/src/bin.rs b/src/bin.rs index 4a72a35..435ed4d 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -41,7 +41,9 @@ impl From for Diagnostic { MainError::Backend(be) => be.into(), MainError::ParserError(pe) => (&pe).into(), MainError::IoError(e) => Diagnostic::error().with_message(format!("IO error: {}", e)), - MainError::WriteError(e) => Diagnostic::error().with_message(format!("Module write error: {}", e)), + MainError::WriteError(e) => { + Diagnostic::error().with_message(format!("Module write error: {}", e)) + } } } } @@ -50,13 +52,27 @@ fn compile(file_database: &mut SimpleFiles) -> Result<(), MainEr let args = CommandLineArguments::parse(); let syntax = Syntax::parse_file(file_database, &args.file)?; + let (mut errors, mut warnings) = syntax.validate(); + let stop = !errors.is_empty(); + let messages = errors + .drain(..) + .map(Into::into) + .chain(warnings.drain(..).map(Into::into)); + let writer = StandardStream::stderr(ColorChoice::Auto); + let config = codespan_reporting::term::Config::default(); + + for message in messages { + term::emit(&mut writer.lock(), &config, file_database, &message).unwrap(); + } + + if stop { + return Ok(()); + } + let ir = IR::from(syntax.simplify()); let compiled = Cranelift::new(Triple::host(), ir)?; let bytes = compiled.bytes()?; - std::fs::write( - args.output.unwrap_or_else(|| "output.o".to_string()), - bytes, - )?; + std::fs::write(args.output.unwrap_or_else(|| "output.o".to_string()), bytes)?; Ok(()) } diff --git a/src/syntax.rs b/src/syntax.rs index d491aa0..4f44395 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -11,6 +11,7 @@ lalrpop_mod!( "/syntax/parser.rs" ); pub mod ast; +mod validate; pub use crate::syntax::ast::*; pub use crate::syntax::location::Location; diff --git a/src/syntax/simplify.rs b/src/syntax/simplify.rs index aaa4cc7..58574c0 100644 --- a/src/syntax/simplify.rs +++ b/src/syntax/simplify.rs @@ -1,4 +1,4 @@ -use crate::syntax::ast::{Program, Statement, Expression}; +use crate::syntax::ast::{Expression, Program, Statement}; impl Program { pub fn simplify(mut self) -> Self { @@ -40,10 +40,14 @@ impl Expression { let new_name = format!("<{}:{}>", base_name, *gensym_index); *gensym_index += 1; - prereqs.push(Statement::Binding(loc.clone(), new_name.clone(), Expression::Primitive(loc.clone(), prim, new_exprs))); + prereqs.push(Statement::Binding( + loc.clone(), + new_name.clone(), + Expression::Primitive(loc.clone(), prim, new_exprs), + )); (prereqs, Expression::Reference(loc, new_name)) } } } -} \ No newline at end of file +} diff --git a/src/syntax/validate.rs b/src/syntax/validate.rs new file mode 100644 index 0000000..32cefb4 --- /dev/null +++ b/src/syntax/validate.rs @@ -0,0 +1,100 @@ +use crate::syntax::{Expression, Location, Program, Statement}; +use codespan_reporting::diagnostic::Diagnostic; +use std::collections::HashMap; + +pub enum Error { + UnboundVariable(Location, String), +} + +impl From for Diagnostic { + fn from(x: Error) -> Self { + match &x { + Error::UnboundVariable(location, name) => location + .labelled_error("unbound here") + .with_message(format!("Unbound variable '{}'", name)), + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Warning { + ShadowedVariable(Location, Location, String), +} + +impl From for Diagnostic { + fn from(x: Warning) -> Self { + match &x { + Warning::ShadowedVariable(original, new, name) => Diagnostic::warning() + .with_labels(vec![ + new.primary_label().with_message("variable rebound here"), + original + .secondary_label() + .with_message("original binding site"), + ]) + .with_message(format!("Variable '{}' is rebound", name)), + } + } +} + +impl Program { + pub fn validate(&self) -> (Vec, Vec) { + let mut errors = vec![]; + let mut warnings = vec![]; + let mut bound_variables = HashMap::new(); + + for stmt in self.statements.iter() { + match stmt { + Statement::Binding(loc, var, val) => { + // we're going to make the decision that a variable is not bound in the right + // hand side of its binding, which makes a lot of things easier. So we'll just + // immediately check the expression, and go from there. + let (mut exp_errors, mut exp_warnings) = val.validate(&bound_variables); + + errors.append(&mut exp_errors); + warnings.append(&mut exp_warnings); + if let Some(original_binding_site) = bound_variables.get(var) { + warnings.push(Warning::ShadowedVariable( + original_binding_site.clone(), + loc.clone(), + var.clone(), + )); + } else { + bound_variables.insert(var.clone(), loc.clone()); + } + } + + Statement::Print(_, var) if bound_variables.contains_key(var) => {} + Statement::Print(loc, var) => { + errors.push(Error::UnboundVariable(loc.clone(), var.clone())) + } + } + } + + (errors, warnings) + } +} + +impl Expression { + fn validate(&self, variable_map: &HashMap) -> (Vec, Vec) { + match self { + Expression::Value(_, _) => (vec![], vec![]), + Expression::Reference(_, var) if variable_map.contains_key(var) => (vec![], vec![]), + Expression::Reference(loc, var) => ( + vec![Error::UnboundVariable(loc.clone(), var.clone())], + vec![], + ), + Expression::Primitive(_, _, args) => { + let mut errors = vec![]; + let mut warnings = vec![]; + + for expr in args.iter() { + let (mut err, mut warn) = expr.validate(variable_map); + errors.append(&mut err); + warnings.append(&mut warn); + } + + (errors, warnings) + } + } + } +}