forma-dsl 0.1.5 → 0.2.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 CHANGED
@@ -99,7 +99,7 @@ Hand-written TypeScript declarations ship with it, so the API is typed from eith
99
99
 
100
100
  **[The full reference](https://kalpak44.github.io/forma-dsl/docs/)** — every declaration,
101
101
  block, function and error, each with usage and troubleshooting. The
102
- **[editor](https://kalpak44.github.io/forma-dsl/)** runs the whole thing in the browser:
102
+ **[editor](https://kalpak44.github.io/forma-dsl/editor/)** runs the whole thing in the browser:
103
103
  type, and the solid updates.
104
104
 
105
105
  ## How it works
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forma-dsl",
3
- "version": "0.1.5",
3
+ "version": "0.2.0",
4
4
  "description": "A declarative DSL for 3D modeling and scene composition, reusable components, and live previews.",
5
5
  "keywords": [
6
6
  "cad",
@@ -177,10 +177,17 @@ export class Args {
177
177
  }
178
178
 
179
179
  /**
180
- * Rejects anything the block did not read.
180
+ * Rejects anything the block did not read, and says what it would have read instead.
181
181
  *
182
182
  * A misspelled attribute is otherwise silent, and the model renders subtly wrong with
183
- * nothing to point at.
183
+ * nothing to point at. Naming the alternatives matters as much as the refusal: the reader
184
+ * is often a program with no copy of the manual, and an error that says only what is wrong
185
+ * leaves it guessing at spellings. What it read is tracked already, so the list costs
186
+ * nothing to produce.
187
+ *
188
+ * A block that branches on which of two spellings it was given — `radius` against
189
+ * `diameter` — only reads one of them, so the list is what this call would accept and not
190
+ * always the whole set.
184
191
  *
185
192
  * @returns {void}
186
193
  * @throws {FormaError} If any attribute went unread.
@@ -189,7 +196,12 @@ export class Args {
189
196
  const extra = Object.keys(this.values).filter((k) => !this.used.has(k));
190
197
  if (extra.length) {
191
198
  const plural = extra.length > 1 ? 's' : '';
192
- throw new FormaError(`${this.type}: unknown attribute${plural} ${quoteAll(extra)}`, this.loc);
199
+ const reads = [...this.used].sort((a, z) => a.localeCompare(z));
200
+ const instead = reads.length ? ` — ${this.type} reads ${quoteAll(reads)}` : '';
201
+ throw new FormaError(
202
+ `${this.type}: unknown attribute${plural} ${quoteAll(extra)}${instead}`,
203
+ this.loc,
204
+ );
193
205
  }
194
206
  }
195
207
  }
@@ -7,6 +7,12 @@ import { BLOCKS, FUNCTIONS, CONSTANTS, TYPE_NAMES, Args, quoteAll } from './buil
7
7
  import { GeometryNode } from '../core/node.js';
8
8
  import { Transform } from '../values/transform.js';
9
9
 
10
+ /**
11
+ * The blocks handled before the registry is consulted, so they are missing from `BLOCKS` and
12
+ * would otherwise be absent from the list an unknown block is measured against.
13
+ */
14
+ const SPECIAL_FORMS = ['align', 'for', 'if', 'part'];
15
+
10
16
  /**
11
17
  * What a body produces: geometry to hand upwards, and any scene parts declared inside it.
12
18
  *
@@ -447,7 +453,19 @@ export class Evaluator {
447
453
 
448
454
  const definition = BLOCKS[block.type];
449
455
  if (!definition) {
450
- throw new FormaError(`unknown block "${block.type}"`, block.loc);
456
+ // The whole vocabulary, because the reader is often a program with no copy of the
457
+ // manual: a misspelling it cannot correct from the error costs a round trip it has no
458
+ // way to end. Declared components come first — they are the names this document itself
459
+ // introduced, and the likeliest thing a near miss was reaching for.
460
+ const known = [
461
+ ...[...this.program.components.keys()].sort((a, z) => a.localeCompare(z)),
462
+ ...SPECIAL_FORMS,
463
+ ...Object.keys(BLOCKS).sort((a, z) => a.localeCompare(z)),
464
+ ];
465
+ throw new FormaError(
466
+ `unknown block "${block.type}" — the blocks are ${quoteAll(known)}`,
467
+ block.loc,
468
+ );
451
469
  }
452
470
  if (block.labels.length) {
453
471
  throw new FormaError(`"${block.type}" does not take a label`, block.loc);