Ensure that if expressions parse and build correctly.

This commit is contained in:
James Harton 2019-03-18 20:20:39 +13:00
parent 536e5cc5f4
commit ee3df9a527
6 changed files with 175 additions and 52 deletions

View file

@ -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<IR>,
}
@ -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<IR> {
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)]

View file

@ -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());
}

View file

@ -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>) -> IR {
pub fn new_call(
result_type: TyIdx,
location: Location,
function: IR,
arguments: Vec<IR>,
) -> 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<IR>),
Call(Box<IR>, Vec<IR>),
/// 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<IR>, BlockIdx, BlockIdx),
/// Unconditional jump.
Jump(BlockIdx),
/// Jump if IR value is false.
JumpIfFalse(Box<IR>, BlockIdx),
/// Jump if IR value is true.
JumpIfTrue(Box<IR>, BlockIdx),
/// A binary operation with LHS and RHS.
Infix(BinOp, Box<IR>, Box<IR>),
/// Take a value and set it as a local variable.

View file

@ -17,7 +17,7 @@ impl StringTable {
}
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct StringIdx(Sym);
impl From<Sym> for StringIdx {

View file

@ -521,12 +521,7 @@ impl<'a> From<Pair<'a, Rule>> 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(

View file

@ -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* }