lawspec 0.2.0 → 0.3.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/README.md +111 -22
- package/bin/lawspec.mjs +27 -3
- package/build.json +10 -10
- package/core.wasm +0 -0
- package/examples/specs/canonical_url.lawspec +21 -0
- package/examples/specs/mixed_inputs.lawspec +33 -0
- package/examples/specs/slug.lawspec +25 -0
- package/examples-command.mjs +51 -0
- package/index.d.ts +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,34 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
**State the law once. Check it everywhere.**
|
|
4
4
|
|
|
5
|
-
LawSpec 0.
|
|
5
|
+
LawSpec 0.3 compiles reusable laws into native property tests, executable examples,
|
|
6
6
|
and implementation adapters. The compiler is Haskell, distributed as prebuilt
|
|
7
7
|
WebAssembly with a Node CLI and an asynchronous, typed JavaScript API.
|
|
8
8
|
|
|
9
9
|
## Install and try it
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Install [LawSpec from npm](https://www.npmjs.com/package/lawspec):
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
|
-
npm
|
|
15
|
-
|
|
14
|
+
npm install --save-dev lawspec@0.3.0
|
|
15
|
+
npx lawspec --version
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
Node 22+ and the selected target's build tools are required. No Haskell toolchain
|
|
19
|
+
is needed to install or run LawSpec. The reference platforms are macOS and Linux.
|
|
20
|
+
Use `npx lawspec` to run the locally installed CLI.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
To try it in a **new, empty directory**, initialize the starter before installing
|
|
23
|
+
local dependencies so LawSpec can create its `package.json` and test script:
|
|
23
24
|
|
|
24
25
|
```sh
|
|
25
|
-
lawspec
|
|
26
|
-
|
|
27
|
-
lawspec
|
|
28
|
-
|
|
29
|
-
lawspec
|
|
30
|
-
lawspec
|
|
26
|
+
mkdir lawspec-example
|
|
27
|
+
cd lawspec-example
|
|
28
|
+
npm exec --package=lawspec@0.3.0 -- lawspec init --target javascript
|
|
29
|
+
npm install --save-dev lawspec@0.3.0
|
|
30
|
+
npx lawspec check
|
|
31
|
+
npx lawspec explain 'example.atoi_codec::itoa and then atoi yields a'
|
|
32
|
+
npx lawspec doctor
|
|
33
|
+
npx lawspec generate
|
|
31
34
|
```
|
|
32
35
|
|
|
36
|
+
In an existing project, install LawSpec first, then run
|
|
37
|
+
`npx lawspec init --target javascript`. Existing build files are preserved;
|
|
38
|
+
apply the printed dependency and test-runner setup instructions before generation.
|
|
39
|
+
|
|
33
40
|
Implement `src/example/atoi_codec.mjs`, then run `npm test`:
|
|
34
41
|
|
|
35
42
|
```javascript
|
|
@@ -54,7 +61,7 @@ properties with the selected framework's shrinking and failure reporting.
|
|
|
54
61
|
| `kotlin` | JDK/JVM 25, Gradle 9.1–9.3, Kotlin 2.3.21 | Kotest 5.9.1 | `gradle test` |
|
|
55
62
|
|
|
56
63
|
Java 25 and Python 3.13 are the minimum baselines. New JVM releases are admitted
|
|
57
|
-
through compatibility profiles after testing; v0.
|
|
64
|
+
through compatibility profiles after testing; v0.3's current JVM profile certifies
|
|
58
65
|
25. Python templates declare `requires-python = ">=3.13"` and runtime checks
|
|
59
66
|
currently recognize 3.13 and 3.14. Kotlin templates pin Gradle's supported build
|
|
60
67
|
configuration to Kotlin 2.3.21 and target JVM 25.
|
|
@@ -139,7 +146,7 @@ end
|
|
|
139
146
|
```
|
|
140
147
|
|
|
141
148
|
The implicit prelude defines `left inverse`, `round trip identity is preserved`,
|
|
142
|
-
and `
|
|
149
|
+
`equivalent`, and `idempotent`.
|
|
143
150
|
A law may reference a local reusable law or a prelude law. The compiler performs
|
|
144
151
|
capture-avoiding expansion and specializes types; it does not recognize codec
|
|
145
152
|
function names specially. `explain` shows the final property:
|
|
@@ -150,10 +157,13 @@ for all (x :: Int32) . atoi (itoa (x)) = x
|
|
|
150
157
|
|
|
151
158
|
Reusable laws can declare typed unary function parameters and `requires Eq a`.
|
|
152
159
|
Definitions support law application, function application/composition, universal
|
|
153
|
-
quantification, integer literals and equality. Function signatures use `Int32`
|
|
154
|
-
and `Text`; generic variables are supported in reusable laws. v0.
|
|
155
|
-
quantified `Int32` inputs, including multiple inputs.
|
|
156
|
-
or compared
|
|
160
|
+
quantification, integer and text literals, and equality. Function signatures use `Int32`
|
|
161
|
+
and `Text`; generic variables are supported in reusable laws. v0.3 generates
|
|
162
|
+
quantified `Int32` and `Text` inputs, including mixed and multiple inputs. Both
|
|
163
|
+
types can also be intermediate or compared results. Functions are synchronous
|
|
164
|
+
and unary. Text literals are double-quoted, with escapes such as `\"`, `\\`,
|
|
165
|
+
`\n`, and `\t`; examples must bind each input to a literal of its declared type.
|
|
166
|
+
Text values contain Unicode scalar values; surrogate code points are rejected.
|
|
157
167
|
|
|
158
168
|
Examples refer to the expanded input names, including names inherited from the
|
|
159
169
|
prelude. Bind every input exactly once. Ambiguous names and out-of-range values
|
|
@@ -192,7 +202,7 @@ This expands to `for all (x :: Int32) . render (x) = referenceRender (x)`.
|
|
|
192
202
|
The example inherits the input name `x` from the prelude. Both functions are
|
|
193
203
|
user-owned adapter functions; either may delegate to your existing code.
|
|
194
204
|
|
|
195
|
-
[The complete example](examples/specs/equivalent.lawspec) compares decimal
|
|
205
|
+
[The complete example](https://github.com/brain-fuel/lawspec/blob/v0.3.0/examples/specs/equivalent.lawspec) compares decimal
|
|
196
206
|
renderers and two implementations that clamp negative integers to zero. For
|
|
197
207
|
JavaScript, their adapters can be:
|
|
198
208
|
|
|
@@ -207,7 +217,82 @@ The same specification generates native tests for all seven targets. The
|
|
|
207
217
|
integration suite checks both examples with matching implementations, then
|
|
208
218
|
breaks each alternative separately to verify detection. Agreement does not
|
|
209
219
|
establish that either implementation meets an independent specification; two
|
|
210
|
-
implementations can share the same bug. Quantified inputs
|
|
220
|
+
implementations can share the same bug. Quantified inputs can be `Int32` or `Text`.
|
|
221
|
+
|
|
222
|
+
## Text properties and idempotence
|
|
223
|
+
|
|
224
|
+
```lawspec
|
|
225
|
+
unit example.slug
|
|
226
|
+
|
|
227
|
+
normalize :: Text -> Text
|
|
228
|
+
referenceNormalize :: Text -> Text
|
|
229
|
+
|
|
230
|
+
law `normalizers agree` is
|
|
231
|
+
definition is
|
|
232
|
+
`equivalent` normalize referenceNormalize
|
|
233
|
+
end
|
|
234
|
+
example `ordinary text` is
|
|
235
|
+
x = "Hello, World!"
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
The [slug example](https://github.com/brain-fuel/lawspec/blob/v0.3.0/examples/specs/slug.lawspec)
|
|
241
|
+
compares two implementations of ASCII-space replacement. It includes empty,
|
|
242
|
+
Unicode and escaped text. Each target uses its native string generator:
|
|
243
|
+
JetCheck `Generator.stringsOf(Generator.asciiPrintableChars())`, Hypothesis `st.text()`, fast-check `fc.string()`,
|
|
244
|
+
Rapid `rapid.String()`, Hedgehog `Gen.text`, or Kotest `Arb.string()`.
|
|
245
|
+
Generator distributions differ between libraries; the generated tests also
|
|
246
|
+
exercise deterministic empty, whitespace, Unicode, combining-mark and escaped
|
|
247
|
+
control-character cases. Hedgehog's generated text length range is 0–100.
|
|
248
|
+
|
|
249
|
+
The prelude's `idempotent` law requires `f (f x) = f x`:
|
|
250
|
+
|
|
251
|
+
```lawspec
|
|
252
|
+
unit example.canonical_url
|
|
253
|
+
|
|
254
|
+
canonicalize :: Text -> Text
|
|
255
|
+
|
|
256
|
+
law `canonicalization reaches a fixed point` is
|
|
257
|
+
definition is
|
|
258
|
+
`idempotent` canonicalize
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
The [canonical URL example](https://github.com/brain-fuel/lawspec/blob/v0.3.0/examples/specs/canonical_url.lawspec)
|
|
264
|
+
uses removal of **all trailing slashes** as a small fixed-point demonstration,
|
|
265
|
+
not a complete URL canonicalization algorithm. For JavaScript:
|
|
266
|
+
|
|
267
|
+
```javascript
|
|
268
|
+
export const canonicalize = value => value.replace(/\/+$/, "");
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Removing just one trailing slash fails the supplied repeated-slash example.
|
|
272
|
+
The [mixed-input example](https://github.com/brain-fuel/lawspec/blob/v0.3.0/examples/specs/mixed_inputs.lawspec)
|
|
273
|
+
shows `Text` and `Int32` in the same quantified property and executable example.
|
|
274
|
+
The JavaScript API represents example values as `number | string`.
|
|
275
|
+
|
|
276
|
+
## Generate all example artifacts
|
|
277
|
+
|
|
278
|
+
```sh
|
|
279
|
+
npx lawspec examples
|
|
280
|
+
# Or select a target and a relative output directory:
|
|
281
|
+
npx lawspec examples --target java --output example_artifacts
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
This command works without a project configuration or native build tools. It
|
|
285
|
+
compiles every bundled example and writes its tests and user-owned stubs to
|
|
286
|
+
`example_artifacts/<language>/`, using each target's normal source/test layout.
|
|
287
|
+
By default it exports all seven languages; `--json` returns the file inventory.
|
|
288
|
+
From a checkout, `make examples` runs the same command.
|
|
289
|
+
|
|
290
|
+
These are inspection artifacts, not initialized projects: no build files are
|
|
291
|
+
created and dependency compatibility is not checked. To run them, configure the
|
|
292
|
+
corresponding native project and implement the stubs. Regeneration preserves
|
|
293
|
+
user-owned stubs and refuses to overwrite edited generated tests. Each target
|
|
294
|
+
has its own ownership manifest. Output paths must be relative and cannot use
|
|
295
|
+
parent traversal or symlinks. The default directory is ignored by Git.
|
|
211
296
|
|
|
212
297
|
## Ownership
|
|
213
298
|
|
|
@@ -247,6 +332,10 @@ by the JS shim.
|
|
|
247
332
|
|
|
248
333
|
## Build and verify
|
|
249
334
|
|
|
335
|
+
For contributors working from a repository checkout, build a local archive with
|
|
336
|
+
`npm pack ./npm` and install it with `npm install --save-dev ./lawspec-0.3.0.tgz`.
|
|
337
|
+
The package payload lives in `npm/`.
|
|
338
|
+
|
|
250
339
|
```sh
|
|
251
340
|
stack test
|
|
252
341
|
# With wasm32-wasi-cabal and wasm32-wasi-ghc installed:
|
package/bin/lawspec.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { readFile, mkdir, readdir, stat } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { createCompiler } from "../api.mjs";
|
|
5
5
|
import { targets, templates, commands, setup } from "../templates.mjs";
|
|
6
|
+
import { generateExamples } from "../examples-command.mjs";
|
|
6
7
|
import { doctor } from "../doctor.mjs";
|
|
7
8
|
import {
|
|
8
9
|
readOptional,
|
|
@@ -17,7 +18,7 @@ const options = {};
|
|
|
17
18
|
const positional = [];
|
|
18
19
|
for (let i = 0; i < args.length; i++) {
|
|
19
20
|
const arg = args[i];
|
|
20
|
-
if (["--target", "--project", "--config"].includes(arg)) {
|
|
21
|
+
if (["--target", "--project", "--config", "--output"].includes(arg)) {
|
|
21
22
|
if (!args[i + 1] || args[i + 1].startsWith("--"))
|
|
22
23
|
throw new Error(`Missing value for ${arg}`);
|
|
23
24
|
options[arg.slice(2)] = args[++i];
|
|
@@ -153,17 +154,40 @@ async function init() {
|
|
|
153
154
|
async function main() {
|
|
154
155
|
if (!verb || ["help", "--help", "-h"].includes(verb)) {
|
|
155
156
|
output(
|
|
156
|
-
"LawSpec 0.
|
|
157
|
+
"LawSpec 0.3.0\nUsage: lawspec init --target <language> [--project <directory>]\n lawspec check | doctor | explain <unit>::<law> | generate\n lawspec examples [--target <language>] [--output example_artifacts]\nOptions: --config <path>, --target <language>, --json\nGeneration: --dry-run, --check\nTargets: " +
|
|
157
158
|
targets.join(", "),
|
|
158
159
|
);
|
|
159
160
|
return;
|
|
160
161
|
}
|
|
161
162
|
if (verb === "--version") {
|
|
162
|
-
output("0.
|
|
163
|
+
output("0.3.0");
|
|
163
164
|
return;
|
|
164
165
|
}
|
|
165
166
|
if (positional.length > (verb === "explain" ? 1 : 0))
|
|
166
167
|
throw new Error(`Unexpected argument: ${positional.join(" ")}`);
|
|
168
|
+
if (verb === "examples") {
|
|
169
|
+
if (
|
|
170
|
+
options.config ||
|
|
171
|
+
options.project ||
|
|
172
|
+
options["dry-run"] ||
|
|
173
|
+
options.check
|
|
174
|
+
)
|
|
175
|
+
throw new Error("examples supports --target, --output and --json only");
|
|
176
|
+
const result = await generateExamples(options);
|
|
177
|
+
output(
|
|
178
|
+
options.json
|
|
179
|
+
? result
|
|
180
|
+
: result
|
|
181
|
+
.map(
|
|
182
|
+
(r) =>
|
|
183
|
+
`${r.target}: ${r.files.length} artifacts in ${r.directory}; ${r.preservedAdapters.length} user adapters preserved.`,
|
|
184
|
+
)
|
|
185
|
+
.join("\n") +
|
|
186
|
+
"\nInspection artifacts only; native toolchains and dependencies are not checked. Stubs must be implemented before running tests.",
|
|
187
|
+
);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (options.output) throw new Error("--output is only supported by examples");
|
|
167
191
|
if (verb === "init") return init();
|
|
168
192
|
if (!["check", "doctor", "explain", "generate"].includes(verb))
|
|
169
193
|
throw new Error(`Unknown command: ${verb}`);
|
package/build.json
CHANGED
|
@@ -17,23 +17,23 @@
|
|
|
17
17
|
"wasm/lawspec-wasm.cabal"
|
|
18
18
|
],
|
|
19
19
|
"digests": {
|
|
20
|
-
"package.yaml": "
|
|
20
|
+
"package.yaml": "a04aefef41a1245f28d4f1d63ea38a5266b1f5302242c9fc3a840252ea7ce3ec",
|
|
21
21
|
"src/LawSpec/Api.hs": "00b1e4bc81231ebd387f10652463904084ebf9c3c4486fd2c2217a6d4dbfc157",
|
|
22
|
-
"src/LawSpec/Compile.hs": "
|
|
23
|
-
"src/LawSpec/Emit.hs": "
|
|
24
|
-
"src/LawSpec/Gen.hs": "
|
|
25
|
-
"src/LawSpec/Model.hs": "
|
|
26
|
-
"src/LawSpec/Parser.hs": "
|
|
27
|
-
"src/LawSpec/Prelude.hs": "
|
|
22
|
+
"src/LawSpec/Compile.hs": "cb32f1ff2f2ded5e93e25b11658fec6a2ffb1d24e0caaa2ffec94b510f4bfe40",
|
|
23
|
+
"src/LawSpec/Emit.hs": "c72ff20088dcaf74576710e144e79d35f9441f37fc933603033584fdff309fbe",
|
|
24
|
+
"src/LawSpec/Gen.hs": "b3d924fddc7e8417665635f049a3f641ea15c2f6f997571287db17a70b1f37b6",
|
|
25
|
+
"src/LawSpec/Model.hs": "5d550a5b75cd6b00f1b01d276100527a066c35bb69e95adb6b549b6603bab196",
|
|
26
|
+
"src/LawSpec/Parser.hs": "0a70a57b6535cfa33121e9ada595f518ced4f7b35bf09bc4732a1362c05a253f",
|
|
27
|
+
"src/LawSpec/Prelude.hs": "0b60dd85bdf0642077dfb497043c63606d507e71e12d3ed8bfe2bf773214c66b",
|
|
28
28
|
"stack.yaml": "20ccf4d599e355e60b7aa4f814a7cd4299fe2048616cc2e6dbdc22a7bd8cec73",
|
|
29
29
|
"stack.yaml.lock": "ae222b9c81af920c56e50fa4596fa57786e7fa5a7b461390362b2b2ff63818c7",
|
|
30
30
|
"wasm/app/Exports.hs": "4ecbdac8faa2449e6fc61b93c82e6278fa14374154f29b4dacf0eef433f06a47",
|
|
31
31
|
"wasm/cabal.project": "021e560afdc5eb4cb7169e7119ecb8f92c9ee170245af909c94b612941bff5cc",
|
|
32
32
|
"wasm/cabal.project.freeze": "733dbed3d2ecccb26e874fd58f136296dad772184deb1658c196b5d54a0814dc",
|
|
33
|
-
"wasm/lawspec-wasm.cabal": "
|
|
34
|
-
"npm/core.wasm": "
|
|
33
|
+
"wasm/lawspec-wasm.cabal": "9f61a4c9e5c7715d1de2c42e518f3fe9cf088a3677182bda4c3f4601aa930ba9",
|
|
34
|
+
"npm/core.wasm": "674f19955c07f0bd987df27463cb0ba35c5e9756a8d22309aa21bff7488e7ed0",
|
|
35
35
|
"npm/core_jsffi.js": "88d136efe92f7cff5758c8fec8d9b6bbc9707fe37741cebeeb415fe34ce3d72b",
|
|
36
36
|
"npm/api.mjs": "d6df654600172131ac66a55b86876fc29cde2a76a0a63bbefff3183488a4837b",
|
|
37
|
-
"npm/index.d.ts": "
|
|
37
|
+
"npm/index.d.ts": "94c4842654d280796aeedcf7b438ffc63e96d0ff639203f93d04610f1de07c53"
|
|
38
38
|
}
|
|
39
39
|
}
|
package/core.wasm
CHANGED
|
Binary file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
unit example.canonical_url
|
|
2
|
+
|
|
3
|
+
canonicalize :: Text -> Text
|
|
4
|
+
|
|
5
|
+
law `canonicalization reaches a fixed point` is
|
|
6
|
+
definition is
|
|
7
|
+
`idempotent` canonicalize
|
|
8
|
+
end
|
|
9
|
+
description is
|
|
10
|
+
"{canonicalize} removes trailing slashes until the result is stable"
|
|
11
|
+
end
|
|
12
|
+
example `trailing slashes` is
|
|
13
|
+
x = "https://example.com/path///"
|
|
14
|
+
end
|
|
15
|
+
example `already canonical` is
|
|
16
|
+
x = "https://example.com/path"
|
|
17
|
+
end
|
|
18
|
+
example `empty` is
|
|
19
|
+
x = ""
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
unit example.mixed.inputs
|
|
2
|
+
|
|
3
|
+
normalize :: Text -> Text
|
|
4
|
+
identity :: Int32 -> Int32
|
|
5
|
+
|
|
6
|
+
law `text and integer inputs remain independent` is
|
|
7
|
+
definition is
|
|
8
|
+
`for all` (text :: Text) (number :: Int32) .
|
|
9
|
+
normalize text = normalize (normalize text)
|
|
10
|
+
end
|
|
11
|
+
example `mixed inputs` is
|
|
12
|
+
text = "Hello, World!"
|
|
13
|
+
number = -42
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
law `integer behavior with a text context` is
|
|
18
|
+
definition is
|
|
19
|
+
`for all` (text :: Text) (number :: Int32) .
|
|
20
|
+
identity number = number
|
|
21
|
+
end
|
|
22
|
+
example `Unicode context` is
|
|
23
|
+
text = "λ 😀"
|
|
24
|
+
number = 2147483647
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
law `normalizing an empty literal` is
|
|
29
|
+
definition is
|
|
30
|
+
`for all` (text :: Text) .
|
|
31
|
+
normalize "" = ""
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
unit example.slug
|
|
2
|
+
|
|
3
|
+
normalize :: Text -> Text
|
|
4
|
+
referenceNormalize :: Text -> Text
|
|
5
|
+
|
|
6
|
+
law `normalizers agree` is
|
|
7
|
+
definition is
|
|
8
|
+
`equivalent` normalize referenceNormalize
|
|
9
|
+
end
|
|
10
|
+
description is
|
|
11
|
+
"{normalize} and {referenceNormalize} replace ASCII spaces with hyphens"
|
|
12
|
+
end
|
|
13
|
+
example `ordinary text` is
|
|
14
|
+
x = "Hello, World!"
|
|
15
|
+
end
|
|
16
|
+
example `empty` is
|
|
17
|
+
x = ""
|
|
18
|
+
end
|
|
19
|
+
example `Unicode` is
|
|
20
|
+
x = "café 日本語 😀"
|
|
21
|
+
end
|
|
22
|
+
example `escaped text` is
|
|
23
|
+
x = "quote: \" slash: \\ newline: \n tab: \t dollar: $"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { readFile, readdir, mkdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createCompiler } from "./api.mjs";
|
|
4
|
+
import { targets } from "./templates.mjs";
|
|
5
|
+
import { safePath, planWrites, applyWrites } from "./files.mjs";
|
|
6
|
+
|
|
7
|
+
export async function generateExamples(options) {
|
|
8
|
+
const selected = options.target ? [options.target] : targets;
|
|
9
|
+
if (selected.some((t) => !targets.includes(t)))
|
|
10
|
+
throw new Error(`Unknown target: ${options.target}`);
|
|
11
|
+
const directory = new URL("./examples/specs/", import.meta.url);
|
|
12
|
+
const sources = await Promise.all(
|
|
13
|
+
(await readdir(directory))
|
|
14
|
+
.filter((n) => n.endsWith(".lawspec"))
|
|
15
|
+
.sort()
|
|
16
|
+
.map(async (name) => ({
|
|
17
|
+
path: name,
|
|
18
|
+
content: await readFile(new URL(name, directory), "utf8"),
|
|
19
|
+
})),
|
|
20
|
+
);
|
|
21
|
+
const compiler = await createCompiler();
|
|
22
|
+
const artifacts = [];
|
|
23
|
+
for (const target of selected) {
|
|
24
|
+
const result = await compiler.planGeneration({ sources, target });
|
|
25
|
+
if (result.diagnostics.length)
|
|
26
|
+
throw Object.assign(new Error("Bundled examples failed to compile"), {
|
|
27
|
+
diagnostics: result.diagnostics,
|
|
28
|
+
});
|
|
29
|
+
artifacts.push(result.files);
|
|
30
|
+
}
|
|
31
|
+
const root = await safePath(
|
|
32
|
+
process.cwd(),
|
|
33
|
+
options.output || "example_artifacts",
|
|
34
|
+
);
|
|
35
|
+
await mkdir(root, { recursive: true });
|
|
36
|
+
const plans = [];
|
|
37
|
+
for (let i = 0; i < selected.length; i++) {
|
|
38
|
+
const targetRoot = await safePath(root, selected[i]);
|
|
39
|
+
await mkdir(targetRoot, { recursive: true });
|
|
40
|
+
plans.push(await planWrites(targetRoot, artifacts[i]));
|
|
41
|
+
}
|
|
42
|
+
await applyWrites(plans);
|
|
43
|
+
return plans.map((plan, i) => ({
|
|
44
|
+
target: selected[i],
|
|
45
|
+
directory: plan.root,
|
|
46
|
+
files: artifacts[i].map((f) => ({ path: f.path, ownership: f.ownership })),
|
|
47
|
+
changes: plan.changes.length,
|
|
48
|
+
preservedAdapters: plan.preserved,
|
|
49
|
+
adapterUpdates: plan.adapterUpdates,
|
|
50
|
+
}));
|
|
51
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -5,9 +5,9 @@ export interface Location { file: string; line: number; column: number }
|
|
|
5
5
|
export interface Diagnostic { code: string; message: string; at: Location | null }
|
|
6
6
|
export interface Artifact { path: string; content: string; ownership: 'user' | 'generated' }
|
|
7
7
|
export type Type = {tag: 'Named' | 'Variable'; contents: string} | {tag: 'Arrow'; contents: [Type, Type]};
|
|
8
|
-
export type Expr = {tag: 'Var'; contents: string} | {tag: 'Number'; contents: number} | {tag: 'Apply' | 'Compose'; contents: [Expr, Expr]};
|
|
8
|
+
export type Expr = {tag: 'Var' | 'StringLit'; contents: string} | {tag: 'Number'; contents: number} | {tag: 'Apply' | 'Compose'; contents: [Expr, Expr]};
|
|
9
9
|
export type Definition = {tag: 'Forall'; contents: [[string, Type][], Definition]} | {tag: 'Equal'; contents: [Expr, Expr]} | {tag: 'Invoke'; contents: [string, Expr[]]};
|
|
10
|
-
export interface Example { exampleName: string; bindings: [string, number][] }
|
|
10
|
+
export interface Example { exampleName: string; bindings: [string, number | string][] }
|
|
11
11
|
export interface Law { lawName: string; parameters: [string, Type][]; requirements: Type[]; definition: Definition; description: string; rationale: string; examples: Example[]; references: string[]; location: Location }
|
|
12
12
|
export interface Expanded { owner: string; name: string; inputs: {inputName: string; inputId: string; inputType: Type}[]; left: Expr; right: Expr; trace: string[]; original: Law }
|
|
13
13
|
export interface CheckRequest { sources: Source[] }
|