syntax_tree 5.0.1 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxTree
4
+ module YARV
5
+ # This module contains the instructions that used to be a part of YARV but
6
+ # have been replaced or removed in more recent versions.
7
+ module Legacy
8
+ # ### Summary
9
+ #
10
+ # `getclassvariable` looks for a class variable in the current class and
11
+ # pushes its value onto the stack.
12
+ #
13
+ # This version of the `getclassvariable` instruction is no longer used
14
+ # since in Ruby 3.0 it gained an inline cache.`
15
+ #
16
+ # ### Usage
17
+ #
18
+ # ~~~ruby
19
+ # @@class_variable
20
+ # ~~~
21
+ #
22
+ class GetClassVariable
23
+ attr_reader :name
24
+
25
+ def initialize(name)
26
+ @name = name
27
+ end
28
+
29
+ def disasm(fmt)
30
+ fmt.instruction("getclassvariable", [fmt.object(name)])
31
+ end
32
+
33
+ def to_a(_iseq)
34
+ [:getclassvariable, name]
35
+ end
36
+
37
+ def length
38
+ 2
39
+ end
40
+
41
+ def pops
42
+ 0
43
+ end
44
+
45
+ def pushes
46
+ 1
47
+ end
48
+ end
49
+
50
+ # ### Summary
51
+ #
52
+ # `opt_getinlinecache` is a wrapper around a series of `putobject` and
53
+ # `getconstant` instructions that allows skipping past them if the inline
54
+ # cache is currently set. It pushes the value of the cache onto the stack
55
+ # if it is set, otherwise it pushes `nil`.
56
+ #
57
+ # This instruction is no longer used since in Ruby 3.2 it was replaced by
58
+ # the consolidated `opt_getconstant_path` instruction.
59
+ #
60
+ # ### Usage
61
+ #
62
+ # ~~~ruby
63
+ # Constant
64
+ # ~~~
65
+ #
66
+ class OptGetInlineCache
67
+ attr_reader :label, :cache
68
+
69
+ def initialize(label, cache)
70
+ @label = label
71
+ @cache = cache
72
+ end
73
+
74
+ def disasm(fmt)
75
+ fmt.instruction(
76
+ "opt_getinlinecache",
77
+ [fmt.label(label), fmt.inline_storage(cache)]
78
+ )
79
+ end
80
+
81
+ def to_a(_iseq)
82
+ [:opt_getinlinecache, label.name, cache]
83
+ end
84
+
85
+ def length
86
+ 3
87
+ end
88
+
89
+ def pops
90
+ 0
91
+ end
92
+
93
+ def pushes
94
+ 1
95
+ end
96
+
97
+ def call(vm)
98
+ vm.push(nil)
99
+ end
100
+ end
101
+
102
+ # ### Summary
103
+ #
104
+ # `opt_setinlinecache` sets an inline cache for a constant lookup. It pops
105
+ # the value it should set off the top of the stack. It then pushes that
106
+ # value back onto the top of the stack.
107
+ #
108
+ # This instruction is no longer used since in Ruby 3.2 it was replaced by
109
+ # the consolidated `opt_getconstant_path` instruction.
110
+ #
111
+ # ### Usage
112
+ #
113
+ # ~~~ruby
114
+ # Constant
115
+ # ~~~
116
+ #
117
+ class OptSetInlineCache
118
+ attr_reader :cache
119
+
120
+ def initialize(cache)
121
+ @cache = cache
122
+ end
123
+
124
+ def disasm(fmt)
125
+ fmt.instruction("opt_setinlinecache", [fmt.inline_storage(cache)])
126
+ end
127
+
128
+ def to_a(_iseq)
129
+ [:opt_setinlinecache, cache]
130
+ end
131
+
132
+ def length
133
+ 2
134
+ end
135
+
136
+ def pops
137
+ 1
138
+ end
139
+
140
+ def pushes
141
+ 1
142
+ end
143
+
144
+ def call(vm)
145
+ vm.push(vm.pop)
146
+ end
147
+ end
148
+
149
+ # ### Summary
150
+ #
151
+ # `setclassvariable` looks for a class variable in the current class and
152
+ # sets its value to the value it pops off the top of the stack.
153
+ #
154
+ # This version of the `setclassvariable` instruction is no longer used
155
+ # since in Ruby 3.0 it gained an inline cache.
156
+ #
157
+ # ### Usage
158
+ #
159
+ # ~~~ruby
160
+ # @@class_variable = 1
161
+ # ~~~
162
+ #
163
+ class SetClassVariable
164
+ attr_reader :name
165
+
166
+ def initialize(name)
167
+ @name = name
168
+ end
169
+
170
+ def disasm(fmt)
171
+ fmt.instruction("setclassvariable", [fmt.object(name)])
172
+ end
173
+
174
+ def to_a(_iseq)
175
+ [:setclassvariable, name]
176
+ end
177
+
178
+ def length
179
+ 2
180
+ end
181
+
182
+ def pops
183
+ 1
184
+ end
185
+
186
+ def pushes
187
+ 0
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxTree
4
+ module YARV
5
+ # This represents every local variable associated with an instruction
6
+ # sequence. There are two kinds of locals: plain locals that are what you
7
+ # expect, and block proxy locals, which represent local variables
8
+ # associated with blocks that were passed into the current instruction
9
+ # sequence.
10
+ class LocalTable
11
+ # A local representing a block passed into the current instruction
12
+ # sequence.
13
+ class BlockLocal
14
+ attr_reader :name
15
+
16
+ def initialize(name)
17
+ @name = name
18
+ end
19
+ end
20
+
21
+ # A regular local variable.
22
+ class PlainLocal
23
+ attr_reader :name
24
+
25
+ def initialize(name)
26
+ @name = name
27
+ end
28
+ end
29
+
30
+ # The result of looking up a local variable in the current local table.
31
+ class Lookup
32
+ attr_reader :local, :index, :level
33
+
34
+ def initialize(local, index, level)
35
+ @local = local
36
+ @index = index
37
+ @level = level
38
+ end
39
+ end
40
+
41
+ attr_reader :locals
42
+
43
+ def initialize
44
+ @locals = []
45
+ end
46
+
47
+ def empty?
48
+ locals.empty?
49
+ end
50
+
51
+ def find(name, level = 0)
52
+ index = locals.index { |local| local.name == name }
53
+ Lookup.new(locals[index], index, level) if index
54
+ end
55
+
56
+ def has?(name)
57
+ locals.any? { |local| local.name == name }
58
+ end
59
+
60
+ def names
61
+ locals.map(&:name)
62
+ end
63
+
64
+ def name_at(index)
65
+ locals[index].name
66
+ end
67
+
68
+ def size
69
+ locals.length
70
+ end
71
+
72
+ # Add a BlockLocal to the local table.
73
+ def block(name)
74
+ locals << BlockLocal.new(name) unless has?(name)
75
+ end
76
+
77
+ # Add a PlainLocal to the local table.
78
+ def plain(name)
79
+ locals << PlainLocal.new(name) unless has?(name)
80
+ end
81
+
82
+ # This is the offset from the top of the stack where this local variable
83
+ # lives.
84
+ def offset(index)
85
+ size - (index - 3) - 1
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,287 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module SyntaxTree
6
+ # This module provides an object representation of the YARV bytecode.
7
+ module YARV
8
+ class VM
9
+ class Jump
10
+ attr_reader :name
11
+
12
+ def initialize(name)
13
+ @name = name
14
+ end
15
+ end
16
+
17
+ class Leave
18
+ attr_reader :value
19
+
20
+ def initialize(value)
21
+ @value = value
22
+ end
23
+ end
24
+
25
+ class Frame
26
+ attr_reader :iseq, :parent, :stack_index, :_self, :nesting, :svars
27
+
28
+ def initialize(iseq, parent, stack_index, _self, nesting)
29
+ @iseq = iseq
30
+ @parent = parent
31
+ @stack_index = stack_index
32
+ @_self = _self
33
+ @nesting = nesting
34
+ @svars = {}
35
+ end
36
+ end
37
+
38
+ class TopFrame < Frame
39
+ def initialize(iseq)
40
+ super(iseq, nil, 0, TOPLEVEL_BINDING.eval("self"), [Object])
41
+ end
42
+ end
43
+
44
+ class BlockFrame < Frame
45
+ def initialize(iseq, parent, stack_index)
46
+ super(iseq, parent, stack_index, parent._self, parent.nesting)
47
+ end
48
+ end
49
+
50
+ class MethodFrame < Frame
51
+ attr_reader :name, :block
52
+
53
+ def initialize(iseq, parent, stack_index, _self, name, block)
54
+ super(iseq, parent, stack_index, _self, parent.nesting)
55
+ @name = name
56
+ @block = block
57
+ end
58
+ end
59
+
60
+ class ClassFrame < Frame
61
+ def initialize(iseq, parent, stack_index, _self)
62
+ super(iseq, parent, stack_index, _self, parent.nesting + [_self])
63
+ end
64
+ end
65
+
66
+ class FrozenCore
67
+ define_method("core#hash_merge_kwd") { |left, right| left.merge(right) }
68
+
69
+ define_method("core#hash_merge_ptr") do |hash, *values|
70
+ hash.merge(values.each_slice(2).to_h)
71
+ end
72
+
73
+ define_method("core#set_method_alias") do |clazz, new_name, old_name|
74
+ clazz.alias_method(new_name, old_name)
75
+ end
76
+
77
+ define_method("core#set_variable_alias") do |new_name, old_name|
78
+ # Using eval here since there isn't a reflection API to be able to
79
+ # alias global variables.
80
+ eval("alias #{new_name} #{old_name}", binding, __FILE__, __LINE__)
81
+ end
82
+
83
+ define_method("core#set_postexe") { |&block| END { block.call } }
84
+
85
+ define_method("core#undef_method") do |clazz, name|
86
+ clazz.undef_method(name)
87
+ end
88
+ end
89
+
90
+ FROZEN_CORE = FrozenCore.new.freeze
91
+
92
+ extend Forwardable
93
+
94
+ attr_reader :stack
95
+ def_delegators :stack, :push, :pop
96
+
97
+ attr_reader :frame
98
+ def_delegators :frame, :_self
99
+
100
+ def initialize
101
+ @stack = []
102
+ @frame = nil
103
+ end
104
+
105
+ ##########################################################################
106
+ # Helper methods for frames
107
+ ##########################################################################
108
+
109
+ def run_frame(frame)
110
+ # First, set the current frame to the given value.
111
+ @frame = frame
112
+
113
+ # Next, set up the local table for the frame. This is actually incorrect
114
+ # as it could use the values already on the stack, but for now we're
115
+ # just doing this for simplicity.
116
+ frame.iseq.local_table.size.times { push(nil) }
117
+
118
+ # Yield so that some frame-specific setup can be done.
119
+ yield if block_given?
120
+
121
+ # This hash is going to hold a mapping of label names to their
122
+ # respective indices in our instruction list.
123
+ labels = {}
124
+
125
+ # This array is going to hold our instructions.
126
+ insns = []
127
+
128
+ # Here we're going to preprocess the instruction list from the
129
+ # instruction sequence to set up the labels hash and the insns array.
130
+ frame.iseq.insns.each do |insn|
131
+ case insn
132
+ when Integer, Symbol
133
+ # skip
134
+ when InstructionSequence::Label
135
+ labels[insn.name] = insns.length
136
+ else
137
+ insns << insn
138
+ end
139
+ end
140
+
141
+ # Finally we can execute the instructions one at a time. If they return
142
+ # jumps or leaves we will handle those appropriately.
143
+ pc = 0
144
+ while pc < insns.length
145
+ insn = insns[pc]
146
+ pc += 1
147
+
148
+ case (result = insn.call(self))
149
+ when Jump
150
+ pc = labels[result.name]
151
+ when Leave
152
+ return result.value
153
+ end
154
+ end
155
+ ensure
156
+ @stack = stack[0...frame.stack_index]
157
+ @frame = frame.parent
158
+ end
159
+
160
+ def run_top_frame(iseq)
161
+ run_frame(TopFrame.new(iseq))
162
+ end
163
+
164
+ def run_block_frame(iseq, *args, &block)
165
+ run_frame(BlockFrame.new(iseq, frame, stack.length)) do
166
+ locals = [*args, block]
167
+ iseq.local_table.size.times do |index|
168
+ local_set(index, 0, locals.shift)
169
+ end
170
+ end
171
+ end
172
+
173
+ def run_class_frame(iseq, clazz)
174
+ run_frame(ClassFrame.new(iseq, frame, stack.length, clazz))
175
+ end
176
+
177
+ def run_method_frame(name, iseq, _self, *args, **kwargs, &block)
178
+ run_frame(
179
+ MethodFrame.new(iseq, frame, stack.length, _self, name, block)
180
+ ) do
181
+ locals = [*args, block]
182
+
183
+ if iseq.argument_options[:keyword]
184
+ # First, set up the keyword bits array.
185
+ keyword_bits =
186
+ iseq.argument_options[:keyword].map do |config|
187
+ kwargs.key?(config.is_a?(Array) ? config[0] : config)
188
+ end
189
+
190
+ iseq.local_table.locals.each_with_index do |local, index|
191
+ # If this is the keyword bits local, then set it appropriately.
192
+ if local.name == 2
193
+ locals.insert(index, keyword_bits)
194
+ next
195
+ end
196
+
197
+ # First, find the configuration for this local in the keywords
198
+ # list if it exists.
199
+ name = local.name
200
+ config =
201
+ iseq.argument_options[:keyword].find do |keyword|
202
+ keyword.is_a?(Array) ? keyword[0] == name : keyword == name
203
+ end
204
+
205
+ # If the configuration doesn't exist, then the local is not a
206
+ # keyword local.
207
+ next unless config
208
+
209
+ if !config.is_a?(Array)
210
+ # required keyword
211
+ locals.insert(index, kwargs.fetch(name))
212
+ elsif !config[1].nil?
213
+ # optional keyword with embedded default value
214
+ locals.insert(index, kwargs.fetch(name, config[1]))
215
+ else
216
+ # optional keyword with expression default value
217
+ locals.insert(index, nil)
218
+ end
219
+ end
220
+ end
221
+
222
+ iseq.local_table.size.times do |index|
223
+ local_set(index, 0, locals.shift)
224
+ end
225
+ end
226
+ end
227
+
228
+ ##########################################################################
229
+ # Helper methods for instructions
230
+ ##########################################################################
231
+
232
+ def const_base
233
+ frame.nesting.last
234
+ end
235
+
236
+ def frame_at(level)
237
+ current = frame
238
+ level.times { current = current.parent }
239
+ current
240
+ end
241
+
242
+ def frame_svar
243
+ current = frame
244
+ current = current.parent while current.is_a?(BlockFrame)
245
+ current
246
+ end
247
+
248
+ def frame_yield
249
+ current = frame
250
+ current = current.parent until current.is_a?(MethodFrame)
251
+ current
252
+ end
253
+
254
+ def frozen_core
255
+ FROZEN_CORE
256
+ end
257
+
258
+ def jump(label)
259
+ Jump.new(label.name)
260
+ end
261
+
262
+ def leave
263
+ Leave.new(pop)
264
+ end
265
+
266
+ def local_get(index, level)
267
+ stack[frame_at(level).stack_index + index]
268
+ end
269
+
270
+ def local_set(index, level, value)
271
+ stack[frame_at(level).stack_index + index] = value
272
+ end
273
+ end
274
+
275
+ # Compile the given source into a YARV instruction sequence.
276
+ def self.compile(source, options = Compiler::Options.new)
277
+ SyntaxTree.parse(source).accept(Compiler.new(options))
278
+ end
279
+
280
+ # Compile and interpret the given source.
281
+ def self.interpret(source, options = Compiler::Options.new)
282
+ iseq = RubyVM::InstructionSequence.compile(source, **options)
283
+ iseq = InstructionSequence.from(iseq.to_a)
284
+ VM.new.run_top_frame(iseq)
285
+ end
286
+ end
287
+ end
data/lib/syntax_tree.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "etc"
4
+ require "fiddle"
4
5
  require "json"
5
6
  require "pp"
6
7
  require "prettier_print"
@@ -9,6 +10,7 @@ require "stringio"
9
10
 
10
11
  require_relative "syntax_tree/formatter"
11
12
  require_relative "syntax_tree/node"
13
+ require_relative "syntax_tree/dsl"
12
14
  require_relative "syntax_tree/version"
13
15
 
14
16
  require_relative "syntax_tree/basic_visitor"
@@ -25,6 +27,17 @@ require_relative "syntax_tree/parser"
25
27
  require_relative "syntax_tree/pattern"
26
28
  require_relative "syntax_tree/search"
27
29
 
30
+ require_relative "syntax_tree/yarv"
31
+ require_relative "syntax_tree/yarv/bf"
32
+ require_relative "syntax_tree/yarv/compiler"
33
+ require_relative "syntax_tree/yarv/decompiler"
34
+ require_relative "syntax_tree/yarv/disassembler"
35
+ require_relative "syntax_tree/yarv/instruction_sequence"
36
+ require_relative "syntax_tree/yarv/instructions"
37
+ require_relative "syntax_tree/yarv/legacy"
38
+ require_relative "syntax_tree/yarv/local_table"
39
+ require_relative "syntax_tree/yarv/assembler"
40
+
28
41
  # Syntax Tree is a suite of tools built on top of the internal CRuby parser. It
29
42
  # provides the ability to generate a syntax tree from source, as well as the
30
43
  # tools necessary to inspect and manipulate that syntax tree. It can be used to
@@ -44,6 +57,10 @@ module SyntaxTree
44
57
  # It shouldn't really be changed except in very niche circumstances.
45
58
  DEFAULT_RUBY_VERSION = Formatter::SemanticVersion.new(RUBY_VERSION).freeze
46
59
 
60
+ # The default indentation level for formatting. We allow changing this so
61
+ # that Syntax Tree can format arbitrary parts of a document.
62
+ DEFAULT_INDENTATION = 0
63
+
47
64
  # This is a hook provided so that plugins can register themselves as the
48
65
  # handler for a particular file type.
49
66
  def self.register_handler(extension, handler)
@@ -61,12 +78,13 @@ module SyntaxTree
61
78
  def self.format(
62
79
  source,
63
80
  maxwidth = DEFAULT_PRINT_WIDTH,
81
+ base_indentation = DEFAULT_INDENTATION,
64
82
  options: Formatter::Options.new
65
83
  )
66
84
  formatter = Formatter.new(source, [], maxwidth, options: options)
67
85
  parse(source).format(formatter)
68
86
 
69
- formatter.flush
87
+ formatter.flush(base_indentation)
70
88
  formatter.output.join
71
89
  end
72
90
 
data/syntax_tree.gemspec CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
26
  spec.require_paths = %w[lib]
27
27
 
28
- spec.add_dependency "prettier_print", ">= 1.1.0"
28
+ spec.add_dependency "prettier_print", ">= 1.2.0"
29
29
 
30
30
  spec.add_development_dependency "bundler"
31
31
  spec.add_development_dependency "minitest"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: syntax_tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.1
4
+ version: 5.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Newton
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-11-10 00:00:00.000000000 Z
11
+ date: 2022-12-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: prettier_print
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 1.1.0
19
+ version: 1.2.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 1.1.0
26
+ version: 1.2.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -125,6 +125,7 @@ files:
125
125
  - lib/syntax_tree.rb
126
126
  - lib/syntax_tree/basic_visitor.rb
127
127
  - lib/syntax_tree/cli.rb
128
+ - lib/syntax_tree/dsl.rb
128
129
  - lib/syntax_tree/formatter.rb
129
130
  - lib/syntax_tree/language_server.rb
130
131
  - lib/syntax_tree/language_server/inlay_hints.rb
@@ -147,6 +148,16 @@ files:
147
148
  - lib/syntax_tree/visitor/mutation_visitor.rb
148
149
  - lib/syntax_tree/visitor/pretty_print_visitor.rb
149
150
  - lib/syntax_tree/visitor/with_environment.rb
151
+ - lib/syntax_tree/yarv.rb
152
+ - lib/syntax_tree/yarv/assembler.rb
153
+ - lib/syntax_tree/yarv/bf.rb
154
+ - lib/syntax_tree/yarv/compiler.rb
155
+ - lib/syntax_tree/yarv/decompiler.rb
156
+ - lib/syntax_tree/yarv/disassembler.rb
157
+ - lib/syntax_tree/yarv/instruction_sequence.rb
158
+ - lib/syntax_tree/yarv/instructions.rb
159
+ - lib/syntax_tree/yarv/legacy.rb
160
+ - lib/syntax_tree/yarv/local_table.rb
150
161
  - syntax_tree.gemspec
151
162
  homepage: https://github.com/kddnewton/syntax_tree
152
163
  licenses: