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/LANGUAGE.md
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
# LawSpec language and compiler boundary (0.9)
|
|
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
|
+
## Lists and algebraic containers
|
|
8
|
+
|
|
9
|
+
`List a` is an ordered, finite sequence of values of one type. Lists nest and
|
|
10
|
+
retain duplicates. Literals use brackets; their element type comes from the
|
|
11
|
+
surrounding signature, quantifier, or annotation. An unconstrained empty list
|
|
12
|
+
needs an annotation such as `([] :: List Int8)`.
|
|
13
|
+
|
|
14
|
+
```lawspec
|
|
15
|
+
unit guide.lists
|
|
16
|
+
|
|
17
|
+
reverse :: List Int32 -> List Int32
|
|
18
|
+
|
|
19
|
+
law `reverse preserves length` is
|
|
20
|
+
definition is
|
|
21
|
+
`for all` (xs :: List Int32) .
|
|
22
|
+
prelude.length (reverse xs) = prelude.length xs
|
|
23
|
+
end
|
|
24
|
+
example `duplicates count separately` is
|
|
25
|
+
xs = [3, 1, 3]
|
|
26
|
+
expect prelude.length xs = 3
|
|
27
|
+
expect reverse xs = [3, 1, 3]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`prelude.length` returns an exact integer. List equality compares corresponding
|
|
33
|
+
values in order and requires equal lengths. It preserves scalar equality rules:
|
|
34
|
+
NaN still differs from itself, signed zeros compare equal, and Symbols compare
|
|
35
|
+
by identity. Generic list equality requires `Eq a`.
|
|
36
|
+
|
|
37
|
+
`Maybe a` has constructors `Nothing` and `Just value`. `Either a b` has
|
|
38
|
+
constructors `Left value` and `Right value`. These are algebraic sums, separate
|
|
39
|
+
from the interoperability types `Nullable a` and `Optional a`.
|
|
40
|
+
|
|
41
|
+
```lawspec
|
|
42
|
+
unit guide.presence
|
|
43
|
+
|
|
44
|
+
echo :: Maybe (Either Int8 Bool) -> Maybe (Either Int8 Bool)
|
|
45
|
+
|
|
46
|
+
law `preserve every alternative` is
|
|
47
|
+
definition is `for all` (x :: Maybe (Either Int8 Bool)) . echo x = x end
|
|
48
|
+
example `absent` is x = Nothing expect echo x = Nothing end
|
|
49
|
+
example `left integer` is x = Just (Left 127) expect echo x = Just (Left 127) end
|
|
50
|
+
example `right boolean` is x = Just (Right false) expect echo x = Just (Right false) end
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
For `Maybe (Maybe Bool)`, `Nothing`, `Just Nothing`, and `Just (Just false)`
|
|
55
|
+
remain distinct. Nesting a constructor application as an argument generally
|
|
56
|
+
requires parentheses, as in `Just (Left 127)`.
|
|
57
|
+
|
|
58
|
+
The [collections example](examples/specs/collections.lawspec) combines reverse
|
|
59
|
+
involution, sorting idempotence, sortedness, length, and permutation preservation.
|
|
60
|
+
`sorted` and `permutation` in that example are adapters supplied by the user;
|
|
61
|
+
they are not built-in helpers. Sortedness and length alone cannot establish that
|
|
62
|
+
a sorting adapter retained the original elements.
|
|
63
|
+
|
|
64
|
+
## Products, sums, and pattern matching
|
|
65
|
+
|
|
66
|
+
A `type` declaration names its constructors and each constructor's fields. One
|
|
67
|
+
constructor describes a product; multiple constructors describe a sum. Type
|
|
68
|
+
parameters are declared explicitly with `:: Type`.
|
|
69
|
+
|
|
70
|
+
```lawspec
|
|
71
|
+
unit guide.trees
|
|
72
|
+
|
|
73
|
+
type Pair (a :: Type) (b :: Type) is
|
|
74
|
+
Pair
|
|
75
|
+
first :: a
|
|
76
|
+
second :: b
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
type Tree (a :: Type) is
|
|
80
|
+
Leaf value :: a
|
|
81
|
+
Branch children :: List (Tree a)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
definition rebuild (tree :: Tree a) :: Tree a is
|
|
85
|
+
match tree with
|
|
86
|
+
| Leaf value -> Leaf value
|
|
87
|
+
| Branch children -> Branch children
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
law `preserve the constructor and its fields` is
|
|
92
|
+
definition is `for all` (tree :: Tree Int8) . rebuild tree = tree end
|
|
93
|
+
example `nested branches` is
|
|
94
|
+
tree = Branch [Leaf 127, Branch [], Leaf -128]
|
|
95
|
+
expect rebuild tree = Branch [Leaf 127, Branch [], Leaf -128]
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Constructors receive fields in declaration order. A match evaluates its
|
|
101
|
+
scrutinee once; each branch binds that constructor's fields in the same order.
|
|
102
|
+
Bindings are scoped to the branch. Matching must be exhaustive and cannot repeat
|
|
103
|
+
a constructor. Lists match with `Nil` and `Cons head tail`; Maybe and Either use
|
|
104
|
+
their constructors above. Recursive declarations must be strictly positive.
|
|
105
|
+
General indexed constructors and GADT result signatures are not supported.
|
|
106
|
+
|
|
107
|
+
Equality is structural and type-directed, including named fields and nested
|
|
108
|
+
containers. Native public declarations retain their names and type parameters;
|
|
109
|
+
schemas and checked codecs support them at runtime. They are not replacements
|
|
110
|
+
for the public data types. See the target guides for native representations:
|
|
111
|
+
[Java](JAVA.md), [Python](PYTHON.md), [JavaScript/TypeScript](WEB.md),
|
|
112
|
+
[Go](GO.md), [Haskell](HASKELL.md), [Kotlin](KOTLIN.md), and [Rust](RUST.md).
|
|
113
|
+
In Haskell, `Text` remains `Data.Text.Text`, while `List Char` becomes the linked
|
|
114
|
+
list `[Char]`. These are distinct LawSpec types even when they contain the same
|
|
115
|
+
characters.
|
|
116
|
+
|
|
117
|
+
Generators compose the target framework's generators and shrinkers. Recursive
|
|
118
|
+
values have a structural size budget; each constructor reserves enough budget
|
|
119
|
+
for its fields before distributing the remainder. Boundaries include empty and
|
|
120
|
+
singleton lists and constructor-specific values. Small finite domains are
|
|
121
|
+
enumerated. An empty type cannot supply a generated argument, but containers
|
|
122
|
+
such as `List Empty` and `Maybe Empty` can still be inhabited. An empty or
|
|
123
|
+
unreachable input domain never makes a property pass vacuously.
|
|
124
|
+
|
|
125
|
+
Whole-value refinements can inspect products and sums using exhaustive matches.
|
|
126
|
+
List element refinements, Maybe/Either payload refinements, and refined arguments
|
|
127
|
+
of recursive and nonrecursive named type constructors are supported; see
|
|
128
|
+
[refinements](REFINEMENTS.md#named-data-payloads). Direct refinements on named
|
|
129
|
+
constructor fields use checked constructor contracts. Recursive payload predicates
|
|
130
|
+
follow stored type arguments and preserve outer dependent inputs.
|
|
131
|
+
|
|
132
|
+
## Total definitions
|
|
133
|
+
|
|
134
|
+
A unit can supply an implementation as a checked total definition:
|
|
135
|
+
|
|
136
|
+
```lawspec
|
|
137
|
+
definition increment (x :: Int8) :: BigInt
|
|
138
|
+
requires Integer Int8
|
|
139
|
+
is
|
|
140
|
+
x + 1
|
|
141
|
+
end
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Parameters and the result have explicit types. The optional `requires` clause
|
|
145
|
+
uses the same `Eq`, `Integer`, `Ordered`, and `Bounded` capabilities as laws.
|
|
146
|
+
Requirements are checked even when the definition is unused. Bodies must have
|
|
147
|
+
exhaustive matches, proven structural descent for recursive calls, and guards
|
|
148
|
+
for operations that could otherwise fail. Calls may use other checked
|
|
149
|
+
definitions; external adapters cannot establish a definition's totality.
|
|
150
|
+
|
|
151
|
+
For exact arithmetic, the totality checker can combine linear bounds and Boolean
|
|
152
|
+
guards. For example, `x >= 0 && 1 / (x + 1) > 0` is safe for integer `x`: the
|
|
153
|
+
right side runs only when its denominator is positive. Proof arithmetic uses
|
|
154
|
+
arbitrary exact fractions, preserves strict boundaries, and has a bounded work
|
|
155
|
+
budget. An unproved obligation is rejected; IEEE expressions never acquire
|
|
156
|
+
rational identities such as `x - x = 0` from this checker.
|
|
157
|
+
|
|
158
|
+
Primitive integer ranges are available to the checker automatically, including
|
|
159
|
+
the selected machine width and BigUInt's nonnegative domain. Integer comparisons
|
|
160
|
+
retain integrality: for Int8 `x`, `x < 127 && prelude.Int8 (x + 1) > x` safely
|
|
161
|
+
narrows only on the guarded branch. The unguarded conversion is rejected because
|
|
162
|
+
`127 + 1` is outside Int8. Range bounds alone do not prove that a Rational or
|
|
163
|
+
Decimal input has no fractional part.
|
|
164
|
+
Pattern matching retains the primitive ranges of extracted fields within that
|
|
165
|
+
branch. For example, an Int8 list head still makes `head + 129` strictly positive;
|
|
166
|
+
that fact cannot be reused for an Int64 field in another constructor branch.
|
|
167
|
+
|
|
168
|
+
Definitions produce reusable generated source with checked native entry points
|
|
169
|
+
on all eight targets. They do not produce user-owned adapter stubs. Integer
|
|
170
|
+
arithmetic preserves the mathematical result; the example above returns `128`
|
|
171
|
+
for the largest Int8 input. See [the total-function example](examples/specs/total_functions.lawspec)
|
|
172
|
+
for recursive list counting, structural equality, and explicit expected values.
|
|
173
|
+
|
|
174
|
+
Definitions can quantify type variables implicitly through their signatures:
|
|
175
|
+
|
|
176
|
+
```lawspec
|
|
177
|
+
definition same (x :: a) (y :: a) :: Bool requires Eq a is x == y end
|
|
178
|
+
definition count (xs :: List a) :: BigInt is
|
|
179
|
+
match xs with
|
|
180
|
+
| Nil -> 0
|
|
181
|
+
| Cons head tail -> 1 + count tail
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Each template is checked for typing, capabilities, termination, and definedness,
|
|
187
|
+
including unused templates. Calls specialize it to concrete argument and result
|
|
188
|
+
types before Core elaboration. Different calls can use different types; recursive
|
|
189
|
+
self-calls must retain the same types. Ambiguous calls require an annotation,
|
|
190
|
+
such as `count ([] :: List Int8)` to specify an empty list's element type.
|
|
191
|
+
Unused templates emit no instances.
|
|
192
|
+
Checked definitions may also appear in refinement predicates and adapter
|
|
193
|
+
preconditions or postconditions. Their closed call graphs contain only other
|
|
194
|
+
checked definitions; adapter calls remain forbidden in predicates. The compiler
|
|
195
|
+
uses the same concrete Core definitions to check example domains and plan finite
|
|
196
|
+
cases and boundaries that generated tests use at runtime.
|
|
197
|
+
Definition parameters and results may carry refinements. The compiler proves
|
|
198
|
+
body definedness and result claims under ordered input preconditions, checks
|
|
199
|
+
callee preconditions, and preserves the contracts through specialization. Native
|
|
200
|
+
entry points enforce the same contracts. See [refined definitions](REFINEMENTS.md#refined-definitions).
|
|
201
|
+
|
|
202
|
+
## Source and declarations
|
|
203
|
+
|
|
204
|
+
A source has one named `unit`, function signatures, data declarations, checked
|
|
205
|
+
definitions, reusable refinements, and laws. Qualified unit names determine target module/package paths. Function
|
|
206
|
+
signatures are curried: `a -> b -> c` takes two inputs and returns `c`. Parentheses
|
|
207
|
+
group types and expressions. Comments start with `--` and run to the line end.
|
|
208
|
+
|
|
209
|
+
The following grammar summarizes the main forms; the parser and executable
|
|
210
|
+
compiler tests specify lexical details:
|
|
211
|
+
|
|
212
|
+
```text
|
|
213
|
+
source = "unit" qualified-name declaration*
|
|
214
|
+
declaration = name "::" type | law | refinement | data-type | function
|
|
215
|
+
data-type = "type" name ("(" name "::" "Type" ")")*
|
|
216
|
+
"is" constructor* "end"
|
|
217
|
+
constructor = name (name "::" type)*
|
|
218
|
+
function = "definition" name parameter* "::" type requirements?
|
|
219
|
+
"is" expression "end"
|
|
220
|
+
type = type-atom ["->" type]
|
|
221
|
+
type-atom = primitive | type-variable | "(" type ")"
|
|
222
|
+
| ("Nullable" | "Optional" | "List" | "Maybe") type-atom
|
|
223
|
+
| "Either" type-atom type-atom
|
|
224
|
+
| data-type-name type-atom*
|
|
225
|
+
| refinement-name argument*
|
|
226
|
+
| "(" name "::" type ["where" expression] ")"
|
|
227
|
+
refinement = "refinement" name parameter* requirements?
|
|
228
|
+
"is" type "end"
|
|
229
|
+
parameter = "(" name "::" type ["where" expression] ")"
|
|
230
|
+
requirements = "requires" (capability type)+
|
|
231
|
+
capability = "Eq" | "Integer" | "Ordered" | "Bounded"
|
|
232
|
+
law = "law" quoted-name parameter* requirements? "is"
|
|
233
|
+
"definition" "is" proposition "end"
|
|
234
|
+
description? rationale? example* references? "end"
|
|
235
|
+
proposition = "`for all`" parameter+ "." proposition
|
|
236
|
+
| expression "=" expression
|
|
237
|
+
| expression "implies" proposition
|
|
238
|
+
| proposition "and" proposition
|
|
239
|
+
| quoted-name expression* | expression
|
|
240
|
+
expression = ... | "[" [expression ("," expression)*] "]"
|
|
241
|
+
| constructor-name expression*
|
|
242
|
+
| "match" expression "with"
|
|
243
|
+
("|" constructor-name name* "->" expression)+ "end"
|
|
244
|
+
example = "example" quoted-name "is" (name "=" literal)+
|
|
245
|
+
("expect" expression "=" literal)+ "end"
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Law names use backticks. Text/metadata use double quotes with escapes. Named
|
|
249
|
+
refinement declarations state their type versus value parameter kinds, including
|
|
250
|
+
forward references; the compiler validates their arity and argument kinds.
|
|
251
|
+
Lowercase type names represent variables. `a :: Type` is a type parameter, not a
|
|
252
|
+
runtime value with a generator.
|
|
253
|
+
|
|
254
|
+
A generic law is specialized when invoked with concrete adapters. Its declared
|
|
255
|
+
capabilities must justify its operations; specialization resolves those
|
|
256
|
+
requirements for the actual types. `Integer` as a capability requires an integer
|
|
257
|
+
type. `Integer` in a concrete result signature denotes a representation-independent
|
|
258
|
+
mathematical integer. See [refinements and abstract integers](REFINEMENTS.md).
|
|
259
|
+
|
|
260
|
+
## Expressions and arithmetic
|
|
261
|
+
|
|
262
|
+
Application binds most tightly. The remaining precedence, highest first, is:
|
|
263
|
+
unary `!`/`-`, composition `.`, multiplication/division, addition/subtraction,
|
|
264
|
+
comparisons (`==`, `!=`, `<`, `<=`, `>`, `>=`), `&&`, then `||`.
|
|
265
|
+
Comparisons do not chain. Arithmetic associates left; composition associates
|
|
266
|
+
right. An adjacent numeric sign remains part of an argument: `f -42` applies `f`
|
|
267
|
+
to negative 42. Write `x - 42` for subtraction.
|
|
268
|
+
|
|
269
|
+
Assertion `=` differs from Boolean `==`. `implies` guards its following
|
|
270
|
+
proposition; `and` requires both assertions. Parentheses determine the scope of a
|
|
271
|
+
shared guard. Both Boolean operators and implications short-circuit.
|
|
272
|
+
|
|
273
|
+
Literals acquire types from declared context. Unconstrained integers have type
|
|
274
|
+
`Integer`; decimal tokens have type `Decimal`. An annotation such as
|
|
275
|
+
`(127 :: Int8)` specifies context. A declared float context can type a decimal
|
|
276
|
+
token directly, but an explicitly constructed exact Decimal is not implicitly
|
|
277
|
+
converted into a float.
|
|
278
|
+
|
|
279
|
+
Integer `+`, `-`, `*`, and negation produce exact `Integer` results. Decimal
|
|
280
|
+
dominates integer/Decimal combinations; Rational dominates exact combinations
|
|
281
|
+
involving Rational. Exact `/` returns Rational. `prelude.quot` truncates integer
|
|
282
|
+
quotients toward zero; `prelude.rem` is the associated remainder. Division by
|
|
283
|
+
zero fails when evaluated. Explicit numeric conversions use `prelude.Type`.
|
|
284
|
+
Exact/inexact mixing otherwise fails type checking. IEEE arithmetic widens float
|
|
285
|
+
or complex precision as required; equality treats NaN as unequal and signed
|
|
286
|
+
zero as equal. Decimal rounding is explicit, with a scale and ties-to-even rule.
|
|
287
|
+
|
|
288
|
+
Passing a computed exact result to a bounded adapter argument performs a checked
|
|
289
|
+
conversion. It never wraps, truncates a fraction, or silently changes precision.
|
|
290
|
+
Adapter results are validated against the declared domain before use. See the
|
|
291
|
+
[primitive reference](PRIMITIVES.md) for all domains and helpers.
|
|
292
|
+
|
|
293
|
+
## Refinements and executable contracts
|
|
294
|
+
|
|
295
|
+
Refinements are pure Boolean predicates over a value and preceding binders.
|
|
296
|
+
They cannot call user adapters. Dependent inputs are generated in order;
|
|
297
|
+
shrinking preserves their predicates. Bounds such as `y > Int8.max - x` are
|
|
298
|
+
computed with exact arithmetic and can drive a dependent generator. If a chosen
|
|
299
|
+
`x` has no possible `y`, generation retries earlier inputs. Search exhaustion
|
|
300
|
+
fails explicitly rather than passing a property with no valid cases.
|
|
301
|
+
|
|
302
|
+
Contracts check preconditions, evaluate an adapter once, validate the result,
|
|
303
|
+
and then check postconditions against that same result. Predicate errors are
|
|
304
|
+
contextual failures, not rejected samples. Small finite domains are enumerated;
|
|
305
|
+
other domains use target property frameworks. `machineBits: 32 | 64` controls
|
|
306
|
+
machine-integer domains independently of the compiler host architecture. Native
|
|
307
|
+
machine-sized adapter bindings additionally verify the executing architecture.
|
|
308
|
+
|
|
309
|
+
## Compiler stages and public API
|
|
310
|
+
|
|
311
|
+
The parser retains source ranges. Resolution and inference check names, kinds,
|
|
312
|
+
capabilities, contextual literals, and generic specializations. Elaboration
|
|
313
|
+
produces typed core expressions with resolved declaration/binder IDs, explicit
|
|
314
|
+
arithmetic evidence and conversions, plus an authoritative proposition tree.
|
|
315
|
+
Refinement declarations become predicates on quantifiers and contracts; targets
|
|
316
|
+
do not interpret refinement syntax.
|
|
317
|
+
|
|
318
|
+
An independent core validator checks scopes, kinds, operand/result types,
|
|
319
|
+
capability evidence, and conversions. A pure core evaluator supports deterministic
|
|
320
|
+
domain checks and reference tests. The testing planner computes finite cases,
|
|
321
|
+
boundaries, and dependent generator requirements. All eight emitters consume
|
|
322
|
+
that plan and the core, without importing source syntax or inference.
|
|
323
|
+
|
|
324
|
+
`check`/`expand` report semantic validity. `planGeneration` additionally reports
|
|
325
|
+
execution feasibility, including empty finite domains. Source syntax errors,
|
|
326
|
+
semantic errors, core invariant failures, and generation errors have distinct
|
|
327
|
+
diagnostic codes. Runtime failures identify the law/example or adapter contract.
|
|
328
|
+
Parsed expressions retain real source ranges; synthesized expressions identify
|
|
329
|
+
the declaration that caused their creation.
|
|
330
|
+
|
|
331
|
+
API schema v3 uses separately defined wire views, with lossless tagged scalar
|
|
332
|
+
values. It does not serialize internal AST constructors. See the
|
|
333
|
+
[API migration guide](API-MIGRATION.md).
|
|
334
|
+
|
|
335
|
+
## Beyond 0.9
|
|
336
|
+
|
|
337
|
+
GADTs, indexed families, and general dependent types are planned after 0.9.
|
|
338
|
+
The Core type model distinguishes type arguments from index arguments, but that
|
|
339
|
+
representation is not a claim that arbitrary dependent programs are accepted.
|
|
340
|
+
User-defined products and sums have ordinary uniform type parameters. External
|
|
341
|
+
type bindings, custom generator bindings, and cross-unit packages are separate
|
|
342
|
+
future features.
|
|
343
|
+
|
|
344
|
+
## Generated project formatting
|
|
345
|
+
|
|
346
|
+
`lawspec init --target java` creates a readable Maven scaffold; Kotlin init
|
|
347
|
+
likewise expands Gradle blocks with two-space indentation. `--minify` explicitly
|
|
348
|
+
selects compact scaffolds and configuration JSON. `generate` and `examples`
|
|
349
|
+
accept the same flag for generated source. The mode is per invocation and does
|
|
350
|
+
not become a project default. Init preserves existing build files, and generation
|
|
351
|
+
preserves user-owned adapters regardless of formatting mode.
|
|
352
|
+
|
|
353
|
+
Generated runtime support and checked definitions belong in source directories;
|
|
354
|
+
framework-specific property helpers belong in test directories. Both follow
|
|
355
|
+
custom layout settings. Python uses PEP 8: four-space indentation, 79-column
|
|
356
|
+
code, and 72-column prose. Other targets follow Google language guidance where
|
|
357
|
+
applicable, with standard Rust formatting. Formatting is deterministic in the
|
|
358
|
+
native and WASM compilers; generation does not download or invoke a formatter.
|
|
359
|
+
|
|
360
|
+
See [the release notes](RELEASE-0.9.md) for compatibility and scope, and the target
|
|
361
|
+
guides for formatting verification and native representation details.
|
package/PRIMITIVES.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# LawSpec scalar reference (0.
|
|
1
|
+
# LawSpec scalar reference (0.9.0)
|
|
2
2
|
|
|
3
3
|
A scalar has a declared domain, checked literals, equality, property inputs, and
|
|
4
4
|
boundary fixtures. General collections, objects, pointers, and type-only constructs
|
|
@@ -142,7 +142,7 @@ portable across architectures.
|
|
|
142
142
|
The bundled `scalars.lawspec`, `scalar_catalog.lawspec`, and
|
|
143
143
|
`scalar_adapters.lawspec` contain explicit fixtures for every scalar family.
|
|
144
144
|
`tools/scalar-reference.py` produces independent Fraction-based conformance
|
|
145
|
-
vectors; `tools/scalar-integration.mjs` executes them across the
|
|
145
|
+
vectors; `tools/scalar-integration.mjs` executes them across the eight targets.
|
|
146
146
|
Set `LAWSPEC_MUTANTS=1` to also verify that incorrect adapters are detected.
|
|
147
147
|
|
|
148
148
|
|
package/PYTHON.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Python backend
|
|
2
|
+
|
|
3
|
+
Python output follows [PEP 8](https://peps.python.org/pep-0008/): four-space
|
|
4
|
+
indentation, a 79-column code target, 72-column prose comments/docstrings, and
|
|
5
|
+
two blank lines between top-level definitions. Explicit `--minify` permits
|
|
6
|
+
compact layout while preserving Python indentation and semantics.
|
|
7
|
+
`tools/python-formatting-integration.mjs` checks generated runtime, adapter,
|
|
8
|
+
definition, and test files with pycodestyle 2.14.0 at both machine widths. It
|
|
9
|
+
also compares readable and compact syntax trees, including literal contents.
|
|
10
|
+
|
|
11
|
+
Use Python 3.13 or later. Generated tests use Hypothesis; generated data classes,
|
|
12
|
+
scalar operations, and schema validation do not depend on a test framework.
|
|
13
|
+
|
|
14
|
+
## Native structural values
|
|
15
|
+
|
|
16
|
+
| LawSpec type | Adapter representation |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `List a` | `list[A]` |
|
|
19
|
+
| `Maybe a` | `lawspec_schema.Maybe[A]`, with `Nothing` and `Just` variants |
|
|
20
|
+
| `Either a b` | `lawspec_schema.Either[A, B]`, with `Left` and `Right` variants |
|
|
21
|
+
| User-defined products and sums | Named generic dataclasses in `lawspec_data` |
|
|
22
|
+
| `Nullable a`, `Optional a` | Tagged `lawspec_runtime.Presence` values |
|
|
23
|
+
|
|
24
|
+
`Nothing()` differs from `Just(Nothing())`. `Left(value)` differs from
|
|
25
|
+
`Right(value)`. These algebraic variants are separate from the interoperability
|
|
26
|
+
states represented by `Nullable`, `Optional`, `Null`, and `Undefined`.
|
|
27
|
+
|
|
28
|
+
For example, this declaration:
|
|
29
|
+
|
|
30
|
+
```lawspec
|
|
31
|
+
type Tree (a :: Type) is
|
|
32
|
+
Leaf value :: a
|
|
33
|
+
Branch children :: List (Tree a)
|
|
34
|
+
end
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
produces a generic `Tree` base and `TreeLeaf` and `TreeBranch` dataclasses. A
|
|
38
|
+
product has one variant: `Pair` with constructor `Pair` produces `PairPair`.
|
|
39
|
+
When names conflict across units or between types and variants, the compiler
|
|
40
|
+
plans distinct names using their Core identities. Use the emitted declarations
|
|
41
|
+
and adapter annotations as the authoritative names.
|
|
42
|
+
|
|
43
|
+
Native pattern matching works on the generated variants:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import lawspec_data as data
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def count_leaves(tree: data.Tree[int]) -> int:
|
|
50
|
+
match tree:
|
|
51
|
+
case data.TreeLeaf():
|
|
52
|
+
return 1
|
|
53
|
+
case data.TreeBranch(children=children):
|
|
54
|
+
return sum(count_leaves(child) for child in children)
|
|
55
|
+
raise TypeError("unknown Tree variant")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Classes are frozen and use slots. Container payloads are copied when crossing
|
|
59
|
+
adapter boundaries, so modifying a native list does not change another use of
|
|
60
|
+
the logical test input. An abstract base cannot be directly constructed; empty
|
|
61
|
+
types acquire no artificial variant.
|
|
62
|
+
|
|
63
|
+
LawSpec equality uses schema-directed comparisons, including IEEE NaN and
|
|
64
|
+
signed-zero rules and Symbol identity. Generated dataclasses do not derive
|
|
65
|
+
Python field equality, whose container shortcuts can change those rules.
|
|
66
|
+
|
|
67
|
+
## Checked boundaries
|
|
68
|
+
|
|
69
|
+
Generated calls validate input values, convert them to native classes, call the
|
|
70
|
+
adapter, and validate its result. A field extracted from a native product can
|
|
71
|
+
be returned directly from an adapter with the corresponding LawSpec result
|
|
72
|
+
type. The same representation is used for nested and standalone containers.
|
|
73
|
+
|
|
74
|
+
Diagnostics identify constructor fields and list indices. Checks distinguish
|
|
75
|
+
Bool from integers, enforce primitive ranges under the selected `machineBits`,
|
|
76
|
+
reject invalid scalar text, and preserve raw code units, code points, and bytes.
|
|
77
|
+
Preconditions and postconditions operate on validated logical values.
|
|
78
|
+
|
|
79
|
+
## Total definitions
|
|
80
|
+
|
|
81
|
+
Checked total definitions emit reusable Python functions separately from adapters:
|
|
82
|
+
|
|
83
|
+
```lawspec
|
|
84
|
+
unit example.total
|
|
85
|
+
|
|
86
|
+
definition increment (x :: Int8) :: BigInt is x + 1 end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from lawspec_definitions.example import total
|
|
91
|
+
|
|
92
|
+
symbols = {}
|
|
93
|
+
assert total.increment(symbols, 127) == 128
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The first argument is the Symbol fixture context shared within an example.
|
|
97
|
+
Public signatures preserve native container and variant annotations; checked
|
|
98
|
+
conversion enforces element types, ranges, machine profiles, and nested presence
|
|
99
|
+
states at runtime. Errors include the resolved definition name. Source functions
|
|
100
|
+
and implementation bodies have no Hypothesis or pytest dependency.
|
|
101
|
+
|
|
102
|
+
`lawspec_definition_bodies.py` holds checked logical implementations. Public
|
|
103
|
+
modules under `lawspec_definitions/` provide native entry points grouped by unit.
|
|
104
|
+
Both belong in source directories and are generated-owned. Properties invoke
|
|
105
|
+
those checked bodies directly; definitions do not create adapter stubs. Unit
|
|
106
|
+
modules cannot shadow support modules or another unit's package. Native function
|
|
107
|
+
names such as `str` do not shadow built-ins used by generated validation.
|
|
108
|
+
|
|
109
|
+
Definition bodies and Python properties share typed expression rendering. Match
|
|
110
|
+
inputs are evaluated once, branches remain lazy, guards short-circuit, and exact
|
|
111
|
+
Decimal arithmetic is independent of Python's ambient rounding context.
|
|
112
|
+
Definitions must pass structural termination and definedness checks. Generic
|
|
113
|
+
definitions specialize to concrete uses. Refined signatures become checked
|
|
114
|
+
contracts, and refinement predicates may call checked definitions.
|
|
115
|
+
|
|
116
|
+
`tools/python-definitions-integration.mjs` checks standalone source calls,
|
|
117
|
+
properties, incorrect adapters, both machine profiles, custom layouts, compact
|
|
118
|
+
execution, and regeneration protection. The bundled Python corpus passes PEP 8 checks at 79 code columns and 72 prose
|
|
119
|
+
columns. The style audit includes the standalone total-definition fixture.
|
|
120
|
+
|
|
121
|
+
## Generation and layouts
|
|
122
|
+
|
|
123
|
+
Hypothesis composes tuples, alternatives, lists, and dependent strategies.
|
|
124
|
+
Recursive generation reserves each field's minimum node budget before sharing
|
|
125
|
+
the remainder. List length and element budgets vary together. Shrinking remains
|
|
126
|
+
native to Hypothesis and preserves the schema and structural size bound.
|
|
127
|
+
Small finite domains are enumerated; empty domains do not pass vacuously.
|
|
128
|
+
|
|
129
|
+
`lawspec_data.py` and `lawspec_schema.py` belong in the configured source
|
|
130
|
+
directory. `lawspec_data_strategies.py` belongs in the test directory. Configure
|
|
131
|
+
Python's import paths for those directories when using a custom layout.
|
|
132
|
+
Adapters remain user-owned. Unit names cannot shadow generated support modules.
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
Internal checked Core definition contracts now run at native and logical entry
|
|
136
|
+
points. Emission proves the obligations first; argument validation precedes
|
|
137
|
+
ordered preconditions, and result validation precedes postconditions. Contract
|
|
138
|
+
binders map explicitly to body inputs and the checked result. Refined source
|
|
139
|
+
signatures now produce these contracts through template proof and specialization.
|
|
140
|
+
|
|
141
|
+
`tools/portable-definition-contract-integration.mjs` exercises Python, JavaScript
|
|
142
|
+
and strict TypeScript with both machine profiles and layouts, without property
|
|
143
|
+
frameworks. It also verifies that deliberately corrupted results are rejected.
|
|
144
|
+
The fixture in `test/DefinitionContractFixture.hs` is shared with the JVM checks.
|
|
145
|
+
|
|
146
|
+
## Checking Python style
|
|
147
|
+
|
|
148
|
+
Set `LAWSPEC_CORE` to the native compiler, `LAWSPEC_PYTHON` to Python 3.13 or
|
|
149
|
+
later, and `LAWSPEC_PYCODESTYLE` to the `pycodestyle.py` source from version
|
|
150
|
+
2.14.0. Then run `node tools/python-formatting-integration.mjs`. The checker is
|
|
151
|
+
a development dependency; generating and executing runtime code does not
|
|
152
|
+
require it. Optional positional arguments select individual specification files.
|