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:
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(())
+}