@flatkit/sugarflat 0.1.1 → 0.2.1

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
@@ -43,11 +43,24 @@ came out looking alike.
43
43
 
44
44
  Two consequences, both deliberate:
45
45
 
46
- - **`raw { … }` passes through verbatim, always.** A sugar you cannot opt out of stops being scaffolding
47
- and becomes a template. Use it beside a gesture to add or override anything.
46
+ - **`raw { … }` passes through verbatim, always** — before or after the block, and so does any plain
47
+ FlatInk you write beside it. A sugar you cannot opt out of stops being scaffolding and becomes a
48
+ template.
49
+ - **`raw scene { … }` goes INSIDE the generated scene** — a program may only have one `scene`, so this
50
+ is the route decor takes. A gesture emits no appearance, which makes this the hatch that matters.
48
51
  - **The composition is yours.** A gesture names the objects it drives; you (or a skin) draw them. This is
49
52
  how `match` has always worked in the language proper.
50
53
 
54
+ ```
55
+ place organs { prompt "…" target Chest at 300,300 item heart -> Chest at 120,140 }
56
+
57
+ raw scene { layer "backdrop" { rect 0 0 760 620 fill linear(90, 0:#1b2a4a, 1:#4a6fa5) } }
58
+ raw { object "TChest" { opacity = 0.9 } }
59
+ ```
60
+
61
+ One gesture per document: each emits its own `scene`, and a second block is refused with
62
+ `MultipleGesturesError` rather than dropped.
63
+
51
64
  ## What it guarantees
52
65
 
53
66
  - **The required header is emitted.** `size` and `timeline`, from a document spec that is *readable*
@@ -78,7 +91,19 @@ learner makes a mistake and the activity stops responding, at the exact moment t
78
91
  | `steps <name> { prompt "…" step "…" at x,y }` | a gated sequence; out-of-order taps do nothing |
79
92
 
80
93
  Each emits `send "correct" / "incorrect" / "step" / "completed"` with a record payload naming the index
81
- (`{ item = 2 }`), so a host reads which one without depending on what the theme drew.
94
+ (`{ item = 2 }`), so a host reads which one without depending on what the theme drew. `desugar()` returns
95
+ the labels behind those indices, and the prompt, in `meta` — the host displays them, the gesture never
96
+ draws them:
97
+
98
+ ```ts
99
+ const { flatink, meta } = desugar(src)
100
+ meta.prompt // "Put each word under its part of speech" — the learner's instruction
101
+ meta.items[2] // the label behind `{ item = 2 }`
102
+ ```
103
+
104
+ For prompting a model, `sugarCard()` assembles the grammar, the canvas and the **footprints** of every
105
+ role. The footprints matter: measured on ten model-written activities, a prompt without them produced
106
+ overlapping hitboxes in 4 of 10, and 0 of 10 with.
82
107
 
83
108
  **Five gestures came in and three shipped.** `tri`, `ordonner` and `placement` shared one implementation:
84
109
  the only thing that differed was whether their targets were drawn as rectangles, squares or dotted discs.
package/dist/index.d.ts CHANGED
@@ -40,6 +40,8 @@ type Theme = {
40
40
  /** Drawing statements for a role, in LOCAL coordinates centred on 0,0. May be empty. */
41
41
  draw(role: Role, label: string): string[];
42
42
  };
43
+ /** Every role, in a stable order — for a reference card, or to sweep a theme. */
44
+ declare const ROLES: Role[];
43
45
  /**
44
46
  * Draws nothing. The gesture still emits every group, every box and every handler, so an author (or a
45
47
  * skin) supplies the whole appearance — and the test suite uses it to prove the gestures themselves
@@ -57,17 +59,59 @@ declare const GREYBOX: Theme;
57
59
  type GestureOptions = {
58
60
  theme?: Theme;
59
61
  };
60
- /** Build the shipped gestures against a theme. `GREYBOX` unless the caller supplies one. */
62
+ /**
63
+ * Build the shipped gestures against a theme. `GREYBOX` unless the caller supplies one.
64
+ *
65
+ * Each `summary` carries the FOOTPRINTS of the roles it places, because the author writes positions and
66
+ * two overlapping surfaces make a drop ambiguous. Measured on ten model-written activities: a prompt
67
+ * giving only "space them out" produced overlapping hitboxes in 4 of 10; the same prompt with the
68
+ * numbers, 0 of 10. The sizes were public all along — nothing said you had to go and read them.
69
+ */
61
70
  declare function gestures(opts?: GestureOptions): Gesture[];
71
+ /**
72
+ * The paste-ready reference for a model writing sugar: the grammar of every gesture, the footprints it
73
+ * must respect, and the canvas it is laying out on. This is the artefact `summary` was being used as by
74
+ * hand — assembled here so nobody has to know that the sizes live on the theme.
75
+ */
76
+ declare function sugarCard(opts?: GestureOptions & {
77
+ document?: {
78
+ width: number;
79
+ height: number;
80
+ };
81
+ }): string;
62
82
 
83
+ /**
84
+ * What a gesture parsed out of the author's block, beside the DSL it wrote.
85
+ *
86
+ * A host needs this. The expansion deliberately carries no text a host can read back — the prompt is the
87
+ * learner's instruction and the payloads are indices (`send "correct", { item = 2 }`), so without the
88
+ * labels there is nothing to display and nothing to map an index onto. Re-parsing the block by hand to
89
+ * recover data the package already parsed is not an integration; it is a second parser waiting to drift.
90
+ */
91
+ type GestureMeta = {
92
+ keyword: string;
93
+ /** The block's name. */
94
+ name: string;
95
+ /** The learner's instruction, verbatim. Drawn by the host or a theme — never by the gesture. */
96
+ prompt: string;
97
+ /** Labels in payload order: `{ item = 2 }` is `items[2]`. */
98
+ items: string[];
99
+ /** Target labels, in the order they were declared. Empty when the gesture has none. */
100
+ targets: string[];
101
+ };
102
+ /** What a gesture returns: the DSL, and what it understood. */
103
+ type Expansion = {
104
+ flatink: string;
105
+ meta: GestureMeta;
106
+ };
63
107
  /** A sugar block: the keyword that opens it, and how it expands. */
64
108
  type Gesture = {
65
- /** The opening keyword, e.g. `tri`. Matched only at column 0, followed by a name and `{`. */
109
+ /** The opening keyword, e.g. `place`. Matched only at column 0, followed by a name and `{`. */
66
110
  keyword: string;
67
111
  /** One line, for diagnostics and for the reference a model is prompted with. */
68
112
  summary: string;
69
- /** `body` is the text between the braces; `name` the block's name. Returns `.flatink`. */
70
- expand: (name: string, body: string, doc: DocumentSpec) => string;
113
+ /** `body` is the text between the braces; `name` the block's name. */
114
+ expand: (name: string, body: string, doc: DocumentSpec) => Expansion;
71
115
  };
72
116
  type DesugarOptions = {
73
117
  /** The canvas + timeline the expansion targets. */
@@ -81,6 +125,9 @@ type DesugarResult = {
81
125
  /** Which gesture matched — `raw` for an escape-only source, `null` for plain FlatInk. */
82
126
  kind: string | null;
83
127
  expanded: boolean;
128
+ /** What the gesture understood: the prompt and the labels behind the payload indices. `null` when
129
+ * nothing was expanded, since there is no block to have understood anything from. */
130
+ meta: GestureMeta | null;
84
131
  };
85
132
  /** Raised when a source opens a block that looks like sugar but no gesture claims it. */
86
133
  declare class UnknownSugarError extends Error {
@@ -89,6 +136,10 @@ declare class UnknownSugarError extends Error {
89
136
  }
90
137
  /** The gestures this package ships, drawn by the default theme. Each entry is a public promise. */
91
138
  declare const GESTURES: Gesture[];
139
+ /** Raised when a source holds more than one gesture block. */
140
+ declare class MultipleGesturesError extends Error {
141
+ constructor(first: string, second: string);
142
+ }
92
143
  /**
93
144
  * Expand a sugar source into `.flatink`.
94
145
  *
@@ -106,4 +157,4 @@ declare const GESTURES: Gesture[];
106
157
  */
107
158
  declare function desugar(src: string, opts?: DesugarOptions): DesugarResult;
108
159
 
109
- export { BLANK, DEFAULT_DOCUMENT, type DesugarOptions, type DesugarResult, type DocumentSpec, GESTURES, GREYBOX, type Gesture, type GestureOptions, type Role, type Theme, UnknownSugarError, desugar, ensureHeader, gestures, hasSizeHeader, hasTimelineHeader, ident, isIdent };
160
+ export { BLANK, DEFAULT_DOCUMENT, type DesugarOptions, type DesugarResult, type DocumentSpec, type Expansion, GESTURES, GREYBOX, type Gesture, type GestureMeta, type GestureOptions, MultipleGesturesError, ROLES, type Role, type Theme, UnknownSugarError, desugar, ensureHeader, gestures, hasSizeHeader, hasTimelineHeader, ident, isIdent, sugarCard };
package/dist/index.js CHANGED
@@ -18,6 +18,7 @@ function ident(label) {
18
18
  var isIdent = (label) => ident(label) === label && label.length > 0;
19
19
 
20
20
  // src/theme.ts
21
+ var ROLES = ["item", "target", "card", "chip"];
21
22
  var SIZES = {
22
23
  item: { w: 92, h: 92 },
23
24
  target: { w: 208, h: 118 },
@@ -81,6 +82,7 @@ function expandPlace(name, body, theme) {
81
82
  const m = parsePlace(name, body);
82
83
  const n = m.items.length;
83
84
  const out = [`// place "${q(name)}" \u2014 generated. The expansion is the artefact of record: edit it, or edit the block.`];
85
+ if (m.prompt) out.push(`// prompt: ${q(m.prompt)}`);
84
86
  out.push("var progress = 0", "var done = 0");
85
87
  for (const it of m.items) out.push(`var ${it.id}X = ${it.x}`, `var ${it.id}Y = ${it.y}`, `var ${it.id}Placed = 0`);
86
88
  out.push("", "scene {", ' layer "targets" {');
@@ -107,7 +109,10 @@ function expandPlace(name, body, theme) {
107
109
  }
108
110
  out.push(` x = ${it.id}X`, ` y = ${it.id}Y`, "}", "");
109
111
  });
110
- return out.join("\n");
112
+ return {
113
+ flatink: out.join("\n"),
114
+ meta: { keyword: "place", name, prompt: m.prompt, items: m.items.map((i) => i.label), targets: m.targets.map((t) => t.label) }
115
+ };
111
116
  }
112
117
  function expandCompose(name, body, theme) {
113
118
  let target = 0;
@@ -122,7 +127,7 @@ function expandCompose(name, body, theme) {
122
127
  }
123
128
  if (!target) throw new Error(`compose "${name}": no \`total <n>\` \u2014 there is nothing to reach`);
124
129
  if (!chips.length) throw new Error(`compose "${name}": no chip \u2014 nothing for the learner to tap`);
125
- const out = [`// compose "${q(name)}" \u2014 generated.`, "var total = 0", "var done = 0", "", "scene {", ' layer "chips" {'];
130
+ const out = [`// compose "${q(name)}" \u2014 generated.`, ...prompt ? [`// prompt: ${q(prompt)}`] : [], "var total = 0", "var done = 0", "", "scene {", ' layer "chips" {'];
126
131
  chips.forEach((c, i) => out.push(...container(`C${i}`, c.x, c.y, theme.draw("chip", c.value), theme.size("chip"))));
127
132
  out.push(" }", "}", "");
128
133
  chips.forEach((c, i) => {
@@ -134,8 +139,7 @@ function expandCompose(name, body, theme) {
134
139
  out.push(` if total == ${target} {`, " done = 1", ' send "completed"', " }");
135
140
  out.push(" }", " }", " }", "}", "");
136
141
  });
137
- if (prompt) out.push(`// prompt: ${q(prompt)}`);
138
- return out.join("\n");
142
+ return { flatink: out.join("\n"), meta: { keyword: "compose", name, prompt, items: chips.map((c) => c.value), targets: [] } };
139
143
  }
140
144
  function expandSteps(name, body, theme) {
141
145
  let prompt = "";
@@ -147,7 +151,7 @@ function expandSteps(name, body, theme) {
147
151
  else throw new Error(`steps "${name}": unrecognised line: ${line}`);
148
152
  }
149
153
  if (!steps.length) throw new Error(`steps "${name}": no step \u2014 the sequence is empty`);
150
- const out = [`// steps "${q(name)}" \u2014 generated.`, "var step = 0", "", "scene {", ' layer "steps" {'];
154
+ const out = [`// steps "${q(name)}" \u2014 generated.`, ...prompt ? [`// prompt: ${q(prompt)}`] : [], "var step = 0", "", "scene {", ' layer "steps" {'];
151
155
  steps.forEach((s, i) => out.push(...container(`S${i}`, s.x, s.y, theme.draw("card", s.label), theme.size("card"))));
152
156
  out.push(" }", "}", "");
153
157
  steps.forEach((_s, i) => {
@@ -156,29 +160,61 @@ function expandSteps(name, body, theme) {
156
160
  out.push(" }", " }");
157
161
  out.push(` opacity = step == ${i} ? 1 : 0.45`, "}", "");
158
162
  });
159
- if (prompt) out.push(`// prompt: ${q(prompt)}`);
160
- return out.join("\n");
163
+ return { flatink: out.join("\n"), meta: { keyword: "steps", name, prompt, items: steps.map((s2) => s2.label), targets: [] } };
161
164
  }
165
+ var footprint = (theme, role) => {
166
+ const { w, h } = theme.size(role);
167
+ return `${w}x${h}`;
168
+ };
162
169
  function gestures(opts = {}) {
163
170
  const theme = opts.theme ?? GREYBOX;
164
171
  return [
165
172
  {
166
173
  keyword: "place",
167
- summary: 'place <name> { prompt "\u2026" target <T> at x,y item <i> -> <T> at x,y } \u2014 drag items onto where they belong',
174
+ summary: `place <name> { prompt "\u2026" target <T> at x,y item <i> -> <T> at x,y } \u2014 drag items onto where they belong. Footprints: target ${footprint(theme, "target")}, item ${footprint(theme, "item")}; keep centres at least one footprint apart or the drop is ambiguous`,
168
175
  expand: (name, body) => expandPlace(name, body, theme)
169
176
  },
170
177
  {
171
178
  keyword: "compose",
172
- summary: 'compose <name> { prompt "\u2026" total <n> chip <v> at x,y } \u2014 tap values until they add up; overshooting resets',
179
+ summary: `compose <name> { prompt "\u2026" total <n> chip <v> at x,y } \u2014 tap values until they add up; overshooting resets. Footprint: chip ${footprint(theme, "chip")}`,
173
180
  expand: (name, body) => expandCompose(name, body, theme)
174
181
  },
175
182
  {
176
183
  keyword: "steps",
177
- summary: 'steps <name> { prompt "\u2026" step "\u2026" at x,y } \u2014 a gated sequence; out-of-order taps do nothing',
184
+ summary: `steps <name> { prompt "\u2026" step "\u2026" at x,y } \u2014 a gated sequence; out-of-order taps do nothing. Footprint: card ${footprint(theme, "card")}`,
178
185
  expand: (name, body) => expandSteps(name, body, theme)
179
186
  }
180
187
  ];
181
188
  }
189
+ function sugarCard(opts = {}) {
190
+ const theme = opts.theme ?? GREYBOX;
191
+ const doc = opts.document ?? { width: 760, height: 620 };
192
+ return [
193
+ "# FlatInk sugar \u2014 one block, one activity",
194
+ "",
195
+ `Canvas: ${doc.width}x${doc.height}, origin top-left. Write ONE block, nothing else. Coordinates are the`,
196
+ "CENTRE of each thing. Anything you do not describe is not drawn.",
197
+ "",
198
+ "## Blocks",
199
+ ...gestures({ theme }).map((g) => `- ${g.summary}`),
200
+ "",
201
+ "## Footprints \u2014 respect them or two things overlap and the drop becomes ambiguous",
202
+ ...ROLES.map((role) => `- ${role}: ${footprint(theme, role)}`),
203
+ "",
204
+ `A row of targets fits about ${Math.floor(doc.width / (theme.size("target").w + 20))} across; a row of items about ${Math.floor(doc.width / (theme.size("item").w + 20))}.`,
205
+ "Leave a gap the size of the thing itself between two centres.",
206
+ "",
207
+ "## Escape hatch \u2014 three forms, and they are not interchangeable",
208
+ "The block draws NOTHING beyond the pieces above. Everything else goes through one of these:",
209
+ "",
210
+ 'raw scene under { layer "bg" { \u2026 } } <- BACKGROUND and decor. Drawn BEHIND the activity.',
211
+ 'raw scene { layer "banner" { \u2026 } } <- overlays drawn ON TOP: a title, a frame, a caption.',
212
+ 'raw { var lives = 3 object "X" { \u2026 } } <- header and behavior: var, fn, asset, object blocks.',
213
+ "",
214
+ "A `layer` written in a plain `raw { \u2026 }` lands OUTSIDE the scene and is a compile error \u2014 anything",
215
+ "that is drawn belongs in one of the two `raw scene` forms. Either may be written on one line."
216
+ ].join("\n");
217
+ }
182
218
 
183
219
  // src/index.ts
184
220
  var UnknownSugarError = class extends Error {
@@ -193,9 +229,56 @@ var UnknownSugarError = class extends Error {
193
229
  };
194
230
  var GESTURES = gestures();
195
231
  var BLOCK_OPEN = /^([a-zA-Z][\w-]*)[ \t]+(?:"([^"]+)"|([^\s{"]+))[ \t]*\{/m;
196
- var RAW_BLOCK = /^raw[ \t]*\{\r?\n([\s\S]*?)\r?\n\}/gm;
197
- var unwrapRaw = (src) => src.replace(RAW_BLOCK, (_m, body) => `// -- raw (escape hatch: verbatim FlatInk) --
198
- ${body}`);
232
+ var RAW_OPEN = /^raw(?:[ \t]+scene(?:[ \t]+(under))?)?[ \t]*\{/gm;
233
+ function takeRaw(text) {
234
+ const out = { top: [], over: [], under: [], rest: "" };
235
+ let cursor = 0;
236
+ RAW_OPEN.lastIndex = 0;
237
+ for (let m = RAW_OPEN.exec(text); m; m = RAW_OPEN.exec(text)) {
238
+ const openBrace = m.index + m[0].length - 1;
239
+ const end = blockEnd(text, openBrace);
240
+ if (end < 0) break;
241
+ const body = text.slice(openBrace + 1, end).replace(/^\r?\n/, "").replace(/\r?\n[ \t]*$/, "");
242
+ const bucket = m[0].includes("scene") ? m[1] ? out.under : out.over : out.top;
243
+ bucket.push(body);
244
+ out.rest += text.slice(cursor, m.index);
245
+ cursor = end + 1;
246
+ RAW_OPEN.lastIndex = cursor;
247
+ }
248
+ out.rest += text.slice(cursor);
249
+ return out;
250
+ }
251
+ function blockEnd(text, openBrace) {
252
+ let depth = 1;
253
+ for (let i = openBrace + 1; i < text.length; i++) {
254
+ if (text[i] === "{") depth++;
255
+ else if (text[i] === "}" && --depth === 0) return i;
256
+ }
257
+ return -1;
258
+ }
259
+ var RAW_NOTE = "// -- raw (escape hatch: verbatim FlatInk) --";
260
+ function spliceIntoScene(flatink, under, over) {
261
+ if (!under.length && !over.length) return flatink;
262
+ const at = flatink.search(/^scene[ \t]*\{/m);
263
+ if (at < 0) throw new Error("`raw scene { \u2026 }` has nowhere to go: this expansion emits no `scene` block \u2014 use a plain `raw { \u2026 }` instead.");
264
+ const openBrace = flatink.indexOf("{", at);
265
+ const close = blockEnd(flatink, openBrace);
266
+ const head = under.length ? `
267
+ // -- raw scene under (verbatim, drawn BEHIND the gesture) --
268
+ ${under.join("\n")}` : "";
269
+ const tail = over.length ? ` // -- raw scene (verbatim, drawn ON TOP of the gesture) --
270
+ ${over.join("\n")}
271
+ ` : "";
272
+ return `${flatink.slice(0, openBrace + 1)}${head}${flatink.slice(openBrace + 1, close)}${tail}${flatink.slice(close)}`;
273
+ }
274
+ var MultipleGesturesError = class extends Error {
275
+ constructor(first, second) {
276
+ super(
277
+ `two gesture blocks in one source ("${first}" and "${second}") \u2014 a document holds ONE. Each gesture emits its own \`scene\`, and a program may only have one, so the second cannot be merged in. Split them into two documents, or write the extra part by hand in \`raw { \u2026 }\` / \`raw scene { \u2026 }\`.`
278
+ );
279
+ this.name = "MultipleGesturesError";
280
+ }
281
+ };
199
282
  function desugar(src, opts = {}) {
200
283
  const doc = opts.document ?? DEFAULT_DOCUMENT;
201
284
  const gestures2 = opts.gestures ?? GESTURES;
@@ -204,14 +287,36 @@ function desugar(src, opts = {}) {
204
287
  const gesture = keyword ? gestures2.find((g) => g.keyword === keyword) : void 0;
205
288
  if (gesture) {
206
289
  const name = open?.[2] ?? open?.[3] ?? gesture.keyword;
207
- const body = blockBody(src, open.index + open[0].length - 1);
208
- return { flatink: ensureHeader(unwrapRaw(gesture.expand(name, body, doc)), doc), kind: gesture.keyword, expanded: true };
290
+ const braceAt = open.index + open[0].length - 1;
291
+ const body = blockBody(src, braceAt);
292
+ const before = src.slice(0, open.index);
293
+ const after = src.slice(braceAt + 1 + body.length + 1);
294
+ const other = findGesture(before + "\n" + after, gestures2);
295
+ if (other) throw new MultipleGesturesError(gesture.keyword, other);
296
+ const { flatink, meta } = gesture.expand(name, body, doc);
297
+ const head = takeRaw(before);
298
+ const tail = takeRaw(after);
299
+ const expanded = spliceIntoScene(flatink, [...head.under, ...tail.under], [...head.over, ...tail.over]);
300
+ const topLevel = (p) => [p.rest.trim(), ...p.top.length ? [RAW_NOTE, ...p.top] : []].filter(Boolean).join("\n");
301
+ const whole = [topLevel(head), expanded, topLevel(tail)].filter(Boolean).join("\n\n");
302
+ return { flatink: ensureHeader(whole, doc), kind: gesture.keyword, expanded: true, meta };
303
+ }
304
+ if (keyword && keyword !== "raw" && !FLATINK_BLOCKS.has(keyword)) throw new UnknownSugarError(keyword, gestures2.map((g) => g.keyword));
305
+ const parts = takeRaw(src);
306
+ if (parts.over.length || parts.under.length) {
307
+ throw new Error("`raw scene { \u2026 }` needs a gesture block to splice into \u2014 this source has none. Write a plain `scene { \u2026 }` yourself.");
209
308
  }
210
- if (keyword && !FLATINK_BLOCKS.has(keyword)) throw new UnknownSugarError(keyword, gestures2.map((g) => g.keyword));
211
- const out = unwrapRaw(src);
212
- return { flatink: out, kind: out === src ? null : "raw", expanded: out !== src };
309
+ const out = [parts.rest.trim(), ...parts.top.length ? [RAW_NOTE, ...parts.top] : []].filter(Boolean).join("\n");
310
+ return { flatink: parts.top.length ? out : src, kind: parts.top.length ? "raw" : null, expanded: parts.top.length > 0, meta: null };
213
311
  }
214
312
  var FLATINK_BLOCKS = /* @__PURE__ */ new Set(["scene", "object", "symbol", "layer", "group", "match", "each", "states", "params", "when", "every", "repeat", "fn", "at"]);
313
+ function findGesture(text, gestures2) {
314
+ const re = new RegExp(BLOCK_OPEN.source, "gm");
315
+ for (let m = re.exec(text); m; m = re.exec(text)) {
316
+ if (gestures2.some((g) => g.keyword === m[1])) return m[1];
317
+ }
318
+ return null;
319
+ }
215
320
  function blockBody(src, openIndex) {
216
321
  let depth = 1;
217
322
  let i = openIndex + 1;
@@ -226,6 +331,8 @@ export {
226
331
  DEFAULT_DOCUMENT,
227
332
  GESTURES,
228
333
  GREYBOX,
334
+ MultipleGesturesError,
335
+ ROLES,
229
336
  UnknownSugarError,
230
337
  desugar,
231
338
  ensureHeader,
@@ -233,6 +340,7 @@ export {
233
340
  hasSizeHeader,
234
341
  hasTimelineHeader,
235
342
  ident,
236
- isIdent
343
+ isIdent,
344
+ sugarCard
237
345
  };
238
346
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/document.ts","../src/ident.ts","../src/theme.ts","../src/gestures.ts","../src/index.ts"],"sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// document.ts — the document a sugar block expands into.\n//\n// Two things a sugar MUST know and an author MUST be able to read: the canvas the gesture is laid out\n// for, and the timeline it rides. Both were literals buried inside an expander, which cost twice:\n// • the expansion never emitted `size` at all, so it fell back on the compiler's 800x600 default\n// while the board was painted for 760x620 -- everything past x=760 was clipped in silence, on the\n// whole corpus, for months (nothing says so: a missing `size` still compiles);\n// • prompting a model for the right coordinates meant reading the generator's source.\n// Exposed here, overridable per call, and stamped onto every expansion by `ensureHeader`.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** The canvas + timeline a sugar block expands into. */\nexport type DocumentSpec = {\n width: number\n height: number\n fps: number\n /** Timeline length. Long enough that `time` does not wrap mid-activity (the compiler warns under 120). */\n durationFrames: number\n}\n\n/** The board the gestures are laid out for. Override per call when a host uses another canvas. */\nexport const DEFAULT_DOCUMENT: DocumentSpec = { width: 760, height: 620, fps: 24, durationFrames: 120 }\n\n/** `true` if the source already declares its canvas — `size` must be the FIRST statement of a program. */\nexport const hasSizeHeader = (src: string): boolean => /^[ \\t]*size\\s+-?[\\d.]+\\s+-?[\\d.]+/m.test(src)\n\n/** `true` if the source already declares a root timeline. */\nexport const hasTimelineHeader = (src: string): boolean => /^[ \\t]*timeline\\s+\\d+/m.test(src)\n\n/**\n * Prepend whatever header the expansion is missing. `size` is REQUIRED by the format and silently\n * defaulted by the compiler when absent, so emitting it is not a nicety: it is the difference between\n * the document the author designed and the one that gets drawn.\n */\nexport function ensureHeader(flatink: string, doc: DocumentSpec = DEFAULT_DOCUMENT): string {\n const head: string[] = []\n if (!hasSizeHeader(flatink)) head.push(`size ${doc.width} ${doc.height}`)\n if (!hasTimelineHeader(flatink)) head.push(`timeline ${doc.fps} ${doc.durationFrames}`)\n return head.length ? `${head.join('\\n')}\\n${flatink}` : flatink\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// ident.ts — a human label -> a FlatInk identifier.\n//\n// A sugar lets an author name things in their own language (\"chene\", \"Vegetaux\", \"21 janvier 1793\"),\n// and those names become identifiers (`var`, `object`). FlatInk identifiers are `[A-Za-z0-9_]`, so an\n// accent does not error -- the lexer stops at it and reads the tail. `chene` written with its accent\n// became `var <accented>X`, read as `neX`: two variables where the author wrote one, and no diagnostic.\n// Transliterate here, once, rather than in every gesture.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * A FlatInk identifier derived from a human label: accents folded to ASCII, anything else to `_`, and\n * a leading digit prefixed (an identifier may not start with one). Stable and idempotent, so the same\n * label always yields the same name — the sugar's variables and the author's references must agree.\n */\nexport function ident(label: string): string {\n const ascii = label.normalize('NFD').replace(/\\p{Diacritic}/gu, '').replace(/[^A-Za-z0-9_]/g, '_')\n return /^[0-9]/.test(ascii) ? `n${ascii}` : ascii\n}\n\n/** `true` if the label survives `ident` unchanged — i.e. it is already a usable identifier. */\nexport const isIdent = (label: string): boolean => ident(label) === label && label.length > 0\n","// ─────────────────────────────────────────────────────────────────────────────\n// theme.ts — where appearance lives, so that gestures do not carry any.\n//\n// A gesture emits STRUCTURE and BEHAVIOR: named groups at the coordinates the author gave, their drop\n// boxes, their state, their handlers. It must never decide what any of it LOOKS like — a sugar that\n// ships a look hands every activity built on it the same face, and that has already happened once.\n//\n// So appearance is a parameter. `BLANK` draws nothing at all, which is what makes the claim testable\n// rather than aspirational: expand a gesture with `BLANK` and the output must not contain one colour,\n// one font, or one stroke. `GREYBOX` is a deliberately plain default so a freshly written block is\n// playable immediately — it is provisional by design, and swapping it is a single argument.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** What a drawn thing is FOR. Four roles cover every gesture; a theme need not distinguish more. */\nexport type Role =\n | 'item' // something the learner moves\n | 'target' // where an item belongs\n | 'card' // a panel the learner reads and taps\n | 'chip' // a small tappable value\n\nexport type Theme = {\n readonly name: string\n /** Footprint of a role. The gesture uses it for drop boxes; the theme must draw within it. */\n size(role: Role): { w: number; h: number }\n /** Drawing statements for a role, in LOCAL coordinates centred on 0,0. May be empty. */\n draw(role: Role, label: string): string[]\n}\n\nconst SIZES: Record<Role, { w: number; h: number }> = {\n item: { w: 92, h: 92 },\n target: { w: 208, h: 118 },\n card: { w: 180, h: 140 },\n chip: { w: 96, h: 96 },\n}\n\n/**\n * Draws nothing. The gesture still emits every group, every box and every handler, so an author (or a\n * skin) supplies the whole appearance — and the test suite uses it to prove the gestures themselves\n * carry no visual opinion.\n */\nexport const BLANK: Theme = {\n name: 'blank',\n size: (role) => SIZES[role],\n draw: () => [],\n}\n\n/** Rounded rectangle path, centred on the origin — the greybox's only shape vocabulary. */\nconst plate = (w: number, h: number, r: number): string => `rect ${-w / 2} ${-h / 2} ${w} ${h} ${r}`\n\n/**\n * A plain, provisional greybox: readable, anonymous, and meant to be replaced. It exists so `place x {\n * … }` is playable the moment it is written; it is not a design, and anything built for an audience\n * should pass its own theme (or redraw beside the gesture with `raw { … }`).\n */\nexport const GREYBOX: Theme = {\n name: 'greybox',\n size: (role) => SIZES[role],\n draw(role, label) {\n const { w, h } = SIZES[role]\n const text = (size: number, boxW: number, boxH: number, colour: string, y: number) =>\n `text \"${label.replace(/\"/g, \"'\")}\" at ${-boxW / 2},${y} font \"system-ui, sans-serif\" size ${size} align center line 1.2 color ${colour} box ${boxW} ${boxH} wrap`\n switch (role) {\n case 'item':\n return [`circle 0 0 ${w / 2 - 12} fill #e8c46b`, `circle 0 0 ${w / 2 - 12} nofill stroke #b8923f 2`, text(14, w - 8, 20, '#4a3a1e', -9)]\n case 'target':\n return [`${plate(w - 8, h - 8, 14)} fill #2e3a4e`, `${plate(w - 8, h - 8, 14)} nofill stroke #5a6f8a 2 dash 8,6`, text(16, w - 28, 22, '#aeb6c4', -h / 2 + 10)]\n case 'card':\n return [`${plate(w, h, 16)} fill #2b3447`, `${plate(w, h, 16)} nofill stroke #44516a 2`, text(15, w - 20, h - 40, '#d7deea', -h / 2 + 26)]\n case 'chip':\n return [`circle 0 0 ${w / 2 - 8} fill #e8c46b`, `circle 0 0 ${w / 2 - 8} nofill stroke #b8923f 3`, text(22, w - 16, 28, '#4a3a1e', -13)]\n }\n },\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// gestures.ts — the three primitives.\n//\n// Five named gestures came in (`tri`, `ordonner`, `placement`, `composer`, `etapes`). Read side by\n// side, the first three share ONE implementation: the only thing that differed between them was which\n// shape their targets were drawn as -- rectangles, squares or dotted discs. That is appearance, and\n// appearance is now the theme's business, so they collapse into a single `place`. Five public promises\n// became three.\n//\n// Each gesture emits state, structure and behavior. Not one colour, not one font: `BLANK` proves it.\n// Coordinates DO appear, and that is not an opinion -- they are the ones the author wrote in the block.\n// ─────────────────────────────────────────────────────────────────────────────\nimport type { Gesture } from './index'\nimport { ident } from './ident'\nimport { GREYBOX, type Theme } from './theme'\n\n/** Everything a gesture needs beyond the author's text. */\nexport type GestureOptions = { theme?: Theme }\n\nconst q = (s: string) => s.replace(/\"/g, \"'\")\n\n/** Statements of a named group placed at `x,y`, drawn by the theme, optionally a drop box. */\nfunction container(name: string, x: string, y: string, art: string[], hitbox?: { w: number; h: number }): string[] {\n const box = hitbox ? ` hitbox ${hitbox.w} ${hitbox.h}` : ''\n return [` group \"${name}\" at ${x},${y} pivot 0,0${box} {`, ' layer \"art\" {', ...art.map((l) => ` ${l}`), ' }', ' }']\n}\n\n/** Split a block body into its non-empty, comment-free lines. */\nconst lines = (body: string): string[] => body.split('\\n').map((l) => l.replace(/\\/\\/.*$/, '').trim()).filter(Boolean)\n\n// ── place ────────────────────────────────────────────────────────────────────\n// The drop gesture: items belong on targets. Several items may share a target (a sort) or each may have\n// its own (an ordering, a labelled diagram) -- that falls out of the data, it is not a separate gesture.\n\ntype PlaceModel = {\n prompt: string\n targets: { id: string; label: string; x: string; y: string }[]\n items: { id: string; label: string; target: string; x: string; y: string }[]\n}\n\nfunction parsePlace(name: string, body: string): PlaceModel {\n const m: PlaceModel = { prompt: '', targets: [], items: [] }\n for (const line of lines(body)) {\n let x: RegExpMatchArray | null\n if ((x = line.match(/^prompt\\s+\"(.*)\"$/))) m.prompt = x[1]\n else if ((x = line.match(/^target\\s+(?:\"([^\"]+)\"|(\\S+))\\s+at\\s+(-?[\\d.]+),(-?[\\d.]+)$/))) {\n const label = x[1] ?? x[2]\n m.targets.push({ id: `T${ident(label)}`, label, x: x[3], y: x[4] })\n } else if ((x = line.match(/^item\\s+(?:\"([^\"]+)\"|(\\S+))\\s*->\\s*(?:\"([^\"]+)\"|(\\S+))\\s+at\\s+(-?[\\d.]+),(-?[\\d.]+)$/))) {\n const label = x[1] ?? x[2]\n m.items.push({ id: `I${ident(label)}`, label, target: `T${ident(x[3] ?? x[4])}`, x: x[5], y: x[6] })\n } else throw new Error(`place \"${name}\": unrecognised line: ${line}`)\n }\n if (!m.targets.length) throw new Error(`place \"${name}\": no target — an item has nowhere to go`)\n if (!m.items.length) throw new Error(`place \"${name}\": no item — nothing for the learner to do`)\n const known = new Set(m.targets.map((t) => t.id))\n for (const it of m.items) {\n if (!known.has(it.target)) throw new Error(`place \"${name}\": item \"${it.label}\" points at an unknown target`)\n }\n return m\n}\n\nfunction expandPlace(name: string, body: string, theme: Theme): string {\n const m = parsePlace(name, body)\n const n = m.items.length\n const out: string[] = [`// place \"${q(name)}\" — generated. The expansion is the artefact of record: edit it, or edit the block.`]\n\n out.push('var progress = 0', 'var done = 0')\n for (const it of m.items) out.push(`var ${it.id}X = ${it.x}`, `var ${it.id}Y = ${it.y}`, `var ${it.id}Placed = 0`)\n\n out.push('', 'scene {', ' layer \"targets\" {')\n for (const t of m.targets) out.push(...container(t.id, t.x, t.y, theme.draw('target', t.label), theme.size('target')))\n out.push(' }', ' layer \"items\" {')\n for (const it of m.items) out.push(...container(it.id, it.x, it.y, theme.draw('item', it.label)))\n out.push(' }', '}', '')\n\n m.items.forEach((it, i) => {\n out.push(`object \"${it.id}\" {`)\n out.push(` drag ${it.id}X, ${it.id}Y { enabled ${it.id}Placed == 0 }`)\n out.push(` when dropped on ${it.target} at pointer {`)\n out.push(` if ${it.id}Placed < 0.5 {`)\n out.push(` ${it.id}Placed = 1`, ' progress = progress + 1')\n // Snap onto the target it was dropped on, reading the target's LIVE position -- so a skin may move\n // the target anywhere and the item still lands on it.\n out.push(` ${it.id}X = ${it.target}.x`, ` ${it.id}Y = ${it.target}.y`)\n out.push(` send \"correct\", { item = ${i} }`)\n out.push(` if progress == ${n} {`, ' done = 1', ' send \"completed\"', ' }')\n out.push(' }', ' }')\n for (const t of m.targets) {\n if (t.id === it.target) continue\n out.push(` when dropped on ${t.id} at pointer {`)\n out.push(` ${it.id}X = ${it.x}`, ` ${it.id}Y = ${it.y}`) // back where it started\n out.push(` send \"incorrect\", { item = ${i} }`)\n out.push(' }')\n }\n out.push(` x = ${it.id}X`, ` y = ${it.id}Y`, '}', '')\n })\n return out.join('\\n')\n}\n\n// ── compose ──────────────────────────────────────────────────────────────────\n// Tap values until they add up to a target. Overshooting resets the total -- the whole point of the\n// exercise is that going over is a mistake you feel, not one you are prevented from making.\n\nfunction expandCompose(name: string, body: string, theme: Theme): string {\n let target = 0\n const chips: { value: string; x: string; y: string }[] = []\n let prompt = ''\n for (const line of lines(body)) {\n let x: RegExpMatchArray | null\n if ((x = line.match(/^prompt\\s+\"(.*)\"$/))) prompt = x[1]\n else if ((x = line.match(/^total\\s+(\\d+)$/))) target = Number(x[1])\n else if ((x = line.match(/^chip\\s+(\\d+)\\s+at\\s+(-?[\\d.]+),(-?[\\d.]+)$/))) chips.push({ value: x[1], x: x[2], y: x[3] })\n else throw new Error(`compose \"${name}\": unrecognised line: ${line}`)\n }\n if (!target) throw new Error(`compose \"${name}\": no \\`total <n>\\` — there is nothing to reach`)\n if (!chips.length) throw new Error(`compose \"${name}\": no chip — nothing for the learner to tap`)\n\n const out: string[] = [`// compose \"${q(name)}\" — generated.`, 'var total = 0', 'var done = 0', '', 'scene {', ' layer \"chips\" {']\n chips.forEach((c, i) => out.push(...container(`C${i}`, c.x, c.y, theme.draw('chip', c.value), theme.size('chip'))))\n out.push(' }', '}', '')\n chips.forEach((c, i) => {\n out.push(`object \"C${i}\" {`, ' when clicked {', ' if done < 0.5 {')\n out.push(` if total + ${c.value} > ${target} {`)\n out.push(' total = 0', ` send \"incorrect\", { chip = ${i} }`)\n out.push(' } else {')\n out.push(` total = total + ${c.value}`, ` send \"correct\", { chip = ${i} }`)\n out.push(` if total == ${target} {`, ' done = 1', ' send \"completed\"', ' }')\n out.push(' }', ' }', ' }', '}', '')\n })\n if (prompt) out.push(`// prompt: ${q(prompt)}`)\n return out.join('\\n')\n}\n\n// ── steps ────────────────────────────────────────────────────────────────────\n// A gated sequence: step i only responds when it is the current one. Tapping out of order does nothing\n// at all -- no error, no penalty, which is what makes it usable as an escape-room stage.\n\nfunction expandSteps(name: string, body: string, theme: Theme): string {\n let prompt = ''\n const steps: { label: string; x: string; y: string }[] = []\n for (const line of lines(body)) {\n let x: RegExpMatchArray | null\n if ((x = line.match(/^prompt\\s+\"(.*)\"$/))) prompt = x[1]\n else if ((x = line.match(/^step\\s+\"(.*)\"\\s+at\\s+(-?[\\d.]+),(-?[\\d.]+)$/))) steps.push({ label: x[1], x: x[2], y: x[3] })\n else throw new Error(`steps \"${name}\": unrecognised line: ${line}`)\n }\n if (!steps.length) throw new Error(`steps \"${name}\": no step — the sequence is empty`)\n\n const out: string[] = [`// steps \"${q(name)}\" — generated.`, 'var step = 0', '', 'scene {', ' layer \"steps\" {']\n steps.forEach((s, i) => out.push(...container(`S${i}`, s.x, s.y, theme.draw('card', s.label), theme.size('card'))))\n out.push(' }', '}', '')\n steps.forEach((_s, i) => {\n out.push(`object \"S${i}\" {`, ' when clicked {', ` if step == ${i} {`, ` step = step + 1`, ` send \"step\", { index = ${i} }`)\n if (i === steps.length - 1) out.push(' send \"completed\"')\n out.push(' }', ' }')\n // The only thing the gesture says about looks: a step that is not current is dimmed, because\n // \"which one is live\" is STATE, not decoration. A theme that disagrees rebinds opacity itself.\n out.push(` opacity = step == ${i} ? 1 : 0.45`, '}', '')\n })\n if (prompt) out.push(`// prompt: ${q(prompt)}`)\n return out.join('\\n')\n}\n\n/** Build the shipped gestures against a theme. `GREYBOX` unless the caller supplies one. */\nexport function gestures(opts: GestureOptions = {}): Gesture[] {\n const theme = opts.theme ?? GREYBOX\n return [\n {\n keyword: 'place',\n summary: 'place <name> { prompt \"…\" target <T> at x,y item <i> -> <T> at x,y } — drag items onto where they belong',\n expand: (name, body) => expandPlace(name, body, theme),\n },\n {\n keyword: 'compose',\n summary: 'compose <name> { prompt \"…\" total <n> chip <v> at x,y } — tap values until they add up; overshooting resets',\n expand: (name, body) => expandCompose(name, body, theme),\n },\n {\n keyword: 'steps',\n summary: 'steps <name> { prompt \"…\" step \"…\" at x,y } — a gated sequence; out-of-order taps do nothing',\n expand: (name, body) => expandSteps(name, body, theme),\n },\n ]\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// @flatkit/sugarflat — declarative sugar for FlatInk.\n//\n// A compact block expands into INSPECTABLE `.flatink`. The expansion is the artefact of record: the\n// sugar is an authoring tool, never a runtime, and anything it writes can be written by hand instead.\n//\n// WHY IT LIVES IN THIS REPO. Not to put authoring opinions into the language — it is a separate\n// package, out of the lockstep group, and `@flatkit/compiler` never depends on it. It lives here so\n// the two move together: a grammar change breaks these tests in the same CI run. That is the whole\n// argument, and it was demonstrated rather than argued — the previous out-of-tree sugar never emitted\n// the format's REQUIRED `size` line, on its entire corpus, for months, unnoticed.\n//\n// THE LINE THIS PACKAGE DOES NOT CROSS: no visual opinion. A gesture emits state and behavior; it\n// never emits a coordinate, a colour or a font. The language already ships interaction opinions\n// (`drag`, `when dropped on … at pointer`, `hitbox`, `match`, `feedback`, `spring`) — what it has\n// never shipped is a look, and a sugar that shipped one would hand every activity the same face.\n// ─────────────────────────────────────────────────────────────────────────────\nimport { DEFAULT_DOCUMENT, ensureHeader, type DocumentSpec } from './document'\nimport { gestures } from './gestures'\n\nexport { DEFAULT_DOCUMENT, ensureHeader, hasSizeHeader, hasTimelineHeader, type DocumentSpec } from './document'\nexport { ident, isIdent } from './ident'\nexport { gestures, type GestureOptions } from './gestures'\nexport { BLANK, GREYBOX, type Role, type Theme } from './theme'\n\n/** A sugar block: the keyword that opens it, and how it expands. */\nexport type Gesture = {\n /** The opening keyword, e.g. `tri`. Matched only at column 0, followed by a name and `{`. */\n keyword: string\n /** One line, for diagnostics and for the reference a model is prompted with. */\n summary: string\n /** `body` is the text between the braces; `name` the block's name. Returns `.flatink`. */\n expand: (name: string, body: string, doc: DocumentSpec) => string\n}\n\nexport type DesugarOptions = {\n /** The canvas + timeline the expansion targets. */\n document?: DocumentSpec\n /** Gestures to recognise. Defaults to the registered set. */\n gestures?: Gesture[]\n}\n\nexport type DesugarResult = {\n /** The expanded (or passed-through) FlatInk. */\n flatink: string\n /** Which gesture matched — `raw` for an escape-only source, `null` for plain FlatInk. */\n kind: string | null\n expanded: boolean\n}\n\n/** Raised when a source opens a block that looks like sugar but no gesture claims it. */\nexport class UnknownSugarError extends Error {\n constructor(readonly keyword: string, known: string[]) {\n super(\n `unknown sugar block \"${keyword}\" — no gesture claims it, so nothing was expanded. ` +\n `Known blocks: ${known.length ? known.join(', ') : '(none registered)'}. ` +\n `If this is meant to be plain FlatInk, it is not: \"${keyword}\" is not a FlatInk statement either.`,\n )\n this.name = 'UnknownSugarError'\n }\n}\n\n/** The gestures this package ships, drawn by the default theme. Each entry is a public promise. */\nexport const GESTURES: Gesture[] = gestures()\n\n/** A block opens at column 0: `<keyword> <name> {`. The name may be quoted (so it can hold spaces). */\nconst BLOCK_OPEN = /^([a-zA-Z][\\w-]*)[ \\t]+(?:\"([^\"]+)\"|([^\\s{\"]+))[ \\t]*\\{/m\n\n/** `raw { … }` (both braces at column 0) passes through VERBATIM — the escape hatch, kept deliberately.\n * A sugar that cannot be opted out of stops being scaffolding and becomes a cage: the previous\n * generation of this idea flattened every activity into the same shape until the template was pulled\n * apart. Anything a gesture writes must remain writable by hand, beside it. */\nconst RAW_BLOCK = /^raw[ \\t]*\\{\\r?\\n([\\s\\S]*?)\\r?\\n\\}/gm\n\nconst unwrapRaw = (src: string): string =>\n src.replace(RAW_BLOCK, (_m, body: string) => `// -- raw (escape hatch: verbatim FlatInk) --\\n${body}`)\n\n/**\n * Expand a sugar source into `.flatink`.\n *\n * Plain FlatInk passes through untouched. A source whose first block matches no gesture RAISES rather\n * than passing through: a silent fall-through sent the author's block downstream as if it were FlatInk,\n * to fail a hundred lines later on messages that only talk about FlatInk.\n *\n * ```ts\n * import { desugar } from '@flatkit/sugarflat'\n * import { checkProgram } from '@flatkit/compiler'\n *\n * const { flatink } = desugar(srcFromAnLLM)\n * const { ok, report } = checkProgram(flatink) // the expansion is the artefact of record\n * ```\n */\nexport function desugar(src: string, opts: DesugarOptions = {}): DesugarResult {\n const doc = opts.document ?? DEFAULT_DOCUMENT\n const gestures = opts.gestures ?? GESTURES\n\n const open = BLOCK_OPEN.exec(src)\n const keyword = open?.[1]\n const gesture = keyword ? gestures.find((g) => g.keyword === keyword) : undefined\n\n if (gesture) {\n const name = open?.[2] ?? open?.[3] ?? gesture.keyword\n const body = blockBody(src, open!.index + open![0].length - 1)\n return { flatink: ensureHeader(unwrapRaw(gesture.expand(name, body, doc)), doc), kind: gesture.keyword, expanded: true }\n }\n // A keyword that opens a block at column 0 and is NOT a FlatInk statement is a sugar block we do not\n // know — never a program. Say so here, with the list, rather than downstream in another vocabulary.\n if (keyword && !FLATINK_BLOCKS.has(keyword)) throw new UnknownSugarError(keyword, gestures.map((g) => g.keyword))\n\n const out = unwrapRaw(src)\n return { flatink: out, kind: out === src ? null : 'raw', expanded: out !== src }\n}\n\n/** Block keywords that belong to FlatINK itself — a source opening with one of these is a program. */\nconst FLATINK_BLOCKS = new Set(['scene', 'object', 'symbol', 'layer', 'group', 'match', 'each', 'states', 'params', 'when', 'every', 'repeat', 'fn', 'at'])\n\n/** Text between the braces opened at `openIndex`, brace-counting so nested blocks survive. */\nfunction blockBody(src: string, openIndex: number): string {\n let depth = 1\n let i = openIndex + 1\n for (; i < src.length && depth > 0; i++) {\n if (src[i] === '{') depth++\n else if (src[i] === '}') depth--\n }\n return src.slice(openIndex + 1, depth === 0 ? i - 1 : src.length)\n}\n"],"mappings":"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","names":["gestures"]}
1
+ {"version":3,"sources":["../src/document.ts","../src/ident.ts","../src/theme.ts","../src/gestures.ts","../src/index.ts"],"sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// document.ts — the document a sugar block expands into.\n//\n// Two things a sugar MUST know and an author MUST be able to read: the canvas the gesture is laid out\n// for, and the timeline it rides. Both were literals buried inside an expander, which cost twice:\n// • the expansion never emitted `size` at all, so it fell back on the compiler's 800x600 default\n// while the board was painted for 760x620 -- everything past x=760 was clipped in silence, on the\n// whole corpus, for months (nothing says so: a missing `size` still compiles);\n// • prompting a model for the right coordinates meant reading the generator's source.\n// Exposed here, overridable per call, and stamped onto every expansion by `ensureHeader`.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** The canvas + timeline a sugar block expands into. */\nexport type DocumentSpec = {\n width: number\n height: number\n fps: number\n /** Timeline length. Long enough that `time` does not wrap mid-activity (the compiler warns under 120). */\n durationFrames: number\n}\n\n/** The board the gestures are laid out for. Override per call when a host uses another canvas. */\nexport const DEFAULT_DOCUMENT: DocumentSpec = { width: 760, height: 620, fps: 24, durationFrames: 120 }\n\n/** `true` if the source already declares its canvas — `size` must be the FIRST statement of a program. */\nexport const hasSizeHeader = (src: string): boolean => /^[ \\t]*size\\s+-?[\\d.]+\\s+-?[\\d.]+/m.test(src)\n\n/** `true` if the source already declares a root timeline. */\nexport const hasTimelineHeader = (src: string): boolean => /^[ \\t]*timeline\\s+\\d+/m.test(src)\n\n/**\n * Prepend whatever header the expansion is missing. `size` is REQUIRED by the format and silently\n * defaulted by the compiler when absent, so emitting it is not a nicety: it is the difference between\n * the document the author designed and the one that gets drawn.\n */\nexport function ensureHeader(flatink: string, doc: DocumentSpec = DEFAULT_DOCUMENT): string {\n const head: string[] = []\n if (!hasSizeHeader(flatink)) head.push(`size ${doc.width} ${doc.height}`)\n if (!hasTimelineHeader(flatink)) head.push(`timeline ${doc.fps} ${doc.durationFrames}`)\n return head.length ? `${head.join('\\n')}\\n${flatink}` : flatink\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// ident.ts — a human label -> a FlatInk identifier.\n//\n// A sugar lets an author name things in their own language (\"chene\", \"Vegetaux\", \"21 janvier 1793\"),\n// and those names become identifiers (`var`, `object`). FlatInk identifiers are `[A-Za-z0-9_]`, so an\n// accent does not error -- the lexer stops at it and reads the tail. `chene` written with its accent\n// became `var <accented>X`, read as `neX`: two variables where the author wrote one, and no diagnostic.\n// Transliterate here, once, rather than in every gesture.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * A FlatInk identifier derived from a human label: accents folded to ASCII, anything else to `_`, and\n * a leading digit prefixed (an identifier may not start with one). Stable and idempotent, so the same\n * label always yields the same name — the sugar's variables and the author's references must agree.\n */\nexport function ident(label: string): string {\n const ascii = label.normalize('NFD').replace(/\\p{Diacritic}/gu, '').replace(/[^A-Za-z0-9_]/g, '_')\n return /^[0-9]/.test(ascii) ? `n${ascii}` : ascii\n}\n\n/** `true` if the label survives `ident` unchanged — i.e. it is already a usable identifier. */\nexport const isIdent = (label: string): boolean => ident(label) === label && label.length > 0\n","// ─────────────────────────────────────────────────────────────────────────────\n// theme.ts — where appearance lives, so that gestures do not carry any.\n//\n// A gesture emits STRUCTURE and BEHAVIOR: named groups at the coordinates the author gave, their drop\n// boxes, their state, their handlers. It must never decide what any of it LOOKS like — a sugar that\n// ships a look hands every activity built on it the same face, and that has already happened once.\n//\n// So appearance is a parameter. `BLANK` draws nothing at all, which is what makes the claim testable\n// rather than aspirational: expand a gesture with `BLANK` and the output must not contain one colour,\n// one font, or one stroke. `GREYBOX` is a deliberately plain default so a freshly written block is\n// playable immediately — it is provisional by design, and swapping it is a single argument.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** What a drawn thing is FOR. Four roles cover every gesture; a theme need not distinguish more. */\nexport type Role =\n | 'item' // something the learner moves\n | 'target' // where an item belongs\n | 'card' // a panel the learner reads and taps\n | 'chip' // a small tappable value\n\nexport type Theme = {\n readonly name: string\n /** Footprint of a role. The gesture uses it for drop boxes; the theme must draw within it. */\n size(role: Role): { w: number; h: number }\n /** Drawing statements for a role, in LOCAL coordinates centred on 0,0. May be empty. */\n draw(role: Role, label: string): string[]\n}\n\n/** Every role, in a stable order — for a reference card, or to sweep a theme. */\nexport const ROLES: Role[] = ['item', 'target', 'card', 'chip']\n\nconst SIZES: Record<Role, { w: number; h: number }> = {\n item: { w: 92, h: 92 },\n target: { w: 208, h: 118 },\n card: { w: 180, h: 140 },\n chip: { w: 96, h: 96 },\n}\n\n/**\n * Draws nothing. The gesture still emits every group, every box and every handler, so an author (or a\n * skin) supplies the whole appearance — and the test suite uses it to prove the gestures themselves\n * carry no visual opinion.\n */\nexport const BLANK: Theme = {\n name: 'blank',\n size: (role) => SIZES[role],\n draw: () => [],\n}\n\n/** Rounded rectangle path, centred on the origin — the greybox's only shape vocabulary. */\nconst plate = (w: number, h: number, r: number): string => `rect ${-w / 2} ${-h / 2} ${w} ${h} ${r}`\n\n/**\n * A plain, provisional greybox: readable, anonymous, and meant to be replaced. It exists so `place x {\n * … }` is playable the moment it is written; it is not a design, and anything built for an audience\n * should pass its own theme (or redraw beside the gesture with `raw { … }`).\n */\nexport const GREYBOX: Theme = {\n name: 'greybox',\n size: (role) => SIZES[role],\n draw(role, label) {\n const { w, h } = SIZES[role]\n const text = (size: number, boxW: number, boxH: number, colour: string, y: number) =>\n `text \"${label.replace(/\"/g, \"'\")}\" at ${-boxW / 2},${y} font \"system-ui, sans-serif\" size ${size} align center line 1.2 color ${colour} box ${boxW} ${boxH} wrap`\n switch (role) {\n case 'item':\n return [`circle 0 0 ${w / 2 - 12} fill #e8c46b`, `circle 0 0 ${w / 2 - 12} nofill stroke #b8923f 2`, text(14, w - 8, 20, '#4a3a1e', -9)]\n case 'target':\n return [`${plate(w - 8, h - 8, 14)} fill #2e3a4e`, `${plate(w - 8, h - 8, 14)} nofill stroke #5a6f8a 2 dash 8,6`, text(16, w - 28, 22, '#aeb6c4', -h / 2 + 10)]\n case 'card':\n return [`${plate(w, h, 16)} fill #2b3447`, `${plate(w, h, 16)} nofill stroke #44516a 2`, text(15, w - 20, h - 40, '#d7deea', -h / 2 + 26)]\n case 'chip':\n return [`circle 0 0 ${w / 2 - 8} fill #e8c46b`, `circle 0 0 ${w / 2 - 8} nofill stroke #b8923f 3`, text(22, w - 16, 28, '#4a3a1e', -13)]\n }\n },\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// gestures.ts — the three primitives.\n//\n// Five named gestures came in (`tri`, `ordonner`, `placement`, `composer`, `etapes`). Read side by\n// side, the first three share ONE implementation: the only thing that differed between them was which\n// shape their targets were drawn as -- rectangles, squares or dotted discs. That is appearance, and\n// appearance is now the theme's business, so they collapse into a single `place`. Five public promises\n// became three.\n//\n// Each gesture emits state, structure and behavior. Not one colour, not one font: `BLANK` proves it.\n// Coordinates DO appear, and that is not an opinion -- they are the ones the author wrote in the block.\n// ─────────────────────────────────────────────────────────────────────────────\nimport type { Expansion, Gesture } from './index'\nimport { ident } from './ident'\nimport { GREYBOX, ROLES, type Role, type Theme } from './theme'\n\n/** Everything a gesture needs beyond the author's text. */\nexport type GestureOptions = { theme?: Theme }\n\nconst q = (s: string) => s.replace(/\"/g, \"'\")\n\n/** Statements of a named group placed at `x,y`, drawn by the theme, optionally a drop box. */\nfunction container(name: string, x: string, y: string, art: string[], hitbox?: { w: number; h: number }): string[] {\n const box = hitbox ? ` hitbox ${hitbox.w} ${hitbox.h}` : ''\n return [` group \"${name}\" at ${x},${y} pivot 0,0${box} {`, ' layer \"art\" {', ...art.map((l) => ` ${l}`), ' }', ' }']\n}\n\n/** Split a block body into its non-empty, comment-free lines. */\nconst lines = (body: string): string[] => body.split('\\n').map((l) => l.replace(/\\/\\/.*$/, '').trim()).filter(Boolean)\n\n// ── place ────────────────────────────────────────────────────────────────────\n// The drop gesture: items belong on targets. Several items may share a target (a sort) or each may have\n// its own (an ordering, a labelled diagram) -- that falls out of the data, it is not a separate gesture.\n\ntype PlaceModel = {\n prompt: string\n targets: { id: string; label: string; x: string; y: string }[]\n items: { id: string; label: string; target: string; x: string; y: string }[]\n}\n\nfunction parsePlace(name: string, body: string): PlaceModel {\n const m: PlaceModel = { prompt: '', targets: [], items: [] }\n for (const line of lines(body)) {\n let x: RegExpMatchArray | null\n if ((x = line.match(/^prompt\\s+\"(.*)\"$/))) m.prompt = x[1]\n else if ((x = line.match(/^target\\s+(?:\"([^\"]+)\"|(\\S+))\\s+at\\s+(-?[\\d.]+),(-?[\\d.]+)$/))) {\n const label = x[1] ?? x[2]\n m.targets.push({ id: `T${ident(label)}`, label, x: x[3], y: x[4] })\n } else if ((x = line.match(/^item\\s+(?:\"([^\"]+)\"|(\\S+))\\s*->\\s*(?:\"([^\"]+)\"|(\\S+))\\s+at\\s+(-?[\\d.]+),(-?[\\d.]+)$/))) {\n const label = x[1] ?? x[2]\n m.items.push({ id: `I${ident(label)}`, label, target: `T${ident(x[3] ?? x[4])}`, x: x[5], y: x[6] })\n } else throw new Error(`place \"${name}\": unrecognised line: ${line}`)\n }\n if (!m.targets.length) throw new Error(`place \"${name}\": no target — an item has nowhere to go`)\n if (!m.items.length) throw new Error(`place \"${name}\": no item — nothing for the learner to do`)\n const known = new Set(m.targets.map((t) => t.id))\n for (const it of m.items) {\n if (!known.has(it.target)) throw new Error(`place \"${name}\": item \"${it.label}\" points at an unknown target`)\n }\n return m\n}\n\nfunction expandPlace(name: string, body: string, theme: Theme): Expansion {\n const m = parsePlace(name, body)\n const n = m.items.length\n const out: string[] = [`// place \"${q(name)}\" — generated. The expansion is the artefact of record: edit it, or edit the block.`]\n if (m.prompt) out.push(`// prompt: ${q(m.prompt)}`)\n\n out.push('var progress = 0', 'var done = 0')\n for (const it of m.items) out.push(`var ${it.id}X = ${it.x}`, `var ${it.id}Y = ${it.y}`, `var ${it.id}Placed = 0`)\n\n out.push('', 'scene {', ' layer \"targets\" {')\n for (const t of m.targets) out.push(...container(t.id, t.x, t.y, theme.draw('target', t.label), theme.size('target')))\n out.push(' }', ' layer \"items\" {')\n for (const it of m.items) out.push(...container(it.id, it.x, it.y, theme.draw('item', it.label)))\n out.push(' }', '}', '')\n\n m.items.forEach((it, i) => {\n out.push(`object \"${it.id}\" {`)\n out.push(` drag ${it.id}X, ${it.id}Y { enabled ${it.id}Placed == 0 }`)\n out.push(` when dropped on ${it.target} at pointer {`)\n out.push(` if ${it.id}Placed < 0.5 {`)\n out.push(` ${it.id}Placed = 1`, ' progress = progress + 1')\n // Snap onto the target it was dropped on, reading the target's LIVE position -- so a skin may move\n // the target anywhere and the item still lands on it.\n out.push(` ${it.id}X = ${it.target}.x`, ` ${it.id}Y = ${it.target}.y`)\n out.push(` send \"correct\", { item = ${i} }`)\n out.push(` if progress == ${n} {`, ' done = 1', ' send \"completed\"', ' }')\n out.push(' }', ' }')\n for (const t of m.targets) {\n if (t.id === it.target) continue\n out.push(` when dropped on ${t.id} at pointer {`)\n out.push(` ${it.id}X = ${it.x}`, ` ${it.id}Y = ${it.y}`) // back where it started\n out.push(` send \"incorrect\", { item = ${i} }`)\n out.push(' }')\n }\n out.push(` x = ${it.id}X`, ` y = ${it.id}Y`, '}', '')\n })\n return {\n flatink: out.join('\\n'),\n meta: { keyword: 'place', name, prompt: m.prompt, items: m.items.map((i) => i.label), targets: m.targets.map((t) => t.label) },\n }\n}\n\n// ── compose ──────────────────────────────────────────────────────────────────\n// Tap values until they add up to a target. Overshooting resets the total -- the whole point of the\n// exercise is that going over is a mistake you feel, not one you are prevented from making.\n\nfunction expandCompose(name: string, body: string, theme: Theme): Expansion {\n let target = 0\n const chips: { value: string; x: string; y: string }[] = []\n let prompt = ''\n for (const line of lines(body)) {\n let x: RegExpMatchArray | null\n if ((x = line.match(/^prompt\\s+\"(.*)\"$/))) prompt = x[1]\n else if ((x = line.match(/^total\\s+(\\d+)$/))) target = Number(x[1])\n else if ((x = line.match(/^chip\\s+(\\d+)\\s+at\\s+(-?[\\d.]+),(-?[\\d.]+)$/))) chips.push({ value: x[1], x: x[2], y: x[3] })\n else throw new Error(`compose \"${name}\": unrecognised line: ${line}`)\n }\n if (!target) throw new Error(`compose \"${name}\": no \\`total <n>\\` — there is nothing to reach`)\n if (!chips.length) throw new Error(`compose \"${name}\": no chip — nothing for the learner to tap`)\n\n const out: string[] = [`// compose \"${q(name)}\" — generated.`, ...(prompt ? [`// prompt: ${q(prompt)}`] : []), 'var total = 0', 'var done = 0', '', 'scene {', ' layer \"chips\" {']\n chips.forEach((c, i) => out.push(...container(`C${i}`, c.x, c.y, theme.draw('chip', c.value), theme.size('chip'))))\n out.push(' }', '}', '')\n chips.forEach((c, i) => {\n out.push(`object \"C${i}\" {`, ' when clicked {', ' if done < 0.5 {')\n out.push(` if total + ${c.value} > ${target} {`)\n out.push(' total = 0', ` send \"incorrect\", { chip = ${i} }`)\n out.push(' } else {')\n out.push(` total = total + ${c.value}`, ` send \"correct\", { chip = ${i} }`)\n out.push(` if total == ${target} {`, ' done = 1', ' send \"completed\"', ' }')\n out.push(' }', ' }', ' }', '}', '')\n })\n return { flatink: out.join('\\n'), meta: { keyword: 'compose', name, prompt, items: chips.map((c) => c.value), targets: [] } }\n}\n\n// ── steps ────────────────────────────────────────────────────────────────────\n// A gated sequence: step i only responds when it is the current one. Tapping out of order does nothing\n// at all -- no error, no penalty, which is what makes it usable as an escape-room stage.\n\nfunction expandSteps(name: string, body: string, theme: Theme): Expansion {\n let prompt = ''\n const steps: { label: string; x: string; y: string }[] = []\n for (const line of lines(body)) {\n let x: RegExpMatchArray | null\n if ((x = line.match(/^prompt\\s+\"(.*)\"$/))) prompt = x[1]\n else if ((x = line.match(/^step\\s+\"(.*)\"\\s+at\\s+(-?[\\d.]+),(-?[\\d.]+)$/))) steps.push({ label: x[1], x: x[2], y: x[3] })\n else throw new Error(`steps \"${name}\": unrecognised line: ${line}`)\n }\n if (!steps.length) throw new Error(`steps \"${name}\": no step — the sequence is empty`)\n\n const out: string[] = [`// steps \"${q(name)}\" — generated.`, ...(prompt ? [`// prompt: ${q(prompt)}`] : []), 'var step = 0', '', 'scene {', ' layer \"steps\" {']\n steps.forEach((s, i) => out.push(...container(`S${i}`, s.x, s.y, theme.draw('card', s.label), theme.size('card'))))\n out.push(' }', '}', '')\n steps.forEach((_s, i) => {\n out.push(`object \"S${i}\" {`, ' when clicked {', ` if step == ${i} {`, ` step = step + 1`, ` send \"step\", { index = ${i} }`)\n if (i === steps.length - 1) out.push(' send \"completed\"')\n out.push(' }', ' }')\n // The only thing the gesture says about looks: a step that is not current is dimmed, because\n // \"which one is live\" is STATE, not decoration. A theme that disagrees rebinds opacity itself.\n out.push(` opacity = step == ${i} ? 1 : 0.45`, '}', '')\n })\n return { flatink: out.join('\\n'), meta: { keyword: 'steps', name, prompt, items: steps.map((s2) => s2.label), targets: [] } }\n}\n\n/** `208x118` — the footprint of a role under a theme, for a summary or a prompt. */\nconst footprint = (theme: Theme, role: Role): string => {\n const { w, h } = theme.size(role)\n return `${w}x${h}`\n}\n\n/**\n * Build the shipped gestures against a theme. `GREYBOX` unless the caller supplies one.\n *\n * Each `summary` carries the FOOTPRINTS of the roles it places, because the author writes positions and\n * two overlapping surfaces make a drop ambiguous. Measured on ten model-written activities: a prompt\n * giving only \"space them out\" produced overlapping hitboxes in 4 of 10; the same prompt with the\n * numbers, 0 of 10. The sizes were public all along — nothing said you had to go and read them.\n */\nexport function gestures(opts: GestureOptions = {}): Gesture[] {\n const theme = opts.theme ?? GREYBOX\n return [\n {\n keyword: 'place',\n summary: `place <name> { prompt \"…\" target <T> at x,y item <i> -> <T> at x,y } — drag items onto where they belong. Footprints: target ${footprint(theme, 'target')}, item ${footprint(theme, 'item')}; keep centres at least one footprint apart or the drop is ambiguous`,\n expand: (name, body) => expandPlace(name, body, theme),\n },\n {\n keyword: 'compose',\n summary: `compose <name> { prompt \"…\" total <n> chip <v> at x,y } — tap values until they add up; overshooting resets. Footprint: chip ${footprint(theme, 'chip')}`,\n expand: (name, body) => expandCompose(name, body, theme),\n },\n {\n keyword: 'steps',\n summary: `steps <name> { prompt \"…\" step \"…\" at x,y } — a gated sequence; out-of-order taps do nothing. Footprint: card ${footprint(theme, 'card')}`,\n expand: (name, body) => expandSteps(name, body, theme),\n },\n ]\n}\n\n/**\n * The paste-ready reference for a model writing sugar: the grammar of every gesture, the footprints it\n * must respect, and the canvas it is laying out on. This is the artefact `summary` was being used as by\n * hand — assembled here so nobody has to know that the sizes live on the theme.\n */\nexport function sugarCard(opts: GestureOptions & { document?: { width: number; height: number } } = {}): string {\n const theme = opts.theme ?? GREYBOX\n const doc = opts.document ?? { width: 760, height: 620 }\n return [\n '# FlatInk sugar — one block, one activity',\n '',\n `Canvas: ${doc.width}x${doc.height}, origin top-left. Write ONE block, nothing else. Coordinates are the`,\n 'CENTRE of each thing. Anything you do not describe is not drawn.',\n '',\n '## Blocks',\n ...gestures({ theme }).map((g) => `- ${g.summary}`),\n '',\n '## Footprints — respect them or two things overlap and the drop becomes ambiguous',\n ...ROLES.map((role) => `- ${role}: ${footprint(theme, role)}`),\n '',\n `A row of targets fits about ${Math.floor(doc.width / (theme.size('target').w + 20))} across; a row of items about ${Math.floor(doc.width / (theme.size('item').w + 20))}.`,\n 'Leave a gap the size of the thing itself between two centres.',\n '',\n '## Escape hatch — three forms, and they are not interchangeable',\n 'The block draws NOTHING beyond the pieces above. Everything else goes through one of these:',\n '',\n 'raw scene under { layer \"bg\" { … } } <- BACKGROUND and decor. Drawn BEHIND the activity.',\n 'raw scene { layer \"banner\" { … } } <- overlays drawn ON TOP: a title, a frame, a caption.',\n 'raw { var lives = 3 object \"X\" { … } } <- header and behavior: var, fn, asset, object blocks.',\n '',\n 'A `layer` written in a plain `raw { … }` lands OUTSIDE the scene and is a compile error — anything',\n 'that is drawn belongs in one of the two `raw scene` forms. Either may be written on one line.',\n ].join('\\n')\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// @flatkit/sugarflat — declarative sugar for FlatInk.\n//\n// A compact block expands into INSPECTABLE `.flatink`. The expansion is the artefact of record: the\n// sugar is an authoring tool, never a runtime, and anything it writes can be written by hand instead.\n//\n// WHY IT LIVES IN THIS REPO. Not to put authoring opinions into the language — it is a separate\n// package, out of the lockstep group, and `@flatkit/compiler` never depends on it. It lives here so\n// the two move together: a grammar change breaks these tests in the same CI run. That is the whole\n// argument, and it was demonstrated rather than argued — the previous out-of-tree sugar never emitted\n// the format's REQUIRED `size` line, on its entire corpus, for months, unnoticed.\n//\n// THE LINE THIS PACKAGE DOES NOT CROSS: no visual opinion. A gesture emits state and behavior; it\n// never emits a coordinate, a colour or a font. The language already ships interaction opinions\n// (`drag`, `when dropped on … at pointer`, `hitbox`, `match`, `feedback`, `spring`) — what it has\n// never shipped is a look, and a sugar that shipped one would hand every activity the same face.\n// ─────────────────────────────────────────────────────────────────────────────\nimport { DEFAULT_DOCUMENT, ensureHeader, type DocumentSpec } from './document'\nimport { gestures } from './gestures'\n\nexport { DEFAULT_DOCUMENT, ensureHeader, hasSizeHeader, hasTimelineHeader, type DocumentSpec } from './document'\nexport { ident, isIdent } from './ident'\nexport { gestures, sugarCard, type GestureOptions } from './gestures'\nexport { BLANK, GREYBOX, ROLES, type Role, type Theme } from './theme'\n\n/**\n * What a gesture parsed out of the author's block, beside the DSL it wrote.\n *\n * A host needs this. The expansion deliberately carries no text a host can read back — the prompt is the\n * learner's instruction and the payloads are indices (`send \"correct\", { item = 2 }`), so without the\n * labels there is nothing to display and nothing to map an index onto. Re-parsing the block by hand to\n * recover data the package already parsed is not an integration; it is a second parser waiting to drift.\n */\nexport type GestureMeta = {\n keyword: string\n /** The block's name. */\n name: string\n /** The learner's instruction, verbatim. Drawn by the host or a theme — never by the gesture. */\n prompt: string\n /** Labels in payload order: `{ item = 2 }` is `items[2]`. */\n items: string[]\n /** Target labels, in the order they were declared. Empty when the gesture has none. */\n targets: string[]\n}\n\n/** What a gesture returns: the DSL, and what it understood. */\nexport type Expansion = { flatink: string; meta: GestureMeta }\n\n/** A sugar block: the keyword that opens it, and how it expands. */\nexport type Gesture = {\n /** The opening keyword, e.g. `place`. Matched only at column 0, followed by a name and `{`. */\n keyword: string\n /** One line, for diagnostics and for the reference a model is prompted with. */\n summary: string\n /** `body` is the text between the braces; `name` the block's name. */\n expand: (name: string, body: string, doc: DocumentSpec) => Expansion\n}\n\nexport type DesugarOptions = {\n /** The canvas + timeline the expansion targets. */\n document?: DocumentSpec\n /** Gestures to recognise. Defaults to the registered set. */\n gestures?: Gesture[]\n}\n\nexport type DesugarResult = {\n /** The expanded (or passed-through) FlatInk. */\n flatink: string\n /** Which gesture matched — `raw` for an escape-only source, `null` for plain FlatInk. */\n kind: string | null\n expanded: boolean\n /** What the gesture understood: the prompt and the labels behind the payload indices. `null` when\n * nothing was expanded, since there is no block to have understood anything from. */\n meta: GestureMeta | null\n}\n\n/** Raised when a source opens a block that looks like sugar but no gesture claims it. */\nexport class UnknownSugarError extends Error {\n constructor(readonly keyword: string, known: string[]) {\n super(\n `unknown sugar block \"${keyword}\" — no gesture claims it, so nothing was expanded. ` +\n `Known blocks: ${known.length ? known.join(', ') : '(none registered)'}. ` +\n `If this is meant to be plain FlatInk, it is not: \"${keyword}\" is not a FlatInk statement either.`,\n )\n this.name = 'UnknownSugarError'\n }\n}\n\n/** The gestures this package ships, drawn by the default theme. Each entry is a public promise. */\nexport const GESTURES: Gesture[] = gestures()\n\n/** A block opens at column 0: `<keyword> <name> {`. The name may be quoted (so it can hold spaces). */\nconst BLOCK_OPEN = /^([a-zA-Z][\\w-]*)[ \\t]+(?:\"([^\"]+)\"|([^\\s{\"]+))[ \\t]*\\{/m\n\n/**\n * The escape hatch, in three forms. A sugar that cannot be opted out of stops being scaffolding and\n * becomes a cage: the previous generation of this idea flattened every activity into the same shape\n * until the template was pulled apart. Anything a gesture writes must remain writable by hand, beside it.\n *\n * raw { … } verbatim at TOP LEVEL — header (`asset`, `var`, `fn`) and `object` blocks\n * raw scene { … } verbatim inside the generated scene, ON TOP of it — banners, overlays\n * raw scene under { … } verbatim inside the generated scene, UNDERNEATH it — backgrounds\n *\n * `under` is not a nicety. A gesture emits no appearance by design, so a background is the FIRST thing\n * any skin needs — and spliced on top, an opaque full-canvas rect hides the entire activity while\n * `checkProgram` reports a clean program. Ten activities shipped that way, read as broken assets.\n */\nconst RAW_OPEN = /^raw(?:[ \\t]+scene(?:[ \\t]+(under))?)?[ \\t]*\\{/gm\n\ntype RawParts = { top: string[]; over: string[]; under: string[]; rest: string }\n\n/**\n * Pull every `raw …` block out of `text`. Brace-counted rather than line-anchored, so the one-line form\n * works — the README and `sugarCard()` both write `raw { … }` on one line, and a regex demanding a\n * newline after `{` left the keyword to travel on to the compiler, which reported `unexpected statement\n * \"raw\"`: a sugar defect described in FlatInk's vocabulary, the very thing `UnknownSugarError` fixed.\n */\nfunction takeRaw(text: string): RawParts {\n const out: RawParts = { top: [], over: [], under: [], rest: '' }\n let cursor = 0\n RAW_OPEN.lastIndex = 0\n for (let m = RAW_OPEN.exec(text); m; m = RAW_OPEN.exec(text)) {\n const openBrace = m.index + m[0].length - 1\n const end = blockEnd(text, openBrace)\n if (end < 0) break // unbalanced: leave it alone, the compiler will say so\n const body = text.slice(openBrace + 1, end).replace(/^\\r?\\n/, '').replace(/\\r?\\n[ \\t]*$/, '')\n const bucket = m[0].includes('scene') ? (m[1] ? out.under : out.over) : out.top\n bucket.push(body)\n out.rest += text.slice(cursor, m.index)\n cursor = end + 1\n RAW_OPEN.lastIndex = cursor\n }\n out.rest += text.slice(cursor)\n return out\n}\n\n/** Index of the `}` closing the brace at `openBrace`, or -1 if unbalanced. */\nfunction blockEnd(text: string, openBrace: number): number {\n let depth = 1\n for (let i = openBrace + 1; i < text.length; i++) {\n if (text[i] === '{') depth++\n else if (text[i] === '}' && --depth === 0) return i\n }\n return -1\n}\n\nconst RAW_NOTE = '// -- raw (escape hatch: verbatim FlatInk) --'\n\n/** Splice verbatim bodies into the generated `scene`: `under` first (drawn behind), `over` last. */\nfunction spliceIntoScene(flatink: string, under: string[], over: string[]): string {\n if (!under.length && !over.length) return flatink\n const at = flatink.search(/^scene[ \\t]*\\{/m)\n if (at < 0) throw new Error('`raw scene { … }` has nowhere to go: this expansion emits no `scene` block — use a plain `raw { … }` instead.')\n const openBrace = flatink.indexOf('{', at)\n const close = blockEnd(flatink, openBrace)\n const head = under.length ? `\\n // -- raw scene under (verbatim, drawn BEHIND the gesture) --\\n${under.join('\\n')}` : ''\n const tail = over.length ? ` // -- raw scene (verbatim, drawn ON TOP of the gesture) --\\n${over.join('\\n')}\\n` : ''\n return `${flatink.slice(0, openBrace + 1)}${head}${flatink.slice(openBrace + 1, close)}${tail}${flatink.slice(close)}`\n}\n\n/** Raised when a source holds more than one gesture block. */\nexport class MultipleGesturesError extends Error {\n constructor(first: string, second: string) {\n super(\n `two gesture blocks in one source (\"${first}\" and \"${second}\") — a document holds ONE. Each gesture ` +\n 'emits its own `scene`, and a program may only have one, so the second cannot be merged in. ' +\n 'Split them into two documents, or write the extra part by hand in `raw { … }` / `raw scene { … }`.',\n )\n this.name = 'MultipleGesturesError'\n }\n}\n\n/**\n * Expand a sugar source into `.flatink`.\n *\n * Plain FlatInk passes through untouched. A source whose first block matches no gesture RAISES rather\n * than passing through: a silent fall-through sent the author's block downstream as if it were FlatInk,\n * to fail a hundred lines later on messages that only talk about FlatInk.\n *\n * ```ts\n * import { desugar } from '@flatkit/sugarflat'\n * import { checkProgram } from '@flatkit/compiler'\n *\n * const { flatink } = desugar(srcFromAnLLM)\n * const { ok, report } = checkProgram(flatink) // the expansion is the artefact of record\n * ```\n */\nexport function desugar(src: string, opts: DesugarOptions = {}): DesugarResult {\n const doc = opts.document ?? DEFAULT_DOCUMENT\n const gestures = opts.gestures ?? GESTURES\n\n const open = BLOCK_OPEN.exec(src)\n const keyword = open?.[1]\n const gesture = keyword ? gestures.find((g) => g.keyword === keyword) : undefined\n\n if (gesture) {\n const name = open?.[2] ?? open?.[3] ?? gesture.keyword\n const braceAt = open!.index + open![0].length - 1\n const body = blockBody(src, braceAt)\n // Everything OUTSIDE the block travels with the expansion. Dropping it made the escape hatch — the\n // one guard-rail this package rests on — a no-op beside a gesture: an author wrote decor, the\n // expansion compiled, `checkProgram` said ok, and nothing was there.\n const before = src.slice(0, open!.index)\n const after = src.slice(braceAt + 1 + body.length + 1)\n const other = findGesture(before + '\\n' + after, gestures)\n if (other) throw new MultipleGesturesError(gesture.keyword, other)\n\n const { flatink, meta } = gesture.expand(name, body, doc)\n const head = takeRaw(before)\n const tail = takeRaw(after)\n const expanded = spliceIntoScene(flatink, [...head.under, ...tail.under], [...head.over, ...tail.over])\n const topLevel = (p: RawParts) => [p.rest.trim(), ...(p.top.length ? [RAW_NOTE, ...p.top] : [])].filter(Boolean).join('\\n')\n const whole = [topLevel(head), expanded, topLevel(tail)].filter(Boolean).join('\\n\\n')\n return { flatink: ensureHeader(whole, doc), kind: gesture.keyword, expanded: true, meta }\n }\n // A keyword that opens a block at column 0 and is NOT a FlatInk statement is a sugar block we do not\n // know — never a program. Say so here, with the list, rather than downstream in another vocabulary.\n // `raw` is this package's own keyword, handled below.\n if (keyword && keyword !== 'raw' && !FLATINK_BLOCKS.has(keyword)) throw new UnknownSugarError(keyword, gestures.map((g) => g.keyword))\n\n // No gesture: plain FlatInk, plus any `raw { … }` the author used on its own. `raw scene` has no\n // generated scene to splice into here, so it would silently vanish — say so instead.\n const parts = takeRaw(src)\n if (parts.over.length || parts.under.length) {\n throw new Error('`raw scene { … }` needs a gesture block to splice into — this source has none. Write a plain `scene { … }` yourself.')\n }\n const out = [parts.rest.trim(), ...(parts.top.length ? [RAW_NOTE, ...parts.top] : [])].filter(Boolean).join('\\n')\n return { flatink: parts.top.length ? out : src, kind: parts.top.length ? 'raw' : null, expanded: parts.top.length > 0, meta: null }\n}\n\n/** Block keywords that belong to FlatINK itself — a source opening with one of these is a program. */\nconst FLATINK_BLOCKS = new Set(['scene', 'object', 'symbol', 'layer', 'group', 'match', 'each', 'states', 'params', 'when', 'every', 'repeat', 'fn', 'at'])\n\n/** The keyword of the first gesture block in `text`, if any — used to refuse a second one loudly. */\nfunction findGesture(text: string, gestures: Gesture[]): string | null {\n const re = new RegExp(BLOCK_OPEN.source, 'gm')\n for (let m = re.exec(text); m; m = re.exec(text)) {\n if (gestures.some((g) => g.keyword === m![1])) return m[1]\n }\n return null\n}\n\n/** Text between the braces opened at `openIndex`, brace-counting so nested blocks survive. */\nfunction blockBody(src: string, openIndex: number): string {\n let depth = 1\n let i = openIndex + 1\n for (; i < src.length && depth > 0; i++) {\n if (src[i] === '{') depth++\n else if (src[i] === '}') depth--\n }\n return src.slice(openIndex + 1, depth === 0 ? i - 1 : src.length)\n}\n"],"mappings":"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","names":["gestures"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flatkit/sugarflat",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Declarative sugar for FlatInk: a compact block expands into inspectable .flatink. An authoring tool, never a runtime.",
5
5
  "license": "MIT",
6
6
  "author": "Zwyk Studio",
@@ -34,14 +34,14 @@
34
34
  "dist"
35
35
  ],
36
36
  "dependencies": {
37
- "@flatkit/compiler": "0.25.0"
37
+ "@flatkit/compiler": "0.26.0"
38
38
  },
39
39
  "publishConfig": {
40
40
  "access": "public"
41
41
  },
42
42
  "devDependencies": {
43
- "@flatkit/types": "0.25.0",
44
- "@flatkit/player": "0.25.0"
43
+ "@flatkit/player": "0.26.0",
44
+ "@flatkit/types": "0.26.0"
45
45
  },
46
46
  "scripts": {
47
47
  "build": "tsup",