orbacle 0.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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +119 -0
- data/LICENSE +22 -0
- data/Makefile +57 -0
- data/README.md +53 -0
- data/circle.yml +82 -0
- data/exe/orbaclerun +6 -0
- data/index.html +106 -0
- data/lib/orbacle.rb +96 -0
- data/lib/orbacle/ast_utils.rb +35 -0
- data/lib/orbacle/bottom_type.rb +23 -0
- data/lib/orbacle/builder.rb +1414 -0
- data/lib/orbacle/builder/context.rb +71 -0
- data/lib/orbacle/builder/operator_assignment_processors.rb +80 -0
- data/lib/orbacle/class_type.rb +32 -0
- data/lib/orbacle/command_line_interface.rb +107 -0
- data/lib/orbacle/const_name.rb +33 -0
- data/lib/orbacle/const_ref.rb +53 -0
- data/lib/orbacle/constants_tree.rb +73 -0
- data/lib/orbacle/define_builtins.rb +139 -0
- data/lib/orbacle/engine.rb +74 -0
- data/lib/orbacle/find_definition_under_position.rb +76 -0
- data/lib/orbacle/generic_type.rb +35 -0
- data/lib/orbacle/global_tree.rb +280 -0
- data/lib/orbacle/graph.rb +126 -0
- data/lib/orbacle/indexer.rb +151 -0
- data/lib/orbacle/integer_id_generator.rb +13 -0
- data/lib/orbacle/lambda_type.rb +37 -0
- data/lib/orbacle/lang_server.rb +64 -0
- data/lib/orbacle/main_type.rb +23 -0
- data/lib/orbacle/nesting.rb +78 -0
- data/lib/orbacle/node.rb +23 -0
- data/lib/orbacle/nominal_type.rb +32 -0
- data/lib/orbacle/ruby_parser.rb +19 -0
- data/lib/orbacle/scope.rb +63 -0
- data/lib/orbacle/selfie.rb +41 -0
- data/lib/orbacle/type_pretty_printer.rb +24 -0
- data/lib/orbacle/typing_service.rb +816 -0
- data/lib/orbacle/union_type.rb +40 -0
- data/lib/orbacle/uuid_id_generator.rb +11 -0
- data/lib/orbacle/worklist.rb +51 -0
- data/orbacle.gemspec +33 -0
- metadata +258 -0
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rgl/adjacency'
|
4
|
+
|
5
|
+
module Orbacle
|
6
|
+
class Graph
|
7
|
+
ZSuper = Struct.new(:send_result, :block)
|
8
|
+
Yield = Struct.new(:send_args, :send_result)
|
9
|
+
Metod = Struct.new(:args, :result, :yields, :zsupers)
|
10
|
+
Lambda = Struct.new(:args, :result)
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@original = RGL::DirectedAdjacencyGraph.new
|
14
|
+
@reversed = RGL::DirectedAdjacencyGraph.new
|
15
|
+
|
16
|
+
@global_variables = {}
|
17
|
+
@constants = {}
|
18
|
+
@main_ivariables = {}
|
19
|
+
@instance_ivariables = {}
|
20
|
+
@class_ivariables = {}
|
21
|
+
@cvariables = {}
|
22
|
+
@metods = {}
|
23
|
+
@lambdas = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :constants
|
27
|
+
|
28
|
+
def add_vertex(node)
|
29
|
+
raise if node.nil?
|
30
|
+
@original.add_vertex(node)
|
31
|
+
@reversed.add_vertex(node)
|
32
|
+
node
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_edges(nodes_source, nodes_target)
|
36
|
+
Array(nodes_source).each do |source|
|
37
|
+
Array(nodes_target).each do |target|
|
38
|
+
add_edge(source, target)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_edge(x, y)
|
44
|
+
raise if x.nil? || y.nil?
|
45
|
+
@original.add_edge(x, y)
|
46
|
+
@reversed.add_edge(y, x)
|
47
|
+
end
|
48
|
+
|
49
|
+
def edges
|
50
|
+
@original.edges
|
51
|
+
end
|
52
|
+
|
53
|
+
def vertices
|
54
|
+
@original.vertices
|
55
|
+
end
|
56
|
+
|
57
|
+
def adjacent_vertices(v)
|
58
|
+
@original.adjacent_vertices(v)
|
59
|
+
end
|
60
|
+
|
61
|
+
def parent_vertices(v)
|
62
|
+
@reversed.adjacent_vertices(v)
|
63
|
+
end
|
64
|
+
|
65
|
+
def has_edge?(x, y)
|
66
|
+
@original.has_edge?(x, y)
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_gvar_definition_node(gvar_name)
|
70
|
+
global_variables[gvar_name] ||= add_vertex(Node.new(:gvar_definition, {}))
|
71
|
+
return global_variables[gvar_name]
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_main_ivar_definition_node(ivar_name)
|
75
|
+
main_ivariables[ivar_name] ||= add_vertex(Node.new(:ivar_definition, {}))
|
76
|
+
return main_ivariables[ivar_name]
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_constant_definition_node(const_name)
|
80
|
+
constants[const_name] ||= add_vertex(Node.new(:const_definition, {}))
|
81
|
+
return constants[const_name]
|
82
|
+
end
|
83
|
+
|
84
|
+
def get_ivar_definition_node(scope, ivar_name)
|
85
|
+
instance_ivariables[scope.absolute_str] ||= {}
|
86
|
+
instance_ivariables[scope.absolute_str][ivar_name] ||= add_vertex(Node.new(:ivar_definition, {}))
|
87
|
+
return instance_ivariables[scope.absolute_str][ivar_name]
|
88
|
+
end
|
89
|
+
|
90
|
+
def get_class_level_ivar_definition_node(scope, ivar_name)
|
91
|
+
class_ivariables[scope.absolute_str] ||= {}
|
92
|
+
class_ivariables[scope.absolute_str][ivar_name] ||= add_vertex(Node.new(:clivar_definition, {}))
|
93
|
+
return class_ivariables[scope.absolute_str][ivar_name]
|
94
|
+
end
|
95
|
+
|
96
|
+
def get_cvar_definition_node(scope, ivar_name)
|
97
|
+
cvariables[scope.absolute_str] ||= {}
|
98
|
+
cvariables[scope.absolute_str][ivar_name] ||= add_vertex(Node.new(:cvar_definition, {}))
|
99
|
+
return cvariables[scope.absolute_str][ivar_name]
|
100
|
+
end
|
101
|
+
|
102
|
+
def get_metod_nodes(metod_id)
|
103
|
+
return metods[metod_id]
|
104
|
+
end
|
105
|
+
|
106
|
+
def store_metod_nodes(metod_id, arguments_nodes)
|
107
|
+
raise if !arguments_nodes.is_a?(Hash)
|
108
|
+
metods[metod_id] ||= Metod.new(
|
109
|
+
arguments_nodes,
|
110
|
+
add_vertex(Node.new(:method_result, {})),
|
111
|
+
[],
|
112
|
+
[])
|
113
|
+
end
|
114
|
+
|
115
|
+
def get_lambda_nodes(lambda_id)
|
116
|
+
return lambdas[lambda_id]
|
117
|
+
end
|
118
|
+
|
119
|
+
def store_lambda_nodes(lambda_id, arguments_nodes, result_node)
|
120
|
+
lambdas[lambda_id] ||= Lambda.new(arguments_nodes, result_node)
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
attr_reader :global_variables, :main_ivariables, :instance_ivariables, :class_ivariables, :cvariables, :metods, :lambdas
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require 'thread'
|
5
|
+
require 'benchmark'
|
6
|
+
|
7
|
+
module Orbacle
|
8
|
+
class Indexer
|
9
|
+
QueueElement = Struct.new(:ast, :file_path)
|
10
|
+
class StatsRecorder
|
11
|
+
def initialize
|
12
|
+
@timers = Hash.new(0.0)
|
13
|
+
@counters = Hash.new(0)
|
14
|
+
@values = Hash.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def measure(timer)
|
18
|
+
started_at = Time.now.to_f
|
19
|
+
process_result = yield
|
20
|
+
finished_at = Time.now.to_f
|
21
|
+
process_result
|
22
|
+
rescue Exception => e
|
23
|
+
finished_at ||= Time.now.to_f
|
24
|
+
raise
|
25
|
+
ensure
|
26
|
+
@timers[timer] += finished_at - started_at
|
27
|
+
end
|
28
|
+
|
29
|
+
def all_stats
|
30
|
+
@timers.merge(@counters).merge(@values)
|
31
|
+
end
|
32
|
+
|
33
|
+
def inc(counter_key, by = 1)
|
34
|
+
@counters[counter_key] += by
|
35
|
+
end
|
36
|
+
|
37
|
+
def counter(counter_key)
|
38
|
+
@counters[counter_key]
|
39
|
+
end
|
40
|
+
|
41
|
+
def set_value(key, value)
|
42
|
+
@values[key] = value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class ReadingProcess
|
47
|
+
def initialize(logger, queue, files)
|
48
|
+
@logger = logger
|
49
|
+
@queue = queue
|
50
|
+
@files = files
|
51
|
+
end
|
52
|
+
|
53
|
+
def call
|
54
|
+
@files.each do |file_path|
|
55
|
+
file_content = File.read(file_path)
|
56
|
+
@queue.push(QueueElement.new(file_content, file_path))
|
57
|
+
end
|
58
|
+
@queue.close
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
attr_reader :logger
|
63
|
+
end
|
64
|
+
|
65
|
+
class ParsingProcess
|
66
|
+
def initialize(logger, queue_contents, queue_asts)
|
67
|
+
@logger = logger
|
68
|
+
@queue_contents = queue_contents
|
69
|
+
@queue_asts = queue_asts
|
70
|
+
end
|
71
|
+
|
72
|
+
def call
|
73
|
+
parser = RubyParser.new
|
74
|
+
while !@queue_contents.closed? || !@queue_contents.empty?
|
75
|
+
element = @queue_contents.shift
|
76
|
+
begin
|
77
|
+
ast = parser.parse(element.ast)
|
78
|
+
@queue_asts.push(QueueElement.new(ast, element.file_path))
|
79
|
+
rescue RubyParser::Error => e
|
80
|
+
logger.warn "Warning: Skipped #{element.file_path} because of #{e}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
@queue_asts.close
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
attr_reader :logger
|
88
|
+
end
|
89
|
+
|
90
|
+
class BuildingProcess
|
91
|
+
def initialize(queue, builder)
|
92
|
+
@queue = queue
|
93
|
+
@builder = builder
|
94
|
+
end
|
95
|
+
|
96
|
+
def call
|
97
|
+
while !@queue.closed? || !@queue.empty?
|
98
|
+
element = @queue.shift
|
99
|
+
@builder.process_file(element.ast, element.file_path)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def initialize(logger, stats)
|
105
|
+
@logger = logger
|
106
|
+
@stats = stats
|
107
|
+
end
|
108
|
+
|
109
|
+
def call(project_root:)
|
110
|
+
project_root_path = Pathname.new(project_root)
|
111
|
+
|
112
|
+
files = Dir.glob("#{project_root_path}/**/*.rb")
|
113
|
+
id_generator = IntegerIdGenerator.new
|
114
|
+
worklist = Worklist.new
|
115
|
+
state = GlobalTree.new(id_generator)
|
116
|
+
graph = Graph.new
|
117
|
+
DefineBuiltins.new(graph, state, id_generator).()
|
118
|
+
@parser = Builder.new(graph, worklist, state, id_generator)
|
119
|
+
|
120
|
+
queue_contents = Queue.new
|
121
|
+
queue_asts = Queue.new
|
122
|
+
|
123
|
+
logger.info "Reading..."
|
124
|
+
reading_process = ReadingProcess.new(logger, queue_contents, files)
|
125
|
+
@stats.measure(:reading) { reading_process.call() }
|
126
|
+
|
127
|
+
logger.info "Parsing..."
|
128
|
+
parsing_process = ParsingProcess.new(logger, queue_contents, queue_asts)
|
129
|
+
@stats.measure(:parsing) { parsing_process.call() }
|
130
|
+
|
131
|
+
logger.info "Building graph..."
|
132
|
+
building_process = BuildingProcess.new(queue_asts, @parser)
|
133
|
+
@stats.measure(:building) { building_process.call() }
|
134
|
+
|
135
|
+
logger.info "Typing..."
|
136
|
+
typing_service = TypingService.new(logger, @stats)
|
137
|
+
@stats.measure(:typing) { typing_service.(graph, worklist, state) }
|
138
|
+
|
139
|
+
type_mapping = state.instance_variable_get(:@type_mapping)
|
140
|
+
@stats.set_value(:typed_nodes_all, type_mapping.size)
|
141
|
+
@stats.set_value(:typed_nodes_not_bottom, type_mapping.count {|k,v| !v.bottom? })
|
142
|
+
@stats.set_value(:typed_nodes_call_result, type_mapping.count {|k,v| k.type == :call_result })
|
143
|
+
@stats.set_value(:typed_nodes_call_result_not_bottom, type_mapping.count {|k,v| k.type == :call_result && !v.bottom? })
|
144
|
+
|
145
|
+
return state, graph, worklist
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
attr_reader :logger
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Orbacle
|
4
|
+
class ProcType
|
5
|
+
def initialize(lambda_id)
|
6
|
+
@lambda_id = lambda_id
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :lambda_id
|
10
|
+
|
11
|
+
def ==(other)
|
12
|
+
self.class == other.class &&
|
13
|
+
self.lambda_id == other.lambda_id
|
14
|
+
end
|
15
|
+
|
16
|
+
def hash
|
17
|
+
[
|
18
|
+
self.class,
|
19
|
+
self.lambda_id,
|
20
|
+
].hash ^ BIG_VALUE
|
21
|
+
end
|
22
|
+
alias eql? ==
|
23
|
+
|
24
|
+
def each_possible_type
|
25
|
+
yield self
|
26
|
+
end
|
27
|
+
|
28
|
+
def bottom?
|
29
|
+
false
|
30
|
+
end
|
31
|
+
|
32
|
+
def name
|
33
|
+
"Proc"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'lsp'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module Orbacle
|
7
|
+
class LangServer
|
8
|
+
include Lsp::LanguageServer
|
9
|
+
|
10
|
+
def initialize(logger, engine)
|
11
|
+
@logger = logger
|
12
|
+
@engine = engine
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :logger, :engine
|
16
|
+
|
17
|
+
def handle_initialize(request)
|
18
|
+
root_path = request.root_uri.path
|
19
|
+
logger.info("Initializing at #{root_path.inspect}")
|
20
|
+
engine.index(root_path)
|
21
|
+
Lsp::ResponseMessage.successful(nil)
|
22
|
+
end
|
23
|
+
|
24
|
+
def handle_text_document_hover(request)
|
25
|
+
log_errors do
|
26
|
+
filepath = request.text_document.uri.path
|
27
|
+
pretty_type = engine.get_type_information(filepath, Position.new(request.position.line, request.position.character))
|
28
|
+
Lsp::ResponseMessage.successful(
|
29
|
+
Lsp::TextDocumentHoverResult.new(
|
30
|
+
"Type of that expression: #{pretty_type}"))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def handle_text_document_definition(request)
|
35
|
+
log_errors do
|
36
|
+
file_path = request.text_document.uri.path
|
37
|
+
file_content = File.read(file_path)
|
38
|
+
locations = engine.locations_for_definition_under_position(file_path, file_content, Position.new(request.position.line, request.position.character))
|
39
|
+
if locations
|
40
|
+
Lsp::ResponseMessage.successful(locations.map(&method(:location_to_lsp_location)))
|
41
|
+
else
|
42
|
+
Lsp::ResponseMessage.successful(nil)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def log_errors
|
48
|
+
begin
|
49
|
+
yield
|
50
|
+
rescue => e
|
51
|
+
logger.error(e)
|
52
|
+
raise
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def location_to_lsp_location(location)
|
57
|
+
Lsp::Location.new(
|
58
|
+
URI("file://#{location.uri}"),
|
59
|
+
Lsp::Range.new(
|
60
|
+
Lsp::Position.new(location.start.line, location.start.character),
|
61
|
+
Lsp::Position.new(location.end.line, location.end.character)))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Orbacle
|
4
|
+
class MainType
|
5
|
+
def each_possible_type
|
6
|
+
end
|
7
|
+
|
8
|
+
def ==(other)
|
9
|
+
self.class == other.class
|
10
|
+
end
|
11
|
+
|
12
|
+
def hash
|
13
|
+
[
|
14
|
+
self.class,
|
15
|
+
].hash ^ BIG_VALUE
|
16
|
+
end
|
17
|
+
alias eql? ==
|
18
|
+
|
19
|
+
def bottom?
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Orbacle
|
4
|
+
class Nesting
|
5
|
+
class ConstLevel < Struct.new(:const_ref)
|
6
|
+
def full_name
|
7
|
+
const_ref.full_name
|
8
|
+
end
|
9
|
+
|
10
|
+
def absolute?
|
11
|
+
const_ref.absolute?
|
12
|
+
end
|
13
|
+
|
14
|
+
def eigenclass?
|
15
|
+
false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class ClassConstLevel < Struct.new(:scope)
|
20
|
+
def full_name
|
21
|
+
scope.absolute_str
|
22
|
+
end
|
23
|
+
|
24
|
+
def absolute?
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
def eigenclass?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.empty
|
34
|
+
new([])
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize(levels)
|
38
|
+
@levels = levels
|
39
|
+
end
|
40
|
+
|
41
|
+
def ==(other)
|
42
|
+
levels.size == other.levels.size &&
|
43
|
+
levels.zip(other.levels).all? {|l1, l2| l1 == l2 }
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_reader :levels
|
47
|
+
|
48
|
+
def to_primitive
|
49
|
+
levels.map {|level| level.full_name }
|
50
|
+
end
|
51
|
+
|
52
|
+
def increase_nesting_const(const_ref)
|
53
|
+
Nesting.new(levels + [ConstLevel.new(const_ref)])
|
54
|
+
end
|
55
|
+
|
56
|
+
def increase_nesting_self
|
57
|
+
Nesting.new(levels + [ClassConstLevel.new(to_scope)])
|
58
|
+
end
|
59
|
+
|
60
|
+
def decrease_nesting
|
61
|
+
Nesting.new(levels[0..-2])
|
62
|
+
end
|
63
|
+
|
64
|
+
def empty?
|
65
|
+
levels.empty?
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_scope
|
69
|
+
levels.inject(Scope.empty) do |scope, nesting_level|
|
70
|
+
if nesting_level.eigenclass?
|
71
|
+
Scope.new(nesting_level.full_name.split("::").reject(&:empty?), nesting_level.eigenclass?)
|
72
|
+
else
|
73
|
+
scope.increase_by_ref(nesting_level.const_ref)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|