orbacle 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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