@polagram/core 0.0.4 → 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/index.d.ts +102 -113
- package/dist/polagram-core.js +1532 -1404
- package/dist/polagram-core.umd.cjs +22 -22
- package/package.json +3 -2
- package/dist/src/api.d.ts +0 -84
- package/dist/src/api.js +0 -183
- package/dist/src/ast/ast.test.d.ts +0 -1
- package/dist/src/ast/ast.test.js +0 -146
- package/dist/src/ast/index.d.ts +0 -119
- package/dist/src/ast/index.js +0 -2
- package/dist/src/config/index.d.ts +0 -1
- package/dist/src/config/index.js +0 -1
- package/dist/src/config/schema.d.ts +0 -198
- package/dist/src/config/schema.js +0 -82
- package/dist/src/config/schema.test.d.ts +0 -1
- package/dist/src/config/schema.test.js +0 -94
- package/dist/src/generator/base/walker.d.ts +0 -19
- package/dist/src/generator/base/walker.js +0 -56
- package/dist/src/generator/base/walker.test.d.ts +0 -1
- package/dist/src/generator/base/walker.test.js +0 -49
- package/dist/src/generator/generators/mermaid.d.ts +0 -24
- package/dist/src/generator/generators/mermaid.js +0 -140
- package/dist/src/generator/generators/mermaid.test.d.ts +0 -1
- package/dist/src/generator/generators/mermaid.test.js +0 -70
- package/dist/src/generator/generators/plantuml.d.ts +0 -17
- package/dist/src/generator/generators/plantuml.js +0 -131
- package/dist/src/generator/generators/plantuml.test.d.ts +0 -1
- package/dist/src/generator/generators/plantuml.test.js +0 -143
- package/dist/src/generator/interface.d.ts +0 -17
- package/dist/src/generator/interface.js +0 -1
- package/dist/src/index.d.ts +0 -13
- package/dist/src/index.js +0 -21
- package/dist/src/parser/base/lexer.d.ts +0 -18
- package/dist/src/parser/base/lexer.js +0 -95
- package/dist/src/parser/base/lexer.test.d.ts +0 -1
- package/dist/src/parser/base/lexer.test.js +0 -53
- package/dist/src/parser/base/parser.d.ts +0 -14
- package/dist/src/parser/base/parser.js +0 -43
- package/dist/src/parser/base/parser.test.d.ts +0 -1
- package/dist/src/parser/base/parser.test.js +0 -90
- package/dist/src/parser/base/token.d.ts +0 -18
- package/dist/src/parser/base/token.js +0 -1
- package/dist/src/parser/base/tokens.d.ts +0 -8
- package/dist/src/parser/base/tokens.js +0 -1
- package/dist/src/parser/format-detector.d.ts +0 -55
- package/dist/src/parser/format-detector.js +0 -98
- package/dist/src/parser/index.d.ts +0 -11
- package/dist/src/parser/index.js +0 -33
- package/dist/src/parser/index.test.d.ts +0 -1
- package/dist/src/parser/index.test.js +0 -23
- package/dist/src/parser/interface.d.ts +0 -8
- package/dist/src/parser/interface.js +0 -1
- package/dist/src/parser/languages/mermaid/constants.d.ts +0 -7
- package/dist/src/parser/languages/mermaid/constants.js +0 -20
- package/dist/src/parser/languages/mermaid/index.d.ts +0 -4
- package/dist/src/parser/languages/mermaid/index.js +0 -11
- package/dist/src/parser/languages/mermaid/lexer.d.ts +0 -14
- package/dist/src/parser/languages/mermaid/lexer.js +0 -152
- package/dist/src/parser/languages/mermaid/lexer.test.d.ts +0 -1
- package/dist/src/parser/languages/mermaid/lexer.test.js +0 -58
- package/dist/src/parser/languages/mermaid/parser.d.ts +0 -22
- package/dist/src/parser/languages/mermaid/parser.js +0 -340
- package/dist/src/parser/languages/mermaid/parser.test.d.ts +0 -1
- package/dist/src/parser/languages/mermaid/parser.test.js +0 -252
- package/dist/src/parser/languages/mermaid/tokens.d.ts +0 -9
- package/dist/src/parser/languages/mermaid/tokens.js +0 -1
- package/dist/src/parser/languages/plantuml/index.d.ts +0 -4
- package/dist/src/parser/languages/plantuml/index.js +0 -11
- package/dist/src/parser/languages/plantuml/lexer.d.ts +0 -15
- package/dist/src/parser/languages/plantuml/lexer.js +0 -143
- package/dist/src/parser/languages/plantuml/parser.d.ts +0 -23
- package/dist/src/parser/languages/plantuml/parser.js +0 -481
- package/dist/src/parser/languages/plantuml/parser.test.d.ts +0 -1
- package/dist/src/parser/languages/plantuml/parser.test.js +0 -236
- package/dist/src/parser/languages/plantuml/tokens.d.ts +0 -9
- package/dist/src/parser/languages/plantuml/tokens.js +0 -1
- package/dist/src/transformer/cleaners/prune-empty.d.ts +0 -9
- package/dist/src/transformer/cleaners/prune-empty.js +0 -27
- package/dist/src/transformer/cleaners/prune-empty.test.d.ts +0 -1
- package/dist/src/transformer/cleaners/prune-empty.test.js +0 -69
- package/dist/src/transformer/cleaners/prune-unused.d.ts +0 -5
- package/dist/src/transformer/cleaners/prune-unused.js +0 -48
- package/dist/src/transformer/cleaners/prune-unused.test.d.ts +0 -1
- package/dist/src/transformer/cleaners/prune-unused.test.js +0 -71
- package/dist/src/transformer/filters/focus.d.ts +0 -13
- package/dist/src/transformer/filters/focus.js +0 -71
- package/dist/src/transformer/filters/focus.test.d.ts +0 -1
- package/dist/src/transformer/filters/focus.test.js +0 -50
- package/dist/src/transformer/filters/remove.d.ts +0 -12
- package/dist/src/transformer/filters/remove.js +0 -82
- package/dist/src/transformer/filters/remove.test.d.ts +0 -1
- package/dist/src/transformer/filters/remove.test.js +0 -38
- package/dist/src/transformer/filters/resolve.d.ts +0 -9
- package/dist/src/transformer/filters/resolve.js +0 -32
- package/dist/src/transformer/filters/resolve.test.d.ts +0 -1
- package/dist/src/transformer/filters/resolve.test.js +0 -48
- package/dist/src/transformer/index.d.ts +0 -10
- package/dist/src/transformer/index.js +0 -10
- package/dist/src/transformer/lens.d.ts +0 -12
- package/dist/src/transformer/lens.js +0 -58
- package/dist/src/transformer/lens.test.d.ts +0 -1
- package/dist/src/transformer/lens.test.js +0 -60
- package/dist/src/transformer/orchestration/engine.d.ts +0 -5
- package/dist/src/transformer/orchestration/engine.js +0 -24
- package/dist/src/transformer/orchestration/engine.test.d.ts +0 -1
- package/dist/src/transformer/orchestration/engine.test.js +0 -52
- package/dist/src/transformer/orchestration/registry.d.ts +0 -10
- package/dist/src/transformer/orchestration/registry.js +0 -27
- package/dist/src/transformer/selector/matcher.d.ts +0 -9
- package/dist/src/transformer/selector/matcher.js +0 -62
- package/dist/src/transformer/selector/matcher.test.d.ts +0 -1
- package/dist/src/transformer/selector/matcher.test.js +0 -70
- package/dist/src/transformer/traverse/walker.d.ts +0 -14
- package/dist/src/transformer/traverse/walker.js +0 -67
- package/dist/src/transformer/traverse/walker.test.d.ts +0 -1
- package/dist/src/transformer/traverse/walker.test.js +0 -111
- package/dist/src/transformer/types.d.ts +0 -47
- package/dist/src/transformer/types.js +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
import { BaseParser } from '../../base/parser';
|
|
2
|
-
import { ARROW_MAPPING } from './constants';
|
|
3
|
-
export class Parser extends BaseParser {
|
|
4
|
-
constructor(lexer) {
|
|
5
|
-
super(lexer);
|
|
6
|
-
Object.defineProperty(this, "currentGroup", {
|
|
7
|
-
enumerable: true,
|
|
8
|
-
configurable: true,
|
|
9
|
-
writable: true,
|
|
10
|
-
value: null
|
|
11
|
-
});
|
|
12
|
-
Object.defineProperty(this, "idCounters", {
|
|
13
|
-
enumerable: true,
|
|
14
|
-
configurable: true,
|
|
15
|
-
writable: true,
|
|
16
|
-
value: {
|
|
17
|
-
evt: 0,
|
|
18
|
-
frag: 0,
|
|
19
|
-
br: 0,
|
|
20
|
-
note: 0,
|
|
21
|
-
group: 0,
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
parse() {
|
|
26
|
-
const root = {
|
|
27
|
-
kind: 'root',
|
|
28
|
-
meta: { version: '1.0.0', source: 'unknown' },
|
|
29
|
-
participants: [],
|
|
30
|
-
groups: [],
|
|
31
|
-
events: []
|
|
32
|
-
};
|
|
33
|
-
root.events = this.parseBlock(root);
|
|
34
|
-
return root;
|
|
35
|
-
}
|
|
36
|
-
parseBlock(root, stopTokens = []) {
|
|
37
|
-
const events = [];
|
|
38
|
-
while (this.currToken.type !== 'EOF') {
|
|
39
|
-
const type = this.currToken.type;
|
|
40
|
-
if (stopTokens.includes(type)) {
|
|
41
|
-
return events;
|
|
42
|
-
}
|
|
43
|
-
if (type === 'NEWLINE') {
|
|
44
|
-
this.advance();
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
if (type === 'SEQUENCE_DIAGRAM') {
|
|
48
|
-
root.meta.source = 'mermaid';
|
|
49
|
-
this.advance();
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
if (type === 'TITLE') {
|
|
53
|
-
this.advance(); // eat TITLE
|
|
54
|
-
root.meta.title = this.readRestOfLine();
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
if (type === 'BOX') {
|
|
58
|
-
const groupEvents = this.parseGroup(root);
|
|
59
|
-
events.push(...groupEvents);
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
if (type === 'PARTICIPANT' || type === 'ACTOR') {
|
|
63
|
-
this.parseParticipant(root);
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
if (type === 'LOOP' || type === 'ALT' || type === 'OPT') {
|
|
67
|
-
events.push(this.parseFragment(root));
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
if (type === 'NOTE') {
|
|
71
|
-
events.push(this.parseNote(root));
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
if (type === 'ACTIVATE' || type === 'DEACTIVATE') {
|
|
75
|
-
events.push(this.parseActivation(root));
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
if (this.isParticipantToken(this.currToken)) {
|
|
79
|
-
const msg = this.parseMessage(root);
|
|
80
|
-
if (msg) {
|
|
81
|
-
events.push(msg);
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
// Skip unknown or unhandled
|
|
86
|
-
this.advance();
|
|
87
|
-
}
|
|
88
|
-
return events;
|
|
89
|
-
}
|
|
90
|
-
isParticipantToken(tok) {
|
|
91
|
-
return tok.type === 'IDENTIFIER' || tok.type === 'STRING';
|
|
92
|
-
}
|
|
93
|
-
parseGroup(root) {
|
|
94
|
-
this.advance(); // eat 'box'
|
|
95
|
-
const rawAttrs = this.readRestOfLine().trim();
|
|
96
|
-
let name = rawAttrs;
|
|
97
|
-
let color;
|
|
98
|
-
const parts = rawAttrs.split(/\s+/);
|
|
99
|
-
if (parts.length > 0) {
|
|
100
|
-
const first = parts[0];
|
|
101
|
-
if (first.startsWith('#') || ['rgb', 'rgba', 'transparent', 'aqua', 'grey', 'gray', 'purple', 'red', 'blue', 'green'].includes(first.toLowerCase())) {
|
|
102
|
-
color = first;
|
|
103
|
-
name = parts.slice(1).join(' ');
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
if (!name)
|
|
107
|
-
name = `Group ${this.idCounters.group + 1}`;
|
|
108
|
-
const group = {
|
|
109
|
-
kind: 'group',
|
|
110
|
-
id: this.generateId('group'),
|
|
111
|
-
name: name,
|
|
112
|
-
type: 'box',
|
|
113
|
-
participantIds: [],
|
|
114
|
-
style: color ? { backgroundColor: color } : undefined
|
|
115
|
-
};
|
|
116
|
-
root.groups.push(group);
|
|
117
|
-
// Set current group context
|
|
118
|
-
const previousGroup = this.currentGroup;
|
|
119
|
-
this.currentGroup = group;
|
|
120
|
-
// Parse content until 'end'
|
|
121
|
-
const events = this.parseBlock(root, ['END']);
|
|
122
|
-
// Restore context
|
|
123
|
-
this.currentGroup = previousGroup;
|
|
124
|
-
if (this.currToken.type === 'END') {
|
|
125
|
-
this.advance(); // eat 'end'
|
|
126
|
-
}
|
|
127
|
-
return events;
|
|
128
|
-
}
|
|
129
|
-
parseFragment(root) {
|
|
130
|
-
const type = this.currToken.type;
|
|
131
|
-
let operator = 'loop';
|
|
132
|
-
if (type === 'ALT')
|
|
133
|
-
operator = 'alt';
|
|
134
|
-
if (type === 'OPT')
|
|
135
|
-
operator = 'opt';
|
|
136
|
-
this.advance(); // eat operator
|
|
137
|
-
const condition = this.readRestOfLine();
|
|
138
|
-
const branches = [];
|
|
139
|
-
const events = this.parseBlock(root, ['END', 'ELSE']);
|
|
140
|
-
branches.push({
|
|
141
|
-
id: this.generateId('br'),
|
|
142
|
-
condition,
|
|
143
|
-
events
|
|
144
|
-
});
|
|
145
|
-
while (this.currToken.type === 'ELSE') {
|
|
146
|
-
this.advance();
|
|
147
|
-
const elseCond = this.readRestOfLine();
|
|
148
|
-
const elseEvents = this.parseBlock(root, ['END', 'ELSE']);
|
|
149
|
-
branches.push({
|
|
150
|
-
id: this.generateId('br'),
|
|
151
|
-
condition: elseCond,
|
|
152
|
-
events: elseEvents
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
if (this.currToken.type === 'END') {
|
|
156
|
-
this.advance();
|
|
157
|
-
}
|
|
158
|
-
return {
|
|
159
|
-
kind: 'fragment',
|
|
160
|
-
id: this.generateId('frag'),
|
|
161
|
-
operator,
|
|
162
|
-
branches
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
parseParticipant(root) {
|
|
166
|
-
const isActor = this.currToken.type === 'ACTOR';
|
|
167
|
-
this.advance(); // eat 'participant' or 'actor'
|
|
168
|
-
let id = '';
|
|
169
|
-
let name = '';
|
|
170
|
-
if (this.isParticipantToken(this.currToken)) {
|
|
171
|
-
id = this.currToken.literal;
|
|
172
|
-
name = id;
|
|
173
|
-
this.advance();
|
|
174
|
-
}
|
|
175
|
-
// Check for 'as'
|
|
176
|
-
if (this.currToken.type === 'AS') {
|
|
177
|
-
this.advance(); // eat 'as'
|
|
178
|
-
// Use readRestOfLine to capture multi-word aliases (e.g., "API Server")
|
|
179
|
-
const alias = this.readRestOfLine().trim();
|
|
180
|
-
if (alias) {
|
|
181
|
-
name = alias;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
const existing = root.participants.find(p => p.id === id);
|
|
185
|
-
if (!existing) {
|
|
186
|
-
root.participants.push({
|
|
187
|
-
id,
|
|
188
|
-
name,
|
|
189
|
-
type: isActor ? 'actor' : 'participant'
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
if (name !== id)
|
|
194
|
-
existing.name = name;
|
|
195
|
-
if (isActor)
|
|
196
|
-
existing.type = 'actor';
|
|
197
|
-
}
|
|
198
|
-
// Assign to current group if exists
|
|
199
|
-
if (this.currentGroup) {
|
|
200
|
-
if (!this.currentGroup.participantIds.includes(id)) {
|
|
201
|
-
this.currentGroup.participantIds.push(id);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
parseNote(root) {
|
|
206
|
-
this.advance(); // eat 'note'
|
|
207
|
-
let position = 'over'; // default
|
|
208
|
-
if (this.currToken.type === 'LEFT') {
|
|
209
|
-
position = 'left';
|
|
210
|
-
this.advance();
|
|
211
|
-
}
|
|
212
|
-
else if (this.currToken.type === 'RIGHT') {
|
|
213
|
-
position = 'right';
|
|
214
|
-
this.advance();
|
|
215
|
-
}
|
|
216
|
-
else if (this.currToken.type === 'OVER') {
|
|
217
|
-
position = 'over';
|
|
218
|
-
this.advance();
|
|
219
|
-
}
|
|
220
|
-
// consume 'of' if present (optional in some cases but usually note right of A)
|
|
221
|
-
if (this.currToken.type === 'OF') {
|
|
222
|
-
this.advance();
|
|
223
|
-
}
|
|
224
|
-
const participantIds = [];
|
|
225
|
-
while (this.isParticipantToken(this.currToken)) {
|
|
226
|
-
participantIds.push(this.currToken.literal);
|
|
227
|
-
this.ensureParticipant(root, this.currToken.literal);
|
|
228
|
-
this.advance();
|
|
229
|
-
if (this.currToken.type === 'COMMA') {
|
|
230
|
-
this.advance();
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
break;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
let text = '';
|
|
237
|
-
if (this.currToken.type === 'COLON') {
|
|
238
|
-
this.advance();
|
|
239
|
-
text = this.readRestOfLine();
|
|
240
|
-
}
|
|
241
|
-
return {
|
|
242
|
-
kind: 'note',
|
|
243
|
-
id: this.generateId('note'),
|
|
244
|
-
position,
|
|
245
|
-
participantIds,
|
|
246
|
-
text
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
parseActivation(root) {
|
|
250
|
-
const action = this.currToken.type === 'ACTIVATE' ? 'activate' : 'deactivate';
|
|
251
|
-
this.advance(); // eat command
|
|
252
|
-
let participantId = '';
|
|
253
|
-
if (this.isParticipantToken(this.currToken)) {
|
|
254
|
-
participantId = this.currToken.literal;
|
|
255
|
-
this.ensureParticipant(root, participantId);
|
|
256
|
-
this.advance();
|
|
257
|
-
}
|
|
258
|
-
return {
|
|
259
|
-
kind: 'activation',
|
|
260
|
-
participantId,
|
|
261
|
-
action
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
parseMessage(root) {
|
|
265
|
-
if (this.peekToken.type !== 'ARROW') {
|
|
266
|
-
return null;
|
|
267
|
-
}
|
|
268
|
-
const fromId = this.currToken.literal;
|
|
269
|
-
this.ensureParticipant(root, fromId);
|
|
270
|
-
this.advance(); // eat from
|
|
271
|
-
if (this.currToken.type !== 'ARROW') {
|
|
272
|
-
return null;
|
|
273
|
-
}
|
|
274
|
-
const arrowLiteral = this.currToken.literal;
|
|
275
|
-
this.advance(); // eat arrow
|
|
276
|
-
let activateTarget = false;
|
|
277
|
-
let deactivateSource = false;
|
|
278
|
-
if (this.currToken.type === 'PLUS') {
|
|
279
|
-
activateTarget = true;
|
|
280
|
-
this.advance();
|
|
281
|
-
}
|
|
282
|
-
if (this.currToken.type === 'MINUS') {
|
|
283
|
-
deactivateSource = true;
|
|
284
|
-
this.advance();
|
|
285
|
-
}
|
|
286
|
-
if (!this.isParticipantToken(this.currToken))
|
|
287
|
-
return null;
|
|
288
|
-
const toId = this.currToken.literal;
|
|
289
|
-
this.ensureParticipant(root, toId);
|
|
290
|
-
this.advance(); // eat to
|
|
291
|
-
let text = '';
|
|
292
|
-
if (this.currToken.type === 'COLON') {
|
|
293
|
-
this.advance();
|
|
294
|
-
text = this.readRestOfLine();
|
|
295
|
-
}
|
|
296
|
-
const { type, style } = this.resolveArrow(arrowLiteral);
|
|
297
|
-
return {
|
|
298
|
-
kind: 'message',
|
|
299
|
-
id: this.generateId('evt'),
|
|
300
|
-
from: fromId,
|
|
301
|
-
to: toId,
|
|
302
|
-
text: text,
|
|
303
|
-
type: type,
|
|
304
|
-
style: style,
|
|
305
|
-
lifecycle: (activateTarget || deactivateSource) ? { activateTarget, deactivateSource } : undefined
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
resolveArrow(arrow) {
|
|
309
|
-
const mapping = ARROW_MAPPING[arrow];
|
|
310
|
-
if (mapping) {
|
|
311
|
-
return mapping;
|
|
312
|
-
}
|
|
313
|
-
return { type: 'sync', style: { line: 'solid', head: 'arrow' } };
|
|
314
|
-
}
|
|
315
|
-
generateId(prefix) {
|
|
316
|
-
this.idCounters[prefix]++;
|
|
317
|
-
return `${prefix}_${this.idCounters[prefix]}`;
|
|
318
|
-
}
|
|
319
|
-
readRestOfLine() {
|
|
320
|
-
if (this.currToken.type === 'NEWLINE' || this.currToken.type === 'EOF') {
|
|
321
|
-
return '';
|
|
322
|
-
}
|
|
323
|
-
const start = this.currToken.start;
|
|
324
|
-
let end = this.currToken.end;
|
|
325
|
-
while (this.currToken.type !== 'NEWLINE' && this.currToken.type !== 'EOF') {
|
|
326
|
-
end = this.currToken.end;
|
|
327
|
-
this.advance();
|
|
328
|
-
}
|
|
329
|
-
return this.lexer.getInput().slice(start, end);
|
|
330
|
-
}
|
|
331
|
-
ensureParticipant(root, id) {
|
|
332
|
-
if (!root.participants.find(p => p.id === id)) {
|
|
333
|
-
root.participants.push({
|
|
334
|
-
id,
|
|
335
|
-
name: id,
|
|
336
|
-
type: 'participant'
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { Lexer } from './lexer';
|
|
3
|
-
import { Parser } from './parser';
|
|
4
|
-
describe('Mermaid Parser', () => {
|
|
5
|
-
const parse = (input) => {
|
|
6
|
-
const lexer = new Lexer(input);
|
|
7
|
-
const parser = new Parser(lexer);
|
|
8
|
-
return parser.parse();
|
|
9
|
-
};
|
|
10
|
-
describe('Basic Document Structure', () => {
|
|
11
|
-
it('should parse "sequenceDiagram" header', () => {
|
|
12
|
-
const input = `sequenceDiagram`;
|
|
13
|
-
const ast = parse(input);
|
|
14
|
-
expect(ast.kind).toBe('root');
|
|
15
|
-
expect(ast.meta.source).toBe('mermaid');
|
|
16
|
-
expect(ast.participants).toEqual([]);
|
|
17
|
-
expect(ast.events).toEqual([]);
|
|
18
|
-
});
|
|
19
|
-
it('should handle newlines around header', () => {
|
|
20
|
-
const input = `
|
|
21
|
-
sequenceDiagram
|
|
22
|
-
`;
|
|
23
|
-
const ast = parse(input);
|
|
24
|
-
expect(ast.meta.source).toBe('mermaid');
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
describe('Participant Definitions', () => {
|
|
28
|
-
it('should parse simple participant', () => {
|
|
29
|
-
const input = `
|
|
30
|
-
sequenceDiagram
|
|
31
|
-
participant Alice
|
|
32
|
-
participant Bob
|
|
33
|
-
`;
|
|
34
|
-
const ast = parse(input);
|
|
35
|
-
expect(ast.participants).toHaveLength(2);
|
|
36
|
-
expect(ast.participants[0]).toMatchObject({
|
|
37
|
-
id: 'Alice',
|
|
38
|
-
name: 'Alice',
|
|
39
|
-
type: 'participant'
|
|
40
|
-
});
|
|
41
|
-
expect(ast.participants[1]).toMatchObject({
|
|
42
|
-
id: 'Bob',
|
|
43
|
-
name: 'Bob',
|
|
44
|
-
type: 'participant'
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
it('should parse participant with multi-word alias', () => {
|
|
48
|
-
const input = `
|
|
49
|
-
sequenceDiagram
|
|
50
|
-
participant API as API Server
|
|
51
|
-
participant DB as Database System
|
|
52
|
-
API->>DB: Query
|
|
53
|
-
`;
|
|
54
|
-
const ast = parse(input);
|
|
55
|
-
const api = ast.participants.find(p => p.id === 'API');
|
|
56
|
-
expect(api).toBeDefined();
|
|
57
|
-
expect(api?.name).toBe('API Server');
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
describe('Message Definitions', () => {
|
|
61
|
-
it('should parse sync message and implicit participants', () => {
|
|
62
|
-
const input = `
|
|
63
|
-
sequenceDiagram
|
|
64
|
-
A->B: Hello
|
|
65
|
-
`;
|
|
66
|
-
const ast = parse(input);
|
|
67
|
-
expect(ast.participants).toHaveLength(2);
|
|
68
|
-
expect(ast.participants).toMatchObject([
|
|
69
|
-
{ id: 'A', name: 'A' },
|
|
70
|
-
{ id: 'B', name: 'B' }
|
|
71
|
-
]);
|
|
72
|
-
expect(ast.events).toHaveLength(1);
|
|
73
|
-
expect(ast.events[0]).toMatchObject({
|
|
74
|
-
kind: 'message',
|
|
75
|
-
from: 'A',
|
|
76
|
-
to: 'B',
|
|
77
|
-
text: 'Hello',
|
|
78
|
-
type: 'sync',
|
|
79
|
-
style: { line: 'solid', head: 'open' }
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
it('should parse various arrow types', () => {
|
|
83
|
-
const input = `
|
|
84
|
-
sequenceDiagram
|
|
85
|
-
A->>B: Arrow
|
|
86
|
-
A-->>B: Reply Arrow
|
|
87
|
-
A-->B: Dotted Open
|
|
88
|
-
A-xB: Cross
|
|
89
|
-
`;
|
|
90
|
-
const ast = parse(input);
|
|
91
|
-
expect(ast.events).toHaveLength(4);
|
|
92
|
-
const m1 = ast.events[0];
|
|
93
|
-
const m2 = ast.events[1];
|
|
94
|
-
const m3 = ast.events[2];
|
|
95
|
-
const m4 = ast.events[3];
|
|
96
|
-
// ->> : sync, solid, arrow
|
|
97
|
-
expect(m1).toMatchObject({
|
|
98
|
-
kind: 'message',
|
|
99
|
-
type: 'sync',
|
|
100
|
-
style: { line: 'solid', head: 'arrow' }
|
|
101
|
-
});
|
|
102
|
-
// -->> : reply, dotted, arrow
|
|
103
|
-
expect(m2).toMatchObject({
|
|
104
|
-
kind: 'message',
|
|
105
|
-
type: 'reply',
|
|
106
|
-
style: { line: 'dotted', head: 'arrow' }
|
|
107
|
-
});
|
|
108
|
-
// --> : reply, dotted, open
|
|
109
|
-
expect(m3).toMatchObject({
|
|
110
|
-
kind: 'message',
|
|
111
|
-
type: 'reply',
|
|
112
|
-
style: { line: 'dotted', head: 'open' }
|
|
113
|
-
});
|
|
114
|
-
// -x : destroy, solid, cross
|
|
115
|
-
expect(m4).toMatchObject({
|
|
116
|
-
kind: 'message',
|
|
117
|
-
type: 'destroy',
|
|
118
|
-
style: { line: 'solid', head: 'cross' }
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
it('should parse lifecycle activations in messages', () => {
|
|
122
|
-
const input = `
|
|
123
|
-
sequenceDiagram
|
|
124
|
-
A->>+B: Activate Target
|
|
125
|
-
B-->>-A: Deactivate Source
|
|
126
|
-
`;
|
|
127
|
-
const ast = parse(input);
|
|
128
|
-
const m1 = ast.events[0];
|
|
129
|
-
const m2 = ast.events[1];
|
|
130
|
-
if (m1.kind !== 'message' || m2.kind !== 'message') {
|
|
131
|
-
throw new Error('Expected message events');
|
|
132
|
-
}
|
|
133
|
-
expect(m1.lifecycle).toMatchObject({ activateTarget: true, deactivateSource: false });
|
|
134
|
-
expect(m2.lifecycle).toMatchObject({ activateTarget: false, deactivateSource: true });
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
describe('Lifecycle Definitions', () => {
|
|
138
|
-
it('should parse standalone activate/deactivate', () => {
|
|
139
|
-
const input = `
|
|
140
|
-
sequenceDiagram
|
|
141
|
-
activate A
|
|
142
|
-
deactivate A
|
|
143
|
-
`;
|
|
144
|
-
const ast = parse(input);
|
|
145
|
-
expect(ast.events).toHaveLength(2);
|
|
146
|
-
expect(ast.events[0]).toMatchObject({ kind: 'activation', participantId: 'A', action: 'activate' });
|
|
147
|
-
expect(ast.events[1]).toMatchObject({ kind: 'activation', participantId: 'A', action: 'deactivate' });
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
describe('Note Definitions', () => {
|
|
151
|
-
it('should parse note positions', () => {
|
|
152
|
-
const input = `
|
|
153
|
-
sequenceDiagram
|
|
154
|
-
note left of A: Note Left
|
|
155
|
-
note right of A: Note Right
|
|
156
|
-
note over A: Note Over
|
|
157
|
-
`;
|
|
158
|
-
const ast = parse(input);
|
|
159
|
-
expect(ast.events).toHaveLength(3);
|
|
160
|
-
expect(ast.events[0]).toMatchObject({ kind: 'note', position: 'left', participantIds: ['A'], text: 'Note Left' });
|
|
161
|
-
expect(ast.events[1]).toMatchObject({ kind: 'note', position: 'right', participantIds: ['A'], text: 'Note Right' });
|
|
162
|
-
expect(ast.events[2]).toMatchObject({ kind: 'note', position: 'over', participantIds: ['A'], text: 'Note Over' });
|
|
163
|
-
});
|
|
164
|
-
it('should parse note over multiple participants', () => {
|
|
165
|
-
const input = `sequenceDiagram\nnote over A,B: Shared Note`;
|
|
166
|
-
const ast = parse(input);
|
|
167
|
-
expect(ast.events[0]).toMatchObject({ kind: 'note', position: 'over', participantIds: ['A', 'B'], text: 'Shared Note' });
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
describe('Fragment Definitions', () => {
|
|
171
|
-
it('should parse loop fragment', () => {
|
|
172
|
-
const input = `
|
|
173
|
-
sequenceDiagram
|
|
174
|
-
loop Every minute
|
|
175
|
-
A->B: Check
|
|
176
|
-
end
|
|
177
|
-
`;
|
|
178
|
-
const ast = parse(input);
|
|
179
|
-
expect(ast.events).toHaveLength(1);
|
|
180
|
-
const fragment = ast.events[0];
|
|
181
|
-
expect(fragment.kind).toBe('fragment');
|
|
182
|
-
if (fragment.kind !== 'fragment')
|
|
183
|
-
return; // type guard
|
|
184
|
-
expect(fragment.operator).toBe('loop');
|
|
185
|
-
expect(fragment.branches).toHaveLength(1);
|
|
186
|
-
expect(fragment.branches[0].condition).toBe('Every minute');
|
|
187
|
-
expect(fragment.branches[0].events).toHaveLength(1);
|
|
188
|
-
expect(fragment.branches[0].events[0].kind).toBe('message');
|
|
189
|
-
});
|
|
190
|
-
it('should parse alt/else fragment', () => {
|
|
191
|
-
const input = `
|
|
192
|
-
sequenceDiagram
|
|
193
|
-
alt Success
|
|
194
|
-
A->B: OK
|
|
195
|
-
else Failure
|
|
196
|
-
A->B: Error
|
|
197
|
-
end
|
|
198
|
-
`;
|
|
199
|
-
const ast = parse(input);
|
|
200
|
-
const fragment = ast.events[0];
|
|
201
|
-
expect(fragment.kind).toBe('fragment');
|
|
202
|
-
expect(fragment.operator).toBe('alt');
|
|
203
|
-
expect(fragment.branches).toHaveLength(2);
|
|
204
|
-
expect(fragment.branches[0].condition).toBe('Success');
|
|
205
|
-
expect(fragment.branches[0].events).toHaveLength(1);
|
|
206
|
-
expect(fragment.branches[1].condition).toBe('Failure');
|
|
207
|
-
expect(fragment.branches[1].events).toHaveLength(1);
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
describe('Box/Group Definitions', () => {
|
|
211
|
-
it('should parse box definition with color and name', () => {
|
|
212
|
-
const code = `
|
|
213
|
-
sequenceDiagram
|
|
214
|
-
box "Frontend" #eef
|
|
215
|
-
participant A
|
|
216
|
-
participant B
|
|
217
|
-
end
|
|
218
|
-
box Backend
|
|
219
|
-
participant C
|
|
220
|
-
end
|
|
221
|
-
A->>C: Request
|
|
222
|
-
C-->>A: Response
|
|
223
|
-
`;
|
|
224
|
-
const ast = parse(code);
|
|
225
|
-
// Verify participants
|
|
226
|
-
expect(ast.participants).toHaveLength(3);
|
|
227
|
-
expect(ast.participants.map(p => p.id)).toEqual(['A', 'B', 'C']);
|
|
228
|
-
// Verify groups
|
|
229
|
-
expect(ast.groups).toHaveLength(2);
|
|
230
|
-
const group1 = ast.groups[0];
|
|
231
|
-
expect(group1.name).toContain('Frontend');
|
|
232
|
-
expect(group1.participantIds).toEqual(['A', 'B']);
|
|
233
|
-
const group2 = ast.groups[1];
|
|
234
|
-
expect(group2.name).toBe('Backend');
|
|
235
|
-
expect(group2.participantIds).toEqual(['C']);
|
|
236
|
-
});
|
|
237
|
-
it('should handle messages inside box block', () => {
|
|
238
|
-
const code = `
|
|
239
|
-
sequenceDiagram
|
|
240
|
-
participant U
|
|
241
|
-
box App
|
|
242
|
-
participant A
|
|
243
|
-
U->>A: inside box
|
|
244
|
-
end
|
|
245
|
-
`;
|
|
246
|
-
const ast = parse(code);
|
|
247
|
-
expect(ast.events).toHaveLength(1);
|
|
248
|
-
expect(ast.events[0].kind).toBe('message');
|
|
249
|
-
expect(ast.groups[0].participantIds).toEqual(['A']);
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
});
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export type TokenType = 'SEQUENCE_DIAGRAM' | 'NEWLINE' | 'EOF' | 'PARTICIPANT' | 'COLON' | 'IDENTIFIER' | 'STRING' | 'ARROW' | 'LOOP' | 'ALT' | 'OPT' | 'END' | 'ELSE' | 'UNKNOWN' | 'AS' | 'ACTOR' | 'TITLE' | 'NOTE' | 'LEFT' | 'RIGHT' | 'OVER' | 'OF' | 'ACTIVATE' | 'DEACTIVATE' | 'PLUS' | 'MINUS' | 'COMMA' | 'BOX';
|
|
2
|
-
export interface Token {
|
|
3
|
-
type: TokenType;
|
|
4
|
-
literal: string;
|
|
5
|
-
line: number;
|
|
6
|
-
column: number;
|
|
7
|
-
start: number;
|
|
8
|
-
end: number;
|
|
9
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Lexer } from './lexer';
|
|
2
|
-
import { Parser } from './parser';
|
|
3
|
-
export const plantumlParser = {
|
|
4
|
-
parse: (code) => {
|
|
5
|
-
const lexer = new Lexer(code);
|
|
6
|
-
const parser = new Parser(lexer);
|
|
7
|
-
return parser.parse();
|
|
8
|
-
}
|
|
9
|
-
};
|
|
10
|
-
export { Lexer } from './lexer';
|
|
11
|
-
export { Parser } from './parser';
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { BaseLexer } from '../../base/lexer';
|
|
2
|
-
import { Token } from './tokens';
|
|
3
|
-
export declare class Lexer extends BaseLexer<Token> {
|
|
4
|
-
constructor(input: string);
|
|
5
|
-
nextToken(): Token;
|
|
6
|
-
private newToken;
|
|
7
|
-
private readIdentifier;
|
|
8
|
-
private readString;
|
|
9
|
-
private lookupIdent;
|
|
10
|
-
private readArrow;
|
|
11
|
-
private peekExact;
|
|
12
|
-
private peekString;
|
|
13
|
-
readRestOfLine(): string;
|
|
14
|
-
private readMulti;
|
|
15
|
-
}
|