@markdy/core 0.7.25 → 0.7.27

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/dist/index.d.ts CHANGED
@@ -116,6 +116,12 @@ type SceneAST = {
116
116
  seqs: Record<string, SequenceDef>;
117
117
  /** User-defined variables — kept in AST for tooling/inspection. */
118
118
  vars: Record<string, string>;
119
+ /**
120
+ * Named actor groups, in declaration order, mapping group name to member
121
+ * actor names. Groups fan out into per-actor events at parse time, so the
122
+ * renderer never sees them — this is kept for tooling and inspection.
123
+ */
124
+ groups: Record<string, string[]>;
119
125
  /** Named chapter blocks in author order. Empty when no chapters were used. */
120
126
  chapters: Chapter[];
121
127
  /** Soft parse issues. Always present; empty in the happy path. */
@@ -180,6 +186,25 @@ type PresetFn = (args: string[]) => string;
180
186
  declare const PRESETS: Record<string, PresetFn>;
181
187
  declare const PRESET_NAMES: readonly string[];
182
188
 
189
+ /**
190
+ * The canonical vocabulary of the language.
191
+ *
192
+ * These arrays are the single source of truth for every tool that needs to
193
+ * know what Markdy understands — the parser, the language server, the
194
+ * playground's syntax highlighter, and the renderer's handler-coverage test.
195
+ * Anything that hard-codes its own copy will silently drift out of date, so
196
+ * import from here instead.
197
+ */
198
+ declare const BUILTIN_ACTOR_TYPES: readonly BuiltinActorType[];
199
+ /** Actions valid on every actor type. */
200
+ declare const UNIVERSAL_ACTION_NAMES: readonly ["enter", "exit", "move", "spring", "follow_path", "fade_in", "fade_out", "scale", "rotate", "shake", "pulse", "glow", "ripple", "blur", "line_reveal", "mask", "parallax", "say", "throw", "play"];
201
+ /**
202
+ * Actions that require a `figure` actor. Applying one to any other actor
203
+ * type is a hard `ParseError`, not a soft warning — it's always a mistake.
204
+ */
205
+ declare const FIGURE_ONLY_ACTION_NAMES: readonly ["punch", "kick", "wave", "nod", "jump", "bounce", "face", "rotate_part", "pose"];
206
+ /** Actions valid on the reserved `camera` actor. */
207
+ declare const CAMERA_ACTION_NAMES: readonly ["pan", "zoom", "shake"];
183
208
  type ActorPack = {
184
209
  name: string;
185
210
  actors: readonly string[];
@@ -187,4 +212,4 @@ type ActorPack = {
187
212
  };
188
213
  declare function registerActorPack(pack: ActorPack): void;
189
214
 
190
- export { type ActorDef, type ActorPack, type ActorType, type AssetDef, type BuiltinActorType, type Chapter, type ImportDecl, PRESETS, PRESET_NAMES, ParseError, type ParseOptions, type ParseWarning, type PresetFn, type SceneAST, type SceneMeta, type SequenceDef, type TemplateDef, type TimelineEvent, parse, registerActorPack };
215
+ export { type ActorDef, type ActorPack, type ActorType, type AssetDef, BUILTIN_ACTOR_TYPES, type BuiltinActorType, CAMERA_ACTION_NAMES, type Chapter, FIGURE_ONLY_ACTION_NAMES, type ImportDecl, PRESETS, PRESET_NAMES, ParseError, type ParseOptions, type ParseWarning, type PresetFn, type SceneAST, type SceneMeta, type SequenceDef, type TemplateDef, type TimelineEvent, UNIVERSAL_ACTION_NAMES, parse, registerActorPack };
package/dist/index.js CHANGED
@@ -232,20 +232,29 @@ var BUILTIN_ACTOR_TYPES = [
232
232
  "figure",
233
233
  "caption"
234
234
  ];
235
- var UNIVERSAL_ACTIONS = /* @__PURE__ */ new Set([
235
+ var UNIVERSAL_ACTION_NAMES = [
236
236
  "enter",
237
237
  "exit",
238
238
  "move",
239
+ "spring",
240
+ "follow_path",
239
241
  "fade_in",
240
242
  "fade_out",
241
243
  "scale",
242
244
  "rotate",
243
245
  "shake",
246
+ "pulse",
247
+ "glow",
248
+ "ripple",
249
+ "blur",
250
+ "line_reveal",
251
+ "mask",
252
+ "parallax",
244
253
  "say",
245
254
  "throw",
246
255
  "play"
247
- ]);
248
- var FIGURE_ONLY_ACTIONS = /* @__PURE__ */ new Set([
256
+ ];
257
+ var FIGURE_ONLY_ACTION_NAMES = [
249
258
  "punch",
250
259
  "kick",
251
260
  "wave",
@@ -255,8 +264,11 @@ var FIGURE_ONLY_ACTIONS = /* @__PURE__ */ new Set([
255
264
  "face",
256
265
  "rotate_part",
257
266
  "pose"
258
- ]);
259
- var CAMERA_ACTIONS = /* @__PURE__ */ new Set(["pan", "zoom", "shake"]);
267
+ ];
268
+ var CAMERA_ACTION_NAMES = ["pan", "zoom", "shake"];
269
+ var UNIVERSAL_ACTIONS = new Set(UNIVERSAL_ACTION_NAMES);
270
+ var FIGURE_ONLY_ACTIONS = new Set(FIGURE_ONLY_ACTION_NAMES);
271
+ var CAMERA_ACTIONS = new Set(CAMERA_ACTION_NAMES);
260
272
  var actorTypes = new Set(BUILTIN_ACTOR_TYPES);
261
273
  var actorActions = /* @__PURE__ */ new Map();
262
274
  function registerActorPack(pack) {
@@ -514,6 +526,22 @@ var SEQ_EVENT_RE = /^@\+([\d.]+):\s+\$\.(!?\w+)\((.*)\)$/;
514
526
  var CHAPTER_HEADER_RE = /^scene\s+"([^"]+)"\s*\{$/;
515
527
  var IMPORT_RE = /^import\s+"([^"]+)"\s+as\s+(\w+)\s*$/;
516
528
  var PRESET_RE = /^preset\s+(\w+)(?:\s*\((.*)\))?\s*$/;
529
+ var ARCHITECTURE_NODE_RE = /^(service|database|db|queue|cache|api|user|client|cloud|region|container|microservice|cluster)\s+(\w+)(?:\s+("[^"]+"|'[^']+'))?(?:\s+at\s+\(\s*(-?[\d.]+)\s*,\s*(-?[\d.]+)\s*\))?(.*)$/;
530
+ var ARCHITECTURE_ALIAS_TO_TYPE = {
531
+ api: "service",
532
+ database: "database",
533
+ db: "db",
534
+ user: "client",
535
+ client: "client",
536
+ service: "service",
537
+ queue: "queue",
538
+ cache: "cache",
539
+ cloud: "cloud",
540
+ region: "region",
541
+ container: "container",
542
+ microservice: "microservice",
543
+ cluster: "cluster"
544
+ };
517
545
  function interpolate(s, vars) {
518
546
  return s.replace(
519
547
  /\\?\$\{([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\}/g,
@@ -555,6 +583,7 @@ function parse(source, opts = {}) {
555
583
  defs: {},
556
584
  seqs: {},
557
585
  vars: {},
586
+ groups: {},
558
587
  chapters: [],
559
588
  warnings: [],
560
589
  imports: []
@@ -775,7 +804,7 @@ function parse(source, opts = {}) {
775
804
  }
776
805
  continue;
777
806
  }
778
- if (raw.startsWith("actor ")) {
807
+ if (raw.startsWith("actor ") || isArchitectureNodeLine(raw)) {
779
808
  const actorInfo = parseActorLine(raw, lineNum, ast);
780
809
  if (!actorCountWarned && actorCountWarningThreshold > 0) {
781
810
  const actorCount = Object.keys(ast.actors).length;
@@ -800,6 +829,10 @@ function parse(source, opts = {}) {
800
829
  }
801
830
  continue;
802
831
  }
832
+ if (raw.startsWith("group ")) {
833
+ parseGroupLine(raw, lineNum, ast);
834
+ continue;
835
+ }
803
836
  if (raw.startsWith("@")) {
804
837
  parseEventLine(raw, lineNum, ast, inChapter, topScope);
805
838
  continue;
@@ -837,6 +870,8 @@ function parseActorCallArgs(argsRaw) {
837
870
  });
838
871
  }
839
872
  function parseActorLine(raw, lineNum, ast) {
873
+ const architectureNode = parseArchitectureNodeLine(raw, lineNum, ast);
874
+ if (architectureNode) return architectureNode;
840
875
  const amAnchor = ACTOR_ANCHOR_POS_RE.exec(raw);
841
876
  const amNum = amAnchor ? null : ACTOR_NUM_POS_RE.exec(raw);
842
877
  if (!amAnchor && !amNum) throw new ParseError(`Invalid actor declaration: ${raw}`, lineNum);
@@ -927,6 +962,110 @@ function parseActorLine(raw, lineNum, ast) {
927
962
  };
928
963
  return { name, args: resolvedArgs };
929
964
  }
965
+ function isArchitectureNodeLine(raw) {
966
+ return ARCHITECTURE_NODE_RE.test(raw);
967
+ }
968
+ function parseArchitectureNodeLine(raw, lineNum, ast) {
969
+ const m = ARCHITECTURE_NODE_RE.exec(raw);
970
+ if (!m) return null;
971
+ const [, alias, name, labelRaw, xRaw, yRaw, trailer] = m;
972
+ if (name === "camera") {
973
+ throw new ParseError(
974
+ `"camera" is a reserved actor name; choose a different architecture node name`,
975
+ lineNum
976
+ );
977
+ }
978
+ const typeName = ARCHITECTURE_ALIAS_TO_TYPE[alias] ?? alias;
979
+ if (!isKnownActorType(typeName)) {
980
+ throw new ParseError(
981
+ `Architecture node "${alias}" requires an actor pack that registers type "${typeName}"`,
982
+ lineNum
983
+ );
984
+ }
985
+ const actorIndex = Object.keys(ast.actors).length;
986
+ const x = xRaw === void 0 ? autoNodeX(actorIndex, ast.meta.width) : Number(xRaw);
987
+ const y = yRaw === void 0 ? autoNodeY(actorIndex, ast.meta.height, ast.meta.width) : Number(yRaw);
988
+ if (x < 0 || x > ast.meta.width || y < 0 || y > ast.meta.height) {
989
+ throw new ParseError(
990
+ `Actor "${name}" position (${x}, ${y}) is outside scene bounds (0\u2013${ast.meta.width}, 0\u2013${ast.meta.height})`,
991
+ lineNum
992
+ );
993
+ }
994
+ const quoted = labelRaw ? parseQuotedLiteral(labelRaw.trim()) : null;
995
+ const label = quoted ?? labelRaw?.trim() ?? name;
996
+ const modifiers = parseActorTrailer(trailer, lineNum, ast.warnings);
997
+ ast.actors[name] = {
998
+ type: typeName,
999
+ args: [label],
1000
+ x,
1001
+ y,
1002
+ ...modifiers
1003
+ };
1004
+ return { name, args: [label] };
1005
+ }
1006
+ function autoNodeX(index, width) {
1007
+ const columns = Math.max(1, Math.floor(width / 220));
1008
+ const gutter = width / (columns + 1);
1009
+ return Math.round(gutter * (index % columns + 1));
1010
+ }
1011
+ function autoNodeY(index, height, width = DEFAULTS.width) {
1012
+ const columns = Math.max(1, Math.floor(width / 220));
1013
+ const row = Math.floor(index / columns);
1014
+ return Math.min(height - 60, 90 + row * 120);
1015
+ }
1016
+ var GROUP_RE = /^group\s+([A-Za-z_][\w.]*)\s*=\s*(.+)$/;
1017
+ function parseGroupLine(raw, lineNum, ast) {
1018
+ const m = GROUP_RE.exec(raw);
1019
+ if (!m) {
1020
+ throw new ParseError(`Invalid group declaration: ${raw}`, lineNum);
1021
+ }
1022
+ const [, name, membersRaw] = m;
1023
+ if (ast.actors[name]) {
1024
+ throw new ParseError(`Group "${name}" collides with an actor of the same name`, lineNum);
1025
+ }
1026
+ if (ast.groups[name]) {
1027
+ throw new ParseError(`Group "${name}" is already defined`, lineNum);
1028
+ }
1029
+ const members = splitByComma(membersRaw).map((part) => part.trim()).filter((part) => part.length > 0);
1030
+ if (members.length === 0) {
1031
+ throw new ParseError(`Group "${name}" has no members`, lineNum);
1032
+ }
1033
+ for (const member of members) {
1034
+ if (!ast.actors[member]) {
1035
+ throw new ParseError(`Unknown actor "${member}" in group "${name}"`, lineNum);
1036
+ }
1037
+ }
1038
+ const seen = /* @__PURE__ */ new Set();
1039
+ for (const member of members) {
1040
+ if (seen.has(member)) {
1041
+ throw new ParseError(`Actor "${member}" listed twice in group "${name}"`, lineNum);
1042
+ }
1043
+ seen.add(member);
1044
+ }
1045
+ ast.groups[name] = members;
1046
+ }
1047
+ function takeStagger(paramsRaw, lineNum) {
1048
+ if (!paramsRaw.includes("stagger")) return { step: 0, rest: paramsRaw };
1049
+ const kept = [];
1050
+ let step = 0;
1051
+ for (const part of splitByComma(paramsRaw)) {
1052
+ const eq = part.indexOf("=");
1053
+ if (eq !== -1 && part.slice(0, eq).trim() === "stagger") {
1054
+ const rawValue = part.slice(eq + 1).trim();
1055
+ const value = Number(rawValue);
1056
+ if (Number.isNaN(value)) {
1057
+ throw new ParseError(`Invalid stagger value: ${rawValue}`, lineNum);
1058
+ }
1059
+ if (value < 0) {
1060
+ throw new ParseError(`stagger must not be negative (got ${rawValue})`, lineNum);
1061
+ }
1062
+ step = value;
1063
+ continue;
1064
+ }
1065
+ kept.push(part);
1066
+ }
1067
+ return { step, rest: kept.join(",") };
1068
+ }
930
1069
  function parseEventLine(raw, lineNum, ast, inChapter, topScope) {
931
1070
  const scope = inChapter?.scope ?? topScope;
932
1071
  const recordEventTime = (t) => {
@@ -977,18 +1116,54 @@ function parseEventLine(raw, lineNum, ast, inChapter, topScope) {
977
1116
  line: lineNum
978
1117
  });
979
1118
  }
980
- const params2 = parseActionParams(action, paramsRaw);
1119
+ const params = parseActionParams(action, paramsRaw);
981
1120
  recordEventTime(time);
982
1121
  pushEvent(ast, scope, {
983
1122
  time,
984
1123
  actor: "camera",
985
1124
  action,
986
- params: params2,
1125
+ params,
987
1126
  line: lineNum,
988
1127
  ...inChapter ? { chapter: inChapter.name } : {}
989
1128
  });
990
1129
  return;
991
1130
  }
1131
+ const groupMembers = ast.groups[actor];
1132
+ if (!groupMembers && !ast.actors[actor]) {
1133
+ throw new ParseError(`Unknown actor: "${actor}"`, lineNum);
1134
+ }
1135
+ if (groupMembers) {
1136
+ const { step, rest } = takeStagger(paramsRaw, lineNum);
1137
+ groupMembers.forEach((member, index) => {
1138
+ emitActorEvent(
1139
+ member,
1140
+ round3(time + index * step),
1141
+ action,
1142
+ rest,
1143
+ mustUnderstand,
1144
+ lineNum,
1145
+ ast,
1146
+ scope,
1147
+ inChapter,
1148
+ recordEventTime
1149
+ );
1150
+ });
1151
+ return;
1152
+ }
1153
+ emitActorEvent(
1154
+ actor,
1155
+ time,
1156
+ action,
1157
+ paramsRaw,
1158
+ mustUnderstand,
1159
+ lineNum,
1160
+ ast,
1161
+ scope,
1162
+ inChapter,
1163
+ recordEventTime
1164
+ );
1165
+ }
1166
+ function emitActorEvent(actor, time, action, paramsRaw, mustUnderstand, lineNum, ast, scope, inChapter, recordEventTime) {
992
1167
  const actorDef = ast.actors[actor];
993
1168
  if (!actorDef) {
994
1169
  throw new ParseError(`Unknown actor: "${actor}"`, lineNum);
@@ -1105,9 +1280,13 @@ function round3(n) {
1105
1280
  return Math.round(n * 1e3) / 1e3;
1106
1281
  }
1107
1282
  export {
1283
+ BUILTIN_ACTOR_TYPES,
1284
+ CAMERA_ACTION_NAMES,
1285
+ FIGURE_ONLY_ACTION_NAMES,
1108
1286
  PRESETS,
1109
1287
  PRESET_NAMES,
1110
1288
  ParseError,
1289
+ UNIVERSAL_ACTION_NAMES,
1111
1290
  parse,
1112
1291
  registerActorPack
1113
1292
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markdy/core",
3
- "version": "0.7.25",
3
+ "version": "0.7.27",
4
4
  "description": "MarkdyScript parser and AST types — zero runtime dependencies.",
5
5
  "license": "MIT",
6
6
  "type": "module",