CHECKPOINT: Everything builds again.

This commit is contained in:
2024-06-03 20:36:31 -07:00
parent afff04259c
commit 88d128df9f
39 changed files with 1514 additions and 1129 deletions

View File

@@ -1,7 +1,9 @@
use crate::syntax::ast::{ConstantType, Expression, Program, TopLevel, Value};
use crate::syntax::name::Name;
use crate::syntax::location::Location;
use crate::syntax::name::Name;
use proptest::sample::select;
use proptest::strategy::{NewTree, ValueTree};
use proptest::test_runner::{TestRng, TestRunner};
use proptest::{
prelude::{Arbitrary, BoxedStrategy, Strategy},
strategy::{Just, Union},
@@ -11,248 +13,296 @@ use std::ops::Range;
pub const VALID_VARIABLE_NAMES: &str = r"[a-z][a-zA-Z0-9_]*";
impl ConstantType {
fn get_operators(&self) -> &'static [(&'static str, usize)] {
match self {
ConstantType::Void => &[],
ConstantType::I8 | ConstantType::I16 | ConstantType::I32 | ConstantType::I64 => {
&[("+", 2), ("negate", 1), ("-", 2), ("*", 2), ("/", 2)]
}
ConstantType::U8 | ConstantType::U16 | ConstantType::U32 | ConstantType::U64 => {
&[("+", 2), ("-", 2), ("*", 2), ("/", 2)]
}
#[derive(Debug, Default)]
pub struct ProgramGenerator {}
impl Strategy for ProgramGenerator {
type Tree = ProgramTree;
type Value = Vec<TopLevel>;
fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
unimplemented!()
}
}
pub struct ProgramTree {
_rng: TestRng,
current: Vec<TopLevel>,
}
impl ProgramTree {
fn new(mut rng: TestRng) -> Self {
ProgramTree {
_rng: rng,
current: vec![],
}
}
}
#[derive(Clone)]
pub struct GenerationEnvironment {
allow_inference: bool,
block_length: Range<usize>,
bindings: HashMap<Name, ConstantType>,
return_type: ConstantType,
}
impl ValueTree for ProgramTree {
type Value = Vec<TopLevel>;
impl Default for GenerationEnvironment {
fn default() -> Self {
GenerationEnvironment {
allow_inference: true,
block_length: 2..10,
bindings: HashMap::new(),
return_type: ConstantType::U64,
}
fn current(&self) -> Self::Value {
self.current.clone()
}
fn simplify(&mut self) -> bool {
false
}
fn complicate(&mut self) -> bool {
false
}
}
impl GenerationEnvironment {
pub fn new(allow_inference: bool) -> Self {
GenerationEnvironment {
allow_inference,
..Default::default()
}
}
}
impl Arbitrary for Program {
type Parameters = GenerationEnvironment;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(genenv: Self::Parameters) -> Self::Strategy {
proptest::collection::vec(
ProgramTopLevelInfo::arbitrary(),
genenv.block_length.clone(),
)
.prop_flat_map(move |mut ptlis| {
let mut items = Vec::new();
let mut genenv = genenv.clone();
for psi in ptlis.drain(..) {
if genenv.bindings.is_empty() || psi.should_be_binding {
genenv.return_type = psi.binding_type;
let expr = Expression::arbitrary_with(genenv.clone());
genenv.bindings.insert(psi.name.clone(), psi.binding_type);
items.push(
expr.prop_map(move |expr| {
TopLevel::Expression(Expression::Binding(
Location::manufactured(),
psi.name.clone(),
Box::new(expr),
))
})
.boxed(),
);
} else {
let printers = genenv.bindings.keys().map(|n| {
Just(TopLevel::Expression(Expression::Call(
Location::manufactured(),
Box::new(Expression::Primitive(
Location::manufactured(),
Name::manufactured("print"),
)),
vec![Expression::Reference(n.clone())],
)))
});
items.push(Union::new(printers).boxed());
}
}
items.prop_map(|items| Program { items }).boxed()
})
.boxed()
}
}
impl Arbitrary for Name {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
VALID_VARIABLE_NAMES.prop_map(Name::manufactured).boxed()
}
}
#[derive(Debug)]
struct ProgramTopLevelInfo {
should_be_binding: bool,
name: Name,
binding_type: ConstantType,
}
impl Arbitrary for ProgramTopLevelInfo {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(
Union::new(vec![Just(true), Just(true), Just(false)]),
Name::arbitrary(),
ConstantType::arbitrary(),
)
.prop_map(
|(should_be_binding, name, binding_type)| ProgramTopLevelInfo {
should_be_binding,
name,
binding_type,
},
)
.boxed()
}
}
impl Arbitrary for Expression {
type Parameters = GenerationEnvironment;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(genenv: Self::Parameters) -> Self::Strategy {
// Value(Location, Value). These are the easiest variations to create, because we can always
// create one.
let value_strategy = Value::arbitrary_with(genenv.clone())
.prop_map(|x| Expression::Value(Location::manufactured(), x))
.boxed();
// Reference(Location, String), These are slightly trickier, because we can end up in a situation
// where either no variables are defined, or where none of the defined variables have a type we
// can work with. So what we're going to do is combine this one with the previous one as a "leaf
// strategy" -- our non-recursive items -- if we can, or just set that to be the value strategy
// if we can't actually create an references.
let mut bound_variables_of_type = genenv
.bindings
.iter()
.filter(|(_, v)| genenv.return_type == **v)
.map(|(n, _)| n)
.collect::<Vec<_>>();
let leaf_strategy = if bound_variables_of_type.is_empty() {
value_strategy
} else {
let mut strats = bound_variables_of_type
.drain(..)
.map(|x| { Just(Expression::Reference(x.clone())).boxed()
})
.collect::<Vec<_>>();
strats.push(value_strategy);
Union::new(strats).boxed()
};
// now we generate our recursive types, given our leaf strategy
leaf_strategy
.prop_recursive(3, 10, 2, move |strat| {
(
select(genenv.return_type.get_operators()),
strat.clone(),
strat,
)
.prop_map(|((oper, count), left, right)| {
let mut args = vec![left, right];
while args.len() > count {
args.pop();
}
Expression::Call(
Location::manufactured(),
Box::new(Expression::Primitive(
Location::manufactured(),
Name::manufactured(oper),
)),
args,
)
})
})
.boxed()
}
}
impl Arbitrary for Value {
type Parameters = GenerationEnvironment;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(genenv: Self::Parameters) -> Self::Strategy {
let printed_base_strategy = Union::new([
Just(None::<u8>),
Just(Some(2)),
Just(Some(8)),
Just(Some(10)),
Just(Some(16)),
]);
let value_strategy = u64::arbitrary();
(printed_base_strategy, bool::arbitrary(), value_strategy)
.prop_map(move |(base, declare_type, value)| {
let converted_value = match genenv.return_type {
ConstantType::Void => value,
ConstantType::I8 => value % (i8::MAX as u64),
ConstantType::U8 => value % (u8::MAX as u64),
ConstantType::I16 => value % (i16::MAX as u64),
ConstantType::U16 => value % (u16::MAX as u64),
ConstantType::I32 => value % (i32::MAX as u64),
ConstantType::U32 => value % (u32::MAX as u64),
ConstantType::I64 => value % (i64::MAX as u64),
ConstantType::U64 => value,
};
let ty = if declare_type || !genenv.allow_inference {
Some(genenv.return_type)
} else {
None
};
Value::Number(base, ty, converted_value)
})
.boxed()
}
}
impl Arbitrary for ConstantType {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
Union::new([
Just(ConstantType::I8),
Just(ConstantType::I16),
Just(ConstantType::I32),
Just(ConstantType::I64),
Just(ConstantType::U8),
Just(ConstantType::U16),
Just(ConstantType::U32),
Just(ConstantType::U64),
])
.boxed()
}
}
//impl ConstantType {
// fn get_operators(&self) -> &'static [(&'static str, usize)] {
// match self {
// ConstantType::Void => &[],
// ConstantType::I8 | ConstantType::I16 | ConstantType::I32 | ConstantType::I64 => {
// &[("+", 2), ("negate", 1), ("-", 2), ("*", 2), ("/", 2)]
// }
// ConstantType::U8 | ConstantType::U16 | ConstantType::U32 | ConstantType::U64 => {
// &[("+", 2), ("-", 2), ("*", 2), ("/", 2)]
// }
// }
// }
//}
//
//#[derive(Clone)]
//pub struct GenerationEnvironment {
// allow_inference: bool,
// block_length: Range<usize>,
// bindings: HashMap<Name, ConstantType>,
// return_type: ConstantType,
//}
//
//impl Default for GenerationEnvironment {
// fn default() -> Self {
// GenerationEnvironment {
// allow_inference: true,
// block_length: 2..10,
// bindings: HashMap::new(),
// return_type: ConstantType::U64,
// }
// }
//}
//
//impl GenerationEnvironment {
// pub fn new(allow_inference: bool) -> Self {
// GenerationEnvironment {
// allow_inference,
// ..Default::default()
// }
// }
//}
//
//impl Arbitrary for Program {
// type Parameters = GenerationEnvironment;
// type Strategy = BoxedStrategy<Self>;
//
// fn arbitrary_with(genenv: Self::Parameters) -> Self::Strategy {
// proptest::collection::vec(
// ProgramTopLevelInfo::arbitrary(),
// genenv.block_length.clone(),
// )
// .prop_flat_map(move |mut ptlis| {
// let mut items = Vec::new();
// let mut genenv = genenv.clone();
//
// for psi in ptlis.drain(..) {
// if genenv.bindings.is_empty() || psi.should_be_binding {
// genenv.return_type = psi.binding_type;
// let expr = Expression::arbitrary_with(genenv.clone());
// genenv.bindings.insert(psi.name.clone(), psi.binding_type);
// items.push(
// expr.prop_map(move |expr| {
// TopLevel::Expression(Expression::Binding(
// Location::manufactured(),
// psi.name.clone(),
// Box::new(expr),
// ))
// })
// .boxed(),
// );
// } else {
// let printers = genenv.bindings.keys().map(|n| {
// Just(TopLevel::Expression(Expression::Call(
// Location::manufactured(),
// Box::new(Expression::Primitive(
// Location::manufactured(),
// Name::manufactured("print"),
// )),
// vec![Expression::Reference(n.clone())],
// )))
// });
// items.push(Union::new(printers).boxed());
// }
// }
//
// items
// .prop_map(|items| Program {
// functions: HashMap::new(),
// structures: HashMap::new(),
// body: unimplemented!(),
// })
// .boxed()
// })
// .boxed()
// }
//}
//
//impl Arbitrary for Name {
// type Parameters = ();
// type Strategy = BoxedStrategy<Self>;
//
// fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
// VALID_VARIABLE_NAMES.prop_map(Name::manufactured).boxed()
// }
//}
//
//#[derive(Debug)]
//struct ProgramTopLevelInfo {
// should_be_binding: bool,
// name: Name,
// binding_type: ConstantType,
//}
//
//impl Arbitrary for ProgramTopLevelInfo {
// type Parameters = ();
// type Strategy = BoxedStrategy<Self>;
//
// fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
// (
// Union::new(vec![Just(true), Just(true), Just(false)]),
// Name::arbitrary(),
// ConstantType::arbitrary(),
// )
// .prop_map(
// |(should_be_binding, name, binding_type)| ProgramTopLevelInfo {
// should_be_binding,
// name,
// binding_type,
// },
// )
// .boxed()
// }
//}
//
//impl Arbitrary for Expression {
// type Parameters = GenerationEnvironment;
// type Strategy = BoxedStrategy<Self>;
//
// fn arbitrary_with(genenv: Self::Parameters) -> Self::Strategy {
// // Value(Location, Value). These are the easiest variations to create, because we can always
// // create one.
// let value_strategy = Value::arbitrary_with(genenv.clone())
// .prop_map(|x| Expression::Value(Location::manufactured(), x))
// .boxed();
//
// // Reference(Location, String), These are slightly trickier, because we can end up in a situation
// // where either no variables are defined, or where none of the defined variables have a type we
// // can work with. So what we're going to do is combine this one with the previous one as a "leaf
// // strategy" -- our non-recursive items -- if we can, or just set that to be the value strategy
// // if we can't actually create an references.
// let mut bound_variables_of_type = genenv
// .bindings
// .iter()
// .filter(|(_, v)| genenv.return_type == **v)
// .map(|(n, _)| n)
// .collect::<Vec<_>>();
// let leaf_strategy = if bound_variables_of_type.is_empty() {
// value_strategy
// } else {
// let mut strats = bound_variables_of_type
// .drain(..)
// .map(|x| Just(Expression::Reference(x.clone())).boxed())
// .collect::<Vec<_>>();
// strats.push(value_strategy);
// Union::new(strats).boxed()
// };
//
// // now we generate our recursive types, given our leaf strategy
// leaf_strategy
// .prop_recursive(3, 10, 2, move |strat| {
// (
// select(genenv.return_type.get_operators()),
// strat.clone(),
// strat,
// )
// .prop_map(|((oper, count), left, right)| {
// let mut args = vec![left, right];
// while args.len() > count {
// args.pop();
// }
// Expression::Call(
// Location::manufactured(),
// Box::new(Expression::Primitive(
// Location::manufactured(),
// Name::manufactured(oper),
// )),
// args,
// )
// })
// })
// .boxed()
// }
//}
//
//impl Arbitrary for Value {
// type Parameters = GenerationEnvironment;
// type Strategy = BoxedStrategy<Self>;
//
// fn arbitrary_with(genenv: Self::Parameters) -> Self::Strategy {
// let printed_base_strategy = Union::new([
// Just(None::<u8>),
// Just(Some(2)),
// Just(Some(8)),
// Just(Some(10)),
// Just(Some(16)),
// ]);
// let value_strategy = u64::arbitrary();
//
// (printed_base_strategy, bool::arbitrary(), value_strategy)
// .prop_map(move |(base, declare_type, value)| {
// let converted_value = match genenv.return_type {
// ConstantType::Void => value,
// ConstantType::I8 => value % (i8::MAX as u64),
// ConstantType::U8 => value % (u8::MAX as u64),
// ConstantType::I16 => value % (i16::MAX as u64),
// ConstantType::U16 => value % (u16::MAX as u64),
// ConstantType::I32 => value % (i32::MAX as u64),
// ConstantType::U32 => value % (u32::MAX as u64),
// ConstantType::I64 => value % (i64::MAX as u64),
// ConstantType::U64 => value,
// };
// let ty = if declare_type || !genenv.allow_inference {
// Some(genenv.return_type)
// } else {
// None
// };
// Value::Number(base, ty, converted_value)
// })
// .boxed()
// }
//}
//
//impl Arbitrary for ConstantType {
// type Parameters = ();
// type Strategy = BoxedStrategy<Self>;
//
// fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
// Union::new([
// Just(ConstantType::I8),
// Just(ConstantType::I16),
// Just(ConstantType::I32),
// Just(ConstantType::I64),
// Just(ConstantType::U8),
// Just(ConstantType::U16),
// Just(ConstantType::U32),
// Just(ConstantType::U64),
// ])
// .boxed()
// }
//}
//

View File

@@ -1,6 +1,9 @@
use crate::syntax::name::Name;
use crate::syntax::Location;
pub use crate::syntax::tokens::ConstantType;
use crate::syntax::Location;
use std::collections::HashMap;
use super::location::Located;
/// A structure represented a parsed program.
///
@@ -12,7 +15,57 @@ pub use crate::syntax::tokens::ConstantType;
/// `validate` and it comes back without errors.
#[derive(Clone, Debug, PartialEq)]
pub struct Program {
pub items: Vec<TopLevel>,
pub functions: HashMap<Name, FunctionDefinition>,
pub structures: HashMap<Name, StructureDefinition>,
pub body: Expression,
}
/// A function that we want to compile.
///
/// Later, when we've done a lot of analysis, the `Option<Type>`s
/// will turn into concrete types. For now, though, we stick with
/// the surface syntax and leave them as optional. The name of the
/// function is intentionally duplicated, to make our life easier.
#[derive(Clone, Debug, PartialEq)]
pub struct FunctionDefinition {
pub name: Name,
pub arguments: Vec<(Name, Option<Type>)>,
pub return_type: Option<Type>,
pub body: Expression,
}
impl FunctionDefinition {
pub fn new(
name: Name,
arguments: Vec<(Name, Option<Type>)>,
return_type: Option<Type>,
body: Expression,
) -> Self {
FunctionDefinition {
name,
arguments,
return_type,
body,
}
}
}
/// A structure type that we might want to reference in the future.
#[derive(Clone, Debug, PartialEq)]
pub struct StructureDefinition {
pub name: Name,
pub location: Location,
pub fields: Vec<(Name, Option<Type>)>,
}
impl StructureDefinition {
pub fn new(location: Location, name: Name, fields: Vec<(Name, Option<Type>)>) -> Self {
StructureDefinition {
name,
location,
fields,
}
}
}
/// A thing that can sit at the top level of a file.
@@ -26,6 +79,15 @@ pub enum TopLevel {
Structure(Location, Name, Vec<(Name, Type)>),
}
impl Located for TopLevel {
fn location(&self) -> &Location {
match self {
TopLevel::Expression(exp) => exp.location(),
TopLevel::Structure(loc, _, _) => loc,
}
}
}
/// An expression in the underlying syntax.
///
/// Like statements, these expressions are guaranteed to have been
@@ -114,9 +176,9 @@ impl PartialEq for Expression {
}
}
impl Expression {
impl Located for Expression {
/// Get the location of the expression in the source file (if there is one).
pub fn location(&self) -> &Location {
fn location(&self) -> &Location {
match self {
Expression::Value(loc, _) => loc,
Expression::Constructor(loc, _, _) => loc,

View File

@@ -1,7 +1,6 @@
use crate::eval::{EvalError, PrimitiveType, Value};
use crate::syntax::{ConstantType, Expression, Program, TopLevel};
use crate::syntax::{ConstantType, Expression, Name, Program};
use crate::util::scoped_map::ScopedMap;
use internment::ArcIntern;
use std::collections::HashMap;
use std::str::FromStr;
@@ -20,19 +19,8 @@ impl Program {
pub fn eval(&self) -> Result<(Value<Expression>, String), EvalError<Expression>> {
let mut env = ScopedMap::new();
let mut stdout = String::new();
let mut last_result = Value::Void;
for stmt in self.items.iter() {
match stmt {
TopLevel::Expression(expr) => last_result = expr.eval(&mut stdout, &mut env)?,
TopLevel::Structure(_, _, _) => {
last_result = Value::Void;
}
}
}
Ok((last_result, stdout))
let result = self.body.eval(&mut stdout, &mut env)?;
Ok((result, stdout))
}
}
@@ -40,7 +28,7 @@ impl Expression {
fn eval(
&self,
stdout: &mut String,
env: &mut ScopedMap<ArcIntern<String>, Value<Expression>>,
env: &mut ScopedMap<Name, Value<Expression>>,
) -> Result<Value<Expression>, EvalError<Expression>> {
match self {
Expression::Value(_, v) => match v {
@@ -63,35 +51,37 @@ impl Expression {
let mut map = HashMap::with_capacity(fields.len());
for (k, v) in fields.iter() {
map.insert(k.clone().intern(), v.eval(stdout, env)?);
map.insert(k.clone(), v.eval(stdout, env)?);
}
Ok(Value::Structure(Some(on.clone().intern()), map))
Ok(Value::Structure(Some(on.clone()), map))
}
Expression::Reference(n) => env
.get(n.current_interned())
.ok_or_else(|| EvalError::LookupFailed(n.location().clone(), n.current_name().to_string()))
.get(n)
.ok_or_else(|| {
EvalError::LookupFailed(n.location().clone(), n.current_name().to_string())
})
.cloned(),
Expression::FieldRef(loc, expr, field) => {
let struck = expr.eval(stdout, env)?;
if let Value::Structure(on, mut fields) = struck {
if let Some(value) = fields.remove(&field.clone().intern()) {
if let Some(value) = fields.remove(&field.clone()) {
Ok(value)
} else {
Err(EvalError::BadFieldForStructure(
loc.clone(),
on,
field.clone().intern(),
field.clone(),
))
}
} else {
Err(EvalError::NoFieldForValue(
loc.clone(),
struck,
field.clone().intern(),
field.clone(),
))
}
}
@@ -130,8 +120,7 @@ impl Expression {
Value::Primitive(name) if name == "print" => {
if let [Expression::Reference(name)] = &args[..] {
let value = Expression::Reference(name.clone())
.eval(stdout, env)?;
let value = Expression::Reference(name.clone()).eval(stdout, env)?;
let value = match value {
Value::Number(x) => Value::U64(x),
x => x,
@@ -172,24 +161,20 @@ impl Expression {
Expression::Binding(_, name, value) => {
let actual_value = value.eval(stdout, env)?;
env.insert(name.clone().intern(), actual_value.clone());
env.insert(name.clone(), actual_value.clone());
Ok(actual_value)
}
Expression::Function(_, name, arg_names, _, body) => {
let result = Value::Closure(
name.as_ref().map(|n| n.current_interned().clone()),
name.clone(),
env.clone(),
arg_names
.iter()
.cloned()
.map(|(x, _)| x.current_interned().clone())
.collect(),
arg_names.iter().cloned().map(|(x, _)| x.clone()).collect(),
*body.clone(),
);
if let Some(name) = name {
env.insert(name.clone().intern(), result.clone());
env.insert(name.clone(), result.clone());
}
Ok(result)
@@ -200,14 +185,17 @@ impl Expression {
#[test]
fn two_plus_three() {
let input = Program::parse(0, "x = 2 + 3; print x;").expect("parse works");
let (_, output) = input.eval().expect("runs successfully");
let input = crate::syntax::parse_string(0, "x = 2 + 3; print x;").expect("parse works");
let program = Program::validate(input).into_result().unwrap();
let (_, output) = program.eval().expect("runs successfully");
assert_eq!("x = 5u64\n", &output);
}
#[test]
fn lotsa_math() {
let input = Program::parse(0, "x = 2 + 3 * 10 / 5 - 1; print x;").expect("parse works");
let (_, output) = input.eval().expect("runs successfully");
let input =
crate::syntax::parse_string(0, "x = 2 + 3 * 10 / 5 - 1; print x;").expect("parse works");
let program = Program::validate(input).into_result().unwrap();
let (_, output) = program.eval().expect("runs successfully");
assert_eq!("x = 7u64\n", &output);
}

View File

@@ -0,0 +1,161 @@
use crate::syntax::{Expression, Name};
use std::collections::HashSet;
impl Expression {
/// Find the set of free variables used within this expression.
///
/// Obviously, if this expression contains a function definition, argument
/// variables in the body will not be reported as free.
pub fn free_variables(&self) -> HashSet<Name> {
match self {
Expression::Value(_, _) => HashSet::new(),
Expression::Constructor(_, _, args) => {
args.iter().fold(HashSet::new(), |mut existing, (_, expr)| {
existing.extend(expr.free_variables());
existing
})
}
Expression::Reference(n) => HashSet::from([n.clone()]),
Expression::FieldRef(_, expr, _) => expr.free_variables(),
Expression::Cast(_, _, expr) => expr.free_variables(),
Expression::Primitive(_, _) => HashSet::new(),
Expression::Call(_, f, args) => {
args.iter().fold(f.free_variables(), |mut existing, expr| {
existing.extend(expr.free_variables());
existing
})
}
Expression::Block(_, exprs) => {
let mut free_vars = HashSet::new();
let mut bound_vars = HashSet::new();
for expr in exprs.iter() {
for var in expr.free_variables().into_iter() {
if !bound_vars.contains(&var) {
free_vars.insert(var);
}
}
bound_vars.extend(expr.new_bindings());
}
free_vars
}
Expression::Binding(_, _, expr) => expr.free_variables(),
Expression::Function(_, name, args, _, body) => {
let mut candidates = body.free_variables();
if let Some(name) = name {
candidates.remove(name);
}
for (name, _) in args.iter() {
candidates.remove(name);
}
candidates
}
}
}
/// Find the set of new bindings in the provided expression.
///
/// New bindings are those that introduce a variable that can be
/// referenced in subsequent statements / expressions within a
/// parent construct. This eventually means something in the next
/// block, but can involve some odd effects in the language.
pub fn new_bindings(&self) -> HashSet<Name> {
match self {
Expression::Value(_, _) => HashSet::new(),
Expression::Constructor(_, _, args) => {
args.iter().fold(HashSet::new(), |mut existing, (_, expr)| {
existing.extend(expr.new_bindings());
existing
})
}
Expression::Reference(_) => HashSet::new(),
Expression::FieldRef(_, expr, _) => expr.new_bindings(),
Expression::Cast(_, _, expr) => expr.new_bindings(),
Expression::Primitive(_, _) => HashSet::new(),
Expression::Call(_, f, args) => {
args.iter().fold(f.new_bindings(), |mut existing, expr| {
existing.extend(expr.new_bindings());
existing
})
}
Expression::Block(_, _) => HashSet::new(),
Expression::Binding(_, name, expr) => {
let mut others = expr.new_bindings();
others.insert(name.clone());
others
}
Expression::Function(_, Some(name), _, _, _) => HashSet::from([name.clone()]),
Expression::Function(_, None, _, _, _) => HashSet::new(),
}
}
}
#[test]
fn basic_frees_works() {
let test = Expression::parse(0, "1u64").unwrap();
assert_eq!(0, test.free_variables().len());
let test = Expression::parse(0, "1u64 + 2").unwrap();
assert_eq!(0, test.free_variables().len());
let test = Expression::parse(0, "x").unwrap();
assert_eq!(1, test.free_variables().len());
assert!(test.free_variables().contains(&Name::manufactured("x")));
let test = Expression::parse(0, "1 + x").unwrap();
assert_eq!(1, test.free_variables().len());
assert!(test.free_variables().contains(&Name::manufactured("x")));
let test = Expression::parse(0, "Structure{ field1: x; field2: y; }").unwrap();
assert_eq!(2, test.free_variables().len());
assert!(test.free_variables().contains(&Name::manufactured("x")));
assert!(test.free_variables().contains(&Name::manufactured("y")));
let test = Expression::parse(0, "{ print x; print y }").unwrap();
assert_eq!(2, test.free_variables().len());
assert!(test.free_variables().contains(&Name::manufactured("x")));
assert!(test.free_variables().contains(&Name::manufactured("y")));
}
#[test]
fn test_around_function() {
let nada = Expression::parse(0, "function(x) x").unwrap();
assert_eq!(0, nada.free_variables().len());
let lift = Expression::parse(0, "function(x) x + y").unwrap();
assert_eq!(1, lift.free_variables().len());
assert!(lift.free_variables().contains(&Name::manufactured("y")));
let nest = Expression::parse(0, "function(y) function(x) x + y").unwrap();
assert_eq!(0, nest.free_variables().len());
let multi = Expression::parse(0, "function(x, y) x + y + z").unwrap();
assert_eq!(1, multi.free_variables().len());
assert!(multi.free_variables().contains(&Name::manufactured("z")));
}
#[test]
fn test_is_set() {
let multi = Expression::parse(0, "function(x, y) x + y + z + z").unwrap();
assert_eq!(1, multi.free_variables().len());
assert!(multi.free_variables().contains(&Name::manufactured("z")));
}
#[test]
fn bindings_remove() {
let x_bind = Expression::parse(0, "{ x = 4; print x }").unwrap();
assert_eq!(0, x_bind.free_variables().len());
let inner = Expression::parse(0, "{ { x = 4; print x }; print y }").unwrap();
assert_eq!(1, inner.free_variables().len());
assert!(inner.free_variables().contains(&Name::manufactured("y")));
let inner = Expression::parse(0, "{ { x = 4; print x }; print x }").unwrap();
assert_eq!(1, inner.free_variables().len());
assert!(inner.free_variables().contains(&Name::manufactured("x")));
let double = Expression::parse(0, "{ x = y = 1; x + y }").unwrap();
assert_eq!(0, double.free_variables().len());
}

View File

@@ -12,6 +12,10 @@ pub struct Location {
location: Range<usize>,
}
pub trait Located {
fn location(&self) -> &Location;
}
impl Location {
/// Generate a new `Location` from a file index and an offset from the
/// start of the file.
@@ -116,4 +120,36 @@ impl Location {
})
}
}
/// Infer a location set by combining all of the information we have
/// in the list of located things.
///
/// This will attempt to throw away manufactured locations whenever
/// possible, but if there's multiple files mixed in it will likely
/// fail. In all failure cases, including when the set of items is
/// empty, will return a manufactured location to use.
pub fn infer_from<T: Located>(items: &[T]) -> Location {
let mut current = None;
for item in items {
let location = item.location();
if (location.file_idx != 0)
|| (location.location.start != 0)
|| (location.location.end != 0)
{
current = match current {
None => Some(Some(location.clone())),
Some(None) => Some(None), // we ran into an error somewhere
Some(Some(actual)) => Some(actual.merge(location)),
};
}
}
match current {
None => Location::manufactured(),
Some(None) => Location::manufactured(),
Some(Some(x)) => x,
}
}
}

View File

@@ -2,19 +2,20 @@ use crate::syntax::Location;
use internment::ArcIntern;
use std::fmt;
use std::hash::Hash;
use std::sync::atomic::{AtomicU64, Ordering};
/// The name of a thing in the source language.
///
/// In many ways, you can treat this like a string, but it's a very tricky
/// string in a couple of ways:
///
///
/// First, it's a string associated with a particular location in the source
/// file, and you can find out what that source location is relatively easily.
///
///
/// Second, it's a name that retains something of its identity across renaming,
/// so that you can keep track of what a variables original name was, as well as
/// what it's new name is if it's been renamed.
///
///
/// Finally, when it comes to equality tests, comparisons, and hashing, `Name`
/// uses *only* the new name, if the variable has been renamed, or the original
/// name, if it has not been renamed. It never uses the location. This allows
@@ -30,7 +31,7 @@ pub struct Name {
impl Name {
/// Create a new name at the given location.
///
///
/// This creates an "original" name, which has not been renamed, at the
/// given location.
pub fn new<S: ToString>(n: S, location: Location) -> Name {
@@ -42,7 +43,7 @@ impl Name {
}
/// Create a new name with no location information.
///
///
/// This creates an "original" name, which has not been renamed, at the
/// given location. You should always prefer to use [`Location::new`] if
/// there is any possible way to get it, because that will be more
@@ -55,8 +56,35 @@ impl Name {
}
}
/// Create a unique name based on the original name provided.
///
/// This will automatically append a number and wrap that in
/// <>, which is hoped to be unique.
pub fn gensym<S: ToString>(n: S) -> Name {
static GENSYM_COUNTER: AtomicU64 = AtomicU64::new(0);
let new_name = format!(
"<{}{}>",
n.to_string(),
GENSYM_COUNTER.fetch_add(1, Ordering::SeqCst)
);
Name {
name: ArcIntern::new(new_name),
rename: None,
location: Location::manufactured(),
}
}
/// As with gensym, but tie the name to the given location
pub fn located_gensym<S: ToString>(location: Location, n: S) -> Name {
Name {
location,
..Name::gensym(n)
}
}
/// Returns a reference to the original name of the variable.
///
///
/// Regardless of whether or not the function has been renamed, this will
/// return whatever name this variable started with.
pub fn original_name(&self) -> &str {
@@ -64,11 +92,14 @@ impl Name {
}
/// Returns a reference to the current name of the variable.
///
///
/// If the variable has been renamed, it will return that, otherwise we'll
/// return the current name.
pub fn current_name(&self) -> &str {
self.rename.as_ref().map(|x| x.as_str()).unwrap_or_else(|| self.name.as_str())
self.rename
.as_ref()
.map(|x| x.as_str())
.unwrap_or_else(|| self.name.as_str())
}
/// Returns the current name of the variable as an interned string.
@@ -110,4 +141,3 @@ impl fmt::Display for Name {
self.current_name().fmt(f)
}
}

View File

@@ -9,7 +9,7 @@
//! eventually want to leave lalrpop behind.)
//!
use crate::syntax::{Location, ParserError};
use crate::syntax::ast::{Program,TopLevel,Expression,Value,Type};
use crate::syntax::ast::{TopLevel,Expression,Value,Type};
use crate::syntax::name::Name;
use crate::syntax::tokens::{ConstantType, Token};
use internment::ArcIntern;
@@ -63,20 +63,12 @@ extern {
}
}
pub Program: Program = {
// a program is just a set of statements
<items:ProgramTopLevel> => Program {
items
},
=> Program { items: vec![] },
}
ProgramTopLevel: Vec<TopLevel> = {
<mut rest: ProgramTopLevel> <t:TopLevel> => {
pub Program: Vec<TopLevel> = {
<mut rest: Program> <t:TopLevel> => {
rest.push(t);
rest
},
<t:TopLevel> => vec![t],
=> vec![],
}
pub TopLevel: TopLevel = {

View File

@@ -6,14 +6,78 @@ impl Program {
pub fn pretty<'a>(&self, allocator: &'a Allocator<'a>) -> DocBuilder<'a, Allocator<'a>> {
let mut result = allocator.nil();
for tl in self.items.iter() {
for definition in self.structures.values() {
result = result
.append(tl.pretty(allocator))
.append(allocator.text(";"))
.append(allocator.text("struct"))
.append(allocator.space())
.append(allocator.text(definition.name.original_name().to_string()))
.append(allocator.space())
.append(allocator.text("{"))
.append(allocator.hardline())
.append(
allocator
.concat(definition.fields.iter().map(|(name, ty)| {
let mut type_bit = allocator.nil();
if let Some(ty) = ty {
type_bit = allocator
.text(":")
.append(allocator.space())
.append(ty.pretty(allocator));
}
allocator
.text(name.original_name().to_string())
.append(type_bit)
.append(allocator.text(";"))
.append(allocator.hardline())
}))
.nest(2),
)
.append(allocator.text("}"))
.append(allocator.hardline());
}
result
for definition in self.functions.values() {
let mut return_type_bit = allocator.nil();
if let Some(rettype) = definition.return_type.as_ref() {
return_type_bit = allocator
.text("->")
.append(allocator.space())
.append(rettype.pretty(allocator));
}
result = result
.append(allocator.text("function"))
.append(allocator.space())
.append(allocator.text(definition.name.original_name().to_string()))
.append(allocator.text("("))
.append(allocator.intersperse(
definition.arguments.iter().map(|(x, t)| {
let mut type_bit = allocator.nil();
if let Some(ty) = t {
type_bit = allocator
.text(":")
.append(allocator.space())
.append(ty.pretty(allocator));
}
allocator
.text(x.original_name().to_string())
.append(type_bit)
}),
allocator.text(","),
))
.append(allocator.text(")"))
.append(return_type_bit)
.append(allocator.softline())
.append(definition.body.pretty(allocator))
.append(allocator.hardline());
}
result.append(self.body.pretty(allocator))
}
}

View File

@@ -0,0 +1,51 @@
use super::{Expression, Name};
use std::collections::HashMap;
impl Expression {
/// Replace all references in the given map to their alternative expression values
pub fn replace_references(&mut self, map: &mut HashMap<Name, Expression>) {
match self {
Expression::Value(_, _) => {}
Expression::Constructor(_, _, items) => {
for (_, item) in items.iter_mut() {
item.replace_references(map);
}
}
Expression::Reference(name) => match map.get(name) {
None => {}
Some(x) => *self = x.clone(),
},
Expression::FieldRef(_, subexp, _) => {
subexp.replace_references(map);
}
Expression::Cast(_, _, subexp) => {
subexp.replace_references(map);
}
Expression::Primitive(_, _) => {}
Expression::Call(_, fun, args) => {
fun.replace_references(map);
for arg in args.iter_mut() {
arg.replace_references(map);
}
}
Expression::Block(_, exprs) => {
for expr in exprs.iter_mut() {
expr.replace_references(map);
}
}
Expression::Binding(_, n, expr) => {
expr.replace_references(map);
map.remove(n);
}
Expression::Function(_, mname, args, _, body) => {
if let Some(name) = mname {
map.remove(name);
}
for (arg_name, _) in args.iter() {
map.remove(arg_name);
}
body.replace_references(map);
}
}
}
}

View File

@@ -1,11 +1,13 @@
use crate::{
eval::PrimitiveType,
syntax::{Expression, Location, Program, TopLevel},
util::scoped_map::ScopedMap,
};
use crate::eval::PrimitiveType;
use crate::syntax::{Expression, Location, Program, StructureDefinition, TopLevel};
use crate::util::scoped_map::ScopedMap;
use crate::util::warning_result::WarningResult;
use codespan_reporting::diagnostic::Diagnostic;
use std::collections::HashMap;
use std::str::FromStr;
use super::{FunctionDefinition, Name, Type};
/// An error we found while validating the input program.
///
/// These errors indicate that we should stop trying to compile
@@ -66,9 +68,9 @@ impl Program {
/// 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>) {
pub fn validate(raw_syntax: Vec<TopLevel>) -> WarningResult<Program, Warning, Error> {
let mut bound_variables = ScopedMap::new();
self.validate_with_bindings(&mut bound_variables)
Self::validate_with_bindings(raw_syntax, &mut bound_variables)
}
/// Validate that the program makes semantic sense, not just syntactic sense.
@@ -77,118 +79,137 @@ impl Program {
/// example, and generates warnings for things that are inadvisable but not
/// actually a problem.
pub fn validate_with_bindings(
&self,
raw_syntax: Vec<TopLevel>,
bound_variables: &mut ScopedMap<String, Location>,
) -> (Vec<Error>, Vec<Warning>) {
let mut errors = vec![];
let mut warnings = vec![];
) -> WarningResult<Program, Warning, Error> {
let mut functions = HashMap::new();
let mut structures = HashMap::new();
let mut result = WarningResult::ok(vec![]);
let location = Location::infer_from(&raw_syntax);
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);
for stmt in raw_syntax.into_iter() {
match stmt {
TopLevel::Expression(expr) => {
let expr_result =
expr.validate(bound_variables, &mut structures, &mut functions);
result = result.merge_with(expr_result, |mut previous, current| {
previous.push(current);
Ok(previous)
});
}
TopLevel::Structure(loc, name, fields) => {
let definition = StructureDefinition::new(
loc,
name.clone(),
fields.into_iter().map(|(n, t)| (n, Some(t))).collect(),
);
structures.insert(name, definition);
}
}
}
(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::Expression(expr) => expr.validate(bound_variables),
TopLevel::Structure(_, _, _) => (vec![], vec![]),
}
result.map(move |exprs| Program {
functions,
structures,
body: Expression::Block(location, exprs),
})
}
}
impl Expression {
fn validate(
&self,
self,
variable_map: &mut ScopedMap<String, Location>,
) -> (Vec<Error>, Vec<Warning>) {
structure_map: &mut HashMap<Name, StructureDefinition>,
function_map: &mut HashMap<Name, FunctionDefinition>,
) -> WarningResult<Expression, Warning, Error> {
match self {
Expression::Value(_, _) => (vec![], vec![]),
Expression::Constructor(_, _, fields) => {
let mut errors = vec![];
let mut warnings = vec![];
Expression::Value(_, _) => WarningResult::ok(self),
for (_, expr) in fields.iter() {
let (mut e, mut w) = expr.validate(variable_map);
errors.append(&mut e);
warnings.append(&mut w);
Expression::Constructor(location, name, fields) => {
let mut result = WarningResult::ok(vec![]);
for (name, expr) in fields.into_iter() {
let expr_result = expr.validate(variable_map, structure_map, function_map);
result = result.merge_with(expr_result, move |mut fields, new_expr| {
fields.push((name, new_expr));
Ok(fields)
});
}
(errors, warnings)
result.map(move |fields| Expression::Constructor(location, name, fields))
}
Expression::Reference(var) if variable_map.contains_key(&var.original_name().to_string()) => (vec![], vec![]),
Expression::Reference(var) => (
vec![Error::UnboundVariable(var.location().clone(), var.original_name().to_string())],
vec![],
),
Expression::FieldRef(_, exp, _) => exp.validate(variable_map),
Expression::Reference(ref var)
if variable_map.contains_key(&var.original_name().to_string()) =>
{
WarningResult::ok(self)
}
Expression::Reference(var) => WarningResult::err(Error::UnboundVariable(
var.location().clone(),
var.original_name().to_string(),
)),
Expression::FieldRef(location, exp, field) => exp
.validate(variable_map, structure_map, function_map)
.map(|x| Expression::FieldRef(location, Box::new(x), field)),
Expression::Cast(location, t, expr) => {
let (mut errs, warns) = expr.validate(variable_map);
let mut expr_result = expr.validate(variable_map, structure_map, function_map);
if PrimitiveType::from_str(t).is_err() {
errs.push(Error::UnknownType(location.clone(), t.clone()))
if PrimitiveType::from_str(&t).is_err() {
expr_result.add_error(Error::UnknownType(location.clone(), t.clone()));
}
(errs, warns)
expr_result.map(|e| Expression::Cast(location, t, Box::new(e)))
}
Expression::Primitive(_, _) => (vec![], vec![]),
Expression::Call(_, func, args) => {
let (mut errors, mut warnings) = func.validate(variable_map);
for arg in args.iter() {
let (mut e, mut w) = arg.validate(variable_map);
errors.append(&mut e);
warnings.append(&mut w);
// FIXME: Check for valid primitives here!!
Expression::Primitive(_, _) => WarningResult::ok(self),
Expression::Call(loc, func, args) => {
let mut result = func
.validate(variable_map, structure_map, function_map)
.map(|x| (x, vec![]));
for arg in args.into_iter() {
let expr_result = arg.validate(variable_map, structure_map, function_map);
result =
result.merge_with(expr_result, |(func, mut previous_args), new_arg| {
previous_args.push(new_arg);
Ok((func, previous_args))
});
}
(errors, warnings)
result.map(|(func, args)| Expression::Call(loc, Box::new(func), args))
}
Expression::Block(_, stmts) => {
let mut errors = vec![];
let mut warnings = vec![];
for stmt in stmts.iter() {
let (mut local_errors, mut local_warnings) = stmt.validate(variable_map);
Expression::Block(loc, stmts) => {
let mut result = WarningResult::ok(vec![]);
errors.append(&mut local_errors);
warnings.append(&mut local_warnings);
for stmt in stmts.into_iter() {
let stmt_result = stmt.validate(variable_map, structure_map, function_map);
result = result.merge_with(stmt_result, |mut stmts, stmt| {
stmts.push(stmt);
Ok(stmts)
});
}
(errors, warnings)
result.map(|stmts| Expression::Block(loc, stmts))
}
Expression::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 (errors, mut warnings) = val.validate(variable_map);
let mut result = val.validate(variable_map, structure_map, function_map);
if let Some(original_binding_site) = variable_map.get(&var.original_name().to_string()) {
warnings.push(Warning::ShadowedVariable(
if let Some(original_binding_site) =
variable_map.get(&var.original_name().to_string())
{
result.add_warning(Warning::ShadowedVariable(
original_binding_site.clone(),
loc.clone(),
var.to_string(),
@@ -197,19 +218,118 @@ impl Expression {
variable_map.insert(var.to_string(), loc.clone());
}
(errors, warnings)
result.map(|val| Expression::Binding(loc, var, Box::new(val)))
}
Expression::Function(_, name, arguments, _, body) => {
if let Some(name) = name {
Expression::Function(loc, name, mut arguments, return_type, body) => {
let mut result = WarningResult::ok(());
// first we should check for shadowing
for new_name in name.iter().chain(arguments.iter().map(|x| &x.0)) {
if let Some(original_site) = variable_map.get(new_name.original_name()) {
result.add_warning(Warning::ShadowedVariable(
original_site.clone(),
loc.clone(),
new_name.original_name().to_string(),
));
}
}
// the function name is now available in our current scope, if the function was given one
if let Some(name) = &name {
variable_map.insert(name.original_name().to_string(), name.location().clone());
}
// the arguments are available in a new scope, which we will use to validate the function
// body
variable_map.new_scope();
for (arg, _) in arguments.iter() {
variable_map.insert(arg.original_name().to_string(), arg.location().clone());
}
let result = body.validate(variable_map);
let body_result = body.validate(variable_map, structure_map, function_map);
variable_map.release_scope();
result
body_result.merge_with(result, move |mut body, _| {
// figure out what, if anything, needs to be in the closure for this function.
let mut free_variables = body.free_variables();
for (n, _) in arguments.iter() {
free_variables.remove(n);
}
// generate a new name for the closure type we're about to create
let closure_type_name = Name::located_gensym(
loc.clone(),
name.as_ref().map(Name::original_name).unwrap_or("closure_"),
);
// ... and then create a structure type that has all of the free variables
// in it
let closure_type = StructureDefinition::new(
loc.clone(),
closure_type_name.clone(),
free_variables.iter().map(|x| (x.clone(), None)).collect(),
);
// this will become the first argument of the function, so name it and add
// it to the argument list.
let closure_arg = Name::gensym("__closure_arg");
arguments.insert(
0,
(
closure_arg.clone(),
Some(Type::Named(closure_type_name.clone())),
),
);
// Now make a map from the old free variable names to references into
// our closure argument
let rebinds = free_variables
.into_iter()
.map(|n| {
(
n.clone(),
Expression::FieldRef(
n.location().clone(),
Box::new(Expression::Reference(closure_arg.clone())),
n,
),
)
})
.collect::<Vec<(Name, Expression)>>();
let mut rebind_map = rebinds.iter().cloned().collect();
// and replace all the references in the function with this map
body.replace_references(&mut rebind_map);
// OK! This function definitely needs a name; if the user didn't give
// it one, we'll do so.
let function_name =
name.unwrap_or_else(|| Name::located_gensym(loc.clone(), "function"));
// And finally, we can make the function definition and insert it into our global
// list along with the new closure type.
let function = FunctionDefinition::new(
function_name.clone(),
arguments.clone(),
return_type.clone(),
body,
);
structure_map.insert(closure_type_name.clone(), closure_type);
function_map.insert(function_name.clone(), function);
// And the result of this function is a call to a primitive that generates
// the closure value in some sort of reasonable way.
Ok(Expression::Call(
Location::manufactured(),
Box::new(Expression::Primitive(
Location::manufactured(),
Name::new("<closure>", Location::manufactured()),
)),
vec![
Expression::Reference(function_name),
Expression::Constructor(
Location::manufactured(),
closure_type_name,
rebinds,
),
],
))
})
}
}
}
@@ -217,16 +337,19 @@ impl Expression {
#[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();
let mut variable_map = ScopedMap::new();
let mut structure_map = HashMap::new();
let mut function_map = HashMap::new();
assert!(good_errs.is_empty());
assert!(good_warns.is_empty());
let good_stmt = Expression::parse(0, "x = <u16>4u8;").expect("valid test case");
let result_good = good_stmt.validate(&mut variable_map, &mut structure_map, &mut function_map);
let bad_stmt = TopLevel::parse(0, "x = <apple>4u8;").expect("valid test case");
let (bad_errs, bad_warns) = bad_stmt.validate();
assert!(result_good.is_ok());
assert!(result_good.warnings().is_empty());
assert!(bad_warns.is_empty());
assert_eq!(bad_errs.len(), 1);
assert!(matches!(bad_errs[0], Error::UnknownType(_, ref x) if x == "apple"));
let bad_stmt = Expression::parse(0, "x = <apple>4u8;").expect("valid test case");
let result_err = bad_stmt.validate(&mut variable_map, &mut structure_map, &mut function_map);
assert!(result_err.is_err());
assert!(result_err.warnings().is_empty());
}