diff --git a/huia-compiler/src/block.rs b/huia-compiler/src/block.rs index 52fa7e2..1f10609 100644 --- a/huia-compiler/src/block.rs +++ b/huia-compiler/src/block.rs @@ -1,8 +1,11 @@ use crate::ir::IR; +use crate::stable::StringIdx; +use crate::ty::TyIdx; use std::slice::IterMut; #[derive(Debug, Default, PartialEq, Clone)] pub struct Block { + arguments: Vec<(StringIdx, TyIdx)>, ir: Vec, } @@ -20,6 +23,13 @@ impl Block { self.ir.iter_mut() } + pub fn new(arguments: Vec<(StringIdx, TyIdx)>) -> Block { + Block { + arguments, + ir: Vec::default(), + } + } + pub fn pop(&mut self) -> Option { self.ir.pop() } @@ -27,6 +37,10 @@ impl Block { pub fn push(&mut self, ir: IR) { self.ir.push(ir); } + + pub fn set_arguments(&mut self, arguments: Vec<(StringIdx, TyIdx)>) { + self.arguments = arguments; + } } #[derive(Debug, Clone, PartialEq)] diff --git a/huia-compiler/src/ir/builder.rs b/huia-compiler/src/ir/builder.rs index 2364e5f..7641b0e 100644 --- a/huia-compiler/src/ir/builder.rs +++ b/huia-compiler/src/ir/builder.rs @@ -79,12 +79,8 @@ impl Builder { let result_type = context.unknown_type(location.clone()); - self.push_ir(IR::new_get_local( - result_type.clone(), - location.clone(), - name, - )); - self.push_ir(IR::new_call(result_type, location, arguments)); + let local = IR::new_get_local(result_type.clone(), location.clone(), name); + self.push_ir(IR::new_call(result_type, location, local, arguments)); } NodeType::Constructor => { let (ty, props) = node.constructor().unwrap(); @@ -152,7 +148,8 @@ impl Builder { self.build(node.clone(), &mut context); } - let clause_data = self.pop_block().unwrap(); + let mut clause_data = self.pop_block().unwrap(); + clause_data.set_arguments(arguments.clone()); let idx = context.push_block(clause_data); fun.push(arguments, idx); } @@ -180,15 +177,31 @@ impl Builder { self.build(node.clone(), &mut context); } let negative = self.pop_block().unwrap(); - let negative_idx = context.push_block(negative); - self.push_ir(IR::new_if( - result_type, - location, - test, - positive_idx, - negative_idx, - )); + if negative.is_empty() { + // there's no `else` block, so let's just skip it. + self.push_ir(IR::new_jump_if_true( + result_type, + location, + test, + positive_idx, + )); + } else { + // there is an else block, so let's push true and false jumps. + let negative_idx = context.push_block(negative); + self.push_ir(IR::new_jump_if_true( + result_type.clone(), + location.clone(), + test.clone(), + positive_idx, + )); + self.push_ir(IR::new_jump_if_false( + result_type, + location, + test, + negative_idx, + )); + } } NodeType::ImplDef => { let (ty, body) = node.impldef().unwrap(); @@ -319,7 +332,8 @@ impl Builder { for node in block { self.build(node.clone(), &mut context); } - let clause_data = self.pop_block().unwrap(); + let mut clause_data = self.pop_block().unwrap(); + clause_data.set_arguments(arguments.clone()); let idx = context.push_block(clause_data); method.push(arguments, idx); @@ -373,7 +387,8 @@ impl Builder { for node in block { self.build(node.clone(), &mut context); } - let clause_data = self.pop_block().unwrap(); + let mut clause_data = self.pop_block().unwrap(); + clause_data.set_arguments(arguments.clone()); let idx = context.push_block(clause_data); method.push(arguments, idx); @@ -427,7 +442,8 @@ impl Builder { for node in block { self.build(node.clone(), &mut context); } - let clause_data = self.pop_block().unwrap(); + let mut clause_data = self.pop_block().unwrap(); + clause_data.set_arguments(arguments.clone()); let idx = context.push_block(clause_data); method.push(arguments, idx); @@ -663,6 +679,33 @@ mod test { assert!(context.find_type("Huia.Native.Integer").is_some()); } + #[test] + fn test_if() { + let term = Term::input(r#" if true do 123 end "#).unwrap()[0].clone(); + let mut builder = Builder::default(); + let mut context = Context::test(); + builder.build(term, &mut context); + let ir = builder.pop_ir().unwrap(); + assert!(ir.is_jump_if_true()); + let block = builder.pop_block().unwrap(); + assert!(block.is_empty()); + } + + #[test] + fn test_if_else() { + let term = Term::input(r#" if true do 123 else 456 end "#).unwrap()[0].clone(); + let mut builder = Builder::default(); + let mut context = Context::test(); + builder.build(term, &mut context); + let ir = builder.pop_ir().unwrap(); + assert!(ir.is_jump_if_false()); + let ir = builder.pop_ir().unwrap(); + assert!(ir.is_jump_if_true()); + let block = builder.pop_block().unwrap(); + println!("Block: {:?}", block); + assert!(block.is_empty()); + } + #[test] fn test_infix() { let term = Term::input(r#" 1 + 2 "#).unwrap()[0].clone(); @@ -704,9 +747,7 @@ mod test { let mut context = Context::test(); builder.build(term, &mut context); let call = builder.pop_ir().unwrap(); - let receiver = builder.pop_ir().unwrap(); assert!(call.is_call()); - assert!(receiver.is_get_local()); assert!(context.find_type("Huia.Native.Integer").is_some()); } diff --git a/huia-compiler/src/ir/mod.rs b/huia-compiler/src/ir/mod.rs index 112340f..bc57701 100644 --- a/huia-compiler/src/ir/mod.rs +++ b/huia-compiler/src/ir/mod.rs @@ -51,7 +51,8 @@ impl IR { improver: F, ) { match self.kind { - IRKind::Call(ref mut args) => { + IRKind::Call(ref mut fun, ref mut args) => { + improver(fun, &mut context); for mut arg in args.iter_mut() { improver(&mut arg, &mut context); } @@ -78,13 +79,17 @@ impl IR { IRKind::FunctionRef(..) => (), IRKind::GetLocal(..) => (), IRKind::GetProperty(..) => (), - IRKind::If(ref mut test, ..) => { - improver(test, &mut context); - } IRKind::Infix(_, ref mut lhs, ref mut rhs) => { improver(lhs, &mut context); improver(rhs, &mut context); } + IRKind::Jump(..) => (), + IRKind::JumpIfFalse(ref mut test, ..) => { + improver(test, &mut context); + } + IRKind::JumpIfTrue(ref mut test, ..) => { + improver(test, &mut context); + } IRKind::SetLocal(_, ref mut rhs) => { improver(rhs, &mut context); } @@ -157,6 +162,27 @@ impl IR { } } + pub fn is_jump(&self) -> bool { + match self.kind { + IRKind::Jump(..) => true, + _ => false, + } + } + + pub fn is_jump_if_false(&self) -> bool { + match self.kind { + IRKind::JumpIfFalse(..) => true, + _ => false, + } + } + + pub fn is_jump_if_true(&self) -> bool { + match self.kind { + IRKind::JumpIfTrue(..) => true, + _ => false, + } + } + pub fn is_type_reference(&self) -> bool { match self.kind { IRKind::TypeReference(..) => true, @@ -178,11 +204,16 @@ impl IR { } } - pub fn new_call(result_type: TyIdx, location: Location, arguments: Vec) -> IR { + pub fn new_call( + result_type: TyIdx, + location: Location, + function: IR, + arguments: Vec, + ) -> IR { IR { location, result_type, - kind: IRKind::Call(arguments), + kind: IRKind::Call(Box::new(function), arguments), } } @@ -230,20 +261,6 @@ impl IR { } } - pub fn new_if( - result_type: TyIdx, - location: Location, - test: IR, - positive: BlockIdx, - negative: BlockIdx, - ) -> IR { - IR { - location, - result_type, - kind: IRKind::If(Box::new(test), positive, negative), - } - } - pub fn new_infix(result_type: TyIdx, location: Location, op: BinOp, lhs: IR, rhs: IR) -> IR { let lhs = Box::new(lhs); let rhs = Box::new(rhs); @@ -254,6 +271,40 @@ impl IR { } } + pub fn new_jump(result_type: TyIdx, location: Location, target: BlockIdx) -> IR { + IR { + location, + result_type, + kind: IRKind::Jump(target), + } + } + + pub fn new_jump_if_false( + result_type: TyIdx, + location: Location, + test: IR, + target: BlockIdx, + ) -> IR { + IR { + location, + result_type, + kind: IRKind::JumpIfFalse(Box::new(test), target), + } + } + + pub fn new_jump_if_true( + result_type: TyIdx, + location: Location, + test: IR, + target: BlockIdx, + ) -> IR { + IR { + location, + result_type, + kind: IRKind::JumpIfTrue(Box::new(test), target), + } + } + pub fn new_set_local(result_type: TyIdx, location: Location, name: StringIdx, value: IR) -> IR { IR { location, @@ -306,7 +357,7 @@ impl IR { #[derive(Debug, PartialEq, Clone)] pub enum IRKind { /// Call top of the stack with arguments. - Call(Vec), + Call(Box, Vec), /// A constant value of the specified type. Constant(Val), /// Construct an instance of type with the provided properties. @@ -317,8 +368,12 @@ pub enum IRKind { GetLocal(StringIdx), /// Refernce an instance property. GetProperty(StringIdx), - /// Consists of test, positives, negatives. - If(Box, BlockIdx, BlockIdx), + /// Unconditional jump. + Jump(BlockIdx), + /// Jump if IR value is false. + JumpIfFalse(Box, BlockIdx), + /// Jump if IR value is true. + JumpIfTrue(Box, BlockIdx), /// A binary operation with LHS and RHS. Infix(BinOp, Box, Box), /// Take a value and set it as a local variable. diff --git a/huia-compiler/src/stable.rs b/huia-compiler/src/stable.rs index e835fe9..2dc232c 100644 --- a/huia-compiler/src/stable.rs +++ b/huia-compiler/src/stable.rs @@ -17,7 +17,7 @@ impl StringTable { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)] pub struct StringIdx(Sym); impl From for StringIdx { diff --git a/huia-parser/src/ast/term.rs b/huia-parser/src/ast/term.rs index bc5ec8f..dece1ff 100644 --- a/huia-parser/src/ast/term.rs +++ b/huia-parser/src/ast/term.rs @@ -521,12 +521,7 @@ impl<'a> From> for Term { Rule::if_expression => { let mut inner = pair.clone().into_inner(); let test = Term::from(inner.next().unwrap()); - let positives = inner - .next() - .unwrap() - .into_inner() - .map(|p| Term::from(p)) - .collect(); + let positives = inner.next().unwrap().into_inner().map(Term::from).collect(); let negatives = inner.next(); if negatives.is_some() { let negatives = negatives.unwrap().into_inner().map(Term::from).collect(); @@ -1087,6 +1082,24 @@ mod test { assert_eq!(clause1.body().len(), 1); } + #[test] + fn test_if() { + let terms = Term::input(" if true do 123 end ").unwrap(); + let (_test, positive, negative) = terms[0].if_expr().unwrap(); + assert_eq!(positive.len(), 1); + assert!(negative.is_empty()); + } + + #[test] + fn test_if_else() { + let terms = Term::input(" if true do 123 else 456 end ").unwrap(); + let (_test, positive, negative) = terms[0].if_expr().unwrap(); + println!("positive: {:?}", positive); + println!("negative: {:?}", negative); + assert_eq!(positive.len(), 1); + assert_eq!(negative.len(), 1); + } + // #[test] // fn test_acceptance() { // let terms = Term::input( diff --git a/huia-parser/src/grammar.pest b/huia-parser/src/grammar.pest index b67c49c..cf669cb 100644 --- a/huia-parser/src/grammar.pest +++ b/huia-parser/src/grammar.pest @@ -3,7 +3,7 @@ file = _{ SOI ~ definition* ~ EOI } WHITESPACE = _{ (" " | "\t" | "\r" | "\n")+ } newline = _{ (" " | "\t")* ~ ("\n" | "\r")+ ~ (" " | "\t")* } -reserved = { "end" | "let" | "true" | "false" } +reserved = { "end" | "let" | "true" | "false" | "else" } expression = _{ infix | expression_inner } infix = { expression_inner ~ (binary_operator ~ expression_inner)+ } @@ -53,7 +53,7 @@ literal = _{ constructor | map | array | typename | string | ident = @{ !reserved ~ 'a'..'z' ~ ('a'..'z' | 'A'..'Z' | "_")* } keyword = @{ ident ~ ":" } -if_expression = { "if" ~ instance_espression ~ if_positive ~ (if_negative | "end") } +if_expression = { "if" ~ instance_espression ~ if_positive ~ if_negative? ~ "end" } if_positive = { "do" ~ instance_espression* } if_negative = { "else" ~ instance_espression* }