jade-lang 0.1.1 → 0.2.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 +13 -1
- data/README.md +1 -1
- data/lib/jade/stdlib/decimal.rb +236 -0
- data/lib/jade/stdlib.rb +4 -3
- data/lib/jade/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 15e51e91117f799dec4acda81faa5fd62f8f75190570cebc69d69e0c588481cb
|
|
4
|
+
data.tar.gz: 1bd08d32f23d5b0e3f184f04d72aa1b44fd391bea3536ff2630ea93b1480f179
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fcb1ae0703cdcd8c29c618b7aaf36d90c1f5b7eb4b95a1ee5f6dd6e4fd81d172977b3786442c1f1635e0ade323ff94a82fd95ee331a23f53459581966d08a346
|
|
7
|
+
data.tar.gz: 4208a71d7852def7334d49142dc7aea3b4f6344941a9e66cbc9d544b8f877d84680c7206518d61aaeb35d6ecd8eec9261f7a1adf5d64fe6346020e775fadaaae
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,17 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.0]
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `Decimal` stdlib module: exact base-10 decimals (`coefficient * 10 ^
|
|
14
|
+
exponent`) for money and rates without `Float` rounding. Opaque type built
|
|
15
|
+
with `of(coeff, exp)`, `scaled(unscaled, scale)`, or `parse("0.0825")`;
|
|
16
|
+
arithmetic via `Numeric` (`+` `-` `*` `/`) plus `div` (scaled, half-up),
|
|
17
|
+
`round`, `to_i`, `to_float`, `coefficient`, `exponent`. Decodes/encodes to a
|
|
18
|
+
`<mantissa>e<exponent>` JSON string. (Supersedes jade-sql's `Sql.Decimal`.)
|
|
19
|
+
|
|
9
20
|
## [0.1.1]
|
|
10
21
|
|
|
11
22
|
### Fixed
|
|
@@ -31,6 +42,7 @@ Initial release.
|
|
|
31
42
|
- `jade` CLI dispatcher: `fmt`, `lsp`, `q`.
|
|
32
43
|
- Language server and headless query interface for editor/agent tooling.
|
|
33
44
|
|
|
34
|
-
[Unreleased]: https://github.com/agustinrhcp/jade/compare/v0.
|
|
45
|
+
[Unreleased]: https://github.com/agustinrhcp/jade/compare/v0.2.0...HEAD
|
|
46
|
+
[0.2.0]: https://github.com/agustinrhcp/jade/compare/v0.1.1...v0.2.0
|
|
35
47
|
[0.1.1]: https://github.com/agustinrhcp/jade/compare/v0.1.0...v0.1.1
|
|
36
48
|
[0.1.0]: https://github.com/agustinrhcp/jade/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -357,7 +357,7 @@ pre-commit hook.
|
|
|
357
357
|
## Standard library
|
|
358
358
|
|
|
359
359
|
`Basics`, `String`, `Char`, `List`, `Dict`, `Set`, `Tuple`, `Maybe`, `Result`,
|
|
360
|
-
`Task`, `Decode`, `Encode`, `Bytes`, `Calendar`, `Clock`. Stdlib operations
|
|
360
|
+
`Task`, `Decode`, `Encode`, `Bytes`, `Calendar`, `Clock`, `Decimal`. Stdlib operations
|
|
361
361
|
compile inline rather than through a runtime dispatch layer.
|
|
362
362
|
|
|
363
363
|
## Docs
|
|
@@ -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.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/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jade-lang
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Agustin Cornu
|
|
@@ -308,6 +308,7 @@ files:
|
|
|
308
308
|
- lib/jade/stdlib/char.rb
|
|
309
309
|
- lib/jade/stdlib/clock.rb
|
|
310
310
|
- lib/jade/stdlib/compiled.rb
|
|
311
|
+
- lib/jade/stdlib/decimal.rb
|
|
311
312
|
- lib/jade/stdlib/decode.rb
|
|
312
313
|
- lib/jade/stdlib/decode/params.rb
|
|
313
314
|
- lib/jade/stdlib/dict.rb
|