@flatkit/sugarflat 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zwyk Studio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # @flatkit/sugarflat
2
+
3
+ Declarative sugar for FlatInk. A compact block expands into **inspectable `.flatink`** — the expansion is
4
+ the artefact of record, and anything the sugar writes can be written by hand instead.
5
+
6
+ ```
7
+ place natures {
8
+ prompt "Put each word under its part of speech"
9
+ target Nouns at 230,470
10
+ target Verbs at 530,470
11
+ item cat -> Nouns at 150,150
12
+ item run -> Verbs at 310,150
13
+ }
14
+ ```
15
+
16
+ ```ts
17
+ import { desugar } from '@flatkit/sugarflat'
18
+ import { checkProgram } from '@flatkit/compiler'
19
+
20
+ const { flatink } = desugar(src)
21
+ const { ok, report } = checkProgram(flatink) // the expansion is what you ship, and what you read
22
+ if (!ok) retry(report)
23
+ ```
24
+
25
+ ## Why it lives in the flatkit repo
26
+
27
+ Not to put authoring opinions into the language. It is a **separate package**, out of the lockstep
28
+ version group (the same standing as `@flatkit/mcp`), and **`@flatkit/compiler` never depends on it** —
29
+ that direction is the whole guarantee.
30
+
31
+ It lives here so the two move together: a grammar change breaks these tests in the same CI run. That was
32
+ argued for a long time and then demonstrated — the previous out-of-tree sugar never emitted the format's
33
+ **required** `size` line, on its entire corpus, for months, and nobody noticed because the compiler
34
+ silently defaults it. Every document was laid out for one canvas and drawn on another.
35
+
36
+ ## The line this package does not cross
37
+
38
+ **No visual opinion.** A gesture emits state and behavior; it never emits a coordinate, a colour or a
39
+ font. The language already ships plenty of *interaction* opinions (`drag`, `when dropped on … at pointer`,
40
+ `hitbox`, `match`, `feedback`, `spring`) — what it has never shipped is a look, and a sugar that shipped
41
+ one would hand every activity built on it the same face. That has happened before, and the activities all
42
+ came out looking alike.
43
+
44
+ Two consequences, both deliberate:
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.
48
+ - **The composition is yours.** A gesture names the objects it drives; you (or a skin) draw them. This is
49
+ how `match` has always worked in the language proper.
50
+
51
+ ## What it guarantees
52
+
53
+ - **The required header is emitted.** `size` and `timeline`, from a document spec that is *readable*
54
+ (`DEFAULT_DOCUMENT`) and overridable per call, rather than a literal buried in a generator.
55
+ - **An unknown block is a hard error.** A sugar block nobody claims used to fall through as if it were
56
+ FlatInk, and failed a hundred lines downstream in a vocabulary that only talks about FlatInk.
57
+ - **Human labels become valid identifiers.** `ident()` folds accents, because FlatInk identifiers are
58
+ `[A-Za-z0-9_]` and the lexer stops at an accent rather than erroring — one variable silently became two.
59
+ - **Every expansion passes `checkProgram` with ZERO warnings.** Not "it compiles": a warning on generated
60
+ DSL is a defect in a place nobody is watching.
61
+
62
+ ## Tests: contracts, not goldens
63
+
64
+ `src/contracts/` states what each gesture **does** — the events a learner's actions produce and the state
65
+ left behind — never the DSL it writes. Text goldens cannot survive a rewrite that changes every emitted
66
+ line; a gesture-replay contract can.
67
+
68
+ It earned that on day one: replaying the *shipped* implementation showed that the full-canvas `Flash`
69
+ overlay carries no `nohit`, so after a wrong answer it swallows every pointer event for ~40 frames. The
70
+ learner makes a mistake and the activity stops responding, at the exact moment they retry.
71
+
72
+ ## The three gestures
73
+
74
+ | | |
75
+ |---|---|
76
+ | `place <name> { prompt "…" target <T> at x,y item <i> -> <T> at x,y }` | drag items onto where they belong |
77
+ | `compose <name> { prompt "…" total <n> chip <v> at x,y }` | tap values until they add up; overshooting resets |
78
+ | `steps <name> { prompt "…" step "…" at x,y }` | a gated sequence; out-of-order taps do nothing |
79
+
80
+ 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.
82
+
83
+ **Five gestures came in and three shipped.** `tri`, `ordonner` and `placement` shared one implementation:
84
+ the only thing that differed was whether their targets were drawn as rectangles, squares or dotted discs.
85
+ That is appearance, so it moved to the theme and the three became `place`. A sort and an ordering differ
86
+ in their data — several items on one target, or one each — not in their code.
87
+
88
+ ## Themes
89
+
90
+ ```ts
91
+ import { desugar, gestures, GREYBOX, BLANK, type Theme } from '@flatkit/sugarflat'
92
+
93
+ desugar(src) // GREYBOX: plain, provisional, playable now
94
+ desugar(src, { gestures: gestures({ theme: mine }) }) // your appearance, same behavior
95
+ ```
96
+
97
+ A theme answers two questions per role (`item`, `target`, `card`, `chip`): how big it is, and what to
98
+ draw inside it. `BLANK` answers "nothing", which is how the no-visual-opinion rule is tested rather than
99
+ promised — expand every gesture with it and the output must not contain one colour, font or stroke.
100
+
101
+ ## Status
102
+
103
+ New. The three gestures are covered by behavior contracts and by the zero-warning rule; the design record
104
+ for how this package came to exist is internal.
@@ -0,0 +1,109 @@
1
+ /** The canvas + timeline a sugar block expands into. */
2
+ type DocumentSpec = {
3
+ width: number;
4
+ height: number;
5
+ fps: number;
6
+ /** Timeline length. Long enough that `time` does not wrap mid-activity (the compiler warns under 120). */
7
+ durationFrames: number;
8
+ };
9
+ /** The board the gestures are laid out for. Override per call when a host uses another canvas. */
10
+ declare const DEFAULT_DOCUMENT: DocumentSpec;
11
+ /** `true` if the source already declares its canvas — `size` must be the FIRST statement of a program. */
12
+ declare const hasSizeHeader: (src: string) => boolean;
13
+ /** `true` if the source already declares a root timeline. */
14
+ declare const hasTimelineHeader: (src: string) => boolean;
15
+ /**
16
+ * Prepend whatever header the expansion is missing. `size` is REQUIRED by the format and silently
17
+ * defaulted by the compiler when absent, so emitting it is not a nicety: it is the difference between
18
+ * the document the author designed and the one that gets drawn.
19
+ */
20
+ declare function ensureHeader(flatink: string, doc?: DocumentSpec): string;
21
+
22
+ /**
23
+ * A FlatInk identifier derived from a human label: accents folded to ASCII, anything else to `_`, and
24
+ * a leading digit prefixed (an identifier may not start with one). Stable and idempotent, so the same
25
+ * label always yields the same name — the sugar's variables and the author's references must agree.
26
+ */
27
+ declare function ident(label: string): string;
28
+ /** `true` if the label survives `ident` unchanged — i.e. it is already a usable identifier. */
29
+ declare const isIdent: (label: string) => boolean;
30
+
31
+ /** What a drawn thing is FOR. Four roles cover every gesture; a theme need not distinguish more. */
32
+ type Role = 'item' | 'target' | 'card' | 'chip';
33
+ type Theme = {
34
+ readonly name: string;
35
+ /** Footprint of a role. The gesture uses it for drop boxes; the theme must draw within it. */
36
+ size(role: Role): {
37
+ w: number;
38
+ h: number;
39
+ };
40
+ /** Drawing statements for a role, in LOCAL coordinates centred on 0,0. May be empty. */
41
+ draw(role: Role, label: string): string[];
42
+ };
43
+ /**
44
+ * Draws nothing. The gesture still emits every group, every box and every handler, so an author (or a
45
+ * skin) supplies the whole appearance — and the test suite uses it to prove the gestures themselves
46
+ * carry no visual opinion.
47
+ */
48
+ declare const BLANK: Theme;
49
+ /**
50
+ * A plain, provisional greybox: readable, anonymous, and meant to be replaced. It exists so `place x {
51
+ * … }` is playable the moment it is written; it is not a design, and anything built for an audience
52
+ * should pass its own theme (or redraw beside the gesture with `raw { … }`).
53
+ */
54
+ declare const GREYBOX: Theme;
55
+
56
+ /** Everything a gesture needs beyond the author's text. */
57
+ type GestureOptions = {
58
+ theme?: Theme;
59
+ };
60
+ /** Build the shipped gestures against a theme. `GREYBOX` unless the caller supplies one. */
61
+ declare function gestures(opts?: GestureOptions): Gesture[];
62
+
63
+ /** A sugar block: the keyword that opens it, and how it expands. */
64
+ type Gesture = {
65
+ /** The opening keyword, e.g. `tri`. Matched only at column 0, followed by a name and `{`. */
66
+ keyword: string;
67
+ /** One line, for diagnostics and for the reference a model is prompted with. */
68
+ 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;
71
+ };
72
+ type DesugarOptions = {
73
+ /** The canvas + timeline the expansion targets. */
74
+ document?: DocumentSpec;
75
+ /** Gestures to recognise. Defaults to the registered set. */
76
+ gestures?: Gesture[];
77
+ };
78
+ type DesugarResult = {
79
+ /** The expanded (or passed-through) FlatInk. */
80
+ flatink: string;
81
+ /** Which gesture matched — `raw` for an escape-only source, `null` for plain FlatInk. */
82
+ kind: string | null;
83
+ expanded: boolean;
84
+ };
85
+ /** Raised when a source opens a block that looks like sugar but no gesture claims it. */
86
+ declare class UnknownSugarError extends Error {
87
+ readonly keyword: string;
88
+ constructor(keyword: string, known: string[]);
89
+ }
90
+ /** The gestures this package ships, drawn by the default theme. Each entry is a public promise. */
91
+ declare const GESTURES: Gesture[];
92
+ /**
93
+ * Expand a sugar source into `.flatink`.
94
+ *
95
+ * Plain FlatInk passes through untouched. A source whose first block matches no gesture RAISES rather
96
+ * than passing through: a silent fall-through sent the author's block downstream as if it were FlatInk,
97
+ * to fail a hundred lines later on messages that only talk about FlatInk.
98
+ *
99
+ * ```ts
100
+ * import { desugar } from '@flatkit/sugarflat'
101
+ * import { checkProgram } from '@flatkit/compiler'
102
+ *
103
+ * const { flatink } = desugar(srcFromAnLLM)
104
+ * const { ok, report } = checkProgram(flatink) // the expansion is the artefact of record
105
+ * ```
106
+ */
107
+ declare function desugar(src: string, opts?: DesugarOptions): DesugarResult;
108
+
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 };
package/dist/index.js ADDED
@@ -0,0 +1,238 @@
1
+ // src/document.ts
2
+ var DEFAULT_DOCUMENT = { width: 760, height: 620, fps: 24, durationFrames: 120 };
3
+ var hasSizeHeader = (src) => /^[ \t]*size\s+-?[\d.]+\s+-?[\d.]+/m.test(src);
4
+ var hasTimelineHeader = (src) => /^[ \t]*timeline\s+\d+/m.test(src);
5
+ function ensureHeader(flatink, doc = DEFAULT_DOCUMENT) {
6
+ const head = [];
7
+ if (!hasSizeHeader(flatink)) head.push(`size ${doc.width} ${doc.height}`);
8
+ if (!hasTimelineHeader(flatink)) head.push(`timeline ${doc.fps} ${doc.durationFrames}`);
9
+ return head.length ? `${head.join("\n")}
10
+ ${flatink}` : flatink;
11
+ }
12
+
13
+ // src/ident.ts
14
+ function ident(label) {
15
+ const ascii = label.normalize("NFD").replace(/\p{Diacritic}/gu, "").replace(/[^A-Za-z0-9_]/g, "_");
16
+ return /^[0-9]/.test(ascii) ? `n${ascii}` : ascii;
17
+ }
18
+ var isIdent = (label) => ident(label) === label && label.length > 0;
19
+
20
+ // src/theme.ts
21
+ var SIZES = {
22
+ item: { w: 92, h: 92 },
23
+ target: { w: 208, h: 118 },
24
+ card: { w: 180, h: 140 },
25
+ chip: { w: 96, h: 96 }
26
+ };
27
+ var BLANK = {
28
+ name: "blank",
29
+ size: (role) => SIZES[role],
30
+ draw: () => []
31
+ };
32
+ var plate = (w, h, r) => `rect ${-w / 2} ${-h / 2} ${w} ${h} ${r}`;
33
+ var GREYBOX = {
34
+ name: "greybox",
35
+ size: (role) => SIZES[role],
36
+ draw(role, label) {
37
+ const { w, h } = SIZES[role];
38
+ const text = (size, boxW, boxH, colour, y) => `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`;
39
+ switch (role) {
40
+ case "item":
41
+ 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)];
42
+ case "target":
43
+ 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)];
44
+ case "card":
45
+ 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)];
46
+ case "chip":
47
+ 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)];
48
+ }
49
+ }
50
+ };
51
+
52
+ // src/gestures.ts
53
+ var q = (s) => s.replace(/"/g, "'");
54
+ function container(name, x, y, art, hitbox) {
55
+ const box = hitbox ? ` hitbox ${hitbox.w} ${hitbox.h}` : "";
56
+ return [` group "${name}" at ${x},${y} pivot 0,0${box} {`, ' layer "art" {', ...art.map((l) => ` ${l}`), " }", " }"];
57
+ }
58
+ var lines = (body) => body.split("\n").map((l) => l.replace(/\/\/.*$/, "").trim()).filter(Boolean);
59
+ function parsePlace(name, body) {
60
+ const m = { prompt: "", targets: [], items: [] };
61
+ for (const line of lines(body)) {
62
+ let x;
63
+ if (x = line.match(/^prompt\s+"(.*)"$/)) m.prompt = x[1];
64
+ else if (x = line.match(/^target\s+(?:"([^"]+)"|(\S+))\s+at\s+(-?[\d.]+),(-?[\d.]+)$/)) {
65
+ const label = x[1] ?? x[2];
66
+ m.targets.push({ id: `T${ident(label)}`, label, x: x[3], y: x[4] });
67
+ } else if (x = line.match(/^item\s+(?:"([^"]+)"|(\S+))\s*->\s*(?:"([^"]+)"|(\S+))\s+at\s+(-?[\d.]+),(-?[\d.]+)$/)) {
68
+ const label = x[1] ?? x[2];
69
+ m.items.push({ id: `I${ident(label)}`, label, target: `T${ident(x[3] ?? x[4])}`, x: x[5], y: x[6] });
70
+ } else throw new Error(`place "${name}": unrecognised line: ${line}`);
71
+ }
72
+ if (!m.targets.length) throw new Error(`place "${name}": no target \u2014 an item has nowhere to go`);
73
+ if (!m.items.length) throw new Error(`place "${name}": no item \u2014 nothing for the learner to do`);
74
+ const known = new Set(m.targets.map((t) => t.id));
75
+ for (const it of m.items) {
76
+ if (!known.has(it.target)) throw new Error(`place "${name}": item "${it.label}" points at an unknown target`);
77
+ }
78
+ return m;
79
+ }
80
+ function expandPlace(name, body, theme) {
81
+ const m = parsePlace(name, body);
82
+ const n = m.items.length;
83
+ const out = [`// place "${q(name)}" \u2014 generated. The expansion is the artefact of record: edit it, or edit the block.`];
84
+ out.push("var progress = 0", "var done = 0");
85
+ 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
+ out.push("", "scene {", ' layer "targets" {');
87
+ for (const t of m.targets) out.push(...container(t.id, t.x, t.y, theme.draw("target", t.label), theme.size("target")));
88
+ out.push(" }", ' layer "items" {');
89
+ for (const it of m.items) out.push(...container(it.id, it.x, it.y, theme.draw("item", it.label)));
90
+ out.push(" }", "}", "");
91
+ m.items.forEach((it, i) => {
92
+ out.push(`object "${it.id}" {`);
93
+ out.push(` drag ${it.id}X, ${it.id}Y { enabled ${it.id}Placed == 0 }`);
94
+ out.push(` when dropped on ${it.target} at pointer {`);
95
+ out.push(` if ${it.id}Placed < 0.5 {`);
96
+ out.push(` ${it.id}Placed = 1`, " progress = progress + 1");
97
+ out.push(` ${it.id}X = ${it.target}.x`, ` ${it.id}Y = ${it.target}.y`);
98
+ out.push(` send "correct", { item = ${i} }`);
99
+ out.push(` if progress == ${n} {`, " done = 1", ' send "completed"', " }");
100
+ out.push(" }", " }");
101
+ for (const t of m.targets) {
102
+ if (t.id === it.target) continue;
103
+ out.push(` when dropped on ${t.id} at pointer {`);
104
+ out.push(` ${it.id}X = ${it.x}`, ` ${it.id}Y = ${it.y}`);
105
+ out.push(` send "incorrect", { item = ${i} }`);
106
+ out.push(" }");
107
+ }
108
+ out.push(` x = ${it.id}X`, ` y = ${it.id}Y`, "}", "");
109
+ });
110
+ return out.join("\n");
111
+ }
112
+ function expandCompose(name, body, theme) {
113
+ let target = 0;
114
+ const chips = [];
115
+ let prompt = "";
116
+ for (const line of lines(body)) {
117
+ let x;
118
+ if (x = line.match(/^prompt\s+"(.*)"$/)) prompt = x[1];
119
+ else if (x = line.match(/^total\s+(\d+)$/)) target = Number(x[1]);
120
+ else if (x = line.match(/^chip\s+(\d+)\s+at\s+(-?[\d.]+),(-?[\d.]+)$/)) chips.push({ value: x[1], x: x[2], y: x[3] });
121
+ else throw new Error(`compose "${name}": unrecognised line: ${line}`);
122
+ }
123
+ if (!target) throw new Error(`compose "${name}": no \`total <n>\` \u2014 there is nothing to reach`);
124
+ 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" {'];
126
+ chips.forEach((c, i) => out.push(...container(`C${i}`, c.x, c.y, theme.draw("chip", c.value), theme.size("chip"))));
127
+ out.push(" }", "}", "");
128
+ chips.forEach((c, i) => {
129
+ out.push(`object "C${i}" {`, " when clicked {", " if done < 0.5 {");
130
+ out.push(` if total + ${c.value} > ${target} {`);
131
+ out.push(" total = 0", ` send "incorrect", { chip = ${i} }`);
132
+ out.push(" } else {");
133
+ out.push(` total = total + ${c.value}`, ` send "correct", { chip = ${i} }`);
134
+ out.push(` if total == ${target} {`, " done = 1", ' send "completed"', " }");
135
+ out.push(" }", " }", " }", "}", "");
136
+ });
137
+ if (prompt) out.push(`// prompt: ${q(prompt)}`);
138
+ return out.join("\n");
139
+ }
140
+ function expandSteps(name, body, theme) {
141
+ let prompt = "";
142
+ const steps = [];
143
+ for (const line of lines(body)) {
144
+ let x;
145
+ if (x = line.match(/^prompt\s+"(.*)"$/)) prompt = x[1];
146
+ else if (x = line.match(/^step\s+"(.*)"\s+at\s+(-?[\d.]+),(-?[\d.]+)$/)) steps.push({ label: x[1], x: x[2], y: x[3] });
147
+ else throw new Error(`steps "${name}": unrecognised line: ${line}`);
148
+ }
149
+ 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" {'];
151
+ steps.forEach((s, i) => out.push(...container(`S${i}`, s.x, s.y, theme.draw("card", s.label), theme.size("card"))));
152
+ out.push(" }", "}", "");
153
+ steps.forEach((_s, i) => {
154
+ out.push(`object "S${i}" {`, " when clicked {", ` if step == ${i} {`, ` step = step + 1`, ` send "step", { index = ${i} }`);
155
+ if (i === steps.length - 1) out.push(' send "completed"');
156
+ out.push(" }", " }");
157
+ out.push(` opacity = step == ${i} ? 1 : 0.45`, "}", "");
158
+ });
159
+ if (prompt) out.push(`// prompt: ${q(prompt)}`);
160
+ return out.join("\n");
161
+ }
162
+ function gestures(opts = {}) {
163
+ const theme = opts.theme ?? GREYBOX;
164
+ return [
165
+ {
166
+ 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',
168
+ expand: (name, body) => expandPlace(name, body, theme)
169
+ },
170
+ {
171
+ keyword: "compose",
172
+ summary: 'compose <name> { prompt "\u2026" total <n> chip <v> at x,y } \u2014 tap values until they add up; overshooting resets',
173
+ expand: (name, body) => expandCompose(name, body, theme)
174
+ },
175
+ {
176
+ keyword: "steps",
177
+ summary: 'steps <name> { prompt "\u2026" step "\u2026" at x,y } \u2014 a gated sequence; out-of-order taps do nothing',
178
+ expand: (name, body) => expandSteps(name, body, theme)
179
+ }
180
+ ];
181
+ }
182
+
183
+ // src/index.ts
184
+ var UnknownSugarError = class extends Error {
185
+ constructor(keyword, known) {
186
+ super(
187
+ `unknown sugar block "${keyword}" \u2014 no gesture claims it, so nothing was expanded. Known blocks: ${known.length ? known.join(", ") : "(none registered)"}. If this is meant to be plain FlatInk, it is not: "${keyword}" is not a FlatInk statement either.`
188
+ );
189
+ this.keyword = keyword;
190
+ this.name = "UnknownSugarError";
191
+ }
192
+ keyword;
193
+ };
194
+ var GESTURES = gestures();
195
+ 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}`);
199
+ function desugar(src, opts = {}) {
200
+ const doc = opts.document ?? DEFAULT_DOCUMENT;
201
+ const gestures2 = opts.gestures ?? GESTURES;
202
+ const open = BLOCK_OPEN.exec(src);
203
+ const keyword = open?.[1];
204
+ const gesture = keyword ? gestures2.find((g) => g.keyword === keyword) : void 0;
205
+ if (gesture) {
206
+ 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 };
209
+ }
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 };
213
+ }
214
+ var FLATINK_BLOCKS = /* @__PURE__ */ new Set(["scene", "object", "symbol", "layer", "group", "match", "each", "states", "params", "when", "every", "repeat", "fn", "at"]);
215
+ function blockBody(src, openIndex) {
216
+ let depth = 1;
217
+ let i = openIndex + 1;
218
+ for (; i < src.length && depth > 0; i++) {
219
+ if (src[i] === "{") depth++;
220
+ else if (src[i] === "}") depth--;
221
+ }
222
+ return src.slice(openIndex + 1, depth === 0 ? i - 1 : src.length);
223
+ }
224
+ export {
225
+ BLANK,
226
+ DEFAULT_DOCUMENT,
227
+ GESTURES,
228
+ GREYBOX,
229
+ UnknownSugarError,
230
+ desugar,
231
+ ensureHeader,
232
+ gestures,
233
+ hasSizeHeader,
234
+ hasTimelineHeader,
235
+ ident,
236
+ isIdent
237
+ };
238
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"]}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@flatkit/sugarflat",
3
+ "version": "0.1.1",
4
+ "description": "Declarative sugar for FlatInk: a compact block expands into inspectable .flatink. An authoring tool, never a runtime.",
5
+ "license": "MIT",
6
+ "author": "Zwyk Studio",
7
+ "homepage": "http://flatink.zwyk-studio.com/",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/flatink/flatkit.git",
11
+ "directory": "packages/sugarflat"
12
+ },
13
+ "bugs": "https://github.com/flatink/flatkit/issues",
14
+ "keywords": [
15
+ "flatink",
16
+ "flatkit",
17
+ "sugar",
18
+ "desugar",
19
+ "dsl",
20
+ "codegen"
21
+ ],
22
+ "type": "module",
23
+ "sideEffects": false,
24
+ "main": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js",
30
+ "require": "./dist/index.js"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "dependencies": {
37
+ "@flatkit/compiler": "0.25.0"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "devDependencies": {
43
+ "@flatkit/types": "0.25.0",
44
+ "@flatkit/player": "0.25.0"
45
+ },
46
+ "scripts": {
47
+ "build": "tsup",
48
+ "typecheck": "tsc -p tsconfig.json"
49
+ },
50
+ "module": "./dist/index.js"
51
+ }