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,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orbacle
4
+ class Builder
5
+ class Context
6
+ AnalyzedKlass = Struct.new(:klass_id, :method_visibility)
7
+
8
+ def initialize(filepath, selfie, nesting, analyzed_klass, analyzed_method, lenv)
9
+ @filepath = filepath.freeze
10
+ @selfie = selfie.freeze
11
+ @nesting = nesting.freeze
12
+ @analyzed_klass = analyzed_klass.freeze
13
+ @analyzed_method = analyzed_method.freeze
14
+ @lenv = lenv.freeze
15
+ end
16
+
17
+ attr_reader :filepath, :selfie, :nesting, :analyzed_klass, :analyzed_method, :lenv
18
+
19
+ def with_selfie(new_selfie)
20
+ self.class.new(filepath, new_selfie, nesting, analyzed_klass, analyzed_method, lenv)
21
+ end
22
+
23
+ def with_nesting(new_nesting)
24
+ self.class.new(filepath, selfie, new_nesting, analyzed_klass, analyzed_method, lenv)
25
+ end
26
+
27
+ def scope
28
+ nesting.to_scope
29
+ end
30
+
31
+ def with_analyzed_klass(new_klass_id)
32
+ self.class.new(filepath, selfie, nesting, AnalyzedKlass.new(new_klass_id, :public), analyzed_method, lenv)
33
+ end
34
+
35
+ def with_visibility(new_visibility)
36
+ self.class.new(filepath, selfie, nesting, AnalyzedKlass.new(analyzed_klass.klass_id, new_visibility), analyzed_method, lenv)
37
+ end
38
+
39
+ def with_analyzed_method(new_analyzed_method_id)
40
+ self.class.new(filepath, selfie, nesting, analyzed_klass, new_analyzed_method_id, lenv)
41
+ end
42
+
43
+ def merge_lenv(new_lenv)
44
+ self.class.new(filepath, selfie, nesting, analyzed_klass, analyzed_method, lenv.merge(new_lenv))
45
+ end
46
+
47
+ def lenv_fetch(key)
48
+ lenv.fetch(key, [])
49
+ end
50
+
51
+ def with_lenv(new_lenv)
52
+ self.class.new(filepath, selfie, nesting, analyzed_klass, analyzed_method, new_lenv)
53
+ end
54
+
55
+ def analyzed_klass_id
56
+ analyzed_klass.klass_id
57
+ end
58
+
59
+ def with_merged_lenvs(lenv1, lenv2)
60
+ final_lenv = {}
61
+
62
+ var_names = (lenv1.keys + lenv2.keys).uniq
63
+ var_names.each do |var_name|
64
+ final_lenv[var_name] = (lenv1.fetch(var_name, []) + lenv2.fetch(var_name, [])).uniq
65
+ end
66
+
67
+ with_lenv(final_lenv)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orbacle
4
+ class Builder
5
+ module OperatorAssignmentProcessors
6
+ def handle_op_asgn(ast, context)
7
+ partial_assignment_ast, operator_name, argument_ast = ast.children
8
+
9
+ return process(
10
+ complete_assignment(
11
+ partial_assignment_ast,
12
+ Parser::AST::Node.new(:send, [
13
+ build_accessor_based_on_assignment(partial_assignment_ast),
14
+ operator_name,
15
+ argument_ast])),
16
+ context)
17
+ end
18
+
19
+ def handle_or_asgn(ast, context)
20
+ partial_assignment_ast, argument_ast = ast.children
21
+
22
+ return process(
23
+ complete_assignment(
24
+ partial_assignment_ast,
25
+ Parser::AST::Node.new(:or, [
26
+ build_accessor_based_on_assignment(partial_assignment_ast),
27
+ argument_ast])),
28
+ context)
29
+ end
30
+
31
+ def handle_and_asgn(ast, context)
32
+ partial_assignment_ast, argument_ast = ast.children
33
+
34
+ return process(
35
+ complete_assignment(
36
+ partial_assignment_ast,
37
+ Parser::AST::Node.new(:and, [
38
+ build_accessor_based_on_assignment(partial_assignment_ast),
39
+ argument_ast])),
40
+ context)
41
+ end
42
+
43
+ def build_accessor_based_on_assignment(assignment_ast)
44
+ case assignment_ast.type
45
+ when :lvasgn
46
+ var_name = assignment_ast.children[0]
47
+ Parser::AST::Node.new(:lvar, [var_name])
48
+ when :ivasgn
49
+ var_name = assignment_ast.children[0]
50
+ Parser::AST::Node.new(:ivar, [var_name])
51
+ when :cvasgn
52
+ var_name = assignment_ast.children[0]
53
+ Parser::AST::Node.new(:cvar, [var_name])
54
+ when :casgn
55
+ scope = assignment_ast.children[0]
56
+ var_name = assignment_ast.children[1]
57
+ Parser::AST::Node.new(:const, [scope, var_name])
58
+ when :send
59
+ send_obj = assignment_ast.children[0]
60
+ asgn_method_name = assignment_ast.children[1]
61
+ args = assignment_ast.children[2..-1]
62
+ Parser::AST::Node.new(:send, [send_obj, asgn_method_name, *args])
63
+ when :gvasgn
64
+ var_name = assignment_ast.children[0]
65
+ Parser::AST::Node.new(:gvar, [var_name])
66
+ else raise ArgumentError
67
+ end
68
+ end
69
+
70
+ def complete_assignment(partial_assignment_ast, full_rhs_ast)
71
+ if partial_assignment_ast.type == :send
72
+ send_obj_ast, accessor_method_name, _ = partial_assignment_ast.children
73
+ partial_assignment_ast.updated(nil, [send_obj_ast, "#{accessor_method_name}=", full_rhs_ast])
74
+ elsif [:lvasgn, :ivasgn, :cvasgn, :casgn, :gvasgn].include?(partial_assignment_ast.type)
75
+ partial_assignment_ast.append(full_rhs_ast)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orbacle
4
+ class ClassType
5
+ def initialize(name)
6
+ @name = name
7
+ end
8
+
9
+ attr_reader :name
10
+
11
+ def ==(other)
12
+ self.class == other.class &&
13
+ self.name == other.name
14
+ end
15
+
16
+ def hash
17
+ [
18
+ self.class,
19
+ self.name,
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
+ end
32
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+ require 'pathname'
5
+ require 'logger'
6
+ require 'fileutils'
7
+ require 'orbacle'
8
+ require 'lsp'
9
+ require 'json'
10
+
11
+ module Orbacle
12
+ class CommandLineInterface
13
+ class Options
14
+ def initialize
15
+ @dir = Dir.pwd
16
+ @stats_file = Pathname.new(Dir.pwd).join("stats.json")
17
+ end
18
+ attr_reader :dir, :stats_file
19
+
20
+ def define_options(parser)
21
+ parser.banner = 'Usage: ./orbacle [options]'
22
+
23
+ parser.on('-d DIR', '--dir', 'Directory in which project resides') do |dir|
24
+ @dir = dir
25
+ end
26
+
27
+ parser.on("-h", "--help", "Prints this help") do
28
+ puts parser
29
+ exit
30
+ end
31
+ end
32
+ end
33
+
34
+ def call(args)
35
+ options = Options.new
36
+ OptionParser.new do |parser|
37
+ options.define_options(parser)
38
+ end.parse!(args)
39
+ call_command(args[0], options)
40
+ end
41
+
42
+ def call_command(command, options)
43
+ case command
44
+ when 'index' then index(options)
45
+ when 'file-server' then file_server(options)
46
+ when 'generate-datajs' then generate_datajs(options)
47
+ else no_command
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def index(options)
54
+ logger = Logger.new(STDOUT)
55
+ project_root = options.dir
56
+
57
+ engine = Engine.new(logger)
58
+ engine.index(project_root)
59
+ ensure
60
+ File.open(options.stats_file, "w") {|f| f.write(engine.stats_recorder.all_stats.to_json) }
61
+ end
62
+
63
+ def file_server(options)
64
+ logger = Logger.new('/tmp/orbacle.log', 'monthly')
65
+ logger.level = Logger::INFO
66
+
67
+ engine = Engine.new(logger)
68
+ lang_server = LangServer.new(logger, engine)
69
+ server = Lsp::FileLanguageServer.new(lang_server, logger: logger)
70
+ server.start
71
+ end
72
+
73
+ def generate_datajs(options)
74
+ logger = Logger.new(STDOUT)
75
+
76
+ require 'base64'
77
+ project_root = options.dir
78
+ engine = Engine.new(logger)
79
+ tree, typing_result, graph = engine.index(project_root)
80
+
81
+ nodes = graph.vertices
82
+ filepaths = nodes.map {|n| n.location&.uri }.compact.uniq
83
+ type_pretty_printer = TypePrettyPrinter.new
84
+
85
+ File.open("data.js", "w") do |f|
86
+ f.puts "window.orbacleFiles = ["
87
+ filepaths.each do |filepath|
88
+ f.puts " ['#{filepath[project_root.to_s.size..-1]}', `#{Base64.encode64(File.read(filepath))}`],"
89
+ end
90
+ f.puts "];"
91
+ f.puts "window.orbacleNodes = ["
92
+ sorted_nodes = nodes.reject {|n| n.location&.uri.nil? }
93
+ sorted_nodes.each do |node|
94
+ filepath = node.location.uri[project_root.to_s.size..-1]
95
+ pretty_type = type_pretty_printer.(typing_result[node])
96
+ f.puts "['#{node.type}', '#{pretty_type}', '#{filepath}', #{node.location&.start&.line&.to_i}, #{node.location&.start&.character&.to_i}, #{node.location&.end&.line&.to_i}, #{node.location&.end&.character&.to_i}],"
97
+ end
98
+ f.puts "];"
99
+ end
100
+ end
101
+
102
+ def no_command
103
+ puts "No command given."
104
+ exit
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orbacle
4
+ class ConstName
5
+ def self.from_string(str)
6
+ raise ArgumentError if str.start_with?("::")
7
+ new(str.split("::"))
8
+ end
9
+
10
+ def initialize(elems)
11
+ @elems = elems
12
+ raise ArgumentError if elems.empty?
13
+ end
14
+
15
+ attr_reader :elems
16
+
17
+ def ==(other)
18
+ elems == other.elems
19
+ end
20
+
21
+ def name
22
+ elems.last
23
+ end
24
+
25
+ def to_string
26
+ elems.join("::")
27
+ end
28
+
29
+ def scope
30
+ Scope.new(elems[0..-2], false)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orbacle
4
+ class ConstRef
5
+ def self.from_ast(ast, nesting)
6
+ full_name = AstUtils.const_to_string(ast)
7
+ from_full_name(full_name, nesting)
8
+ end
9
+
10
+ def self.from_full_name(full_name, nesting)
11
+ if full_name.start_with?("::")
12
+ name = full_name[2..-1]
13
+ new(ConstName.from_string(name), true, nesting)
14
+ else
15
+ new(ConstName.from_string(full_name), false, nesting)
16
+ end
17
+ end
18
+
19
+ def initialize(const_name, is_absolute, nesting)
20
+ @const_name = const_name
21
+ @is_absolute = is_absolute
22
+ @nesting = nesting
23
+ end
24
+
25
+ attr_reader :const_name, :is_absolute, :nesting
26
+
27
+ def to_full_const_name
28
+ if absolute?
29
+ const_name
30
+ else
31
+ ConstName.new([*nesting.to_primitive, const_name.to_string])
32
+ end
33
+ end
34
+
35
+ def absolute?
36
+ @is_absolute
37
+ end
38
+
39
+ def relative_name
40
+ const_name.to_string
41
+ end
42
+
43
+ def name
44
+ const_name.name
45
+ end
46
+
47
+ def ==(other)
48
+ const_name == other.const_name &&
49
+ is_absolute == other.is_absolute &&
50
+ nesting == other.nesting
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orbacle
4
+ class ConstantsTree
5
+ ScopeLevel = Struct.new(:elements, :children) do
6
+ def self.empty
7
+ new([], build_empty_hash)
8
+ end
9
+
10
+ def self.build_empty_hash
11
+ Hash.new {|h, k| h[k] = ScopeLevel.empty }
12
+ end
13
+ end
14
+
15
+ def initialize
16
+ @tree = ScopeLevel.build_empty_hash
17
+ end
18
+
19
+ def add_element(scope, name, element)
20
+ current_children = @tree
21
+ scope.elems.each do |scope_level|
22
+ current_children = current_children[scope_level].children
23
+ end
24
+ current_children[name].elements << element
25
+ end
26
+
27
+ def find_by_const_name(const_name)
28
+ scope_children = children_of_scope(const_name.scope)
29
+ scope_children[const_name.name].elements.first
30
+ end
31
+
32
+ def select_by_const_ref(const_ref)
33
+ nesting = const_ref.nesting
34
+ while !nesting.empty?
35
+ result = select_by_scope_and_name(nesting.to_scope.increase_by_ref(const_ref).decrease, const_ref.name)
36
+ return result if !result.empty?
37
+ nesting = nesting.decrease_nesting
38
+ end
39
+ select_by_scope_and_name(Scope.empty.increase_by_ref(const_ref).decrease, const_ref.name)
40
+ end
41
+
42
+ def find_by_const_ref(const_ref)
43
+ select_by_const_ref(const_ref).first
44
+ end
45
+
46
+ def find(&block)
47
+ find_in_children(@tree, &block)
48
+ end
49
+
50
+ private
51
+ def select_by_scope_and_name(scope, name)
52
+ scope_level = children_of_scope(scope)[name]
53
+ scope_level.elements
54
+ end
55
+
56
+ def children_of_scope(scope)
57
+ scope.elems.reduce(@tree) do |current_scope_level, scope_elem|
58
+ current_scope_level[scope_elem].children
59
+ end
60
+ end
61
+
62
+ def find_in_children(children, &block)
63
+ children.each do |_child_name, child_level|
64
+ child_level.elements.each do |constant|
65
+ return constant if block.call(constant)
66
+ end
67
+ result_in_child_level = find_in_children(child_level.children, &block)
68
+ return result_in_child_level if result_in_child_level
69
+ end
70
+ nil
71
+ end
72
+ end
73
+ end