diff --git a/huia-compiler/src/context.rs b/huia-compiler/src/context.rs index 3d5a6e9..7d68a2b 100644 --- a/huia-compiler/src/context.rs +++ b/huia-compiler/src/context.rs @@ -188,6 +188,7 @@ impl Context { let types = vec![ Ty::native_array(strings.intern("Huia.Native.Array")), Ty::native_atom(strings.intern("Huia.Native.Atom")), + Ty::native_boolean(strings.intern("Huia.Native.Boolean")), Ty::native_float(strings.intern("Huia.Native.Float")), Ty::native_integer(strings.intern("Huia.Native.Integer")), Ty::native_map(strings.intern("Huia.Native.Map")), diff --git a/huia-compiler/src/ir/builder.rs b/huia-compiler/src/ir/builder.rs index 7260638..8bfb52a 100644 --- a/huia-compiler/src/ir/builder.rs +++ b/huia-compiler/src/ir/builder.rs @@ -19,6 +19,7 @@ impl Builder { /// Consume an AST node and build IR. /// /// Any IR will be on the Builder's stack. + #[allow(clippy::cyclomatic_complexity)] pub fn build(&mut self, node: Term, mut context: &mut Context) { match node.node_type() { NodeType::Array => { @@ -58,7 +59,18 @@ impl Builder { self.build(rhs.clone(), &mut context); let rhs = self.pop_ir().unwrap(); let lhs = self.pop_ir().unwrap(); - let result_type = lhs.result_type(); + + let result_type = if op.is_logical() { + let name = context.constant_string("Huia.Native.Boolean"); + context.reference_type(&name, location.clone()) + } + else if op.is_arithmetic() { + lhs.result_type() + } + else { + panic!("Operator {:?} is neither arithmetic nor logical", op); + }; + self.push_ir(IR::new_infix( result_type, location, diff --git a/huia-compiler/src/ir/constant_folding.rs b/huia-compiler/src/ir/constant_folding.rs index 4df469d..83c3d01 100644 --- a/huia-compiler/src/ir/constant_folding.rs +++ b/huia-compiler/src/ir/constant_folding.rs @@ -1,6 +1,7 @@ use crate::context::Context; use crate::ir::{IRKind, Val, IR}; use huia_parser::ast::binary::Operator as BinOp; +use huia_parser::ast::unary::Operator as UnOp; use std::f64; /// Perform constant folding on the provided node, or not. @@ -17,7 +18,7 @@ pub fn pass(ir: &mut IR, context: &mut Context) { if lhs.is_integer() && rhs.is_integer() { let lhs = lhs.integer().unwrap(); let rhs = rhs.integer().unwrap(); - let value = perform_i64(&op, lhs, rhs); + let value = perform_i64_infix_fold(&op, lhs, rhs); if let Some(value) = value { ir.set_kind(IRKind::Constant(Val::Integer(value))); let idx = context.constant_string("Huia.Native.Integer"); @@ -26,17 +27,57 @@ pub fn pass(ir: &mut IR, context: &mut Context) { } else if lhs.is_float() && rhs.is_float() { let lhs = lhs.float().unwrap(); let rhs = rhs.float().unwrap(); - let value = perform_f64(&op, lhs, rhs); + let value = perform_f64_infix_fold(&op, lhs, rhs); if let Some(value) = value { ir.set_kind(IRKind::Constant(Val::Float(value))); let idx = context.constant_string("Huia.Native.Float"); ir.set_result_type(context.reference_type(&idx, ir.location.clone())); } } + } else if ir.is_unary() { + let (op, rhs) = ir.get_unary().unwrap(); + if !rhs.is_constant() { + return; + } + let rhs = rhs.get_value().unwrap(); + + match op { + UnOp::LogicalNot => { + if rhs.is_boolean() { + // if the RHS is a boolean constant then fold it. + let value = rhs.boolean().unwrap(); + ir.set_kind(IRKind::Constant(Val::Boolean(!value))); + } else { + // otherwise just return false for all other constant values. + ir.set_kind(IRKind::Constant(Val::Boolean(false))); + } + let tyname = context.constant_string("Huia.Native.Boolean"); + let ty = context.reference_type(&tyname, ir.location.clone()); + ir.set_result_type(ty) + } + UnOp::Minus => { + if rhs.is_integer() { + let value = rhs.integer().unwrap(); + ir.set_kind(IRKind::Constant(Val::Integer(-value))); + } else if rhs.is_float() { + let value = rhs.float().unwrap(); + ir.set_kind(IRKind::Constant(Val::Float(-value))); + } + } + UnOp::Plus => { + if rhs.is_integer() { + let value = rhs.integer().unwrap(); + ir.set_kind(IRKind::Constant(Val::Integer(value))); + } else if rhs.is_float() { + let value = rhs.float().unwrap(); + ir.set_kind(IRKind::Constant(Val::Float(value))); + } + } + } } } -fn perform_i64(op: &BinOp, lhs: i64, rhs: i64) -> Option { +fn perform_i64_infix_fold(op: &BinOp, lhs: i64, rhs: i64) -> Option { match op { BinOp::BitwiseAnd => Some(lhs & rhs), BinOp::BitwiseOr => Some(lhs | rhs), @@ -53,7 +94,7 @@ fn perform_i64(op: &BinOp, lhs: i64, rhs: i64) -> Option { } } -fn perform_f64(op: &BinOp, lhs: f64, rhs: f64) -> Option { +fn perform_f64_infix_fold(op: &BinOp, lhs: f64, rhs: f64) -> Option { match op { BinOp::Divide => Some(lhs / rhs), BinOp::Minus => Some(lhs - rhs), @@ -71,7 +112,7 @@ mod test { use huia_parser::ast::Term; #[test] - fn integer_folding() { + fn integer_infix_folding() { let term = Term::input("3 * 2 + 13 / 2").unwrap()[0].clone(); let mut context = Context::test(); let mut builder = Builder::default(); @@ -85,7 +126,7 @@ mod test { } #[test] - fn float_folding() { + fn float_infix_folding() { let term = Term::input("1.5 * 2.0 / 3.0").unwrap()[0].clone(); let mut context = Context::test(); let mut builder = Builder::default(); @@ -97,4 +138,21 @@ mod test { assert!(ir.is_constant()); assert!(ir.get_value().unwrap().float().unwrap() - 1.0 < f64::EPSILON); } + + #[test] + fn integer_unary_folding() { + let term = Term::input("!3").unwrap()[0].clone(); + let mut context = Context::test(); + let mut builder = Builder::default(); + + builder.build(term, &mut context); + let mut ir = builder.pop_ir().unwrap(); + ir.improve(&mut context, pass); + + assert!(ir.is_constant()); + assert!(!ir.get_value().unwrap().boolean().unwrap()); + + let ty = context.get_type(&ir.result_type()).unwrap(); + assert!(ty.is_native_boolean()); + } } diff --git a/huia-compiler/src/ir/desugar_infix.rs b/huia-compiler/src/ir/desugar_infix.rs new file mode 100644 index 0000000..53a2f97 --- /dev/null +++ b/huia-compiler/src/ir/desugar_infix.rs @@ -0,0 +1,59 @@ +use crate::context::Context; +use crate::ir::{IRKind, IR}; +use huia_parser::ast::binary::Operator as BinOp; + +/// Desugar infix operations into method calls. +pub fn pass(ir: &mut IR, context: &mut Context) { + if ir.is_infix() { + let (op, lhs, rhs) = ir.get_infix().unwrap(); + let method_name = match op { + BinOp::BitwiseAnd => "bitwise_and", + BinOp::BitwiseOr => "bitwise_or", + BinOp::BitwiseXor => "bitwise_xor", + BinOp::Divide => "divide", + BinOp::Equal => "equal", + BinOp::Exponent => "exponent", + BinOp::GreaterThan => "greater_than", + BinOp::GreaterThanOrEqual => "greater_than_or_equal", + BinOp::LessThan => "less_than", + BinOp::LessThanOrEqual => "less_than_or_equal", + BinOp::LogicalAnd => "logical_and", + BinOp::LogicalOr => "logical_or", + BinOp::Minus => "minus", + BinOp::Modulus => "modulus", + BinOp::Multiply => "multiply", + BinOp::NotEqual => "not_equal", + BinOp::Plus => "plus", + BinOp::ShiftLeft => "shift_left", + BinOp::ShiftRight => "shift_right", + }; + let method_name = context.constant_string(method_name); + ir.kind = IRKind::MethodCall(Box::new(lhs.clone()), method_name, vec![rhs.clone()]); + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::ir::builder::Builder; + use huia_parser::ast::Term; + + #[test] + fn works() { + let terms = Term::input("3 * 2").unwrap(); + let mut context = Context::test(); + let mut builder = Builder::default(); + + for term in terms { + builder.build(term, &mut context); + } + + let mut ir = builder.pop_ir().unwrap(); + + assert!(ir.is_infix()); + + ir.improve(&mut context, pass); + + assert!(ir.is_method_call()); + } +} diff --git a/huia-compiler/src/ir/desugar_unary.rs b/huia-compiler/src/ir/desugar_unary.rs new file mode 100644 index 0000000..98547c0 --- /dev/null +++ b/huia-compiler/src/ir/desugar_unary.rs @@ -0,0 +1,43 @@ +use crate::context::Context; +use crate::ir::{IRKind, IR}; +use huia_parser::ast::unary::Operator as UnOp; + +/// Desugar unary operations into method calls. +pub fn pass(ir: &mut IR, context: &mut Context) { + if ir.is_unary() { + let (op, rhs) = ir.get_unary().unwrap(); + let method_name = match op { + UnOp::LogicalNot => "logical_not", + UnOp::Minus => "unary_minus", + UnOp::Plus => "unary_plus", + }; + let method_name = context.constant_string(method_name); + ir.kind = IRKind::MethodCall(Box::new(rhs.clone()), method_name, Vec::default()); + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::ir::builder::Builder; + use huia_parser::ast::Term; + + #[test] + fn works() { + let terms = Term::input("!3").unwrap(); + let mut context = Context::test(); + let mut builder = Builder::default(); + + for term in terms { + builder.build(term, &mut context); + } + + let mut ir = builder.pop_ir().unwrap(); + + assert!(ir.is_unary()); + + ir.improve(&mut context, pass); + + assert!(ir.is_method_call()); + } +} diff --git a/huia-compiler/src/ir/mod.rs b/huia-compiler/src/ir/mod.rs index f5cdbc5..aada17f 100644 --- a/huia-compiler/src/ir/mod.rs +++ b/huia-compiler/src/ir/mod.rs @@ -1,5 +1,7 @@ -mod builder; -mod constant_folding; +pub mod builder; +pub mod constant_folding; +pub mod desugar_infix; +pub mod desugar_unary; use crate::block::BlockIdx; use crate::context::Context; @@ -33,6 +35,13 @@ impl IR { } } + pub fn get_unary(&self) -> Option<(&UnOp, &IR)> { + match self.kind { + IRKind::Unary(ref op, ref rhs) => Some((op, rhs)), + _ => None, + } + } + pub fn get_value(&self) -> Option<&Val> { match self.kind { IRKind::Constant(ref value) => Some(value), @@ -92,7 +101,7 @@ impl IR { } IRKind::MethodCall(ref mut callee, _, ref mut arguments) => { improver(callee, &mut context); - for ref mut argument in arguments { + for argument in arguments { improver(argument, &mut context); } } @@ -189,6 +198,13 @@ impl IR { } } + pub fn is_method_call(&self) -> bool { + match self.kind { + IRKind::MethodCall(..) => true, + _ => false, + } + } + pub fn is_type_reference(&self) -> bool { match self.kind { IRKind::TypeReference(..) => true, @@ -428,6 +444,13 @@ impl Val { } } + pub fn boolean(&self) -> Option { + match self { + Val::Boolean(ref value) => Some(*value), + _ => None, + } + } + pub fn float(&self) -> Option { match self { Val::Float(ref value) => Some(*value), diff --git a/huia-compiler/src/lib.rs b/huia-compiler/src/lib.rs index 6d0a725..4187026 100644 --- a/huia-compiler/src/lib.rs +++ b/huia-compiler/src/lib.rs @@ -1,5 +1,6 @@ mod block; mod context; +mod env; mod error; mod function; mod ir; @@ -7,4 +8,25 @@ mod location; mod method; mod stable; mod ty; -mod env; + +use crate::context::Context; +use huia_parser::ast::Term; +use std::fs; + +pub fn compile_file(path: &str) -> Context { + let contents = fs::read_to_string(path).expect("Unable to open file"); + let terms = Term::file(&contents).expect("Unable to parse file"); + let mut context = Context::new(path); + let mut builder = ir::builder::Builder::default(); + for term in terms { + builder.build(term, &mut context); + } + + context.improve_ir(|mut ir, mut context| { + ir::constant_folding::pass(&mut ir, &mut context); + ir::desugar_infix::pass(&mut ir, &mut context); + ir::desugar_unary::pass(&mut ir, &mut context); + }); + + context +} diff --git a/huia-compiler/src/ty.rs b/huia-compiler/src/ty.rs index 5f8e16f..f1f0f2f 100644 --- a/huia-compiler/src/ty.rs +++ b/huia-compiler/src/ty.rs @@ -1,4 +1,3 @@ -use crate::ir::IR; use crate::location::Location; use crate::stable::StringIdx; use std::collections::BTreeMap; @@ -32,6 +31,13 @@ impl Ty { } } + pub fn is_native_boolean(&self) -> bool { + match self.inner { + TyInner::NativeBoolean => true, + _ => false, + } + } + pub fn is_unresolved(&self) -> bool { match self.kind { TyKind::Unresolved => true, @@ -135,6 +141,15 @@ impl Ty { } } + pub fn native_boolean(name: StringIdx) -> Ty { + Ty { + name: Some(name), + location: None, + kind: TyKind::Native, + inner: TyInner::NativeBoolean, + } + } + pub fn native_float(name: StringIdx) -> Ty { Ty { name: Some(name), @@ -223,6 +238,7 @@ enum TyInner { }, NativeArray, NativeAtom, + NativeBoolean, NativeFloat, NativeInteger, NativeMap, diff --git a/huia-parser/src/ast/binary.rs b/huia-parser/src/ast/binary.rs index e9f09ee..6f3fb3b 100644 --- a/huia-parser/src/ast/binary.rs +++ b/huia-parser/src/ast/binary.rs @@ -9,6 +9,16 @@ pub struct Binary { location: InputLocation, } +impl Binary { + pub fn is_arithmetic(&self) -> bool { + self.value.is_arithmetic() + } + + pub fn is_logical(&self) -> bool { + self.value.is_logical() + } +} + #[derive(Debug, Clone, PartialEq)] pub enum Operator { BitwiseAnd, @@ -23,7 +33,6 @@ pub enum Operator { LessThanOrEqual, LogicalAnd, LogicalOr, - Method, Minus, Modulus, Multiply, @@ -33,6 +42,39 @@ pub enum Operator { ShiftRight, } +impl Operator { + pub fn is_arithmetic(&self) -> bool { + match self { + Operator::BitwiseAnd => true, + Operator::BitwiseOr => true, + Operator::BitwiseXor => true, + Operator::Divide => true, + Operator::Exponent => true, + Operator::Minus => true, + Operator::Modulus => true, + Operator::Multiply => true, + Operator::Plus => true, + Operator::ShiftLeft => true, + Operator::ShiftRight => true, + _ => false, + } + } + + pub fn is_logical(&self) -> bool { + match self { + Operator::Equal => true, + Operator::GreaterThan => true, + Operator::GreaterThanOrEqual => true, + Operator::LessThan => true, + Operator::LessThanOrEqual => true, + Operator::LogicalAnd => true, + Operator::LogicalOr => true, + Operator::NotEqual => true, + _ => false, + } + } +} + impl Value for Binary { type Item = Operator; diff --git a/huia-parser/src/ast/function.rs b/huia-parser/src/ast/function.rs index d3f19cf..9ea0338 100644 --- a/huia-parser/src/ast/function.rs +++ b/huia-parser/src/ast/function.rs @@ -31,7 +31,7 @@ impl<'a> From> for Function { fn from(pair: Pair<'a, Rule>) -> Self { match pair.as_rule() { Rule::function => { - let clauses = pair.clone().into_inner().map(|p| Clause::from(p)).collect(); + let clauses = pair.clone().into_inner().map(Clause::from).collect(); let location = InputLocation::from(pair.into_span()); Function { clauses, location } } diff --git a/huia-parser/src/ast/unary.rs b/huia-parser/src/ast/unary.rs index 3a5c0ce..9767f5b 100644 --- a/huia-parser/src/ast/unary.rs +++ b/huia-parser/src/ast/unary.rs @@ -9,6 +9,16 @@ pub struct Unary { location: InputLocation, } +impl Unary { + pub fn is_arithmetic(&self) -> bool { + self.value.is_arithmetic() + } + + pub fn is_logical(&self) -> bool { + self.value.is_logical() + } +} + #[derive(Debug, Clone, PartialEq)] pub enum Operator { LogicalNot, @@ -16,6 +26,23 @@ pub enum Operator { Plus, } +impl Operator { + pub fn is_arithmetic(&self) -> bool { + match self { + Operator::Minus => true, + Operator::Plus => true, + _ => false, + } + } + + pub fn is_logical(&self) -> bool { + match self { + Operator::LogicalNot => true, + _ => false, + } + } +} + impl Value for Unary { type Item = Operator;