gloss 0.0.3 → 0.0.4

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