diff --git a/src/backend/eval.rs b/src/backend/eval.rs index 2b7a1a2..8e87f60 100644 --- a/src/backend/eval.rs +++ b/src/backend/eval.rs @@ -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 { @@ -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::::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::::eval(program); - assert_eq!(basic_result, compiled_result); + proptest::prop_assert_eq!(basic_result, compiled_result); } } } diff --git a/src/backend/runtime.rs b/src/backend/runtime.rs index f766b97..ec5a2db 100644 --- a/src/backend/runtime.rs +++ b/src/backend/runtime.rs @@ -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(_) => "", + + 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!("{} = {}", 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); } -} +} \ No newline at end of file diff --git a/src/ir/ast.rs b/src/ir/ast.rs index 9360de1..478f393 100644 --- a/src/ir/ast.rs +++ b/src/ir/ast.rs @@ -56,7 +56,7 @@ where } impl Arbitrary for Program { - type Parameters = (); + type Parameters = crate::syntax::arbitrary::GenerationEnvironment; type Strategy = BoxedStrategy; fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { diff --git a/src/syntax.rs b/src/syntax.rs index 258831c..8d505a1 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -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(_))))); } } diff --git a/src/syntax/arbitrary.rs b/src/syntax/arbitrary.rs index 2feded7..688a305 100644 --- a/src/syntax/arbitrary.rs +++ b/src/syntax/arbitrary.rs @@ -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, + bindings: HashMap, + 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; + + 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; - - 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, - output_type: Option, -} - impl Arbitrary for Expression { - type Parameters = ExpressionGeneratorSettings; + type Parameters = GenerationEnvironment; type Strategy = BoxedStrategy; - 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::>(); 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::>(); 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; + type Parameters = GenerationEnvironment; type Strategy = BoxedStrategy; - 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::), 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) }) diff --git a/src/type_infer.rs b/src/type_infer.rs index 172d44e..d08ff53 100644 --- a/src/type_infer.rs +++ b/src/type_infer.rs @@ -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); } } diff --git a/src/type_infer/solve.rs b/src/type_infer/solve.rs index b22053b..4689bf8 100644 --- a/src/type_infer/solve.rs +++ b/src/type_infer/solve.rs @@ -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.