@likec4/language-server 1.5.0 → 1.6.1

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.
@@ -4,10 +4,22 @@ entry LikeC4Grammar:
4
4
  (
5
5
  specifications+=SpecificationRule |
6
6
  models+=Model |
7
- views+=ModelViews
7
+ views+=ModelViews |
8
+ likec4lib+=LikeC4Lib
8
9
  )*
9
10
  ;
10
11
 
12
+ // Lib -------------------------------------
13
+ LikeC4Lib:
14
+ 'likec4lib' '{'
15
+ 'icons' '{'
16
+ (icons+=LibIcon)+
17
+ '}'
18
+ '}';
19
+
20
+ LibIcon:
21
+ name=IconId;
22
+
11
23
  // Specification -------------------------------------
12
24
 
13
25
  ElementKind:
@@ -80,7 +92,7 @@ ElementBody: '{'
80
92
  ;
81
93
 
82
94
  ElementProperty:
83
- ElementStringProperty | StyleProperties | LinkProperty;
95
+ ElementStringProperty | StyleProperties | LinkProperty | IconProperty;
84
96
 
85
97
  ElementStringProperty:
86
98
  key=('title' | 'technology' | 'description') ':'? value=String ';'?;
@@ -214,84 +226,95 @@ DynamicViewRule:
214
226
  ;
215
227
 
216
228
  DynamicViewStep:
217
- source=ElementRef (isBackward?='<-' | '->' | '-[' kind=[RelationshipKind] ']->') target=ElementRef title=String?
229
+ source=ElementRef (isBackward?='<-' | '->' | '-[' kind=[RelationshipKind] ']->' | kind=[RelationshipKind:DotId] ) target=ElementRef title=String?
218
230
  ;
219
231
 
220
232
  ViewRulePredicate:
221
- ({infer IncludePredicate} 'include' | {infer ExcludePredicate} 'exclude')
222
- expressions+=ViewRulePredicateExpr (',' expressions+=ViewRulePredicateExpr)* ','?
223
- ;
224
-
225
- DynamicViewRulePredicate:
226
- 'include' expressions+=ElementPredicateExpression (',' expressions+=ElementPredicateExpression)* ','?
233
+ {infer IncludePredicate} 'include' exprs=Expressions |
234
+ {infer ExcludePredicate} 'exclude' exprs=Expressions
227
235
  ;
228
236
 
229
- ViewRuleStyle:
230
- 'style' targets+=ElementExpr (',' targets+=ElementExpr)* ','? '{'
231
- styleprops+=StyleProperty*
232
- '}';
237
+ // IncludePredicate:
238
+ // 'include' expr=WithExpressions ({infer IncludePredicate.prev=current} ',' (expr=WithExpressions)?)*
239
+ // ;
240
+ // ExcludePredicate:
241
+ // 'exclude' expr=WithExpressions ({infer ExcludePredicate.prev=current} ',' (expr=WithExpressions)?)*
242
+ // ;
233
243
 
234
- ViewRuleAutoLayout:
235
- 'autoLayout' direction=ViewLayoutDirection;
244
+ Expressions:
245
+ value=WithExpressions ({infer Expressions.prev=current} ',' (value=WithExpressions)?)*
246
+ ;
236
247
 
237
- ViewRulePredicateExpr:
238
- ElementPredicateExpression |
239
- RelationPredicateExpression
248
+ WithExpressions infers Expression:
249
+ RelationExpression ({infer CustomRelationExpression.relation=current} 'with' custom=CustomRelationProperties)? |
250
+ ElementExpression ({infer CustomElementExpression.target=current} 'with' custom=CustomElementProperties)?
240
251
  ;
241
252
 
242
- ElementPredicateExpression:
243
- ElementExpr ({infer CustomElementExpr.target=current} 'with' body=CustomElementExprBody)?
253
+ ElementExpression:
254
+ ElementSelectorExpression |
255
+ ElementDescedantsExpression
244
256
  ;
245
257
 
246
- RelationPredicateExpression:
247
- InOutExpr |
248
- CustomRelationExpr |
249
- OutgoingExpr
258
+ ElementSelectorExpression infers ElementExpression:
259
+ {infer WildcardExpression} isWildcard?='*' |
260
+ {infer ElementTagExpression} 'element.tag' IsEqual tag=[Tag:TagId]? |
261
+ {infer ElementKindExpression} 'element.kind' IsEqual kind=[ElementKind]?
250
262
  ;
251
263
 
252
- CustomRelationExpr infers ViewRulePredicateRelations:
253
- RelationExpr ({infer CustomRelationExpr.relation=current} 'with' body=CustomRelationExprBody)?
264
+ ElementDescedantsExpression infers ElementExpression:
265
+ ElementRef (
266
+ {infer ExpandElementExpression.expand=current} DotUnderscore |
267
+ {infer ElementDescedantsExpression.parent=current} DotWildcard
268
+ )?
254
269
  ;
255
270
 
256
- RelationExpr:
257
- source=ElementExpr (isBidirectional?='<->' | '->') target=ElementExpr
271
+ RelationExpression:
272
+ InOutRelationExpressions |
273
+ DirectedRelationExpressions
258
274
  ;
259
275
 
260
- OutgoingExpr:
261
- from=ElementExpr '->'
276
+ InOutRelationExpressions infers RelationExpression:
277
+ IncomingRelationExpression ({infer InOutRelationExpression.inout=current} '->')?
262
278
  ;
263
279
 
264
- InOutExpr infers ViewRulePredicateRelations:
265
- IncomingExpr ({infer InOutExpr.inout=current} '->')?;
280
+ IncomingRelationExpression:
281
+ '->' to=ElementExpression;
266
282
 
267
- IncomingExpr:
268
- '->' to=ElementExpr;
283
+ DirectedRelationExpressions infers RelationExpression:
284
+ OutgoingRelationExpression ({infer DirectedRelationExpression.source=current} target=ElementExpression)?
285
+ ;
269
286
 
270
- ElementExpr:
271
- WildcardExpr |
272
- ElementSelectorExpr |
273
- DescedantsExpr
287
+ OutgoingRelationExpression:
288
+ from=ElementExpression
289
+ (isBidirectional?='<->' | '->' | '-[' kind=[RelationshipKind] ']->' | kind=[RelationshipKind:DotId])
274
290
  ;
275
291
 
276
- WildcardExpr:
277
- isWildcard?='*'
292
+ // Comma-separated list of ElementExpressions
293
+ ElementExpressionsIterator:
294
+ value=ElementExpression ({infer ElementExpressionsIterator.prev=current} ',' (value=ElementExpression)?)*
278
295
  ;
279
296
 
280
- ElementSelectorExpr infers ElementExpr:
281
- 'element' StickyDot (
282
- {infer ElementTagExpr} 'tag' IsEqual tag=[Tag:TagId] |
283
- {infer ElementKindExpr} 'kind' IsEqual kind=[ElementKind]
284
- )
297
+ DynamicViewRulePredicate:
298
+ 'include' exprs=DynamicViewRulePredicateIterator
285
299
  ;
286
300
 
287
- DescedantsExpr infers ElementExpr:
288
- ElementRef (
289
- {infer ExpandElementExpr.parent=current} DotUnderscore |
290
- {infer DescedantsExpr.parent=current} DotWildcard
291
- )?
301
+ DynamicViewRulePredicateIterator:
302
+ value=DynamicViewElementExpressions ({infer DynamicViewRulePredicateIterator.prev=current} ',' (value=DynamicViewElementExpressions)?)*
303
+ ;
304
+
305
+ DynamicViewElementExpressions infers DynamicViewElementExpression:
306
+ ElementExpression ({infer CustomElementExpression.target=current} 'with' custom=CustomElementProperties)?
292
307
  ;
293
308
 
294
- CustomElementExprBody: '{'
309
+ ViewRuleStyle:
310
+ 'style' target=ElementExpressionsIterator '{'
311
+ props+=StyleProperty*
312
+ '}';
313
+
314
+ ViewRuleAutoLayout:
315
+ 'autoLayout' direction=ViewLayoutDirection;
316
+
317
+ CustomElementProperties: '{'
295
318
  props+=(
296
319
  NavigateToProperty |
297
320
  ElementStringProperty |
@@ -300,7 +323,7 @@ CustomElementExprBody: '{'
300
323
  '}'
301
324
  ;
302
325
 
303
- CustomRelationExprBody: '{'
326
+ CustomRelationProperties: '{'
304
327
  props+=(
305
328
  RelationStringProperty |
306
329
  RelationshipStyleProperty
@@ -323,7 +346,7 @@ OpacityProperty:
323
346
 
324
347
  // Element properties -------------------------------------
325
348
  IconProperty:
326
- key='icon' ':'? value=Uri ';'?;
349
+ key='icon' ':'? (libicon=[LibIcon:IconId] | value=Uri) ';'?;
327
350
 
328
351
  ShapeProperty:
329
352
  key='shape' ':'? value=ElementShape ';'?;
@@ -381,6 +404,11 @@ ThemeColor returns string:
381
404
  'primary' | 'secondary' | 'muted' | 'slate' | 'blue' | 'indigo' | 'sky' | 'red' | 'gray' | 'green' | 'amber';
382
405
  ElementShape returns string:
383
406
  'rectangle' | 'person' | 'browser' | 'mobile' | 'cylinder' | 'storage' | 'queue';
407
+
408
+ IconId returns string:
409
+ LIB_ICON;
410
+ // ('aws:' | 'gcp:' | 'tech:') IdTerminal;
411
+
384
412
  Uri returns string:
385
413
  URI_WITH_SCHEMA | URI_RELATIVE;
386
414
 
@@ -418,6 +446,9 @@ hidden terminal WS: /\s+/;
418
446
  // Terminals
419
447
  //terminal LineStartWithDash: /(?<=([\r?\n][^\S\r\n]*))-/;
420
448
 
449
+ // LibIcons
450
+ terminal LIB_ICON: /\b(aws|gcp|tech):[-\w]*/;
451
+
421
452
  terminal URI_WITH_SCHEMA: /\w+:\/\/\S+/;
422
453
  terminal URI_RELATIVE: /\.{0,2}\/[^\/]\S+/;
423
454
 
@@ -436,4 +467,4 @@ terminal String: /"[^"]*"|'[^']*'/;
436
467
 
437
468
  // terminal TagId: HASH LETTER (LETTER | DIGIT | UNDERSCORE | DASH)*;
438
469
  // terminal IdTerminal: (LETTER | UNDERSCORE+ (LETTER | DIGIT)) (LETTER | DIGIT | UNDERSCORE | DASH)*;
439
- terminal IdTerminal: /\b[_]*[a-zA-Z][_-\w]*/;
470
+ terminal IdTerminal: /[_]*[a-zA-Z][-\w]*/;
@@ -0,0 +1,7 @@
1
+ import { LibIcons } from './generated-lib/icons'
2
+
3
+ export const Scheme = 'likec4-lib'
4
+ export const Path = `/icons.c4`
5
+ export const Uri = `${Scheme}://${Path}`
6
+
7
+ export { LibIcons as Content }
@@ -17,10 +17,11 @@ export class LikeC4DocumentSymbolProvider implements DocumentSymbolProvider {
17
17
 
18
18
  getSymbols({
19
19
  parseResult: {
20
- value: { specifications, models, views }
20
+ value: { specifications, models, views, likec4lib }
21
21
  }
22
22
  }: LikeC4LangiumDocument): MaybePromise<DocumentSymbol[]> {
23
23
  return [
24
+ ...likec4lib.map(l => () => this.getLikec4LibSymbol(l)),
24
25
  ...specifications.map(s => () => this.getSpecSymbol(s)),
25
26
  ...models.map(s => () => this.getModelSymbol(s)),
26
27
  ...views.map(s => () => this.getModelViewsSymbol(s))
@@ -34,6 +35,22 @@ export class LikeC4DocumentSymbolProvider implements DocumentSymbolProvider {
34
35
  })
35
36
  }
36
37
 
38
+ protected getLikec4LibSymbol(astLib: ast.LikeC4Lib): DocumentSymbol[] {
39
+ const cstModel = astLib?.$cstNode
40
+ if (!cstModel) return []
41
+ const children = astLib.icons.map(i => this.getLibIconSymbol(i)).filter(isTruthy)
42
+ if (children.length === 0) return []
43
+ return [
44
+ {
45
+ kind: SymbolKind.Namespace,
46
+ name: 'icons',
47
+ range: cstModel.range,
48
+ selectionRange: GrammarUtils.findNodeForKeyword(cstModel, 'icons')?.range ?? cstModel.range,
49
+ children
50
+ }
51
+ ]
52
+ }
53
+
37
54
  protected getSpecSymbol(astSpec: ast.SpecificationRule): DocumentSymbol[] {
38
55
  const cstModel = astSpec?.$cstNode
39
56
  if (!cstModel) return []
@@ -179,6 +196,16 @@ export class LikeC4DocumentSymbolProvider implements DocumentSymbolProvider {
179
196
  }
180
197
  }
181
198
 
199
+ protected getLibIconSymbol(astTag: ast.LibIcon): DocumentSymbol | null {
200
+ if (!astTag.$cstNode || isEmpty(astTag.name)) return null
201
+ return {
202
+ kind: this.symbolKind(astTag),
203
+ name: astTag.name,
204
+ range: astTag.$cstNode.range,
205
+ selectionRange: astTag.$cstNode.range
206
+ }
207
+ }
208
+
182
209
  protected getViewSymbol(astView: ast.LikeC4View): DocumentSymbol[] {
183
210
  const cst = astView?.$cstNode
184
211
  if (!cst) return []
@@ -1,5 +1,6 @@
1
1
  import type { AstNode } from 'langium'
2
2
  import { AbstractSemanticTokenProvider, type SemanticTokenAcceptor } from 'langium/lsp'
3
+ import { isTruthy } from 'remeda'
3
4
  import { SemanticTokenModifiers, SemanticTokenTypes } from 'vscode-languageserver-protocol'
4
5
  import { ast } from '../ast'
5
6
 
@@ -16,8 +17,18 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
16
17
  type: SemanticTokenTypes.function
17
18
  })
18
19
  }
20
+ if (ast.isLibIcon(node)) {
21
+ acceptor({
22
+ node,
23
+ property: 'name',
24
+ type: SemanticTokenTypes.type,
25
+ modifier: [SemanticTokenModifiers.definition]
26
+ })
27
+ return 'prune'
28
+ }
29
+
19
30
  if (ast.isRelation(node) && 'kind' in node) {
20
- return acceptor({
31
+ acceptor({
21
32
  node,
22
33
  property: 'kind',
23
34
  type: SemanticTokenTypes.function
@@ -40,7 +51,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
40
51
  })
41
52
  return 'prune'
42
53
  }
43
- if ((ast.isDescedantsExpr(node) || ast.isWildcardExpr(node)) && node.$cstNode) {
54
+ if ((ast.isElementDescedantsExpression(node) || ast.isWildcardExpression(node)) && node.$cstNode) {
44
55
  acceptor({
45
56
  cst: node.$cstNode,
46
57
  type: SemanticTokenTypes.variable,
@@ -51,7 +62,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
51
62
  })
52
63
  return 'prune'
53
64
  }
54
- if (ast.isElementKindExpr(node)) {
65
+ if (ast.isElementKindExpression(node) && isTruthy(node.kind)) {
55
66
  acceptor({
56
67
  node,
57
68
  property: 'kind',
@@ -59,7 +70,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
59
70
  modifier: [SemanticTokenModifiers.definition]
60
71
  })
61
72
  }
62
- if (ast.isElementTagExpr(node)) {
73
+ if (ast.isElementTagExpression(node) && isTruthy(node.tag)) {
63
74
  acceptor({
64
75
  node,
65
76
  property: 'tag',
@@ -158,11 +169,21 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
158
169
  property: 'key',
159
170
  type: SemanticTokenTypes.property
160
171
  })
161
- acceptor({
162
- node,
163
- property: 'value',
164
- type: SemanticTokenTypes.string
165
- })
172
+ if (ast.isIconProperty(node) && node.libicon) {
173
+ acceptor({
174
+ node,
175
+ property: 'libicon',
176
+ type: SemanticTokenTypes.enum
177
+ })
178
+ }
179
+ if ('value' in node) {
180
+ acceptor({
181
+ node,
182
+ property: 'value',
183
+ type: SemanticTokenTypes.string
184
+ })
185
+ }
186
+
166
187
  return 'prune'
167
188
  }
168
189
  if (ast.isElement(node)) {
@@ -145,11 +145,20 @@ export class LikeC4ModelParser {
145
145
  const bodyProps = mapToObj(astNode.body?.props.filter(ast.isElementStringProperty) ?? [], p => [p.key, p.value])
146
146
 
147
147
  title = toSingleLine(title ?? bodyProps.title)
148
- description = removeIndent(description ?? bodyProps.description)
149
- technology = toSingleLine(technology ?? bodyProps.technology)
148
+ description = removeIndent(bodyProps.description ?? description)
149
+ technology = toSingleLine(bodyProps.technology ?? technology)
150
150
 
151
151
  const links = astNode.body?.props.filter(ast.isLinkProperty).map(p => p.value)
152
152
 
153
+ // Property has higher priority than from style
154
+ const iconProp = astNode.body?.props.find(ast.isIconProperty)
155
+ if (iconProp) {
156
+ const value = iconProp.libicon?.ref?.name ?? iconProp.value
157
+ if (isTruthy(value)) {
158
+ style.icon = value as c4.IconUrl
159
+ }
160
+ }
161
+
153
162
  return {
154
163
  id,
155
164
  kind,
@@ -209,39 +218,70 @@ export class LikeC4ModelParser {
209
218
  }
210
219
  }
211
220
 
212
- private parseElementExpr(astNode: ast.ElementExpr): c4.ElementExpression {
213
- if (ast.isWildcardExpr(astNode)) {
221
+ // TODO validate view rules
222
+ private parseViewRulePredicate(astNode: ast.ViewRulePredicate, _isValid: IsValidFn): c4.ViewRulePredicate {
223
+ const exprs = [] as c4.Expression[]
224
+ let exprNode: ast.Expressions | undefined = astNode.exprs
225
+ while (exprNode) {
226
+ try {
227
+ if (isTruthy(exprNode.value)) {
228
+ exprs.unshift(this.parseExpression(exprNode.value))
229
+ }
230
+ } catch (e) {
231
+ logWarnError(e)
232
+ }
233
+ exprNode = exprNode.prev
234
+ }
235
+ return ast.isIncludePredicate(astNode) ? { include: exprs } : { exclude: exprs }
236
+ }
237
+
238
+ private parseElementExpressionsIterator(astNode: ast.ElementExpressionsIterator): c4.ElementExpression[] {
239
+ const exprs = [] as c4.ElementExpression[]
240
+ let iter: ast.ElementExpressionsIterator | undefined = astNode
241
+ while (iter) {
242
+ try {
243
+ exprs.unshift(this.parseElementExpr(iter.value))
244
+ } catch (e) {
245
+ logWarnError(e)
246
+ }
247
+ iter = iter.prev
248
+ }
249
+ return exprs
250
+ }
251
+
252
+ private parseElementExpr(astNode: ast.ElementExpression): c4.ElementExpression {
253
+ if (ast.isWildcardExpression(astNode)) {
214
254
  return {
215
255
  wildcard: true
216
256
  }
217
257
  }
218
- if (ast.isElementKindExpr(astNode)) {
219
- // invariant(astNode.kind.ref, 'ElementKindExpr kind is not resolved: ' + astNode.$cstNode?.text)
258
+ if (ast.isElementKindExpression(astNode)) {
259
+ invariant(astNode.kind, 'ElementKindExpr kind is not resolved: ' + astNode.$cstNode?.text)
220
260
  return {
221
261
  elementKind: astNode.kind.$refText as c4.ElementKind,
222
262
  isEqual: astNode.isEqual
223
263
  }
224
264
  }
225
- if (ast.isElementTagExpr(astNode)) {
265
+ if (ast.isElementTagExpression(astNode)) {
266
+ invariant(astNode.tag, 'ElementTagExpr tag is not resolved: ' + astNode.$cstNode?.text)
226
267
  let elementTag = astNode.tag.$refText
227
268
  if (elementTag.startsWith('#')) {
228
269
  elementTag = elementTag.slice(1)
229
270
  }
230
- // invariant(astNode.tag.ref, 'ElementTagExpr tag is not resolved: ' + astNode.$cstNode?.text)
231
271
  return {
232
272
  elementTag: elementTag as c4.Tag,
233
273
  isEqual: astNode.isEqual
234
274
  }
235
275
  }
236
- if (ast.isExpandElementExpr(astNode)) {
237
- const elementNode = elementRef(astNode.parent)
238
- invariant(elementNode, 'Element not found ' + astNode.parent.$cstNode?.text)
276
+ if (ast.isExpandElementExpression(astNode)) {
277
+ const elementNode = elementRef(astNode.expand)
278
+ invariant(elementNode, 'Element not found ' + astNode.expand.$cstNode?.text)
239
279
  const expanded = this.resolveFqn(elementNode)
240
280
  return {
241
281
  expanded
242
282
  }
243
283
  }
244
- if (ast.isDescedantsExpr(astNode)) {
284
+ if (ast.isElementDescedantsExpression(astNode)) {
245
285
  const elementNode = elementRef(astNode.parent)
246
286
  invariant(elementNode, 'Element not found ' + astNode.parent.$cstNode?.text)
247
287
  const element = this.resolveFqn(elementNode)
@@ -261,20 +301,25 @@ export class LikeC4ModelParser {
261
301
  nonexhaustive(astNode)
262
302
  }
263
303
 
264
- private parseCustomElementExpr(astNode: ast.CustomElementExpr): c4.CustomElementExpr {
304
+ private parseCustomElementExpr(astNode: ast.CustomElementExpression): c4.CustomElementExpr {
265
305
  let targetRef
266
- if (ast.isElementRef(astNode.target)) {
267
- targetRef = astNode.target
268
- } else if (ast.isExpandElementExpr(astNode.target)) {
269
- targetRef = astNode.target.parent
270
- } else {
271
- invariant(false, 'ElementRef expected as target of custom element')
272
- }
273
- // invariant(ast.isElementRef(astNode.target), 'ElementRef expected as target of custom element')
306
+ switch (true) {
307
+ case ast.isElementRef(astNode.target):
308
+ targetRef = astNode.target
309
+ break
310
+ case ast.isExpandElementExpression(astNode.target):
311
+ targetRef = astNode.target.expand
312
+ break
313
+ case ast.isElementDescedantsExpression(astNode.target):
314
+ targetRef = astNode.target.parent
315
+ break
316
+ default:
317
+ throw new Error('Unsupported target of custom element')
318
+ }
274
319
  const elementNode = elementRef(targetRef)
275
320
  invariant(elementNode, 'element not found: ' + astNode.$cstNode?.text)
276
321
  const element = this.resolveFqn(elementNode)
277
- const props = astNode.body?.props ?? []
322
+ const props = astNode.custom.props ?? []
278
323
  return props.reduce(
279
324
  (acc, prop) => {
280
325
  if (ast.isNavigateToProperty(prop)) {
@@ -290,7 +335,10 @@ export class LikeC4ModelParser {
290
335
  return acc
291
336
  }
292
337
  if (ast.isIconProperty(prop)) {
293
- acc.custom[prop.key] = prop.value as c4.IconUrl
338
+ const value = prop.libicon?.ref?.name ?? prop.value
339
+ if (isTruthy(value)) {
340
+ acc.custom[prop.key] = value as c4.IconUrl
341
+ }
294
342
  return acc
295
343
  }
296
344
  if (ast.isColorProperty(prop)) {
@@ -320,40 +368,25 @@ export class LikeC4ModelParser {
320
368
  )
321
369
  }
322
370
 
323
- private parsePredicateExpr(astNode: ast.ViewRulePredicateExpr): c4.Expression {
324
- if (ast.isCustomRelationExpr(astNode)) {
371
+ private parseExpression(astNode: ast.Expression): c4.Expression {
372
+ if (ast.isCustomRelationExpression(astNode)) {
325
373
  return this.parseCustomRelationExpr(astNode)
326
374
  }
327
- if (ast.isRelationExpr(astNode)) {
328
- return this.parseRelationExpr(astNode)
329
- }
330
- if (ast.isInOutExpr(astNode)) {
331
- return {
332
- inout: this.parseElementExpr(astNode.inout.to)
333
- }
334
- }
335
- if (ast.isOutgoingExpr(astNode)) {
336
- return {
337
- outgoing: this.parseElementExpr(astNode.from)
338
- }
339
- }
340
- if (ast.isIncomingExpr(astNode)) {
341
- return {
342
- incoming: this.parseElementExpr(astNode.to)
343
- }
344
- }
345
- if (ast.isCustomElementExpr(astNode)) {
375
+ if (ast.isCustomElementExpression(astNode)) {
346
376
  return this.parseCustomElementExpr(astNode)
347
377
  }
348
- if (ast.isElementExpr(astNode)) {
378
+ if (ast.isElementExpression(astNode)) {
349
379
  return this.parseElementExpr(astNode)
350
380
  }
381
+ if (ast.isRelationExpression(astNode)) {
382
+ return this.parseRelationExpr(astNode)
383
+ }
351
384
  nonexhaustive(astNode)
352
385
  }
353
386
 
354
- private parseCustomRelationExpr(astNode: ast.CustomRelationExpr): c4.CustomRelationExpr {
387
+ private parseCustomRelationExpr(astNode: ast.CustomRelationExpression): c4.CustomRelationExpr {
355
388
  const relation = this.parseRelationExpr(astNode.relation)
356
- const props = astNode.body?.props ?? []
389
+ const props = astNode.custom.props ?? []
357
390
  return props.reduce(
358
391
  (acc, prop) => {
359
392
  if (ast.isRelationStringProperty(prop)) {
@@ -385,30 +418,40 @@ export class LikeC4ModelParser {
385
418
  )
386
419
  }
387
420
 
388
- private parseRelationExpr(astNode: ast.RelationExpr): c4.RelationExpr {
389
- return {
390
- source: this.parseElementExpr(astNode.source),
391
- target: this.parseElementExpr(astNode.target),
392
- isBidirectional: astNode.isBidirectional
421
+ private parseRelationExpr(astNode: ast.RelationExpression): c4.RelationExpression {
422
+ if (ast.isDirectedRelationExpression(astNode)) {
423
+ return {
424
+ source: this.parseElementExpr(astNode.source.from),
425
+ target: this.parseElementExpr(astNode.target),
426
+ isBidirectional: astNode.source.isBidirectional
427
+ }
428
+ }
429
+ if (ast.isInOutRelationExpression(astNode)) {
430
+ return {
431
+ inout: this.parseElementExpr(astNode.inout.to)
432
+ }
393
433
  }
434
+ if (ast.isOutgoingRelationExpression(astNode)) {
435
+ return {
436
+ outgoing: this.parseElementExpr(astNode.from)
437
+ }
438
+ }
439
+ if (ast.isIncomingRelationExpression(astNode)) {
440
+ return {
441
+ incoming: this.parseElementExpr(astNode.to)
442
+ }
443
+ }
444
+ nonexhaustive(astNode)
394
445
  }
395
446
 
396
447
  private parseViewRule(astRule: ast.ViewRule, isValid: IsValidFn): c4.ViewRule {
397
- if (ast.isIncludePredicate(astRule) || ast.isExcludePredicate(astRule)) {
398
- const exprs = astRule.expressions.flatMap(n => {
399
- try {
400
- return isValid(n) ? this.parsePredicateExpr(n) : []
401
- } catch (e) {
402
- logWarnError(e)
403
- return []
404
- }
405
- })
406
- return ast.isIncludePredicate(astRule) ? { include: exprs } : { exclude: exprs }
448
+ if (ast.isViewRulePredicate(astRule)) {
449
+ return this.parseViewRulePredicate(astRule, isValid)
407
450
  }
408
451
  if (ast.isViewRuleStyle(astRule)) {
409
- const styleProps = toElementStyle(astRule.styleprops)
452
+ const styleProps = toElementStyle(astRule.props)
410
453
  return {
411
- targets: astRule.targets.map(n => this.parseElementExpr(n)),
454
+ targets: this.parseElementExpressionsIterator(astRule.target),
412
455
  style: {
413
456
  ...styleProps
414
457
  }
@@ -563,15 +606,24 @@ export class LikeC4ModelParser {
563
606
  try {
564
607
  if (ast.isDynamicViewRulePredicate(n)) {
565
608
  const include = [] as (c4.ElementExpression | c4.CustomElementExpr)[]
566
- for (const expr of n.expressions) {
567
- if (ast.isElementExpr(expr)) {
568
- include.push(this.parseElementExpr(expr))
569
- continue
570
- }
571
- if (ast.isCustomElementExpr(expr)) {
572
- include.push(this.parseCustomElementExpr(expr))
573
- continue
609
+ let iter: ast.DynamicViewRulePredicateIterator | undefined = n.exprs
610
+ while (iter) {
611
+ try {
612
+ switch (true) {
613
+ case ast.isElementExpression(iter.value):
614
+ isValid(iter.value) && include.unshift(this.parseElementExpr(iter.value))
615
+ break
616
+
617
+ case ast.isCustomElementExpression(iter.value):
618
+ isValid(iter.value) && include.unshift(this.parseCustomElementExpr(iter.value))
619
+ break
620
+ default:
621
+ nonexhaustive(iter.value)
622
+ }
623
+ } catch (e) {
624
+ logWarnError(e)
574
625
  }
626
+ iter = iter.prev
575
627
  }
576
628
  if (include.length > 0) {
577
629
  acc.push({ include })
@@ -579,8 +631,8 @@ export class LikeC4ModelParser {
579
631
  return acc
580
632
  }
581
633
  if (ast.isViewRuleStyle(n)) {
582
- const styleProps = toElementStyle(n.styleprops)
583
- const targets = n.targets.map(n => this.parseElementExpr(n))
634
+ const styleProps = toElementStyle(n.props)
635
+ const targets = this.parseElementExpressionsIterator(n.target)
584
636
  if (targets.length > 0) {
585
637
  acc.push({
586
638
  targets,