λ Support functions! #5

Open
acw wants to merge 59 commits from awick/functions into develop
21 changed files with 759 additions and 153 deletions
Showing only changes of commit e5db6640f2 - Show all commits

View File

@@ -9,25 +9,27 @@ name = "ngr"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
clap = { version = "^3.0.14", features = ["derive"] } clap = { version = "4.4.11", features = ["derive"] }
codespan = "0.11.1" codespan = "0.11.1"
codespan-reporting = "0.11.1" codespan-reporting = "0.11.1"
cranelift-codegen = "0.99.2" cranelift-codegen = "0.103.0"
cranelift-jit = "0.99.2" cranelift-jit = "0.103.0"
cranelift-frontend = "0.99.2" cranelift-frontend = "0.103.0"
cranelift-module = "0.99.2" cranelift-module = "0.103.0"
cranelift-native = "0.99.2" cranelift-native = "0.103.0"
cranelift-object = "0.99.2" cranelift-object = "0.103.0"
internment = { version = "0.7.0", default-features = false, features = ["arc"] } internment = { version = "0.7.4", default-features = false, features = ["arc"] }
lalrpop-util = "^0.20.0" lalrpop-util = "0.20.0"
lazy_static = "^1.4.0" lazy_static = "1.4.0"
logos = "^0.12.0" logos = "0.13.0"
pretty = { version = "^0.11.2", features = ["termcolor"] } pretty = { version = "0.12.3", features = ["termcolor"] }
proptest = "^1.0.0" proptest = "1.4.0"
rustyline = "^11.0.0" rand = "0.8.5"
target-lexicon = "^0.12.5" rustyline = "13.0.0"
tempfile = "^3.5.0" target-lexicon = "0.12.12"
thiserror = "^1.0.30" tempfile = "3.8.1"
thiserror = "1.0.52"
anyhow = "1.0.77"
[build-dependencies] [build-dependencies]
lalrpop = "^0.20.0" lalrpop = "0.20.0"

View File

@@ -72,13 +72,28 @@ fn generate_tests(f: &mut File, path_so_far: PathBuf) -> std::io::Result<()> {
f, f,
" assert_eq!(errors.len(), 0, \"file should have no validation errors\");" " assert_eq!(errors.len(), 0, \"file should have no validation errors\");"
)?; )?;
writeln!(f, " let syntax_result = syntax.eval();")?;
writeln!( writeln!(
f, f,
" let ir = syntax.type_infer().expect(\"example is typed correctly\");" " let ir = syntax.type_infer().expect(\"example is typed correctly\");"
)?; )?;
writeln!(f, " let ir_result = ir.eval();")?; 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::<JITModule>::eval(ir);")?; writeln!(f, " let compiled_result = Backend::<JITModule>::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, "}}")?; writeln!(f, "}}")?;
} }

View File

@@ -1,8 +1,6 @@
use crate::backend::Backend; use crate::backend::Backend;
use crate::eval::EvalError; use crate::eval::EvalError;
use crate::ir::{Expression, Program, TopLevel, Type}; use crate::ir::{Expression, Program, TopLevel, Type};
#[cfg(test)]
use crate::syntax::arbitrary::GenerationEnvironment;
use crate::syntax::Location; use crate::syntax::Location;
use cranelift_jit::JITModule; use cranelift_jit::JITModule;
use cranelift_object::ObjectModule; use cranelift_object::ObjectModule;
@@ -176,7 +174,7 @@ proptest::proptest! {
// without error, assuming any possible input ... well, any possible input that // without error, assuming any possible input ... well, any possible input that
// doesn't involve overflow or underflow. // doesn't involve overflow or underflow.
#[test] #[test]
fn static_backend(program in Program::arbitrary_with(GenerationEnvironment::new(false))) { fn static_backend(program in Program::arbitrary()) {
use crate::eval::PrimOpError; use crate::eval::PrimOpError;
let basic_result = program.eval().map(|(_,x)| x); 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 // without error, assuming any possible input ... well, any possible input that
// doesn't involve overflow or underflow. // doesn't involve overflow or underflow.
#[test] #[test]
fn jit_backend(program in Program::arbitrary_with(GenerationEnvironment::new(false))) { fn jit_backend(program in Program::arbitrary()) {
use crate::eval::PrimOpError; use crate::eval::PrimOpError;
// use pretty::{DocAllocator, Pretty}; // use pretty::{DocAllocator, Pretty};
// let allocator = pretty::BoxAllocator; // let allocator = pretty::BoxAllocator;

View File

@@ -338,7 +338,7 @@ impl<M: Module> Backend<M> {
let vtype_repr = builder.ins().iconst(types::I64, vtype as i64); let vtype_repr = builder.ins().iconst(types::I64, vtype as i64);
let casted_val = match vtype { let casted_val = match vtype {
ConstantType::U64 | ConstantType::I64 => val, ConstantType::U64 | ConstantType::I64 | ConstantType::Void => val,
ConstantType::I8 | ConstantType::I16 | ConstantType::I32 => { ConstantType::I8 | ConstantType::I16 | ConstantType::I32 => {
builder.ins().sextend(types::I64, val) builder.ins().sextend(types::I64, val)
} }
@@ -401,6 +401,7 @@ impl<M: Module> Backend<M> {
builder.ins().iconst(types::I64, v as i64), builder.ins().iconst(types::I64, v as i64),
ConstantType::U64, ConstantType::U64,
)), )),
Value::Void => Ok((builder.ins().iconst(types::I64, 0i64), ConstantType::Void)),
}, },
ValueOrRef::Ref(_, _, name) => match variables.get(&name) { ValueOrRef::Ref(_, _, name) => match variables.get(&name) {
None => Err(BackendError::VariableLookupFailure(name)), None => Err(BackendError::VariableLookupFailure(name)),

View File

@@ -110,6 +110,7 @@ extern "C" fn runtime_print(
let reconstituted = cstr.to_string_lossy(); let reconstituted = cstr.to_string_lossy();
let output = match vtype_repr.try_into() { let output = match vtype_repr.try_into() {
Ok(ConstantType::Void) => format!("{} = <void>", reconstituted),
Ok(ConstantType::I8) => format!("{} = {}i8", reconstituted, value as i8), Ok(ConstantType::I8) => format!("{} = {}i8", reconstituted, value as i8),
Ok(ConstantType::I16) => format!("{} = {}i16", reconstituted, value as i16), Ok(ConstantType::I16) => format!("{} = {}i16", reconstituted, value as i16),
Ok(ConstantType::I32) => format!("{} = {}i32", reconstituted, value as i32), Ok(ConstantType::I32) => format!("{} = {}i32", reconstituted, value as i32),

74
src/bin/ngrun.rs Normal file
View File

@@ -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<E>(result: (Value<E>, 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!();
}

View File

@@ -43,7 +43,9 @@ impl<IR1: Clone,IR2: Clone> PartialEq<PrimOpError<IR2>> for PrimOpError<IR1> {
(PrimOpError::TypeMismatch(a, b, c), PrimOpError::TypeMismatch(x, y, z)) => { (PrimOpError::TypeMismatch(a, b, c), PrimOpError::TypeMismatch(x, y, z)) => {
a == x && b.strip() == y.strip() && c.strip() == z.strip() 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::BadArgCount(a, b), PrimOpError::BadArgCount(x, y)) => a == x && b == y,
(PrimOpError::UnknownPrimOp(a), PrimOpError::UnknownPrimOp(x)) => a == x, (PrimOpError::UnknownPrimOp(a), PrimOpError::UnknownPrimOp(x)) => a == x,
( (

View File

@@ -63,6 +63,7 @@ impl<'a, IR> TryFrom<&'a Value<IR>> for PrimitiveType {
impl From<ConstantType> for PrimitiveType { impl From<ConstantType> for PrimitiveType {
fn from(value: ConstantType) -> Self { fn from(value: ConstantType) -> Self {
match value { match value {
ConstantType::Void => PrimitiveType::Void,
ConstantType::I8 => PrimitiveType::I8, ConstantType::I8 => PrimitiveType::I8,
ConstantType::I16 => PrimitiveType::I16, ConstantType::I16 => PrimitiveType::I16,
ConstantType::I32 => PrimitiveType::I32, ConstantType::I32 => PrimitiveType::I32,
@@ -100,44 +101,43 @@ impl FromStr for PrimitiveType {
} }
impl 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. /// Return true if this type can be safely cast into the target type.
pub fn can_cast_to(&self, target: &PrimitiveType) -> bool { pub fn can_cast_to(&self, target: &PrimitiveType) -> bool {
match self { self.allowed_casts().contains(target)
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,
}
} }
/// Try to cast the given value to this type, returning the new value. /// 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), 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)]
}
}
}
} }

View File

@@ -44,9 +44,7 @@ impl<IR: Clone> Value<IR> {
Value::I32(x) => Value::I32(*x), Value::I32(x) => Value::I32(*x),
Value::I64(x) => Value::I64(*x), Value::I64(x) => Value::I64(*x),
Value::Closure(name, env, args, _) => { Value::Closure(name, env, args, _) => {
let new_env = env let new_env = env.clone().map_values(|x| x.strip());
.clone()
.map_values(|x| x.strip());
Value::Closure(name.clone(), new_env, args.clone(), ()) Value::Closure(name.clone(), new_env, args.clone(), ())
} }
} }

View File

@@ -1,21 +1,433 @@
use crate::ir::{Program, TopLevel, Expression, ValueOrRef, Value, Type}; use crate::eval::PrimitiveType;
use proptest::{ use crate::ir::{Expression, Primitive, Program, TopLevel, Type, Value, ValueOrRef, Variable};
prelude::Arbitrary, use crate::syntax::Location;
strategy::{BoxedStrategy, Strategy}, 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;
lazy_static::lazy_static! {
static ref PROGRAM_LENGTH_DISTRIBUTION: WeightedIndex<usize> = 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();
static ref BLOCK_LENGTH_DISTRIBUTION: WeightedIndex<usize> = 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<usize> = 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<usize> = WeightedIndex::new(
STATEMENT_TYPE_FREQUENCIES.iter().map(|x| x.1)
).unwrap();
static ref EXPRESSION_TYPE_DISTRIBUTION: WeightedIndex<usize> = WeightedIndex::new(
EXPRESSION_TYPE_FREQUENCIES.iter().map(|x| x.1)
).unwrap();
static ref ARGUMENT_TYPE_DISTRIBUTION: WeightedIndex<usize> = WeightedIndex::new(
ARGUMENT_TYPE_FREQUENCIES.iter().map(|x| x.1)
).unwrap();
static ref VALUE_TYPE_DISTRIBUTION: WeightedIndex<usize> = 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<PrimitiveType> 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<Type>;
fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
NewTree::<ProgramGenerator>::Ok(ProgramTree::new(runner.new_rng()))
}
}
pub struct ProgramTree {
_rng: TestRng,
current: Program<Type>,
}
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<Type>;
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<Type>;
fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
NewTree::<ExpressionGenerator>::Ok(ExpressionTree::new(runner.new_rng()))
}
}
struct ExpressionTree {
_rng: TestRng,
current: Expression<Type>,
}
impl ValueTree for ExpressionTree {
type Value = Expression<Type>;
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<Variable, Type>,
) -> Expression<Type> {
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::<Vec<Variable>>();
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<Variable, Type>,
) -> Expression<Type> {
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<Variable, Type>,
target_type: Option<PrimitiveType>,
) -> ValueOrRef<Type> {
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
}; };
impl<Type: core::fmt::Debug> Arbitrary for Program<Type> { // generate a constant
type Parameters = crate::syntax::arbitrary::GenerationEnvironment; let val = match value_type {
type Strategy = BoxedStrategy<Self>; 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,
};
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { ValueOrRef::Value(Location::manufactured(), val.type_of(), val)
unimplemented!() } else {
//crate::syntax::Program::arbitrary_with(args) // generate a reference
// .prop_map(|x| { let weighted_keys = bindings.keys().map(|x| (1, x)).collect::<Vec<_>>();
// x.type_infer() let distribution = WeightedIndex::new(weighted_keys.iter().map(|x| x.0)).unwrap();
// .expect("arbitrary_with should generate type-correct programs") let var = weighted_keys[distribution.sample(rng)].1.clone();
// }) let ty = bindings
// .boxed() .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()
}

View File

@@ -5,8 +5,11 @@ use crate::{
}; };
use internment::ArcIntern; use internment::ArcIntern;
use pretty::{BoxAllocator, DocAllocator, Pretty}; use pretty::{BoxAllocator, DocAllocator, Pretty};
use proptest::arbitrary::Arbitrary;
use std::{fmt, str::FromStr, sync::atomic::AtomicUsize}; use std::{fmt, str::FromStr, sync::atomic::AtomicUsize};
use super::arbitrary::ProgramGenerator;
/// We're going to represent variables as interned strings. /// 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 /// 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 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, /// 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. /// it's easiest to just make it an argument.
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct Program<Type> { pub struct Program<Type> {
// For now, a program is just a vector of statements. In the future, we'll probably // 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. // 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> {
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. /// A thing that can sit at the top level of a file.
/// ///
/// For the moment, these are statements and functions. Other things /// For the moment, these are statements and functions. Other things
/// will likely be added in the future, but for now: just statements /// will likely be added in the future, but for now: just statements
/// and functions /// and functions
#[derive(Debug)] #[derive(Clone, Debug)]
pub enum TopLevel<Type> { pub enum TopLevel<Type> {
Statement(Expression<Type>), Statement(Expression<Type>),
Function(Variable, Vec<(Variable, Type)>, Type, Expression<Type>), Function(Variable, Vec<(Variable, Type)>, Type, Expression<Type>),
@@ -140,8 +152,7 @@ impl<Type: Clone + TypeWithVoid> Expression<Type> {
/// computed. /// computed.
pub fn type_of(&self) -> Type { pub fn type_of(&self) -> Type {
match self { match self {
Expression::Atomic(ValueOrRef::Ref(_, t, _)) => t.clone(), Expression::Atomic(x) => x.type_of(),
Expression::Atomic(ValueOrRef::Value(_, t, _)) => t.clone(),
Expression::Cast(_, t, _) => t.clone(), Expression::Cast(_, t, _) => t.clone(),
Expression::Primitive(_, t, _, _) => t.clone(), Expression::Primitive(_, t, _, _) => t.clone(),
Expression::Block(_, t, _) => t.clone(), Expression::Block(_, t, _) => t.clone(),
@@ -300,6 +311,15 @@ where
} }
} }
impl<Type: Clone> ValueOrRef<Type> {
pub fn type_of(&self) -> Type {
match self {
ValueOrRef::Ref(_, t, _) => t.clone(),
ValueOrRef::Value(_, t, _) => t.clone(),
}
}
}
impl<Type> From<ValueOrRef<Type>> for Expression<Type> { impl<Type> From<ValueOrRef<Type>> for Expression<Type> {
fn from(value: ValueOrRef<Type>) -> Self { fn from(value: ValueOrRef<Type>) -> Self {
Expression::Atomic(value) Expression::Atomic(value)
@@ -322,6 +342,7 @@ pub enum Value {
U16(Option<u8>, u16), U16(Option<u8>, u16),
U32(Option<u8>, u32), U32(Option<u8>, u32),
U64(Option<u8>, u64), U64(Option<u8>, u64),
Void,
} }
impl Value { impl Value {
@@ -336,6 +357,7 @@ impl Value {
Value::U16(_, _) => Type::Primitive(PrimitiveType::U16), Value::U16(_, _) => Type::Primitive(PrimitiveType::U16),
Value::U32(_, _) => Type::Primitive(PrimitiveType::U32), Value::U32(_, _) => Type::Primitive(PrimitiveType::U32),
Value::U64(_, _) => Type::Primitive(PrimitiveType::U64), Value::U64(_, _) => Type::Primitive(PrimitiveType::U64),
Value::Void => Type::void(),
} }
} }
} }
@@ -379,6 +401,7 @@ where
pretty_internal(opt_base, *value as u64, ConstantType::U32) pretty_internal(opt_base, *value as u64, ConstantType::U32)
} }
Value::U64(opt_base, value) => pretty_internal(opt_base, *value, ConstantType::U64), Value::U64(opt_base, value) => pretty_internal(opt_base, *value, ConstantType::U64),
Value::Void => allocator.text("<void>"),
} }
} }
} }
@@ -389,6 +412,14 @@ pub enum Type {
Function(Vec<Type>, Box<Type>), Function(Vec<Type>, Box<Type>),
} }
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 impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Type
where where
A: 'a, A: 'a,

View File

@@ -32,7 +32,7 @@ impl<T: Clone + Into<Type>> Program<T> {
} }
TopLevel::Statement(expr) => { 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( fn eval(
&self, &self,
env: &ScopedMap<Variable, IRValue<T>>, env: &mut ScopedMap<Variable, IRValue<T>>,
stdout: &mut String, stdout: &mut String,
) -> Result<IRValue<T>, IREvalError<T>> { ) -> Result<IRValue<T>, IREvalError<T>> {
match self { match self {
@@ -79,8 +79,14 @@ where
} }
} }
Expression::Block(_, _, _) => { Expression::Block(_, _, stmts) => {
unimplemented!() let mut result = Value::Void;
for stmt in stmts.iter() {
result = stmt.eval(env, stdout)?;
}
Ok(result)
} }
Expression::Print(loc, n) => { Expression::Print(loc, n) => {
@@ -92,16 +98,17 @@ where
Ok(Value::Void) 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<T: Clone> ValueOrRef<T> { impl<T: Clone> ValueOrRef<T> {
fn eval( fn eval(&self, env: &ScopedMap<Variable, IRValue<T>>) -> Result<IRValue<T>, IREvalError<T>> {
&self,
env: &ScopedMap<Variable, IRValue<T>>,
) -> Result<IRValue<T>, IREvalError<T>> {
match self { match self {
ValueOrRef::Value(_, _, v) => match v { ValueOrRef::Value(_, _, v) => match v {
super::Value::I8(_, v) => Ok(Value::I8(*v)), super::Value::I8(_, v) => Ok(Value::I8(*v)),
@@ -112,6 +119,7 @@ impl<T: Clone> ValueOrRef<T> {
super::Value::U16(_, v) => Ok(Value::U16(*v)), super::Value::U16(_, v) => Ok(Value::U16(*v)),
super::Value::U32(_, v) => Ok(Value::U32(*v)), super::Value::U32(_, v) => Ok(Value::U32(*v)),
super::Value::U64(_, v) => Ok(Value::U64(*v)), super::Value::U64(_, v) => Ok(Value::U64(*v)),
super::Value::Void => Ok(Value::Void),
}, },
ValueOrRef::Ref(loc, _, n) => env ValueOrRef::Ref(loc, _, n) => env

View File

@@ -51,6 +51,7 @@ use ::pretty::{Arena, Pretty};
use lalrpop_util::ParseError; use lalrpop_util::ParseError;
#[cfg(test)] #[cfg(test)]
use proptest::{arbitrary::Arbitrary, prop_assert, prop_assert_eq}; use proptest::{arbitrary::Arbitrary, prop_assert, prop_assert_eq};
use std::ops::Range;
#[cfg(test)] #[cfg(test)]
use std::str::FromStr; use std::str::FromStr;
use thiserror::Error; use thiserror::Error;
@@ -105,7 +106,7 @@ impl ParserError {
/// closely. The major thing we do here is convert [`lalrpop`]'s notion of a location, /// 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 /// which is just an offset that it got from the lexer, into an actual location that
/// we can use in our [`Diagnostic`]s. /// we can use in our [`Diagnostic`]s.
fn convert(file_idx: usize, err: ParseError<usize, Token, LexerError>) -> Self { fn convert(file_idx: usize, err: ParseError<usize, Token, ParserError>) -> Self {
match err { match err {
ParseError::InvalidToken { location } => { ParseError::InvalidToken { location } => {
ParserError::InvalidToken(Location::new(file_idx, location..location + 1)) ParserError::InvalidToken(Location::new(file_idx, location..location + 1))
@@ -123,11 +124,7 @@ impl ParserError {
ParseError::ExtraToken { ParseError::ExtraToken {
token: (start, token, end), token: (start, token, end),
} => ParserError::ExtraToken(Location::new(file_idx, start..end), token), } => ParserError::ExtraToken(Location::new(file_idx, start..end), token),
ParseError::User { error } => match error { ParseError::User { error } => error,
LexerError::LexFailure(offset) => {
ParserError::LexFailure(Location::new(file_idx, offset..offset + 1))
}
},
} }
} }
} }
@@ -236,7 +233,7 @@ impl Program {
pub fn parse(file_idx: usize, buffer: &str) -> Result<Program, ParserError> { pub fn parse(file_idx: usize, buffer: &str) -> Result<Program, ParserError> {
let lexer = Token::lexer(buffer) let lexer = Token::lexer(buffer)
.spanned() .spanned()
.map(|(token, range)| (range.start, token, range.end)); .map(|x| permute_lexer_result(file_idx, x));
ProgramParser::new() ProgramParser::new()
.parse(file_idx, lexer) .parse(file_idx, lexer)
.map_err(|e| ParserError::convert(file_idx, e)) .map_err(|e| ParserError::convert(file_idx, e))
@@ -253,13 +250,25 @@ impl TopLevel {
pub fn parse(file_idx: usize, buffer: &str) -> Result<TopLevel, ParserError> { pub fn parse(file_idx: usize, buffer: &str) -> Result<TopLevel, ParserError> {
let lexer = Token::lexer(buffer) let lexer = Token::lexer(buffer)
.spanned() .spanned()
.map(|(token, range)| (range.start, token, range.end)); .map(|x| permute_lexer_result(file_idx, x));
TopLevelParser::new() TopLevelParser::new()
.parse(file_idx, lexer) .parse(file_idx, lexer)
.map_err(|e| ParserError::convert(file_idx, e)) .map_err(|e| ParserError::convert(file_idx, e))
} }
} }
fn permute_lexer_result(
file_idx: usize,
result: (Result<Token, ()>, Range<usize>),
) -> 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)] #[cfg(test)]
impl FromStr for Program { impl FromStr for Program {
type Err = ParserError; type Err = ParserError;

View File

@@ -8,11 +8,12 @@ use proptest::{
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::Range; 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 { impl ConstantType {
fn get_operators(&self) -> &'static [(&'static str, usize)] { fn get_operators(&self) -> &'static [(&'static str, usize)] {
match self { match self {
ConstantType::Void => &[],
ConstantType::I8 | ConstantType::I16 | ConstantType::I32 | ConstantType::I64 => { ConstantType::I8 | ConstantType::I16 | ConstantType::I32 | ConstantType::I64 => {
&[("+", 2), ("-", 1), ("-", 2), ("*", 2), ("/", 2)] &[("+", 2), ("-", 1), ("-", 2), ("*", 2), ("/", 2)]
} }
@@ -209,6 +210,7 @@ impl Arbitrary for Value {
(printed_base_strategy, bool::arbitrary(), value_strategy) (printed_base_strategy, bool::arbitrary(), value_strategy)
.prop_map(move |(base, declare_type, value)| { .prop_map(move |(base, declare_type, value)| {
let converted_value = match genenv.return_type { let converted_value = match genenv.return_type {
ConstantType::Void => value,
ConstantType::I8 => value % (i8::MAX as u64), ConstantType::I8 => value % (i8::MAX as u64),
ConstantType::U8 => value % (u8::MAX as u64), ConstantType::U8 => value % (u8::MAX as u64),
ConstantType::I16 => value % (i16::MAX as u64), ConstantType::I16 => value % (i16::MAX as u64),

View File

@@ -66,6 +66,7 @@ impl Expression {
super::Value::Number(_, ty, v) => match ty { super::Value::Number(_, ty, v) => match ty {
None => Ok(Value::U64(*v)), None => Ok(Value::U64(*v)),
// FIXME: make these types validate their input size // 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::I8) => Ok(Value::I8(*v as i8)),
Some(ConstantType::I16) => Ok(Value::I16(*v as i16)), Some(ConstantType::I16) => Ok(Value::I16(*v as i16)),
Some(ConstantType::I32) => Ok(Value::I32(*v as i32)), Some(ConstantType::I32) => Ok(Value::I32(*v as i32)),

View File

@@ -8,7 +8,7 @@
//! (Although, at some point, things can become so complicated that you might //! (Although, at some point, things can become so complicated that you might
//! eventually want to leave lalrpop behind.) //! 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::ast::{Program,TopLevel,Statement,Expression,Value,Name};
use crate::syntax::tokens::{ConstantType, Token}; use crate::syntax::tokens::{ConstantType, Token};
use internment::ArcIntern; use internment::ArcIntern;
@@ -24,7 +24,7 @@ grammar(file_idx: usize);
extern { extern {
type Location = usize; // Logos, our lexer, implements locations as type Location = usize; // Logos, our lexer, implements locations as
// offsets from the start of the file. // offsets from the start of the file.
type Error = LexerError; type Error = ParserError;
// here we redeclare all of the tokens. // here we redeclare all of the tokens.
enum Token { enum Token {

View File

@@ -135,6 +135,7 @@ where
fn type_suffix(x: &Option<ConstantType>) -> &'static str { fn type_suffix(x: &Option<ConstantType>) -> &'static str {
match x { match x {
None => "", None => "",
Some(ConstantType::Void) => "<void>",
Some(ConstantType::I8) => "i8", Some(ConstantType::I8) => "i8",
Some(ConstantType::I16) => "i16", Some(ConstantType::I16) => "i16",
Some(ConstantType::I32) => "i32", Some(ConstantType::I32) => "i32",

View File

@@ -1,7 +1,6 @@
use internment::ArcIntern; use internment::ArcIntern;
use logos::{Lexer, Logos}; use logos::{Lexer, Logos};
use std::fmt; use std::fmt;
use std::num::ParseIntError;
use thiserror::Error; use thiserror::Error;
/// A single token of the input stream; used to help the parsing go down /// 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. /// trait, you should get back the exact same token.
#[derive(Logos, Clone, Debug, PartialEq, Eq)] #[derive(Logos, Clone, Debug, PartialEq, Eq)]
pub enum Token { 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 // Our first set of tokens are simple characters that we're
// going to use to structure NGR programs. // going to use to structure NGR programs.
#[token("=")] #[token("=")]
@@ -87,18 +92,6 @@ pub enum Token {
// letter, too. // letter, too.
#[regex(r"[a-z][a-zA-Z0-9_]*", |v| ArcIntern::new(v.slice().to_string()))] #[regex(r"[a-z][a-zA-Z0-9_]*", |v| ArcIntern::new(v.slice().to_string()))]
Variable(ArcIntern<String>), Variable(ArcIntern<String>),
// 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 { impl fmt::Display for Token {
@@ -137,7 +130,6 @@ impl fmt::Display for Token {
) )
} }
Token::Variable(s) => write!(f, "'{}'", s), Token::Variable(s) => write!(f, "'{}'", s),
Token::Error => write!(f, "<error>"),
} }
} }
} }
@@ -171,11 +163,13 @@ pub enum ConstantType {
I16 = 21, I16 = 21,
I32 = 22, I32 = 22,
I64 = 23, I64 = 23,
Void = 255,
} }
impl From<ConstantType> for cranelift_codegen::ir::Type { impl From<ConstantType> for cranelift_codegen::ir::Type {
fn from(value: ConstantType) -> Self { fn from(value: ConstantType) -> Self {
match value { match value {
ConstantType::Void => cranelift_codegen::ir::types::I64,
ConstantType::I8 | ConstantType::U8 => cranelift_codegen::ir::types::I8, ConstantType::I8 | ConstantType::U8 => cranelift_codegen::ir::types::I8,
ConstantType::I16 | ConstantType::U16 => cranelift_codegen::ir::types::I16, ConstantType::I16 | ConstantType::U16 => cranelift_codegen::ir::types::I16,
ConstantType::I32 | ConstantType::U32 => cranelift_codegen::ir::types::I32, 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. /// Return the set of types that can be safely casted into this type.
pub fn safe_casts_to(self) -> Vec<ConstantType> { pub fn safe_casts_to(self) -> Vec<ConstantType> {
match self { match self {
ConstantType::Void => vec![ConstantType::Void],
ConstantType::I8 => vec![ConstantType::I8], ConstantType::I8 => vec![ConstantType::I8],
ConstantType::I16 => vec![ConstantType::I16, ConstantType::I8, ConstantType::U8], ConstantType::I16 => vec![ConstantType::I16, ConstantType::I8, ConstantType::U8],
ConstantType::I32 => vec![ ConstantType::I32 => vec![
@@ -243,6 +238,7 @@ impl ConstantType {
/// Return the name of the given type, as a string /// Return the name of the given type, as a string
pub fn name(&self) -> String { pub fn name(&self) -> String {
match self { match self {
ConstantType::Void => "void".to_string(),
ConstantType::I8 => "i8".to_string(), ConstantType::I8 => "i8".to_string(),
ConstantType::I16 => "i16".to_string(), ConstantType::I16 => "i16".to_string(),
ConstantType::I32 => "i32".to_string(), ConstantType::I32 => "i32".to_string(),
@@ -286,7 +282,7 @@ impl TryFrom<i64> for ConstantType {
fn parse_number( fn parse_number(
base: Option<u8>, base: Option<u8>,
value: &Lexer<Token>, value: &Lexer<Token>,
) -> Result<(Option<u8>, Option<ConstantType>, u64), ParseIntError> { ) -> Result<(Option<u8>, Option<ConstantType>, u64), ()> {
let (radix, strval) = match base { let (radix, strval) = match base {
None => (10, value.slice()), None => (10, value.slice()),
Some(radix) => (radix, &value.slice()[2..]), Some(radix) => (radix, &value.slice()[2..]),
@@ -312,13 +308,14 @@ fn parse_number(
(None, strval) (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)) Ok((base, declared_type, intval))
} }
fn display_optional_type(otype: &Option<ConstantType>) -> &'static str { fn display_optional_type(otype: &Option<ConstantType>) -> &'static str {
match otype { match otype {
None => "", None => "",
Some(ConstantType::Void) => "void",
Some(ConstantType::I8) => "i8", Some(ConstantType::I8) => "i8",
Some(ConstantType::I16) => "i16", Some(ConstantType::I16) => "i16",
Some(ConstantType::I32) => "i32", Some(ConstantType::I32) => "i32",
@@ -333,18 +330,18 @@ fn display_optional_type(otype: &Option<ConstantType>) -> &'static str {
#[test] #[test]
fn lex_numbers() { fn lex_numbers() {
let mut lex0 = Token::lexer("12 0b1100 0o14 0d12 0xc 12u8 0xci64// 9"); 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(Ok(Token::Number((None, None, 12)))));
assert_eq!(lex0.next(), Some(Token::Number((Some(2), None, 12)))); assert_eq!(lex0.next(), Some(Ok(Token::Number((Some(2), None, 12)))));
assert_eq!(lex0.next(), Some(Token::Number((Some(8), None, 12)))); assert_eq!(lex0.next(), Some(Ok(Token::Number((Some(8), None, 12)))));
assert_eq!(lex0.next(), Some(Token::Number((Some(10), None, 12)))); assert_eq!(lex0.next(), Some(Ok(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((Some(16), None, 12)))));
assert_eq!( assert_eq!(
lex0.next(), lex0.next(),
Some(Token::Number((None, Some(ConstantType::U8), 12))) Some(Ok(Token::Number((None, Some(ConstantType::U8), 12))))
); );
assert_eq!( assert_eq!(
lex0.next(), 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); assert_eq!(lex0.next(), None);
} }
@@ -352,46 +349,52 @@ fn lex_numbers() {
#[test] #[test]
fn lex_symbols() { fn lex_symbols() {
let mut lex0 = Token::lexer("x + \t y * \n z // rest"); let mut lex0 = Token::lexer("x + \t y * \n z // rest");
assert_eq!(lex0.next(), Some(Token::var("x"))); assert_eq!(lex0.next(), Some(Ok(Token::var("x"))));
assert_eq!(lex0.next(), Some(Token::Operator('+'))); assert_eq!(lex0.next(), Some(Ok(Token::Operator('+'))));
assert_eq!(lex0.next(), Some(Token::var("y"))); assert_eq!(lex0.next(), Some(Ok(Token::var("y"))));
assert_eq!(lex0.next(), Some(Token::Operator('*'))); assert_eq!(lex0.next(), Some(Ok(Token::Operator('*'))));
assert_eq!(lex0.next(), Some(Token::var("z"))); assert_eq!(lex0.next(), Some(Ok(Token::var("z"))));
assert_eq!(lex0.next(), None); assert_eq!(lex0.next(), None);
} }
#[test] #[test]
fn lexer_spans() { fn lexer_spans() {
let mut lex0 = Token::lexer("y = x + 1//foo").spanned(); 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((Ok(Token::var("y")), 0..1)));
assert_eq!(lex0.next(), Some((Token::Equals, 2..3))); assert_eq!(lex0.next(), Some((Ok(Token::Equals), 2..3)));
assert_eq!(lex0.next(), Some((Token::var("x"), 4..5))); assert_eq!(lex0.next(), Some((Ok(Token::var("x")), 4..5)));
assert_eq!(lex0.next(), Some((Token::Operator('+'), 6..7))); assert_eq!(lex0.next(), Some((Ok(Token::Operator('+')), 6..7)));
assert_eq!(lex0.next(), Some((Token::Number((None, None, 1)), 8..9))); assert_eq!(
lex0.next(),
Some((Ok(Token::Number((None, None, 1))), 8..9))
);
assert_eq!(lex0.next(), None); assert_eq!(lex0.next(), None);
} }
#[test] #[test]
fn further_spans() { fn further_spans() {
let mut lex0 = Token::lexer("x = 2i64 + 2i64;\ny = -x;\nprint y;").spanned(); 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((Ok(Token::var("x")), 0..1)));
assert_eq!(lex0.next(), Some((Token::Equals, 2..3))); assert_eq!(lex0.next(), Some((Ok(Token::Equals), 2..3)));
assert_eq!( assert_eq!(
lex0.next(), 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!( assert_eq!(
lex0.next(), 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((Ok(Token::Semi), 15..16)));
assert_eq!(lex0.next(), Some((Token::var("y"), 17..18))); assert_eq!(lex0.next(), Some((Ok(Token::var("y")), 17..18)));
assert_eq!(lex0.next(), Some((Token::Equals, 19..20))); assert_eq!(lex0.next(), Some((Ok(Token::Equals), 19..20)));
assert_eq!(lex0.next(), Some((Token::Operator('-'), 21..22))); assert_eq!(lex0.next(), Some((Ok(Token::Operator('-')), 21..22)));
assert_eq!(lex0.next(), Some((Token::var("x"), 22..23))); assert_eq!(lex0.next(), Some((Ok(Token::var("x")), 22..23)));
assert_eq!(lex0.next(), Some((Token::Semi, 23..24))); assert_eq!(lex0.next(), Some((Ok(Token::Semi), 23..24)));
assert_eq!(lex0.next(), Some((Token::Print, 25..30))); assert_eq!(lex0.next(), Some((Ok(Token::Print), 25..30)));
assert_eq!(lex0.next(), Some((Token::var("y"), 31..32))); assert_eq!(lex0.next(), Some((Ok(Token::var("y")), 31..32)));
assert_eq!(lex0.next(), Some((Token::Semi, 32..33))); assert_eq!(lex0.next(), Some((Ok(Token::Semi), 32..33)));
} }

View File

@@ -129,6 +129,7 @@ fn convert_statement(
syntax::Statement::Binding(loc, name, expr) => { syntax::Statement::Binding(loc, name, expr) => {
let (expr, ty) = convert_expression(expr, constraint_db, renames, bindings); let (expr, ty) = convert_expression(expr, constraint_db, renames, bindings);
let final_name = finalize_name(bindings, renames, name); 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)) ir::Expression::Bind(loc, final_name, ty, Box::new(expr))
} }
@@ -168,6 +169,10 @@ fn convert_expression(
)); ));
(newval, newtype) (newval, newtype)
} }
Some(ConstantType::Void) => (
ir::Value::Void,
ir::TypeOrVar::Primitive(PrimitiveType::Void),
),
Some(ConstantType::U8) => ( Some(ConstantType::U8) => (
ir::Value::U8(base, value as u8), ir::Value::U8(base, value as u8),
ir::TypeOrVar::Primitive(PrimitiveType::U8), ir::TypeOrVar::Primitive(PrimitiveType::U8),

View File

@@ -174,6 +174,11 @@ fn finalize_val_or_ref(
assert!(matches!(new_type, Type::Primitive(PrimitiveType::I64))); assert!(matches!(new_type, Type::Primitive(PrimitiveType::I64)));
ValueOrRef::Value(loc, new_type, Value::I64(base, value)) 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)
}
} }
} }
} }

View File

@@ -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. /// A version of [`std::collections::HashMap`] with a built-in notion of scope.
#[derive(Clone)] #[derive(Clone)]
@@ -105,4 +107,28 @@ impl<K: Eq + Hash + PartialEq, V> ScopedMap<K, V> {
ScopedMap { scopes } 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<K: Clone + Eq + Hash, V: Clone> ScopedMap<K, V> {
/// Returns the set of all variables bound at this time, with shadowed
/// variables hidden.
pub fn bindings(&self) -> HashMap<K, V> {
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
}
} }