From e5db6640f2d4b599a342e553912c8bc1d8687048 Mon Sep 17 00:00:00 2001 From: Adam Wick Date: Tue, 26 Dec 2023 21:08:01 -0800 Subject: [PATCH] checkpoint in reconstruction --- Cargo.toml | 38 ++-- build.rs | 17 +- src/backend/eval.rs | 6 +- src/backend/into_crane.rs | 3 +- src/backend/runtime.rs | 1 + src/bin/ngrun.rs | 74 +++++++ src/eval/primop.rs | 6 +- src/eval/primtype.rs | 84 ++++--- src/eval/value.rs | 4 +- src/ir/arbitrary.rs | 444 +++++++++++++++++++++++++++++++++++-- src/ir/ast.rs | 39 +++- src/ir/eval.rs | 26 ++- src/syntax.rs | 25 ++- src/syntax/arbitrary.rs | 4 +- src/syntax/eval.rs | 1 + src/syntax/parser.lalrpop | 4 +- src/syntax/pretty.rs | 1 + src/syntax/tokens.rs | 97 ++++---- src/type_infer/convert.rs | 5 + src/type_infer/finalize.rs | 5 + src/util/scoped_map.rs | 28 ++- 21 files changed, 759 insertions(+), 153 deletions(-) create mode 100644 src/bin/ngrun.rs diff --git a/Cargo.toml b/Cargo.toml index 9531890..296f284 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,25 +9,27 @@ name = "ngr" path = "src/lib.rs" [dependencies] -clap = { version = "^3.0.14", features = ["derive"] } +clap = { version = "4.4.11", features = ["derive"] } codespan = "0.11.1" codespan-reporting = "0.11.1" -cranelift-codegen = "0.99.2" -cranelift-jit = "0.99.2" -cranelift-frontend = "0.99.2" -cranelift-module = "0.99.2" -cranelift-native = "0.99.2" -cranelift-object = "0.99.2" -internment = { version = "0.7.0", default-features = false, features = ["arc"] } -lalrpop-util = "^0.20.0" -lazy_static = "^1.4.0" -logos = "^0.12.0" -pretty = { version = "^0.11.2", features = ["termcolor"] } -proptest = "^1.0.0" -rustyline = "^11.0.0" -target-lexicon = "^0.12.5" -tempfile = "^3.5.0" -thiserror = "^1.0.30" +cranelift-codegen = "0.103.0" +cranelift-jit = "0.103.0" +cranelift-frontend = "0.103.0" +cranelift-module = "0.103.0" +cranelift-native = "0.103.0" +cranelift-object = "0.103.0" +internment = { version = "0.7.4", default-features = false, features = ["arc"] } +lalrpop-util = "0.20.0" +lazy_static = "1.4.0" +logos = "0.13.0" +pretty = { version = "0.12.3", features = ["termcolor"] } +proptest = "1.4.0" +rand = "0.8.5" +rustyline = "13.0.0" +target-lexicon = "0.12.12" +tempfile = "3.8.1" +thiserror = "1.0.52" +anyhow = "1.0.77" [build-dependencies] -lalrpop = "^0.20.0" +lalrpop = "0.20.0" diff --git a/build.rs b/build.rs index 2f4266e..f883c2b 100644 --- a/build.rs +++ b/build.rs @@ -72,13 +72,28 @@ fn generate_tests(f: &mut File, path_so_far: PathBuf) -> std::io::Result<()> { f, " assert_eq!(errors.len(), 0, \"file should have no validation errors\");" )?; + writeln!(f, " let syntax_result = syntax.eval();")?; writeln!( f, " let ir = syntax.type_infer().expect(\"example is typed correctly\");" )?; writeln!(f, " let ir_result = ir.eval();")?; + writeln!(f, " match (&syntax_result, &ir_result) {{")?; + writeln!(f, " (Err(e1), Err(e2)) => assert_eq!(e1, e2),")?; + writeln!(f, " (Ok((v1, o1)), Ok((v2, o2))) => {{")?; + writeln!(f, " assert_eq!(v1, v2);")?; + writeln!(f, " assert_eq!(o1, o2);")?; + writeln!(f, " }}")?; + writeln!(f, " _ => panic!(\"mismatched outputs, {{:?}} and {{:?}}\", syntax_result, ir_result)")?; + writeln!(f, " }}")?; writeln!(f, " let compiled_result = Backend::::eval(ir);")?; - writeln!(f, " assert_eq!(ir_result, compiled_result);")?; + writeln!(f, " match (&compiled_result, &ir_result) {{")?; + writeln!(f, " (Err(e1), Err(e2)) => assert_eq!(e1, e2),")?; + writeln!(f, " (Ok(o1), Ok((_, o2))) => {{")?; + writeln!(f, " assert_eq!(o1, o2);")?; + writeln!(f, " }}")?; + writeln!(f, " _ => panic!(\"mismatched outputs, {{:?}} and {{:?}}\", compiled_result, ir_result)")?; + writeln!(f, " }}")?; } writeln!(f, "}}")?; } diff --git a/src/backend/eval.rs b/src/backend/eval.rs index de7a1fa..7768ef8 100644 --- a/src/backend/eval.rs +++ b/src/backend/eval.rs @@ -1,8 +1,6 @@ use crate::backend::Backend; use crate::eval::EvalError; use crate::ir::{Expression, Program, TopLevel, Type}; -#[cfg(test)] -use crate::syntax::arbitrary::GenerationEnvironment; use crate::syntax::Location; use cranelift_jit::JITModule; use cranelift_object::ObjectModule; @@ -176,7 +174,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 in Program::arbitrary_with(GenerationEnvironment::new(false))) { + fn static_backend(program in Program::arbitrary()) { use crate::eval::PrimOpError; let basic_result = program.eval().map(|(_,x)| x); @@ -206,7 +204,7 @@ proptest::proptest! { // without error, assuming any possible input ... well, any possible input that // doesn't involve overflow or underflow. #[test] - fn jit_backend(program in Program::arbitrary_with(GenerationEnvironment::new(false))) { + fn jit_backend(program in Program::arbitrary()) { use crate::eval::PrimOpError; // use pretty::{DocAllocator, Pretty}; // let allocator = pretty::BoxAllocator; diff --git a/src/backend/into_crane.rs b/src/backend/into_crane.rs index d897f7c..1b409f6 100644 --- a/src/backend/into_crane.rs +++ b/src/backend/into_crane.rs @@ -338,7 +338,7 @@ impl Backend { let vtype_repr = builder.ins().iconst(types::I64, vtype as i64); let casted_val = match vtype { - ConstantType::U64 | ConstantType::I64 => val, + ConstantType::U64 | ConstantType::I64 | ConstantType::Void => val, ConstantType::I8 | ConstantType::I16 | ConstantType::I32 => { builder.ins().sextend(types::I64, val) } @@ -401,6 +401,7 @@ impl Backend { builder.ins().iconst(types::I64, v as i64), ConstantType::U64, )), + Value::Void => Ok((builder.ins().iconst(types::I64, 0i64), ConstantType::Void)), }, ValueOrRef::Ref(_, _, name) => match variables.get(&name) { None => Err(BackendError::VariableLookupFailure(name)), diff --git a/src/backend/runtime.rs b/src/backend/runtime.rs index 40907aa..1df45a3 100644 --- a/src/backend/runtime.rs +++ b/src/backend/runtime.rs @@ -110,6 +110,7 @@ extern "C" fn runtime_print( let reconstituted = cstr.to_string_lossy(); let output = match vtype_repr.try_into() { + Ok(ConstantType::Void) => format!("{} = ", reconstituted), 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), diff --git a/src/bin/ngrun.rs b/src/bin/ngrun.rs new file mode 100644 index 0000000..9230f63 --- /dev/null +++ b/src/bin/ngrun.rs @@ -0,0 +1,74 @@ +use clap::Parser; +use codespan_reporting::files::SimpleFiles; +use ngr::eval::Value; +use ngr::syntax; +use ngr::type_infer::TypeInferenceResult; +use pretty::termcolor::StandardStream; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct CommandLineArguments { + /// Which interpreter to use: syntax, ir, or jit + #[arg(value_enum)] + interpreter: Interpreter, + + /// The file to parse + file: String, +} + +#[allow(clippy::upper_case_acronyms)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] +enum Interpreter { + /// Run the syntax-level interpreter + Syntax, + /// Run the IR-level interpreter + IR, + /// Run the JIT backend + JIT, +} + +fn print_result(result: (Value, String)) { + println!("{}", result.1); + println!("RESULT: {}", result.0); +} + +fn main() -> Result<(), anyhow::Error> { + let cli = CommandLineArguments::parse(); + let mut file_database = SimpleFiles::new(); + let mut console = StandardStream::stdout(pretty::termcolor::ColorChoice::Auto); + let console_options = codespan_reporting::term::Config::default(); + let syntax = syntax::Program::parse_file(&mut file_database, cli.file.as_ref())?; + let mut emit = |x| { + let _ = codespan_reporting::term::emit(&mut console, &console_options, &file_database, &x); + }; + + if cli.interpreter == Interpreter::Syntax { + print_result(syntax.eval()?); + return Ok(()); + } + + let ir = match syntax.type_infer() { + TypeInferenceResult::Success { result, warnings } => { + for warning in warnings { + emit(warning.into()); + } + result + } + TypeInferenceResult::Failure { errors, warnings } => { + for warning in warnings { + emit(warning.into()); + } + for error in errors { + emit(error.into()); + } + return Err(anyhow::anyhow!("failed to infer program types")); + } + }; + + if cli.interpreter == Interpreter::IR { + print_result(ir.eval()?); + return Ok(()); + } + + unimplemented!(); +} \ No newline at end of file diff --git a/src/eval/primop.rs b/src/eval/primop.rs index c4fcd25..aa9ffef 100644 --- a/src/eval/primop.rs +++ b/src/eval/primop.rs @@ -36,14 +36,16 @@ pub enum PrimOpError { ValuePrimitiveTypeError(#[from] ValuePrimitiveTypeError), } -impl PartialEq> for PrimOpError { +impl PartialEq> for PrimOpError { fn eq(&self, other: &PrimOpError) -> bool { match (self, other) { (PrimOpError::MathFailure(a), PrimOpError::MathFailure(b)) => a == b, (PrimOpError::TypeMismatch(a, b, c), PrimOpError::TypeMismatch(x, y, z)) => { a == x && b.strip() == y.strip() && c.strip() == z.strip() } - (PrimOpError::BadTypeFor(a, b), PrimOpError::BadTypeFor(x, y)) => a == x && b.strip() == y.strip(), + (PrimOpError::BadTypeFor(a, b), PrimOpError::BadTypeFor(x, y)) => { + a == x && b.strip() == y.strip() + } (PrimOpError::BadArgCount(a, b), PrimOpError::BadArgCount(x, y)) => a == x && b == y, (PrimOpError::UnknownPrimOp(a), PrimOpError::UnknownPrimOp(x)) => a == x, ( diff --git a/src/eval/primtype.rs b/src/eval/primtype.rs index 0876310..3ed2b1c 100644 --- a/src/eval/primtype.rs +++ b/src/eval/primtype.rs @@ -63,6 +63,7 @@ impl<'a, IR> TryFrom<&'a Value> for PrimitiveType { impl From for PrimitiveType { fn from(value: ConstantType) -> Self { match value { + ConstantType::Void => PrimitiveType::Void, ConstantType::I8 => PrimitiveType::I8, ConstantType::I16 => PrimitiveType::I16, ConstantType::I32 => PrimitiveType::I32, @@ -100,44 +101,43 @@ impl FromStr for PrimitiveType { } impl PrimitiveType { + /// Return the set of types that this type can be safely cast to + pub fn allowed_casts(&self) -> &'static [PrimitiveType] { + match self { + PrimitiveType::Void => &[PrimitiveType::Void], + PrimitiveType::U8 => &[ + PrimitiveType::U8, + PrimitiveType::U16, + PrimitiveType::U32, + PrimitiveType::U64, + PrimitiveType::I16, + PrimitiveType::I32, + PrimitiveType::I64, + ], + PrimitiveType::U16 => &[ + PrimitiveType::U16, + PrimitiveType::U32, + PrimitiveType::U64, + PrimitiveType::I32, + PrimitiveType::I64, + ], + PrimitiveType::U32 => &[PrimitiveType::U32, PrimitiveType::U64, PrimitiveType::I64], + PrimitiveType::U64 => &[PrimitiveType::U64], + PrimitiveType::I8 => &[ + PrimitiveType::I8, + PrimitiveType::I16, + PrimitiveType::I32, + PrimitiveType::I64, + ], + PrimitiveType::I16 => &[PrimitiveType::I16, PrimitiveType::I32, PrimitiveType::I64], + PrimitiveType::I32 => &[PrimitiveType::I32, PrimitiveType::I64], + PrimitiveType::I64 => &[PrimitiveType::I64], + } + } + /// Return true if this type can be safely cast into the target type. pub fn can_cast_to(&self, target: &PrimitiveType) -> bool { - match self { - PrimitiveType::Void => matches!(target, PrimitiveType::Void), - PrimitiveType::U8 => matches!( - target, - PrimitiveType::U8 - | PrimitiveType::U16 - | PrimitiveType::U32 - | PrimitiveType::U64 - | PrimitiveType::I16 - | PrimitiveType::I32 - | PrimitiveType::I64 - ), - PrimitiveType::U16 => matches!( - target, - PrimitiveType::U16 - | PrimitiveType::U32 - | PrimitiveType::U64 - | PrimitiveType::I32 - | PrimitiveType::I64 - ), - PrimitiveType::U32 => matches!( - target, - PrimitiveType::U32 | PrimitiveType::U64 | PrimitiveType::I64 - ), - PrimitiveType::U64 => target == &PrimitiveType::U64, - PrimitiveType::I8 => matches!( - target, - PrimitiveType::I8 | PrimitiveType::I16 | PrimitiveType::I32 | PrimitiveType::I64 - ), - PrimitiveType::I16 => matches!( - target, - PrimitiveType::I16 | PrimitiveType::I32 | PrimitiveType::I64 - ), - PrimitiveType::I32 => matches!(target, PrimitiveType::I32 | PrimitiveType::I64), - PrimitiveType::I64 => target == &PrimitiveType::I64, - } + self.allowed_casts().contains(target) } /// Try to cast the given value to this type, returning the new value. @@ -192,4 +192,16 @@ impl PrimitiveType { PrimitiveType::I64 => Some(i64::MAX as u64), } } + + pub fn valid_operators(&self) -> &'static [(&'static str, usize)] { + match self { + PrimitiveType::Void => &[], + PrimitiveType::U8 | PrimitiveType::U16 | PrimitiveType::U32 | PrimitiveType::U64 => { + &[("+", 2), ("-", 2), ("*", 2), ("/", 2)] + } + PrimitiveType::I8 | PrimitiveType::I16 | PrimitiveType::I32 | PrimitiveType::I64 => { + &[("+", 2), ("-", 1), ("-", 2), ("*", 2), ("/", 2)] + } + } + } } diff --git a/src/eval/value.rs b/src/eval/value.rs index 26c020b..c31d1ed 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -44,9 +44,7 @@ impl Value { Value::I32(x) => Value::I32(*x), Value::I64(x) => Value::I64(*x), Value::Closure(name, env, args, _) => { - let new_env = env - .clone() - .map_values(|x| x.strip()); + let new_env = env.clone().map_values(|x| x.strip()); Value::Closure(name.clone(), new_env, args.clone(), ()) } } diff --git a/src/ir/arbitrary.rs b/src/ir/arbitrary.rs index 94f925a..bf531a5 100644 --- a/src/ir/arbitrary.rs +++ b/src/ir/arbitrary.rs @@ -1,21 +1,433 @@ -use crate::ir::{Program, TopLevel, Expression, ValueOrRef, Value, Type}; -use proptest::{ - prelude::Arbitrary, - strategy::{BoxedStrategy, Strategy}, -}; +use crate::eval::PrimitiveType; +use crate::ir::{Expression, Primitive, Program, TopLevel, Type, Value, ValueOrRef, Variable}; +use crate::syntax::Location; +use crate::util::scoped_map::ScopedMap; +use proptest::strategy::{NewTree, Strategy, ValueTree}; +use proptest::test_runner::{TestRng, TestRunner}; +use rand::distributions::{Distribution, WeightedIndex}; +use rand::seq::SliceRandom; +use rand::Rng; +use std::str::FromStr; -impl Arbitrary for Program { - type Parameters = crate::syntax::arbitrary::GenerationEnvironment; - type Strategy = BoxedStrategy; +lazy_static::lazy_static! { + static ref PROGRAM_LENGTH_DISTRIBUTION: WeightedIndex = WeightedIndex::new([ + 0, // % chance of 0 + 10, // % chance of 1 + 10, // % chance of 2 + 15, // % chance of 3 + 10, // % chance of 4 + 10, // % chance of 5 + 10, // % chance of 6 + 5, // % chance of 7 + 5, // % chance of 8 + 5, // % chance of 9 + 5, // % chance of 10 + 5, // % chance of 11 + 3, // % chance of 12 + 3, // % chance of 13 + 3, // % chance of 14 + 1, // % chance of 15 + ]).unwrap(); - fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - unimplemented!() - //crate::syntax::Program::arbitrary_with(args) - // .prop_map(|x| { - // x.type_infer() - // .expect("arbitrary_with should generate type-correct programs") - // }) - // .boxed() + static ref BLOCK_LENGTH_DISTRIBUTION: WeightedIndex = WeightedIndex::new([ + 1, // % chance of 0 + 10, // % chance of 1 + 20, // % chance of 2 + 15, // % chance of 3 + 10, // % chance of 4 + 8, // % chance of 5 + 8, // % chance of 6 + 5, // % chance of 7 + 5, // % chance of 8 + 4, // % chance of 9 + 3, // % chance of 10 + 3, // % chance of 11 + 3, // % chance of 12 + 2, // % chance of 13 + 2, // % chance of 14 + 1, // % chance of 15 + ]).unwrap(); + + static ref FUNCTION_ARGUMENTS_DISTRIBUTION: WeightedIndex = WeightedIndex::new([ + 5, // % chance of 0 + 20, // % chance of 1 + 20, // % chance of 2 + 20, // % chance of 3 + 15, // % chance of 4 + 10, // % chance of 5 + 5, // % chance of 6 + 2, // % chance of 7 + 1, // % chance of 8 + 1, // % chance of 9 + 1, // % chance of 10 + ]).unwrap(); + + static ref STATEMENT_TYPE_DISTRIBUTION: WeightedIndex = WeightedIndex::new( + STATEMENT_TYPE_FREQUENCIES.iter().map(|x| x.1) + ).unwrap(); + + static ref EXPRESSION_TYPE_DISTRIBUTION: WeightedIndex = WeightedIndex::new( + EXPRESSION_TYPE_FREQUENCIES.iter().map(|x| x.1) + ).unwrap(); + + static ref ARGUMENT_TYPE_DISTRIBUTION: WeightedIndex = WeightedIndex::new( + ARGUMENT_TYPE_FREQUENCIES.iter().map(|x| x.1) + ).unwrap(); + + static ref VALUE_TYPE_DISTRIBUTION: WeightedIndex = WeightedIndex::new( + VALUE_TYPE_FREQUENCIES.iter().map(|x| x.1) + ).unwrap(); +} + +static STATEMENT_TYPE_FREQUENCIES: &[(StatementType, usize)] = &[ + (StatementType::Binding, 3), + (StatementType::Function, 1), + (StatementType::Expression, 2), +]; + +static EXPRESSION_TYPE_FREQUENCIES: &[(ExpressionType, usize)] = &[ + (ExpressionType::Atomic, 50), + (ExpressionType::Cast, 5), + (ExpressionType::Primitive, 5), + (ExpressionType::Block, 10), + (ExpressionType::Print, 10), + (ExpressionType::Bind, 20), +]; + +static ARGUMENT_TYPE_FREQUENCIES: &[(Type, usize)] = &[ + (Type::Primitive(PrimitiveType::U8), 1), + (Type::Primitive(PrimitiveType::U16), 1), + (Type::Primitive(PrimitiveType::U32), 1), + (Type::Primitive(PrimitiveType::U64), 1), + (Type::Primitive(PrimitiveType::I8), 1), + (Type::Primitive(PrimitiveType::I16), 1), + (Type::Primitive(PrimitiveType::I32), 1), + (Type::Primitive(PrimitiveType::I64), 1), +]; + +enum StatementType { + Binding, + Function, + Expression, +} + +enum ExpressionType { + Atomic, + Cast, + Primitive, + Block, + Print, + Bind, +} + +// this knowingly excludes void +static VALUE_TYPE_FREQUENCIES: &[(ValueType, usize)] = &[ + (ValueType::I8, 1), + (ValueType::I16, 1), + (ValueType::I32, 1), + (ValueType::I64, 1), + (ValueType::U8, 1), + (ValueType::U16, 1), + (ValueType::U32, 1), + (ValueType::U64, 1), +]; + +#[derive(Copy, Clone)] +enum ValueType { + I8, + I16, + I32, + I64, + U8, + U16, + U32, + U64, + Void, +} + +impl From for ValueType { + fn from(value: PrimitiveType) -> Self { + match value { + PrimitiveType::U8 => ValueType::U8, + PrimitiveType::U16 => ValueType::U16, + PrimitiveType::U32 => ValueType::U32, + PrimitiveType::U64 => ValueType::U64, + PrimitiveType::I8 => ValueType::I8, + PrimitiveType::I16 => ValueType::I16, + PrimitiveType::I32 => ValueType::I32, + PrimitiveType::I64 => ValueType::I64, + PrimitiveType::Void => ValueType::Void, + } } } +#[derive(Debug, Default)] +pub struct ProgramGenerator {} + +impl Strategy for ProgramGenerator { + type Tree = ProgramTree; + type Value = Program; + + fn new_tree(&self, runner: &mut TestRunner) -> NewTree { + NewTree::::Ok(ProgramTree::new(runner.new_rng())) + } +} + +pub struct ProgramTree { + _rng: TestRng, + current: Program, +} + +impl ProgramTree { + fn new(mut rng: TestRng) -> Self { + let mut items = vec![]; + let program_length = PROGRAM_LENGTH_DISTRIBUTION.sample(&mut rng); + let mut env = ScopedMap::new(); + + for _ in 0..program_length { + match STATEMENT_TYPE_FREQUENCIES[STATEMENT_TYPE_DISTRIBUTION.sample(&mut rng)].0 { + StatementType::Binding => { + let binding = generate_random_binding(&mut rng, &mut env); + items.push(TopLevel::Statement(binding)); + } + StatementType::Expression => { + let expr = generate_random_expression(&mut rng, &mut env); + items.push(TopLevel::Statement(expr)); + } + StatementType::Function => { + env.new_scope(); + let name = generate_random_name(&mut rng); + let mut args = vec![]; + let arg_count = FUNCTION_ARGUMENTS_DISTRIBUTION.sample(&mut rng); + for _ in 0..arg_count { + let name = generate_random_name(&mut rng); + let ty = generate_random_argument_type(&mut rng); + args.push((name, ty)); + } + let body = generate_random_expression(&mut rng, &mut env); + let rettype = body.type_of(); + env.release_scope(); + items.push(TopLevel::Function(name, args, rettype, body)) + } + } + } + + let current = Program { items }; + + ProgramTree { _rng: rng, current } + } +} + +impl ValueTree for ProgramTree { + type Value = Program; + + fn current(&self) -> Self::Value { + self.current.clone() + } + + fn simplify(&mut self) -> bool { + unimplemented!() + } + + fn complicate(&mut self) -> bool { + unimplemented!() + } +} + +#[derive(Debug)] +struct ExpressionGenerator {} + +impl Strategy for ExpressionGenerator { + type Tree = ExpressionTree; + type Value = Expression; + + fn new_tree(&self, runner: &mut TestRunner) -> NewTree { + NewTree::::Ok(ExpressionTree::new(runner.new_rng())) + } +} + +struct ExpressionTree { + _rng: TestRng, + current: Expression, +} + +impl ValueTree for ExpressionTree { + type Value = Expression; + + fn current(&self) -> Self::Value { + self.current.clone() + } + + fn simplify(&mut self) -> bool { + unimplemented!() + } + + fn complicate(&mut self) -> bool { + unimplemented!() + } +} + +impl ExpressionTree { + fn new(mut rng: TestRng) -> Self { + let mut env = ScopedMap::new(); + let current = generate_random_expression(&mut rng, &mut env); + + ExpressionTree { _rng: rng, current } + } +} + +fn generate_random_expression( + rng: &mut TestRng, + env: &mut ScopedMap, +) -> Expression { + match EXPRESSION_TYPE_FREQUENCIES[EXPRESSION_TYPE_DISTRIBUTION.sample(rng)].0 { + ExpressionType::Atomic => Expression::Atomic(generate_random_valueref(rng, env, None)), + + ExpressionType::Bind => generate_random_binding(rng, env), + + ExpressionType::Block => { + let num_stmts = BLOCK_LENGTH_DISTRIBUTION.sample(rng); + let mut stmts = Vec::new(); + let mut last_type = Type::Primitive(PrimitiveType::Void); + + env.new_scope(); + for _ in 0..num_stmts { + let next = generate_random_expression(rng, env); + last_type = next.type_of(); + stmts.push(next); + } + env.release_scope(); + + Expression::Block(Location::manufactured(), last_type, stmts) + } + + ExpressionType::Cast => { + let inner = generate_random_valueref(rng, env, None); + + match inner.type_of() { + // nevermind + Type::Function(_, _) => Expression::Atomic(inner), + Type::Primitive(primty) => { + let to_type = primty + .allowed_casts() + .choose(rng) + .expect("actually chose type"); + Expression::Cast(Location::manufactured(), Type::Primitive(*to_type), inner) + } + } + } + + ExpressionType::Primitive => { + let base_expr = generate_random_valueref(rng, env, None); + let out_type = base_expr.type_of(); + + match out_type { + Type::Function(_, _) => Expression::Atomic(base_expr), + Type::Primitive(primty) => match primty.valid_operators().choose(rng) { + None => Expression::Atomic(base_expr), + Some((operator, arg_count)) => { + let primop = Primitive::from_str(operator).expect("chose valid primitive"); + let mut args = vec![base_expr]; + + while args.len() < *arg_count { + args.push(generate_random_valueref(rng, env, Some(primty))); + } + + Expression::Primitive(Location::manufactured(), out_type, primop, args) + } + }, + } + } + + ExpressionType::Print => { + let possible_variables = env + .bindings() + .iter() + .filter_map(|(variable, ty)| { + if ty.is_printable() { + Some(variable.clone()) + } else { + None + } + }) + .collect::>(); + + if possible_variables.is_empty() { + generate_random_binding(rng, env) + } else { + Expression::Print( + Location::manufactured(), + possible_variables.choose(rng).unwrap().clone(), + ) + } + } + } +} + +fn generate_random_binding( + rng: &mut TestRng, + env: &mut ScopedMap, +) -> Expression { + let name = generate_random_name(rng); + let expr = generate_random_expression(rng, env); + let ty = expr.type_of(); + env.insert(name.clone(), ty.clone()); + Expression::Bind(Location::manufactured(), name, ty, Box::new(expr)) +} + +fn generate_random_valueref( + rng: &mut TestRng, + env: &mut ScopedMap, + target_type: Option, +) -> ValueOrRef { + let mut bindings = env.bindings(); + + bindings.retain(|_, value| { + target_type + .map(|x| value == &Type::Primitive(x)) + .unwrap_or(true) + }); + + if rng.gen() || bindings.is_empty() { + let value_type = if let Some(target_type) = target_type { + ValueType::from(target_type) + } else { + VALUE_TYPE_FREQUENCIES[VALUE_TYPE_DISTRIBUTION.sample(rng)].0 + }; + + // generate a constant + let val = match value_type { + ValueType::I8 => Value::I8(None, rng.gen()), + ValueType::I16 => Value::I16(None, rng.gen()), + ValueType::I32 => Value::I32(None, rng.gen()), + ValueType::I64 => Value::I64(None, rng.gen()), + ValueType::U8 => Value::U8(None, rng.gen()), + ValueType::U16 => Value::U16(None, rng.gen()), + ValueType::U32 => Value::U32(None, rng.gen()), + ValueType::U64 => Value::U64(None, rng.gen()), + ValueType::Void => Value::Void, + }; + + ValueOrRef::Value(Location::manufactured(), val.type_of(), val) + } else { + // generate a reference + let weighted_keys = bindings.keys().map(|x| (1, x)).collect::>(); + let distribution = WeightedIndex::new(weighted_keys.iter().map(|x| x.0)).unwrap(); + let var = weighted_keys[distribution.sample(rng)].1.clone(); + let ty = bindings + .remove(&var) + .expect("chose unbound variable somehow?"); + ValueOrRef::Ref(Location::manufactured(), ty, var) + } +} + +fn generate_random_name(rng: &mut TestRng) -> Variable { + let start = rng.gen_range('a'..='z'); + crate::ir::gensym(&format!("{}", start)) +} + +fn generate_random_argument_type(rng: &mut TestRng) -> Type { + ARGUMENT_TYPE_FREQUENCIES[ARGUMENT_TYPE_DISTRIBUTION.sample(rng)] + .0 + .clone() +} diff --git a/src/ir/ast.rs b/src/ir/ast.rs index 67c8b12..62737fd 100644 --- a/src/ir/ast.rs +++ b/src/ir/ast.rs @@ -5,8 +5,11 @@ use crate::{ }; use internment::ArcIntern; use pretty::{BoxAllocator, DocAllocator, Pretty}; +use proptest::arbitrary::Arbitrary; use std::{fmt, str::FromStr, sync::atomic::AtomicUsize}; +use super::arbitrary::ProgramGenerator; + /// We're going to represent variables as interned strings. /// /// These should be fast enough for comparison that it's OK, since it's going to end up @@ -45,7 +48,7 @@ pub fn gensym(base: &str) -> Variable { /// The type variable is, somewhat confusingly, the current definition of a type within /// the IR. Since the makeup of this structure may change over the life of the compiler, /// it's easiest to just make it an argument. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Program { // For now, a program is just a vector of statements. In the future, we'll probably // extend this to include a bunch of other information, but for now: just a list. @@ -74,12 +77,21 @@ where } } +impl Arbitrary for Program { + type Parameters = (); + type Strategy = ProgramGenerator; + + fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { + ProgramGenerator::default() + } +} + /// A thing that can sit at the top level of a file. /// /// For the moment, these are statements and functions. Other things /// will likely be added in the future, but for now: just statements /// and functions -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum TopLevel { Statement(Expression), Function(Variable, Vec<(Variable, Type)>, Type, Expression), @@ -140,8 +152,7 @@ impl Expression { /// computed. pub fn type_of(&self) -> Type { match self { - Expression::Atomic(ValueOrRef::Ref(_, t, _)) => t.clone(), - Expression::Atomic(ValueOrRef::Value(_, t, _)) => t.clone(), + Expression::Atomic(x) => x.type_of(), Expression::Cast(_, t, _) => t.clone(), Expression::Primitive(_, t, _, _) => t.clone(), Expression::Block(_, t, _) => t.clone(), @@ -300,6 +311,15 @@ where } } +impl ValueOrRef { + pub fn type_of(&self) -> Type { + match self { + ValueOrRef::Ref(_, t, _) => t.clone(), + ValueOrRef::Value(_, t, _) => t.clone(), + } + } +} + impl From> for Expression { fn from(value: ValueOrRef) -> Self { Expression::Atomic(value) @@ -322,6 +342,7 @@ pub enum Value { U16(Option, u16), U32(Option, u32), U64(Option, u64), + Void, } impl Value { @@ -336,6 +357,7 @@ impl Value { Value::U16(_, _) => Type::Primitive(PrimitiveType::U16), Value::U32(_, _) => Type::Primitive(PrimitiveType::U32), Value::U64(_, _) => Type::Primitive(PrimitiveType::U64), + Value::Void => Type::void(), } } } @@ -379,6 +401,7 @@ where pretty_internal(opt_base, *value as u64, ConstantType::U32) } Value::U64(opt_base, value) => pretty_internal(opt_base, *value, ConstantType::U64), + Value::Void => allocator.text(""), } } } @@ -389,6 +412,14 @@ pub enum Type { Function(Vec, Box), } +impl Type { + /// Returns true if this variable can reasonably be passed to the print + /// expression for printing. + pub fn is_printable(&self) -> bool { + matches!(self, Type::Primitive(_)) + } +} + impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Type where A: 'a, diff --git a/src/ir/eval.rs b/src/ir/eval.rs index dd6340d..30bf920 100644 --- a/src/ir/eval.rs +++ b/src/ir/eval.rs @@ -32,7 +32,7 @@ impl> Program { } TopLevel::Statement(expr) => { - last_value = expr.eval(&env, &mut stdout)?; + last_value = expr.eval(&mut env, &mut stdout)?; } } } @@ -47,7 +47,7 @@ where { fn eval( &self, - env: &ScopedMap>, + env: &mut ScopedMap>, stdout: &mut String, ) -> Result, IREvalError> { match self { @@ -79,8 +79,14 @@ where } } - Expression::Block(_, _, _) => { - unimplemented!() + Expression::Block(_, _, stmts) => { + let mut result = Value::Void; + + for stmt in stmts.iter() { + result = stmt.eval(env, stdout)?; + } + + Ok(result) } Expression::Print(loc, n) => { @@ -92,16 +98,17 @@ where Ok(Value::Void) } - Expression::Bind(_, _, _, _) => unimplemented!(), + Expression::Bind(_, name, _, value) => { + let value = value.eval(env, stdout)?; + env.insert(name.clone(), value); + Ok(Value::Void) + } } } } impl ValueOrRef { - fn eval( - &self, - env: &ScopedMap>, - ) -> Result, IREvalError> { + fn eval(&self, env: &ScopedMap>) -> Result, IREvalError> { match self { ValueOrRef::Value(_, _, v) => match v { super::Value::I8(_, v) => Ok(Value::I8(*v)), @@ -112,6 +119,7 @@ impl ValueOrRef { super::Value::U16(_, v) => Ok(Value::U16(*v)), super::Value::U32(_, v) => Ok(Value::U32(*v)), super::Value::U64(_, v) => Ok(Value::U64(*v)), + super::Value::Void => Ok(Value::Void), }, ValueOrRef::Ref(loc, _, n) => env diff --git a/src/syntax.rs b/src/syntax.rs index a33119b..0047573 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -51,6 +51,7 @@ use ::pretty::{Arena, Pretty}; use lalrpop_util::ParseError; #[cfg(test)] use proptest::{arbitrary::Arbitrary, prop_assert, prop_assert_eq}; +use std::ops::Range; #[cfg(test)] use std::str::FromStr; use thiserror::Error; @@ -105,7 +106,7 @@ impl ParserError { /// closely. The major thing we do here is convert [`lalrpop`]'s notion of a location, /// which is just an offset that it got from the lexer, into an actual location that /// we can use in our [`Diagnostic`]s. - fn convert(file_idx: usize, err: ParseError) -> Self { + fn convert(file_idx: usize, err: ParseError) -> Self { match err { ParseError::InvalidToken { location } => { ParserError::InvalidToken(Location::new(file_idx, location..location + 1)) @@ -123,11 +124,7 @@ impl ParserError { ParseError::ExtraToken { token: (start, token, end), } => ParserError::ExtraToken(Location::new(file_idx, start..end), token), - ParseError::User { error } => match error { - LexerError::LexFailure(offset) => { - ParserError::LexFailure(Location::new(file_idx, offset..offset + 1)) - } - }, + ParseError::User { error } => error, } } } @@ -236,7 +233,7 @@ impl Program { pub fn parse(file_idx: usize, buffer: &str) -> Result { let lexer = Token::lexer(buffer) .spanned() - .map(|(token, range)| (range.start, token, range.end)); + .map(|x| permute_lexer_result(file_idx, x)); ProgramParser::new() .parse(file_idx, lexer) .map_err(|e| ParserError::convert(file_idx, e)) @@ -253,13 +250,25 @@ impl TopLevel { pub fn parse(file_idx: usize, buffer: &str) -> Result { let lexer = Token::lexer(buffer) .spanned() - .map(|(token, range)| (range.start, token, range.end)); + .map(|x| permute_lexer_result(file_idx, x)); TopLevelParser::new() .parse(file_idx, lexer) .map_err(|e| ParserError::convert(file_idx, e)) } } +fn permute_lexer_result( + file_idx: usize, + result: (Result, Range), +) -> Result<(usize, Token, usize), ParserError> { + let (token, range) = result; + + match token { + Ok(v) => Ok((range.start, v, range.end)), + Err(()) => Err(ParserError::LexFailure(Location::new(file_idx, range))), + } +} + #[cfg(test)] impl FromStr for Program { type Err = ParserError; diff --git a/src/syntax/arbitrary.rs b/src/syntax/arbitrary.rs index 7ee27a1..cbe03ad 100644 --- a/src/syntax/arbitrary.rs +++ b/src/syntax/arbitrary.rs @@ -8,11 +8,12 @@ use proptest::{ use std::collections::HashMap; use std::ops::Range; -const VALID_VARIABLE_NAMES: &str = r"[a-z][a-zA-Z0-9_]*"; +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), ("-", 1), ("-", 2), ("*", 2), ("/", 2)] } @@ -209,6 +210,7 @@ impl Arbitrary for Value { (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), diff --git a/src/syntax/eval.rs b/src/syntax/eval.rs index 8025d07..5813702 100644 --- a/src/syntax/eval.rs +++ b/src/syntax/eval.rs @@ -66,6 +66,7 @@ impl Expression { super::Value::Number(_, ty, v) => match ty { None => Ok(Value::U64(*v)), // FIXME: make these types validate their input size + Some(ConstantType::Void) => Ok(Value::Void), Some(ConstantType::I8) => Ok(Value::I8(*v as i8)), Some(ConstantType::I16) => Ok(Value::I16(*v as i16)), Some(ConstantType::I32) => Ok(Value::I32(*v as i32)), diff --git a/src/syntax/parser.lalrpop b/src/syntax/parser.lalrpop index d433571..40987f3 100644 --- a/src/syntax/parser.lalrpop +++ b/src/syntax/parser.lalrpop @@ -8,7 +8,7 @@ //! (Although, at some point, things can become so complicated that you might //! eventually want to leave lalrpop behind.) //! -use crate::syntax::{LexerError, Location}; +use crate::syntax::{Location, ParserError}; use crate::syntax::ast::{Program,TopLevel,Statement,Expression,Value,Name}; use crate::syntax::tokens::{ConstantType, Token}; use internment::ArcIntern; @@ -24,7 +24,7 @@ grammar(file_idx: usize); extern { type Location = usize; // Logos, our lexer, implements locations as // offsets from the start of the file. - type Error = LexerError; + type Error = ParserError; // here we redeclare all of the tokens. enum Token { diff --git a/src/syntax/pretty.rs b/src/syntax/pretty.rs index c52a827..9bb0461 100644 --- a/src/syntax/pretty.rs +++ b/src/syntax/pretty.rs @@ -135,6 +135,7 @@ where fn type_suffix(x: &Option) -> &'static str { match x { None => "", + Some(ConstantType::Void) => "", Some(ConstantType::I8) => "i8", Some(ConstantType::I16) => "i16", Some(ConstantType::I32) => "i32", diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index f6da79e..5a2bb99 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -1,7 +1,6 @@ use internment::ArcIntern; use logos::{Lexer, Logos}; use std::fmt; -use std::num::ParseIntError; use thiserror::Error; /// A single token of the input stream; used to help the parsing go down @@ -26,6 +25,12 @@ use thiserror::Error; /// trait, you should get back the exact same token. #[derive(Logos, Clone, Debug, PartialEq, Eq)] pub enum Token { + // we're actually just going to skip whitespace, though + #[regex(r"[ \t\r\n\f]+", logos::skip)] + // this is an extremely simple version of comments, just line + // comments. More complicated /* */ comments can be harder to + // implement, and didn't seem worth it at the time. + #[regex(r"//.*", logos::skip)] // Our first set of tokens are simple characters that we're // going to use to structure NGR programs. #[token("=")] @@ -87,18 +92,6 @@ pub enum Token { // letter, too. #[regex(r"[a-z][a-zA-Z0-9_]*", |v| ArcIntern::new(v.slice().to_string()))] Variable(ArcIntern), - - // the next token will be an error token - #[error] - // we're actually just going to skip whitespace, though - #[regex(r"[ \t\r\n\f]+", logos::skip)] - // this is an extremely simple version of comments, just line - // comments. More complicated /* */ comments can be harder to - // implement, and didn't seem worth it at the time. - #[regex(r"//.*", logos::skip)] - /// This token represents that some core error happened in lexing; - /// possibly that something didn't match anything at all. - Error, } impl fmt::Display for Token { @@ -137,7 +130,6 @@ impl fmt::Display for Token { ) } Token::Variable(s) => write!(f, "'{}'", s), - Token::Error => write!(f, ""), } } } @@ -171,11 +163,13 @@ pub enum ConstantType { I16 = 21, I32 = 22, I64 = 23, + Void = 255, } impl From for cranelift_codegen::ir::Type { fn from(value: ConstantType) -> Self { match value { + ConstantType::Void => cranelift_codegen::ir::types::I64, ConstantType::I8 | ConstantType::U8 => cranelift_codegen::ir::types::I8, ConstantType::I16 | ConstantType::U16 => cranelift_codegen::ir::types::I16, ConstantType::I32 | ConstantType::U32 => cranelift_codegen::ir::types::I32, @@ -196,6 +190,7 @@ impl ConstantType { /// Return the set of types that can be safely casted into this type. pub fn safe_casts_to(self) -> Vec { match self { + ConstantType::Void => vec![ConstantType::Void], ConstantType::I8 => vec![ConstantType::I8], ConstantType::I16 => vec![ConstantType::I16, ConstantType::I8, ConstantType::U8], ConstantType::I32 => vec![ @@ -243,6 +238,7 @@ impl ConstantType { /// Return the name of the given type, as a string pub fn name(&self) -> String { match self { + ConstantType::Void => "void".to_string(), ConstantType::I8 => "i8".to_string(), ConstantType::I16 => "i16".to_string(), ConstantType::I32 => "i32".to_string(), @@ -286,7 +282,7 @@ impl TryFrom for ConstantType { fn parse_number( base: Option, value: &Lexer, -) -> Result<(Option, Option, u64), ParseIntError> { +) -> Result<(Option, Option, u64), ()> { let (radix, strval) = match base { None => (10, value.slice()), Some(radix) => (radix, &value.slice()[2..]), @@ -312,13 +308,14 @@ fn parse_number( (None, strval) }; - let intval = u64::from_str_radix(strval, radix as u32)?; + let intval = u64::from_str_radix(strval, radix as u32).map_err(|_| ())?; Ok((base, declared_type, intval)) } fn display_optional_type(otype: &Option) -> &'static str { match otype { None => "", + Some(ConstantType::Void) => "void", Some(ConstantType::I8) => "i8", Some(ConstantType::I16) => "i16", Some(ConstantType::I32) => "i32", @@ -333,18 +330,18 @@ fn display_optional_type(otype: &Option) -> &'static str { #[test] fn lex_numbers() { let mut lex0 = Token::lexer("12 0b1100 0o14 0d12 0xc 12u8 0xci64// 9"); - assert_eq!(lex0.next(), Some(Token::Number((None, None, 12)))); - assert_eq!(lex0.next(), Some(Token::Number((Some(2), None, 12)))); - assert_eq!(lex0.next(), Some(Token::Number((Some(8), None, 12)))); - assert_eq!(lex0.next(), Some(Token::Number((Some(10), None, 12)))); - assert_eq!(lex0.next(), Some(Token::Number((Some(16), None, 12)))); + assert_eq!(lex0.next(), Some(Ok(Token::Number((None, None, 12))))); + assert_eq!(lex0.next(), Some(Ok(Token::Number((Some(2), None, 12))))); + assert_eq!(lex0.next(), Some(Ok(Token::Number((Some(8), None, 12))))); + assert_eq!(lex0.next(), Some(Ok(Token::Number((Some(10), None, 12))))); + assert_eq!(lex0.next(), Some(Ok(Token::Number((Some(16), None, 12))))); assert_eq!( lex0.next(), - Some(Token::Number((None, Some(ConstantType::U8), 12))) + Some(Ok(Token::Number((None, Some(ConstantType::U8), 12)))) ); assert_eq!( lex0.next(), - Some(Token::Number((Some(16), Some(ConstantType::I64), 12))) + Some(Ok(Token::Number((Some(16), Some(ConstantType::I64), 12)))) ); assert_eq!(lex0.next(), None); } @@ -352,46 +349,52 @@ fn lex_numbers() { #[test] fn lex_symbols() { let mut lex0 = Token::lexer("x + \t y * \n z // rest"); - assert_eq!(lex0.next(), Some(Token::var("x"))); - assert_eq!(lex0.next(), Some(Token::Operator('+'))); - assert_eq!(lex0.next(), Some(Token::var("y"))); - assert_eq!(lex0.next(), Some(Token::Operator('*'))); - assert_eq!(lex0.next(), Some(Token::var("z"))); + assert_eq!(lex0.next(), Some(Ok(Token::var("x")))); + assert_eq!(lex0.next(), Some(Ok(Token::Operator('+')))); + assert_eq!(lex0.next(), Some(Ok(Token::var("y")))); + assert_eq!(lex0.next(), Some(Ok(Token::Operator('*')))); + assert_eq!(lex0.next(), Some(Ok(Token::var("z")))); assert_eq!(lex0.next(), None); } #[test] fn lexer_spans() { let mut lex0 = Token::lexer("y = x + 1//foo").spanned(); - assert_eq!(lex0.next(), Some((Token::var("y"), 0..1))); - assert_eq!(lex0.next(), Some((Token::Equals, 2..3))); - assert_eq!(lex0.next(), Some((Token::var("x"), 4..5))); - assert_eq!(lex0.next(), Some((Token::Operator('+'), 6..7))); - assert_eq!(lex0.next(), Some((Token::Number((None, None, 1)), 8..9))); + assert_eq!(lex0.next(), Some((Ok(Token::var("y")), 0..1))); + assert_eq!(lex0.next(), Some((Ok(Token::Equals), 2..3))); + assert_eq!(lex0.next(), Some((Ok(Token::var("x")), 4..5))); + assert_eq!(lex0.next(), Some((Ok(Token::Operator('+')), 6..7))); + assert_eq!( + lex0.next(), + Some((Ok(Token::Number((None, None, 1))), 8..9)) + ); assert_eq!(lex0.next(), None); } #[test] fn further_spans() { let mut lex0 = Token::lexer("x = 2i64 + 2i64;\ny = -x;\nprint y;").spanned(); - assert_eq!(lex0.next(), Some((Token::var("x"), 0..1))); - assert_eq!(lex0.next(), Some((Token::Equals, 2..3))); + assert_eq!(lex0.next(), Some((Ok(Token::var("x")), 0..1))); + assert_eq!(lex0.next(), Some((Ok(Token::Equals), 2..3))); assert_eq!( lex0.next(), - Some((Token::Number((None, Some(ConstantType::I64), 2)), 4..8)) + Some((Ok(Token::Number((None, Some(ConstantType::I64), 2))), 4..8)) ); - assert_eq!(lex0.next(), Some((Token::Operator('+'), 9..10))); + assert_eq!(lex0.next(), Some((Ok(Token::Operator('+')), 9..10))); assert_eq!( lex0.next(), - Some((Token::Number((None, Some(ConstantType::I64), 2)), 11..15)) + Some(( + Ok(Token::Number((None, Some(ConstantType::I64), 2))), + 11..15 + )) ); - assert_eq!(lex0.next(), Some((Token::Semi, 15..16))); - assert_eq!(lex0.next(), Some((Token::var("y"), 17..18))); - assert_eq!(lex0.next(), Some((Token::Equals, 19..20))); - assert_eq!(lex0.next(), Some((Token::Operator('-'), 21..22))); - assert_eq!(lex0.next(), Some((Token::var("x"), 22..23))); - assert_eq!(lex0.next(), Some((Token::Semi, 23..24))); - assert_eq!(lex0.next(), Some((Token::Print, 25..30))); - assert_eq!(lex0.next(), Some((Token::var("y"), 31..32))); - assert_eq!(lex0.next(), Some((Token::Semi, 32..33))); + assert_eq!(lex0.next(), Some((Ok(Token::Semi), 15..16))); + assert_eq!(lex0.next(), Some((Ok(Token::var("y")), 17..18))); + assert_eq!(lex0.next(), Some((Ok(Token::Equals), 19..20))); + assert_eq!(lex0.next(), Some((Ok(Token::Operator('-')), 21..22))); + assert_eq!(lex0.next(), Some((Ok(Token::var("x")), 22..23))); + assert_eq!(lex0.next(), Some((Ok(Token::Semi), 23..24))); + assert_eq!(lex0.next(), Some((Ok(Token::Print), 25..30))); + assert_eq!(lex0.next(), Some((Ok(Token::var("y")), 31..32))); + assert_eq!(lex0.next(), Some((Ok(Token::Semi), 32..33))); } diff --git a/src/type_infer/convert.rs b/src/type_infer/convert.rs index 5cfa210..3e968b3 100644 --- a/src/type_infer/convert.rs +++ b/src/type_infer/convert.rs @@ -129,6 +129,7 @@ fn convert_statement( syntax::Statement::Binding(loc, name, expr) => { let (expr, ty) = convert_expression(expr, constraint_db, renames, bindings); let final_name = finalize_name(bindings, renames, name); + bindings.insert(final_name.clone(), ty.clone()); ir::Expression::Bind(loc, final_name, ty, Box::new(expr)) } @@ -168,6 +169,10 @@ fn convert_expression( )); (newval, newtype) } + Some(ConstantType::Void) => ( + ir::Value::Void, + ir::TypeOrVar::Primitive(PrimitiveType::Void), + ), Some(ConstantType::U8) => ( ir::Value::U8(base, value as u8), ir::TypeOrVar::Primitive(PrimitiveType::U8), diff --git a/src/type_infer/finalize.rs b/src/type_infer/finalize.rs index 59ff329..d681c34 100644 --- a/src/type_infer/finalize.rs +++ b/src/type_infer/finalize.rs @@ -174,6 +174,11 @@ fn finalize_val_or_ref( assert!(matches!(new_type, Type::Primitive(PrimitiveType::I64))); ValueOrRef::Value(loc, new_type, Value::I64(base, value)) } + + Value::Void => { + assert!(matches!(new_type, Type::Primitive(PrimitiveType::Void))); + ValueOrRef::Value(loc, new_type, Value::Void) + } } } } diff --git a/src/util/scoped_map.rs b/src/util/scoped_map.rs index 34e968b..83e54d4 100644 --- a/src/util/scoped_map.rs +++ b/src/util/scoped_map.rs @@ -1,4 +1,6 @@ -use std::{borrow::Borrow, collections::HashMap, hash::Hash}; +use std::borrow::Borrow; +use std::collections::HashMap; +use std::hash::Hash; /// A version of [`std::collections::HashMap`] with a built-in notion of scope. #[derive(Clone)] @@ -105,4 +107,28 @@ impl ScopedMap { ScopedMap { scopes } } + + /// Returns true if this map is completely empty, at every level of + /// scope. + pub fn is_empty(&self) -> bool { + self.scopes.iter().all(|x| x.is_empty()) + } +} + +impl ScopedMap { + /// Returns the set of all variables bound at this time, with shadowed + /// variables hidden. + pub fn bindings(&self) -> HashMap { + let mut result = HashMap::new(); + + for scope in self.scopes.iter().rev() { + for (key, value) in scope.iter() { + if !result.contains_key(key) { + result.insert(key.clone(), value.clone()); + } + } + } + + result + } }