gloss 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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