lawspec 0.7.0 → 0.9.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 +186 -67
- package/GO.md +144 -0
- package/HASKELL.md +171 -0
- package/JAVA.md +113 -0
- package/KOTLIN.md +214 -0
- package/LANGUAGE.md +361 -0
- package/PRIMITIVES.md +2 -2
- package/PYTHON.md +152 -0
- package/README.md +45 -26
- package/REFINEMENTS.md +289 -3
- package/RELEASE-0.9.md +61 -0
- package/RUST.md +211 -0
- package/WEB.md +144 -0
- package/api.mjs +8 -2
- package/bin/lawspec.mjs +22 -47
- package/build.json +198 -26
- 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/collections.lawspec +67 -0
- package/examples/specs/currying.lawspec +1 -1
- package/examples/specs/data_types.lawspec +66 -0
- package/examples/specs/finite_data.lawspec +37 -0
- package/examples/specs/list_contracts.lawspec +136 -0
- package/examples/specs/list_refinements.lawspec +51 -0
- package/examples/specs/matching.lawspec +49 -0
- package/examples/specs/recursive_refinements.lawspec +41 -0
- package/examples/specs/refined_definitions.lawspec +44 -0
- package/examples/specs/refinements.lawspec +11 -0
- package/examples/specs/sum_refinements.lawspec +49 -0
- package/examples/specs/total_functions.lawspec +101 -0
- package/examples-command.mjs +7 -2
- package/files.mjs +10 -1
- package/index.d.ts +246 -27
- package/package.json +2 -2
- package/templates.mjs +130 -16
package/API-MIGRATION.md
CHANGED
|
@@ -1,27 +1,120 @@
|
|
|
1
|
-
# Compiler API migration: schema
|
|
1
|
+
# Compiler API migration: schema 2 → schema 3
|
|
2
|
+
|
|
3
|
+
LawSpec 0.8 introduced API schema version **3**, which 0.9 continues to use.
|
|
4
|
+
Requests may omit `schemaVersion` or
|
|
5
|
+
send `3`. An explicit `2` (or any other version) receives a request diagnostic;
|
|
6
|
+
it is never silently reinterpreted. LawSpec specification syntax remains compatible.
|
|
7
|
+
The generated `index.d.ts` describes the public protocol. Internal Haskell
|
|
8
|
+
constructors and record fields are no longer the wire format.
|
|
9
|
+
|
|
10
|
+
## 0.9 structural data and definition metadata
|
|
11
|
+
|
|
12
|
+
Successful results also expose `dataTypes: DataTypeDeclaration[]`. Each named
|
|
13
|
+
declaration has a resolved `id`, a display `name`, parameter IDs, an origin, and
|
|
14
|
+
constructors with resolved IDs and ordered typed fields. Use resolved IDs to
|
|
15
|
+
join references; constructors with the same display name can belong to different
|
|
16
|
+
units. Built-in container types do not need user declarations in this array.
|
|
17
|
+
|
|
18
|
+
Example bindings now use `DataValue`: either an existing tagged scalar or
|
|
19
|
+
`{kind: "data", type: Type, constructor: string, fields: DataValue[]}`. List values
|
|
20
|
+
use `List::Nil` and `List::Cons`, with head and tail fields; Maybe and Either use
|
|
21
|
+
their qualified constructor IDs. Do not flatten these values to JSON arrays or
|
|
22
|
+
nullable fields: that would lose constructor and nested-presence distinctions.
|
|
23
|
+
|
|
24
|
+
Expressions add `construct` nodes with a constructor ID and argument expressions,
|
|
25
|
+
and `match` nodes with a scrutinee and cases. Each case has a constructor ID,
|
|
26
|
+
ordered typed binders, and a body. Its binders are local to that case. Structural
|
|
27
|
+
expressions are represented by these nodes, not by scalar `constant` nodes.
|
|
28
|
+
Exhaustive API visitors must handle the added expression variants even though
|
|
29
|
+
the protocol continues to use schema version 3.
|
|
30
|
+
|
|
31
|
+
Successful results add `definitions: Definition[]`. Each entry contains `owner`,
|
|
32
|
+
the resolved declaration `id`, typed `arguments: Binder[]`, and a typed `body`.
|
|
33
|
+
The matching entry in `units[].declarations` supplies the signature and origin.
|
|
34
|
+
Calls retain their declaration ID; consumers can join against `definitions` to
|
|
35
|
+
distinguish checked bodies from external adapters. An empty array means the
|
|
36
|
+
program has no definitions.
|
|
37
|
+
|
|
38
|
+
The native frontend checks these bodies for typing, exhaustive matching,
|
|
39
|
+
structural termination, and potentially failing operations. Native reference
|
|
40
|
+
execution and source emission for Rust, Java, Kotlin, Python, JavaScript,
|
|
41
|
+
TypeScript, Go, and Haskell are implemented, including generic definitions
|
|
42
|
+
specialized to concrete signatures. The API exposes concrete instances with
|
|
43
|
+
generated names and resolved IDs, not unspecialized templates. Calls sharing a
|
|
44
|
+
signature reuse an instance; unused templates emit none. Consumers should join
|
|
45
|
+
by ID rather than parse generated names. Refinement-bearing definitions are
|
|
46
|
+
checked and emitted by both the native compiler and bundled WASM distribution.
|
|
47
|
+
Definition bodies produce generated source rather than
|
|
48
|
+
user-owned adapter stubs. Haskell native entry points take an `LS.SymbolContext`
|
|
49
|
+
and return `Either String a`; generated tests allocate a context per example or
|
|
50
|
+
property iteration so Symbol fixture identity does not escape its scope.
|
|
51
|
+
|
|
52
|
+
Refinement and contract expressions may now contain calls whose IDs resolve to
|
|
53
|
+
checked `definitions`. Validation still rejects calls to external adapters in
|
|
54
|
+
these expressions. Test planning evaluates the closed definitions when filtering
|
|
55
|
+
finite cases, boundaries, and concrete example inputs. Calls remain ordinary
|
|
56
|
+
typed call nodes; consumers need no source-level refinement interpreter.
|
|
57
|
+
|
|
58
|
+
## Laws and typed expressions
|
|
59
|
+
|
|
60
|
+
Read `result.laws[i].examples` instead of `law.original.examples`. A binding is
|
|
61
|
+
now `{id, name, type, value}` rather than `[name, value]`. `value` retains the
|
|
62
|
+
lossless tagged scalar representation from schema 2. Expected results are typed
|
|
63
|
+
expressions in an assertion, for example `example.expectations[0].right.node.value`.
|
|
64
|
+
|
|
65
|
+
Every expression has `{type, origin, text, node}`. `text` is for presentation;
|
|
66
|
+
inspect `node` for semantics. Nodes explicitly distinguish constants, resolved
|
|
67
|
+
locals, declaration calls, arithmetic with capability evidence, short-circuit
|
|
68
|
+
operators, helpers, and conversions. Conversion mode `checked` means an exact
|
|
69
|
+
result must fit its adapter parameter; `explicit` records a source conversion.
|
|
70
|
+
There is no separate `typedExpressions` side table to match against source ASTs.
|
|
71
|
+
|
|
72
|
+
Assertions are the authoritative proposition tree:
|
|
73
|
+
|
|
74
|
+
- `{kind: "equal", evidence, left, right}`
|
|
75
|
+
- `{kind: "implies", guard, body}`
|
|
76
|
+
- `{kind: "all", items}`
|
|
77
|
+
|
|
78
|
+
The `left`, `right`, and `guards` compatibility projections on laws are removed.
|
|
79
|
+
Traverse the tree to preserve shared guard scope and conjunction order. Do not
|
|
80
|
+
flatten guards or turn refinement predicates into implications.
|
|
81
|
+
|
|
82
|
+
Inputs use `{id, name, type, predicates, bounds}`. IDs identify binders;
|
|
83
|
+
display names need not be globally unique. Bounds are derived generation hints
|
|
84
|
+
`{operator, value}` over preceding inputs. Predicates remain authoritative.
|
|
85
|
+
`generationPlan` and `inputRefinements` are replaced by these explicit fields.
|
|
86
|
+
Contracts expose typed argument/result binders, preconditions, and postconditions.
|
|
87
|
+
Refinement declarations expose documented parameter kinds, requirements, and a
|
|
88
|
+
printed definition; they are not serialized source ASTs.
|
|
89
|
+
|
|
90
|
+
## Types, identity, and locations
|
|
91
|
+
|
|
92
|
+
Types are discriminated views, not `tag`/`contents` encodings:
|
|
2
93
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
94
|
+
```js
|
|
95
|
+
{ kind: "constructor", name: "Int8", arguments: [] }
|
|
96
|
+
{ kind: "constructor", name: "Optional", arguments: [
|
|
97
|
+
{ kind: "type", type: { kind: "constructor", name: "Int8", arguments: [] } }
|
|
98
|
+
] }
|
|
99
|
+
```
|
|
9
100
|
|
|
10
|
-
|
|
11
|
-
|
|
101
|
+
Function types use `{kind: "function", parameter, result}`; type variables use
|
|
102
|
+
`{kind: "variable", id}`. The argument model distinguishes types, natural indices
|
|
103
|
+
(encoded as decimal strings), and index variables. This representation does not
|
|
104
|
+
make unimplemented containers or dependent families available in 0.8.
|
|
12
105
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
106
|
+
Declaration/property/binder IDs remain stable when unrelated laws are inserted.
|
|
107
|
+
Origins are either `{kind: "source", span: {start, end}}` or
|
|
108
|
+
`{kind: "generated", declaration}`. Source ranges come from parsing, including
|
|
109
|
+
expressions in reused laws and refinements. Generated nodes identify their owner
|
|
110
|
+
instead of inventing source coordinates. Positions use one-based lines/columns;
|
|
111
|
+
end positions are exclusive and can include trailing parser whitespace.
|
|
17
112
|
|
|
18
|
-
|
|
19
|
-
{ type: "UInt64", value: "18446744073709551615" }
|
|
20
|
-
```
|
|
113
|
+
## Scalar values remain lossless
|
|
21
114
|
|
|
22
115
|
| Domain | Payload after `type` |
|
|
23
116
|
| --- | --- |
|
|
24
|
-
|
|
|
117
|
+
| Integers, including logical Integer | `value`: decimal string |
|
|
25
118
|
| Bool | `value`: Boolean |
|
|
26
119
|
| Decimal | `coefficient`, `exponent`: decimal strings |
|
|
27
120
|
| Rational | `numerator`, `denominator`: decimal strings; reduced, denominator positive |
|
|
@@ -33,53 +126,79 @@ numeric value to JavaScript Number.
|
|
|
33
126
|
| Unit / Null / Undefined | No payload |
|
|
34
127
|
| Nullable / Optional | `value`: null for missing, otherwise a tagged scalar |
|
|
35
128
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
the inner type of a missing presence value.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
and
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
Requests
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
129
|
+
Do not coerce integer strings to JavaScript Number. Text uses code units or code
|
|
130
|
+
points as appropriate to its domain; raw surrogates never pass through JSON
|
|
131
|
+
strings. The enclosing type supplies the inner type of a missing presence value.
|
|
132
|
+
|
|
133
|
+
## Checking, generation, and artifacts
|
|
134
|
+
|
|
135
|
+
`check` and `expand` validate the language. `planGeneration` additionally checks
|
|
136
|
+
whether its input domains can be executed. For example, a well-typed empty finite
|
|
137
|
+
refinement can pass `check` and fail generation with an empty-domain diagnostic.
|
|
138
|
+
Exhausted refinement searches fail explicitly; rejected inputs do not count as
|
|
139
|
+
successful tests.
|
|
140
|
+
|
|
141
|
+
Requests retain `machineBits?: 32 | 64` (default 64) and partial `generation`
|
|
142
|
+
settings (`cases`, `maxAttempts`, `maxShrinks`, `exhaustiveLimit`). Rust joins the
|
|
143
|
+
seven existing targets. Artifacts retain separate `ownership` and `placement`:
|
|
144
|
+
generated runtime source belongs in source directories, adapters remain
|
|
145
|
+
user-owned, and test helpers belong in test directories. Never infer placement
|
|
146
|
+
from ownership or assume a fixed number of generated files. Continue using the
|
|
147
|
+
manifest writer to protect edited files.
|
|
148
|
+
|
|
149
|
+
All nonempty test plans now emit the portable scalar runtime. Existing Haskell
|
|
150
|
+
projects must include `text` and `bytestring` in the component that compiles
|
|
151
|
+
that source. Doctor reports the missing dependencies before generation. Build
|
|
152
|
+
files remain user-owned; new scaffolds already include these dependencies.
|
|
153
|
+
|
|
154
|
+
## Formatting requests and adapter references (0.9)
|
|
155
|
+
|
|
156
|
+
`GenerationRequest` accepts `minify?: boolean`, defaulting to `false`. The CLI
|
|
157
|
+
passes an explicit `--minify` from `generate` and `examples`. `init --minify`
|
|
158
|
+
also compacts newly created scaffolds and the configuration JSON; the choice is
|
|
159
|
+
not saved as a project setting. Existing project build files stay user-owned. Source/test placement is independent of
|
|
160
|
+
formatting. Compact rendering preserves mandatory newlines, indentation, token
|
|
161
|
+
separators, comments, and literal contents.
|
|
162
|
+
|
|
163
|
+
User-owned artifacts may include `adapterReference`, the compiler's canonical
|
|
164
|
+
readable scaffold. It is comparison data, not the user's implementation and not
|
|
165
|
+
an additional file to write. Manifest writers should hash this reference when
|
|
166
|
+
present, falling back to `content` for older producers. Continue hashing actual
|
|
167
|
+
`content` for generated-file ownership. This keeps a switch of formatting mode
|
|
168
|
+
from producing false adapter-update reports, while declared interface changes
|
|
169
|
+
still request review. Never normalize, overwrite, or hash user implementations
|
|
170
|
+
as the required adapter interface. Existing version-1 manifests remain readable.
|
|
171
|
+
|
|
172
|
+
Native and bundled WASM requests share this formatting behavior. CLI scaffolds,
|
|
173
|
+
generated sources, and tests preserve the same ownership rules in both modes.
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
Scoped List payload predicates use the schema-3 expression node
|
|
177
|
+
`{kind: "allElements", value: Expr, binder: Binder, predicate: Expr}`. The binder
|
|
178
|
+
is local to `predicate`; `value` is evaluated in the surrounding scope. The
|
|
179
|
+
predicate and result have type Bool. Visitors must handle this node alongside
|
|
180
|
+
`match`, including empty-list truth and short-circuit evaluation.
|
|
181
|
+
|
|
182
|
+
The Core also defines a scoped recursive payload operation:
|
|
183
|
+
`{kind: "allPayloads", value: Expr, predicates: PayloadPredicate[]}`, where each
|
|
184
|
+
`PayloadPredicate` contains a `binder: Binder` and `predicate: Expr`. Entries
|
|
185
|
+
correspond, in order, to the root data type's type arguments. Each binder has
|
|
186
|
+
that argument's type and is local only to its own predicate; sibling predicates
|
|
187
|
+
cannot refer to it. The scrutinee is evaluated in the surrounding scope, and
|
|
188
|
+
both each predicate and the whole operation have type Bool.
|
|
189
|
+
|
|
190
|
+
Traversal follows stored parameter occurrences through recursive declarations,
|
|
191
|
+
including nested containers and changing type arguments. It does not constrain
|
|
192
|
+
unrelated fixed fields that happen to have the same concrete type. Empty and
|
|
193
|
+
phantom occurrences are vacuously true; rejection short-circuits traversal.
|
|
194
|
+
|
|
195
|
+
Source named-payload refinements now elaborate to this discriminator, including
|
|
196
|
+
recursive applications. API consumers should handle it in checked Core views.
|
|
197
|
+
All eight Core emitters support this operation in definitions, properties and
|
|
198
|
+
constructor predicates.
|
|
199
|
+
The internal surface predicate node is type checked and lowered through
|
|
200
|
+
specialization and template proofs into this same Core operation. Internal
|
|
201
|
+
definition and constructor contracts support recursive payload proof facts. Constructor predicates are audited in order, and callback
|
|
202
|
+
matches/constructions participate in the constructor dependency-cycle check.
|
|
203
|
+
The TypeScript declarations now include both
|
|
204
|
+
`allElements` and `allPayloads`; exhaustive visitors should handle both.
|
package/GO.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Native Go data
|
|
2
|
+
|
|
3
|
+
LawSpec 0.9 generates native named Go types for parameterized products and sums.
|
|
4
|
+
The compiler consumes checked Core declarations, plans names across all units,
|
|
5
|
+
and emits sealed interfaces plus named variant structs, following Go+'s enum
|
|
6
|
+
lowering approach.
|
|
7
|
+
|
|
8
|
+
For example:
|
|
9
|
+
|
|
10
|
+
```lawspec
|
|
11
|
+
unit example.trees
|
|
12
|
+
|
|
13
|
+
type Tree (a :: Type) is
|
|
14
|
+
Leaf value :: a
|
|
15
|
+
Branch children :: List (Tree a)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
echo :: Tree Int8 -> Tree Int8
|
|
19
|
+
|
|
20
|
+
law `echo preserves the tree` is
|
|
21
|
+
definition is `for all` (x :: Tree Int8) . echo x = x end
|
|
22
|
+
end
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The adapter receives `Tree[int8]`. Its native variants are `TreeLeaf[int8]`,
|
|
26
|
+
with an exported `Value` field, and `TreeBranch[int8]`, with an exported
|
|
27
|
+
`Children []Tree[int8]` field. The interface's private marker method includes
|
|
28
|
+
the type parameters, so even a nullary or phantom variant retains its generic
|
|
29
|
+
identity. Go rejects a `TreeLeaf[bool]` where `Tree[int8]` is required.
|
|
30
|
+
|
|
31
|
+
A correct identity adapter is:
|
|
32
|
+
|
|
33
|
+
```go
|
|
34
|
+
func Echo(value0 Tree[int8]) Tree[int8] {
|
|
35
|
+
return value0
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Adapters remain user-owned. Generated files belong to the same unit package;
|
|
40
|
+
custom Go layouts keep source and tests in a shared directory tree. Duplicate
|
|
41
|
+
type names across units receive qualified generated names. The compiler rejects
|
|
42
|
+
adapter names that collide with generated native declarations or runtime names.
|
|
43
|
+
|
|
44
|
+
## Containers and presence
|
|
45
|
+
|
|
46
|
+
- `List a` uses `[]T`. Nil and empty slices represent the same empty list.
|
|
47
|
+
- `Maybe a` uses `LawSpecMaybe[T]`, constructed with `LawSpecNothing[T]()` or
|
|
48
|
+
`LawSpecJust(value)`. `Value()` returns the payload and its presence flag.
|
|
49
|
+
- `Either a b` uses `LawSpecEither[L, R]`, constructed with
|
|
50
|
+
`LawSpecLeft[L, R](value)` or `LawSpecRight[L, R](value)`. Its zero value is
|
|
51
|
+
invalid and checked conversion rejects it.
|
|
52
|
+
- `Nullable a` and `Optional a` use distinct generic support structs with
|
|
53
|
+
`Present` and `Value` fields. These tags preserve nested absence states.
|
|
54
|
+
|
|
55
|
+
Native scalar fields retain their domains: UInt64 uses `uint64`, arbitrary
|
|
56
|
+
integers use `*LawSpecBigInt`, rational values use `*LawSpecRational`, and raw
|
|
57
|
+
UTF-16 text uses `[]uint16`. Text uses a valid UTF-8 Go string; arbitrary bytes
|
|
58
|
+
use `[]byte`. Unit, Null, and Undefined have distinct named support types when
|
|
59
|
+
stored inside data. Native void adapter results continue to normalize to Unit.
|
|
60
|
+
|
|
61
|
+
## Total definitions
|
|
62
|
+
|
|
63
|
+
Checked source definitions emit native methods on `LawSpecDefinitions` in the
|
|
64
|
+
unit's package:
|
|
65
|
+
|
|
66
|
+
```lawspec
|
|
67
|
+
unit example.total
|
|
68
|
+
|
|
69
|
+
definition increment (x :: Int8) :: BigInt is x + 1 end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```go
|
|
73
|
+
symbols := map[string]*LawSpecSymbol{}
|
|
74
|
+
result := LawSpecDefinitions.Increment(symbols, 127) // big integer 128
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The method accepts `int8` and returns `*LawSpecBigInt`. Lists, products, sums, and
|
|
78
|
+
nested presence retain their native parameterized types. A dedicated method
|
|
79
|
+
namespace allows a definition named `architecture` to coexist with the native
|
|
80
|
+
`Architecture` data type. Ordinary adapter functions retain their existing names.
|
|
81
|
+
|
|
82
|
+
Reuse the Symbol context within one example. Checked codecs copy and validate
|
|
83
|
+
native arguments and results; failures panic with the resolved definition name.
|
|
84
|
+
Machine-sized native bindings check the architecture across the complete type,
|
|
85
|
+
including alternatives not selected by the current value. Logical evaluation
|
|
86
|
+
still follows the explicitly selected machine profile.
|
|
87
|
+
|
|
88
|
+
`lawspec_definitions.go` belongs in source directories and does not import Rapid.
|
|
89
|
+
It contains the unit's native entry points and checked logical bodies. Properties
|
|
90
|
+
call the bodies directly; definitions never become user-owned adapter stubs.
|
|
91
|
+
Definitions and properties share typed expression rendering, preserving lazy
|
|
92
|
+
branches, short-circuit guards, single evaluation of match inputs, exact
|
|
93
|
+
arithmetic, and structural equality.
|
|
94
|
+
|
|
95
|
+
The frontend checks structural termination and potentially failing operations.
|
|
96
|
+
Generic definitions specialize to concrete uses. Refined signatures become
|
|
97
|
+
checked contracts, and refinement predicates may call checked definitions.
|
|
98
|
+
|
|
99
|
+
`tools/go-definitions-integration.mjs` checks source-only packages, four rejected
|
|
100
|
+
native type mismatches, both profiles and architecture diagnostics, recursive
|
|
101
|
+
properties, incorrect adapters, custom layouts, compact execution, and
|
|
102
|
+
regeneration protection. Readable definition source matches `gofmt` exactly.
|
|
103
|
+
Compact Go documents retain tabs rather than expanding them into spaces.
|
|
104
|
+
|
|
105
|
+
## Checked bridges and tests
|
|
106
|
+
|
|
107
|
+
Generated codecs validate both conversion directions and copy mutable payloads.
|
|
108
|
+
An adapter cannot mutate a list, byte slice, or big integer in a test fixture
|
|
109
|
+
through an input alias. Unexpected or nil native variants, invalid scalar
|
|
110
|
+
representations, and cyclic values fail with type/field context. Shared acyclic
|
|
111
|
+
subtrees remain valid. Native machine-sized fields require the selected
|
|
112
|
+
`machineBits` profile to match the Go architecture.
|
|
113
|
+
|
|
114
|
+
Generated equality follows LawSpec semantics, including componentwise floating
|
|
115
|
+
comparison and Symbol identity. It does not substitute Go pointer identity or
|
|
116
|
+
reflection-based equality for structural equality.
|
|
117
|
+
|
|
118
|
+
Rapid generation composes native generators and shrinkers. A structural node
|
|
119
|
+
budget bounds recursive values, reserves each product field's minimum cost,
|
|
120
|
+
and permits list lengths supported by the remaining budget. Empty domains,
|
|
121
|
+
nullary constructors, and nested absence states are handled explicitly.
|
|
122
|
+
|
|
123
|
+
The reusable source files (`lawspec_runtime.go`, `lawspec_schema.go`,
|
|
124
|
+
`lawspec_codecs.go`, and generated data/schema/codec declarations) do not import
|
|
125
|
+
Rapid. Framework-specific generation lives in
|
|
126
|
+
`lawspec_data_strategies_test.go`; generated laws live in `lawspec_test.go`.
|
|
127
|
+
|
|
128
|
+
Native declarations, schema descriptions, codecs, runtime support, and generated
|
|
129
|
+
tests use structured formatting that matches `gofmt`. The bundled examples and
|
|
130
|
+
total-definition fixture are checked against `gofmt` under both machine profiles.
|
|
131
|
+
No external formatter is needed when generating code.
|
|
132
|
+
|
|
133
|
+
The compiler and CLI accept explicit `--minify` for generated output.
|
|
134
|
+
Readable output remains the default; formatting changes preserve adapter ownership
|
|
135
|
+
and edited-file protection. The bundled WASM uses the same layout.
|
|
136
|
+
|
|
137
|
+
Internal typed Core definitions with attached contracts are proved before Go
|
|
138
|
+
emission. Generated logical entry points validate arguments, check preconditions
|
|
139
|
+
in order, validate the result, and check postconditions; native wrappers share
|
|
140
|
+
these checks. The standalone contract fixture exercises both machine profiles
|
|
141
|
+
and formatting modes, including exact division, checked narrowing, nested calls,
|
|
142
|
+
and rejection of deliberately corrupted results. Readable contract bodies match
|
|
143
|
+
`gofmt`. Refined source definition signatures now produce these contracts through
|
|
144
|
+
template proof and specialization.
|
package/HASKELL.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Native Haskell data
|
|
2
|
+
|
|
3
|
+
LawSpec 0.9 emits ordinary parameterized algebraic data types from checked Core
|
|
4
|
+
products and sums. Constructors and record selectors have stable names planned
|
|
5
|
+
across all units. For example:
|
|
6
|
+
|
|
7
|
+
```lawspec
|
|
8
|
+
unit example.trees
|
|
9
|
+
|
|
10
|
+
type Tree (a :: Type) is
|
|
11
|
+
Leaf value :: a
|
|
12
|
+
Branch children :: List (Tree a)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
echo :: Tree Int8 -> Tree Int8
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The generated `LawSpecData` module supplies `Tree a`, `TreeLeaf`, and
|
|
19
|
+
`TreeBranch`. An adapter can implement identity directly:
|
|
20
|
+
|
|
21
|
+
```haskell
|
|
22
|
+
echo :: Data.Tree I.Int8 -> Data.Tree I.Int8
|
|
23
|
+
echo value = value
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The generated adapter imports `LawSpecData` as `Data` and `Data.Int` as `I`.
|
|
27
|
+
Adapters remain user-owned. Recursive and mutually recursive fields use native
|
|
28
|
+
Haskell recursion; phantom parameters remain in signatures. Empty data types
|
|
29
|
+
have no constructors. They can appear in inhabited containers such as
|
|
30
|
+
`Maybe Empty`, but cannot supply a standalone generated argument.
|
|
31
|
+
|
|
32
|
+
## Containers and scalar representations
|
|
33
|
+
|
|
34
|
+
| LawSpec | Haskell |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| `List a` | `[a]` |
|
|
37
|
+
| `Text` | `Data.Text.Text` |
|
|
38
|
+
| `List Char` | `[Char]`, the linked-list representation |
|
|
39
|
+
| `Maybe a` | `Maybe a`, with `Nothing` and `Just` |
|
|
40
|
+
| `Either a b` | `Either a b`, with `Left` and `Right` |
|
|
41
|
+
| `Nullable a` | `LS.Nullable a`, with `NullValue` and `NullableValue` |
|
|
42
|
+
| `Optional a` | `LS.Optional a`, with `UndefinedValue` and `OptionalValue` |
|
|
43
|
+
| `BigInt`, `BigUInt`, `Integer` | `Integer`, with domain checks |
|
|
44
|
+
| `Decimal` | `LS.Decimal`, wrapping an exact finite base-ten `Rational` |
|
|
45
|
+
| `Rational` | `Rational` |
|
|
46
|
+
| `Complex64`, `Complex128` | `Complex Float`, `Complex Double` |
|
|
47
|
+
| `CodePoint` | `Char`, including surrogate code points |
|
|
48
|
+
| `CodeUnit16` | `Word16` |
|
|
49
|
+
| `CodePointText`, `Utf16Text` | `LS.CodePointText [Char]`, `LS.Utf16Text [Word16]` |
|
|
50
|
+
| `Bytes` | `ByteString` |
|
|
51
|
+
| `Symbol` | `LS.Symbol`, with scoped fixture identities |
|
|
52
|
+
| `Unit` | `()` |
|
|
53
|
+
| `Null`, `Undefined` | `LS.Null`, `LS.Undefined` |
|
|
54
|
+
|
|
55
|
+
`Char` and `Text` exclude surrogate code points. Checked codecs reject invalid
|
|
56
|
+
native values rather than replacing characters. `Symbol` equality uses its
|
|
57
|
+
identity; matching descriptions alone do not establish equality. Distinct
|
|
58
|
+
presence wrappers preserve nested absence states. IEEE NaN and signed-zero
|
|
59
|
+
equality remain the same inside containers and custom types.
|
|
60
|
+
|
|
61
|
+
## Generated support and testing
|
|
62
|
+
|
|
63
|
+
`LawSpecData`, `LawSpecDataSchema`, `LawSpecDataCodecs`, `LawSpecSchema`,
|
|
64
|
+
`LawSpecCodecs`, and `LawSpecRuntime` belong in the source directory. They have no
|
|
65
|
+
property-framework dependency. Native conversion returns contextual failures for
|
|
66
|
+
invalid domains, fields, tags, or arities. Binding machine-sized native types
|
|
67
|
+
requires the selected `machineBits` profile to match the host, including fields
|
|
68
|
+
of unselected variants.
|
|
69
|
+
|
|
70
|
+
`LawSpecDataStrategies` belongs in the test directory and composes native Hedgehog
|
|
71
|
+
generators and shrinkers. Bounded recursive generation reserves every product
|
|
72
|
+
field's minimum size before distributing spare nodes. Lists have variable lengths
|
|
73
|
+
within the available budget. Shrinking uses Hedgehog's choices, integers, and
|
|
74
|
+
lists, retaining schema-valid representations.
|
|
75
|
+
|
|
76
|
+
The Haskell scaffold uses Hspec, Hedgehog, hspec-hedgehog, containers, and mtl in
|
|
77
|
+
its test component. Source and test roots can be customized independently.
|
|
78
|
+
Readable/compact declaration fixtures are covered by
|
|
79
|
+
`tools/haskell-data-integration.mjs`; main compiler output and incorrect adapters
|
|
80
|
+
are covered by `tools/haskell-data-properties.mjs`.
|
|
81
|
+
|
|
82
|
+
## Formatting
|
|
83
|
+
|
|
84
|
+
Haskell output uses an 80-column layout with spaces for indentation. Runtime
|
|
85
|
+
sources, native declarations, adapter stubs, definitions, and property files are
|
|
86
|
+
readable by default. Explicit `--minify` selects compact documents while retaining
|
|
87
|
+
the layout required by Haskell. Generation uses the same deterministic document
|
|
88
|
+
renderer in native and WASM builds; it does not invoke a downloaded formatter.
|
|
89
|
+
|
|
90
|
+
`tools/haskell-formatting-integration.mjs` checks the bundled corpus at both
|
|
91
|
+
machine widths for line length, tabs, and trailing whitespace. It compares
|
|
92
|
+
readable and compact parsed syntax using `tools/HaskellSyntaxCheck.hs`, built
|
|
93
|
+
against the installed GHC parser. The comparison discards source locations and
|
|
94
|
+
layout annotations while retaining literals, operators, and program structure.
|
|
95
|
+
Set `LAWSPEC_CORE`, `LAWSPEC_GHC`, and `LAWSPEC_HASKELL_SYNTAX_CHECK` to the
|
|
96
|
+
corresponding executables. This is a development check, not a package dependency.
|
|
97
|
+
|
|
98
|
+
## Total definitions
|
|
99
|
+
|
|
100
|
+
Checked definitions become native functions in `LawSpecDefinitions.<Unit>`:
|
|
101
|
+
|
|
102
|
+
```lawspec
|
|
103
|
+
unit example.total
|
|
104
|
+
|
|
105
|
+
definition increment (x :: Int8) :: BigInt is x + 1 end
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The generated native signature is equivalent to:
|
|
109
|
+
|
|
110
|
+
```haskell
|
|
111
|
+
increment :: LS.SymbolContext -> I.Int8 -> Either String Integer
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Create a context with `LS.newSymbolContext`, then pass it to calls belonging to
|
|
115
|
+
the same example. A repeated Symbol fixture ID has the same identity in that
|
|
116
|
+
context; separate contexts remain distinct even when IDs and descriptions match.
|
|
117
|
+
Codecs preserve the identity of already-scoped native Symbols. Creating the
|
|
118
|
+
context uses IO; evaluating a definition is pure.
|
|
119
|
+
|
|
120
|
+
Native entry points check arguments and results and return contextual `Left`
|
|
121
|
+
diagnostics for invalid native representations. Native machine-sized bindings
|
|
122
|
+
check the whole type, including unselected constructors, against `machineBits`.
|
|
123
|
+
`LawSpecDefinitionBodies` contains the shared logical implementation, which uses
|
|
124
|
+
the configured machine profile independently of the host architecture.
|
|
125
|
+
|
|
126
|
+
Definitions do not create adapter stubs. Their source modules and scalar/schema
|
|
127
|
+
support have no Hspec or Hedgehog dependency. Generated tests allocate a context
|
|
128
|
+
per example, boundary, or property iteration. Properties and definitions share a
|
|
129
|
+
typed expression renderer with lazy guards, exhaustive matching, structural
|
|
130
|
+
equality, checked conversions, and exact integer promotion.
|
|
131
|
+
|
|
132
|
+
`tools/haskell-definitions-integration.mjs` exercises native and property calls,
|
|
133
|
+
both machine profiles, readable/compact definitions, complete minified projects,
|
|
134
|
+
custom source/test roots,
|
|
135
|
+
native type errors, incorrect adapters, generic specialization, and regeneration
|
|
136
|
+
protection. Refined definition signatures are checked before emission and
|
|
137
|
+
enforced at native entry points.
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
Hspec property files now use structured documents for helpers, examples,
|
|
141
|
+
boundaries/finite cases, native Hedgehog strategies, dependent refinement domains,
|
|
142
|
+
guarded assertions and contract wrappers. Adapter bridges compose checked codecs
|
|
143
|
+
and force argument values before calling native code. Contracts retain lazy
|
|
144
|
+
precondition checks and force the result before checking postconditions. Fresh
|
|
145
|
+
Symbol contexts cover refined properties as well as ordinary native strategies.
|
|
146
|
+
Scalar literals and diagnostic strings now use document-level encoding. Long
|
|
147
|
+
strings concatenate independently escaped chunks; large integers parse exact
|
|
148
|
+
decimal strings with their required Integer type.
|
|
149
|
+
|
|
150
|
+
`tools/haskell-data-properties.mjs` accepts `LAWSPEC_MINIFY=1` and
|
|
151
|
+
`LAWSPEC_MACHINE_BITS=32,64`. Its scalar scenario includes bundled examples and
|
|
152
|
+
shared arithmetic conformance vectors; its refinements scenario includes four
|
|
153
|
+
faulty adapters. Native machine-sized adapter bindings require a matching host
|
|
154
|
+
architecture; the scalar scenario defaults to 64 bits.
|
|
155
|
+
|
|
156
|
+
`tools/haskell-message-integration.mjs` uses native code-point strings and integer
|
|
157
|
+
constants independent of the emitter to check Unicode, escapes, whitespace,
|
|
158
|
+
large signed integers and diagnostic prefixes in readable and compact modes.
|
|
159
|
+
Its executable fixture lines fit within 80 columns. Long metadata comments wrap
|
|
160
|
+
to the output width.
|
|
161
|
+
|
|
162
|
+
Internal typed Core definitions with attached contracts are proved before Haskell
|
|
163
|
+
emission. Generated source checks preconditions after argument validation and
|
|
164
|
+
postconditions after result validation, without property-framework dependencies.
|
|
165
|
+
Checks sequence through Either, so a rejected precondition prevents later
|
|
166
|
+
predicates and the body from running. Results are forced and validated before
|
|
167
|
+
postconditions. Readable contract fixtures fit within 80 columns.
|
|
168
|
+
The shared native fixture passes both machine profiles and formatting modes,
|
|
169
|
+
including exact division, narrowing, nested calls, direct logical entry checks,
|
|
170
|
+
and rejection of corrupted results. Refined source definition signatures now
|
|
171
|
+
produce these contracts through template proof and specialization.
|