λ Support functions! #5
38
Cargo.toml
38
Cargo.toml
@@ -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"
|
||||||
|
|||||||
17
build.rs
17
build.rs
@@ -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, "}}")?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)),
|
||||||
|
|||||||
@@ -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
74
src/bin/ngrun.rs
Normal 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!();
|
||||||
|
}
|
||||||
@@ -36,14 +36,16 @@ pub enum PrimOpError<IR> {
|
|||||||
ValuePrimitiveTypeError(#[from] ValuePrimitiveTypeError),
|
ValuePrimitiveTypeError(#[from] ValuePrimitiveTypeError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<IR1: Clone,IR2: Clone> PartialEq<PrimOpError<IR2>> for PrimOpError<IR1> {
|
impl<IR1: Clone, IR2: Clone> PartialEq<PrimOpError<IR2>> for PrimOpError<IR1> {
|
||||||
fn eq(&self, other: &PrimOpError<IR2>) -> bool {
|
fn eq(&self, other: &PrimOpError<IR2>) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(PrimOpError::MathFailure(a), PrimOpError::MathFailure(b)) => a == b,
|
(PrimOpError::MathFailure(a), PrimOpError::MathFailure(b)) => a == b,
|
||||||
(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,
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -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)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(), ())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
impl<Type: core::fmt::Debug> Arbitrary for Program<Type> {
|
lazy_static::lazy_static! {
|
||||||
type Parameters = crate::syntax::arbitrary::GenerationEnvironment;
|
static ref PROGRAM_LENGTH_DISTRIBUTION: WeightedIndex<usize> = WeightedIndex::new([
|
||||||
type Strategy = BoxedStrategy<Self>;
|
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 {
|
static ref BLOCK_LENGTH_DISTRIBUTION: WeightedIndex<usize> = WeightedIndex::new([
|
||||||
unimplemented!()
|
1, // % chance of 0
|
||||||
//crate::syntax::Program::arbitrary_with(args)
|
10, // % chance of 1
|
||||||
// .prop_map(|x| {
|
20, // % chance of 2
|
||||||
// x.type_infer()
|
15, // % chance of 3
|
||||||
// .expect("arbitrary_with should generate type-correct programs")
|
10, // % chance of 4
|
||||||
// })
|
8, // % chance of 5
|
||||||
// .boxed()
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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::<Vec<_>>();
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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)),
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user