lawspec 0.6.0 → 0.8.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 +103 -0
- package/LANGUAGE.md +136 -0
- package/PRIMITIVES.md +150 -0
- package/README.md +55 -30
- package/REFINEMENTS.md +158 -0
- package/RUST.md +97 -0
- package/api.mjs +1 -1
- package/bin/lawspec.mjs +24 -35
- package/build.json +67 -11
- package/compatibility.json +102 -19
- package/core.wasm +0 -0
- package/doctor.mjs +31 -2
- package/examples/specs/algebra.lawspec +36 -8
- package/examples/specs/currying.lawspec +1 -1
- package/examples/specs/refinements.lawspec +86 -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 +2 -2
- package/index.d.ts +23 -13
- package/package.json +2 -2
- package/scalars.mjs +13 -0
- package/templates.mjs +25 -1
package/API-MIGRATION.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Compiler API migration: schema 2 → schema 3
|
|
2
|
+
|
|
3
|
+
LawSpec 0.8 uses API schema version **3**. Requests may omit `schemaVersion` or
|
|
4
|
+
send `3`. An explicit `2` (or any other version) receives a request diagnostic;
|
|
5
|
+
it is never silently reinterpreted. LawSpec specification syntax remains compatible.
|
|
6
|
+
The generated `index.d.ts` describes the public protocol. Internal Haskell
|
|
7
|
+
constructors and record fields are no longer the wire format.
|
|
8
|
+
|
|
9
|
+
## Laws and typed expressions
|
|
10
|
+
|
|
11
|
+
Read `result.laws[i].examples` instead of `law.original.examples`. A binding is
|
|
12
|
+
now `{id, name, type, value}` rather than `[name, value]`. `value` retains the
|
|
13
|
+
lossless tagged scalar representation from schema 2. Expected results are typed
|
|
14
|
+
expressions in an assertion, for example `example.expectations[0].right.node.value`.
|
|
15
|
+
|
|
16
|
+
Every expression has `{type, origin, text, node}`. `text` is for presentation;
|
|
17
|
+
inspect `node` for semantics. Nodes explicitly distinguish constants, resolved
|
|
18
|
+
locals, declaration calls, arithmetic with capability evidence, short-circuit
|
|
19
|
+
operators, helpers, and conversions. Conversion mode `checked` means an exact
|
|
20
|
+
result must fit its adapter parameter; `explicit` records a source conversion.
|
|
21
|
+
There is no separate `typedExpressions` side table to match against source ASTs.
|
|
22
|
+
|
|
23
|
+
Assertions are the authoritative proposition tree:
|
|
24
|
+
|
|
25
|
+
- `{kind: "equal", evidence, left, right}`
|
|
26
|
+
- `{kind: "implies", guard, body}`
|
|
27
|
+
- `{kind: "all", items}`
|
|
28
|
+
|
|
29
|
+
The `left`, `right`, and `guards` compatibility projections on laws are removed.
|
|
30
|
+
Traverse the tree to preserve shared guard scope and conjunction order. Do not
|
|
31
|
+
flatten guards or turn refinement predicates into implications.
|
|
32
|
+
|
|
33
|
+
Inputs use `{id, name, type, predicates, bounds}`. IDs identify binders;
|
|
34
|
+
display names need not be globally unique. Bounds are derived generation hints
|
|
35
|
+
`{operator, value}` over preceding inputs. Predicates remain authoritative.
|
|
36
|
+
`generationPlan` and `inputRefinements` are replaced by these explicit fields.
|
|
37
|
+
Contracts expose typed argument/result binders, preconditions, and postconditions.
|
|
38
|
+
Refinement declarations expose documented parameter kinds, requirements, and a
|
|
39
|
+
printed definition; they are not serialized source ASTs.
|
|
40
|
+
|
|
41
|
+
## Types, identity, and locations
|
|
42
|
+
|
|
43
|
+
Types are discriminated views, not `tag`/`contents` encodings:
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
{ kind: "constructor", name: "Int8", arguments: [] }
|
|
47
|
+
{ kind: "constructor", name: "Optional", arguments: [
|
|
48
|
+
{ kind: "type", type: { kind: "constructor", name: "Int8", arguments: [] } }
|
|
49
|
+
] }
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Function types use `{kind: "function", parameter, result}`; type variables use
|
|
53
|
+
`{kind: "variable", id}`. The argument model distinguishes types, natural indices
|
|
54
|
+
(encoded as decimal strings), and index variables. This representation does not
|
|
55
|
+
make unimplemented containers or dependent families available in 0.8.
|
|
56
|
+
|
|
57
|
+
Declaration/property/binder IDs remain stable when unrelated laws are inserted.
|
|
58
|
+
Origins are either `{kind: "source", span: {start, end}}` or
|
|
59
|
+
`{kind: "generated", declaration}`. Source ranges come from parsing, including
|
|
60
|
+
expressions in reused laws and refinements. Generated nodes identify their owner
|
|
61
|
+
instead of inventing source coordinates. Positions use one-based lines/columns;
|
|
62
|
+
end positions are exclusive and can include trailing parser whitespace.
|
|
63
|
+
|
|
64
|
+
## Scalar values remain lossless
|
|
65
|
+
|
|
66
|
+
| Domain | Payload after `type` |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| Integers, including logical Integer | `value`: decimal string |
|
|
69
|
+
| Bool | `value`: Boolean |
|
|
70
|
+
| Decimal | `coefficient`, `exponent`: decimal strings |
|
|
71
|
+
| Rational | `numerator`, `denominator`: decimal strings; reduced, denominator positive |
|
|
72
|
+
| Float32 / Float64 | `bits`: 8 / 16 hexadecimal digits in IEEE bit order |
|
|
73
|
+
| Complex64 / Complex128 | `real`, `imaginary`: tagged component scalars |
|
|
74
|
+
| Char / CodePoint / CodeUnit16 | `value`: numeric unit |
|
|
75
|
+
| Text / CodePointText / Utf16Text / Bytes | `units`: numeric unit array |
|
|
76
|
+
| Symbol | `id`, `description`: strings; identity comes from the ID |
|
|
77
|
+
| Unit / Null / Undefined | No payload |
|
|
78
|
+
| Nullable / Optional | `value`: null for missing, otherwise a tagged scalar |
|
|
79
|
+
|
|
80
|
+
Do not coerce integer strings to JavaScript Number. Text uses code units or code
|
|
81
|
+
points as appropriate to its domain; raw surrogates never pass through JSON
|
|
82
|
+
strings. The enclosing type supplies the inner type of a missing presence value.
|
|
83
|
+
|
|
84
|
+
## Checking, generation, and artifacts
|
|
85
|
+
|
|
86
|
+
`check` and `expand` validate the language. `planGeneration` additionally checks
|
|
87
|
+
whether its input domains can be executed. For example, a well-typed empty finite
|
|
88
|
+
refinement can pass `check` and fail generation with an empty-domain diagnostic.
|
|
89
|
+
Exhausted refinement searches fail explicitly; rejected inputs do not count as
|
|
90
|
+
successful tests.
|
|
91
|
+
|
|
92
|
+
Requests retain `machineBits?: 32 | 64` (default 64) and partial `generation`
|
|
93
|
+
settings (`cases`, `maxAttempts`, `maxShrinks`, `exhaustiveLimit`). Rust joins the
|
|
94
|
+
seven existing targets. Artifacts retain separate `ownership` and `placement`:
|
|
95
|
+
generated runtime source belongs in source directories, adapters remain
|
|
96
|
+
user-owned, and test helpers belong in test directories. Never infer placement
|
|
97
|
+
from ownership or assume a fixed number of generated files. Continue using the
|
|
98
|
+
manifest writer to protect edited files.
|
|
99
|
+
|
|
100
|
+
All nonempty test plans now emit the portable scalar runtime. Existing Haskell
|
|
101
|
+
projects must include `text` and `bytestring` in the component that compiles
|
|
102
|
+
that source. Doctor reports the missing dependencies before generation. Build
|
|
103
|
+
files remain user-owned; new scaffolds already include these dependencies.
|
package/LANGUAGE.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# LawSpec language and compiler boundary (0.8)
|
|
2
|
+
|
|
3
|
+
LawSpec describes portable laws, concrete examples, and adapter contracts. The
|
|
4
|
+
compiler is written in Haskell. Rust is an output backend alongside Java, Python,
|
|
5
|
+
JavaScript, TypeScript, Go, Haskell, and Kotlin.
|
|
6
|
+
|
|
7
|
+
## Source and declarations
|
|
8
|
+
|
|
9
|
+
A source has one named `unit`, function signatures, reusable refinements, and
|
|
10
|
+
laws. Qualified unit names determine target module/package paths. Function
|
|
11
|
+
signatures are curried: `a -> b -> c` takes two inputs and returns `c`. Parentheses
|
|
12
|
+
group types and expressions. Comments start with `--` and run to the line end.
|
|
13
|
+
|
|
14
|
+
The following grammar summarizes the main forms; the parser and executable
|
|
15
|
+
compiler tests specify lexical details:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
source = "unit" qualified-name declaration*
|
|
19
|
+
declaration = name "::" type | law | refinement
|
|
20
|
+
type = type-atom ["->" type]
|
|
21
|
+
type-atom = primitive | type-variable | "(" type ")"
|
|
22
|
+
| ("Nullable" | "Optional") type-atom
|
|
23
|
+
| refinement-name argument*
|
|
24
|
+
| "(" name "::" type ["where" expression] ")"
|
|
25
|
+
refinement = "refinement" name parameter* requirements?
|
|
26
|
+
"is" type "end"
|
|
27
|
+
parameter = "(" name "::" type ["where" expression] ")"
|
|
28
|
+
requirements = "requires" (capability type)+
|
|
29
|
+
capability = "Eq" | "Integer" | "Ordered" | "Bounded"
|
|
30
|
+
law = "law" quoted-name parameter* requirements? "is"
|
|
31
|
+
"definition" "is" proposition "end"
|
|
32
|
+
description? rationale? example* references? "end"
|
|
33
|
+
proposition = "`for all`" parameter+ "." proposition
|
|
34
|
+
| expression "=" expression
|
|
35
|
+
| expression "implies" proposition
|
|
36
|
+
| proposition "and" proposition
|
|
37
|
+
| quoted-name expression* | expression
|
|
38
|
+
example = "example" quoted-name "is" (name "=" literal)+
|
|
39
|
+
("expect" expression "=" literal)+ "end"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Law names use backticks. Text/metadata use double quotes with escapes. Named
|
|
43
|
+
refinement declarations state their type versus value parameter kinds, including
|
|
44
|
+
forward references; the compiler validates their arity and argument kinds.
|
|
45
|
+
Lowercase type names represent variables. `a :: Type` is a type parameter, not a
|
|
46
|
+
runtime value with a generator.
|
|
47
|
+
|
|
48
|
+
A generic law is specialized when invoked with concrete adapters. Its declared
|
|
49
|
+
capabilities must justify its operations; specialization resolves those
|
|
50
|
+
requirements for the actual types. `Integer` as a capability requires an integer
|
|
51
|
+
type. `Integer` in a concrete result signature denotes a representation-independent
|
|
52
|
+
mathematical integer. See [refinements and abstract integers](REFINEMENTS.md).
|
|
53
|
+
|
|
54
|
+
## Expressions and arithmetic
|
|
55
|
+
|
|
56
|
+
Application binds most tightly. The remaining precedence, highest first, is:
|
|
57
|
+
unary `!`/`-`, composition `.`, multiplication/division, addition/subtraction,
|
|
58
|
+
comparisons (`==`, `!=`, `<`, `<=`, `>`, `>=`), `&&`, then `||`.
|
|
59
|
+
Comparisons do not chain. Arithmetic associates left; composition associates
|
|
60
|
+
right. An adjacent numeric sign remains part of an argument: `f -42` applies `f`
|
|
61
|
+
to negative 42. Write `x - 42` for subtraction.
|
|
62
|
+
|
|
63
|
+
Assertion `=` differs from Boolean `==`. `implies` guards its following
|
|
64
|
+
proposition; `and` requires both assertions. Parentheses determine the scope of a
|
|
65
|
+
shared guard. Both Boolean operators and implications short-circuit.
|
|
66
|
+
|
|
67
|
+
Literals acquire types from declared context. Unconstrained integers have type
|
|
68
|
+
`Integer`; decimal tokens have type `Decimal`. An annotation such as
|
|
69
|
+
`(127 :: Int8)` specifies context. A declared float context can type a decimal
|
|
70
|
+
token directly, but an explicitly constructed exact Decimal is not implicitly
|
|
71
|
+
converted into a float.
|
|
72
|
+
|
|
73
|
+
Integer `+`, `-`, `*`, and negation produce exact `Integer` results. Decimal
|
|
74
|
+
dominates integer/Decimal combinations; Rational dominates exact combinations
|
|
75
|
+
involving Rational. Exact `/` returns Rational. `prelude.quot` truncates integer
|
|
76
|
+
quotients toward zero; `prelude.rem` is the associated remainder. Division by
|
|
77
|
+
zero fails when evaluated. Explicit numeric conversions use `prelude.Type`.
|
|
78
|
+
Exact/inexact mixing otherwise fails type checking. IEEE arithmetic widens float
|
|
79
|
+
or complex precision as required; equality treats NaN as unequal and signed
|
|
80
|
+
zero as equal. Decimal rounding is explicit, with a scale and ties-to-even rule.
|
|
81
|
+
|
|
82
|
+
Passing a computed exact result to a bounded adapter argument performs a checked
|
|
83
|
+
conversion. It never wraps, truncates a fraction, or silently changes precision.
|
|
84
|
+
Adapter results are validated against the declared domain before use. See the
|
|
85
|
+
[primitive reference](PRIMITIVES.md) for all domains and helpers.
|
|
86
|
+
|
|
87
|
+
## Refinements and executable contracts
|
|
88
|
+
|
|
89
|
+
Refinements are pure Boolean predicates over a value and preceding binders.
|
|
90
|
+
They cannot call user adapters. Dependent inputs are generated in order;
|
|
91
|
+
shrinking preserves their predicates. Bounds such as `y > Int8.max - x` are
|
|
92
|
+
computed with exact arithmetic and can drive a dependent generator. If a chosen
|
|
93
|
+
`x` has no possible `y`, generation retries earlier inputs. Search exhaustion
|
|
94
|
+
fails explicitly rather than passing a property with no valid cases.
|
|
95
|
+
|
|
96
|
+
Contracts check preconditions, evaluate an adapter once, validate the result,
|
|
97
|
+
and then check postconditions against that same result. Predicate errors are
|
|
98
|
+
contextual failures, not rejected samples. Small finite domains are enumerated;
|
|
99
|
+
other domains use target property frameworks. `machineBits: 32 | 64` controls
|
|
100
|
+
machine-integer domains independently of the compiler host architecture. Native
|
|
101
|
+
machine-sized adapter bindings additionally verify the executing architecture.
|
|
102
|
+
|
|
103
|
+
## Compiler stages and public API
|
|
104
|
+
|
|
105
|
+
The parser retains source ranges. Resolution and inference check names, kinds,
|
|
106
|
+
capabilities, contextual literals, and generic specializations. Elaboration
|
|
107
|
+
produces typed core expressions with resolved declaration/binder IDs, explicit
|
|
108
|
+
arithmetic evidence and conversions, plus an authoritative proposition tree.
|
|
109
|
+
Refinement declarations become predicates on quantifiers and contracts; targets
|
|
110
|
+
do not interpret refinement syntax.
|
|
111
|
+
|
|
112
|
+
An independent core validator checks scopes, kinds, operand/result types,
|
|
113
|
+
capability evidence, and conversions. A pure core evaluator supports deterministic
|
|
114
|
+
domain checks and reference tests. The testing planner computes finite cases,
|
|
115
|
+
boundaries, and dependent generator requirements. All eight emitters consume
|
|
116
|
+
that plan and the core, without importing source syntax or inference.
|
|
117
|
+
|
|
118
|
+
`check`/`expand` report semantic validity. `planGeneration` additionally reports
|
|
119
|
+
execution feasibility, including empty finite domains. Source syntax errors,
|
|
120
|
+
semantic errors, core invariant failures, and generation errors have distinct
|
|
121
|
+
diagnostic codes. Runtime failures identify the law/example or adapter contract.
|
|
122
|
+
Parsed expressions retain real source ranges; synthesized expressions identify
|
|
123
|
+
the declaration that caused their creation.
|
|
124
|
+
|
|
125
|
+
API schema v3 uses separately defined wire views, with lossless tagged scalar
|
|
126
|
+
values. It does not serialize internal AST constructors. See the
|
|
127
|
+
[API migration guide](API-MIGRATION.md).
|
|
128
|
+
|
|
129
|
+
## Next language features
|
|
130
|
+
|
|
131
|
+
`List`, algebraic `Maybe`/`Either`, user-defined sums and products, pattern
|
|
132
|
+
matching, GADTs, and general dependent types are outside 0.8. The core type model
|
|
133
|
+
supports arbitrary constructor arity and distinguishes type and index arguments
|
|
134
|
+
so those features can be added without target-specific surface interpretation.
|
|
135
|
+
`Nullable` and `Optional` retain their interoperability semantics; they do not
|
|
136
|
+
stand in for future algebraic sum types.
|
package/PRIMITIVES.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# LawSpec scalar reference (0.8.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 eight 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,33 @@
|
|
|
2
2
|
|
|
3
3
|
**State the law once. Check it everywhere.**
|
|
4
4
|
|
|
5
|
-
LawSpec 0.
|
|
5
|
+
LawSpec 0.8 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
|
+
## Rust and the typed front end in 0.8.0
|
|
10
|
+
|
|
11
|
+
Rust joins Java, Python, JavaScript, TypeScript, Go, Haskell, and Kotlin. All
|
|
12
|
+
eight backends consume the same typed core and testing plan. Source expressions
|
|
13
|
+
retain their ranges, resolved identities, arithmetic evidence, and checked
|
|
14
|
+
conversions. See the [Rust guide](RUST.md) and [language reference](LANGUAGE.md).
|
|
15
|
+
|
|
16
|
+
The scalar catalog includes fixed and arbitrary integers, exact decimals and rationals,
|
|
17
|
+
IEEE floats and complex values, Unicode and raw-text domains, bytes, symbols,
|
|
18
|
+
and nested absence states on all eight targets. Integer arithmetic produces representation-independent
|
|
19
|
+
`Integer` values; exact division returns Rational. See the [primitive reference](PRIMITIVES.md)
|
|
20
|
+
for constructors, arithmetic, native bridges, and machine-width profiles.
|
|
21
|
+
|
|
22
|
+
Compiler API consumers should read the [schema v3 migration guide](API-MIGRATION.md).
|
|
23
|
+
Scalar values use tagged, lossless encodings; generated runtime placement is
|
|
24
|
+
separate from artifact ownership. Java 25+ and Python 3.13+ baselines are unchanged.
|
|
25
|
+
|
|
9
26
|
## Install and try it
|
|
10
27
|
|
|
11
28
|
Install [LawSpec from npm](https://www.npmjs.com/package/lawspec):
|
|
12
29
|
|
|
13
30
|
```sh
|
|
14
|
-
npm install --save-dev lawspec@0.
|
|
31
|
+
npm install --save-dev lawspec@0.8.0
|
|
15
32
|
npx lawspec --version
|
|
16
33
|
```
|
|
17
34
|
|
|
@@ -25,8 +42,8 @@ local dependencies so LawSpec can create its `package.json` and test script:
|
|
|
25
42
|
```sh
|
|
26
43
|
mkdir lawspec-example
|
|
27
44
|
cd lawspec-example
|
|
28
|
-
npm exec --package=lawspec@0.
|
|
29
|
-
npm install --save-dev lawspec@0.
|
|
45
|
+
npm exec --package=lawspec@0.8.0 -- lawspec init --target javascript
|
|
46
|
+
npm install --save-dev lawspec@0.8.0
|
|
30
47
|
npx lawspec check
|
|
31
48
|
npx lawspec explain 'example.atoi_codec::itoa and then atoi yields a'
|
|
32
49
|
npx lawspec doctor
|
|
@@ -59,9 +76,10 @@ properties with the selected framework's shrinking and failure reporting.
|
|
|
59
76
|
| `go` | Go modules, Go 1.22–1.26 | Rapid 1.2.0, testing | `go test ./...` |
|
|
60
77
|
| `haskell` | Stack, GHC 9.10, LTS 24.58 | Hspec 2.11, Hedgehog 1.5, hspec-hedgehog 0.3 | `stack test` |
|
|
61
78
|
| `kotlin` | JDK/JVM 25, Gradle 9.1–9.3, Kotlin 2.3.21 | Kotest 5.9.1 | `gradle test` |
|
|
79
|
+
| `rust` | Rust 1.85+, edition 2024, Cargo | Proptest 1.11.0 | `cargo test` |
|
|
62
80
|
|
|
63
81
|
Java 25 and Python 3.13 are the minimum baselines. New JVM releases are admitted
|
|
64
|
-
through compatibility profiles after testing;
|
|
82
|
+
through compatibility profiles after testing; the current JVM profile certifies
|
|
65
83
|
25. Python templates declare `requires-python = ">=3.13"` and runtime checks
|
|
66
84
|
currently recognize 3.13 and 3.14. Kotlin templates pin Gradle's supported build
|
|
67
85
|
configuration to Kotlin 2.3.21 and target JVM 25.
|
|
@@ -157,13 +175,13 @@ function names specially. `explain` shows the final property:
|
|
|
157
175
|
for all (x :: Int32) . atoi (itoa (x)) = x
|
|
158
176
|
```
|
|
159
177
|
|
|
160
|
-
Reusable laws can declare typed unary function parameters and `requires Eq a
|
|
178
|
+
Reusable laws can declare typed unary function parameters and `requires Eq a`, numeric capabilities such as `requires Integer a`, and
|
|
179
|
+
[parameterized refinements and executable contracts](REFINEMENTS.md).
|
|
161
180
|
Definitions support law application, function application/composition, universal
|
|
162
181
|
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
|
|
182
|
+
Function signatures and quantified inputs support the [scalar catalog](PRIMITIVES.md),
|
|
183
|
+
including mixed and multiple inputs. Generic variables are supported in reusable
|
|
184
|
+
laws. Scalar types can also be intermediate or compared results. Functions are synchronous and support curried signatures with any positive
|
|
167
185
|
number of scalar arguments. Text literals are double-quoted, with escapes such as `\"`, `\\`,
|
|
168
186
|
`\n`, and `\t`; examples must bind each input to a literal of its declared type.
|
|
169
187
|
Text values contain Unicode scalar values; surrogate code points are rejected.
|
|
@@ -175,7 +193,7 @@ produce literal braces. Law blocks use this order:
|
|
|
175
193
|
definition, optional description, optional rationale, examples, optional references.
|
|
176
194
|
`--` starts a line comment. Names that cannot be emitted portably are diagnosed.
|
|
177
195
|
|
|
178
|
-
|
|
196
|
+
General collections, external law packages, cross-unit imports beyond the
|
|
179
197
|
prelude, async functions, direct existing-symbol binding and browser hosting are
|
|
180
198
|
outside this release.
|
|
181
199
|
|
|
@@ -232,7 +250,7 @@ reusable laws accept these curried functions, their partial applications, and
|
|
|
232
250
|
scalar parameters. Quantified test inputs remain scalar.
|
|
233
251
|
|
|
234
252
|
The prelude defines the following laws. Every row has an executable example in
|
|
235
|
-
[algebra.lawspec](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
253
|
+
[algebra.lawspec](https://github.com/brain-fuel/lawspec/blob/v0.8.0/examples/specs/algebra.lawspec), including both sides of every
|
|
236
254
|
combined law. `f` and `g` are binary operations, `inverse` is unary, and `e` and
|
|
237
255
|
`zero` are scalar parameters. All these laws require equality of the element type.
|
|
238
256
|
|
|
@@ -268,10 +286,10 @@ explicit domain predicate and conditional equations.
|
|
|
268
286
|
`invertible` checks the supplied inverse operation. Check `identity` and
|
|
269
287
|
`associative` as well when specifying a group. The prelude states contracts;
|
|
270
288
|
it does not supply arithmetic implementations or prove a structure from random
|
|
271
|
-
tests. The numeric examples use
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
289
|
+
tests. The algebra and numeric currying examples use mathematical `Integer`
|
|
290
|
+
values and exact native arithmetic. Explicit expectations exceed Int32 and
|
|
291
|
+
machine bounds, so wrapping or lossy adapters fail even when their modular
|
|
292
|
+
arithmetic happens to satisfy the algebraic identities.
|
|
275
293
|
|
|
276
294
|
Use `and` to require multiple conclusions in one law. For example:
|
|
277
295
|
|
|
@@ -300,11 +318,11 @@ their entire consequence. Every conjunct is type-checked and emitted. As with
|
|
|
300
318
|
existing assertions, the first failure stops that individual test. `and` is now
|
|
301
319
|
a reserved word.
|
|
302
320
|
|
|
303
|
-
[Currying examples](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
321
|
+
[Currying examples](https://github.com/brain-fuel/lawspec/blob/v0.8.0/examples/specs/currying.lawspec) demonstrate a four-argument
|
|
304
322
|
function partially applied twice, a formatter with four heterogeneous arguments,
|
|
305
323
|
and composition after partial application. Each example states its exact outputs.
|
|
306
324
|
Run `node npm/bin/lawspec.mjs examples` after rebuilding to inspect all nine units
|
|
307
|
-
in all
|
|
325
|
+
in all eight target languages.
|
|
308
326
|
|
|
309
327
|
The expanded API's **`assertion` tree is authoritative**: `AssertEqual` contains
|
|
310
328
|
two expressions, `AssertImplies` contains a condition and consequence, and
|
|
@@ -352,7 +370,7 @@ law `valid ports round trip` is
|
|
|
352
370
|
end
|
|
353
371
|
```
|
|
354
372
|
|
|
355
|
-
The [complete port example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
373
|
+
The [complete port example](https://github.com/brain-fuel/lawspec/blob/v0.8.0/examples/specs/parse_port.lawspec)
|
|
356
374
|
defines valid ports as 1–65535, and covers both endpoints, ordinary ports, zero,
|
|
357
375
|
negative values, and 65536. All explicit `expect` assertions run regardless of
|
|
358
376
|
the law's condition. A false condition skips only the consequence: invalid ports
|
|
@@ -370,7 +388,7 @@ The prelude includes `satisfies predicate` (the predicate holds for every input)
|
|
|
370
388
|
and `left inverse when predicate parse render` (the guarded round trip above).
|
|
371
389
|
These reusable laws preserve the condition and its lexical bindings when expanded.
|
|
372
390
|
`equivalent` can also compare two predicates, since `Bool` supports equality.
|
|
373
|
-
The [Boolean flags example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
391
|
+
The [Boolean flags example](https://github.com/brain-fuel/lawspec/blob/v0.8.0/examples/specs/boolean_flags.lawspec)
|
|
374
392
|
checks that flipping twice restores both `false` and `true`; all targets generate
|
|
375
393
|
Boolean property inputs and explicit tests for both Boolean boundary values. Java caps Boolean-only JetCheck runs at the number of
|
|
376
394
|
possible input combinations (up to 100), avoiding generator exhaustion.
|
|
@@ -391,7 +409,7 @@ preserves your adapter and reports the required stub shape. Generated tests call
|
|
|
391
409
|
|
|
392
410
|
Every `example` must bind all quantified inputs and then include one or more
|
|
393
411
|
`expect <expression> = <literal>` assertions. The expected literal must have the
|
|
394
|
-
|
|
412
|
+
contextual scalar type of the expression. Expressions can reference the
|
|
395
413
|
example's inputs and the unit's functions, including composed function calls.
|
|
396
414
|
Input names shadow function names within expectations, following lexical scope.
|
|
397
415
|
|
|
@@ -449,7 +467,7 @@ This expands to `for all (x :: Int32) . render (x) = referenceRender (x)`.
|
|
|
449
467
|
The example inherits the input name `x` from the prelude. Both functions are
|
|
450
468
|
user-owned adapter functions; either may delegate to your existing code.
|
|
451
469
|
|
|
452
|
-
[The complete example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
470
|
+
[The complete example](https://github.com/brain-fuel/lawspec/blob/v0.8.0/examples/specs/equivalent.lawspec) compares decimal
|
|
453
471
|
renderers and two implementations that clamp negative integers to zero. For
|
|
454
472
|
JavaScript, their adapters can be:
|
|
455
473
|
|
|
@@ -460,11 +478,11 @@ export const clamp = x => Math.max(0, x);
|
|
|
460
478
|
export const referenceClamp = x => x < 0 ? 0 : x;
|
|
461
479
|
```
|
|
462
480
|
|
|
463
|
-
The same specification generates native tests for all
|
|
481
|
+
The same specification generates native tests for all eight targets. The
|
|
464
482
|
integration suite checks both examples with matching implementations, then
|
|
465
483
|
breaks each alternative separately to verify detection. The general equivalence law alone does not establish independent correctness;
|
|
466
484
|
two implementations can share the same bug. Explicit expectations additionally
|
|
467
|
-
check the specified outputs at the supplied example inputs. Quantified inputs can
|
|
485
|
+
check the specified outputs at the supplied example inputs. Quantified inputs can use any supported scalar domain.
|
|
468
486
|
|
|
469
487
|
## Text properties and idempotence
|
|
470
488
|
|
|
@@ -486,7 +504,7 @@ law `normalizers agree` is
|
|
|
486
504
|
end
|
|
487
505
|
```
|
|
488
506
|
|
|
489
|
-
The [slug example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
507
|
+
The [slug example](https://github.com/brain-fuel/lawspec/blob/v0.8.0/examples/specs/slug.lawspec)
|
|
490
508
|
compares two implementations of ASCII-space replacement. It includes empty,
|
|
491
509
|
Unicode and escaped text. Each target uses its native string generator:
|
|
492
510
|
JetCheck `Generator.stringsOf(Generator.asciiPrintableChars())`, Hypothesis `st.text()`, fast-check `fc.string()`,
|
|
@@ -513,7 +531,7 @@ law `canonicalization reaches a fixed point` is
|
|
|
513
531
|
end
|
|
514
532
|
```
|
|
515
533
|
|
|
516
|
-
The [canonical URL example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
534
|
+
The [canonical URL example](https://github.com/brain-fuel/lawspec/blob/v0.8.0/examples/specs/canonical_url.lawspec)
|
|
517
535
|
uses removal of **all trailing slashes** as a small fixed-point demonstration,
|
|
518
536
|
not a complete URL canonicalization algorithm. For JavaScript:
|
|
519
537
|
|
|
@@ -522,7 +540,7 @@ export const canonicalize = value => value.replace(/\/+$/, "");
|
|
|
522
540
|
```
|
|
523
541
|
|
|
524
542
|
Removing just one trailing slash fails the supplied repeated-slash example.
|
|
525
|
-
The [mixed-input example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
543
|
+
The [mixed-input example](https://github.com/brain-fuel/lawspec/blob/v0.8.0/examples/specs/mixed_inputs.lawspec)
|
|
526
544
|
shows `Text` and `Int32` in the same quantified property and executable example.
|
|
527
545
|
The JavaScript API represents input bindings and expected values as `number | string | boolean`.
|
|
528
546
|
Each example includes `expectations: { actual: Expr; expected: number | string | boolean }[]`.
|
|
@@ -538,7 +556,7 @@ npx lawspec examples --target java --output example_artifacts
|
|
|
538
556
|
This command works without a project configuration or native build tools. It
|
|
539
557
|
compiles every bundled example and writes its tests and user-owned stubs to
|
|
540
558
|
`example_artifacts/<language>/`, using each target's normal source/test layout.
|
|
541
|
-
By default it exports all
|
|
559
|
+
By default it exports all eight languages; `--json` returns the file inventory.
|
|
542
560
|
From a checkout, `make examples` runs the same command.
|
|
543
561
|
|
|
544
562
|
These are inspection artifacts, not initialized projects: no build files are
|
|
@@ -587,7 +605,7 @@ by the JS shim.
|
|
|
587
605
|
## Build and verify
|
|
588
606
|
|
|
589
607
|
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.
|
|
608
|
+
`npm pack ./npm` and install it with `npm install --save-dev ./lawspec-0.8.0.tgz`.
|
|
591
609
|
The package payload lives in `npm/`.
|
|
592
610
|
|
|
593
611
|
```sh
|
|
@@ -611,7 +629,7 @@ compiler-source and artifact hashes in `npm/build.json`; CI rejects stale WASM
|
|
|
611
629
|
or hand-edited generated wrappers. The npm archive is a self-contained consumer
|
|
612
630
|
artifact, with no install-time compilation or download hook.
|
|
613
631
|
|
|
614
|
-
For all
|
|
632
|
+
For all eight native integrations, install their build tools, then:
|
|
615
633
|
|
|
616
634
|
```sh
|
|
617
635
|
# Set LAWSPEC_GRADLE to a Gradle 9.3.0 executable if it is not on PATH.
|
|
@@ -625,3 +643,10 @@ fail, regeneration preserves implementations, and generation leaves build files
|
|
|
625
643
|
unchanged. Arguments select individual targets. `LAWSPEC_PYTHON=3.14` selects the
|
|
626
644
|
additional Python reference environment. CI also exercises Node 22/24/26 and packs
|
|
627
645
|
and installs the npm archive. Registry publication is a separate release action.
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
Refinement predicates can depend on earlier inputs. Generated tests backtrack from
|
|
649
|
+
impossible prefixes, preserve the domain during shrinking, and validate function
|
|
650
|
+
preconditions and postconditions. `Integer` results specify exact values without
|
|
651
|
+
choosing a storage width. See the [refinement reference](REFINEMENTS.md) and
|
|
652
|
+
[bundled examples](examples/specs/refinements.lawspec).
|