mirrors 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rubocop.yml +2 -0
  4. data/Gemfile +2 -0
  5. data/Gemfile.lock +23 -0
  6. data/LICENSE.txt +59 -0
  7. data/README.md +1 -0
  8. data/asdfasdf.rb +53 -0
  9. data/bin/bundler +17 -0
  10. data/bin/byebug +17 -0
  11. data/bin/testunit +8 -0
  12. data/circle.yml +6 -0
  13. data/dev.yml +6 -0
  14. data/lib/mirrors.rb +150 -0
  15. data/lib/mirrors/class_mirror.rb +197 -0
  16. data/lib/mirrors/class_mixin.rb +11 -0
  17. data/lib/mirrors/field_mirror.rb +24 -0
  18. data/lib/mirrors/field_mirror/class_variable_mirror.rb +23 -0
  19. data/lib/mirrors/field_mirror/constant_mirror.rb +34 -0
  20. data/lib/mirrors/field_mirror/instance_variable_mirror.rb +23 -0
  21. data/lib/mirrors/hook.rb +33 -0
  22. data/lib/mirrors/index/indexer.rb +6 -0
  23. data/lib/mirrors/index/marker.rb +40 -0
  24. data/lib/mirrors/invoke.rb +29 -0
  25. data/lib/mirrors/method_mirror.rb +206 -0
  26. data/lib/mirrors/mirror.rb +37 -0
  27. data/lib/mirrors/object_mirror.rb +25 -0
  28. data/lib/mirrors/package_inference.rb +164 -0
  29. data/lib/mirrors/package_inference/class_to_file_resolver.rb +66 -0
  30. data/lib/mirrors/package_mirror.rb +33 -0
  31. data/lib/mirrors/visitors/disasm_visitor.rb +11 -0
  32. data/lib/mirrors/visitors/iseq_visitor.rb +84 -0
  33. data/lib/mirrors/visitors/references_visitor.rb +58 -0
  34. data/lib/mirrors/visitors/yasmdata.rb +212 -0
  35. data/lol.rb +35 -0
  36. data/mirrors.gemspec +19 -0
  37. data/test/fixtures/class.rb +29 -0
  38. data/test/fixtures/field.rb +9 -0
  39. data/test/fixtures/method.rb +15 -0
  40. data/test/fixtures/object.rb +5 -0
  41. data/test/fixtures/reflect.rb +14 -0
  42. data/test/mirrors/class_mirror_test.rb +87 -0
  43. data/test/mirrors/field_mirror_test.rb +125 -0
  44. data/test/mirrors/iseq_visitor_test.rb +56 -0
  45. data/test/mirrors/marker_test.rb +48 -0
  46. data/test/mirrors/method_mirror_test.rb +62 -0
  47. data/test/mirrors/object_mirror_test.rb +16 -0
  48. data/test/mirrors/package_inference_test.rb +31 -0
  49. data/test/mirrors/references_visitor_test.rb +30 -0
  50. data/test/mirrors_test.rb +38 -0
  51. data/test/test_helper.rb +12 -0
  52. metadata +137 -0
@@ -0,0 +1,11 @@
1
+ # mixin, convenience operator for method reflection
2
+ # inspired by Smalltalk
3
+ class Class
4
+ def >>(symbol)
5
+ Mirrors.reflect(method(symbol))
6
+ end
7
+
8
+ def reflect
9
+ Mirrors.reflect(self)
10
+ end
11
+ end
@@ -0,0 +1,24 @@
1
+ module Mirrors
2
+ # A class to reflect on instance, class, and class instance variables,
3
+ # as well as constants.
4
+ class FieldMirror < Mirror
5
+ Field = Struct.new(:object, :name)
6
+
7
+ attr_reader :name
8
+
9
+ def initialize(obj)
10
+ super
11
+ @object = obj.object
12
+ @name = obj.name.to_s
13
+ end
14
+
15
+ # @return [ClassMirror] The class this method was originally defined in
16
+ def defining_class
17
+ Mirrors.reflect(@object)
18
+ end
19
+ end
20
+ end
21
+
22
+ require 'mirrors/field_mirror/class_variable_mirror'
23
+ require 'mirrors/field_mirror/instance_variable_mirror'
24
+ require 'mirrors/field_mirror/constant_mirror'
@@ -0,0 +1,23 @@
1
+ module Mirrors
2
+ class ClassVariableMirror < FieldMirror
3
+ def value
4
+ Mirrors.reflect(@object.class_variable_get(@name))
5
+ end
6
+
7
+ def value=(o)
8
+ @object.class_variable_set(@name, o)
9
+ end
10
+
11
+ def public?
12
+ false
13
+ end
14
+
15
+ def protected?
16
+ false
17
+ end
18
+
19
+ def private?
20
+ true
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,34 @@
1
+ module Mirrors
2
+ class ConstantMirror < FieldMirror
3
+ def value
4
+ if path = @object.autoload?(@name)
5
+ unless $LOADED_FEATURES.include?(path) ||
6
+ $LOADED_FEATURES.include?(File.expand_path(path))
7
+ # Do not trigger autoload
8
+ return nil
9
+ end
10
+ end
11
+ Mirrors.reflect @object.const_get(@name)
12
+ end
13
+
14
+ def value=(o)
15
+ @object.const_set(@name, o)
16
+ end
17
+
18
+ def public?
19
+ true
20
+ end
21
+
22
+ def protected?
23
+ false
24
+ end
25
+
26
+ def private?
27
+ false
28
+ end
29
+
30
+ def delete
31
+ @object.send(:remove_const, @name)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,23 @@
1
+ module Mirrors
2
+ class InstanceVariableMirror < FieldMirror
3
+ def value
4
+ Mirrors.reflect(@object.instance_variable_get(@name))
5
+ end
6
+
7
+ def value=(o)
8
+ @object.instance_variable_set(@name, o)
9
+ end
10
+
11
+ def public?
12
+ false
13
+ end
14
+
15
+ def protected?
16
+ false
17
+ end
18
+
19
+ def private?
20
+ true
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,33 @@
1
+ require 'mirrors/invoke'
2
+
3
+ module Mirrors
4
+ CLASS_DEFINITION_POINTS = {}
5
+
6
+ NoGemfile = Class.new(StandardError)
7
+
8
+ class << self
9
+ attr_accessor :project_root
10
+ end
11
+ end
12
+
13
+ begin
14
+ unless Mirrors.project_root
15
+ appline = caller.detect { |line| line !~ /:in `require'/ }
16
+ f = File.expand_path(appline.sub(/:\d.*/, ''))
17
+ Mirrors.project_root = loop do
18
+ f = File.dirname(f)
19
+ raise Mirrors::NoGemfile if f == '/'
20
+ break f if File.exist?("#{f}/Gemfile")
21
+ end
22
+ end
23
+ end
24
+
25
+ class Class
26
+ alias_method :__lg_orig_inherited, :inherited
27
+ def inherited(subclass)
28
+ file = caller[0].sub(/:\d+:in.*/, '')
29
+ key = Mirrors.module_instance_invoke(subclass, :inspect)
30
+ Mirrors::CLASS_DEFINITION_POINTS[key] = file
31
+ __lg_orig_inherited(subclass)
32
+ end
33
+ end
@@ -0,0 +1,6 @@
1
+ module Mirrors
2
+ module Index
3
+ class Indexer
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,40 @@
1
+ module Mirrors
2
+ class Marker
3
+ TYPE_TASK = 'mirrors.marker.task'.freeze
4
+ TYPE_PROBLEM = 'mirrors.marker.problem'.freeze
5
+ TYPE_TEXT = 'mirrors.marker.text'.freeze
6
+ TYPE_CLASS_REFERENCE = 'mirrors.marker.text.class_reference'.freeze
7
+ TYPE_METHOD_REFERENCE = 'mirrors.marker.text.method_reference'.freeze
8
+ TYPE_FIELD_REFERENCE = 'mirrors.marker.text.field_reference'.freeze
9
+
10
+ NO_LINE = -1
11
+ NO_COLUMN = -1
12
+
13
+ attr_reader :message, :type, :file, :line, :start_column, :end_column
14
+
15
+ def initialize(type: TYPE_TASK, message: '', file: nil, line: NO_LINE, start_column: NO_COLUMN, end_column: NO_COLUMN)
16
+ @type = type
17
+ @message = message
18
+ @file = file
19
+ @line = line
20
+ @start_column = start_column
21
+ @end_column = end_column
22
+ end
23
+
24
+ def ==(other)
25
+ type == other.type && message == other.message && line == other.line && start_column == other.start_column && end_column == other.end_column
26
+ end
27
+
28
+ def eql?(other)
29
+ self == other
30
+ end
31
+
32
+ def hash
33
+ [@type, @message, @line, @start_column, @end_column].hash
34
+ end
35
+
36
+ def hashy
37
+ [@type, @message, @line, @start_column, @end_column]
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,29 @@
1
+ module Mirrors
2
+ # Methods here follow the pattern of:
3
+ # <target>_<instance or singleton>_<invoke or method>
4
+ #
5
+ # * target is the owner of the method
6
+ # * instance or singleton indicates whether we want an instance method or a
7
+ # singleton method from the target
8
+ # * invoke calls the method on a receiver, which must be compatible with the
9
+ # method. method returns the method to bind to whatever receiver you want.
10
+
11
+ @unbound_module_instance_methods = {}
12
+ @unbound_class_singleton_methods = {}
13
+
14
+ def self.module_instance_invoke(receiver, msg)
15
+ module_instance_method(msg).bind(receiver).call
16
+ end
17
+
18
+ def self.module_instance_method(msg)
19
+ @unbound_module_instance_methods[msg] ||= Module.instance_method(msg)
20
+ end
21
+
22
+ def self.class_singleton_invoke(receiver, msg)
23
+ class_singleton_method(msg).bind(receiver).call
24
+ end
25
+
26
+ def self.class_singleton_method(msg)
27
+ @unbound_class_singleton_methods[msg] ||= Class.method(msg).unbind
28
+ end
29
+ end
@@ -0,0 +1,206 @@
1
+ require 'method_source'
2
+ require 'base64'
3
+ require 'ripper'
4
+ require 'pp'
5
+ require 'mirrors/visitors/iseq_visitor'
6
+ require 'mirrors/visitors/references_visitor'
7
+
8
+ module Mirrors
9
+ # A MethodMirror should reflect on methods, but in a more general
10
+ # sense than the Method and UnboundMethod classes in Ruby are able
11
+ # to offer.
12
+ #
13
+ # In actual execution, a method is pretty much every chunk of code,
14
+ # even loading a file triggers a process not unlike compiling a
15
+ # method (if only for the side-effects). Method mirrors should allow
16
+ # access to the runtime objects, but also to their static
17
+ # representations (bytecode, source, ...), their debugging
18
+ # information and statistical information
19
+ class MethodMirror < Mirror
20
+ # @return [String, nil] The filename, if available
21
+ def file
22
+ sl = source_location
23
+ sl ? sl.first : nil
24
+ end
25
+
26
+ # @return [Fixnum, nil] The source line, if available
27
+ def line
28
+ sl = source_location
29
+ sl && sl.last ? sl.last - 1 : nil
30
+ end
31
+
32
+ # @return [String] The method name
33
+ def selector
34
+ @subject.name.to_s
35
+ end
36
+
37
+ # @return [ClassMirror] The class this method was originally defined in
38
+ def defining_class
39
+ Mirrors.reflect @subject.send(:owner)
40
+ end
41
+
42
+ # Return the value the block argument, or nil
43
+ #
44
+ # @return [String, nil]
45
+ def block_argument
46
+ args(:block).first
47
+ end
48
+
49
+ # Returns a field mirror with name and possibly value of the splat
50
+ # argument, or nil, if there is none to this method.
51
+ #
52
+ # @return [String, nil]
53
+ def splat_argument
54
+ args(:rest).first
55
+ end
56
+
57
+ # Returns names and values of the optional arguments.
58
+ #
59
+ # @return [Array<String>, nil]
60
+ def optional_arguments
61
+ args(:opt)
62
+ end
63
+
64
+ # Returns the name and possibly values of the required arguments
65
+ #
66
+ # @return [Array<String>]
67
+ def required_arguments
68
+ args(:req)
69
+ end
70
+
71
+ # Queries the method for it's arguments and returns a list of
72
+ # mirrors that hold name and value information.
73
+ #
74
+ # @return [Array<String>]
75
+ def arguments
76
+ @subject.send(:parameters).map { |_, a| a.to_s }
77
+ end
78
+
79
+ # Is the method :public, :private, or :protected?
80
+ #
81
+ # @return [String]
82
+ def visibility
83
+ return :public if visibility?(:public)
84
+ return :private if visibility?(:private)
85
+ :protected
86
+ end
87
+
88
+ def protected?
89
+ visibility?(:protected)
90
+ end
91
+
92
+ def public?
93
+ visibility?(:public)
94
+ end
95
+
96
+ def private?
97
+ visibility?(:private)
98
+ end
99
+
100
+ def super_method
101
+ owner = @subject.send(:owner)
102
+
103
+ if owner.is_a?(Class)
104
+ meth = Mirrors
105
+ .class_singleton_method(:allocate)
106
+ .bind(instance)
107
+ .super_method
108
+ .unbind
109
+ else
110
+ meth = @subject.bind(owner).super_method.unbind
111
+ end
112
+
113
+ meth ? Mirrors.reflect(meth) : nil
114
+ end
115
+
116
+ # @return [String,nil] The source code of this method
117
+ def source
118
+ @source ||= unindent(@subject.send(:source))
119
+ rescue MethodSource::SourceNotFoundError
120
+ nil
121
+ end
122
+
123
+ # @return [String,nil] The pre-definition comment of this method
124
+ def comment
125
+ @subject.send(:comment)
126
+ rescue MethodSource::SourceNotFoundError
127
+ nil
128
+ end
129
+
130
+ # Returns the instruction sequence for the method (cached)
131
+ def iseq
132
+ @iseq ||= RubyVM::InstructionSequence.of(@subject)
133
+ end
134
+
135
+ # Returns the disassembled code if available.
136
+ #
137
+ # @return [String, nil] human-readable bytedcode dump
138
+ def bytecode
139
+ @bytecode ||= iseq.disasm if iseq
140
+ @bytecode
141
+ end
142
+
143
+ # Returns the parse tree if available
144
+ #
145
+ # @return [String, nil] prettified AST
146
+ def sexp
147
+ src = source
148
+ src ? Ripper.sexp(src).pretty_inspect : nil
149
+ end
150
+
151
+ # Returns the compiled code if available.
152
+ #
153
+ # @return [RubyVM::InstructionSequence, nil] native code
154
+ def native_code
155
+ RubyVM::InstructionSequence.of(@subject)
156
+ end
157
+
158
+ def name
159
+ @subject.name
160
+ end
161
+
162
+ def references
163
+ visitor = Mirrors::ReferencesVisitor.new
164
+ visitor.call(self)
165
+ visitor.markers
166
+ end
167
+
168
+ private
169
+
170
+ def visibility?(type)
171
+ list = @subject.send(:owner).send("#{type}_instance_methods")
172
+ list.any? { |m| m.to_s == selector }
173
+ end
174
+
175
+ def args(type)
176
+ args = []
177
+ @subject.send(:parameters).select { |t, n| args << n.to_s if t == type }
178
+ args
179
+ end
180
+
181
+ def source_location
182
+ @subject.send(:source_location)
183
+ end
184
+
185
+ def unindent(str)
186
+ lines = str.split("\n")
187
+ return str if lines.empty?
188
+
189
+ indents = lines.map do |line|
190
+ if line =~ /\S/
191
+ line.start_with?(" ") ? line.match(/^ +/).offset(0)[1] : 0
192
+ end
193
+ end
194
+ indents.compact!
195
+
196
+ if indents.empty?
197
+ # No lines had any non-whitespace characters.
198
+ return ([""] * lines.size).join "\n"
199
+ end
200
+
201
+ min_indent = indents.min
202
+ return str if min_indent.zero?
203
+ lines.map { |line| line =~ /\S/ ? line.gsub(/^ {#{min_indent}}/, "") : line }.join("\n")
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,37 @@
1
+ module Mirrors
2
+ # The basic mirror
3
+ class Mirror
4
+ def initialize(obj)
5
+ @subject = obj
6
+ end
7
+
8
+ def subject_id
9
+ @subject.__id__.to_s
10
+ end
11
+
12
+ # A generic representation of the object under observation.
13
+ def name
14
+ if @subject.is_a?(String) || @subject.is_a?(Symbol)
15
+ @subject
16
+ else
17
+ @subject.inspect
18
+ end
19
+ end
20
+
21
+ # The equivalent to #==/#eql? for comparison of mirrors against objects
22
+ def mirrors?(other)
23
+ @subject == other
24
+ end
25
+
26
+ # Accessor to the reflected object
27
+ def reflectee
28
+ @subject
29
+ end
30
+
31
+ private
32
+
33
+ def mirrors(list)
34
+ list.collect { |e| Mirrors.reflect(e) }
35
+ end
36
+ end
37
+ end