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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +3 -0
  3. data/.github/workflows/{crystal.yml → crystal_specs.yml} +1 -1
  4. data/.github/workflows/{ruby.yml → ruby_specs.yml} +2 -2
  5. data/.github/workflows/self_build.yml +45 -0
  6. data/.gloss.yml +1 -0
  7. data/Gemfile.lock +3 -3
  8. data/README.md +35 -5
  9. data/Rakefile +1 -1
  10. data/exe/gloss +13 -2
  11. data/ext/gloss/Makefile +8 -19
  12. data/ext/gloss/lib/cr_ruby.cr +5 -4
  13. data/ext/gloss/src/cr_ast.cr +61 -77
  14. data/ext/gloss/src/gloss.cr +7 -3
  15. data/ext/gloss/src/rb_ast.cr +37 -36
  16. data/lib/gloss.rb +11 -7
  17. data/lib/gloss/cli.rb +61 -23
  18. data/lib/gloss/config.rb +3 -1
  19. data/lib/gloss/errors.rb +1 -1
  20. data/lib/gloss/initializer.rb +2 -1
  21. data/lib/gloss/logger.rb +29 -0
  22. data/lib/gloss/parser.rb +17 -2
  23. data/lib/gloss/prog_loader.rb +141 -0
  24. data/lib/gloss/scope.rb +1 -1
  25. data/lib/gloss/source.rb +1 -1
  26. data/lib/gloss/type_checker.rb +80 -32
  27. data/lib/gloss/utils.rb +44 -0
  28. data/lib/gloss/version.rb +4 -4
  29. data/lib/gloss/{builder.rb → visitor.rb} +93 -54
  30. data/lib/gloss/watcher.rb +41 -19
  31. data/lib/gloss/writer.rb +21 -10
  32. data/sig/core.rbs +2 -0
  33. data/sig/fast_blank.rbs +4 -0
  34. data/sig/{gloss.rbs → gls.rbs} +0 -0
  35. data/sig/optparse.rbs +6 -0
  36. data/sig/rubygems.rbs +9 -0
  37. data/sig/yaml.rbs +3 -0
  38. data/src/exe/gloss +19 -0
  39. data/src/lib/gloss.gl +25 -0
  40. data/src/lib/gloss/cli.gl +40 -14
  41. data/src/lib/gloss/config.gl +2 -2
  42. data/src/lib/gloss/initializer.gl +1 -1
  43. data/src/lib/gloss/logger.gl +21 -0
  44. data/src/lib/gloss/parser.gl +17 -5
  45. data/src/lib/gloss/prog_loader.gl +133 -0
  46. data/src/lib/gloss/scope.gl +0 -2
  47. data/src/lib/gloss/type_checker.gl +85 -39
  48. data/src/lib/gloss/utils.gl +38 -0
  49. data/src/lib/gloss/version.gl +1 -1
  50. data/src/lib/gloss/{builder.gl → visitor.gl} +80 -49
  51. data/src/lib/gloss/watcher.gl +42 -24
  52. data/src/lib/gloss/writer.gl +15 -13
  53. 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
@@ -1,3 +1,3 @@
1
1
  module Gloss
2
- VERSION = "0.0.5"
2
+ VERSION = "0.1.3"
3
3
  end
@@ -1,35 +1,26 @@
1
1
  module Gloss
2
- module Utils
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 = nil
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
- superclass_type = RBS::Parser.parse_type superclass_output
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}" if 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[:location],
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[class_type.name.name] = class_type unless @current_scope
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[:location],
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[module_type.name.name] = module_type unless @current_scope
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
- src.write_ln "def #{node[:name]}#{args[:representation]}"
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[:location]
133
+ location: build_location(node)
126
134
  )
127
135
  else
128
136
  RBS::Types::Bases::Any.new(
129
- location: node[:location]
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[:location])
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[:location]
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[:location],
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
- call = "#{obj}#{node[:name]}#{opening_delimiter}#{args}#{")" if has_parens}#{block}"
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
- src.write "(", visit_node(node[:from]), dots, visit_node(node[:to]), ")"
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
- src.write_ln %(require "#{node[:value]}")
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.inspect} => #{value}"
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
- src.write_ln "#{visit_node(node[:var])} = #{visit_node(node[:value])}"
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[:location],
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[:location],
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! do |a|
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[:location]
533
+ location: build_location(a)
516
534
  )
517
535
  )
518
536
  end
519
- op.map! do |a|
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[:location])
540
+ type: RBS::Types::Bases::Any.new(location: build_location(a))
523
541
  )
524
542
  end
525
- rest_p = (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
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: rp,
530
- optional_positionals: op,
531
- rest_positionals: rest_p,
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[:location])
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
@@ -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
- @paths = [File.join(Dir.pwd, Config.src_dir)] if @paths.empty?
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
- puts "=====> Now listening for changes in #{@paths.join(', ')}"
13
- listener = Listen.to(*@paths, latency: 2) do |modified, added, removed|
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
- next unless f.end_with? ".gl"
16
-
17
- puts "====> Rewriting #{f}"
31
+ Gloss.logger.info "Rewriting #{f}"
18
32
  content = File.read(f)
19
- Writer.new(
20
- Builder.new(
21
- Parser.new(
22
- content
23
- ).run
24
- ).run, f
25
- ).run
26
-
27
- puts "====> Done"
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
- puts "====> Removing #{out_path}"
51
+ Gloss.logger.info "Removing #{out_path}"
34
52
  File.delete out_path if File.exist? out_path
35
53
 
36
- puts "====> Done"
54
+ Gloss.logger.info "Done"
37
55
  end
38
56
  end
39
- listener.start
40
57
  begin
41
- loop { sleep 10 }
58
+ listener.start
59
+ sleep
42
60
  rescue Interrupt
43
- puts "=====> Interrupt signal received, shutting down"
61
+ Gloss.logger.info "Interrupt signal received, shutting down"
44
62
  exit 0
45
63
  end
46
64
  end
@@ -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(src_path_to_output_path(src_path))
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