Keep pushing forward on type inference.
This commit is contained in:
@@ -10,7 +10,7 @@ use std::collections::HashMap;
|
||||
const VALID_VARIABLE_NAMES: &str = r"[a-z][a-zA-Z0-9_]*";
|
||||
const OPERATORS: &[(&str, usize)] = &[("+", 2), ("-", 1), ("-", 2), ("*", 2), ("/", 2)];
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
struct Name(String);
|
||||
|
||||
impl Arbitrary for Name {
|
||||
@@ -22,153 +22,133 @@ impl Arbitrary for Name {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ProgramStatementInfo {
|
||||
should_be_binding: bool,
|
||||
name: Name,
|
||||
binding_type: ConstantType,
|
||||
}
|
||||
|
||||
impl Arbitrary for ProgramStatementInfo {
|
||||
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)| ProgramStatementInfo {
|
||||
should_be_binding,
|
||||
name,
|
||||
binding_type,
|
||||
},
|
||||
)
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl Arbitrary for Program {
|
||||
type Parameters = ();
|
||||
type Strategy = BoxedStrategy<Self>;
|
||||
|
||||
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
|
||||
let optionals = Vec::<(Name, ConstantType, u8)>::arbitrary();
|
||||
|
||||
optionals
|
||||
.prop_flat_map(|mut possible_names| {
|
||||
proptest::collection::vec(ProgramStatementInfo::arbitrary(), 1..100)
|
||||
.prop_flat_map(|mut items| {
|
||||
let mut statements = Vec::new();
|
||||
let mut defined_variables: HashMap<String, ConstantType> = HashMap::new();
|
||||
let mut defined_variables = HashMap::new();
|
||||
|
||||
for (possible_name, possible_type, dice_roll) in possible_names.drain(..) {
|
||||
if !defined_variables.is_empty() && dice_roll < 100 {
|
||||
for psi in items.drain(..) {
|
||||
if defined_variables.is_empty() || psi.should_be_binding {
|
||||
let expr = Expression::arbitrary_with(ExpressionGeneratorSettings {
|
||||
bound_variables: defined_variables.clone(),
|
||||
output_type: Some(psi.binding_type),
|
||||
});
|
||||
|
||||
defined_variables.insert(psi.name.0.clone(), psi.binding_type);
|
||||
statements.push(
|
||||
Union::new(defined_variables.keys().map(|name| {
|
||||
Just(Statement::Print(Location::manufactured(), name.to_string()))
|
||||
}))
|
||||
expr.prop_map(move |expr| {
|
||||
Statement::Binding(
|
||||
Location::manufactured(),
|
||||
psi.name.0.clone(),
|
||||
expr,
|
||||
)
|
||||
})
|
||||
.boxed(),
|
||||
);
|
||||
} else {
|
||||
let closures_name = possible_name.0.clone();
|
||||
let retval = Expression::arbitrary_with((
|
||||
Some(defined_variables.clone()),
|
||||
Some(possible_type),
|
||||
))
|
||||
.prop_map(move |exp| {
|
||||
Statement::Binding(Location::manufactured(), closures_name.clone(), exp)
|
||||
})
|
||||
.boxed();
|
||||
|
||||
defined_variables.insert(possible_name.0, possible_type);
|
||||
statements.push(retval);
|
||||
let printers = defined_variables
|
||||
.keys()
|
||||
.map(|n| Just(Statement::Print(Location::manufactured(), n.clone())));
|
||||
statements.push(Union::new(printers).boxed());
|
||||
}
|
||||
}
|
||||
|
||||
statements
|
||||
.prop_map(|statements| Program { statements })
|
||||
.boxed()
|
||||
})
|
||||
.prop_map(|statements| Program { statements })
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl Arbitrary for Statement {
|
||||
type Parameters = Option<HashMap<String, ConstantType>>;
|
||||
type Strategy = BoxedStrategy<Self>;
|
||||
|
||||
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
|
||||
let duplicated_args = args.clone();
|
||||
let defined_variables = args.unwrap_or_default();
|
||||
|
||||
let binding_strategy = (
|
||||
VALID_VARIABLE_NAMES,
|
||||
Expression::arbitrary_with((duplicated_args, None)),
|
||||
)
|
||||
.prop_map(|(name, exp)| Statement::Binding(Location::manufactured(), name, exp))
|
||||
.boxed();
|
||||
|
||||
if defined_variables.is_empty() {
|
||||
binding_strategy
|
||||
} else {
|
||||
let print_strategy = Union::new(
|
||||
defined_variables
|
||||
.keys()
|
||||
.map(|x| Just(Statement::Print(Location::manufactured(), x.to_string()))),
|
||||
)
|
||||
.boxed();
|
||||
|
||||
Union::new([binding_strategy, print_strategy]).boxed()
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct ExpressionGeneratorSettings {
|
||||
bound_variables: HashMap<String, ConstantType>,
|
||||
output_type: Option<ConstantType>,
|
||||
}
|
||||
|
||||
impl Arbitrary for Expression {
|
||||
type Parameters = (Option<HashMap<String, ConstantType>>, Option<ConstantType>);
|
||||
type Parameters = ExpressionGeneratorSettings;
|
||||
type Strategy = BoxedStrategy<Self>;
|
||||
|
||||
fn arbitrary_with((env, target_type): Self::Parameters) -> Self::Strategy {
|
||||
let defined_variables = env.unwrap_or_default();
|
||||
let mut acceptable_variables = defined_variables
|
||||
.iter()
|
||||
.filter(|(_, ctype)| Some(**ctype) == target_type)
|
||||
.map(|(x, _)| x)
|
||||
.peekable();
|
||||
|
||||
let value_strategy = Value::arbitrary_with(target_type)
|
||||
.prop_map(move |x| Expression::Value(Location::manufactured(), x))
|
||||
fn arbitrary_with(params: 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(params.output_type)
|
||||
.prop_map(|x| Expression::Value(Location::manufactured(), x))
|
||||
.boxed();
|
||||
|
||||
let leaf_strategy = if acceptable_variables.peek().is_none() {
|
||||
// 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 = params
|
||||
.bound_variables
|
||||
.iter()
|
||||
.filter(|(_, v)| {
|
||||
params
|
||||
.output_type
|
||||
.as_ref()
|
||||
.map(|ot| ot == *v)
|
||||
.unwrap_or(true)
|
||||
})
|
||||
.map(|(n, _)| n)
|
||||
.collect::<Vec<_>>();
|
||||
let leaf_strategy = if bound_variables_of_type.is_empty() {
|
||||
value_strategy
|
||||
} else {
|
||||
let reference_strategy = Union::new(acceptable_variables.map(|x| {
|
||||
Just(Expression::Reference(
|
||||
Location::manufactured(),
|
||||
x.to_owned(),
|
||||
))
|
||||
}))
|
||||
.boxed();
|
||||
Union::new([value_strategy, reference_strategy]).boxed()
|
||||
let mut strats = bound_variables_of_type
|
||||
.drain(..)
|
||||
.map(|x| Just(Expression::Reference(Location::manufactured(), x.clone())).boxed())
|
||||
.collect::<Vec<_>>();
|
||||
strats.push(value_strategy);
|
||||
Union::new(strats).boxed()
|
||||
};
|
||||
|
||||
let cast_strategy = if let Some(bigger_type) = target_type {
|
||||
let mut smaller_types = bigger_type.safe_casts_to();
|
||||
|
||||
if smaller_types.is_empty() {
|
||||
leaf_strategy
|
||||
} else {
|
||||
let duplicated_env = defined_variables.clone();
|
||||
let cast_exp = |t, e| Expression::Cast(Location::manufactured(), t, Box::new(e));
|
||||
|
||||
let smaller_strats: Vec<BoxedStrategy<Expression>> = smaller_types
|
||||
.drain(..)
|
||||
.map(|t| {
|
||||
Expression::arbitrary_with((Some(duplicated_env.clone()), Some(t)))
|
||||
.prop_map(move |e| cast_exp(t.name(), e))
|
||||
.boxed()
|
||||
})
|
||||
.collect();
|
||||
Union::new(smaller_strats).boxed()
|
||||
}
|
||||
} else {
|
||||
leaf_strategy
|
||||
};
|
||||
|
||||
cast_strategy
|
||||
.prop_recursive(3, 64, 2, move |inner| {
|
||||
(select(OPERATORS), proptest::collection::vec(inner, 2)).prop_map(
|
||||
move |((operator, arg_count), mut exprs)| {
|
||||
if arg_count == 1 && operator == "-" {
|
||||
if target_type.map(|x| x.is_signed()).unwrap_or(false) {
|
||||
Expression::Primitive(
|
||||
Location::manufactured(),
|
||||
operator.to_string(),
|
||||
exprs,
|
||||
)
|
||||
} else {
|
||||
exprs.pop().unwrap()
|
||||
}
|
||||
} else {
|
||||
exprs.truncate(arg_count);
|
||||
Expression::Primitive(
|
||||
Location::manufactured(),
|
||||
operator.to_string(),
|
||||
exprs,
|
||||
)
|
||||
// now we generate our recursive types, given our leaf strategy
|
||||
leaf_strategy
|
||||
.prop_recursive(3, 10, 2, move |strat| {
|
||||
(select(OPERATORS), strat.clone(), strat).prop_map(
|
||||
|((oper, count), left, right)| {
|
||||
let mut args = vec![left, right];
|
||||
while args.len() > count {
|
||||
args.pop();
|
||||
}
|
||||
Expression::Primitive(Location::manufactured(), oper.to_string(), args)
|
||||
},
|
||||
)
|
||||
})
|
||||
@@ -181,7 +161,7 @@ impl Arbitrary for Value {
|
||||
type Strategy = BoxedStrategy<Self>;
|
||||
|
||||
fn arbitrary_with(target_type: Self::Parameters) -> Self::Strategy {
|
||||
let base_strategy = Union::new([
|
||||
let printed_base_strategy = Union::new([
|
||||
Just(None::<u8>),
|
||||
Just(Some(2)),
|
||||
Just(Some(8)),
|
||||
@@ -189,14 +169,13 @@ impl Arbitrary for Value {
|
||||
Just(Some(16)),
|
||||
]);
|
||||
|
||||
let type_strategy = if target_type.is_some() {
|
||||
Just(target_type).boxed()
|
||||
} else {
|
||||
proptest::option::of(ConstantType::arbitrary()).boxed()
|
||||
let type_strategy = match target_type {
|
||||
None => proptest::option::of(ConstantType::arbitrary()).boxed(),
|
||||
Some(target) => proptest::option::of(Just(target)).boxed(),
|
||||
};
|
||||
let value_strategy = u64::arbitrary();
|
||||
|
||||
(base_strategy, type_strategy, value_strategy)
|
||||
(printed_base_strategy, type_strategy, value_strategy)
|
||||
.prop_map(move |(base, ty, value)| {
|
||||
let converted_value = match ty {
|
||||
Some(ConstantType::I8) => value % (i8::MAX as u64),
|
||||
|
||||
Reference in New Issue
Block a user