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/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 { loadCore } from './launcher.mjs';
2
+ import {loadCore} from './launcher.mjs';
3
+
3
4
  export async function createCompiler() {
4
5
  const call = await loadCore();
5
- return { check: (input) => call({ ...input, method: 'check' }), expand: (input) => call({ ...input, method: 'expand' }), planGeneration: (input) => call({ ...input, method: 'planGeneration' }) };
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 showType(type) {
157
- if (type.tag === 'Named' || type.tag === 'Variable') return type.contents;
158
- if (type.tag === 'Applied') return `${type.contents[0]} (${showType(type.contents[1])})`;
159
- return `${showType(type.contents[0])} -> ${showType(type.contents[1])}`;
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.original.examples
181
- .map(
182
- (ex) =>
183
- `\nexample ${JSON.stringify(ex.exampleName)}\n` +
184
- ex.bindings
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.7.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>, --machine-bits <32|64>, --json\nGeneration: --dry-run, --check\nTargets: " +
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.7.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 --json only");
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} executable law(s).`,
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 executable law");
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)