gloss 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) 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} +1 -1
  5. data/.gloss.yml +1 -0
  6. data/.rspec +1 -0
  7. data/Gemfile.lock +25 -27
  8. data/README.md +36 -5
  9. data/exe/gloss +13 -2
  10. data/ext/gloss/{src/lib → lib}/cr_ruby.cr +0 -0
  11. data/ext/gloss/lib/rbs_types.cr +3 -0
  12. data/ext/gloss/spec/parser_spec.cr +83 -50
  13. data/ext/gloss/src/cr_ast.cr +146 -72
  14. data/ext/gloss/src/gloss.cr +2 -2
  15. data/ext/gloss/src/lexer.cr +59 -1
  16. data/ext/gloss/src/parser.cr +4 -4
  17. data/ext/gloss/src/rb_ast.cr +152 -57
  18. data/lib/gloss.rb +15 -7
  19. data/lib/gloss/cli.rb +85 -27
  20. data/lib/gloss/config.rb +18 -10
  21. data/lib/gloss/errors.rb +13 -7
  22. data/lib/gloss/initializer.rb +11 -6
  23. data/lib/gloss/logger.rb +29 -0
  24. data/lib/gloss/parser.rb +22 -5
  25. data/lib/gloss/prog_loader.rb +141 -0
  26. data/lib/gloss/scope.rb +7 -2
  27. data/lib/gloss/source.rb +17 -14
  28. data/lib/gloss/type_checker.rb +105 -66
  29. data/lib/gloss/utils.rb +44 -0
  30. data/lib/gloss/version.rb +6 -1
  31. data/lib/gloss/visitor.rb +667 -0
  32. data/lib/gloss/watcher.rb +63 -19
  33. data/lib/gloss/writer.rb +35 -18
  34. data/sig/core.rbs +2 -0
  35. data/sig/fast_blank.rbs +4 -0
  36. data/sig/gls.rbs +3 -0
  37. data/sig/listen.rbs +1 -0
  38. data/sig/optparse.rbs +6 -0
  39. data/sig/rubygems.rbs +9 -0
  40. data/sig/yaml.rbs +3 -0
  41. data/src/exe/gloss +19 -0
  42. data/src/lib/gloss.gl +26 -0
  43. data/src/lib/gloss/cli.gl +70 -0
  44. data/src/lib/gloss/config.gl +21 -0
  45. data/src/lib/gloss/errors.gl +11 -0
  46. data/src/lib/gloss/initializer.gl +20 -0
  47. data/src/lib/gloss/logger.gl +21 -0
  48. data/src/lib/gloss/parser.gl +31 -0
  49. data/src/lib/gloss/prog_loader.gl +133 -0
  50. data/src/lib/gloss/scope.gl +7 -0
  51. data/src/lib/gloss/source.gl +32 -0
  52. data/src/lib/gloss/type_checker.gl +119 -0
  53. data/src/lib/gloss/utils.gl +38 -0
  54. data/src/lib/gloss/version.gl +3 -0
  55. data/src/lib/gloss/visitor.gl +575 -0
  56. data/src/lib/gloss/watcher.gl +66 -0
  57. data/src/lib/gloss/writer.gl +35 -0
  58. metadata +35 -8
  59. data/lib/gloss/builder.rb +0 -393
  60. data/src/lib/hrb/initializer.gl +0 -22
  61. data/src/lib/hrb/watcher.gl +0 -32
@@ -0,0 +1,66 @@
1
+ require "listen"
2
+
3
+ module Gloss
4
+ class Watcher
5
+ def initialize(@paths : Array[String])
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
21
+ end
22
+
23
+ def watch
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|
30
+ (modified + added).each do |f|
31
+ Gloss.logger.info "Rewriting #{f}"
32
+ content = File.read(f)
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
48
+ end
49
+ removed.each do |f|
50
+ out_path = Utils.src_path_to_output_path(f)
51
+ Gloss.logger.info "Removing #{out_path}"
52
+ File.delete out_path if File.exist? out_path
53
+
54
+ Gloss.logger.info "Done"
55
+ end
56
+ end
57
+ begin
58
+ listener.start
59
+ sleep
60
+ rescue Interrupt
61
+ Gloss.logger.info "Interrupt signal received, shutting down"
62
+ exit 0
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+ require "fileutils"
5
+
6
+ module Gloss
7
+ class Writer
8
+ def initialize(
9
+ @content,
10
+ @src_path : String,
11
+ @output_path : Pathname? = Pathname.new(
12
+ Utils.src_path_to_output_path(src_path)
13
+ )
14
+ )
15
+ end
16
+
17
+ def run
18
+ FileUtils.mkdir_p(@output_path.parent) unless @output_path.parent.exist?
19
+ File.open(@output_path, "wb") do |file|
20
+ sb = shebang
21
+ file.puts sb if sb
22
+ file.puts @content
23
+ end
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
34
+ end
35
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gloss
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - johansenja
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-03 00:00:00.000000000 Z
11
+ date: 2021-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fast_blank
@@ -131,10 +131,12 @@ extensions:
131
131
  - ext/gloss/extconf.rb
132
132
  extra_rdoc_files: []
133
133
  files:
134
- - ".github/workflows/crystal.yml"
135
- - ".github/workflows/ruby.yml"
134
+ - ".gitattributes"
135
+ - ".github/workflows/crystal_specs.yml"
136
+ - ".github/workflows/ruby_specs.yml"
136
137
  - ".gitignore"
137
138
  - ".gloss.yml"
139
+ - ".rspec"
138
140
  - ".rubocop.yml"
139
141
  - Gemfile
140
142
  - Gemfile.lock
@@ -145,32 +147,57 @@ files:
145
147
  - exe/gloss
146
148
  - ext/gloss/Makefile
147
149
  - ext/gloss/extconf.rb
150
+ - ext/gloss/lib/cr_ruby.cr
151
+ - ext/gloss/lib/rbs_types.cr
148
152
  - ext/gloss/shard.yml
149
153
  - ext/gloss/spec/parser_spec.cr
150
154
  - ext/gloss/spec/spec_helper.cr
151
155
  - ext/gloss/src/cr_ast.cr
152
156
  - ext/gloss/src/gloss.cr
153
157
  - ext/gloss/src/lexer.cr
154
- - ext/gloss/src/lib/cr_ruby.cr
155
158
  - ext/gloss/src/parser.cr
156
159
  - ext/gloss/src/rb_ast.cr
157
160
  - gloss.gemspec
158
161
  - lib/gloss.rb
159
- - lib/gloss/builder.rb
160
162
  - lib/gloss/cli.rb
161
163
  - lib/gloss/config.rb
162
164
  - lib/gloss/errors.rb
163
165
  - lib/gloss/initializer.rb
166
+ - lib/gloss/logger.rb
164
167
  - lib/gloss/parser.rb
168
+ - lib/gloss/prog_loader.rb
165
169
  - lib/gloss/scope.rb
166
170
  - lib/gloss/source.rb
167
171
  - lib/gloss/type_checker.rb
172
+ - lib/gloss/utils.rb
168
173
  - lib/gloss/version.rb
174
+ - lib/gloss/visitor.rb
169
175
  - lib/gloss/watcher.rb
170
176
  - lib/gloss/writer.rb
177
+ - sig/core.rbs
178
+ - sig/fast_blank.rbs
179
+ - sig/gls.rbs
171
180
  - sig/listen.rbs
172
- - src/lib/hrb/initializer.gl
173
- - src/lib/hrb/watcher.gl
181
+ - sig/optparse.rbs
182
+ - sig/rubygems.rbs
183
+ - sig/yaml.rbs
184
+ - src/exe/gloss
185
+ - src/lib/gloss.gl
186
+ - src/lib/gloss/cli.gl
187
+ - src/lib/gloss/config.gl
188
+ - src/lib/gloss/errors.gl
189
+ - src/lib/gloss/initializer.gl
190
+ - src/lib/gloss/logger.gl
191
+ - src/lib/gloss/parser.gl
192
+ - src/lib/gloss/prog_loader.gl
193
+ - src/lib/gloss/scope.gl
194
+ - src/lib/gloss/source.gl
195
+ - src/lib/gloss/type_checker.gl
196
+ - src/lib/gloss/utils.gl
197
+ - src/lib/gloss/version.gl
198
+ - src/lib/gloss/visitor.gl
199
+ - src/lib/gloss/watcher.gl
200
+ - src/lib/gloss/writer.gl
174
201
  homepage:
175
202
  licenses:
176
203
  - MIT
data/lib/gloss/builder.rb DELETED
@@ -1,393 +0,0 @@
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
- rb_output = "# frozen_string_literal: true\n#{rb_output}" if Config.frozen_string_literals
19
- rb_output
20
- end
21
-
22
- def visit_node(node, scope = Scope.new)
23
- src = Source.new(@indent_level)
24
- case node[:type]
25
- when "ClassNode"
26
- class_name = visit_node(node[:name])
27
- superclass = if node[:superclass]
28
- @eval_vars = true
29
- visit_node(node[:superclass])
30
- @eval_vars = false
31
- else
32
- nil
33
- end
34
-
35
- src.write_ln "class #{class_name}#{" < #{superclass}" if superclass}"
36
-
37
- current_namespace = @current_scope ? @current_scope.name.to_namespace : RBS::Namespace.root
38
- class_type = RBS::AST::Declarations::Class.new(
39
- name: RBS::TypeName.new(
40
- namespace: current_namespace,
41
- name: class_name.to_sym
42
- ),
43
- type_params: RBS::AST::Declarations::ModuleTypeParams.new, # responds to #add to add params
44
- super_class: superclass ? RBS::AST::Declarations::Class::Super.new(name: RBS::Typename.new(name: super_class.to_sym, namespace: RBS::Namespace.root), args: [], location: nil) : nil,
45
- members: [],
46
- annotations: [],
47
- location: node[:location],
48
- comment: node[:comment]
49
- )
50
- old_parent_scope = @current_scope
51
- @current_scope = class_type
52
-
53
- indented(src) { src.write_ln visit_node(node[:body]) if node[:body] }
54
-
55
- src.write_ln "end"
56
-
57
- @current_scope = old_parent_scope
58
- if @type_checker
59
- @type_checker.top_level_decls[class_type.name.name] = class_type unless @current_scope
60
- end
61
- when "ModuleNode"
62
- module_name = visit_node node[:name]
63
- src.write_ln "module #{module_name}"
64
-
65
- current_namespace = RBS::Namespace.root # RBS::Namespace.new(path: [module_name.to_sym], absolute: false)
66
-
67
- module_type = RBS::AST::Declarations::Module.new(
68
- name: RBS::TypeName.new(
69
- namespace: current_namespace,
70
- name: module_name.to_sym
71
- ),
72
- type_params: RBS::AST::Declarations::ModuleTypeParams.new, # responds to #add to add params
73
- self_types: [],
74
- members: [],
75
- annotations: [],
76
- location: node[:location],
77
- comment: node[:comment]
78
- )
79
- old_parent_scope = @current_scope
80
- @current_scope = module_type
81
-
82
- indented(src) { src.write_ln visit_node(node[:body]) if node[:body] }
83
-
84
- @current_scope = old_parent_scope
85
- if @type_checker
86
- @type_checker.top_level_decls[module_type.name.name] = module_type unless @current_scope
87
- end
88
- src.write_ln "end"
89
- when "DefNode"
90
- args = render_args(node)
91
- src.write_ln "def #{node[:name]}#{args}"
92
-
93
- return_type = if node[:return_type]
94
- RBS::Types::ClassInstance.new(
95
- name: RBS::TypeName.new(
96
- name: eval(visit_node(node[:return_type])).to_s.to_sym,
97
- namespace: RBS::Namespace.root
98
- ),
99
- args: [],
100
- location: nil
101
- )
102
- else
103
- RBS::Types::Bases::Any.new(location: nil)
104
- end
105
-
106
- method_types = [
107
- RBS::MethodType.new(
108
- type_params: [],
109
- type: RBS::Types::Function.new(
110
- required_positionals: [],
111
- optional_positionals: [],
112
- rest_positionals: nil,
113
- trailing_positionals: [],
114
- required_keywords: {},
115
- optional_keywords: {},
116
- rest_keywords: node[:rest_kw_args] ?
117
- RBS::Types::Function::Param.new(
118
- name: visit_node(node[:rest_kw_args]).to_sym,
119
- type: RBS::Types::Bases::Any.new(location: nil)
120
- ) : nil,
121
- return_type: return_type
122
- ),
123
- block: nil,
124
- location: nil
125
- )
126
- ]
127
- method_definition = RBS::AST::Members::MethodDefinition.new(
128
- name: node[:name].to_sym,
129
- kind: :instance,
130
- types: method_types,
131
- annotations: [],
132
- location: node[:location],
133
- comment: node[:comment],
134
- overload: false
135
- )
136
-
137
- if @current_scope
138
- @current_scope.members << method_definition
139
- else
140
- @type_checker.type_env << method_definition if @type_checker # should be new class declaration for Object with method_definition as private method
141
- end
142
-
143
- indented(src) { src.write_ln visit_node(node[:body]) if node[:body] }
144
-
145
- src.write_ln "end"
146
- when "CollectionNode"
147
- src.write(*node[:children].map { |a| visit_node(a, scope) })
148
- when "Call"
149
- obj = node[:object] ? "#{visit_node(node[:object], scope)}." : ""
150
- args = node[:args] || EMPTY_ARRAY
151
- args = if !args.empty? || node[:block_arg]
152
- "(#{args.map { |a| visit_node(a, scope).strip }.reject(&:blank?).join(", ")}#{"&#{visit_node(node[:block_arg]).strip}" if node[:block_arg]})"
153
- else
154
- nil
155
- end
156
- block = node[:block] ? " #{visit_node(node[:block])}" : nil
157
- src.write_ln "#{obj}#{node[:name]}#{args}#{block}"
158
-
159
- when "Block"
160
-
161
- src.write "{ |#{node[:args].map { |a| visit_node a }.join(", ")}|\n"
162
-
163
- indented(src) { src.write visit_node(node[:body]) }
164
-
165
- src.write_ln "}"
166
-
167
- when "RangeLiteral"
168
- dots = node[:exclusive] ? "..." : ".."
169
-
170
- # parentheses help the compatibility with precendence of operators in some situations
171
- # eg. (1..3).cover? 2 vs. 1..3.cover? 2
172
- src.write "(", visit_node(node[:from]), dots, visit_node(node[:to]), ")"
173
-
174
- when "LiteralNode"
175
-
176
- src.write node[:value]
177
-
178
- when "ArrayLiteral"
179
-
180
- src.write("[", *node[:elements].map { |e| visit_node e }.join(", "), "]")
181
- src.write ".freeze" if node[:frozen]
182
-
183
- when "StringInterpolation"
184
-
185
- contents = node[:contents].inject(String.new) do |str, c|
186
- str << case c[:type]
187
- when "LiteralNode"
188
- c[:value][1...-1]
189
- else
190
- "\#{#{visit_node(c).strip}}"
191
- end
192
- end
193
- src.write '"', contents, '"'
194
-
195
- when "Path"
196
-
197
- src.write node[:value]
198
-
199
- when "Require"
200
-
201
- src.write_ln %(require "#{node[:value]}")
202
-
203
- when "Assign", "OpAssign"
204
-
205
- src.write_ln "#{visit_node(node[:target])} #{node[:op]}= #{visit_node(node[:value]).strip}"
206
-
207
- when "Var"
208
-
209
- if @eval_vars
210
- src.write scope[node[:name]]
211
- else
212
- src.write node[:name]
213
- end
214
-
215
- when "InstanceVar"
216
-
217
- src.write node[:name]
218
-
219
- when "GlobalVar"
220
-
221
- src.write node[:name]
222
-
223
- when "Arg"
224
- val = node[:external_name]
225
- if node[:keyword_arg]
226
- val += ":"
227
- val += " #{visit_node(node[:default_value])}" if node[:default_value]
228
- elsif node[:default_value]
229
- val += " = #{visit_node(node[:default_value])}"
230
- end
231
-
232
- src.write val
233
-
234
- when "UnaryExpr"
235
-
236
- src.write "#{node[:op]}#{visit_node(node[:value]).strip}"
237
-
238
- when "BinaryOp"
239
-
240
- src.write visit_node(node[:left]).strip, " #{node[:op]} ", visit_node(node[:right]).strip
241
-
242
- when "HashLiteral"
243
-
244
- contents = node[:elements].map do |k, v|
245
- key = case k
246
- when String
247
- k.to_sym
248
- else
249
- visit_node k
250
- end
251
- value = visit_node v
252
- "#{key.inspect} => #{value}"
253
- end
254
-
255
- src.write "{#{contents.join(",\n")}}"
256
- src.write ".freeze" if node[:frozen]
257
-
258
- when "Enum"
259
- src.write_ln "module #{node[:name]}"
260
- node[:members].each_with_index do |m, i|
261
- indented(src) { src.write_ln(visit_node(m) + (!m[:value] ? " = #{i}" : "")) }
262
- end
263
- src.write_ln "end"
264
- when "If"
265
- src.write_ln "(if #{visit_node(node[:condition]).strip}"
266
-
267
- indented(src) { src.write_ln visit_node(node[:then]) }
268
-
269
- if node[:else]
270
- src.write_ln "else"
271
- indented(src) { src.write_ln visit_node(node[:else]) }
272
- end
273
-
274
- src.write_ln "end)"
275
- when "Case"
276
- src.write "case"
277
- src.write " #{visit_node(node[:condition]).strip}\n" if node[:condition]
278
- indented(src) do
279
- node[:whens].each do |w|
280
- src.write_ln visit_node(w)
281
- end
282
- end
283
- src.write_ln "end"
284
- when "When"
285
- src.write_ln "when #{node[:conditions].map { |n| visit_node(n) }.join(", ")}"
286
-
287
- indented(src) { src.write_ln visit_node(node[:body]) }
288
- when "MacroFor"
289
- vars, expr, body = node[:vars], node[:expr], node[:body]
290
- var_names = vars.map { |v| visit_node v }
291
- @inside_macro = true
292
- indent_level = @indent_level
293
- @indent_level -= 1 unless indent_level.zero?
294
- expanded = eval(visit_node(expr)).map do |*a|
295
- locals = Hash[[var_names.join(%(", "))].zip(a)]
296
- locals.merge!(scope) if @inside_macro
297
- visit_node(body, locals)
298
- end.flatten
299
- @indent_level += 1 unless indent_level.zero?
300
- src.write(*expanded)
301
- @inside_macro = false
302
- when "MacroLiteral"
303
- src.write node[:value]
304
- when "MacroExpression"
305
- if node[:output]
306
- expr = visit_node node[:expr], scope
307
- val = scope[expr]
308
- src.write val
309
- end
310
- when "MacroIf"
311
- if evaluate_macro_condition(node[:condition], scope)
312
- src.write_ln visit_node(node[:then], scope) if node[:then]
313
- else
314
- src.write_ln visit_node(node[:else], scope) if node[:else]
315
- end
316
- when "Return"
317
- val = node[:value] ? " #{visit_node(node[:value]).strip}" : nil
318
- src.write "return#{val}"
319
- when "TypeDeclaration"
320
- src.write_ln "# @type var #{visit_node(node[:var])}: #{visit_node(node[:declared_type])}"
321
- src.write_ln "#{visit_node(node[:var])} = #{visit_node(node[:value])}"
322
- when "ExceptionHandler"
323
- src.write_ln "begin"
324
- src.write_ln visit_node(node[:body])
325
- node[:rescues]&.each do |r|
326
- src.write_ln "rescue #{r[:types].map { |n| visit_node n }.join(", ") if r[:types]}#{" => #{r[:name]}" if r[:name]}"
327
- src.write_ln visit_node(r[:body]) if r[:body]
328
- end
329
- if node[:else]
330
- src.write_ln "else"
331
- src.write_ln visit_node(node[:else])
332
- end
333
- if node[:ensure]
334
- src.write_ln "ensure"
335
- src.write_ln visit_node(node[:ensure])
336
- end
337
- src.write_ln "end"
338
- when "Generic"
339
- src.write "#{node[:name]}[#{node[:args].map { |a| visit_node a }.join(", ")}]"
340
- when "EmptyNode"
341
- # pass
342
- else
343
- raise "Not implemented: #{node[:type]}"
344
- end
345
-
346
- src
347
- end
348
-
349
- private
350
-
351
- def evaluate_macro_condition(condition_node, scope)
352
- @eval_vars = true
353
- eval(visit_node(condition_node, scope))
354
- @eval_vars = false
355
- end
356
-
357
- def indented(src)
358
- increment_indent(src)
359
- yield
360
- decrement_indent(src)
361
- end
362
-
363
- def increment_indent(src)
364
- @indent_level += 1
365
- src.increment_indent
366
- end
367
-
368
- def decrement_indent(src)
369
- @indent_level -= 1
370
- src.decrement_indent
371
- end
372
-
373
- def render_args(node)
374
- rp = node[:rp_args] || EMPTY_ARRAY
375
- op = node[:op_args] || EMPTY_ARRAY
376
- rkw = node[:req_kw_args] || EMPTY_HASH
377
- okw = node[:opt_kw_args] || EMPTY_HASH
378
- rest_p = node[:rest_p_args]
379
- rest_kw = node[:rest_kw_args]
380
- return nil unless [rp, op, rkw, okw, rest_p, rest_kw].any? { |a| !a.nil? || !a.empty? }
381
-
382
- contents = [
383
- rp.map { |a| visit_node(a) },
384
- op.map { |pos| "#{pos.name} = #{value}" },
385
- rkw.map { |name, _| "#{name}:" },
386
- okw.map { |name, _| "#{name}: #{value}" },
387
- rest_p ? "*#{rest_p}" : "",
388
- rest_kw ? "**#{visit_node(rest_kw)}" : ""
389
- ].reject(&:empty?).flatten.join(", ")
390
- "(#{contents})"
391
- end
392
- end
393
- end