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/RUST.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Rust backend
|
|
2
|
+
|
|
3
|
+
Rust is the first backend built on LawSpec's typed core and testing plan. The
|
|
4
|
+
compiler remains implemented in Haskell. Rust generation does not parse source
|
|
5
|
+
expressions, infer their types, or expand refinement declarations.
|
|
6
|
+
|
|
7
|
+
## Project setup
|
|
8
|
+
|
|
9
|
+
Use Rust 1.85 or later, edition 2024, and Cargo. The generated project pins
|
|
10
|
+
Proptest 1.11.0, num-bigint 0.4.8, num-rational 0.4.2, num-complex 0.4.6, and
|
|
11
|
+
num-traits 0.2.19.
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
lawspec init --target rust
|
|
15
|
+
lawspec doctor
|
|
16
|
+
lawspec generate
|
|
17
|
+
cargo test
|
|
18
|
+
cargo test --release
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Adapters are user-owned files under `src/`. LawSpec maintains
|
|
22
|
+
`lawspec_runtime.rs`, module declarations in `lawspec_modules.rs`, and tests
|
|
23
|
+
under `tests/`. The scaffolded `src/lib.rs` includes the generated declarations;
|
|
24
|
+
an existing library can include those declarations explicitly. Generation
|
|
25
|
+
preserves edited adapters and reports required signature updates.
|
|
26
|
+
|
|
27
|
+
The numeric runtime has no Proptest dependency. Framework support lives in a
|
|
28
|
+
separate generated file, `tests/support/lawspec_strategies.rs`.
|
|
29
|
+
|
|
30
|
+
## Total definitions (0.9)
|
|
31
|
+
|
|
32
|
+
Unit-level definitions supply executable implementations alongside laws:
|
|
33
|
+
|
|
34
|
+
```lawspec
|
|
35
|
+
unit example.total
|
|
36
|
+
|
|
37
|
+
definition size (xs :: List Int8) :: BigInt is
|
|
38
|
+
match xs with
|
|
39
|
+
| Nil -> 0
|
|
40
|
+
| Cons head tail -> 1 + size tail
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The compiler checks types, exhaustive matching, structural termination, and
|
|
46
|
+
potentially failing operations before emission. Definitions may call other
|
|
47
|
+
checked definitions, including forward references; they cannot call adapters.
|
|
48
|
+
Generic definitions specialize to concrete uses. Refinement predicates may call
|
|
49
|
+
checked definitions. Refined definition signatures become checked native contracts.
|
|
50
|
+
|
|
51
|
+
Rust emits their implementations into the generated source file
|
|
52
|
+
`lawspec_definitions.rs`. They are not user-owned adapter stubs. Typed native
|
|
53
|
+
entry points are grouped by unit, for example:
|
|
54
|
+
|
|
55
|
+
```rust
|
|
56
|
+
let mut context = lawspec_runtime::Context::default();
|
|
57
|
+
let count = lawspec_definitions::example_total::size(&mut context, vec![1, 2, 3])?;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
These entry points return `lawspec_runtime::Result<T>` and check native values
|
|
61
|
+
at the boundary. Share the context when Symbol fixture identities must match.
|
|
62
|
+
Native machine-sized bindings check the architecture, including fields in
|
|
63
|
+
unselected data variants. The implementation has no Proptest dependency and can
|
|
64
|
+
be used by the application library. Generated properties invoke the same checked
|
|
65
|
+
implementation; remaining external declarations still receive adapter stubs.
|
|
66
|
+
|
|
67
|
+
`tools/rust-definitions-integration.mjs` verifies both machine profiles, custom
|
|
68
|
+
source/test roots, native calls, readable and compact source, exact `rustfmt`
|
|
69
|
+
layout, incorrect adapters, and regeneration ownership.
|
|
70
|
+
|
|
71
|
+
## Formatting (0.9)
|
|
72
|
+
|
|
73
|
+
Rust source uses four-space indentation and structured line wrapping. Request
|
|
74
|
+
compact layout explicitly with `lawspec generate --minify`, or `minify: true`
|
|
75
|
+
in the compiler API. The mode is not saved in project configuration. Formatting
|
|
76
|
+
switches preserve user-owned adapters and their canonical interface references.
|
|
77
|
+
|
|
78
|
+
`tools/rust-formatting-integration.mjs` compares all Rust artifacts generated
|
|
79
|
+
from the bundled examples and the total-definition fixture against rustfmt,
|
|
80
|
+
under both machine profiles. Formatting is implemented in the compiler; generated
|
|
81
|
+
projects do not need rustfmt to run. The bundled WASM uses the same layout.
|
|
82
|
+
|
|
83
|
+
## Owned adapter values
|
|
84
|
+
|
|
85
|
+
Arguments and results are owned Rust values. LawSpec does not add borrowing,
|
|
86
|
+
lifetimes, or pointer operations to its language. Tests clone values where an
|
|
87
|
+
expression needs to use an input more than once.
|
|
88
|
+
|
|
89
|
+
| LawSpec domain | Rust adapter representation |
|
|
90
|
+
| --- | --- |
|
|
91
|
+
| `Bool` | `bool` |
|
|
92
|
+
| Fixed signed/unsigned integers | `i8`…`i64`, `u8`…`u64` |
|
|
93
|
+
| `IntSize`, `UIntSize`, `UIntPtr` | `isize`, `usize`, `usize` |
|
|
94
|
+
| `BigInt`, `BigUInt` | `BigInt`, `BigUint` |
|
|
95
|
+
| `Integer` input | `BigInt` |
|
|
96
|
+
| `Integer` result | `Integer`, with lossless `From` implementations |
|
|
97
|
+
| `Decimal`, `Rational` | Generated `Decimal`, `BigRational` |
|
|
98
|
+
| `Float32`, `Float64` | `f32`, `f64` |
|
|
99
|
+
| `Complex64`, `Complex128` | `Complex32`, `Complex64` from num-complex |
|
|
100
|
+
| `Char`, `Text` | `char`, `String` |
|
|
101
|
+
| `CodePoint`, `CodePointText` | Generated checked wrappers |
|
|
102
|
+
| `CodeUnit16`, `Utf16Text`, `Bytes` | `u16`, generated UTF-16 wrapper, `Vec<u8>` |
|
|
103
|
+
| `Symbol` | Generated identity type; cloning preserves identity |
|
|
104
|
+
| `Unit`, `Null`, `Undefined` | `()`, distinct generated absence types |
|
|
105
|
+
| `Nullable a`, `Optional a` | Distinct generated enums, including nested presence |
|
|
106
|
+
| `List a` | `Vec<A>` |
|
|
107
|
+
| `Maybe a` | `Option<A>` |
|
|
108
|
+
| `Either a b` | Generated `Either<A, B>` |
|
|
109
|
+
| User-defined products and sums | Named generic enums in `lawspec_data` |
|
|
110
|
+
|
|
111
|
+
Names such as `Integer` and `Decimal` above are exported by the generated
|
|
112
|
+
`lawspec_runtime` module. Machine-sized native bindings check the requested
|
|
113
|
+
`machineBits` against the executing architecture.
|
|
114
|
+
|
|
115
|
+
For a declaration such as `successor :: Int8 -> Integer`, an implementation can
|
|
116
|
+
return a wider native integer through the logical result wrapper:
|
|
117
|
+
|
|
118
|
+
```rust
|
|
119
|
+
use crate::lawspec_runtime as ls;
|
|
120
|
+
|
|
121
|
+
pub fn successor(value: i8) -> ls::Integer {
|
|
122
|
+
(i16::from(value) + 1).into()
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Exact arithmetic in laws uses arbitrary precision. Passing its result to an
|
|
127
|
+
`i8` adapter parameter performs a checked conversion. A fractional or
|
|
128
|
+
out-of-range value fails with the adapter's context.
|
|
129
|
+
|
|
130
|
+
`Decimal` stores an arbitrary integer coefficient and exponent. Decimal
|
|
131
|
+
arithmetic is exact; explicit rounding uses the requested scale and ties to
|
|
132
|
+
even. Exact-to-float conversion rounds directly to the requested IEEE precision,
|
|
133
|
+
including subnormal and halfway cases.
|
|
134
|
+
|
|
135
|
+
## Native data declarations
|
|
136
|
+
|
|
137
|
+
User-defined data emits reusable `lawspec_data.rs` and `lawspec_schema.rs` in the
|
|
138
|
+
source directory. Adapters receive native enum values with named, typed fields.
|
|
139
|
+
A product uses an enum with one record variant. Parameterized fields retain
|
|
140
|
+
Rust generic types; recursive fields use `Box` where an inline cycle requires
|
|
141
|
+
indirection, while `Vec` already supplies it. Mutually recursive declarations
|
|
142
|
+
are resolved together. When two units use the same type name, generated names
|
|
143
|
+
are qualified by their unit identities.
|
|
144
|
+
|
|
145
|
+
The runtime's `FromValue` and `IntoValue` traits provide conversion bridges.
|
|
146
|
+
Generated calls validate the complete logical value before conversion and the
|
|
147
|
+
native result before checking postconditions. This preserves raw UTF-16 units,
|
|
148
|
+
bytes, nested presence, and primitive range checks inside custom fields.
|
|
149
|
+
Machine-width checks also apply to machine integers inside custom data.
|
|
150
|
+
|
|
151
|
+
Generated enums derive `Clone` and `Debug`. LawSpec equality remains a runtime
|
|
152
|
+
operation: it compares fields structurally, retains IEEE NaN and signed-zero
|
|
153
|
+
rules, and compares Symbols by identity. It does not replace these rules with a
|
|
154
|
+
derived Rust `Eq` implementation.
|
|
155
|
+
|
|
156
|
+
Unused or exclusively recursive type parameters use a `PhantomData` marker.
|
|
157
|
+
Types with no constructors remain uninhabited; they do not acquire a synthetic
|
|
158
|
+
variant. Generated schemas and conversion support have no Proptest dependency.
|
|
159
|
+
The test support composes native Proptest strategies for recursive values and
|
|
160
|
+
shrinking, with deterministic boundary cases and finite-domain enumeration.
|
|
161
|
+
The structural budget counts each scalar, container, and constructor as one
|
|
162
|
+
node. Generation reserves every product field's minimum before distributing
|
|
163
|
+
remaining nodes. List length and element budgets vary together, so a deep
|
|
164
|
+
singleton remains reachable and lists have no hidden four-element limit.
|
|
165
|
+
Native shrinking preserves the schema and the structural budget.
|
|
166
|
+
|
|
167
|
+
## Refinements and generation
|
|
168
|
+
|
|
169
|
+
Small finite domains are enumerated. Larger domains use native Proptest
|
|
170
|
+
strategies. Integer bounds over earlier inputs become dependent strategies;
|
|
171
|
+
when a prefix has no possible continuation, generation retries the prefix.
|
|
172
|
+
Shrinking recomputes those dependent bounds and retains only valid tuples.
|
|
173
|
+
Refinement evaluation errors fail the test rather than being counted as rejected
|
|
174
|
+
samples. Generation limits prevent an empty or unreachable domain from passing
|
|
175
|
+
vacuously.
|
|
176
|
+
|
|
177
|
+
Contracts check preconditions, evaluate the adapter once, validate its native
|
|
178
|
+
result, and then check postconditions on that result.
|
|
179
|
+
|
|
180
|
+
## Custom layouts
|
|
181
|
+
|
|
182
|
+
`sourceDir` and `testDir` move generated sources and imports together. Set Cargo's
|
|
183
|
+
`[lib] path` to the library entry point under the selected source directory. For
|
|
184
|
+
test directories other than `tests`, register generated test files using Cargo
|
|
185
|
+
`[[test]]` entries. Doctor checks the selected Cargo package, edition, resolved
|
|
186
|
+
dependencies, library directory, and custom test registration.
|
|
187
|
+
|
|
188
|
+
Internal typed Core definitions with attached contracts are proved before Rust
|
|
189
|
+
emission. Generated source checks preconditions after argument validation and
|
|
190
|
+
postconditions after result validation, without property-framework dependencies.
|
|
191
|
+
Checks sequence through Result, preserving ordered predicates and contextual
|
|
192
|
+
failures. Native wrappers share logical enforcement. Readable contract bodies
|
|
193
|
+
match rustfmt exactly.
|
|
194
|
+
The shared native fixture passes both machine profiles and formatting modes,
|
|
195
|
+
including exact division, narrowing, nested calls, direct logical entry checks,
|
|
196
|
+
and rejection of corrupted results. Refined source definition signatures now
|
|
197
|
+
produce these contracts through template proof and specialization.
|
|
198
|
+
|
|
199
|
+
## Constructor field contracts
|
|
200
|
+
|
|
201
|
+
Rust checks constructor predicates at logical definition and native adapter
|
|
202
|
+
boundaries. Generated proptest strategies validate complete candidates, retain
|
|
203
|
+
native shrinking, and share each case's Symbol context with witness literals and
|
|
204
|
+
assertions. Only false predicates trigger retries; evaluation errors fail the
|
|
205
|
+
property. `maxAttempts` bounds local and global rejection, and exhaustion does
|
|
206
|
+
not prove an empty domain.
|
|
207
|
+
|
|
208
|
+
Validated witnesses seed nested payloads. Builtin lists and presence/sum
|
|
209
|
+
containers retain native structural shrinking; sampled named witnesses can
|
|
210
|
+
still produce larger counterexamples than native branches. Recursive named
|
|
211
|
+
refined payloads remain outside the currently supported contract fragment.
|
package/WEB.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# JavaScript and TypeScript backends
|
|
2
|
+
|
|
3
|
+
LawSpec emits ES modules. JavaScript uses `.mjs`; TypeScript uses `.ts` with `.js`
|
|
4
|
+
import paths for NodeNext compilation. Generated data and numeric support belong
|
|
5
|
+
in source directories and do not depend on fast-check. Generated properties use
|
|
6
|
+
fast-check's native generators and shrinkers.
|
|
7
|
+
|
|
8
|
+
## Native structural values
|
|
9
|
+
|
|
10
|
+
`List a` uses `Array<A>`. `Maybe a` and `Either a b` use named classes and, in
|
|
11
|
+
TypeScript, generic unions from `lawspec_data`. User products and sums likewise
|
|
12
|
+
have named native variant classes. TypeScript retains payload types and nominal
|
|
13
|
+
variant identity, including recursive fields and phantom parameters.
|
|
14
|
+
|
|
15
|
+
`Nullable a` and `Optional a` use tagged `data.Presence<A>` values, preserving
|
|
16
|
+
nested states separately from algebraic `Maybe`. Runtime checks enforce the
|
|
17
|
+
presence kind and payload domain. Scalar and structural presence values both use
|
|
18
|
+
the schema bridge when crossing native adapter boundaries.
|
|
19
|
+
|
|
20
|
+
Checked conversions copy containers, reject sparse arrays and invalid fields,
|
|
21
|
+
validate numeric ranges and machine profiles, and preserve raw text and bytes.
|
|
22
|
+
LawSpec equality follows the schema: NaN differs from itself, signed zeros compare
|
|
23
|
+
equal, and Symbols compare by identity. Ordinary JavaScript object equality is
|
|
24
|
+
not a replacement for structural LawSpec equality.
|
|
25
|
+
|
|
26
|
+
## Total definitions
|
|
27
|
+
|
|
28
|
+
Checked definitions produce reusable source functions:
|
|
29
|
+
|
|
30
|
+
```lawspec
|
|
31
|
+
unit example.total
|
|
32
|
+
|
|
33
|
+
definition increment (x :: Int8) :: BigInt is x + 1 end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
import {increment} from './lawspec_definitions/example/total.mjs';
|
|
38
|
+
|
|
39
|
+
const symbols = new Map();
|
|
40
|
+
const result = increment(symbols, 127); // 128n
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The TypeScript entry point has `symbols: Map<string, symbol>`, `value0: number`,
|
|
44
|
+
and a `bigint` result. Native signatures preserve lists, products, sums, and
|
|
45
|
+
nested presence payloads. Runtime validation also rejects invalid number ranges,
|
|
46
|
+
fractional primitive inputs, and malformed values from JavaScript callers.
|
|
47
|
+
Failures identify the resolved definition.
|
|
48
|
+
|
|
49
|
+
The first argument shares Symbol fixture identity across calls within an example.
|
|
50
|
+
Equal descriptions in separate contexts do not create equal Symbols. Exact
|
|
51
|
+
integers use bigint; exact Decimal and Rational operations use the scalar runtime
|
|
52
|
+
without passing through Number arithmetic.
|
|
53
|
+
|
|
54
|
+
Public modules live under `lawspec_definitions/`, grouped by unit. Checked logical
|
|
55
|
+
bodies live in `lawspec_definition_bodies`. Both are generated-owned source.
|
|
56
|
+
Properties call the implementations directly; a definition never becomes an
|
|
57
|
+
adapter stub. Ordinary adapters remain user-owned. Support-module collisions and
|
|
58
|
+
bindings that shadow generated global access are rejected before output.
|
|
59
|
+
|
|
60
|
+
Definitions and properties share typed Core expression rendering. Match inputs
|
|
61
|
+
are evaluated once, only the selected branch runs, and Boolean guards
|
|
62
|
+
short-circuit. Totality validation checks structural descent and potentially
|
|
63
|
+
failing operations. Generic definitions specialize to concrete uses. Refined signatures become
|
|
64
|
+
checked contracts, and refinement predicates may call checked definitions.
|
|
65
|
+
|
|
66
|
+
## Formatting and verification
|
|
67
|
+
|
|
68
|
+
Definition output uses two-space blocks, four-space continuations, single-quoted
|
|
69
|
+
strings, and an 80-column layout. String tokens preserve escaped and raw payloads
|
|
70
|
+
in readable and compact modes. Compact mode removes optional document breaks.
|
|
71
|
+
The public `--minify` path covers runtime, declaration, definition, adapter, and
|
|
72
|
+
test artifacts. `tools/web-formatting-integration.mjs` checks the full bundled
|
|
73
|
+
corpus for formatting rules and readable/compact syntax-tree equivalence. It
|
|
74
|
+
records Prettier differences separately: Prettier's continuation layout is not
|
|
75
|
+
the Google four-space continuation rule used here.
|
|
76
|
+
|
|
77
|
+
`tools/web-definitions-integration.mjs` checks both targets and machine profiles,
|
|
78
|
+
strict TypeScript entry points and rejected assignments, source-only execution,
|
|
79
|
+
recursive properties, incorrect adapters, compact output, custom directories,
|
|
80
|
+
and regeneration protection. The implementation modules pass strict TypeScript
|
|
81
|
+
without `any` or `@ts-nocheck`. Existing schema and numeric runtime TypeScript
|
|
82
|
+
files still use `@ts-nocheck`; their public data declarations and definition
|
|
83
|
+
entry points are checked separately.
|
|
84
|
+
|
|
85
|
+
## Runtime source formatting
|
|
86
|
+
|
|
87
|
+
The checked-in JavaScript scalar runtime, schema runtime, and fast-check helpers
|
|
88
|
+
use two-space indentation, single quotes, and an 80-column target, formatted with
|
|
89
|
+
Prettier 3.6.2. `node tools/format-web-runtimes.mjs` checks these sources;
|
|
90
|
+
`--write` formats them. Set `LAWSPEC_PRETTIER` to the formatter's `index.mjs` when
|
|
91
|
+
it is not in `.artifacts/formatter-deps/prettier/`. This development tool never
|
|
92
|
+
runs in generated projects or during compiler requests.
|
|
93
|
+
|
|
94
|
+
Run `python3 tools/embed-runtimes.py` after runtime edits, then use `--check` to
|
|
95
|
+
verify that native/WASM source embedding is current. Both JavaScript and
|
|
96
|
+
TypeScript receive the reviewed runtime source, with the existing typed-schema
|
|
97
|
+
constructor annotation and module-extension adjustments for TypeScript.
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
Internal checked Core definition contracts now run at native and logical entry
|
|
101
|
+
points. Emission proves the obligations first; argument validation precedes
|
|
102
|
+
ordered preconditions, and result validation precedes postconditions. Contract
|
|
103
|
+
binders map explicitly to body inputs and the checked result. Refined source
|
|
104
|
+
signatures now produce these contracts through template proof and specialization.
|
|
105
|
+
|
|
106
|
+
`tools/portable-definition-contract-integration.mjs` exercises Python, JavaScript
|
|
107
|
+
and strict TypeScript with both machine profiles and layouts, without property
|
|
108
|
+
frameworks. It also verifies that deliberately corrupted results are rejected.
|
|
109
|
+
The fixture in `test/DefinitionContractFixture.hs` is shared with the JVM checks.
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
## Generated source style checks
|
|
113
|
+
|
|
114
|
+
JavaScript and TypeScript emitters use two-space blocks and four-space
|
|
115
|
+
continuations. The latter follows the [Google JavaScript line-wrapping guide](https://google.github.io/styleguide/jsguide.html#s4.5-line-wrapping).
|
|
116
|
+
Binary operators remain on the preceding line when an expression wraps.
|
|
117
|
+
Generated adapter error messages and tagged scalar values use the shared
|
|
118
|
+
single-quoted JavaScript literal renderer.
|
|
119
|
+
|
|
120
|
+
`tools/web-formatting-integration.mjs` independently parses both output modes
|
|
121
|
+
with TypeScript, compares their syntax trees and literal contents, and checks
|
|
122
|
+
quote choice, binary-operator breaks, continuation and statement-block indentation,
|
|
123
|
+
the 80-column limit, tabs, and trailing
|
|
124
|
+
whitespace. Module imports/re-exports and indivisible source excerpts on their
|
|
125
|
+
own comment line are explicit column exceptions. Ordinary prose and code remain
|
|
126
|
+
subject to the limit. Long scalar string values use escaped concatenated chunks;
|
|
127
|
+
property names remain single tokens. It runs
|
|
128
|
+
both machine profiles for the bundled examples and total-definition fixture.
|
|
129
|
+
Set `LAWSPEC_CORE` to the native compiler; optional `LAWSPEC_TYPESCRIPT` and
|
|
130
|
+
`LAWSPEC_PRETTIER` paths select the cached development tools. Prettier must be
|
|
131
|
+
version 3.6.2. Positional arguments select specification files.
|
|
132
|
+
|
|
133
|
+
Prettier differences are saved for inspection, not treated as an exact Google
|
|
134
|
+
style oracle: its continuation indentation differs from Google's. These checks
|
|
135
|
+
are partial style evidence. Additional indentation cases (such as switch bodies
|
|
136
|
+
and type declarations) and Kotlin formatter acceptance remain tracked in the 0.9 plan; passing this script alone does not
|
|
137
|
+
establish complete style conformance.
|
|
138
|
+
|
|
139
|
+
The development-only runtime formatter (`tools/format-web-runtimes.mjs`) applies
|
|
140
|
+
Prettier layout and then syntax-tree-directed continuation indentation. It verifies
|
|
141
|
+
that JavaScript tokens remain unchanged, retains relative callback block indentation,
|
|
142
|
+
and rejects adjustments inside multiline template literals. It is idempotent and
|
|
143
|
+
checks the resulting 80-column limit. Reviewed runtime source is embedded at build
|
|
144
|
+
time; generated projects never download or invoke these development tools.
|
package/api.mjs
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// Generated from LawSpec.Gen. Do not edit.
|
|
2
|
-
import {
|
|
2
|
+
import {loadCore} from './launcher.mjs';
|
|
3
|
+
|
|
3
4
|
export async function createCompiler() {
|
|
4
5
|
const call = await loadCore();
|
|
5
|
-
return {
|
|
6
|
+
return {
|
|
7
|
+
check: (input) => call({schemaVersion: 3, ...input, method: 'check'}),
|
|
8
|
+
expand: (input) => call({schemaVersion: 3, ...input, method: 'expand'}),
|
|
9
|
+
planGeneration: (input) =>
|
|
10
|
+
call({schemaVersion: 3, ...input, method: 'planGeneration'}),
|
|
11
|
+
};
|
|
6
12
|
}
|
package/bin/lawspec.mjs
CHANGED
|
@@ -23,7 +23,7 @@ for (let i = 0; i < args.length; i++) {
|
|
|
23
23
|
if (!args[i + 1] || args[i + 1].startsWith("--"))
|
|
24
24
|
throw new Error(`Missing value for ${arg}`);
|
|
25
25
|
options[arg.slice(2)] = args[++i];
|
|
26
|
-
} else if (["--dry-run", "--check", "--json"].includes(arg))
|
|
26
|
+
} else if (["--dry-run", "--check", "--json", "--minify"].includes(arg))
|
|
27
27
|
options[arg.slice(2)] = true;
|
|
28
28
|
else if (arg.startsWith("--")) throw new Error(`Unknown option: ${arg}`);
|
|
29
29
|
else positional.push(arg);
|
|
@@ -104,6 +104,7 @@ async function init() {
|
|
|
104
104
|
throw new Error("Target is already configured");
|
|
105
105
|
await mkdir(root, { recursive: true });
|
|
106
106
|
const buildFiles = [
|
|
107
|
+
"Cargo.toml",
|
|
107
108
|
"pom.xml",
|
|
108
109
|
"pyproject.toml",
|
|
109
110
|
"package.json",
|
|
@@ -117,7 +118,7 @@ async function init() {
|
|
|
117
118
|
const hasBuild = (await readdir(root)).some(
|
|
118
119
|
(f) => buildFiles.includes(f) || f.endsWith(".cabal"),
|
|
119
120
|
);
|
|
120
|
-
const additions = hasBuild ? {} : templates(language);
|
|
121
|
+
const additions = hasBuild ? {} : templates(language, {minify: options.minify === true});
|
|
121
122
|
for (const name of Object.keys(additions)) {
|
|
122
123
|
const file = await safePath(root, name);
|
|
123
124
|
if ((await readOptional(file)) !== null)
|
|
@@ -146,53 +147,24 @@ async function init() {
|
|
|
146
147
|
});
|
|
147
148
|
await atomicWrite(
|
|
148
149
|
configFile,
|
|
149
|
-
JSON.stringify(config, null, 2) + "\n",
|
|
150
|
+
JSON.stringify(config, null, options.minify ? undefined : 2) + "\n",
|
|
150
151
|
current === null,
|
|
151
152
|
);
|
|
152
153
|
output(
|
|
153
154
|
`Configured ${language}. ${hasBuild ? "Existing build files preserved." : "Created missing project build files."}\n${setup[language]}\nNext: lawspec doctor, then lawspec generate.`,
|
|
154
155
|
);
|
|
155
156
|
}
|
|
156
|
-
function
|
|
157
|
-
if (
|
|
158
|
-
if (
|
|
159
|
-
return
|
|
160
|
-
}
|
|
161
|
-
function showExpression(expr) {
|
|
162
|
-
const value = expr.contents;
|
|
163
|
-
if (expr.tag === "Var" || expr.tag === "Number") return value;
|
|
164
|
-
if (expr.tag === "DecimalNumber") return `${value[0]}e${value[1]}`;
|
|
165
|
-
if (expr.tag === "ScalarLit") return showScalar(value);
|
|
166
|
-
if (expr.tag === "Binary") return `(${showExpression(value[1])} ${value[0]} ${showExpression(value[2])})`;
|
|
167
|
-
if (expr.tag === "Unary") return `${value[0]}(${showExpression(value[1])})`;
|
|
168
|
-
if (expr.tag === "Annotate") return `(${showExpression(value[0])} :: ${showType(value[1])})`;
|
|
169
|
-
if (
|
|
170
|
-
expr.tag === "Number" ||
|
|
171
|
-
expr.tag === "StringLit" ||
|
|
172
|
-
expr.tag === "BoolLit"
|
|
173
|
-
)
|
|
174
|
-
return JSON.stringify(value);
|
|
175
|
-
if (expr.tag === "Apply")
|
|
176
|
-
return `${showExpression(value[0])} (${showExpression(value[1])})`;
|
|
177
|
-
return `(${showExpression(value[0])} . ${showExpression(value[1])})`;
|
|
157
|
+
function showAssertion(assertion) {
|
|
158
|
+
if (assertion.kind === "equal") return `${assertion.left.text} = ${assertion.right.text}`;
|
|
159
|
+
if (assertion.kind === "implies") return `${assertion.guard.text} implies ${showAssertion(assertion.body)}`;
|
|
160
|
+
return assertion.items.map(showAssertion).join(" and ");
|
|
178
161
|
}
|
|
179
162
|
function explainExamples(law) {
|
|
180
|
-
return law.
|
|
181
|
-
.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
.map(([n, v]) => ` ${n} = ${showScalar(v)}`)
|
|
186
|
-
.join("\n") +
|
|
187
|
-
"\n" +
|
|
188
|
-
ex.expectations
|
|
189
|
-
.map(
|
|
190
|
-
(e) =>
|
|
191
|
-
` expect ${showExpression(e.actual)} = ${showScalar(e.expected)}`,
|
|
192
|
-
)
|
|
193
|
-
.join("\n"),
|
|
194
|
-
)
|
|
195
|
-
.join("\n");
|
|
163
|
+
return law.examples.map(ex =>
|
|
164
|
+
`\nexample ${JSON.stringify(ex.name)}\n` +
|
|
165
|
+
ex.bindings.map(b => ` ${b.name} = ${showScalar(b.value)}`).join("\n") + "\n" +
|
|
166
|
+
ex.expectations.map(e => ` expect ${showAssertion(e)}`).join("\n")
|
|
167
|
+
).join("\n");
|
|
196
168
|
}
|
|
197
169
|
async function main() {
|
|
198
170
|
if (options['machine-bits'] !== undefined) {
|
|
@@ -201,13 +173,13 @@ async function main() {
|
|
|
201
173
|
}
|
|
202
174
|
if (!verb || ["help", "--help", "-h"].includes(verb)) {
|
|
203
175
|
output(
|
|
204
|
-
"LawSpec 0.
|
|
176
|
+
"LawSpec 0.9.0\nUsage: lawspec init --target <language> [--project <directory>] [--minify]\n lawspec check | doctor | explain <unit>::<law> | generate\n lawspec examples [--target <language>] [--output example_artifacts]\nOptions: --config <path>, --target <language>, --machine-bits <32|64>, --json\nGeneration: --dry-run, --check, --minify\nTargets: " +
|
|
205
177
|
targets.join(", "),
|
|
206
178
|
);
|
|
207
179
|
return;
|
|
208
180
|
}
|
|
209
181
|
if (verb === "--version") {
|
|
210
|
-
output("0.
|
|
182
|
+
output("0.9.0");
|
|
211
183
|
return;
|
|
212
184
|
}
|
|
213
185
|
if (positional.length > (verb === "explain" ? 1 : 0))
|
|
@@ -219,7 +191,7 @@ async function main() {
|
|
|
219
191
|
options["dry-run"] ||
|
|
220
192
|
options.check
|
|
221
193
|
)
|
|
222
|
-
throw new Error("examples supports --target, --output and --
|
|
194
|
+
throw new Error("examples supports --target, --output, --json and --minify only");
|
|
223
195
|
const result = await generateExamples(options);
|
|
224
196
|
output(
|
|
225
197
|
options.json
|
|
@@ -235,6 +207,8 @@ async function main() {
|
|
|
235
207
|
return;
|
|
236
208
|
}
|
|
237
209
|
if (options.output) throw new Error("--output is only supported by examples");
|
|
210
|
+
if (options.minify && !["init", "generate", "examples"].includes(verb))
|
|
211
|
+
throw new Error("--minify applies to init, generate and examples");
|
|
238
212
|
if (verb === "init") return init();
|
|
239
213
|
if (!["check", "doctor", "explain", "generate"].includes(verb))
|
|
240
214
|
throw new Error(`Unknown command: ${verb}`);
|
|
@@ -280,7 +254,7 @@ async function main() {
|
|
|
280
254
|
output(
|
|
281
255
|
options.json
|
|
282
256
|
? result
|
|
283
|
-
: `Checked ${result.laws.length}
|
|
257
|
+
: `Checked ${result.laws.length} law(s).`,
|
|
284
258
|
);
|
|
285
259
|
return;
|
|
286
260
|
}
|
|
@@ -291,7 +265,7 @@ async function main() {
|
|
|
291
265
|
.filter(
|
|
292
266
|
({ e }) => !positional[0] || `${e.owner}::${e.name}` === positional[0],
|
|
293
267
|
);
|
|
294
|
-
if (!indices.length) throw new Error("No matching
|
|
268
|
+
if (!indices.length) throw new Error("No matching law");
|
|
295
269
|
output(
|
|
296
270
|
options.json
|
|
297
271
|
? indices.map(({ e, i }) => ({ ...e, expansion: result.expansions[i] }))
|
|
@@ -315,11 +289,12 @@ async function main() {
|
|
|
315
289
|
target: target.language,
|
|
316
290
|
sourceDir: target.sourceDir,
|
|
317
291
|
testDir: target.testDir,
|
|
292
|
+
minify: options.minify === true,
|
|
318
293
|
}),
|
|
319
294
|
).files,
|
|
320
295
|
);
|
|
321
296
|
const reports = await Promise.all(
|
|
322
|
-
selected.map((t, i) => doctor(t, roots[i])),
|
|
297
|
+
selected.map((t, i) => doctor(t, roots[i], artifacts[i])),
|
|
323
298
|
);
|
|
324
299
|
const failed = reports.filter((r) => !r.ok);
|
|
325
300
|
if (failed.length)
|