syntax_tree 5.0.0 → 5.1.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.
@@ -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
@@ -40,6 +53,14 @@ module SyntaxTree
40
53
  # optional second argument to ::format.
41
54
  DEFAULT_PRINT_WIDTH = 80
42
55
 
56
+ # This is the default ruby version that we're going to target for formatting.
57
+ # It shouldn't really be changed except in very niche circumstances.
58
+ DEFAULT_RUBY_VERSION = Formatter::SemanticVersion.new(RUBY_VERSION).freeze
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
+
43
64
  # This is a hook provided so that plugins can register themselves as the
44
65
  # handler for a particular file type.
45
66
  def self.register_handler(extension, handler)
@@ -57,12 +78,13 @@ module SyntaxTree
57
78
  def self.format(
58
79
  source,
59
80
  maxwidth = DEFAULT_PRINT_WIDTH,
81
+ base_indentation = DEFAULT_INDENTATION,
60
82
  options: Formatter::Options.new
61
83
  )
62
84
  formatter = Formatter.new(source, [], maxwidth, options: options)
63
85
  parse(source).format(formatter)
64
86
 
65
- formatter.flush
87
+ formatter.flush(base_indentation)
66
88
  formatter.output.join
67
89
  end
68
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.0
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-09 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: