CHECKPOINT: Everything builds again.
This commit is contained in:
@@ -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()
|
||||
// }
|
||||
//}
|
||||
//
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
161
src/syntax/free_variables.rs
Normal file
161
src/syntax/free_variables.rs
Normal 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());
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
51
src/syntax/replace_references.rs
Normal file
51
src/syntax/replace_references.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user