syntax_tree 5.1.0 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -775,6 +775,10 @@ module SyntaxTree
775
775
  other.is_a?(ArgParen) && arguments === other.arguments
776
776
  end
777
777
 
778
+ def arity
779
+ arguments&.arity || 0
780
+ end
781
+
778
782
  private
779
783
 
780
784
  def trailing_comma?
@@ -848,6 +852,21 @@ module SyntaxTree
848
852
  def ===(other)
849
853
  other.is_a?(Args) && ArrayMatch.call(parts, other.parts)
850
854
  end
855
+
856
+ def arity
857
+ parts.sum do |part|
858
+ case part
859
+ when ArgStar, ArgsForward
860
+ Float::INFINITY
861
+ when BareAssocHash
862
+ part.assocs.sum do |assoc|
863
+ assoc.is_a?(AssocSplat) ? Float::INFINITY : 1
864
+ end
865
+ else
866
+ 1
867
+ end
868
+ end
869
+ end
851
870
  end
852
871
 
853
872
  # ArgBlock represents using a block operator on an expression.
@@ -1008,6 +1027,10 @@ module SyntaxTree
1008
1027
  def ===(other)
1009
1028
  other.is_a?(ArgsForward)
1010
1029
  end
1030
+
1031
+ def arity
1032
+ Float::INFINITY
1033
+ end
1011
1034
  end
1012
1035
 
1013
1036
  # ArrayLiteral represents an array literal, which can optionally contain
@@ -1080,58 +1103,6 @@ module SyntaxTree
1080
1103
  end
1081
1104
  end
1082
1105
 
1083
- # Formats an array that contains only a list of variable references. To make
1084
- # things simpler, if there are a bunch, we format them all using the "fill"
1085
- # algorithm as opposed to breaking them into a ton of lines. For example,
1086
- #
1087
- # [foo, bar, baz]
1088
- #
1089
- # instead of becoming:
1090
- #
1091
- # [
1092
- # foo,
1093
- # bar,
1094
- # baz
1095
- # ]
1096
- #
1097
- # would instead become:
1098
- #
1099
- # [
1100
- # foo, bar,
1101
- # baz
1102
- # ]
1103
- #
1104
- # provided the line length was hit between `bar` and `baz`.
1105
- class VarRefsFormatter
1106
- # The separator for the fill algorithm.
1107
- class Separator
1108
- def call(q)
1109
- q.text(",")
1110
- q.fill_breakable
1111
- end
1112
- end
1113
-
1114
- # [Args] the contents of the array
1115
- attr_reader :contents
1116
-
1117
- def initialize(contents)
1118
- @contents = contents
1119
- end
1120
-
1121
- def format(q)
1122
- q.text("[")
1123
- q.group do
1124
- q.indent do
1125
- q.breakable_empty
1126
- q.seplist(contents.parts, Separator.new) { |part| q.format(part) }
1127
- q.if_break { q.text(",") } if q.trailing_comma?
1128
- end
1129
- q.breakable_empty
1130
- end
1131
- q.text("]")
1132
- end
1133
- end
1134
-
1135
1106
  # This is a special formatter used if the array literal contains no values
1136
1107
  # but _does_ contain comments. In this case we do some special formatting to
1137
1108
  # make sure the comments gets indented properly.
@@ -1206,19 +1177,17 @@ module SyntaxTree
1206
1177
  end
1207
1178
 
1208
1179
  def format(q)
1209
- if qwords?
1210
- QWordsFormatter.new(contents).format(q)
1211
- return
1212
- end
1213
-
1214
- if qsymbols?
1215
- QSymbolsFormatter.new(contents).format(q)
1216
- return
1217
- end
1180
+ if lbracket.comments.empty? && contents && contents.comments.empty? &&
1181
+ contents.parts.length > 1
1182
+ if qwords?
1183
+ QWordsFormatter.new(contents).format(q)
1184
+ return
1185
+ end
1218
1186
 
1219
- if var_refs?(q)
1220
- VarRefsFormatter.new(contents).format(q)
1221
- return
1187
+ if qsymbols?
1188
+ QSymbolsFormatter.new(contents).format(q)
1189
+ return
1190
+ end
1222
1191
  end
1223
1192
 
1224
1193
  if empty_with_comments?
@@ -1250,39 +1219,24 @@ module SyntaxTree
1250
1219
  private
1251
1220
 
1252
1221
  def qwords?
1253
- lbracket.comments.empty? && contents && contents.comments.empty? &&
1254
- contents.parts.length > 1 &&
1255
- contents.parts.all? do |part|
1256
- case part
1257
- when StringLiteral
1258
- part.comments.empty? && part.parts.length == 1 &&
1259
- part.parts.first.is_a?(TStringContent) &&
1260
- !part.parts.first.value.match?(/[\s\[\]\\]/)
1261
- when CHAR
1262
- !part.value.match?(/[\[\]\\]/)
1263
- else
1264
- false
1265
- end
1222
+ contents.parts.all? do |part|
1223
+ case part
1224
+ when StringLiteral
1225
+ part.comments.empty? && part.parts.length == 1 &&
1226
+ part.parts.first.is_a?(TStringContent) &&
1227
+ !part.parts.first.value.match?(/[\s\[\]\\]/)
1228
+ when CHAR
1229
+ !part.value.match?(/[\[\]\\]/)
1230
+ else
1231
+ false
1266
1232
  end
1233
+ end
1267
1234
  end
1268
1235
 
1269
1236
  def qsymbols?
1270
- lbracket.comments.empty? && contents && contents.comments.empty? &&
1271
- contents.parts.length > 1 &&
1272
- contents.parts.all? do |part|
1273
- part.is_a?(SymbolLiteral) && part.comments.empty?
1274
- end
1275
- end
1276
-
1277
- def var_refs?(q)
1278
- lbracket.comments.empty? && contents && contents.comments.empty? &&
1279
- contents.parts.all? do |part|
1280
- part.is_a?(VarRef) && part.comments.empty?
1281
- end &&
1282
- (
1283
- contents.parts.sum { |part| part.value.value.length + 2 } >
1284
- q.maxwidth * 2
1285
- )
1237
+ contents.parts.all? do |part|
1238
+ part.is_a?(SymbolLiteral) && part.comments.empty?
1239
+ end
1286
1240
  end
1287
1241
 
1288
1242
  # If we have an empty array that contains only comments, then we're going
@@ -3001,16 +2955,25 @@ module SyntaxTree
3001
2955
  else
3002
2956
  q.format(message)
3003
2957
 
3004
- if arguments.is_a?(ArgParen) && arguments.arguments.nil? &&
3005
- !message.is_a?(Const)
3006
- # If you're using an explicit set of parentheses on something that
3007
- # looks like a constant, then we need to match that in order to
3008
- # maintain valid Ruby. For example, you could do something like Foo(),
3009
- # on which we would need to keep the parentheses to make it look like
3010
- # a method call.
3011
- else
3012
- q.format(arguments)
3013
- end
2958
+ # Note that this explicitly leaves parentheses in place even if they are
2959
+ # empty. There are two reasons we would need to do this. The first is if
2960
+ # we're calling something that looks like a constant, as in:
2961
+ #
2962
+ # Foo()
2963
+ #
2964
+ # In this case if we remove the parentheses then this becomes a constant
2965
+ # reference and not a method call. The second is if we're calling a
2966
+ # method that is the same name as a local variable that is in scope, as
2967
+ # in:
2968
+ #
2969
+ # foo = foo()
2970
+ #
2971
+ # In this case we have to keep the parentheses or else it treats this
2972
+ # like assigning nil to the local variable. Note that we could attempt
2973
+ # to be smarter about this by tracking the local variables that are in
2974
+ # scope, but for now it's simpler and more efficient to just leave the
2975
+ # parentheses in place.
2976
+ q.format(arguments) if arguments
3014
2977
  end
3015
2978
  end
3016
2979
 
@@ -3059,6 +3022,10 @@ module SyntaxTree
3059
3022
  end
3060
3023
  end
3061
3024
  end
3025
+
3026
+ def arity
3027
+ arguments&.arity || 0
3028
+ end
3062
3029
  end
3063
3030
 
3064
3031
  # Case represents the beginning of a case chain.
@@ -3472,6 +3439,10 @@ module SyntaxTree
3472
3439
  arguments === other.arguments && block === other.block
3473
3440
  end
3474
3441
 
3442
+ def arity
3443
+ arguments.arity
3444
+ end
3445
+
3475
3446
  private
3476
3447
 
3477
3448
  def align(q, node, &block)
@@ -3637,6 +3608,10 @@ module SyntaxTree
3637
3608
  arguments === other.arguments && block === other.block
3638
3609
  end
3639
3610
 
3611
+ def arity
3612
+ arguments&.arity || 0
3613
+ end
3614
+
3640
3615
  private
3641
3616
 
3642
3617
  def argument_alignment(q, doc)
@@ -4166,6 +4141,17 @@ module SyntaxTree
4166
4141
  def endless?
4167
4142
  !bodystmt.is_a?(BodyStmt)
4168
4143
  end
4144
+
4145
+ def arity
4146
+ case params
4147
+ when Params
4148
+ params.arity
4149
+ when Paren
4150
+ params.contents.arity
4151
+ else
4152
+ 0..0
4153
+ end
4154
+ end
4169
4155
  end
4170
4156
 
4171
4157
  # Defined represents the use of the +defined?+ operator. It can be used with
@@ -4353,6 +4339,15 @@ module SyntaxTree
4353
4339
  opening.is_a?(Kw)
4354
4340
  end
4355
4341
 
4342
+ def arity
4343
+ case block_var
4344
+ when BlockVar
4345
+ block_var.params.arity
4346
+ else
4347
+ 0..0
4348
+ end
4349
+ end
4350
+
4356
4351
  private
4357
4352
 
4358
4353
  # If this is nested anywhere inside certain nodes, then we can't change
@@ -6144,7 +6139,7 @@ module SyntaxTree
6144
6139
  module Ternaryable
6145
6140
  class << self
6146
6141
  def call(q, node)
6147
- return false if ENV["STREE_FAST_FORMAT"]
6142
+ return false if ENV["STREE_FAST_FORMAT"] || q.disable_auto_ternary?
6148
6143
 
6149
6144
  # If this is a conditional inside of a parentheses as the only content,
6150
6145
  # then we don't want to transform it into a ternary. Presumably the user
@@ -6487,9 +6482,26 @@ module SyntaxTree
6487
6482
 
6488
6483
  def format(q)
6489
6484
  force_flat = [
6490
- AliasNode, Assign, Break, Command, CommandCall, Heredoc, IfNode, IfOp,
6491
- Lambda, MAssign, Next, OpAssign, RescueMod, ReturnNode, Super, Undef,
6492
- UnlessNode, VoidStmt, YieldNode, ZSuper
6485
+ AliasNode,
6486
+ Assign,
6487
+ Break,
6488
+ Command,
6489
+ CommandCall,
6490
+ Heredoc,
6491
+ IfNode,
6492
+ IfOp,
6493
+ Lambda,
6494
+ MAssign,
6495
+ Next,
6496
+ OpAssign,
6497
+ RescueMod,
6498
+ ReturnNode,
6499
+ Super,
6500
+ Undef,
6501
+ UnlessNode,
6502
+ VoidStmt,
6503
+ YieldNode,
6504
+ ZSuper
6493
6505
  ]
6494
6506
 
6495
6507
  if q.parent.is_a?(Paren) || force_flat.include?(truthy.class) ||
@@ -8316,6 +8328,29 @@ module SyntaxTree
8316
8328
  keyword_rest === other.keyword_rest && block === other.block
8317
8329
  end
8318
8330
 
8331
+ # Returns a range representing the possible number of arguments accepted
8332
+ # by this params node not including the block. For example:
8333
+ #
8334
+ # def foo(a, b = 1, c:, d: 2, &block)
8335
+ # ...
8336
+ # end
8337
+ #
8338
+ # has arity 2..4.
8339
+ #
8340
+ def arity
8341
+ optional_keywords = keywords.count { |_label, value| value }
8342
+
8343
+ lower_bound =
8344
+ requireds.length + posts.length + keywords.length - optional_keywords
8345
+
8346
+ upper_bound =
8347
+ if keyword_rest.nil? && rest.nil?
8348
+ lower_bound + optionals.length + optional_keywords
8349
+ end
8350
+
8351
+ lower_bound..upper_bound
8352
+ end
8353
+
8319
8354
  private
8320
8355
 
8321
8356
  def format_contents(q, parts)
@@ -11585,6 +11620,10 @@ module SyntaxTree
11585
11620
  def access_control?
11586
11621
  @access_control ||= %w[private protected public].include?(value.value)
11587
11622
  end
11623
+
11624
+ def arity
11625
+ 0
11626
+ end
11588
11627
  end
11589
11628
 
11590
11629
  # VoidStmt represents an empty lexical block of code.
@@ -53,7 +53,7 @@ module SyntaxTree
53
53
  # there's a BOM at the beginning of the file, which is the reason we need
54
54
  # to compare it to 0 here.
55
55
  def [](byteindex)
56
- indices[byteindex < 0 ? 0 : byteindex]
56
+ indices[[byteindex, 0].max]
57
57
  end
58
58
  end
59
59
 
@@ -1103,6 +1103,7 @@ module SyntaxTree
1103
1103
  # :call-seq:
1104
1104
  # on_comment: (String value) -> Comment
1105
1105
  def on_comment(value)
1106
+ # char is the index of the # character in the source.
1106
1107
  char = char_pos
1107
1108
  location =
1108
1109
  Location.token(
@@ -1112,8 +1113,24 @@ module SyntaxTree
1112
1113
  size: value.size - 1
1113
1114
  )
1114
1115
 
1115
- index = source.rindex(/[^\t ]/, char - 1) if char != 0
1116
- inline = index && (source[index] != "\n")
1116
+ # Loop backward in the source string, starting from the beginning of the
1117
+ # comment, and find the first character that is not a space or a tab. If
1118
+ # index is -1, this indicates that we've checked all of the characters
1119
+ # back to the start of the source, so this comment must be at the
1120
+ # beginning of the file.
1121
+ #
1122
+ # We are purposefully not using rindex or regular expressions here because
1123
+ # they check if there are invalid characters, which is actually possible
1124
+ # with the use of __END__ syntax.
1125
+ index = char - 1
1126
+ while index > -1 && (source[index] == "\t" || source[index] == " ")
1127
+ index -= 1
1128
+ end
1129
+
1130
+ # If we found a character that was not a space or a tab before the comment
1131
+ # and it's a newline, then this comment is inline. Otherwise, it stands on
1132
+ # its own and can be attached as its own node in the tree.
1133
+ inline = index != -1 && source[index] != "\n"
1117
1134
  comment =
1118
1135
  Comment.new(value: value.chomp, inline: inline, location: location)
1119
1136
 
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxTree
4
+ class Formatter
5
+ DISABLE_TERNARY = true
6
+ end
7
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SyntaxTree
4
- VERSION = "5.1.0"
4
+ VERSION = "5.3.0"
5
5
  end
@@ -62,22 +62,26 @@ module SyntaxTree
62
62
  "constant-from"
63
63
  ].freeze
64
64
 
65
- attr_reader :filepath
65
+ attr_reader :lines
66
66
 
67
- def initialize(filepath)
68
- @filepath = filepath
67
+ def initialize(lines)
68
+ @lines = lines
69
69
  end
70
70
 
71
71
  def assemble
72
- iseq = InstructionSequence.new(:top, "<main>", nil, Location.default)
73
- assemble_iseq(iseq, File.readlines(filepath, chomp: true))
72
+ iseq = InstructionSequence.new("<main>", "<compiled>", 1, :top)
73
+ assemble_iseq(iseq, lines)
74
74
 
75
75
  iseq.compile!
76
76
  iseq
77
77
  end
78
78
 
79
- def self.assemble(filepath)
80
- new(filepath).assemble
79
+ def self.assemble(source)
80
+ new(source.lines(chomp: true)).assemble
81
+ end
82
+
83
+ def self.assemble_file(filepath)
84
+ new(File.readlines(filepath, chomp: true)).assemble
81
85
  end
82
86
 
83
87
  private
@@ -138,7 +142,7 @@ module SyntaxTree
138
142
  name = parse_symbol(name_value)
139
143
  flags = parse_number(flags_value)
140
144
 
141
- class_iseq = iseq.class_child_iseq(name.to_s, Location.default)
145
+ class_iseq = iseq.class_child_iseq(name.to_s, 1)
142
146
  assemble_iseq(class_iseq, body)
143
147
  iseq.defineclass(name, class_iseq, flags)
144
148
  when "defined"
@@ -153,7 +157,7 @@ module SyntaxTree
153
157
  line_index += body.length
154
158
 
155
159
  name = parse_symbol(operands)
156
- method_iseq = iseq.method_child_iseq(name.to_s, Location.default)
160
+ method_iseq = iseq.method_child_iseq(name.to_s, 1)
157
161
  assemble_iseq(method_iseq, body)
158
162
 
159
163
  iseq.definemethod(name, method_iseq)
@@ -162,7 +166,7 @@ module SyntaxTree
162
166
  line_index += body.length
163
167
 
164
168
  name = parse_symbol(operands)
165
- method_iseq = iseq.method_child_iseq(name.to_s, Location.default)
169
+ method_iseq = iseq.method_child_iseq(name.to_s, 1)
166
170
 
167
171
  assemble_iseq(method_iseq, body)
168
172
  iseq.definesmethod(name, method_iseq)
@@ -221,7 +225,7 @@ module SyntaxTree
221
225
  body = parse_nested(lines[line_index..])
222
226
  line_index += body.length
223
227
 
224
- block_iseq = iseq.block_child_iseq(Location.default)
228
+ block_iseq = iseq.block_child_iseq(1)
225
229
  assemble_iseq(block_iseq, body)
226
230
  block_iseq
227
231
  end
@@ -249,7 +253,7 @@ module SyntaxTree
249
253
  body = parse_nested(lines[line_index..])
250
254
  line_index += body.length
251
255
 
252
- block_iseq = iseq.block_child_iseq(Location.default)
256
+ block_iseq = iseq.block_child_iseq(1)
253
257
  assemble_iseq(block_iseq, body)
254
258
  block_iseq
255
259
  end
@@ -354,7 +358,7 @@ module SyntaxTree
354
358
  body = parse_nested(lines[line_index..])
355
359
  line_index += body.length
356
360
 
357
- block_iseq = iseq.block_child_iseq(Location.default)
361
+ block_iseq = iseq.block_child_iseq(1)
358
362
  assemble_iseq(block_iseq, body)
359
363
  block_iseq
360
364
  end
@@ -13,7 +13,7 @@ module SyntaxTree
13
13
 
14
14
  def compile
15
15
  # Set up the top-level instruction sequence that will be returned.
16
- iseq = InstructionSequence.new(:top, "<compiled>", nil, location)
16
+ iseq = InstructionSequence.new("<compiled>", "<compiled>", 1, :top)
17
17
 
18
18
  # Set up the $tape global variable that will hold our state.
19
19
  iseq.duphash({ 0 => 0 })
@@ -80,19 +80,6 @@ module SyntaxTree
80
80
 
81
81
  private
82
82
 
83
- # This is the location of the top instruction sequence, derived from the
84
- # source string.
85
- def location
86
- Location.new(
87
- start_line: 1,
88
- start_char: 0,
89
- start_column: 0,
90
- end_line: source.count("\n") + 1,
91
- end_char: source.size,
92
- end_column: source.size - (source.rindex("\n") || 0) - 1
93
- )
94
- end
95
-
96
83
  # $tape[$cursor] += value
97
84
  def change_by(iseq, value)
98
85
  iseq.getglobal(:$tape)
@@ -111,6 +98,7 @@ module SyntaxTree
111
98
  end
112
99
 
113
100
  iseq.send(YARV.calldata(:[]=, 2))
101
+ iseq.pop
114
102
  end
115
103
 
116
104
  # $cursor += value
@@ -138,6 +126,7 @@ module SyntaxTree
138
126
  iseq.send(YARV.calldata(:chr))
139
127
 
140
128
  iseq.send(YARV.calldata(:putc, 1))
129
+ iseq.pop
141
130
  end
142
131
 
143
132
  # $tape[$cursor] = $stdin.getc.ord
@@ -150,6 +139,7 @@ module SyntaxTree
150
139
  iseq.send(YARV.calldata(:ord))
151
140
 
152
141
  iseq.send(YARV.calldata(:[]=, 2))
142
+ iseq.pop
153
143
  end
154
144
 
155
145
  # unless $tape[$cursor] == 0
@@ -164,14 +154,21 @@ module SyntaxTree
164
154
 
165
155
  iseq.putobject(0)
166
156
  iseq.send(YARV.calldata(:==, 1))
167
- iseq.branchunless(end_label)
157
+ iseq.branchif(end_label)
168
158
 
169
159
  [start_label, end_label]
170
160
  end
171
161
 
172
162
  # Jump back to the start of the loop.
173
163
  def loop_end(iseq, start_label, end_label)
174
- iseq.jump(start_label)
164
+ iseq.getglobal(:$tape)
165
+ iseq.getglobal(:$cursor)
166
+ iseq.send(YARV.calldata(:[], 1))
167
+
168
+ iseq.putobject(0)
169
+ iseq.send(YARV.calldata(:==, 1))
170
+ iseq.branchunless(start_label)
171
+
175
172
  iseq.push(end_label)
176
173
  end
177
174
  end