gloss 0.0.3 → 0.1.1

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +3 -0
  3. data/.github/workflows/{crystal.yml → crystal_specs.yml} +1 -1
  4. data/.github/workflows/{ruby.yml → ruby_specs.yml} +2 -2
  5. data/.gloss.yml +1 -0
  6. data/.rspec +1 -0
  7. data/Gemfile.lock +10 -12
  8. data/README.md +36 -5
  9. data/Rakefile +1 -1
  10. data/exe/gloss +13 -2
  11. data/ext/gloss/Makefile +8 -19
  12. data/ext/gloss/{src/lib → lib}/cr_ruby.cr +0 -0
  13. data/ext/gloss/lib/rbs_types.cr +3 -0
  14. data/ext/gloss/spec/parser_spec.cr +83 -83
  15. data/ext/gloss/src/cr_ast.cr +96 -77
  16. data/ext/gloss/src/gloss.cr +2 -2
  17. data/ext/gloss/src/lexer.cr +59 -1
  18. data/ext/gloss/src/rb_ast.cr +114 -63
  19. data/lib/gloss.rb +15 -7
  20. data/lib/gloss/cli.rb +85 -28
  21. data/lib/gloss/config.rb +13 -7
  22. data/lib/gloss/errors.rb +3 -4
  23. data/lib/gloss/initializer.rb +9 -9
  24. data/lib/gloss/logger.rb +29 -0
  25. data/lib/gloss/parser.rb +19 -5
  26. data/lib/gloss/prog_loader.rb +141 -0
  27. data/lib/gloss/scope.rb +7 -2
  28. data/lib/gloss/source.rb +17 -14
  29. data/lib/gloss/type_checker.rb +86 -33
  30. data/lib/gloss/utils.rb +44 -0
  31. data/lib/gloss/version.rb +1 -2
  32. data/lib/gloss/visitor.rb +667 -0
  33. data/lib/gloss/watcher.rb +51 -16
  34. data/lib/gloss/writer.rb +24 -15
  35. data/sig/core.rbs +2 -0
  36. data/sig/fast_blank.rbs +4 -0
  37. data/sig/{gloss.rbs → gls.rbs} +0 -0
  38. data/sig/listen.rbs +1 -0
  39. data/sig/optparse.rbs +6 -0
  40. data/sig/rubygems.rbs +9 -0
  41. data/sig/yaml.rbs +3 -0
  42. data/src/exe/gloss +19 -0
  43. data/src/lib/gloss.gl +26 -0
  44. data/src/lib/gloss/cli.gl +70 -0
  45. data/src/lib/gloss/config.gl +9 -3
  46. data/src/lib/gloss/initializer.gl +4 -6
  47. data/src/lib/gloss/logger.gl +21 -0
  48. data/src/lib/gloss/parser.gl +17 -5
  49. data/src/lib/gloss/prog_loader.gl +133 -0
  50. data/src/lib/gloss/scope.gl +7 -0
  51. data/src/lib/gloss/source.gl +32 -0
  52. data/src/lib/gloss/type_checker.gl +85 -36
  53. data/src/lib/gloss/utils.gl +38 -0
  54. data/src/lib/gloss/version.gl +1 -1
  55. data/src/lib/gloss/visitor.gl +575 -0
  56. data/src/lib/gloss/watcher.gl +44 -10
  57. data/src/lib/gloss/writer.gl +16 -14
  58. metadata +28 -8
  59. data/lib/gloss/builder.rb +0 -447
data/lib/gloss/scope.rb CHANGED
@@ -1,9 +1,14 @@
1
- # frozen_string_literal: true
1
+ # frozen_string_literal: true
2
+
3
+ ##### This file was generated by Gloss; any changes made here will be overwritten.
4
+ ##### See src/ to make changes
2
5
 
3
6
  module Gloss
4
7
  class Scope < Hash
5
8
  def [](k)
6
- fetch(k) { raise "Undefined expression for current scope: #{k}" }
9
+ fetch(k) { ||
10
+ raise("Undefined expression for current scope: #{k}")
11
+ }
7
12
  end
8
13
  end
9
14
  end
data/lib/gloss/source.rb CHANGED
@@ -1,31 +1,34 @@
1
- # frozen_string_literal: true
1
+ # frozen_string_literal: true
2
+
3
+ ##### This file was generated by Gloss; any changes made here will be overwritten.
4
+ ##### See src/ to make changes
2
5
 
3
6
  module Gloss
4
7
  class Source < String
5
8
  def initialize(indent_level)
6
- super()
7
9
  @indent_level = indent_level
10
+ super()
8
11
  end
9
-
10
12
  def write(*args)
11
- args.each do |a|
12
- self << a
13
- end
13
+ args.each() { |a|
14
+ self.<<(a)
15
+ }
16
+ self
14
17
  end
15
-
16
18
  def write_indnt(*args)
17
- write(*args.map { |a| "#{(" " * @indent_level)}#{a}" })
19
+ write(*args.map() { |a|
20
+ "#{" ".*(@indent_level)}#{a}" })
18
21
  end
19
-
20
22
  def write_ln(*args)
21
- write_indnt(*args.map { |a| a.strip << "\n" })
23
+ write_indnt(*args.map() { |a|
24
+ a.strip
25
+ .<<("\n")
26
+ })
22
27
  end
23
-
24
- def increment_indent
28
+ def increment_indent()
25
29
  @indent_level += 1
26
30
  end
27
-
28
- def decrement_indent
31
+ def decrement_indent()
29
32
  @indent_level -= 1
30
33
  end
31
34
  end
@@ -1,61 +1,114 @@
1
- # frozen_string_literal: true
1
+ # frozen_string_literal: true
2
2
 
3
- ##### This file was generated by Gloss; any changes made here will be overwritten.
4
- ##### See src/ to make changes
3
+ ##### This file was generated by Gloss; any changes made here will be overwritten.
4
+ ##### See src/ to make changes
5
5
 
6
+ require "set"
6
7
  module Gloss
7
8
  class TypeChecker
8
- Project = Struct.new(:targets)
9
- attr_reader(:steep_target, :top_level_decls)
9
+ Project = Struct.new(:"targets")
10
+ attr_reader(:"steep_target", :"top_level_decls", :"env", :"rbs_gem_dir")
10
11
  def initialize()
11
12
  @steep_target = Steep::Project::Target.new(name: "gloss", options: Steep::Project::Options.new
12
- .tap { |o|
13
+ .tap() { |o|
13
14
  o.allow_unknown_constant_assignment=(true)
14
- }, source_patterns: ["gloss.rb"], ignore_patterns: Array.new, signature_patterns: ["sig"])
15
- @top_level_decls = {}
15
+ }, source_patterns: ["**/*.rb"], ignore_patterns: Array.new, signature_patterns: ["sig"])
16
+ @top_level_decls = Set.new
17
+ @rbs_gem_dir = Utils.gem_path_for("rbs")
18
+ env_loader = RBS::EnvironmentLoader.new
19
+ @env = RBS::Environment.from_loader(env_loader)
20
+ project = Steep::Project.new(steepfile_path: Pathname.new(Config.src_dir)
21
+ .realpath)
22
+ project.targets
23
+ .<<(@steep_target)
24
+ loader = Steep::Project::FileLoader.new(project: project)
16
25
  end
17
- def run(rb_str)
18
- unless check_types(rb_str)
19
- raise(Errors::TypeError, @steep_target.errors
20
- .map { |e|
26
+ def run(filepath, rb_str)
27
+ begin
28
+ valid_types = check_types(filepath, rb_str)
29
+ rescue ParseError => e
30
+ throw(:"error", "")
31
+ rescue => e
32
+ throw(:"error", "Type checking Error: #{e.message} (#{e.class})")
33
+ end
34
+ unless valid_types
35
+ errors = @steep_target.errors
36
+ .map() { |e|
21
37
  case e
22
- when Steep::Errors::NoMethod
38
+ when Steep::Diagnostic::Ruby::NoMethod
23
39
  "Unknown method :#{e.method}, location: #{e.type
24
40
  .location
25
41
  .inspect}"
26
- when Steep::Errors::MethodBodyTypeMismatch
42
+ when Steep::Diagnostic::Ruby::MethodBodyTypeMismatch
27
43
  "Invalid method body type - expected: #{e.expected}, actual: #{e.actual}"
28
- when Steep::Errors::IncompatibleArguments
29
- "Invalid argmuents - method type: #{e.method_type}, receiver type: #{e.receiver_type}"
30
- when Steep::Errors::ReturnTypeMismatch
44
+ when Steep::Diagnostic::Ruby::IncompatibleArguments
45
+ "Invalid argmuents - method type: #{e.method_types
46
+ .first}\nmethod name: #{e.method_name}"
47
+ when Steep::Diagnostic::Ruby::ReturnTypeMismatch
31
48
  "Invalid return type - expected: #{e.expected}, actual: #{e.actual}"
32
- when Steep::Errors::IncompatibleAssignment
49
+ when Steep::Diagnostic::Ruby::IncompatibleAssignment
33
50
  "Invalid assignment - cannot assign #{e.rhs_type} to type #{e.lhs_type}"
51
+ when Steep::Diagnostic::Ruby::UnexpectedBlockGiven
52
+ "Unexpected block given"
53
+ else
54
+ "#{e.header_line}\n#{e.inspect}"
34
55
  end
35
56
  }
36
- .join("\n"))
57
+ .join("\n")
58
+ throw(:"error", errors)
37
59
  end
38
60
  true
39
61
  end
40
- def check_types(rb_str)
41
- env_loader = RBS::EnvironmentLoader.new
42
- env = RBS::Environment.from_loader(env_loader)
43
- project = Steep::Project.new(steepfile_path: Pathname.new(Config.src_dir)
44
- .realpath)
45
- project.targets
46
- .<<(@steep_target)
47
- loader = Steep::Project::FileLoader.new(project: project)
48
- loader.load_signatures
49
- @steep_target.add_source("gloss.rb", rb_str)
50
- @top_level_decls.each { |_, decl|
51
- env.<<(decl)
62
+ def ready_for_checking!()
63
+ @top_level_decls.each() { |decl|
64
+ @env.<<(decl)
52
65
  }
53
- env = env.resolve_type_names
54
- @steep_target.instance_variable_set("@environment", env)
66
+ @env = @env.resolve_type_names
67
+ @steep_target.instance_variable_set("@environment", @env)
68
+ end
69
+ def check_types(filepath, rb_str)
70
+ @steep_target.add_source(filepath, rb_str)
71
+ ready_for_checking!
55
72
  @steep_target.type_check
73
+ (if @steep_target.status
74
+ .is_a?(Steep::Project::Target::SignatureErrorStatus)
75
+ throw(:"error", @steep_target.status
76
+ .errors
77
+ .map() { |e|
78
+ msg = case e
79
+ when Steep::Diagnostic::Signature::UnknownTypeName
80
+ "Unknown type name: #{e.name
81
+ .name} (#{e.location
82
+ .source
83
+ .[](/^.*$/)})"
84
+ when Steep::Diagnostic::Signature::InvalidTypeApplication
85
+ "Invalid type application: #{e.header_line}"
86
+ when Steep::Diagnostic::Signature::DuplicatedMethodDefinition
87
+ "Duplicated method: #{e.header_line}"
88
+ else
89
+ e.header_line
90
+ end
91
+ " SignatureSyntaxError:\n Location: #{e.location}\n Message: \"#{msg}\"" }
92
+ .join("\n"))
93
+ end)
94
+ @steep_target.source_files
95
+ .each() { |path, f|
96
+ (if f.status
97
+ .is_a?(Steep::Project::SourceFile::ParseErrorStatus)
98
+ e = f.status
99
+ .error
100
+ throw(:"error", "#{e.class}: #{e.message}")
101
+ end)
102
+ }
56
103
  @steep_target.status
57
104
  .is_a?(Steep::Project::Target::TypeCheckStatus) && @steep_target.no_error? && @steep_target.errors
58
105
  .empty?
59
106
  end
107
+ def load_sig_path(path)
108
+ Gloss.logger
109
+ .debug("Loading signature file for #{path}")
110
+ @steep_target.add_signature(path, File.open(path)
111
+ .read)
112
+ end
60
113
  end
61
114
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##### This file was generated by Gloss; any changes made here will be overwritten.
4
+ ##### See src/ to make changes
5
+
6
+ require "rubygems"
7
+ module Gloss
8
+ module Utils
9
+ module_function
10
+ def absolute_path(path)
11
+ pn = Pathname.new(path)
12
+ (if pn.absolute?
13
+ pn.to_s
14
+ else
15
+ ap = File.absolute_path(path)
16
+ (if File.exist?(ap)
17
+ ap
18
+ else
19
+ throw(:"error", "File path #{path} does not exist (also looked for #{ap})")
20
+ end)
21
+ end)
22
+ end
23
+ def gem_path_for(gem_name)
24
+ begin
25
+ Gem.ui
26
+ .instance_variable_set(:"@outs", StringIO.new)
27
+ Gem::GemRunner.new
28
+ .run(["which", gem_name])
29
+ Gem.ui
30
+ .outs
31
+ .string
32
+ rescue SystemExit => e
33
+ nil
34
+ end
35
+ end
36
+ def with_file_header(str)
37
+ "#{Visitor::FILE_HEADER}\n\n#{str}"
38
+ end
39
+ def src_path_to_output_path(src_path)
40
+ src_path.sub("#{Config.src_dir}/", "")
41
+ .sub(/\.gl$/, ".rb")
42
+ end
43
+ end
44
+ end
data/lib/gloss/version.rb CHANGED
@@ -4,6 +4,5 @@
4
4
  ##### See src/ to make changes
5
5
 
6
6
  module Gloss
7
- VERSION = "0.0.3"
7
+ VERSION = "0.1.1"
8
8
  end
9
-
@@ -0,0 +1,667 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##### This file was generated by Gloss; any changes made here will be overwritten.
4
+ ##### See src/ to make changes
5
+
6
+ module Gloss
7
+ class Visitor
8
+ FILE_HEADER = " #{(if Config.frozen_string_literals
9
+ "# frozen_string_literal: true\n"
10
+ end)}\n ##### This file was generated by Gloss; any changes made here will be overwritten.\n ##### See #{Config.src_dir}/ to make changes"
11
+ attr_reader(:"tree")
12
+ def initialize(tree_hash, type_checker = nil, on_new_file_referenced = nil)
13
+ @on_new_file_referenced = on_new_file_referenced
14
+ @indent_level = 0
15
+ @inside_macro = false
16
+ @eval_vars = false
17
+ @current_scope = nil
18
+ @tree = tree_hash
19
+ @type_checker = type_checker
20
+ @after_module_function = false
21
+ end
22
+ def run()
23
+ rb_output = visit_node(@tree)
24
+ Utils.with_file_header(rb_output)
25
+ end
26
+ def visit_node(node, scope = Scope.new)
27
+ src = Source.new(@indent_level)
28
+ case node.[](:"type")
29
+ when "ClassNode"
30
+ class_name = visit_node(node.[](:"name"))
31
+ current_namespace = (if @current_scope
32
+ @current_scope.name
33
+ .to_namespace
34
+ else
35
+ RBS::Namespace.root
36
+ end)
37
+ superclass_type = nil
38
+ superclass_output = ""
39
+ (if node.[](:"superclass")
40
+ @eval_vars = true
41
+ superclass_output = visit_node(node.[](:"superclass"))
42
+ @eval_vars = false
43
+ args = Array.new
44
+ (if node.dig(:"superclass", :"type")
45
+ .==("Generic")
46
+ superclass_output = superclass_output.[](/^[^\[]+/) || superclass_output
47
+ args = node.dig(:"superclass", :"args")
48
+ .map() { |n|
49
+ RBS::Parser.parse_type(visit_node(n))
50
+ }
51
+ end)
52
+ class_name_index = superclass_output.index(/[^(?:::)]+\z/) || 0
53
+ namespace = superclass_output.[](0, class_name_index)
54
+ superclass_name = superclass_output.[](/[^(?:::)]+\z/) || superclass_output
55
+ superclass_type = RBS::AST::Declarations::Class::Super.new(name: RBS::TypeName.new(namespace: method(:"Namespace")
56
+ .call(namespace), name: superclass_name.to_sym), args: args, location: build_location(node))
57
+ end)
58
+ src.write_ln("class #{class_name}#{unless superclass_output.blank?
59
+ " < #{superclass_output}"
60
+ end}")
61
+ class_type = RBS::AST::Declarations::Class.new(name: RBS::TypeName.new(namespace: current_namespace, name: class_name.to_sym), type_params: RBS::AST::Declarations::ModuleTypeParams.new, super_class: superclass_type, members: Array.new, annotations: Array.new, location: build_location(node), comment: node.[](:"comment"))
62
+ old_parent_scope = @current_scope
63
+ @current_scope = class_type
64
+ indented(src) { ||
65
+ (if node.[](:"body")
66
+ src.write_ln(visit_node(node.[](:"body")))
67
+ end)
68
+ }
69
+ src.write_ln("end")
70
+ @current_scope = old_parent_scope
71
+ (if @current_scope
72
+ @current_scope.members
73
+ .<<(class_type)
74
+ end)
75
+ (if @type_checker && !@current_scope
76
+ @type_checker.top_level_decls
77
+ .add(class_type)
78
+ end)
79
+ when "ModuleNode"
80
+ existing_module_function_state = @after_module_function.dup
81
+ @after_module_function = false
82
+ module_name = visit_node(node.[](:"name"))
83
+ src.write_ln("module #{module_name}")
84
+ current_namespace = (if @current_scope
85
+ @current_scope.name
86
+ .to_namespace
87
+ else
88
+ RBS::Namespace.root
89
+ end)
90
+ module_type = RBS::AST::Declarations::Module.new(name: RBS::TypeName.new(namespace: current_namespace, name: module_name.to_sym), type_params: RBS::AST::Declarations::ModuleTypeParams.new, self_types: Array.new, members: Array.new, annotations: Array.new, location: build_location(node), comment: node.[](:"comment"))
91
+ old_parent_scope = @current_scope
92
+ @current_scope = module_type
93
+ indented(src) { ||
94
+ (if node.[](:"body")
95
+ src.write_ln(visit_node(node.[](:"body")))
96
+ end)
97
+ }
98
+ @current_scope = old_parent_scope
99
+ (if @current_scope
100
+ @current_scope.members
101
+ .<<(module_type)
102
+ end)
103
+ (if @type_checker && !@current_scope
104
+ @type_checker.top_level_decls
105
+ .add(module_type)
106
+ end)
107
+ src.write_ln("end")
108
+ @after_module_function = existing_module_function_state
109
+ when "DefNode"
110
+ args = render_args(node)
111
+ receiver = (if node.[](:"receiver")
112
+ visit_node(node.[](:"receiver"))
113
+ else
114
+ nil
115
+ end)
116
+ src.write_ln("def #{(if receiver
117
+ "#{receiver}."
118
+ end)}#{node.[](:"name")}#{args.[](:"representation")}")
119
+ return_type = (if node.[](:"return_type")
120
+ RBS::Types::ClassInstance.new(name: RBS::TypeName.new(name: eval(visit_node(node.[](:"return_type")))
121
+ .to_s
122
+ .to_sym, namespace: RBS::Namespace.root), args: EMPTY_ARRAY, location: build_location(node))
123
+ else
124
+ RBS::Types::Bases::Any.new(location: build_location(node))
125
+ end)
126
+ method_types = [RBS::MethodType.new(type_params: EMPTY_ARRAY, type: RBS::Types::Function.new(required_positionals: args.dig(:"types", :"required_positionals"), optional_positionals: args.dig(:"types", :"optional_positionals"), rest_positionals: args.dig(:"types", :"rest_positionals"), trailing_positionals: args.dig(:"types", :"trailing_positionals"), required_keywords: args.dig(:"types", :"required_keywords"), optional_keywords: args.dig(:"types", :"optional_keywords"), rest_keywords: args.dig(:"types", :"rest_keywords"), return_type: return_type), block: (if node.[](:"yield_arg_count")
127
+ RBS::Types::Block.new(type: RBS::Types::Function.new(required_positionals: Array.new, optional_positionals: Array.new, rest_positionals: nil, trailing_positionals: Array.new, required_keywords: Hash.new, optional_keywords: Hash.new, rest_keywords: nil, return_type: RBS::Types::Bases::Any.new(location: build_location(node))), required: !!node.[](:"block_arg") || node.[](:"yield_arg_count"))
128
+ else
129
+ nil
130
+ end), location: build_location(node))]
131
+ method_definition = RBS::AST::Members::MethodDefinition.new(name: node.[](:"name")
132
+ .to_sym, kind: (if @after_module_function
133
+ :"singleton_instance"
134
+ else
135
+ (if receiver
136
+ :"singleton"
137
+ else
138
+ :"instance"
139
+ end)
140
+ end), types: method_types, annotations: EMPTY_ARRAY, location: build_location(node), comment: node.[](:"comment"), overload: false)
141
+ (if @current_scope
142
+ @current_scope.members
143
+ .<<(method_definition)
144
+ else
145
+ (if @type_checker
146
+ @type_checker.type_env
147
+ .<<(method_definition)
148
+ end)
149
+ end)
150
+ indented(src) { ||
151
+ (if node.[](:"body")
152
+ src.write_ln(visit_node(node.[](:"body")))
153
+ end)
154
+ }
155
+ src.write_ln("end")
156
+ when "VisibilityModifier"
157
+ src.write_ln("#{node.[](:"visibility")} #{visit_node(node.[](:"exp"))}")
158
+ when "CollectionNode"
159
+ node.[](:"children")
160
+ .each() { |a|
161
+ src.write(visit_node(a, scope))
162
+ }
163
+ when "Call"
164
+ obj = (if node.[](:"object")
165
+ "#{visit_node(node.[](:"object"), scope)}."
166
+ else
167
+ ""
168
+ end)
169
+ arg_arr = node.fetch(:"args") { ||
170
+ EMPTY_ARRAY }
171
+ (if node.[](:"named_args")
172
+ arg_arr += node.[](:"named_args")
173
+ end)
174
+ args = (if !arg_arr.empty? || node.[](:"block_arg")
175
+ "#{arg_arr.map() { |a|
176
+ visit_node(a, scope)
177
+ .strip
178
+ }
179
+ .reject(&:"blank?")
180
+ .join(", ")}#{(if node.[](:"block_arg")
181
+ "&#{visit_node(node.[](:"block_arg"))
182
+ .strip}"
183
+ end)}"
184
+ else
185
+ nil
186
+ end)
187
+ block = (if node.[](:"block")
188
+ " #{visit_node(node.[](:"block"))}"
189
+ else
190
+ nil
191
+ end)
192
+ has_parens = !!node.[](:"has_parentheses") || args || block
193
+ opening_delimiter = (if has_parens
194
+ "("
195
+ else
196
+ nil
197
+ end)
198
+ name = node.[](:"name")
199
+ call = "#{obj}#{name}#{opening_delimiter}#{args}#{(if has_parens
200
+ ")"
201
+ end)}#{block}"
202
+ case name
203
+ when "require_relative"
204
+ (if @on_new_file_referenced
205
+ @on_new_file_referenced.call(name, true)
206
+ end)
207
+ when "module_function"
208
+ @after_module_function = true
209
+ end
210
+ src.write_ln(call)
211
+ when "Block"
212
+ args = render_args(node)
213
+ src.write("{ #{args.[](:"representation")
214
+ .gsub(/(\A\(|\)\z)/, "|")}\n")
215
+ indented(src) { ||
216
+ src.write(visit_node(node.[](:"body")))
217
+ }
218
+ src.write_ln("}")
219
+ when "RangeLiteral"
220
+ dots = (if node.[](:"exclusive")
221
+ "..."
222
+ else
223
+ ".."
224
+ end)
225
+ src.write("(", "(", visit_node(node.[](:"from")), ")", dots, "(", visit_node(node.[](:"to")), ")", ")")
226
+ when "LiteralNode"
227
+ src.write(node.[](:"value"))
228
+ when "ArrayLiteral"
229
+ src.write("[", node.[](:"elements")
230
+ .map() { |e|
231
+ visit_node(e)
232
+ .strip
233
+ }
234
+ .join(", "), "]")
235
+ (if node.[](:"frozen")
236
+ src.write(".freeze")
237
+ end)
238
+ when "StringInterpolation"
239
+ contents = node.[](:"contents")
240
+ .inject(String.new) { |str, c|
241
+ str.<<(case c.[](:"type")
242
+ when "LiteralNode"
243
+ c.[](:"value")
244
+ .[](((1)...(-1)))
245
+ else
246
+ ["\#{", visit_node(c)
247
+ .strip, "}"].join
248
+ end)
249
+ }
250
+ src.write("\"", contents, "\"")
251
+ when "Path"
252
+ src.write(node.[](:"value"))
253
+ when "Require"
254
+ path = node.[](:"value")
255
+ src.write_ln("require \"#{path}\"")
256
+ (if @on_new_file_referenced
257
+ @on_new_file_referenced.call(path, false)
258
+ end)
259
+ when "Assign", "OpAssign"
260
+ src.write_ln("#{visit_node(node.[](:"target"))} #{node.[](:"op")}= #{visit_node(node.[](:"value"))
261
+ .strip}")
262
+ when "MultiAssign"
263
+ src.write_ln("#{node.[](:"targets")
264
+ .map() { |t|
265
+ visit_node(t)
266
+ .strip
267
+ }
268
+ .join(", ")} = #{node.[](:"values")
269
+ .map() { |v|
270
+ visit_node(v)
271
+ .strip
272
+ }
273
+ .join(", ")}")
274
+ when "Var"
275
+ (if @eval_vars
276
+ src.write(scope.[](node.[](:"name")))
277
+ else
278
+ src.write(node.[](:"name"))
279
+ end)
280
+ when "InstanceVar"
281
+ src.write(node.[](:"name"))
282
+ when "GlobalVar"
283
+ src.write(node.[](:"name"))
284
+ when "Arg"
285
+ val = node.[](:"external_name")
286
+ (if node.[](:"keyword_arg")
287
+ val += ":"
288
+ (if node.[](:"value")
289
+ val += " #{visit_node(node.[](:"value"))}"
290
+ end)
291
+ else
292
+ (if node.[](:"value")
293
+ val += " = #{visit_node(node.[](:"value"))}"
294
+ end)
295
+ end)
296
+ src.write(val)
297
+ when "UnaryExpr"
298
+ src.write("#{node.[](:"op")}#{visit_node(node.[](:"value"))
299
+ .strip}")
300
+ when "BinaryOp"
301
+ src.write(visit_node(node.[](:"left"))
302
+ .strip, " #{node.[](:"op")} ", visit_node(node.[](:"right"))
303
+ .strip)
304
+ when "HashLiteral"
305
+ contents = node.[](:"elements")
306
+ .map() { |k, v|
307
+ key = case k
308
+ when String
309
+ k.to_sym
310
+ .inspect
311
+ else
312
+ visit_node(k)
313
+ end
314
+ value = visit_node(v)
315
+ "#{key} => #{value}" }
316
+ src.write("{#{contents.join(",\n")}}")
317
+ (if node.[](:"frozen")
318
+ src.write(".freeze")
319
+ end)
320
+ when "Enum"
321
+ src.write_ln("module #{node.[](:"name")}")
322
+ node.[](:"members")
323
+ .each_with_index() { |m, i|
324
+ indented(src) { ||
325
+ src.write_ln(visit_node(m)
326
+ .+((if !m.[](:"value")
327
+ " = #{i}"
328
+ else
329
+ ""
330
+ end)))
331
+ }
332
+ }
333
+ src.write_ln("end")
334
+ when "If"
335
+ src.write_ln("(if #{visit_node(node.[](:"condition"))
336
+ .strip}")
337
+ indented(src) { ||
338
+ src.write_ln(visit_node(node.[](:"then")))
339
+ }
340
+ (if node.[](:"else")
341
+ src.write_ln("else")
342
+ indented(src) { ||
343
+ src.write_ln(visit_node(node.[](:"else")))
344
+ }
345
+ end)
346
+ src.write_ln("end)")
347
+ when "Unless"
348
+ src.write_ln("unless #{visit_node(node.[](:"condition"))}")
349
+ indented(src) { ||
350
+ src.write_ln(visit_node(node.[](:"then")))
351
+ }
352
+ (if node.[](:"else")
353
+ src.write_ln("else")
354
+ indented(src) { ||
355
+ src.write_ln(visit_node(node.[](:"else")))
356
+ }
357
+ end)
358
+ src.write_ln("end")
359
+ when "Case"
360
+ src.write("case")
361
+ (if node.[](:"condition")
362
+ src.write(" #{visit_node(node.[](:"condition"))
363
+ .strip}\n")
364
+ end)
365
+ indented(src) { ||
366
+ node.[](:"whens")
367
+ .each() { |w|
368
+ src.write_ln(visit_node(w))
369
+ }
370
+ (if node.[](:"else")
371
+ src.write_ln("else")
372
+ indented(src) { ||
373
+ src.write_ln(visit_node(node.[](:"else")))
374
+ }
375
+ end)
376
+ }
377
+ src.write_ln("end")
378
+ when "When"
379
+ src.write_ln("when #{node.[](:"conditions")
380
+ .map() { |n|
381
+ visit_node(n)
382
+ }
383
+ .join(", ")}")
384
+ indented(src) { ||
385
+ src.write_ln((if node.[](:"body")
386
+ visit_node(node.[](:"body"))
387
+ else
388
+ "# no op"
389
+ end))
390
+ }
391
+ when "MacroFor"
392
+ vars, expr, body = node.[](:"vars"), node.[](:"expr"), node.[](:"body")
393
+ var_names = vars.map() { |v|
394
+ visit_node(v)
395
+ }
396
+ @inside_macro = true
397
+ indent_level = @indent_level
398
+ unless indent_level.zero?
399
+ @indent_level -= 1
400
+ end
401
+ # @type var expanded: Array[String]
402
+ expanded = eval(visit_node(expr))
403
+ .map() { |*a|
404
+ locals = [var_names.join("\", \"")].zip(a)
405
+ .to_h
406
+ (if @inside_macro
407
+ locals.merge!(scope)
408
+ end)
409
+ visit_node(body, locals)
410
+ }
411
+ .flatten
412
+ unless indent_level.zero?
413
+ @indent_level += 1
414
+ end
415
+ expanded.each() { |e|
416
+ src.write(e)
417
+ }
418
+ @inside_macro = false
419
+ when "MacroLiteral"
420
+ src.write(node.[](:"value"))
421
+ when "MacroExpression"
422
+ (if node.[](:"output")
423
+ expr = visit_node(node.[](:"expr"), scope)
424
+ val = scope.[](expr)
425
+ src.write(val)
426
+ end)
427
+ when "MacroIf"
428
+ (if evaluate_macro_condition(node.[](:"condition"), scope)
429
+ (if node.[](:"then")
430
+ src.write_ln(visit_node(node.[](:"then"), scope))
431
+ end)
432
+ else
433
+ (if node.[](:"else")
434
+ src.write_ln(visit_node(node.[](:"else"), scope))
435
+ end)
436
+ end)
437
+ when "Return"
438
+ val = (if node.[](:"value")
439
+ " #{visit_node(node.[](:"value"))
440
+ .strip}"
441
+ else
442
+ nil
443
+ end)
444
+ src.write("return#{val}")
445
+ when "TypeDeclaration"
446
+ src.write_ln("# @type var #{visit_node(node.[](:"var"))}: #{visit_node(node.[](:"declared_type"))}")
447
+ value = (if node.[](:"value")
448
+ " = #{visit_node(node.[](:"value"))}"
449
+ else
450
+ nil
451
+ end)
452
+ src.write_ln("#{visit_node(node.[](:"var"))}#{value}")
453
+ when "ExceptionHandler"
454
+ src.write_ln("begin")
455
+ indented(src) { ||
456
+ src.write_ln(visit_node(node.[](:"body")))
457
+ }
458
+ (if node.[](:"rescues")
459
+ node.[](:"rescues")
460
+ .each() { |r|
461
+ src.write_ln("rescue #{(if r.[](:"types")
462
+ r.[](:"types")
463
+ .map() { |n|
464
+ visit_node(n)
465
+ }
466
+ .join(", ")
467
+ end)}#{(if r.[](:"name")
468
+ " => #{r.[](:"name")}"
469
+ end)}")
470
+ (if r.[](:"body")
471
+ indented(src) { ||
472
+ src.write_ln(visit_node(r.[](:"body")))
473
+ }
474
+ end)
475
+ }
476
+ end)
477
+ (if node.[](:"else")
478
+ src.write_ln("else")
479
+ indented(src) { ||
480
+ src.write_ln(visit_node(node.[](:"else")))
481
+ }
482
+ end)
483
+ (if node.[](:"ensure")
484
+ src.write_ln("ensure")
485
+ indented(src) { ||
486
+ src.write_ln(visit_node(node.[](:"ensure")))
487
+ }
488
+ end)
489
+ src.write_ln("end")
490
+ when "Generic"
491
+ src.write("#{visit_node(node.[](:"name"))}[#{node.[](:"args")
492
+ .map() { |a|
493
+ visit_node(a)
494
+ }
495
+ .join(", ")}]")
496
+ when "Proc"
497
+ fn = node.[](:"function")
498
+ src.write("->#{render_args(fn)} { #{visit_node(fn.[](:"body"))} }")
499
+ when "Include"
500
+ current_namespace = (if @current_scope
501
+ @current_scope.name
502
+ .to_namespace
503
+ else
504
+ RBS::Namespace.root
505
+ end)
506
+ name = visit_node(node.[](:"name"))
507
+ src.write_ln("include #{name}")
508
+ type = RBS::AST::Members::Include.new(name: method(:"TypeName")
509
+ .call(name), args: Array.new, annotations: Array.new, location: build_location(node), comment: node.[](:"comment"))
510
+ (if @current_scope
511
+ @current_scope.members
512
+ .<<(type)
513
+ else
514
+ @type_checker.type_env
515
+ .<<(type)
516
+ end)
517
+ when "Extend"
518
+ current_namespace = (if @current_scope
519
+ @current_scope.name
520
+ .to_namespace
521
+ else
522
+ RBS::Namespace.root
523
+ end)
524
+ name = visit_node(node.[](:"name"))
525
+ src.write_ln("extend #{name}")
526
+ type = RBS::AST::Members::Extend.new(name: method(:"TypeName")
527
+ .call(name), args: Array.new, annotations: Array.new, location: build_location(node), comment: node.[](:"comment"))
528
+ (if @current_scope
529
+ @current_scope.members
530
+ .<<(type)
531
+ else
532
+ @type_checker.type_env
533
+ .<<(type)
534
+ end)
535
+ when "RegexLiteral"
536
+ contents = visit_node(node.[](:"value"))
537
+ src.write(Regexp.new(contents.undump)
538
+ .inspect)
539
+ when "Union"
540
+ types = node.[](:"types")
541
+ output = (if types.length
542
+ .==(2) && types.[](1)
543
+ .[](:"type")
544
+ .==("Path") && types.[](1)
545
+ .[]("value")
546
+ .==(nil)
547
+ "#{visit_node(types.[](0))}?"
548
+ else
549
+ types.map() { |t|
550
+ visit_node(t)
551
+ }
552
+ .join(" | ")
553
+ end)
554
+ src.write(output)
555
+ when "Next"
556
+ (if node.[](:"value")
557
+ val = " #{node.[](:"value")}"
558
+ end)
559
+ src.write("next#{val}")
560
+ when "EmptyNode"
561
+ # no op
562
+ else
563
+ raise("Not implemented: #{node.[](:"type")}")
564
+ end
565
+ src
566
+ end
567
+ private def evaluate_macro_condition(condition_node, scope)
568
+ @eval_vars = true
569
+ eval(visit_node(condition_node, scope))
570
+ @eval_vars = false
571
+ end
572
+ private def indented(src)
573
+ increment_indent(src)
574
+ yield
575
+ decrement_indent(src)
576
+ end
577
+ private def increment_indent(src)
578
+ @indent_level += 1
579
+ src.increment_indent
580
+ end
581
+ private def decrement_indent(src)
582
+ @indent_level -= 1
583
+ src.decrement_indent
584
+ end
585
+ private def render_args(node)
586
+ # @type var rp: Array[Hash[Symbol, Any]]
587
+ rp = node.fetch(:"positional_args") { ||
588
+ EMPTY_ARRAY }
589
+ .filter() { |a|
590
+ !a.[](:"value") }
591
+ # @type var op: Array[Hash[Symbol, Any]]
592
+ op = node.fetch(:"positional_args") { ||
593
+ EMPTY_ARRAY }
594
+ .filter() { |a|
595
+ a.[](:"value")
596
+ }
597
+ # @type var rkw: Hash[Symbol, Any]
598
+ rkw = node.fetch(:"req_kw_args") { ||
599
+ EMPTY_HASH }
600
+ # @type var okw: Hash[Symbol, Any]
601
+ okw = node.fetch(:"opt_kw_args") { ||
602
+ EMPTY_HASH }
603
+ # @type var rest_p: String?
604
+ rest_p = (if node.[](:"rest_p_args")
605
+ visit_node(node.[](:"rest_p_args"))
606
+ else
607
+ nil
608
+ end)
609
+ # @type var rest_kw: Hash[Symbol, Any]?
610
+ rest_kw = node.[](:"rest_kw_args")
611
+ (if [rp, op, rkw, okw, rest_p, rest_kw].all?() { |a|
612
+ a && a.empty? }
613
+ return nil
614
+ end)
615
+ contents = [rp.map() { |a|
616
+ visit_node(a)
617
+ }, op.map() { |a|
618
+ "#{a.[](:"name")} = #{visit_node(a.[](:"value"))
619
+ .strip}" }, rkw.map() { |name, _|
620
+ "#{name}:" }, okw.map() { |name, value|
621
+ "#{name}: #{value}" }, (if rest_p
622
+ "*#{rest_p}"
623
+ else
624
+ ""
625
+ end), (if rest_kw
626
+ "**#{visit_node(rest_kw)}"
627
+ else
628
+ ""
629
+ end)].reject(&:"empty?")
630
+ .flatten
631
+ .join(", ")
632
+ representation = "(#{contents})"
633
+ rp_args = rp.map() { |a|
634
+ RBS::Types::Function::Param.new(name: visit_node(a)
635
+ .to_sym, type: RBS::Types::Bases::Any.new(location: build_location(a)))
636
+ }
637
+ op_args = op.map() { |a|
638
+ RBS::Types::Function::Param.new(name: visit_node(a)
639
+ .to_sym, type: RBS::Types::Bases::Any.new(location: build_location(a)))
640
+ }
641
+ rpa = (if rest_p
642
+ RBS::Types::Function::Param.new(name: rest_p.to_sym, type: RBS::Types::Bases::Any.new(location: build_location(node)))
643
+ else
644
+ nil
645
+ end)
646
+ {:representation => representation,
647
+ :types => {:required_positionals => rp_args,
648
+ :optional_positionals => op_args,
649
+ :rest_positionals => rpa,
650
+ :trailing_positionals => EMPTY_ARRAY,
651
+ :required_keywords => node.[](:"req_kw_args") || EMPTY_HASH,
652
+ :optional_keywords => node.[](:"opt_kw_args") || EMPTY_HASH,
653
+ :rest_keywords => (if node.[](:"rest_kw_args")
654
+ RBS::Types::Function::Param.new(name: visit_node(node.[](:"rest_kw_args"))
655
+ .to_sym, type: RBS::Types::Bases::Any.new(location: build_location(node)))
656
+ else
657
+ nil
658
+ end)
659
+ }.freeze}.freeze
660
+ end
661
+ def build_location(node)
662
+ unless node.[](:"location")
663
+ return nil
664
+ end
665
+ end
666
+ end
667
+ end