valhallac

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

commit 59e198024a6f1eb1001bcd79e71276580298e223
parent c13b46254d442af6b0f701ce98c4e5a39569efd3
Author: Demonstrandum <moi@knutsen.co>
Date:   Wed,  7 Aug 2019 21:41:50 +0100

Rewrote static typing for variables.

Diffstat:
MCargo.toml | 1+
Msrc/err.rs | 8++++++--
Msrc/syntax/analyser.rs | 107++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Msrc/syntax/ast.rs | 41++++++++++++++++++++++++++++++++++++-----
Msrc/syntax/parser.rs | 84+------------------------------------------------------------------------------
Mtest.vh | 13+++++--------
6 files changed, 145 insertions(+), 109 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -24,6 +24,7 @@ path = "src/bin.rs" [dependencies] lazy_static = "1.3.0" regex = "1" +unindent = "0.1.3" snailquote = "0.2.0" unicode-width = "0.1.5" enum-primitive-derive = "^0.1" diff --git a/src/err.rs b/src/err.rs @@ -7,6 +7,8 @@ use std::io::{BufRead, BufReader}; use colored; use colored::*; +use unindent::unindent; + #[allow(non_camel_case_types)] pub struct NO_TOKEN; @@ -33,7 +35,8 @@ pub fn tissue(class : Types, filename : &str, token : &token::Token, message : let file = fs::File::open(filename).expect("Invalid filename for error message."); let line = BufReader::new(file).lines().nth((token.location.line - 1) as usize).unwrap().unwrap(); - eprintln!("{}{} {}", "issue".bold().red(), ":".white(), message); + let unindented = unindent(message); + eprintln!("{}{} {}", "issue".bold().red(), ":".white(), unindented.bold()); eprint!("{}", "".clear()); eprintln!(" ==> {class} in (`{file}`:{line}:{col}):\n{space}|\n{line_str}| {stuff}", class=class.to_string().bold(), file=filename, line=token.location.line, @@ -48,7 +51,8 @@ pub fn lissue(class : Types, filename : &str, line_n : usize, message : &str) { let file = fs::File::open(filename).expect("Invalid filename for error message."); let line = BufReader::new(file).lines().nth((line_n - 1) as usize).unwrap().unwrap(); - eprintln!("{}{} {}", "issue".bold().red(), ":".white(), message.bold()); + let unindented = unindent(message); + eprintln!("{}{} {}", "issue".bold().red(), ":".white(), unindented.bold()); eprint!("{}", "".clear()); eprintln!(" ==> {class} in (`{file}`:{line}):\n{space}|\n{line_str}| {stuff}", class=class.to_string().bold(), file=filename, line=line_n, diff --git a/src/syntax/analyser.rs b/src/syntax/analyser.rs @@ -1,4 +1,9 @@ +use std::collections::{HashMap, VecDeque}; + +use crate::err; + use super::ast; +use ast::Nodes; /// Constant folding. /// A static optimisation that relieves the runtime of having to perform @@ -6,8 +11,8 @@ use super::ast; /// instead. This function takes a node and recurses down, looking /// for arithmetic operations containing exactly two numeric type nodes /// as operands, and performs the stated operation. -fn const_fold(node : &ast::Nodes) -> ast::Nodes { - if let ast::Nodes::Call(call) = node { +fn const_fold(node : &Nodes) -> Nodes { + if let Nodes::Call(call) = node { if call.is_binary() { let bin_op = call.callee.call().unwrap().callee.ident().unwrap(); let left = const_fold(&call.callee.call().unwrap().operands[0]); @@ -39,7 +44,7 @@ fn const_fold(node : &ast::Nodes) -> ast::Nodes { return default; } }; - return ast::Nodes::Num(ast::NumNode { value }); + return Nodes::Num(ast::NumNode { value }); } else { return default; } @@ -52,7 +57,7 @@ fn const_fold(node : &ast::Nodes) -> ast::Nodes { } -fn create_cast(node : &ast::Nodes, cast : &ast::StaticTypes) -> ast::Nodes { +fn create_cast(node : &Nodes, cast : &ast::StaticTypes) -> Nodes { let to_type = match cast { ast::StaticTypes::TReal => ":Real", ast::StaticTypes::TInteger => ":Int", @@ -65,7 +70,7 @@ fn create_cast(node : &ast::Nodes, cast : &ast::StaticTypes) -> ast::Nodes { ast::IdentNode::new("cast"), vec![node.clone()]), vec![ast::SymNode::new(to_type)]); - if let ast::Nodes::Call(ref mut call) = cast_node { + if let Nodes::Call(ref mut call) = cast_node { call.set_return_type(cast.clone()) } cast_node @@ -86,8 +91,8 @@ fn cast_strength(st : &ast::StaticTypes) -> i32 { /// cast call to one of the arguments. /// We always cast up (without loss of information), so, 4.3 + 6 will cast the 6 /// to be 6.0. i.e. 4.3 + 6 ==> 4.3 + (cast 6 :Real) <=> 4.3 + 6.0. -fn balance_types(node : &ast::Nodes) -> ast::Nodes { - if let ast::Nodes::Call(call) = node { +fn balance_types(node : &Nodes) -> Nodes { + if let Nodes::Call(call) = node { if call.is_binary() { let bin_op = call.callee.call().unwrap().callee.ident().unwrap(); let left = balance_types(&call.callee.call().unwrap().operands[0]); @@ -114,13 +119,13 @@ fn balance_types(node : &ast::Nodes) -> ast::Nodes { vec![create_cast(&left, &cast_to)]), vec![right]); } - if let ast::Nodes::Call(ref mut c) = new_call { + if let Nodes::Call(ref mut c) = new_call { c.set_return_type(cast_to); } return new_call; } else { let mut cloned_node = node.clone(); - if let ast::Nodes::Call(ref mut c) = cloned_node { + if let Nodes::Call(ref mut c) = cloned_node { c.set_return_type(right_yield); } return cloned_node; @@ -132,7 +137,7 @@ fn balance_types(node : &ast::Nodes) -> ast::Nodes { let mut new_call = ast::CallNode::new( *call.callee.clone(), vec![create_cast(&right, &left_yield)]); - if let ast::Nodes::Call(ref mut c) = new_call { + if let Nodes::Call(ref mut c) = new_call { c.set_return_type(left_yield); } return new_call; @@ -143,7 +148,7 @@ fn balance_types(node : &ast::Nodes) -> ast::Nodes { let mut non_bi = ast::CallNode::new( balance_types(&*call.callee), vec![balance_types(&call.operands[0])]); - if let ast::Nodes::Call(ref mut c) = non_bi { + if let Nodes::Call(ref mut c) = non_bi { c.set_return_type(node.yield_type()); } return non_bi; @@ -151,10 +156,90 @@ fn balance_types(node : &ast::Nodes) -> ast::Nodes { return node.to_owned(); } +type VarType = (String, ast::StaticTypes); + +struct TypeChecker { + source_line : usize, + source_file : String, + annotations : VecDeque<VarType>, + last_annotated : Option<VarType>, +} + +impl TypeChecker { + pub fn new() -> Self { + Self { + source_line: 0, + source_file: String::from("UNANNOUNCED_FILE"), + annotations: VecDeque::new(), + last_annotated: None, + } + } + + pub fn type_branch(&mut self, node : &Nodes) -> Nodes { + let mut clone = node.to_owned(); + match clone { + Nodes::Line(l) => self.source_line = l.line, + Nodes::File(f) => self.source_file = f.filename.to_owned(), + Nodes::Ident(ref mut i) => { + for pairs in &self.annotations { + if pairs.0 == i.value { + if let ast::StaticTypes::TSet(class) = pairs.1.to_owned() { + i.static_type = *class; + } + } + } + return Nodes::Ident(i.to_owned()); + } + Nodes::Call(ref mut call) => { + if let Nodes::Call(ref mut callee) = *call.callee { + if let Nodes::Ident(ref binary_ident) = *callee.callee { + match binary_ident.value.as_str() { + ":" => { + if let Nodes::Ident(ref mut annotatee) = callee.operands[0] { + let annotation = ( + annotatee.value.to_owned(), + self.type_branch(&call.operands[0]).yield_type() + ); + self.last_annotated = Some(annotation.clone()); + self.annotations.push_back(annotation.clone()); + + if let ast::StaticTypes::TSet(class) = annotation.1 { + annotatee.static_type = *class; + } + return clone; + } else { + // Error: We need the left to be an ident. + issue!(err::Types::TypeError, + self.source_file.as_str(), + err::NO_TOKEN, self.source_line, + "The left side of the member-of operator (`:`), must be an identifier. + Only variable names can be declared as being members of sets."); + } + }, + _ => () + } + } + } + call.callee = Box::new(self.type_branch(&*call.callee)); + call.operands = vec![self.type_branch(&call.operands[0])]; + return Nodes::Call(call.to_owned()); + }, + _ => () + }; + node.to_owned() + } +} + pub fn replace(root : &mut ast::Root) { + let mut type_checker = TypeChecker::new(); + let length = root.branches.len(); let mut i = 0; while i < length { + { // START TOP-LEVEL TYPE-CHECKING + let new = type_checker.type_branch(&root.branches[i]); + root.branches[i] = new; + } // END TOP-LEVEL TYPE-CHECKING { // START TOP-LEVEL CONSTANT FOLD let new = const_fold(&root.branches[i]); root.branches[i] = new; diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs @@ -331,8 +331,8 @@ impl Nodes { /// of any syntactic node generated. pub fn yield_type(&self) -> StaticTypes { match self { - Nodes::Num(nn) => { - match nn.value { + Nodes::Num(num) => { + match num.value { Numerics::Natural(_) => StaticTypes::TNatural, Numerics::Integer(_) => StaticTypes::TInteger, Numerics::Real(_) => StaticTypes::TReal, @@ -340,9 +340,40 @@ impl Nodes { }, Nodes::Str(_) => StaticTypes::TString, Nodes::Sym(_) => StaticTypes::TSymbol, - Nodes::Ident(i) => i.static_type.clone(), - Nodes::Call(c) => c.return_type.clone(), - + Nodes::Ident(ident) => { + match ident.value.as_str() { + "Nat" => StaticTypes::TSet(Box::new(StaticTypes::TNatural)), + "Int" => StaticTypes::TSet(Box::new(StaticTypes::TInteger)), + "Real" => StaticTypes::TSet(Box::new(StaticTypes::TReal)), + "Universal" => StaticTypes::TSet(Box::new(StaticTypes::TUnknown)), + _ => ident.static_type.to_owned() + } + }, + Nodes::Call(call) => { + match &*call.callee { + Nodes::Ident(ident) => { + match ident.value.as_str() { + "Set" => return StaticTypes::TSet(Box::new(call.operands[0].yield_type())), + _ => () + }; + }, + Nodes::Call(sub_call) => { + if let Nodes::Ident(ident) = &*sub_call.callee { + match ident.value.as_str() { + "->" => { + return StaticTypes::TFunction( + Box::new(sub_call.operands[0].yield_type()), + Box::new(call.operands[0].yield_type()) + ); + }, + _ => () + } + } + } + _ => () + }; + call.return_type.to_owned() + }, _ => StaticTypes::TUnknown } } diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs @@ -1,5 +1,3 @@ -use std::collections::{HashMap, VecDeque}; - use super::token; use super::ast; use super::operators; @@ -8,7 +6,6 @@ use super::super::err; use token::{Token, TokenType}; use ast::Nodes; -use ast::StaticTypes as ST; pub fn parse(stream : Vec<Token>, file : &str) -> ast::Root { let mut environment = ParseEnvironment::new(stream, file); @@ -24,8 +21,6 @@ struct ParseEnvironment<'a> { pub stream : Vec<Token>, pub optable : operators::PrecedenceTable<'a>, pub file : &'a str, - pub annotations : VecDeque<ast::CallNode>, - pub ident_types : HashMap<String, ast::StaticTypes>, ignore_newline : bool, line_number : usize, @@ -37,8 +32,6 @@ impl<'a> ParseEnvironment<'a> { root: ast::Root::new(), stream: stream, optable: operators::PrecedenceTable::new(), - annotations: VecDeque::new(), - ident_types: HashMap::new(), file, ignore_newline: false, @@ -60,28 +53,7 @@ impl<'a> ParseEnvironment<'a> { self.root.branches.push(e); current = self.stream.get(0); } - self.assign_types(); - } - - fn get_type(&self, node : &ast::Nodes) -> ast::StaticTypes { - if let Some(ident) = node.ident() { - return match ident.value.as_str() { - "Nat" => ST::TSet(Box::new(ST::TNatural)), - "Int" => ST::TSet(Box::new(ST::TInteger)), - "Real" => ST::TSet(Box::new(ST::TReal)), - "Universal" => ST::TSet(Box::new(ST::TUnknown)), - _ => ident.static_type.clone() - }; - } - node.yield_type() - } - - fn remember_type(&mut self, k : String, v : ast::StaticTypes) { - if self.ident_types.contains_key(&k) { - self.ident_types.insert(k, ast::StaticTypes::TUnknown); - return; - } - self.ident_types.insert(k, v); + //self.assign_types(); } fn shift(&mut self) -> Token { @@ -189,27 +161,6 @@ impl<'a> ParseEnvironment<'a> { left = self.func_apply(left); } } - if left.call().is_none() || !left.call().unwrap().is_binary() { return left; } - if let Some(call_ident) = left.call().unwrap().callee.call().unwrap().callee.ident() { - if call_ident.value == ":" { - self.annotations.push_back(left.call().unwrap().clone()); - } - if call_ident.value == "=" { - let maybe_annotation = self.annotations.pop_front(); - if let Some(annotation) = maybe_annotation { - let maybe_set = self.get_type(&annotation.operands[0]); - if let ast::StaticTypes::TSet(set) = maybe_set { - self.remember_type( - left.call().unwrap().callee.call().unwrap().operands[0].ident().unwrap().value.to_owned(), - *set); - } else { - // Error, annotation must be set. - } - } else { - // Error, missing annotation for assignment. - } - } - } return left; } @@ -248,39 +199,6 @@ impl<'a> ParseEnvironment<'a> { "Unexpected token type: `{}`, expected: `{}`.", t.class, tt); } } - - - - fn assign_types(&mut self) { - - fn recurse_type_assign(subtree : &Nodes, map : &HashMap<String, ST>) -> Nodes { - match subtree { - Nodes::Ident(ident_node) => { - if map.contains_key(&ident_node.value) { - let mut cloned_ident = ident_node.clone(); - cloned_ident.static_type = map[&ident_node.value].clone(); - return Nodes::Ident(cloned_ident); - } - }, - Nodes::Call(call_node) => { - let mut cloned_call = call_node.clone(); - cloned_call.callee = Box::new(recurse_type_assign(&*call_node.callee, map)); - cloned_call.operands = vec![recurse_type_assign(&call_node.operands[0], map)]; - return Nodes::Call(cloned_call); - }, - _ => () - }; - return subtree.to_owned(); - }; - - let mut i = 0; - let tree_size = self.root.branches.len(); - - while i < tree_size { - self.root.branches[i] = recurse_type_assign(&self.root.branches[i], &self.ident_types); - i += 1; - } - } } #[cfg(test)] diff --git a/test.vh b/test.vh @@ -1,8 +1,5 @@ -a : Nat -a = 3 + 9 +a : Set Nat +a = Nat -b : Real -b = a + 4 - -c : XyZ -c = hello (3 * 8)- \ No newline at end of file +b : a +b = 6 * 7+ \ No newline at end of file