Files
ngr/src/syntax/validate.rs

231 lines
8.6 KiB
Rust

use crate::{
eval::PrimitiveType,
syntax::{Expression, Location, Program, Statement, TopLevel},
util::scoped_map::ScopedMap,
};
use codespan_reporting::diagnostic::Diagnostic;
use std::str::FromStr;
/// An error we found while validating the input program.
///
/// These errors indicate that we should stop trying to compile
/// the program, because it's just fundamentally broken in a way
/// that we're not going to be able to work through. As with most
/// of these errors, we recommend converting this to a [`Diagnostic`]
/// and using [`codespan_reporting`] to present them to the user.
pub enum Error {
UnboundVariable(Location, String),
UnknownType(Location, String),
}
impl From<Error> for Diagnostic<usize> {
fn from(x: Error) -> Self {
match &x {
Error::UnboundVariable(location, name) => location
.labelled_error("unbound here")
.with_message(format!("Unbound variable '{}'", name)),
Error::UnknownType(location, name) => location
.labelled_error("type referenced here")
.with_message(format!("Unknown type '{}'", name)),
}
}
}
/// A problem we found validating the input that isn't critical.
///
/// These are things that the user might want to do something about,
/// but we can keep going without it being a problem. As with most of
/// these things, if you want to present this information to the user,
/// the best way to do so is via [`From`] and [`Diagnostic`], and then
/// interactions via [`codespan_reporting`].
#[derive(Debug, PartialEq, Eq)]
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) => 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 {
/// Validate that the program makes semantic sense, not just syntactic sense.
///
/// This checks for things like references to variables that don't exist, for
/// example, and generates warnings for things that are inadvisable but not
/// actually a problem.
pub fn validate(&self) -> (Vec<Error>, Vec<Warning>) {
let mut bound_variables = ScopedMap::new();
self.validate_with_bindings(&mut bound_variables)
}
/// Validate that the program makes semantic sense, not just syntactic sense.
///
/// This checks for things like references to variables that don't exist, for
/// example, and generates warnings for things that are inadvisable but not
/// actually a problem.
pub fn validate_with_bindings(
&self,
bound_variables: &mut ScopedMap<String, Location>,
) -> (Vec<Error>, Vec<Warning>) {
let mut errors = vec![];
let mut warnings = vec![];
for stmt in self.items.iter() {
let (mut new_errors, mut new_warnings) = stmt.validate_with_bindings(bound_variables);
errors.append(&mut new_errors);
warnings.append(&mut new_warnings);
}
(errors, warnings)
}
}
impl TopLevel {
/// Validate that the top level item makes semantic sense, not just syntactic
/// sense.
///
/// This checks for things like references to variables that don't exist, for
/// example, and generates warnings for thins that are inadvisable but not
/// actually a problem.
pub fn validate(&self) -> (Vec<Error>, Vec<Warning>) {
let mut bound_variables = ScopedMap::new();
self.validate_with_bindings(&mut bound_variables)
}
/// Validate that the top level item makes semantic sense, not just syntactic
/// sense.
///
/// This checks for things like references to variables that don't exist, for
/// example, and generates warnings for thins that are inadvisable but not
/// actually a problem.
pub fn validate_with_bindings(
&self,
bound_variables: &mut ScopedMap<String, Location>,
) -> (Vec<Error>, Vec<Warning>) {
match self {
TopLevel::Function(name, arguments, body) => {
bound_variables.new_scope();
if let Some(name) = name {
bound_variables.insert(name.name.clone(), name.location.clone());
}
for arg in arguments.iter() {
bound_variables.insert(arg.name.clone(), arg.location.clone());
}
let result = body.validate(&bound_variables);
bound_variables.release_scope();
result
}
TopLevel::Statement(stmt) => stmt.validate(bound_variables),
}
}
}
impl Statement {
/// Validate that the statement makes semantic sense, not just syntactic sense.
///
/// This checks for things like references to variables that don't exist, for
/// example, and generates warnings for things that are inadvisable but not
/// actually a problem. Since statements appear in a broader context, you'll
/// need to provide the set of variables that are bound where this statement
/// occurs. We use a `HashMap` to map these bound locations to the locations
/// where their bound, because these locations are handy when generating errors
/// and warnings.
fn validate(
&self,
bound_variables: &mut ScopedMap<String, Location>,
) -> (Vec<Error>, Vec<Warning>) {
let mut errors = vec![];
let mut warnings = vec![];
match self {
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.name) {
warnings.push(Warning::ShadowedVariable(
original_binding_site.clone(),
loc.clone(),
var.to_string(),
));
} else {
bound_variables.insert(var.to_string(), loc.clone());
}
}
Statement::Print(_, var) if bound_variables.contains_key(&var.name) => {}
Statement::Print(loc, var) => {
errors.push(Error::UnboundVariable(loc.clone(), var.to_string()))
}
}
(errors, warnings)
}
}
impl Expression {
fn validate(&self, variable_map: &ScopedMap<String, Location>) -> (Vec<Error>, Vec<Warning>) {
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::Cast(location, t, expr) => {
let (mut errs, warns) = expr.validate(variable_map);
if PrimitiveType::from_str(t).is_err() {
errs.push(Error::UnknownType(location.clone(), t.clone()))
}
(errs, warns)
}
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)
}
}
}
}
#[test]
fn cast_checks_are_reasonable() {
let good_stmt = TopLevel::parse(0, "x = <u16>4u8;").expect("valid test case");
let (good_errs, good_warns) = good_stmt.validate();
assert!(good_errs.is_empty());
assert!(good_warns.is_empty());
let bad_stmt = TopLevel::parse(0, "x = <apple>4u8;").expect("valid test case");
let (bad_errs, bad_warns) = bad_stmt.validate();
assert!(bad_warns.is_empty());
assert_eq!(bad_errs.len(), 1);
assert!(matches!(bad_errs[0], Error::UnknownType(_, ref x) if x == "apple"));
}