Skip to content

Commit 4e6d5dd

Browse files
committed
Mark flags as private
The flags integer is an implementation detail. We want people to use the query methods to access the individual fields so we are freed from having to maintain a specific order. As such, this commit changes the Ruby API to mark all flags fields as private attr_readers. The only one that has a clear use case is returning the set of options given to regular expressions, to mirror the Regexp#options API. So, to support this use case, this commit introduces RegularExpressionNode#options and InterpolatedRegularExpressionNode#options. These APIs provide back the same integer so that they can be used interchangeably.
1 parent 3a34464 commit 4e6d5dd

File tree

3 files changed

+72
-31
lines changed

3 files changed

+72
-31
lines changed

lib/yarp.rb

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -389,30 +389,6 @@ def to_str
389389
end
390390
end
391391

392-
class FloatNode < Node
393-
def value
394-
Float(slice)
395-
end
396-
end
397-
398-
class ImaginaryNode < Node
399-
def value
400-
Complex(0, numeric.value)
401-
end
402-
end
403-
404-
class IntegerNode < Node
405-
def value
406-
Integer(slice)
407-
end
408-
end
409-
410-
class RationalNode < Node
411-
def value
412-
Rational(slice.chomp("r"))
413-
end
414-
end
415-
416392
# Load the serialized AST using the source as a reference into a tree.
417393
def self.load(source, serialized)
418394
Serialize.load(source, serialized)
@@ -599,3 +575,57 @@ def self.parse_serialize_file(filepath)
599575
else
600576
require_relative "yarp/ffi"
601577
end
578+
579+
# Reopening the YARP module after yarp/node is required so that constant
580+
# reflection APIs will find the constants defined in the node file before these.
581+
# This block is meant to contain extra APIs we define on YARP nodes that aren't
582+
# templated and are meant as convenience methods.
583+
module YARP
584+
class FloatNode < Node
585+
# Returns the value of the node as a Ruby Float.
586+
def value
587+
Float(slice)
588+
end
589+
end
590+
591+
class ImaginaryNode < Node
592+
# Returns the value of the node as a Ruby Complex.
593+
def value
594+
Complex(0, numeric.value)
595+
end
596+
end
597+
598+
class IntegerNode < Node
599+
# Returns the value of the node as a Ruby Integer.
600+
def value
601+
Integer(slice)
602+
end
603+
end
604+
605+
class InterpolatedRegularExpressionNode < Node
606+
# Returns a numeric value that represents the flags that were used to create
607+
# the regular expression. This mirrors the Regexp#options method in Ruby.
608+
# Note that this is effectively masking only the three common flags that are
609+
# used in Ruby, and does not include the full set of flags like encoding.
610+
def options
611+
flags & 0b111
612+
end
613+
end
614+
615+
class RationalNode < Node
616+
# Returns the value of the node as a Ruby Rational.
617+
def value
618+
Rational(slice.chomp("r"))
619+
end
620+
end
621+
622+
class RegularExpressionNode < Node
623+
# Returns a numeric value that represents the flags that were used to create
624+
# the regular expression. This mirrors the Regexp#options method in Ruby.
625+
# Note that this is effectively masking only the three common flags that are
626+
# used in Ruby, and does not include the full set of flags like encoding.
627+
def options
628+
flags & 0b111
629+
end
630+
end
631+
end

templates/lib/yarp/node.rb.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module YARP
33
<%= "#{node.comment.split("\n").map { |line| line.empty? ? "#" : "# #{line}" }.join("\n ")}\n " if node.comment %>class <%= node.name -%> < Node
44
<%- node.fields.each do |field| -%>
55
# attr_reader <%= field.name %>: <%= field.rbs_class %>
6-
attr_reader :<%= field.name %>
6+
<%= "private " if field.is_a?(YARP::FlagsField) %>attr_reader :<%= field.name %>
77

88
<%- end -%>
99
# def initialize: (<%= (node.fields.map { |field| "#{field.name}: #{field.rbs_class}" } + ["location: Location"]).join(", ") %>) -> void

test/yarp/regexp_test.rb

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -197,20 +197,20 @@ def test_fake_range_quantifier_because_of_spaces
197197
##############################################################################
198198

199199
def test_flag_ignorecase
200-
assert_equal(Regexp::IGNORECASE, flags("i"))
200+
assert_equal(Regexp::IGNORECASE, options("i"))
201201
end
202202

203203
def test_flag_extended
204-
assert_equal(Regexp::EXTENDED, flags("x"))
204+
assert_equal(Regexp::EXTENDED, options("x"))
205205
end
206206

207207
def test_flag_multiline
208-
assert_equal(Regexp::MULTILINE, flags("m"))
208+
assert_equal(Regexp::MULTILINE, options("m"))
209209
end
210210

211211
def test_flag_combined
212212
value = Regexp::IGNORECASE | Regexp::MULTILINE | Regexp::EXTENDED
213-
assert_equal(value, flags("mix"))
213+
assert_equal(value, options("mix"))
214214
end
215215

216216
private
@@ -219,8 +219,19 @@ def named_captures(source)
219219
Debug.named_captures(source)
220220
end
221221

222-
def flags(str)
223-
YARP.parse("/foo/#{str}").value.child_nodes.first.child_nodes.first.flags
222+
def options(flags)
223+
options =
224+
["/foo/#{flags}", "/foo\#{1}/#{flags}"].map do |source|
225+
YARP.parse(source).value.statements.body.first.options
226+
end
227+
228+
# Check that we get the same set of options from both regular expressions
229+
# and interpolated regular expressions.
230+
assert_equal(1, options.uniq.length)
231+
232+
# Return the options from the first regular expression since we know they
233+
# are the same.
234+
options.first
224235
end
225236
end
226237
end

0 commit comments

Comments
 (0)