jade-lang 0.3.1 → 0.5.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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +164 -0
  3. data/CHANGELOG.md +215 -1
  4. data/README.md +17 -12
  5. data/docs/interop.md +208 -0
  6. data/docs/json.md +163 -0
  7. data/docs/lsp.md +105 -0
  8. data/docs/stdlib.md +69 -0
  9. data/docs/syntax.md +458 -0
  10. data/docs/testing.md +70 -0
  11. data/lib/jade/api.rb +244 -0
  12. data/lib/jade/cli/check.rb +97 -0
  13. data/lib/jade/cli/q.rb +47 -1
  14. data/lib/jade/cli.rb +4 -2
  15. data/lib/jade/codegen/emitter.rb +11 -2
  16. data/lib/jade/codegen/function_call.rb +1 -1
  17. data/lib/jade/codegen/helpers.rb +6 -0
  18. data/lib/jade/codegen/inlines.rb +18 -0
  19. data/lib/jade/codegen/{port_decoder.rb → port_codec.rb} +27 -15
  20. data/lib/jade/codegen.rb +2 -2
  21. data/lib/jade/debug.rb +59 -0
  22. data/lib/jade/decode.rb +332 -212
  23. data/lib/jade/diagnostics/renderer.rb +16 -5
  24. data/lib/jade/frontend/fixity_fixer.rb +3 -2
  25. data/lib/jade/frontend/forward_declaration/interop_import_declaration.rb +13 -3
  26. data/lib/jade/frontend/pattern_analysis/matrix.rb +57 -23
  27. data/lib/jade/frontend/semantic_analysis/constructor_reference.rb +33 -7
  28. data/lib/jade/frontend/semantic_analysis/error/constructor_not_found.rb +20 -5
  29. data/lib/jade/frontend/semantic_analysis/error/variable_not_found.rb +9 -2
  30. data/lib/jade/frontend/semantic_analysis/member_access.rb +11 -1
  31. data/lib/jade/frontend/type_checking/constraints/deriving/decodable.rb +35 -32
  32. data/lib/jade/frontend/type_checking/constraints/deriving/encodable.rb +32 -50
  33. data/lib/jade/frontend/type_checking/constraints/deriving/eq.rb +51 -175
  34. data/lib/jade/frontend/type_checking/constraints/deriving/helpers.rb +133 -0
  35. data/lib/jade/frontend/type_checking/constraints/deriving/show.rb +186 -0
  36. data/lib/jade/frontend/type_checking/constraints/deriving.rb +2 -1
  37. data/lib/jade/frontend/type_checking/error/port_not_encodable.rb +38 -0
  38. data/lib/jade/frontend/type_checking/port_resolution.rb +64 -16
  39. data/lib/jade/interop/boundary.rb +2 -3
  40. data/lib/jade/interop/runtime.rb +10 -2
  41. data/lib/jade/lsp/converters.rb +2 -15
  42. data/lib/jade/lsp/snippets.rb +21 -3
  43. data/lib/jade/parsing/error.rb +19 -0
  44. data/lib/jade/runtime.rb +1 -0
  45. data/lib/jade/signature.rb +47 -0
  46. data/lib/jade/stdlib/calendar.rb +20 -19
  47. data/lib/jade/stdlib/clock.rb +7 -13
  48. data/lib/jade/stdlib/debug.rb +12 -0
  49. data/lib/jade/stdlib/decimal.rb +3 -16
  50. data/lib/jade/stdlib/decode.rb +57 -12
  51. data/lib/jade/stdlib/encode.rb +26 -0
  52. data/lib/jade/stdlib/intrinsics.rb +12 -1
  53. data/lib/jade/stdlib/result.rb +1 -1
  54. data/lib/jade/stdlib/show.rb +40 -0
  55. data/lib/jade/stdlib/text.rb +131 -0
  56. data/lib/jade/stdlib.rb +5 -2
  57. data/lib/jade/symbol/interop_function.rb +3 -1
  58. data/lib/jade/symbol.rb +2 -2
  59. data/lib/jade/task.rb +2 -3
  60. data/lib/jade/version.rb +1 -1
  61. metadata +19 -3
@@ -76,10 +76,17 @@ module Jade
76
76
  constraints:,
77
77
  )
78
78
  .with(module_name:)
79
- .tap { store(it) unless private }
79
+ .tap { store(it) }
80
+ .tap { private_names << name.to_s if private }
80
81
  .tap { Runtime.register(qualified_fn_name, &impl) }
81
82
  end
82
83
 
84
+ # Private means defined but not exposed: codegen and `implementation`
85
+ # resolve it, user modules cannot name it.
86
+ def private_names
87
+ @private_names ||= []
88
+ end
89
+
83
90
  # Approach A: the inline template is the single source of truth for a
84
91
  # function's behaviour. A hand-written block always wins. With no block
85
92
  # and a `body:` codegen override the function is codegen/dictionary-only
@@ -219,6 +226,7 @@ module Jade
219
226
  def exposes
220
227
  @symbols
221
228
  .reject { it.is_a?(Symbol::Implementation) }
229
+ .reject { it.is_a?(Symbol::StdlibFunction) && private_names.include?(it.name) }
222
230
  .map { it.to_ref }
223
231
  end
224
232
 
@@ -256,6 +264,9 @@ module Jade
256
264
 
257
265
  in 'Encodable'
258
266
  'Encode'
267
+
268
+ in 'Show'
269
+ 'Show'
259
270
  end
260
271
  .then { Symbol.type_ref(it, interface.to_s) }
261
272
  end
@@ -111,7 +111,7 @@ module Jade
111
111
  def on_error(result: Result(a, e), fn: e -> Result(a, f)) -> Result(a, f)
112
112
  case result
113
113
  in Err(error) then error |> fn
114
- in Ok(_) then result
114
+ in Ok(value) then Ok(value)
115
115
  end
116
116
  end
117
117
 
@@ -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
@@ -0,0 +1,131 @@
1
+ module Jade
2
+ module Stdlib
3
+ # Text representations of stdlib types, read in Ruby. Lives here rather
4
+ # than in Decimal / Clock / Calendar: those import Decode, so the
5
+ # instance cannot be registered the other way round. The types are
6
+ # compiled Jade, so constants resolve on first use.
7
+ #
8
+ # Accepts exactly what the Jade parsers did, down to `String.to_int`
9
+ # being `Integer(s, 10)` — whitespace and underscores included.
10
+ module Text
11
+ extend self
12
+
13
+ EPOCH_RATA_DIE = 719163
14
+ DAY_MS = 86_400_000
15
+
16
+ def to_decimal(s)
17
+ coefficient, exponent = s.split('e', 2)
18
+ return nil unless exponent
19
+
20
+ m = int(coefficient)
21
+ e = int(exponent)
22
+
23
+ m && e ? Jade::Decimal::Decimal[m, e] : nil
24
+ end
25
+
26
+ # No timezone offsets: `Clock.from_iso` never took them either.
27
+ def to_instant(s)
28
+ date_part, time_part = split_dt(s)
29
+ return nil unless date_part
30
+
31
+ d = to_date(date_part)
32
+ time = split_time(time_part)
33
+ return nil unless d && time
34
+
35
+ hour, minute, second, sub_ms = time
36
+ days = Jade::Calendar::Internal.to_rata_die(d) - EPOCH_RATA_DIE
37
+
38
+ Jade::Clock::Instant[
39
+ days * DAY_MS +
40
+ hour * 3_600_000 + minute * 60_000 + second * 1_000 + sub_ms
41
+ ]
42
+ end
43
+
44
+ def to_date(s)
45
+ parts = s.split('-')
46
+ return nil unless parts.length == 3
47
+
48
+ year, month, day = parts.map { int(it) }
49
+ return nil unless year && month && day
50
+
51
+ # Answers Jan outside 1..12 and never validates the day — calling
52
+ # it keeps that. Don't cache the Months: a second compile
53
+ # redefines the class.
54
+ Jade::Calendar::Date[
55
+ year,
56
+ Jade::Calendar::Internal.month_from_int(month),
57
+ day,
58
+ ]
59
+ end
60
+
61
+ # Seconds precision, always UTC. `rjust` matches `pad_to`, which
62
+ # left-pads without truncating: a negative year renders "00-5".
63
+ def from_instant(instant)
64
+ ms = instant._1
65
+ day_ms = ms % DAY_MS
66
+ d = Jade::Calendar::Internal.from_rata_die(ms / DAY_MS + EPOCH_RATA_DIE)
67
+
68
+ [
69
+ pad(d.year, 4),
70
+ '-', pad(Jade::Calendar::Internal.month_to_int(d.month), 2),
71
+ '-', pad(d.day, 2),
72
+ 'T', pad(day_ms / 3_600_000, 2),
73
+ ':', pad(day_ms % 3_600_000 / 60_000, 2),
74
+ ':', pad(day_ms % 60_000 / 1_000, 2),
75
+ 'Z',
76
+ ].join
77
+ end
78
+
79
+ private
80
+
81
+ def int(s) = Integer(s, 10, exception: false)
82
+
83
+ def pad(n, width) = n.to_s.rjust(width, '0')
84
+
85
+ def split_dt(s)
86
+ parts = s.split('T')
87
+ parts = s.split(' ') unless parts.length == 2
88
+
89
+ parts.length == 2 ? parts : nil
90
+ end
91
+
92
+ def split_time(s)
93
+ parts = strip_z(s).split(':')
94
+ return nil unless parts.length == 3
95
+
96
+ hour = int(parts[0])
97
+ minute = int(parts[1])
98
+ second, sub_ms = seconds(parts[2])
99
+
100
+ hour && minute && second && sub_ms ? [hour, minute, second, sub_ms] : nil
101
+ end
102
+
103
+ # A stray second "Z" falls through unstripped, as it did before.
104
+ def strip_z(s)
105
+ parts = s.split('Z')
106
+
107
+ parts.length == 1 || parts.length == 2 ? parts[0] : s
108
+ end
109
+
110
+ def seconds(s)
111
+ parts = s.split('.')
112
+
113
+ case parts.length
114
+ when 1 then [int(parts[0]), 0]
115
+ when 2 then [int(parts[0]), millis(int(parts[1]), parts[1].length)]
116
+ else [nil, nil]
117
+ end
118
+ end
119
+
120
+ def millis(n, digits)
121
+ return nil unless n
122
+
123
+ case digits <=> 3
124
+ when 0 then n
125
+ when -1 then n * 10.pow(3 - digits)
126
+ else n / 10.pow(digits - 3)
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
data/lib/jade/stdlib.rb CHANGED
@@ -16,6 +16,8 @@ require 'jade/stdlib/bytes'
16
16
  require 'jade/stdlib/calendar'
17
17
  require 'jade/stdlib/clock'
18
18
  require 'jade/stdlib/decimal'
19
+ require 'jade/stdlib/show'
20
+ require 'jade/stdlib/debug'
19
21
 
20
22
  module Jade
21
23
  module Stdlib
@@ -26,6 +28,7 @@ module Jade
26
28
  Decode Encode
27
29
  Bytes
28
30
  Dict Set
31
+ Show Debug
29
32
  ].freeze
30
33
  COMPILED = %w[Maybe Result Decode.Params Calendar Clock Decimal].freeze
31
34
  TOPLEVELS = (INTRINSICS + COMPILED).map { it.split('.', 2).first }.to_set.freeze
@@ -35,13 +38,13 @@ module Jade
35
38
  Stdlib::Dict, Stdlib::Set,
36
39
  Stdlib::Decode, Stdlib::Decode::Params, Stdlib::Encode,
37
40
  Stdlib::Bytes,
38
- Stdlib::Calendar, Stdlib::Clock, Stdlib::Decimal,
41
+ Stdlib::Calendar, Stdlib::Clock, Stdlib::Decimal, Stdlib::Show, Stdlib::Debug,
39
42
  ]
40
43
  # Loaded into the registry but not auto-imported into user modules.
41
44
  EXTENSIONS = [
42
45
  Stdlib::Dict, Stdlib::Set,
43
46
  Stdlib::Decode, Stdlib::Decode::Params, Stdlib::Encode,
44
- Stdlib::Calendar, Stdlib::Clock, Stdlib::Decimal,
47
+ Stdlib::Calendar, Stdlib::Clock, Stdlib::Decimal, Stdlib::Show, Stdlib::Debug,
45
48
  ]
46
49
 
47
50
  def load(registry)
@@ -6,8 +6,10 @@ module Jade
6
6
  :params,
7
7
  :return_type,
8
8
  :interop_module_name,
9
- :constraints, # [[iface_qname, var_name]] — implicit Decodable on var arms
9
+ :constraints, # [[iface_qname, var_name]] — implicit Decodable on the
10
+ # return arms' vars, Encodable on the params'
10
11
  :decoders, # { ok: impl_or_pass_or_dict, err: ... } | nil
12
+ :encoders, # [impl_or_pass_or_dict], one per param | nil
11
13
  ) do
12
14
  include Base
13
15
 
data/lib/jade/symbol.rb CHANGED
@@ -103,11 +103,11 @@ module Jade
103
103
  end
104
104
 
105
105
  def predeclared_interop_function(name)
106
- InteropFunction[nil, name, [], nil, nil, [], nil]
106
+ InteropFunction[nil, name, [], nil, nil, [], nil, nil]
107
107
  end
108
108
 
109
109
  def interop_function(name, params, return_type, interop_module_name, constraints: [])
110
- InteropFunction[nil, name, params, return_type, interop_module_name, constraints, nil]
110
+ InteropFunction[nil, name, params, return_type, interop_module_name, constraints, nil, nil]
111
111
  end
112
112
 
113
113
  def type_application(constructor, args, span)
data/lib/jade/task.rb CHANGED
@@ -92,9 +92,8 @@ module Jade
92
92
  private
93
93
 
94
94
  def decode(decoder, value)
95
- case Jade::Decode::Runner.run(decoder, value)
96
- in Jade::Result::Ok[v] then v
97
- in Jade::Result::Err[e] then fail Jade::Interop::DecodeError.new(e, value)
95
+ Jade::Decode::Runner.run!(decoder, value) do |error|
96
+ fail Jade::Interop::DecodeError.new(error, value)
98
97
  end
99
98
  end
100
99
  end
data/lib/jade/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Jade
2
- VERSION = '0.3.1'
2
+ VERSION = '0.5.0'
3
3
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jade-lang
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Agustin Cornu
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-07-31 00:00:00.000000000 Z
10
+ date: 2026-08-11 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64
@@ -35,17 +35,26 @@ executables:
35
35
  extensions: []
36
36
  extra_rdoc_files: []
37
37
  files:
38
+ - AGENTS.md
38
39
  - CHANGELOG.md
39
40
  - LICENSE
40
41
  - README.md
42
+ - docs/interop.md
43
+ - docs/json.md
44
+ - docs/lsp.md
45
+ - docs/stdlib.md
46
+ - docs/syntax.md
47
+ - docs/testing.md
41
48
  - exe/jade
42
49
  - lib/jade.rb
50
+ - lib/jade/api.rb
43
51
  - lib/jade/ast.rb
44
52
  - lib/jade/ast/node.rb
45
53
  - lib/jade/ast/nodes.rb
46
54
  - lib/jade/ast/pretty_printer.rb
47
55
  - lib/jade/calendar/runtime.rb
48
56
  - lib/jade/cli.rb
57
+ - lib/jade/cli/check.rb
49
58
  - lib/jade/cli/fmt.rb
50
59
  - lib/jade/cli/lsp.rb
51
60
  - lib/jade/cli/q.rb
@@ -69,13 +78,14 @@ files:
69
78
  - lib/jade/codegen/inlines.rb
70
79
  - lib/jade/codegen/method_names.rb
71
80
  - lib/jade/codegen/pattern/constructor.rb
72
- - lib/jade/codegen/port_decoder.rb
81
+ - lib/jade/codegen/port_codec.rb
73
82
  - lib/jade/codegen/pretty.rb
74
83
  - lib/jade/codegen/transforms/fold_shape.rb
75
84
  - lib/jade/codegen/transforms/self_call.rb
76
85
  - lib/jade/codegen/transforms/tail_call.rb
77
86
  - lib/jade/codegen/variant_declaration.rb
78
87
  - lib/jade/compiler.rb
88
+ - lib/jade/debug.rb
79
89
  - lib/jade/decode.rb
80
90
  - lib/jade/diagnostics.rb
81
91
  - lib/jade/diagnostics/renderer.rb
@@ -211,6 +221,7 @@ files:
211
221
  - lib/jade/frontend/type_checking/constraints/deriving/encodable.rb
212
222
  - lib/jade/frontend/type_checking/constraints/deriving/eq.rb
213
223
  - lib/jade/frontend/type_checking/constraints/deriving/helpers.rb
224
+ - lib/jade/frontend/type_checking/constraints/deriving/show.rb
214
225
  - lib/jade/frontend/type_checking/constraints/deriving/sql_mapper.rb
215
226
  - lib/jade/frontend/type_checking/definition.rb
216
227
  - lib/jade/frontend/type_checking/env.rb
@@ -228,6 +239,7 @@ files:
228
239
  - lib/jade/frontend/type_checking/error/missing_patterns.rb
229
240
  - lib/jade/frontend/type_checking/error/pattern_type_mismatch.rb
230
241
  - lib/jade/frontend/type_checking/error/port_not_decodable.rb
242
+ - lib/jade/frontend/type_checking/error/port_not_encodable.rb
231
243
  - lib/jade/frontend/type_checking/error/record_access_type_mismatch.rb
232
244
  - lib/jade/frontend/type_checking/error/type_mismatch.rb
233
245
  - lib/jade/frontend/type_checking/error/unresolved_constraint.rb
@@ -303,6 +315,7 @@ files:
303
315
  - lib/jade/registry.rb
304
316
  - lib/jade/result.rb
305
317
  - lib/jade/runtime.rb
318
+ - lib/jade/signature.rb
306
319
  - lib/jade/source.rb
307
320
  - lib/jade/stdlib.rb
308
321
  - lib/jade/stdlib/basics.rb
@@ -311,6 +324,7 @@ files:
311
324
  - lib/jade/stdlib/char.rb
312
325
  - lib/jade/stdlib/clock.rb
313
326
  - lib/jade/stdlib/compiled.rb
327
+ - lib/jade/stdlib/debug.rb
314
328
  - lib/jade/stdlib/decimal.rb
315
329
  - lib/jade/stdlib/decode.rb
316
330
  - lib/jade/stdlib/decode/params.rb
@@ -321,8 +335,10 @@ files:
321
335
  - lib/jade/stdlib/maybe.rb
322
336
  - lib/jade/stdlib/result.rb
323
337
  - lib/jade/stdlib/set.rb
338
+ - lib/jade/stdlib/show.rb
324
339
  - lib/jade/stdlib/string.rb
325
340
  - lib/jade/stdlib/task.rb
341
+ - lib/jade/stdlib/text.rb
326
342
  - lib/jade/stdlib/tuple.rb
327
343
  - lib/jade/symbol.rb
328
344
  - lib/jade/symbol/anonymous_record.rb