commit 0d685187969bfcd3123055f1bee02ece4e490f5f
parent 85f7752a7fd2bb3fa60f3c294f86ccd4c8971209
Author: Demonstrandum <moi@knutsen.co>
Date: Tue, 16 Jun 2020 02:32:42 +0100
Implemented basic type checking on function definitions.
Diffstat:
10 files changed, 192 insertions(+), 52 deletions(-)
diff --git a/src/bin.rs b/src/bin.rs
@@ -179,8 +179,6 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
} else {
format!("{:0.5}s", seconds)
}.white());
-
- println!();
});
}
diff --git a/src/compiler/block.rs b/src/compiler/block.rs
@@ -166,7 +166,7 @@ impl<'a> LocalBlock<'a> {
}
fn function_assign(&mut self, left : &ast::CallNode, right : &'a Nodes) {
- let mut arguments = left.collect_operands();
+ let mut arguments = left.collect();
let base_node = arguments.remove(0);
if let Nodes::Ident(ident) = base_node {
@@ -412,11 +412,11 @@ impl<'a> fmt::Display for LocalBlock<'a> {
for key in self.locals_map.keys() {
writeln!(f, " | {: >3} | {}", self.locals_map[key], key)?;
}
- writeln!(f, " |====Globals=================")?;
+ writeln!(f, " |====Superiors===============")?;
for (i, c) in self.globals.iter().enumerate() {
writeln!(f, " | {: >3} | {}", i, c)?;
}
- writeln!(f, " |====Bytecodes===============")?;
+ writeln!(f, " |====Bytecode================")?;
for inst in &self.instructions {
if let Instr::Operand(_) = inst {
write!(f, "{}", inst)?;
diff --git a/src/issue.rs b/src/issue.rs
@@ -73,13 +73,17 @@ impl Issue {
}
pub fn panic(&self) -> ! {
- panic!("Cannot continue after such an issue.")
+ panic!("Cannot continue after such an issue: `{}'.",
+ self.message)
}
pub fn print(self) -> Self {
unsafe {
- crate::PANIC_MESSAGE = "Compilation could not continue.";
- };
+ #[cfg(feature="loud-panic")]
+ eprintln!("Issue was: `{}'.", self.message);
+ crate::PANIC_MESSAGE =
+ "Compilation could not continue.";
+ }
eprintln!("\n{}", self);
if self.is_fatal {
@@ -155,6 +159,8 @@ impl fmt::Display for Issue {
let mut line_content = if let Some(Ok(line_str)) =
BufReader::new(file)
.lines().nth(line - 1) {
+ // Add space at end to represent
+ // line-feed character.
line_str + " "
} else {
"[**] THIS LINE DOES NOT EXIST! \
diff --git a/src/syntax/analysis/type_resolver.rs b/src/syntax/analysis/type_resolver.rs
@@ -23,11 +23,13 @@ impl SymbolEntry {
if let StaticTypes::TFunction(_, _) = self.signature {
return true;
}
- return false;
+ false
}
- pub fn was_defined(&mut self) {
+ pub fn was_defined(&mut self) -> bool {
+ let def = self.defined;
self.defined = true;
+ def
}
}
@@ -132,6 +134,20 @@ fn search_chain(&mut self, ident : &str) -> Option<&mut SymbolTable> {
return None;
}
+fn unwrap_set(&self, set : &StaticTypes) -> StaticTypes {
+ if let StaticTypes::TSet(internal) = set {
+ *internal.clone()
+ } else {
+ // We should never get here, we should always have
+ // checked earlier if a function signature tries to map
+ // between non-sets.
+ use crate::site::Site;
+ issue!(TypeError, Site::new().with_filename(&self.filename),
+ "Cannot create mapping (function) between non-sets.")
+ .crash_and_burn()
+ }
+}
+
/// # Terminology
/// `appl_0` - refers to the the 0th (base) application (call).
/// `appl_n` - refers to any nested application n-levels deep.
@@ -251,10 +267,10 @@ pub fn resolve_branch(&mut self, branch : &Nodes) -> Nodes {
if maybe_op_inner_type.is_none() {
// Fatal, we should really never get here,
// because we _should_ check for this earlier.
- issue!(TypeError,
+ fatal!(TypeError,
(*appl_0.callee).site().with_filename(&self.filename),
"Function should map from a set, it does not.")
- .print();
+ .print();
}
// Safe to unwrap, we've checked for none.
@@ -295,7 +311,7 @@ pub fn resolve_branch(&mut self, branch : &Nodes) -> Nodes {
issue!(TypeError,
appl_0.callee.site().with_filename(&self.filename),
"Function-application / juxtaposition is not \
- defined on type of `{}'.", appl_0_st)
+ defined on type of `{}'.", appl_0_st)
.print();
}
}
@@ -313,6 +329,19 @@ fn resolve_assignment(&mut self,
// Either way, we must say it is now 'defined'
// (as well as 'declared') in the table.
+ // Assignment with no type annotation needs to be
+ // more flexible when it comes to types, i.e.
+ // ```
+ // a : Int
+ // a = 3
+ // a = "Somethin" -- is illegal, doesn't match type.
+ // ```
+ // Compared to:
+ // ```
+ // a = 3
+ // a = "Something" -- legal, `a' has type `Nat | String` now.
+ // ```
+
// TODO: '=' with a type annotation should
// cast the value on the right if possible, to
// match the annotation. If not possible, throw
@@ -322,6 +351,7 @@ fn resolve_assignment(&mut self,
// a function (e.g. `f x = x + 1`).
let filename = &self.filename.to_owned();
+ let rhs = appl_0.operands[0].clone();
let lhs = &appl_1.operands[0];
// Handle variable (identifier) assignment:
if let Nodes::Ident(ident_op_1) = lhs {
@@ -381,19 +411,102 @@ fn resolve_assignment(&mut self,
let base_call = call_op_1.base_call();
if !base_call.is_ident() {
// Fatal, we must define the call on some sort of ident.
- issue!(ParseError,
+ fatal!(ParseError,
base_call.site().with_filename(&self.filename),
"You have to assign to a call on an identifier,
- this identifier is the function you are defining.
- Expected an `identifier', found `{}'!",
- base_call.node_type())
- .print();
+ this identifier is the function you are defining.")
+ .note(&format!("Expected an `identifier', found `{}'!",
+ base_call.node_type()))
+ .print();
}
// We've checked, and we may unwrap it.
let base_call = base_call.ident().unwrap();
- // TODO Continue, collect the arguments too!!!
+
+ let func_type;
+ if let Some(table) = self.search_chain(&base_call.value) {
+ let signatures = table.collect_signatures(&base_call.value);
+ let mut sig_iter = signatures.iter();
+ // TODO: Select which signature to use (establish some order).
+ // Specifically need to select the correct overload.
+ // For now pick a random one.
+ func_type = sig_iter.next().unwrap().to_owned(); // We know this exists.
+ } else {
+ // TODO: Determine implicit type for this function.
+ // This has to be done in a way that considers the structure
+ // of the LHS, considering all pattern matches in each of the
+ // cases, and what their types are. The inductive case
+ // should also be analysed for what kind of functions it calls,
+ // in order to narrow down the type as much as possible.
+ func_type = StaticTypes::TUnknown; // FIXME.
+ }
+
+ let mut left_type = StaticTypes::TUnknown;
+ let mut right_type = StaticTypes::TUnknown;
+ // Check if we do actually have a function type.
+ if let StaticTypes::TFunction(l, r) = func_type {
+ left_type = self.unwrap_set(&*l); // This should have already been
+ right_type = self.unwrap_set(&*r); // checked for.
+ } else { // Fatal, needs to be a function.
+ fatal!(TypeError, call_op_1.site.with_filename(&self.filename),
+ "Trying to define a function on a variable that does \
+ not have type of `function'.")
+ .note(&format!("`{}' has type of `{}', which is not a function.",
+ base_call.value, func_type))
+ .print();
+ }
+
+ let lhs_operands = call_op_1.collect_operands();
+ let operand_count = lhs_operands.len();
+ let mut function_scope = SymbolTable::new(&base_call.value);
+ for (i, lhs_operand) in lhs_operands.iter().enumerate() {
+ if let Nodes::Ident(lhs_op_ident) = lhs_operand {
+ function_scope.push(&lhs_op_ident.value, left_type.clone(), true);
+ if i == operand_count - 1 {
+ break; // No need to disect any further.
+ }
+
+ if let StaticTypes::TFunction(l, r) = right_type {
+ left_type = self.unwrap_set(&*l);
+ right_type = self.unwrap_set(&*r);
+ } else {
+ fatal!(TypeError,
+ lhs_operands
+ .last().unwrap()
+ .site().with_filename(&self.filename),
+ "Function definition provided with too many arguments.
+ The type signature disagrees with the number
+ of arguments you have provided.")
+ .note("Consider removing this, or altering \
+ the type signature.")
+ .print();
+ }
+ } else {
+ // TODO: Not an ident, that means we're
+ // pattern matching. This will need a general
+ // implementation in the future.
+ }
+ }
+ // Now the function scope is populated with the arguments.
+ self.table_chain.push(function_scope); // Add the scope to the stack.
+ // Type the right side of the equality:
+ let typed_rhs = self.resolve_branch(&rhs);
+ // Check if the RHS has the correct type.
+ if typed_rhs.yield_type() == right_type {
+ appl_0.operands[0] = typed_rhs;
+ } else {
+ // TODO: If the the types disagree, but the type is
+ // a subset, just cast the type. For now, it's only an error:
+ issue!(TypeError, rhs.site().with_filename(&self.filename),
+ "Right hand side of function definition does not agree \
+ with type signature.
+ Expected type of `{}', got `{}'.",
+ &right_type, &typed_rhs.yield_type())
+ .note("Either convert the value, or alter the type signature.")
+ .print();
+ }
+ // The function scope is no longer in use.
+ self.table_chain.pop();
} else {
- // TODO: Assignment to functions.
// TODO: Pattern matching etc.
issue!(ParseError,
diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs
@@ -577,8 +577,9 @@ impl CallNode {
}
}
- /// Collect arguments to a call.
- pub fn collect_operands(&self) -> Vec<Nodes> {
+
+ /// List of callee and operands in a list, in a double ended queue.
+ pub fn collect_deque(&self) -> VecDeque<Nodes> {
fn make_argument_vector(call_node : &Nodes,
operands : VecDeque<Nodes>) -> VecDeque<Nodes> {
let mut pushable = operands;
@@ -591,15 +592,26 @@ impl CallNode {
pushable.push_front(call_node.clone());
return pushable;
}
- let q = make_argument_vector(&Nodes::Call(self.clone()), VecDeque::new());
- Vec::from(q)
+
+ make_argument_vector(
+ &Nodes::Call(self.clone()),
+ VecDeque::new())
}
- /// List of callee and operands, lisp call style list.
+ /// Flatten a function call into a list containing the
+ /// callee at the head, and arguments at tail (LISP style).
+ /// ---
+ /// Mainly for easier flattened/non-recursive
+ /// iteration over function calls.
pub fn collect(&self) -> Vec<Nodes> {
- let mut list = vec![self.base_call()];
- list.extend_from_slice(&self.collect_operands());
- list
+ Vec::from(self.collect_deque())
+ }
+
+ /// Collects only operands/arguments to a call.
+ pub fn collect_operands(&self) -> Vec<Nodes> {
+ let mut list = self.collect_deque();
+ list.pop_front();
+ Vec::from(list)
}
pub fn is_unary(&self) -> bool {
diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs
@@ -1,4 +1,5 @@
-use std::collections::VecDeque;
+use std::collections::{VecDeque, HashSet};
+use lazy_static::lazy_static;
use super::token;
use super::ast;
@@ -7,10 +8,19 @@ use super::operators;
use crate::{issue, site};
use site::{Site, Location};
-
use token::{Token, TokenType};
use ast::Nodes;
+lazy_static! {
+ static ref EXPR_TERM : HashSet<TokenType> = {
+ let mut set = HashSet::new();
+ set.insert(TokenType::RParen);
+ set.insert(TokenType::EOF);
+ set.insert(TokenType::Term);
+ set
+ };
+}
+
fn location_range(loc_begin : &Location, loc_end : &Location) -> Location {
let mut loc_final = loc_end.clone();
@@ -204,13 +214,14 @@ impl<'a> ParseEnvironment<'a> {
while self.optable.precedence(&self.stream[0].string).unwrap_or(190) > right_prec {
- let next = &(&self.stream[0].string).clone();
+ let ahead = self.stream[0].clone();
+ let next = &ahead.string.clone();
if self.ignore_newline && next == "\n" {
self.shift();
continue;
}
- if next == "\0" || next == "\n" || next == ")" { break; }
+ if EXPR_TERM.contains(&ahead.class) { break; }
let maybe_op = self.optable.lookup(next, 2);
if let Some(op) = maybe_op {
diff --git a/src/syntax/token.rs b/src/syntax/token.rs
@@ -18,7 +18,7 @@ enum Indent {
/// Contains all possible types/classes of
/// lexiacal tokens.
-#[derive(PartialEq, Clone)]
+#[derive(PartialEq, Eq, Hash, Clone, Copy)]
pub enum TokenType {
/// Identifiers, variables, function names etc.
Ident,
diff --git a/test_source.vh b/test_source.vh
@@ -2,24 +2,16 @@
-- TODO: Test casting from subsets of Real, upto Real.
f : Real -> Real -> Real
-
f a b = (1 + 1) * a + b -- 2a + b
a : Nat
a = 3
-( : ) b Int
+b : Int
b = 1 - 8 -- -7
c : Real
c = f (a + 1.0) (b - 0.0)
---* This should yield three errors *--
-
-w : Beep -- Beep doesn't exits yet.
--- This is an error, it should be `<-` not `:`.
-w = a + b : Int
-
-2 = 3 -- Also an error.
diff --git a/tests/expect_success/semi-colon.vh b/tests/expect_success/semi-colon.vh
@@ -0,0 +1,2 @@
+3 + 2; 1 - 89; 2 - 3 * 4
+3 + 1
diff --git a/tests/src/main.rs b/tests/src/main.rs
@@ -23,13 +23,16 @@ 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 }
+ total += 1;
+ if lambda(&path, source) { passes += 1 }
} else { // Otherwise just skip.
- println!("Skipping `{}'...", path.to_string_lossy());
+ println!(" Skipping `{}'...", path
+ .file_name().unwrap()
+ .to_string_lossy()
+ .underline());
}
}
}
@@ -61,7 +64,9 @@ fn main() -> Result<(), DynErr> {
let mut compile_attempt = |path: &Path, source: String| {
count += 1;
let filename = path.to_string_lossy();
- let prefix = format!("{: >4}. (`{}'):",
+ // For the log.
+ eprintln!(" === Compiling: `{}' ===", filename.underline());
+ let prefix = format!("{: >3}. (`{}'):",
count.to_string().bold(),
path.file_stem().unwrap()
.to_string_lossy()
@@ -69,15 +74,16 @@ fn main() -> Result<(), DynErr> {
.white());
// Catch errors:
let did_panic = panic::catch_unwind(|| unsafe {
+ valhallac::PANIC_MESSAGE = "";
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.");
+ panic!("Did not pass: `{}'", valhallac::PANIC_MESSAGE);
}
} else {
- panic!("Did not pass.");
+ panic!("Did not pass: `{}'", valhallac::PANIC_MESSAGE);
}
});
print!("{} {} ", prefix, ".".repeat(80 - prefix.len()));
@@ -85,7 +91,7 @@ fn main() -> Result<(), DynErr> {
};
// Expecting success:
- println!("{} {}", "==>".blue().bold(),
+ println!("\n{} {}", "==>".blue().bold(),
"Expecting compilation success:".white().bold());
let succ_ratio = on_vh_files("./expect_success", |path, source| {
let passed = compile_attempt(path, source);
@@ -98,9 +104,9 @@ fn main() -> Result<(), DynErr> {
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
+ let failed = !compile_attempt(path, source);
+ println!("{}", status(failed));
+ failed
})?;
println!();