valhallac

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

commit fdfff7510d9e645cd4d49b30e9a58371ea596aaa
parent 8a2f62cbf31b7686c185371c0c27bb5c13c4cc4c
Author: Demonstrandum <moi@knutsen.co>
Date:   Mon,  5 Aug 2019 04:20:50 +0100

implicit type-casting for static numeric types.

Diffstat:
Msrc/compiler/block.rs | 43+++++++++++++++++++++++++++++++++++++++----
Msrc/compiler/instructions.rs | 2++
Msrc/compiler/internal_functions.rs | 3+++
Msrc/compiler/types.rs | 5++++-
Msrc/syntax/analyser.rs | 112++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/syntax/ast.rs | 31+++++++++++++++++++++++++------
Msrc/syntax/mod.rs | 2+-
Mtest.vh | 3---
8 files changed, 183 insertions(+), 18 deletions(-)

diff --git a/src/compiler/block.rs b/src/compiler/block.rs @@ -38,7 +38,8 @@ pub struct LocalBlock<'a> { constants : Vec<Element<'a>>, instructions : Vec<Instr>, globals : Vec<String>, - pub return_type : ast::StaticTypes, + pub operand_type : ast::StaticTypes, + pub return_type : ast::StaticTypes, // Used only for compilation: locals_map : HashMap<String, u16>, @@ -61,7 +62,8 @@ impl<'a> LocalBlock<'a> { constants: vec![], instructions: vec![], globals: vec![], - return_type: ast::StaticTypes::TUnknown, + operand_type: ast::StaticTypes::TUnknown, + return_type: ast::StaticTypes::TUnknown, locals_map: HashMap::new(), types_to_check: VecDeque::new(), @@ -88,11 +90,16 @@ impl<'a> LocalBlock<'a> { self.locals_map.insert(left.value.to_owned(), index); self.emit(right); - if left.static_type == ast::StaticTypes::TUnknown { + if left.static_type == ast::StaticTypes::TUnknown + || left.static_type != right.yield_type() { self.instructions.push(Instr::Operator(Operators::DUP as u8)); let type_node = self.types_to_check.pop_front().unwrap().1; self.emit(type_node); self.instructions.push(Instr::Operator(Operators::CHECK_TYPE as u8)); + } else { // Otherwise just pop, type was already checked statically so + // its of no use to include in the compiled program, + // as no dynamic checking is needed. + self.types_to_check.pop_front(); } self.instructions.push(Instr::Operator(Operators::STORE_LOCAL as u8)); self.instructions.push(Instr::Operand(index)); @@ -138,10 +145,38 @@ impl<'a> LocalBlock<'a> { &call_node.operands[0], // right ]; + // Check for cast. + if ident.value == "cast" { + self.emit(args[0]); + self.instructions.push(Instr::Operator(Operators::CAST as u8)); + + if let Some(cast_name) = args[1].get_name() { + let cast_to : u16 = match cast_name { + "Real" => 0b00000011, + "Int" => 0b00000010, + "Nat" => 0b00000001, + _ => issue!(err::Types::TypeError, self.filename, err::NO_TOKEN, self.current_line, + "Compiler does not know how to cast to `{}'.", cast_name) + }; + let cast_from = match args[0].yield_type() { + ast::StaticTypes::TReal => 0b00000011, + ast::StaticTypes::TInteger => 0b00000010, + ast::StaticTypes::TNatural => 0b00000001, + _ => issue!(err::Types::TypeError, self.filename, err::NO_TOKEN, self.current_line, + "Compiler does not know how to cast from `{}'.", args[0].yield_type()) + }; + self.instructions.push(Instr::Operand(cast_from << 8 | cast_to)); + } else { + issue!(err::Types::CompError, self.filename, err::NO_TOKEN, self.current_line, + "Cast-type provided to `cast' has to be a type-name.") + } + return; + } + // Check for assignment. if ident.value == "=" { // Direct variable assignment: - if let Some(left) = args[0].ident() { + if let ast::Nodes::Ident(left) = args[0] { self.ident_assignment(left, args[1]); } return; diff --git a/src/compiler/instructions.rs b/src/compiler/instructions.rs @@ -41,6 +41,7 @@ pub enum Operators { SWAP = 8, CALL_1 = 9, CHECK_TYPE = 10, + CAST = 11, N_ADD = 40, I_ADD = 41, @@ -80,6 +81,7 @@ impl fmt::Display for Operators { Operators::SWAP => "SWAP\n", Operators::CALL_1 => "CALL_1\n", Operators::CHECK_TYPE => "CHECK_TYPE\n", + Operators::CAST => "CAST", Operators::N_ADD => "N_ADD\n", Operators::I_ADD => "I_ADD\n", diff --git a/src/compiler/internal_functions.rs b/src/compiler/internal_functions.rs @@ -13,6 +13,9 @@ pub fn get_internal_op(ident : &str, args : Option<&Vec<&ast::Nodes>>) -> Option let unwrapped = args.unwrap(); first = unwrapped[0].yield_type(); is_uni = !unwrapped.iter().all(|e| e.yield_type() == first); + // for n in unwrapped { + // println!("\n|||\n{} -> {}\n|||\n", n, n.yield_type()); + // } } match ident { diff --git a/src/compiler/types.rs b/src/compiler/types.rs @@ -47,7 +47,10 @@ impl<'a> Set<'a> { ast::StaticTypes::TString => is_elem!(e, Element::EString), ast::StaticTypes::TFunction(o, r) => { match e { - Element::ECode(code) => code.return_type == **r, + Element::ECode(code) => { + code.return_type == **r + && code.operand_type == **o + }, _ => false } }, diff --git a/src/syntax/analyser.rs b/src/syntax/analyser.rs @@ -1,6 +1,11 @@ use super::ast; - +/// Constant folding. +/// A static optimisation that relieves the runtime of having to perform +/// pre-computable trivial calculations, by doing them at compile time +/// 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 constant_fold(node : &ast::Nodes) -> Option<ast::Nodes> { if node.num().is_some() { return Some(node.clone()); } if node.call().is_some() && node.call().unwrap().is_binary() { @@ -76,17 +81,118 @@ fn constant_fold(node : &ast::Nodes) -> Option<ast::Nodes> { None } +fn create_cast(node : &ast::Nodes, cast : &ast::StaticTypes) -> ast::Nodes { + let to_type = match cast { + ast::StaticTypes::TReal => ":Real", + ast::StaticTypes::TInteger => ":Int", + ast::StaticTypes::TNatural => ":Nat", + _ => panic!(".is_number() must be broken.") + }; + + let mut cast_node = ast::CallNode::new( + ast::CallNode::new( + ast::IdentNode::new("cast"), + vec![node.clone()]), + vec![ast::SymNode::new(to_type)]); + if let ast::Nodes::Call(ref mut call) = cast_node { + call.set_return_type(cast.clone()) + } + cast_node +} + +fn cast_strenght(st : &ast::StaticTypes) -> i32 { + match st { + ast::StaticTypes::TReal => 4, + ast::StaticTypes::TInteger => 2, + ast::StaticTypes::TNatural => 0, + _ => -1, + } +} + +/// The type balancer is a static utility that checks if something +/// like an arithmetic operator has unequal types (e.g. 4.3 + 6 (Real + Natural)). +/// If it does, it balances the two sides of the expressions by injecting a type +/// 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 { + if call.is_binary() { + let bin_op = call.callee.call().unwrap().callee.ident().unwrap(); + let left = balance_types(&call.callee.call().unwrap().operands[0]); + let right = balance_types(&call.operands[0].clone()); + + let left_yield = left.yield_type(); + let right_yield = right.yield_type(); + if ["+", "-", "*", "/"].contains(&bin_op.value.as_str()) { + if left_yield.is_number() && right_yield.is_number() { + if cast_strenght(&left_yield) != cast_strenght(&right_yield) { + + let casting_right = cast_strenght(&left_yield) > cast_strenght(&right_yield); + let cast_to = (if casting_right { &left } else { &right }).yield_type(); + + let mut new_call; + if casting_right { + new_call = ast::CallNode::new( + *call.callee.clone(), + vec![create_cast(&right, &cast_to)]); + } else { + new_call = ast::CallNode::new( + ast::CallNode::new( + *call.callee.call().unwrap().callee.clone(), + vec![create_cast(&left, &cast_to)]), + vec![right]); + } + if let ast::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 { + c.set_return_type(right_yield); + } + return cloned_node; + } + } + } else if bin_op.value == "=" { + if left_yield.is_number() { + if cast_strenght(&left_yield) > cast_strenght(&right_yield) { + 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 { + c.set_return_type(left_yield); + } + return new_call; + } + } + } + } + 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 { + c.set_return_type(node.yield_type()); + } + } + return node.to_owned(); +} + pub fn replace(root : &mut ast::Root) { let length = root.branches.len(); let mut i = 0; while i < length { - let node = &root.branches[i]; { // START TOP-LEVEL CONSTANT FOLD - let new = constant_fold(node); + let new = constant_fold(&root.branches[i]); if let Some(branch) = new { root.branches[i] = branch; } } // END TOP-LEVEL CONSTANT FOLD + { // START TOP-LEVEL TYPE BALANCING + let new = balance_types(&root.branches[i]); + root.branches[i] = new; + } // END TOP-LEVEL TYPE BALANCING i += 1; } } \ No newline at end of file diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs @@ -216,8 +216,6 @@ pub struct CallNode { /// Pointer to list of operand nodes. pub operands : Vec<Nodes>, - /// What type its operand is. - pub operand_type : StaticTypes, /// What type it returns. pub return_type : StaticTypes } @@ -255,6 +253,17 @@ pub enum StaticTypes { TUnknown } +impl StaticTypes { + pub fn is_number(&self) -> bool { + match self { + StaticTypes::TNatural + | StaticTypes::TInteger + | StaticTypes::TReal => true, + _ => false + } + } +} + impl fmt::Display for StaticTypes { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let s = match self { @@ -331,14 +340,21 @@ impl Nodes { Nodes::Str(_) => StaticTypes::TString, Nodes::Sym(_) => StaticTypes::TSymbol, Nodes::Ident(i) => i.static_type.clone(), - Nodes::Call(c) => StaticTypes::TFunction( - Box::new(c.operand_type.clone()), - Box::new(c.return_type.clone())), + Nodes::Call(c) => c.return_type.clone(), _ => StaticTypes::TUnknown } } + pub fn get_name(&self) -> Option<&str> { + match self { + Nodes::Str(n) => Some(n.value.as_str()), + Nodes::Sym(n) => Some(n.value.as_str()), + Nodes::Ident(n) => Some(n.value.as_str()), + _ => None + } + } + pub fn ident(&self) -> Option<&IdentNode> { unwrap_enum!(self, Nodes::Ident) } pub fn num(&self) -> Option<&NumNode> { unwrap_enum!(self, Nodes::Num) } pub fn str(&self) -> Option<&StrNode> { unwrap_enum!(self, Nodes::Str) } @@ -397,11 +413,14 @@ impl CallNode { Nodes::Call(CallNode { callee: Box::new(callee), operands: operands, - operand_type: StaticTypes::TUnknown, return_type: StaticTypes::TUnknown }) } + pub fn set_return_type(&mut self, new_type : StaticTypes) { + self.return_type = new_type; + } + pub fn is_unary(&self) -> bool { self.callee.ident().is_some() && !self.operands.is_empty() } diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs @@ -38,7 +38,7 @@ pub fn parse_file(filename : &str) -> ast::Root { println!("Stream:\n{}\n", stream.to_string()); let mut tree = parser::parse(stream, filename); - println!("AST:\n{}\n", tree); analyser::replace(&mut tree); + println!("AST:\n{}\n", tree); tree } \ No newline at end of file diff --git a/test.vh b/test.vh @@ -4,5 +4,3 @@ a = 3 + 9 b : Real b = a + 4 -c : XYZ -c = a + b- \ No newline at end of file