From 277891bd9fcc637ab73c409845c1d58410370014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 8 Oct 2018 18:54:52 +0200 Subject: [PATCH] Refactor interface of PyObject#__call__ to not return Procs --- playground/objects.md | 2 +- spec/util/pyobject_spec.cr | 7 +++---- src/lib/function/cycler.cr | 8 +++++--- src/runtime/evaluator.cr | 26 +++++++++++++++----------- src/runtime/py_object.cr | 9 ++++----- src/runtime/resolver.cr | 16 ++++++++-------- 6 files changed, 36 insertions(+), 32 deletions(-) diff --git a/playground/objects.md b/playground/objects.md index 3e72812b..1a88f0ff 100644 --- a/playground/objects.md +++ b/playground/objects.md @@ -8,7 +8,7 @@ Classes *may* implement the following methods to make properties accessbile: 1. `#getattr(name : Crinja::Value)`: Access an attribute (e.g. an instance property) of this class. 2. `#__getitem__(name : Crinja::Value)`: Access an item (e.g. an array member) of this class. -3. `#__call__(name : String) : Crinja::Callable | Callable::Proc`: Expose a callable as method of this class. +3. `#__call__(name : String, &block : -> Callable::Arguments) : Crinja::Value?`: Expose a callable as method of this class. They *must* return an `Undefined` if there is no attribute or item of that name. diff --git a/spec/util/pyobject_spec.cr b/spec/util/pyobject_spec.cr index 88ee408f..2de83764 100644 --- a/spec/util/pyobject_spec.cr +++ b/spec/util/pyobject_spec.cr @@ -24,11 +24,10 @@ private class User end end - def __call__(name : String) + def __call__(name : String) : Crinja::Value? if name == "days_old" - ->(arguments : Crinja::Callable::Arguments) do - self.age.days - end + yield + Crinja::Value.new self.age.days end end diff --git a/src/lib/function/cycler.cr b/src/lib/function/cycler.cr index 9f43eef1..73344c5b 100644 --- a/src/lib/function/cycler.cr +++ b/src/lib/function/cycler.cr @@ -32,12 +32,14 @@ class Crinja::Function::Cycler rewind end - def __call__(method) + def __call__(method : String) : Crinja::Value? case method when "next" - ->(arguments : Callable::Arguments) { self.next } + yield + Value.new self.next when "reset", "rewind" - ->(arguments : Callable::Arguments) { reset } + yield + Value.new reset end end end diff --git a/src/runtime/evaluator.cr b/src/runtime/evaluator.cr index 56470442..39dbaf21 100644 --- a/src/runtime/evaluator.cr +++ b/src/runtime/evaluator.cr @@ -82,8 +82,14 @@ class Crinja::Evaluator end if !callable && identifier.is_a?(AST::MemberExpression) - callable = call_on_member(identifier) - # raise UndefinedError.new(name_for_expression(expression.identifier)) unless callable + object = evaluate identifier.identifier + member = identifier.member.name + + result = Resolver.resolve_method(member, object) do + build_arguments(expression) + end + + return result if result end unless callable @@ -98,21 +104,19 @@ class Crinja::Evaluator # FIXME: Shouldn't be needed. callable = callable.not_nil! - argumentlist = evaluate(expression.argumentlist).as(Array(Value)) + @env.execute_call(callable, build_arguments(expression)) + end - keyword_arguments = Variables.new.tap do |args| + private def build_arguments(expression) + varargs = evaluate(expression.argumentlist).as(Array(Value)) + + kwargs = Variables.new.tap do |args| expression.keyword_arguments.each do |(keyword, value_expression)| args[keyword.name] = value value_expression end end - @env.execute_call(callable, argumentlist, keyword_arguments) - end - - private def call_on_member(expression : AST::MemberExpression) - identifier = evaluate expression.identifier - member = expression.member.name - Resolver.resolve_method(member, identifier) + Callable::Arguments.new(@env, varargs, kwargs) end private def identifier_or_member(identifier : AST::IdentifierLiteral) diff --git a/src/runtime/py_object.cr b/src/runtime/py_object.cr index 2ec7275d..04c7d39b 100644 --- a/src/runtime/py_object.cr +++ b/src/runtime/py_object.cr @@ -3,7 +3,7 @@ # # 1. `#getattr(name : Crinja::Value) : Crinja::Value`: Access an attribute (e.g. an instance property) of this class. # 2. `#__getitem__(name : Crinja::Value) : Crinja::Value`: Access an item (e.g. an array member) of this class. -# 3. `#__call__(name : String) : Crinja::Callable | Callable::Proc`: Expose a callable as method of this class. +# 3. `#__call__(name : String, &block : -> Crinja::Callable::Attributes) : Crinja::Value?`: Expose a callable as method of this class. # # Through the static comilation it is not possible to access properties or methods of an object # directly from inside the Crinja runtime. These methods allow to define a name-based lookup and @@ -45,11 +45,10 @@ # end # end # -# def __call__(name) +# def __call__(name : String) : Crinja::Value? # if name == "days_old" -# ->(arguments : Crinja::Arguments) do -# self.age.days -# end +# yield +# Value.new self.age.days # end # end # end diff --git a/src/runtime/resolver.cr b/src/runtime/resolver.cr index 5dea0f37..bb9fbe66 100644 --- a/src/runtime/resolver.cr +++ b/src/runtime/resolver.cr @@ -77,18 +77,18 @@ module Crinja::Resolver resolve_getattr(Value.new(name), Value.new(value)) end - def self.resolve_method(name, object) : Callable | Callable::Proc? - if object.responds_to?(:__call__) && (callable = object.__call__(name)) - return ->(arguments : Callable::Arguments) do - # wrap the return value of the proc as a Value - Value.new callable.not_nil!.call(arguments) - end.as(Callable::Proc) + def self.resolve_method(name, object, &block : -> Callable::Arguments) : Value? + if object.responds_to?(:__call__) + object.__call__(name) do + # Arguments are lazy evaluated + yield + end end end # ditto - def self.resolve_method(name, value : Value) : Callable | Callable::Proc? - self.resolve_method(name, value.raw) + def self.resolve_method(name, value : Value, &block : -> Callable::Arguments) : Value? + self.resolve_method(name, value.raw) { yield } end def self.resolve_with_hash_accessor(name : Value, value : Value) : Value