Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor interface of PyObject#__call__ to not return Procs #14

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion playground/objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
7 changes: 3 additions & 4 deletions spec/util/pyobject_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 5 additions & 3 deletions src/lib/function/cycler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
26 changes: 15 additions & 11 deletions src/runtime/evaluator.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
9 changes: 4 additions & 5 deletions src/runtime/py_object.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
16 changes: 8 additions & 8 deletions src/runtime/resolver.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down