gloss 0.0.1

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.
@@ -0,0 +1,26 @@
1
+ require "./lib/gloss/version"
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'gloss'
5
+ s.version = Gloss::VERSION
6
+ s.licenses = %w[MIT]
7
+ s.summary = "A superset of ruby"
8
+ s.description =
9
+ "A rich language which compiles to ruby. Including type annotations, type checking, macros, annotations, enums and more"
10
+ s.authors = %w[johansenja]
11
+ s.extensions = %w[ext/gloss/extconf.rb]
12
+ s.files = Dir.chdir(File.expand_path('..', __FILE__)) do
13
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
14
+ end
15
+ s.bindir = "exe"
16
+ s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
17
+
18
+ s.add_runtime_dependency "fast_blank"
19
+ s.add_runtime_dependency "listen"
20
+ s.add_runtime_dependency "steep"
21
+
22
+ s.add_development_dependency "rake-compiler"
23
+ s.add_development_dependency "rspec"
24
+ s.add_development_dependency "pry-byebug"
25
+ s.add_development_dependency "rubocop"
26
+ end
Binary file
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbs"
4
+ require "json"
5
+ require "steep"
6
+ require "fast_blank"
7
+
8
+ require "gloss/version"
9
+ require "gloss/cli"
10
+ require "gloss/watcher"
11
+ require "gloss/initializer"
12
+ require "gloss/config"
13
+ require "gloss/writer"
14
+ require "gloss/source"
15
+ require "gloss/scope"
16
+ require "gloss/builder"
17
+ require "gloss/errors"
18
+
19
+ require "gloss.bundle"
20
+
21
+ EMPTY_ARRAY = [].freeze
22
+ EMPTY_HASH = {}.freeze
@@ -0,0 +1,442 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gloss
4
+ class Builder
5
+ attr_reader :tree
6
+
7
+ def initialize(str)
8
+ @indent_level = 0
9
+ @inside_macro = false
10
+ @eval_vars = false
11
+ tree_json = Gloss.parse_buffer str
12
+ begin
13
+ @tree = JSON.parse tree_json, symbolize_names: true
14
+ rescue JSON::ParserError
15
+ raise Errors::ParserError, tree_json
16
+ end
17
+ @current_scope = nil
18
+ @steep_target = Steep::Project::Target.new(
19
+ name: "gloss",
20
+ options: Steep::Project::Options.new,
21
+ source_patterns: ["gloss"],
22
+ ignore_patterns: [],
23
+ signature_patterns: []
24
+ )
25
+ Dir.glob("sig/**/*.rbs").each do |fp|
26
+ next if !@steep_target.possible_signature_file?(fp) || @steep_target.signature_file?(fp)
27
+
28
+ Steep.logger.info { "Adding signature file: #{fp}" }
29
+ @steep_target.add_signature path, (Pathname(".") + fp).cleanpath.read
30
+ end
31
+ @top_level_decls = {}
32
+ end
33
+
34
+ def run
35
+ rb_output = visit_node(@tree)
36
+ rb_output = "# frozen_string_literal: true\n#{rb_output}" if Config.frozen_string_literals
37
+
38
+ unless check_types(rb_output)
39
+ raise Errors::TypeError,
40
+ @steep_target.errors.map { |e|
41
+ case e
42
+ when Steep::Errors::NoMethod
43
+ "Unknown method :#{e.method}, location: #{e.type.location.inspect}"
44
+ when Steep::Errors::MethodBodyTypeMismatch
45
+ "Invalid method body type - expected: #{e.expected}, actual: #{e.actual}"
46
+ when Steep::Errors::IncompatibleArguments
47
+ "Invalid argmuents - method type: #{e.method_type}, receiver type: #{e.receiver_type}"
48
+ when Steep::Errors::ReturnTypeMismatch
49
+ "Invalid return type - expected: #{e.expected}, actual: #{e.actual}"
50
+ when Steep::Errors::IncompatibleAssignment
51
+ "Invalid assignment - cannot assign #{e.rhs_type} to type #{e.lhs_type}"
52
+ else
53
+ e.inspect
54
+ end
55
+ }.join("\n")
56
+ end
57
+
58
+ rb_output
59
+ end
60
+
61
+ def check_types(rb_str)
62
+ env_loader = RBS::EnvironmentLoader.new
63
+ env = RBS::Environment.from_loader(env_loader)
64
+
65
+ @top_level_decls.each do |_, decl|
66
+ env << decl
67
+ end
68
+ env = env.resolve_type_names
69
+
70
+ @steep_target.instance_variable_set("@environment", env)
71
+ @steep_target.add_source("gloss", rb_str)
72
+
73
+ definition_builder = RBS::DefinitionBuilder.new(env: env)
74
+ factory = Steep::AST::Types::Factory.new(builder: definition_builder)
75
+ check = Steep::Subtyping::Check.new(factory: factory)
76
+ validator = Steep::Signature::Validator.new(checker: check)
77
+ validator.validate
78
+
79
+ raise Errors::TypeValidationError, validator.each_error.to_a.join("\n") unless validator.no_error?
80
+
81
+ @steep_target.run_type_check(env, check, Time.now)
82
+
83
+ @steep_target.status.is_a?(Steep::Project::Target::TypeCheckStatus) &&
84
+ @steep_target.no_error? &&
85
+ @steep_target.errors.empty?
86
+ end
87
+
88
+ def visit_node(node, scope = Scope.new)
89
+ src = Source.new(@indent_level)
90
+ case node[:type]
91
+ when "ClassNode"
92
+ class_name = visit_node(node[:name])
93
+ superclass = if node[:superclass]
94
+ @eval_vars = true
95
+ visit_node(node[:superclass])
96
+ @eval_vars = false
97
+ else
98
+ nil
99
+ end
100
+
101
+ src.write_ln "class #{class_name}#{" < #{superclass}" if superclass}"
102
+
103
+ current_namespace = @current_scope ? @current_scope.name.to_namespace : RBS::Namespace.root
104
+ class_type = RBS::AST::Declarations::Class.new(
105
+ name: RBS::TypeName.new(
106
+ namespace: current_namespace,
107
+ name: class_name.to_sym
108
+ ),
109
+ type_params: RBS::AST::Declarations::ModuleTypeParams.new, # responds to #add to add params
110
+ 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,
111
+ members: [],
112
+ annotations: [],
113
+ location: node[:location],
114
+ comment: node[:comment]
115
+ )
116
+ old_parent_scope = @current_scope
117
+ @current_scope = class_type
118
+
119
+ indented(src) { src.write_ln visit_node(node[:body]) if node[:body] }
120
+
121
+ src.write_ln "end"
122
+
123
+ @current_scope = old_parent_scope
124
+ @top_level_decls[class_type.name.name] = class_type unless @current_scope
125
+ when "ModuleNode"
126
+ module_name = visit_node node[:name]
127
+ src.write_ln "module #{module_name}"
128
+
129
+ current_namespace = RBS::Namespace.root # RBS::Namespace.new(path: [module_name.to_sym], absolute: false)
130
+
131
+ module_type = RBS::AST::Declarations::Module.new(
132
+ name: RBS::TypeName.new(
133
+ namespace: current_namespace,
134
+ name: module_name.to_sym
135
+ ),
136
+ type_params: RBS::AST::Declarations::ModuleTypeParams.new, # responds to #add to add params
137
+ self_types: [],
138
+ members: [],
139
+ annotations: [],
140
+ location: node[:location],
141
+ comment: node[:comment]
142
+ )
143
+ old_parent_scope = @current_scope
144
+ @current_scope = module_type
145
+
146
+ indented(src) { src.write_ln visit_node(node[:body]) if node[:body] }
147
+
148
+ @current_scope = old_parent_scope
149
+ @top_level_decls[module_type.name.name] = module_type unless @current_scope
150
+ src.write_ln "end"
151
+ when "DefNode"
152
+ args = render_args(node)
153
+ src.write_ln "def #{node[:name]}#{args}"
154
+
155
+ return_type = if node[:return_type]
156
+ RBS::Types::ClassInstance.new(
157
+ name: RBS::TypeName.new(
158
+ name: eval(visit_node(node[:return_type])).to_s.to_sym,
159
+ namespace: RBS::Namespace.root
160
+ ),
161
+ args: [],
162
+ location: nil
163
+ )
164
+ else
165
+ RBS::Types::Bases::Any.new(location: nil)
166
+ end
167
+
168
+ method_types = [
169
+ RBS::MethodType.new(
170
+ type_params: [],
171
+ type: RBS::Types::Function.new(
172
+ required_positionals: [],
173
+ optional_positionals: [],
174
+ rest_positionals: nil,
175
+ trailing_positionals: [],
176
+ required_keywords: {},
177
+ optional_keywords: {},
178
+ rest_keywords: node[:rest_kw_args] ?
179
+ RBS::Types::Function::Param.new(
180
+ name: visit_node(node[:rest_kw_args]).to_sym,
181
+ type: RBS::Types::Bases::Any.new(location: nil)
182
+ ) : nil,
183
+ return_type: return_type
184
+ ),
185
+ block: nil,
186
+ location: nil
187
+ )
188
+ ]
189
+ method_definition = RBS::AST::Members::MethodDefinition.new(
190
+ name: node[:name].to_sym,
191
+ kind: :instance,
192
+ types: method_types,
193
+ annotations: [],
194
+ location: node[:location],
195
+ comment: node[:comment],
196
+ overload: false
197
+ )
198
+
199
+ if @current_scope
200
+ @current_scope.members << method_definition
201
+ else
202
+ @type_env << method_definition # should be new class declaration for Object with method_definition as private method
203
+ end
204
+
205
+ indented(src) { src.write_ln visit_node(node[:body]) if node[:body] }
206
+
207
+ src.write_ln "end"
208
+ when "CollectionNode"
209
+ src.write(*node[:children].map { |a| visit_node(a, scope) })
210
+ when "Call"
211
+ obj = node[:object] ? "#{visit_node(node[:object], scope)}." : ""
212
+ args = node[:args] || EMPTY_ARRAY
213
+ args = if !args.empty? || node[:block_arg]
214
+ "(#{args.map { |a| visit_node(a, scope).strip }.reject(&:blank?).join(", ")}#{"&#{visit_node(node[:block_arg]).strip}" if node[:block_arg]})"
215
+ else
216
+ nil
217
+ end
218
+ block = node[:block] ? " #{visit_node(node[:block])}" : nil
219
+ src.write_ln "#{obj}#{node[:name]}#{args}#{block}"
220
+
221
+ when "Block"
222
+
223
+ src.write "{ |#{node[:args].map { |a| visit_node a }.join(", ")}|\n"
224
+
225
+ indented(src) { src.write visit_node(node[:body]) }
226
+
227
+ src.write_ln "}"
228
+
229
+ when "RangeLiteral"
230
+ dots = node[:exclusive] ? "..." : ".."
231
+
232
+ # parentheses help the compatibility with precendence of operators in some situations
233
+ # eg. (1..3).cover? 2 vs. 1..3.cover? 2
234
+ src.write "(", visit_node(node[:from]), dots, visit_node(node[:to]), ")"
235
+
236
+ when "LiteralNode"
237
+
238
+ src.write node[:value]
239
+
240
+ when "ArrayLiteral"
241
+
242
+ src.write("[", *node[:elements].map { |e| visit_node e }.join(", "), "]")
243
+ src.write ".freeze" if node[:frozen]
244
+
245
+ when "StringInterpolation"
246
+
247
+ contents = node[:contents].inject(String.new) do |str, c|
248
+ str << case c[:type]
249
+ when "LiteralNode"
250
+ c[:value][1...-1]
251
+ else
252
+ "\#{#{visit_node(c).strip}}"
253
+ end
254
+ end
255
+ src.write '"', contents, '"'
256
+
257
+ when "Path"
258
+
259
+ src.write node[:value]
260
+
261
+ when "Require"
262
+
263
+ src.write_ln %(require "#{node[:value]}")
264
+
265
+ when "Assign", "OpAssign"
266
+
267
+ src.write_ln "#{visit_node(node[:target])} #{node[:op]}= #{visit_node(node[:value]).strip}"
268
+
269
+ when "Var"
270
+
271
+ if @eval_vars
272
+ src.write scope[node[:name]]
273
+ else
274
+ src.write node[:name]
275
+ end
276
+
277
+ when "InstanceVar"
278
+
279
+ src.write node[:name]
280
+
281
+ when "Arg"
282
+
283
+ src.write node[:external_name]
284
+
285
+ when "UnaryExpr"
286
+
287
+ src.write "#{node[:op]}#{visit_node(node[:value]).strip}"
288
+
289
+ when "BinaryOp"
290
+
291
+ src.write visit_node(node[:left]).strip, " #{node[:op]} ", visit_node(node[:right]).strip
292
+
293
+ when "HashLiteral"
294
+
295
+ contents = node[:elements].map do |k, v|
296
+ key = case k
297
+ when String
298
+ k.to_sym
299
+ else
300
+ visit_node k
301
+ end
302
+ value = visit_node v
303
+ "#{key.inspect} => #{value}"
304
+ end
305
+
306
+ src.write "{#{contents.join(",\n")}}"
307
+ src.write ".freeze" if node[:frozen]
308
+
309
+ when "Enum"
310
+ src.write_ln "module #{node[:name]}"
311
+ node[:members].each_with_index do |m, i|
312
+ indented(src) { src.write_ln(visit_node(m) + (!m[:value] ? " = #{i}" : "")) }
313
+ end
314
+ src.write_ln "end"
315
+ when "If"
316
+ src.write_ln "(if #{visit_node(node[:condition]).strip}"
317
+
318
+ indented(src) { src.write_ln visit_node(node[:then]) }
319
+
320
+ if node[:else]
321
+ src.write_ln "else"
322
+ indented(src) { src.write_ln visit_node(node[:else]) }
323
+ end
324
+
325
+ src.write_ln "end)"
326
+ when "Case"
327
+ src.write "case"
328
+ src.write " #{visit_node(node[:condition]).strip}\n" if node[:condition]
329
+ indented(src) do
330
+ node[:whens].each do |w|
331
+ src.write_ln visit_node(w)
332
+ end
333
+ end
334
+ src.write_ln "end"
335
+ when "When"
336
+ src.write_ln "when #{node[:conditions].map { |n| visit_node(n) }.join(", ")}"
337
+
338
+ indented(src) { src.write_ln visit_node(node[:body]) }
339
+ when "MacroFor"
340
+ vars, expr, body = node[:vars], node[:expr], node[:body]
341
+ var_names = vars.map { |v| visit_node v }
342
+ @inside_macro = true
343
+ indent_level = @indent_level
344
+ @indent_level -= 1 unless indent_level.zero?
345
+ expanded = eval(visit_node(expr)).map do |*a|
346
+ locals = Hash[[var_names.join(%(", "))].zip(a)]
347
+ locals.merge!(scope) if @inside_macro
348
+ visit_node(body, locals)
349
+ end.flatten
350
+ @indent_level += 1 unless indent_level.zero?
351
+ src.write(*expanded)
352
+ @inside_macro = false
353
+ when "MacroLiteral"
354
+ src.write node[:value]
355
+ when "MacroExpression"
356
+ if node[:output]
357
+ expr = visit_node node[:expr], scope
358
+ val = scope[expr]
359
+ src.write val
360
+ end
361
+ when "MacroIf"
362
+ if evaluate_macro_condition(node[:condition], scope)
363
+ src.write_ln visit_node(node[:then], scope) if node[:then]
364
+ else
365
+ src.write_ln visit_node(node[:else], scope) if node[:else]
366
+ end
367
+ when "Return"
368
+ val = node[:value] ? " #{visit_node(node[:value]).strip}" : nil
369
+ src.write "return#{val}"
370
+ when "TypeDeclaration"
371
+ src.write_ln "# @type var #{visit_node(node[:var])}: #{visit_node(node[:declared_type])}"
372
+ src.write_ln "#{visit_node(node[:var])} = #{visit_node(node[:value])}"
373
+ when "ExceptionHandler"
374
+ src.write_ln "begin"
375
+ src.write_ln visit_node(node[:body])
376
+ node[:rescues]&.each do |r|
377
+ src.write_ln "rescue #{r[:types].map { |n| visit_node n }.join(", ") if r[:types]}#{" => #{r[:name]}" if r[:name]}"
378
+ src.write_ln visit_node(r[:body]) if r[:body]
379
+ end
380
+ if node[:else]
381
+ src.write_ln "else"
382
+ src.write_ln visit_node(node[:else])
383
+ end
384
+ if node[:ensure]
385
+ src.write_ln "ensure"
386
+ src.write_ln visit_node(node[:ensure])
387
+ end
388
+ src.write_ln "end"
389
+ when "EmptyNode"
390
+ # pass
391
+ else
392
+ raise "Not implemented: #{node[:type]}"
393
+ end
394
+
395
+ src
396
+ end
397
+
398
+ private
399
+
400
+ def evaluate_macro_condition(condition_node, scope)
401
+ @eval_vars = true
402
+ eval(visit_node(condition_node, scope))
403
+ @eval_vars = false
404
+ end
405
+
406
+ def indented(src)
407
+ increment_indent(src)
408
+ yield
409
+ decrement_indent(src)
410
+ end
411
+
412
+ def increment_indent(src)
413
+ @indent_level += 1
414
+ src.increment_indent
415
+ end
416
+
417
+ def decrement_indent(src)
418
+ @indent_level -= 1
419
+ src.decrement_indent
420
+ end
421
+
422
+ def render_args(node)
423
+ rp = node[:rp_args] || EMPTY_ARRAY
424
+ op = node[:op_args] || EMPTY_ARRAY
425
+ rkw = node[:req_kw_args] || EMPTY_HASH
426
+ okw = node[:opt_kw_args] || EMPTY_HASH
427
+ rest_p = node[:rest_p_args]
428
+ rest_kw = node[:rest_kw_args]
429
+ return nil unless [rp, op, rkw, okw, rest_p, rest_kw].any? { |a| !a.nil? || !a.empty? }
430
+
431
+ contents = [
432
+ rp.map { |a| visit_node(a) },
433
+ op.map { |pos| "#{pos.name} = #{value}" },
434
+ rkw.map { |name, _| "#{name}:" },
435
+ okw.map { |name, _| "#{name}: #{value}" },
436
+ rest_p ? "*#{rest_p}" : "",
437
+ rest_kw ? "**#{visit_node(rest_kw)}" : ""
438
+ ].reject(&:empty?).flatten.join(", ")
439
+ "(#{contents})"
440
+ end
441
+ end
442
+ end