Skip to content

Commit

Permalink
Merge pull request #2275 from natalie-lang/rb/argument-forwarding
Browse files Browse the repository at this point in the history
Implement anonymous splat and keyword splat forwarding
  • Loading branch information
richardboehme authored Oct 27, 2024
2 parents e66e7f6 + aa43961 commit 746cde6
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 8 deletions.
6 changes: 6 additions & 0 deletions lib/natalie/compiler/args.rb
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,9 @@ def transform_rest_arg(arg)
if (name = arg.name)
@instructions << variable_set(name)
@instructions << VariableGetInstruction.new(name)
else
@instructions << AnonymousSplatSetInstruction.new
@instructions << AnonymousSplatGetInstruction.new
end
:reverse
end
Expand Down Expand Up @@ -271,6 +274,9 @@ def transform_keyword_splat_arg(arg)
if arg.name
@instructions << variable_set(arg.name)
@instructions << VariableGetInstruction.new(arg.name)
else
@instructions << AnonymousKeywordSplatSetInstruction.new
@instructions << AnonymousKeywordSplatGetInstruction.new
end
@has_keyword_splat = true
:reverse unless remaining_keyword_args.any?
Expand Down
4 changes: 4 additions & 0 deletions lib/natalie/compiler/instructions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
require_relative './instructions/alias_method_instruction'
require_relative './instructions/anonymous_block_get_instruction'
require_relative './instructions/anonymous_block_set_instruction'
require_relative './instructions/anonymous_keyword_splat_get_instruction'
require_relative './instructions/anonymous_keyword_splat_set_instruction'
require_relative './instructions/anonymous_splat_get_instruction'
require_relative './instructions/anonymous_splat_set_instruction'
require_relative './instructions/array_concat_instruction'
require_relative './instructions/array_pop_instruction'
require_relative './instructions/array_pop_with_default_instruction'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
require_relative './base_instruction'

module Natalie
class Compiler
class AnonymousKeywordSplatGetInstruction < BaseInstruction
def to_s
'anonymous_keyword_splat_get'
end

def generate(transform)
transform.push('anon_keyword_splat')
end

def execute(vm)
if (var = vm.find_var(:anon_keyword_splat))
vm.push(var.fetch(:value))
else
raise "anonymous keyword splat was not defined #{@name}"
end
end

def serialize(_)
[instruction_number].pack('C')
end

def self.deserialize(_, _)
new
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require_relative './base_instruction'

module Natalie
class Compiler
class AnonymousKeywordSplatSetInstruction < BaseInstruction
def to_s
'anonymous_keyword_splat_set'
end

def generate(transform)
value = transform.pop
transform.exec("Value anon_keyword_splat = #{value}")
end

def execute(vm)
vm.scope[:vars][:anon_keyword_splat] = { value: vm.pop }
end

def serialize(_)
[instruction_number].pack('C')
end

def self.deserialize(_, _)
new
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
require_relative './base_instruction'

module Natalie
class Compiler
class AnonymousSplatGetInstruction < BaseInstruction
def to_s
'anonymous_splat_get'
end

def generate(transform)
transform.push('anon_splat')
end

def execute(vm)
if (var = vm.find_var(:anon_splat))
vm.push(var.fetch(:value))
else
raise "anonymous splat was not defined #{@name}"
end
end

def serialize(_)
[instruction_number].pack('C')
end

def self.deserialize(_, _)
new
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require_relative './base_instruction'

module Natalie
class Compiler
class AnonymousSplatSetInstruction < BaseInstruction
def to_s
'anonymous_splat_set'
end

def generate(transform)
value = transform.pop
transform.exec("Value anon_splat = #{value}")
end

def execute(vm)
vm.scope[:vars][:anon_splat] = { value: vm.pop }
end

def serialize(_)
[instruction_number].pack('C')
end

def self.deserialize(_, _)
new
end
end
end
end
4 changes: 4 additions & 0 deletions lib/natalie/compiler/instructions/meta.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ class Compiler
AliasMethodInstruction,
AnonymousBlockGetInstruction,
AnonymousBlockSetInstruction,
AnonymousKeywordSplatGetInstruction,
AnonymousKeywordSplatSetInstruction,
AnonymousSplatGetInstruction,
AnonymousSplatSetInstruction,
ArrayConcatInstruction,
ArrayPopInstruction,
ArrayPopWithDefaultInstruction,
Expand Down
21 changes: 13 additions & 8 deletions lib/natalie/compiler/pass1.rb
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,13 @@ def transform_array_elements_with_splat(elements)
# now add to the array the first splat item and everything after
elements.each do |arg|
if arg.type == :splat_node
if arg.expression.nil?
raise "Anonymous splat argument forwarding not yet supported (#{file.path}:#{arg.location.start_line})"
end
instructions << transform_expression(arg.expression, used: true)
instructions <<
if arg.expression
transform_expression(arg.expression, used: true)
else
AnonymousSplatGetInstruction.new
end

instructions << ArrayConcatInstruction.new
else
instructions << transform_expression(arg, used: true)
Expand Down Expand Up @@ -1463,10 +1466,12 @@ def transform_hash_node(node, used:)
# now, if applicable, add to the hash the splat element and everything after
node.elements[prior_to_splat_count..].each do |element|
if element.type == :assoc_splat_node
if element.value.nil?
raise "Anonymous keyword splat argument forwarding not yet supported (#{file.path}:#{node.location.start_line})"
end
instructions << transform_expression(element.value, used: true)
instructions <<
if element.value
transform_expression(element.value, used: true)
else
AnonymousKeywordSplatGetInstruction.new
end
instructions << HashMergeInstruction.new
else
instructions << transform_expression(element.key, used: true)
Expand Down
35 changes: 35 additions & 0 deletions test/natalie/method_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,28 @@ def blank_splat_middle(x, *, y)
blank_splat_middle(1, 2, 3, 4).should == [1, 4]
end

def anonymous_splat_forward_receiver(*args)
args
end

def anonymous_splat_forwarding_middle(a, *, b)
anonymous_splat_forward_receiver(*)
end

def anonymous_splat_forwarding_first(*, a)
anonymous_splat_forward_receiver(*)
end

def anonymous_splat_forwarding_last(a, *)
anonymous_splat_forward_receiver(*)
end

it 'forwards splats' do
anonymous_splat_forwarding_middle(1, 2, 3, 4, 5).should == [2, 3, 4]
anonymous_splat_forwarding_first(1, 2, 3, 4, 5).should == [1, 2, 3, 4]
anonymous_splat_forwarding_last(1, 2, 3, 4, 5).should == [2, 3, 4, 5]
end

def method_name
__method__
end
Expand Down Expand Up @@ -439,6 +461,14 @@ def method_with_kwargs14(a = '', **kwargs)
[a, kwargs]
end

def method_forwarding_kwargs_receiver(a: nil, **kwargs)
[a, kwargs]
end

def method_forwarding_kwargs(a: 1, **)
method_forwarding_kwargs_receiver(a:, **)
end

def method_implicit_kwargs(*args)
args
end
Expand Down Expand Up @@ -480,6 +510,11 @@ def method_no_kwargs(*args, **nil)
method_with_kwargs14([]).should == [[], {}]
end

it 'anonymously forward keyword args' do
method_forwarding_kwargs(b: 2, c: 3).should == [1, { b: 2, c: 3 }]
method_forwarding_kwargs(a: 2, b: 2, c: 3).should == [2, { b: 2, c: 3 }]
end

it 'accepts hash key shorthand' do
b = 2
method_with_kwargs1(1, b:).should == [1, 2]
Expand Down

0 comments on commit 746cde6

Please sign in to comment.