gloss 0.0.5 → 0.1.3
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/.gitattributes +3 -0
- data/.github/workflows/{crystal.yml → crystal_specs.yml} +1 -1
- data/.github/workflows/{ruby.yml → ruby_specs.yml} +2 -2
- data/.github/workflows/self_build.yml +45 -0
- data/.gloss.yml +1 -0
- data/Gemfile.lock +3 -3
- data/README.md +35 -5
- data/Rakefile +1 -1
- data/exe/gloss +13 -2
- data/ext/gloss/Makefile +8 -19
- data/ext/gloss/lib/cr_ruby.cr +5 -4
- data/ext/gloss/src/cr_ast.cr +61 -77
- data/ext/gloss/src/gloss.cr +7 -3
- data/ext/gloss/src/rb_ast.cr +37 -36
- data/lib/gloss.rb +11 -7
- data/lib/gloss/cli.rb +61 -23
- data/lib/gloss/config.rb +3 -1
- data/lib/gloss/errors.rb +1 -1
- data/lib/gloss/initializer.rb +2 -1
- data/lib/gloss/logger.rb +29 -0
- data/lib/gloss/parser.rb +17 -2
- data/lib/gloss/prog_loader.rb +141 -0
- data/lib/gloss/scope.rb +1 -1
- data/lib/gloss/source.rb +1 -1
- data/lib/gloss/type_checker.rb +80 -32
- data/lib/gloss/utils.rb +44 -0
- data/lib/gloss/version.rb +4 -4
- data/lib/gloss/{builder.rb → visitor.rb} +93 -54
- data/lib/gloss/watcher.rb +41 -19
- data/lib/gloss/writer.rb +21 -10
- data/sig/core.rbs +2 -0
- data/sig/fast_blank.rbs +4 -0
- data/sig/{gloss.rbs → gls.rbs} +0 -0
- data/sig/optparse.rbs +6 -0
- data/sig/rubygems.rbs +9 -0
- data/sig/yaml.rbs +3 -0
- data/src/exe/gloss +19 -0
- data/src/lib/gloss.gl +25 -0
- data/src/lib/gloss/cli.gl +40 -14
- data/src/lib/gloss/config.gl +2 -2
- data/src/lib/gloss/initializer.gl +1 -1
- data/src/lib/gloss/logger.gl +21 -0
- data/src/lib/gloss/parser.gl +17 -5
- data/src/lib/gloss/prog_loader.gl +133 -0
- data/src/lib/gloss/scope.gl +0 -2
- data/src/lib/gloss/type_checker.gl +85 -39
- data/src/lib/gloss/utils.gl +38 -0
- data/src/lib/gloss/version.gl +1 -1
- data/src/lib/gloss/{builder.gl → visitor.gl} +80 -49
- data/src/lib/gloss/watcher.gl +42 -24
- data/src/lib/gloss/writer.gl +15 -13
- metadata +22 -7
@@ -0,0 +1,38 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
|
3
|
+
module Gloss
|
4
|
+
module Utils
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def absolute_path(path)
|
8
|
+
pn = Pathname.new(path)
|
9
|
+
if pn.absolute?
|
10
|
+
pn.to_s
|
11
|
+
else
|
12
|
+
ap = File.absolute_path path
|
13
|
+
if File.exist? ap
|
14
|
+
ap
|
15
|
+
else
|
16
|
+
throw :error, "File path #{path} does not exist (also looked for #{ap})"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def gem_path_for(gem_name)
|
22
|
+
Gem.ui.instance_variable_set :"@outs", StringIO.new
|
23
|
+
Gem::GemRunner.new.run(["which", gem_name])
|
24
|
+
Gem.ui.outs.string
|
25
|
+
rescue SystemExit => e
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def with_file_header(str)
|
30
|
+
"#{Visitor::FILE_HEADER}\n\n#{str}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def src_path_to_output_path(src_path : String) : String
|
34
|
+
src_path.sub("#{Config.src_dir}/", "")
|
35
|
+
.sub(/\.gl$/, ".rb")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/src/lib/gloss/version.gl
CHANGED
@@ -1,35 +1,26 @@
|
|
1
1
|
module Gloss
|
2
|
-
|
3
|
-
module_function
|
4
|
-
|
5
|
-
def with_file_header(str)
|
6
|
-
"#{Builder::FILE_HEADER}\n\n#{str}"
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
class Builder
|
2
|
+
class Visitor
|
11
3
|
FILE_HEADER = <<~RUBY
|
12
4
|
#{"# frozen_string_literal: true\n" if Config.frozen_string_literals}
|
13
5
|
##### This file was generated by Gloss; any changes made here will be overwritten.
|
14
6
|
##### See #{Config.src_dir}/ to make changes
|
15
7
|
RUBY
|
16
8
|
|
17
|
-
include Utils
|
18
|
-
|
19
9
|
attr_reader :tree
|
20
10
|
|
21
|
-
def initialize(tree_hash, type_checker = nil)
|
11
|
+
def initialize(tree_hash, type_checker = nil, @on_new_file_referenced = nil)
|
22
12
|
@indent_level = 0
|
23
13
|
@inside_macro = false
|
24
14
|
@eval_vars = false
|
25
15
|
@current_scope = nil
|
26
16
|
@tree = tree_hash
|
27
17
|
@type_checker = type_checker
|
18
|
+
@after_module_function = false
|
28
19
|
end
|
29
20
|
|
30
21
|
def run
|
31
22
|
rb_output = visit_node(@tree)
|
32
|
-
with_file_header(rb_output)
|
23
|
+
Utils.with_file_header(rb_output)
|
33
24
|
end
|
34
25
|
|
35
26
|
# type node = Hash[Symbol, String | Array[String | node] | Hash[Symbol, node]] | true | false
|
@@ -41,18 +32,31 @@ module Gloss
|
|
41
32
|
class_name = visit_node(node[:name])
|
42
33
|
current_namespace = @current_scope ? @current_scope.name.to_namespace : RBS::Namespace.root
|
43
34
|
superclass_type = nil
|
44
|
-
superclass_output =
|
35
|
+
superclass_output = ""
|
45
36
|
if node[:superclass]
|
46
37
|
@eval_vars = true
|
47
38
|
superclass_output = visit_node(node[:superclass])
|
48
39
|
@eval_vars = false
|
49
|
-
|
40
|
+
args = Array.new
|
50
41
|
if node.dig(:superclass, :type) == "Generic"
|
51
|
-
superclass_output = superclass_output[/^[^\[]+/]
|
42
|
+
superclass_output = superclass_output[/^[^\[]+/] || superclass_output
|
43
|
+
args = node.dig(:superclass, :args).map { |n| RBS::Parser.parse_type(visit_node(n)) }
|
52
44
|
end
|
45
|
+
|
46
|
+
class_name_index = superclass_output.index(/[^(?:::)]+\z/) || 0
|
47
|
+
namespace = superclass_output[0, class_name_index]
|
48
|
+
superclass_name = superclass_output[/[^(?:::)]+\z/] || superclass_output
|
49
|
+
superclass_type = RBS::AST::Declarations::Class::Super.new(
|
50
|
+
name: RBS::TypeName.new(
|
51
|
+
namespace: method(:Namespace).call(namespace),
|
52
|
+
name: superclass_name.to_sym,
|
53
|
+
),
|
54
|
+
args: args,
|
55
|
+
location: build_location(node),
|
56
|
+
)
|
53
57
|
end
|
54
58
|
|
55
|
-
src.write_ln "class #{class_name}#{" < #{superclass_output}"
|
59
|
+
src.write_ln "class #{class_name}#{" < #{superclass_output}" unless superclass_output.blank?}"
|
56
60
|
|
57
61
|
class_type = RBS::AST::Declarations::Class.new(
|
58
62
|
name: RBS::TypeName.new(
|
@@ -63,7 +67,7 @@ module Gloss
|
|
63
67
|
super_class: superclass_type,
|
64
68
|
members: Array.new, # TODO
|
65
69
|
annotations: Array.new, # TODO
|
66
|
-
location: node
|
70
|
+
location: build_location(node),
|
67
71
|
comment: node[:comment]
|
68
72
|
)
|
69
73
|
old_parent_scope = @current_scope
|
@@ -77,10 +81,12 @@ module Gloss
|
|
77
81
|
|
78
82
|
@current_scope.members << class_type if @current_scope
|
79
83
|
|
80
|
-
if @type_checker
|
81
|
-
@type_checker.top_level_decls
|
84
|
+
if @type_checker && !@current_scope
|
85
|
+
@type_checker.top_level_decls.add(class_type)
|
82
86
|
end
|
83
87
|
when "ModuleNode"
|
88
|
+
existing_module_function_state = @after_module_function.dup
|
89
|
+
@after_module_function = false
|
84
90
|
module_name = visit_node node[:name]
|
85
91
|
src.write_ln "module #{module_name}"
|
86
92
|
|
@@ -95,7 +101,7 @@ module Gloss
|
|
95
101
|
self_types: Array.new, # TODO
|
96
102
|
members: Array.new, # TODO
|
97
103
|
annotations: Array.new, # TODO
|
98
|
-
location: node
|
104
|
+
location: build_location(node),
|
99
105
|
comment: node[:comment]
|
100
106
|
)
|
101
107
|
old_parent_scope = @current_scope
|
@@ -107,13 +113,15 @@ module Gloss
|
|
107
113
|
|
108
114
|
@current_scope.members << module_type if @current_scope
|
109
115
|
|
110
|
-
if @type_checker
|
111
|
-
@type_checker.top_level_decls
|
116
|
+
if @type_checker && !@current_scope
|
117
|
+
@type_checker.top_level_decls.add(module_type)
|
112
118
|
end
|
113
119
|
src.write_ln "end"
|
120
|
+
@after_module_function = existing_module_function_state
|
114
121
|
when "DefNode"
|
115
122
|
args = render_args(node)
|
116
|
-
|
123
|
+
receiver = node[:receiver] ? visit_node(node[:receiver]) : nil
|
124
|
+
src.write_ln "def #{"#{receiver}." if receiver}#{node[:name]}#{args[:representation]}"
|
117
125
|
|
118
126
|
return_type = if node[:return_type]
|
119
127
|
RBS::Types::ClassInstance.new(
|
@@ -122,11 +130,11 @@ module Gloss
|
|
122
130
|
namespace: RBS::Namespace.root
|
123
131
|
),
|
124
132
|
args: EMPTY_ARRAY, # TODO
|
125
|
-
location: node
|
133
|
+
location: build_location(node)
|
126
134
|
)
|
127
135
|
else
|
128
136
|
RBS::Types::Bases::Any.new(
|
129
|
-
location: node
|
137
|
+
location: build_location(node)
|
130
138
|
)
|
131
139
|
end
|
132
140
|
|
@@ -153,19 +161,19 @@ module Gloss
|
|
153
161
|
required_keywords: Hash.new,
|
154
162
|
optional_keywords: Hash.new,
|
155
163
|
rest_keywords: nil,
|
156
|
-
return_type: RBS::Types::Bases::Any.new(location: node
|
164
|
+
return_type: RBS::Types::Bases::Any.new(location: build_location(node))
|
157
165
|
),
|
158
166
|
required: !!(node[:block_arg] || node[:yield_arg_count])
|
159
167
|
) : nil,
|
160
|
-
location: node
|
168
|
+
location: build_location(node)
|
161
169
|
)
|
162
170
|
]
|
163
171
|
method_definition = RBS::AST::Members::MethodDefinition.new(
|
164
172
|
name: node[:name].to_sym,
|
165
|
-
kind: :instance,
|
173
|
+
kind: @after_module_function ? :singleton_instance : receiver ? :singleton : :instance,
|
166
174
|
types: method_types,
|
167
175
|
annotations: EMPTY_ARRAY, # TODO
|
168
|
-
location: node
|
176
|
+
location: build_location(node),
|
169
177
|
comment: node[:comment],
|
170
178
|
overload: false
|
171
179
|
)
|
@@ -201,7 +209,14 @@ module Gloss
|
|
201
209
|
else
|
202
210
|
nil
|
203
211
|
end
|
204
|
-
|
212
|
+
name = node[:name]
|
213
|
+
call = "#{obj}#{name}#{opening_delimiter}#{args}#{")" if has_parens}#{block}"
|
214
|
+
case name
|
215
|
+
when "require_relative"
|
216
|
+
@on_new_file_referenced.call(name, true) if @on_new_file_referenced
|
217
|
+
when "module_function"
|
218
|
+
@after_module_function = true
|
219
|
+
end
|
205
220
|
src.write_ln(call)
|
206
221
|
|
207
222
|
when "Block"
|
@@ -215,9 +230,10 @@ module Gloss
|
|
215
230
|
when "RangeLiteral"
|
216
231
|
dots = node[:exclusive] ? "..." : ".."
|
217
232
|
|
218
|
-
# parentheses help the compatibility with precendence of operators in some situations
|
233
|
+
# parentheses around the whole thing help the compatibility with precendence of operators in some situations
|
219
234
|
# eg. (1..3).cover? 2 vs. 1..3.cover? 2
|
220
|
-
|
235
|
+
# parentheses around either number help with things like arithemtic ((x-1)..(y+5))
|
236
|
+
src.write "(", "(", visit_node(node[:from]), ")", dots, "(", visit_node(node[:to]), ")", ")"
|
221
237
|
|
222
238
|
when "LiteralNode"
|
223
239
|
|
@@ -245,9 +261,10 @@ module Gloss
|
|
245
261
|
src.write node[:value]
|
246
262
|
|
247
263
|
when "Require"
|
264
|
+
path = node[:value]
|
265
|
+
src.write_ln %(require "#{path}")
|
248
266
|
|
249
|
-
|
250
|
-
|
267
|
+
@on_new_file_referenced.call(path, false) if @on_new_file_referenced
|
251
268
|
when "Assign", "OpAssign"
|
252
269
|
|
253
270
|
src.write_ln "#{visit_node(node[:target])} #{node[:op]}= #{visit_node(node[:value]).strip}"
|
@@ -296,12 +313,12 @@ module Gloss
|
|
296
313
|
contents = node[:elements].map do |k, v|
|
297
314
|
key = case k
|
298
315
|
when String
|
299
|
-
k.to_sym
|
316
|
+
k.to_sym.inspect
|
300
317
|
else
|
301
318
|
visit_node k
|
302
319
|
end
|
303
320
|
value = visit_node v
|
304
|
-
"#{key
|
321
|
+
"#{key} => #{value}"
|
305
322
|
end
|
306
323
|
|
307
324
|
src.write "{#{contents.join(",\n")}}"
|
@@ -386,7 +403,8 @@ module Gloss
|
|
386
403
|
src.write "return#{val}"
|
387
404
|
when "TypeDeclaration"
|
388
405
|
src.write_ln "# @type var #{visit_node(node[:var])}: #{visit_node(node[:declared_type])}"
|
389
|
-
|
406
|
+
value = node[:value] ? " = #{visit_node node[:value]}" : nil
|
407
|
+
src.write_ln "#{visit_node(node[:var])}#{value}"
|
390
408
|
when "ExceptionHandler"
|
391
409
|
src.write_ln "begin"
|
392
410
|
indented src do
|
@@ -420,7 +438,7 @@ module Gloss
|
|
420
438
|
name: method(:TypeName).call(name),
|
421
439
|
args: Array.new,
|
422
440
|
annotations: Array.new,
|
423
|
-
location: node
|
441
|
+
location: build_location(node),
|
424
442
|
comment: node[:comment]
|
425
443
|
)
|
426
444
|
if @current_scope
|
@@ -436,7 +454,7 @@ module Gloss
|
|
436
454
|
name: method(:TypeName).call(name),
|
437
455
|
args: Array.new,
|
438
456
|
annotations: Array.new,
|
439
|
-
location: node
|
457
|
+
location: build_location(node),
|
440
458
|
comment: node[:comment]
|
441
459
|
)
|
442
460
|
if @current_scope
|
@@ -508,37 +526,50 @@ module Gloss
|
|
508
526
|
rest_kw ? "**#{visit_node(rest_kw)}" : ""
|
509
527
|
].reject(&:empty?).flatten.join(", ")
|
510
528
|
representation = "(#{contents})"
|
511
|
-
rp.map
|
529
|
+
rp_args = rp.map do |a|
|
512
530
|
RBS::Types::Function::Param.new(
|
513
531
|
name: visit_node(a).to_sym,
|
514
532
|
type: RBS::Types::Bases::Any.new(
|
515
|
-
location: a
|
533
|
+
location: build_location(a)
|
516
534
|
)
|
517
535
|
)
|
518
536
|
end
|
519
|
-
op.map
|
537
|
+
op_args = op.map do |a|
|
520
538
|
RBS::Types::Function::Param.new(
|
521
539
|
name: visit_node(a).to_sym,
|
522
|
-
type: RBS::Types::Bases::Any.new(location: a
|
540
|
+
type: RBS::Types::Bases::Any.new(location: build_location(a))
|
523
541
|
)
|
524
542
|
end
|
525
|
-
|
543
|
+
rpa = rest_p ? RBS::Types::Function::Param.new(
|
544
|
+
name: rest_p.to_sym,
|
545
|
+
type: RBS::Types::Bases::Any.new(location: build_location(node))
|
546
|
+
) : nil
|
526
547
|
{
|
527
548
|
representation: representation,
|
528
549
|
types: {
|
529
|
-
required_positionals:
|
530
|
-
optional_positionals:
|
531
|
-
rest_positionals:
|
550
|
+
required_positionals: rp_args,
|
551
|
+
optional_positionals: op_args,
|
552
|
+
rest_positionals: rpa,
|
532
553
|
trailing_positionals: EMPTY_ARRAY, # TODO
|
533
554
|
required_keywords: node[:req_kw_args] || EMPTY_HASH,
|
534
555
|
optional_keywords: node[:opt_kw_args] || EMPTY_HASH,
|
535
556
|
rest_keywords: node[:rest_kw_args] ?
|
536
557
|
RBS::Types::Function::Param.new(
|
537
558
|
name: visit_node(node[:rest_kw_args]).to_sym,
|
538
|
-
type: RBS::Types::Bases::Any.new(location: node
|
559
|
+
type: RBS::Types::Bases::Any.new(location: build_location(node))
|
539
560
|
) : nil
|
540
561
|
}
|
541
562
|
}
|
542
563
|
end
|
564
|
+
|
565
|
+
def build_location(node)
|
566
|
+
return nil unless node[:location]
|
567
|
+
|
568
|
+
# RBS::Location.new(
|
569
|
+
# buffer: RBS::Buffer.new(@file_path, @file_content),
|
570
|
+
# start_pos: node[:location][:start],
|
571
|
+
# end_pos: node[:location][:end]
|
572
|
+
# )
|
573
|
+
end
|
543
574
|
end
|
544
575
|
end
|
data/src/lib/gloss/watcher.gl
CHANGED
@@ -1,46 +1,64 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
require "listen"
|
4
2
|
|
5
3
|
module Gloss
|
6
4
|
class Watcher
|
7
5
|
def initialize(@paths : Array[String])
|
8
|
-
|
6
|
+
if @paths.empty?
|
7
|
+
@paths = [File.join(Dir.pwd, Config.src_dir)]
|
8
|
+
# either any filepath with .gl extension, or executable with extension
|
9
|
+
@only = /(?:(\.gl|(?:(?<=\/)[^\.\/]+))\z|\A[^\.\/]+\z)/
|
10
|
+
else
|
11
|
+
file_names = Array.new
|
12
|
+
paths = Array.new
|
13
|
+
@paths.each do |pa|
|
14
|
+
pn = Pathname.new(pa)
|
15
|
+
paths << pn.parent.to_s
|
16
|
+
file_names << (pn.file? ? pn.basename.to_s : pa)
|
17
|
+
end
|
18
|
+
@paths = paths.uniq
|
19
|
+
@only = /#{Regexp.union(file_names)}/
|
20
|
+
end
|
9
21
|
end
|
10
22
|
|
11
23
|
def watch
|
12
|
-
|
13
|
-
listener = Listen.to(
|
24
|
+
Gloss.logger.info "Now listening for changes in #{@paths.join(', ')}"
|
25
|
+
listener = Listen.to(
|
26
|
+
*@paths,
|
27
|
+
latency: 2,
|
28
|
+
only: @only
|
29
|
+
) do |modified, added, removed|
|
14
30
|
(modified + added).each do |f|
|
15
|
-
|
16
|
-
|
17
|
-
puts "====> Rewriting #{f}"
|
31
|
+
Gloss.logger.info "Rewriting #{f}"
|
18
32
|
content = File.read(f)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
33
|
+
err = catch :error do
|
34
|
+
Writer.new(
|
35
|
+
Visitor.new(
|
36
|
+
Parser.new(
|
37
|
+
content
|
38
|
+
).run
|
39
|
+
).run, f
|
40
|
+
).run
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
if err
|
44
|
+
Gloss.logger.error err
|
45
|
+
else
|
46
|
+
Gloss.logger.info "Done"
|
47
|
+
end
|
28
48
|
end
|
29
49
|
removed.each do |f|
|
30
|
-
next unless f.end_with? ".gl"
|
31
|
-
|
32
50
|
out_path = Utils.src_path_to_output_path(f)
|
33
|
-
|
51
|
+
Gloss.logger.info "Removing #{out_path}"
|
34
52
|
File.delete out_path if File.exist? out_path
|
35
53
|
|
36
|
-
|
54
|
+
Gloss.logger.info "Done"
|
37
55
|
end
|
38
56
|
end
|
39
|
-
listener.start
|
40
57
|
begin
|
41
|
-
|
58
|
+
listener.start
|
59
|
+
sleep
|
42
60
|
rescue Interrupt
|
43
|
-
|
61
|
+
Gloss.logger.info "Interrupt signal received, shutting down"
|
44
62
|
exit 0
|
45
63
|
end
|
46
64
|
end
|
data/src/lib/gloss/writer.gl
CHANGED
@@ -4,30 +4,32 @@ require "pathname"
|
|
4
4
|
require "fileutils"
|
5
5
|
|
6
6
|
module Gloss
|
7
|
-
module Utils
|
8
|
-
module_function
|
9
|
-
|
10
|
-
def src_path_to_output_path(src_path : String) : String
|
11
|
-
src_path.sub("#{Config.src_dir}/", "")
|
12
|
-
.sub(/\.gl$/, ".rb")
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
7
|
class Writer
|
17
|
-
include Utils
|
18
|
-
|
19
8
|
def initialize(
|
20
9
|
@content,
|
21
|
-
src_path : String,
|
22
|
-
@output_path : Pathname? = Pathname.new(
|
10
|
+
@src_path : String,
|
11
|
+
@output_path : Pathname? = Pathname.new(
|
12
|
+
Utils.src_path_to_output_path(src_path)
|
13
|
+
)
|
23
14
|
)
|
24
15
|
end
|
25
16
|
|
26
17
|
def run
|
27
18
|
FileUtils.mkdir_p(@output_path.parent) unless @output_path.parent.exist?
|
28
19
|
File.open(@output_path, "wb") do |file|
|
20
|
+
sb = shebang
|
21
|
+
file.puts sb if sb
|
29
22
|
file.puts @content
|
30
23
|
end
|
31
24
|
end
|
25
|
+
|
26
|
+
private def shebang
|
27
|
+
if @output_path.executable?
|
28
|
+
first_line = File.open(@src_path) { |f| f.readline }
|
29
|
+
first_line.start_with?("#!") ? first_line : nil
|
30
|
+
else
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
end
|
32
34
|
end
|
33
35
|
end
|