lawspec 0.6.0 → 0.7.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.
- package/API-MIGRATION.md +85 -0
- package/PRIMITIVES.md +150 -0
- package/README.md +39 -20
- package/REFINEMENTS.md +158 -0
- package/bin/lawspec.mjs +24 -7
- package/build.json +36 -10
- package/core.wasm +0 -0
- package/examples/specs/refinements.lawspec +75 -0
- package/examples/specs/scalar_adapters.lawspec +83 -0
- package/examples/specs/scalar_catalog.lawspec +166 -0
- package/examples/specs/scalars.lawspec +138 -0
- package/examples-command.mjs +1 -1
- package/index.d.ts +19 -9
- package/package.json +2 -2
- package/scalars.mjs +13 -0
- package/templates.mjs +1 -1
package/API-MIGRATION.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Compiler API migration: schema 1 → schema 2
|
|
2
|
+
|
|
3
|
+
LawSpec 0.7.0 responses include `schemaVersion: 2`. Requests may omit the version
|
|
4
|
+
or explicitly send `schemaVersion: 2`; unsupported versions receive a request
|
|
5
|
+
diagnostic. Existing specification source remains compatible. Consumers of the
|
|
6
|
+
JSON AST must migrate together with the compiler.
|
|
7
|
+
|
|
8
|
+
## Scalar values
|
|
9
|
+
|
|
10
|
+
Example bindings and expected values are uniformly tagged. Do not coerce every
|
|
11
|
+
numeric value to JavaScript Number.
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
// Previously: ["x", 42]
|
|
15
|
+
// Now:
|
|
16
|
+
["x", { type: "Int32", value: "42" }]
|
|
17
|
+
|
|
18
|
+
// Lossless unsigned 64-bit example:
|
|
19
|
+
{ type: "UInt64", value: "18446744073709551615" }
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
| Domain | Payload after `type` |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| All integers | `value`: decimal string |
|
|
25
|
+
| Bool | `value`: Boolean |
|
|
26
|
+
| Decimal | `coefficient`, `exponent`: decimal strings |
|
|
27
|
+
| Rational | `numerator`, `denominator`: decimal strings; reduced, denominator positive |
|
|
28
|
+
| Float32 / Float64 | `bits`: 8 / 16 hexadecimal digits in IEEE bit order |
|
|
29
|
+
| Complex64 / Complex128 | `real`, `imaginary`: tagged component scalars |
|
|
30
|
+
| Char / CodePoint / CodeUnit16 | `value`: numeric unit |
|
|
31
|
+
| Text / CodePointText / Utf16Text / Bytes | `units`: numeric unit array |
|
|
32
|
+
| Symbol | `id`, `description`: strings; identity comes from the ID |
|
|
33
|
+
| Unit / Null / Undefined | No payload |
|
|
34
|
+
| Nullable / Optional | `value`: null for missing, otherwise a tagged scalar |
|
|
35
|
+
|
|
36
|
+
Text is also encoded as units, making the scalar schema uniform. Raw surrogate
|
|
37
|
+
code points never travel through JSON strings. The enclosing input type supplies
|
|
38
|
+
the inner type of a missing presence value.
|
|
39
|
+
|
|
40
|
+
`Expr.Number.contents` is now a decimal **string**. New expression nodes are
|
|
41
|
+
`DecimalNumber` (coefficient/exponent strings), `ScalarLit`, `Binary`, `Unary`, and `Annotate`. `Type.Applied` represents Nullable
|
|
42
|
+
and Optional. Expanded laws include `typedExpressions`, with operation operand
|
|
43
|
+
and result types plus an explicit `requiredConversion` for checked adapter bridges. Assertion trees remain authoritative;
|
|
44
|
+
`left`, `right`, and `guards` remain compatibility projections.
|
|
45
|
+
|
|
46
|
+
## Profiles and artifacts
|
|
47
|
+
|
|
48
|
+
Requests accept `machineBits?: 32 | 64`, defaulting to 64. Successful responses
|
|
49
|
+
include the selected `machineBits`. A profile controls language domains, not the
|
|
50
|
+
architecture of the WASM compiler itself.
|
|
51
|
+
|
|
52
|
+
Artifacts now include `placement: "source" | "test"` independently of
|
|
53
|
+
`ownership: "user" | "generated"`. A generated runtime belongs in a source
|
|
54
|
+
directory. Do not infer its placement from generated ownership. Continue using
|
|
55
|
+
the manifest writer to protect edited generated files and preserve user adapters.
|
|
56
|
+
|
|
57
|
+
The generated `index.d.ts` declares the complete discriminated unions. CLI
|
|
58
|
+
`explain` prints the new scalar values without converting large integers to
|
|
59
|
+
floating point. Native and WASM dispatch expose the same schema.
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
## Refinements in the unreleased 0.7.0 schema
|
|
63
|
+
|
|
64
|
+
API v2 also carries parameterized refinements and executable contracts. `Integer`
|
|
65
|
+
is a logical integer scalar tag whose `value` is a decimal string. Default integer
|
|
66
|
+
literals and promoted integer operations now have logical type `Integer`; explicit
|
|
67
|
+
`BigInt` declarations retain their native mappings. Both tags preserve exact values.
|
|
68
|
+
|
|
69
|
+
`Law.requirements` contains `Capability` nodes (`Eq`, `Integer`, `Ordered`,
|
|
70
|
+
`Bounded`) with their target types. Types add `Refined`, `RefinementApp`,
|
|
71
|
+
`Qualified`, and `CheckedType` nodes. Refinement arguments explicitly distinguish
|
|
72
|
+
`TypeArgument` from `ValueArgument`; declaration parameter kind `Type` is not a
|
|
73
|
+
runtime scalar. Expressions add `TypeBound` and logical operators.
|
|
74
|
+
|
|
75
|
+
Responses expose unit-owned `refinements` and `contracts`. Each expanded property
|
|
76
|
+
has `propertyKind` (`law` or `contract`), `generation` settings, and a
|
|
77
|
+
`generationPlan`. Inputs retain their underlying `inputType` and carry separate
|
|
78
|
+
`inputRefinements`. Domain plans include derived comparison bounds; the complete
|
|
79
|
+
predicate remains authoritative. Refinements must not be interpreted as implication
|
|
80
|
+
guards or as permission to count rejected inputs as successful checks.
|
|
81
|
+
|
|
82
|
+
Requests accept partial `generation` settings (`cases`, `maxAttempts`,
|
|
83
|
+
`maxShrinks`, `exhaustiveLimit`). Project configuration accepts the same object.
|
|
84
|
+
Generated TypeScript declarations define the complete wire shapes. These additions
|
|
85
|
+
are included in schema v2 before its release; schema v1 remains unsupported.
|
package/PRIMITIVES.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# LawSpec scalar reference (0.7.0)
|
|
2
|
+
|
|
3
|
+
A scalar has a declared domain, checked literals, equality, property inputs, and
|
|
4
|
+
boundary fixtures. General collections, objects, pointers, and type-only constructs
|
|
5
|
+
such as `never` are outside this release.
|
|
6
|
+
|
|
7
|
+
| Domain | Types | Representation and equality |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| Boolean | `Bool` | `true` or `false`; distinct from integers |
|
|
10
|
+
| Signed integers | `Int8`, `Int16`, `Int32`, `Int64` | −2^(bits−1) through 2^(bits−1)−1 |
|
|
11
|
+
| Unsigned integers | `UInt8`, `UInt16`, `UInt32`, `UInt64` | 0 through 2^bits−1 |
|
|
12
|
+
| Machine integers | `IntSize`, `UIntSize`, `UIntPtr` | Explicit 32- or 64-bit profile; no pointer operations |
|
|
13
|
+
| Abstract integers | `Integer` | Exact mathematical values, independent of storage width |
|
|
14
|
+
| Arbitrary integers | `BigInt`, `BigUInt` | Unbounded integer; BigUInt is nonnegative |
|
|
15
|
+
| Exact fractions | `Decimal`, `Rational` | Finite coefficient × 10^exponent; reduced numerator / positive denominator |
|
|
16
|
+
| Floating point | `Float32`, `Float64` | IEEE binary32 / binary64; NaN differs from itself, signed zeros compare equal |
|
|
17
|
+
| Complex | `Complex64`, `Complex128` | Two Float32 / Float64 components; componentwise IEEE equality |
|
|
18
|
+
| Characters | `Char`, `CodePoint`, `CodeUnit16` | Unicode scalar; code point including surrogates; arbitrary 16-bit unit |
|
|
19
|
+
| Sequences | `Text`, `CodePointText`, `Utf16Text`, `Bytes` | Unicode scalars; code points; UTF-16 units; octets |
|
|
20
|
+
| Identity | `Symbol` | Identity, independent of description |
|
|
21
|
+
| Absence | `Unit`, `Null`, `Undefined` | Three distinct singleton domains |
|
|
22
|
+
| Presence | `Nullable a`, `Optional a` | Null or a present scalar; Undefined or a present scalar |
|
|
23
|
+
|
|
24
|
+
`Char` and `Text` exclude U+D800–U+DFFF. Code points range from 0 through
|
|
25
|
+
0x10FFFF. UTF-16 units range from 0 through 65535. Bytes range from 0 through
|
|
26
|
+
255. Raw constructors preserve units without decoding or replacement.
|
|
27
|
+
|
|
28
|
+
## Literals and arithmetic
|
|
29
|
+
|
|
30
|
+
Integer literals inherit a declared parameter or example type, otherwise they
|
|
31
|
+
have type `Integer`. Decimal literals default to exact `Decimal`. An annotation
|
|
32
|
+
supplies context: `(127 :: Int8)`, `(0.1 :: Float32)`. Literals outside the
|
|
33
|
+
contextual domain are rejected during checking.
|
|
34
|
+
|
|
35
|
+
```lawspec
|
|
36
|
+
unit example.increment
|
|
37
|
+
successor :: (x :: Int8) -> (result :: Integer where result == x + 1)
|
|
38
|
+
law `promotes instead of wrapping` is
|
|
39
|
+
definition is `for all` (x :: Int8) . successor x = x + 1 end
|
|
40
|
+
example `maximum input` is x = 127 expect successor x = 128 end
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Integer `+`, `-`, `*`, and negation produce `Integer`. Decimal dominates integer
|
|
45
|
+
operands; Rational dominates exact operands. Exact `/` always produces Rational.
|
|
46
|
+
`prelude.quot` truncates toward zero, and `prelude.rem a b` satisfies
|
|
47
|
+
`a = quot a b * b + rem a b`. Division by zero fails when evaluated. Implication
|
|
48
|
+
guards short-circuit, including arithmetic and adapter calls.
|
|
49
|
+
|
|
50
|
+
Float32 operations round at binary32 precision; Float64 operations use binary64.
|
|
51
|
+
Mixed inexact operations widen to the greater component precision and to complex
|
|
52
|
+
when needed. Exact and inexact variables require explicit conversion:
|
|
53
|
+
`prelude.Float32 x`, `prelude.Float64 x`, `prelude.Rational x`,
|
|
54
|
+
`prelude.Decimal x`, or `prelude.Int8 x` (and the other numeric type names).
|
|
55
|
+
Fractional and out-of-range conversions to integers fail. A Rational conversion
|
|
56
|
+
to Decimal must terminate. Nonfinite floats cannot convert to exact numbers.
|
|
57
|
+
|
|
58
|
+
`prelude.round value scale` rounds an exact value to a specified number of decimal
|
|
59
|
+
places using ties-to-even. Negative scales round to powers of ten. Decimal
|
|
60
|
+
operations use exact arithmetic, independent of Python's decimal context or other
|
|
61
|
+
ambient rounding settings.
|
|
62
|
+
|
|
63
|
+
Comparison operators are `<`, `<=`, `>`, `>=`, `==`, and `!=`. Numeric comparisons
|
|
64
|
+
follow arithmetic's exact/inexact restriction. Complex numbers are not ordered.
|
|
65
|
+
`=` remains a law assertion. `==` and `!=` produce Bool and also support other
|
|
66
|
+
scalar domains. Multiplication and division bind more tightly than addition and
|
|
67
|
+
subtraction. Application and composition retain their existing syntax.
|
|
68
|
+
|
|
69
|
+
An adjacent sign remains an argument, as in `f -42`. Write `x - 42` for
|
|
70
|
+
subtraction, or parenthesize arithmetic arguments: `f (x - 42)`.
|
|
71
|
+
|
|
72
|
+
## Constructors and helpers
|
|
73
|
+
|
|
74
|
+
| Expression | Meaning |
|
|
75
|
+
| --- | --- |
|
|
76
|
+
| `decimal(123, -2)` | Exact 1.23 |
|
|
77
|
+
| `rational(1, 2)` | Exact 1/2 |
|
|
78
|
+
| `complex64(1, -2)`, `complex128(1, -2)` | Complex components at declared precision |
|
|
79
|
+
| `float32Bits("7fc00000")` | A binary32 NaN |
|
|
80
|
+
| `float64Bits("7ff0000000000000")` | Positive binary64 infinity |
|
|
81
|
+
| `float32Bits("80000000")` | Negative binary32 zero |
|
|
82
|
+
| `char(128512)` | Supplementary Unicode scalar |
|
|
83
|
+
| `codePoint(55296)` | Surrogate code point |
|
|
84
|
+
| `codeUnit16(55296)` | Lone UTF-16 surrogate unit |
|
|
85
|
+
| `codePoints([55296, 128512])` | Raw code-point sequence |
|
|
86
|
+
| `utf16([55296])` | Raw UTF-16 sequence |
|
|
87
|
+
| `bytes([0, 128, 255])` | Raw octets |
|
|
88
|
+
| `symbol("fixture-id", "description")` | Identity shared by the same ID within an example |
|
|
89
|
+
| `unitValue`, `null`, `undefined` | Distinct singleton values |
|
|
90
|
+
| `nullable(7)`, `optional(nullable(7))` | Present states, including nested states |
|
|
91
|
+
| `prelude.isNaN x`, `prelude.isInfinite x`, `prelude.isFinite x` | Float classification |
|
|
92
|
+
| `prelude.isNegativeZero x` | Float sign-bit classification |
|
|
93
|
+
| `prelude.real z`, `prelude.imag z` | Complex component access |
|
|
94
|
+
|
|
95
|
+
For `Optional (Nullable Int8)`, `undefined`, `optional(null)`, and
|
|
96
|
+
`optional(nullable(7))` are different values. Tagged support types preserve this
|
|
97
|
+
nesting, including on targets whose native null/optional types would collapse it.
|
|
98
|
+
|
|
99
|
+
## Target bridges and generated runtime
|
|
100
|
+
|
|
101
|
+
Existing Int32/Text/Bool-only specifications retain their adapter signatures.
|
|
102
|
+
Scalar specifications use native representations where the bridge implements the
|
|
103
|
+
whole domain, and generated support values elsewhere:
|
|
104
|
+
|
|
105
|
+
- Python: native int, bool, str, bytes, float, complex, Decimal and Fraction;
|
|
106
|
+
`Raw`, `Presence`, `Symbol`, and absence support values.
|
|
107
|
+
- JavaScript/TypeScript: number for small integers and floats, bigint for larger
|
|
108
|
+
integers, native strings, Uint8Array bytes and symbols; Decimal, Rational, Complex, Raw and
|
|
109
|
+
Presence support types. Unit is normalized from a void return.
|
|
110
|
+
- Java/Kotlin: native signed primitives and widened unsigned primitives,
|
|
111
|
+
BigInteger, BigDecimal, strings, byte arrays, code points, UTF-16 units and floating primitives; `LawSpecRuntime.Value`
|
|
112
|
+
for remaining domains. Unit-returning adapters normalize native void/Unit.
|
|
113
|
+
- Go: native integer widths, machine integers, floats and complex values,
|
|
114
|
+
`math/big.Int` and `math/big.Rat` (exported aliases), strings, runes, byte arrays and UTF-16 arrays;
|
|
115
|
+
`LawSpecValue` for remaining domains.
|
|
116
|
+
- Haskell: native fixed integers, Integer, Rational, Float, Double, Complex, Char, Text, ByteString and `()`;
|
|
117
|
+
the `Scalar` support type for remaining domains. `Text` maps to `Data.Text.Text`,
|
|
118
|
+
not Haskell’s linked-list `String` (`[Char]`). General list types such as `[Char]`
|
|
119
|
+
are outside this scalar release.
|
|
120
|
+
|
|
121
|
+
Input bridges check primitive bounds before native calls. Result bridges validate
|
|
122
|
+
returned values. In particular, Text bridges reject invalid Unicode rather than
|
|
123
|
+
repairing it. Use raw domains when preserving arbitrary bytes or UTF-16 units.
|
|
124
|
+
|
|
125
|
+
The runtime sources are emitted separately from framework-specific tests. They
|
|
126
|
+
have no property-testing or assertion-library dependencies. Runtime files are
|
|
127
|
+
generated-owned **source** artifacts; adapters are user-owned source artifacts.
|
|
128
|
+
Custom source/test directories and generation manifests preserve that distinction.
|
|
129
|
+
Haskell scalar projects require the standard `bytestring` package in their library
|
|
130
|
+
dependencies (included by new project templates).
|
|
131
|
+
Kotlin uses the shared Java runtime under `src/main/java` by default; custom
|
|
132
|
+
projects must include the configured source directory in their Java source set.
|
|
133
|
+
|
|
134
|
+
## Machine profiles
|
|
135
|
+
|
|
136
|
+
Set `"machineBits": 32` or `64` in `lawspec.json`, the compiler request, or use
|
|
137
|
+
`--machine-bits 32`. The default is 64. Bounds, examples, generators, and bridges
|
|
138
|
+
use this profile. Go and Haskell native machine-sized bridges report an
|
|
139
|
+
architecture mismatch when the native word size differs. Fixed-width types remain
|
|
140
|
+
portable across architectures.
|
|
141
|
+
|
|
142
|
+
The bundled `scalars.lawspec`, `scalar_catalog.lawspec`, and
|
|
143
|
+
`scalar_adapters.lawspec` contain explicit fixtures for every scalar family.
|
|
144
|
+
`tools/scalar-reference.py` produces independent Fraction-based conformance
|
|
145
|
+
vectors; `tools/scalar-integration.mjs` executes them across the seven targets.
|
|
146
|
+
Set `LAWSPEC_MUTANTS=1` to also verify that incorrect adapters are detected.
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
Refinements, parameterized domains, abstract native integer results, and executable
|
|
150
|
+
function contracts are described in [REFINEMENTS.md](REFINEMENTS.md).
|
package/README.md
CHANGED
|
@@ -6,12 +6,24 @@ LawSpec 0.6 compiles reusable laws into native property tests, executable exampl
|
|
|
6
6
|
and implementation adapters. The compiler is Haskell, distributed as prebuilt
|
|
7
7
|
WebAssembly with a Node CLI and an asynchronous, typed JavaScript API.
|
|
8
8
|
|
|
9
|
+
## Portable scalars in 0.7.0
|
|
10
|
+
|
|
11
|
+
LawSpec now supports fixed and arbitrary integers, exact decimals and rationals,
|
|
12
|
+
IEEE floats and complex values, Unicode and raw-text domains, bytes, symbols,
|
|
13
|
+
and nested absence states on all seven targets. Integer arithmetic produces representation-independent
|
|
14
|
+
`Integer` values; exact division returns Rational. See the [primitive reference](PRIMITIVES.md)
|
|
15
|
+
for constructors, arithmetic, native bridges, and machine-width profiles.
|
|
16
|
+
|
|
17
|
+
Compiler API consumers should read the [schema v2 migration guide](API-MIGRATION.md).
|
|
18
|
+
Scalar values use tagged, lossless encodings; generated runtime placement is
|
|
19
|
+
separate from artifact ownership. Java 25+ and Python 3.13+ baselines are unchanged.
|
|
20
|
+
|
|
9
21
|
## Install and try it
|
|
10
22
|
|
|
11
23
|
Install [LawSpec from npm](https://www.npmjs.com/package/lawspec):
|
|
12
24
|
|
|
13
25
|
```sh
|
|
14
|
-
npm install --save-dev lawspec@0.
|
|
26
|
+
npm install --save-dev lawspec@0.7.0
|
|
15
27
|
npx lawspec --version
|
|
16
28
|
```
|
|
17
29
|
|
|
@@ -25,8 +37,8 @@ local dependencies so LawSpec can create its `package.json` and test script:
|
|
|
25
37
|
```sh
|
|
26
38
|
mkdir lawspec-example
|
|
27
39
|
cd lawspec-example
|
|
28
|
-
npm exec --package=lawspec@0.
|
|
29
|
-
npm install --save-dev lawspec@0.
|
|
40
|
+
npm exec --package=lawspec@0.7.0 -- lawspec init --target javascript
|
|
41
|
+
npm install --save-dev lawspec@0.7.0
|
|
30
42
|
npx lawspec check
|
|
31
43
|
npx lawspec explain 'example.atoi_codec::itoa and then atoi yields a'
|
|
32
44
|
npx lawspec doctor
|
|
@@ -157,13 +169,13 @@ function names specially. `explain` shows the final property:
|
|
|
157
169
|
for all (x :: Int32) . atoi (itoa (x)) = x
|
|
158
170
|
```
|
|
159
171
|
|
|
160
|
-
Reusable laws can declare typed unary function parameters and `requires Eq a
|
|
172
|
+
Reusable laws can declare typed unary function parameters and `requires Eq a`, numeric capabilities such as `requires Integer a`, and
|
|
173
|
+
[parameterized refinements and executable contracts](REFINEMENTS.md).
|
|
161
174
|
Definitions support law application, function application/composition, universal
|
|
162
175
|
quantification, `implies`, Boolean predicates, scalar literals, and equality.
|
|
163
|
-
Function signatures
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
compared results. Functions are synchronous and support curried signatures with any positive
|
|
176
|
+
Function signatures and quantified inputs support the [scalar catalog](PRIMITIVES.md),
|
|
177
|
+
including mixed and multiple inputs. Generic variables are supported in reusable
|
|
178
|
+
laws. Scalar types can also be intermediate or compared results. Functions are synchronous and support curried signatures with any positive
|
|
167
179
|
number of scalar arguments. Text literals are double-quoted, with escapes such as `\"`, `\\`,
|
|
168
180
|
`\n`, and `\t`; examples must bind each input to a literal of its declared type.
|
|
169
181
|
Text values contain Unicode scalar values; surrogate code points are rejected.
|
|
@@ -175,7 +187,7 @@ produce literal braces. Law blocks use this order:
|
|
|
175
187
|
definition, optional description, optional rationale, examples, optional references.
|
|
176
188
|
`--` starts a line comment. Names that cannot be emitted portably are diagnosed.
|
|
177
189
|
|
|
178
|
-
|
|
190
|
+
General collections, external law packages, cross-unit imports beyond the
|
|
179
191
|
prelude, async functions, direct existing-symbol binding and browser hosting are
|
|
180
192
|
outside this release.
|
|
181
193
|
|
|
@@ -232,7 +244,7 @@ reusable laws accept these curried functions, their partial applications, and
|
|
|
232
244
|
scalar parameters. Quantified test inputs remain scalar.
|
|
233
245
|
|
|
234
246
|
The prelude defines the following laws. Every row has an executable example in
|
|
235
|
-
[algebra.lawspec](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
247
|
+
[algebra.lawspec](https://github.com/brain-fuel/lawspec/blob/v0.7.0/examples/specs/algebra.lawspec), including both sides of every
|
|
236
248
|
combined law. `f` and `g` are binary operations, `inverse` is unary, and `e` and
|
|
237
249
|
`zero` are scalar parameters. All these laws require equality of the element type.
|
|
238
250
|
|
|
@@ -300,7 +312,7 @@ their entire consequence. Every conjunct is type-checked and emitted. As with
|
|
|
300
312
|
existing assertions, the first failure stops that individual test. `and` is now
|
|
301
313
|
a reserved word.
|
|
302
314
|
|
|
303
|
-
[Currying examples](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
315
|
+
[Currying examples](https://github.com/brain-fuel/lawspec/blob/v0.7.0/examples/specs/currying.lawspec) demonstrate a four-argument
|
|
304
316
|
function partially applied twice, a formatter with four heterogeneous arguments,
|
|
305
317
|
and composition after partial application. Each example states its exact outputs.
|
|
306
318
|
Run `node npm/bin/lawspec.mjs examples` after rebuilding to inspect all nine units
|
|
@@ -352,7 +364,7 @@ law `valid ports round trip` is
|
|
|
352
364
|
end
|
|
353
365
|
```
|
|
354
366
|
|
|
355
|
-
The [complete port example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
367
|
+
The [complete port example](https://github.com/brain-fuel/lawspec/blob/v0.7.0/examples/specs/parse_port.lawspec)
|
|
356
368
|
defines valid ports as 1–65535, and covers both endpoints, ordinary ports, zero,
|
|
357
369
|
negative values, and 65536. All explicit `expect` assertions run regardless of
|
|
358
370
|
the law's condition. A false condition skips only the consequence: invalid ports
|
|
@@ -370,7 +382,7 @@ The prelude includes `satisfies predicate` (the predicate holds for every input)
|
|
|
370
382
|
and `left inverse when predicate parse render` (the guarded round trip above).
|
|
371
383
|
These reusable laws preserve the condition and its lexical bindings when expanded.
|
|
372
384
|
`equivalent` can also compare two predicates, since `Bool` supports equality.
|
|
373
|
-
The [Boolean flags example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
385
|
+
The [Boolean flags example](https://github.com/brain-fuel/lawspec/blob/v0.7.0/examples/specs/boolean_flags.lawspec)
|
|
374
386
|
checks that flipping twice restores both `false` and `true`; all targets generate
|
|
375
387
|
Boolean property inputs and explicit tests for both Boolean boundary values. Java caps Boolean-only JetCheck runs at the number of
|
|
376
388
|
possible input combinations (up to 100), avoiding generator exhaustion.
|
|
@@ -391,7 +403,7 @@ preserves your adapter and reports the required stub shape. Generated tests call
|
|
|
391
403
|
|
|
392
404
|
Every `example` must bind all quantified inputs and then include one or more
|
|
393
405
|
`expect <expression> = <literal>` assertions. The expected literal must have the
|
|
394
|
-
|
|
406
|
+
contextual scalar type of the expression. Expressions can reference the
|
|
395
407
|
example's inputs and the unit's functions, including composed function calls.
|
|
396
408
|
Input names shadow function names within expectations, following lexical scope.
|
|
397
409
|
|
|
@@ -449,7 +461,7 @@ This expands to `for all (x :: Int32) . render (x) = referenceRender (x)`.
|
|
|
449
461
|
The example inherits the input name `x` from the prelude. Both functions are
|
|
450
462
|
user-owned adapter functions; either may delegate to your existing code.
|
|
451
463
|
|
|
452
|
-
[The complete example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
464
|
+
[The complete example](https://github.com/brain-fuel/lawspec/blob/v0.7.0/examples/specs/equivalent.lawspec) compares decimal
|
|
453
465
|
renderers and two implementations that clamp negative integers to zero. For
|
|
454
466
|
JavaScript, their adapters can be:
|
|
455
467
|
|
|
@@ -464,7 +476,7 @@ The same specification generates native tests for all seven targets. The
|
|
|
464
476
|
integration suite checks both examples with matching implementations, then
|
|
465
477
|
breaks each alternative separately to verify detection. The general equivalence law alone does not establish independent correctness;
|
|
466
478
|
two implementations can share the same bug. Explicit expectations additionally
|
|
467
|
-
check the specified outputs at the supplied example inputs. Quantified inputs can
|
|
479
|
+
check the specified outputs at the supplied example inputs. Quantified inputs can use any supported scalar domain.
|
|
468
480
|
|
|
469
481
|
## Text properties and idempotence
|
|
470
482
|
|
|
@@ -486,7 +498,7 @@ law `normalizers agree` is
|
|
|
486
498
|
end
|
|
487
499
|
```
|
|
488
500
|
|
|
489
|
-
The [slug example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
501
|
+
The [slug example](https://github.com/brain-fuel/lawspec/blob/v0.7.0/examples/specs/slug.lawspec)
|
|
490
502
|
compares two implementations of ASCII-space replacement. It includes empty,
|
|
491
503
|
Unicode and escaped text. Each target uses its native string generator:
|
|
492
504
|
JetCheck `Generator.stringsOf(Generator.asciiPrintableChars())`, Hypothesis `st.text()`, fast-check `fc.string()`,
|
|
@@ -513,7 +525,7 @@ law `canonicalization reaches a fixed point` is
|
|
|
513
525
|
end
|
|
514
526
|
```
|
|
515
527
|
|
|
516
|
-
The [canonical URL example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
528
|
+
The [canonical URL example](https://github.com/brain-fuel/lawspec/blob/v0.7.0/examples/specs/canonical_url.lawspec)
|
|
517
529
|
uses removal of **all trailing slashes** as a small fixed-point demonstration,
|
|
518
530
|
not a complete URL canonicalization algorithm. For JavaScript:
|
|
519
531
|
|
|
@@ -522,7 +534,7 @@ export const canonicalize = value => value.replace(/\/+$/, "");
|
|
|
522
534
|
```
|
|
523
535
|
|
|
524
536
|
Removing just one trailing slash fails the supplied repeated-slash example.
|
|
525
|
-
The [mixed-input example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
537
|
+
The [mixed-input example](https://github.com/brain-fuel/lawspec/blob/v0.7.0/examples/specs/mixed_inputs.lawspec)
|
|
526
538
|
shows `Text` and `Int32` in the same quantified property and executable example.
|
|
527
539
|
The JavaScript API represents input bindings and expected values as `number | string | boolean`.
|
|
528
540
|
Each example includes `expectations: { actual: Expr; expected: number | string | boolean }[]`.
|
|
@@ -587,7 +599,7 @@ by the JS shim.
|
|
|
587
599
|
## Build and verify
|
|
588
600
|
|
|
589
601
|
For contributors working from a repository checkout, build a local archive with
|
|
590
|
-
`npm pack ./npm` and install it with `npm install --save-dev ./lawspec-0.
|
|
602
|
+
`npm pack ./npm` and install it with `npm install --save-dev ./lawspec-0.7.0.tgz`.
|
|
591
603
|
The package payload lives in `npm/`.
|
|
592
604
|
|
|
593
605
|
```sh
|
|
@@ -625,3 +637,10 @@ fail, regeneration preserves implementations, and generation leaves build files
|
|
|
625
637
|
unchanged. Arguments select individual targets. `LAWSPEC_PYTHON=3.14` selects the
|
|
626
638
|
additional Python reference environment. CI also exercises Node 22/24/26 and packs
|
|
627
639
|
and installs the npm archive. Registry publication is a separate release action.
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
Refinement predicates can depend on earlier inputs. Generated tests backtrack from
|
|
643
|
+
impossible prefixes, preserve the domain during shrinking, and validate function
|
|
644
|
+
preconditions and postconditions. `Integer` results specify exact values without
|
|
645
|
+
choosing a storage width. See the [refinement reference](REFINEMENTS.md) and
|
|
646
|
+
[bundled examples](examples/specs/refinements.lawspec).
|
package/REFINEMENTS.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Refinements and abstract integers (0.7.0)
|
|
2
|
+
|
|
3
|
+
A refinement restricts a scalar domain with a pure Boolean expression. LawSpec
|
|
4
|
+
checks concrete examples, generates satisfying input tuples, and checks adapter
|
|
5
|
+
contracts during testing. It does not prove that an adapter is correct.
|
|
6
|
+
|
|
7
|
+
## Exact integers without a storage width
|
|
8
|
+
|
|
9
|
+
`Integer` is the mathematical integer domain. Integer literals default to it;
|
|
10
|
+
integer addition, subtraction, multiplication, negation, quotient, and remainder
|
|
11
|
+
produce it. Calculations use arbitrary precision internally. `BigInt` remains
|
|
12
|
+
available when a concrete arbitrary-precision adapter representation is wanted.
|
|
13
|
+
|
|
14
|
+
```lawspec
|
|
15
|
+
unit example.increment
|
|
16
|
+
successor :: (x :: Int8) -> (result :: Integer where result == x + 1)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This signature generates contract tests without a separate law. The maximum
|
|
20
|
+
input, `127`, requires the value `128`. Returning a wrapped `-128` fails.
|
|
21
|
+
`Integer` does not assert which storage width the implementation used.
|
|
22
|
+
|
|
23
|
+
Abstract integer results accept Python `int` (excluding `bool`), JavaScript or
|
|
24
|
+
TypeScript `bigint` and safe integral `number` values, Java/Kotlin standard signed
|
|
25
|
+
integral wrappers and `BigInteger` through `Number`, and Go signed/unsigned integer
|
|
26
|
+
values or `big.Int` values/pointers through `any`. Floating representations are
|
|
27
|
+
rejected even when their current value is integral. Haskell uses `IntegerValue`:
|
|
28
|
+
|
|
29
|
+
```haskell
|
|
30
|
+
successor :: Int8 -> IntegerValue
|
|
31
|
+
successor x = integerValue (toInteger x + 1)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`integerValue :: Integral a => a -> IntegerValue` erases the width losslessly.
|
|
35
|
+
Abstract integer arguments use the target's arbitrary-precision integer type.
|
|
36
|
+
Concrete signatures keep their existing checked native mappings.
|
|
37
|
+
|
|
38
|
+
## Inline and named refinements
|
|
39
|
+
|
|
40
|
+
Use `where` on a quantified input, function argument, or function result:
|
|
41
|
+
|
|
42
|
+
```lawspec
|
|
43
|
+
`for all` (x :: Int8) (y :: Int8 where y > Int8.max - x) .
|
|
44
|
+
add x y = x + y
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
A predicate can reference its own value and earlier inputs. A function result
|
|
48
|
+
predicate can also reference the function's named arguments. Forward value
|
|
49
|
+
references are errors. `Int8.min` and `Int8.max` denote representation bounds;
|
|
50
|
+
machine-sized bounds use the requested `machineBits` profile.
|
|
51
|
+
|
|
52
|
+
Named refinements distinguish type parameters from value parameters:
|
|
53
|
+
|
|
54
|
+
```lawspec
|
|
55
|
+
refinement Between
|
|
56
|
+
(T :: Type) (minimum :: T) (maximum :: T)
|
|
57
|
+
requires Ordered T is
|
|
58
|
+
(value :: T where minimum <= value && value <= maximum)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
refinement AdditionOverflows
|
|
62
|
+
(T :: Type) (left :: T)
|
|
63
|
+
requires Integer T Bounded T is
|
|
64
|
+
(right :: T where left + right > T.max)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
add :: (x :: Int8)
|
|
68
|
+
-> (y :: AdditionOverflows Int8 x)
|
|
69
|
+
-> (result :: Integer where result == x + y)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`(x :: Between Int8 1 10)` applies a refinement. Parenthesize compound arguments,
|
|
73
|
+
for example `Between Int8 (-10) (5 + 5)`. Later value-parameter types can use
|
|
74
|
+
earlier parameters. Value arguments must satisfy their declared domains,
|
|
75
|
+
including refinements. Substitution avoids capturing names from the call site.
|
|
76
|
+
Declarations can appear in any order within a unit; recursive aliases are errors.
|
|
77
|
+
Fixed refinements are the zero-parameter form:
|
|
78
|
+
|
|
79
|
+
```lawspec
|
|
80
|
+
refinement PositiveInt8 is (value :: Int8 where value > 0) end
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Aliases preserve their underlying native representation. They can be nested in
|
|
84
|
+
`Nullable` and `Optional`; inner predicates are checked only for present values.
|
|
85
|
+
Type parameters range over the supported scalar types, including presence types.
|
|
86
|
+
|
|
87
|
+
`requires Integer T` describes integer capabilities in a generic declaration.
|
|
88
|
+
`Integer` entails `Eq` and `Ordered`. `Ordered` currently covers exact real numbers
|
|
89
|
+
and floats, with the existing IEEE comparisons (including NaN behavior).
|
|
90
|
+
`Bounded` covers fixed and machine-sized integers. Bounds refer to the underlying
|
|
91
|
+
integer representation, rather than a tighter interval inferred from a predicate.
|
|
92
|
+
Capabilities are checked on generic declarations and their specializations.
|
|
93
|
+
|
|
94
|
+
## Predicate expressions and contracts
|
|
95
|
+
|
|
96
|
+
Predicates support existing pure arithmetic, comparisons, scalar constructors,
|
|
97
|
+
conversions, and built-in helpers. `&&`, `||`, and `!` short-circuit; comparisons
|
|
98
|
+
bind more tightly than `&&`, which binds more tightly than `||`. Assertion `and`
|
|
99
|
+
continues to combine law conclusions. Exact/inexact mixing still needs an explicit
|
|
100
|
+
conversion, and floating expressions retain their declared precision.
|
|
101
|
+
|
|
102
|
+
`prelude.length` counts Unicode scalars for Text, code points for CodePointText,
|
|
103
|
+
UTF-16 units for Utf16Text, and octets for Bytes. `prelude.isPresent` and
|
|
104
|
+
`prelude.presentValue` inspect tagged presence values; guard extraction with
|
|
105
|
+
`isPresent` before accessing a possibly absent value.
|
|
106
|
+
|
|
107
|
+
Adapter calls are forbidden in refinements. Predicate errors such as division by
|
|
108
|
+
zero are reported as failures when evaluated; they are not ordinary rejection of
|
|
109
|
+
an input. Short-circuited branches are not evaluated by generation optimizations.
|
|
110
|
+
|
|
111
|
+
Every refined function signature creates a standalone property. Calls from other
|
|
112
|
+
laws also check argument preconditions, invoke the adapter once, snapshot its
|
|
113
|
+
result, and check postconditions. Calling a function outside its precondition is
|
|
114
|
+
a test failure, not a discarded example. Ordinary `implies` guards retain their
|
|
115
|
+
existing conditional behavior.
|
|
116
|
+
|
|
117
|
+
## Dependent generation and shrinking
|
|
118
|
+
|
|
119
|
+
A quantified law ranges over satisfying tuples. For `AdditionOverflows Int8 x`,
|
|
120
|
+
`x = 0` has no admissible `y`. The generator backtracks to choose another `x`;
|
|
121
|
+
it does not count that dead end as a passing test. Valid pairs satisfy:
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
1 <= x <= 127
|
|
125
|
+
128 - x <= y <= 127
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The generator derives integer bounds from affine comparisons and conjunctions,
|
|
129
|
+
seeds direct comparison values and boundaries, and checks the complete predicate.
|
|
130
|
+
Predicates outside that analysis use bounded sampling. Small finite base-domain
|
|
131
|
+
products are enumerated exhaustively, retaining only satisfying tuples.
|
|
132
|
+
|
|
133
|
+
Shrinking checks refinements again and repairs dependent later inputs when an
|
|
134
|
+
earlier value changes. An overflowing counterexample can shrink to `(1, 127)`;
|
|
135
|
+
it cannot shrink to `(0, 127)` because that pair is outside the domain.
|
|
136
|
+
|
|
137
|
+
Compiler requests and `lawspec.json` accept:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"generation": {
|
|
142
|
+
"cases": 100,
|
|
143
|
+
"maxAttempts": 10000,
|
|
144
|
+
"maxShrinks": 1000,
|
|
145
|
+
"exhaustiveLimit": 4096
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
All limits are positive integers. These are the defaults for refinement properties.
|
|
151
|
+
`cases` counts accepted property inputs; explicit examples and boundary fixtures
|
|
152
|
+
are additional checks. Exhausted searches report an error with reproduction
|
|
153
|
+
information. Exhaustion does not prove that the mathematical domain is empty.
|
|
154
|
+
Statically established empty executable domains and invalid examples are rejected.
|
|
155
|
+
|
|
156
|
+
See [the bundled examples](examples/specs/refinements.lawspec) for overflow,
|
|
157
|
+
abstract integer arguments/results, dependent bounds, optional refinements,
|
|
158
|
+
floating classification, and raw-byte lengths.
|
package/bin/lawspec.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { createCompiler } from "../api.mjs";
|
|
5
5
|
import { targets, templates, commands, setup } from "../templates.mjs";
|
|
6
6
|
import { generateExamples } from "../examples-command.mjs";
|
|
7
|
+
import { showScalar } from "../scalars.mjs";
|
|
7
8
|
import { doctor } from "../doctor.mjs";
|
|
8
9
|
import {
|
|
9
10
|
readOptional,
|
|
@@ -18,7 +19,7 @@ const options = {};
|
|
|
18
19
|
const positional = [];
|
|
19
20
|
for (let i = 0; i < args.length; i++) {
|
|
20
21
|
const arg = args[i];
|
|
21
|
-
if (["--target", "--project", "--config", "--output"].includes(arg)) {
|
|
22
|
+
if (["--target", "--project", "--config", "--output", "--machine-bits"].includes(arg)) {
|
|
22
23
|
if (!args[i + 1] || args[i + 1].startsWith("--"))
|
|
23
24
|
throw new Error(`Missing value for ${arg}`);
|
|
24
25
|
options[arg.slice(2)] = args[++i];
|
|
@@ -138,6 +139,7 @@ async function init() {
|
|
|
138
139
|
await readFile(new URL("../starter.lawspec", import.meta.url), "utf8"),
|
|
139
140
|
true,
|
|
140
141
|
);
|
|
142
|
+
if (options.machineBits !== undefined) config.machineBits = options.machineBits;
|
|
141
143
|
config.targets.push({
|
|
142
144
|
language,
|
|
143
145
|
root: path.relative(configRoot, root) || ".",
|
|
@@ -151,9 +153,19 @@ async function init() {
|
|
|
151
153
|
`Configured ${language}. ${hasBuild ? "Existing build files preserved." : "Created missing project build files."}\n${setup[language]}\nNext: lawspec doctor, then lawspec generate.`,
|
|
152
154
|
);
|
|
153
155
|
}
|
|
156
|
+
function showType(type) {
|
|
157
|
+
if (type.tag === 'Named' || type.tag === 'Variable') return type.contents;
|
|
158
|
+
if (type.tag === 'Applied') return `${type.contents[0]} (${showType(type.contents[1])})`;
|
|
159
|
+
return `${showType(type.contents[0])} -> ${showType(type.contents[1])}`;
|
|
160
|
+
}
|
|
154
161
|
function showExpression(expr) {
|
|
155
162
|
const value = expr.contents;
|
|
156
|
-
if (expr.tag === "Var") return value;
|
|
163
|
+
if (expr.tag === "Var" || expr.tag === "Number") return value;
|
|
164
|
+
if (expr.tag === "DecimalNumber") return `${value[0]}e${value[1]}`;
|
|
165
|
+
if (expr.tag === "ScalarLit") return showScalar(value);
|
|
166
|
+
if (expr.tag === "Binary") return `(${showExpression(value[1])} ${value[0]} ${showExpression(value[2])})`;
|
|
167
|
+
if (expr.tag === "Unary") return `${value[0]}(${showExpression(value[1])})`;
|
|
168
|
+
if (expr.tag === "Annotate") return `(${showExpression(value[0])} :: ${showType(value[1])})`;
|
|
157
169
|
if (
|
|
158
170
|
expr.tag === "Number" ||
|
|
159
171
|
expr.tag === "StringLit" ||
|
|
@@ -170,28 +182,32 @@ function explainExamples(law) {
|
|
|
170
182
|
(ex) =>
|
|
171
183
|
`\nexample ${JSON.stringify(ex.exampleName)}\n` +
|
|
172
184
|
ex.bindings
|
|
173
|
-
.map(([n, v]) => ` ${n} = ${
|
|
185
|
+
.map(([n, v]) => ` ${n} = ${showScalar(v)}`)
|
|
174
186
|
.join("\n") +
|
|
175
187
|
"\n" +
|
|
176
188
|
ex.expectations
|
|
177
189
|
.map(
|
|
178
190
|
(e) =>
|
|
179
|
-
` expect ${showExpression(e.actual)} = ${
|
|
191
|
+
` expect ${showExpression(e.actual)} = ${showScalar(e.expected)}`,
|
|
180
192
|
)
|
|
181
193
|
.join("\n"),
|
|
182
194
|
)
|
|
183
195
|
.join("\n");
|
|
184
196
|
}
|
|
185
197
|
async function main() {
|
|
198
|
+
if (options['machine-bits'] !== undefined) {
|
|
199
|
+
options.machineBits = Number(options['machine-bits']);
|
|
200
|
+
if (![32,64].includes(options.machineBits)) throw new Error('machineBits must be 32 or 64');
|
|
201
|
+
}
|
|
186
202
|
if (!verb || ["help", "--help", "-h"].includes(verb)) {
|
|
187
203
|
output(
|
|
188
|
-
"LawSpec 0.
|
|
204
|
+
"LawSpec 0.7.0\nUsage: lawspec init --target <language> [--project <directory>]\n lawspec check | doctor | explain <unit>::<law> | generate\n lawspec examples [--target <language>] [--output example_artifacts]\nOptions: --config <path>, --target <language>, --machine-bits <32|64>, --json\nGeneration: --dry-run, --check\nTargets: " +
|
|
189
205
|
targets.join(", "),
|
|
190
206
|
);
|
|
191
207
|
return;
|
|
192
208
|
}
|
|
193
209
|
if (verb === "--version") {
|
|
194
|
-
output("0.
|
|
210
|
+
output("0.7.0");
|
|
195
211
|
return;
|
|
196
212
|
}
|
|
197
213
|
if (positional.length > (verb === "explain" ? 1 : 0))
|
|
@@ -256,7 +272,8 @@ async function main() {
|
|
|
256
272
|
if (reports.some((r) => !r.ok)) process.exitCode = 1;
|
|
257
273
|
return;
|
|
258
274
|
}
|
|
259
|
-
|
|
275
|
+
if (config.machineBits !== undefined && ![32, 64].includes(config.machineBits)) throw new Error("machineBits must be 32 or 64");
|
|
276
|
+
const input = { sources: await sources(config), generation: config.generation, machineBits: options.machineBits ?? config.machineBits ?? 64 };
|
|
260
277
|
const compiler = await createCompiler();
|
|
261
278
|
if (verb === "check") {
|
|
262
279
|
const result = diagnostics(await compiler.check(input));
|
package/build.json
CHANGED
|
@@ -2,38 +2,64 @@
|
|
|
2
2
|
"version": 1,
|
|
3
3
|
"compilerSources": [
|
|
4
4
|
"package.yaml",
|
|
5
|
+
"runtime/LawSpecRuntime.hs",
|
|
6
|
+
"runtime/LawSpecRuntime.java",
|
|
7
|
+
"runtime/lawspec_runtime.go",
|
|
8
|
+
"runtime/lawspec_runtime.mjs",
|
|
9
|
+
"runtime/lawspec_runtime.py",
|
|
5
10
|
"src/LawSpec/Api.hs",
|
|
6
11
|
"src/LawSpec/Compile.hs",
|
|
12
|
+
"src/LawSpec/Domain.hs",
|
|
7
13
|
"src/LawSpec/Emit.hs",
|
|
14
|
+
"src/LawSpec/Eval.hs",
|
|
8
15
|
"src/LawSpec/Gen.hs",
|
|
9
16
|
"src/LawSpec/Model.hs",
|
|
17
|
+
"src/LawSpec/NativeScalarEmit.hs",
|
|
10
18
|
"src/LawSpec/Parser.hs",
|
|
11
19
|
"src/LawSpec/Prelude.hs",
|
|
20
|
+
"src/LawSpec/Refinement.hs",
|
|
21
|
+
"src/LawSpec/RuntimeSources.hs",
|
|
22
|
+
"src/LawSpec/Scalar.hs",
|
|
23
|
+
"src/LawSpec/ScalarEmit.hs",
|
|
12
24
|
"stack.yaml",
|
|
13
25
|
"stack.yaml.lock",
|
|
26
|
+
"tools/embed-runtimes.py",
|
|
14
27
|
"wasm/app/Exports.hs",
|
|
15
28
|
"wasm/cabal.project",
|
|
16
29
|
"wasm/cabal.project.freeze",
|
|
17
30
|
"wasm/lawspec-wasm.cabal"
|
|
18
31
|
],
|
|
19
32
|
"digests": {
|
|
20
|
-
"package.yaml": "
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"src/LawSpec/
|
|
33
|
+
"package.yaml": "ca15186d8aa807c58258a4b0fef8fa7d64afaa828f2e71803b4e8741066422fe",
|
|
34
|
+
"runtime/LawSpecRuntime.hs": "1c3ca22232c64227717d836a57d375d906f748ecde6b79705944a2319ce5064d",
|
|
35
|
+
"runtime/LawSpecRuntime.java": "5637914bd491f75ad0c75fb19d1bec798164295b7bcf9d23e1549e8ebf780d01",
|
|
36
|
+
"runtime/lawspec_runtime.go": "62d2041c33760b67db619e0a803501768a505c4dfd9837bca85e158fff81fda9",
|
|
37
|
+
"runtime/lawspec_runtime.mjs": "7d0abd53fa45dd9828c10ff2ffa7876162e965e7bb4913f4c460af13e06142fa",
|
|
38
|
+
"runtime/lawspec_runtime.py": "7541353effebd133e50cd00a3f4d8999651c93ba751a6d951164b1f390ce0c15",
|
|
39
|
+
"src/LawSpec/Api.hs": "f1a283fd3696e8e0dd1bd6240ba636a4c1abe19c6e0ea25d6d80a1af89f762cb",
|
|
40
|
+
"src/LawSpec/Compile.hs": "a67ee7cb19ebeea644bbf8dbdc0647323a77197b15f8e87fe2227deb6c995b8e",
|
|
41
|
+
"src/LawSpec/Domain.hs": "8ad50ad3a37aacb3a429c2d7924279e2a3b973204ce3c5c04c634c1b9ada0151",
|
|
42
|
+
"src/LawSpec/Emit.hs": "0b29d449f762af635bb65e5fcbf1f4cfffb1b91b28db115b08b2309557498693",
|
|
43
|
+
"src/LawSpec/Eval.hs": "9bdb694352ec4bd7140cdf8ab18045978b7407e93db5b661270160d08e4e9b7d",
|
|
44
|
+
"src/LawSpec/Gen.hs": "d4b4e5a21f5a2886dcdc98f56f649980d961c0570d9463154bed459844ab66aa",
|
|
45
|
+
"src/LawSpec/Model.hs": "67cad75f1d344bc2dfc997731c37f68994c6a6b568e03209e5b446484ae54674",
|
|
46
|
+
"src/LawSpec/NativeScalarEmit.hs": "67f50a7ea381ddada8ecbfc7534917c53f4074f8ce1d4fb6948adb8b7dfb2007",
|
|
47
|
+
"src/LawSpec/Parser.hs": "b6e5ee4a8c58b0ab2709420460f1fcabe287714f6defc0fab6cf52ccd0c416a5",
|
|
27
48
|
"src/LawSpec/Prelude.hs": "b1e155c12f58e7c346551a0c65c5d5af76d045b3f1625977ca2a5d1c97cb8c46",
|
|
49
|
+
"src/LawSpec/Refinement.hs": "060f7bdaaa9cebc17969fa28474e9402cc918ceea0f18cac54050d25010b473c",
|
|
50
|
+
"src/LawSpec/RuntimeSources.hs": "4a49d98919971bdbe2b9eabda4eb2e7cda643aaa65ef1a4e6892b6da40ee8acd",
|
|
51
|
+
"src/LawSpec/Scalar.hs": "d89d8233abd893f65bb20d4dabe11066340eb51b982ad6fa7bce04d3e22c3547",
|
|
52
|
+
"src/LawSpec/ScalarEmit.hs": "e1f05d255db08c8634b6af98fd5d0b938f9a3735f8adf13a952f092a90ee884c",
|
|
28
53
|
"stack.yaml": "20ccf4d599e355e60b7aa4f814a7cd4299fe2048616cc2e6dbdc22a7bd8cec73",
|
|
29
54
|
"stack.yaml.lock": "ae222b9c81af920c56e50fa4596fa57786e7fa5a7b461390362b2b2ff63818c7",
|
|
55
|
+
"tools/embed-runtimes.py": "7520956a91f4b4ec8a6ab12078195cf1243c989b388a3f9d0b10a0fa60193bc3",
|
|
30
56
|
"wasm/app/Exports.hs": "4ecbdac8faa2449e6fc61b93c82e6278fa14374154f29b4dacf0eef433f06a47",
|
|
31
57
|
"wasm/cabal.project": "021e560afdc5eb4cb7169e7119ecb8f92c9ee170245af909c94b612941bff5cc",
|
|
32
58
|
"wasm/cabal.project.freeze": "733dbed3d2ecccb26e874fd58f136296dad772184deb1658c196b5d54a0814dc",
|
|
33
|
-
"wasm/lawspec-wasm.cabal": "
|
|
34
|
-
"npm/core.wasm": "
|
|
59
|
+
"wasm/lawspec-wasm.cabal": "afdb97eb4a08de190f7533b7be79c6ba11fab9ef30792aca71ef5fb448001106",
|
|
60
|
+
"npm/core.wasm": "32e3f553dc8346a0d7aac63ad556eacc0e791803b8957f0804a609b883ed0ea8",
|
|
35
61
|
"npm/core_jsffi.js": "88d136efe92f7cff5758c8fec8d9b6bbc9707fe37741cebeeb415fe34ce3d72b",
|
|
36
62
|
"npm/api.mjs": "d6df654600172131ac66a55b86876fc29cde2a76a0a63bbefff3183488a4837b",
|
|
37
|
-
"npm/index.d.ts": "
|
|
63
|
+
"npm/index.d.ts": "ec19e731ed33e0d0481345888c73778ae0d3f2db58e4c3310ba684b801e698bc"
|
|
38
64
|
}
|
|
39
65
|
}
|
package/core.wasm
CHANGED
|
Binary file
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
unit example.refinements
|
|
2
|
+
|
|
3
|
+
refinement Between (T :: Type) (minimum :: T) (maximum :: T)
|
|
4
|
+
requires Ordered T is
|
|
5
|
+
(value :: T where minimum <= value && value <= maximum)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
refinement Positive (T :: Type) requires Integer T is
|
|
9
|
+
(value :: T where value > 0)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
refinement AdditionOverflows (T :: Type) (left :: T)
|
|
13
|
+
requires Integer T Bounded T is
|
|
14
|
+
(right :: T where left + right > T.max)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
add :: (x :: Int8)
|
|
18
|
+
-> (y :: AdditionOverflows Int8 x)
|
|
19
|
+
-> (result :: Integer where result == x + y)
|
|
20
|
+
successor :: (x :: Int8) -> (result :: Integer where result == x + 1)
|
|
21
|
+
count :: (text :: Text where prelude.length text > 0)
|
|
22
|
+
-> (result :: Integer where result == prelude.length text)
|
|
23
|
+
preserve :: (x :: UInt64) -> (result :: Integer where result == x)
|
|
24
|
+
positive :: (x :: Positive Int8) -> (result :: Positive Int8 where result == x)
|
|
25
|
+
|
|
26
|
+
law `maximum input` is
|
|
27
|
+
definition is `for all` (x :: Int8) . successor x = x + 1 end
|
|
28
|
+
example `beyond Int8` is x = 127 expect successor x = 128 end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
law `overflow pairs` is
|
|
32
|
+
definition is
|
|
33
|
+
`for all` (x :: Int8) (y :: AdditionOverflows Int8 x) .
|
|
34
|
+
add x y = x + y
|
|
35
|
+
end
|
|
36
|
+
example `smallest overflowing sum` is x = 127 y = 1 expect add x y = 128 end
|
|
37
|
+
example `largest sum` is x = 127 y = 127 expect add x y = 254 end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
law `bounded input` is
|
|
41
|
+
definition is `for all` (x :: Between Int8 1 3) . positive x = x end
|
|
42
|
+
example `middle` is x = 2 expect positive x = 2 end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
law `large integer` is
|
|
46
|
+
definition is `for all` (x :: UInt64) . preserve x = x end
|
|
47
|
+
example `maximum UInt64` is x = 18446744073709551615 expect preserve x = 18446744073709551615 end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
abstractEcho :: (x :: Integer) -> (result :: Integer where result == x)
|
|
51
|
+
|
|
52
|
+
law `nested presence` is
|
|
53
|
+
definition is `for all` (x :: Optional (Nullable (Positive Int8))) . x = x end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
law `finite positive floats` is
|
|
57
|
+
definition is
|
|
58
|
+
`for all` (x :: Float32 where prelude.isFinite x && x > 0) .
|
|
59
|
+
prelude.isFinite x
|
|
60
|
+
end
|
|
61
|
+
example `positive` is x = 0.5 expect x = 0.5 end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
law `raw byte length` is
|
|
65
|
+
definition is `for all` (x :: Bytes where prelude.length x == 3) . prelude.length x = 3 end
|
|
66
|
+
example `arbitrary octets` is x = bytes([0,128,255]) expect prelude.length x = 3 end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
law `guarded predicate arithmetic` is
|
|
70
|
+
definition is `for all` (x :: Int8) (y :: Int8 where x != 0 && y > 10 / x) . y > 10 / x end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
law `machine bound` is
|
|
74
|
+
definition is `for all` (x :: IntSize where x == IntSize.max) . x = IntSize.max end
|
|
75
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
unit example.scalar_adapters
|
|
2
|
+
echoChar :: Char -> Char
|
|
3
|
+
echoCodePoint :: CodePoint -> CodePoint
|
|
4
|
+
echoCodeUnit :: CodeUnit16 -> CodeUnit16
|
|
5
|
+
echoBytes :: Bytes -> Bytes
|
|
6
|
+
echoComplex :: Complex64 -> Complex64
|
|
7
|
+
successor :: Int8 -> BigInt
|
|
8
|
+
narrow :: Int8 -> Int8
|
|
9
|
+
addDecimal :: Decimal -> Decimal -> Decimal
|
|
10
|
+
sameSymbol :: Symbol -> Symbol -> Bool
|
|
11
|
+
echoRaw :: Utf16Text -> Utf16Text
|
|
12
|
+
echoPresence :: Optional (Nullable Int8) -> Optional (Nullable Int8)
|
|
13
|
+
finish :: Unit -> Unit
|
|
14
|
+
preserveBig :: UInt64 -> UInt64
|
|
15
|
+
machineEcho :: IntSize -> IntSize
|
|
16
|
+
|
|
17
|
+
law `promoted successor` is
|
|
18
|
+
definition is `for all` (x :: Int8) . successor x = x + 1 end
|
|
19
|
+
example `no overflow` is x = 127 expect successor x = 128 end
|
|
20
|
+
end
|
|
21
|
+
law `checked bridge` is
|
|
22
|
+
definition is `for all` (x :: Int8) . narrow (x + 0) = x end
|
|
23
|
+
example `maximum` is x = 127 expect narrow (x + 0) = 127 end
|
|
24
|
+
end
|
|
25
|
+
law `exact decimal adapter` is
|
|
26
|
+
definition is `for all` (x :: Decimal) (y :: Decimal) . addDecimal x y = x + y end
|
|
27
|
+
example `tenths` is x = 0.1 y = 0.2 expect addDecimal x y = 0.3 end
|
|
28
|
+
end
|
|
29
|
+
law `symbol identity adapter` is
|
|
30
|
+
definition is `for all` (x :: Symbol) (y :: Symbol) . sameSymbol x x and sameSymbol y y end
|
|
31
|
+
example `same description distinct identities` is
|
|
32
|
+
x = symbol("a", "same") y = symbol("b", "same")
|
|
33
|
+
expect sameSymbol x y = false
|
|
34
|
+
expect sameSymbol x x = true
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
law `raw adapter` is
|
|
38
|
+
definition is `for all` (x :: Utf16Text) . echoRaw x = x end
|
|
39
|
+
example `lone surrogate` is x = utf16([55296]) expect echoRaw x = utf16([55296]) end
|
|
40
|
+
end
|
|
41
|
+
law `presence adapter` is
|
|
42
|
+
definition is `for all` (x :: Optional (Nullable Int8)) . echoPresence x = x end
|
|
43
|
+
example `missing` is x = undefined expect echoPresence x = undefined end
|
|
44
|
+
example `null present` is x = optional(null) expect echoPresence x = optional(null) end
|
|
45
|
+
end
|
|
46
|
+
law `unit adapter` is
|
|
47
|
+
definition is `for all` (x :: Unit) . finish x = unitValue end
|
|
48
|
+
example `completed` is x = unitValue expect finish x = unitValue end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
law `no safe-number truncation` is
|
|
52
|
+
definition is `for all` (x :: UInt64) . preserveBig x = x end
|
|
53
|
+
example `unsigned maximum` is x = 18446744073709551615 expect preserveBig x = 18446744073709551615 end
|
|
54
|
+
end
|
|
55
|
+
law `machine profile binding` is
|
|
56
|
+
definition is `for all` (x :: IntSize) . machineEcho x = x end
|
|
57
|
+
example `portable width` is x = 2147483647 expect machineEcho x = 2147483647 end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
law `Char native bridge` is
|
|
61
|
+
definition is `for all` (x :: Char) . echoChar x = x end
|
|
62
|
+
example `native value` is x = char(128512) expect echoChar x = char(128512) end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
law `CodePoint native bridge` is
|
|
66
|
+
definition is `for all` (x :: CodePoint) . echoCodePoint x = x end
|
|
67
|
+
example `native value` is x = codePoint(55296) expect echoCodePoint x = codePoint(55296) end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
law `CodeUnit16 native bridge` is
|
|
71
|
+
definition is `for all` (x :: CodeUnit16) . echoCodeUnit x = x end
|
|
72
|
+
example `native value` is x = codeUnit16(65535) expect echoCodeUnit x = codeUnit16(65535) end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
law `Bytes native bridge` is
|
|
76
|
+
definition is `for all` (x :: Bytes) . echoBytes x = x end
|
|
77
|
+
example `native value` is x = bytes([0,128,255]) expect echoBytes x = bytes([0,128,255]) end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
law `Complex64 native bridge` is
|
|
81
|
+
definition is `for all` (x :: Complex64) . prelude.isNaN (prelude.real (echoComplex x)) = prelude.isNaN (prelude.real x) end
|
|
82
|
+
example `native value` is x = complex64(1,2) expect echoComplex x = complex64(1,2) end
|
|
83
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
unit example.scalar_catalog
|
|
2
|
+
|
|
3
|
+
law `Bool representation` is
|
|
4
|
+
definition is `for all` (x :: Bool) . x = x end
|
|
5
|
+
example `explicit value` is x = true expect x = true end
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
law `Int8 representation` is
|
|
9
|
+
definition is `for all` (x :: Int8) . x = x end
|
|
10
|
+
example `explicit value` is x = -128 expect x = -128 end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
law `Int16 representation` is
|
|
14
|
+
definition is `for all` (x :: Int16) . x = x end
|
|
15
|
+
example `explicit value` is x = 32767 expect x = 32767 end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
law `Int32 representation` is
|
|
19
|
+
definition is `for all` (x :: Int32) . x = x end
|
|
20
|
+
example `explicit value` is x = 2147483647 expect x = 2147483647 end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
law `Int64 representation` is
|
|
24
|
+
definition is `for all` (x :: Int64) . x = x end
|
|
25
|
+
example `explicit value` is x = 9223372036854775807 expect x = 9223372036854775807 end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
law `UInt8 representation` is
|
|
29
|
+
definition is `for all` (x :: UInt8) . x = x end
|
|
30
|
+
example `explicit value` is x = 255 expect x = 255 end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
law `UInt16 representation` is
|
|
34
|
+
definition is `for all` (x :: UInt16) . x = x end
|
|
35
|
+
example `explicit value` is x = 65535 expect x = 65535 end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
law `UInt32 representation` is
|
|
39
|
+
definition is `for all` (x :: UInt32) . x = x end
|
|
40
|
+
example `explicit value` is x = 4294967295 expect x = 4294967295 end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
law `UInt64 representation` is
|
|
44
|
+
definition is `for all` (x :: UInt64) . x = x end
|
|
45
|
+
example `explicit value` is x = 18446744073709551615 expect x = 18446744073709551615 end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
law `IntSize representation` is
|
|
49
|
+
definition is `for all` (x :: IntSize) . x = x end
|
|
50
|
+
example `explicit value` is x = 2147483647 expect x = 2147483647 end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
law `UIntSize representation` is
|
|
54
|
+
definition is `for all` (x :: UIntSize) . x = x end
|
|
55
|
+
example `explicit value` is x = 4294967295 expect x = 4294967295 end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
law `UIntPtr representation` is
|
|
59
|
+
definition is `for all` (x :: UIntPtr) . x = x end
|
|
60
|
+
example `explicit value` is x = 4294967295 expect x = 4294967295 end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
law `BigInt representation` is
|
|
64
|
+
definition is `for all` (x :: BigInt) . x = x end
|
|
65
|
+
example `explicit value` is x = -1234567890123456789012345678901234567890 expect x = -1234567890123456789012345678901234567890 end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
law `BigUInt representation` is
|
|
69
|
+
definition is `for all` (x :: BigUInt) . x = x end
|
|
70
|
+
example `explicit value` is x = 1234567890123456789012345678901234567890 expect x = 1234567890123456789012345678901234567890 end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
law `Decimal representation` is
|
|
74
|
+
definition is `for all` (x :: Decimal) . x = x end
|
|
75
|
+
example `explicit value` is x = 0.000000000000000000000000000000000001 expect x = 0.000000000000000000000000000000000001 end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
law `Rational representation` is
|
|
79
|
+
definition is `for all` (x :: Rational) . x = x end
|
|
80
|
+
example `explicit value` is x = rational(-7, 11) expect x = rational(-7, 11) end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
law `Float32 representation` is
|
|
84
|
+
definition is `for all` (x :: Float32) . prelude.isNaN x = prelude.isNaN x end
|
|
85
|
+
example `explicit value` is x = float32Bits("80000000") expect x = float32Bits("80000000") end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
law `Float64 representation` is
|
|
89
|
+
definition is `for all` (x :: Float64) . prelude.isNaN x = prelude.isNaN x end
|
|
90
|
+
example `explicit value` is x = float64Bits("7ff0000000000000") expect x = float64Bits("7ff0000000000000") end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
law `Complex64 representation` is
|
|
94
|
+
definition is `for all` (x :: Complex64) . prelude.isNaN (prelude.real x) = prelude.isNaN (prelude.real x) end
|
|
95
|
+
example `explicit value` is x = complex64(1, -2) expect x = complex64(1, -2) end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
law `Complex128 representation` is
|
|
99
|
+
definition is `for all` (x :: Complex128) . prelude.isNaN (prelude.real x) = prelude.isNaN (prelude.real x) end
|
|
100
|
+
example `explicit value` is x = complex128(0.5, -3.5) expect x = complex128(0.5, -3.5) end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
law `Char representation` is
|
|
104
|
+
definition is `for all` (x :: Char) . x = x end
|
|
105
|
+
example `explicit value` is x = char(128512) expect x = char(128512) end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
law `CodePoint representation` is
|
|
109
|
+
definition is `for all` (x :: CodePoint) . x = x end
|
|
110
|
+
example `explicit value` is x = codePoint(55296) expect x = codePoint(55296) end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
law `CodeUnit16 representation` is
|
|
114
|
+
definition is `for all` (x :: CodeUnit16) . x = x end
|
|
115
|
+
example `explicit value` is x = codeUnit16(65535) expect x = codeUnit16(65535) end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
law `Text representation` is
|
|
119
|
+
definition is `for all` (x :: Text) . x = x end
|
|
120
|
+
example `explicit value` is x = "λ日本語😀" expect x = "λ日本語😀" end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
law `CodePointText representation` is
|
|
124
|
+
definition is `for all` (x :: CodePointText) . x = x end
|
|
125
|
+
example `explicit value` is x = codePoints([55296, 1114111]) expect x = codePoints([55296, 1114111]) end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
law `Utf16Text representation` is
|
|
129
|
+
definition is `for all` (x :: Utf16Text) . x = x end
|
|
130
|
+
example `explicit value` is x = utf16([55296, 0, 65535]) expect x = utf16([55296, 0, 65535]) end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
law `Bytes representation` is
|
|
134
|
+
definition is `for all` (x :: Bytes) . x = x end
|
|
135
|
+
example `explicit value` is x = bytes([0, 128, 255]) expect x = bytes([0, 128, 255]) end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
law `Symbol representation` is
|
|
139
|
+
definition is `for all` (x :: Symbol) . x = x end
|
|
140
|
+
example `explicit value` is x = symbol("catalog", "same") expect x = symbol("catalog", "same") end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
law `Unit representation` is
|
|
144
|
+
definition is `for all` (x :: Unit) . x = x end
|
|
145
|
+
example `explicit value` is x = unitValue expect x = unitValue end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
law `Null representation` is
|
|
149
|
+
definition is `for all` (x :: Null) . x = x end
|
|
150
|
+
example `explicit value` is x = null expect x = null end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
law `Undefined representation` is
|
|
154
|
+
definition is `for all` (x :: Undefined) . x = x end
|
|
155
|
+
example `explicit value` is x = undefined expect x = undefined end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
law `Nullable Int8 representation` is
|
|
159
|
+
definition is `for all` (x :: Nullable Int8) . x = x end
|
|
160
|
+
example `explicit value` is x = nullable(127) expect x = nullable(127) end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
law `Optional Int8 representation` is
|
|
164
|
+
definition is `for all` (x :: Optional Int8) . x = x end
|
|
165
|
+
example `explicit value` is x = undefined expect x = undefined end
|
|
166
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
unit example.scalars
|
|
2
|
+
|
|
3
|
+
law `integer promotion` is
|
|
4
|
+
definition is `for all` (x :: Int8) . x + 1 = 1 + x end
|
|
5
|
+
example `overflow is preserved` is
|
|
6
|
+
x = 127
|
|
7
|
+
expect x + 1 = 128
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
law `unsigned precision` is
|
|
12
|
+
definition is `for all` (x :: UInt64) . x + 0 = x end
|
|
13
|
+
example `maximum` is
|
|
14
|
+
x = 18446744073709551615
|
|
15
|
+
expect x + 1 = 18446744073709551616
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
law `decimal exactness` is
|
|
20
|
+
definition is `for all` (x :: Decimal) . x + 0.1 = 0.1 + x end
|
|
21
|
+
example `tenths` is
|
|
22
|
+
x = 0.1
|
|
23
|
+
expect x + 0.2 = 0.3
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
law `exact division` is
|
|
28
|
+
definition is `for all` (x :: Int16) . x / 2 = x / 2 end
|
|
29
|
+
example `one half` is
|
|
30
|
+
x = 1
|
|
31
|
+
expect x / 2 = rational(1, 2)
|
|
32
|
+
expect prelude.quot -7 3 = -2
|
|
33
|
+
expect prelude.rem -7 3 = -1
|
|
34
|
+
expect prelude.round rational(125, 100) 1 = 1.2
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
law `IEEE classification` is
|
|
39
|
+
definition is `for all` (x :: Float32) . prelude.isNaN x = prelude.isNaN x end
|
|
40
|
+
example `NaN` is
|
|
41
|
+
x = float32Bits("7fc00000")
|
|
42
|
+
expect prelude.isNaN x = true
|
|
43
|
+
end
|
|
44
|
+
example `negative zero` is
|
|
45
|
+
x = float32Bits("80000000")
|
|
46
|
+
expect prelude.isNegativeZero x = true
|
|
47
|
+
expect x = (0 :: Float32)
|
|
48
|
+
end
|
|
49
|
+
example `infinity` is
|
|
50
|
+
x = float32Bits("7f800000")
|
|
51
|
+
expect prelude.isInfinite x = true
|
|
52
|
+
expect prelude.Float32 0.1 = (0.1 :: Float32)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
law `complex arithmetic` is
|
|
57
|
+
definition is `for all` (x :: Unit) . complex64(1, 2) * complex64(3, 4) = complex64(-5, 10) end
|
|
58
|
+
example `product` is
|
|
59
|
+
x = unitValue
|
|
60
|
+
expect complex64(1, 2) * complex64(3, 4) = complex64(-5, 10)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
law `raw units survive` is
|
|
65
|
+
definition is `for all` (x :: Utf16Text) . x = x end
|
|
66
|
+
example `unpaired surrogate` is
|
|
67
|
+
x = utf16([55296, 0, 56320])
|
|
68
|
+
expect x = utf16([55296, 0, 56320])
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
law `arbitrary bytes` is
|
|
73
|
+
definition is `for all` (x :: Bytes) . x = x end
|
|
74
|
+
example `octets` is
|
|
75
|
+
x = bytes([0, 128, 255])
|
|
76
|
+
expect x = bytes([0, 128, 255])
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
law `symbol fixture identity` is
|
|
81
|
+
definition is `for all` (x :: Symbol) . x = x end
|
|
82
|
+
example `same reference` is
|
|
83
|
+
x = symbol("first", "description")
|
|
84
|
+
expect x = symbol("first", "description")
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
law `nested absence` is
|
|
89
|
+
definition is `for all` (x :: Optional (Nullable Int8)) . x = x end
|
|
90
|
+
example `undefined` is
|
|
91
|
+
x = undefined
|
|
92
|
+
expect x = undefined
|
|
93
|
+
end
|
|
94
|
+
example `present null` is
|
|
95
|
+
x = optional(null)
|
|
96
|
+
expect x = optional(null)
|
|
97
|
+
end
|
|
98
|
+
example `present integer` is
|
|
99
|
+
x = optional(nullable(7))
|
|
100
|
+
expect x = optional(nullable(7))
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
law `supplementary scalar` is
|
|
105
|
+
definition is `for all` (x :: Char) . x = x end
|
|
106
|
+
example `emoji` is
|
|
107
|
+
x = char(128512)
|
|
108
|
+
expect x = char(128512)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
law `surrogate code point` is
|
|
113
|
+
definition is `for all` (x :: CodePointText) . x = x end
|
|
114
|
+
example `surrogate` is
|
|
115
|
+
x = codePoints([55296, 128512])
|
|
116
|
+
expect x = codePoints([55296, 128512])
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
law `explicit float rounding` is
|
|
121
|
+
definition is `for all` (x :: Unit) . x = x end
|
|
122
|
+
example `IEEE halfway cases` is
|
|
123
|
+
x = unitValue
|
|
124
|
+
expect prelude.Complex128 complex64(1, 2) = complex128(1, 2)
|
|
125
|
+
expect prelude.Complex64 complex128(1.25, -2.5) = complex64(1.25, -2.5)
|
|
126
|
+
expect prelude.Float32 rational(16777217, 16777216) = float32Bits("3f800000")
|
|
127
|
+
expect prelude.Float32 rational(1208925891672223212634113, 1208925819614629174706176) = float32Bits("3f800001")
|
|
128
|
+
expect prelude.Float64 rational(9007199254740993, 9007199254740992) = float64Bits("3ff0000000000000")
|
|
129
|
+
expect prelude.Float64 rational(1606938044258990453947923680586147734807949174969684883144705, 1606938044258990275541962092341162602522202993782792835301376) = float64Bits("3ff0000000000001")
|
|
130
|
+
expect prelude.Float32 rational(1, 1427247692705959881058285969449495136382746624) = float32Bits("00000000")
|
|
131
|
+
expect prelude.Float32 rational(1125899906842625, 1606938044258990275541962092341162602522202993782792835301376) = float32Bits("00000001")
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
law `short circuit exact failures` is
|
|
136
|
+
definition is `for all` (x :: Unit) . false implies 1 / 0 = rational(1, 2) end
|
|
137
|
+
example `guard prevents division` is x = unitValue expect x = unitValue end
|
|
138
|
+
end
|
package/examples-command.mjs
CHANGED
|
@@ -21,7 +21,7 @@ export async function generateExamples(options) {
|
|
|
21
21
|
const compiler = await createCompiler();
|
|
22
22
|
const artifacts = [];
|
|
23
23
|
for (const target of selected) {
|
|
24
|
-
const result = await compiler.planGeneration({ sources, target });
|
|
24
|
+
const result = await compiler.planGeneration({ sources, target, machineBits: options.machineBits ?? 64 });
|
|
25
25
|
if (result.diagnostics.length)
|
|
26
26
|
throw Object.assign(new Error("Bundled examples failed to compile"), {
|
|
27
27
|
diagnostics: result.diagnostics,
|
package/index.d.ts
CHANGED
|
@@ -3,17 +3,27 @@ export type Target = 'java' | 'python' | 'javascript' | 'typescript' | 'go' | 'h
|
|
|
3
3
|
export interface Source { path: string; content: string }
|
|
4
4
|
export interface Location { file: string; line: number; column: number }
|
|
5
5
|
export interface Diagnostic { code: string; message: string; at: Location | null }
|
|
6
|
-
export interface Artifact { path: string; content: string; ownership: 'user' | 'generated' }
|
|
7
|
-
export type Type = {tag: 'Named' | 'Variable'; contents: string} | {tag: 'Arrow'; contents: [Type, Type]};
|
|
8
|
-
export
|
|
6
|
+
export interface Artifact { path: string; content: string; ownership: 'user' | 'generated'; placement: 'source' | 'test' }
|
|
7
|
+
export type Type = {tag: 'Named' | 'Variable'; contents: string} | {tag: 'Arrow'; contents: [Type, Type]} | {tag: 'Applied'; contents: ['Nullable' | 'Optional', Type]} | {tag:'Refined'; contents:[string, Type, Expr | null]} | {tag:'Qualified'; contents:[Constraint[],Type]} | {tag:'CheckedType'; contents:[Expr[],Type]} | {tag:'RefinementApp'; contents:[string,RefinementArgument[]]};
|
|
8
|
+
export interface Constraint {tag:'Capability'; contents:['Eq'|'Integer'|'Ordered'|'Bounded',Type]}
|
|
9
|
+
export type RefinementArgument = {tag:'TypeArgument'; contents:Type} | {tag:'ValueArgument'; contents:Expr};
|
|
10
|
+
export interface Refinement {refinementName:string; refinementParameters:[string,Type][]; refinementRequirements:Constraint[]; refinementBody:Type}
|
|
11
|
+
export interface Contract {contractName:string; contractArguments:[string,Type][]; contractResult:[string,Type]; contractPreconditions:Expr[]; contractPostconditions:Expr[]}
|
|
12
|
+
export interface Generation {cases:number; maxAttempts:number; maxShrinks:number; exhaustiveLimit:number}
|
|
13
|
+
export interface Input {inputName:string; inputId:string; inputType:Type; inputRefinements:Expr[]}
|
|
14
|
+
export interface DomainPlan {domainInput:Input; domainBounds:[string,Expr][]}
|
|
15
|
+
export type IntegerType = 'Int8' | 'Int16' | 'Int32' | 'Int64' | 'UInt8' | 'UInt16' | 'UInt32' | 'UInt64' | 'IntSize' | 'UIntSize' | 'UIntPtr' | 'Integer' | 'BigInt' | 'BigUInt';
|
|
16
|
+
export type ScalarValue = {type: IntegerType; value: string} | {type: 'Bool'; value: boolean} | {type: 'Decimal'; coefficient: string; exponent: string} | {type: 'Rational'; numerator: string; denominator: string} | {type: 'Float32' | 'Float64'; bits: string} | {type: 'Complex64' | 'Complex128'; real: ScalarValue; imaginary: ScalarValue} | {type: 'Char' | 'CodePoint' | 'CodeUnit16'; value: number} | {type: 'Text' | 'CodePointText' | 'Utf16Text' | 'Bytes'; units: number[]} | {type: 'Symbol'; id: string; description: string} | {type: 'Unit' | 'Null' | 'Undefined'} | {type: 'Nullable' | 'Optional'; value: ScalarValue | null};
|
|
17
|
+
export type Expr = {tag: 'Var' | 'StringLit'; contents: string} | {tag: 'Number'; contents: string} | {tag: 'DecimalNumber'; contents: [string, string]} | {tag: 'BoolLit'; contents: boolean} | {tag: 'Apply' | 'Compose'; contents: [Expr, Expr]} | {tag: 'ScalarLit'; contents: ScalarValue} | {tag: 'Annotate'; contents: [Expr, Type]} | {tag: 'Unary'; contents: [string, Expr]} | {tag: 'Binary'; contents: [string, Expr, Expr]} | {tag:'TypeBound'; contents:['min'|'max',Type]};
|
|
9
18
|
export type Definition = {tag: 'Forall'; contents: [[string, Type][], Definition]} | {tag: 'Equal'; contents: [Expr, Expr]} | {tag: 'Holds'; contents: Expr} | {tag: 'Implies'; contents: [Expr, Definition]} | {tag: 'And'; contents: [Definition, Definition]} | {tag: 'Invoke'; contents: [string, Expr[]]};
|
|
10
|
-
export interface Expectation { actual: Expr; expected:
|
|
11
|
-
export interface Example { exampleName: string; bindings: [string,
|
|
12
|
-
export interface Law { lawName: string; parameters: [string, Type][]; requirements:
|
|
19
|
+
export interface Expectation { actual: Expr; expected: ScalarValue }
|
|
20
|
+
export interface Example { exampleName: string; bindings: [string, ScalarValue][]; expectations: Expectation[] }
|
|
21
|
+
export interface Law { lawName: string; parameters: [string, Type][]; requirements: Constraint[]; definition: Definition; description: string; rationale: string; examples: Example[]; references: string[]; location: Location }
|
|
13
22
|
export type Assertion = {tag: 'AssertEqual'; contents: [Expr, Expr]} | {tag: 'AssertImplies'; contents: [Expr, Assertion]} | {tag: 'AssertAll'; contents: Assertion[]};
|
|
14
|
-
export interface Expanded { owner: string; name: string; inputs:
|
|
15
|
-
export interface
|
|
23
|
+
export interface Expanded { owner: string; name: string; inputs: Input[]; left: Expr; right: Expr; guards: Expr[]; assertion: Assertion; trace: string[]; original: Law; typedExpressions: TypedExpr[]; propertyKind: 'law'|'contract'; generation:Generation; generationPlan:DomainPlan[] }
|
|
24
|
+
export interface TypedExpr { expressionType: Type; expression: Expr; operands: TypedExpr[]; requiredConversion: Type | null }
|
|
25
|
+
export interface CheckRequest { sources: Source[]; schemaVersion?: 2; machineBits?: 32 | 64; generation?:Partial<Generation> }
|
|
16
26
|
export interface GenerationRequest extends CheckRequest { target: Target; sourceDir?: string; testDir?: string }
|
|
17
|
-
export interface Result { diagnostics: Diagnostic[]; laws?: Expanded[]; expansions?: string[]; files?: Artifact[] }
|
|
27
|
+
export interface Result { schemaVersion: 2; machineBits?: 32 | 64; generation?:Generation; refinements?:{owner:string; declaration:Refinement}[]; contracts?:{owner:string; contract:Contract}[]; diagnostics: Diagnostic[]; laws?: Expanded[]; expansions?: string[]; files?: Artifact[] }
|
|
18
28
|
export interface Compiler { check(input: CheckRequest): Promise<Result>; expand(input: CheckRequest): Promise<Result>; planGeneration(input: GenerationRequest): Promise<Result> }
|
|
19
29
|
export function createCompiler(): Promise<Compiler>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lawspec",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "State the law once. Check it everywhere.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
"bin": { "lawspec": "bin/lawspec.mjs" },
|
|
9
9
|
"exports": { ".": { "types": "./index.d.ts", "import": "./api.mjs" } },
|
|
10
10
|
"types": "index.d.ts",
|
|
11
|
-
"files": ["*.mjs", "*.json", "index.d.ts", "bin", "core.wasm", "core_jsffi.js", "README.md", "LICENSE", "starter.lawspec", "examples"],
|
|
11
|
+
"files": ["*.mjs", "*.json", "index.d.ts", "bin", "core.wasm", "core_jsffi.js", "README.md", "PRIMITIVES.md", "REFINEMENTS.md", "API-MIGRATION.md", "LICENSE", "starter.lawspec", "examples"],
|
|
12
12
|
"scripts": { "test": "node --test test/*.test.mjs" }
|
|
13
13
|
}
|
package/scalars.mjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Human-readable, lossless API v2 scalar display.
|
|
2
|
+
export function showScalar(v) {
|
|
3
|
+
if (!v || typeof v !== 'object') throw new TypeError('Expected an API v2 scalar');
|
|
4
|
+
if ('coefficient' in v) return `${v.coefficient}e${v.exponent}`;
|
|
5
|
+
if ('numerator' in v) return `rational(${v.numerator}, ${v.denominator})`;
|
|
6
|
+
if ('bits' in v) return `${v.type === 'Float32' ? 'float32Bits' : 'float64Bits'}(${JSON.stringify(v.bits)})`;
|
|
7
|
+
if ('real' in v) return `${v.type}(${showScalar(v.real)}, ${showScalar(v.imaginary)})`;
|
|
8
|
+
if ('units' in v) return v.type === 'Text' ? JSON.stringify(v.units.map(c => String.fromCodePoint(c)).join('')) : `${v.type}([${v.units.join(', ')}])`;
|
|
9
|
+
if (v.type === 'Symbol') return `symbol(${JSON.stringify(v.id)}, ${JSON.stringify(v.description)})`;
|
|
10
|
+
if (v.type === 'Optional' || v.type === 'Nullable') return v.value === null ? (v.type === 'Optional' ? 'undefined' : 'null') : `${v.type.toLowerCase()}(${showScalar(v.value)})`;
|
|
11
|
+
if ('value' in v) return String(v.value);
|
|
12
|
+
return {Unit:'unitValue', Null:'null', Undefined:'undefined'}[v.type] ?? v.type;
|
|
13
|
+
}
|
package/templates.mjs
CHANGED
|
@@ -100,7 +100,7 @@ export function templates(target) {
|
|
|
100
100
|
case "haskell":
|
|
101
101
|
return {
|
|
102
102
|
"stack.yaml": "snapshot: lts-24.58\npackages: [.]\n",
|
|
103
|
-
"package.yaml": `name: lawspec-example\nversion: 0.1.0\ndependencies: [base, text]\nlibrary:\n source-dirs: src\ntests:\n laws:\n main: Spec.hs\n source-dirs: test\n dependencies: [lawspec-example, hspec, hedgehog, hspec-hedgehog]\n build-tools: [hspec-discover]\n`,
|
|
103
|
+
"package.yaml": `name: lawspec-example\nversion: 0.1.0\ndependencies: [base, text, bytestring]\nlibrary:\n source-dirs: src\ntests:\n laws:\n main: Spec.hs\n source-dirs: test\n dependencies: [lawspec-example, hspec, hedgehog, hspec-hedgehog]\n build-tools: [hspec-discover]\n`,
|
|
104
104
|
"test/Spec.hs": "{-# OPTIONS_GHC -F -pgmF hspec-discover #-}\n",
|
|
105
105
|
};
|
|
106
106
|
case "kotlin":
|