@orcalang/orca-lang 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/LICENSE +176 -0
- package/README.md +128 -0
- package/dist/auth/index.d.ts +6 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +6 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/lock.d.ts +2 -0
- package/dist/auth/lock.d.ts.map +1 -0
- package/dist/auth/lock.js +59 -0
- package/dist/auth/lock.js.map +1 -0
- package/dist/auth/providers/anthropic.d.ts +14 -0
- package/dist/auth/providers/anthropic.d.ts.map +1 -0
- package/dist/auth/providers/anthropic.js +145 -0
- package/dist/auth/providers/anthropic.js.map +1 -0
- package/dist/auth/providers/index.d.ts +3 -0
- package/dist/auth/providers/index.d.ts.map +1 -0
- package/dist/auth/providers/index.js +3 -0
- package/dist/auth/providers/index.js.map +1 -0
- package/dist/auth/providers/minimax.d.ts +6 -0
- package/dist/auth/providers/minimax.d.ts.map +1 -0
- package/dist/auth/providers/minimax.js +65 -0
- package/dist/auth/providers/minimax.js.map +1 -0
- package/dist/auth/refresh.d.ts +8 -0
- package/dist/auth/refresh.d.ts.map +1 -0
- package/dist/auth/refresh.js +104 -0
- package/dist/auth/refresh.js.map +1 -0
- package/dist/auth/store.d.ts +11 -0
- package/dist/auth/store.d.ts.map +1 -0
- package/dist/auth/store.js +63 -0
- package/dist/auth/store.js.map +1 -0
- package/dist/auth/types.d.ts +51 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +2 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/compiler/mermaid.d.ts +3 -0
- package/dist/compiler/mermaid.d.ts.map +1 -0
- package/dist/compiler/mermaid.js +86 -0
- package/dist/compiler/mermaid.js.map +1 -0
- package/dist/compiler/xstate.d.ts +15 -0
- package/dist/compiler/xstate.d.ts.map +1 -0
- package/dist/compiler/xstate.js +542 -0
- package/dist/compiler/xstate.js.map +1 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +3 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +4 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +109 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/types.d.ts +13 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +8 -0
- package/dist/config/types.js.map +1 -0
- package/dist/generators/index.d.ts +5 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +5 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/generators/registry.d.ts +12 -0
- package/dist/generators/registry.d.ts.map +1 -0
- package/dist/generators/registry.js +15 -0
- package/dist/generators/registry.js.map +1 -0
- package/dist/generators/typescript.d.ts +9 -0
- package/dist/generators/typescript.d.ts.map +1 -0
- package/dist/generators/typescript.js +55 -0
- package/dist/generators/typescript.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +630 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/anthropic.d.ts +14 -0
- package/dist/llm/anthropic.d.ts.map +1 -0
- package/dist/llm/anthropic.js +87 -0
- package/dist/llm/anthropic.js.map +1 -0
- package/dist/llm/grok.d.ts +13 -0
- package/dist/llm/grok.d.ts.map +1 -0
- package/dist/llm/grok.js +60 -0
- package/dist/llm/grok.js.map +1 -0
- package/dist/llm/index.d.ts +11 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +23 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/ollama.d.ts +11 -0
- package/dist/llm/ollama.d.ts.map +1 -0
- package/dist/llm/ollama.js +51 -0
- package/dist/llm/ollama.js.map +1 -0
- package/dist/llm/openai.d.ts +13 -0
- package/dist/llm/openai.d.ts.map +1 -0
- package/dist/llm/openai.js +61 -0
- package/dist/llm/openai.js.map +1 -0
- package/dist/llm/provider.d.ts +32 -0
- package/dist/llm/provider.d.ts.map +1 -0
- package/dist/llm/provider.js +2 -0
- package/dist/llm/provider.js.map +1 -0
- package/dist/parser/ast-to-markdown.d.ts +3 -0
- package/dist/parser/ast-to-markdown.d.ts.map +1 -0
- package/dist/parser/ast-to-markdown.js +209 -0
- package/dist/parser/ast-to-markdown.js.map +1 -0
- package/dist/parser/ast.d.ts +183 -0
- package/dist/parser/ast.d.ts.map +1 -0
- package/dist/parser/ast.js +3 -0
- package/dist/parser/ast.js.map +1 -0
- package/dist/parser/markdown-parser.d.ts +8 -0
- package/dist/parser/markdown-parser.d.ts.map +1 -0
- package/dist/parser/markdown-parser.js +838 -0
- package/dist/parser/markdown-parser.js.map +1 -0
- package/dist/runtime/effects.d.ts +17 -0
- package/dist/runtime/effects.d.ts.map +1 -0
- package/dist/runtime/effects.js +28 -0
- package/dist/runtime/effects.js.map +1 -0
- package/dist/runtime/machine.d.ts +8 -0
- package/dist/runtime/machine.d.ts.map +1 -0
- package/dist/runtime/machine.js +158 -0
- package/dist/runtime/machine.js.map +1 -0
- package/dist/runtime/types.d.ts +37 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +3 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/skills.d.ts +114 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +1103 -0
- package/dist/skills.js.map +1 -0
- package/dist/tools.d.ts +18 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +124 -0
- package/dist/tools.js.map +1 -0
- package/dist/verifier/completeness.d.ts +4 -0
- package/dist/verifier/completeness.d.ts.map +1 -0
- package/dist/verifier/completeness.js +82 -0
- package/dist/verifier/completeness.js.map +1 -0
- package/dist/verifier/determinism.d.ts +17 -0
- package/dist/verifier/determinism.d.ts.map +1 -0
- package/dist/verifier/determinism.js +301 -0
- package/dist/verifier/determinism.js.map +1 -0
- package/dist/verifier/properties.d.ts +6 -0
- package/dist/verifier/properties.d.ts.map +1 -0
- package/dist/verifier/properties.js +404 -0
- package/dist/verifier/properties.js.map +1 -0
- package/dist/verifier/structural.d.ts +50 -0
- package/dist/verifier/structural.d.ts.map +1 -0
- package/dist/verifier/structural.js +692 -0
- package/dist/verifier/structural.js.map +1 -0
- package/dist/verifier/types.d.ts +40 -0
- package/dist/verifier/types.d.ts.map +1 -0
- package/dist/verifier/types.js +2 -0
- package/dist/verifier/types.js.map +1 -0
- package/package.json +49 -0
- package/src/auth/index.ts +5 -0
- package/src/auth/lock.ts +71 -0
- package/src/auth/providers/anthropic.ts +192 -0
- package/src/auth/providers/index.ts +17 -0
- package/src/auth/providers/minimax.ts +100 -0
- package/src/auth/refresh.ts +138 -0
- package/src/auth/store.ts +75 -0
- package/src/auth/types.ts +62 -0
- package/src/compiler/mermaid.ts +109 -0
- package/src/compiler/xstate.ts +615 -0
- package/src/config/index.ts +2 -0
- package/src/config/loader.ts +122 -0
- package/src/config/types.ts +21 -0
- package/src/generators/index.ts +6 -0
- package/src/generators/registry.ts +27 -0
- package/src/generators/typescript.ts +67 -0
- package/src/index.ts +671 -0
- package/src/llm/anthropic.ts +102 -0
- package/src/llm/grok.ts +73 -0
- package/src/llm/index.ts +29 -0
- package/src/llm/ollama.ts +62 -0
- package/src/llm/openai.ts +74 -0
- package/src/llm/provider.ts +35 -0
- package/src/parser/ast-to-markdown.ts +220 -0
- package/src/parser/ast.ts +236 -0
- package/src/parser/markdown-parser.ts +844 -0
- package/src/runtime/effects.ts +48 -0
- package/src/runtime/machine.ts +201 -0
- package/src/runtime/types.ts +44 -0
- package/src/skills.ts +1339 -0
- package/src/tools.ts +144 -0
- package/src/verifier/completeness.ts +89 -0
- package/src/verifier/determinism.ts +328 -0
- package/src/verifier/properties.ts +507 -0
- package/src/verifier/structural.ts +803 -0
- package/src/verifier/types.ts +45 -0
|
@@ -0,0 +1,838 @@
|
|
|
1
|
+
// Markdown Parser for Orca (.orca.md files)
|
|
2
|
+
// Two-phase parser: structural markdown → semantic AST
|
|
3
|
+
// Produces identical MachineDef AST as the DSL parser
|
|
4
|
+
function parseMarkdownStructure(source) {
|
|
5
|
+
const lines = source.split('\n');
|
|
6
|
+
const elements = [];
|
|
7
|
+
let i = 0;
|
|
8
|
+
while (i < lines.length) {
|
|
9
|
+
const trimmed = lines[i].trim();
|
|
10
|
+
if (trimmed === '') {
|
|
11
|
+
i++;
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
// Skip fenced code blocks
|
|
15
|
+
if (trimmed.startsWith('```')) {
|
|
16
|
+
i++;
|
|
17
|
+
while (i < lines.length && !lines[i].trim().startsWith('```'))
|
|
18
|
+
i++;
|
|
19
|
+
if (i < lines.length)
|
|
20
|
+
i++;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
// Horizontal rule separator (--- between machines)
|
|
24
|
+
if (trimmed === '---') {
|
|
25
|
+
elements.push({ kind: 'separator', line: i + 1 });
|
|
26
|
+
i++;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
// Heading
|
|
30
|
+
const headingMatch = trimmed.match(/^(#{1,6})\s+(.+)$/);
|
|
31
|
+
if (headingMatch) {
|
|
32
|
+
elements.push({ kind: 'heading', level: headingMatch[1].length, text: headingMatch[2].trim(), line: i + 1 });
|
|
33
|
+
i++;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
// Blockquote
|
|
37
|
+
if (trimmed.startsWith('>')) {
|
|
38
|
+
const quoteLines = [];
|
|
39
|
+
const startLine = i + 1;
|
|
40
|
+
while (i < lines.length && lines[i].trim().startsWith('>')) {
|
|
41
|
+
quoteLines.push(lines[i].trim().replace(/^>\s*/, ''));
|
|
42
|
+
i++;
|
|
43
|
+
}
|
|
44
|
+
elements.push({ kind: 'blockquote', text: quoteLines.join('\n'), line: startLine });
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
// Table
|
|
48
|
+
if (trimmed.startsWith('|')) {
|
|
49
|
+
const tableLines = [];
|
|
50
|
+
const startLine = i + 1;
|
|
51
|
+
while (i < lines.length && lines[i].trim().startsWith('|')) {
|
|
52
|
+
tableLines.push(lines[i].trim());
|
|
53
|
+
i++;
|
|
54
|
+
}
|
|
55
|
+
if (tableLines.length >= 2) {
|
|
56
|
+
const headers = parseTableRow(tableLines[0]);
|
|
57
|
+
const isSeparator = /^\|[\s\-:|]+\|$/.test(tableLines[1]);
|
|
58
|
+
const dataStart = isSeparator ? 2 : 1;
|
|
59
|
+
const rows = [];
|
|
60
|
+
for (let j = dataStart; j < tableLines.length; j++) {
|
|
61
|
+
rows.push(parseTableRow(tableLines[j]));
|
|
62
|
+
}
|
|
63
|
+
elements.push({ kind: 'table', headers, rows, line: startLine });
|
|
64
|
+
}
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
// Bullet list
|
|
68
|
+
if (trimmed.startsWith('- ')) {
|
|
69
|
+
const items = [];
|
|
70
|
+
const startLine = i + 1;
|
|
71
|
+
while (i < lines.length && lines[i].trim().startsWith('- ')) {
|
|
72
|
+
items.push(lines[i].trim().substring(2).trim());
|
|
73
|
+
i++;
|
|
74
|
+
}
|
|
75
|
+
elements.push({ kind: 'bullets', items, line: startLine });
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
// Paragraph (any other text)
|
|
79
|
+
const paraLines = [];
|
|
80
|
+
const startLine = i + 1;
|
|
81
|
+
while (i < lines.length && lines[i].trim() !== '' &&
|
|
82
|
+
!lines[i].trim().startsWith('#') &&
|
|
83
|
+
!lines[i].trim().startsWith('|') &&
|
|
84
|
+
!lines[i].trim().startsWith('>') &&
|
|
85
|
+
!lines[i].trim().startsWith('- ') &&
|
|
86
|
+
!lines[i].trim().startsWith('```')) {
|
|
87
|
+
paraLines.push(lines[i].trim());
|
|
88
|
+
i++;
|
|
89
|
+
}
|
|
90
|
+
if (paraLines.length > 0) {
|
|
91
|
+
elements.push({ kind: 'paragraph', text: paraLines.join('\n'), line: startLine });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return elements;
|
|
95
|
+
}
|
|
96
|
+
function parseTableRow(line) {
|
|
97
|
+
const cells = line.split('|').map(c => c.trim());
|
|
98
|
+
if (cells.length > 0 && cells[0] === '')
|
|
99
|
+
cells.shift();
|
|
100
|
+
if (cells.length > 0 && cells[cells.length - 1] === '')
|
|
101
|
+
cells.pop();
|
|
102
|
+
return cells;
|
|
103
|
+
}
|
|
104
|
+
// ============================================================
|
|
105
|
+
// Micro-Parsers
|
|
106
|
+
// ============================================================
|
|
107
|
+
function stripBackticks(text) {
|
|
108
|
+
if (text.startsWith('`') && text.endsWith('`'))
|
|
109
|
+
return text.slice(1, -1);
|
|
110
|
+
return text;
|
|
111
|
+
}
|
|
112
|
+
function findColumnIndex(headers, name) {
|
|
113
|
+
return headers.findIndex(h => h.toLowerCase() === name.toLowerCase());
|
|
114
|
+
}
|
|
115
|
+
// --- Type Micro-Parser ---
|
|
116
|
+
function parseTypeString(text) {
|
|
117
|
+
text = text.trim();
|
|
118
|
+
if (text.endsWith('?')) {
|
|
119
|
+
return { kind: 'optional', innerType: text.slice(0, -1) };
|
|
120
|
+
}
|
|
121
|
+
if (text.endsWith('[]')) {
|
|
122
|
+
return { kind: 'array', elementType: text.slice(0, -2) };
|
|
123
|
+
}
|
|
124
|
+
const mapMatch = text.match(/^map<\s*(\w+)\s*,\s*(\w+)\s*>$/);
|
|
125
|
+
if (mapMatch) {
|
|
126
|
+
return { kind: 'map', keyType: mapMatch[1], valueType: mapMatch[2] };
|
|
127
|
+
}
|
|
128
|
+
switch (text) {
|
|
129
|
+
case 'string': return { kind: 'string' };
|
|
130
|
+
case 'int': return { kind: 'int' };
|
|
131
|
+
case 'decimal': return { kind: 'decimal' };
|
|
132
|
+
case 'bool': return { kind: 'bool' };
|
|
133
|
+
default: return { kind: 'custom', name: text };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function tokenizeExpression(text) {
|
|
137
|
+
const tokens = [];
|
|
138
|
+
let i = 0;
|
|
139
|
+
while (i < text.length) {
|
|
140
|
+
if (/\s/.test(text[i])) {
|
|
141
|
+
i++;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
// Two-char operators
|
|
145
|
+
if (i + 1 < text.length) {
|
|
146
|
+
const two = text.slice(i, i + 2);
|
|
147
|
+
if (two === '==') {
|
|
148
|
+
tokens.push({ type: 'EQ', value: '==' });
|
|
149
|
+
i += 2;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (two === '!=') {
|
|
153
|
+
tokens.push({ type: 'NE', value: '!=' });
|
|
154
|
+
i += 2;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (two === '<=') {
|
|
158
|
+
tokens.push({ type: 'LE', value: '<=' });
|
|
159
|
+
i += 2;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (two === '>=') {
|
|
163
|
+
tokens.push({ type: 'GE', value: '>=' });
|
|
164
|
+
i += 2;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (two === '&&') {
|
|
168
|
+
tokens.push({ type: 'AND', value: '&&' });
|
|
169
|
+
i += 2;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (two === '||') {
|
|
173
|
+
tokens.push({ type: 'OR', value: '||' });
|
|
174
|
+
i += 2;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const ch = text[i];
|
|
179
|
+
if (ch === '<') {
|
|
180
|
+
tokens.push({ type: 'LT', value: '<' });
|
|
181
|
+
i++;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (ch === '>') {
|
|
185
|
+
tokens.push({ type: 'GT', value: '>' });
|
|
186
|
+
i++;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (ch === '!') {
|
|
190
|
+
tokens.push({ type: 'NOT', value: '!' });
|
|
191
|
+
i++;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (ch === '=') {
|
|
195
|
+
tokens.push({ type: 'EQ', value: '=' });
|
|
196
|
+
i++;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (ch === '.') {
|
|
200
|
+
tokens.push({ type: 'DOT', value: '.' });
|
|
201
|
+
i++;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (ch === '(') {
|
|
205
|
+
tokens.push({ type: 'LPAREN', value: '(' });
|
|
206
|
+
i++;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (ch === ')') {
|
|
210
|
+
tokens.push({ type: 'RPAREN', value: ')' });
|
|
211
|
+
i++;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (/[0-9]/.test(ch)) {
|
|
215
|
+
let num = '';
|
|
216
|
+
while (i < text.length && /[0-9.]/.test(text[i]))
|
|
217
|
+
num += text[i++];
|
|
218
|
+
tokens.push({ type: 'NUMBER', value: num });
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (ch === '"' || ch === "'") {
|
|
222
|
+
const quote = ch;
|
|
223
|
+
i++;
|
|
224
|
+
let str = '';
|
|
225
|
+
while (i < text.length && text[i] !== quote) {
|
|
226
|
+
if (text[i] === '\\')
|
|
227
|
+
i++;
|
|
228
|
+
str += text[i++];
|
|
229
|
+
}
|
|
230
|
+
if (i < text.length)
|
|
231
|
+
i++;
|
|
232
|
+
tokens.push({ type: 'STRING', value: str });
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (/[a-zA-Z_]/.test(ch)) {
|
|
236
|
+
let ident = '';
|
|
237
|
+
while (i < text.length && /[a-zA-Z0-9_]/.test(text[i]))
|
|
238
|
+
ident += text[i++];
|
|
239
|
+
tokens.push({ type: 'IDENT', value: ident });
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
i++; // skip unknown
|
|
243
|
+
}
|
|
244
|
+
tokens.push({ type: 'EOF', value: '' });
|
|
245
|
+
return tokens;
|
|
246
|
+
}
|
|
247
|
+
class ExpressionParser {
|
|
248
|
+
tokens;
|
|
249
|
+
pos = 0;
|
|
250
|
+
constructor(tokens) { this.tokens = tokens; }
|
|
251
|
+
peek() { return this.tokens[this.pos] || { type: 'EOF', value: '' }; }
|
|
252
|
+
advance() { return this.tokens[this.pos++] || { type: 'EOF', value: '' }; }
|
|
253
|
+
match(type) {
|
|
254
|
+
if (this.peek().type === type) {
|
|
255
|
+
this.advance();
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
expect(type) {
|
|
261
|
+
const tok = this.advance();
|
|
262
|
+
if (tok.type !== type)
|
|
263
|
+
throw new Error(`Expected ${type}, got ${tok.type} "${tok.value}"`);
|
|
264
|
+
return tok;
|
|
265
|
+
}
|
|
266
|
+
parse() { return this.parseOr(); }
|
|
267
|
+
parseOr() {
|
|
268
|
+
let left = this.parseAnd();
|
|
269
|
+
while (this.peek().type === 'OR' || (this.peek().type === 'IDENT' && this.peek().value === 'or')) {
|
|
270
|
+
this.advance();
|
|
271
|
+
left = { kind: 'or', left, right: this.parseAnd() };
|
|
272
|
+
}
|
|
273
|
+
return left;
|
|
274
|
+
}
|
|
275
|
+
parseAnd() {
|
|
276
|
+
let left = this.parseNot();
|
|
277
|
+
while (this.peek().type === 'AND' || (this.peek().type === 'IDENT' && this.peek().value === 'and')) {
|
|
278
|
+
this.advance();
|
|
279
|
+
left = { kind: 'and', left, right: this.parseNot() };
|
|
280
|
+
}
|
|
281
|
+
return left;
|
|
282
|
+
}
|
|
283
|
+
parseNot() {
|
|
284
|
+
if (this.peek().type === 'NOT' || (this.peek().type === 'IDENT' && this.peek().value === 'not')) {
|
|
285
|
+
this.advance();
|
|
286
|
+
return { kind: 'not', expr: this.parsePrimary() };
|
|
287
|
+
}
|
|
288
|
+
return this.parsePrimary();
|
|
289
|
+
}
|
|
290
|
+
parsePrimary() {
|
|
291
|
+
if (this.match('LPAREN')) {
|
|
292
|
+
const expr = this.parseOr();
|
|
293
|
+
this.expect('RPAREN');
|
|
294
|
+
return expr;
|
|
295
|
+
}
|
|
296
|
+
if (this.peek().type === 'IDENT' && this.peek().value === 'true') {
|
|
297
|
+
this.advance();
|
|
298
|
+
return { kind: 'true' };
|
|
299
|
+
}
|
|
300
|
+
if (this.peek().type === 'IDENT' && this.peek().value === 'false') {
|
|
301
|
+
this.advance();
|
|
302
|
+
return { kind: 'false' };
|
|
303
|
+
}
|
|
304
|
+
const varPath = this.parseVariablePath();
|
|
305
|
+
const op = this.peek().type;
|
|
306
|
+
if (['EQ', 'NE', 'LT', 'GT', 'LE', 'GE'].includes(op)) {
|
|
307
|
+
const opTok = this.advance();
|
|
308
|
+
const cmpOp = { EQ: 'eq', NE: 'ne', LT: 'lt', GT: 'gt', LE: 'le', GE: 'ge' }[opTok.type];
|
|
309
|
+
return { kind: 'compare', op: cmpOp, left: varPath, right: this.parseValue() };
|
|
310
|
+
}
|
|
311
|
+
return { kind: 'nullcheck', expr: varPath, isNull: false };
|
|
312
|
+
}
|
|
313
|
+
parseVariablePath() {
|
|
314
|
+
const parts = [this.expect('IDENT').value];
|
|
315
|
+
while (this.peek().type === 'DOT') {
|
|
316
|
+
this.advance();
|
|
317
|
+
parts.push(this.expect('IDENT').value);
|
|
318
|
+
}
|
|
319
|
+
return { kind: 'variable', path: parts };
|
|
320
|
+
}
|
|
321
|
+
parseValue() {
|
|
322
|
+
const tok = this.peek();
|
|
323
|
+
if (tok.type === 'NUMBER') {
|
|
324
|
+
this.advance();
|
|
325
|
+
return { kind: 'value', type: 'number', value: parseFloat(tok.value) };
|
|
326
|
+
}
|
|
327
|
+
if (tok.type === 'STRING') {
|
|
328
|
+
this.advance();
|
|
329
|
+
return { kind: 'value', type: 'string', value: tok.value };
|
|
330
|
+
}
|
|
331
|
+
if (tok.type === 'IDENT') {
|
|
332
|
+
this.advance();
|
|
333
|
+
if (tok.value === 'null')
|
|
334
|
+
return { kind: 'value', type: 'null', value: null };
|
|
335
|
+
if (tok.value === 'true')
|
|
336
|
+
return { kind: 'value', type: 'boolean', value: true };
|
|
337
|
+
if (tok.value === 'false')
|
|
338
|
+
return { kind: 'value', type: 'boolean', value: false };
|
|
339
|
+
return { kind: 'value', type: 'null', value: tok.value };
|
|
340
|
+
}
|
|
341
|
+
return { kind: 'value', type: 'null', value: null };
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function parseGuardExpressionFromString(text) {
|
|
345
|
+
return new ExpressionParser(tokenizeExpression(text)).parse();
|
|
346
|
+
}
|
|
347
|
+
// --- Action Signature Micro-Parser ---
|
|
348
|
+
function parseActionSignatureFromString(name, text) {
|
|
349
|
+
text = text.trim();
|
|
350
|
+
const parenStart = text.indexOf('(');
|
|
351
|
+
const parenEnd = text.indexOf(')');
|
|
352
|
+
const paramsStr = text.slice(parenStart + 1, parenEnd).trim();
|
|
353
|
+
const parameters = [];
|
|
354
|
+
if (paramsStr) {
|
|
355
|
+
for (const param of paramsStr.split(',')) {
|
|
356
|
+
const paramName = param.trim().split(':')[0].trim();
|
|
357
|
+
if (paramName)
|
|
358
|
+
parameters.push(paramName);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const afterParen = text.slice(parenEnd + 1).trim();
|
|
362
|
+
const arrowIdx = afterParen.indexOf('->');
|
|
363
|
+
const returnPart = afterParen.slice(arrowIdx + 2).trim();
|
|
364
|
+
let returnType = 'Context';
|
|
365
|
+
let hasEffect = false;
|
|
366
|
+
let effectType;
|
|
367
|
+
const plusIdx = returnPart.indexOf('+');
|
|
368
|
+
if (plusIdx !== -1) {
|
|
369
|
+
returnType = returnPart.slice(0, plusIdx).trim();
|
|
370
|
+
const effectMatch = returnPart.slice(plusIdx + 1).trim().match(/Effect<(\w+)>/);
|
|
371
|
+
if (effectMatch) {
|
|
372
|
+
hasEffect = true;
|
|
373
|
+
effectType = effectMatch[1];
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
returnType = returnPart;
|
|
378
|
+
}
|
|
379
|
+
return { name, parameters, returnType, hasEffect, effectType };
|
|
380
|
+
}
|
|
381
|
+
function parseAnnotations(text) {
|
|
382
|
+
let isInitial = false, isFinal = false, isParallel = false;
|
|
383
|
+
let syncStrategy;
|
|
384
|
+
const bracketMatch = text.match(/\[(.+)\]/);
|
|
385
|
+
if (bracketMatch) {
|
|
386
|
+
for (const part of bracketMatch[1].split(',').map(p => p.trim())) {
|
|
387
|
+
if (part === 'initial')
|
|
388
|
+
isInitial = true;
|
|
389
|
+
else if (part === 'final')
|
|
390
|
+
isFinal = true;
|
|
391
|
+
else if (part === 'parallel')
|
|
392
|
+
isParallel = true;
|
|
393
|
+
else if (part.startsWith('sync:')) {
|
|
394
|
+
const v = part.slice(5).trim();
|
|
395
|
+
if (v === 'all-final' || v === 'all_final')
|
|
396
|
+
syncStrategy = 'all-final';
|
|
397
|
+
else if (v === 'any-final' || v === 'any_final')
|
|
398
|
+
syncStrategy = 'any-final';
|
|
399
|
+
else if (v === 'custom')
|
|
400
|
+
syncStrategy = 'custom';
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return { isInitial, isFinal, isParallel, syncStrategy };
|
|
405
|
+
}
|
|
406
|
+
function parseStateBullet(entry, text) {
|
|
407
|
+
if (text.startsWith('on_entry:')) {
|
|
408
|
+
let val = text.slice(9).trim();
|
|
409
|
+
if (val.startsWith('->'))
|
|
410
|
+
val = val.slice(2).trim();
|
|
411
|
+
entry.onEntry = val;
|
|
412
|
+
}
|
|
413
|
+
else if (text.startsWith('on_exit:')) {
|
|
414
|
+
let val = text.slice(8).trim();
|
|
415
|
+
if (val.startsWith('->'))
|
|
416
|
+
val = val.slice(2).trim();
|
|
417
|
+
entry.onExit = val;
|
|
418
|
+
}
|
|
419
|
+
else if (text.startsWith('timeout:')) {
|
|
420
|
+
const rest = text.slice(8).trim();
|
|
421
|
+
const arrowIdx = rest.indexOf('->');
|
|
422
|
+
if (arrowIdx !== -1) {
|
|
423
|
+
entry.timeout = { duration: rest.slice(0, arrowIdx).trim(), target: rest.slice(arrowIdx + 2).trim() };
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
else if (text.startsWith('ignore:')) {
|
|
427
|
+
const names = text.slice(7).trim().split(',').map(e => e.trim()).filter(Boolean);
|
|
428
|
+
if (!entry.ignoredEvents)
|
|
429
|
+
entry.ignoredEvents = [];
|
|
430
|
+
entry.ignoredEvents.push(...names);
|
|
431
|
+
}
|
|
432
|
+
else if (text.startsWith('on_done:')) {
|
|
433
|
+
let val = text.slice(8).trim();
|
|
434
|
+
if (val.startsWith('->'))
|
|
435
|
+
val = val.slice(2).trim();
|
|
436
|
+
if (entry.invoke) {
|
|
437
|
+
entry.invoke.onDone = val;
|
|
438
|
+
}
|
|
439
|
+
// Also set entry.onDone for non-invoke states (like parallel sync on_done)
|
|
440
|
+
entry.onDone = val;
|
|
441
|
+
}
|
|
442
|
+
else if (text.startsWith('on_error:')) {
|
|
443
|
+
let val = text.slice(9).trim();
|
|
444
|
+
if (val.startsWith('->'))
|
|
445
|
+
val = val.slice(2).trim();
|
|
446
|
+
if (entry.invoke) {
|
|
447
|
+
entry.invoke.onError = val;
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
entry._pendingOnError = val;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
else if (text.startsWith('invoke:')) {
|
|
454
|
+
const rest = text.slice(7).trim();
|
|
455
|
+
// Format: "MachineName" or "MachineName input: { field: ctx.field }"
|
|
456
|
+
const inputMatch = rest.match(/^(\w+)\s+input:\s*\{(.+)\}$/);
|
|
457
|
+
const pendingOnError = entry._pendingOnError;
|
|
458
|
+
delete entry._pendingOnError;
|
|
459
|
+
if (inputMatch) {
|
|
460
|
+
const machineName = inputMatch[1];
|
|
461
|
+
const inputStr = inputMatch[2];
|
|
462
|
+
const input = {};
|
|
463
|
+
// Parse { field: ctx.field } into { field: "ctx.field" }
|
|
464
|
+
const pairs = inputStr.split(',').map(p => p.trim());
|
|
465
|
+
for (const pair of pairs) {
|
|
466
|
+
const colonIdx = pair.indexOf(':');
|
|
467
|
+
if (colonIdx !== -1) {
|
|
468
|
+
const key = pair.slice(0, colonIdx).trim();
|
|
469
|
+
const val = pair.slice(colonIdx + 1).trim();
|
|
470
|
+
input[key] = val;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
entry.invoke = { machine: machineName, input, onError: pendingOnError };
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
entry.invoke = { machine: rest, onError: pendingOnError };
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
function parsePropertyFromBullet(text) {
|
|
481
|
+
text = text.trim();
|
|
482
|
+
if (text === 'live')
|
|
483
|
+
return { kind: 'live' };
|
|
484
|
+
if (text.startsWith('reachable:') || text.startsWith('unreachable:')) {
|
|
485
|
+
const kind = text.startsWith('reachable:') ? 'reachable' : 'unreachable';
|
|
486
|
+
const rest = text.slice(text.indexOf(':') + 1).trim();
|
|
487
|
+
const parts = rest.split(/\s+from\s+/);
|
|
488
|
+
// Match DSL parser property order: kind, from, to
|
|
489
|
+
return { kind, from: parts[1].trim(), to: parts[0].trim() };
|
|
490
|
+
}
|
|
491
|
+
if (text.startsWith('passes_through:')) {
|
|
492
|
+
const rest = text.slice(15).trim();
|
|
493
|
+
const forParts = rest.split(/\s+for\s+/);
|
|
494
|
+
const through = forParts[0].trim();
|
|
495
|
+
const arrowParts = forParts[1].split(/\s*->\s*/);
|
|
496
|
+
// Match DSL parser property order: from, to, through
|
|
497
|
+
return { kind: 'passes_through', from: arrowParts[0].trim(), to: arrowParts[1].trim(), through };
|
|
498
|
+
}
|
|
499
|
+
if (text.startsWith('responds:')) {
|
|
500
|
+
const rest = text.slice(9).trim();
|
|
501
|
+
const parts = rest.split(/\s+from\s+/);
|
|
502
|
+
const to = parts[0].trim();
|
|
503
|
+
const withinParts = parts[1].split(/\s+within\s+/);
|
|
504
|
+
const from = withinParts[0].trim();
|
|
505
|
+
const within = parseInt(withinParts[1].trim(), 10);
|
|
506
|
+
// Match DSL parser property order: kind, from, to, within
|
|
507
|
+
return { kind: 'responds', from, to, within };
|
|
508
|
+
}
|
|
509
|
+
if (text.startsWith('invariant:')) {
|
|
510
|
+
const rest = text.slice(10).trim();
|
|
511
|
+
const backtickMatch = rest.match(/`([^`]+)`(\s+in\s+(\S+))?/);
|
|
512
|
+
if (backtickMatch) {
|
|
513
|
+
const prop = { kind: 'invariant', expression: parseGuardExpressionFromString(backtickMatch[1]) };
|
|
514
|
+
if (backtickMatch[3])
|
|
515
|
+
prop.inState = backtickMatch[3];
|
|
516
|
+
return prop;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
throw new Error(`Unknown property: ${text}`);
|
|
520
|
+
}
|
|
521
|
+
// --- Context / Events / Transitions / Guards / Actions table parsers ---
|
|
522
|
+
function parseContextTable(table) {
|
|
523
|
+
const fi = findColumnIndex(table.headers, 'field');
|
|
524
|
+
const ti = findColumnIndex(table.headers, 'type');
|
|
525
|
+
const di = findColumnIndex(table.headers, 'default');
|
|
526
|
+
return table.rows.map(row => {
|
|
527
|
+
const field = {
|
|
528
|
+
name: row[fi]?.trim() || '',
|
|
529
|
+
type: parseTypeString(row[ti]?.trim() || 'string'),
|
|
530
|
+
};
|
|
531
|
+
const def = di >= 0 ? row[di]?.trim() : '';
|
|
532
|
+
if (def)
|
|
533
|
+
field.defaultValue = def;
|
|
534
|
+
return field;
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
function parseEventsList(list) {
|
|
538
|
+
const events = [];
|
|
539
|
+
for (const item of list.items) {
|
|
540
|
+
for (const name of item.split(',').map(n => n.trim()).filter(Boolean)) {
|
|
541
|
+
events.push({ name });
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return events;
|
|
545
|
+
}
|
|
546
|
+
function parseTransitionsTable(table) {
|
|
547
|
+
const si = findColumnIndex(table.headers, 'source');
|
|
548
|
+
const ei = findColumnIndex(table.headers, 'event');
|
|
549
|
+
const gi = findColumnIndex(table.headers, 'guard');
|
|
550
|
+
const ti = findColumnIndex(table.headers, 'target');
|
|
551
|
+
const ai = findColumnIndex(table.headers, 'action');
|
|
552
|
+
return table.rows.map(row => {
|
|
553
|
+
const source = row[si]?.trim() || '';
|
|
554
|
+
const event = row[ei]?.trim() || '';
|
|
555
|
+
const guardStr = row[gi]?.trim() || '';
|
|
556
|
+
const target = row[ti]?.trim() || '';
|
|
557
|
+
const actionStr = row[ai]?.trim() || '';
|
|
558
|
+
// Build transition with same property order as DSL parser
|
|
559
|
+
const t = { source, event, target };
|
|
560
|
+
if (guardStr) {
|
|
561
|
+
// Insert guard before target to match DSL property order
|
|
562
|
+
const guard = guardStr.startsWith('!')
|
|
563
|
+
? { name: guardStr.slice(1), negated: true }
|
|
564
|
+
: { name: guardStr, negated: false };
|
|
565
|
+
return { source, event, guard, target, ...(actionStr && actionStr !== '_' ? { action: actionStr } : {}) };
|
|
566
|
+
}
|
|
567
|
+
if (actionStr && actionStr !== '_')
|
|
568
|
+
t.action = actionStr;
|
|
569
|
+
return t;
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
function parseGuardsTable(table) {
|
|
573
|
+
const ni = findColumnIndex(table.headers, 'name');
|
|
574
|
+
const ei = findColumnIndex(table.headers, 'expression');
|
|
575
|
+
return table.rows.map(row => ({
|
|
576
|
+
name: row[ni]?.trim() || '',
|
|
577
|
+
expression: parseGuardExpressionFromString(stripBackticks(row[ei]?.trim() || '')),
|
|
578
|
+
}));
|
|
579
|
+
}
|
|
580
|
+
function parseActionsTable(table) {
|
|
581
|
+
const ni = findColumnIndex(table.headers, 'name');
|
|
582
|
+
const si = findColumnIndex(table.headers, 'signature');
|
|
583
|
+
const ei = findColumnIndex(table.headers, 'effect'); // optional separate column
|
|
584
|
+
return table.rows.map(row => {
|
|
585
|
+
const action = parseActionSignatureFromString(row[ni]?.trim() || '', stripBackticks(row[si]?.trim() || ''));
|
|
586
|
+
// If a separate Effect column exists and signature didn't already embed an effect
|
|
587
|
+
if (ei >= 0 && !action.hasEffect) {
|
|
588
|
+
const effectName = row[ei]?.trim() || '';
|
|
589
|
+
if (effectName) {
|
|
590
|
+
action.hasEffect = true;
|
|
591
|
+
action.effectType = effectName;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return action;
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
// --- State Hierarchy Builder ---
|
|
598
|
+
function buildStatesAtLevel(entries, startIdx, level, parentName) {
|
|
599
|
+
const states = [];
|
|
600
|
+
let i = startIdx;
|
|
601
|
+
while (i < entries.length) {
|
|
602
|
+
const entry = entries[i];
|
|
603
|
+
if (entry.level < level)
|
|
604
|
+
break;
|
|
605
|
+
if (entry.type === 'region')
|
|
606
|
+
break;
|
|
607
|
+
if (entry.level > level) {
|
|
608
|
+
i++;
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
// Build state with same property order as DSL parser:
|
|
612
|
+
// { name, isInitial, isFinal, parent?, description?, onEntry?, onExit?, ... }
|
|
613
|
+
const state = { name: entry.name, isInitial: entry.isInitial, isFinal: entry.isFinal };
|
|
614
|
+
if (parentName)
|
|
615
|
+
state.parent = parentName;
|
|
616
|
+
if (entry.description)
|
|
617
|
+
state.description = entry.description;
|
|
618
|
+
if (entry.onEntry)
|
|
619
|
+
state.onEntry = entry.onEntry;
|
|
620
|
+
if (entry.onExit)
|
|
621
|
+
state.onExit = entry.onExit;
|
|
622
|
+
if (entry.onDone)
|
|
623
|
+
state.onDone = entry.onDone;
|
|
624
|
+
if (entry.timeout)
|
|
625
|
+
state.timeout = entry.timeout;
|
|
626
|
+
if (entry.ignoredEvents?.length)
|
|
627
|
+
state.ignoredEvents = entry.ignoredEvents;
|
|
628
|
+
if (entry.invoke)
|
|
629
|
+
state.invoke = entry.invoke;
|
|
630
|
+
i++;
|
|
631
|
+
if (entry.isParallel) {
|
|
632
|
+
const result = buildParallelRegions(entries, i, level + 1, entry.name, entry.syncStrategy);
|
|
633
|
+
state.parallel = result.parallelDef;
|
|
634
|
+
i = result.nextIdx;
|
|
635
|
+
}
|
|
636
|
+
else if (i < entries.length && entries[i].level === level + 1 && entries[i].type === 'state') {
|
|
637
|
+
const result = buildStatesAtLevel(entries, i, level + 1, entry.name);
|
|
638
|
+
state.contains = result.states;
|
|
639
|
+
i = result.nextIdx;
|
|
640
|
+
}
|
|
641
|
+
states.push(state);
|
|
642
|
+
}
|
|
643
|
+
return { states, nextIdx: i };
|
|
644
|
+
}
|
|
645
|
+
function buildParallelRegions(entries, startIdx, regionLevel, parentName, syncStrategy) {
|
|
646
|
+
const regions = [];
|
|
647
|
+
let i = startIdx;
|
|
648
|
+
while (i < entries.length && entries[i].level >= regionLevel) {
|
|
649
|
+
if (entries[i].type !== 'region' || entries[i].level !== regionLevel)
|
|
650
|
+
break;
|
|
651
|
+
const regionName = entries[i].name;
|
|
652
|
+
i++;
|
|
653
|
+
const regionStates = [];
|
|
654
|
+
while (i < entries.length && entries[i].level > regionLevel) {
|
|
655
|
+
if (entries[i].type === 'state' && entries[i].level === regionLevel + 1) {
|
|
656
|
+
const e = entries[i];
|
|
657
|
+
// Match DSL parser property order: name, isInitial, isFinal, parent, then body props
|
|
658
|
+
const s = {
|
|
659
|
+
name: e.name, isInitial: e.isInitial, isFinal: e.isFinal,
|
|
660
|
+
parent: `${parentName}.${regionName}`,
|
|
661
|
+
};
|
|
662
|
+
if (e.description)
|
|
663
|
+
s.description = e.description;
|
|
664
|
+
if (e.onEntry)
|
|
665
|
+
s.onEntry = e.onEntry;
|
|
666
|
+
if (e.onExit)
|
|
667
|
+
s.onExit = e.onExit;
|
|
668
|
+
if (e.onDone)
|
|
669
|
+
s.onDone = e.onDone;
|
|
670
|
+
if (e.timeout)
|
|
671
|
+
s.timeout = e.timeout;
|
|
672
|
+
if (e.ignoredEvents?.length)
|
|
673
|
+
s.ignoredEvents = e.ignoredEvents;
|
|
674
|
+
if (e.invoke)
|
|
675
|
+
s.invoke = e.invoke;
|
|
676
|
+
regionStates.push(s);
|
|
677
|
+
i++;
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
break;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
regions.push({ name: regionName, states: regionStates });
|
|
684
|
+
}
|
|
685
|
+
return { parallelDef: { regions, sync: syncStrategy }, nextIdx: i };
|
|
686
|
+
}
|
|
687
|
+
// --- Main Semantic Parser ---
|
|
688
|
+
function parseMachineFromElements(elements) {
|
|
689
|
+
let machineName = '';
|
|
690
|
+
let context = [];
|
|
691
|
+
let events = [];
|
|
692
|
+
let transitions = [];
|
|
693
|
+
let guards = [];
|
|
694
|
+
let actions = [];
|
|
695
|
+
let effects;
|
|
696
|
+
let properties;
|
|
697
|
+
const stateEntries = [];
|
|
698
|
+
let currentStateEntry = null;
|
|
699
|
+
for (let i = 0; i < elements.length; i++) {
|
|
700
|
+
const el = elements[i];
|
|
701
|
+
if (el.kind === 'heading') {
|
|
702
|
+
// Machine heading
|
|
703
|
+
if (el.level === 1 && el.text.startsWith('machine ')) {
|
|
704
|
+
machineName = el.text.slice(8).trim();
|
|
705
|
+
currentStateEntry = null;
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
// Non-state section headings
|
|
709
|
+
const sectionName = el.text.toLowerCase();
|
|
710
|
+
if (['context', 'events', 'transitions', 'guards', 'actions', 'effects', 'properties'].includes(sectionName)) {
|
|
711
|
+
currentStateEntry = null;
|
|
712
|
+
const nextEl = elements[i + 1];
|
|
713
|
+
if (sectionName === 'context' && nextEl?.kind === 'table') {
|
|
714
|
+
context = parseContextTable(nextEl);
|
|
715
|
+
i++;
|
|
716
|
+
}
|
|
717
|
+
else if (sectionName === 'events' && nextEl?.kind === 'bullets') {
|
|
718
|
+
events = parseEventsList(nextEl);
|
|
719
|
+
i++;
|
|
720
|
+
}
|
|
721
|
+
else if (sectionName === 'transitions' && nextEl?.kind === 'table') {
|
|
722
|
+
transitions = parseTransitionsTable(nextEl);
|
|
723
|
+
i++;
|
|
724
|
+
}
|
|
725
|
+
else if (sectionName === 'guards' && nextEl?.kind === 'table') {
|
|
726
|
+
guards = parseGuardsTable(nextEl);
|
|
727
|
+
i++;
|
|
728
|
+
}
|
|
729
|
+
else if (sectionName === 'actions' && nextEl?.kind === 'table') {
|
|
730
|
+
actions = parseActionsTable(nextEl);
|
|
731
|
+
i++;
|
|
732
|
+
}
|
|
733
|
+
else if (sectionName === 'effects' && nextEl?.kind === 'table') {
|
|
734
|
+
effects = parseEffectsTable(nextEl);
|
|
735
|
+
i++;
|
|
736
|
+
}
|
|
737
|
+
else if (sectionName === 'properties' && nextEl?.kind === 'bullets') {
|
|
738
|
+
properties = parsePropertiesList(nextEl);
|
|
739
|
+
i++;
|
|
740
|
+
}
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
// State heading
|
|
744
|
+
const stateMatch = el.text.match(/^state\s+(\w+)(.*)$/);
|
|
745
|
+
if (stateMatch) {
|
|
746
|
+
currentStateEntry = {
|
|
747
|
+
type: 'state', level: el.level, name: stateMatch[1], line: el.line,
|
|
748
|
+
...parseAnnotations(stateMatch[2]?.trim() || ''),
|
|
749
|
+
};
|
|
750
|
+
stateEntries.push(currentStateEntry);
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
// Region heading
|
|
754
|
+
const regionMatch = el.text.match(/^region\s+(\w+)$/);
|
|
755
|
+
if (regionMatch) {
|
|
756
|
+
currentStateEntry = null;
|
|
757
|
+
stateEntries.push({
|
|
758
|
+
type: 'region', level: el.level, name: regionMatch[1], line: el.line,
|
|
759
|
+
isInitial: false, isFinal: false, isParallel: false,
|
|
760
|
+
});
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
// Unknown heading — close current state
|
|
764
|
+
currentStateEntry = null;
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
// Content belonging to current state
|
|
768
|
+
if (currentStateEntry) {
|
|
769
|
+
if (el.kind === 'blockquote') {
|
|
770
|
+
currentStateEntry.description = el.text;
|
|
771
|
+
}
|
|
772
|
+
else if (el.kind === 'bullets') {
|
|
773
|
+
for (const item of el.items)
|
|
774
|
+
parseStateBullet(currentStateEntry, item);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
// Build state hierarchy from flat entries
|
|
779
|
+
const baseLevel = stateEntries.length > 0 ? stateEntries[0].level : 2;
|
|
780
|
+
const states = buildStatesAtLevel(stateEntries, 0, baseLevel).states;
|
|
781
|
+
const machine = { name: machineName, context, events, states, transitions, guards, actions };
|
|
782
|
+
if (effects !== undefined)
|
|
783
|
+
machine.effects = effects;
|
|
784
|
+
if (properties && properties.length > 0)
|
|
785
|
+
machine.properties = properties;
|
|
786
|
+
return machine;
|
|
787
|
+
}
|
|
788
|
+
function parseMarkdownSemantic(elements) {
|
|
789
|
+
// Split elements by --- separators for multi-machine files
|
|
790
|
+
const chunks = [];
|
|
791
|
+
let currentChunk = [];
|
|
792
|
+
for (const el of elements) {
|
|
793
|
+
if (el.kind === 'separator') {
|
|
794
|
+
if (currentChunk.length > 0) {
|
|
795
|
+
chunks.push(currentChunk);
|
|
796
|
+
currentChunk = [];
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
else {
|
|
800
|
+
currentChunk.push(el);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
if (currentChunk.length > 0) {
|
|
804
|
+
chunks.push(currentChunk);
|
|
805
|
+
}
|
|
806
|
+
// Parse each chunk as a separate machine
|
|
807
|
+
return chunks.map(chunk => parseMachineFromElements(chunk));
|
|
808
|
+
}
|
|
809
|
+
function parseEffectsTable(table) {
|
|
810
|
+
const ni = findColumnIndex(table.headers, 'name');
|
|
811
|
+
const ii = findColumnIndex(table.headers, 'input');
|
|
812
|
+
const oi = findColumnIndex(table.headers, 'output');
|
|
813
|
+
return table.rows.map(row => ({
|
|
814
|
+
name: stripBackticks((ni >= 0 ? row[ni] : '') || '').trim(),
|
|
815
|
+
input: ((ii >= 0 ? row[ii] : '') || '').trim(),
|
|
816
|
+
output: ((oi >= 0 ? row[oi] : '') || '').trim(),
|
|
817
|
+
})).filter(e => e.name !== '');
|
|
818
|
+
}
|
|
819
|
+
function parsePropertiesList(list) {
|
|
820
|
+
return list.items.map(parsePropertyFromBullet);
|
|
821
|
+
}
|
|
822
|
+
// ============================================================
|
|
823
|
+
// Public API
|
|
824
|
+
// ============================================================
|
|
825
|
+
export function parseMarkdown(source) {
|
|
826
|
+
const elements = parseMarkdownStructure(source);
|
|
827
|
+
const machines = parseMarkdownSemantic(elements);
|
|
828
|
+
return { file: { machines }, tokens: [] };
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Parse a single-machine markdown source. For multi-machine files, returns the first machine.
|
|
832
|
+
* @deprecated Use parseMarkdown() and access result.file.machines[0] for explicit handling
|
|
833
|
+
*/
|
|
834
|
+
export function parseMachine(source) {
|
|
835
|
+
const { file } = parseMarkdown(source);
|
|
836
|
+
return file.machines[0];
|
|
837
|
+
}
|
|
838
|
+
//# sourceMappingURL=markdown-parser.js.map
|