Add some simple desugaring to the compiler.
This commit is contained in:
parent
a45d9627dc
commit
91a52574c8
11 changed files with 317 additions and 14 deletions
|
@ -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")),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<i64> {
|
||||
fn perform_i64_infix_fold(op: &BinOp, lhs: i64, rhs: i64) -> Option<i64> {
|
||||
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<i64> {
|
|||
}
|
||||
}
|
||||
|
||||
fn perform_f64(op: &BinOp, lhs: f64, rhs: f64) -> Option<f64> {
|
||||
fn perform_f64_infix_fold(op: &BinOp, lhs: f64, rhs: f64) -> Option<f64> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
59
huia-compiler/src/ir/desugar_infix.rs
Normal file
59
huia-compiler/src/ir/desugar_infix.rs
Normal file
|
@ -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());
|
||||
}
|
||||
}
|
43
huia-compiler/src/ir/desugar_unary.rs
Normal file
43
huia-compiler/src/ir/desugar_unary.rs
Normal file
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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<bool> {
|
||||
match self {
|
||||
Val::Boolean(ref value) => Some(*value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn float(&self) -> Option<f64> {
|
||||
match self {
|
||||
Val::Float(ref value) => Some(*value),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ impl<'a> From<Pair<'a, Rule>> 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 }
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Reference in a new issue