The typed literal formatting mirrors that of Rust. If no type can be inferred for an untagged literal, the type inference engine will warn the user and then assume that they meant an unsigned 64-bit number. (This is slightly inconvenient, because there can be cases in which our Arbitrary instance may generate a unary negation, in which we should assume that it's a signed 64-bit number; we may want to revisit this later.) The type inference engine is a standard two phase one, in which we first generate a series of type constraints, and then we solve those constraints. In this particular implementation, we actually use a third phase to generate a final AST. Finally, to increase the amount of testing performed, I've removed the overflow checking in the evaluator. The only thing we now check for is division by zero. This does make things a trace slower in testing, but hopefully we get more coverage this way.
89 lines
3.3 KiB
Rust
89 lines
3.3 KiB
Rust
extern crate lalrpop;
|
|
|
|
use std::env;
|
|
use std::fs::{self, File};
|
|
use std::io::Write;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
fn main() {
|
|
lalrpop::process_root().unwrap();
|
|
if let Err(e) = generate_example_tests() {
|
|
eprintln!("Failure building example tests: {}", e);
|
|
std::process::exit(3);
|
|
}
|
|
}
|
|
|
|
fn generate_example_tests() -> std::io::Result<()> {
|
|
let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR");
|
|
let dest_path = Path::new(&out_dir).join("examples.rs");
|
|
let mut output = File::create(dest_path)?;
|
|
|
|
generate_tests(&mut output, PathBuf::from("examples"))?;
|
|
|
|
println!("cargo:rerun-if-changed=build.rs");
|
|
println!("cargo:rerun-if-changed=example.rs");
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_tests(f: &mut File, path_so_far: PathBuf) -> std::io::Result<()> {
|
|
for entry in fs::read_dir(path_so_far)? {
|
|
let entry = entry?;
|
|
let file_name = entry
|
|
.file_name()
|
|
.into_string()
|
|
.expect("reasonable file name");
|
|
let name = file_name.trim_end_matches(".ngr");
|
|
|
|
if entry.file_type()?.is_dir() {
|
|
writeln!(f, "mod {} {{", name)?;
|
|
writeln!(f, " use super::*;")?;
|
|
generate_tests(f, entry.path())?;
|
|
writeln!(f, "}}")?;
|
|
} else {
|
|
writeln!(f, "#[test]")?;
|
|
writeln!(f, "fn {}() {{", name)?;
|
|
writeln!(f, " let mut file_database = SimpleFiles::new();")?;
|
|
writeln!(
|
|
f,
|
|
" let syntax = Syntax::parse_file(&mut file_database, {:?});",
|
|
entry.path().display()
|
|
)?;
|
|
if entry.path().to_string_lossy().contains("broken") {
|
|
writeln!(f, " if syntax.is_err() {{")?;
|
|
writeln!(f, " return;")?;
|
|
writeln!(f, " }}")?;
|
|
writeln!(f, " let (errors, _) = syntax.unwrap().validate();")?;
|
|
writeln!(
|
|
f,
|
|
" assert_ne!(errors.len(), 0, \"should have seen an error\");"
|
|
)?;
|
|
} else {
|
|
// NOTE: Since the advent of defaulting rules and type checking, we
|
|
// can't guarantee that syntax.eval() will return the same result as
|
|
// ir.eval() or backend::eval(). We must do type checking to force
|
|
// constants into the right types, first. So this now checks only that
|
|
// the result of ir.eval() and backend::eval() are the same.
|
|
writeln!(
|
|
f,
|
|
" let syntax = syntax.expect(\"file should have parsed\");"
|
|
)?;
|
|
writeln!(f, " let (errors, _) = syntax.validate();")?;
|
|
writeln!(
|
|
f,
|
|
" assert_eq!(errors.len(), 0, \"file should have no validation errors\");"
|
|
)?;
|
|
writeln!(
|
|
f,
|
|
" let ir = syntax.type_infer().expect(\"example is typed correctly\");"
|
|
)?;
|
|
writeln!(f, " let ir_result = ir.eval();")?;
|
|
writeln!(f, " let compiled_result = Backend::<JITModule>::eval(ir);")?;
|
|
writeln!(f, " assert_eq!(ir_result, compiled_result);")?;
|
|
}
|
|
writeln!(f, "}}")?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|