morphir-python 0.0.1__py3-none-any.whl
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.
- morphir/__init__.py +14 -0
- morphir/ir/__init__.py +110 -0
- morphir/ir/access_controlled.py +15 -0
- morphir/ir/decorations.py +19 -0
- morphir/ir/diagnostics.py +105 -0
- morphir/ir/distribution.py +21 -0
- morphir/ir/document.py +37 -0
- morphir/ir/documented.py +11 -0
- morphir/ir/format_version.py +483 -0
- morphir/ir/fqname.py +9 -0
- morphir/ir/json.py +11 -0
- morphir/ir/literal.py +25 -0
- morphir/ir/meta.py +11 -0
- morphir/ir/migrate.py +1267 -0
- morphir/ir/module.py +17 -0
- morphir/ir/name.py +27 -0
- morphir/ir/package.py +15 -0
- morphir/ir/path.py +25 -0
- morphir/ir/qname.py +9 -0
- morphir/ir/ref.py +17 -0
- morphir/ir/type.py +37 -0
- morphir/ir/type_constraints.py +33 -0
- morphir/ir/type_def.py +19 -0
- morphir/ir/type_spec.py +25 -0
- morphir/ir/v3/__init__.py +64 -0
- morphir/ir/v3/_legacy_value.py +201 -0
- morphir/ir/v3/access_controlled.py +31 -0
- morphir/ir/v3/codec.py +1275 -0
- morphir/ir/v3/codec_v1.py +491 -0
- morphir/ir/v3/codec_v2.py +187 -0
- morphir/ir/v3/distribution.py +42 -0
- morphir/ir/v3/documented.py +13 -0
- morphir/ir/v3/fqname.py +30 -0
- morphir/ir/v3/literal.py +46 -0
- morphir/ir/v3/module.py +32 -0
- morphir/ir/v3/name.py +42 -0
- morphir/ir/v3/package.py +23 -0
- morphir/ir/v3/path.py +29 -0
- morphir/ir/v3/qname.py +16 -0
- morphir/ir/v3/type.py +126 -0
- morphir/ir/v3/value.py +235 -0
- morphir/ir/v4/__init__.py +160 -0
- morphir/ir/v4/access_controlled.py +17 -0
- morphir/ir/v4/annotation.py +75 -0
- morphir/ir/v4/codec.py +2161 -0
- morphir/ir/v4/decorations.py +88 -0
- morphir/ir/v4/distribution.py +56 -0
- morphir/ir/v4/document.py +69 -0
- morphir/ir/v4/documented.py +10 -0
- morphir/ir/v4/fqname.py +69 -0
- morphir/ir/v4/incompleteness.py +78 -0
- morphir/ir/v4/json.py +103 -0
- morphir/ir/v4/literal.py +51 -0
- morphir/ir/v4/meta.py +27 -0
- morphir/ir/v4/module.py +30 -0
- morphir/ir/v4/name.py +69 -0
- morphir/ir/v4/package.py +19 -0
- morphir/ir/v4/path.py +50 -0
- morphir/ir/v4/qname.py +34 -0
- morphir/ir/v4/ref.py +35 -0
- morphir/ir/v4/type.py +140 -0
- morphir/ir/v4/type_constraints.py +91 -0
- morphir/ir/v4/type_def.py +38 -0
- morphir/ir/v4/type_spec.py +64 -0
- morphir/ir/v4/value.py +480 -0
- morphir/ir/value.py +81 -0
- morphir/py.typed +0 -0
- morphir/sdk/__init__.py +110 -0
- morphir/sdk/_compare.py +187 -0
- morphir/sdk/aggregate.py +421 -0
- morphir/sdk/basics.py +504 -0
- morphir/sdk/char.py +110 -0
- morphir/sdk/decimal.py +311 -0
- morphir/sdk/dict.py +336 -0
- morphir/sdk/instant.py +118 -0
- morphir/sdk/int.py +86 -0
- morphir/sdk/key.py +522 -0
- morphir/sdk/list.py +356 -0
- morphir/sdk/local_date.py +436 -0
- morphir/sdk/local_time.py +267 -0
- morphir/sdk/maybe.py +135 -0
- morphir/sdk/number.py +237 -0
- morphir/sdk/regex.py +287 -0
- morphir/sdk/result.py +162 -0
- morphir/sdk/result_list.py +169 -0
- morphir/sdk/rule.py +103 -0
- morphir/sdk/set.py +214 -0
- morphir/sdk/stateful_app.py +49 -0
- morphir/sdk/string.py +427 -0
- morphir/sdk/tuple.py +43 -0
- morphir/sdk/uuid.py +209 -0
- morphir_python-0.0.1.dist-info/METADATA +269 -0
- morphir_python-0.0.1.dist-info/RECORD +99 -0
- morphir_python-0.0.1.dist-info/WHEEL +4 -0
- morphir_python-0.0.1.dist-info/entry_points.txt +2 -0
- morphir_python-0.0.1.dist-info/licenses/LICENSE +201 -0
- morphir_tools/__init__.py +13 -0
- morphir_tools/cli.py +223 -0
- morphir_tools/py.typed +0 -0
morphir/sdk/basics.py
ADDED
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
"""Morphir.SDK.Basics: Elm's Basics module.
|
|
2
|
+
|
|
3
|
+
Operators have the names that the Morphir IR SDK specification gives them
|
|
4
|
+
(`add`, `equal`, `append`, ...). Elm's `Int` is a Python `int` and `Float` is a
|
|
5
|
+
Python `float`. The names `not`, `and` and `or` are Python keywords, so they are
|
|
6
|
+
`not_`, `and_` and `or_` here.
|
|
7
|
+
|
|
8
|
+
Some names in this module shadow Python built-ins (`abs`, `max`, `min`, `round`).
|
|
9
|
+
Import the module, not the names:
|
|
10
|
+
|
|
11
|
+
>>> from morphir.sdk import basics
|
|
12
|
+
>>> basics.round(2.5)
|
|
13
|
+
3
|
|
14
|
+
|
|
15
|
+
Departures from Elm:
|
|
16
|
+
|
|
17
|
+
* Python integers do not overflow, so `Int` arithmetic is exact at any size.
|
|
18
|
+
* `remainder_by(0, x)` raises `ZeroDivisionError`. Elm yields `NaN`, which a
|
|
19
|
+
Python `int` cannot hold.
|
|
20
|
+
* `round`, `floor`, `ceiling` and `truncate` raise on `NaN` and the infinities,
|
|
21
|
+
which a Python `int` cannot hold.
|
|
22
|
+
* Strings compare by code point. Elm compares by UTF-16 code unit, so the order
|
|
23
|
+
differs only between characters above U+FFFF and those in U+E000..U+FFFF.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import builtins
|
|
27
|
+
import math
|
|
28
|
+
from typing import TYPE_CHECKING, Any, Never, overload
|
|
29
|
+
|
|
30
|
+
from morphir.sdk import _compare
|
|
31
|
+
from morphir.sdk._compare import Comparable, Order
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from collections.abc import Callable, Sequence
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"Comparable",
|
|
38
|
+
"Order",
|
|
39
|
+
"abs",
|
|
40
|
+
"acos",
|
|
41
|
+
"add",
|
|
42
|
+
"always",
|
|
43
|
+
"and_",
|
|
44
|
+
"append",
|
|
45
|
+
"asin",
|
|
46
|
+
"atan",
|
|
47
|
+
"atan2",
|
|
48
|
+
"ceiling",
|
|
49
|
+
"clamp",
|
|
50
|
+
"compare",
|
|
51
|
+
"compose_left",
|
|
52
|
+
"compose_right",
|
|
53
|
+
"cos",
|
|
54
|
+
"degrees",
|
|
55
|
+
"divide",
|
|
56
|
+
"e",
|
|
57
|
+
"equal",
|
|
58
|
+
"floor",
|
|
59
|
+
"from_polar",
|
|
60
|
+
"greater_than",
|
|
61
|
+
"greater_than_or_equal",
|
|
62
|
+
"identity",
|
|
63
|
+
"integer_divide",
|
|
64
|
+
"is_infinite",
|
|
65
|
+
"is_nan",
|
|
66
|
+
"less_than",
|
|
67
|
+
"less_than_or_equal",
|
|
68
|
+
"log_base",
|
|
69
|
+
"max",
|
|
70
|
+
"min",
|
|
71
|
+
"mod_by",
|
|
72
|
+
"multiply",
|
|
73
|
+
"negate",
|
|
74
|
+
"never",
|
|
75
|
+
"not_",
|
|
76
|
+
"not_equal",
|
|
77
|
+
"or_",
|
|
78
|
+
"pi",
|
|
79
|
+
"power",
|
|
80
|
+
"radians",
|
|
81
|
+
"remainder_by",
|
|
82
|
+
"round",
|
|
83
|
+
"sin",
|
|
84
|
+
"sqrt",
|
|
85
|
+
"subtract",
|
|
86
|
+
"tan",
|
|
87
|
+
"to_float",
|
|
88
|
+
"to_polar",
|
|
89
|
+
"truncate",
|
|
90
|
+
"turns",
|
|
91
|
+
"xor",
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
# Numbers
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def add[N: (int, float)](a: N, b: N) -> N:
|
|
98
|
+
"""Add two numbers (Elm `+`)."""
|
|
99
|
+
return a + b
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def subtract[N: (int, float)](a: N, b: N) -> N:
|
|
103
|
+
"""Subtract the second number from the first (Elm `-`)."""
|
|
104
|
+
return a - b
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def multiply[N: (int, float)](a: N, b: N) -> N:
|
|
108
|
+
"""Multiply two numbers (Elm `*`)."""
|
|
109
|
+
return a * b
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def divide(a: float, b: float) -> float:
|
|
113
|
+
"""Divide two floats (Elm `/`).
|
|
114
|
+
|
|
115
|
+
A zero divisor follows IEEE 754 like Elm, and does not raise: the result is
|
|
116
|
+
an infinity, or `NaN` for `0 / 0`.
|
|
117
|
+
"""
|
|
118
|
+
if b == 0:
|
|
119
|
+
if a == 0 or math.isnan(a):
|
|
120
|
+
return math.nan
|
|
121
|
+
return math.copysign(math.inf, a) * math.copysign(1.0, b)
|
|
122
|
+
return a / b
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def integer_divide(a: int, b: int) -> int:
|
|
126
|
+
"""Divide two integers (Elm `//`).
|
|
127
|
+
|
|
128
|
+
The result is truncated toward zero. A zero divisor yields 0, as in Elm.
|
|
129
|
+
"""
|
|
130
|
+
if b == 0:
|
|
131
|
+
return 0
|
|
132
|
+
quotient = builtins.abs(a) // builtins.abs(b)
|
|
133
|
+
return quotient if (a < 0) == (b < 0) else -quotient
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _is_odd_integer(value: float) -> bool:
|
|
137
|
+
return value.is_integer() and value % 2 == 1
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _float_power(base: float, exponent: float) -> float:
|
|
141
|
+
if math.isnan(exponent):
|
|
142
|
+
return math.nan
|
|
143
|
+
if exponent == 0:
|
|
144
|
+
return 1.0
|
|
145
|
+
if math.isnan(base) or (math.isinf(exponent) and abs(base) == 1):
|
|
146
|
+
return math.nan
|
|
147
|
+
negative = math.copysign(1.0, base) < 0 and _is_odd_integer(exponent)
|
|
148
|
+
try:
|
|
149
|
+
return math.pow(base, exponent)
|
|
150
|
+
except ValueError:
|
|
151
|
+
# Zero to a negative power is an infinity. A negative base with an
|
|
152
|
+
# exponent that is not an integer has no real result.
|
|
153
|
+
if base == 0:
|
|
154
|
+
return -math.inf if negative else math.inf
|
|
155
|
+
return math.nan
|
|
156
|
+
except OverflowError:
|
|
157
|
+
return -math.inf if negative else math.inf
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def power[N: (int, float)](base: N, exponent: N) -> N:
|
|
161
|
+
"""Raise a number to a power (Elm `^`).
|
|
162
|
+
|
|
163
|
+
Two integers with an exponent of zero or more give an exact integer. Every
|
|
164
|
+
other case follows JavaScript `Math.pow`, as the Elm runtime does, so the
|
|
165
|
+
result is always a real number, `NaN` or an infinity: `power(-1.0, 0.5)` is
|
|
166
|
+
`NaN` and `power(0.0, -1.0)` is infinity. Python `**` gives a complex number
|
|
167
|
+
for the first and raises for the second.
|
|
168
|
+
|
|
169
|
+
An integer with a negative exponent gives a float (`power(2, -1)` is 0.5),
|
|
170
|
+
as it does in Elm at run time.
|
|
171
|
+
"""
|
|
172
|
+
if (
|
|
173
|
+
isinstance(base, int)
|
|
174
|
+
and isinstance(exponent, int)
|
|
175
|
+
and not isinstance(base, bool)
|
|
176
|
+
and not isinstance(exponent, bool)
|
|
177
|
+
and exponent >= 0
|
|
178
|
+
):
|
|
179
|
+
exact: N = base**exponent
|
|
180
|
+
return exact
|
|
181
|
+
try:
|
|
182
|
+
real = _float_power(float(base), float(exponent))
|
|
183
|
+
except OverflowError:
|
|
184
|
+
# An integer too large for a float.
|
|
185
|
+
negative = base < 0 and _is_odd_integer(float(exponent))
|
|
186
|
+
real = -math.inf if negative else math.inf
|
|
187
|
+
# The result is a float even when N is int: an integer with a negative
|
|
188
|
+
# exponent has no integer result, in Elm as here.
|
|
189
|
+
result: Any = real
|
|
190
|
+
return result # type: ignore[no-any-return]
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def to_float(a: int) -> float:
|
|
194
|
+
"""Convert an integer to a float."""
|
|
195
|
+
return float(a)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def round(a: float) -> int:
|
|
199
|
+
"""Round to the nearest integer; a half goes toward positive infinity.
|
|
200
|
+
|
|
201
|
+
This is the behaviour of Elm and of JavaScript `Math.round`, not the
|
|
202
|
+
half-to-even rule of the Python built-in: `round(2.5)` is 3 and
|
|
203
|
+
`round(-2.5)` is -2.
|
|
204
|
+
|
|
205
|
+
Raises:
|
|
206
|
+
ValueError: If the value is `NaN`.
|
|
207
|
+
OverflowError: If the value is an infinity.
|
|
208
|
+
"""
|
|
209
|
+
lower = math.floor(a)
|
|
210
|
+
return lower + 1 if a - lower >= 0.5 else lower
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def floor(a: float) -> int:
|
|
214
|
+
"""Round down to an integer."""
|
|
215
|
+
return math.floor(a)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def ceiling(a: float) -> int:
|
|
219
|
+
"""Round up to an integer."""
|
|
220
|
+
return math.ceil(a)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def truncate(a: float) -> int:
|
|
224
|
+
"""Round toward zero to an integer."""
|
|
225
|
+
return math.trunc(a)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def mod_by(modulus: int, a: int) -> int:
|
|
229
|
+
"""Perform modular arithmetic; the result has the sign of the modulus.
|
|
230
|
+
|
|
231
|
+
Raises:
|
|
232
|
+
ZeroDivisionError: If the modulus is 0. Elm's runtime also fails.
|
|
233
|
+
"""
|
|
234
|
+
if modulus == 0:
|
|
235
|
+
raise ZeroDivisionError("mod_by: cannot perform mod 0")
|
|
236
|
+
return a % modulus
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def remainder_by(divisor: int, a: int) -> int:
|
|
240
|
+
"""Return the remainder of a division; the result has the sign of `a`.
|
|
241
|
+
|
|
242
|
+
Raises:
|
|
243
|
+
ZeroDivisionError: If the divisor is 0. Elm yields `NaN` here.
|
|
244
|
+
"""
|
|
245
|
+
if divisor == 0:
|
|
246
|
+
raise ZeroDivisionError("remainder_by: cannot divide by 0")
|
|
247
|
+
remainder = builtins.abs(a) % builtins.abs(divisor)
|
|
248
|
+
return -remainder if a < 0 else remainder
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def negate[N: (int, float)](a: N) -> N:
|
|
252
|
+
"""Change the sign of a number."""
|
|
253
|
+
return -a
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def abs[N: (int, float)](a: N) -> N:
|
|
257
|
+
"""Return the absolute value of a number."""
|
|
258
|
+
return builtins.abs(a) # pyright: ignore[reportReturnType]
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def clamp[A: Comparable](low: A, high: A, a: A) -> A:
|
|
262
|
+
"""Keep a value in the range from `low` to `high`."""
|
|
263
|
+
if less_than(a, low):
|
|
264
|
+
return low
|
|
265
|
+
if greater_than(a, high):
|
|
266
|
+
return high
|
|
267
|
+
return a
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def is_nan(a: float) -> bool:
|
|
271
|
+
"""Check that a float is `NaN` (Elm `isNaN`)."""
|
|
272
|
+
return math.isnan(a)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def is_infinite(a: float) -> bool:
|
|
276
|
+
"""Check that a float is positive or negative infinity."""
|
|
277
|
+
return math.isinf(a)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def sqrt(a: float) -> float:
|
|
281
|
+
"""Return the square root. A negative input yields `NaN`, as in Elm."""
|
|
282
|
+
return math.nan if a < 0 else math.sqrt(a)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _ln(a: float) -> float:
|
|
286
|
+
if a > 0:
|
|
287
|
+
return math.log(a)
|
|
288
|
+
return -math.inf if a == 0 else math.nan
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def log_base(base: float, a: float) -> float:
|
|
292
|
+
"""Return the logarithm of `a` in the given base.
|
|
293
|
+
|
|
294
|
+
Inputs outside the domain yield `NaN` or an infinity, as in Elm.
|
|
295
|
+
"""
|
|
296
|
+
return divide(_ln(a), _ln(base))
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
e: float = math.e
|
|
300
|
+
"""Euler's number."""
|
|
301
|
+
|
|
302
|
+
pi: float = math.pi
|
|
303
|
+
"""The ratio of the circumference of a circle to its diameter."""
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def cos(a: float) -> float:
|
|
307
|
+
"""Return the cosine of an angle in radians."""
|
|
308
|
+
return math.cos(a)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def sin(a: float) -> float:
|
|
312
|
+
"""Return the sine of an angle in radians."""
|
|
313
|
+
return math.sin(a)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def tan(a: float) -> float:
|
|
317
|
+
"""Return the tangent of an angle in radians."""
|
|
318
|
+
return math.tan(a)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def acos(a: float) -> float:
|
|
322
|
+
"""Return the arc cosine. An input outside -1..1 yields `NaN`, as in Elm."""
|
|
323
|
+
return math.acos(a) if -1 <= a <= 1 else math.nan
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def asin(a: float) -> float:
|
|
327
|
+
"""Return the arc sine. An input outside -1..1 yields `NaN`, as in Elm."""
|
|
328
|
+
return math.asin(a) if -1 <= a <= 1 else math.nan
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def atan(a: float) -> float:
|
|
332
|
+
"""Return the arc tangent."""
|
|
333
|
+
return math.atan(a)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def atan2(y: float, x: float) -> float:
|
|
337
|
+
"""Return the angle of the vector `(x, y)`, in the correct quadrant."""
|
|
338
|
+
return math.atan2(y, x)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def degrees(a: float) -> float:
|
|
342
|
+
"""Convert degrees to radians."""
|
|
343
|
+
return a * math.pi / 180
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def radians(a: float) -> float:
|
|
347
|
+
"""Convert radians to radians, which does nothing."""
|
|
348
|
+
return a
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def turns(a: float) -> float:
|
|
352
|
+
"""Convert turns to radians. One turn is 360 degrees."""
|
|
353
|
+
return a * 2 * math.pi
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def to_polar(point: tuple[float, float]) -> tuple[float, float]:
|
|
357
|
+
"""Convert Cartesian coordinates `(x, y)` to polar coordinates `(r, theta)`."""
|
|
358
|
+
x, y = point
|
|
359
|
+
return (math.hypot(x, y), math.atan2(y, x))
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def from_polar(polar: tuple[float, float]) -> tuple[float, float]:
|
|
363
|
+
"""Convert polar coordinates `(r, theta)` to Cartesian coordinates `(x, y)`."""
|
|
364
|
+
r, theta = polar
|
|
365
|
+
return (r * math.cos(theta), r * math.sin(theta))
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
# Equality and ordering
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def equal(a: object, b: object) -> bool:
|
|
372
|
+
"""Check structural equality (Elm `==`).
|
|
373
|
+
|
|
374
|
+
Raises:
|
|
375
|
+
TypeError: If a value is a function.
|
|
376
|
+
"""
|
|
377
|
+
return _compare.equal(a, b)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def not_equal(a: object, b: object) -> bool:
|
|
381
|
+
"""Check structural inequality (Elm `/=`).
|
|
382
|
+
|
|
383
|
+
Raises:
|
|
384
|
+
TypeError: If a value is a function.
|
|
385
|
+
"""
|
|
386
|
+
return not _compare.equal(a, b)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def compare[A: Comparable](a: A, b: A) -> Order:
|
|
390
|
+
"""Compare two comparable values.
|
|
391
|
+
|
|
392
|
+
Comparable values are numbers, strings, and tuples or lists of comparables.
|
|
393
|
+
|
|
394
|
+
Raises:
|
|
395
|
+
TypeError: If the values are not comparable or not of the same kind.
|
|
396
|
+
"""
|
|
397
|
+
return _compare.compare(a, b)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def less_than[A: Comparable](a: A, b: A) -> bool:
|
|
401
|
+
"""Check `a < b`."""
|
|
402
|
+
return _compare.compare(a, b) is Order.LT
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def greater_than[A: Comparable](a: A, b: A) -> bool:
|
|
406
|
+
"""Check `a > b`."""
|
|
407
|
+
return _compare.compare(a, b) is Order.GT
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def less_than_or_equal[A: Comparable](a: A, b: A) -> bool:
|
|
411
|
+
"""Check `a <= b`."""
|
|
412
|
+
return _compare.compare(a, b) is not Order.GT
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def greater_than_or_equal[A: Comparable](a: A, b: A) -> bool:
|
|
416
|
+
"""Check `a >= b`."""
|
|
417
|
+
return _compare.compare(a, b) is not Order.LT
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def max[A: Comparable](a: A, b: A) -> A:
|
|
421
|
+
"""Return the larger of two comparable values."""
|
|
422
|
+
return a if greater_than(a, b) else b
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def min[A: Comparable](a: A, b: A) -> A:
|
|
426
|
+
"""Return the smaller of two comparable values."""
|
|
427
|
+
return a if less_than(a, b) else b
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
# Booleans
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def not_(a: bool) -> bool:
|
|
434
|
+
"""Negate a boolean (Elm `not`)."""
|
|
435
|
+
return not a
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def and_(a: bool, b: bool) -> bool:
|
|
439
|
+
"""Return the logical AND of two booleans (Elm `&&`)."""
|
|
440
|
+
return a and b
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def or_(a: bool, b: bool) -> bool:
|
|
444
|
+
"""Return the logical OR of two booleans (Elm `||`)."""
|
|
445
|
+
return a or b
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def xor(a: bool, b: bool) -> bool:
|
|
449
|
+
"""Return the exclusive OR of two booleans."""
|
|
450
|
+
return a != b
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
# Appendables
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
@overload
|
|
457
|
+
def append(a: str, b: str) -> str: ... # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload]
|
|
458
|
+
@overload
|
|
459
|
+
def append[A](a: Sequence[A], b: Sequence[A]) -> tuple[A, ...]: ...
|
|
460
|
+
def append[A](a: str | Sequence[A], b: str | Sequence[A]) -> str | tuple[A, ...]:
|
|
461
|
+
"""Append two strings or two lists (Elm `++`).
|
|
462
|
+
|
|
463
|
+
Raises:
|
|
464
|
+
TypeError: If one argument is a string and the other is not.
|
|
465
|
+
"""
|
|
466
|
+
if isinstance(a, str) and isinstance(b, str):
|
|
467
|
+
return a + b
|
|
468
|
+
if isinstance(a, str) or isinstance(b, str):
|
|
469
|
+
raise TypeError("append: cannot append a string and a list")
|
|
470
|
+
return (*a, *b)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
# Functions
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def identity[A](a: A) -> A:
|
|
477
|
+
"""Return the argument."""
|
|
478
|
+
return a
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def always[A](a: A, _b: object) -> A:
|
|
482
|
+
"""Return the first argument and ignore the second."""
|
|
483
|
+
return a
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def compose_left[A, B, C](g: Callable[[B], C], f: Callable[[A], B]) -> Callable[[A], C]:
|
|
487
|
+
"""Compose two functions (Elm `g << f`): the result calls `f`, then `g`."""
|
|
488
|
+
return lambda a: g(f(a))
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def compose_right[A, B, C](
|
|
492
|
+
f: Callable[[A], B], g: Callable[[B], C]
|
|
493
|
+
) -> Callable[[A], C]:
|
|
494
|
+
"""Compose two functions (Elm `f >> g`): the result calls `f`, then `g`."""
|
|
495
|
+
return lambda a: g(f(a))
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def never(_a: Never) -> Never:
|
|
499
|
+
"""Handle a value of type `Never`. No such value exists, so this raises.
|
|
500
|
+
|
|
501
|
+
Raises:
|
|
502
|
+
TypeError: Always.
|
|
503
|
+
"""
|
|
504
|
+
raise TypeError("never: a value of type Never cannot exist")
|
morphir/sdk/char.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Morphir.SDK.Char: Elm's Char module.
|
|
2
|
+
|
|
3
|
+
A `Char` is a Python `str` that holds one code point. The classification
|
|
4
|
+
functions are ASCII-only, like elm/core: `is_alpha("é")` is false. They yield
|
|
5
|
+
false for a string that does not hold exactly one code point.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Char",
|
|
10
|
+
"from_code",
|
|
11
|
+
"is_alpha",
|
|
12
|
+
"is_alpha_num",
|
|
13
|
+
"is_digit",
|
|
14
|
+
"is_hex_digit",
|
|
15
|
+
"is_lower",
|
|
16
|
+
"is_oct_digit",
|
|
17
|
+
"is_upper",
|
|
18
|
+
"to_code",
|
|
19
|
+
"to_locale_lower",
|
|
20
|
+
"to_locale_upper",
|
|
21
|
+
"to_lower",
|
|
22
|
+
"to_upper",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
type Char = str
|
|
26
|
+
"""A string that holds one code point."""
|
|
27
|
+
|
|
28
|
+
_MAX_CODE_POINT = 0x10FFFF
|
|
29
|
+
_REPLACEMENT_CHAR = "\N{REPLACEMENT CHARACTER}"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _in_range(low: str, high: str, c: Char) -> bool:
|
|
33
|
+
return len(c) == 1 and low <= c <= high
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def is_upper(c: Char) -> bool:
|
|
37
|
+
"""Check for an ASCII upper case letter, `A` to `Z`."""
|
|
38
|
+
return _in_range("A", "Z", c)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def is_lower(c: Char) -> bool:
|
|
42
|
+
"""Check for an ASCII lower case letter, `a` to `z`."""
|
|
43
|
+
return _in_range("a", "z", c)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def is_alpha(c: Char) -> bool:
|
|
47
|
+
"""Check for an ASCII letter."""
|
|
48
|
+
return is_lower(c) or is_upper(c)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def is_alpha_num(c: Char) -> bool:
|
|
52
|
+
"""Check for an ASCII letter or digit."""
|
|
53
|
+
return is_alpha(c) or is_digit(c)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def is_digit(c: Char) -> bool:
|
|
57
|
+
"""Check for an ASCII digit, `0` to `9`."""
|
|
58
|
+
return _in_range("0", "9", c)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def is_oct_digit(c: Char) -> bool:
|
|
62
|
+
"""Check for an ASCII octal digit, `0` to `7`."""
|
|
63
|
+
return _in_range("0", "7", c)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def is_hex_digit(c: Char) -> bool:
|
|
67
|
+
"""Check for an ASCII hexadecimal digit: `0` to `9`, `a` to `f`, `A` to `F`."""
|
|
68
|
+
return is_digit(c) or _in_range("a", "f", c) or _in_range("A", "F", c)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def to_upper(c: Char) -> Char:
|
|
72
|
+
"""Convert to upper case.
|
|
73
|
+
|
|
74
|
+
Some characters expand to more than one code point ("ß" gives "SS"), as they
|
|
75
|
+
do in Elm.
|
|
76
|
+
"""
|
|
77
|
+
return c.upper()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def to_lower(c: Char) -> Char:
|
|
81
|
+
"""Convert to lower case."""
|
|
82
|
+
return c.lower()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def to_locale_upper(c: Char) -> Char:
|
|
86
|
+
"""Convert to upper case. Python has no locale rules here; see `to_upper`."""
|
|
87
|
+
return c.upper()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def to_locale_lower(c: Char) -> Char:
|
|
91
|
+
"""Convert to lower case. Python has no locale rules here; see `to_lower`."""
|
|
92
|
+
return c.lower()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def to_code(c: Char) -> int:
|
|
96
|
+
"""Return the Unicode code point of a character.
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
TypeError: If the string does not hold exactly one code point.
|
|
100
|
+
"""
|
|
101
|
+
return ord(c)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def from_code(code: int) -> Char:
|
|
105
|
+
"""Return the character for a Unicode code point.
|
|
106
|
+
|
|
107
|
+
A code outside the Unicode range yields U+FFFD, the replacement character,
|
|
108
|
+
like elm/core.
|
|
109
|
+
"""
|
|
110
|
+
return chr(code) if 0 <= code <= _MAX_CODE_POINT else _REPLACEMENT_CHAR
|