diff --git a/src/asts.rs b/src/asts.rs new file mode 100644 index 0000000..b743795 --- /dev/null +++ b/src/asts.rs @@ -0,0 +1 @@ +pub mod hil; diff --git a/src/asts/hil.rs b/src/asts/hil.rs new file mode 100644 index 0000000..f25246e --- /dev/null +++ b/src/asts/hil.rs @@ -0,0 +1,173 @@ +use crate::variable_map::{Variable, VariableMap}; +use pretty::{DocAllocator, DocBuilder, Pretty}; + +#[derive(Debug, PartialEq)] +pub struct Program { + pub statements: Vec>, +} + +impl Program { + pub fn pretty<'a, D, A>( + &self, + variable_map: &VariableMap, + allocator: &'a D, + ) -> DocBuilder<'a, D, A> + where + A: 'a, + D: ?Sized + DocAllocator<'a, A>, + { + let mut result = allocator.nil(); + + for stmt in self.statements.iter() { + result = result + .append(stmt.pretty(variable_map, allocator)) + .append(allocator.text(";")) + .append(allocator.hardline()); + } + + result + } +} + +#[derive(Debug, PartialEq)] +pub enum Statement { + Binding(Annotation, Variable, Expression), + Print(Annotation, Variable), +} + +impl Statement { + pub fn pretty<'a, D, A>( + &self, + variable_map: &VariableMap, + allocator: &'a D, + ) -> DocBuilder<'a, D, A> + where + A: 'a, + D: ?Sized + DocAllocator<'a, A>, + { + match self { + Statement::Binding(_, var, expr) => { + let name = variable_map.get_name(*var).unwrap_or(""); + + allocator + .text(name.to_string()) + .append(allocator.space()) + .append(allocator.text("=")) + .append(allocator.space()) + .append(expr.pretty(variable_map, allocator)) + } + Statement::Print(_, var) => { + let name = variable_map.get_name(*var).unwrap_or(""); + + allocator + .text("print") + .append(allocator.space()) + .append(allocator.text(name.to_string())) + } + } + } +} + +#[derive(Debug, PartialEq)] +pub enum Expression { + Value(Annotation, Value), + Reference(Annotation, Variable), + Primitive(Annotation, Primitive, Vec>), +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Primitive { + Plus, + Minus, + Times, + Divide, +} + +impl Expression { + fn pretty<'a, A, D>(&self, variable_map: &VariableMap, allocator: &'a D) -> DocBuilder<'a, D, A> + where + A: 'a, + D: ?Sized + DocAllocator<'a, A>, + { + match self { + Expression::Value(_, val) => val.pretty(allocator), + Expression::Reference(_, var) => { + let name = variable_map.get_name(*var).unwrap_or(""); + + allocator.text(name.to_string()) + } + Expression::Primitive(_, op, exprs) if exprs.len() == 1 => op + .pretty(allocator) + .append(exprs[0].pretty(variable_map, allocator)), + Expression::Primitive(_, op, exprs) if exprs.len() == 2 => { + let left = exprs[0].pretty(variable_map, allocator); + let right = exprs[1].pretty(variable_map, allocator); + + left.append(allocator.space()) + .append(op.pretty(allocator)) + .append(allocator.space()) + .append(right) + .parens() + } + Expression::Primitive(_, op, exprs) => { + allocator.text(format!("!!{:?} with {} arguments!!", op, exprs.len())) + } + } + } +} + +impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Primitive +where + A: 'a, + D: ?Sized + DocAllocator<'a, A>, +{ + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> { + match self { + Primitive::Plus => allocator.text("+"), + Primitive::Minus => allocator.text("-"), + Primitive::Times => allocator.text("*"), + Primitive::Divide => allocator.text("/"), + } + } +} + +#[derive(Debug, PartialEq)] +pub enum Value { + Number(Option, i128), +} + +impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Value +where + A: 'a, + D: ?Sized + DocAllocator<'a, A>, +{ + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> { + match self { + Value::Number(opt_base, value) => { + let value_str = match opt_base { + None => format!("{}", value), + Some(2) => format!("0b{:b}", value), + Some(8) => format!("0o{:o}", value), + Some(10) => format!("0d{}", value), + Some(16) => format!("0x{:x}", value), + Some(_) => format!("!!{:x}!!", value), + }; + + allocator.text(value_str) + } + } + } +} + +#[derive(Clone, Copy)] +struct CommaSep {} + +impl<'a, 'b, D, A> Pretty<'a, D, A> for CommaSep +where + A: 'a, + D: ?Sized + DocAllocator<'a, A>, +{ + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> { + allocator.text(",").append(allocator.space()) + } +} diff --git a/src/bin.rs b/src/bin.rs index a7d0aa4..99d6d6f 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -3,10 +3,8 @@ use codespan_reporting::diagnostic::Diagnostic; use codespan_reporting::files::SimpleFiles; use codespan_reporting::term; use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; -use ngr::error::Error; -use ngr::syntax::Program; -use pretty::{Arena, Pretty}; -use std::fs; +use ngr::passes::run_front_end; +use pretty::Arena; #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] @@ -19,36 +17,27 @@ struct CommandLineArguments { file: String, } -fn compile_file( - file_database: &mut SimpleFiles, - initial_file_name: &str, -) -> Result { - let initial_file_contents = fs::read_to_string(initial_file_name)?; - let initial_file = file_database.add(initial_file_name.to_string(), initial_file_contents); - let db_version = file_database.get(initial_file)?; - let db_version_source = db_version.source(); - Ok(Program::parse(initial_file, db_version_source)?) -} - fn main() { let args = CommandLineArguments::parse(); let mut file_database = SimpleFiles::new(); let initial_file_name = &args.file; + let mut hil_conversion_result = run_front_end(&mut file_database, initial_file_name); - match compile_file(&mut file_database, initial_file_name) { - Ok(p) => { - let arena = Arena::new(); - p.pretty(&arena) - .into_doc() - .render_colored(72, StandardStream::stdout(ColorChoice::Auto)) - .unwrap() - } - Err(e) => { - let diagnostic = Diagnostic::from(e); - let writer = StandardStream::stderr(ColorChoice::Auto); - let config = codespan_reporting::term::Config::default(); + let writer = StandardStream::stderr(ColorChoice::Auto); + let config = codespan_reporting::term::Config::default(); - term::emit(&mut writer.lock(), &config, &file_database, &diagnostic).unwrap(); - } + for error in hil_conversion_result.errors.drain(..) { + term::emit(&mut writer.lock(), &config, &file_database, &Diagnostic::from(error)).unwrap(); + } + for warning in hil_conversion_result.warnings.drain(..) { + term::emit(&mut writer.lock(), &config, &file_database, &Diagnostic::from(warning)).unwrap(); + } + + if let Some((tree, variable_map)) = hil_conversion_result.result { + let arena = Arena::new(); + tree.pretty(&variable_map, &arena) + .into_doc() + .render_colored(72, StandardStream::stdout(ColorChoice::Auto)) + .unwrap() } } diff --git a/src/error.rs b/src/errors.rs similarity index 79% rename from src/error.rs rename to src/errors.rs index eee0d05..09b2d75 100644 --- a/src/error.rs +++ b/src/errors.rs @@ -15,6 +15,12 @@ pub enum Error { #[error("Error in parser: {0}")] ParserError(#[from] ParseError), + + #[error("Internal error: Couldn't deal with bound variable with no bindiing site ({0})")] + BindingSiteFailure(Location, String), + + #[error("Unbound variable '{0}'")] + UnboundVariable(Location, String), } fn locations_to_labels(start: &Location, end: &Location) -> Vec> { @@ -136,6 +142,34 @@ impl From for Diagnostic { }, }, }, + + Error::BindingSiteFailure(location, name) => match location { + Location::Manufactured => Diagnostic::error().with_message(format!( + "Internal Error: Lost binding site for bound variable {}", + name + )), + Location::InFile(file_id, offset) => Diagnostic::error() + .with_labels(vec![ + Label::primary(*file_id, *offset..*offset).with_message("discovered here") + ]) + .with_message(format!( + "Internal Error: Lost binding site for bound variable {}", + name + )), + }, + + Error::UnboundVariable(location, name) => match location { + Location::Manufactured => Diagnostic::error().with_message(format!( + "Unbound variable '{}'", + name + )), + Location::InFile(file_id, offset) => Diagnostic::error() + .with_labels(vec![ + Label::primary(*file_id, *offset..*offset).with_message("unbound here") + ]) + .with_message(format!("Unbound variable '{}'", name)), + + } } } } diff --git a/src/lib.rs b/src/lib.rs index 4c51213..1936d8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,7 @@ -pub mod error; +pub mod asts; +pub mod errors; +pub mod passes; pub mod syntax; pub mod util; +pub mod variable_map; +pub mod warnings; diff --git a/src/passes.rs b/src/passes.rs new file mode 100644 index 0000000..fad4677 --- /dev/null +++ b/src/passes.rs @@ -0,0 +1,64 @@ +use codespan_reporting::files::SimpleFiles; +use crate::asts::hil; +use crate::errors::Error; +use crate::syntax; +use crate::syntax::Location; +use crate::variable_map::VariableMap; +use crate::warnings::Warning; +use std::fs; + +mod syntax_to_hil; + +pub struct PassResult { + pub result: T, + pub warnings: Vec, + pub errors: Vec, +} + +impl From for PassResult> + where + Error: From +{ + fn from(x: E) -> Self { + PassResult { + result: None, + warnings: vec![], + errors: vec![Error::from(x)], + } + } +} + +pub fn run_front_end( + file_database: &mut SimpleFiles, + initial_file_name: &str, +) -> PassResult, VariableMap)>> { + let initial_file_contents = match fs::read_to_string(initial_file_name) { + Ok(x) => x, + Err(e) => return PassResult::from(e), + }; + + let initial_file = file_database.add(initial_file_name.to_string(), initial_file_contents); + let db_version = match file_database.get(initial_file) { + Ok(x) => x, + Err(e) => return PassResult::from(e), + }; + let db_version_source = db_version.source(); + let raw_syntax = match syntax::Program::parse(initial_file, db_version_source) { + Ok(x) => x, + Err(e) => return PassResult::from(e), + }; + + let mut variable_map = VariableMap::empty(); + let conversion_result = hil::Program::convert(raw_syntax, &mut variable_map); + let result = if conversion_result.errors.is_empty() { + Some((conversion_result.result, variable_map)) + } else { + None + }; + + PassResult { + result, + warnings: conversion_result.warnings, + errors: conversion_result.errors, + } +} \ No newline at end of file diff --git a/src/passes/syntax_to_hil.rs b/src/passes/syntax_to_hil.rs new file mode 100644 index 0000000..21b6b55 --- /dev/null +++ b/src/passes/syntax_to_hil.rs @@ -0,0 +1,152 @@ +use crate::asts::hil::{self, Primitive}; +use crate::errors::Error; +use crate::passes::PassResult; +use crate::syntax; +use crate::syntax::Location; +use crate::variable_map::VariableMap; +use crate::warnings::Warning; + +impl hil::Program { + pub fn convert( + mut input: syntax::Program, + var_map: &mut VariableMap, + ) -> PassResult> { + let mut statements = Vec::new(); + let mut warnings = Vec::new(); + let mut errors = Vec::new(); + + for syntax_stmt in input.statements.drain(..) { + let mut result = hil::Statement::convert(syntax_stmt, var_map); + + statements.push(result.result); + warnings.append(&mut result.warnings); + errors.append(&mut result.errors); + } + + PassResult { + result: hil::Program { statements }, + warnings, + errors, + } + } +} + +impl hil::Statement { + fn convert( + input: syntax::Statement, + var_map: &mut VariableMap, + ) -> PassResult> { + match input { + syntax::Statement::Binding(loc, variable_name, expr) => { + let mut expr_result = hil::Expression::convert(expr, var_map); + let mut errors = Vec::new(); + let mut warnings = Vec::new(); + + if let Some(var) = var_map.get_variable(&variable_name) { + if let Some(orig_loc) = var_map.get_binding_site(var) { + warnings.push(Warning::ShadowedVariable(orig_loc, loc.clone(), variable_name.clone())); + } else { + errors.push(Error::BindingSiteFailure( + loc.clone(), + variable_name.clone(), + )); + } + } + + let variable = var_map.add(variable_name, loc.clone()); + let result = hil::Statement::Binding(loc, variable, expr_result.result); + warnings.append(&mut expr_result.warnings); + errors.append(&mut expr_result.errors); + + PassResult { + result, + warnings, + errors, + } + } + + syntax::Statement::Print(variable_loc, variable_name) => match var_map.get_variable(&variable_name) { + None => PassResult { + result: hil::Statement::Print(Location::Manufactured, 0), + warnings: vec![], + errors: vec![Error::UnboundVariable(variable_loc, variable_name)], + }, + + Some(variable) => PassResult { + result: hil::Statement::Print(variable_loc, variable), + warnings: vec![], + errors: vec![], + }, + } + } + } +} + +impl hil::Expression { + fn convert( + input: syntax::Expression, + var_map: &mut VariableMap, + ) -> PassResult> { + match input { + syntax::Expression::Value(location, value) => PassResult { + result: hil::Expression::Value(location, hil::Value::from(value)), + warnings: vec![], + errors: vec![], + }, + + syntax::Expression::Reference(location, name) => match var_map.get_variable(&name) { + None => PassResult { + result: hil::Expression::Reference(Location::Manufactured, 0), + warnings: vec![], + errors: vec![Error::UnboundVariable(location, name)], + }, + + Some(variable) => PassResult { + result: hil::Expression::Reference(location, variable), + warnings: vec![], + errors: vec![], + }, + } + + syntax::Expression::Primitive(location, name, mut exprs) => { + let mut args = vec![]; + let mut warnings = vec![]; + let mut errors = vec![]; + + let op = match name.as_ref() { + "+" => Primitive::Plus, + "-" => Primitive::Minus, + "*" => Primitive::Times, + "/" => Primitive::Divide, + other => { + errors.push(Error::UnboundVariable(location.clone(), other.to_string())); + Primitive::Plus + } + }; + + for orig_expr in exprs.drain(..) { + let mut sub_result = hil::Expression::convert(orig_expr, var_map); + + args.push(sub_result.result); + warnings.append(&mut sub_result.warnings); + errors.append(&mut sub_result.errors); + } + + PassResult { + result: hil::Expression::Primitive(location, op, args), + warnings, + errors, + } + } + } + } +} + +impl From for hil::Value { + fn from(x: syntax::Value) -> hil::Value { + match x { + syntax::Value::Number(base, value) => + hil::Value::Number(base, value), + } + } +} \ No newline at end of file diff --git a/src/syntax.rs b/src/syntax.rs index 0304fd4..62df280 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -38,13 +38,14 @@ impl FromStr for Program { #[test] fn order_of_operations() { - let muladd1 = "1 + 2 * 3;"; + let muladd1 = "x = 1 + 2 * 3;"; let testfile = 0; assert_eq!( Program::from_str(muladd1).unwrap(), Program { - statements: vec![Statement::Expr( + statements: vec![Statement::Binding( Location::InFile(testfile, 0), + "x".to_string(), Expression::Primitive( Location::InFile(testfile, 2), "+".to_string(), diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 90f7d79..bdd9a13 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -31,7 +31,6 @@ where pub enum Statement { Binding(Location, String, Expression), Print(Location, String), - Expr(Location, Expression), } impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Statement @@ -51,7 +50,6 @@ where .text("print") .append(allocator.space()) .append(allocator.text(var.to_string())), - Statement::Expr(_, expr) => expr.pretty(allocator), } } } diff --git a/src/syntax/parser.lalrpop b/src/syntax/parser.lalrpop index 2e34778..95fc2a5 100644 --- a/src/syntax/parser.lalrpop +++ b/src/syntax/parser.lalrpop @@ -43,8 +43,7 @@ Statements: Vec = { Statement: Statement = { "> "=" ";" => Statement::Binding(l, v.to_string(), e), - "print" "> ";" => Statement::Print(l, v.to_string()), - ";" => Statement::Expr(l, e), + "print" "> ";" => Statement::Print(l, v.to_string()), } Expression: Expression = { diff --git a/src/variable_map.rs b/src/variable_map.rs new file mode 100644 index 0000000..882bdd6 --- /dev/null +++ b/src/variable_map.rs @@ -0,0 +1,57 @@ +use crate::syntax::Location; +use std::collections::HashMap; + +pub type Variable = usize; +pub struct VariableMap { + map: HashMap, + next_index: Variable, +} + +struct VariableInfo { + name: String, + binding_location: Location, +} + +impl VariableInfo { + fn new(name: String, binding_location: Location) -> Self { + VariableInfo { + name, + binding_location, + } + } +} + +impl VariableMap { + pub fn empty() -> Self { + VariableMap { + map: HashMap::new(), + next_index: 0, + } + } + + pub fn add(&mut self, name: String, loc: Location) -> Variable { + let result = self.next_index; + + self.next_index += 1; + self.map.insert(result, VariableInfo::new(name, loc)); + + result + } + + pub fn get_name(&self, var: Variable) -> Option<&str> { + self.map.get(&var).map(|x| x.name.as_ref()) + } + + pub fn get_binding_site(&self, var: Variable) -> Option { + self.map.get(&var).map(|x| x.binding_location.clone()) + } + + pub fn get_variable(&self, name: &str) -> Option { + for (num, info) in self.map.iter() { + if info.name == name { + return Some(*num); + } + } + None + } +} diff --git a/src/warnings.rs b/src/warnings.rs new file mode 100644 index 0000000..f76e712 --- /dev/null +++ b/src/warnings.rs @@ -0,0 +1,41 @@ +use crate::syntax::Location; +use codespan_reporting::diagnostic::{Diagnostic, Label}; + +#[derive(Debug, PartialEq)] +pub enum Warning { + ShadowedVariable(Location, Location, String), +} + +impl From for Diagnostic { + fn from(x: Warning) -> Self { + match &x { + Warning::ShadowedVariable(original, new, name) => match original { + Location::Manufactured => match new { + Location::Manufactured => Diagnostic::warning() + .with_message(format!("Variable '{}' is rebound", name)), + Location::InFile(file_id, offset) => Diagnostic::warning() + .with_labels(vec![Label::primary(*file_id, *offset..*offset) + .with_message("variable rebound here")]) + .with_message(format!("Variable '{}' is rebound", name)), + }, + Location::InFile(orig_file_id, orig_offset) => match new { + Location::Manufactured => Diagnostic::warning() + .with_labels(vec![Label::primary( + *orig_file_id, + *orig_offset..*orig_offset, + ) + .with_message("original binding site")]) + .with_message(format!("Variable '{}' is rebound", name)), + Location::InFile(new_file_id, new_offset) => Diagnostic::warning() + .with_labels(vec![ + Label::primary(*new_file_id, *new_offset..*new_offset) + .with_message("variable rebound here"), + Label::secondary(*orig_file_id, *orig_offset..*orig_offset) + .with_message("original binding site"), + ]) + .with_message(format!("Variable '{}' is rebound", name)), + }, + }, + } + } +}