syntax_tree 5.1.0 → 5.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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