jade-lang 0.9.0 → 0.10.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +442 -12
- data/README.md +1 -0
- data/docs/interop.md +18 -1
- data/docs/json.md +34 -3
- data/docs/stdlib.md +3 -1
- data/docs/syntax.md +96 -0
- data/lib/jade/api.rb +9 -0
- data/lib/jade/ast.rb +37 -1
- data/lib/jade/cli/eject.rb +132 -0
- data/lib/jade/cli/init.rb +86 -0
- data/lib/jade/cli.rb +4 -0
- data/lib/jade/codegen/boundary/specialized/list.rb +11 -7
- data/lib/jade/codegen/boundary/specialized/maybe.rb +2 -2
- data/lib/jade/codegen/boundary/specialized/record.rb +19 -4
- data/lib/jade/codegen/boundary/specialized/scalar.rb +2 -2
- data/lib/jade/codegen/boundary/specialized.rb +13 -5
- data/lib/jade/codegen/boundary.rb +12 -2
- data/lib/jade/codegen/context.rb +17 -0
- data/lib/jade/codegen/error.rb +1 -0
- data/lib/jade/codegen/function_call.rb +38 -7
- data/lib/jade/codegen/function_declaration.rb +25 -13
- data/lib/jade/codegen/helpers.rb +27 -4
- data/lib/jade/codegen/implementation.rb +2 -2
- data/lib/jade/codegen/inline.rb +1 -1
- data/lib/jade/codegen/inlines.rb +6 -1
- data/lib/jade/codegen/names.rb +31 -0
- data/lib/jade/codegen/pattern/constructor.rb +2 -2
- data/lib/jade/codegen/transforms/fold_shape.rb +1 -1
- data/lib/jade/codegen.rb +76 -15
- data/lib/jade/compiler.rb +2 -0
- data/lib/jade/entry.rb +1 -1
- data/lib/jade/formatter/calls.rb +68 -11
- data/lib/jade/formatter/collections.rb +4 -4
- data/lib/jade/formatter/declarations.rb +13 -15
- data/lib/jade/formatter/helper.rb +20 -0
- data/lib/jade/formatter/infix_application.rb +19 -4
- data/lib/jade/formatter/lambda.rb +24 -12
- data/lib/jade/formatter/pattern.rb +3 -0
- data/lib/jade/formatter/type.rb +3 -5
- data/lib/jade/frontend/desugaring/placeholder.rb +40 -17
- data/lib/jade/frontend/desugaring.rb +29 -9
- data/lib/jade/frontend/fixity_fixer.rb +6 -4
- data/lib/jade/frontend/forward_declaration/error/alias_expansion.rb +23 -0
- data/lib/jade/frontend/forward_declaration/error/duplicate_constructor_name.rb +45 -0
- data/lib/jade/frontend/forward_declaration/error/duplicate_type_name.rb +42 -0
- data/lib/jade/frontend/forward_declaration/error.rb +3 -0
- data/lib/jade/frontend/forward_declaration/helper.rb +58 -0
- data/lib/jade/frontend/forward_declaration/import_declaration.rb +4 -0
- data/lib/jade/frontend/forward_declaration/interface_declaration.rb +6 -0
- data/lib/jade/frontend/forward_declaration/module.rb +4 -0
- data/lib/jade/frontend/forward_declaration/struct_declaration.rb +10 -1
- data/lib/jade/frontend/forward_declaration/type_alias_declaration.rb +37 -0
- data/lib/jade/frontend/forward_declaration/type_declaration.rb +10 -1
- data/lib/jade/frontend/forward_declaration.rb +2 -0
- data/lib/jade/frontend/pattern_analysis/exhaustiveness.rb +47 -43
- data/lib/jade/frontend/pattern_analysis/matrix.rb +28 -219
- data/lib/jade/frontend/pattern_analysis/pattern.rb +132 -0
- data/lib/jade/frontend/pattern_analysis/redundancy.rb +24 -0
- data/lib/jade/frontend/pattern_analysis/signature.rb +220 -0
- data/lib/jade/frontend/pattern_analysis/usefulness.rb +72 -0
- data/lib/jade/frontend/pattern_analysis/witnesses.rb +73 -0
- data/lib/jade/frontend/pattern_analysis.rb +5 -38
- data/lib/jade/frontend/semantic_analysis/error/implementation_on_alias.rb +24 -0
- data/lib/jade/frontend/semantic_analysis/error/no_base_case.rb +45 -0
- data/lib/jade/frontend/semantic_analysis/error/placeholder_not_allowed.rb +15 -3
- data/lib/jade/frontend/semantic_analysis/error/recursive_type_alias.rb +24 -0
- data/lib/jade/frontend/semantic_analysis/error.rb +3 -0
- data/lib/jade/frontend/semantic_analysis/helper.rb +31 -0
- data/lib/jade/frontend/semantic_analysis/implementation.rb +13 -0
- data/lib/jade/frontend/semantic_analysis/inhabitedness.rb +90 -0
- data/lib/jade/frontend/semantic_analysis/interop_import_declaration.rb +28 -16
- data/lib/jade/frontend/semantic_analysis/keyed_call.rb +21 -5
- data/lib/jade/frontend/semantic_analysis/pattern_range.rb +16 -0
- data/lib/jade/frontend/semantic_analysis/struct_declaration.rb +1 -0
- data/lib/jade/frontend/semantic_analysis/type_alias_declaration/cycle_detection.rb +95 -0
- data/lib/jade/frontend/semantic_analysis/type_alias_declaration.rb +48 -0
- data/lib/jade/frontend/semantic_analysis/type_declaration.rb +1 -0
- data/lib/jade/frontend/semantic_analysis.rb +5 -0
- data/lib/jade/frontend/type_checking/canonicalize.rb +1 -0
- data/lib/jade/frontend/type_checking/cascade.rb +90 -0
- data/lib/jade/frontend/type_checking/constraints/deriving/encodable.rb +10 -0
- data/lib/jade/frontend/type_checking/constraints.rb +18 -3
- data/lib/jade/frontend/type_checking/error/display.rb +49 -0
- data/lib/jade/frontend/type_checking/error/division_by_zero.rb +31 -0
- data/lib/jade/frontend/type_checking/error/empty_range_pattern.rb +25 -0
- data/lib/jade/frontend/type_checking/error/function_body_type_mismatch.rb +4 -3
- data/lib/jade/frontend/type_checking/error/function_call_type_mismatch.rb +108 -5
- data/lib/jade/frontend/type_checking/error/if_branch_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/if_branches_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/if_condition_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/implementation_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/list_item_type_mismatch.rb +1 -1
- data/lib/jade/frontend/type_checking/error/missing_patterns.rb +20 -4
- data/lib/jade/frontend/type_checking/error/pattern_type_mismatch.rb +7 -1
- data/lib/jade/frontend/type_checking/error/range_pattern_type.rb +29 -0
- data/lib/jade/frontend/type_checking/error/record_access_type_mismatch.rb +2 -2
- data/lib/jade/frontend/type_checking/error/record_update_type_mismatch.rb +21 -0
- data/lib/jade/frontend/type_checking/error/recursive_derivation.rb +25 -0
- data/lib/jade/frontend/type_checking/error/type_mismatch.rb +10 -2
- data/lib/jade/frontend/type_checking/error/unreachable_branch.rb +22 -0
- data/lib/jade/frontend/type_checking/error.rb +7 -0
- data/lib/jade/frontend/type_checking/inference/assign.rb +1 -1
- data/lib/jade/frontend/type_checking/inference/case_of.rb +21 -4
- data/lib/jade/frontend/type_checking/inference/division.rb +50 -0
- data/lib/jade/frontend/type_checking/inference/function_call.rb +26 -1
- data/lib/jade/frontend/type_checking/inference/lambda.rb +1 -1
- data/lib/jade/frontend/type_checking/inference/pattern.rb +35 -0
- data/lib/jade/frontend/type_checking/inference/record_update.rb +8 -1
- data/lib/jade/frontend/type_checking/inference/type_alias_declaration.rb +18 -0
- data/lib/jade/frontend/type_checking/inference.rb +2 -0
- data/lib/jade/frontend/type_checking/loader.rb +11 -0
- data/lib/jade/frontend/type_checking/port_resolution.rb +26 -8
- data/lib/jade/frontend/type_checking/substitution.rb +2 -2
- data/lib/jade/frontend/type_checking.rb +3 -1
- data/lib/jade/frontend/usage_analysis.rb +5 -1
- data/lib/jade/interop/boundary.rb +64 -24
- data/lib/jade/interop/error.rb +35 -7
- data/lib/jade/interop/lowering.rb +8 -2
- data/lib/jade/lsp/converters.rb +83 -2
- data/lib/jade/lsp/handlers.rb +122 -25
- data/lib/jade/lsp/snippets.rb +12 -0
- data/lib/jade/lsp/state.rb +11 -4
- data/lib/jade/module_loader/build.rb +36 -0
- data/lib/jade/module_loader.rb +1 -0
- data/lib/jade/parsing/combinators.rb +78 -2
- data/lib/jade/parsing/error.rb +43 -6
- data/lib/jade/parsing/type.rb +6 -4
- data/lib/jade/parsing.rb +140 -26
- data/lib/jade/runtime.rb +32 -13
- data/lib/jade/source.rb +6 -2
- data/lib/jade/stdlib/basics.rb +22 -3
- data/lib/jade/stdlib/char.rb +2 -0
- data/lib/jade/stdlib/clock.rb +6 -2
- data/lib/jade/stdlib/decimal.rb +51 -14
- data/lib/jade/stdlib/decode.rb +11 -0
- data/lib/jade/stdlib/intrinsics.rb +59 -6
- data/lib/jade/stdlib/list.rb +37 -1
- data/lib/jade/stdlib/number.rb +47 -0
- data/lib/jade/stdlib/range.rb +211 -0
- data/lib/jade/stdlib.rb +11 -4
- data/lib/jade/symbol/alias.rb +11 -0
- data/lib/jade/symbol/parser.rb +1 -1
- data/lib/jade/symbol/stdlib_function.rb +1 -1
- data/lib/jade/symbol.rb +13 -1
- data/lib/jade/task.rb +0 -2
- data/lib/jade/type/anonymous_record.rb +11 -2
- data/lib/jade/type/application.rb +11 -2
- data/lib/jade/type/base.rb +26 -0
- data/lib/jade/type/function.rb +11 -3
- data/lib/jade/type.rb +36 -10
- data/lib/jade/version.rb +1 -1
- metadata +35 -2
|
@@ -16,6 +16,9 @@ module Jade
|
|
|
16
16
|
Inline.try_for(callee, args, dictionaries, registry)
|
|
17
17
|
.then { return it if it }
|
|
18
18
|
|
|
19
|
+
constrained_constant(callee, dictionaries, registry)
|
|
20
|
+
.then { return it if it }
|
|
21
|
+
|
|
19
22
|
return constructor_call(callee, args, registry) if constructor_callee?(callee, registry)
|
|
20
23
|
|
|
21
24
|
[generate_many(args, registry), generate_dict_args(callee, dictionaries, registry)]
|
|
@@ -24,6 +27,18 @@ module Jade
|
|
|
24
27
|
.then { "#{generate_callee(callee, args, registry, dictionaries)}#{invocation_op(callee, registry)}(#{it})" }
|
|
25
28
|
end
|
|
26
29
|
|
|
30
|
+
# A constrained constant — `Decode.decoder` — has no parameters, so its
|
|
31
|
+
# dictionary slot holds the value itself rather than something to call.
|
|
32
|
+
# generate_callee already produced that value; invoking it would be
|
|
33
|
+
# calling a Decoder.
|
|
34
|
+
def constrained_constant(callee, dictionaries, registry)
|
|
35
|
+
symbol = resolve_callee_symbol(callee, registry)
|
|
36
|
+
return nil unless symbol.is_a?(Symbol::StdlibFunction)
|
|
37
|
+
return nil unless symbol.params.empty? && symbol.constraints.any?
|
|
38
|
+
|
|
39
|
+
generate_callee(callee, [], registry, dictionaries)
|
|
40
|
+
end
|
|
41
|
+
|
|
27
42
|
def constructor_call(callee, args, registry)
|
|
28
43
|
resolve_callee_symbol(callee, registry)
|
|
29
44
|
.then { "::#{to_qualified(it.qualified_name)}" }
|
|
@@ -102,10 +117,25 @@ module Jade
|
|
|
102
117
|
Codegen.dict_env[[interface, id]]
|
|
103
118
|
|
|
104
119
|
in Symbol::Implementation
|
|
105
|
-
Pretty.hash(generate_impl_dispatch(entry, registry))
|
|
120
|
+
hoisted(Pretty.hash(generate_impl_dispatch(entry, registry)))
|
|
106
121
|
end
|
|
107
122
|
end
|
|
108
123
|
|
|
124
|
+
# A dictionary built only from concrete implementations is the same
|
|
125
|
+
# object on every call, so it is worth building once. One that names
|
|
126
|
+
# a dict parameter is not: that slot is the caller's, and differs per
|
|
127
|
+
# call. Outside a module there is nowhere to put a constant, so the
|
|
128
|
+
# literal stands.
|
|
129
|
+
#
|
|
130
|
+
# Keyed by source, which dedupes the same dictionary across every
|
|
131
|
+
# call site in the module.
|
|
132
|
+
def hoisted(source)
|
|
133
|
+
table = Codegen.dict_consts or return source
|
|
134
|
+
return source if Codegen.dict_env.values.any? { source.include?(it) }
|
|
135
|
+
|
|
136
|
+
table[source] ||= "DICT_#{table.size}"
|
|
137
|
+
end
|
|
138
|
+
|
|
109
139
|
# Polymorphic fn referenced as a value (not called). Wraps the fn with
|
|
110
140
|
# its dispatched dictionaries so the result is a monomorphic callable
|
|
111
141
|
# matching the type at the use site. Returns nil when the symbol
|
|
@@ -127,13 +157,13 @@ module Jade
|
|
|
127
157
|
.each_with_index
|
|
128
158
|
.filter_map { |c, i| dispatch_value(dictionaries[i], registry) if c.type.is_a?(Type::Var) }
|
|
129
159
|
.then { (param_names + it).join(', ') }
|
|
130
|
-
.then { "#{
|
|
160
|
+
.then { "#{internal(fn.module_name)}.#{fn_target_name(fn, registry)}(#{it})" }
|
|
131
161
|
.then { Pretty.lambda(param_names.join(', '), it) }
|
|
132
162
|
|
|
133
163
|
in Symbol::InterfaceFunction => fn
|
|
134
|
-
|
|
135
|
-
&.then { "#{it}[#{fn.name.inspect}]" } ||
|
|
164
|
+
dispatch_lookup(dictionaries.first, fn.name, registry) {
|
|
136
165
|
fail("no dict in scope to reference interface method `#{fn.qualified_name}` as a value")
|
|
166
|
+
}
|
|
137
167
|
|
|
138
168
|
else
|
|
139
169
|
nil
|
|
@@ -199,7 +229,7 @@ module Jade
|
|
|
199
229
|
generate_node(callee, registry)
|
|
200
230
|
|
|
201
231
|
in Symbol::Function => fn_sym
|
|
202
|
-
|
|
232
|
+
internal(fn_sym.module_name) + "." + fn_target_name(fn_sym, registry)
|
|
203
233
|
|
|
204
234
|
in Symbol::StdlibImplementation => symbol
|
|
205
235
|
dictionaries
|
|
@@ -323,10 +353,10 @@ module Jade
|
|
|
323
353
|
# 0-arg fn: Ruby auto-invokes on bare reference. Result is the
|
|
324
354
|
# dispatch-slot value (decoder, encoder template, ...) ready to use.
|
|
325
355
|
in Symbol::Function => fn if fn.constant?
|
|
326
|
-
"#{
|
|
356
|
+
"#{internal(fn.module_name)}.#{fn.name}"
|
|
327
357
|
|
|
328
358
|
in Symbol::Function => fn
|
|
329
|
-
"#{
|
|
359
|
+
"#{internal(fn.module_name)}.method(:#{fn.name})"
|
|
330
360
|
end
|
|
331
361
|
end
|
|
332
362
|
|
|
@@ -334,6 +364,7 @@ module Jade
|
|
|
334
364
|
dep_dispatches
|
|
335
365
|
.map { it.is_a?(String) ? it : Pretty.hash(it) }
|
|
336
366
|
.then { Pretty.array(it) }
|
|
367
|
+
.then { hoisted(it) }
|
|
337
368
|
end
|
|
338
369
|
|
|
339
370
|
# Stdlib intrinsics implementation language.
|
|
@@ -20,12 +20,12 @@ module Jade
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
args, return_type = Type.signature(fn_type)
|
|
23
|
-
param_names = params.map(
|
|
23
|
+
param_names = params.map { Names.local(it.name) }
|
|
24
24
|
|
|
25
25
|
if task_return?(return_type)
|
|
26
|
-
task_wrapper_pair(name, args, param_names, return_type, registry)
|
|
26
|
+
task_wrapper_pair(name, args, param_names, return_type, registry, symbol)
|
|
27
27
|
else
|
|
28
|
-
eligible_wrapper(name, args, param_names, return_type, registry)
|
|
28
|
+
eligible_wrapper(name, args, param_names, return_type, registry, symbol)
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
@@ -58,8 +58,8 @@ module Jade
|
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
def eligible_wrapper(name, args, param_names, return_type, registry)
|
|
62
|
-
decoded_args(args, param_names, registry)
|
|
61
|
+
def eligible_wrapper(name, args, param_names, return_type, registry, symbol)
|
|
62
|
+
decoded_args(args, param_names, registry, name, symbol)
|
|
63
63
|
.then { Pretty.call("Internal.#{name}", it) }
|
|
64
64
|
.then { encode_return(return_type, it, registry) }
|
|
65
65
|
.then { Pretty.block(boundary_def_header(name, param_names), it) }
|
|
@@ -69,14 +69,26 @@ module Jade
|
|
|
69
69
|
"def self.#{name}(#{param_names.join(', ')})"
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
-
def decoded_args(args, param_names, registry)
|
|
73
|
-
args
|
|
72
|
+
def decoded_args(args, param_names, registry, name, symbol)
|
|
73
|
+
args
|
|
74
|
+
.zip(param_names)
|
|
75
|
+
.map do |t, pname|
|
|
76
|
+
decode_call(t, pname, registry, "#{to_qualified(symbol.module_name)}.#{name}(#{pname})")
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def named(expr, where)
|
|
81
|
+
"Jade::Interop::Boundary.arg(#{where.inspect}) { #{expr} }"
|
|
74
82
|
end
|
|
75
83
|
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
# A specialized decoder carries the argument name itself, in a
|
|
85
|
+
# constant it was going to pass anyway. Only the generic path needs a
|
|
86
|
+
# frame around it to say where the value came from.
|
|
87
|
+
def decode_call(arg_type, pname, registry, where)
|
|
88
|
+
Codegen::Boundary::Specialized.decode_expr(arg_type, pname, registry, where) ||
|
|
78
89
|
Codegen::Boundary::Cache.decoder_for(arg_type, registry)
|
|
79
90
|
.then { "Jade::Interop::Boundary.decode_or_raise(#{it}, #{pname})" }
|
|
91
|
+
.then { named(it, where) }
|
|
80
92
|
end
|
|
81
93
|
|
|
82
94
|
def encode_return(return_type, call_expr, registry)
|
|
@@ -92,17 +104,17 @@ module Jade
|
|
|
92
104
|
end
|
|
93
105
|
end
|
|
94
106
|
|
|
95
|
-
def task_wrapper_pair(name, args, param_names, task_return, registry)
|
|
107
|
+
def task_wrapper_pair(name, args, param_names, task_return, registry, symbol)
|
|
96
108
|
ok_enc, err_enc = Codegen::Boundary::Cache.task_arms(task_return, registry)
|
|
97
109
|
|
|
98
110
|
[
|
|
99
|
-
task_run_def(name, args, param_names, ok_enc, err_enc, registry),
|
|
111
|
+
task_run_def(name, args, param_names, ok_enc, err_enc, registry, symbol),
|
|
100
112
|
task_bang_def(name, param_names),
|
|
101
113
|
].join(Pretty.newline(2))
|
|
102
114
|
end
|
|
103
115
|
|
|
104
|
-
def task_run_def(name, args, param_names, ok_enc, err_enc, registry)
|
|
105
|
-
decoded_args(args, param_names, registry)
|
|
116
|
+
def task_run_def(name, args, param_names, ok_enc, err_enc, registry, symbol)
|
|
117
|
+
decoded_args(args, param_names, registry, name, symbol)
|
|
106
118
|
.then { Pretty.call("Internal.#{name}", it) }
|
|
107
119
|
.then { task_run_body(it, ok_enc, err_enc) }
|
|
108
120
|
.then { Pretty.block(boundary_def_header(name, param_names), it) }
|
data/lib/jade/codegen/helpers.rb
CHANGED
|
@@ -16,8 +16,19 @@ module Jade
|
|
|
16
16
|
Stdlib.stdlib_name?(module_name) ? "Jade::#{base}" : base
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
# Inside `module Plan`, a bare `Plan::` resolves to whatever `Plan`
|
|
20
|
+
# names there, which may be a type. `module X` is the only name that
|
|
21
|
+
# cannot be rooted.
|
|
22
|
+
def rooted(module_name)
|
|
23
|
+
"::#{to_qualified(module_name)}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def internal(module_name)
|
|
27
|
+
"#{rooted(module_name)}::Internal"
|
|
28
|
+
end
|
|
29
|
+
|
|
19
30
|
def data_define(fields)
|
|
20
|
-
return "
|
|
31
|
+
return "Jade.nullary" if fields.empty?
|
|
21
32
|
|
|
22
33
|
"Data.define(#{fields.map { ":#{it}" }.join(', ')})"
|
|
23
34
|
end
|
|
@@ -44,8 +55,14 @@ module Jade
|
|
|
44
55
|
end
|
|
45
56
|
|
|
46
57
|
def impl_synthetic_name(interface, type_name, fn_name)
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
sanitized_name(fn_name)
|
|
59
|
+
.then { "__impl_#{interface}_#{type_name}_#{it}__" }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# `contains?` is a fine Jade name and not a fine Ruby one anywhere but
|
|
63
|
+
# the end of a method name, which is not where these put it.
|
|
64
|
+
def sanitized_name(name)
|
|
65
|
+
name.gsub(/[^a-zA-Z0-9_]/) { |c| "x#{c.ord.to_s(16)}" }
|
|
49
66
|
end
|
|
50
67
|
|
|
51
68
|
def dict_synthetic_name(index)
|
|
@@ -53,7 +70,8 @@ module Jade
|
|
|
53
70
|
end
|
|
54
71
|
|
|
55
72
|
def fn_impl_synthetic_name(name)
|
|
56
|
-
|
|
73
|
+
sanitized_name(name)
|
|
74
|
+
.then { "__#{it}__impl__" }
|
|
57
75
|
end
|
|
58
76
|
|
|
59
77
|
def fn_constraints(fn_symbol, registry)
|
|
@@ -101,6 +119,11 @@ module Jade
|
|
|
101
119
|
|
|
102
120
|
in Symbol::Interface
|
|
103
121
|
[]
|
|
122
|
+
|
|
123
|
+
in other
|
|
124
|
+
raise(UnrepresentableType, "`#{qname}` has no runtime form " \
|
|
125
|
+
"(#{other.class}); this is a jade bug, " \
|
|
126
|
+
'please report it')
|
|
104
127
|
end
|
|
105
128
|
end
|
|
106
129
|
|
|
@@ -83,7 +83,7 @@ module Jade
|
|
|
83
83
|
impl_sym
|
|
84
84
|
.functions[fn_name]
|
|
85
85
|
.then { it.is_a?(Symbol::ValueRef) ? it : nil }
|
|
86
|
-
&.then { "
|
|
86
|
+
&.then { "#{internal(it.module_name)}.#{it.name}" }
|
|
87
87
|
&.then { Pretty.block("def #{ruby_method}(other)", "#{it}(self, other)") }
|
|
88
88
|
end
|
|
89
89
|
end
|
|
@@ -143,7 +143,7 @@ module Jade
|
|
|
143
143
|
fn_map = symbol.functions.filter_map { |fn_name, ref|
|
|
144
144
|
next unless ref.is_a?(Symbol::ValueRef)
|
|
145
145
|
|
|
146
|
-
[fn_name, "->(*args) {
|
|
146
|
+
[fn_name, "->(*args) { #{internal(ref.module_name)}.#{ref.name}(*args) }"]
|
|
147
147
|
}.to_h
|
|
148
148
|
|
|
149
149
|
return "" if fn_map.empty?
|
data/lib/jade/codegen/inline.rb
CHANGED
data/lib/jade/codegen/inlines.rb
CHANGED
|
@@ -5,7 +5,9 @@ module Jade
|
|
|
5
5
|
|
|
6
6
|
# Ruby's native ordering on these types matches LT/EQ/GT semantics, so
|
|
7
7
|
# the derived comparison ops can collapse to plain operators.
|
|
8
|
-
RUBY_NATIVE_COMPARES = %w[
|
|
8
|
+
RUBY_NATIVE_COMPARES = %w[
|
|
9
|
+
Basics.int_compare Basics.float_compare String.str_compare Char.char_compare
|
|
10
|
+
].to_set.freeze
|
|
9
11
|
RUBY_NATIVE_EQS = %w[Basics.int_eq Basics.float_eq Basics.bool_eq String.str_eq].to_set.freeze
|
|
10
12
|
|
|
11
13
|
DERIVED_COMPARISONS = {
|
|
@@ -32,6 +34,7 @@ module Jade
|
|
|
32
34
|
'Basics.int_mul' => ->(a, b) { "(#{a} * #{b})" },
|
|
33
35
|
'Basics.int_div' => ->(a, b) { "(#{a} / #{b})" },
|
|
34
36
|
'Basics.mod' => ->(a, b) { "(#{a} % #{b})" },
|
|
37
|
+
'Number.unwrap' => ->(n) { n },
|
|
35
38
|
'Basics.float_add' => ->(a, b) { "(#{a} + #{b})" },
|
|
36
39
|
'Basics.float_sub' => ->(a, b) { "(#{a} - #{b})" },
|
|
37
40
|
'Basics.float_mul' => ->(a, b) { "(#{a} * #{b})" },
|
|
@@ -113,6 +116,7 @@ module Jade
|
|
|
113
116
|
'Char.upper?' => ->(c) { "#{c}.match?(/[A-Z]/)" },
|
|
114
117
|
'Char.lower?' => ->(c) { "#{c}.match?(/[a-z]/)" },
|
|
115
118
|
'Char.char_eq' => ->(a, b) { "(#{a} == #{b})" },
|
|
119
|
+
'Char.char_compare' => ->(a, b) { "#{a}.compare(#{b})" },
|
|
116
120
|
|
|
117
121
|
'Tuple.pair' => ->(a, b) { "Jade::Tuple::Tuple2[#{a}, #{b}]" },
|
|
118
122
|
'Tuple.first' => ->(t) { "#{t}._1" },
|
|
@@ -173,6 +177,7 @@ module Jade
|
|
|
173
177
|
|
|
174
178
|
# Bodies that don't fit a single Ruby expression.
|
|
175
179
|
NO_INLINE = %w[
|
|
180
|
+
Number.checked
|
|
176
181
|
List.sort_with
|
|
177
182
|
List.sort_by_with
|
|
178
183
|
List.filter_map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Codegen
|
|
3
|
+
# Ruby rejects its keywords as local variables and parameters, but accepts
|
|
4
|
+
# them as method names and Data members — so only bindings are rewritten
|
|
5
|
+
# here, and nothing a Ruby caller sees changes.
|
|
6
|
+
module Names
|
|
7
|
+
extend self
|
|
8
|
+
|
|
9
|
+
RESERVED = ::Set[
|
|
10
|
+
'BEGIN', 'END', '__ENCODING__', '__FILE__', '__LINE__',
|
|
11
|
+
'alias', 'and', 'begin', 'break', 'case', 'class', 'def',
|
|
12
|
+
'defined?', 'do', 'else', 'elsif', 'end', 'ensure', 'false',
|
|
13
|
+
'for', 'if', 'in', 'module', 'next', 'nil', 'not', 'or', 'redo',
|
|
14
|
+
'rescue', 'retry', 'return', 'self', 'super', 'then', 'true',
|
|
15
|
+
'undef', 'unless', 'until', 'when', 'while', 'yield',
|
|
16
|
+
].freeze
|
|
17
|
+
|
|
18
|
+
# Injective: a name that is a reserved word under trailing underscores
|
|
19
|
+
# gains one more, so `begin` and `begin_` stay distinct.
|
|
20
|
+
def local(name)
|
|
21
|
+
reserved_stem?(name) ? "#{name}_" : name
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def reserved_stem?(name)
|
|
27
|
+
RESERVED.include?(name.sub(/_+\z/, ''))
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -8,7 +8,7 @@ module Jade
|
|
|
8
8
|
def generate(node, registry)
|
|
9
9
|
node => AST::Pattern::Constructor(symbol:, patterns:)
|
|
10
10
|
constructor = registry.lookup(symbol)
|
|
11
|
-
qualified =
|
|
11
|
+
qualified = rooted(constructor.qualified_name)
|
|
12
12
|
|
|
13
13
|
if keyed_variant?(constructor)
|
|
14
14
|
generate_keyed(qualified, patterns.first, registry)
|
|
@@ -39,7 +39,7 @@ module Jade
|
|
|
39
39
|
def generate_keyed(qualified, pattern, registry)
|
|
40
40
|
case pattern
|
|
41
41
|
in AST::Pattern::Binding(name:)
|
|
42
|
-
"#{qualified} => #{name}"
|
|
42
|
+
"#{qualified} => #{Names.local(name)}"
|
|
43
43
|
|
|
44
44
|
in AST::Pattern::Wildcard
|
|
45
45
|
qualified
|
data/lib/jade/codegen.rb
CHANGED
|
@@ -3,6 +3,7 @@ require 'jade/codegen/context'
|
|
|
3
3
|
require 'jade/codegen/helpers'
|
|
4
4
|
require 'jade/codegen/pretty'
|
|
5
5
|
require 'jade/codegen/method_names'
|
|
6
|
+
require 'jade/codegen/names'
|
|
6
7
|
require 'jade/codegen/inlines'
|
|
7
8
|
require 'jade/codegen/inline'
|
|
8
9
|
require 'jade/codegen/boundary'
|
|
@@ -83,10 +84,13 @@ module Jade
|
|
|
83
84
|
def generate(node, registry, depth: 0)
|
|
84
85
|
case node
|
|
85
86
|
in AST::Module(name:, body:)
|
|
87
|
+
reopened = reopened_type(name, registry)
|
|
88
|
+
|
|
86
89
|
preamble = [
|
|
87
90
|
"require 'jade/runtime'",
|
|
88
91
|
*Stdlib.requires(name),
|
|
89
92
|
*namespace_setup_lines(name),
|
|
93
|
+
*reopening_require(reopened, name),
|
|
90
94
|
].reject(&:empty?).join(Pretty.newline)
|
|
91
95
|
|
|
92
96
|
shape_consts = collect_record_shapes(body)
|
|
@@ -100,11 +104,15 @@ module Jade
|
|
|
100
104
|
boundary_helpers = Boundary::Specialized.collect_helpers(body, registry)
|
|
101
105
|
.then { Boundary::Specialized.emit_helpers(it, registry) }
|
|
102
106
|
|
|
107
|
+
dict_consts = {}
|
|
108
|
+
|
|
103
109
|
outer, inner, wrappers =
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
110
|
+
with_dict_consts(dict_consts) do
|
|
111
|
+
with_boundary_cache(boundary_cache) do
|
|
112
|
+
with_dispatched_methods(collect_dispatched_methods(body, registry)) do
|
|
113
|
+
with_hoisted_records do
|
|
114
|
+
partition_module_body(body.expressions, registry, name.count('.'))
|
|
115
|
+
end
|
|
108
116
|
end
|
|
109
117
|
end
|
|
110
118
|
end
|
|
@@ -114,17 +122,18 @@ module Jade
|
|
|
114
122
|
.then { Pretty.block("module Internal", it) }
|
|
115
123
|
|
|
116
124
|
[
|
|
117
|
-
"extend self",
|
|
125
|
+
(reopened ? "" : "extend self"),
|
|
118
126
|
*shape_consts,
|
|
119
127
|
*outer,
|
|
120
128
|
inner_module,
|
|
129
|
+
*referenced_dicts(dict_consts, [*outer, *inner, *wrappers]),
|
|
121
130
|
*boundary_consts,
|
|
122
131
|
*boundary_helpers,
|
|
123
132
|
*wrappers,
|
|
124
133
|
]
|
|
125
134
|
.reject(&:empty?)
|
|
126
135
|
.join(Pretty.newline(2))
|
|
127
|
-
.then { Pretty.block("module #{to_qualified(name)}", it) }
|
|
136
|
+
.then { Pretty.block("#{reopened ? 'class' : 'module'} #{to_qualified(name)}", it) }
|
|
128
137
|
.then { [preamble, it].reject(&:empty?).join(Pretty.newline(2)) }
|
|
129
138
|
|
|
130
139
|
in AST::ImportDeclaration(module_name:)
|
|
@@ -138,7 +147,11 @@ module Jade
|
|
|
138
147
|
end
|
|
139
148
|
|
|
140
149
|
in AST::InteropImportDeclaration(module: mod)
|
|
141
|
-
Pretty.block(
|
|
150
|
+
Pretty.block(
|
|
151
|
+
"begin",
|
|
152
|
+
"require '#{mod.name.gsub('::', '/').downcase}'",
|
|
153
|
+
"rescue LoadError\nend",
|
|
154
|
+
)
|
|
142
155
|
|
|
143
156
|
in AST::Implementation
|
|
144
157
|
Implementation.generate(node, registry)
|
|
@@ -166,17 +179,17 @@ module Jade
|
|
|
166
179
|
codegen
|
|
167
180
|
|
|
168
181
|
in Symbol::Function => fn
|
|
169
|
-
"#{
|
|
182
|
+
"#{internal(fn.module_name)}.method(:#{fn.name})"
|
|
170
183
|
|
|
171
184
|
else
|
|
172
|
-
name
|
|
185
|
+
Names.local(name)
|
|
173
186
|
end
|
|
174
187
|
end
|
|
175
188
|
|
|
176
189
|
in AST::Assign(pattern:, expression:)
|
|
177
190
|
case pattern
|
|
178
191
|
in AST::Pattern::Binding(name:)
|
|
179
|
-
"#{name} = #{generate(expression, registry)}"
|
|
192
|
+
"#{Names.local(name)} = #{generate(expression, registry)}"
|
|
180
193
|
in AST::Pattern::Wildcard
|
|
181
194
|
generate(expression, registry)
|
|
182
195
|
else
|
|
@@ -193,7 +206,7 @@ module Jade
|
|
|
193
206
|
FunctionDeclaration.generate(node, registry)
|
|
194
207
|
|
|
195
208
|
in AST::FunctionDeclarationParam(name:)
|
|
196
|
-
name
|
|
209
|
+
Names.local(name)
|
|
197
210
|
|
|
198
211
|
in AST::FunctionCall
|
|
199
212
|
FunctionCall.generate(node, registry)
|
|
@@ -209,6 +222,9 @@ module Jade
|
|
|
209
222
|
in AST::StructDeclaration(name:, record_type:, symbol:)
|
|
210
223
|
data_define_with_methods(name, record_type.fields.keys, "::#{to_qualified(symbol.qualified_name)}")
|
|
211
224
|
|
|
225
|
+
in AST::TypeAliasDeclaration
|
|
226
|
+
""
|
|
227
|
+
|
|
212
228
|
in AST::InterfaceDeclaration
|
|
213
229
|
""
|
|
214
230
|
|
|
@@ -221,7 +237,7 @@ module Jade
|
|
|
221
237
|
codegen
|
|
222
238
|
|
|
223
239
|
in Symbol::Function => fn
|
|
224
|
-
"#{
|
|
240
|
+
"#{internal(fn.module_name)}.method(:#{fn.name})"
|
|
225
241
|
|
|
226
242
|
in Symbol::Constructor => sym
|
|
227
243
|
ConstructorReference.from_symbol(sym)
|
|
@@ -254,11 +270,14 @@ module Jade
|
|
|
254
270
|
in AST::Pattern::Literal(literal:)
|
|
255
271
|
generate(literal, registry)
|
|
256
272
|
|
|
273
|
+
in AST::Pattern::Range(from:, to:)
|
|
274
|
+
"#{from&.value}..#{to&.value}"
|
|
275
|
+
|
|
257
276
|
in AST::Pattern::Wildcard
|
|
258
277
|
"_"
|
|
259
278
|
|
|
260
279
|
in AST::Pattern::Binding(name:)
|
|
261
|
-
name
|
|
280
|
+
Names.local(name)
|
|
262
281
|
|
|
263
282
|
in AST::Pattern::Record(fields:)
|
|
264
283
|
generate_many(fields, registry)
|
|
@@ -270,7 +289,7 @@ module Jade
|
|
|
270
289
|
in AST::Pattern::List(patterns:, rest:)
|
|
271
290
|
rest_part =
|
|
272
291
|
case rest
|
|
273
|
-
in AST::Pattern::Binding(name:) then ["*#{name}"]
|
|
292
|
+
in AST::Pattern::Binding(name:) then ["*#{Names.local(name)}"]
|
|
274
293
|
in AST::Pattern::Wildcard then ["*"]
|
|
275
294
|
in nil then []
|
|
276
295
|
end
|
|
@@ -325,7 +344,7 @@ module Jade
|
|
|
325
344
|
|
|
326
345
|
def param_name(pattern, index = 0)
|
|
327
346
|
case pattern
|
|
328
|
-
in AST::Pattern::Binding(name:) then name
|
|
347
|
+
in AST::Pattern::Binding(name:) then Names.local(name)
|
|
329
348
|
in AST::Pattern::Wildcard then '_'
|
|
330
349
|
else
|
|
331
350
|
param_synthetic_name(index)
|
|
@@ -340,6 +359,48 @@ module Jade
|
|
|
340
359
|
"#{'../' * current_depth}#{import_path}"
|
|
341
360
|
end
|
|
342
361
|
|
|
362
|
+
# Emission generates some expressions only to discard them (a decoder
|
|
363
|
+
# weighed against its specialized form, say), and a discarded one can
|
|
364
|
+
# still have claimed a constant. Keep the ones that survived, and the
|
|
365
|
+
# ones those refer to.
|
|
366
|
+
def referenced_dicts(table, body)
|
|
367
|
+
reachable_dicts(table, body.join(Pretty.newline))
|
|
368
|
+
.map { |source, const| "#{const} = #{source}.freeze" }
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# A kept dictionary can name another one, so widen the search text
|
|
372
|
+
# with what was kept until nothing new turns up.
|
|
373
|
+
def reachable_dicts(table, code, kept = {})
|
|
374
|
+
table
|
|
375
|
+
.select { |_, const| code.match?(/\b#{const}\b/) }
|
|
376
|
+
.then do |found|
|
|
377
|
+
next found if found.size == kept.size
|
|
378
|
+
|
|
379
|
+
reachable_dicts(table, [code, *found.keys].join(Pretty.newline), found)
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# A module whose name is already a type in its parent shares one Ruby
|
|
384
|
+
# constant with it, so it reopens that class rather than defining a
|
|
385
|
+
# module beside it and clobbering whichever loads second.
|
|
386
|
+
def reopened_type(name, registry)
|
|
387
|
+
*parent, last = name.split('.')
|
|
388
|
+
return nil if parent.empty?
|
|
389
|
+
|
|
390
|
+
registry
|
|
391
|
+
.get(parent.join('.'))
|
|
392
|
+
&.then { it.defined_types.key?(last) ? it : nil }
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# Before the class body, not inside it: an import writes its require
|
|
396
|
+
# within the body, by which point `class` has already made a plain
|
|
397
|
+
# class for the constant and the type will clobber it on load.
|
|
398
|
+
def reopening_require(entry, name)
|
|
399
|
+
return [] if entry.nil? || Stdlib.is_stdlib?(entry)
|
|
400
|
+
|
|
401
|
+
["require_relative '#{relative_require(entry.path, name.count('.'))}'"]
|
|
402
|
+
end
|
|
403
|
+
|
|
343
404
|
def namespace_setup_lines(name)
|
|
344
405
|
parts = name.split('.')
|
|
345
406
|
(0...parts.length - 1)
|
data/lib/jade/compiler.rb
CHANGED
|
@@ -7,6 +7,7 @@ module Jade
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def require(path)
|
|
10
|
+
ModuleLoader::Build.discard_foreign(build_root)
|
|
10
11
|
target = File.expand_path("#{build_root}/#{path}.rb", config.project_root)
|
|
11
12
|
|
|
12
13
|
if needs_rebuild?(target)
|
|
@@ -14,6 +15,7 @@ module Jade
|
|
|
14
15
|
.load(config.source_root, path + '.jd', cache_dir: cache_root)
|
|
15
16
|
.tap { render_diagnostics(it) }
|
|
16
17
|
.then { ModuleLoader.emit(it, path: build_root) }
|
|
18
|
+
.then { ModuleLoader::Build.stamp(build_root) }
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
Kernel.require(File.realpath(target))
|
data/lib/jade/entry.rb
CHANGED