lawspec 0.3.0 → 0.4.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 +66 -21
- package/bin/lawspec.mjs +30 -3
- package/build.json +9 -9
- package/core.wasm +0 -0
- package/examples/specs/atoi_codec.lawspec +6 -2
- package/examples/specs/canonical_url.lawspec +6 -3
- package/examples/specs/equivalent.lawspec +12 -4
- package/examples/specs/mixed_inputs.lawspec +12 -4
- package/examples/specs/slug.lawspec +12 -4
- package/index.d.ts +2 -1
- package/package.json +1 -1
- package/starter.lawspec +6 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**State the law once. Check it everywhere.**
|
|
4
4
|
|
|
5
|
-
LawSpec 0.
|
|
5
|
+
LawSpec 0.4 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
|
|
|
@@ -11,7 +11,7 @@ WebAssembly with a Node CLI and an asynchronous, typed JavaScript API.
|
|
|
11
11
|
Install [LawSpec from npm](https://www.npmjs.com/package/lawspec):
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
|
-
npm install --save-dev lawspec@0.
|
|
14
|
+
npm install --save-dev lawspec@0.4.0
|
|
15
15
|
npx lawspec --version
|
|
16
16
|
```
|
|
17
17
|
|
|
@@ -25,8 +25,8 @@ local dependencies so LawSpec can create its `package.json` and test script:
|
|
|
25
25
|
```sh
|
|
26
26
|
mkdir lawspec-example
|
|
27
27
|
cd lawspec-example
|
|
28
|
-
npm exec --package=lawspec@0.
|
|
29
|
-
npm install --save-dev lawspec@0.
|
|
28
|
+
npm exec --package=lawspec@0.4.0 -- lawspec init --target javascript
|
|
29
|
+
npm install --save-dev lawspec@0.4.0
|
|
30
30
|
npx lawspec check
|
|
31
31
|
npx lawspec explain 'example.atoi_codec::itoa and then atoi yields a'
|
|
32
32
|
npx lawspec doctor
|
|
@@ -52,7 +52,7 @@ properties with the selected framework's shrinking and failure reporting.
|
|
|
52
52
|
|
|
53
53
|
| Target | Build setup | Test libraries | Test command |
|
|
54
54
|
| --- | --- | --- | --- |
|
|
55
|
-
| `java` | Maven, JDK 25, release 25 | JetCheck 0.
|
|
55
|
+
| `java` | Maven, JDK 25, release 25 | JetCheck 0.4.0, JUnit Jupiter 5.14.x | `mvn test` |
|
|
56
56
|
| `python` | Python 3.13 or 3.14, pyproject | pytest 8.4.x, Hypothesis 6.135.26+ (6.x) | `python -m pytest` |
|
|
57
57
|
| `javascript` | Node 22+, npm, ESM | fast-check 4.x, node:test | `npm test` |
|
|
58
58
|
| `typescript` | Node 22+, npm, TypeScript 5.9.x, ESM | fast-check 4.x, node:test | `npm test` |
|
|
@@ -61,7 +61,7 @@ properties with the selected framework's shrinking and failure reporting.
|
|
|
61
61
|
| `kotlin` | JDK/JVM 25, Gradle 9.1–9.3, Kotlin 2.3.21 | Kotest 5.9.1 | `gradle test` |
|
|
62
62
|
|
|
63
63
|
Java 25 and Python 3.13 are the minimum baselines. New JVM releases are admitted
|
|
64
|
-
through compatibility profiles after testing; v0.
|
|
64
|
+
through compatibility profiles after testing; v0.4's current JVM profile certifies
|
|
65
65
|
25. Python templates declare `requires-python = ">=3.13"` and runtime checks
|
|
66
66
|
currently recognize 3.13 and 3.14. Kotlin templates pin Gradle's supported build
|
|
67
67
|
configuration to Kotlin 2.3.21 and target JVM 25.
|
|
@@ -113,7 +113,7 @@ uses `test/Spec.hs` with `hspec-discover`.
|
|
|
113
113
|
Commands:
|
|
114
114
|
|
|
115
115
|
- `check`: parse, resolve, type-check and expand laws without target dependencies.
|
|
116
|
-
- `explain [unit::law]`: display
|
|
116
|
+
- `explain [unit::law]`: display expansions, example inputs, and expected results.
|
|
117
117
|
- `doctor`: inspect selected native environments and print corrective instructions.
|
|
118
118
|
- `generate`: check environments, validate every output, then write artifacts.
|
|
119
119
|
- `generate --dry-run`: show proposed file operations without applying them.
|
|
@@ -139,8 +139,10 @@ law `round trip` is
|
|
|
139
139
|
description is
|
|
140
140
|
"applying {itoa} and then {atoi} recovers the original integer"
|
|
141
141
|
end
|
|
142
|
-
example `negative` is
|
|
142
|
+
example `negative integers use a minus sign and round-trip unchanged` is
|
|
143
143
|
x = -42
|
|
144
|
+
expect itoa x = "-42"
|
|
145
|
+
expect atoi (itoa x) = -42
|
|
144
146
|
end
|
|
145
147
|
end
|
|
146
148
|
```
|
|
@@ -158,7 +160,7 @@ for all (x :: Int32) . atoi (itoa (x)) = x
|
|
|
158
160
|
Reusable laws can declare typed unary function parameters and `requires Eq a`.
|
|
159
161
|
Definitions support law application, function application/composition, universal
|
|
160
162
|
quantification, integer and text literals, and equality. Function signatures use `Int32`
|
|
161
|
-
and `Text`; generic variables are supported in reusable laws. v0.
|
|
163
|
+
and `Text`; generic variables are supported in reusable laws. v0.4 generates
|
|
162
164
|
quantified `Int32` and `Text` inputs, including mixed and multiple inputs. Both
|
|
163
165
|
types can also be intermediate or compared results. Functions are synchronous
|
|
164
166
|
and unary. Text literals are double-quoted, with escapes such as `\"`, `\\`,
|
|
@@ -168,7 +170,7 @@ Text values contain Unicode scalar values; surrogate code points are rejected.
|
|
|
168
170
|
Examples refer to the expanded input names, including names inherited from the
|
|
169
171
|
prelude. Bind every input exactly once. Ambiguous names and out-of-range values
|
|
170
172
|
are errors. Descriptions and rationales use `{function}` references; `{{` and `}}`
|
|
171
|
-
produce literal braces.
|
|
173
|
+
produce literal braces. Law blocks use this order:
|
|
172
174
|
definition, optional description, optional rationale, examples, optional references.
|
|
173
175
|
`--` starts a line comment. Names that cannot be emitted portably are diagnosed.
|
|
174
176
|
|
|
@@ -176,6 +178,40 @@ Additional primitives, external law packages, cross-unit imports beyond the
|
|
|
176
178
|
prelude, async functions, direct existing-symbol binding and browser hosting are
|
|
177
179
|
outside this release.
|
|
178
180
|
|
|
181
|
+
## Expected results and migration to 0.4
|
|
182
|
+
|
|
183
|
+
Every `example` must bind all quantified inputs and then include one or more
|
|
184
|
+
`expect <expression> = <literal>` assertions. The expected literal must have the
|
|
185
|
+
same `Int32` or `Text` type as the expression. Expressions can reference the
|
|
186
|
+
example's inputs and the unit's functions, including composed function calls.
|
|
187
|
+
Input names shadow function names within expectations, following lexical scope.
|
|
188
|
+
|
|
189
|
+
An example passes only when **all its expected results and its enclosing law**
|
|
190
|
+
pass. Expected results are authored specifications, never inferred by executing
|
|
191
|
+
your adapter. They apply to that example's inputs; randomized and boundary tests
|
|
192
|
+
continue to check the general law. Generated assertions show compared values and
|
|
193
|
+
identify the example, input bindings and expression. A failing assertion stops
|
|
194
|
+
that individual test; other tests remain independent.
|
|
195
|
+
|
|
196
|
+
This is an intentional syntax break from 0.3: input-only examples are rejected,
|
|
197
|
+
and `expect` is now a reserved keyword. Laws can still omit examples altogether.
|
|
198
|
+
For an existing input-only example, retain its bindings and add the intended
|
|
199
|
+
result before `end`:
|
|
200
|
+
|
|
201
|
+
```lawspec
|
|
202
|
+
example `zero renders as 0 and round-trips unchanged` is
|
|
203
|
+
x = 0
|
|
204
|
+
expect itoa x = "0"
|
|
205
|
+
expect atoi (itoa x) = 0
|
|
206
|
+
end
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Update every example before running `check` or `generate`. Use
|
|
210
|
+
`npx lawspec explain` to inspect the law expansion, example inputs and expected
|
|
211
|
+
results; this command displays the specification without executing adapters.
|
|
212
|
+
Existing implementation adapters remain user-owned and are never overwritten.
|
|
213
|
+
The historical `scratch.md` is a design draft, not the current syntax reference.
|
|
214
|
+
|
|
179
215
|
## Comparing alternative implementations
|
|
180
216
|
|
|
181
217
|
`equivalent` compares two functions with the same input and output types. Its
|
|
@@ -192,8 +228,10 @@ law `decimal renderers agree` is
|
|
|
192
228
|
definition is
|
|
193
229
|
`equivalent` render referenceRender
|
|
194
230
|
end
|
|
195
|
-
example `negative
|
|
231
|
+
example `both renderers produce a negative decimal string` is
|
|
196
232
|
x = -42
|
|
233
|
+
expect render x = "-42"
|
|
234
|
+
expect referenceRender x = "-42"
|
|
197
235
|
end
|
|
198
236
|
end
|
|
199
237
|
```
|
|
@@ -202,7 +240,7 @@ This expands to `for all (x :: Int32) . render (x) = referenceRender (x)`.
|
|
|
202
240
|
The example inherits the input name `x` from the prelude. Both functions are
|
|
203
241
|
user-owned adapter functions; either may delegate to your existing code.
|
|
204
242
|
|
|
205
|
-
[The complete example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
243
|
+
[The complete example](https://github.com/brain-fuel/lawspec/blob/v0.4.0/examples/specs/equivalent.lawspec) compares decimal
|
|
206
244
|
renderers and two implementations that clamp negative integers to zero. For
|
|
207
245
|
JavaScript, their adapters can be:
|
|
208
246
|
|
|
@@ -215,9 +253,9 @@ export const referenceClamp = x => x < 0 ? 0 : x;
|
|
|
215
253
|
|
|
216
254
|
The same specification generates native tests for all seven targets. The
|
|
217
255
|
integration suite checks both examples with matching implementations, then
|
|
218
|
-
breaks each alternative separately to verify detection.
|
|
219
|
-
|
|
220
|
-
|
|
256
|
+
breaks each alternative separately to verify detection. The general equivalence law alone does not establish independent correctness;
|
|
257
|
+
two implementations can share the same bug. Explicit expectations additionally
|
|
258
|
+
check the specified outputs at the supplied example inputs. Quantified inputs can be `Int32` or `Text`.
|
|
221
259
|
|
|
222
260
|
## Text properties and idempotence
|
|
223
261
|
|
|
@@ -231,13 +269,15 @@ law `normalizers agree` is
|
|
|
231
269
|
definition is
|
|
232
270
|
`equivalent` normalize referenceNormalize
|
|
233
271
|
end
|
|
234
|
-
example `
|
|
272
|
+
example `spaces become hyphens; punctuation is preserved` is
|
|
235
273
|
x = "Hello, World!"
|
|
274
|
+
expect normalize x = "Hello,-World!"
|
|
275
|
+
expect referenceNormalize x = "Hello,-World!"
|
|
236
276
|
end
|
|
237
277
|
end
|
|
238
278
|
```
|
|
239
279
|
|
|
240
|
-
The [slug example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
280
|
+
The [slug example](https://github.com/brain-fuel/lawspec/blob/v0.4.0/examples/specs/slug.lawspec)
|
|
241
281
|
compares two implementations of ASCII-space replacement. It includes empty,
|
|
242
282
|
Unicode and escaped text. Each target uses its native string generator:
|
|
243
283
|
JetCheck `Generator.stringsOf(Generator.asciiPrintableChars())`, Hypothesis `st.text()`, fast-check `fc.string()`,
|
|
@@ -257,10 +297,14 @@ law `canonicalization reaches a fixed point` is
|
|
|
257
297
|
definition is
|
|
258
298
|
`idempotent` canonicalize
|
|
259
299
|
end
|
|
300
|
+
example `all trailing slashes are removed in one pass` is
|
|
301
|
+
x = "https://example.com/path///"
|
|
302
|
+
expect canonicalize x = "https://example.com/path"
|
|
303
|
+
end
|
|
260
304
|
end
|
|
261
305
|
```
|
|
262
306
|
|
|
263
|
-
The [canonical URL example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
307
|
+
The [canonical URL example](https://github.com/brain-fuel/lawspec/blob/v0.4.0/examples/specs/canonical_url.lawspec)
|
|
264
308
|
uses removal of **all trailing slashes** as a small fixed-point demonstration,
|
|
265
309
|
not a complete URL canonicalization algorithm. For JavaScript:
|
|
266
310
|
|
|
@@ -269,9 +313,10 @@ export const canonicalize = value => value.replace(/\/+$/, "");
|
|
|
269
313
|
```
|
|
270
314
|
|
|
271
315
|
Removing just one trailing slash fails the supplied repeated-slash example.
|
|
272
|
-
The [mixed-input example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
316
|
+
The [mixed-input example](https://github.com/brain-fuel/lawspec/blob/v0.4.0/examples/specs/mixed_inputs.lawspec)
|
|
273
317
|
shows `Text` and `Int32` in the same quantified property and executable example.
|
|
274
|
-
The JavaScript API represents
|
|
318
|
+
The JavaScript API represents input bindings and expected values as `number | string`.
|
|
319
|
+
Each example includes `expectations: { actual: Expr; expected: number | string }[]`.
|
|
275
320
|
|
|
276
321
|
## Generate all example artifacts
|
|
277
322
|
|
|
@@ -333,7 +378,7 @@ by the JS shim.
|
|
|
333
378
|
## Build and verify
|
|
334
379
|
|
|
335
380
|
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.
|
|
381
|
+
`npm pack ./npm` and install it with `npm install --save-dev ./lawspec-0.4.0.tgz`.
|
|
337
382
|
The package payload lives in `npm/`.
|
|
338
383
|
|
|
339
384
|
```sh
|
package/bin/lawspec.mjs
CHANGED
|
@@ -151,16 +151,43 @@ async function init() {
|
|
|
151
151
|
`Configured ${language}. ${hasBuild ? "Existing build files preserved." : "Created missing project build files."}\n${setup[language]}\nNext: lawspec doctor, then lawspec generate.`,
|
|
152
152
|
);
|
|
153
153
|
}
|
|
154
|
+
function showExpression(expr) {
|
|
155
|
+
const value = expr.contents;
|
|
156
|
+
if (expr.tag === "Var") return value;
|
|
157
|
+
if (expr.tag === "Number" || expr.tag === "StringLit")
|
|
158
|
+
return JSON.stringify(value);
|
|
159
|
+
if (expr.tag === "Apply")
|
|
160
|
+
return `${showExpression(value[0])} (${showExpression(value[1])})`;
|
|
161
|
+
return `(${showExpression(value[0])} . ${showExpression(value[1])})`;
|
|
162
|
+
}
|
|
163
|
+
function explainExamples(law) {
|
|
164
|
+
return law.original.examples
|
|
165
|
+
.map(
|
|
166
|
+
(ex) =>
|
|
167
|
+
`\nexample ${JSON.stringify(ex.exampleName)}\n` +
|
|
168
|
+
ex.bindings
|
|
169
|
+
.map(([n, v]) => ` ${n} = ${JSON.stringify(v)}`)
|
|
170
|
+
.join("\n") +
|
|
171
|
+
"\n" +
|
|
172
|
+
ex.expectations
|
|
173
|
+
.map(
|
|
174
|
+
(e) =>
|
|
175
|
+
` expect ${showExpression(e.actual)} = ${JSON.stringify(e.expected)}`,
|
|
176
|
+
)
|
|
177
|
+
.join("\n"),
|
|
178
|
+
)
|
|
179
|
+
.join("\n");
|
|
180
|
+
}
|
|
154
181
|
async function main() {
|
|
155
182
|
if (!verb || ["help", "--help", "-h"].includes(verb)) {
|
|
156
183
|
output(
|
|
157
|
-
"LawSpec 0.
|
|
184
|
+
"LawSpec 0.4.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: " +
|
|
158
185
|
targets.join(", "),
|
|
159
186
|
);
|
|
160
187
|
return;
|
|
161
188
|
}
|
|
162
189
|
if (verb === "--version") {
|
|
163
|
-
output("0.
|
|
190
|
+
output("0.4.0");
|
|
164
191
|
return;
|
|
165
192
|
}
|
|
166
193
|
if (positional.length > (verb === "explain" ? 1 : 0))
|
|
@@ -250,7 +277,7 @@ async function main() {
|
|
|
250
277
|
: indices
|
|
251
278
|
.map(
|
|
252
279
|
({ e, i }) =>
|
|
253
|
-
`${e.owner}::${e.name}\n${e.trace.join("\n=> ")}\n=> ${result.expansions[i]}`,
|
|
280
|
+
`${e.owner}::${e.name}\n${e.trace.join("\n=> ")}\n=> ${result.expansions[i]}${explainExamples(e)}`,
|
|
254
281
|
)
|
|
255
282
|
.join("\n\n"),
|
|
256
283
|
);
|
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": "2c1176c912901bd218743a88df1b67fb1ee8d58ed73fcbe7e2f23827f10a3a0f",
|
|
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": "
|
|
22
|
+
"src/LawSpec/Compile.hs": "336eca415ed6c8594612ad5acfc7ff6f29ef387cf3cb374b60b2407f811c31c7",
|
|
23
|
+
"src/LawSpec/Emit.hs": "2e23ca38aa5c4c65305283705633209843dd6ba116c43ba8a42ee8731040f7c3",
|
|
24
|
+
"src/LawSpec/Gen.hs": "305d7710dbc69b25d95fa6c0ac9a59622481f6b89682c1e5c9048def3962315a",
|
|
25
|
+
"src/LawSpec/Model.hs": "d1eda2b420edbc327b1adbf437c6f39a9e576ef0ab9b82f468c28202a433eb96",
|
|
26
|
+
"src/LawSpec/Parser.hs": "7fef1876c52d2204f852704150288910bb0c12bcde1d3159219ad25526a879b6",
|
|
27
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": "098f1d0fc52575907312c95095846d1d4f6e7ace7cfbe0419a305b9fe8a75a92",
|
|
34
|
+
"npm/core.wasm": "0a4ce15cefcd866cd7dd584c32d683c7f8a46823104afcceba1298570883a88a",
|
|
35
35
|
"npm/core_jsffi.js": "88d136efe92f7cff5758c8fec8d9b6bbc9707fe37741cebeeb415fe34ce3d72b",
|
|
36
36
|
"npm/api.mjs": "d6df654600172131ac66a55b86876fc29cde2a76a0a63bbefff3183488a4837b",
|
|
37
|
-
"npm/index.d.ts": "
|
|
37
|
+
"npm/index.d.ts": "85ad409ae09d243d3b3f21243d0784390d7b0209e2a09fc7c3b733e8dd2e8765"
|
|
38
38
|
}
|
|
39
39
|
}
|
package/core.wasm
CHANGED
|
Binary file
|
|
@@ -16,12 +16,16 @@ law `itoa and then atoi yields a` is
|
|
|
16
16
|
"representing an Int32 as Text must not change its value"
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
example `negative
|
|
19
|
+
example `negative integers use a minus sign and round-trip unchanged` is
|
|
20
20
|
x = -42
|
|
21
|
+
expect itoa x = "-42"
|
|
22
|
+
expect atoi (itoa x) = -42
|
|
21
23
|
end
|
|
22
24
|
|
|
23
|
-
example `zero` is
|
|
25
|
+
example `zero renders as 0 and round-trips unchanged` is
|
|
24
26
|
x = 0
|
|
27
|
+
expect itoa x = "0"
|
|
28
|
+
expect atoi (itoa x) = 0
|
|
25
29
|
end
|
|
26
30
|
|
|
27
31
|
references are
|
|
@@ -9,13 +9,16 @@ law `canonicalization reaches a fixed point` is
|
|
|
9
9
|
description is
|
|
10
10
|
"{canonicalize} removes trailing slashes until the result is stable"
|
|
11
11
|
end
|
|
12
|
-
example `trailing slashes` is
|
|
12
|
+
example `all trailing slashes are removed in one pass` is
|
|
13
13
|
x = "https://example.com/path///"
|
|
14
|
+
expect canonicalize x = "https://example.com/path"
|
|
14
15
|
end
|
|
15
|
-
example `
|
|
16
|
+
example `a URL without trailing slashes stays unchanged` is
|
|
16
17
|
x = "https://example.com/path"
|
|
18
|
+
expect canonicalize x = "https://example.com/path"
|
|
17
19
|
end
|
|
18
|
-
example `empty` is
|
|
20
|
+
example `empty input remains empty` is
|
|
19
21
|
x = ""
|
|
22
|
+
expect canonicalize x = ""
|
|
20
23
|
end
|
|
21
24
|
end
|
|
@@ -15,11 +15,15 @@ law `decimal renderers agree` is
|
|
|
15
15
|
rationale is
|
|
16
16
|
"changing the formatting implementation must preserve its result"
|
|
17
17
|
end
|
|
18
|
-
example `
|
|
18
|
+
example `both renderers produce "-42"` is
|
|
19
19
|
x = -42
|
|
20
|
+
expect render x = "-42"
|
|
21
|
+
expect referenceRender x = "-42"
|
|
20
22
|
end
|
|
21
|
-
example `
|
|
23
|
+
example `both renderers produce "0"` is
|
|
22
24
|
x = 0
|
|
25
|
+
expect render x = "0"
|
|
26
|
+
expect referenceRender x = "0"
|
|
23
27
|
end
|
|
24
28
|
end
|
|
25
29
|
|
|
@@ -30,10 +34,14 @@ law `nonnegative clamps agree` is
|
|
|
30
34
|
description is
|
|
31
35
|
"{clamp} agrees with {referenceClamp} when clamping negative inputs to zero"
|
|
32
36
|
end
|
|
33
|
-
example `
|
|
37
|
+
example `both clamps return 0 for -2147483648` is
|
|
34
38
|
x = -2147483648
|
|
39
|
+
expect clamp x = 0
|
|
40
|
+
expect referenceClamp x = 0
|
|
35
41
|
end
|
|
36
|
-
example `
|
|
42
|
+
example `both clamps return 2147483647 for 2147483647` is
|
|
37
43
|
x = 2147483647
|
|
44
|
+
expect clamp x = 2147483647
|
|
45
|
+
expect referenceClamp x = 2147483647
|
|
38
46
|
end
|
|
39
47
|
end
|
|
@@ -3,25 +3,29 @@ unit example.mixed.inputs
|
|
|
3
3
|
normalize :: Text -> Text
|
|
4
4
|
identity :: Int32 -> Int32
|
|
5
5
|
|
|
6
|
-
law `text
|
|
6
|
+
law `text normalization reaches a fixed point with an unused integer input` is
|
|
7
7
|
definition is
|
|
8
8
|
`for all` (text :: Text) (number :: Int32) .
|
|
9
9
|
normalize text = normalize (normalize text)
|
|
10
10
|
end
|
|
11
|
-
example `
|
|
11
|
+
example `text normalization and integer identity have explicit results` is
|
|
12
12
|
text = "Hello, World!"
|
|
13
13
|
number = -42
|
|
14
|
+
expect normalize text = "Hello,-World!"
|
|
15
|
+
expect identity number = -42
|
|
14
16
|
end
|
|
15
17
|
end
|
|
16
18
|
|
|
17
|
-
law `integer
|
|
19
|
+
law `integer identity with an unused text input` is
|
|
18
20
|
definition is
|
|
19
21
|
`for all` (text :: Text) (number :: Int32) .
|
|
20
22
|
identity number = number
|
|
21
23
|
end
|
|
22
|
-
example `
|
|
24
|
+
example `text normalization and integer identity have explicit results` is
|
|
23
25
|
text = "λ 😀"
|
|
24
26
|
number = 2147483647
|
|
27
|
+
expect normalize text = "λ-😀"
|
|
28
|
+
expect identity number = 2147483647
|
|
25
29
|
end
|
|
26
30
|
end
|
|
27
31
|
|
|
@@ -30,4 +34,8 @@ law `normalizing an empty literal` is
|
|
|
30
34
|
`for all` (text :: Text) .
|
|
31
35
|
normalize "" = ""
|
|
32
36
|
end
|
|
37
|
+
example `empty literal stays empty; quantified text is unused` is
|
|
38
|
+
text = "unused context"
|
|
39
|
+
expect normalize "" = ""
|
|
40
|
+
end
|
|
33
41
|
end
|
|
@@ -10,16 +10,24 @@ law `normalizers agree` is
|
|
|
10
10
|
description is
|
|
11
11
|
"{normalize} and {referenceNormalize} replace ASCII spaces with hyphens"
|
|
12
12
|
end
|
|
13
|
-
example `
|
|
13
|
+
example `spaces become hyphens; punctuation is preserved` is
|
|
14
14
|
x = "Hello, World!"
|
|
15
|
+
expect normalize x = "Hello,-World!"
|
|
16
|
+
expect referenceNormalize x = "Hello,-World!"
|
|
15
17
|
end
|
|
16
|
-
example `empty` is
|
|
18
|
+
example `empty text remains empty` is
|
|
17
19
|
x = ""
|
|
20
|
+
expect normalize x = ""
|
|
21
|
+
expect referenceNormalize x = ""
|
|
18
22
|
end
|
|
19
|
-
example `Unicode` is
|
|
23
|
+
example `Unicode is preserved while spaces become hyphens` is
|
|
20
24
|
x = "café 日本語 😀"
|
|
25
|
+
expect normalize x = "café-日本語-😀"
|
|
26
|
+
expect referenceNormalize x = "café-日本語-😀"
|
|
21
27
|
end
|
|
22
|
-
example `
|
|
28
|
+
example `quotes, backslashes and controls survive space replacement` is
|
|
23
29
|
x = "quote: \" slash: \\ newline: \n tab: \t dollar: $"
|
|
30
|
+
expect normalize x = "quote:-\"-slash:-\\-newline:-\n-tab:-\t-dollar:-$"
|
|
31
|
+
expect referenceNormalize x = "quote:-\"-slash:-\\-newline:-\n-tab:-\t-dollar:-$"
|
|
24
32
|
end
|
|
25
33
|
end
|
package/index.d.ts
CHANGED
|
@@ -7,7 +7,8 @@ export interface Artifact { path: string; content: string; ownership: 'user' | '
|
|
|
7
7
|
export type Type = {tag: 'Named' | 'Variable'; contents: string} | {tag: 'Arrow'; contents: [Type, Type]};
|
|
8
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
|
|
10
|
+
export interface Expectation { actual: Expr; expected: number | string }
|
|
11
|
+
export interface Example { exampleName: string; bindings: [string, number | string][]; expectations: Expectation[] }
|
|
11
12
|
export interface Law { lawName: string; parameters: [string, Type][]; requirements: Type[]; definition: Definition; description: string; rationale: string; examples: Example[]; references: string[]; location: Location }
|
|
12
13
|
export interface Expanded { owner: string; name: string; inputs: {inputName: string; inputId: string; inputType: Type}[]; left: Expr; right: Expr; trace: string[]; original: Law }
|
|
13
14
|
export interface CheckRequest { sources: Source[] }
|
package/package.json
CHANGED
package/starter.lawspec
CHANGED
|
@@ -16,12 +16,16 @@ law `itoa and then atoi yields a` is
|
|
|
16
16
|
"representing an Int32 as Text must not change its value"
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
example `negative
|
|
19
|
+
example `negative integers use a minus sign and round-trip unchanged` is
|
|
20
20
|
x = -42
|
|
21
|
+
expect itoa x = "-42"
|
|
22
|
+
expect atoi (itoa x) = -42
|
|
21
23
|
end
|
|
22
24
|
|
|
23
|
-
example `zero` is
|
|
25
|
+
example `zero renders as 0 and round-trips unchanged` is
|
|
24
26
|
x = 0
|
|
27
|
+
expect itoa x = "0"
|
|
28
|
+
expect atoi (itoa x) = 0
|
|
25
29
|
end
|
|
26
30
|
|
|
27
31
|
references are
|