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:
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