jade-lang 0.3.0 → 0.4.0
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 +91 -1
- data/README.md +12 -11
- data/lib/jade/codegen/function_call.rb +1 -1
- data/lib/jade/codegen/inlines.rb +13 -0
- data/lib/jade/codegen/{port_decoder.rb → port_codec.rb} +27 -15
- data/lib/jade/codegen.rb +7 -3
- data/lib/jade/debug.rb +59 -0
- data/lib/jade/diagnostics/renderer.rb +16 -5
- data/lib/jade/frontend/forward_declaration/interop_import_declaration.rb +13 -3
- data/lib/jade/frontend/pattern_analysis/matrix.rb +57 -23
- data/lib/jade/frontend/semantic_analysis/constructor_reference.rb +33 -7
- data/lib/jade/frontend/semantic_analysis/error/constructor_not_found.rb +20 -5
- data/lib/jade/frontend/type_checking/constraints/deriving/decodable.rb +25 -17
- data/lib/jade/frontend/type_checking/constraints/deriving/encodable.rb +25 -34
- data/lib/jade/frontend/type_checking/constraints/deriving/eq.rb +51 -175
- data/lib/jade/frontend/type_checking/constraints/deriving/helpers.rb +133 -0
- data/lib/jade/frontend/type_checking/constraints/deriving/show.rb +186 -0
- data/lib/jade/frontend/type_checking/constraints/deriving.rb +2 -1
- data/lib/jade/frontend/type_checking/env.rb +3 -0
- data/lib/jade/frontend/type_checking/error/port_not_encodable.rb +38 -0
- data/lib/jade/frontend/type_checking/port_resolution.rb +64 -16
- data/lib/jade/interop/runtime.rb +10 -2
- data/lib/jade/module_loader.rb +27 -3
- data/lib/jade/parsing/error.rb +19 -0
- data/lib/jade/runtime.rb +1 -0
- data/lib/jade/stdlib/debug.rb +12 -0
- data/lib/jade/stdlib/decode.rb +22 -0
- data/lib/jade/stdlib/encode.rb +17 -0
- data/lib/jade/stdlib/intrinsics.rb +3 -0
- data/lib/jade/stdlib/show.rb +40 -0
- data/lib/jade/stdlib.rb +5 -2
- data/lib/jade/symbol/interop_function.rb +3 -1
- data/lib/jade/symbol.rb +2 -2
- data/lib/jade/version.rb +1 -1
- metadata +8 -3
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module TypeChecking
|
|
4
|
+
module Constraints
|
|
5
|
+
module Deriving
|
|
6
|
+
module Show
|
|
7
|
+
extend self
|
|
8
|
+
include Helpers
|
|
9
|
+
|
|
10
|
+
INTERFACE = 'Show.Show'
|
|
11
|
+
|
|
12
|
+
def supports?(interface) = interface == INTERFACE
|
|
13
|
+
|
|
14
|
+
def derive(constraint, registry, entry_name, &lookup)
|
|
15
|
+
resolve_constraint(constraint, registry, entry_name, lookup)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
# List has no variants to walk, so it is matched by name here rather
|
|
21
|
+
# than declared as an instance in the Show module. That is a stopgap:
|
|
22
|
+
# a parameterised stdlib instance needs a registration path the DSL
|
|
23
|
+
# does not have. Encodable hardcodes List and Maybe for the same
|
|
24
|
+
# reason. Contained because derivation is private — see
|
|
25
|
+
# vault jade/plans/jade-test-runner.md for the four layers involved.
|
|
26
|
+
LIST = 'List.List'
|
|
27
|
+
|
|
28
|
+
def special_case(constraint, lookup)
|
|
29
|
+
case constraint.type
|
|
30
|
+
in Type::Function
|
|
31
|
+
Ok[implementation(constraint, { 'show' => constant('<function>') })]
|
|
32
|
+
|
|
33
|
+
in Type::Application(constructor: Type::Constructor(name: LIST), args: [inner])
|
|
34
|
+
list_show(constraint, inner, lookup)
|
|
35
|
+
|
|
36
|
+
else
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def list_show(constraint, inner, lookup)
|
|
42
|
+
lookup
|
|
43
|
+
.call(Type.constraint(INTERFACE, inner, constraint.origin))
|
|
44
|
+
.and_then do |dep|
|
|
45
|
+
body = concat([
|
|
46
|
+
'[',
|
|
47
|
+
[:call, [:stdlib_fn, 'String.join'], [
|
|
48
|
+
[:call, [:stdlib_fn, 'List.map'], [[:var, 'list'], [:impl_arg, 0, 'show']]],
|
|
49
|
+
', ',
|
|
50
|
+
]],
|
|
51
|
+
']',
|
|
52
|
+
])
|
|
53
|
+
|
|
54
|
+
Symbol::DerivedFunction
|
|
55
|
+
.new(params: ['list'], body:)
|
|
56
|
+
.then { Ok[implementation(constraint, { 'show' => it }, deps: [dep])] }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def constant(text)
|
|
61
|
+
Symbol::DerivedFunction.new(params: ['value'], body: text)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def concat(parts)
|
|
65
|
+
[:call, [:stdlib_fn, 'String.concat'], [[:list, parts]]]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def shown(dict_index, expr)
|
|
69
|
+
[:call, [:impl_arg, dict_index, 'show'], [expr]]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def derive_union(constraint, symbol, registry, lookup, entry_name)
|
|
73
|
+
type_vars = symbol.type_params.map(&:name)
|
|
74
|
+
index_map = type_vars.each_with_index.map.to_h
|
|
75
|
+
variants = symbol.variants.map { registry.lookup(it) }
|
|
76
|
+
|
|
77
|
+
concrete = variants
|
|
78
|
+
.flat_map(&:args)
|
|
79
|
+
.reject { it in Symbol::Variable }
|
|
80
|
+
.map { instantiate(it, {}, registry) }
|
|
81
|
+
.uniq
|
|
82
|
+
|
|
83
|
+
return failed(constraint, entry_name) if variants.empty?
|
|
84
|
+
|
|
85
|
+
concrete
|
|
86
|
+
.map { lookup.call(Type.constraint(INTERFACE, it, constraint.origin)) }
|
|
87
|
+
.then { Results.sequence(it) }
|
|
88
|
+
.and_then do |deps|
|
|
89
|
+
cases = variants.map {
|
|
90
|
+
variant_case(it, index_map, concrete, registry)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
show_fn = Symbol::DerivedFunction.new(
|
|
94
|
+
params: ['value'],
|
|
95
|
+
body: [:case, [:var, 'value'], cases],
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
Ok[union_impl(constraint, type_vars, concrete, show_fn, deps)]
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def union_impl(constraint, type_vars, concrete, show_fn, deps)
|
|
103
|
+
return implementation(constraint, { 'show' => show_fn }, deps:) if type_vars.empty?
|
|
104
|
+
|
|
105
|
+
Symbol::ImplementationTemplate.new(
|
|
106
|
+
interface: Symbol.type_ref_from_qualified_name(constraint.interface),
|
|
107
|
+
type: constraint.type,
|
|
108
|
+
type_params: type_vars.map { Type.var(it) },
|
|
109
|
+
constraints: union_constraints(constraint, type_vars, concrete),
|
|
110
|
+
functions: { 'show' => show_fn },
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def variant_case(variant, index_map, concrete, registry)
|
|
115
|
+
vars = (0...variant.args.length).map { |i| "a#{i}" }
|
|
116
|
+
name = variant.qualified_name.split('.').last
|
|
117
|
+
pattern = [:constructor, variant.qualified_name, vars]
|
|
118
|
+
|
|
119
|
+
return [pattern, [name]] if vars.empty?
|
|
120
|
+
|
|
121
|
+
rendered = variant.args.each_with_index.map do |arg_type, i|
|
|
122
|
+
idx =
|
|
123
|
+
case arg_type
|
|
124
|
+
in Symbol::Variable(name: var_name) then index_map[var_name]
|
|
125
|
+
else
|
|
126
|
+
index_map.size + concrete.index(instantiate(arg_type, {}, registry))
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
shown(idx, [:var, vars[i]])
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
parts = [name, '('] + intersperse(rendered, ', ') + [')']
|
|
133
|
+
|
|
134
|
+
[pattern, [concat(parts)]]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def intersperse(items, separator)
|
|
138
|
+
items.flat_map { [separator, it] }.drop(1)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def derive_struct(constraint, struct_sym, type_args, registry, lookup, entry_name)
|
|
142
|
+
fields = struct_fields(struct_sym, type_args, registry)
|
|
143
|
+
name = struct_sym.qualified_name.split('.').last
|
|
144
|
+
|
|
145
|
+
resolve_field_deps(fields.map { |_, type| type }, lookup, constraint.origin)
|
|
146
|
+
.and_then do |deps|
|
|
147
|
+
rendered = fields.each_with_index.map { |(field, _), idx|
|
|
148
|
+
[:call, [:stdlib_fn, 'String.concat'], [[:list, [
|
|
149
|
+
"#{field}: ",
|
|
150
|
+
shown(idx, [:access, [:var, 'value'], field.to_s]),
|
|
151
|
+
]]]]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
parts = [name, ' { '] + intersperse(rendered, ', ') + [' }']
|
|
155
|
+
|
|
156
|
+
Ok[record_impl(constraint, concat(parts), deps)]
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def derive_record(constraint, fields, lookup)
|
|
161
|
+
resolve_field_deps(fields.values, lookup, constraint.origin)
|
|
162
|
+
.and_then do |deps|
|
|
163
|
+
rendered = fields.keys.each_with_index.map { |field, idx|
|
|
164
|
+
[:call, [:stdlib_fn, 'String.concat'], [[:list, [
|
|
165
|
+
"#{field}: ",
|
|
166
|
+
shown(idx, [:access, [:var, 'value'], field.to_s]),
|
|
167
|
+
]]]]
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
parts = ['{ '] + intersperse(rendered, ', ') + [' }']
|
|
171
|
+
|
|
172
|
+
Ok[record_impl(constraint, concat(parts), deps)]
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def record_impl(constraint, body, deps)
|
|
177
|
+
Symbol::DerivedFunction
|
|
178
|
+
.new(params: ['value'], body:)
|
|
179
|
+
.then { implementation(constraint, { 'show' => it }, deps:) }
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -3,6 +3,7 @@ require_relative './deriving/eq.rb'
|
|
|
3
3
|
require_relative './deriving/decodable.rb'
|
|
4
4
|
require_relative './deriving/encodable.rb'
|
|
5
5
|
require_relative './deriving/sql_mapper.rb'
|
|
6
|
+
require_relative './deriving/show.rb'
|
|
6
7
|
|
|
7
8
|
module Jade
|
|
8
9
|
module Frontend
|
|
@@ -11,7 +12,7 @@ module Jade
|
|
|
11
12
|
module Deriving
|
|
12
13
|
extend self
|
|
13
14
|
|
|
14
|
-
DERIVERS = [Eq, Decodable, Encodable, SqlMapper]
|
|
15
|
+
DERIVERS = [Eq, Show, Decodable, Encodable, SqlMapper]
|
|
15
16
|
|
|
16
17
|
def derivable?(interface)
|
|
17
18
|
DERIVERS.any? { it.supports?(interface) }
|
|
@@ -53,6 +53,9 @@ module Jade
|
|
|
53
53
|
in Placeholder => placeholder
|
|
54
54
|
Scheme[placeholder.free_vars, placeholder.type, placeholder.constraints]
|
|
55
55
|
.then { Instantiation.instantiate(it, var_gen) }
|
|
56
|
+
|
|
57
|
+
in nil
|
|
58
|
+
fail "no type binding for `#{key}`"
|
|
56
59
|
end
|
|
57
60
|
|
|
58
61
|
Result.init(type, constraints)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Jade
|
|
2
|
+
module Frontend
|
|
3
|
+
module TypeChecking
|
|
4
|
+
module Error
|
|
5
|
+
class PortNotEncodable < Jade::Error
|
|
6
|
+
attr_reader :port_name, :position, :type
|
|
7
|
+
|
|
8
|
+
def initialize(entry, span, port_name:, position:, type:)
|
|
9
|
+
@port_name = port_name
|
|
10
|
+
@position = position
|
|
11
|
+
@type = type
|
|
12
|
+
super(entry:, span:)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def message
|
|
16
|
+
"Port `#{@port_name}` cannot encode argument #{@position} (`#{@type}`): " \
|
|
17
|
+
"no Encodable instance"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def label
|
|
21
|
+
"no Encodable instance for `#{@type}`"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def notes
|
|
25
|
+
[
|
|
26
|
+
Jade::Diagnostics::Annotation[
|
|
27
|
+
:help,
|
|
28
|
+
"implement Encodable for `#{@type}` so it can be encoded on the " \
|
|
29
|
+
"way out, or declare the argument as `Decode.Value` to hand " \
|
|
30
|
+
"Ruby the value untouched",
|
|
31
|
+
],
|
|
32
|
+
]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -2,15 +2,18 @@ require 'jade/type'
|
|
|
2
2
|
require 'jade/frontend/type_checking/constraints'
|
|
3
3
|
require 'jade/frontend/type_checking/var_gen'
|
|
4
4
|
require 'jade/frontend/type_checking/error/port_not_decodable'
|
|
5
|
+
require 'jade/frontend/type_checking/error/port_not_encodable'
|
|
5
6
|
|
|
6
7
|
module Jade
|
|
7
8
|
module Frontend
|
|
8
9
|
module TypeChecking
|
|
9
|
-
# Resolves the
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
10
|
+
# Resolves the instances each port needs to convert at the boundary:
|
|
11
|
+
# Decode.Decodable for its ok/err arms, Encode.Encodable for its
|
|
12
|
+
# arguments. Runs at the end of type-checking, when
|
|
13
|
+
# registry.implementations is fully populated. The resolved
|
|
14
|
+
# Symbol::Implementation (or the :pass sentinel for Decode.Value / Never)
|
|
15
|
+
# is stamped onto each InteropFunction's `decoders` / `encoders` fields
|
|
16
|
+
# so codegen can emit straight away.
|
|
14
17
|
module PortResolution
|
|
15
18
|
extend self
|
|
16
19
|
|
|
@@ -46,28 +49,61 @@ module Jade
|
|
|
46
49
|
def resolve_port(interop_fn, entry, registry)
|
|
47
50
|
interop_fn.return_type => Symbol::TypeApplication(args: [ok_sym, err_sym])
|
|
48
51
|
|
|
49
|
-
# Single Type.from_symbol
|
|
50
|
-
#
|
|
51
|
-
# those ids to build the call-site synthetic dict_env.
|
|
52
|
+
# Single Type.from_symbol over the whole port so a var appearing in
|
|
53
|
+
# more than one position gets the same Type::Var id. PortCodec relies
|
|
54
|
+
# on those ids to build the call-site synthetic dict_env.
|
|
52
55
|
Type
|
|
53
|
-
.from_symbol(interop_fn
|
|
54
|
-
.first => Type::Application(args: [ok_type, err_type])
|
|
56
|
+
.from_symbol(port_symbol(interop_fn), registry, VarGen.new)
|
|
57
|
+
.first => Type::Function(args: param_types, return_type: Type::Application(args: [ok_type, err_type]))
|
|
55
58
|
|
|
56
59
|
ok, ok_errors = resolve_arm(ok_sym, ok_type, interop_fn, :ok, entry, registry)
|
|
57
60
|
err, err_errors = resolve_arm(err_sym, err_type, interop_fn, :err, entry, registry)
|
|
61
|
+
encoders, param_errors = resolve_params(interop_fn, param_types, entry, registry)
|
|
58
62
|
|
|
59
63
|
[
|
|
60
|
-
interop_fn.with(decoders: { ok:, err: }),
|
|
61
|
-
ok_errors + err_errors,
|
|
64
|
+
interop_fn.with(decoders: { ok:, err: }, encoders:),
|
|
65
|
+
ok_errors + err_errors + param_errors,
|
|
62
66
|
]
|
|
63
67
|
end
|
|
64
68
|
|
|
69
|
+
# Without the constraints the shared VarGen would still hand every
|
|
70
|
+
# position its own var; `from_symbol` only ties them together through
|
|
71
|
+
# the map it threads across params and return.
|
|
72
|
+
def port_symbol(interop_fn)
|
|
73
|
+
Symbol.function_type(interop_fn.params, interop_fn.return_type)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def resolve_params(interop_fn, param_types, entry, registry)
|
|
77
|
+
interop_fn
|
|
78
|
+
.params
|
|
79
|
+
.zip(param_types)
|
|
80
|
+
.each_with_index
|
|
81
|
+
.map { |(sym, type), index| resolve_param(sym, type, interop_fn, index, entry, registry) }
|
|
82
|
+
.then { |results| [results.map(&:first), results.flat_map(&:last)] }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def resolve_param(type_sym, type, interop_fn, index, entry, registry)
|
|
86
|
+
return [Symbol::InteropFunction::PASS, []] if pass_through?(type_sym)
|
|
87
|
+
|
|
88
|
+
case type
|
|
89
|
+
in Type::Var(name:)
|
|
90
|
+
constraint_index_for(interop_fn, 'Encode.Encodable', name)
|
|
91
|
+
.then { [Symbol::InteropFunction::Dict.new(constraint_index: it), []] }
|
|
92
|
+
|
|
93
|
+
else
|
|
94
|
+
Type
|
|
95
|
+
.constraint('Encode.Encodable', type, nil)
|
|
96
|
+
.then { Constraints.resolve(it, registry, entry.name) }
|
|
97
|
+
.then { encoder_result(it, interop_fn, index, entry, type, span_of(type_sym, interop_fn)) }
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
65
101
|
def resolve_arm(type_sym, type, interop_fn, arm, entry, registry)
|
|
66
102
|
return [Symbol::InteropFunction::PASS, []] if pass_through?(type_sym)
|
|
67
103
|
|
|
68
104
|
case type
|
|
69
105
|
in Type::Var(name:)
|
|
70
|
-
constraint_index_for(interop_fn, name)
|
|
106
|
+
constraint_index_for(interop_fn, 'Decode.Decodable', name)
|
|
71
107
|
.then { [Symbol::InteropFunction::Dict.new(constraint_index: it), []] }
|
|
72
108
|
|
|
73
109
|
else
|
|
@@ -81,11 +117,11 @@ module Jade
|
|
|
81
117
|
end
|
|
82
118
|
end
|
|
83
119
|
|
|
84
|
-
def constraint_index_for(interop_fn, var_name)
|
|
120
|
+
def constraint_index_for(interop_fn, interface, var_name)
|
|
85
121
|
interop_fn
|
|
86
122
|
.constraints
|
|
87
|
-
.index { |
|
|
88
|
-
.tap { fail "no
|
|
123
|
+
.index { |iface, name| iface == interface && name == var_name }
|
|
124
|
+
.tap { fail "no #{interface} constraint for #{var_name.inspect}" if it.nil? }
|
|
89
125
|
end
|
|
90
126
|
|
|
91
127
|
def decoder_result(constraint_result, interop_fn, arm, entry, type, span)
|
|
@@ -100,6 +136,18 @@ module Jade
|
|
|
100
136
|
end
|
|
101
137
|
end
|
|
102
138
|
|
|
139
|
+
def encoder_result(constraint_result, interop_fn, index, entry, type, span)
|
|
140
|
+
case constraint_result
|
|
141
|
+
in Ok[impl]
|
|
142
|
+
[impl, []]
|
|
143
|
+
|
|
144
|
+
in Err
|
|
145
|
+
Error::PortNotEncodable
|
|
146
|
+
.new(entry, span, port_name: interop_fn.name, position: index + 1, type:)
|
|
147
|
+
.then { [nil, [it]] }
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
103
151
|
def span_of(type_sym, interop_fn)
|
|
104
152
|
case type_sym
|
|
105
153
|
in Symbol::TypeApplication(span:) then span
|
data/lib/jade/interop/runtime.rb
CHANGED
|
@@ -3,7 +3,7 @@ require 'jade/interop/error'
|
|
|
3
3
|
module Jade
|
|
4
4
|
module Interop
|
|
5
5
|
module Runtime
|
|
6
|
-
def task_call(interop_module_name, function_name, ok_decoder, err_decoder)
|
|
6
|
+
def task_call(interop_module_name, function_name, ok_decoder, err_decoder, arg_encoders)
|
|
7
7
|
->(*args) do
|
|
8
8
|
interop_module_name
|
|
9
9
|
.send(function_name)
|
|
@@ -12,13 +12,21 @@ module Jade
|
|
|
12
12
|
fail(Interop::PortNotRegistered.new(interop_module_name, function_name))
|
|
13
13
|
|
|
14
14
|
Jade::Task::Decoded.new(
|
|
15
|
-
Jade::Task::Dispatch.new(port, args),
|
|
15
|
+
Jade::Task::Dispatch.new(port, encode_args(args, arg_encoders)),
|
|
16
16
|
ok_decoder,
|
|
17
17
|
err_decoder,
|
|
18
18
|
)
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
|
+
|
|
23
|
+
# Ports are the boundary in the other direction: what Ruby gets handed is
|
|
24
|
+
# encoded the same way a return value is decoded on the way back.
|
|
25
|
+
def encode_args(args, encoders)
|
|
26
|
+
args
|
|
27
|
+
.each_with_index
|
|
28
|
+
.map { |arg, i| encoders.fetch(i).call(arg) }
|
|
29
|
+
end
|
|
22
30
|
end
|
|
23
31
|
end
|
|
24
32
|
end
|
data/lib/jade/module_loader.rb
CHANGED
|
@@ -51,13 +51,37 @@ module Jade
|
|
|
51
51
|
.modules_in_topo_order
|
|
52
52
|
.reject { Stdlib.is_stdlib?(it) }
|
|
53
53
|
.reduce([registry, {}]) do |(acc, digests), entry|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
case broken_dependency(entry, acc)
|
|
55
|
+
in String => dependency
|
|
56
|
+
[acc.update_module(blocked(entry, dependency)), digests]
|
|
57
|
+
|
|
58
|
+
else
|
|
59
|
+
compile_with_cache(entry, acc, digests, cache_dir, tolerant)
|
|
60
|
+
.then { |(compiled, digest)| [acc.update_module(compiled), digests.merge(entry.name => digest)] }
|
|
61
|
+
end
|
|
57
62
|
end
|
|
58
63
|
.first
|
|
59
64
|
end
|
|
60
65
|
|
|
66
|
+
def broken_dependency(entry, registry)
|
|
67
|
+
direct_deps(entry.name, registry)
|
|
68
|
+
.filter_map { registry.modules[it] }
|
|
69
|
+
.find { it.diagnostics.any_errors? }
|
|
70
|
+
&.name
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def blocked(entry, dependency)
|
|
74
|
+
Diagnostics::List
|
|
75
|
+
.empty
|
|
76
|
+
.add(
|
|
77
|
+
Diagnostics::Diagnostic.error(
|
|
78
|
+
"Not checked: #{dependency} failed to compile",
|
|
79
|
+
primary: nil,
|
|
80
|
+
),
|
|
81
|
+
)
|
|
82
|
+
.then { entry.with(diagnostics: it) }
|
|
83
|
+
end
|
|
84
|
+
|
|
61
85
|
def compile_with_cache(entry, registry, digests, cache_dir, tolerant)
|
|
62
86
|
return [compile_one(entry, registry, tolerant:), nil] unless cache_dir
|
|
63
87
|
|
data/lib/jade/parsing/error.rb
CHANGED
|
@@ -58,11 +58,20 @@ module Jade
|
|
|
58
58
|
super
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
+
# Blocks whose entries are comma-separated, so a newline between two of
|
|
62
|
+
# them reads as the end of the block.
|
|
63
|
+
COMMA_SEPARATED_BLOCKS = [
|
|
64
|
+
'interop import declaration',
|
|
65
|
+
'interface declaration',
|
|
66
|
+
'implementation',
|
|
67
|
+
].freeze
|
|
68
|
+
|
|
61
69
|
def hint
|
|
62
70
|
return leading_pipe_hint if leading_pipe_in_type_decl?
|
|
63
71
|
return reserved_keyword_hint if reserved_keyword_as_name?
|
|
64
72
|
return colon_not_eq_hint if eq_where_colon_expected?
|
|
65
73
|
return record_eq_hint if eq_where_record_pipe_expected?
|
|
74
|
+
return missing_comma_hint if entry_after_unterminated_entry?
|
|
66
75
|
nil
|
|
67
76
|
end
|
|
68
77
|
|
|
@@ -115,6 +124,16 @@ module Jade
|
|
|
115
124
|
def record_eq_hint
|
|
116
125
|
"(record fields use `:`, not `=` — write `{ name: value }`)"
|
|
117
126
|
end
|
|
127
|
+
|
|
128
|
+
def entry_after_unterminated_entry?
|
|
129
|
+
expected == :end &&
|
|
130
|
+
[:identifier, :lparen].include?(actual.type) &&
|
|
131
|
+
(@context & COMMA_SEPARATED_BLOCKS).any?
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def missing_comma_hint
|
|
135
|
+
"(entries here are separated by `,` — add one to the line above)"
|
|
136
|
+
end
|
|
118
137
|
end
|
|
119
138
|
|
|
120
139
|
# Specific case-branch shape: `in <pat> <body>` on the same source
|
data/lib/jade/runtime.rb
CHANGED
data/lib/jade/stdlib/decode.rb
CHANGED
|
@@ -10,6 +10,7 @@ module Jade
|
|
|
10
10
|
import Result
|
|
11
11
|
import List
|
|
12
12
|
import Dict
|
|
13
|
+
import Set
|
|
13
14
|
|
|
14
15
|
union :DecodeError
|
|
15
16
|
variant :MissingField, of: :DecodeError, args: ['String']
|
|
@@ -46,6 +47,13 @@ module Jade
|
|
|
46
47
|
Jade::Decode::Decoder[Jade::Decode::Desc::Bool[]]
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
# Keeps whatever came in. `Value` is the un-decoded value, so this is
|
|
51
|
+
# what makes it usable nested — `List(Value)`, a field of a struct —
|
|
52
|
+
# and not only as a whole arm.
|
|
53
|
+
function('value', {}, 'Decoder(Value)') {
|
|
54
|
+
Jade::Decode::Decoder[Jade::Decode::Desc::Pass[]]
|
|
55
|
+
}
|
|
56
|
+
|
|
49
57
|
# Structural
|
|
50
58
|
|
|
51
59
|
function(
|
|
@@ -88,6 +96,19 @@ module Jade
|
|
|
88
96
|
Jade::Decode::Decoder[Jade::Decode::Desc::Lst[decoder.desc]]
|
|
89
97
|
}
|
|
90
98
|
|
|
99
|
+
# Reads the same shape a list does, dropping duplicates. Like
|
|
100
|
+
# `Decode.dict`, it builds the keyed structure directly, so it asks
|
|
101
|
+
# nothing of the element beyond being decodable.
|
|
102
|
+
function(
|
|
103
|
+
'set',
|
|
104
|
+
{ decoder: 'Decoder(a)' },
|
|
105
|
+
'Decoder(Set(a))',
|
|
106
|
+
) { |decoder|
|
|
107
|
+
Jade::Decode::Desc::Lst[decoder.desc]
|
|
108
|
+
.then { Jade::Decode::Desc::Map[->(vs) { Jade::Set::Set[vs.to_h { [it, true] }] }, it] }
|
|
109
|
+
.then { Jade::Decode::Decoder[it] }
|
|
110
|
+
}
|
|
111
|
+
|
|
91
112
|
# Decodes either a Hash (natural Ruby/JSON object form) or a list
|
|
92
113
|
# of `[k, v]` pairs (what Encode.dict emits — also the only shape
|
|
93
114
|
# that round-trips non-String key types).
|
|
@@ -328,6 +349,7 @@ module Jade
|
|
|
328
349
|
implementation('Decodable', 'Basics.Float', 'decoder' => 'float')
|
|
329
350
|
implementation('Decodable', 'Basics.Bool', 'decoder' => 'bool')
|
|
330
351
|
implementation('Decodable', 'String.String', 'decoder' => 'string')
|
|
352
|
+
implementation('Decodable', 'Decode.Value', 'decoder' => 'value')
|
|
331
353
|
end
|
|
332
354
|
end
|
|
333
355
|
end
|
data/lib/jade/stdlib/encode.rb
CHANGED
|
@@ -10,6 +10,7 @@ module Jade
|
|
|
10
10
|
import List
|
|
11
11
|
import Tuple
|
|
12
12
|
import Dict
|
|
13
|
+
import Set
|
|
13
14
|
|
|
14
15
|
interface(
|
|
15
16
|
'Encodable',
|
|
@@ -25,6 +26,11 @@ module Jade
|
|
|
25
26
|
function('bool', { b: 'Bool' }, 'Value') { it }
|
|
26
27
|
function('null', {}, 'Value') { nil }
|
|
27
28
|
|
|
29
|
+
# A `Value` is already encoded, so its encoder is identity. Ports and
|
|
30
|
+
# boundaries take it as the opt-out; the instance is what carries that
|
|
31
|
+
# through `List(Value)` and friends.
|
|
32
|
+
function('value', { v: 'Value' }, 'Value') { it }
|
|
33
|
+
|
|
28
34
|
# Structural
|
|
29
35
|
|
|
30
36
|
function(
|
|
@@ -57,6 +63,16 @@ module Jade
|
|
|
57
63
|
dict.hash.map { |k, v| [k_enc.call(k), v_enc.call(v)] }
|
|
58
64
|
}
|
|
59
65
|
|
|
66
|
+
# A set is its elements, in insertion order, with no duplicates —
|
|
67
|
+
# the same shape a list encodes to.
|
|
68
|
+
function(
|
|
69
|
+
'set',
|
|
70
|
+
{ encoder: 'a -> Value', set: 'Set(a)' },
|
|
71
|
+
'Value',
|
|
72
|
+
) { |encoder, set|
|
|
73
|
+
set.hash.keys.map { encoder.call(it) }
|
|
74
|
+
}
|
|
75
|
+
|
|
60
76
|
function(
|
|
61
77
|
'object',
|
|
62
78
|
{ pairs: 'List(Tuple2(String, Value))' },
|
|
@@ -138,6 +154,7 @@ module Jade
|
|
|
138
154
|
implementation('Encodable', 'Basics.Float', 'encoder' => 'float')
|
|
139
155
|
implementation('Encodable', 'Basics.Bool', 'encoder' => 'bool')
|
|
140
156
|
implementation('Encodable', 'String.String', 'encoder' => 'string')
|
|
157
|
+
implementation('Encodable', 'Decode.Value', 'encoder' => 'value')
|
|
141
158
|
end
|
|
142
159
|
end
|
|
143
160
|
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'jade/stdlib/intrinsics'
|
|
2
|
+
|
|
3
|
+
module Jade
|
|
4
|
+
module Stdlib
|
|
5
|
+
module Show
|
|
6
|
+
extend Intrinsics
|
|
7
|
+
|
|
8
|
+
import Basics
|
|
9
|
+
import Char
|
|
10
|
+
import String
|
|
11
|
+
|
|
12
|
+
interface(
|
|
13
|
+
'Show',
|
|
14
|
+
'a',
|
|
15
|
+
{ 'show' => 'a -> String' },
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
implementation('Show', 'Int', 'show' => 'int_show')
|
|
19
|
+
implementation('Show', 'Float', 'show' => 'float_show')
|
|
20
|
+
implementation('Show', 'Bool', 'show' => 'bool_show')
|
|
21
|
+
implementation('Show', 'String', 'show' => 'str_show')
|
|
22
|
+
implementation('Show', 'Char', 'show' => 'char_show')
|
|
23
|
+
|
|
24
|
+
# Never is uninhabited, so this can only be reached by a compiler bug.
|
|
25
|
+
# The instance exists because the constraint does: without it no
|
|
26
|
+
# `Result(a, Never)` — the shape every port-free task returns — can be
|
|
27
|
+
# shown, and the error surfaces as an unresolved constraint far from
|
|
28
|
+
# its cause.
|
|
29
|
+
implementation('Show', 'Never', 'show' => 'never_show')
|
|
30
|
+
|
|
31
|
+
function('int_show', { n: 'Int' }, 'String')
|
|
32
|
+
function('float_show', { f: 'Float' }, 'String')
|
|
33
|
+
function('bool_show', { b: 'Bool' }, 'String')
|
|
34
|
+
function('str_show', { s: 'String' }, 'String')
|
|
35
|
+
function('char_show', { c: 'Char' }, 'String')
|
|
36
|
+
|
|
37
|
+
function('never_show', { n: 'Never' }, 'String')
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|