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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +71 -1
- data/README.md +1 -1
- data/lib/jade/ast/node.rb +30 -0
- data/lib/jade/cli/q.rb +9 -5
- data/lib/jade/cli.rb +3 -0
- data/lib/jade/codegen/emitter.rb +2 -2
- data/lib/jade/codegen/inlines.rb +1 -0
- data/lib/jade/compiler.rb +17 -8
- data/lib/jade/formatter/helper.rb +11 -5
- data/lib/jade/frontend/comment_attacher.rb +13 -2
- data/lib/jade/frontend/semantic_analysis/error/placeholder_not_allowed.rb +22 -0
- data/lib/jade/frontend/semantic_analysis/error.rb +1 -0
- data/lib/jade/frontend/semantic_analysis/keyed_call.rb +55 -0
- data/lib/jade/frontend/type_checking/constraints/deriving/decodable.rb +26 -7
- data/lib/jade/frontend/type_checking/constraints/deriving/encodable.rb +20 -7
- data/lib/jade/frontend/type_checking/constraints/deriving/eq.rb +2 -1
- data/lib/jade/frontend/type_checking/constraints/deriving/helpers.rb +17 -0
- data/lib/jade/frontend/type_checking/constraints/deriving/sql_mapper.rb +130 -0
- data/lib/jade/frontend/type_checking/constraints/deriving.rb +2 -1
- data/lib/jade/frontend/type_checking/generalizer.rb +0 -1
- data/lib/jade/frontend/usage_analysis/reference_index.rb +7 -1
- data/lib/jade/frontend/usage_analysis.rb +78 -52
- data/lib/jade/parsing.rb +7 -1
- data/lib/jade/project.rb +100 -0
- data/lib/jade/source.rb +7 -3
- data/lib/jade/stdlib/decimal.rb +236 -0
- data/lib/jade/stdlib/decode.rb +18 -0
- data/lib/jade/stdlib.rb +4 -3
- data/lib/jade/type.rb +8 -5
- data/lib/jade/version.rb +1 -1
- data/lib/jade.rb +1 -0
- 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
|
data/lib/jade/stdlib/decode.rb
CHANGED
|
@@ -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
data/lib/jade.rb
CHANGED
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.
|
|
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-
|
|
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
|