Fix function and method calling once and for all.

This commit is contained in:
James Harton 2019-03-21 10:18:51 +13:00
parent 7be2c3e525
commit a45d9627dc
7 changed files with 154 additions and 81 deletions

View file

@ -35,4 +35,5 @@ impl Error for CompileError {
#[derive(Debug)]
pub enum ErrorKind {
TypeRedefined,
UnknownVariable,
}

View file

@ -1,5 +1,6 @@
use crate::block::Block;
use crate::context::Context;
use crate::error::ErrorKind;
use crate::function::Function;
use crate::ir::{Val, IR};
use crate::method::Method;
@ -67,9 +68,11 @@ impl Builder {
));
}
NodeType::Call => {
let (name, args) = node.call().unwrap();
let (callee, args) = node.call().unwrap();
let location = context.location(&node.location());
let name = context.constant_string(name.value_ref().as_str());
self.build(callee.clone(), &mut context);
let callee = self.pop_ir().unwrap();
self.push_block();
for node in args {
@ -79,8 +82,7 @@ impl Builder {
let result_type = context.unknown_type(location.clone());
let local = IR::new_get_local(result_type.clone(), location.clone(), name);
self.push_ir(IR::new_call(result_type, location, local, arguments));
self.push_ir(IR::new_call(result_type, location, callee, arguments));
}
NodeType::Constructor => {
let (ty, props) = node.constructor().unwrap();
@ -239,10 +241,16 @@ impl Builder {
}
NodeType::Local => {
let name = node.local().unwrap();
let name = name.value_ref().as_str();
let location = context.location(&node.location());
let name = context.constant_string(name.value_ref().as_str());
let result_type = self.env_get(&name).unwrap().clone();
self.push_ir(IR::new_get_local(result_type, location, name));
let constant_name = context.constant_string(name);
match self.env_get(&constant_name) {
Some(result_type) => self.push_ir(IR::new_get_local(result_type.clone(), location, constant_name)),
None => {
let message = format!("Unknown variable {}", name);
context.compile_error(&message, location, ErrorKind::UnknownVariable);
}
}
}
NodeType::Map => {
let ty_idx = context.constant_string("Huia.Native.Map");
@ -263,6 +271,24 @@ impl Builder {
.collect();
self.push_ir(IR::new_constant(ty, location, Val::Map(elements)));
}
NodeType::MethodCall => {
let location = context.location(&node.location());
let (callee, method_name, arguments) = node.method_call().unwrap();
let method_name = context.constant_string(method_name.value_ref());
self.build(callee.clone(), &mut context);
let callee = self.pop_ir().unwrap();
self.push_block();
for argument in arguments {
self.build(argument.clone(), &mut context);
}
let arguments = self.pop_block().unwrap().ir();
let result_type = context.unknown_type(location.clone());
self.push_ir(IR::new_method_call(result_type, location, callee, method_name, arguments))
}
NodeType::PropertyGet => {
let location = context.location(&node.location());
let name =
@ -790,10 +816,12 @@ mod test {
#[test]
fn test_call() {
let term = Term::input(r#" x(123) "#).unwrap()[0].clone();
let terms = Term::input(" let x = 1\n x(123) ").unwrap();
let mut builder = Builder::default();
let mut context = Context::test();
builder.build(term, &mut context);
for term in terms {
builder.build(term, &mut context);
}
let call = builder.pop_ir().unwrap();
assert!(call.is_call());
assert!(context.find_type("Huia.Native.Integer").is_some());

View file

@ -90,6 +90,12 @@ impl IR {
IRKind::JumpIfTrue(ref mut test, ..) => {
improver(test, &mut context);
}
IRKind::MethodCall(ref mut callee, _, ref mut arguments) => {
improver(callee, &mut context);
for ref mut argument in arguments {
improver(argument, &mut context);
}
}
IRKind::SetLocal(_, ref mut rhs) => {
improver(rhs, &mut context);
}
@ -305,6 +311,20 @@ impl IR {
}
}
pub fn new_method_call(
result_type: TyIdx,
location: Location,
callee: IR,
method_name: StringIdx,
arguments: Vec<IR>,
) -> IR {
IR {
location,
result_type,
kind: IRKind::MethodCall(Box::new(callee), method_name, arguments),
}
}
pub fn new_set_local(result_type: TyIdx, location: Location, name: StringIdx, value: IR) -> IR {
IR {
location,
@ -356,7 +376,7 @@ impl IR {
#[derive(Debug, PartialEq, Clone)]
pub enum IRKind {
/// Call top of the stack with arguments.
/// Call the provided callee with arguments.
Call(Box<IR>, Vec<IR>),
/// A constant value of the specified type.
Constant(Val),
@ -376,6 +396,8 @@ pub enum IRKind {
JumpIfTrue(Box<IR>, BlockIdx),
/// A binary operation with LHS and RHS.
Infix(BinOp, Box<IR>, Box<IR>),
/// Call the named method on the receiver.
MethodCall(Box<IR>, StringIdx, Vec<IR>),
/// Take a value and set it as a local variable.
SetLocal(StringIdx, Box<IR>),
/// Set properties on an instance, returning the modified instance.

View file

@ -102,10 +102,6 @@ impl<'a> From<Pair<'a, Rule>> for Binary {
value: Operator::LogicalOr,
location: InputLocation::from(pair.into_span()),
},
Rule::method => Binary {
value: Operator::Method,
location: InputLocation::from(pair.into_span()),
},
Rule::minus => Binary {
value: Operator::Minus,
location: InputLocation::from(pair.into_span()),

View file

@ -3,7 +3,7 @@ use crate::error::ParseError;
use crate::grammar::{Grammar, Rule};
use crate::input_location::InputLocation;
use crate::precedence;
use pest::iterators::Pair;
use pest::iterators::{Pair, Pairs};
use pest::Parser;
// I've tried to make Huia terms as simple as possible, there are node types for
@ -40,6 +40,7 @@ pub enum NodeType {
Function,
If,
Local,
MethodCall,
PropertyGet,
PropertySet,
@ -57,7 +58,7 @@ enum Inner {
Atom(Atom),
Binary(Binary, Box<Term>, Box<Term>),
Boolean(Boolean),
Call(Identifier, Vec<Term>),
Call(Box<Term>, Vec<Term>),
Constructor(Ty, Vec<(Identifier, Term)>),
Declaration(Identifier, Box<Term>),
Float(Float),
@ -67,6 +68,7 @@ enum Inner {
Integer(Integer),
Local(Local),
Map(Vec<(Term, Term)>),
MethodCall(Box<Term>, Identifier, Vec<Term>),
PrivateMethod(
Identifier,
Vec<(Identifier, TypeSpec)>,
@ -146,6 +148,7 @@ impl Term {
Inner::Integer(..) => NodeType::Integer,
Inner::Local(..) => NodeType::Local,
Inner::Map(..) => NodeType::Map,
Inner::MethodCall(..) => NodeType::MethodCall,
Inner::PropertyGet(..) => NodeType::PropertyGet,
Inner::PropertySet(..) => NodeType::PropertySet,
Inner::String(..) => NodeType::String,
@ -187,9 +190,9 @@ impl Term {
}
}
pub fn call(&self) -> Option<(&Identifier, &Vec<Term>)> {
pub fn call(&self) -> Option<(&Term, &Vec<Term>)> {
match self.inner {
Inner::Call(ref name, ref args) => Some((name, args)),
Inner::Call(ref callee, ref args) => Some((callee, args)),
_ => None,
}
}
@ -257,6 +260,15 @@ impl Term {
}
}
pub fn method_call(&self) -> Option<(&Term, &Identifier, &Vec<Term>)> {
match self.inner {
Inner::MethodCall(ref callee, ref method_name, ref arguments) => {
Some((callee, method_name, arguments))
}
_ => None,
}
}
pub fn private_method(
&self,
) -> Option<(
@ -384,23 +396,28 @@ impl<'a> From<Pair<'a, Rule>> for Term {
location: InputLocation::from(pair.clone()),
inner: Inner::Boolean(Boolean::from(pair)),
},
Rule::call => {
Rule::call_local => {
let mut inner = pair.clone().into_inner();
let name = Identifier::from(inner.next().unwrap());
let mut args = Vec::new();
let callee = Term::from(inner.next().unwrap());
let mut arguments = Vec::new();
for argument in inner {
match argument.as_rule() {
Rule::call_argument => {
args.push(Term::from(argument.into_inner().next().unwrap()))
arguments.push(Term::from(argument.into_inner().next().unwrap()))
}
_ => unreachable!("Expected method_call argument but found {:?}", argument),
_ => unreachable!("Expected call argument but found {:?}", argument),
}
}
Term {
location: InputLocation::from(pair),
inner: Inner::Call(name, args),
inner: Inner::Call(Box::new(callee), arguments),
}
}
Rule::call_method => {
let mut inner = pair.clone().into_inner();
let callee = Term::from(inner.next().unwrap());
unroll_method_call(callee, inner)
}
Rule::typename => Term {
location: InputLocation::from(pair.clone()),
inner: Inner::Ty(Ty::from(pair)),
@ -643,11 +660,36 @@ impl<'a> From<Pair<'a, Rule>> for Term {
}
}
_ => panic!("Unexpected pair {:?}", pair),
_ => panic!("Unexpected pair {:#?}", pair),
}
}
}
// Expects to be called with the inner of a `call_method` pair.
//
// Recursively builds a stack of method call terms from the repeating pattern
// off identifier followed by arguments.
fn unroll_method_call(callee: Term, mut inner: Pairs<'_, Rule>) -> Term {
let method_name = Identifier::from(inner.next().unwrap());
let mut arguments = Vec::new();
while inner.peek().is_some() && inner.peek().unwrap().as_rule() == Rule::call_argument {
let argument = inner.next().unwrap();
arguments.push(Term::from(argument.into_inner().next().unwrap()));
}
let result = Term {
location: callee.location().clone(), // this location is wrong.
inner: Inner::MethodCall(Box::new(callee), method_name, arguments),
};
if inner.peek().is_some() && inner.peek().unwrap().as_rule() == Rule::ident {
return unroll_method_call(result, inner);
}
result
}
#[cfg(test)]
mod test {
use super::*;
@ -717,8 +759,9 @@ mod test {
fn test_call() {
let terms = Term::input("hello(\"Marty\", \"McFly\")").unwrap();
assert_eq!(terms[0].node_type(), NodeType::Call);
let (method_name, arguments) = terms[0].call().unwrap();
assert_eq!(method_name.value_ref(), "hello");
let (callee, arguments) = terms[0].call().unwrap();
let local = callee.local().unwrap();
assert_eq!(local.value_ref(), "hello");
assert_eq!(arguments[0].string().unwrap().value_ref(), "Marty");
assert_eq!(arguments[1].string().unwrap().value_ref(), "McFly");
}
@ -726,8 +769,9 @@ mod test {
#[test]
fn test_call_no_args() {
let terms = Term::input("hello()").unwrap();
let (method_name, arguments) = terms[0].call().unwrap();
assert_eq!(method_name.value_ref(), "hello");
let (callee, arguments) = terms[0].call().unwrap();
let local = callee.local().unwrap();
assert_eq!(local.value_ref(), "hello");
assert_eq!(arguments.len(), 0);
}
@ -934,16 +978,9 @@ mod test {
#[test]
fn test_method_call() {
let terms = Term::input("greeter.hello(\"Marty\", \"McFly\")").unwrap();
assert_eq!(terms[0].node_type(), NodeType::Binary);
let (op, lhs, rhs) = terms[0].binary().unwrap();
assert_eq!(op.value_ref(), &binary::Operator::Method);
assert_eq!(lhs.node_type(), NodeType::Local);
assert_eq!(lhs.local().unwrap().value_ref(), "greeter");
assert_eq!(rhs.node_type(), NodeType::Call);
let (method_name, arguments) = rhs.call().unwrap();
let (callee, method_name, arguments) = terms[0].method_call().unwrap();
let local = callee.local().unwrap();
assert_eq!(local.value_ref(), "greeter");
assert_eq!(method_name.value_ref(), "hello");
assert_eq!(arguments[0].string().unwrap().value_ref(), "Marty");
assert_eq!(arguments[1].string().unwrap().value_ref(), "McFly");
@ -953,43 +990,25 @@ mod test {
fn test_method_call_multi() {
let terms = Term::input("delorean.target_year(1985).accellerate(88)").unwrap();
println!("terms = {:#?}", terms);
let (op0, lhs0, rhs0) = terms[0].binary().unwrap();
assert_eq!(op0.value_ref(), &binary::Operator::Method);
assert_eq!(lhs0.node_type(), NodeType::Binary);
assert_eq!(rhs0.node_type(), NodeType::Call);
let (method_name0, arguments0) = rhs0.call().unwrap();
let (callee0, method_name0, arguments0) = terms[0].method_call().unwrap();
assert_eq!(method_name0.value_ref(), "accellerate");
assert_eq!(*arguments0[0].integer().unwrap().value_ref(), 88);
assert_eq!(arguments0.len(), 1);
assert_eq!(arguments0[0].integer().unwrap().value_ref(), &88);
let (op1, lhs1, rhs1) = lhs0.binary().unwrap();
assert_eq!(op1.value_ref(), &binary::Operator::Method);
assert_eq!(lhs1.node_type(), NodeType::Local);
assert_eq!(rhs1.node_type(), NodeType::Call);
assert_eq!(lhs1.local().unwrap().value_ref(), "delorean");
let (method_name1, arguments1) = rhs1.call().unwrap();
let (callee1, method_name1, arguments1) = callee0.method_call().unwrap();
assert_eq!(callee1.local().unwrap().value_ref(), "delorean");
assert_eq!(method_name1.value_ref(), "target_year");
assert_eq!(*arguments1[0].integer().unwrap().value_ref(), 1985);
assert_eq!(arguments1.len(), 1);
assert_eq!(arguments1[0].integer().unwrap().value_ref(), &1985);
}
#[test]
fn test_method_call_empty() {
let terms = Term::input("greeter.hello()").unwrap();
let (op, lhs, rhs) = terms[0].binary().unwrap();
assert_eq!(op.value_ref(), &binary::Operator::Method);
assert_eq!(lhs.node_type(), NodeType::Local);
assert_eq!(lhs.local().unwrap().value_ref(), "greeter");
assert_eq!(rhs.node_type(), NodeType::Call);
let (method_name, arguments) = rhs.call().unwrap();
let (callee, method_name, arguments) = terms[0].method_call().unwrap();
let local = callee.local().unwrap();
assert_eq!(local.value_ref(), "greeter");
assert_eq!(method_name.value_ref(), "hello");
assert_eq!(arguments.len(), 0);
}
@ -998,14 +1017,9 @@ mod test {
fn test_method_call_on_typename() {
let terms = Term::input("My.Greeter.hello()").unwrap();
let (op, lhs, rhs) = terms[0].binary().unwrap();
assert_eq!(op.value_ref(), &binary::Operator::Method);
assert_eq!(lhs.node_type(), NodeType::Ty);
assert_eq!(lhs.ty().unwrap().value_ref(), "My.Greeter");
assert_eq!(rhs.node_type(), NodeType::Call);
let (method_name, arguments) = rhs.call().unwrap();
let (callee, method_name, arguments) = terms[0].method_call().unwrap();
let ty = callee.ty().unwrap();
assert_eq!(ty.value_ref(), "My.Greeter");
assert_eq!(method_name.value_ref(), "hello");
assert_eq!(arguments.len(), 0);
}

View file

@ -15,7 +15,22 @@ declaration = { "let" ~ ident ~ assign ~ instance_espression }
unary = { unary_operator ~ (literal | local | braced_expression) }
call = { ident ~ call_arguments }
// Call types:
//
// ```
// foo() - call a local named `foo` which happens to be a function.
// @foo() - call a property named `foo` which happens to be a function.
// foo.bar() - call the `bar` method on `foo`.
// @foo.bar() - call the `bar` method on the property `foo`.
// (1 + 2).bar() - call the `bar` method on the result of an arbitrary expression.
// foo.bar().baz() - call the `baz` method on the result of `bar` on `foo`.
// ```
call = _{ call_method | call_local }
callable = _{ braced_expression | local | property_get | call_local | typename }
call_local = { (local | property_get) ~ call_arguments }
call_method = { callable ~ ("." ~ ident ~ call_arguments)+ }
call_arguments = _{ "(" ~ (call_argument ~ ("," ~ call_argument)* )? ~ ")" }
call_argument = { expression }
@ -123,7 +138,6 @@ bitwise_and = { "&" }
bitwise_or = { "|" }
bitwise_xor = { "^" }
assign = { "=" }
method = { "." }
all_operators = _{
exponent |
multiply | divide | modulus |
@ -134,7 +148,7 @@ all_operators = _{
not_equal | equal |
logical_and | logical_or | logical_not |
bitwise_and | bitwise_or | bitwise_xor |
assign | method
assign
}
binary_operator = _{
exponent |
@ -145,8 +159,7 @@ binary_operator = _{
greater_than_or_equal | greater_than |
not_equal | equal |
logical_and | logical_or |
bitwise_and | bitwise_or | bitwise_xor |
method
bitwise_and | bitwise_or | bitwise_xor
}
unary_operator = _{ plus | minus | logical_not }

View file

@ -12,7 +12,6 @@ lazy_static! {
fn build_precedence_climber() -> PrecClimber<Rule> {
PrecClimber::new(vec![
Operator::new(Rule::method, Assoc::Left),
Operator::new(Rule::logical_or, Assoc::Left),
Operator::new(Rule::logical_and, Assoc::Left),
Operator::new(Rule::equal, Assoc::Right) | Operator::new(Rule::not_equal, Assoc::Right),