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.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +400 -12
  3. data/docs/interop.md +18 -1
  4. data/docs/stdlib.md +3 -1
  5. data/docs/syntax.md +96 -0
  6. data/lib/jade/api.rb +9 -0
  7. data/lib/jade/ast.rb +37 -1
  8. data/lib/jade/cli/eject.rb +132 -0
  9. data/lib/jade/cli.rb +2 -0
  10. data/lib/jade/codegen/boundary/specialized/list.rb +11 -7
  11. data/lib/jade/codegen/boundary/specialized/maybe.rb +2 -2
  12. data/lib/jade/codegen/boundary/specialized/record.rb +19 -4
  13. data/lib/jade/codegen/boundary/specialized/scalar.rb +2 -2
  14. data/lib/jade/codegen/boundary/specialized.rb +13 -5
  15. data/lib/jade/codegen/boundary.rb +12 -2
  16. data/lib/jade/codegen/context.rb +17 -0
  17. data/lib/jade/codegen/error.rb +1 -0
  18. data/lib/jade/codegen/function_call.rb +23 -7
  19. data/lib/jade/codegen/function_declaration.rb +25 -13
  20. data/lib/jade/codegen/helpers.rb +27 -4
  21. data/lib/jade/codegen/implementation.rb +2 -2
  22. data/lib/jade/codegen/inline.rb +1 -1
  23. data/lib/jade/codegen/inlines.rb +6 -1
  24. data/lib/jade/codegen/names.rb +31 -0
  25. data/lib/jade/codegen/pattern/constructor.rb +2 -2
  26. data/lib/jade/codegen/transforms/fold_shape.rb +1 -1
  27. data/lib/jade/codegen.rb +76 -15
  28. data/lib/jade/compiler.rb +2 -0
  29. data/lib/jade/entry.rb +1 -1
  30. data/lib/jade/formatter/calls.rb +68 -11
  31. data/lib/jade/formatter/collections.rb +4 -4
  32. data/lib/jade/formatter/declarations.rb +13 -15
  33. data/lib/jade/formatter/helper.rb +20 -0
  34. data/lib/jade/formatter/infix_application.rb +19 -4
  35. data/lib/jade/formatter/lambda.rb +24 -12
  36. data/lib/jade/formatter/pattern.rb +3 -0
  37. data/lib/jade/frontend/desugaring/placeholder.rb +40 -17
  38. data/lib/jade/frontend/desugaring.rb +29 -9
  39. data/lib/jade/frontend/fixity_fixer.rb +6 -4
  40. data/lib/jade/frontend/forward_declaration/error/alias_expansion.rb +23 -0
  41. data/lib/jade/frontend/forward_declaration/error/duplicate_constructor_name.rb +45 -0
  42. data/lib/jade/frontend/forward_declaration/error/duplicate_type_name.rb +42 -0
  43. data/lib/jade/frontend/forward_declaration/error.rb +3 -0
  44. data/lib/jade/frontend/forward_declaration/helper.rb +58 -0
  45. data/lib/jade/frontend/forward_declaration/import_declaration.rb +4 -0
  46. data/lib/jade/frontend/forward_declaration/interface_declaration.rb +6 -0
  47. data/lib/jade/frontend/forward_declaration/module.rb +4 -0
  48. data/lib/jade/frontend/forward_declaration/struct_declaration.rb +10 -1
  49. data/lib/jade/frontend/forward_declaration/type_alias_declaration.rb +37 -0
  50. data/lib/jade/frontend/forward_declaration/type_declaration.rb +10 -1
  51. data/lib/jade/frontend/forward_declaration.rb +2 -0
  52. data/lib/jade/frontend/pattern_analysis/exhaustiveness.rb +47 -43
  53. data/lib/jade/frontend/pattern_analysis/matrix.rb +28 -219
  54. data/lib/jade/frontend/pattern_analysis/pattern.rb +132 -0
  55. data/lib/jade/frontend/pattern_analysis/redundancy.rb +24 -0
  56. data/lib/jade/frontend/pattern_analysis/signature.rb +220 -0
  57. data/lib/jade/frontend/pattern_analysis/usefulness.rb +72 -0
  58. data/lib/jade/frontend/pattern_analysis/witnesses.rb +73 -0
  59. data/lib/jade/frontend/pattern_analysis.rb +5 -38
  60. data/lib/jade/frontend/semantic_analysis/error/implementation_on_alias.rb +24 -0
  61. data/lib/jade/frontend/semantic_analysis/error/no_base_case.rb +45 -0
  62. data/lib/jade/frontend/semantic_analysis/error/placeholder_not_allowed.rb +15 -3
  63. data/lib/jade/frontend/semantic_analysis/error/recursive_type_alias.rb +24 -0
  64. data/lib/jade/frontend/semantic_analysis/error.rb +3 -0
  65. data/lib/jade/frontend/semantic_analysis/helper.rb +31 -0
  66. data/lib/jade/frontend/semantic_analysis/implementation.rb +13 -0
  67. data/lib/jade/frontend/semantic_analysis/inhabitedness.rb +90 -0
  68. data/lib/jade/frontend/semantic_analysis/interop_import_declaration.rb +28 -16
  69. data/lib/jade/frontend/semantic_analysis/keyed_call.rb +21 -5
  70. data/lib/jade/frontend/semantic_analysis/pattern_range.rb +16 -0
  71. data/lib/jade/frontend/semantic_analysis/struct_declaration.rb +1 -0
  72. data/lib/jade/frontend/semantic_analysis/type_alias_declaration/cycle_detection.rb +95 -0
  73. data/lib/jade/frontend/semantic_analysis/type_alias_declaration.rb +48 -0
  74. data/lib/jade/frontend/semantic_analysis/type_declaration.rb +1 -0
  75. data/lib/jade/frontend/semantic_analysis.rb +5 -0
  76. data/lib/jade/frontend/type_checking/canonicalize.rb +1 -0
  77. data/lib/jade/frontend/type_checking/cascade.rb +90 -0
  78. data/lib/jade/frontend/type_checking/constraints/deriving/encodable.rb +10 -0
  79. data/lib/jade/frontend/type_checking/constraints.rb +18 -3
  80. data/lib/jade/frontend/type_checking/error/display.rb +49 -0
  81. data/lib/jade/frontend/type_checking/error/division_by_zero.rb +31 -0
  82. data/lib/jade/frontend/type_checking/error/empty_range_pattern.rb +25 -0
  83. data/lib/jade/frontend/type_checking/error/function_body_type_mismatch.rb +4 -3
  84. data/lib/jade/frontend/type_checking/error/function_call_type_mismatch.rb +108 -5
  85. data/lib/jade/frontend/type_checking/error/if_branch_type_mismatch.rb +1 -1
  86. data/lib/jade/frontend/type_checking/error/if_branches_type_mismatch.rb +1 -1
  87. data/lib/jade/frontend/type_checking/error/if_condition_type_mismatch.rb +1 -1
  88. data/lib/jade/frontend/type_checking/error/implementation_type_mismatch.rb +1 -1
  89. data/lib/jade/frontend/type_checking/error/list_item_type_mismatch.rb +1 -1
  90. data/lib/jade/frontend/type_checking/error/missing_patterns.rb +20 -4
  91. data/lib/jade/frontend/type_checking/error/pattern_type_mismatch.rb +7 -1
  92. data/lib/jade/frontend/type_checking/error/range_pattern_type.rb +29 -0
  93. data/lib/jade/frontend/type_checking/error/record_access_type_mismatch.rb +2 -2
  94. data/lib/jade/frontend/type_checking/error/recursive_derivation.rb +25 -0
  95. data/lib/jade/frontend/type_checking/error/type_mismatch.rb +10 -2
  96. data/lib/jade/frontend/type_checking/error/unreachable_branch.rb +22 -0
  97. data/lib/jade/frontend/type_checking/error.rb +6 -0
  98. data/lib/jade/frontend/type_checking/inference/assign.rb +1 -1
  99. data/lib/jade/frontend/type_checking/inference/case_of.rb +21 -4
  100. data/lib/jade/frontend/type_checking/inference/division.rb +50 -0
  101. data/lib/jade/frontend/type_checking/inference/function_call.rb +26 -1
  102. data/lib/jade/frontend/type_checking/inference/lambda.rb +1 -1
  103. data/lib/jade/frontend/type_checking/inference/pattern.rb +35 -0
  104. data/lib/jade/frontend/type_checking/inference/type_alias_declaration.rb +18 -0
  105. data/lib/jade/frontend/type_checking/inference.rb +2 -0
  106. data/lib/jade/frontend/type_checking/loader.rb +11 -0
  107. data/lib/jade/frontend/type_checking/port_resolution.rb +26 -8
  108. data/lib/jade/frontend/type_checking/substitution.rb +2 -2
  109. data/lib/jade/frontend/type_checking.rb +3 -1
  110. data/lib/jade/frontend/usage_analysis.rb +5 -1
  111. data/lib/jade/interop/boundary.rb +64 -24
  112. data/lib/jade/interop/error.rb +35 -7
  113. data/lib/jade/interop/lowering.rb +8 -2
  114. data/lib/jade/lsp/converters.rb +83 -2
  115. data/lib/jade/lsp/handlers.rb +122 -25
  116. data/lib/jade/lsp/snippets.rb +12 -0
  117. data/lib/jade/lsp/state.rb +11 -4
  118. data/lib/jade/module_loader/build.rb +36 -0
  119. data/lib/jade/module_loader.rb +1 -0
  120. data/lib/jade/parsing/combinators.rb +78 -2
  121. data/lib/jade/parsing/error.rb +43 -6
  122. data/lib/jade/parsing/type.rb +5 -3
  123. data/lib/jade/parsing.rb +140 -26
  124. data/lib/jade/runtime.rb +32 -13
  125. data/lib/jade/source.rb +6 -2
  126. data/lib/jade/stdlib/basics.rb +22 -3
  127. data/lib/jade/stdlib/char.rb +2 -0
  128. data/lib/jade/stdlib/clock.rb +6 -2
  129. data/lib/jade/stdlib/decimal.rb +51 -14
  130. data/lib/jade/stdlib/intrinsics.rb +59 -6
  131. data/lib/jade/stdlib/list.rb +37 -1
  132. data/lib/jade/stdlib/number.rb +47 -0
  133. data/lib/jade/stdlib/range.rb +211 -0
  134. data/lib/jade/stdlib.rb +11 -4
  135. data/lib/jade/symbol/alias.rb +11 -0
  136. data/lib/jade/symbol/parser.rb +1 -1
  137. data/lib/jade/symbol.rb +13 -1
  138. data/lib/jade/task.rb +0 -2
  139. data/lib/jade/type/anonymous_record.rb +11 -2
  140. data/lib/jade/type/application.rb +11 -2
  141. data/lib/jade/type/base.rb +26 -0
  142. data/lib/jade/type/function.rb +11 -2
  143. data/lib/jade/type.rb +36 -10
  144. data/lib/jade/version.rb +1 -1
  145. metadata +33 -2
@@ -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
- def context(name)
286
- map_error { |err| err.with_context(name) }
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
@@ -21,14 +21,15 @@ module Jade
21
21
  self
22
22
  end
23
23
 
24
- def with_context(name)
24
+ def copy(**changes)
25
25
  self.class.new(
26
- entry: @entry,
27
- span: @span,
28
- actual: @actual,
29
- expected: @expected,
26
+ entry: @entry,
27
+ span: @span,
28
+ actual: @actual,
29
+ expected: @expected,
30
30
  committed: @committed,
31
- context: [name, *@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.',
@@ -5,7 +5,9 @@ module Jade
5
5
  module Type
6
6
  extend Combinators::Dsl
7
7
 
8
- parser(:type_expression) { type_function | type_atom | type_record }
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 | type_declaration | import_declaration | interop_import_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
- .context("ternary")
132
+ .parsing("ternary")
133
133
  }
134
134
 
135
135
  parser(:tuple) {
136
136
  (
137
137
  type(:lparen) >>
138
- lazy { expression } >>
138
+ function_call_arg >>
139
139
  type(:comma).skip >>
140
- comma_sequence(lazy { expression }) >>
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
- .context("if/then/else")
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
- .context("case")
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 | literal_pattern | binding_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
- parser(:body) { sequence(lazy { statement }).map(&AST.body) }
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
- .context("import declaration")
494
+ .parsing("import declaration")
398
495
  }
399
496
 
400
497
  parser(:exposing) {
401
498
  (type(:exposing).skip >>
402
- type(:lparen).skip >>
403
- (expose_list | expose_all) >>
404
- type(:rparen).skip
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
- sequence(statement).map(&AST.body) >>
539
+ body >>
441
540
  type(:end)
442
541
  ).commit
443
542
  ).map(&AST.function_declaration)
444
- .context("function declaration")
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
- .context("type declaration")
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 >> lazy { expression }).map(&AST.record_field)
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
- .context("interop import declaration")
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
- .context("implementation")
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
- .context("interface declaration")
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
- .context("struct declaration")
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 = Data.define()
27
- EQ = Data.define()
28
- LT = Data.define()
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 "jade/stdlib/basics"
85
- require "jade/stdlib/string"
86
- require "jade/stdlib/list"
87
- require "jade/stdlib/tuple"
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 { Source.camelize(it) }
45
+ .map { camelize(it) }
42
46
  .join('.')
43
47
  end
44
48
 
@@ -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
- { '(+)' => 'a, a -> a', '(-)' => 'a, a -> a', '(*)' => 'a, a -> a', '(/)' => 'a, a -> a' }
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('Numeric', 'Int', '(+)' => 'int_add', '(-)' => 'int_sub', '(*)' => 'int_mul', '(/)' => 'int_div')
48
- implementation('Numeric', 'Float', '(+)' => 'float_add', '(-)' => 'float_sub', '(*)' => 'float_mul', '(/)' => 'float_div')
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')
@@ -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
@@ -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
- digits == 3 ? n : digits < 3 ? n * pow10(3 - digits) : n / pow10(digits - 3)
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