🤔 Add a type inference engine, along with typed literals. #4
@@ -1,9 +1,13 @@
|
||||
use crate::backend::Backend;
|
||||
use crate::eval::EvalError;
|
||||
use crate::ir::Program;
|
||||
#[cfg(test)]
|
||||
use crate::syntax::arbitrary::GenerationEnvironment;
|
||||
use cranelift_jit::JITModule;
|
||||
use cranelift_object::ObjectModule;
|
||||
use std::path::Path;
|
||||
#[cfg(test)]
|
||||
use proptest::arbitrary::Arbitrary;
|
||||
use target_lexicon::Triple;
|
||||
|
||||
impl Backend<JITModule> {
|
||||
@@ -115,7 +119,7 @@ proptest::proptest! {
|
||||
// without error, assuming any possible input ... well, any possible input that
|
||||
// doesn't involve overflow or underflow.
|
||||
#[test]
|
||||
fn static_backend(program: Program) {
|
||||
fn static_backend(program in Program::arbitrary_with(GenerationEnvironment::new(false))) {
|
||||
use crate::eval::PrimOpError;
|
||||
|
||||
let basic_result = program.eval();
|
||||
@@ -137,7 +141,7 @@ proptest::proptest! {
|
||||
// .expect("rendering works");
|
||||
|
||||
let compiled_result = Backend::<ObjectModule>::eval(program);
|
||||
assert_eq!(basic_result, compiled_result);
|
||||
proptest::prop_assert_eq!(basic_result, compiled_result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,14 +149,24 @@ proptest::proptest! {
|
||||
// without error, assuming any possible input ... well, any possible input that
|
||||
// doesn't involve overflow or underflow.
|
||||
#[test]
|
||||
fn jit_backend(program: Program) {
|
||||
fn jit_backend(program in Program::arbitrary_with(GenerationEnvironment::new(false))) {
|
||||
use crate::eval::PrimOpError;
|
||||
// use pretty::{DocAllocator, Pretty};
|
||||
// let allocator = pretty::BoxAllocator;
|
||||
// allocator
|
||||
// .text("---------------")
|
||||
// .append(allocator.hardline())
|
||||
// .append(program.pretty(&allocator))
|
||||
// .1
|
||||
// .render_colored(70, pretty::termcolor::StandardStream::stdout(pretty::termcolor::ColorChoice::Auto))
|
||||
// .expect("rendering works");
|
||||
|
||||
|
||||
let basic_result = program.eval();
|
||||
|
||||
if !matches!(basic_result, Err(EvalError::PrimOp(PrimOpError::MathFailure(_)))) {
|
||||
let compiled_result = Backend::<JITModule>::eval(program);
|
||||
assert_eq!(basic_result, compiled_result);
|
||||
proptest::prop_assert_eq!(basic_result, compiled_result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,21 +108,22 @@ extern "C" fn runtime_print(
|
||||
) {
|
||||
let cstr = unsafe { CStr::from_ptr(name) };
|
||||
let reconstituted = cstr.to_string_lossy();
|
||||
let vtype = match vtype_repr.try_into() {
|
||||
Ok(ConstantType::I8) => "i8",
|
||||
Ok(ConstantType::I16) => "i16",
|
||||
Ok(ConstantType::I32) => "i32",
|
||||
Ok(ConstantType::I64) => "i64",
|
||||
Ok(ConstantType::U8) => "u8",
|
||||
Ok(ConstantType::U16) => "u16",
|
||||
Ok(ConstantType::U32) => "u32",
|
||||
Ok(ConstantType::U64) => "u64",
|
||||
Err(_) => "<unknown type>",
|
||||
|
||||
let output = match vtype_repr.try_into() {
|
||||
Ok(ConstantType::I8) => format!("{} = {}i8", reconstituted, value as i8),
|
||||
Ok(ConstantType::I16) => format!("{} = {}i16", reconstituted, value as i16),
|
||||
Ok(ConstantType::I32) => format!("{} = {}i32", reconstituted, value as i32),
|
||||
Ok(ConstantType::I64) => format!("{} = {}i64", reconstituted, value),
|
||||
Ok(ConstantType::U8) => format!("{} = {}u8", reconstituted, value as u8),
|
||||
Ok(ConstantType::U16) => format!("{} = {}u16", reconstituted, value as u16),
|
||||
Ok(ConstantType::U32) => format!("{} = {}u32", reconstituted, value as u32),
|
||||
Ok(ConstantType::U64) => format!("{} = {}u64", reconstituted, value as u64),
|
||||
Err(_) => format!("{} = {}<unknown type>", reconstituted, value),
|
||||
};
|
||||
|
||||
if let Some(output_buffer) = unsafe { output_buffer.as_mut() } {
|
||||
writeln!(output_buffer, "{} = {}{}", reconstituted, value, vtype).unwrap();
|
||||
if let Some(output_buffer) = unsafe{ output_buffer.as_mut() } {
|
||||
writeln!(output_buffer, "{}", output).unwrap();
|
||||
} else {
|
||||
println!("{} = {}{}", reconstituted, value, vtype);
|
||||
println!("{}", output);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ where
|
||||
}
|
||||
|
||||
impl Arbitrary for Program {
|
||||
type Parameters = ();
|
||||
type Parameters = crate::syntax::arbitrary::GenerationEnvironment;
|
||||
type Strategy = BoxedStrategy<Self>;
|
||||
|
||||
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
|
||||
|
||||
@@ -27,7 +27,7 @@ use codespan_reporting::{diagnostic::Diagnostic, files::SimpleFiles};
|
||||
use lalrpop_util::lalrpop_mod;
|
||||
use logos::Logos;
|
||||
|
||||
mod arbitrary;
|
||||
pub mod arbitrary;
|
||||
mod ast;
|
||||
mod eval;
|
||||
mod location;
|
||||
@@ -40,6 +40,8 @@ lalrpop_mod!(
|
||||
mod pretty;
|
||||
mod validate;
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::syntax::arbitrary::GenerationEnvironment;
|
||||
pub use crate::syntax::ast::*;
|
||||
pub use crate::syntax::location::Location;
|
||||
pub use crate::syntax::parser::{ProgramParser, StatementParser};
|
||||
@@ -48,7 +50,7 @@ pub use crate::syntax::tokens::{LexerError, Token};
|
||||
use ::pretty::{Arena, Pretty};
|
||||
use lalrpop_util::ParseError;
|
||||
#[cfg(test)]
|
||||
use proptest::{prop_assert, prop_assert_eq};
|
||||
use proptest::{arbitrary::Arbitrary, prop_assert, prop_assert_eq};
|
||||
#[cfg(test)]
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
@@ -335,8 +337,8 @@ proptest::proptest! {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generated_run_or_overflow(program: Program) {
|
||||
fn generated_run_or_overflow(program in Program::arbitrary_with(GenerationEnvironment::new(false))) {
|
||||
use crate::eval::{EvalError, PrimOpError};
|
||||
assert!(matches!(program.eval(), Ok(_) | Err(EvalError::PrimOp(PrimOpError::MathFailure(_)))))
|
||||
prop_assert!(matches!(program.eval(), Ok(_) | Err(EvalError::PrimOp(PrimOpError::MathFailure(_)))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,89 @@ use proptest::{
|
||||
strategy::{Just, Union},
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Range;
|
||||
|
||||
const VALID_VARIABLE_NAMES: &str = r"[a-z][a-zA-Z0-9_]*";
|
||||
const OPERATORS: &[(&str, usize)] = &[("+", 2), ("-", 1), ("-", 2), ("*", 2), ("/", 2)];
|
||||
|
||||
impl ConstantType {
|
||||
fn get_operators(&self) -> &'static [(&'static str, usize)] {
|
||||
match self {
|
||||
ConstantType::I8| ConstantType::I16 | ConstantType::I32 | ConstantType::I64 =>
|
||||
&[("+", 2), ("-", 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(ProgramStatementInfo::arbitrary(), genenv.block_length.clone())
|
||||
.prop_flat_map(move |mut items| {
|
||||
let mut statements = Vec::new();
|
||||
let mut genenv = genenv.clone();
|
||||
|
||||
for psi in items.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);
|
||||
statements.push(
|
||||
expr.prop_map(move |expr| {
|
||||
Statement::Binding(Location::manufactured(), psi.name.clone(), expr)
|
||||
})
|
||||
.boxed(),
|
||||
);
|
||||
} else {
|
||||
let printers = genenv.bindings.keys().map(|n| {
|
||||
Just(Statement::Print(
|
||||
Location::manufactured(),
|
||||
Name::manufactured(n),
|
||||
))
|
||||
});
|
||||
statements.push(Union::new(printers).boxed());
|
||||
}
|
||||
}
|
||||
|
||||
statements
|
||||
.prop_map(|statements| Program { statements })
|
||||
.boxed()
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl Arbitrary for Name {
|
||||
type Parameters = ();
|
||||
@@ -47,63 +127,14 @@ impl Arbitrary for ProgramStatementInfo {
|
||||
}
|
||||
}
|
||||
|
||||
impl Arbitrary for Program {
|
||||
type Parameters = ();
|
||||
type Strategy = BoxedStrategy<Self>;
|
||||
|
||||
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
|
||||
proptest::collection::vec(ProgramStatementInfo::arbitrary(), 1..100)
|
||||
.prop_flat_map(|mut items| {
|
||||
let mut statements = Vec::new();
|
||||
let mut defined_variables = HashMap::new();
|
||||
|
||||
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.name.clone(), psi.binding_type);
|
||||
statements.push(
|
||||
expr.prop_map(move |expr| {
|
||||
Statement::Binding(Location::manufactured(), psi.name.clone(), expr)
|
||||
})
|
||||
.boxed(),
|
||||
);
|
||||
} else {
|
||||
let printers = defined_variables.keys().map(|n| {
|
||||
Just(Statement::Print(
|
||||
Location::manufactured(),
|
||||
Name::manufactured(n),
|
||||
))
|
||||
});
|
||||
statements.push(Union::new(printers).boxed());
|
||||
}
|
||||
}
|
||||
|
||||
statements
|
||||
.prop_map(|statements| Program { statements })
|
||||
.boxed()
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ExpressionGeneratorSettings {
|
||||
bound_variables: HashMap<String, ConstantType>,
|
||||
output_type: Option<ConstantType>,
|
||||
}
|
||||
|
||||
impl Arbitrary for Expression {
|
||||
type Parameters = ExpressionGeneratorSettings;
|
||||
type Parameters = GenerationEnvironment;
|
||||
type Strategy = BoxedStrategy<Self>;
|
||||
|
||||
fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
|
||||
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(params.output_type)
|
||||
let value_strategy = Value::arbitrary_with(genenv.clone())
|
||||
.prop_map(|x| Expression::Value(Location::manufactured(), x))
|
||||
.boxed();
|
||||
|
||||
@@ -112,16 +143,10 @@ impl Arbitrary for Expression {
|
||||
// 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
|
||||
let mut bound_variables_of_type = genenv
|
||||
.bindings
|
||||
.iter()
|
||||
.filter(|(_, v)| {
|
||||
params
|
||||
.output_type
|
||||
.as_ref()
|
||||
.map(|ot| ot == *v)
|
||||
.unwrap_or(true)
|
||||
})
|
||||
.filter(|(_, v)| genenv.return_type == **v)
|
||||
.map(|(n, _)| n)
|
||||
.collect::<Vec<_>>();
|
||||
let leaf_strategy = if bound_variables_of_type.is_empty() {
|
||||
@@ -129,7 +154,13 @@ impl Arbitrary for Expression {
|
||||
} else {
|
||||
let mut strats = bound_variables_of_type
|
||||
.drain(..)
|
||||
.map(|x| Just(Expression::Reference(Location::manufactured(), x.clone())).boxed())
|
||||
.map(|x| {
|
||||
Just(Expression::Reference(
|
||||
Location::manufactured(),
|
||||
x.name.clone(),
|
||||
))
|
||||
.boxed()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
strats.push(value_strategy);
|
||||
Union::new(strats).boxed()
|
||||
@@ -138,7 +169,7 @@ impl Arbitrary for Expression {
|
||||
// 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(
|
||||
(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 {
|
||||
@@ -153,10 +184,10 @@ impl Arbitrary for Expression {
|
||||
}
|
||||
|
||||
impl Arbitrary for Value {
|
||||
type Parameters = Option<ConstantType>;
|
||||
type Parameters = GenerationEnvironment;
|
||||
type Strategy = BoxedStrategy<Self>;
|
||||
|
||||
fn arbitrary_with(target_type: Self::Parameters) -> Self::Strategy {
|
||||
fn arbitrary_with(genenv: Self::Parameters) -> Self::Strategy {
|
||||
let printed_base_strategy = Union::new([
|
||||
Just(None::<u8>),
|
||||
Just(Some(2)),
|
||||
@@ -164,25 +195,24 @@ impl Arbitrary for Value {
|
||||
Just(Some(10)),
|
||||
Just(Some(16)),
|
||||
]);
|
||||
|
||||
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();
|
||||
|
||||
(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),
|
||||
Some(ConstantType::U8) => value % (u8::MAX as u64),
|
||||
Some(ConstantType::I16) => value % (i16::MAX as u64),
|
||||
Some(ConstantType::U16) => value % (u16::MAX as u64),
|
||||
Some(ConstantType::I32) => value % (i32::MAX as u64),
|
||||
Some(ConstantType::U32) => value % (u32::MAX as u64),
|
||||
Some(ConstantType::I64) => value % (i64::MAX as u64),
|
||||
Some(ConstantType::U64) => value,
|
||||
None => value,
|
||||
(printed_base_strategy, bool::arbitrary(), value_strategy)
|
||||
.prop_map(move |(base, declare_type, value)| {
|
||||
let converted_value = match genenv.return_type {
|
||||
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)
|
||||
})
|
||||
|
||||
@@ -3,12 +3,17 @@ mod convert;
|
||||
mod finalize;
|
||||
mod solve;
|
||||
|
||||
|
||||
use self::convert::convert_program;
|
||||
use self::finalize::finalize_program;
|
||||
use self::solve::solve_constraints;
|
||||
pub use self::solve::{TypeInferenceError, TypeInferenceResult, TypeInferenceWarning};
|
||||
use crate::ir::ast as ir;
|
||||
use crate::syntax;
|
||||
#[cfg(test)]
|
||||
use proptest::prelude::Arbitrary;
|
||||
#[cfg(test)]
|
||||
use crate::syntax::arbitrary::GenerationEnvironment;
|
||||
|
||||
impl syntax::Program {
|
||||
/// Infer the types for the syntactic AST, returning either a type-checked program in
|
||||
@@ -27,20 +32,10 @@ impl syntax::Program {
|
||||
|
||||
proptest::proptest! {
|
||||
#[test]
|
||||
fn translation_maintains_semantics(input: syntax::Program) {
|
||||
use pretty::{DocAllocator, Pretty};
|
||||
let allocator = pretty::BoxAllocator;
|
||||
allocator
|
||||
.text("---------------")
|
||||
.append(allocator.hardline())
|
||||
.append(input.pretty(&allocator))
|
||||
.1
|
||||
.render_colored(70, pretty::termcolor::StandardStream::stdout(pretty::termcolor::ColorChoice::Auto))
|
||||
.expect("rendering works");
|
||||
|
||||
fn translation_maintains_semantics(input in syntax::Program::arbitrary_with(GenerationEnvironment::new(false))) {
|
||||
let syntax_result = input.eval();
|
||||
let ir = input.type_infer().expect("arbitrary should generate type-safe programs");
|
||||
let ir_result = ir.eval();
|
||||
assert_eq!(syntax_result, ir_result);
|
||||
proptest::prop_assert_eq!(syntax_result, ir_result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,11 +190,6 @@ pub fn solve_constraints(
|
||||
// constraints. Internal to the loop, we have a check that will make sure that we
|
||||
// do (eventually) stop.
|
||||
while changed_something && !constraint_db.is_empty() {
|
||||
println!("-------CONSTRAINTS---------");
|
||||
for constraint in constraint_db.iter() {
|
||||
println!("{}", constraint);
|
||||
}
|
||||
println!("---------------------------");
|
||||
// Set this to false at the top of the loop. We'll set this to true if we make
|
||||
// progress in any way further down, but having this here prevents us from going
|
||||
// into an infinite look when we can't figure stuff out.
|
||||
|
||||
Reference in New Issue
Block a user