jade-lang 0.9.0 → 0.10.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 +400 -12
- data/docs/interop.md +18 -1
- 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.rb +2 -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 +23 -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/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/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 +6 -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/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 +5 -3
- 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/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.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 -2
- data/lib/jade/type.rb +36 -10
- data/lib/jade/version.rb +1 -1
- metadata +33 -2
data/lib/jade/module_loader.rb
CHANGED
|
@@ -6,6 +6,7 @@ require 'jade/ast'
|
|
|
6
6
|
require 'jade/registry'
|
|
7
7
|
require 'jade/frontend'
|
|
8
8
|
require 'jade/codegen'
|
|
9
|
+
require 'jade/module_loader/build'
|
|
9
10
|
require 'jade/module_loader/cache'
|
|
10
11
|
require 'jade/module_loader/dependency_resolver'
|
|
11
12
|
require 'jade/module_loader/dependency_graph'
|
|
@@ -166,6 +166,26 @@ module Jade
|
|
|
166
166
|
end
|
|
167
167
|
end
|
|
168
168
|
|
|
169
|
+
# A word that only reads as a keyword in one position, so it stays
|
|
170
|
+
# usable as an ordinary name everywhere else.
|
|
171
|
+
def contextual(word)
|
|
172
|
+
(@contextuals ||= {})[word] ||= P.new do |state|
|
|
173
|
+
type(:identifier).call(state).and_then do |(token, next_state)|
|
|
174
|
+
next Ok[[token, next_state]] if token.value == word
|
|
175
|
+
|
|
176
|
+
Err[[
|
|
177
|
+
Parsing::UnexpectedTokenError.new(
|
|
178
|
+
entry: state.entry,
|
|
179
|
+
span: token.range,
|
|
180
|
+
actual: token,
|
|
181
|
+
expected: word,
|
|
182
|
+
),
|
|
183
|
+
state,
|
|
184
|
+
]]
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
169
189
|
def lazy(&block)
|
|
170
190
|
P.new do |input|
|
|
171
191
|
block.call.call(input)
|
|
@@ -216,6 +236,13 @@ module Jade
|
|
|
216
236
|
with(diagnostics: diagnostics.add(diagnostic))
|
|
217
237
|
end
|
|
218
238
|
|
|
239
|
+
# Is the token here on the same line as the one before it?
|
|
240
|
+
def continues_line?
|
|
241
|
+
return true if eof? || position.zero?
|
|
242
|
+
|
|
243
|
+
same_line?(tokens[position - 1].range.begin, current.range.begin)
|
|
244
|
+
end
|
|
245
|
+
|
|
219
246
|
def skip_until(sync_types)
|
|
220
247
|
(position...tokens.length)
|
|
221
248
|
.find { sync_types.include?(tokens[it].type) }
|
|
@@ -228,6 +255,12 @@ module Jade
|
|
|
228
255
|
@fn = block
|
|
229
256
|
end
|
|
230
257
|
|
|
258
|
+
def stalled?(error, state)
|
|
259
|
+
return state.eof? if error.span.nil?
|
|
260
|
+
|
|
261
|
+
error.span.begin == state.current&.range&.begin
|
|
262
|
+
end
|
|
263
|
+
|
|
231
264
|
def call(tokens)
|
|
232
265
|
@fn.call(tokens)
|
|
233
266
|
end
|
|
@@ -282,8 +315,51 @@ module Jade
|
|
|
282
315
|
map_error(&:commit)
|
|
283
316
|
end
|
|
284
317
|
|
|
285
|
-
|
|
286
|
-
|
|
318
|
+
# A stalled alternation reports whichever branch it tried last, so
|
|
319
|
+
# say what the position wanted instead. One that got somewhere is
|
|
320
|
+
# already reporting the useful error.
|
|
321
|
+
def expecting(what)
|
|
322
|
+
P.new do |state|
|
|
323
|
+
call(state).map_error do |(err, err_state)|
|
|
324
|
+
[!err.committed? && stalled?(err, state) ? err.copy(expected: what) : err, err_state]
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# A `(` opening a new line starts a statement, it does not continue
|
|
330
|
+
# the expression above it: `(x, y) = p` is a binding, not a call on
|
|
331
|
+
# whatever the line before ended with.
|
|
332
|
+
def on_same_line
|
|
333
|
+
P.new do |state|
|
|
334
|
+
next Err[[
|
|
335
|
+
Parsing::UnexpectedTokenError.new(
|
|
336
|
+
entry: state.entry,
|
|
337
|
+
span: state.current.range,
|
|
338
|
+
actual: state.current,
|
|
339
|
+
expected: 'the same line',
|
|
340
|
+
),
|
|
341
|
+
state,
|
|
342
|
+
]] unless state.continues_line?
|
|
343
|
+
|
|
344
|
+
call(state)
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def parsing(name)
|
|
349
|
+
map_error { |err| err.copy(context: [name, *err.context]) }
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# Opens an empty body's span out to the construct it belongs to.
|
|
353
|
+
# The innermost one gets there first; past that the span is wider
|
|
354
|
+
# than the marker `body` left, so an enclosing construct skips it.
|
|
355
|
+
def spanning_empty_body
|
|
356
|
+
P.new do |state|
|
|
357
|
+
call(state).map_error do |(err, err_state)|
|
|
358
|
+
next [err, err_state] unless err.is_a?(MissingBodyError) && err.span.size == 1
|
|
359
|
+
|
|
360
|
+
[err.copy(span: state.current.range.begin...err.actual.range.end), err_state]
|
|
361
|
+
end
|
|
362
|
+
end
|
|
287
363
|
end
|
|
288
364
|
end
|
|
289
365
|
end
|
data/lib/jade/parsing/error.rb
CHANGED
|
@@ -21,14 +21,15 @@ module Jade
|
|
|
21
21
|
self
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def
|
|
24
|
+
def copy(**changes)
|
|
25
25
|
self.class.new(
|
|
26
|
-
entry:
|
|
27
|
-
span:
|
|
28
|
-
actual:
|
|
29
|
-
expected:
|
|
26
|
+
entry: @entry,
|
|
27
|
+
span: @span,
|
|
28
|
+
actual: @actual,
|
|
29
|
+
expected: @expected,
|
|
30
30
|
committed: @committed,
|
|
31
|
-
context:
|
|
31
|
+
context: @context,
|
|
32
|
+
**changes,
|
|
32
33
|
)
|
|
33
34
|
end
|
|
34
35
|
|
|
@@ -152,6 +153,42 @@ module Jade
|
|
|
152
153
|
end
|
|
153
154
|
end
|
|
154
155
|
|
|
156
|
+
# `in 0..limit`. Rejecting it is right — coverage is decided before
|
|
157
|
+
# `limit` exists — but the reader needs to be told that, not sent to
|
|
158
|
+
# look at their `then`.
|
|
159
|
+
class NonLiteralRangeBoundError < Error
|
|
160
|
+
def message
|
|
161
|
+
"#{context_prefix}A range pattern's bounds have to be literals, and " \
|
|
162
|
+
"#{@actual.value.inspect} is a value. Which integers a branch " \
|
|
163
|
+
"covers is settled when the code is compiled, so a bound the " \
|
|
164
|
+
"compiler cannot see cannot be checked."
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def label
|
|
168
|
+
'range bound is not a literal'
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def notes
|
|
172
|
+
[
|
|
173
|
+
Jade::Diagnostics::Annotation[
|
|
174
|
+
:help,
|
|
175
|
+
'match `_` and compare in the body, or use `if` — either way the ' \
|
|
176
|
+
'branch is no longer something exhaustiveness can account for',
|
|
177
|
+
],
|
|
178
|
+
]
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
class MissingBodyError < UnexpectedTokenError
|
|
183
|
+
def hint
|
|
184
|
+
'(a body has to produce a value)'
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def label
|
|
188
|
+
'missing body'
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
155
192
|
class InvalidOperatorError < Error
|
|
156
193
|
HINTS = {
|
|
157
194
|
'/=' => 'Use `!=` for inequality.',
|
data/lib/jade/parsing/type.rb
CHANGED
|
@@ -5,7 +5,9 @@ module Jade
|
|
|
5
5
|
module Type
|
|
6
6
|
extend Combinators::Dsl
|
|
7
7
|
|
|
8
|
-
parser(:type_expression) {
|
|
8
|
+
parser(:type_expression) {
|
|
9
|
+
(type_function | type_atom | type_record).expecting('a type')
|
|
10
|
+
}
|
|
9
11
|
|
|
10
12
|
parser(:type_atom) {
|
|
11
13
|
type_application | type_var | type_tuple | grouped(lazy { type_function })
|
|
@@ -65,8 +67,8 @@ module Jade
|
|
|
65
67
|
no_args = [Combinators::CommaList.empty, nil]
|
|
66
68
|
|
|
67
69
|
(
|
|
68
|
-
(type_name >> optional(type_application_args, default: no_args)) |
|
|
69
|
-
(type_var >> type_application_args)
|
|
70
|
+
(type_name >> optional(type_application_args.on_same_line, default: no_args)) |
|
|
71
|
+
(type_var >> type_application_args.on_same_line)
|
|
70
72
|
).map(&AST.type_application)
|
|
71
73
|
}
|
|
72
74
|
|
data/lib/jade/parsing.rb
CHANGED
|
@@ -92,7 +92,7 @@ module Jade
|
|
|
92
92
|
parser(:program) { module_ | program_body }
|
|
93
93
|
|
|
94
94
|
parser(:program_body) {
|
|
95
|
-
(declaration | statement).then { |item|
|
|
95
|
+
(declaration | statement).expecting('a declaration or an expression').then { |item|
|
|
96
96
|
P.new do |state|
|
|
97
97
|
(
|
|
98
98
|
state.tolerant ?
|
|
@@ -105,11 +105,11 @@ module Jade
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
parser(:statement) { bind | assign | expression }
|
|
108
|
+
parser(:statement) { (bind | assign | expression).expecting('an expression') }
|
|
109
109
|
|
|
110
110
|
parser(:declaration) {
|
|
111
|
-
function_declaration |
|
|
112
|
-
struct_declaration | implementation | interface_declaration
|
|
111
|
+
function_declaration | type_alias_declaration | type_declaration | import_declaration |
|
|
112
|
+
interop_import_declaration | struct_declaration | implementation | interface_declaration
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
parser(:expression) {
|
|
@@ -129,15 +129,15 @@ module Jade
|
|
|
129
129
|
type(:colon).skip >>
|
|
130
130
|
lazy { expression }
|
|
131
131
|
).commit
|
|
132
|
-
.
|
|
132
|
+
.parsing("ternary")
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
parser(:tuple) {
|
|
136
136
|
(
|
|
137
137
|
type(:lparen) >>
|
|
138
|
-
|
|
138
|
+
function_call_arg >>
|
|
139
139
|
type(:comma).skip >>
|
|
140
|
-
comma_sequence(
|
|
140
|
+
comma_sequence(function_call_arg) >>
|
|
141
141
|
type(:rparen)
|
|
142
142
|
).map(&AST.tuple)
|
|
143
143
|
}
|
|
@@ -182,7 +182,8 @@ module Jade
|
|
|
182
182
|
type(:end)
|
|
183
183
|
).commit
|
|
184
184
|
).map(&AST.if_then_else)
|
|
185
|
-
.
|
|
185
|
+
.parsing("if/then/else")
|
|
186
|
+
.spanning_empty_body
|
|
186
187
|
}
|
|
187
188
|
|
|
188
189
|
parser(:case_of) {
|
|
@@ -195,7 +196,8 @@ module Jade
|
|
|
195
196
|
type(:end)
|
|
196
197
|
).commit
|
|
197
198
|
).map(&AST.case_of)
|
|
198
|
-
.
|
|
199
|
+
.parsing("case")
|
|
200
|
+
.spanning_empty_body
|
|
199
201
|
}
|
|
200
202
|
|
|
201
203
|
# `then` is optional before a branch body. The formatter emits it for
|
|
@@ -233,6 +235,21 @@ module Jade
|
|
|
233
235
|
state.current&.type == :then ? body.call(state.advance) : body.call(state)
|
|
234
236
|
end
|
|
235
237
|
|
|
238
|
+
def empty_body?(state)
|
|
239
|
+
state.position.positive? &&
|
|
240
|
+
!state.eof? &&
|
|
241
|
+
BODY_TERMINATORS.include?(state.current.type)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Widened to the whole construct by `spanning_empty_body`.
|
|
245
|
+
def empty_body_start(state)
|
|
246
|
+
state
|
|
247
|
+
.tokens[state.position - 1]
|
|
248
|
+
.range
|
|
249
|
+
.end
|
|
250
|
+
.then { it...(it + 1) }
|
|
251
|
+
end
|
|
252
|
+
|
|
236
253
|
def checked_branch_body(in_tok)
|
|
237
254
|
P.new do |state|
|
|
238
255
|
next body.call(state.advance) if state.current&.type == :then
|
|
@@ -256,10 +273,72 @@ module Jade
|
|
|
256
273
|
end
|
|
257
274
|
|
|
258
275
|
parser(:pattern) {
|
|
259
|
-
wildcard_pattern | list_pattern |
|
|
260
|
-
constructor_pattern | tuple_pattern | record_pattern
|
|
276
|
+
wildcard_pattern | list_pattern | range_pattern | literal_pattern |
|
|
277
|
+
binding_pattern | constructor_pattern | tuple_pattern | record_pattern
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
# Tried ahead of `literal_pattern`, which would otherwise take the lower
|
|
281
|
+
# bound and leave the `..` stranded.
|
|
282
|
+
parser(:range_pattern) {
|
|
283
|
+
((range_bound >> type(:dotdot) >> upper_bound).map(&AST.range_pattern)) |
|
|
284
|
+
((type(:dotdot) >> range_bound).map(&AST.unbounded_below_pattern)) |
|
|
285
|
+
non_literal_lower_bound
|
|
261
286
|
}
|
|
262
287
|
|
|
288
|
+
parser(:non_literal_lower_bound, private: true) {
|
|
289
|
+
P.new do |state|
|
|
290
|
+
bound = state.current
|
|
291
|
+
|
|
292
|
+
next not_a_range(state) unless bound&.type == :identifier
|
|
293
|
+
next not_a_range(state) unless state.advance.current&.type == :dotdot
|
|
294
|
+
|
|
295
|
+
Err[[non_literal_bound(state, bound), state]]
|
|
296
|
+
end
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
parser(:range_bound, private: true) {
|
|
300
|
+
negative_literal | int | float | char | string
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
# A branch whose body is on the next line has no `then` to stop at, so an
|
|
304
|
+
# open range would otherwise take the first line of the body as its upper
|
|
305
|
+
# bound.
|
|
306
|
+
parser(:upper_bound, private: true) {
|
|
307
|
+
P.new do |state|
|
|
308
|
+
next Ok[[nil, state]] if state.eof?
|
|
309
|
+
next Ok[[nil, state]] unless state.same_line?(
|
|
310
|
+
state.tokens[state.position - 1].range.begin,
|
|
311
|
+
state.current.range.begin,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
bound = state.current
|
|
315
|
+
next Err[[non_literal_bound(state, bound), state]] if bound.type == :identifier
|
|
316
|
+
|
|
317
|
+
optional(range_bound).call(state)
|
|
318
|
+
end
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
def not_a_range(state)
|
|
322
|
+
UnexpectedTokenError
|
|
323
|
+
.new(
|
|
324
|
+
entry: state.entry,
|
|
325
|
+
span: state.current&.range || (0...0),
|
|
326
|
+
actual: state.current,
|
|
327
|
+
expected: :int,
|
|
328
|
+
)
|
|
329
|
+
.then { Err[[it, state]] }
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def non_literal_bound(state, token)
|
|
333
|
+
NonLiteralRangeBoundError.new(
|
|
334
|
+
entry: state.entry,
|
|
335
|
+
span: token.range,
|
|
336
|
+
actual: token,
|
|
337
|
+
expected: :int,
|
|
338
|
+
committed: true,
|
|
339
|
+
)
|
|
340
|
+
end
|
|
341
|
+
|
|
263
342
|
parser(:tuple_pattern) {
|
|
264
343
|
(
|
|
265
344
|
type(:lparen) >>
|
|
@@ -308,13 +387,31 @@ module Jade
|
|
|
308
387
|
).map(&AST.record_field_pattern)
|
|
309
388
|
}
|
|
310
389
|
|
|
311
|
-
|
|
390
|
+
# One of these with nothing before it is an empty body.
|
|
391
|
+
BODY_TERMINATORS = %i[end else rbrace in].freeze
|
|
392
|
+
|
|
393
|
+
parser(:body) {
|
|
394
|
+
P.new do |state|
|
|
395
|
+
next sequence(lazy { statement }).map(&AST.body).call(state) unless empty_body?(state)
|
|
396
|
+
|
|
397
|
+
Err[[
|
|
398
|
+
MissingBodyError.new(
|
|
399
|
+
entry: state.entry,
|
|
400
|
+
span: empty_body_start(state),
|
|
401
|
+
actual: state.current,
|
|
402
|
+
expected: 'an expression',
|
|
403
|
+
committed: true,
|
|
404
|
+
),
|
|
405
|
+
state,
|
|
406
|
+
]]
|
|
407
|
+
end
|
|
408
|
+
}
|
|
312
409
|
|
|
313
410
|
parser(:operator) {
|
|
314
411
|
type(:plus) | type(:minus) | type(:star) | type(:slash) |
|
|
315
412
|
type(:pipe_forward) | type(:pipe_backward) | type(:eq) | type(:not_eq) |
|
|
316
413
|
type(:lt) | type(:gt) | type(:lte) | type(:gte) | type(:andand) | type(:oror) |
|
|
317
|
-
type(:plusplus)
|
|
414
|
+
type(:plusplus) | type(:dotdot)
|
|
318
415
|
}
|
|
319
416
|
|
|
320
417
|
parser(:primary) {
|
|
@@ -342,7 +439,7 @@ module Jade
|
|
|
342
439
|
record_literal | record_update_sugar | record_access_sugar | record_update
|
|
343
440
|
}
|
|
344
441
|
|
|
345
|
-
parser(:postfix) { function_call | member_access }
|
|
442
|
+
parser(:postfix) { function_call.on_same_line | member_access }
|
|
346
443
|
|
|
347
444
|
parser(:module_name) {
|
|
348
445
|
sequence(constant, separated_by: type(:dot).skip).map { [it] }
|
|
@@ -394,14 +491,16 @@ module Jade
|
|
|
394
491
|
optional(exposing, default: [[]])
|
|
395
492
|
).commit
|
|
396
493
|
).map(&AST.import_declaration)
|
|
397
|
-
.
|
|
494
|
+
.parsing("import declaration")
|
|
398
495
|
}
|
|
399
496
|
|
|
400
497
|
parser(:exposing) {
|
|
401
498
|
(type(:exposing).skip >>
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
499
|
+
(
|
|
500
|
+
type(:lparen).skip >>
|
|
501
|
+
(expose_list | expose_all).expecting('a name to expose, or `..`') >>
|
|
502
|
+
type(:rparen).skip
|
|
503
|
+
).commit
|
|
405
504
|
) | expose_none
|
|
406
505
|
}
|
|
407
506
|
|
|
@@ -437,11 +536,12 @@ module Jade
|
|
|
437
536
|
) >>
|
|
438
537
|
type(:arrow).skip >>
|
|
439
538
|
type_expression >>
|
|
440
|
-
|
|
539
|
+
body >>
|
|
441
540
|
type(:end)
|
|
442
541
|
).commit
|
|
443
542
|
).map(&AST.function_declaration)
|
|
444
|
-
.
|
|
543
|
+
.parsing("function declaration")
|
|
544
|
+
.spanning_empty_body
|
|
445
545
|
}
|
|
446
546
|
|
|
447
547
|
parser(:type_declaration) {
|
|
@@ -454,7 +554,21 @@ module Jade
|
|
|
454
554
|
sequence(variant_declaration, separated_by: type(:pipe).skip).map { [it] }
|
|
455
555
|
).commit
|
|
456
556
|
).map(&AST.type_declaration)
|
|
457
|
-
.
|
|
557
|
+
.parsing("type declaration")
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
parser(:type_alias_declaration) {
|
|
561
|
+
(
|
|
562
|
+
type(:type) >>
|
|
563
|
+
contextual('alias').skip >>
|
|
564
|
+
(
|
|
565
|
+
constant >>
|
|
566
|
+
(type_params | empty_comma_list) >>
|
|
567
|
+
type(:assign).skip >>
|
|
568
|
+
type_expression
|
|
569
|
+
).commit
|
|
570
|
+
).map(&AST.type_alias_declaration)
|
|
571
|
+
.parsing("type alias declaration")
|
|
458
572
|
}
|
|
459
573
|
|
|
460
574
|
parser(:record_declaration) {
|
|
@@ -549,7 +663,7 @@ module Jade
|
|
|
549
663
|
parser(:record_fields) { comma_sequence(record_field) }
|
|
550
664
|
|
|
551
665
|
parser(:record_field) {
|
|
552
|
-
(identifier >> type(:colon).skip >>
|
|
666
|
+
(identifier >> type(:colon).skip >> function_call_arg).map(&AST.record_field)
|
|
553
667
|
}
|
|
554
668
|
|
|
555
669
|
parser(:record_access_sugar) {
|
|
@@ -571,7 +685,7 @@ module Jade
|
|
|
571
685
|
(interop_module_name >> type(:with) >> interop_functions >> type(:end))
|
|
572
686
|
.commit
|
|
573
687
|
).map(&AST.interop_import_declaration)
|
|
574
|
-
.
|
|
688
|
+
.parsing("interop import declaration")
|
|
575
689
|
}
|
|
576
690
|
|
|
577
691
|
parser(:interop_namespace_sep) { type(:coloncolon) }
|
|
@@ -615,7 +729,7 @@ module Jade
|
|
|
615
729
|
type(:end)
|
|
616
730
|
).commit
|
|
617
731
|
).map(&AST.implementation)
|
|
618
|
-
.
|
|
732
|
+
.parsing("implementation")
|
|
619
733
|
}
|
|
620
734
|
|
|
621
735
|
parser(:interface_declaration) {
|
|
@@ -635,7 +749,7 @@ module Jade
|
|
|
635
749
|
type(:end)
|
|
636
750
|
).commit
|
|
637
751
|
).map(&AST.interface_declaration)
|
|
638
|
-
.
|
|
752
|
+
.parsing("interface declaration")
|
|
639
753
|
}
|
|
640
754
|
|
|
641
755
|
parser(:interface_function_decl) {
|
|
@@ -665,7 +779,7 @@ module Jade
|
|
|
665
779
|
type_record
|
|
666
780
|
).commit
|
|
667
781
|
).map(&AST.struct_declaration)
|
|
668
|
-
.
|
|
782
|
+
.parsing("struct declaration")
|
|
669
783
|
}
|
|
670
784
|
|
|
671
785
|
parser(:int) { type(:int).map(&AST.literal) }
|
data/lib/jade/runtime.rb
CHANGED
|
@@ -8,6 +8,25 @@ require 'jade/interop/boundary'
|
|
|
8
8
|
module Jade
|
|
9
9
|
module Records; end
|
|
10
10
|
|
|
11
|
+
# A variant with no fields carries no state, so every construction can
|
|
12
|
+
# hand back the same object. Prepended to the class's singleton so
|
|
13
|
+
# `super` still reaches the real constructor the first time.
|
|
14
|
+
module Nullary
|
|
15
|
+
def new
|
|
16
|
+
@instance ||= super
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def []
|
|
20
|
+
@instance ||= super
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.nullary(&block)
|
|
25
|
+
Data.define
|
|
26
|
+
.tap { it.class_eval(&block) if block }
|
|
27
|
+
.tap { it.singleton_class.prepend(Nullary) }
|
|
28
|
+
end
|
|
29
|
+
|
|
11
30
|
module Tuple
|
|
12
31
|
Tuple2 = Data.define(:_1, :_2) do
|
|
13
32
|
def to_s = "(#{[_1, _2].map(&:to_s).join(', ')})"
|
|
@@ -23,9 +42,9 @@ module Jade
|
|
|
23
42
|
end
|
|
24
43
|
|
|
25
44
|
module Basics
|
|
26
|
-
GT =
|
|
27
|
-
EQ =
|
|
28
|
-
LT =
|
|
45
|
+
GT = Jade.nullary
|
|
46
|
+
EQ = Jade.nullary
|
|
47
|
+
LT = Jade.nullary
|
|
29
48
|
end
|
|
30
49
|
|
|
31
50
|
# Compiled code calls `a.compare(b)`; natives ship `<=>` not `compare`.
|
|
@@ -77,20 +96,20 @@ module Jade
|
|
|
77
96
|
RECORD_CLASSES = {}
|
|
78
97
|
@booted = false
|
|
79
98
|
|
|
99
|
+
# In dependency order: a module's `import` names the constant of another.
|
|
100
|
+
STDLIB = %w[
|
|
101
|
+
basics maybe number tuple list char string result task dict set
|
|
102
|
+
decode decode/params encode bytes show debug
|
|
103
|
+
].freeze
|
|
104
|
+
|
|
80
105
|
def boot!
|
|
81
106
|
return if @booted
|
|
82
107
|
@booted = true
|
|
83
108
|
|
|
84
|
-
require
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
require "jade/stdlib/task"
|
|
89
|
-
require "jade/stdlib/decode"
|
|
90
|
-
require "jade/stdlib/encode"
|
|
91
|
-
require "jade/stdlib/bytes"
|
|
92
|
-
require "jade/stdlib/dict"
|
|
93
|
-
require "jade/stdlib/set"
|
|
109
|
+
require 'jade/stdlib/intrinsics'
|
|
110
|
+
Stdlib::Intrinsics.runtime_only! unless defined?(Jade::Frontend)
|
|
111
|
+
|
|
112
|
+
STDLIB.each { require_relative "stdlib/#{it}" }
|
|
94
113
|
end
|
|
95
114
|
|
|
96
115
|
def intr(name)
|
data/lib/jade/source.rb
CHANGED
|
@@ -35,10 +35,14 @@ module Jade
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def to_module_name
|
|
38
|
-
uri
|
|
38
|
+
Source.module_name_for(uri)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.module_name_for(path)
|
|
42
|
+
path
|
|
39
43
|
.delete_suffix('.jd')
|
|
40
44
|
.split('/')
|
|
41
|
-
.map {
|
|
45
|
+
.map { camelize(it) }
|
|
42
46
|
.join('.')
|
|
43
47
|
end
|
|
44
48
|
|
data/lib/jade/stdlib/basics.rb
CHANGED
|
@@ -14,6 +14,10 @@ module Jade
|
|
|
14
14
|
native_type :Float, ::Float
|
|
15
15
|
native_type :Bool, TrueClass, FalseClass
|
|
16
16
|
|
|
17
|
+
# No variants, so the only way to hold one is `non_zero`. Erased at
|
|
18
|
+
# runtime: a NonZero(Int) is the Int.
|
|
19
|
+
union :NonZero, 'a'
|
|
20
|
+
|
|
17
21
|
union :Ordering
|
|
18
22
|
variant :GT, of: :Ordering
|
|
19
23
|
variant :EQ, of: :Ordering
|
|
@@ -41,11 +45,25 @@ module Jade
|
|
|
41
45
|
interface(
|
|
42
46
|
'Numeric',
|
|
43
47
|
'a',
|
|
44
|
-
{
|
|
48
|
+
{
|
|
49
|
+
'(+)' => 'a, a -> a',
|
|
50
|
+
'(-)' => 'a, a -> a',
|
|
51
|
+
'(*)' => 'a, a -> a',
|
|
52
|
+
'(/)' => 'a, NonZero(a) -> a',
|
|
53
|
+
'from_int' => 'Int -> a',
|
|
54
|
+
}
|
|
45
55
|
)
|
|
46
56
|
|
|
47
|
-
implementation(
|
|
48
|
-
|
|
57
|
+
implementation(
|
|
58
|
+
'Numeric', 'Int',
|
|
59
|
+
'(+)' => 'int_add', '(-)' => 'int_sub', '(*)' => 'int_mul', '(/)' => 'int_div',
|
|
60
|
+
'from_int' => 'identity'
|
|
61
|
+
)
|
|
62
|
+
implementation(
|
|
63
|
+
'Numeric', 'Float',
|
|
64
|
+
'(+)' => 'float_add', '(-)' => 'float_sub', '(*)' => 'float_mul', '(/)' => 'float_div',
|
|
65
|
+
'from_int' => 'to_float'
|
|
66
|
+
)
|
|
49
67
|
|
|
50
68
|
interface(
|
|
51
69
|
'Appendable',
|
|
@@ -82,6 +100,7 @@ module Jade
|
|
|
82
100
|
function('int_div', { a: 'Int', b: 'Int' }, 'Int')
|
|
83
101
|
function('mod', { a: 'Int', b: 'Int' }, 'Int')
|
|
84
102
|
|
|
103
|
+
|
|
85
104
|
function('float_add', { a: 'Float', b: 'Float' }, 'Float')
|
|
86
105
|
function('float_sub', { a: 'Float', b: 'Float' }, 'Float')
|
|
87
106
|
function('float_mul', { a: 'Float', b: 'Float' }, 'Float')
|
data/lib/jade/stdlib/char.rb
CHANGED
|
@@ -10,6 +10,7 @@ module Jade
|
|
|
10
10
|
union :Char
|
|
11
11
|
|
|
12
12
|
implementation('Eq', 'Char', '(==)' => 'char_eq')
|
|
13
|
+
implementation('Comparable', 'Char', 'compare' => 'char_compare')
|
|
13
14
|
|
|
14
15
|
function(:to_code, { char: 'Char' }, 'Int')
|
|
15
16
|
function(:from_code, { code: 'Int' }, 'Maybe(Char)')
|
|
@@ -22,6 +23,7 @@ module Jade
|
|
|
22
23
|
default_importing('Char')
|
|
23
24
|
|
|
24
25
|
function('char_eq', { one: 'Char', other: 'Char' }, 'Bool')
|
|
26
|
+
function('char_compare', { one: 'Char', other: 'Char' }, 'Ordering')
|
|
25
27
|
end
|
|
26
28
|
end
|
|
27
29
|
end
|
data/lib/jade/stdlib/clock.rb
CHANGED
|
@@ -11,7 +11,7 @@ module Jade
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def imports
|
|
14
|
-
[Basics, Maybe, Result, Task, String, Tuple, Decode, Encode, Calendar]
|
|
14
|
+
[Basics, Maybe, Number, Result, Task, String, Tuple, Decode, Encode, Calendar]
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def default_imports
|
|
@@ -46,6 +46,7 @@ module Jade
|
|
|
46
46
|
|
|
47
47
|
import Decode exposing (Decodable, Decoder, Value)
|
|
48
48
|
import Encode exposing (Encodable)
|
|
49
|
+
import Number exposing (non_zero)
|
|
49
50
|
|
|
50
51
|
|
|
51
52
|
type Instant = Instant(Int)
|
|
@@ -259,7 +260,10 @@ module Jade
|
|
|
259
260
|
|
|
260
261
|
|
|
261
262
|
def frac_to_millis(n: Int, digits: Int) -> Int
|
|
262
|
-
|
|
263
|
+
case non_zero(pow10(digits - 3))
|
|
264
|
+
in Just(d) then digits == 3 ? n : digits < 3 ? n * pow10(3 - digits) : n / d
|
|
265
|
+
in Nothing then n
|
|
266
|
+
end
|
|
263
267
|
end
|
|
264
268
|
|
|
265
269
|
|