Add variable resolution; the error behavior is pretty bad here.

This commit is contained in:
2022-02-20 21:08:01 -08:00
parent f45488b9af
commit 9d82c8ca2d
12 changed files with 549 additions and 36 deletions

1
src/asts.rs Normal file
View File

@@ -0,0 +1 @@
pub mod hil;

173
src/asts/hil.rs Normal file
View File

@@ -0,0 +1,173 @@
use crate::variable_map::{Variable, VariableMap};
use pretty::{DocAllocator, DocBuilder, Pretty};
#[derive(Debug, PartialEq)]
pub struct Program<Annotation> {
pub statements: Vec<Statement<Annotation>>,
}
impl<Annotation> Program<Annotation> {
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<Annotation> {
Binding(Annotation, Variable, Expression<Annotation>),
Print(Annotation, Variable),
}
impl<Annotation> Statement<Annotation> {
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("<unknown>");
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("<unknown>");
allocator
.text("print")
.append(allocator.space())
.append(allocator.text(name.to_string()))
}
}
}
}
#[derive(Debug, PartialEq)]
pub enum Expression<Annotation> {
Value(Annotation, Value),
Reference(Annotation, Variable),
Primitive(Annotation, Primitive, Vec<Expression<Annotation>>),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Primitive {
Plus,
Minus,
Times,
Divide,
}
impl<Annotation> Expression<Annotation> {
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("<unknown>");
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<u8>, 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())
}
}

View File

@@ -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<String, String>,
initial_file_name: &str,
) -> Result<Program, Error> {
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()
}
}

View File

@@ -15,6 +15,12 @@ pub enum Error {
#[error("Error in parser: {0}")]
ParserError(#[from] ParseError<Location, Token, LexerError>),
#[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<Label<usize>> {
@@ -136,6 +142,34 @@ impl From<Error> for Diagnostic<usize> {
},
},
},
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)),
}
}
}
}

View File

@@ -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;

64
src/passes.rs Normal file
View File

@@ -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<T> {
pub result: T,
pub warnings: Vec<Warning>,
pub errors: Vec<Error>,
}
impl<T,E> From<E> for PassResult<Option<T>>
where
Error: From<E>
{
fn from(x: E) -> Self {
PassResult {
result: None,
warnings: vec![],
errors: vec![Error::from(x)],
}
}
}
pub fn run_front_end(
file_database: &mut SimpleFiles<String, String>,
initial_file_name: &str,
) -> PassResult<Option<(hil::Program<Location>, 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,
}
}

152
src/passes/syntax_to_hil.rs Normal file
View File

@@ -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<Location> {
pub fn convert(
mut input: syntax::Program,
var_map: &mut VariableMap,
) -> PassResult<hil::Program<Location>> {
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<Location> {
fn convert(
input: syntax::Statement,
var_map: &mut VariableMap,
) -> PassResult<hil::Statement<Location>> {
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<Location> {
fn convert(
input: syntax::Expression,
var_map: &mut VariableMap,
) -> PassResult<hil::Expression<Location>> {
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<syntax::Value> for hil::Value {
fn from(x: syntax::Value) -> hil::Value {
match x {
syntax::Value::Number(base, value) =>
hil::Value::Number(base, value),
}
}
}

View File

@@ -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(),

View File

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

View File

@@ -43,8 +43,7 @@ Statements: Vec<Statement> = {
Statement: Statement = {
<l:@L> <v:"<var>"> "=" <e:Expression> ";" => Statement::Binding(l, v.to_string(), e),
<l:@L> "print" <v:"<var>"> ";" => Statement::Print(l, v.to_string()),
<l:@L> <e:Expression> ";" => Statement::Expr(l, e),
"print" <l:@L> <v:"<var>"> ";" => Statement::Print(l, v.to_string()),
}
Expression: Expression = {

57
src/variable_map.rs Normal file
View File

@@ -0,0 +1,57 @@
use crate::syntax::Location;
use std::collections::HashMap;
pub type Variable = usize;
pub struct VariableMap {
map: HashMap<Variable, VariableInfo>,
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<Location> {
self.map.get(&var).map(|x| x.binding_location.clone())
}
pub fn get_variable(&self, name: &str) -> Option<usize> {
for (num, info) in self.map.iter() {
if info.name == name {
return Some(*num);
}
}
None
}
}

41
src/warnings.rs Normal file
View File

@@ -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<Warning> for Diagnostic<usize> {
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)),
},
},
}
}
}