jade-lang 0.1.1 → 0.3.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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +71 -1
  3. data/README.md +1 -1
  4. data/lib/jade/ast/node.rb +30 -0
  5. data/lib/jade/cli/q.rb +9 -5
  6. data/lib/jade/cli.rb +3 -0
  7. data/lib/jade/codegen/emitter.rb +2 -2
  8. data/lib/jade/codegen/inlines.rb +1 -0
  9. data/lib/jade/compiler.rb +17 -8
  10. data/lib/jade/formatter/helper.rb +11 -5
  11. data/lib/jade/frontend/comment_attacher.rb +13 -2
  12. data/lib/jade/frontend/semantic_analysis/error/placeholder_not_allowed.rb +22 -0
  13. data/lib/jade/frontend/semantic_analysis/error.rb +1 -0
  14. data/lib/jade/frontend/semantic_analysis/keyed_call.rb +55 -0
  15. data/lib/jade/frontend/type_checking/constraints/deriving/decodable.rb +26 -7
  16. data/lib/jade/frontend/type_checking/constraints/deriving/encodable.rb +20 -7
  17. data/lib/jade/frontend/type_checking/constraints/deriving/eq.rb +2 -1
  18. data/lib/jade/frontend/type_checking/constraints/deriving/helpers.rb +17 -0
  19. data/lib/jade/frontend/type_checking/constraints/deriving/sql_mapper.rb +130 -0
  20. data/lib/jade/frontend/type_checking/constraints/deriving.rb +2 -1
  21. data/lib/jade/frontend/type_checking/generalizer.rb +0 -1
  22. data/lib/jade/frontend/usage_analysis/reference_index.rb +7 -1
  23. data/lib/jade/frontend/usage_analysis.rb +78 -52
  24. data/lib/jade/parsing.rb +7 -1
  25. data/lib/jade/project.rb +100 -0
  26. data/lib/jade/source.rb +7 -3
  27. data/lib/jade/stdlib/decimal.rb +236 -0
  28. data/lib/jade/stdlib/decode.rb +18 -0
  29. data/lib/jade/stdlib.rb +4 -3
  30. data/lib/jade/type.rb +8 -5
  31. data/lib/jade/version.rb +1 -1
  32. data/lib/jade.rb +1 -0
  33. metadata +6 -2
@@ -0,0 +1,236 @@
1
+ require 'jade/stdlib/compiled'
2
+
3
+ module Jade
4
+ module Stdlib
5
+ module Decimal
6
+ extend self
7
+ extend Compiled
8
+
9
+ def uri
10
+ 'decimal.jd'
11
+ end
12
+
13
+ def imports
14
+ [Basics, Maybe, String, Tuple, Decode, Encode]
15
+ end
16
+
17
+ def default_imports
18
+ []
19
+ end
20
+
21
+ def code
22
+ <<~JADE
23
+ module Decimal exposing (
24
+ Decimal,
25
+ coefficient,
26
+ div,
27
+ exponent,
28
+ of,
29
+ parse,
30
+ round,
31
+ scaled,
32
+ to_float,
33
+ to_i,
34
+ )
35
+
36
+ import Maybe
37
+ import Decode exposing (Decodable, Decoder)
38
+ import Encode exposing (Encodable)
39
+
40
+
41
+ # An exact base-10 decimal: value = coefficient * 10 ^ exponent.
42
+ # Lossless for money and rates — no Float rounding. Opaque: build one
43
+ # with `of`, `scaled`, or `parse`.
44
+ type Decimal = Decimal(Int, Int)
45
+
46
+
47
+ # value = coefficient * 10 ^ exponent. of(825, -6) = 0.000825.
48
+ def of(coeff: Int, exp: Int) -> Decimal
49
+ Decimal(coeff, exp)
50
+ end
51
+
52
+
53
+ # `scale` digits after the decimal point. scaled(825, 4) = 0.0825.
54
+ def scaled(unscaled: Int, scale: Int) -> Decimal
55
+ of(unscaled, 0 - scale)
56
+ end
57
+
58
+
59
+ def coefficient(d: Decimal) -> Int
60
+ Decimal(m, _) = d
61
+
62
+ m
63
+ end
64
+
65
+
66
+ def exponent(d: Decimal) -> Int
67
+ Decimal(_, e) = d
68
+
69
+ e
70
+ end
71
+
72
+
73
+ def pow10(n: Int) -> Int
74
+ n <= 0 ? 1 : 10 * pow10(n - 1)
75
+ end
76
+
77
+
78
+ def abs(x: Int) -> Int
79
+ x < 0 ? 0 - x : x
80
+ end
81
+
82
+
83
+ # Integer division, rounding ties half away from zero (Java HALF_UP /
84
+ # Ruby BigDecimal default: 2.5 -> 3, -2.5 -> -3). A zero `den` raises
85
+ # through Int `/`.
86
+ def round_div(num: Int, den: Int) -> Int
87
+ negative = not ((num < 0) == (den < 0))
88
+ n = abs(num)
89
+ d = abs(den)
90
+ q = n / d
91
+ r = n - q * d
92
+ rounded = (2 * r) >= d ? q + 1 : q
93
+
94
+ negative ? 0 - rounded : rounded
95
+ end
96
+
97
+
98
+ def trunc_div(num: Int, den: Int) -> Int
99
+ q = abs(num) / abs(den)
100
+
101
+ num < 0 ? 0 - q : q
102
+ end
103
+
104
+
105
+ # `+` `-` `*` are exact. `+`/`-` line the operands up on the smaller
106
+ # exponent first; `*` adds exponents.
107
+ def add(a: Decimal, b: Decimal) -> Decimal
108
+ Decimal(ma, ea) = a
109
+ Decimal(mb, eb) = b
110
+ e = ea < eb ? ea : eb
111
+
112
+ Decimal((ma * pow10(ea - e)) + (mb * pow10(eb - e)), e)
113
+ end
114
+
115
+
116
+ def sub(a: Decimal, b: Decimal) -> Decimal
117
+ Decimal(ma, ea) = a
118
+ Decimal(mb, eb) = b
119
+ e = ea < eb ? ea : eb
120
+
121
+ Decimal((ma * pow10(ea - e)) - (mb * pow10(eb - e)), e)
122
+ end
123
+
124
+
125
+ def mul(a: Decimal, b: Decimal) -> Decimal
126
+ Decimal(ma, ea) = a
127
+ Decimal(mb, eb) = b
128
+
129
+ Decimal(ma * mb, ea + eb)
130
+ end
131
+
132
+
133
+ # a / b rounded half-up to `scale` decimal places, like Java's
134
+ # BigDecimal.divide(divisor, scale, HALF_UP). Division by zero raises.
135
+ def div(a: Decimal, b: Decimal, scale: Int) -> Decimal
136
+ Decimal(ma, ea) = a
137
+ Decimal(mb, eb) = b
138
+ k = (ea - eb) + scale
139
+ num = k >= 0 ? ma * pow10(k) : ma
140
+ den = k >= 0 ? mb : mb * pow10(0 - k)
141
+
142
+ Decimal(round_div(num, den), 0 - scale)
143
+ end
144
+
145
+
146
+ # The Numeric `/`: like Ruby's BigDecimal `/`, it can't be exact for a
147
+ # repeating quotient (1/3), so it rounds half-up to a generous scale.
148
+ def divide(a: Decimal, b: Decimal) -> Decimal
149
+ div(a, b, 32)
150
+ end
151
+
152
+
153
+ implements Numeric(Decimal) with
154
+ (+): add,
155
+ (-): sub,
156
+ (*): mul,
157
+ (/): divide
158
+ end
159
+
160
+
161
+ # Round to `scale` decimal places, half-up. Values with fewer places
162
+ # than `scale` are left untouched.
163
+ def round(d: Decimal, scale: Int) -> Decimal
164
+ Decimal(m, e) = d
165
+ shift = e + scale
166
+
167
+ shift >= 0 ? d : Decimal(round_div(m, pow10(0 - shift)), 0 - scale)
168
+ end
169
+
170
+
171
+ # Truncates toward zero (drops the fractional part).
172
+ def to_i(d: Decimal) -> Int
173
+ Decimal(m, e) = d
174
+
175
+ e >= 0 ? m * pow10(e) : trunc_div(m, pow10(0 - e))
176
+ end
177
+
178
+
179
+ def to_float(d: Decimal) -> Float
180
+ Decimal(m, e) = d
181
+
182
+ e >= 0
183
+ ? Basics.to_float(m * pow10(e))
184
+ : Basics.to_float(m) / Basics.to_float(pow10(0 - e))
185
+ end
186
+
187
+
188
+ # Parse a decimal string: "0.0825", "-12.5", "42". Returns Nothing on
189
+ # anything that isn't a plain base-10 numeral.
190
+ def parse(s: String) -> Maybe(Decimal)
191
+ case String.split(s, ".")
192
+ in [whole] then
193
+ String.to_int(whole) |> Maybe.map((m) -> { of(m, 0) })
194
+
195
+ in [whole, frac] then
196
+ String.to_int(whole ++ frac)
197
+ |> Maybe.map((m) -> { of(m, 0 - String.length(frac)) })
198
+
199
+ else Nothing
200
+ end
201
+ end
202
+
203
+
204
+ def to_wire(d: Decimal) -> String
205
+ Decimal(m, e) = d
206
+
207
+ String.from_int(m) ++ "e" ++ String.from_int(e)
208
+ end
209
+
210
+
211
+ def from_wire(s: String) -> Decoder(Decimal)
212
+ case String.split(s, "e")
213
+ in [m, e]
214
+ case (String.to_int(m), String.to_int(e))
215
+ in (Just(mi), Just(ei)) then Decode.succeed(of(mi, ei))
216
+ else Decode.fail("invalid decimal: " ++ s)
217
+ end
218
+
219
+ else Decode.fail("invalid decimal: " ++ s)
220
+ end
221
+ end
222
+
223
+
224
+ implements Decodable(Decimal) with
225
+ decoder: -> { Decode.string |> Decode.and_then(from_wire) }
226
+ end
227
+
228
+
229
+ implements Encodable(Decimal) with
230
+ encoder: (d) -> { Encode.string(to_wire(d)) }
231
+ end
232
+ JADE
233
+ end
234
+ end
235
+ end
236
+ end
@@ -182,6 +182,24 @@ module Jade
182
182
  Jade::Decode::Decoder[Jade::Decode::Desc::Fail[msg]]
183
183
  }
184
184
 
185
+ # Backs derived Decodable for unions whose variants take no
186
+ # arguments. `names` and `values` are positionally paired.
187
+ function(
188
+ 'string_enum',
189
+ { names: 'List(String)', values: 'List(a)' },
190
+ 'Decoder(a)',
191
+ ) { |names, values|
192
+ table = names.zip(values).to_h
193
+
194
+ ->(s) {
195
+ table.key?(s) \
196
+ ? Jade::Decode::Decoder[Jade::Decode::Desc::Succeed[table[s]]]
197
+ : Jade::Decode::Decoder[Jade::Decode::Desc::Fail["expected one of #{names.join(', ')}, got #{s.inspect}"]]
198
+ }
199
+ .then { Jade::Decode::Desc::AndThen[it, Jade::Decode::Desc::Str[]] }
200
+ .then { Jade::Decode::Decoder[it] }
201
+ }
202
+
185
203
  function(
186
204
  'from_result',
187
205
  { r: 'Result(a, String)' },
data/lib/jade/stdlib.rb CHANGED
@@ -15,6 +15,7 @@ require 'jade/stdlib/encode'
15
15
  require 'jade/stdlib/bytes'
16
16
  require 'jade/stdlib/calendar'
17
17
  require 'jade/stdlib/clock'
18
+ require 'jade/stdlib/decimal'
18
19
 
19
20
  module Jade
20
21
  module Stdlib
@@ -26,7 +27,7 @@ module Jade
26
27
  Bytes
27
28
  Dict Set
28
29
  ].freeze
29
- COMPILED = %w[Maybe Result Decode.Params Calendar Clock].freeze
30
+ COMPILED = %w[Maybe Result Decode.Params Calendar Clock Decimal].freeze
30
31
  TOPLEVELS = (INTRINSICS + COMPILED).map { it.split('.', 2).first }.to_set.freeze
31
32
  STDLIBS = [
32
33
  Stdlib::Basics, Stdlib::Maybe, Stdlib::Tuple, Stdlib::List, Stdlib::Char,
@@ -34,13 +35,13 @@ module Jade
34
35
  Stdlib::Dict, Stdlib::Set,
35
36
  Stdlib::Decode, Stdlib::Decode::Params, Stdlib::Encode,
36
37
  Stdlib::Bytes,
37
- Stdlib::Calendar, Stdlib::Clock,
38
+ Stdlib::Calendar, Stdlib::Clock, Stdlib::Decimal,
38
39
  ]
39
40
  # Loaded into the registry but not auto-imported into user modules.
40
41
  EXTENSIONS = [
41
42
  Stdlib::Dict, Stdlib::Set,
42
43
  Stdlib::Decode, Stdlib::Decode::Params, Stdlib::Encode,
43
- Stdlib::Calendar, Stdlib::Clock,
44
+ Stdlib::Calendar, Stdlib::Clock, Stdlib::Decimal,
44
45
  ]
45
46
 
46
47
  def load(registry)
data/lib/jade/type.rb CHANGED
@@ -168,12 +168,15 @@ module Jade
168
168
  end
169
169
 
170
170
  interface = registry.lookup(symbol.interface)
171
+
172
+ # Keep the map the type param lands in: the return type has to see
173
+ # the same variable, or the constraint ends up on a variable that
174
+ # appears in no type and nothing can ever bind it.
175
+ param_type, _, local_map =
176
+ from_symbol_r(interface.type_param, registry, var_gen, local_map)
177
+
171
178
  constraint = Type
172
- .constraint(
173
- symbol.interface.qualified_name,
174
- from_symbol_r(interface.type_param, registry, var_gen, local_map).first,
175
- nil,
176
- )
179
+ .constraint(symbol.interface.qualified_name, param_type, nil)
177
180
 
178
181
  from_symbol_r(symbol.return_type, registry, var_gen, local_map)
179
182
  .then { |(t, c, _)| [args.empty? ? t : Type.function(args, t), c + arg_cs + [constraint]] }
data/lib/jade/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Jade
2
- VERSION = '0.1.1'
2
+ VERSION = '0.3.0'
3
3
  end
data/lib/jade.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'jade/version'
2
+ require 'jade/project'
2
3
  require 'jade/did_you_mean'
3
4
  require 'jade/symbol'
4
5
  require 'jade/registry'
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.1.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Agustin Cornu
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-06-17 00:00:00.000000000 Z
10
+ date: 2026-07-30 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64
@@ -157,6 +157,7 @@ files:
157
157
  - lib/jade/frontend/semantic_analysis/error/nested_task_port.rb
158
158
  - lib/jade/frontend/semantic_analysis/error/non_task_port.rb
159
159
  - lib/jade/frontend/semantic_analysis/error/orphan_implementation.rb
160
+ - lib/jade/frontend/semantic_analysis/error/placeholder_not_allowed.rb
160
161
  - lib/jade/frontend/semantic_analysis/error/predicate_must_return_bool.rb
161
162
  - lib/jade/frontend/semantic_analysis/error/predicate_name_not_allowed.rb
162
163
  - lib/jade/frontend/semantic_analysis/error/shadowing_error.rb
@@ -210,6 +211,7 @@ files:
210
211
  - lib/jade/frontend/type_checking/constraints/deriving/encodable.rb
211
212
  - lib/jade/frontend/type_checking/constraints/deriving/eq.rb
212
213
  - lib/jade/frontend/type_checking/constraints/deriving/helpers.rb
214
+ - lib/jade/frontend/type_checking/constraints/deriving/sql_mapper.rb
213
215
  - lib/jade/frontend/type_checking/definition.rb
214
216
  - lib/jade/frontend/type_checking/env.rb
215
217
  - lib/jade/frontend/type_checking/error.rb
@@ -297,6 +299,7 @@ files:
297
299
  - lib/jade/parsing/token.rb
298
300
  - lib/jade/parsing/type.rb
299
301
  - lib/jade/port.rb
302
+ - lib/jade/project.rb
300
303
  - lib/jade/registry.rb
301
304
  - lib/jade/result.rb
302
305
  - lib/jade/runtime.rb
@@ -308,6 +311,7 @@ files:
308
311
  - lib/jade/stdlib/char.rb
309
312
  - lib/jade/stdlib/clock.rb
310
313
  - lib/jade/stdlib/compiled.rb
314
+ - lib/jade/stdlib/decimal.rb
311
315
  - lib/jade/stdlib/decode.rb
312
316
  - lib/jade/stdlib/decode/params.rb
313
317
  - lib/jade/stdlib/dict.rb