@markdy/core 0.8.5 → 0.8.7
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 +7 -1
- package/dist/index.js +250 -42
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -77,6 +77,12 @@ type Cue = {
|
|
|
77
77
|
zoom?: number;
|
|
78
78
|
dur?: number;
|
|
79
79
|
line: number;
|
|
80
|
+
} | {
|
|
81
|
+
kind: "frame";
|
|
82
|
+
targets: string[];
|
|
83
|
+
zoom?: number;
|
|
84
|
+
dur?: number;
|
|
85
|
+
line: number;
|
|
80
86
|
} | {
|
|
81
87
|
kind: "use";
|
|
82
88
|
pattern: string;
|
|
@@ -163,7 +169,7 @@ type RoutedEdge = {
|
|
|
163
169
|
type TimedCue = {
|
|
164
170
|
start: number;
|
|
165
171
|
duration: number;
|
|
166
|
-
kind: "show" | "hide" | "flow" | "glow" | "focus";
|
|
172
|
+
kind: "show" | "hide" | "flow" | "glow" | "focus" | "frame";
|
|
167
173
|
targets: string[];
|
|
168
174
|
edgeId?: string;
|
|
169
175
|
segments?: FlowSegment[];
|
package/dist/index.js
CHANGED
|
@@ -349,8 +349,9 @@ var EDGE_OPERATORS = {
|
|
|
349
349
|
"~>": "event",
|
|
350
350
|
"--": "dependency"
|
|
351
351
|
};
|
|
352
|
-
var
|
|
353
|
-
var
|
|
352
|
+
var RESERVED_SELECTORS = /* @__PURE__ */ new Set(["$title", "$nodes", "$edges"]);
|
|
353
|
+
var BEAT_CUE_KEYWORDS = /* @__PURE__ */ new Set(["show", "hide", "glow", "focus", "frame", "use"]);
|
|
354
|
+
var SCENE_KEYS = /* @__PURE__ */ new Set(["width", "height", "fps", "theme", "duration", "direction", "layout"]);
|
|
354
355
|
function nodeRole(kind) {
|
|
355
356
|
return TECHNICAL_NODE_KINDS[kind] ?? "compute";
|
|
356
357
|
}
|
|
@@ -391,6 +392,7 @@ var DEFAULTS = {
|
|
|
391
392
|
flow: 0.55,
|
|
392
393
|
glow: 0.45,
|
|
393
394
|
focus: 0.6,
|
|
395
|
+
frame: 0.7,
|
|
394
396
|
beatGap: 0.14,
|
|
395
397
|
cueGap: 0.08,
|
|
396
398
|
stagger: 0.06
|
|
@@ -575,7 +577,7 @@ function scheduleBeats(ast, edges) {
|
|
|
575
577
|
}
|
|
576
578
|
return;
|
|
577
579
|
}
|
|
578
|
-
if (cue.kind !== "show" && cue.kind !== "hide" && cue.kind !== "glow" && cue.kind !== "focus") return;
|
|
580
|
+
if (cue.kind !== "show" && cue.kind !== "hide" && cue.kind !== "glow" && cue.kind !== "focus" && cue.kind !== "frame") return;
|
|
579
581
|
const targets = resolveTargets(cue.targets, ast, Object.fromEntries(Object.entries(ast.groups).map(([k, g]) => [k, g.members])));
|
|
580
582
|
scheduled.push({
|
|
581
583
|
start: t,
|
|
@@ -586,7 +588,7 @@ function scheduleBeats(ast, edges) {
|
|
|
586
588
|
stagger: cue.kind === "show" ? cue.stagger ?? DEFAULTS.stagger : void 0,
|
|
587
589
|
color: cue.kind === "glow" ? cue.color : void 0,
|
|
588
590
|
strength: cue.kind === "glow" ? cue.strength : void 0,
|
|
589
|
-
zoom: cue.kind === "focus" ? cue.zoom : void 0
|
|
591
|
+
zoom: cue.kind === "focus" || cue.kind === "frame" ? cue.zoom : void 0
|
|
590
592
|
},
|
|
591
593
|
beat: beat.name
|
|
592
594
|
});
|
|
@@ -756,24 +758,79 @@ var ParseError = class extends Error {
|
|
|
756
758
|
}
|
|
757
759
|
};
|
|
758
760
|
var FLOW_OP_RE = /(->|<-|~>|--)/;
|
|
759
|
-
var PROP_RE = /(\w[\w.-]*)=(\S+)/g;
|
|
760
761
|
function stripComment(line) {
|
|
761
|
-
|
|
762
|
-
|
|
762
|
+
let inString = false;
|
|
763
|
+
let escaped = false;
|
|
764
|
+
for (let i = 0; i < line.length; i++) {
|
|
765
|
+
const ch = line[i];
|
|
766
|
+
if (escaped) {
|
|
767
|
+
escaped = false;
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
if (inString && ch === "\\") {
|
|
771
|
+
escaped = true;
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
if (ch === '"') {
|
|
775
|
+
inString = !inString;
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
if (inString) continue;
|
|
779
|
+
if (ch === "/" && line[i + 1] === "/") return line.slice(0, i);
|
|
780
|
+
if (ch === "#" && (i === 0 || /\s/.test(line[i - 1]))) return line.slice(0, i);
|
|
781
|
+
}
|
|
782
|
+
return line;
|
|
783
|
+
}
|
|
784
|
+
function parsePropValue(raw) {
|
|
785
|
+
let val = raw;
|
|
786
|
+
if (raw.startsWith('"')) {
|
|
787
|
+
const parsed = parseStringToken(raw);
|
|
788
|
+
if (parsed && parsed.rest === "") val = parsed.value;
|
|
789
|
+
}
|
|
790
|
+
if (typeof val === "string") {
|
|
791
|
+
if (/^\d+(\.\d+)?$/.test(val)) val = Number(val);
|
|
792
|
+
else if (val === "true") val = true;
|
|
793
|
+
else if (val === "false") val = false;
|
|
794
|
+
else if (/^\d+ms$/.test(val)) val = Number(val.slice(0, -2)) / 1e3;
|
|
795
|
+
else if (/^\d+(\.\d+)?s$/.test(val)) val = Number(val.slice(0, -1));
|
|
796
|
+
}
|
|
797
|
+
return val;
|
|
763
798
|
}
|
|
764
799
|
function parseProps(raw) {
|
|
765
800
|
const props = {};
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
if (
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
801
|
+
let i = 0;
|
|
802
|
+
while (i < raw.length) {
|
|
803
|
+
const ch = raw[i];
|
|
804
|
+
if (ch === '"') {
|
|
805
|
+
const parsed = parseStringToken(raw.slice(i));
|
|
806
|
+
if (!parsed) break;
|
|
807
|
+
i = raw.length - parsed.rest.length;
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
const keyMatch = raw.slice(i).match(/^(\w[\w.-]*)=/);
|
|
811
|
+
if (!keyMatch) {
|
|
812
|
+
i++;
|
|
813
|
+
continue;
|
|
775
814
|
}
|
|
776
|
-
|
|
815
|
+
const key = keyMatch[1];
|
|
816
|
+
i += key.length + 1;
|
|
817
|
+
let value = "";
|
|
818
|
+
if (raw[i] === '"') {
|
|
819
|
+
const start = i;
|
|
820
|
+
const parsed = parseStringToken(raw.slice(i));
|
|
821
|
+
if (!parsed) {
|
|
822
|
+
value = raw.slice(i);
|
|
823
|
+
i = raw.length;
|
|
824
|
+
} else {
|
|
825
|
+
i = raw.length - parsed.rest.length;
|
|
826
|
+
value = raw.slice(start, i).trim();
|
|
827
|
+
}
|
|
828
|
+
} else {
|
|
829
|
+
const start = i;
|
|
830
|
+
while (i < raw.length && !/\s/.test(raw[i])) i++;
|
|
831
|
+
value = raw.slice(start, i);
|
|
832
|
+
}
|
|
833
|
+
props[key] = parsePropValue(value);
|
|
777
834
|
}
|
|
778
835
|
return props;
|
|
779
836
|
}
|
|
@@ -798,6 +855,56 @@ function parseStringToken(raw) {
|
|
|
798
855
|
function splitTargets(raw) {
|
|
799
856
|
return raw.split(/[\s,]+/).map((t) => t.trim()).filter(Boolean);
|
|
800
857
|
}
|
|
858
|
+
function splitOutsideQuotes(raw, separator) {
|
|
859
|
+
const parts = [];
|
|
860
|
+
let start = 0;
|
|
861
|
+
let inString = false;
|
|
862
|
+
let escaped = false;
|
|
863
|
+
for (let i = 0; i < raw.length; i++) {
|
|
864
|
+
const ch = raw[i];
|
|
865
|
+
if (escaped) {
|
|
866
|
+
escaped = false;
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
if (inString && ch === "\\") {
|
|
870
|
+
escaped = true;
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
if (ch === '"') {
|
|
874
|
+
inString = !inString;
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
if (!inString && ch === separator && /\s/.test(raw[i - 1] ?? "") && /\s/.test(raw[i + 1] ?? "")) {
|
|
878
|
+
parts.push(raw.slice(start, i).trim());
|
|
879
|
+
start = i + 1;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
parts.push(raw.slice(start).trim());
|
|
883
|
+
return parts.filter(Boolean);
|
|
884
|
+
}
|
|
885
|
+
function stripCueProps(raw, keys) {
|
|
886
|
+
let inString = false;
|
|
887
|
+
let escaped = false;
|
|
888
|
+
for (let i = 0; i < raw.length; i++) {
|
|
889
|
+
const ch = raw[i];
|
|
890
|
+
if (escaped) {
|
|
891
|
+
escaped = false;
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
if (inString && ch === "\\") {
|
|
895
|
+
escaped = true;
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
if (ch === '"') {
|
|
899
|
+
inString = !inString;
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
if (inString || !/\s/.test(ch)) continue;
|
|
903
|
+
const rest = raw.slice(i + 1);
|
|
904
|
+
if (keys.some((key) => rest.startsWith(`${key}=`))) return raw.slice(0, i).trim();
|
|
905
|
+
}
|
|
906
|
+
return raw.trim();
|
|
907
|
+
}
|
|
801
908
|
function parseFlowChain(line, lineNo) {
|
|
802
909
|
const segments = [];
|
|
803
910
|
const parts = line.split(FLOW_OP_RE).map((p) => p.trim()).filter(Boolean);
|
|
@@ -834,18 +941,17 @@ function splitTargetLabel(token, lineNo) {
|
|
|
834
941
|
}
|
|
835
942
|
function parseCueLine(line, lineNo) {
|
|
836
943
|
const trimmed = line.trim();
|
|
837
|
-
const
|
|
838
|
-
const
|
|
839
|
-
if (
|
|
840
|
-
const parts = trimmed.split(/\s+&\s+/);
|
|
944
|
+
const props = parseProps(trimmed);
|
|
945
|
+
const parallelParts = splitOutsideQuotes(trimmed, "&");
|
|
946
|
+
if (parallelParts.length > 1) {
|
|
841
947
|
return {
|
|
842
948
|
kind: "parallel",
|
|
843
|
-
cues:
|
|
949
|
+
cues: parallelParts.map((p, idx) => parseCueLine(p, lineNo + idx * 1e-3)),
|
|
844
950
|
line: lineNo
|
|
845
951
|
};
|
|
846
952
|
}
|
|
847
953
|
if (FLOW_OP_RE.test(trimmed)) {
|
|
848
|
-
const chainPart = trimmed
|
|
954
|
+
const chainPart = stripCueProps(trimmed, ["dur", "stagger", "color", "strength", "zoom", "after"]);
|
|
849
955
|
return {
|
|
850
956
|
kind: "flow",
|
|
851
957
|
segments: parseFlowChain(chainPart, lineNo),
|
|
@@ -856,7 +962,7 @@ function parseCueLine(line, lineNo) {
|
|
|
856
962
|
const [head, ...rest] = trimmed.split(/\s+/);
|
|
857
963
|
const keyword = head.toLowerCase();
|
|
858
964
|
if (keyword === "show" || keyword === "hide") {
|
|
859
|
-
const targetRaw = rest.join(" ")
|
|
965
|
+
const targetRaw = stripCueProps(rest.join(" "), ["dur", "stagger"]);
|
|
860
966
|
return {
|
|
861
967
|
kind: keyword,
|
|
862
968
|
targets: splitTargets(targetRaw),
|
|
@@ -866,7 +972,7 @@ function parseCueLine(line, lineNo) {
|
|
|
866
972
|
};
|
|
867
973
|
}
|
|
868
974
|
if (keyword === "glow") {
|
|
869
|
-
const targetRaw = rest.join(" ")
|
|
975
|
+
const targetRaw = stripCueProps(rest.join(" "), ["color", "strength", "dur"]);
|
|
870
976
|
return {
|
|
871
977
|
kind: "glow",
|
|
872
978
|
targets: splitTargets(targetRaw),
|
|
@@ -877,7 +983,7 @@ function parseCueLine(line, lineNo) {
|
|
|
877
983
|
};
|
|
878
984
|
}
|
|
879
985
|
if (keyword === "focus") {
|
|
880
|
-
const targetRaw = rest.join(" ")
|
|
986
|
+
const targetRaw = stripCueProps(rest.join(" "), ["zoom", "dur"]);
|
|
881
987
|
return {
|
|
882
988
|
kind: "focus",
|
|
883
989
|
targets: splitTargets(targetRaw),
|
|
@@ -886,6 +992,18 @@ function parseCueLine(line, lineNo) {
|
|
|
886
992
|
line: lineNo
|
|
887
993
|
};
|
|
888
994
|
}
|
|
995
|
+
if (keyword === "frame") {
|
|
996
|
+
const targetRaw = stripCueProps(rest.join(" "), ["zoom", "dur"]);
|
|
997
|
+
const targets = splitTargets(targetRaw);
|
|
998
|
+
if (targets.length === 0) throw new ParseError(`expected frame target`, lineNo);
|
|
999
|
+
return {
|
|
1000
|
+
kind: "frame",
|
|
1001
|
+
targets,
|
|
1002
|
+
zoom: typeof props.zoom === "number" ? props.zoom : void 0,
|
|
1003
|
+
dur: typeof props.dur === "number" ? props.dur : void 0,
|
|
1004
|
+
line: lineNo
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
889
1007
|
if (keyword === "use") {
|
|
890
1008
|
const call = rest.join(" ");
|
|
891
1009
|
const m = call.match(/^(\w+)\s*\((.*)\)\s*$/);
|
|
@@ -931,10 +1049,9 @@ function substitutePatternCue(cue, params, args) {
|
|
|
931
1049
|
params.forEach((p, i) => {
|
|
932
1050
|
if (resolvedArgs[p] === void 0 && positional[i]) resolvedArgs[p] = positional[i];
|
|
933
1051
|
});
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
delete resolvedArgs.__pos_3;
|
|
1052
|
+
for (const key of Object.keys(resolvedArgs)) {
|
|
1053
|
+
if (key.startsWith("__pos_")) delete resolvedArgs[key];
|
|
1054
|
+
}
|
|
938
1055
|
const sub = (s) => {
|
|
939
1056
|
for (const p of params) {
|
|
940
1057
|
if (resolvedArgs[p]) s = s.replaceAll(`$${p}`, resolvedArgs[p]);
|
|
@@ -952,7 +1069,7 @@ function substitutePatternCue(cue, params, args) {
|
|
|
952
1069
|
}))
|
|
953
1070
|
};
|
|
954
1071
|
}
|
|
955
|
-
if (cue.kind === "show" || cue.kind === "hide" || cue.kind === "glow" || cue.kind === "focus") {
|
|
1072
|
+
if (cue.kind === "show" || cue.kind === "hide" || cue.kind === "glow" || cue.kind === "focus" || cue.kind === "frame") {
|
|
956
1073
|
return { ...cue, targets: cue.targets.map(sub) };
|
|
957
1074
|
}
|
|
958
1075
|
if (cue.kind === "parallel") {
|
|
@@ -980,6 +1097,91 @@ function readIndentedBody(blocks, startIdx, parentIndent) {
|
|
|
980
1097
|
}
|
|
981
1098
|
return { body, nextIdx: i };
|
|
982
1099
|
}
|
|
1100
|
+
function readBody(blocks, startIdx, parentIndent, braceDelimited) {
|
|
1101
|
+
if (!braceDelimited) return readIndentedBody(blocks, startIdx, parentIndent);
|
|
1102
|
+
const body = [];
|
|
1103
|
+
let i = startIdx;
|
|
1104
|
+
while (i < blocks.length) {
|
|
1105
|
+
if (blocks[i].text === "}") return { body, nextIdx: i + 1 };
|
|
1106
|
+
body.push(blocks[i]);
|
|
1107
|
+
i++;
|
|
1108
|
+
}
|
|
1109
|
+
return { body, nextIdx: i };
|
|
1110
|
+
}
|
|
1111
|
+
function normalizeCueBlocks(blocks) {
|
|
1112
|
+
const normalized = [];
|
|
1113
|
+
for (const block of blocks) {
|
|
1114
|
+
if (block.text.startsWith("& ")) {
|
|
1115
|
+
const prev = normalized[normalized.length - 1];
|
|
1116
|
+
if (!prev) throw new ParseError(`parallel continuation must follow a cue`, block.line);
|
|
1117
|
+
prev.text = `${prev.text} ${block.text}`;
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
normalized.push({ ...block });
|
|
1121
|
+
}
|
|
1122
|
+
return normalized;
|
|
1123
|
+
}
|
|
1124
|
+
function pushWarning(diagnostics, seen, line, message) {
|
|
1125
|
+
const key = `${line}:${message}`;
|
|
1126
|
+
if (seen.has(key)) return;
|
|
1127
|
+
seen.add(key);
|
|
1128
|
+
diagnostics.push({ severity: "warning", message, line });
|
|
1129
|
+
}
|
|
1130
|
+
function visitCues(cues, visit) {
|
|
1131
|
+
for (const cue of cues) {
|
|
1132
|
+
visit(cue);
|
|
1133
|
+
if (cue.kind === "parallel") visitCues(cue.cues, visit);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
function validateReferences(ast) {
|
|
1137
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1138
|
+
const hasNode = (id) => Boolean(ast.nodes[id]);
|
|
1139
|
+
const hasGroup = (id) => Boolean(ast.groups[id]);
|
|
1140
|
+
const isKnownTarget = (target) => {
|
|
1141
|
+
if (RESERVED_SELECTORS.has(target)) return true;
|
|
1142
|
+
if (target.startsWith("$")) return hasGroup(target.slice(1));
|
|
1143
|
+
return hasNode(target) || hasGroup(target);
|
|
1144
|
+
};
|
|
1145
|
+
for (const group of Object.values(ast.groups)) {
|
|
1146
|
+
for (const member of group.members) {
|
|
1147
|
+
if (!hasNode(member)) {
|
|
1148
|
+
pushWarning(ast.diagnostics, seen, group.line, `group '${group.id}' references unknown node '${member}'`);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
for (const node of Object.values(ast.nodes)) {
|
|
1153
|
+
if (node.style && !ast.styles[node.style]) {
|
|
1154
|
+
pushWarning(ast.diagnostics, seen, node.line, `node '${node.id}' references unknown style '${node.style}'`);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
const validateFlowEndpoint = (line, endpoint) => {
|
|
1158
|
+
if (!hasNode(endpoint)) {
|
|
1159
|
+
pushWarning(ast.diagnostics, seen, line, `flow references unknown node '${endpoint}'`);
|
|
1160
|
+
}
|
|
1161
|
+
};
|
|
1162
|
+
for (const edge of ast.edges) {
|
|
1163
|
+
validateFlowEndpoint(edge.line, edge.from);
|
|
1164
|
+
validateFlowEndpoint(edge.line, edge.to);
|
|
1165
|
+
}
|
|
1166
|
+
for (const beat of ast.beats) {
|
|
1167
|
+
visitCues(beat.cues, (cue) => {
|
|
1168
|
+
if (cue.kind === "flow") {
|
|
1169
|
+
for (const segment of cue.segments) {
|
|
1170
|
+
validateFlowEndpoint(cue.line, segment.from);
|
|
1171
|
+
validateFlowEndpoint(cue.line, segment.to);
|
|
1172
|
+
}
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
if (cue.kind === "show" || cue.kind === "hide" || cue.kind === "glow" || cue.kind === "focus" || cue.kind === "frame") {
|
|
1176
|
+
for (const target of cue.targets) {
|
|
1177
|
+
if (!isKnownTarget(target)) {
|
|
1178
|
+
pushWarning(ast.diagnostics, seen, cue.line, `${cue.kind} references unknown target '${target}'`);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
983
1185
|
function parse(source, opts = {}) {
|
|
984
1186
|
const lines = source.replace(/\r\n/g, "\n").split("\n");
|
|
985
1187
|
const diagnostics = [];
|
|
@@ -1012,6 +1214,11 @@ function parse(source, opts = {}) {
|
|
|
1012
1214
|
title = str.value;
|
|
1013
1215
|
remainder = str.rest;
|
|
1014
1216
|
}
|
|
1217
|
+
const inlineLayout = remainder.match(/\blayout\s+(LR|RL|TB|BT)\b/i);
|
|
1218
|
+
if (inlineLayout) {
|
|
1219
|
+
meta.direction = inlineLayout[1].toUpperCase();
|
|
1220
|
+
remainder = remainder.replace(/\blayout\s+(LR|RL|TB|BT)\b/i, " ");
|
|
1221
|
+
}
|
|
1015
1222
|
const props = parseProps(remainder);
|
|
1016
1223
|
for (const [k, v] of Object.entries(props)) {
|
|
1017
1224
|
if (!SCENE_KEYS.has(k)) {
|
|
@@ -1021,7 +1228,7 @@ function parse(source, opts = {}) {
|
|
|
1021
1228
|
if (k === "width" || k === "height" || k === "fps") meta[k] = Number(v);
|
|
1022
1229
|
else if (k === "duration") meta.duration = Number(v);
|
|
1023
1230
|
else if (k === "theme") meta.theme = String(v);
|
|
1024
|
-
else if (k === "direction") meta.direction = String(v).toUpperCase();
|
|
1231
|
+
else if (k === "direction" || k === "layout") meta.direction = String(v).toUpperCase();
|
|
1025
1232
|
}
|
|
1026
1233
|
i++;
|
|
1027
1234
|
continue;
|
|
@@ -1039,11 +1246,11 @@ function parse(source, opts = {}) {
|
|
|
1039
1246
|
continue;
|
|
1040
1247
|
}
|
|
1041
1248
|
if (line.startsWith("pattern ")) {
|
|
1042
|
-
const m = line.match(/^pattern\s+(\w+)\s*\(([^)]*)\)\s
|
|
1249
|
+
const m = line.match(/^pattern\s+(\w+)\s*\(([^)]*)\)\s*(?::|\{)\s*$/);
|
|
1043
1250
|
if (!m) throw new ParseError(`expected pattern name(params):`, lineNo);
|
|
1044
1251
|
const params = m[2].trim() ? m[2].split(",").map((p) => p.trim()) : [];
|
|
1045
|
-
const { body, nextIdx } =
|
|
1046
|
-
const cues = body.map((b) => parseCueLine(b.text, b.line));
|
|
1252
|
+
const { body, nextIdx } = readBody(blocks, i + 1, block.indent, line.endsWith("{"));
|
|
1253
|
+
const cues = normalizeCueBlocks(body).map((b) => parseCueLine(b.text, b.line));
|
|
1047
1254
|
patterns[m[1]] = { name: m[1], params, body: cues, line: lineNo };
|
|
1048
1255
|
i = nextIdx;
|
|
1049
1256
|
continue;
|
|
@@ -1080,10 +1287,10 @@ function parse(source, opts = {}) {
|
|
|
1080
1287
|
continue;
|
|
1081
1288
|
}
|
|
1082
1289
|
if (line.startsWith("beat ")) {
|
|
1083
|
-
const m = line.match(/^beat\s+(\w+)(?:\s+"([^"]*)")?\s
|
|
1290
|
+
const m = line.match(/^beat\s+(\w+)(?:\s+"([^"]*)")?\s*(?::|\{)\s*$/);
|
|
1084
1291
|
if (!m) throw new ParseError(`expected beat name:`, lineNo);
|
|
1085
|
-
const { body, nextIdx } =
|
|
1086
|
-
let cues = body.map((b) => parseCueLine(b.text, b.line));
|
|
1292
|
+
const { body, nextIdx } = readBody(blocks, i + 1, block.indent, line.endsWith("{"));
|
|
1293
|
+
let cues = normalizeCueBlocks(body).map((b) => parseCueLine(b.text, b.line));
|
|
1087
1294
|
cues = expandPatternCues(cues, patterns, lineNo);
|
|
1088
1295
|
beats.push({ name: m[1], label: m[2], cues, line: lineNo });
|
|
1089
1296
|
i = nextIdx;
|
|
@@ -1122,10 +1329,6 @@ function parse(source, opts = {}) {
|
|
|
1122
1329
|
}
|
|
1123
1330
|
throw new ParseError(`unexpected statement`, lineNo);
|
|
1124
1331
|
}
|
|
1125
|
-
const errors = diagnostics.filter((d) => d.severity === "error");
|
|
1126
|
-
if (errors.length) {
|
|
1127
|
-
throw new ParseError(errors[0].message, errors[0].line, errors[0].column);
|
|
1128
|
-
}
|
|
1129
1332
|
const ast = {
|
|
1130
1333
|
meta: { ...meta, title: title || void 0 },
|
|
1131
1334
|
styles,
|
|
@@ -1136,6 +1339,11 @@ function parse(source, opts = {}) {
|
|
|
1136
1339
|
beats,
|
|
1137
1340
|
diagnostics
|
|
1138
1341
|
};
|
|
1342
|
+
validateReferences(ast);
|
|
1343
|
+
const errors = ast.diagnostics.filter((d) => d.severity === "error");
|
|
1344
|
+
if (errors.length) {
|
|
1345
|
+
throw new ParseError(errors[0].message, errors[0].line, errors[0].column);
|
|
1346
|
+
}
|
|
1139
1347
|
if (!opts.parseOnly && Object.keys(nodes).length === 0 && beats.length === 0) {
|
|
1140
1348
|
}
|
|
1141
1349
|
return ast;
|
package/package.json
CHANGED