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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +4 -0
  6. data/Gemfile +2 -0
  7. data/Gemfile.lock +119 -0
  8. data/LICENSE +22 -0
  9. data/Makefile +57 -0
  10. data/README.md +53 -0
  11. data/circle.yml +82 -0
  12. data/exe/orbaclerun +6 -0
  13. data/index.html +106 -0
  14. data/lib/orbacle.rb +96 -0
  15. data/lib/orbacle/ast_utils.rb +35 -0
  16. data/lib/orbacle/bottom_type.rb +23 -0
  17. data/lib/orbacle/builder.rb +1414 -0
  18. data/lib/orbacle/builder/context.rb +71 -0
  19. data/lib/orbacle/builder/operator_assignment_processors.rb +80 -0
  20. data/lib/orbacle/class_type.rb +32 -0
  21. data/lib/orbacle/command_line_interface.rb +107 -0
  22. data/lib/orbacle/const_name.rb +33 -0
  23. data/lib/orbacle/const_ref.rb +53 -0
  24. data/lib/orbacle/constants_tree.rb +73 -0
  25. data/lib/orbacle/define_builtins.rb +139 -0
  26. data/lib/orbacle/engine.rb +74 -0
  27. data/lib/orbacle/find_definition_under_position.rb +76 -0
  28. data/lib/orbacle/generic_type.rb +35 -0
  29. data/lib/orbacle/global_tree.rb +280 -0
  30. data/lib/orbacle/graph.rb +126 -0
  31. data/lib/orbacle/indexer.rb +151 -0
  32. data/lib/orbacle/integer_id_generator.rb +13 -0
  33. data/lib/orbacle/lambda_type.rb +37 -0
  34. data/lib/orbacle/lang_server.rb +64 -0
  35. data/lib/orbacle/main_type.rb +23 -0
  36. data/lib/orbacle/nesting.rb +78 -0
  37. data/lib/orbacle/node.rb +23 -0
  38. data/lib/orbacle/nominal_type.rb +32 -0
  39. data/lib/orbacle/ruby_parser.rb +19 -0
  40. data/lib/orbacle/scope.rb +63 -0
  41. data/lib/orbacle/selfie.rb +41 -0
  42. data/lib/orbacle/type_pretty_printer.rb +24 -0
  43. data/lib/orbacle/typing_service.rb +816 -0
  44. data/lib/orbacle/union_type.rb +40 -0
  45. data/lib/orbacle/uuid_id_generator.rb +11 -0
  46. data/lib/orbacle/worklist.rb +51 -0
  47. data/orbacle.gemspec +33 -0
  48. 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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orbacle
4
+ class IntegerIdGenerator
5
+ def initialize
6
+ @last_id = 0
7
+ end
8
+
9
+ def call
10
+ @last_id += 1
11
+ end
12
+ end
13
+ 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