Fix function and method calling once and for all.
This commit is contained in:
parent
7be2c3e525
commit
a45d9627dc
7 changed files with 154 additions and 81 deletions
|
@ -35,4 +35,5 @@ impl Error for CompileError {
|
|||
#[derive(Debug)]
|
||||
pub enum ErrorKind {
|
||||
TypeRedefined,
|
||||
UnknownVariable,
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
Reference in a new issue