valhallac

Compiler for set-theoretic programming language.
git clone git://git.knutsen.co/valhallac
Log | Files | Refs | README | LICENSE

commit 44e2b85b381d726581074dc92fbef3d9c13bbb5e
parent cf4ec231e8c8c009b7bd5f9a80c63e2f8fdedab4
Author: Demonstrandum <moi@knutsen.co>
Date:   Mon, 15 Jun 2020 04:39:04 +0100

Added compilation tests for the compiler.

Diffstat:
MCargo.toml | 1+
Msrc/bin.rs | 2--
Msrc/issue.rs | 17+++++++++++------
Msrc/lib.rs | 11++++++++++-
Msrc/syntax/lexer.rs | 3+++
Msrc/syntax/mod.rs | 19+++++++++++++------
Msrc/syntax/parser.rs | 1+
Msrc/syntax/token.rs | 2+-
Atests/Cargo.toml | 13+++++++++++++
Atests/README.md | 15+++++++++++++++
Atests/README.md.bak | 17+++++++++++++++++
Atests/expect_fail/bad_paren.vh | 4++++
Mtests/expect_fail/invalid_operations_1.vh | 3---
Atests/expect_fail/no_such_operator.vh | 5+++++
Atests/expect_success/arith_1.vh | 4++++
Atests/expect_success/arith_2.vh | 6++++++
Atests/expect_success/arith_3.vh | 8++++++++
Atests/expect_success/auto_function.vh | 30++++++++++++++++++++++++++++++
Atests/expect_success/io_1.vh | 7+++++++
Atests/src/main.rs | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
20 files changed, 265 insertions(+), 19 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -20,6 +20,7 @@ build = "static/build.rs" [features] debug = ["snailquote"] +loud-panic = [] [lib] name = "valhallac" diff --git a/src/bin.rs b/src/bin.rs @@ -8,10 +8,8 @@ use std::collections::HashMap; use lazy_static::lazy_static; -use colored; use colored::*; - fn is_vh_file(filename : &String) -> bool { filename.ends_with(".vh") && Path::new(filename).exists() diff --git a/src/issue.rs b/src/issue.rs @@ -72,6 +72,10 @@ impl Issue { self } + pub fn panic(&self) -> ! { + panic!("Cannot continue after such an issue.") + } + pub fn print(self) -> Self { unsafe { crate::PANIC_MESSAGE = "Compilation could not continue."; @@ -79,14 +83,15 @@ impl Issue { eprintln!("\n{}", self); if self.is_fatal { - std::process::exit(1); + self.panic(); } + self } pub fn crash_and_burn(self) -> ! { - self.print(); - std::process::exit(1) + self.print() + .panic() } } @@ -150,7 +155,7 @@ impl fmt::Display for Issue { let mut line_content = if let Some(Ok(line_str)) = BufReader::new(file) .lines().nth(line - 1) { - line_str + line_str + " " } else { "[**] THIS LINE DOES NOT EXIST! \ Either the file was deleted, \ @@ -180,8 +185,8 @@ impl fmt::Display for Issue { } } - let note_ascii = self.note_message.as_ref().map(|some| - format!("{} {}", + let note_ascii = self.note_message.as_ref().map( + |some| format!("{} {}", "|\n+-".yellow(), some.bold())); if let Some(column) = self.site.location.column { diff --git a/src/lib.rs b/src/lib.rs @@ -27,6 +27,8 @@ pub mod syntax; /// instructions for the Brokkr VM, and marshals the instructions. pub mod compiler; +pub use syntax::parse_source; + /// Parses the contents of a file with path `filename : &str`. pub fn parse(filename : &str) -> syntax::ast::Root { syntax::parse_file(filename) @@ -59,12 +61,19 @@ pub fn set_panic() { if PANIC_MESSAGE.is_empty() { eprintln!("\n{}", "The compiler panicked! This is a bug." .white().bold()); - eprintln!("{} {}\n", ">>>".blue(), msg.to_string().white()); + eprintln!("{} {}\n", + ">>>".blue(), + msg.to_string().white()); } else { eprintln!(" {} {} {}", "::".white().bold(), "Halt".blue().bold(), PANIC_MESSAGE.white()); + #[cfg(any(feature="loud-panic", feature="debug"))] { + eprintln!("{} {}\n", + ">>>".blue(), + msg.to_string().white()); + } } })); } diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs @@ -111,6 +111,9 @@ pub fn lex(string : &str, filename : &str) -> VecDeque<Token> { continue; } + // TODO: Consume multi-line comments (`--* ... *--`). + // TODO: Lex `with:` `let:` `in:` `where:` `do:` + // indentation blocks. let vec_brack = match two_chars { "[|" => Some(TokenType::LVec), diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs @@ -31,12 +31,8 @@ use std::collections::HashSet; #[cfg(feature="debug")] use token::ShowStream; -/// Parses a given file, calling various methods from -/// the `syntax` sub-module. -pub fn parse_file(filename : &str) -> ast::Root { - let code = fs::read_to_string(filename) - .expect("Could not open file for reading."); - +pub fn parse_source(code : &str, filename : &str) -> ast::Root { + // First lex: #[cfg(feature="debug")] println!("Code:\n{}\n", code); @@ -65,4 +61,15 @@ pub fn parse_file(filename : &str) -> ast::Root { println!("AST:\n{}\n", tree); tree + +} + +/// Parses a given file, calling various methods from +/// the `syntax` sub-module. +pub fn parse_file(filename : &str) -> ast::Root { + let code = fs::read_to_string(filename) + .expect("Could not open file for reading."); + parse_source(&code, filename) } + + diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs @@ -288,6 +288,7 @@ impl<'a> ParseEnvironment<'a> { if t.class != tt { fatal!(ParseError, t.location.with_filename(self.file), "Unexpected token type: `{}`, expected: `{}`.", t.class, tt) + .note("Perhaps you forgot to write something?") .print(); } } diff --git a/src/syntax/token.rs b/src/syntax/token.rs @@ -122,7 +122,7 @@ impl fmt::Display for Token { spaces1=" ".repeat(12 - self.class.to_string().width()), spaces2=" ".repeat(30 - escaped.width()), l=self.location.location.line.unwrap(), - c=self.location.location.line.unwrap(), + c=self.location.location.column.unwrap(), span=self.location.location.columns.unwrap()) } } diff --git a/tests/Cargo.toml b/tests/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "valhalla-compiler-tests" +version = "0.1.0" +authors = ["Demonstrandum <moi@knutsen.co>"] +edition = "2018" + +[dependencies] +colored = "1.8" + +[dependencies.valhallac] +path = "../" +default-features = false +features = ["loud-panic"] diff --git a/tests/README.md b/tests/README.md @@ -0,0 +1,15 @@ +# Valhalla Compiler Test + +A series of good and bad files are run, to see if the compiler +ends up returning an error or not. These tests serve to see if +the compiler is implemented correctly. + +It is however by no means very extensive right now, and will need +to continue to be expanded on. + +## Failure + +Most tests for successful compilation at the will fail, +this is because most of the compiler features are only half +implemented or so. The standard library / prelude is also lacking, +which makes this worse. diff --git a/tests/README.md.bak b/tests/README.md.bak @@ -0,0 +1,17 @@ +# Valhalla Compiler Test + +dewdke jfnj d + +A series of good and bad files are run, to see if the compiler +ends up returning an error or not. These tests serve to see if +the compiler is implemented correctly. + +It is however by no means very extensive right now, and will need +to continue to be expanded on. + +## Failure + +Most tests for successful compilation at the will fail, +this is because most of the compiler features are only half +implemented or so. The standard library / prelude is also lacking, +which makes this worse. diff --git a/tests/expect_fail/bad_paren.vh b/tests/expect_fail/bad_paren.vh @@ -0,0 +1,4 @@ + +ys = [| 2; 2; 3 |] + +assert (ys == [| 2; 2; 3 |] diff --git a/tests/expect_fail/invalid_operations_1.vh b/tests/expect_fail/invalid_operations_1.vh @@ -1,6 +1,3 @@ --- TODO: Test overloading with `f`. --- TODO: Test casting from subsets of Real, upto Real. - f : Real -> Real -> Real f a b = (1 + 1) * a + b -- 2a + b diff --git a/tests/expect_fail/no_such_operator.vh b/tests/expect_fail/no_such_operator.vh @@ -0,0 +1,5 @@ +!infix (+++) 20 :left +!posfix ~ 70 + +a = 3 +++ 2 +b = a + 7~ diff --git a/tests/expect_success/arith_1.vh b/tests/expect_success/arith_1.vh @@ -0,0 +1,4 @@ +f : Int -> Int +f n = n - 20 + +a = 2 + f -3 * 4 / 6 diff --git a/tests/expect_success/arith_2.vh b/tests/expect_success/arith_2.vh @@ -0,0 +1,6 @@ +f : Int -> Int +f n = n - 20 + +a = 2 + f 3 * 4 / 6 + +-- Treat `3` literal as intger numeric in type checker. diff --git a/tests/expect_success/arith_3.vh b/tests/expect_success/arith_3.vh @@ -0,0 +1,8 @@ +f : Int -> Int +f n = n - 20 + +b : Nat +b = 12 + +a = 2 + f b * 4 / 6 +-- `b` will get automatically cast to an int. diff --git a/tests/expect_success/auto_function.vh b/tests/expect_success/auto_function.vh @@ -0,0 +1,30 @@ +-- Values can be automatically wrapped in functions +-- if the return type matches. + +--*-- + * Examples +--*-- + +f : [] -> Nat +f = 3 -- same as, +--f = _ |-> 3 -- same as, +--f a = 3 -- same as, +--f _ = 3 -- same as, +--f () = 3 + +g : Nat -> Int +g = -4 -- same as, +--g = _ |-> { -4 } + + +a = 3 -- a : Int + +b : 'A -> 'B +b = 3 -- b : [] -> Int + +-- This works everywhere: + +xs = [| 1; 2; 3 |] +ys = map 3 xs -- same as, +--ys = map (_ |-> 3) xs +assert (ys == [| 3; 3; 3 |]) diff --git a/tests/expect_success/io_1.vh b/tests/expect_success/io_1.vh @@ -0,0 +1,7 @@ +io = import :IO + +name : String +name = "Sammy" + +io::puts "Hello, ${name}!" + -- expands to: `"Hello, " + name.to_string + "!"` diff --git a/tests/src/main.rs b/tests/src/main.rs @@ -0,0 +1,116 @@ +#![feature(set_stdio)] + +use valhallac; + +use colored::*; + +use std::{fs, path::Path, ffi::OsStr}; +use std::panic; + +type DynErr = Box<dyn std::error::Error>; + +fn get_source(path : &Path) -> Result<String, ()> { + let mut source = Err(()); + if path.is_file() && path.extension() == Some(OsStr::new("vh")) { + source = fs::read_to_string(path).map_err(|_| ()); + } + source +} + +struct Ratio(u32, u32); + +fn on_vh_files<F>(dir_path : &str, mut lambda : F) -> Result<Ratio, DynErr> + where F : FnMut(&Path, String) -> bool { + let (mut passes, mut total) = (0, 0); + for entry in fs::read_dir(dir_path)? { + total += 1; + if let Ok(path) = entry { + let path = path.path(); + if let Ok(source) = get_source(&path) { + if lambda(&path, source) { passes += 1 } + } else { // Otherwise just skip. + println!("Skipping `{}'...", path.to_string_lossy()); + } + } + } + Ok(Ratio(passes, total)) +} + +use std::io::prelude::Write; + +fn redirect_stderr(file : &str) -> Result<(), DynErr> { + let f = fs::File::create(file)?; + let sink : Box<dyn Send + Write> = Box::new(f); + std::io::set_panic(Some(sink)); + Ok(()) +} + +fn status(good : bool) -> String { + if good { + "Ok".green() + } else { + "Failed".red() + }.bold().to_string() +} + +fn main() -> Result<(), DynErr> { + valhallac::set_panic(); + redirect_stderr("stderr.log")?; + + let mut count = 0; + let mut compile_attempt = |path: &Path, source: String| { + count += 1; + let filename = path.to_string_lossy(); + let prefix = format!("{: >4}. (`{}'):", + count.to_string().bold(), + path.file_stem().unwrap() + .to_string_lossy() + .underline() + .white()); + // Catch errors: + let did_panic = panic::catch_unwind(|| unsafe { + let tree = valhallac::parse_source(&source, &filename); + if valhallac::PANIC_MESSAGE.is_empty() { + // Try to compile. + valhallac::compile(&tree); + if !valhallac::PANIC_MESSAGE.is_empty() { + panic!("Did not pass."); + } + } else { + panic!("Did not pass."); + } + }); + print!("{} {} ", prefix, ".".repeat(80 - prefix.len())); + did_panic.is_ok() + }; + + // Expecting success: + println!("{} {}", "==>".blue().bold(), + "Expecting compilation success:".white().bold()); + let succ_ratio = on_vh_files("./expect_success", |path, source| { + let passed = compile_attempt(path, source); + println!("{}", status(passed)); + passed + })?; + println!(); + + // Expecting failure: + println!("{} {}", "==>".blue().bold(), + "Expecting compilation failure:".white().bold()); + let fail_ratio = on_vh_files("./expect_fail", |path, source| { + let passed = compile_attempt(path, source); + println!("{}", status(!passed)); + passed + })?; + println!(); + + // Results: + println!("{}", format!("Success Tests: {}/{}.", + succ_ratio.0.to_string().yellow(), + succ_ratio.1.to_string().yellow()).bold()); + println!("{}", format!("Failure Tests: {}/{}.", + fail_ratio.0.to_string().yellow(), + fail_ratio.1.to_string().yellow()).bold()); + + Ok(()) +}