From cf04b8ef3d0dd3626962729a7154f450344cd188 Mon Sep 17 00:00:00 2001 From: Herwin Date: Mon, 24 Jun 2024 10:41:19 +0200 Subject: [PATCH 1/5] Add tests for MatchRequiredNode with expressions as LHS The use of a local variable currently fails in Natalie. --- test/natalie/pattern_matching_test.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/natalie/pattern_matching_test.rb b/test/natalie/pattern_matching_test.rb index e46e44d55..88e4d2788 100644 --- a/test/natalie/pattern_matching_test.rb +++ b/test/natalie/pattern_matching_test.rb @@ -1,5 +1,9 @@ require_relative '../spec_helper' +module PatternMatchingHelper + def self.one = 1 +end + # NATFIXME: Temprorary file until we can run some things in `language/pattern_matching_spec.rb` describe 'pattern matching' do it 'can assign a single value' do @@ -7,6 +11,22 @@ a.should == 1 end + it 'can assign a single value from an expression' do + 1 + 1 => a + a.should == 2 + end + + it 'can assign a single value from a variable' do + a = 1 + a => b + b.should == 1 + end + + it 'can assign a single value from a method call' do + PatternMatchingHelper.one => a + a.should == 1 + end + it 'does not define a local variable if the expression fails' do -> { (raise RuntimeError, 'expected error'; 2) => a From 5be5075acd0c744b099aa706f93212119b3c8836 Mon Sep 17 00:00:00 2001 From: Herwin Date: Mon, 24 Jun 2024 10:43:56 +0200 Subject: [PATCH 2/5] Use correct locals for statement `a => b` --- lib/natalie/compiler/transformers/match_required_node.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/natalie/compiler/transformers/match_required_node.rb b/lib/natalie/compiler/transformers/match_required_node.rb index 372507535..a84d0fb99 100644 --- a/lib/natalie/compiler/transformers/match_required_node.rb +++ b/lib/natalie/compiler/transformers/match_required_node.rb @@ -65,7 +65,7 @@ def transform_local_variable_target_node(node, value) result end.call(#{value.location.slice}) RUBY - parser = Natalie::Parser.new(code_str, compiler.file.path, locals: [node.name]) + parser = Natalie::Parser.new(code_str, compiler.file.path, locals: compiler.current_locals) compiler.transform_expression(parser.ast.statements, used: false) end end From 50149e1d92be172172a5c420e0a6058a67201d00 Mon Sep 17 00:00:00 2001 From: Herwin Date: Mon, 24 Jun 2024 10:45:44 +0200 Subject: [PATCH 3/5] Split code generation and code parsing in pattern match transformations The latest bug fix made these three snippets exactly the same. --- .../transformers/match_required_node.rb | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/lib/natalie/compiler/transformers/match_required_node.rb b/lib/natalie/compiler/transformers/match_required_node.rb index a84d0fb99..9649fcb90 100644 --- a/lib/natalie/compiler/transformers/match_required_node.rb +++ b/lib/natalie/compiler/transformers/match_required_node.rb @@ -11,14 +11,16 @@ def initialize(compiler) end def call(node) - case node.pattern.type - when :array_pattern_node - transform_array_pattern_node(node.pattern, node.value) - when :local_variable_target_node - transform_local_variable_target_node(node.pattern, node.value) - else - transform_eqeqeq_check(node.pattern, node.value) - end + code_str = case node.pattern.type + when :array_pattern_node + transform_array_pattern_node(node.pattern, node.value) + when :local_variable_target_node + transform_local_variable_target_node(node.pattern, node.value) + else + transform_eqeqeq_check(node.pattern, node.value) + end + parser = Natalie::Parser.new(code_str, compiler.file.path, locals: compiler.current_locals) + compiler.transform_expression(parser.ast.statements, used: false) end private @@ -30,7 +32,7 @@ def transform_array_pattern_node(node, value) # Transform `expr => [a, b] into `a, b = ->(expr) { expr.deconstruct }.call(expr)` target = node.requireds.map(&:name).join(', ') - code_str = <<~RUBY + <<~RUBY #{target} = lambda do |result| values = result.deconstruct if values.size != #{node.requireds.size} @@ -41,32 +43,26 @@ def transform_array_pattern_node(node, value) raise ::NoMatchingPatternError, "\#{result}: \#{result} does not respond to #deconstruct" end.call(#{value.location.slice}) RUBY - parser = Natalie::Parser.new(code_str, compiler.file.path, locals: compiler.current_locals) - compiler.transform_expression(parser.ast.statements, used: false) end def transform_eqeqeq_check(node, value) # Transform `expr => var` into `->(res, var) { res === var }.call(expr, var)` - code_str = <<~RUBY + <<~RUBY lambda do |result, expect| unless expect === result raise ::NoMatchingPatternError, "\#{result}: \#{expect} === \#{result} does not return true" end end.call(#{value.location.slice}, #{node.location.slice}) RUBY - parser = Natalie::Parser.new(code_str, compiler.file.path, locals: compiler.current_locals) - compiler.transform_expression(parser.ast.statements, used: false) end def transform_local_variable_target_node(node, value) # Transform `expr => var` into `var = ->(res) { res }.call(expr)` - code_str = <<~RUBY + <<~RUBY #{node.name} = lambda do |result| result end.call(#{value.location.slice}) RUBY - parser = Natalie::Parser.new(code_str, compiler.file.path, locals: compiler.current_locals) - compiler.transform_expression(parser.ast.statements, used: false) end end end From 7484ded99eea5ad118baf35783b58b19bb989ad7 Mon Sep 17 00:00:00 2001 From: Herwin Date: Mon, 24 Jun 2024 10:54:52 +0200 Subject: [PATCH 4/5] Move instruction generation of MatchRequiredNode back into Pass1 The MatchRequiredNode transformer had a bit of feature envy for the compiler. With the previous bugfix and refactor, this code could be moved back into the compiler. --- lib/natalie/compiler/pass1.rb | 6 +++-- .../transformers/match_required_node.rb | 24 +++++++------------ 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/lib/natalie/compiler/pass1.rb b/lib/natalie/compiler/pass1.rb index 38deb3b4c..bc4cf8730 100644 --- a/lib/natalie/compiler/pass1.rb +++ b/lib/natalie/compiler/pass1.rb @@ -2005,8 +2005,10 @@ def transform_match_last_line_node(node, used:) end def transform_match_required_node(node, used:) - match_required_node_compiler = Transformers::MatchRequiredNode.new(self) - instructions = match_required_node_compiler.call(node) + match_required_node_compiler = Transformers::MatchRequiredNode.new + code_str = match_required_node_compiler.call(node) + parser = Natalie::Parser.new(code_str, @file.path, locals: current_locals) + instructions = transform_expression(parser.ast.statements, used: false) instructions << PushNilInstruction.new if used instructions end diff --git a/lib/natalie/compiler/transformers/match_required_node.rb b/lib/natalie/compiler/transformers/match_required_node.rb index 9649fcb90..14720f35c 100644 --- a/lib/natalie/compiler/transformers/match_required_node.rb +++ b/lib/natalie/compiler/transformers/match_required_node.rb @@ -4,23 +4,15 @@ module Natalie class Compiler module Transformers class MatchRequiredNode - attr_reader :compiler - - def initialize(compiler) - @compiler = compiler - end - def call(node) - code_str = case node.pattern.type - when :array_pattern_node - transform_array_pattern_node(node.pattern, node.value) - when :local_variable_target_node - transform_local_variable_target_node(node.pattern, node.value) - else - transform_eqeqeq_check(node.pattern, node.value) - end - parser = Natalie::Parser.new(code_str, compiler.file.path, locals: compiler.current_locals) - compiler.transform_expression(parser.ast.statements, used: false) + case node.pattern.type + when :array_pattern_node + transform_array_pattern_node(node.pattern, node.value) + when :local_variable_target_node + transform_local_variable_target_node(node.pattern, node.value) + else + transform_eqeqeq_check(node.pattern, node.value) + end end private From 51c7a972cec34cb32c49846912d4752a91ca4b46 Mon Sep 17 00:00:00 2001 From: Herwin Date: Mon, 24 Jun 2024 10:57:33 +0200 Subject: [PATCH 5/5] Remove current_locals method from Pass1 This is internal information, and should not be exposed to the outside users. --- lib/natalie/compiler/pass1.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/natalie/compiler/pass1.rb b/lib/natalie/compiler/pass1.rb index bc4cf8730..7fed9a17f 100644 --- a/lib/natalie/compiler/pass1.rb +++ b/lib/natalie/compiler/pass1.rb @@ -109,10 +109,6 @@ def expand_macros(node) @macro_expander.expand(node, locals: locals, depth: @depth, file: @file) end - def current_locals - @locals_stack.last - end - def transform_body(body, location:, used:) return transform_begin_node(body, used:) if body.is_a?(Prism::BeginNode) body = body.body if body.is_a?(Prism::StatementsNode) @@ -2007,7 +2003,7 @@ def transform_match_last_line_node(node, used:) def transform_match_required_node(node, used:) match_required_node_compiler = Transformers::MatchRequiredNode.new code_str = match_required_node_compiler.call(node) - parser = Natalie::Parser.new(code_str, @file.path, locals: current_locals) + parser = Natalie::Parser.new(code_str, @file.path, locals: @locals_stack.last) instructions = transform_expression(parser.ast.statements, used: false) instructions << PushNilInstruction.new if used instructions