chalk-talk 0.6.0 → 0.7.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.
@@ -1,4 +1,4 @@
1
- // GENERATED by tools/assets.mjs from the drawing engine v0.241.2. Do not edit.
1
+ // GENERATED by tools/assets.mjs from the drawing engine v0.243.2. Do not edit.
2
2
  var WORDS = ["and", "or", "not"];
3
3
 
4
4
  var PROP_NAMES = [
@@ -327,34 +327,34 @@ var STEP_TYPES = [
327
327
  var PLACEABLE = ["text", "prop", "card", "counter", "circle", "ellipse"];
328
328
  var ARRANGEMENTS = ["row", "column", "grid"];
329
329
  var MAX_REPEAT = 50;
330
- function object(value, name) {
331
- if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${name}: expected an object.`);
330
+ function object(value2, name) {
331
+ if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) throw new Error(`${name}: expected an object.`);
332
332
  }
333
- function text(value, name) {
334
- if (typeof value !== "string" || !value.trim() || value.length > 2e3) {
333
+ function text(value2, name) {
334
+ if (typeof value2 !== "string" || !value2.trim() || value2.length > 2e3) {
335
335
  throw new Error(`${name}: expected text of 1 to 2000 characters.`);
336
336
  }
337
337
  }
338
- function finite(value, name, min = -1e4, max = 1e4) {
339
- if (typeof value !== "number" || !Number.isFinite(value) || value < min || value > max) {
338
+ function finite(value2, name, min = -1e4, max = 1e4) {
339
+ if (typeof value2 !== "number" || !Number.isFinite(value2) || value2 < min || value2 > max) {
340
340
  throw new Error(`${name}: expected a number between ${min} and ${max}.`);
341
341
  }
342
342
  }
343
- function numeric(value, name) {
344
- if (typeof value === "string") {
345
- text(value, name);
346
- if (value.length > 300) throw new Error(`${name}: expression too long.`);
347
- } else finite(value, name);
343
+ function numeric(value2, name) {
344
+ if (typeof value2 === "string") {
345
+ text(value2, name);
346
+ if (value2.length > 300) throw new Error(`${name}: expression too long.`);
347
+ } else finite(value2, name);
348
348
  }
349
- function point(value, name) {
350
- if (!Array.isArray(value) || value.length !== 2) throw new Error(`${name}: expected [x, y].`);
351
- value.forEach((v, i) => numeric(v, `${name}[${i}]`));
349
+ function point(value2, name) {
350
+ if (!Array.isArray(value2) || value2.length !== 2) throw new Error(`${name}: expected [x, y].`);
351
+ value2.forEach((v, i) => numeric(v, `${name}[${i}]`));
352
352
  }
353
- function optionalText(value, name) {
354
- if (value !== void 0) text(value, name);
353
+ function optionalText(value2, name) {
354
+ if (value2 !== void 0) text(value2, name);
355
355
  }
356
- function choice(value, choices, name) {
357
- if (value !== void 0 && (typeof value !== "string" || !choices.includes(value))) {
356
+ function choice(value2, choices, name) {
357
+ if (value2 !== void 0 && (typeof value2 !== "string" || !choices.includes(value2))) {
358
358
  throw new Error(`${name}: choose one of ${choices.join(", ")}.`);
359
359
  }
360
360
  }
@@ -364,215 +364,215 @@ function identifier(key) {
364
364
  }
365
365
  }
366
366
  var known = (ids, id) => id.includes("{") || ids.exact.has(id) || [...ids.repeated].some((r) => id.startsWith(`${r}-`) && /^\d+$/.test(id.slice(r.length + 1)));
367
- function pointOrPlaced(step, key, name, placed) {
368
- if (placed && step[key] === void 0) step[key] = [0, 0];
369
- point(step[key], `${name}.${key}`);
367
+ function pointOrPlaced(step2, key, name, placed) {
368
+ if (placed && step2[key] === void 0) step2[key] = [0, 0];
369
+ point(step2[key], `${name}.${key}`);
370
370
  }
371
- function validateStep(step, name, ids, inside, inRow = false) {
372
- object(step, name);
371
+ function validateStep(step2, name, ids, inside, inRow = false) {
372
+ object(step2, name);
373
373
  if (++ids.count > 500) throw new Error(`${name}: a recipe draws at most 500 steps, counting those inside repeat and when.`);
374
374
  if (inside) {
375
375
  for (const key of ["chapter", "say", "duration"]) {
376
- if (step[key] !== void 0) throw new Error(`${name}.${key}: put it on the ${inside} step itself, not on a step inside it.`);
376
+ if (step2[key] !== void 0) throw new Error(`${name}.${key}: put it on the ${inside} step itself, not on a step inside it.`);
377
377
  }
378
- if (step.type === "pause") throw new Error(`${name}: a pause cannot go inside ${inside}.`);
379
- if (step.type === "repeat" && inside === "repeat") throw new Error(`${name}: a repeat cannot go inside another repeat.`);
380
- if (ARRANGEMENTS.includes(inside) && step.place !== void 0) throw new Error(`${name}.place: the ${inside} places the steps inside it.`);
378
+ if (step2.type === "pause") throw new Error(`${name}: a pause cannot go inside ${inside}.`);
379
+ if (step2.type === "repeat" && inside === "repeat") throw new Error(`${name}: a repeat cannot go inside another repeat.`);
380
+ if (ARRANGEMENTS.includes(inside) && step2.place !== void 0) throw new Error(`${name}.place: the ${inside} places the steps inside it.`);
381
381
  }
382
- if (!STEP_TYPES.includes(step.type)) throw new Error(`${name}: unknown type "${step.type}".`);
383
- for (const key of ["id", "say", "chapter"]) optionalText(step[key], `${name}.${key}`);
384
- if (step.duration !== void 0) finite(step.duration, `${name}.duration`, 0, 3e4);
385
- if (step.nudge !== void 0) {
386
- if (["pause", "move", "link"].includes(step.type)) throw new Error(`${name}.nudge: a ${step.type} has no place of its own to move.`);
387
- if (!Array.isArray(step.nudge) || step.nudge.length !== 2) throw new Error(`${name}.nudge: use [dx, dy].`);
388
- step.nudge.forEach((v, k) => finite(v, `${name}.nudge[${k}]`, -900, 900));
382
+ if (!STEP_TYPES.includes(step2.type)) throw new Error(`${name}: unknown type "${step2.type}".`);
383
+ for (const key of ["id", "say", "chapter"]) optionalText(step2[key], `${name}.${key}`);
384
+ if (step2.duration !== void 0) finite(step2.duration, `${name}.duration`, 0, 3e4);
385
+ if (step2.nudge !== void 0) {
386
+ if (["pause", "move", "link"].includes(step2.type)) throw new Error(`${name}.nudge: a ${step2.type} has no place of its own to move.`);
387
+ if (!Array.isArray(step2.nudge) || step2.nudge.length !== 2) throw new Error(`${name}.nudge: use [dx, dy].`);
388
+ step2.nudge.forEach((v, k) => finite(v, `${name}.nudge[${k}]`, -900, 900));
389
389
  }
390
390
  const arranged = inside !== null && ARRANGEMENTS.includes(inside) || inRow;
391
- if (step.place !== void 0) {
392
- if (!PLACEABLE.includes(step.type)) throw new Error(`${name}.place: only ${PLACEABLE.join(", ")} can be placed.`);
393
- object(step.place, `${name}.place`);
394
- text(step.place.to, `${name}.place.to`);
395
- if (!known(ids, String(step.place.to))) throw new Error(`${name}.place.to: no earlier step has the id "${step.place.to}".`);
396
- if (!["below", "above", "left", "right"].includes(step.place.side)) throw new Error(`${name}.place.side: choose one of below, above, left, right.`);
397
- if (step.place.gap !== void 0) finite(step.place.gap, `${name}.place.gap`, -200, 400);
398
- choice(step.place.align, ["center", "start", "end"], `${name}.place.align`);
399
- }
400
- const placed = arranged || step.place !== void 0;
401
- if (step.style !== void 0) {
402
- object(step.style, `${name}.style`);
391
+ if (step2.place !== void 0) {
392
+ if (!PLACEABLE.includes(step2.type)) throw new Error(`${name}.place: only ${PLACEABLE.join(", ")} can be placed.`);
393
+ object(step2.place, `${name}.place`);
394
+ text(step2.place.to, `${name}.place.to`);
395
+ if (!known(ids, String(step2.place.to))) throw new Error(`${name}.place.to: no earlier step has the id "${step2.place.to}".`);
396
+ if (!["below", "above", "left", "right"].includes(step2.place.side)) throw new Error(`${name}.place.side: choose one of below, above, left, right.`);
397
+ if (step2.place.gap !== void 0) finite(step2.place.gap, `${name}.place.gap`, -200, 400);
398
+ choice(step2.place.align, ["center", "start", "end"], `${name}.place.align`);
399
+ }
400
+ const placed = arranged || step2.place !== void 0;
401
+ if (step2.style !== void 0) {
402
+ object(step2.style, `${name}.style`);
403
403
  for (const key of ["color", "fill"]) {
404
- const color = step.style[key];
404
+ const color = step2.style[key];
405
405
  if (color !== void 0 && (typeof color !== "string" || !/^(#[\da-f]{3,8}|[a-z]+)$/i.test(color))) {
406
406
  throw new Error(`${name}.style.${key}: use a hex colour or a colour name, no URLs.`);
407
407
  }
408
408
  }
409
409
  for (const key of ["width", "roughness", "bowing"]) {
410
- if (step.style[key] !== void 0) finite(step.style[key], `${name}.style.${key}`, 0, 20);
410
+ if (step2.style[key] !== void 0) finite(step2.style[key], `${name}.style.${key}`, 0, 20);
411
411
  }
412
- if (step.style.opacity !== void 0) finite(step.style.opacity, `${name}.style.opacity`, 0, 1);
413
- if (step.style.dashed !== void 0 && typeof step.style.dashed !== "boolean") throw new Error(`${name}: dashed must be a boolean.`);
414
- choice(step.style.fillStyle, ["hachure", "solid", "zigzag", "cross-hatch", "dots"], `${name}.style.fillStyle`);
412
+ if (step2.style.opacity !== void 0) finite(step2.style.opacity, `${name}.style.opacity`, 0, 1);
413
+ if (step2.style.dashed !== void 0 && typeof step2.style.dashed !== "boolean") throw new Error(`${name}: dashed must be a boolean.`);
414
+ choice(step2.style.fillStyle, ["hachure", "solid", "zigzag", "cross-hatch", "dots"], `${name}.style.fillStyle`);
415
415
  }
416
- switch (step.type) {
416
+ switch (step2.type) {
417
417
  case "line":
418
418
  case "arrow":
419
419
  case "highlight":
420
- point(step.from, `${name}.from`);
421
- point(step.to, `${name}.to`);
422
- if (step.thickness !== void 0) finite(step.thickness, `${name}.thickness`, 1, 200);
420
+ point(step2.from, `${name}.from`);
421
+ point(step2.to, `${name}.to`);
422
+ if (step2.thickness !== void 0) finite(step2.thickness, `${name}.thickness`, 1, 200);
423
423
  break;
424
424
  case "polyline":
425
425
  case "polygon":
426
426
  case "curve":
427
- if (!Array.isArray(step.points) || step.points.length < (step.type === "polygon" ? 3 : 2) || step.points.length > 500) {
428
- throw new Error(`${name}: points needs ${step.type === "polygon" ? 3 : 2} to 500 points.`);
427
+ if (!Array.isArray(step2.points) || step2.points.length < (step2.type === "polygon" ? 3 : 2) || step2.points.length > 500) {
428
+ throw new Error(`${name}: points needs ${step2.type === "polygon" ? 3 : 2} to 500 points.`);
429
429
  }
430
- step.points.forEach((p, j) => point(p, `${name}.points[${j}]`));
430
+ step2.points.forEach((p, j) => point(p, `${name}.points[${j}]`));
431
431
  break;
432
432
  case "circle":
433
433
  case "ellipse":
434
- pointOrPlaced(step, "center", name, placed);
435
- for (const key of step.type === "circle" ? ["r"] : ["rx", "ry"]) {
436
- numeric(step[key], `${name}.${key}`);
437
- if (typeof step[key] === "number") finite(step[key], `${name}.${key}`, 0);
434
+ pointOrPlaced(step2, "center", name, placed);
435
+ for (const key of step2.type === "circle" ? ["r"] : ["rx", "ry"]) {
436
+ numeric(step2[key], `${name}.${key}`);
437
+ if (typeof step2[key] === "number") finite(step2[key], `${name}.${key}`, 0);
438
438
  }
439
439
  break;
440
440
  case "text":
441
- pointOrPlaced(step, "at", name, placed);
442
- text(step.text, `${name}.text`);
443
- if (step.size !== void 0) finite(step.size, `${name}.size`, 1, 200);
444
- if (step.rotate !== void 0) finite(step.rotate, `${name}.rotate`, -360, 360);
445
- choice(step.font, ["hand", "body"], `${name}.font`);
446
- choice(step.anchor, ["start", "middle", "end"], `${name}.anchor`);
441
+ pointOrPlaced(step2, "at", name, placed);
442
+ text(step2.text, `${name}.text`);
443
+ if (step2.size !== void 0) finite(step2.size, `${name}.size`, 1, 200);
444
+ if (step2.rotate !== void 0) finite(step2.rotate, `${name}.rotate`, -360, 360);
445
+ choice(step2.font, ["hand", "body"], `${name}.font`);
446
+ choice(step2.anchor, ["start", "middle", "end"], `${name}.anchor`);
447
447
  break;
448
448
  case "card":
449
- pointOrPlaced(step, "at", name, placed);
450
- numeric(step.w, `${name}.w`);
451
- numeric(step.h, `${name}.h`);
452
- for (const key of ["w", "h"]) if (typeof step[key] === "number") finite(step[key], `${name}.${key}`, 40);
453
- text(step.title, `${name}.title`);
454
- if (step.lines !== void 0) {
455
- if (!Array.isArray(step.lines) || step.lines.length > 6) throw new Error(`${name}: at most 6 lines per card.`);
456
- step.lines.forEach((line2, j) => text(line2, `${name}.lines[${j}]`));
449
+ pointOrPlaced(step2, "at", name, placed);
450
+ numeric(step2.w, `${name}.w`);
451
+ numeric(step2.h, `${name}.h`);
452
+ for (const key of ["w", "h"]) if (typeof step2[key] === "number") finite(step2[key], `${name}.${key}`, 40);
453
+ text(step2.title, `${name}.title`);
454
+ if (step2.lines !== void 0) {
455
+ if (!Array.isArray(step2.lines) || step2.lines.length > 6) throw new Error(`${name}: at most 6 lines per card.`);
456
+ step2.lines.forEach((line2, j) => text(line2, `${name}.lines[${j}]`));
457
457
  }
458
458
  break;
459
459
  case "plot":
460
- text(step.fn, `${name}.fn`);
461
- if (step.var !== void 0) {
462
- text(step.var, `${name}.var`);
463
- identifier(String(step.var));
460
+ text(step2.fn, `${name}.fn`);
461
+ if (step2.var !== void 0) {
462
+ text(step2.var, `${name}.var`);
463
+ identifier(String(step2.var));
464
464
  }
465
- point(step.domain, `${name}.domain`);
466
- point(step.range, `${name}.range`);
467
- object(step.box, `${name}.box`);
468
- for (const key of ["x", "y", "w", "h"]) numeric(step.box[key], `${name}.box.${key}`);
469
- if (step.samples !== void 0) {
470
- finite(step.samples, `${name}.samples`, 2, 1e3);
471
- if (!Number.isInteger(step.samples)) throw new Error(`${name}: samples must be an integer.`);
465
+ point(step2.domain, `${name}.domain`);
466
+ point(step2.range, `${name}.range`);
467
+ object(step2.box, `${name}.box`);
468
+ for (const key of ["x", "y", "w", "h"]) numeric(step2.box[key], `${name}.box.${key}`);
469
+ if (step2.samples !== void 0) {
470
+ finite(step2.samples, `${name}.samples`, 2, 1e3);
471
+ if (!Number.isInteger(step2.samples)) throw new Error(`${name}: samples must be an integer.`);
472
472
  }
473
473
  break;
474
474
  case "pause":
475
- finite(step.ms, `${name}.ms`, 0, 3e4);
476
- optionalText(step.ask, `${name}.ask`);
475
+ finite(step2.ms, `${name}.ms`, 0, 3e4);
476
+ optionalText(step2.ask, `${name}.ask`);
477
477
  break;
478
478
  case "prop":
479
- if (!isPropName(step.name)) {
480
- throw new Error(`${name}.name: unknown prop "${String(step.name)}"; use one of ${PROP_NAMES.join(", ")}.`);
479
+ if (!isPropName(step2.name)) {
480
+ throw new Error(`${name}.name: unknown prop "${String(step2.name)}"; use one of ${PROP_NAMES.join(", ")}.`);
481
481
  }
482
- pointOrPlaced(step, "at", name, placed);
483
- if (step.size !== void 0) {
484
- numeric(step.size, `${name}.size`);
485
- if (typeof step.size === "number") finite(step.size, `${name}.size`, 10, 1e3);
482
+ pointOrPlaced(step2, "at", name, placed);
483
+ if (step2.size !== void 0) {
484
+ numeric(step2.size, `${name}.size`);
485
+ if (typeof step2.size === "number") finite(step2.size, `${name}.size`, 10, 1e3);
486
486
  }
487
- optionalText(step.label, `${name}.label`);
488
- if (step.flip !== void 0 && typeof step.flip !== "boolean") throw new Error(`${name}.flip: expected true or false.`);
489
- if (step.mood !== void 0) {
490
- if (!hasFace(step.name)) throw new Error(`${name}.mood: only a person (person, person-pointing, person-sitting) has a face.`);
491
- choice(step.mood, MOODS, `${name}.mood`);
487
+ optionalText(step2.label, `${name}.label`);
488
+ if (step2.flip !== void 0 && typeof step2.flip !== "boolean") throw new Error(`${name}.flip: expected true or false.`);
489
+ if (step2.mood !== void 0) {
490
+ if (!hasFace(step2.name)) throw new Error(`${name}.mood: only a person (person, person-pointing, person-sitting) has a face.`);
491
+ choice(step2.mood, MOODS, `${name}.mood`);
492
492
  }
493
493
  break;
494
494
  case "repeat":
495
- numeric(step.times, `${name}.times`);
496
- if (typeof step.times === "number") {
497
- finite(step.times, `${name}.times`, 0, MAX_REPEAT);
498
- if (!Number.isInteger(step.times)) throw new Error(`${name}.times: expected a whole number.`);
495
+ numeric(step2.times, `${name}.times`);
496
+ if (typeof step2.times === "number") {
497
+ finite(step2.times, `${name}.times`, 0, MAX_REPEAT);
498
+ if (!Number.isInteger(step2.times)) throw new Error(`${name}.times: expected a whole number.`);
499
499
  }
500
- validateStep(step.step, `${name}.step`, ids, "repeat", arranged);
500
+ validateStep(step2.step, `${name}.step`, ids, "repeat", arranged);
501
501
  break;
502
502
  case "when":
503
- numeric(step.if, `${name}.if`);
504
- if (!Array.isArray(step.steps) || step.steps.length === 0 || step.steps.length > 50) {
503
+ numeric(step2.if, `${name}.if`);
504
+ if (!Array.isArray(step2.steps) || step2.steps.length === 0 || step2.steps.length > 50) {
505
505
  throw new Error(`${name}.steps: expected 1 to 50 steps.`);
506
506
  }
507
- step.steps.forEach((inner, j) => validateStep(inner, `${name}.steps[${j}]`, ids, inside ?? "when"));
507
+ step2.steps.forEach((inner, j) => validateStep(inner, `${name}.steps[${j}]`, ids, inside ?? "when"));
508
508
  break;
509
509
  case "move":
510
- text(step.target, `${name}.target`);
511
- if (!known(ids, String(step.target))) throw new Error(`${name}.target: no earlier step has the id "${step.target}".`);
512
- point(step.to, `${name}.to`);
513
- choice(step.along, ["line", "curve"], `${name}.along`);
514
- if (step.ms !== void 0) finite(step.ms, `${name}.ms`, 200, 5e3);
510
+ text(step2.target, `${name}.target`);
511
+ if (!known(ids, String(step2.target))) throw new Error(`${name}.target: no earlier step has the id "${step2.target}".`);
512
+ point(step2.to, `${name}.to`);
513
+ choice(step2.along, ["line", "curve"], `${name}.along`);
514
+ if (step2.ms !== void 0) finite(step2.ms, `${name}.ms`, 200, 5e3);
515
515
  break;
516
516
  case "link":
517
517
  for (const key of ["from", "to"]) {
518
- text(step[key], `${name}.${key}`);
519
- if (!known(ids, String(step[key]))) throw new Error(`${name}.${key}: no earlier step has the id "${step[key]}".`);
518
+ text(step2[key], `${name}.${key}`);
519
+ if (!known(ids, String(step2[key]))) throw new Error(`${name}.${key}: no earlier step has the id "${step2[key]}".`);
520
520
  }
521
- optionalText(step.label, `${name}.label`);
521
+ optionalText(step2.label, `${name}.label`);
522
522
  break;
523
523
  case "counter":
524
- pointOrPlaced(step, "at", name, placed);
525
- numeric(step.value, `${name}.value`);
526
- if (step.format !== void 0) {
527
- text(step.format, `${name}.format`);
528
- if (!String(step.format).includes("{v")) throw new Error(`${name}.format: show the number as {v}, e.g. "R$ {v:0}".`);
524
+ pointOrPlaced(step2, "at", name, placed);
525
+ numeric(step2.value, `${name}.value`);
526
+ if (step2.format !== void 0) {
527
+ text(step2.format, `${name}.format`);
528
+ if (!String(step2.format).includes("{v")) throw new Error(`${name}.format: show the number as {v}, e.g. "R$ {v:0}".`);
529
529
  }
530
- if (step.size !== void 0) finite(step.size, `${name}.size`, 1, 200);
531
- choice(step.anchor, ["start", "middle", "end"], `${name}.anchor`);
530
+ if (step2.size !== void 0) finite(step2.size, `${name}.size`, 1, 200);
531
+ choice(step2.anchor, ["start", "middle", "end"], `${name}.anchor`);
532
532
  break;
533
533
  case "row":
534
534
  case "column":
535
535
  case "grid":
536
- if (arranged) throw new Error(`${name}: a ${step.type} cannot go inside another ${inside}.`);
537
- pointOrPlaced(step, "at", name, false);
538
- if (step.gap !== void 0) finite(step.gap, `${name}.gap`, 0, 400);
539
- if (step.type === "grid") {
540
- finite(step.columns, `${name}.columns`, 1, 12);
541
- if (!Number.isInteger(step.columns)) throw new Error(`${name}.columns: expected a whole number.`);
542
- } else choice(step.align, ["center", "start", "end"], `${name}.align`);
543
- if (!Array.isArray(step.steps) || step.steps.length === 0 || step.steps.length > 50) {
536
+ if (arranged) throw new Error(`${name}: a ${step2.type} cannot go inside another ${inside}.`);
537
+ pointOrPlaced(step2, "at", name, false);
538
+ if (step2.gap !== void 0) finite(step2.gap, `${name}.gap`, 0, 400);
539
+ if (step2.type === "grid") {
540
+ finite(step2.columns, `${name}.columns`, 1, 12);
541
+ if (!Number.isInteger(step2.columns)) throw new Error(`${name}.columns: expected a whole number.`);
542
+ } else choice(step2.align, ["center", "start", "end"], `${name}.align`);
543
+ if (!Array.isArray(step2.steps) || step2.steps.length === 0 || step2.steps.length > 50) {
544
544
  throw new Error(`${name}.steps: expected 1 to 50 steps.`);
545
545
  }
546
- step.steps.forEach((inner, j) => {
546
+ step2.steps.forEach((inner, j) => {
547
547
  object(inner, `${name}.steps[${j}]`);
548
548
  const kind = inner.type === "repeat" ? inner.step?.type : inner.type;
549
- if (!PLACEABLE.includes(kind)) throw new Error(`${name}.steps[${j}]: a ${step.type} holds only ${PLACEABLE.join(", ")} (or a repeat of one).`);
550
- validateStep(inner, `${name}.steps[${j}]`, ids, step.type);
549
+ if (!PLACEABLE.includes(kind)) throw new Error(`${name}.steps[${j}]: a ${step2.type} holds only ${PLACEABLE.join(", ")} (or a repeat of one).`);
550
+ validateStep(inner, `${name}.steps[${j}]`, ids, step2.type);
551
551
  });
552
552
  break;
553
553
  case "axis":
554
554
  case "timeline":
555
- point(step.from, `${name}.from`);
556
- point(step.to, `${name}.to`);
557
- point(step.domain, `${name}.domain`);
558
- if (step.type === "axis") {
559
- if (step.ticks !== void 0) {
560
- if (Array.isArray(step.ticks)) {
561
- if (step.ticks.length > 30) throw new Error(`${name}.ticks: at most 30 marks.`);
562
- step.ticks.forEach((v, j) => finite(v, `${name}.ticks[${j}]`));
555
+ point(step2.from, `${name}.from`);
556
+ point(step2.to, `${name}.to`);
557
+ point(step2.domain, `${name}.domain`);
558
+ if (step2.type === "axis") {
559
+ if (step2.ticks !== void 0) {
560
+ if (Array.isArray(step2.ticks)) {
561
+ if (step2.ticks.length > 30) throw new Error(`${name}.ticks: at most 30 marks.`);
562
+ step2.ticks.forEach((v, j) => finite(v, `${name}.ticks[${j}]`));
563
563
  } else {
564
- finite(step.ticks, `${name}.ticks`, 0, 30);
565
- if (!Number.isInteger(step.ticks)) throw new Error(`${name}.ticks: expected a whole number.`);
564
+ finite(step2.ticks, `${name}.ticks`, 0, 30);
565
+ if (!Number.isInteger(step2.ticks)) throw new Error(`${name}.ticks: expected a whole number.`);
566
566
  }
567
567
  }
568
- optionalText(step.label, `${name}.label`);
569
- if (step.format !== void 0) {
570
- text(step.format, `${name}.format`);
571
- if (!String(step.format).includes("{v")) throw new Error(`${name}.format: show the number as {v}, e.g. "{v} km".`);
568
+ optionalText(step2.label, `${name}.label`);
569
+ if (step2.format !== void 0) {
570
+ text(step2.format, `${name}.format`);
571
+ if (!String(step2.format).includes("{v")) throw new Error(`${name}.format: show the number as {v}, e.g. "{v} km".`);
572
572
  }
573
573
  } else {
574
- if (!Array.isArray(step.marks) || step.marks.length === 0 || step.marks.length > 20) throw new Error(`${name}.marks: expected 1 to 20 marks.`);
575
- step.marks.forEach((m, j) => {
574
+ if (!Array.isArray(step2.marks) || step2.marks.length === 0 || step2.marks.length > 20) throw new Error(`${name}.marks: expected 1 to 20 marks.`);
575
+ step2.marks.forEach((m, j) => {
576
576
  object(m, `${name}.marks[${j}]`);
577
577
  numeric(m.at, `${name}.marks[${j}].at`);
578
578
  optionalText(m.label, `${name}.marks[${j}].label`);
@@ -581,60 +581,60 @@ function validateStep(step, name, ids, inside, inRow = false) {
581
581
  }
582
582
  break;
583
583
  case "balloon":
584
- text(step.to, `${name}.to`);
585
- if (!known(ids, String(step.to))) throw new Error(`${name}.to: no earlier step has the id "${step.to}".`);
586
- text(step.text, `${name}.text`);
587
- if (String(step.text).length > 300) throw new Error(`${name}.text: keep a balloon under 300 characters.`);
588
- choice(step.side, ["below", "above", "left", "right"], `${name}.side`);
589
- choice(step.kind, ["speech", "thought"], `${name}.kind`);
590
- if (step.width !== void 0) finite(step.width, `${name}.width`, 80, 600);
591
- if (step.size !== void 0) finite(step.size, `${name}.size`, 12, 60);
584
+ text(step2.to, `${name}.to`);
585
+ if (!known(ids, String(step2.to))) throw new Error(`${name}.to: no earlier step has the id "${step2.to}".`);
586
+ text(step2.text, `${name}.text`);
587
+ if (String(step2.text).length > 300) throw new Error(`${name}.text: keep a balloon under 300 characters.`);
588
+ choice(step2.side, ["below", "above", "left", "right"], `${name}.side`);
589
+ choice(step2.kind, ["speech", "thought"], `${name}.kind`);
590
+ if (step2.width !== void 0) finite(step2.width, `${name}.width`, 80, 600);
591
+ if (step2.size !== void 0) finite(step2.size, `${name}.size`, 12, 60);
592
592
  break;
593
593
  case "brace":
594
- point(step.from, `${name}.from`);
595
- point(step.to, `${name}.to`);
596
- optionalText(step.label, `${name}.label`);
597
- if (step.flip !== void 0 && typeof step.flip !== "boolean") throw new Error(`${name}.flip: expected true or false.`);
594
+ point(step2.from, `${name}.from`);
595
+ point(step2.to, `${name}.to`);
596
+ optionalText(step2.label, `${name}.label`);
597
+ if (step2.flip !== void 0 && typeof step2.flip !== "boolean") throw new Error(`${name}.flip: expected true or false.`);
598
598
  break;
599
599
  case "note":
600
- point(step.at, `${name}.at`);
601
- text(step.text, `${name}.text`);
602
- if (step.to === void 0 === (step.point === void 0)) throw new Error(`${name}: a note points either to an element (to) or to a point (point).`);
603
- if (step.to !== void 0) {
604
- text(step.to, `${name}.to`);
605
- if (!known(ids, String(step.to))) throw new Error(`${name}.to: no earlier step has the id "${step.to}".`);
606
- } else point(step.point, `${name}.point`);
607
- if (step.size !== void 0) finite(step.size, `${name}.size`, 12, 60);
600
+ point(step2.at, `${name}.at`);
601
+ text(step2.text, `${name}.text`);
602
+ if (step2.to === void 0 === (step2.point === void 0)) throw new Error(`${name}: a note points either to an element (to) or to a point (point).`);
603
+ if (step2.to !== void 0) {
604
+ text(step2.to, `${name}.to`);
605
+ if (!known(ids, String(step2.to))) throw new Error(`${name}.to: no earlier step has the id "${step2.to}".`);
606
+ } else point(step2.point, `${name}.point`);
607
+ if (step2.size !== void 0) finite(step2.size, `${name}.size`, 12, 60);
608
608
  break;
609
609
  case "table":
610
- point(step.at, `${name}.at`);
611
- if (!Array.isArray(step.rows) || step.rows.length === 0 || step.rows.length > 12) throw new Error(`${name}.rows: expected 1 to 12 rows.`);
612
- step.rows.forEach((row, r) => {
610
+ point(step2.at, `${name}.at`);
611
+ if (!Array.isArray(step2.rows) || step2.rows.length === 0 || step2.rows.length > 12) throw new Error(`${name}.rows: expected 1 to 12 rows.`);
612
+ step2.rows.forEach((row, r) => {
613
613
  if (!Array.isArray(row) || row.length === 0 || row.length > 6) throw new Error(`${name}.rows[${r}]: expected 1 to 6 cells.`);
614
614
  row.forEach((cell, c) => {
615
615
  if (typeof cell !== "string" || cell.length > 120) throw new Error(`${name}.rows[${r}][${c}]: expected text of up to 120 characters.`);
616
616
  });
617
617
  });
618
- if (step.header !== void 0 && typeof step.header !== "boolean") throw new Error(`${name}.header: expected true or false.`);
619
- if (step.size !== void 0) finite(step.size, `${name}.size`, 12, 40);
618
+ if (step2.header !== void 0 && typeof step2.header !== "boolean") throw new Error(`${name}.header: expected true or false.`);
619
+ if (step2.size !== void 0) finite(step2.size, `${name}.size`, 12, 40);
620
620
  break;
621
621
  case "bars":
622
- object(step.box, `${name}.box`);
623
- for (const key of ["x", "y", "w", "h"]) numeric(step.box[key], `${name}.box.${key}`);
624
- if (!Array.isArray(step.values) || step.values.length === 0 || step.values.length > 12) throw new Error(`${name}.values: expected 1 to 12 values.`);
625
- step.values.forEach((v, j) => numeric(v, `${name}.values[${j}]`));
626
- if (step.labels !== void 0) {
627
- if (!Array.isArray(step.labels) || step.labels.length > step.values.length) throw new Error(`${name}.labels: at most one label per value.`);
628
- step.labels.forEach((l, j) => text(l, `${name}.labels[${j}]`));
622
+ object(step2.box, `${name}.box`);
623
+ for (const key of ["x", "y", "w", "h"]) numeric(step2.box[key], `${name}.box.${key}`);
624
+ if (!Array.isArray(step2.values) || step2.values.length === 0 || step2.values.length > 12) throw new Error(`${name}.values: expected 1 to 12 values.`);
625
+ step2.values.forEach((v, j) => numeric(v, `${name}.values[${j}]`));
626
+ if (step2.labels !== void 0) {
627
+ if (!Array.isArray(step2.labels) || step2.labels.length > step2.values.length) throw new Error(`${name}.labels: at most one label per value.`);
628
+ step2.labels.forEach((l, j) => text(l, `${name}.labels[${j}]`));
629
629
  }
630
- if (step.max !== void 0) numeric(step.max, `${name}.max`);
631
- if (step.format !== void 0) {
632
- text(step.format, `${name}.format`);
633
- if (!String(step.format).includes("{v")) throw new Error(`${name}.format: show the number as {v}, e.g. "{v}%".`);
630
+ if (step2.max !== void 0) numeric(step2.max, `${name}.max`);
631
+ if (step2.format !== void 0) {
632
+ text(step2.format, `${name}.format`);
633
+ if (!String(step2.format).includes("{v")) throw new Error(`${name}.format: show the number as {v}, e.g. "{v}%".`);
634
634
  }
635
635
  break;
636
636
  }
637
- if (typeof step.id === "string") (inside === "repeat" ? ids.repeated : ids.exact).add(step.id);
637
+ if (typeof step2.id === "string") (inside === "repeat" ? ids.repeated : ids.exact).add(step2.id);
638
638
  }
639
639
  function validateScene(data) {
640
640
  object(data, "The recipe");
@@ -679,9 +679,9 @@ function validateScene(data) {
679
679
  }
680
680
  if (s.vars !== void 0) {
681
681
  object(s.vars, "vars");
682
- for (const [key, value] of Object.entries(s.vars)) {
682
+ for (const [key, value2] of Object.entries(s.vars)) {
683
683
  identifier(key);
684
- numeric(value, `vars.${key}`);
684
+ numeric(value2, `vars.${key}`);
685
685
  }
686
686
  }
687
687
  if (s.switches !== void 0) {
@@ -702,7 +702,7 @@ function validateScene(data) {
702
702
  throw new Error("A recipe needs 1 to 250 steps.");
703
703
  }
704
704
  const ids = { exact: /* @__PURE__ */ new Set(), repeated: /* @__PURE__ */ new Set(), count: 0 };
705
- s.steps.forEach((step, i) => validateStep(step, `Step ${i + 1}`, ids, null));
705
+ s.steps.forEach((step2, i) => validateStep(step2, `Step ${i + 1}`, ids, null));
706
706
  return {
707
707
  ...s,
708
708
  id: s.id ?? "pasted",
@@ -711,14 +711,573 @@ function validateScene(data) {
711
711
  };
712
712
  }
713
713
  function chaptersFor(scene, first = "Start") {
714
- const starts = scene.steps.flatMap((step, start) => step.chapter ? [{ title: step.chapter, start }] : []);
714
+ const starts = scene.steps.flatMap((step2, start) => step2.chapter ? [{ title: step2.chapter, start }] : []);
715
715
  if (!starts.length || starts[0].start !== 0) starts.unshift({ title: first, start: 0 });
716
716
  return starts.map((chapter, i) => ({ ...chapter, end: (starts[i + 1]?.start ?? scene.steps.length) - 1 }));
717
717
  }
718
+
719
+ var ChalkTextError = class extends Error {
720
+ line;
721
+ constructor(line2, message) {
722
+ super(`line ${line2}: ${message}`);
723
+ this.line = line2;
724
+ }
725
+ };
726
+ function tokenize(text2, no) {
727
+ const out = [];
728
+ let cur = "";
729
+ let quoted = false;
730
+ for (let i = 0; i < text2.length; i++) {
731
+ const c = text2[i];
732
+ if (c === "\\" && quoted && i + 1 < text2.length) {
733
+ cur += c + text2[++i];
734
+ continue;
735
+ }
736
+ if (c === '"') quoted = !quoted;
737
+ if (/\s/.test(c) && !quoted) {
738
+ if (cur) out.push(cur);
739
+ cur = "";
740
+ } else cur += c;
741
+ }
742
+ if (quoted) throw new ChalkTextError(no, "a quote is not closed.");
743
+ if (cur) out.push(cur);
744
+ return out;
745
+ }
746
+ function treeOf(text2) {
747
+ const root = { no: 0, indent: -1, tokens: [], children: [] };
748
+ const stack = [root];
749
+ text2.split(/\r?\n/).forEach((raw, i) => {
750
+ const no = i + 1;
751
+ if (!raw.trim() || raw.trim().startsWith("//")) return;
752
+ const indent = raw.length - raw.trimStart().length;
753
+ const body = raw.trim();
754
+ const tokens = body.startsWith("#") ? ["#", body.slice(1).trim()] : body.startsWith(">") ? [">", body.slice(1).trim()] : body.startsWith("json ") ? ["json", body.slice(5).trim()] : body.startsWith("|") ? ["|", body] : tokenize(body, no);
755
+ const line2 = { no, indent, tokens, children: [] };
756
+ while (stack.length > 1 && stack[stack.length - 1].indent >= indent) stack.pop();
757
+ stack[stack.length - 1].children.push(line2);
758
+ stack.push(line2);
759
+ });
760
+ return root.children;
761
+ }
762
+ var isQuoted = (t) => t.length >= 2 && t.startsWith('"') && t.endsWith('"');
763
+ var unquote = (t) => isQuoted(t) ? t.slice(1, -1).replace(/\\(.)/g, "$1") : t;
764
+ var NUMBER = /^-?\d+(\.\d+)?$/;
765
+ function value(t, no) {
766
+ if (NUMBER.test(t)) return Number(t);
767
+ if (isQuoted(t)) return unquote(t);
768
+ if (/^[A-Za-z_][A-Za-z_0-9]*$/.test(t)) return t;
769
+ throw new ChalkTextError(no, `expected a number or an expression in quotes, got ${t}.`);
770
+ }
771
+ function parts(t) {
772
+ const out = [];
773
+ let cur = "";
774
+ let quoted = false;
775
+ for (const c of t) {
776
+ if (c === '"') quoted = !quoted;
777
+ if (c === "," && !quoted) {
778
+ out.push(cur);
779
+ cur = "";
780
+ } else cur += c;
781
+ }
782
+ out.push(cur);
783
+ return out;
784
+ }
785
+ function point2(t, no) {
786
+ const p = t ? parts(t) : [];
787
+ if (p.length !== 2) throw new ChalkTextError(no, `expected a point x,y, got ${t ?? "nothing"}.`);
788
+ return [value(p[0], no), value(p[1], no)];
789
+ }
790
+ function range(t, no) {
791
+ const m = t?.match(/^(.+?)\.\.(.+)$/);
792
+ if (!m) throw new ChalkTextError(no, `expected a range a..b, got ${t ?? "nothing"}.`);
793
+ return [value(m[1], no), value(m[2], no)];
794
+ }
795
+ var STEP_WORDS = /* @__PURE__ */ new Set([
796
+ "text",
797
+ "card",
798
+ "line",
799
+ "arrow",
800
+ "highlight",
801
+ "polyline",
802
+ "polygon",
803
+ "curve",
804
+ "circle",
805
+ "ellipse",
806
+ "plot",
807
+ "pause",
808
+ "row",
809
+ "column",
810
+ "grid",
811
+ "repeat",
812
+ "when",
813
+ "move",
814
+ "counter",
815
+ "axis",
816
+ "timeline",
817
+ "brace",
818
+ "note",
819
+ "table",
820
+ "bars",
821
+ "json"
822
+ ]);
823
+ var SIDES = { "right-of": "right", "left-of": "left", above: "above", below: "below" };
824
+ var STYLE = ["color", "fill", "roughness", "bowing", "fillStyle", "opacity"];
825
+ var SPACED = /* @__PURE__ */ new Set(["gap", "size", "r", "rx", "ry", "w", "h", "columns", "ms", "thickness"]);
826
+ var FLAGS = /* @__PURE__ */ new Set(["dashed", "flip", "noheader"]);
827
+ function attrs(tokens, step2, no) {
828
+ const style = {};
829
+ for (let i = 0; i < tokens.length; i++) {
830
+ const t = tokens[i];
831
+ let key;
832
+ let raw;
833
+ if (FLAGS.has(t)) {
834
+ if (t === "dashed") style.dashed = true;
835
+ else if (t === "flip") step2.flip = true;
836
+ else step2.header = false;
837
+ continue;
838
+ }
839
+ const eq = t.indexOf("=");
840
+ if (eq > 0 && !isQuoted(t)) [key, raw] = [t.slice(0, eq), t.slice(eq + 1)];
841
+ else if (SPACED.has(t) && i + 1 < tokens.length) [key, raw] = [t, tokens[++i]];
842
+ else throw new ChalkTextError(no, `did not understand \xAB${t}\xBB.`);
843
+ if (key === "stroke") style.width = Number(raw);
844
+ else if (STYLE.includes(key)) style[key] = NUMBER.test(raw) ? Number(raw) : unquote(raw);
845
+ else if (key === "nudge") step2.nudge = point2(raw, no).map(Number);
846
+ else if (key === "ticks") step2.ticks = raw.includes(",") ? parts(raw).map(Number) : Number(raw);
847
+ else if (key === "labels") step2.labels = parts(raw).map(unquote);
848
+ else if (key === "id" || key === "anchor" || key === "font" || key === "mood" || key === "along" || key === "side" || key === "kind" || key === "var" || key === "align" || key === "label" || key === "format" || key === "fn") step2[key] = unquote(raw);
849
+ else step2[key] = value(raw, no);
850
+ }
851
+ if (Object.keys(style).length) step2.style = style;
852
+ return step2;
853
+ }
854
+ function placement(tokens, step2, key, no) {
855
+ const [w, arg] = tokens;
856
+ if (w === "at") {
857
+ step2[key] = point2(arg, no);
858
+ return tokens.slice(2);
859
+ }
860
+ if (w in SIDES) {
861
+ if (!arg || isQuoted(arg)) throw new ChalkTextError(no, `${w} needs the id of an earlier step.`);
862
+ step2.place = { to: arg, side: SIDES[w] };
863
+ let rest = tokens.slice(2);
864
+ if (rest[0] === "gap" || rest[0]?.startsWith("gap=")) {
865
+ const g = rest[0] === "gap" ? rest[1] : rest[0].slice(4);
866
+ step2.place.gap = Number(g);
867
+ rest = rest.slice(rest[0] === "gap" ? 2 : 1);
868
+ }
869
+ const align = rest.findIndex((t) => t.startsWith("align="));
870
+ if (align >= 0) {
871
+ ;
872
+ step2.place.align = rest[align].slice(6);
873
+ rest = rest.filter((_, i) => i !== align);
874
+ }
875
+ return rest;
876
+ }
877
+ return tokens;
878
+ }
879
+ function step(line2, props) {
880
+ const { no, tokens } = line2;
881
+ const [w, ...rest] = tokens;
882
+ const kids = () => line2.children.map((c) => step(c, props));
883
+ const quoted = (t, what) => {
884
+ if (!t || !isQuoted(t)) throw new ChalkTextError(no, `${w} needs ${what} in quotes.`);
885
+ return unquote(t);
886
+ };
887
+ const fromTo = (type) => {
888
+ if (rest[1] !== "->") throw new ChalkTextError(no, `write ${type} x,y -> x,y.`);
889
+ return { type, from: point2(rest[0], no), to: point2(rest[2], no) };
890
+ };
891
+ if (w === "json") {
892
+ try {
893
+ return JSON.parse(rest[0]);
894
+ } catch {
895
+ throw new ChalkTextError(no, "the JSON on this line is not valid.");
896
+ }
897
+ }
898
+ if (rest[0] === "->" && !STEP_WORDS.has(w) && !props.has(w)) {
899
+ const s = { type: "link", from: w, to: rest[1] };
900
+ let more = rest.slice(2);
901
+ if (more[0] && isQuoted(more[0])) [s.label, more] = [unquote(more[0]), more.slice(1)];
902
+ return attrs(more, s, no);
903
+ }
904
+ if (rest[0] === "says" || rest[0] === "thinks") {
905
+ const s = { type: "balloon", to: w, text: quoted(rest[1], "what is said"), ...rest[0] === "thinks" ? { kind: "thought" } : {} };
906
+ return attrs(rest.slice(2), s, no);
907
+ }
908
+ if (props.has(w)) {
909
+ const s = { type: "prop", name: w };
910
+ let more = rest;
911
+ const word = more[0];
912
+ if (word && !isQuoted(word) && !word.includes("=") && word !== "at" && !(word in SIDES) && !SPACED.has(word) && !FLAGS.has(word)) [s.id, more] = [word, more.slice(1)];
913
+ more = placement(more, s, "at", no);
914
+ if (more[0] && isQuoted(more[0])) [s.label, more] = [unquote(more[0]), more.slice(1)];
915
+ return attrs(more, s, no);
916
+ }
917
+ switch (w) {
918
+ case "text": {
919
+ const s = { type: "text", text: quoted(rest[0], "its text") };
920
+ return attrs(placement(rest.slice(1), s, "at", no), s, no);
921
+ }
922
+ case "card": {
923
+ const s = { type: "card" };
924
+ let more = placement(rest, s, "at", no);
925
+ const texts = more.filter(isQuoted).map(unquote);
926
+ more = more.filter((t) => !isQuoted(t));
927
+ if (!texts.length) throw new ChalkTextError(no, "a card needs a title in quotes.");
928
+ [s.title] = texts;
929
+ if (texts.length > 1) s.lines = texts.slice(1);
930
+ return attrs(more, s, no);
931
+ }
932
+ case "line":
933
+ case "arrow":
934
+ case "highlight":
935
+ return attrs(rest.slice(3), fromTo(w), no);
936
+ case "brace": {
937
+ const s = fromTo("brace");
938
+ let more = rest.slice(3);
939
+ if (more[0] && isQuoted(more[0])) [s.label, more] = [unquote(more[0]), more.slice(1)];
940
+ return attrs(more, s, no);
941
+ }
942
+ case "polyline":
943
+ case "polygon":
944
+ case "curve": {
945
+ const pts = rest.filter((t) => /,/.test(t) && !t.includes("="));
946
+ return attrs(rest.slice(pts.length), { type: w, points: pts.map((p) => point2(p, no)) }, no);
947
+ }
948
+ case "circle":
949
+ case "ellipse": {
950
+ const s = { type: w };
951
+ return attrs(placement(rest, s, "center", no), s, no);
952
+ }
953
+ case "plot": {
954
+ const s = { type: "plot", fn: quoted(rest[0], "the formula") };
955
+ const more = [];
956
+ for (let i = 1; i < rest.length; i++) {
957
+ if (rest[i] === "domain") s.domain = range(rest[++i], no);
958
+ else if (rest[i] === "range") s.range = range(rest[++i], no);
959
+ else if (rest[i] === "box") {
960
+ const b = parts(rest[++i] ?? "");
961
+ if (b.length !== 4) throw new ChalkTextError(no, "write box x,y,w,h.");
962
+ const [x, y, bw, bh] = b.map((v) => value(v, no));
963
+ s.box = { x, y, w: bw, h: bh };
964
+ } else more.push(rest[i]);
965
+ }
966
+ return attrs(more, s, no);
967
+ }
968
+ case "pause": {
969
+ const s = { type: "pause", ms: 600 };
970
+ let more = rest;
971
+ if (more[0] && NUMBER.test(more[0])) [s.ms, more] = [Number(more[0]), more.slice(1)];
972
+ if (more[0] === "ask") [s.ask, more] = [quoted(more[1], "the question"), more.slice(2)];
973
+ return attrs(more, s, no);
974
+ }
975
+ case "row":
976
+ case "column":
977
+ case "grid": {
978
+ const s = { type: w };
979
+ const more = placement(rest, s, "at", no);
980
+ attrs(more, s, no);
981
+ s.steps = kids();
982
+ return s;
983
+ }
984
+ case "repeat": {
985
+ const head = rest[0]?.replace(/:$/, "");
986
+ if (!head) throw new ChalkTextError(no, "write repeat n: <step>.");
987
+ const s = { type: "repeat", times: value(head, no) };
988
+ const inline = rest.slice(1);
989
+ if (inline.length) s.step = step({ ...line2, tokens: inline, children: [] }, props);
990
+ else if (line2.children.length === 1) [s.step] = kids();
991
+ else throw new ChalkTextError(no, "a repeat holds one step: on the same line after \xAB:\xBB, or one line under it.");
992
+ return s;
993
+ }
994
+ case "when": {
995
+ const cond = rest[0]?.replace(/:$/, "");
996
+ if (!cond) throw new ChalkTextError(no, "write when <condition>:.");
997
+ const inline = rest.slice(1);
998
+ return { type: "when", if: value(cond, no), steps: inline.length ? [step({ ...line2, tokens: inline, children: [] }, props)] : kids() };
999
+ }
1000
+ case "move": {
1001
+ if (rest[1] !== "to") throw new ChalkTextError(no, "write move <id> to x,y.");
1002
+ return attrs(rest.slice(3), { type: "move", target: rest[0], to: point2(rest[2], no) }, no);
1003
+ }
1004
+ case "counter": {
1005
+ const s = { type: "counter" };
1006
+ let more = placement(rest, s, "at", no);
1007
+ s.value = value(more[0] ?? "", no);
1008
+ more = more.slice(1);
1009
+ if (more[0] && isQuoted(more[0])) [s.format, more] = [unquote(more[0]), more.slice(1)];
1010
+ return attrs(more, s, no);
1011
+ }
1012
+ case "axis":
1013
+ case "timeline": {
1014
+ const s = fromTo(w);
1015
+ let more = rest.slice(3);
1016
+ if (more[0] !== "domain") throw new ChalkTextError(no, `write ${w} x,y -> x,y domain a..b.`);
1017
+ s.domain = range(more[1], no);
1018
+ more = more.slice(2);
1019
+ if (w === "timeline") {
1020
+ s.marks = line2.children.map((c) => {
1021
+ const [m, at, label, text2] = c.tokens;
1022
+ if (m !== "mark") throw new ChalkTextError(c.no, 'under a timeline, write mark <when> "label" "text".');
1023
+ return { at: value(at ?? "", c.no), ...label ? { label: unquote(label) } : {}, ...text2 ? { text: unquote(text2) } : {} };
1024
+ });
1025
+ }
1026
+ return attrs(more, s, no);
1027
+ }
1028
+ case "note": {
1029
+ const s = { type: "note" };
1030
+ let more = placement(rest, s, "at", no);
1031
+ s.text = quoted(more[0], "its text");
1032
+ if (more[1] !== "->") throw new ChalkTextError(no, 'write note at x,y "text" -> <id or x,y>.');
1033
+ const target = more[2] ?? "";
1034
+ if (/,/.test(target)) s.point = point2(target, no);
1035
+ else s.to = target;
1036
+ more = more.slice(3);
1037
+ return attrs(more, s, no);
1038
+ }
1039
+ case "table": {
1040
+ const s = { type: "table" };
1041
+ attrs(placement(rest, s, "at", no), s, no);
1042
+ s.rows = line2.children.map((c) => {
1043
+ if (c.tokens[0] !== "|") throw new ChalkTextError(c.no, "under a table, write rows as | a | b |.");
1044
+ return c.tokens[1].replace(/^\||\|$/g, "").split("|").map((cell) => cell.trim());
1045
+ });
1046
+ return s;
1047
+ }
1048
+ case "bars": {
1049
+ const s = { type: "bars" };
1050
+ const more = [];
1051
+ for (let i = 0; i < rest.length; i++) {
1052
+ if (rest[i] === "box") {
1053
+ const b = parts(rest[++i] ?? "");
1054
+ if (b.length !== 4) throw new ChalkTextError(no, "write box x,y,w,h.");
1055
+ const [x, y, bw, bh] = b.map((v) => value(v, no));
1056
+ s.box = { x, y, w: bw, h: bh };
1057
+ } else if (rest[i] === "values") s.values = parts(rest[++i] ?? "").map((v) => value(v, no));
1058
+ else if (rest[i] === "labels") s.labels = parts(rest[++i] ?? "").map(unquote);
1059
+ else more.push(rest[i]);
1060
+ }
1061
+ return attrs(more, s, no);
1062
+ }
1063
+ }
1064
+ throw new ChalkTextError(no, `\xAB${w}\xBB is not a step, a prop or an id followed by -> / says.`);
1065
+ }
1066
+ function parseChalk(text2, props) {
1067
+ const names = new Set(props);
1068
+ const scene = { id: "lesson", title: "", width: 900, height: 560 };
1069
+ const steps = [];
1070
+ let chapter;
1071
+ let say;
1072
+ for (const line2 of treeOf(text2)) {
1073
+ const { no, tokens } = line2;
1074
+ const [w, ...rest] = tokens;
1075
+ const one = (what) => {
1076
+ if (!rest[0]) throw new ChalkTextError(no, `${w} needs ${what}.`);
1077
+ return unquote(rest.join(" "));
1078
+ };
1079
+ const header = !["->", "says", "thinks"].includes(rest[0]);
1080
+ switch (header ? w : "") {
1081
+ case "#":
1082
+ chapter = rest[0];
1083
+ continue;
1084
+ case ">":
1085
+ say = rest[0];
1086
+ continue;
1087
+ case "title":
1088
+ scene.title = one("a title");
1089
+ continue;
1090
+ case "id":
1091
+ scene.id = one("an id");
1092
+ continue;
1093
+ case "category":
1094
+ scene.category = one("a category");
1095
+ continue;
1096
+ case "theme":
1097
+ scene.theme = one("paper or chalkboard");
1098
+ continue;
1099
+ case "locale":
1100
+ scene.locale = one("a language tag");
1101
+ continue;
1102
+ case "background":
1103
+ scene.background = one("dots, grid or plain");
1104
+ continue;
1105
+ case "size": {
1106
+ const m = rest[0]?.match(/^(\d+)x(\d+)$/);
1107
+ if (!m) throw new ChalkTextError(no, "write size 900x560.");
1108
+ [scene.width, scene.height] = [Number(m[1]), Number(m[2])];
1109
+ continue;
1110
+ }
1111
+ case "param": {
1112
+ const [name, label, span, ...more] = rest;
1113
+ const [min, max] = range(span, no).map(Number);
1114
+ const p = { label: unquote(label ?? ""), min, max, step: 1, value: min };
1115
+ for (let i = 0; i < more.length; i++) {
1116
+ if (more[i].startsWith("step=")) p.step = Number(more[i].slice(5));
1117
+ else if (more[i] === "=") p.value = Number(more[++i]);
1118
+ else throw new ChalkTextError(no, `did not understand \xAB${more[i]}\xBB.`);
1119
+ }
1120
+ scene.params = { ...scene.params, [name]: p };
1121
+ continue;
1122
+ }
1123
+ case "switch": {
1124
+ const [name, label, on] = rest;
1125
+ scene.switches = { ...scene.switches, [name]: { label: unquote(label ?? ""), on: on === "on" } };
1126
+ continue;
1127
+ }
1128
+ case "var": {
1129
+ if (rest[1] !== "=") throw new ChalkTextError(no, "write var name = value.");
1130
+ scene.vars = { ...scene.vars, [rest[0]]: value(rest[2] ?? "", no) };
1131
+ continue;
1132
+ }
1133
+ case "learning": {
1134
+ const [part, t] = rest;
1135
+ if (!["goal", "question", "answer", "caveat"].includes(part)) throw new ChalkTextError(no, 'write learning goal|question|answer|caveat "\u2026".');
1136
+ scene.learning = { ...scene.learning, [part]: unquote(t ?? "") };
1137
+ continue;
1138
+ }
1139
+ case "quiz": {
1140
+ scene.quiz = {
1141
+ question: unquote(rest[0] ?? ""),
1142
+ options: line2.children.map((c) => {
1143
+ const [mark, ...words] = c.tokens;
1144
+ if (mark !== "*" && mark !== "-") throw new ChalkTextError(c.no, "under a quiz, write * right option -- why, or - wrong option -- why.");
1145
+ const body = words.map(unquote).join(" ");
1146
+ const [t, why = ""] = body.split(/\s+--\s+/);
1147
+ return { text: t.trim(), right: mark === "*", why: why.trim() };
1148
+ })
1149
+ };
1150
+ continue;
1151
+ }
1152
+ }
1153
+ const s = step(line2, names);
1154
+ if (chapter !== void 0) [s.chapter, chapter] = [chapter, void 0];
1155
+ if (say !== void 0) [s.say, say] = [say, void 0];
1156
+ steps.push(s);
1157
+ }
1158
+ scene.steps = steps;
1159
+ return scene;
1160
+ }
1161
+ var q = (s) => `"${String(s).replace(/(["\\])/g, "\\$1")}"`;
1162
+ var num = (v) => typeof v === "number" ? String(v) : /^[A-Za-z_][A-Za-z_0-9]*$/.test(String(v)) && !NUMBER.test(String(v)) ? String(v) : q(v);
1163
+ var pt = (p) => p.map(num).join(",");
1164
+ function printAttrs(s, skip) {
1165
+ const out = [];
1166
+ for (const [k, v] of Object.entries(s)) {
1167
+ if (skip.includes(k) || ["type", "chapter", "say"].includes(k)) continue;
1168
+ if (k === "style") {
1169
+ for (const [sk, sv] of Object.entries(v)) {
1170
+ if (sk === "dashed") {
1171
+ if (sv === true) out.push("dashed");
1172
+ } else if (sk === "width") out.push(`stroke=${sv}`);
1173
+ else if (STYLE.includes(sk)) out.push(`${sk}=${typeof sv === "number" ? sv : q(sv)}`);
1174
+ else return null;
1175
+ }
1176
+ } else if (k === "nudge") out.push(`nudge=${v.join(",")}`);
1177
+ else if (k === "flip") {
1178
+ if (v === true) out.push("flip");
1179
+ } else if (typeof v === "number" || typeof v === "string") out.push(`${k}=${typeof v === "number" ? v : num(v)}`);
1180
+ else return null;
1181
+ }
1182
+ return out;
1183
+ }
1184
+ function place(s, key) {
1185
+ const p = s.place;
1186
+ if (!p) return `at ${pt(s[key])}`;
1187
+ return [`${Object.keys(SIDES).find((w) => SIDES[w] === p.side)} ${p.to}`, p.gap !== void 0 ? `gap ${p.gap}` : "", p.align ? `align=${p.align}` : ""].filter(Boolean).join(" ");
1188
+ }
1189
+ function printStep(s, props, indent) {
1190
+ const json = () => [`${indent}json ${JSON.stringify(Object.fromEntries(Object.entries(s).filter(([k]) => k !== "chapter" && k !== "say")))}`];
1191
+ const line2 = (head, skip) => {
1192
+ const a = printAttrs(s, skip);
1193
+ return a ? [`${indent}${[head, ...a].join(" ")}`] : json();
1194
+ };
1195
+ const placedSkip = s.place ? ["at", "center", "place"] : ["at", "center"];
1196
+ switch (s.type) {
1197
+ case "prop":
1198
+ if (!props.has(String(s.name)) || s.id && !/^[A-Za-z_][\w-]*$/.test(String(s.id))) return json();
1199
+ return line2([s.name, s.id ?? "", place(s, "at"), s.label !== void 0 ? q(s.label) : ""].filter(Boolean).join(" "), ["name", "id", "label", ...placedSkip]);
1200
+ case "text":
1201
+ return line2(`text ${q(s.text)} ${place(s, "at")}`, ["text", ...placedSkip]);
1202
+ case "circle":
1203
+ case "ellipse":
1204
+ return line2(`${s.type} ${place(s, "center")}`, placedSkip);
1205
+ case "line":
1206
+ case "arrow":
1207
+ case "highlight":
1208
+ return line2(`${s.type} ${pt(s.from)} -> ${pt(s.to)}`, ["from", "to"]);
1209
+ case "polyline":
1210
+ case "polygon":
1211
+ case "curve":
1212
+ return line2(`${s.type} ${s.points.map(pt).join(" ")}`, ["points"]);
1213
+ case "link":
1214
+ return line2(`${s.from} -> ${s.to}${s.label !== void 0 ? ` ${q(s.label)}` : ""}`, ["from", "to", "label"]);
1215
+ case "balloon":
1216
+ return line2(`${s.to} ${s.kind === "thought" ? "thinks" : "says"} ${q(s.text)}`, ["to", "text", "kind"]);
1217
+ case "counter":
1218
+ return line2(`counter ${place(s, "at")} ${num(s.value)}${s.format !== void 0 ? ` ${q(s.format)}` : ""}`, ["value", "format", ...placedSkip]);
1219
+ case "plot": {
1220
+ const b = s.box;
1221
+ const r = (v) => v.map(num).join("..");
1222
+ return line2(`plot ${q(s.fn)} domain ${r(s.domain)} range ${r(s.range)} box ${[b.x, b.y, b.w, b.h].map(num).join(",")}`, ["fn", "domain", "range", "box"]);
1223
+ }
1224
+ case "pause":
1225
+ return line2(`pause ${s.ms}${s.ask !== void 0 ? ` ask ${q(s.ask)}` : ""}`, ["ms", "ask"]);
1226
+ case "repeat": {
1227
+ const inner = printStep(s.step, props, "");
1228
+ if (inner.length !== 1 || s.step.chapter || s.step.say) return json();
1229
+ const head = printAttrs(s, ["times", "step"]);
1230
+ return head && !head.length ? [`${indent}repeat ${num(s.times)}: ${inner[0]}`] : json();
1231
+ }
1232
+ case "row":
1233
+ case "column":
1234
+ case "grid": {
1235
+ const head = line2(`${s.type} ${place(s, "at")}`, ["steps", ...placedSkip]);
1236
+ if (head[0].trimStart().startsWith("json")) return head;
1237
+ return [...head, ...s.steps.flatMap((c) => printStep(c, props, `${indent} `))];
1238
+ }
1239
+ case "when": {
1240
+ const head = printAttrs(s, ["if", "steps"]);
1241
+ if (!head || head.length) return json();
1242
+ return [`${indent}when ${num(s.if)}:`, ...s.steps.flatMap((c) => printStep(c, props, `${indent} `))];
1243
+ }
1244
+ default:
1245
+ return json();
1246
+ }
1247
+ }
1248
+ function printChalk(scene, props) {
1249
+ const names = new Set(props);
1250
+ const out = [];
1251
+ if (scene.id !== void 0) out.push(`id ${scene.id}`);
1252
+ if (scene.title !== void 0) out.push(`title ${scene.title}`);
1253
+ for (const k of ["category", "theme", "locale", "background"]) if (scene[k] !== void 0) out.push(`${k} ${scene[k]}`);
1254
+ if (scene.width !== 900 || scene.height !== 560) out.push(`size ${scene.width}x${scene.height}`);
1255
+ for (const [k, p] of Object.entries(scene.params ?? {})) {
1256
+ out.push(`param ${k} ${q(p.label)} ${p.min}..${p.max}${p.step !== 1 ? ` step=${p.step}` : ""} = ${p.value}`);
1257
+ }
1258
+ for (const [k, sw] of Object.entries(scene.switches ?? {})) out.push(`switch ${k} ${q(sw.label)} ${sw.on ? "on" : "off"}`);
1259
+ for (const [k, v] of Object.entries(scene.vars ?? {})) out.push(`var ${k} = ${num(v)}`);
1260
+ for (const [k, v] of Object.entries(scene.learning ?? {})) out.push(`learning ${k} ${q(v)}`);
1261
+ for (const s of scene.steps ?? []) {
1262
+ if (s.chapter !== void 0 || s.say !== void 0) out.push("");
1263
+ if (s.chapter !== void 0) out.push(`# ${s.chapter}`);
1264
+ if (s.say !== void 0) out.push(`> ${s.say}`);
1265
+ out.push(...printStep(s, names, ""));
1266
+ }
1267
+ const quiz = scene.quiz;
1268
+ if (quiz) {
1269
+ out.push("", `quiz ${q(quiz.question)}`);
1270
+ for (const o of quiz.options) out.push(` ${o.right ? "*" : "-"} ${o.text} -- ${o.why}`);
1271
+ }
1272
+ return out.join("\n").replace(/\n{3,}/g, "\n\n") + "\n";
1273
+ }
718
1274
  export {
1275
+ ChalkTextError,
719
1276
  PROP_ALIASES,
720
1277
  PROP_NAMES,
721
1278
  chaptersFor,
722
1279
  defaultValues,
1280
+ parseChalk,
1281
+ printChalk,
723
1282
  validateScene
724
1283
  };