Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit d4d7f0b

Browse files
committed
Pattern match for arrays
1 parent 1262b52 commit d4d7f0b

File tree

4 files changed

+149
-3
lines changed

4 files changed

+149
-3
lines changed

lib/syntax_tree/compiler.rb

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,101 @@ def visit_array(node)
343343
end
344344
end
345345

346+
def visit_aryptn(node)
347+
match_failures = []
348+
jumps_to_exit = []
349+
350+
# If there's a constant, then check if we match against that constant or
351+
# not first. Branch to failure if we don't.
352+
if node.constant
353+
iseq.dup
354+
visit(node.constant)
355+
iseq.checkmatch(YARV::VM_CHECKMATCH_TYPE_CASE)
356+
match_failures << iseq.branchunless(-1)
357+
end
358+
359+
# First, check if the #deconstruct cache is nil. If it is, we're going to
360+
# call #deconstruct on the object and cache the result.
361+
iseq.topn(2)
362+
branchnil = iseq.branchnil(-1)
363+
364+
# Next, ensure that the cached value was cached correctly, otherwise fail
365+
# the match.
366+
iseq.topn(2)
367+
match_failures << iseq.branchunless(-1)
368+
369+
# Since we have a valid cached value, we can skip past the part where we
370+
# call #deconstruct on the object.
371+
iseq.pop
372+
iseq.topn(1)
373+
jump = iseq.jump(-1)
374+
375+
# Check if the object responds to #deconstruct, fail the match otherwise.
376+
branchnil.patch!(iseq)
377+
iseq.dup
378+
iseq.putobject(:deconstruct)
379+
iseq.send(:respond_to?, 1)
380+
iseq.setn(3)
381+
match_failures << iseq.branchunless(-1)
382+
383+
# Call #deconstruct and ensure that it's an array, raise an error
384+
# otherwise.
385+
iseq.send(:deconstruct, 0)
386+
iseq.setn(2)
387+
iseq.dup
388+
iseq.checktype(YARV::VM_CHECKTYPE_ARRAY)
389+
match_error = iseq.branchunless(-1)
390+
391+
# Ensure that the deconstructed array has the correct size, fail the match
392+
# otherwise.
393+
jump[1] = iseq.label
394+
iseq.dup
395+
iseq.send(:length, 0)
396+
iseq.putobject(node.requireds.length)
397+
iseq.send(:==, 1)
398+
match_failures << iseq.branchunless(-1)
399+
400+
# For each required element, check if the deconstructed array contains the
401+
# element, otherwise jump out to the top-level match failure.
402+
iseq.dup
403+
node.requireds.each_with_index do |required, index|
404+
iseq.putobject(index)
405+
iseq.send(:[], 1)
406+
407+
case required
408+
when VarField
409+
lookup = visit(required)
410+
iseq.setlocal(lookup.index, lookup.level)
411+
else
412+
visit(required)
413+
iseq.checkmatch(YARV::VM_CHECKMATCH_TYPE_CASE)
414+
match_failures << iseq.branchunless(-1)
415+
end
416+
417+
if index < node.requireds.length - 1
418+
iseq.dup
419+
else
420+
iseq.pop
421+
jumps_to_exit << iseq.jump(-1)
422+
end
423+
end
424+
425+
# Set up the routine here to raise an error to indicate that the type of
426+
# the deconstructed array was incorrect.
427+
match_error.patch!(iseq)
428+
iseq.putspecialobject(YARV::VM_SPECIAL_OBJECT_VMCORE)
429+
iseq.putobject(TypeError)
430+
iseq.putobject("deconstruct must return Array")
431+
iseq.send(:"core#raise", 2)
432+
iseq.pop
433+
434+
# Patch all of the match failures to jump here so that we pop a final
435+
# value before returning to the parent node.
436+
match_failures.each { |match_failure| match_failure.patch!(iseq) }
437+
iseq.pop
438+
jumps_to_exit
439+
end
440+
346441
def visit_assign(node)
347442
case node.target
348443
when ARefField
@@ -1298,6 +1393,33 @@ def visit_range(node)
12981393
end
12991394
end
13001395

1396+
def visit_rassign(node)
1397+
if node.operator.is_a?(Kw)
1398+
iseq.putnil
1399+
visit(node.value)
1400+
iseq.dup
1401+
jumps = []
1402+
1403+
case node.pattern
1404+
when VarField
1405+
lookup = visit(node.pattern)
1406+
iseq.setlocal(lookup.index, lookup.level)
1407+
jumps << iseq.jump(-1)
1408+
else
1409+
jumps.concat(visit(node.pattern))
1410+
end
1411+
1412+
iseq.pop
1413+
iseq.pop
1414+
iseq.putobject(false)
1415+
iseq.leave
1416+
1417+
jumps.each { |jump| jump[1] = iseq.label }
1418+
iseq.adjuststack(2)
1419+
iseq.putobject(true)
1420+
end
1421+
end
1422+
13011423
def visit_rational(node)
13021424
iseq.putobject(node.accept(RubyVisitor.new))
13031425
end

lib/syntax_tree/yarv.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,16 @@ def checkkeyword(keyword_bits_index, keyword_index)
370370
push(CheckKeyword.new(keyword_bits_index, keyword_index))
371371
end
372372

373+
def checkmatch(flag)
374+
stack.change_by(-2 + 1)
375+
push([:checkmatch, flag])
376+
end
377+
378+
def checktype(type)
379+
stack.change_by(-1 + 2)
380+
push([:checktype, type])
381+
end
382+
373383
def concatarray
374384
push(ConcatArray.new)
375385
end
@@ -832,5 +842,13 @@ def call_data(method_id, argc, flag = VM_CALL_ARGS_SIMPLE)
832842
VM_SVAR_LASTLINE = 0 # $_
833843
VM_SVAR_BACKREF = 1 # $~
834844
VM_SVAR_FLIPFLOP_START = 2 # flipflop
845+
846+
# These constants correspond to the checktype instruction.
847+
VM_CHECKTYPE_ARRAY = 7
848+
849+
# These constants correspond to the checkmatch instruction.
850+
VM_CHECKMATCH_TYPE_WHEN = 1
851+
VM_CHECKMATCH_TYPE_CASE = 2
852+
VM_CHECKMATCH_TYPE_RESCUE = 3
835853
end
836854
end

lib/syntax_tree/yarv/instructions.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -632,11 +632,11 @@ def length
632632
end
633633

634634
def pops
635-
number
635+
0
636636
end
637637

638638
def pushes
639-
number * 2
639+
number
640640
end
641641
end
642642

test/compiler_test.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,13 @@ class CompilerTest < Minitest::Test
416416
"-> {}",
417417
"-> (bar) do end",
418418
"-> (bar) {}",
419-
"-> (bar; baz) { }"
419+
"-> (bar; baz) { }",
420+
# Pattern matching
421+
"foo in bar",
422+
"foo in [bar]",
423+
"foo in [bar, baz]",
424+
"foo in [1, 2, 3, bar, 4, 5, 6, baz]",
425+
"foo in Foo[1, 2, 3, bar, 4, 5, 6, baz]",
420426
]
421427

422428
# These are the combinations of instructions that we're going to test.

0 commit comments

Comments
 (0)