@orcalang/orca-runtime-ts 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/parser.js ADDED
@@ -0,0 +1,737 @@
1
+ /**
2
+ * Orca markdown parser.
3
+ *
4
+ * Parses .orca.md format into MachineDef objects.
5
+ * Supports hierarchical (nested) states and parallel regions.
6
+ */
7
+ export class ParseError extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "ParseError";
11
+ }
12
+ }
13
+ function parseMarkdownStructure(source) {
14
+ const lines = source.split('\n');
15
+ const elements = [];
16
+ let i = 0;
17
+ while (i < lines.length) {
18
+ const trimmed = lines[i].trim();
19
+ if (trimmed === '') {
20
+ i++;
21
+ continue;
22
+ }
23
+ // Skip fenced code blocks
24
+ if (trimmed.startsWith('```')) {
25
+ i++;
26
+ while (i < lines.length && !lines[i].trim().startsWith('```'))
27
+ i++;
28
+ if (i < lines.length)
29
+ i++;
30
+ continue;
31
+ }
32
+ // Horizontal rule separator
33
+ if (trimmed === '---') {
34
+ elements.push({ kind: 'separator' });
35
+ i++;
36
+ continue;
37
+ }
38
+ // Heading
39
+ const headingMatch = trimmed.match(/^(#{1,6})\s+(.+)$/);
40
+ if (headingMatch) {
41
+ elements.push({ kind: 'heading', level: headingMatch[1].length, text: headingMatch[2].trim() });
42
+ i++;
43
+ continue;
44
+ }
45
+ // Blockquote
46
+ if (trimmed.startsWith('>')) {
47
+ const quoteLines = [];
48
+ while (i < lines.length && lines[i].trim().startsWith('>')) {
49
+ quoteLines.push(lines[i].trim().replace(/^>\s*/, ''));
50
+ i++;
51
+ }
52
+ elements.push({ kind: 'blockquote', text: quoteLines.join('\n') });
53
+ continue;
54
+ }
55
+ // Table
56
+ if (trimmed.startsWith('|')) {
57
+ const tableLines = [];
58
+ while (i < lines.length && lines[i].trim().startsWith('|')) {
59
+ tableLines.push(lines[i].trim());
60
+ i++;
61
+ }
62
+ if (tableLines.length >= 2) {
63
+ const headers = splitTableRow(tableLines[0]);
64
+ const isSeparator = /^\|[\s\-:|]+\|$/.test(tableLines[1]);
65
+ const dataStart = isSeparator ? 2 : 1;
66
+ const rows = [];
67
+ for (let j = dataStart; j < tableLines.length; j++) {
68
+ rows.push(splitTableRow(tableLines[j]));
69
+ }
70
+ elements.push({ kind: 'table', headers, rows });
71
+ }
72
+ continue;
73
+ }
74
+ // Bullet list
75
+ if (trimmed.startsWith('- ')) {
76
+ const items = [];
77
+ while (i < lines.length && lines[i].trim().startsWith('- ')) {
78
+ items.push(lines[i].trim().substring(2).trim());
79
+ i++;
80
+ }
81
+ elements.push({ kind: 'bullets', items });
82
+ continue;
83
+ }
84
+ // Skip other text
85
+ i++;
86
+ }
87
+ return elements;
88
+ }
89
+ function splitTableRow(line) {
90
+ const cells = line.split('|').map(c => c.trim());
91
+ if (cells.length > 0 && cells[0] === '')
92
+ cells.shift();
93
+ if (cells.length > 0 && cells[cells.length - 1] === '')
94
+ cells.pop();
95
+ return cells;
96
+ }
97
+ function stripBackticks(text) {
98
+ if (text.startsWith('`') && text.endsWith('`'))
99
+ return text.slice(1, -1);
100
+ return text;
101
+ }
102
+ function findColumnIndex(headers, name) {
103
+ return headers.findIndex(h => h.toLowerCase() === name.toLowerCase());
104
+ }
105
+ function parseMdAnnotations(text) {
106
+ let isInitial = false, isFinal = false, isParallel = false;
107
+ let syncStrategy;
108
+ const bracketMatch = text.match(/\[(.+)\]/);
109
+ if (bracketMatch) {
110
+ for (const part of bracketMatch[1].split(',').map(p => p.trim())) {
111
+ if (part === 'initial')
112
+ isInitial = true;
113
+ else if (part === 'final')
114
+ isFinal = true;
115
+ else if (part === 'parallel')
116
+ isParallel = true;
117
+ else if (part.startsWith('sync:')) {
118
+ const v = part.slice(5).trim();
119
+ if (v === 'all-final' || v === 'all_final')
120
+ syncStrategy = 'all-final';
121
+ else if (v === 'any-final' || v === 'any_final')
122
+ syncStrategy = 'any-final';
123
+ else if (v === 'custom')
124
+ syncStrategy = 'custom';
125
+ }
126
+ else if (part.includes('sync:')) {
127
+ // Handle "parallel sync: any_final" format (no comma between annotations)
128
+ if (part.includes('parallel'))
129
+ isParallel = true;
130
+ const v = part.split('sync:')[1]?.trim();
131
+ if (v === 'all-final' || v === 'all_final')
132
+ syncStrategy = 'all-final';
133
+ else if (v === 'any-final' || v === 'any_final')
134
+ syncStrategy = 'any-final';
135
+ else if (v === 'custom')
136
+ syncStrategy = 'custom';
137
+ }
138
+ else if (part.includes('parallel')) {
139
+ // Handle "parallel" when combined with other non-sync annotations
140
+ isParallel = true;
141
+ }
142
+ }
143
+ }
144
+ return { isInitial, isFinal, isParallel, syncStrategy };
145
+ }
146
+ function parseMdStateBullet(entry, text) {
147
+ if (text.startsWith('on_entry:')) {
148
+ let val = text.slice(9).trim();
149
+ if (val.startsWith('->'))
150
+ val = val.slice(2).trim();
151
+ entry.onEntry = val;
152
+ }
153
+ else if (text.startsWith('on_exit:')) {
154
+ let val = text.slice(8).trim();
155
+ if (val.startsWith('->'))
156
+ val = val.slice(2).trim();
157
+ entry.onExit = val;
158
+ }
159
+ else if (text.startsWith('timeout:')) {
160
+ const rest = text.slice(8).trim();
161
+ const arrowIdx = rest.indexOf('->');
162
+ if (arrowIdx !== -1) {
163
+ entry.timeout = { duration: rest.slice(0, arrowIdx).trim(), target: rest.slice(arrowIdx + 2).trim() };
164
+ }
165
+ }
166
+ else if (text.startsWith('ignore:')) {
167
+ const names = text.slice(7).trim().split(',').map(e => e.trim()).filter(Boolean);
168
+ if (!entry.ignoredEvents)
169
+ entry.ignoredEvents = [];
170
+ entry.ignoredEvents.push(...names);
171
+ }
172
+ else if (text.startsWith('on_done:')) {
173
+ let val = text.slice(8).trim();
174
+ if (val.startsWith('->'))
175
+ val = val.slice(2).trim();
176
+ if (entry.invoke) {
177
+ entry.invoke.onDone = val;
178
+ }
179
+ entry.onDone = val;
180
+ }
181
+ else if (text.startsWith('on_error:')) {
182
+ let val = text.slice(9).trim();
183
+ if (val.startsWith('->'))
184
+ val = val.slice(2).trim();
185
+ if (entry.invoke) {
186
+ entry.invoke.onError = val;
187
+ }
188
+ else {
189
+ entry._pendingOnError = val;
190
+ }
191
+ }
192
+ else if (text.startsWith('invoke:')) {
193
+ const rest = text.slice(7).trim();
194
+ // Format: "MachineName" or "MachineName input: { field: ctx.field }"
195
+ const inputMatch = rest.match(/^(\w+)\s+input:\s*\{(.+)\}$/);
196
+ const pendingOnError = entry._pendingOnError;
197
+ delete entry._pendingOnError;
198
+ if (inputMatch) {
199
+ const machineName = inputMatch[1];
200
+ const inputStr = inputMatch[2];
201
+ const input = {};
202
+ const pairs = inputStr.split(',').map(p => p.trim());
203
+ for (const pair of pairs) {
204
+ const colonIdx = pair.indexOf(':');
205
+ if (colonIdx !== -1) {
206
+ const key = pair.slice(0, colonIdx).trim();
207
+ const val = pair.slice(colonIdx + 1).trim();
208
+ input[key] = val;
209
+ }
210
+ }
211
+ entry.invoke = { machine: machineName, input, onError: pendingOnError };
212
+ }
213
+ else {
214
+ entry.invoke = { machine: rest, onError: pendingOnError };
215
+ }
216
+ }
217
+ }
218
+ function buildMdStatesAtLevel(entries, startIdx, level, parentName) {
219
+ const states = [];
220
+ let i = startIdx;
221
+ while (i < entries.length) {
222
+ const entry = entries[i];
223
+ if (entry.level < level)
224
+ break;
225
+ if (entry.type === 'region')
226
+ break;
227
+ if (entry.level > level) {
228
+ i++;
229
+ continue;
230
+ }
231
+ const state = {
232
+ name: entry.name,
233
+ isInitial: entry.isInitial,
234
+ isFinal: entry.isFinal,
235
+ contains: [],
236
+ ignoredEvents: entry.ignoredEvents || [],
237
+ };
238
+ if (parentName)
239
+ state.parent = parentName;
240
+ if (entry.description)
241
+ state.description = entry.description;
242
+ if (entry.onEntry)
243
+ state.onEntry = entry.onEntry;
244
+ if (entry.onExit)
245
+ state.onExit = entry.onExit;
246
+ if (entry.onDone)
247
+ state.onDone = entry.onDone;
248
+ if (entry.timeout)
249
+ state.timeout = entry.timeout;
250
+ if (entry.invoke)
251
+ state.invoke = entry.invoke;
252
+ i++;
253
+ if (entry.isParallel) {
254
+ const result = buildMdParallelRegions(entries, i, level + 1, entry.name, entry.syncStrategy);
255
+ state.parallel = result.parallelDef;
256
+ i = result.nextIdx;
257
+ }
258
+ else if (i < entries.length && entries[i].level === level + 1 && entries[i].type === 'state') {
259
+ const result = buildMdStatesAtLevel(entries, i, level + 1, entry.name);
260
+ state.contains = result.states;
261
+ i = result.nextIdx;
262
+ }
263
+ states.push(state);
264
+ }
265
+ return { states, nextIdx: i };
266
+ }
267
+ function buildMdParallelRegions(entries, startIdx, regionLevel, parentName, syncStrategy) {
268
+ const regions = [];
269
+ let i = startIdx;
270
+ while (i < entries.length && entries[i].level >= regionLevel) {
271
+ if (entries[i].type !== 'region' || entries[i].level !== regionLevel)
272
+ break;
273
+ const regionName = entries[i].name;
274
+ i++;
275
+ const regionStates = [];
276
+ while (i < entries.length && entries[i].level > regionLevel) {
277
+ if (entries[i].type === 'state' && entries[i].level === regionLevel + 1) {
278
+ const e = entries[i];
279
+ const s = {
280
+ name: e.name,
281
+ isInitial: e.isInitial,
282
+ isFinal: e.isFinal,
283
+ parent: `${parentName}.${regionName}`,
284
+ contains: [],
285
+ ignoredEvents: e.ignoredEvents || [],
286
+ };
287
+ if (e.description)
288
+ s.description = e.description;
289
+ if (e.onEntry)
290
+ s.onEntry = e.onEntry;
291
+ if (e.onExit)
292
+ s.onExit = e.onExit;
293
+ if (e.timeout)
294
+ s.timeout = e.timeout;
295
+ regionStates.push(s);
296
+ i++;
297
+ }
298
+ else {
299
+ break;
300
+ }
301
+ }
302
+ regions.push({ name: regionName, states: regionStates });
303
+ }
304
+ return { parallelDef: { regions, sync: syncStrategy }, nextIdx: i };
305
+ }
306
+ function tokenizeGuardExpr(input) {
307
+ const tokens = [];
308
+ let i = 0;
309
+ while (i < input.length) {
310
+ if (/\s/.test(input[i])) {
311
+ i++;
312
+ continue;
313
+ }
314
+ if (input[i] === '"' || input[i] === "'") {
315
+ const quote = input[i];
316
+ let str = "";
317
+ i++;
318
+ while (i < input.length && input[i] !== quote) {
319
+ str += input[i];
320
+ i++;
321
+ }
322
+ i++;
323
+ tokens.push({ type: "string", value: str });
324
+ continue;
325
+ }
326
+ if (i + 1 < input.length) {
327
+ const two = input[i] + input[i + 1];
328
+ if (two === "==" || two === "!=" || two === "<=" || two === ">=") {
329
+ tokens.push({ type: "op", value: two });
330
+ i += 2;
331
+ continue;
332
+ }
333
+ }
334
+ if (input[i] === "<" || input[i] === ">") {
335
+ tokens.push({ type: "op", value: input[i] });
336
+ i++;
337
+ continue;
338
+ }
339
+ if (input[i] === "(") {
340
+ tokens.push({ type: "lparen", value: "(" });
341
+ i++;
342
+ continue;
343
+ }
344
+ if (input[i] === ")") {
345
+ tokens.push({ type: "rparen", value: ")" });
346
+ i++;
347
+ continue;
348
+ }
349
+ if (input[i] === ".") {
350
+ tokens.push({ type: "dot", value: "." });
351
+ i++;
352
+ continue;
353
+ }
354
+ if (/\d/.test(input[i]) || (input[i] === "-" && i + 1 < input.length && /\d/.test(input[i + 1]))) {
355
+ let num = input[i];
356
+ i++;
357
+ while (i < input.length && (/\d/.test(input[i]) || input[i] === ".")) {
358
+ num += input[i];
359
+ i++;
360
+ }
361
+ tokens.push({ type: "number", value: num });
362
+ continue;
363
+ }
364
+ if (/[a-zA-Z_]/.test(input[i])) {
365
+ let ident = "";
366
+ while (i < input.length && /[a-zA-Z0-9_]/.test(input[i])) {
367
+ ident += input[i];
368
+ i++;
369
+ }
370
+ tokens.push({ type: "ident", value: ident });
371
+ continue;
372
+ }
373
+ i++;
374
+ }
375
+ tokens.push({ type: "eof", value: "" });
376
+ return tokens;
377
+ }
378
+ function parseGuardExpression(input) {
379
+ const tokens = tokenizeGuardExpr(input);
380
+ let pos = 0;
381
+ function peek() { return tokens[pos]; }
382
+ function advance() { return tokens[pos++]; }
383
+ function parseOr() {
384
+ let left = parseAnd();
385
+ while (peek().type === "ident" && peek().value === "or") {
386
+ advance();
387
+ left = { kind: "or", left, right: parseAnd() };
388
+ }
389
+ return left;
390
+ }
391
+ function parseAnd() {
392
+ let left = parseNot();
393
+ while (peek().type === "ident" && peek().value === "and") {
394
+ advance();
395
+ left = { kind: "and", left, right: parseNot() };
396
+ }
397
+ return left;
398
+ }
399
+ function parseNot() {
400
+ if (peek().type === "ident" && peek().value === "not") {
401
+ advance();
402
+ return { kind: "not", expr: parsePrimary() };
403
+ }
404
+ return parsePrimary();
405
+ }
406
+ function parsePrimary() {
407
+ const tok = peek();
408
+ if (tok.type === "lparen") {
409
+ advance();
410
+ const expr = parseOr();
411
+ if (peek().type === "rparen")
412
+ advance();
413
+ return expr;
414
+ }
415
+ if (tok.type === "ident" && tok.value === "true") {
416
+ advance();
417
+ return { kind: "true" };
418
+ }
419
+ if (tok.type === "ident" && tok.value === "false") {
420
+ advance();
421
+ return { kind: "false" };
422
+ }
423
+ const varPath = parseVarPath();
424
+ if (peek().type === "ident" && peek().value === "is") {
425
+ advance();
426
+ if (peek().type === "ident" && peek().value === "not") {
427
+ advance();
428
+ if (peek().type === "ident" && peek().value === "null")
429
+ advance();
430
+ return { kind: "nullcheck", expr: varPath, isNull: false };
431
+ }
432
+ if (peek().type === "ident" && peek().value === "null") {
433
+ advance();
434
+ return { kind: "nullcheck", expr: varPath, isNull: true };
435
+ }
436
+ }
437
+ if (peek().type === "op") {
438
+ const op = advance().value;
439
+ const right = parseValue();
440
+ if (right.type === "null") {
441
+ return { kind: "nullcheck", expr: varPath, isNull: op === "==" };
442
+ }
443
+ return { kind: "compare", op: mapOp(op), left: varPath, right };
444
+ }
445
+ return { kind: "nullcheck", expr: varPath, isNull: false };
446
+ }
447
+ function parseVarPath() {
448
+ const parts = [];
449
+ if (peek().type === "ident") {
450
+ parts.push(advance().value);
451
+ while (peek().type === "dot") {
452
+ advance();
453
+ if (peek().type === "ident") {
454
+ parts.push(advance().value);
455
+ }
456
+ }
457
+ }
458
+ return { kind: "variable", path: parts };
459
+ }
460
+ function parseValue() {
461
+ const tok = peek();
462
+ if (tok.type === "number") {
463
+ advance();
464
+ const num = parseFloat(tok.value);
465
+ return { kind: "value", type: "number", value: num };
466
+ }
467
+ if (tok.type === "string") {
468
+ advance();
469
+ return { kind: "value", type: "string", value: tok.value };
470
+ }
471
+ if (tok.type === "ident") {
472
+ advance();
473
+ if (tok.value === "null")
474
+ return { kind: "value", type: "null", value: null };
475
+ if (tok.value === "true")
476
+ return { kind: "value", type: "boolean", value: true };
477
+ if (tok.value === "false")
478
+ return { kind: "value", type: "boolean", value: false };
479
+ return { kind: "value", type: "string", value: tok.value };
480
+ }
481
+ advance();
482
+ return { kind: "value", type: "null", value: null };
483
+ }
484
+ function mapOp(op) {
485
+ switch (op) {
486
+ case "==": return "eq";
487
+ case "!=": return "ne";
488
+ case "<": return "lt";
489
+ case ">": return "gt";
490
+ case "<=": return "le";
491
+ case ">=": return "ge";
492
+ default: return "eq";
493
+ }
494
+ }
495
+ return parseOr();
496
+ }
497
+ function parseMdActionSignature(name, text) {
498
+ text = text.trim();
499
+ const parenStart = text.indexOf('(');
500
+ const parenEnd = text.indexOf(')');
501
+ const paramsStr = text.slice(parenStart + 1, parenEnd).trim();
502
+ const parameters = [];
503
+ if (paramsStr) {
504
+ for (const param of paramsStr.split(',')) {
505
+ const paramName = param.trim().split(':')[0].trim();
506
+ if (paramName)
507
+ parameters.push(paramName);
508
+ }
509
+ }
510
+ const afterParen = text.slice(parenEnd + 1).trim();
511
+ const arrowIdx = afterParen.indexOf('->');
512
+ const returnPart = afterParen.slice(arrowIdx + 2).trim();
513
+ let returnType = 'Context';
514
+ let hasEffect = false;
515
+ let effectType;
516
+ const plusIdx = returnPart.indexOf('+');
517
+ if (plusIdx !== -1) {
518
+ returnType = returnPart.slice(0, plusIdx).trim();
519
+ const effectMatch = returnPart.slice(plusIdx + 1).trim().match(/Effect<(\w+)>/);
520
+ if (effectMatch) {
521
+ hasEffect = true;
522
+ effectType = effectMatch[1];
523
+ }
524
+ }
525
+ else {
526
+ returnType = returnPart;
527
+ }
528
+ return { name, parameters, returnType, hasEffect, effectType };
529
+ }
530
+ function parseMachineFromElements(elements) {
531
+ let machineName = 'unknown';
532
+ const context = {};
533
+ const events = [];
534
+ const transitions = [];
535
+ const guards = {};
536
+ const actions = [];
537
+ const effects = [];
538
+ const stateEntries = [];
539
+ let currentStateEntry = null;
540
+ for (let i = 0; i < elements.length; i++) {
541
+ const el = elements[i];
542
+ if (el.kind === 'heading') {
543
+ // Machine heading
544
+ if (el.level === 1 && el.text.startsWith('machine ')) {
545
+ machineName = el.text.slice(8).trim();
546
+ currentStateEntry = null;
547
+ continue;
548
+ }
549
+ // Section headings
550
+ const sectionName = el.text.toLowerCase();
551
+ if (['context', 'events', 'transitions', 'guards', 'actions', 'effects'].includes(sectionName)) {
552
+ currentStateEntry = null;
553
+ const nextEl = elements[i + 1];
554
+ if (sectionName === 'context' && nextEl?.kind === 'table') {
555
+ const fi = findColumnIndex(nextEl.headers, 'field');
556
+ const ti = findColumnIndex(nextEl.headers, 'type');
557
+ const di = findColumnIndex(nextEl.headers, 'default');
558
+ for (const row of nextEl.rows) {
559
+ const name = row[fi]?.trim() || '';
560
+ const defaultStr = di >= 0 ? row[di]?.trim() : '';
561
+ let defaultValue = null;
562
+ if (defaultStr) {
563
+ if (/^\d+$/.test(defaultStr))
564
+ defaultValue = parseInt(defaultStr, 10);
565
+ else if (/^\d+\.\d+$/.test(defaultStr))
566
+ defaultValue = parseFloat(defaultStr);
567
+ else if (defaultStr === 'true' || defaultStr === 'false')
568
+ defaultValue = defaultStr === 'true';
569
+ else if (defaultStr.startsWith('"') || defaultStr.startsWith("'"))
570
+ defaultValue = defaultStr.slice(1, -1);
571
+ else
572
+ defaultValue = defaultStr;
573
+ }
574
+ context[name] = defaultValue;
575
+ }
576
+ i++;
577
+ }
578
+ else if (sectionName === 'events' && nextEl?.kind === 'bullets') {
579
+ for (const item of nextEl.items) {
580
+ for (const name of item.split(',').map(n => n.trim()).filter(Boolean)) {
581
+ events.push(name);
582
+ }
583
+ }
584
+ i++;
585
+ }
586
+ else if (sectionName === 'transitions' && nextEl?.kind === 'table') {
587
+ const si = findColumnIndex(nextEl.headers, 'source');
588
+ const ei = findColumnIndex(nextEl.headers, 'event');
589
+ const gi = findColumnIndex(nextEl.headers, 'guard');
590
+ const ti = findColumnIndex(nextEl.headers, 'target');
591
+ const ai = findColumnIndex(nextEl.headers, 'action');
592
+ for (const row of nextEl.rows) {
593
+ const t = {
594
+ source: row[si]?.trim() || '',
595
+ event: row[ei]?.trim() || '',
596
+ guard: row[gi]?.trim() || undefined,
597
+ target: row[ti]?.trim() || '',
598
+ action: row[ai]?.trim() || undefined,
599
+ };
600
+ if (!t.guard)
601
+ delete t.guard;
602
+ if (!t.action || t.action === '_')
603
+ delete t.action;
604
+ transitions.push(t);
605
+ }
606
+ i++;
607
+ }
608
+ else if (sectionName === 'guards' && nextEl?.kind === 'table') {
609
+ const ni = findColumnIndex(nextEl.headers, 'name');
610
+ const ei = findColumnIndex(nextEl.headers, 'expression');
611
+ for (const row of nextEl.rows) {
612
+ const name = row[ni]?.trim() || '';
613
+ const exprStr = stripBackticks(row[ei]?.trim() || '');
614
+ guards[name] = parseGuardExpression(exprStr);
615
+ }
616
+ i++;
617
+ }
618
+ else if (sectionName === 'actions' && nextEl?.kind === 'table') {
619
+ const ni = findColumnIndex(nextEl.headers, 'name');
620
+ const si = findColumnIndex(nextEl.headers, 'signature');
621
+ for (const row of nextEl.rows) {
622
+ actions.push(parseMdActionSignature(row[ni]?.trim() || '', stripBackticks(row[si]?.trim() || '')));
623
+ }
624
+ i++;
625
+ }
626
+ else if (sectionName === 'effects' && nextEl?.kind === 'table') {
627
+ const ni = findColumnIndex(nextEl.headers, 'name');
628
+ const ii = findColumnIndex(nextEl.headers, 'input');
629
+ const oi = findColumnIndex(nextEl.headers, 'output');
630
+ for (const row of nextEl.rows) {
631
+ effects.push({
632
+ name: stripBackticks(row[ni]?.trim() || ''),
633
+ input: stripBackticks(row[ii]?.trim() || ''),
634
+ output: stripBackticks(row[oi]?.trim() || ''),
635
+ });
636
+ }
637
+ i++;
638
+ }
639
+ continue;
640
+ }
641
+ // State heading
642
+ const stateMatch = el.text.match(/^state\s+(\w+)(.*)$/);
643
+ if (stateMatch) {
644
+ currentStateEntry = {
645
+ type: 'state', level: el.level, name: stateMatch[1],
646
+ ...parseMdAnnotations(stateMatch[2]?.trim() || ''),
647
+ };
648
+ stateEntries.push(currentStateEntry);
649
+ continue;
650
+ }
651
+ // Region heading
652
+ const regionMatch = el.text.match(/^region\s+(\w+)$/);
653
+ if (regionMatch) {
654
+ currentStateEntry = null;
655
+ stateEntries.push({
656
+ type: 'region', level: el.level, name: regionMatch[1],
657
+ isInitial: false, isFinal: false, isParallel: false,
658
+ });
659
+ continue;
660
+ }
661
+ currentStateEntry = null;
662
+ continue;
663
+ }
664
+ // Content belonging to current state
665
+ if (currentStateEntry) {
666
+ if (el.kind === 'blockquote') {
667
+ currentStateEntry.description = el.text;
668
+ }
669
+ else if (el.kind === 'bullets') {
670
+ for (const item of el.items)
671
+ parseMdStateBullet(currentStateEntry, item);
672
+ }
673
+ }
674
+ }
675
+ // Build state hierarchy
676
+ const baseLevel = stateEntries.length > 0 ? stateEntries[0].level : 2;
677
+ const states = buildMdStatesAtLevel(stateEntries, 0, baseLevel).states;
678
+ return { name: machineName, context, events, states, transitions, guards, actions, effects };
679
+ }
680
+ /**
681
+ * Parse Orca markdown (.orca.md) format into a MachineDef.
682
+ * For multi-machine files, returns the first machine.
683
+ */
684
+ export function parseOrcaMd(source) {
685
+ const elements = parseMarkdownStructure(source);
686
+ // Split by separators for multi-machine files
687
+ const chunks = [];
688
+ let currentChunk = [];
689
+ for (const el of elements) {
690
+ if (el.kind === 'separator') {
691
+ if (currentChunk.length > 0) {
692
+ chunks.push(currentChunk);
693
+ currentChunk = [];
694
+ }
695
+ }
696
+ else {
697
+ currentChunk.push(el);
698
+ }
699
+ }
700
+ if (currentChunk.length > 0) {
701
+ chunks.push(currentChunk);
702
+ }
703
+ // Parse first chunk as machine
704
+ return parseMachineFromElements(chunks[0] || []);
705
+ }
706
+ /**
707
+ * Parse Orca markdown (.orca.md) format into multiple MachineDefs.
708
+ * For single-machine files, returns an array with one machine.
709
+ */
710
+ export function parseOrcaMdMulti(source) {
711
+ const elements = parseMarkdownStructure(source);
712
+ // Split by separators for multi-machine files
713
+ const chunks = [];
714
+ let currentChunk = [];
715
+ for (const el of elements) {
716
+ if (el.kind === 'separator') {
717
+ if (currentChunk.length > 0) {
718
+ chunks.push(currentChunk);
719
+ currentChunk = [];
720
+ }
721
+ }
722
+ else {
723
+ currentChunk.push(el);
724
+ }
725
+ }
726
+ if (currentChunk.length > 0) {
727
+ chunks.push(currentChunk);
728
+ }
729
+ return chunks.map(chunk => parseMachineFromElements(chunk));
730
+ }
731
+ /**
732
+ * Parse Orca machine definition (markdown format).
733
+ */
734
+ export function parseOrcaAuto(source) {
735
+ return parseOrcaMd(source);
736
+ }
737
+ //# sourceMappingURL=parser.js.map