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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e4552bf1ca2e4a8214da4dff385246f52151d7b827d3305171639694e4ee4a5
4
- data.tar.gz: 9a1bbdcb02eafa6a62b37547a6ba8b2b73784908c0ceb5e8e7486691244f1da7
3
+ metadata.gz: 15e51e91117f799dec4acda81faa5fd62f8f75190570cebc69d69e0c588481cb
4
+ data.tar.gz: 1bd08d32f23d5b0e3f184f04d72aa1b44fd391bea3536ff2630ea93b1480f179
5
5
  SHA512:
6
- metadata.gz: 6a3a989e2ddea89618c0abb919e032953b244dbc8217617d009e7f55f62ba43b01bd9c033a4e0984770bfc3f81c75fc40f2f07aa99d43b3e164e13186b196bef
7
- data.tar.gz: bc7bfa312c8f42e70359ec9a9fdb6a67cfbcee12c8376195c7c4a36f626cd3840c3d6a0fdcd634147a61aa204776e9a0c99aeed587b92ab98d3d60dc4ace0f26
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.1.1...HEAD
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
@@ -1,3 +1,3 @@
1
1
  module Jade
2
- VERSION = '0.1.1'
2
+ VERSION = '0.2.0'
3
3
  end
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.1.1
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