231 lines
8.6 KiB
Rust
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"));
|
|
}
|