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.
- checksums.yaml +4 -4
- data/Gemfile.lock +4 -4
- data/ext/gloss/{src/lib → lib}/cr_ruby.cr +0 -0
- data/ext/gloss/lib/rbs_types.cr +3 -0
- data/ext/gloss/src/cr_ast.cr +41 -8
- data/ext/gloss/src/gloss.cr +2 -1
- data/ext/gloss/src/lexer.cr +59 -1
- data/ext/gloss/src/rb_ast.cr +80 -31
- data/lib/gloss/builder.rb +581 -412
- data/lib/gloss/cli.rb +42 -28
- data/lib/gloss/config.rb +5 -6
- data/lib/gloss/errors.rb +4 -5
- data/lib/gloss/initializer.rb +5 -6
- data/lib/gloss/parser.rb +4 -5
- data/lib/gloss/scope.rb +8 -3
- data/lib/gloss/source.rb +18 -15
- data/lib/gloss/type_checker.rb +15 -10
- data/lib/gloss/version.rb +5 -6
- data/lib/gloss/watcher.rb +7 -8
- data/lib/gloss/writer.rb +5 -7
- data/sig/listen.rbs +1 -0
- data/src/lib/gloss/builder.gl +520 -0
- data/src/lib/gloss/cli.gl +41 -0
- data/src/lib/gloss/scope.gl +9 -0
- data/src/lib/gloss/source.gl +32 -0
- data/src/lib/gloss/type_checker.gl +4 -1
- data/src/lib/gloss/version.gl +1 -1
- data/src/lib/gloss/writer.gl +3 -3
- metadata +8 -3
data/lib/gloss/cli.rb
CHANGED
@@ -1,38 +1,52 @@
|
|
1
|
-
# frozen_string_literal: true
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
data/lib/gloss/config.rb
CHANGED
@@ -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
|
-
|
data/lib/gloss/errors.rb
CHANGED
@@ -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
|
-
|
data/lib/gloss/initializer.rb
CHANGED
@@ -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
|
-
|
data/lib/gloss/parser.rb
CHANGED
@@ -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
|
-
|
data/lib/gloss/scope.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
|
-
# frozen_string_literal: true
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
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) {
|
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
2
|
|
3
|
-
|
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
|
12
|
-
self
|
13
|
-
|
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|
|
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|
|
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
|
data/lib/gloss/type_checker.rb
CHANGED
@@ -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}
|
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
|
data/lib/gloss/version.rb
CHANGED
@@ -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.
|
6
|
+
module Gloss
|
7
|
+
VERSION = "0.0.4"
|
8
8
|
end
|
9
|
-
|
data/lib/gloss/watcher.rb
CHANGED
@@ -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
|
-
|
data/lib/gloss/writer.rb
CHANGED
@@ -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 =
|
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
|
-
|
data/sig/listen.rbs
CHANGED
@@ -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
|