gloss 0.0.3 → 0.0.4

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.
@@ -1,38 +1,52 @@
1
- # frozen_string_literal: true
1
+ # frozen_string_literal: true
2
2
 
3
- require "optparse"
3
+ ##### This file was generated by Gloss; any changes made here will be overwritten.
4
+ ##### See src/ to make changes
4
5
 
6
+ require "optparse"
5
7
  module Gloss
6
8
  class CLI
7
9
  def initialize(argv)
8
10
  @argv = argv
9
11
  end
10
-
11
- def run
12
- command, *files = @argv
13
- case command
14
- when "watch"
15
- Watcher.new.watch
16
- when "build"
17
- (files.empty? ? Dir.glob("#{Config.src_dir}/**/*.gl") : files).each do |fp|
18
- puts "=====> Building #{fp}"
19
- content = File.read(fp)
20
- tree_hash = Parser.new(content).run
21
- type_checker = TypeChecker.new
22
- rb_output = Builder.new(tree_hash, type_checker).run
23
- type_checker.run(rb_output)
24
-
25
- puts "=====> Writing #{fp}"
26
- Writer.new(rb_output, fp).run
27
- end
28
- when "init"
29
- force = false
30
- OptionParser.new do |opt|
31
- opt.on("--force", "-f") { force = true }
32
- end.parse(@argv)
33
- Initializer.new(force).run
34
- else
35
- abort "Gloss doesn't know how to #{command}"
12
+ def run()
13
+ command = @argv.first
14
+ files = @argv.[]((1..-1))
15
+ case command
16
+ when "watch"
17
+ Watcher.new
18
+ .watch
19
+ when "build"
20
+ (if files.empty?
21
+ Dir.glob("#{Config.src_dir}/**/*.gl")
22
+ else
23
+ files
24
+ end)
25
+ .each() { |fp|
26
+ puts("=====> Building #{fp}")
27
+ content = File.read(fp)
28
+ tree_hash = Parser.new(content)
29
+ .run
30
+ type_checker = TypeChecker.new
31
+ rb_output = Builder.new(tree_hash, type_checker)
32
+ .run
33
+ type_checker.run(rb_output)
34
+ puts("=====> Writing #{fp}")
35
+ Writer.new(rb_output, fp)
36
+ .run
37
+ }
38
+ when "init"
39
+ force = false
40
+ OptionParser.new() { |opt|
41
+ opt.on("--force", "-f") { ||
42
+ force = true
43
+ }
44
+ }
45
+ .parse(@argv)
46
+ Initializer.new(force)
47
+ .run
48
+ else
49
+ abort("Gloss doesn't know how to #{command}")
36
50
  end
37
51
  end
38
52
  end
@@ -1,17 +1,16 @@
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 "ostruct"
6
+ require "ostruct"
7
7
  require "yaml"
8
8
  module Gloss
9
9
  user_config = YAML.safe_load(File.read(".gloss.yml"))
10
10
  Config = OpenStruct.new(default_config: {:frozen_string_literals => true,
11
11
  :src_dir => "src"}.freeze)
12
12
  Config.default_config
13
- .each { |k, v|
13
+ .each() { |k, v|
14
14
  Config.send(:"#{k}=", user_config.[](k.to_s) || v)
15
15
  }
16
16
  end
17
-
@@ -1,9 +1,9 @@
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
- module Gloss
6
+ module Gloss
7
7
  module Errors
8
8
  class BaseGlossError < StandardError
9
9
  end
@@ -15,4 +15,3 @@ module Gloss
15
15
  end
16
16
  end
17
17
  end
18
-
@@ -1,9 +1,9 @@
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 "yaml"
6
+ require "yaml"
7
7
  module Gloss
8
8
  class Initializer
9
9
  def initialize(force)
@@ -15,11 +15,10 @@ module Gloss
15
15
  end)
16
16
  File.open(".gloss.yml", "wb") { |file|
17
17
  file.puts(Config.default_config
18
- .transform_keys(&:to_s)
18
+ .transform_keys(&:"to_s")
19
19
  .to_yaml)
20
20
  }
21
21
  puts("Created .gloss.yml with default preferences")
22
22
  end
23
23
  end
24
24
  end
25
-
@@ -1,9 +1,9 @@
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
- module Gloss
6
+ module Gloss
7
7
  class Parser
8
8
  def initialize(str)
9
9
  @str = str
@@ -18,4 +18,3 @@ module Gloss
18
18
  end
19
19
  end
20
20
  end
21
-
@@ -1,9 +1,14 @@
1
- # frozen_string_literal: true
1
+ # frozen_string_literal: true
2
2
 
3
- module Gloss
3
+ ##### This file was generated by Gloss; any changes made here will be overwritten.
4
+ ##### See src/ to make changes
5
+
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
@@ -1,31 +1,34 @@
1
- # frozen_string_literal: true
1
+ # frozen_string_literal: true
2
2
 
3
- module Gloss
3
+ ##### This file was generated by Gloss; any changes made here will be overwritten.
4
+ ##### See src/ to make changes
5
+
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,15 +1,15 @@
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
- module Gloss
6
+ module Gloss
7
7
  class TypeChecker
8
- Project = Struct.new(:targets)
9
- attr_reader(:steep_target, :top_level_decls)
8
+ Project = Struct.new(:"targets")
9
+ attr_reader(:"steep_target", :"top_level_decls")
10
10
  def initialize()
11
11
  @steep_target = Steep::Project::Target.new(name: "gloss", options: Steep::Project::Options.new
12
- .tap { |o|
12
+ .tap() { |o|
13
13
  o.allow_unknown_constant_assignment=(true)
14
14
  }, source_patterns: ["gloss.rb"], ignore_patterns: Array.new, signature_patterns: ["sig"])
15
15
  @top_level_decls = {}
@@ -17,7 +17,7 @@ module Gloss
17
17
  def run(rb_str)
18
18
  unless check_types(rb_str)
19
19
  raise(Errors::TypeError, @steep_target.errors
20
- .map { |e|
20
+ .map() { |e|
21
21
  case e
22
22
  when Steep::Errors::NoMethod
23
23
  "Unknown method :#{e.method}, location: #{e.type
@@ -26,11 +26,16 @@ case e
26
26
  when Steep::Errors::MethodBodyTypeMismatch
27
27
  "Invalid method body type - expected: #{e.expected}, actual: #{e.actual}"
28
28
  when Steep::Errors::IncompatibleArguments
29
- "Invalid argmuents - method type: #{e.method_type}, receiver type: #{e.receiver_type}"
29
+ " Invalid argmuents - method type: #{e.method_type}\n method name: #{e.method_type
30
+ .method_decls
31
+ .first
32
+ .method_name}"
30
33
  when Steep::Errors::ReturnTypeMismatch
31
34
  "Invalid return type - expected: #{e.expected}, actual: #{e.actual}"
32
35
  when Steep::Errors::IncompatibleAssignment
33
36
  "Invalid assignment - cannot assign #{e.rhs_type} to type #{e.lhs_type}"
37
+ else
38
+ e.inspect
34
39
  end
35
40
  }
36
41
  .join("\n"))
@@ -47,7 +52,7 @@ true
47
52
  loader = Steep::Project::FileLoader.new(project: project)
48
53
  loader.load_signatures
49
54
  @steep_target.add_source("gloss.rb", rb_str)
50
- @top_level_decls.each { |_, decl|
55
+ @top_level_decls.each() { |_, decl|
51
56
  env.<<(decl)
52
57
  }
53
58
  env = env.resolve_type_names
@@ -1,9 +1,8 @@
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
- module Gloss
7
- VERSION = "0.0.3"
6
+ module Gloss
7
+ VERSION = "0.0.4"
8
8
  end
9
-
@@ -1,9 +1,9 @@
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 "listen"
6
+ require "listen"
7
7
  module Gloss
8
8
  class Watcher
9
9
  def initialize()
@@ -13,13 +13,13 @@ module Gloss
13
13
  puts("=====> Now listening for changes in #{@paths.join(", ")}")
14
14
  listener = Listen.to(*@paths, latency: 2) { |modified, added, removed|
15
15
  modified.+(added)
16
- .each { |f|
16
+ .each() { |f|
17
17
  content = File.read(f)
18
18
  Writer.new(Builder.new(content)
19
19
  .run, f)
20
20
  .run
21
21
  }
22
- removed.each { |f|
22
+ removed.each() { |f|
23
23
  out_path = Utils.src_path_to_output_path(f)
24
24
  (if File.exist?(out_path)
25
25
  File.delete(out_path)
@@ -28,7 +28,7 @@ module Gloss
28
28
  }
29
29
  listener.start
30
30
  begin
31
- loop { ||
31
+ loop() { ||
32
32
  sleep(10)
33
33
  }
34
34
  rescue Interrupt
@@ -38,4 +38,3 @@ module Gloss
38
38
  end
39
39
  end
40
40
  end
41
-
@@ -1,9 +1,9 @@
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 "pathname"
6
+ require "pathname"
7
7
  require "fileutils"
8
8
  module Gloss
9
9
  module Utils
@@ -15,8 +15,7 @@ module Gloss
15
15
  end
16
16
  class Writer
17
17
  include Utils
18
- def initialize(content, src_path, output_path = Pathname.new(src_path_to_output_path(src_path))
19
- )
18
+ def initialize(content, src_path, output_path = Pathname.new(src_path_to_output_path(src_path)))
20
19
  @content = content
21
20
  @output_path = output_path
22
21
  end
@@ -31,4 +30,3 @@ module Gloss
31
30
  end
32
31
  end
33
32
  end
34
-
@@ -19,6 +19,7 @@ module Listen
19
19
  ?ignore: Regexp | Array[Regexp],
20
20
  ?ignore!: Regexp,
21
21
  ?only: Regexp?,
22
+ ?latency: (Integer | Float)?,
22
23
  ?polling_fallback_message: String?) {
23
24
  (Array[String] modified, Array[String] added, Array[String] removed) -> void
24
25
  } -> Listener
@@ -0,0 +1,520 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gloss
4
+ class Builder
5
+ attr_reader :tree
6
+
7
+ def initialize(tree_hash, type_checker = nil)
8
+ @indent_level = 0
9
+ @inside_macro = false
10
+ @eval_vars = false
11
+ @current_scope = nil
12
+ @tree = tree_hash
13
+ @type_checker = type_checker
14
+ end
15
+
16
+ def run
17
+ rb_output = visit_node(@tree)
18
+ <<~RUBY
19
+ #{"# frozen_string_literal: true\n" if Config.frozen_string_literals}
20
+ ##### This file was generated by Gloss; any changes made here will be overwritten.
21
+ ##### See #{Config.src_dir}/ to make changes
22
+
23
+ #{rb_output}
24
+ RUBY
25
+ end
26
+
27
+ # type node = Hash[Symbol, String | Array[String | node] | Hash[Symbol, node]] | true | false
28
+
29
+ def visit_node(node : Hash[Symbol, Any], scope = Scope.new) : String
30
+ src = Source.new(@indent_level)
31
+ case node[:type]
32
+ when "ClassNode"
33
+ class_name = visit_node(node[:name])
34
+ current_namespace = @current_scope ? @current_scope.name.to_namespace : RBS::Namespace.root
35
+ superclass_type = nil
36
+ superclass_output = nil
37
+ if node[:superclass]
38
+ @eval_vars = true
39
+ superclass_output = visit_node(node[:superclass])
40
+ @eval_vars = false
41
+ superclass_type = RBS::Parser.parse_type superclass_output
42
+ if node.dig(:superclass, :type) == "Generic"
43
+ superclass_output = superclass_output[/^[^\[]+/]
44
+ end
45
+ end
46
+
47
+ src.write_ln "class #{class_name}#{" < #{superclass_output}" if superclass_output}"
48
+
49
+ class_type = RBS::AST::Declarations::Class.new(
50
+ name: RBS::TypeName.new(
51
+ namespace: current_namespace,
52
+ name: class_name.to_sym
53
+ ),
54
+ type_params: RBS::AST::Declarations::ModuleTypeParams.new, # responds to #add to add params
55
+ super_class: superclass_type,
56
+ members: Array.new, # TODO
57
+ annotations: Array.new, # TODO
58
+ location: node[:location],
59
+ comment: node[:comment]
60
+ )
61
+ old_parent_scope = @current_scope
62
+ @current_scope = class_type
63
+
64
+ indented(src) { src.write_ln visit_node(node[:body]) if node[:body] }
65
+
66
+ src.write_ln "end"
67
+
68
+ @current_scope = old_parent_scope
69
+
70
+ @current_scope.members << class_type if @current_scope
71
+
72
+ if @type_checker
73
+ @type_checker.top_level_decls[class_type.name.name] = class_type unless @current_scope
74
+ end
75
+ when "ModuleNode"
76
+ module_name = visit_node node[:name]
77
+ src.write_ln "module #{module_name}"
78
+
79
+ current_namespace = @current_scope ? @current_scope.name.to_namespace : RBS::Namespace.root
80
+
81
+ module_type = RBS::AST::Declarations::Module.new(
82
+ name: RBS::TypeName.new(
83
+ namespace: current_namespace,
84
+ name: module_name.to_sym
85
+ ),
86
+ type_params: RBS::AST::Declarations::ModuleTypeParams.new, # responds to #add to add params
87
+ self_types: Array.new, # TODO
88
+ members: Array.new, # TODO
89
+ annotations: Array.new, # TODO
90
+ location: node[:location],
91
+ comment: node[:comment]
92
+ )
93
+ old_parent_scope = @current_scope
94
+ @current_scope = module_type
95
+
96
+ indented(src) { src.write_ln visit_node(node[:body]) if node[:body] }
97
+
98
+ @current_scope = old_parent_scope
99
+
100
+ @current_scope.members << module_type if @current_scope
101
+
102
+ if @type_checker
103
+ @type_checker.top_level_decls[module_type.name.name] = module_type unless @current_scope
104
+ end
105
+ src.write_ln "end"
106
+ when "DefNode"
107
+ args = render_args(node)
108
+ src.write_ln "def #{node[:name]}#{args}"
109
+
110
+ return_type = if node[:return_type]
111
+ RBS::Types::ClassInstance.new(
112
+ name: RBS::TypeName.new(
113
+ name: eval(visit_node(node[:return_type])).to_s.to_sym,
114
+ namespace: RBS::Namespace.root
115
+ ),
116
+ args: EMPTY_ARRAY, # TODO
117
+ location: node[:location]
118
+ )
119
+ else
120
+ RBS::Types::Bases::Any.new(
121
+ location: node[:location]
122
+ )
123
+ end
124
+
125
+ rp : Array[Hash[Symbol, Any]] = node.fetch(:positional_args) { EMPTY_ARRAY }.filter { |a| !a[:value] }
126
+ op : Array[Hash[Symbol, Any]] = node.fetch(:positional_args) { EMPTY_ARRAY }.filter { |a| a[:value] }
127
+
128
+ method_types = [
129
+ RBS::MethodType.new(
130
+ type_params: EMPTY_ARRAY, # TODO
131
+ type: RBS::Types::Function.new(
132
+ required_positionals: rp.map do |a|
133
+ RBS::Types::Function::Param.new(
134
+ name: visit_node(a).to_sym,
135
+ type: RBS::Types::Bases::Any.new(
136
+ location: a[:location]
137
+ )
138
+ )
139
+ end,
140
+ optional_positionals: op.map do |a|
141
+ RBS::Types::Function::Param.new(
142
+ name: visit_node(a).to_sym,
143
+ type: RBS::Types::Bases::Any.new(location: a[:location])
144
+ )
145
+ end,
146
+ rest_positionals: (rpa = node[:rest_p_args]) ? RBS::Types::Function::Param.new(name: visit_node(rpa).to_sym, type: RBS::Types::Bases::Any.new(location: node[:location])) : nil,
147
+ trailing_positionals: EMPTY_ARRAY, # TODO
148
+ required_keywords: node[:req_kw_args] || EMPTY_HASH,
149
+ optional_keywords: node[:opt_kw_args] || EMPTY_HASH,
150
+ rest_keywords: node[:rest_kw_args] ?
151
+ RBS::Types::Function::Param.new(
152
+ name: visit_node(node[:rest_kw_args]).to_sym,
153
+ type: RBS::Types::Bases::Any.new(location: node[:location])
154
+ ) : nil,
155
+ return_type: return_type
156
+ ),
157
+ block: node[:yield_arg_count] ?
158
+ RBS::Types::Block.new(
159
+ type: RBS::Types::Function.new(
160
+ required_positionals: Array.new,
161
+ optional_positionals: Array.new,
162
+ rest_positionals: nil,
163
+ trailing_positionals: Array.new,
164
+ required_keywords: Hash.new,
165
+ optional_keywords: Hash.new,
166
+ rest_keywords: nil,
167
+ return_type: RBS::Types::Bases::Any.new(location: node[:location])
168
+ ),
169
+ required: !!(node[:block_arg] || node[:yield_arg_count])
170
+ ) : nil,
171
+ location: node[:location]
172
+ )
173
+ ]
174
+ method_definition = RBS::AST::Members::MethodDefinition.new(
175
+ name: node[:name].to_sym,
176
+ kind: :instance,
177
+ types: method_types,
178
+ annotations: EMPTY_ARRAY, # TODO
179
+ location: node[:location],
180
+ comment: node[:comment],
181
+ overload: false
182
+ )
183
+
184
+ if @current_scope
185
+ @current_scope.members << method_definition
186
+ else
187
+ @type_checker.type_env << method_definition if @type_checker # should be new class declaration for Object with method_definition as private method
188
+ end
189
+
190
+ indented(src) { src.write_ln visit_node(node[:body]) if node[:body] }
191
+
192
+ src.write_ln "end"
193
+
194
+ when "VisibilityModifier"
195
+
196
+ src.write_ln "#{node[:visibility]} #{visit_node(node[:exp])}"
197
+ when "CollectionNode"
198
+ node[:children].each { |a| src.write visit_node(a, scope) }
199
+ when "Call"
200
+ obj = node[:object] ? "#{visit_node(node[:object], scope)}." : ""
201
+ arg_arr = node.fetch(:args) { EMPTY_ARRAY }
202
+ arg_arr += node[:named_args] if node[:named_args]
203
+ args = if !arg_arr.empty? || node[:block_arg]
204
+ "#{arg_arr.map { |a| visit_node(a, scope).strip }.reject(&:blank?).join(", ")}#{"&#{visit_node(node[:block_arg]).strip}" if node[:block_arg]}"
205
+ else
206
+ nil
207
+ end
208
+ block = node[:block] ? " #{visit_node(node[:block])}" : nil
209
+ has_parens = !!(node[:has_parentheses] || args || block)
210
+ opening_delimiter = if has_parens
211
+ "("
212
+ else
213
+ nil
214
+ end
215
+ call = "#{obj}#{node[:name]}#{opening_delimiter}#{args}#{")" if has_parens}#{block}"
216
+ src.write_ln(call)
217
+
218
+ when "Block"
219
+
220
+ src.write "{ |#{node[:args].map { |a| visit_node a }.join(", ")}|\n"
221
+
222
+ indented(src) { src.write visit_node(node[:body]) }
223
+
224
+ src.write_ln "}"
225
+
226
+ when "RangeLiteral"
227
+ dots = node[:exclusive] ? "..." : ".."
228
+
229
+ # parentheses help the compatibility with precendence of operators in some situations
230
+ # eg. (1..3).cover? 2 vs. 1..3.cover? 2
231
+ src.write "(", visit_node(node[:from]), dots, visit_node(node[:to]), ")"
232
+
233
+ when "LiteralNode"
234
+
235
+ src.write node[:value]
236
+
237
+ when "ArrayLiteral"
238
+
239
+ src.write("[", node[:elements].map { |e| visit_node(e).strip }.join(", "), "]")
240
+ src.write ".freeze" if node[:frozen]
241
+
242
+ when "StringInterpolation"
243
+
244
+ contents = node[:contents].inject(String.new) do |str, c|
245
+ str << case c[:type]
246
+ when "LiteralNode"
247
+ c[:value][1...-1]
248
+ else
249
+ [%q|#{|, visit_node(c).strip, "}"].join
250
+ end
251
+ end
252
+ src.write '"', contents, '"'
253
+
254
+ when "Path"
255
+
256
+ src.write node[:value]
257
+
258
+ when "Require"
259
+
260
+ src.write_ln %(require "#{node[:value]}")
261
+
262
+ when "Assign", "OpAssign"
263
+
264
+ src.write_ln "#{visit_node(node[:target])} #{node[:op]}= #{visit_node(node[:value]).strip}"
265
+
266
+ when "MultiAssign"
267
+
268
+ src.write_ln "#{node[:targets].map{ |t| visit_node(t).strip }.join(", ")} = #{node[:values].map { |v| visit_node(v).strip }.join(", ")}"
269
+
270
+ when "Var"
271
+
272
+ if @eval_vars
273
+ src.write scope[node[:name]]
274
+ else
275
+ src.write node[:name]
276
+ end
277
+
278
+ when "InstanceVar"
279
+
280
+ src.write node[:name]
281
+
282
+ when "GlobalVar"
283
+
284
+ src.write node[:name]
285
+
286
+ when "Arg"
287
+ val = node[:external_name]
288
+ if node[:keyword_arg]
289
+ val += ":"
290
+ val += " #{visit_node(node[:value])}" if node[:value]
291
+ elsif node[:value]
292
+ val += " = #{visit_node(node[:value])}"
293
+ end
294
+
295
+ src.write val
296
+
297
+ when "UnaryExpr"
298
+
299
+ src.write "#{node[:op]}#{visit_node(node[:value]).strip}"
300
+
301
+ when "BinaryOp"
302
+
303
+ src.write visit_node(node[:left]).strip, " #{node[:op]} ", visit_node(node[:right]).strip
304
+
305
+ when "HashLiteral"
306
+
307
+ contents = node[:elements].map do |k, v|
308
+ key = case k
309
+ when String
310
+ k.to_sym
311
+ else
312
+ visit_node k
313
+ end
314
+ value = visit_node v
315
+ "#{key.inspect} => #{value}"
316
+ end
317
+
318
+ src.write "{#{contents.join(",\n")}}"
319
+ src.write ".freeze" if node[:frozen]
320
+
321
+ when "Enum"
322
+ src.write_ln "module #{node[:name]}"
323
+ node[:members].each_with_index do |m, i|
324
+ indented(src) { src.write_ln(visit_node(m) + (!m[:value] ? " = #{i}" : "")) }
325
+ end
326
+ src.write_ln "end"
327
+ when "If"
328
+ src.write_ln "(if #{visit_node(node[:condition]).strip}"
329
+
330
+ indented(src) { src.write_ln visit_node(node[:then]) }
331
+
332
+ if node[:else]
333
+ src.write_ln "else"
334
+ indented(src) { src.write_ln visit_node(node[:else]) }
335
+ end
336
+
337
+ src.write_ln "end)"
338
+ when "Unless"
339
+ src.write_ln "unless #{visit_node node[:condition]}"
340
+ indented(src) { src.write_ln visit_node(node[:then]) }
341
+
342
+ if node[:else]
343
+ src.write_ln "else"
344
+ indented(src) { src.write_ln visit_node(node[:else]) }
345
+ end
346
+
347
+ src.write_ln "end"
348
+ when "Case"
349
+ src.write "case"
350
+ src.write " #{visit_node(node[:condition]).strip}\n" if node[:condition]
351
+ indented(src) do
352
+ node[:whens].each do |w|
353
+ src.write_ln visit_node(w)
354
+ end
355
+ if node[:else]
356
+ src.write_ln "else"
357
+ indented(src) do
358
+ src.write_ln visit_node(node[:else])
359
+ end
360
+ end
361
+ end
362
+ src.write_ln "end"
363
+ when "When"
364
+ src.write_ln "when #{node[:conditions].map { |n| visit_node(n) }.join(", ")}"
365
+
366
+ indented(src) { src.write_ln(node[:body] ? visit_node(node[:body]) : "# no op") }
367
+ when "MacroFor"
368
+ vars, expr, body = node[:vars], node[:expr], node[:body]
369
+ var_names = vars.map { |v| visit_node v }
370
+ @inside_macro = true
371
+ indent_level = @indent_level
372
+ @indent_level -= 1 unless indent_level.zero?
373
+ expanded : Array[String] = eval(visit_node(expr)).map do |*a|
374
+ locals = [var_names.join(%(", "))].zip(a).to_h
375
+ locals.merge!(scope) if @inside_macro
376
+ visit_node(body, locals)
377
+ end.flatten
378
+ @indent_level += 1 unless indent_level.zero?
379
+ expanded.each { |e| src.write e }
380
+ @inside_macro = false
381
+ when "MacroLiteral"
382
+ src.write node[:value]
383
+ when "MacroExpression"
384
+ if node[:output]
385
+ expr = visit_node node[:expr], scope
386
+ val = scope[expr]
387
+ src.write val
388
+ end
389
+ when "MacroIf"
390
+ if evaluate_macro_condition(node[:condition], scope)
391
+ src.write_ln visit_node(node[:then], scope) if node[:then]
392
+ else
393
+ src.write_ln visit_node(node[:else], scope) if node[:else]
394
+ end
395
+ when "Return"
396
+ val = node[:value] ? " #{visit_node(node[:value]).strip}" : nil
397
+ src.write "return#{val}"
398
+ when "TypeDeclaration"
399
+ src.write_ln "# @type var #{visit_node(node[:var])}: #{visit_node(node[:declared_type])}"
400
+ src.write_ln "#{visit_node(node[:var])} = #{visit_node(node[:value])}"
401
+ when "ExceptionHandler"
402
+ src.write_ln "begin"
403
+ indented src do
404
+ src.write_ln visit_node(node[:body])
405
+ end
406
+ if node[:rescues]
407
+ node[:rescues].each do |r|
408
+ src.write_ln "rescue #{r[:types].map { |n| visit_node n }.join(", ") if r[:types]}#{" => #{r[:name]}" if r[:name]}"
409
+ indented(src) { src.write_ln visit_node(r[:body]) } if r[:body]
410
+ end
411
+ end
412
+ if node[:else]
413
+ src.write_ln "else"
414
+ indented(src) { src.write_ln visit_node(node[:else]) }
415
+ end
416
+ if node[:ensure]
417
+ src.write_ln "ensure"
418
+ indented(src) { src.write_ln visit_node(node[:ensure]) }
419
+ end
420
+ src.write_ln "end"
421
+ when "Generic"
422
+ src.write "#{visit_node(node[:name])}[#{node[:args].map { |a| visit_node a }.join(", ")}]"
423
+ when "Proc"
424
+ fn = node[:function]
425
+ src.write "->#{render_args(fn)} { #{visit_node fn[:body]} }"
426
+ when "Include"
427
+ current_namespace = @current_scope ? @current_scope.name.to_namespace : RBS::Namespace.root
428
+ name = visit_node node[:name]
429
+ src.write_ln "include #{name}"
430
+ type = RBS::AST::Members::Include.new(
431
+ name: method(:TypeName).call(name),
432
+ args: Array.new,
433
+ annotations: Array.new,
434
+ location: node[:location],
435
+ comment: node[:comment]
436
+ )
437
+ if @current_scope
438
+ @current_scope.members << type
439
+ else
440
+ @type_checker.type_env << type
441
+ end
442
+ when "Extend"
443
+ current_namespace = @current_scope ? @current_scope.name.to_namespace : RBS::Namespace.root
444
+ name = visit_node node[:name]
445
+ src.write_ln "extend #{name}"
446
+ type = RBS::AST::Members::Extend.new(
447
+ name: method(:TypeName).call(name),
448
+ args: Array.new,
449
+ annotations: Array.new,
450
+ location: node[:location],
451
+ comment: node[:comment]
452
+ )
453
+ if @current_scope
454
+ @current_scope.members << type
455
+ else
456
+ @type_checker.type_env << type
457
+ end
458
+ when "RegexLiteral"
459
+ contents = visit_node node[:value]
460
+ src.write Regexp.new(contents.undump).inspect
461
+ when "Union"
462
+ types = node[:types]
463
+ output = if types.length == 2 && types[1][:type] == "Path" && types[1]["value"] == nil
464
+ "#{visit_node(types[0])}?"
465
+ else
466
+ types.map { |t| visit_node(t) }.join(" | ")
467
+ end
468
+ src.write output
469
+ when "EmptyNode"
470
+ # pass
471
+ else
472
+ raise "Not implemented: #{node[:type]}"
473
+ end
474
+
475
+ src
476
+ end
477
+
478
+ private def evaluate_macro_condition(condition_node, scope)
479
+ @eval_vars = true
480
+ eval(visit_node(condition_node, scope))
481
+ @eval_vars = false
482
+ end
483
+
484
+ private def indented(src)
485
+ increment_indent(src)
486
+ yield
487
+ decrement_indent(src)
488
+ end
489
+
490
+ private def increment_indent(src)
491
+ @indent_level += 1
492
+ src.increment_indent
493
+ end
494
+
495
+ private def decrement_indent(src)
496
+ @indent_level -= 1
497
+ src.decrement_indent
498
+ end
499
+
500
+ private def render_args(node)
501
+ rp : Array[Hash[Symbol, Any]] = node.fetch(:positional_args) { EMPTY_ARRAY }.filter { |a| !a[:value] }
502
+ op : Array[Hash[Symbol, Any]] = node.fetch(:positional_args) { EMPTY_ARRAY }.filter { |a| a[:value] }
503
+ rkw : Hash[Symbol, Any] = node.fetch(:req_kw_args) { EMPTY_HASH }
504
+ okw : Hash[Symbol, Any] = node.fetch(:opt_kw_args) { EMPTY_HASH }
505
+ rest_p : String? = node[:rest_p_args] ? visit_node(node[:rest_p_args]) : nil
506
+ rest_kw : Hash[Symbol, Any]? = node[:rest_kw_args]
507
+ return nil if [rp, op, rkw, okw, rest_p, rest_kw].all? { |a| a && a.empty? }
508
+
509
+ contents = [
510
+ rp.map { |a| visit_node(a) },
511
+ op.map { |a| "#{a[:name]} = #{visit_node(a[:value]).strip}" },
512
+ rkw.map { |name, _| "#{name}:" },
513
+ okw.map { |name, value| "#{name}: #{value}" },
514
+ rest_p ? "*#{rest_p}" : "",
515
+ rest_kw ? "**#{visit_node(rest_kw)}" : ""
516
+ ].reject(&:empty?).flatten.join(", ")
517
+ "(#{contents})"
518
+ end
519
+ end
520
+ end