@polagram/core 0.0.2

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.
Files changed (100) hide show
  1. package/README.md +124 -0
  2. package/dist/index.d.ts +625 -0
  3. package/dist/polagram-core.js +3653 -0
  4. package/dist/polagram-core.umd.cjs +28 -0
  5. package/dist/src/api.d.ts +75 -0
  6. package/dist/src/api.js +160 -0
  7. package/dist/src/ast/ast.test.d.ts +1 -0
  8. package/dist/src/ast/ast.test.js +146 -0
  9. package/dist/src/ast/index.d.ts +119 -0
  10. package/dist/src/ast/index.js +2 -0
  11. package/dist/src/config/index.d.ts +1 -0
  12. package/dist/src/config/index.js +1 -0
  13. package/dist/src/config/schema.d.ts +182 -0
  14. package/dist/src/config/schema.js +78 -0
  15. package/dist/src/config/schema.test.d.ts +1 -0
  16. package/dist/src/config/schema.test.js +94 -0
  17. package/dist/src/generator/base/walker.d.ts +19 -0
  18. package/dist/src/generator/base/walker.js +56 -0
  19. package/dist/src/generator/base/walker.test.d.ts +1 -0
  20. package/dist/src/generator/base/walker.test.js +49 -0
  21. package/dist/src/generator/generators/mermaid.d.ts +24 -0
  22. package/dist/src/generator/generators/mermaid.js +140 -0
  23. package/dist/src/generator/generators/mermaid.test.d.ts +1 -0
  24. package/dist/src/generator/generators/mermaid.test.js +70 -0
  25. package/dist/src/generator/interface.d.ts +17 -0
  26. package/dist/src/generator/interface.js +1 -0
  27. package/dist/src/index.d.ts +9 -0
  28. package/dist/src/index.js +17 -0
  29. package/dist/src/parser/base/lexer.d.ts +18 -0
  30. package/dist/src/parser/base/lexer.js +95 -0
  31. package/dist/src/parser/base/lexer.test.d.ts +1 -0
  32. package/dist/src/parser/base/lexer.test.js +53 -0
  33. package/dist/src/parser/base/parser.d.ts +14 -0
  34. package/dist/src/parser/base/parser.js +43 -0
  35. package/dist/src/parser/base/parser.test.d.ts +1 -0
  36. package/dist/src/parser/base/parser.test.js +90 -0
  37. package/dist/src/parser/index.d.ts +10 -0
  38. package/dist/src/parser/index.js +29 -0
  39. package/dist/src/parser/index.test.d.ts +1 -0
  40. package/dist/src/parser/index.test.js +23 -0
  41. package/dist/src/parser/interface.d.ts +8 -0
  42. package/dist/src/parser/interface.js +1 -0
  43. package/dist/src/parser/languages/mermaid/constants.d.ts +7 -0
  44. package/dist/src/parser/languages/mermaid/constants.js +20 -0
  45. package/dist/src/parser/languages/mermaid/index.d.ts +4 -0
  46. package/dist/src/parser/languages/mermaid/index.js +11 -0
  47. package/dist/src/parser/languages/mermaid/lexer.d.ts +14 -0
  48. package/dist/src/parser/languages/mermaid/lexer.js +152 -0
  49. package/dist/src/parser/languages/mermaid/lexer.test.d.ts +1 -0
  50. package/dist/src/parser/languages/mermaid/lexer.test.js +58 -0
  51. package/dist/src/parser/languages/mermaid/parser.d.ts +21 -0
  52. package/dist/src/parser/languages/mermaid/parser.js +340 -0
  53. package/dist/src/parser/languages/mermaid/parser.test.d.ts +1 -0
  54. package/dist/src/parser/languages/mermaid/parser.test.js +252 -0
  55. package/dist/src/parser/languages/mermaid/tokens.d.ts +9 -0
  56. package/dist/src/parser/languages/mermaid/tokens.js +1 -0
  57. package/dist/src/transformer/cleaners/prune-empty.d.ts +9 -0
  58. package/dist/src/transformer/cleaners/prune-empty.js +27 -0
  59. package/dist/src/transformer/cleaners/prune-empty.test.d.ts +1 -0
  60. package/dist/src/transformer/cleaners/prune-empty.test.js +69 -0
  61. package/dist/src/transformer/cleaners/prune-unused.d.ts +5 -0
  62. package/dist/src/transformer/cleaners/prune-unused.js +48 -0
  63. package/dist/src/transformer/cleaners/prune-unused.test.d.ts +1 -0
  64. package/dist/src/transformer/cleaners/prune-unused.test.js +71 -0
  65. package/dist/src/transformer/filters/focus.d.ts +13 -0
  66. package/dist/src/transformer/filters/focus.js +71 -0
  67. package/dist/src/transformer/filters/focus.test.d.ts +1 -0
  68. package/dist/src/transformer/filters/focus.test.js +50 -0
  69. package/dist/src/transformer/filters/remove.d.ts +12 -0
  70. package/dist/src/transformer/filters/remove.js +82 -0
  71. package/dist/src/transformer/filters/remove.test.d.ts +1 -0
  72. package/dist/src/transformer/filters/remove.test.js +38 -0
  73. package/dist/src/transformer/filters/resolve.d.ts +9 -0
  74. package/dist/src/transformer/filters/resolve.js +32 -0
  75. package/dist/src/transformer/filters/resolve.test.d.ts +1 -0
  76. package/dist/src/transformer/filters/resolve.test.js +48 -0
  77. package/dist/src/transformer/index.d.ts +10 -0
  78. package/dist/src/transformer/index.js +10 -0
  79. package/dist/src/transformer/lens.d.ts +12 -0
  80. package/dist/src/transformer/lens.js +58 -0
  81. package/dist/src/transformer/lens.test.d.ts +1 -0
  82. package/dist/src/transformer/lens.test.js +60 -0
  83. package/dist/src/transformer/orchestration/engine.d.ts +5 -0
  84. package/dist/src/transformer/orchestration/engine.js +24 -0
  85. package/dist/src/transformer/orchestration/engine.test.d.ts +1 -0
  86. package/dist/src/transformer/orchestration/engine.test.js +41 -0
  87. package/dist/src/transformer/orchestration/registry.d.ts +10 -0
  88. package/dist/src/transformer/orchestration/registry.js +27 -0
  89. package/dist/src/transformer/selector/matcher.d.ts +9 -0
  90. package/dist/src/transformer/selector/matcher.js +62 -0
  91. package/dist/src/transformer/selector/matcher.test.d.ts +1 -0
  92. package/dist/src/transformer/selector/matcher.test.js +53 -0
  93. package/dist/src/transformer/traverse/walker.d.ts +14 -0
  94. package/dist/src/transformer/traverse/walker.js +67 -0
  95. package/dist/src/transformer/traverse/walker.test.d.ts +1 -0
  96. package/dist/src/transformer/traverse/walker.test.js +48 -0
  97. package/dist/src/transformer/types.d.ts +47 -0
  98. package/dist/src/transformer/types.js +1 -0
  99. package/dist/tsconfig.tsbuildinfo +1 -0
  100. package/package.json +45 -0
@@ -0,0 +1,7 @@
1
+ import { MessageNode } from '../../../ast';
2
+ export declare const ARROW_MAPPING: Record<string, {
3
+ type: MessageNode['type'];
4
+ style: MessageNode['style'];
5
+ }>;
6
+ export declare const REVERSE_ARROW_MAPPING: Record<string, string>;
7
+ export declare function getArrowString(type: MessageNode['type'], style: MessageNode['style']): string;
@@ -0,0 +1,20 @@
1
+ export const ARROW_MAPPING = {
2
+ '->': { type: 'sync', style: { line: 'solid', head: 'open' } },
3
+ '->>': { type: 'sync', style: { line: 'solid', head: 'arrow' } },
4
+ '-->': { type: 'reply', style: { line: 'dotted', head: 'open' } },
5
+ '-->>': { type: 'reply', style: { line: 'dotted', head: 'arrow' } },
6
+ '-)': { type: 'async', style: { line: 'solid', head: 'async' } },
7
+ '--)': { type: 'async', style: { line: 'dotted', head: 'async' } },
8
+ '-x': { type: 'destroy', style: { line: 'solid', head: 'cross' } },
9
+ '--x': { type: 'destroy', style: { line: 'dotted', head: 'cross' } },
10
+ };
11
+ export const REVERSE_ARROW_MAPPING = Object.entries(ARROW_MAPPING).reduce((acc, [key, value]) => {
12
+ // Create a unique key for the style + type combination to map back to the string
13
+ const lookupKey = JSON.stringify({ type: value.type, style: value.style });
14
+ acc[lookupKey] = key;
15
+ return acc;
16
+ }, {});
17
+ export function getArrowString(type, style) {
18
+ const key = JSON.stringify({ type, style });
19
+ return REVERSE_ARROW_MAPPING[key] || '->>'; // Default fallthrough
20
+ }
@@ -0,0 +1,4 @@
1
+ import { DiagramParser } from '../../interface';
2
+ export declare const mermaidParser: DiagramParser;
3
+ export { Lexer } from './lexer';
4
+ export { Parser } from './parser';
@@ -0,0 +1,11 @@
1
+ import { Lexer } from './lexer';
2
+ import { Parser } from './parser';
3
+ export const mermaidParser = {
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';
@@ -0,0 +1,14 @@
1
+ import { BaseLexer } from '../../base/lexer';
2
+ import { Token } from './tokens';
3
+ export declare class Lexer extends BaseLexer {
4
+ constructor(input: string);
5
+ nextToken(): Token;
6
+ private newToken;
7
+ private readIdentifier;
8
+ private readNumber;
9
+ private readString;
10
+ private isArrowStart;
11
+ private readArrow;
12
+ private readMulti;
13
+ private lookupIdent;
14
+ }
@@ -0,0 +1,152 @@
1
+ import { BaseLexer } from '../../base/lexer';
2
+ export class Lexer extends BaseLexer {
3
+ constructor(input) {
4
+ super(input);
5
+ }
6
+ nextToken() {
7
+ this.skipWhitespace();
8
+ const start = this.position;
9
+ const startColumn = this.column;
10
+ let tok;
11
+ switch (this.ch) {
12
+ case ':':
13
+ tok = this.newToken('COLON', this.ch, start, startColumn);
14
+ break;
15
+ case ',':
16
+ tok = this.newToken('COMMA', this.ch, start, startColumn);
17
+ break;
18
+ case '+':
19
+ tok = this.newToken('PLUS', this.ch, start, startColumn);
20
+ break;
21
+ case '-':
22
+ if (this.isArrowStart()) {
23
+ const literal = this.readArrow();
24
+ tok = this.newToken('ARROW', literal, start, startColumn);
25
+ return tok;
26
+ }
27
+ else {
28
+ tok = this.newToken('MINUS', this.ch, start, startColumn);
29
+ }
30
+ break;
31
+ case '"':
32
+ // eslint-disable-next-line no-case-declarations
33
+ const str = this.readString();
34
+ tok = this.newToken('STRING', str, start, startColumn);
35
+ return tok;
36
+ case '\n':
37
+ tok = this.newToken('NEWLINE', this.ch, start, startColumn);
38
+ break;
39
+ case '':
40
+ tok = this.newToken('EOF', '', start, startColumn);
41
+ break;
42
+ default:
43
+ if (this.isLetter(this.ch)) {
44
+ const literal = this.readIdentifier();
45
+ const type = this.lookupIdent(literal);
46
+ return this.newToken(type, literal, start, startColumn);
47
+ }
48
+ else if (this.isDigit(this.ch)) {
49
+ const literal = this.readNumber();
50
+ return this.newToken('IDENTIFIER', literal, start, startColumn);
51
+ }
52
+ else {
53
+ tok = this.newToken('UNKNOWN', this.ch, start, startColumn);
54
+ }
55
+ }
56
+ this.readChar();
57
+ return tok;
58
+ }
59
+ newToken(type, literal, start, startColumn) {
60
+ return {
61
+ type: type,
62
+ literal,
63
+ line: this.line,
64
+ column: startColumn,
65
+ start,
66
+ // If the lexer position has advanced beyond the start (consumed tokens like String/Arrow), use that position.
67
+ // Otherwise (simple chars), assume length-based calculation.
68
+ end: (this.position > start) ? this.position : start + literal.length
69
+ };
70
+ }
71
+ readIdentifier() {
72
+ const position = this.position;
73
+ while (this.isLetter(this.ch) || this.isDigit(this.ch)) {
74
+ this.readChar();
75
+ }
76
+ return this.input.slice(position, this.position);
77
+ }
78
+ readNumber() {
79
+ const position = this.position;
80
+ while (this.isDigit(this.ch)) {
81
+ this.readChar();
82
+ }
83
+ return this.input.slice(position, this.position);
84
+ }
85
+ readString() {
86
+ const position = this.position + 1;
87
+ this.readChar();
88
+ while (this.ch !== '"' && this.ch !== '' && this.ch !== '\n') {
89
+ this.readChar();
90
+ }
91
+ const str = this.input.slice(position, this.position);
92
+ if (this.ch === '"') {
93
+ // Logic handled in nextToken via readChar or here?
94
+ // In BaseLexer readChar advances.
95
+ // Original logic was slightly tricky.
96
+ // Let's keep consistent with valid implementation.
97
+ }
98
+ this.readChar();
99
+ return str;
100
+ }
101
+ isArrowStart() {
102
+ if (this.ch !== '-')
103
+ return false;
104
+ const next = this.peekChar();
105
+ return next === '>' || next === '-' || next === ')' || next === 'x';
106
+ }
107
+ readArrow() {
108
+ const potential4 = this.input.slice(this.position, this.position + 4);
109
+ if (potential4 === '-->>') {
110
+ this.readMulti(4);
111
+ return '-->>';
112
+ }
113
+ const potential3 = this.input.slice(this.position, this.position + 3);
114
+ if (potential3 === '-->' || potential3 === '--)' || potential3 === '->>' || potential3 === '--x') {
115
+ this.readMulti(3);
116
+ return potential3;
117
+ }
118
+ const potential2 = this.input.slice(this.position, this.position + 2);
119
+ if (potential2 === '->' || potential2 === '-)' || potential2 === '-x') {
120
+ this.readMulti(2);
121
+ return potential2;
122
+ }
123
+ return '-';
124
+ }
125
+ readMulti(count) {
126
+ for (let i = 0; i < count; i++)
127
+ this.readChar();
128
+ }
129
+ lookupIdent(ident) {
130
+ const keywords = {
131
+ 'sequenceDiagram': 'SEQUENCE_DIAGRAM',
132
+ 'participant': 'PARTICIPANT',
133
+ 'actor': 'ACTOR',
134
+ 'loop': 'LOOP',
135
+ 'alt': 'ALT',
136
+ 'opt': 'OPT',
137
+ 'end': 'END',
138
+ 'else': 'ELSE',
139
+ 'note': 'NOTE',
140
+ 'left': 'LEFT',
141
+ 'right': 'RIGHT',
142
+ 'over': 'OVER',
143
+ 'of': 'OF',
144
+ 'as': 'AS',
145
+ 'title': 'TITLE',
146
+ 'activate': 'ACTIVATE',
147
+ 'deactivate': 'DEACTIVATE',
148
+ 'box': 'BOX'
149
+ };
150
+ return keywords[ident] || 'IDENTIFIER';
151
+ }
152
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,58 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { Lexer } from './lexer';
3
+ describe('Mermaid Lexer', () => {
4
+ const checkValues = (input, expected) => {
5
+ const lexer = new Lexer(input);
6
+ for (const [type, literal] of expected) {
7
+ const token = lexer.nextToken();
8
+ expect(token.type).toBe(type);
9
+ expect(token.literal).toBe(literal);
10
+ }
11
+ };
12
+ it('should parse simple tokens', () => {
13
+ const input = 'sequenceDiagram participant :';
14
+ checkValues(input, [
15
+ ['SEQUENCE_DIAGRAM', 'sequenceDiagram'],
16
+ ['PARTICIPANT', 'participant'],
17
+ ['COLON', ':']
18
+ ]);
19
+ });
20
+ it('should parse keywords correctly', () => {
21
+ // Re-verify 'participant'
22
+ const lexer = new Lexer('participant actor loop');
23
+ const t1 = lexer.nextToken();
24
+ expect(t1.type).toBe('PARTICIPANT');
25
+ const t2 = lexer.nextToken();
26
+ expect(t2.type).toBe('ACTOR');
27
+ const t3 = lexer.nextToken();
28
+ expect(t3.type).toBe('LOOP');
29
+ });
30
+ it('should parse arrows', () => {
31
+ checkValues('-> --> ->> -->> -) --) -x --x', [
32
+ ['ARROW', '->'],
33
+ ['ARROW', '-->'],
34
+ ['ARROW', '->>'],
35
+ ['ARROW', '-->>'],
36
+ ['ARROW', '-)'],
37
+ ['ARROW', '--)'],
38
+ // Let's debug my lexer readArrow implementation logic mentally.
39
+ // potential3: '--)' is checked.
40
+ // So expected is ARROW '--)'
41
+ ]);
42
+ const l2 = new Lexer('--)');
43
+ const t = l2.nextToken();
44
+ expect(t.type).toBe('ARROW');
45
+ expect(t.literal).toBe('--)');
46
+ });
47
+ it('should parse strings', () => {
48
+ checkValues('"hello world"', [
49
+ ['STRING', 'hello world']
50
+ ]);
51
+ });
52
+ it('should parse plus and minus', () => {
53
+ checkValues('+ -', [
54
+ ['PLUS', '+'],
55
+ ['MINUS', '-']
56
+ ]);
57
+ });
58
+ });
@@ -0,0 +1,21 @@
1
+ import { PolagramRoot } from '../../../ast';
2
+ import { BaseParser } from '../../base/parser';
3
+ import { Lexer } from './lexer';
4
+ export declare class Parser extends BaseParser {
5
+ private currentGroup;
6
+ private idCounters;
7
+ constructor(lexer: Lexer);
8
+ parse(): PolagramRoot;
9
+ private parseBlock;
10
+ private isParticipantToken;
11
+ private parseGroup;
12
+ private parseFragment;
13
+ private parseParticipant;
14
+ private parseNote;
15
+ private parseActivation;
16
+ private parseMessage;
17
+ private resolveArrow;
18
+ private generateId;
19
+ private readRestOfLine;
20
+ private ensureParticipant;
21
+ }
@@ -0,0 +1,340 @@
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
+ }
@@ -0,0 +1 @@
1
+ export {};