lawspec 0.5.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 +174 -21
- package/REFINEMENTS.md +158 -0
- package/bin/lawspec.mjs +24 -7
- package/build.json +37 -11
- package/core.wasm +0 -0
- package/examples/specs/algebra.lawspec +254 -0
- package/examples/specs/currying.lawspec +60 -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 +21 -10
- 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
|
@@ -2,16 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
**State the law once. Check it everywhere.**
|
|
4
4
|
|
|
5
|
-
LawSpec 0.
|
|
5
|
+
LawSpec 0.6 compiles reusable laws into native property tests, executable examples,
|
|
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
|
|
@@ -61,7 +73,7 @@ properties with the selected framework's shrinking and failure reporting.
|
|
|
61
73
|
| `kotlin` | JDK/JVM 25, Gradle 9.1–9.3, Kotlin 2.3.21 | Kotest 5.9.1 | `gradle test` |
|
|
62
74
|
|
|
63
75
|
Java 25 and Python 3.13 are the minimum baselines. New JVM releases are admitted
|
|
64
|
-
through compatibility profiles after testing; v0.
|
|
76
|
+
through compatibility profiles after testing; v0.6's current JVM profile certifies
|
|
65
77
|
25. Python templates declare `requires-python = ">=3.13"` and runtime checks
|
|
66
78
|
currently recognize 3.13 and 3.14. Kotlin templates pin Gradle's supported build
|
|
67
79
|
configuration to Kotlin 2.3.21 and target JVM 25.
|
|
@@ -157,14 +169,14 @@ 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
|
-
|
|
167
|
-
and unary. Text literals are double-quoted, with escapes such as `\"`, `\\`,
|
|
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
|
|
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.
|
|
170
182
|
|
|
@@ -175,10 +187,144 @@ 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
|
|
|
194
|
+
## Algebra and currying (0.6)
|
|
195
|
+
|
|
196
|
+
Version 0.6 adds algebra laws, scalar law parameters, curried signatures,
|
|
197
|
+
and conjunctions.
|
|
198
|
+
|
|
199
|
+
```lawspec
|
|
200
|
+
unit example.addition
|
|
201
|
+
|
|
202
|
+
add :: Int32 -> Int32 -> Int32
|
|
203
|
+
|
|
204
|
+
law `addition commutes` is
|
|
205
|
+
definition is
|
|
206
|
+
`commutative` add
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
example `3 plus 5 and 5 plus 3 both produce 8` is
|
|
210
|
+
x = 3
|
|
211
|
+
y = 5
|
|
212
|
+
expect add x y = 8
|
|
213
|
+
expect add y x = 8
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
law `zero is an identity on both sides` is
|
|
218
|
+
definition is
|
|
219
|
+
`identity` add 0
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
example `zero preserves 3 on either side` is
|
|
223
|
+
x = 3
|
|
224
|
+
expect add 0 x = 3
|
|
225
|
+
expect add x 0 = 3
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Arrows associate to the right and application associates to the left:
|
|
231
|
+
`f :: a -> b -> c` takes two arguments, and `f x y` means `(f x) y`.
|
|
232
|
+
A partial application such as `add 1` can be passed to a reusable unary law;
|
|
233
|
+
`sumFour 1 2` can be passed to a binary law. Partial applications also compose.
|
|
234
|
+
The compiler specializes these expressions before emission. Java, Kotlin,
|
|
235
|
+
Python, JavaScript, TypeScript and Go adapters take ordinary positional arguments
|
|
236
|
+
(`add(x, y)`); Haskell adapters use native currying (`add x y`). Argument order
|
|
237
|
+
and types are preserved, including mixtures of `Text`, `Bool` and `Int32`.
|
|
238
|
+
|
|
239
|
+
Law parameters can also be scalar values: `(e :: a)` supplies an identity and
|
|
240
|
+
`(zero :: a)` supplies an absorbing element. Pass literals directly, for example
|
|
241
|
+
`left identity` with arguments `add 0`, or `absorbing element` with `multiply 0`.
|
|
242
|
+
Functions declared by a unit take one or more scalar inputs and return a scalar;
|
|
243
|
+
reusable laws accept these curried functions, their partial applications, and
|
|
244
|
+
scalar parameters. Quantified test inputs remain scalar.
|
|
245
|
+
|
|
246
|
+
The prelude defines the following laws. Every row has an executable example in
|
|
247
|
+
[algebra.lawspec](https://github.com/brain-fuel/lawspec/blob/v0.7.0/examples/specs/algebra.lawspec), including both sides of every
|
|
248
|
+
combined law. `f` and `g` are binary operations, `inverse` is unary, and `e` and
|
|
249
|
+
`zero` are scalar parameters. All these laws require equality of the element type.
|
|
250
|
+
|
|
251
|
+
| Law and arguments | Equations checked for every quantified input |
|
|
252
|
+
| --- | --- |
|
|
253
|
+
| `commutative f` | `f x y = f y x` |
|
|
254
|
+
| `associative f` | `f (f x y) z = f x (f y z)` |
|
|
255
|
+
| `left identity f e` | `f e x = x` |
|
|
256
|
+
| `right identity f e` | `f x e = x` |
|
|
257
|
+
| `identity f e` | Both identity equations |
|
|
258
|
+
| `left absorbing element f zero` | `f zero x = zero` |
|
|
259
|
+
| `right absorbing element f zero` | `f x zero = zero` |
|
|
260
|
+
| `absorbing element f zero` | Both absorbing equations |
|
|
261
|
+
| `left distributive f g` | `f x (g y z) = g (f x y) (f x z)` |
|
|
262
|
+
| `right distributive f g` | `f (g x y) z = g (f x z) (f y z)` |
|
|
263
|
+
| `distributive f g` | Both distributive equations |
|
|
264
|
+
| `idempotent operation f` | `f x x = x` (the existing `idempotent` law is unary) |
|
|
265
|
+
| `left inverse element f inverse e` | `f (inverse x) x = e` |
|
|
266
|
+
| `right inverse element f inverse e` | `f x (inverse x) = e` |
|
|
267
|
+
| `invertible f inverse e` | Both inverse equations |
|
|
268
|
+
| `left division f divideLeft` | `f x (divideLeft x y) = y` and `divideLeft x (f x y) = y` |
|
|
269
|
+
| `right division f divideRight` | `f (divideRight x y) y = x` and `divideRight (f x y) y = x` |
|
|
270
|
+
| `divisible f divideLeft divideRight` | All four division equations |
|
|
271
|
+
| `involution f` | `f (f x) = x` |
|
|
272
|
+
|
|
273
|
+
Here **divisible** means algebraic left/right division. `divideLeft x y` solves
|
|
274
|
+
`f x result = y`; `divideRight x y` solves `f result y = x`. The subtraction
|
|
275
|
+
example deliberately uses a noncommutative operation: with `x = 3` and `y = 5`,
|
|
276
|
+
the left solution is `-2` and the right solution is `8`. Recovery is checked in
|
|
277
|
+
both directions. These are total laws; a partially defined division needs an
|
|
278
|
+
explicit domain predicate and conditional equations.
|
|
279
|
+
|
|
280
|
+
`invertible` checks the supplied inverse operation. Check `identity` and
|
|
281
|
+
`associative` as well when specifying a group. The prelude states contracts;
|
|
282
|
+
it does not supply arithmetic implementations or prove a structure from random
|
|
283
|
+
tests. The numeric examples use Int32 arithmetic modulo 2^32 so their laws hold
|
|
284
|
+
at overflow boundaries on every target. JavaScript uses `Math.imul` for products,
|
|
285
|
+
and Python explicitly wraps results into the signed Int32 range in the test
|
|
286
|
+
adapters.
|
|
287
|
+
|
|
288
|
+
Use `and` to require multiple conclusions in one law. For example:
|
|
289
|
+
|
|
290
|
+
```lawspec
|
|
291
|
+
unit example.absorption
|
|
292
|
+
multiply :: Int32 -> Int32 -> Int32
|
|
293
|
+
|
|
294
|
+
law `zero absorbs on both sides` is
|
|
295
|
+
definition is
|
|
296
|
+
`for all` (x :: Int32) .
|
|
297
|
+
multiply 0 x = 0 and multiply x 0 = 0
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
example `3 times zero and zero times 3 both produce zero` is
|
|
301
|
+
x = 3
|
|
302
|
+
expect multiply 0 x = 0
|
|
303
|
+
expect multiply x 0 = 0
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Quantification and implication extend through the following conjunction:
|
|
309
|
+
`p x implies A and B` guards both conclusions. Write `(p x implies A) and B`
|
|
310
|
+
to guard only the first. A shared guard runs once per check; false guards skip
|
|
311
|
+
their entire consequence. Every conjunct is type-checked and emitted. As with
|
|
312
|
+
existing assertions, the first failure stops that individual test. `and` is now
|
|
313
|
+
a reserved word.
|
|
314
|
+
|
|
315
|
+
[Currying examples](https://github.com/brain-fuel/lawspec/blob/v0.7.0/examples/specs/currying.lawspec) demonstrate a four-argument
|
|
316
|
+
function partially applied twice, a formatter with four heterogeneous arguments,
|
|
317
|
+
and composition after partial application. Each example states its exact outputs.
|
|
318
|
+
Run `node npm/bin/lawspec.mjs examples` after rebuilding to inspect all nine units
|
|
319
|
+
in all seven target languages (126 artifacts).
|
|
320
|
+
|
|
321
|
+
The expanded API's **`assertion` tree is authoritative**: `AssertEqual` contains
|
|
322
|
+
two expressions, `AssertImplies` contains a condition and consequence, and
|
|
323
|
+
`AssertAll` contains every conjunct. Existing `left`, `right`, and `guards`
|
|
324
|
+
fields are compatibility projections of the first conclusion only; consumers
|
|
325
|
+
checking compound laws must traverse `assertion`. Source definitions add `And`.
|
|
326
|
+
`lawspec explain` prints the full conjunction and its conditional scope.
|
|
327
|
+
|
|
182
328
|
## Predicates and conditional laws (0.5)
|
|
183
329
|
|
|
184
330
|
A predicate is a unary function returning `Bool`. Use `true` and `false` in
|
|
@@ -218,7 +364,7 @@ law `valid ports round trip` is
|
|
|
218
364
|
end
|
|
219
365
|
```
|
|
220
366
|
|
|
221
|
-
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)
|
|
222
368
|
defines valid ports as 1–65535, and covers both endpoints, ordinary ports, zero,
|
|
223
369
|
negative values, and 65536. All explicit `expect` assertions run regardless of
|
|
224
370
|
the law's condition. A false condition skips only the consequence: invalid ports
|
|
@@ -236,7 +382,7 @@ The prelude includes `satisfies predicate` (the predicate holds for every input)
|
|
|
236
382
|
and `left inverse when predicate parse render` (the guarded round trip above).
|
|
237
383
|
These reusable laws preserve the condition and its lexical bindings when expanded.
|
|
238
384
|
`equivalent` can also compare two predicates, since `Bool` supports equality.
|
|
239
|
-
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)
|
|
240
386
|
checks that flipping twice restores both `false` and `true`; all targets generate
|
|
241
387
|
Boolean property inputs and explicit tests for both Boolean boundary values. Java caps Boolean-only JetCheck runs at the number of
|
|
242
388
|
possible input combinations (up to 100), avoiding generator exhaustion.
|
|
@@ -257,7 +403,7 @@ preserves your adapter and reports the required stub shape. Generated tests call
|
|
|
257
403
|
|
|
258
404
|
Every `example` must bind all quantified inputs and then include one or more
|
|
259
405
|
`expect <expression> = <literal>` assertions. The expected literal must have the
|
|
260
|
-
|
|
406
|
+
contextual scalar type of the expression. Expressions can reference the
|
|
261
407
|
example's inputs and the unit's functions, including composed function calls.
|
|
262
408
|
Input names shadow function names within expectations, following lexical scope.
|
|
263
409
|
|
|
@@ -315,7 +461,7 @@ This expands to `for all (x :: Int32) . render (x) = referenceRender (x)`.
|
|
|
315
461
|
The example inherits the input name `x` from the prelude. Both functions are
|
|
316
462
|
user-owned adapter functions; either may delegate to your existing code.
|
|
317
463
|
|
|
318
|
-
[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
|
|
319
465
|
renderers and two implementations that clamp negative integers to zero. For
|
|
320
466
|
JavaScript, their adapters can be:
|
|
321
467
|
|
|
@@ -330,7 +476,7 @@ The same specification generates native tests for all seven targets. The
|
|
|
330
476
|
integration suite checks both examples with matching implementations, then
|
|
331
477
|
breaks each alternative separately to verify detection. The general equivalence law alone does not establish independent correctness;
|
|
332
478
|
two implementations can share the same bug. Explicit expectations additionally
|
|
333
|
-
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.
|
|
334
480
|
|
|
335
481
|
## Text properties and idempotence
|
|
336
482
|
|
|
@@ -352,7 +498,7 @@ law `normalizers agree` is
|
|
|
352
498
|
end
|
|
353
499
|
```
|
|
354
500
|
|
|
355
|
-
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)
|
|
356
502
|
compares two implementations of ASCII-space replacement. It includes empty,
|
|
357
503
|
Unicode and escaped text. Each target uses its native string generator:
|
|
358
504
|
JetCheck `Generator.stringsOf(Generator.asciiPrintableChars())`, Hypothesis `st.text()`, fast-check `fc.string()`,
|
|
@@ -379,7 +525,7 @@ law `canonicalization reaches a fixed point` is
|
|
|
379
525
|
end
|
|
380
526
|
```
|
|
381
527
|
|
|
382
|
-
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)
|
|
383
529
|
uses removal of **all trailing slashes** as a small fixed-point demonstration,
|
|
384
530
|
not a complete URL canonicalization algorithm. For JavaScript:
|
|
385
531
|
|
|
@@ -388,7 +534,7 @@ export const canonicalize = value => value.replace(/\/+$/, "");
|
|
|
388
534
|
```
|
|
389
535
|
|
|
390
536
|
Removing just one trailing slash fails the supplied repeated-slash example.
|
|
391
|
-
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)
|
|
392
538
|
shows `Text` and `Int32` in the same quantified property and executable example.
|
|
393
539
|
The JavaScript API represents input bindings and expected values as `number | string | boolean`.
|
|
394
540
|
Each example includes `expectations: { actual: Expr; expected: number | string | boolean }[]`.
|
|
@@ -453,7 +599,7 @@ by the JS shim.
|
|
|
453
599
|
## Build and verify
|
|
454
600
|
|
|
455
601
|
For contributors working from a repository checkout, build a local archive with
|
|
456
|
-
`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`.
|
|
457
603
|
The package payload lives in `npm/`.
|
|
458
604
|
|
|
459
605
|
```sh
|
|
@@ -491,3 +637,10 @@ fail, regeneration preserves implementations, and generation leaves build files
|
|
|
491
637
|
unchanged. Arguments select individual targets. `LAWSPEC_PYTHON=3.14` selects the
|
|
492
638
|
additional Python reference environment. CI also exercises Node 22/24/26 and packs
|
|
493
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).
|