@purview-dev/astroc4 1.0.0-prerelease.3 → 1.0.0-prerelease.4
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/expressive-code.d.ts +14 -0
- package/dist/expressive-code.js +1171 -0
- package/dist/expressive-code.js.map +13 -0
- package/dist/fences.d.ts +32 -0
- package/dist/grammar/langium.d.ts +42 -0
- package/dist/grammar/source.d.ts +90 -0
- package/dist/grammar/textmate.d.ts +51 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.js +1125 -0
- package/dist/index.js.map +15 -0
- package/dist/like-c4.langium +1357 -0
- package/dist/likec4.tmLanguage.json +327 -0
- package/dist/translations.d.ts +21 -0
- package/package.json +2 -1
|
@@ -0,0 +1,1171 @@
|
|
|
1
|
+
// src/grammar/likec4.tmLanguage.json
|
|
2
|
+
var likec4_tmLanguage_default = {
|
|
3
|
+
$schema: "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
|
|
4
|
+
$generated: {
|
|
5
|
+
repo: "likec4/likec4",
|
|
6
|
+
ref: "main",
|
|
7
|
+
sha: "da5af77245f9",
|
|
8
|
+
by: "astroc4/scripts/sync-grammar.ts"
|
|
9
|
+
},
|
|
10
|
+
name: "likec4",
|
|
11
|
+
displayName: "LikeC4",
|
|
12
|
+
scopeName: "source.likec4",
|
|
13
|
+
fileTypes: [
|
|
14
|
+
"c4",
|
|
15
|
+
"likec4"
|
|
16
|
+
],
|
|
17
|
+
patterns: [
|
|
18
|
+
{
|
|
19
|
+
include: "#everything"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
repository: {
|
|
23
|
+
everything: {
|
|
24
|
+
patterns: [
|
|
25
|
+
{
|
|
26
|
+
include: "#comments"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
include: "#strings"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
include: "#urls"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
include: "#icons"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
include: "#hex-colors"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
include: "#tags"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
include: "#typed-arrow"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
include: "#declarations"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
include: "#keywords"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
include: "#numbers"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
include: "#operators"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
include: "#punctuation"
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
comments: {
|
|
64
|
+
patterns: [
|
|
65
|
+
{
|
|
66
|
+
name: "comment.block.likec4",
|
|
67
|
+
begin: "/\\*",
|
|
68
|
+
end: "\\*/",
|
|
69
|
+
captures: {
|
|
70
|
+
"0": {
|
|
71
|
+
name: "punctuation.definition.comment.likec4"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: "comment.line.double-slash.likec4",
|
|
77
|
+
match: "//.*$",
|
|
78
|
+
captures: {
|
|
79
|
+
"0": {
|
|
80
|
+
name: "punctuation.definition.comment.likec4"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
strings: {
|
|
87
|
+
patterns: [
|
|
88
|
+
{
|
|
89
|
+
name: "string.quoted.triple.likec4",
|
|
90
|
+
begin: "'''",
|
|
91
|
+
end: "'''",
|
|
92
|
+
beginCaptures: {
|
|
93
|
+
"0": {
|
|
94
|
+
name: "punctuation.definition.string.begin.likec4"
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
endCaptures: {
|
|
98
|
+
"0": {
|
|
99
|
+
name: "punctuation.definition.string.end.likec4"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: "string.quoted.triple.likec4",
|
|
105
|
+
begin: '"""',
|
|
106
|
+
end: '"""',
|
|
107
|
+
beginCaptures: {
|
|
108
|
+
"0": {
|
|
109
|
+
name: "punctuation.definition.string.begin.likec4"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
endCaptures: {
|
|
113
|
+
"0": {
|
|
114
|
+
name: "punctuation.definition.string.end.likec4"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "string.quoted.single.likec4",
|
|
120
|
+
begin: "'",
|
|
121
|
+
end: "'|$",
|
|
122
|
+
beginCaptures: {
|
|
123
|
+
"0": {
|
|
124
|
+
name: "punctuation.definition.string.begin.likec4"
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
endCaptures: {
|
|
128
|
+
"0": {
|
|
129
|
+
name: "punctuation.definition.string.end.likec4"
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
patterns: [
|
|
133
|
+
{
|
|
134
|
+
name: "constant.character.escape.likec4",
|
|
135
|
+
match: "\\\\."
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: "string.quoted.double.likec4",
|
|
141
|
+
begin: '"',
|
|
142
|
+
end: '"|$',
|
|
143
|
+
beginCaptures: {
|
|
144
|
+
"0": {
|
|
145
|
+
name: "punctuation.definition.string.begin.likec4"
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
endCaptures: {
|
|
149
|
+
"0": {
|
|
150
|
+
name: "punctuation.definition.string.end.likec4"
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
patterns: [
|
|
154
|
+
{
|
|
155
|
+
name: "constant.character.escape.likec4",
|
|
156
|
+
match: "\\\\."
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
urls: {
|
|
163
|
+
name: "markup.underline.link.likec4",
|
|
164
|
+
match: `\\bhttps?://[^\\s'"]+`
|
|
165
|
+
},
|
|
166
|
+
icons: {
|
|
167
|
+
comment: "Bundled icon ids: `tech:nginx`, `aws:…`, `gcp:…`, …",
|
|
168
|
+
match: "\\b(aws|azure|bootstrap|gcp|md|tech)(:)([\\w-]+)",
|
|
169
|
+
captures: {
|
|
170
|
+
"1": {
|
|
171
|
+
name: "support.constant.icon-pack.likec4"
|
|
172
|
+
},
|
|
173
|
+
"2": {
|
|
174
|
+
name: "punctuation.separator.likec4"
|
|
175
|
+
},
|
|
176
|
+
"3": {
|
|
177
|
+
name: "support.constant.icon.likec4"
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
"hex-colors": {
|
|
182
|
+
name: "constant.other.color.rgb-value.hex.likec4",
|
|
183
|
+
match: "#(?:[0-9A-Fa-f]{8}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})(?![\\w-])"
|
|
184
|
+
},
|
|
185
|
+
tags: {
|
|
186
|
+
name: "entity.name.tag.likec4",
|
|
187
|
+
match: "#[A-Za-z_][\\w-]*"
|
|
188
|
+
},
|
|
189
|
+
"typed-arrow": {
|
|
190
|
+
comment: "`a -[https]-> b`",
|
|
191
|
+
match: "(-\\[)\\s*([A-Za-z_][\\w-]*)\\s*(\\]->)",
|
|
192
|
+
captures: {
|
|
193
|
+
"1": {
|
|
194
|
+
name: "keyword.operator.arrow.likec4"
|
|
195
|
+
},
|
|
196
|
+
"2": {
|
|
197
|
+
name: "entity.name.type.relationship.likec4"
|
|
198
|
+
},
|
|
199
|
+
"3": {
|
|
200
|
+
name: "keyword.operator.arrow.likec4"
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
declarations: {
|
|
205
|
+
patterns: [
|
|
206
|
+
{
|
|
207
|
+
comment: "`element system`, `deploymentNode region`, `tag tls`, `relationship https`. Not `where tag is #x`.",
|
|
208
|
+
match: "\\b(element|deploymentNode|tag|relationship)\\s+(?!is\\b)([A-Za-z_][\\w-]*)(?=\\s*(?:\\{|$|//))",
|
|
209
|
+
captures: {
|
|
210
|
+
"1": {
|
|
211
|
+
name: "storage.type.likec4"
|
|
212
|
+
},
|
|
213
|
+
"2": {
|
|
214
|
+
name: "entity.name.type.likec4"
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
comment: "`color testingBlue #0046BE` declares; `color testingBlue` inside a style block only refers.",
|
|
220
|
+
match: "\\b(color)\\s+([A-Za-z_][\\w-]*)(?=\\s+#)",
|
|
221
|
+
captures: {
|
|
222
|
+
"1": {
|
|
223
|
+
name: "storage.type.likec4"
|
|
224
|
+
},
|
|
225
|
+
"2": {
|
|
226
|
+
name: "entity.name.type.likec4"
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
comment: "`view index`, `dynamic view firstRequest`, `deployment view prod`, `view internals of site`",
|
|
232
|
+
match: "\\b(?:(dynamic|deployment)\\s+)?(view)\\s+([A-Za-z_]\\w*)",
|
|
233
|
+
captures: {
|
|
234
|
+
"1": {
|
|
235
|
+
name: "storage.modifier.likec4"
|
|
236
|
+
},
|
|
237
|
+
"2": {
|
|
238
|
+
name: "storage.type.view.likec4"
|
|
239
|
+
},
|
|
240
|
+
"3": {
|
|
241
|
+
name: "entity.name.function.view.likec4"
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
comment: "`reader = actor 'Engineer'`",
|
|
247
|
+
match: "\\b([A-Za-z_]\\w*)\\s*(=)(?!=)\\s*(?!(?:autoLayout|deploymentNode|dynamic|element|exclude|extend|extends|from|icons|import|include|instance|instanceOf|is|metadata|node|not|of|predicate|relationship|rgb|rgba|view|where|with|add|deployment|diff|dynamicPredicateGroup|global|group|likec4lib|model|predicateGroup|rank|remove|specification|style|styleGroup|views|alt|break|catch|else|finally|if|loop|opt|par|parallel|try|when)\\b)([A-Za-z_]\\w*)?",
|
|
248
|
+
captures: {
|
|
249
|
+
"1": {
|
|
250
|
+
name: "variable.other.definition.likec4"
|
|
251
|
+
},
|
|
252
|
+
"2": {
|
|
253
|
+
name: "keyword.operator.assignment.likec4"
|
|
254
|
+
},
|
|
255
|
+
"3": {
|
|
256
|
+
name: "entity.name.type.likec4"
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
]
|
|
261
|
+
},
|
|
262
|
+
keywords: {
|
|
263
|
+
patterns: [
|
|
264
|
+
{
|
|
265
|
+
comment: "Keywords that open a block: `model { … }`.",
|
|
266
|
+
name: "keyword.control.block.likec4",
|
|
267
|
+
match: "\\b(?:add|deployment|diff|dynamicPredicateGroup|global|group|likec4lib|model|predicateGroup|rank|remove|specification|style|styleGroup|views)\\b(?=\\s*\\{)"
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: "keyword.operator.word.likec4",
|
|
271
|
+
match: "\\b(?:and|or|not|is)\\b"
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
name: "keyword.control.likec4",
|
|
275
|
+
match: "\\b(?:autoLayout|deploymentNode|dynamic|element|exclude|extend|extends|from|icons|import|include|instance|instanceOf|is|metadata|node|not|of|predicate|relationship|rgb|rgba|view|where|with|alt|break|catch|else|finally|if|loop|opt|par|parallel|try|when|add|deployment|diff|dynamicPredicateGroup|global|group|likec4lib|model|predicateGroup|rank|remove|specification|style|styleGroup|views)\\b"
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: "variable.language.likec4",
|
|
279
|
+
match: "\\b(?:source|target)\\b"
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
name: "support.type.property-name.likec4",
|
|
283
|
+
match: "\\b(?:border|color|description|descriptionFile|head|icon|iconColor|iconPosition|iconSize|includeAncestors|kind|line|link|multiple|navigateTo|notation|notes|opacity|order|padding|routing|shape|size|sizing|style|summary|tag|tail|technology|textSize|title|variant)\\b"
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
name: "constant.language.likec4",
|
|
287
|
+
match: "\\b(?:agent|amber|auto|blue|bottom|BottomTop|browser|bucket|component|crow|cylinder|dashed|diagram|diamond|document|dot|dotted|fixed|gray|green|indigo|large|left|LeftRight|lg|max|md|medium|min|mobile|muted|none|normal|odiamond|odot|onormal|open|ortho|person|primary|queue|rectangle|red|right|RightLeft|same|secondary|sequence|sink|sky|slate|sm|small|solid|source|spline|storage|top|TopBottom|vee|xl|xlarge|xs|xsmall)\\b"
|
|
288
|
+
}
|
|
289
|
+
]
|
|
290
|
+
},
|
|
291
|
+
numbers: {
|
|
292
|
+
name: "constant.numeric.likec4",
|
|
293
|
+
match: "\\b\\d+(?:\\.\\d+)?%?"
|
|
294
|
+
},
|
|
295
|
+
operators: {
|
|
296
|
+
patterns: [
|
|
297
|
+
{
|
|
298
|
+
name: "keyword.operator.arrow.likec4",
|
|
299
|
+
match: "\\]<->|\\]->|<->|->"
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
name: "keyword.operator.wildcard.likec4",
|
|
303
|
+
match: "\\.\\*\\*|\\.\\*|\\*\\*|\\*"
|
|
304
|
+
}
|
|
305
|
+
]
|
|
306
|
+
},
|
|
307
|
+
punctuation: {
|
|
308
|
+
patterns: [
|
|
309
|
+
{
|
|
310
|
+
name: "punctuation.section.block.begin.likec4",
|
|
311
|
+
match: "\\{"
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
name: "punctuation.section.block.end.likec4",
|
|
315
|
+
match: "\\}"
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
name: "punctuation.separator.comma.likec4",
|
|
319
|
+
match: ","
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: "punctuation.accessor.likec4",
|
|
323
|
+
match: "\\."
|
|
324
|
+
}
|
|
325
|
+
]
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// src/grammar/source.ts
|
|
331
|
+
import { createHash } from "node:crypto";
|
|
332
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
333
|
+
import { join } from "node:path";
|
|
334
|
+
import { fileURLToPath } from "node:url";
|
|
335
|
+
|
|
336
|
+
// src/grammar/langium.ts
|
|
337
|
+
var NOT_VALUE_DOMAINS = /Id$/;
|
|
338
|
+
var PROPERTY_SUPPLEMENTS = ["kind", "tag"];
|
|
339
|
+
function parseLangium(source) {
|
|
340
|
+
const statements = splitStatements(stripComments(source).replace(/^[ \t]*grammar[ \t]+[A-Za-z_]\w*[ \t]*$/gm, ""));
|
|
341
|
+
const rules = statements.map(readStatement).filter((rule) => rule !== undefined);
|
|
342
|
+
const parserRules = rules.filter((rule) => rule.kind === "rule" || rule.kind === "fragment");
|
|
343
|
+
if (parserRules.length === 0) {
|
|
344
|
+
throw new Error("the file contains no Langium parser rules — is this still `packages/language-server/src/like-c4.langium`?");
|
|
345
|
+
}
|
|
346
|
+
const enums = resolveEnums(parserRules);
|
|
347
|
+
const blocks = findBlocks(parserRules);
|
|
348
|
+
const properties = unique([...findProperties(parserRules), ...PROPERTY_SUPPLEMENTS]);
|
|
349
|
+
const arrows = findArrows(parserRules);
|
|
350
|
+
const literals = unique(parserRules.flatMap((rule) => rule.tokens.filter((t) => t.type === "literal").map((t) => t.value)));
|
|
351
|
+
const classified = new Set([...blocks, ...properties, ...Object.values(enums).flat()]);
|
|
352
|
+
const keywords = literals.filter((value) => /^[A-Za-z_]\w*$/.test(value) && !classified.has(value));
|
|
353
|
+
const terminals = readTerminals(rules);
|
|
354
|
+
if (keywords.length === 0 && blocks.length === 0 && Object.keys(enums).length === 0) {
|
|
355
|
+
throw new Error("the file contains no LikeC4 keywords — the Langium grammar could not be read");
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
blocks,
|
|
359
|
+
keywords,
|
|
360
|
+
properties,
|
|
361
|
+
arrows,
|
|
362
|
+
enums,
|
|
363
|
+
icons: terminalAlternatives(terminals.LIB_ICON),
|
|
364
|
+
booleans: terminalAlternatives(terminals.BOOLEAN),
|
|
365
|
+
ruleCount: parserRules.length
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
function stripComments(source) {
|
|
369
|
+
let out = "";
|
|
370
|
+
let i = 0;
|
|
371
|
+
while (i < source.length) {
|
|
372
|
+
const char = source[i];
|
|
373
|
+
if (char === "/" && source[i + 1] === "/") {
|
|
374
|
+
while (i < source.length && source[i] !== `
|
|
375
|
+
`)
|
|
376
|
+
i++;
|
|
377
|
+
out += " ";
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (char === "/" && source[i + 1] === "*") {
|
|
381
|
+
i += 2;
|
|
382
|
+
while (i < source.length && !(source[i] === "*" && source[i + 1] === "/"))
|
|
383
|
+
i++;
|
|
384
|
+
i += 2;
|
|
385
|
+
out += " ";
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (char === "'" || char === '"') {
|
|
389
|
+
const end = endOfString(source, i);
|
|
390
|
+
out += source.slice(i, end);
|
|
391
|
+
i = end;
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
if (char === "/") {
|
|
395
|
+
const end = endOfRegex(source, i);
|
|
396
|
+
out += source.slice(i, end);
|
|
397
|
+
i = end;
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
out += char;
|
|
401
|
+
i++;
|
|
402
|
+
}
|
|
403
|
+
return out;
|
|
404
|
+
}
|
|
405
|
+
function endOfString(source, start) {
|
|
406
|
+
const quote = source[start];
|
|
407
|
+
let i = start + 1;
|
|
408
|
+
while (i < source.length) {
|
|
409
|
+
if (source[i] === "\\") {
|
|
410
|
+
i += 2;
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
if (source[i] === quote)
|
|
414
|
+
return i + 1;
|
|
415
|
+
i++;
|
|
416
|
+
}
|
|
417
|
+
return source.length;
|
|
418
|
+
}
|
|
419
|
+
function endOfRegex(source, start) {
|
|
420
|
+
let i = start + 1;
|
|
421
|
+
let inClass = false;
|
|
422
|
+
while (i < source.length) {
|
|
423
|
+
const char = source[i];
|
|
424
|
+
if (char === "\\") {
|
|
425
|
+
i += 2;
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
if (char === "[")
|
|
429
|
+
inClass = true;
|
|
430
|
+
else if (char === "]")
|
|
431
|
+
inClass = false;
|
|
432
|
+
else if (char === "/" && !inClass)
|
|
433
|
+
return i + 1;
|
|
434
|
+
i++;
|
|
435
|
+
}
|
|
436
|
+
return source.length;
|
|
437
|
+
}
|
|
438
|
+
function splitStatements(source) {
|
|
439
|
+
const statements = [];
|
|
440
|
+
let depth = 0;
|
|
441
|
+
let start = 0;
|
|
442
|
+
let i = 0;
|
|
443
|
+
while (i < source.length) {
|
|
444
|
+
const char = source[i];
|
|
445
|
+
if (char === "'" || char === '"') {
|
|
446
|
+
i = endOfString(source, i);
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (char === "/") {
|
|
450
|
+
i = endOfRegex(source, i);
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
if (char === "{")
|
|
454
|
+
depth++;
|
|
455
|
+
else if (char === "}")
|
|
456
|
+
depth = Math.max(0, depth - 1);
|
|
457
|
+
else if (char === ";" && depth === 0) {
|
|
458
|
+
statements.push(source.slice(start, i));
|
|
459
|
+
start = i + 1;
|
|
460
|
+
}
|
|
461
|
+
i++;
|
|
462
|
+
}
|
|
463
|
+
const tail = source.slice(start).trim();
|
|
464
|
+
if (tail.length > 0)
|
|
465
|
+
statements.push(tail);
|
|
466
|
+
return statements.filter((statement) => statement.trim().length > 0);
|
|
467
|
+
}
|
|
468
|
+
var HEADERS = [
|
|
469
|
+
{ kind: "terminal", hidden: true, pattern: /^\s*hidden\s+terminal\s+([A-Za-z_]\w*)\s*(?:<[^>]*>)?\s*:/ },
|
|
470
|
+
{
|
|
471
|
+
kind: "terminal",
|
|
472
|
+
hidden: false,
|
|
473
|
+
pattern: /^\s*terminal\s+(?:fragment\s+)?([A-Za-z_]\w*)\s*(?:<[^>]*>)?\s*(?:returns\s+[A-Za-z_]\w*\s*)?:/
|
|
474
|
+
},
|
|
475
|
+
{ kind: "fragment", hidden: false, pattern: /^\s*fragment\s+([A-Za-z_]\w*)\s*:/ },
|
|
476
|
+
{
|
|
477
|
+
kind: "rule",
|
|
478
|
+
hidden: false,
|
|
479
|
+
pattern: /^\s*(?:entry\s+)?([A-Za-z_]\w*)\s*(?:<[^>]*>)?\s*(?:returns\s+([A-Za-z_]\w*)\s*)?:/
|
|
480
|
+
}
|
|
481
|
+
];
|
|
482
|
+
function readStatement(statement) {
|
|
483
|
+
for (const header of HEADERS) {
|
|
484
|
+
const match = header.pattern.exec(statement);
|
|
485
|
+
const name = match?.[1];
|
|
486
|
+
if (!match || !name)
|
|
487
|
+
continue;
|
|
488
|
+
return {
|
|
489
|
+
name,
|
|
490
|
+
kind: header.kind,
|
|
491
|
+
hidden: header.hidden,
|
|
492
|
+
returns: header.kind === "rule" ? match[2] : undefined,
|
|
493
|
+
tokens: tokenize(statement.slice(match[0].length))
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
function tokenize(body) {
|
|
499
|
+
const tokens = [];
|
|
500
|
+
let i = 0;
|
|
501
|
+
while (i < body.length) {
|
|
502
|
+
const char = body.charAt(i);
|
|
503
|
+
if (/\s/.test(char)) {
|
|
504
|
+
i++;
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
if (char === "'" || char === '"') {
|
|
508
|
+
const end = endOfString(body, i);
|
|
509
|
+
tokens.push({ type: "literal", value: unquote(body.slice(i, end)) });
|
|
510
|
+
i = end;
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
if (char === "/") {
|
|
514
|
+
const end = endOfRegex(body, i);
|
|
515
|
+
tokens.push({ type: "regex", value: body.slice(i + 1, end - 1) });
|
|
516
|
+
i = end;
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
const identifier = /^[A-Za-z_]\w*/.exec(body.slice(i));
|
|
520
|
+
if (identifier) {
|
|
521
|
+
tokens.push({ type: "ident", value: identifier[0] });
|
|
522
|
+
i += identifier[0].length;
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
tokens.push({ type: "punct", value: char });
|
|
526
|
+
i++;
|
|
527
|
+
}
|
|
528
|
+
return tokens;
|
|
529
|
+
}
|
|
530
|
+
function unquote(quoted) {
|
|
531
|
+
const inner = quoted.slice(1, -1);
|
|
532
|
+
let out = "";
|
|
533
|
+
for (let i = 0;i < inner.length; i++) {
|
|
534
|
+
if (inner[i] !== "\\") {
|
|
535
|
+
out += inner[i];
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
const escaped = inner[++i];
|
|
539
|
+
if (escaped === "n")
|
|
540
|
+
out += `
|
|
541
|
+
`;
|
|
542
|
+
else if (escaped === "t")
|
|
543
|
+
out += "\t";
|
|
544
|
+
else if (escaped === "r")
|
|
545
|
+
out += "\r";
|
|
546
|
+
else
|
|
547
|
+
out += escaped ?? "";
|
|
548
|
+
}
|
|
549
|
+
return out;
|
|
550
|
+
}
|
|
551
|
+
function findBlocks(rules) {
|
|
552
|
+
return unique(rules.flatMap((rule) => {
|
|
553
|
+
const tokens = rule.tokens;
|
|
554
|
+
const first = tokens.findIndex((token) => token.type === "literal");
|
|
555
|
+
const keyword = first === -1 ? undefined : tokens[first]?.value;
|
|
556
|
+
if (!keyword || keyword === "{" || !/^[A-Za-z_]\w*$/.test(keyword))
|
|
557
|
+
return [];
|
|
558
|
+
for (let i = first + 1;i < tokens.length; i++) {
|
|
559
|
+
const token = tokens[i];
|
|
560
|
+
if (!token)
|
|
561
|
+
break;
|
|
562
|
+
if (token.value === "(" || token.value === "|")
|
|
563
|
+
return [];
|
|
564
|
+
if (token.type === "literal" && token.value === "{")
|
|
565
|
+
return [keyword];
|
|
566
|
+
}
|
|
567
|
+
return [];
|
|
568
|
+
}));
|
|
569
|
+
}
|
|
570
|
+
function findProperties(rules) {
|
|
571
|
+
const properties = [];
|
|
572
|
+
for (const rule of rules) {
|
|
573
|
+
const tokens = rule.tokens;
|
|
574
|
+
for (let i = 0;i < tokens.length; i++) {
|
|
575
|
+
const token = tokens[i];
|
|
576
|
+
if (!token || token.type !== "ident" || token.value !== "key")
|
|
577
|
+
continue;
|
|
578
|
+
const equals = equalsIndex(tokens, i);
|
|
579
|
+
if (equals === -1)
|
|
580
|
+
continue;
|
|
581
|
+
let j = equals + 1;
|
|
582
|
+
if (tokens[j]?.value === "(") {
|
|
583
|
+
for (j++;j < tokens.length && tokens[j]?.value !== ")"; j++) {
|
|
584
|
+
const inner = tokens[j];
|
|
585
|
+
if (inner?.type === "literal")
|
|
586
|
+
properties.push(inner.value);
|
|
587
|
+
}
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
const value = tokens[j];
|
|
591
|
+
if (value?.type === "literal")
|
|
592
|
+
properties.push(value.value);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return properties.filter((value) => /^[A-Za-z_]\w*$/.test(value));
|
|
596
|
+
}
|
|
597
|
+
function equalsIndex(tokens, i) {
|
|
598
|
+
if (tokens[i]?.type !== "ident")
|
|
599
|
+
return -1;
|
|
600
|
+
const next = tokens[i + 1]?.value;
|
|
601
|
+
if (next === "=")
|
|
602
|
+
return i + 1;
|
|
603
|
+
if ((next === "?" || next === "+") && tokens[i + 2]?.value === "=")
|
|
604
|
+
return i + 2;
|
|
605
|
+
return -1;
|
|
606
|
+
}
|
|
607
|
+
function findArrows(rules) {
|
|
608
|
+
const connector = rules.find((rule) => rule.name === "RelationConnector");
|
|
609
|
+
const fromConnector = (connector?.tokens ?? []).filter((token) => token.type === "literal" && token.value.includes("->")).map((token) => token.value);
|
|
610
|
+
return unique([...fromConnector, "->", "<->"]).sort((a, b) => b.length - a.length || a.localeCompare(b));
|
|
611
|
+
}
|
|
612
|
+
function resolveEnums(rules) {
|
|
613
|
+
const candidates = new Map;
|
|
614
|
+
for (const rule of rules) {
|
|
615
|
+
if (rule.returns !== "string" || NOT_VALUE_DOMAINS.test(rule.name))
|
|
616
|
+
continue;
|
|
617
|
+
const onlyValuesAndChoices = rule.tokens.every((token) => token.type === "literal" || token.type === "ident" || token.type === "punct" && (token.value === "|" || token.value === ";"));
|
|
618
|
+
if (onlyValuesAndChoices)
|
|
619
|
+
candidates.set(rule.name, []);
|
|
620
|
+
}
|
|
621
|
+
for (let pass = 0;pass < rules.length; pass++) {
|
|
622
|
+
let changed = false;
|
|
623
|
+
for (const rule of rules) {
|
|
624
|
+
if (!candidates.has(rule.name))
|
|
625
|
+
continue;
|
|
626
|
+
const resolved = [];
|
|
627
|
+
let complete = true;
|
|
628
|
+
for (const token of rule.tokens) {
|
|
629
|
+
if (token.type === "literal") {
|
|
630
|
+
resolved.push(token.value);
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (token.type !== "ident")
|
|
634
|
+
continue;
|
|
635
|
+
const inherited = candidates.get(token.value);
|
|
636
|
+
if (inherited === undefined) {
|
|
637
|
+
complete = false;
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
640
|
+
resolved.push(...inherited);
|
|
641
|
+
}
|
|
642
|
+
const next = unique(resolved);
|
|
643
|
+
if (!complete || next.length === 0)
|
|
644
|
+
continue;
|
|
645
|
+
if ((candidates.get(rule.name) ?? []).length !== next.length) {
|
|
646
|
+
candidates.set(rule.name, next);
|
|
647
|
+
changed = true;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (!changed)
|
|
651
|
+
break;
|
|
652
|
+
}
|
|
653
|
+
const enums = {};
|
|
654
|
+
for (const [name, values] of [...candidates].sort(([a], [b]) => a.localeCompare(b))) {
|
|
655
|
+
if (values.length > 0)
|
|
656
|
+
enums[name] = values;
|
|
657
|
+
}
|
|
658
|
+
return enums;
|
|
659
|
+
}
|
|
660
|
+
function readTerminals(rules) {
|
|
661
|
+
const terminals = {};
|
|
662
|
+
for (const rule of rules) {
|
|
663
|
+
if (rule.kind !== "terminal")
|
|
664
|
+
continue;
|
|
665
|
+
const token = rule.tokens.find((candidate) => candidate.type === "regex" || candidate.type === "literal");
|
|
666
|
+
if (!token)
|
|
667
|
+
continue;
|
|
668
|
+
terminals[rule.name] = {
|
|
669
|
+
hidden: rule.hidden,
|
|
670
|
+
kind: token.type === "regex" ? "regex" : "literal",
|
|
671
|
+
value: token.value
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
return terminals;
|
|
675
|
+
}
|
|
676
|
+
function terminalAlternatives(terminal) {
|
|
677
|
+
if (!terminal || terminal.kind !== "regex")
|
|
678
|
+
return [];
|
|
679
|
+
const group = /\(([^()]*)\)/.exec(terminal.value);
|
|
680
|
+
if (!group)
|
|
681
|
+
return [];
|
|
682
|
+
return unique((group[1] ?? "").split("|").map((value) => value.trim())).filter((value) => /^[A-Za-z][\w-]*$/.test(value));
|
|
683
|
+
}
|
|
684
|
+
function unique(values) {
|
|
685
|
+
return [...new Set(values)].sort((a, b) => a.localeCompare(b));
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// src/grammar/textmate.ts
|
|
689
|
+
var SCOPE_NAME = "source.likec4";
|
|
690
|
+
var LANGUAGE_NAME = "likec4";
|
|
691
|
+
var LANGUAGE_ALIASES = ["c4"];
|
|
692
|
+
var TMLANGUAGE_SCHEMA = "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json";
|
|
693
|
+
var VALUE_DOMAINS = [
|
|
694
|
+
"BorderStyleValue",
|
|
695
|
+
"DynamicViewDisplayVariantValue",
|
|
696
|
+
"EdgeRoutingValue",
|
|
697
|
+
"ElementShape",
|
|
698
|
+
"IconPositionValue",
|
|
699
|
+
"LineOptions",
|
|
700
|
+
"ArrowType",
|
|
701
|
+
"RankValue",
|
|
702
|
+
"SizeValue",
|
|
703
|
+
"SizingValue",
|
|
704
|
+
"ThemeColor",
|
|
705
|
+
"ViewLayoutDirection"
|
|
706
|
+
];
|
|
707
|
+
var KEYWORD_DOMAINS = ["DynamicViewFlowKeyword"];
|
|
708
|
+
var VARIABLE_DOMAINS = ["Participant"];
|
|
709
|
+
var OPERATOR_WORDS = ["and", "or", "not", "is"];
|
|
710
|
+
var FALLBACK_ICON_PACKS = ["aws", "azure", "bootstrap", "gcp", "tech"];
|
|
711
|
+
function buildTmLanguage(vocabulary, meta = {}) {
|
|
712
|
+
const control = uniqueDomain(vocabulary, KEYWORD_DOMAINS);
|
|
713
|
+
const variables = uniqueDomain(vocabulary, VARIABLE_DOMAINS);
|
|
714
|
+
const icons = vocabulary.icons.length > 0 ? vocabulary.icons : FALLBACK_ICON_PACKS;
|
|
715
|
+
const reserved = alternationOf([...vocabulary.keywords, ...vocabulary.blocks, ...control]);
|
|
716
|
+
const grammar = {
|
|
717
|
+
$schema: TMLANGUAGE_SCHEMA,
|
|
718
|
+
...meta.repo && meta.ref ? {
|
|
719
|
+
$generated: {
|
|
720
|
+
repo: meta.repo,
|
|
721
|
+
ref: meta.ref,
|
|
722
|
+
...meta.sha ? { sha: meta.sha } : {},
|
|
723
|
+
by: "astroc4/scripts/sync-grammar.ts"
|
|
724
|
+
}
|
|
725
|
+
} : {},
|
|
726
|
+
name: LANGUAGE_NAME,
|
|
727
|
+
displayName: "LikeC4",
|
|
728
|
+
scopeName: SCOPE_NAME,
|
|
729
|
+
fileTypes: ["c4", "likec4"],
|
|
730
|
+
patterns: [{ include: "#everything" }],
|
|
731
|
+
repository: {
|
|
732
|
+
everything: {
|
|
733
|
+
patterns: [
|
|
734
|
+
"comments",
|
|
735
|
+
"strings",
|
|
736
|
+
"urls",
|
|
737
|
+
"icons",
|
|
738
|
+
"hex-colors",
|
|
739
|
+
"tags",
|
|
740
|
+
"typed-arrow",
|
|
741
|
+
"declarations",
|
|
742
|
+
"keywords",
|
|
743
|
+
"numbers",
|
|
744
|
+
"operators",
|
|
745
|
+
"punctuation"
|
|
746
|
+
].map((name) => ({ include: `#${name}` }))
|
|
747
|
+
},
|
|
748
|
+
comments: comments(),
|
|
749
|
+
strings: strings(),
|
|
750
|
+
urls: urls(),
|
|
751
|
+
icons: iconIds(icons),
|
|
752
|
+
"hex-colors": hexColours(),
|
|
753
|
+
tags: tags(),
|
|
754
|
+
"typed-arrow": typedArrow(),
|
|
755
|
+
declarations: declarations(reserved),
|
|
756
|
+
keywords: keywords({ ...vocabulary, control, variables }),
|
|
757
|
+
numbers: numbers(),
|
|
758
|
+
operators: operators(vocabulary.arrows),
|
|
759
|
+
punctuation: punctuation()
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
if (meta.repo && meta.ref) {
|
|
763
|
+
grammar.$generated = {
|
|
764
|
+
repo: meta.repo,
|
|
765
|
+
ref: meta.ref,
|
|
766
|
+
...meta.sha ? { sha: meta.sha } : {},
|
|
767
|
+
by: "astroc4/scripts/sync-grammar.ts"
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
return grammar;
|
|
771
|
+
}
|
|
772
|
+
function comments() {
|
|
773
|
+
return {
|
|
774
|
+
patterns: [
|
|
775
|
+
{
|
|
776
|
+
name: "comment.block.likec4",
|
|
777
|
+
begin: "/\\*",
|
|
778
|
+
end: "\\*/",
|
|
779
|
+
captures: { 0: { name: "punctuation.definition.comment.likec4" } }
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
name: "comment.line.double-slash.likec4",
|
|
783
|
+
match: "//.*$",
|
|
784
|
+
captures: { 0: { name: "punctuation.definition.comment.likec4" } }
|
|
785
|
+
}
|
|
786
|
+
]
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
function strings() {
|
|
790
|
+
const triple = (quote) => ({
|
|
791
|
+
name: "string.quoted.triple.likec4",
|
|
792
|
+
begin: quote.repeat(3),
|
|
793
|
+
end: quote.repeat(3),
|
|
794
|
+
beginCaptures: { 0: { name: "punctuation.definition.string.begin.likec4" } },
|
|
795
|
+
endCaptures: { 0: { name: "punctuation.definition.string.end.likec4" } }
|
|
796
|
+
});
|
|
797
|
+
const single = (quote, name) => ({
|
|
798
|
+
name,
|
|
799
|
+
begin: quote,
|
|
800
|
+
end: `${quote}|$`,
|
|
801
|
+
beginCaptures: { 0: { name: "punctuation.definition.string.begin.likec4" } },
|
|
802
|
+
endCaptures: { 0: { name: "punctuation.definition.string.end.likec4" } },
|
|
803
|
+
patterns: [{ name: "constant.character.escape.likec4", match: "\\\\." }]
|
|
804
|
+
});
|
|
805
|
+
return {
|
|
806
|
+
patterns: [
|
|
807
|
+
triple("'"),
|
|
808
|
+
triple('"'),
|
|
809
|
+
single("'", "string.quoted.single.likec4"),
|
|
810
|
+
single('"', "string.quoted.double.likec4")
|
|
811
|
+
]
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
function urls() {
|
|
815
|
+
return { name: "markup.underline.link.likec4", match: `\\bhttps?://[^\\s'"]+` };
|
|
816
|
+
}
|
|
817
|
+
function iconIds(packs) {
|
|
818
|
+
return {
|
|
819
|
+
comment: "Bundled icon ids: `tech:nginx`, `aws:…`, `gcp:…`, …",
|
|
820
|
+
match: `\\b(${alternationOf(packs)})(:)([\\w-]+)`,
|
|
821
|
+
captures: {
|
|
822
|
+
1: { name: "support.constant.icon-pack.likec4" },
|
|
823
|
+
2: { name: "punctuation.separator.likec4" },
|
|
824
|
+
3: { name: "support.constant.icon.likec4" }
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
function hexColours() {
|
|
829
|
+
return {
|
|
830
|
+
name: "constant.other.color.rgb-value.hex.likec4",
|
|
831
|
+
match: "#(?:[0-9A-Fa-f]{8}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})(?![\\w-])"
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
function tags() {
|
|
835
|
+
return { name: "entity.name.tag.likec4", match: "#[A-Za-z_][\\w-]*" };
|
|
836
|
+
}
|
|
837
|
+
function typedArrow() {
|
|
838
|
+
return {
|
|
839
|
+
comment: "`a -[https]-> b`",
|
|
840
|
+
match: "(-\\[)\\s*([A-Za-z_][\\w-]*)\\s*(\\]->)",
|
|
841
|
+
captures: {
|
|
842
|
+
1: { name: "keyword.operator.arrow.likec4" },
|
|
843
|
+
2: { name: "entity.name.type.relationship.likec4" },
|
|
844
|
+
3: { name: "keyword.operator.arrow.likec4" }
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
function declarations(reserved) {
|
|
849
|
+
const notReserved = reserved ? `(?!(?:${reserved})\\b)` : "";
|
|
850
|
+
return {
|
|
851
|
+
patterns: [
|
|
852
|
+
{
|
|
853
|
+
comment: "`element system`, `deploymentNode region`, `tag tls`, `relationship https`. Not `where tag is #x`.",
|
|
854
|
+
match: "\\b(element|deploymentNode|tag|relationship)\\s+(?!is\\b)([A-Za-z_][\\w-]*)(?=\\s*(?:\\{|$|//))",
|
|
855
|
+
captures: {
|
|
856
|
+
1: { name: "storage.type.likec4" },
|
|
857
|
+
2: { name: "entity.name.type.likec4" }
|
|
858
|
+
}
|
|
859
|
+
},
|
|
860
|
+
{
|
|
861
|
+
comment: "`color testingBlue #0046BE` declares; `color testingBlue` inside a style block only refers.",
|
|
862
|
+
match: "\\b(color)\\s+([A-Za-z_][\\w-]*)(?=\\s+#)",
|
|
863
|
+
captures: {
|
|
864
|
+
1: { name: "storage.type.likec4" },
|
|
865
|
+
2: { name: "entity.name.type.likec4" }
|
|
866
|
+
}
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
comment: "`view index`, `dynamic view firstRequest`, `deployment view prod`, `view internals of site`",
|
|
870
|
+
match: "\\b(?:(dynamic|deployment)\\s+)?(view)\\s+([A-Za-z_]\\w*)",
|
|
871
|
+
captures: {
|
|
872
|
+
1: { name: "storage.modifier.likec4" },
|
|
873
|
+
2: { name: "storage.type.view.likec4" },
|
|
874
|
+
3: { name: "entity.name.function.view.likec4" }
|
|
875
|
+
}
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
comment: "`reader = actor 'Engineer'`",
|
|
879
|
+
match: `\\b([A-Za-z_]\\w*)\\s*(=)(?!=)\\s*${notReserved}([A-Za-z_]\\w*)?`,
|
|
880
|
+
captures: {
|
|
881
|
+
1: { name: "variable.other.definition.likec4" },
|
|
882
|
+
2: { name: "keyword.operator.assignment.likec4" },
|
|
883
|
+
3: { name: "entity.name.type.likec4" }
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
]
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
function keywords(vocabulary) {
|
|
890
|
+
const patterns = [];
|
|
891
|
+
const blocks = alternationOf(vocabulary.blocks);
|
|
892
|
+
if (blocks) {
|
|
893
|
+
patterns.push({
|
|
894
|
+
comment: "Keywords that open a block: `model { … }`.",
|
|
895
|
+
name: "keyword.control.block.likec4",
|
|
896
|
+
match: `\\b(?:${blocks})\\b(?=\\s*\\{)`
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
const control = alternationOf([...vocabulary.keywords, ...vocabulary.control, ...vocabulary.blocks]);
|
|
900
|
+
patterns.push({
|
|
901
|
+
name: "keyword.operator.word.likec4",
|
|
902
|
+
match: `\\b(?:${alternationOf(OPERATOR_WORDS)})\\b`
|
|
903
|
+
});
|
|
904
|
+
if (control) {
|
|
905
|
+
patterns.push({ name: "keyword.control.likec4", match: `\\b(?:${control})\\b` });
|
|
906
|
+
}
|
|
907
|
+
const variables = alternationOf(vocabulary.variables);
|
|
908
|
+
if (variables) {
|
|
909
|
+
patterns.push({ name: "variable.language.likec4", match: `\\b(?:${variables})\\b` });
|
|
910
|
+
}
|
|
911
|
+
const properties = alternationOf(vocabulary.properties);
|
|
912
|
+
if (properties) {
|
|
913
|
+
patterns.push({ name: "support.type.property-name.likec4", match: `\\b(?:${properties})\\b` });
|
|
914
|
+
}
|
|
915
|
+
const values = alternationOf(uniqueDomain(vocabulary, VALUE_DOMAINS));
|
|
916
|
+
if (values) {
|
|
917
|
+
patterns.push({ name: "constant.language.likec4", match: `\\b(?:${values})\\b` });
|
|
918
|
+
}
|
|
919
|
+
return { patterns };
|
|
920
|
+
}
|
|
921
|
+
function numbers() {
|
|
922
|
+
return { name: "constant.numeric.likec4", match: "\\b\\d+(?:\\.\\d+)?%?" };
|
|
923
|
+
}
|
|
924
|
+
function operators(arrows) {
|
|
925
|
+
const patterns = [];
|
|
926
|
+
const connectors = alternationOf(arrows);
|
|
927
|
+
if (connectors) {
|
|
928
|
+
patterns.push({ name: "keyword.operator.arrow.likec4", match: connectors });
|
|
929
|
+
}
|
|
930
|
+
patterns.push({ name: "keyword.operator.wildcard.likec4", match: "\\.\\*\\*|\\.\\*|\\*\\*|\\*" });
|
|
931
|
+
return { patterns };
|
|
932
|
+
}
|
|
933
|
+
function punctuation() {
|
|
934
|
+
return {
|
|
935
|
+
patterns: [
|
|
936
|
+
{ name: "punctuation.section.block.begin.likec4", match: "\\{" },
|
|
937
|
+
{ name: "punctuation.section.block.end.likec4", match: "\\}" },
|
|
938
|
+
{ name: "punctuation.separator.comma.likec4", match: "," },
|
|
939
|
+
{ name: "punctuation.accessor.likec4", match: "\\." }
|
|
940
|
+
]
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
function uniqueDomain(vocabulary, names) {
|
|
944
|
+
const values = names.flatMap((name) => vocabulary.enums[name] ?? []);
|
|
945
|
+
return [...new Set(values)].sort((a, b) => a.localeCompare(b));
|
|
946
|
+
}
|
|
947
|
+
function alternationOf(values) {
|
|
948
|
+
return values.map(escapeRegExp).join("|");
|
|
949
|
+
}
|
|
950
|
+
function escapeRegExp(value) {
|
|
951
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// src/grammar/source.ts
|
|
955
|
+
var DEFAULT_REPO = "likec4/likec4";
|
|
956
|
+
var DEFAULT_REF = "main";
|
|
957
|
+
var LANGIUM_PATH = "packages/language-server/src/like-c4.langium";
|
|
958
|
+
var DEFAULT_BASE_URL = "https://raw.githubusercontent.com";
|
|
959
|
+
var CACHE_VERSION = 1;
|
|
960
|
+
var BUNDLED_GRAMMAR = new URL("./likec4.tmLanguage.json", import.meta.url);
|
|
961
|
+
var BUNDLED_LANGIUM = new URL("./like-c4.langium", import.meta.url);
|
|
962
|
+
var REF_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/;
|
|
963
|
+
|
|
964
|
+
class GrammarRefError extends Error {
|
|
965
|
+
name = "GrammarRefError";
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
class GrammarFetchError extends Error {
|
|
969
|
+
name = "GrammarFetchError";
|
|
970
|
+
}
|
|
971
|
+
async function resolveLikeC4Grammar(options = {}) {
|
|
972
|
+
const env = options.env ?? process.env;
|
|
973
|
+
const repo = options.repo ?? DEFAULT_REPO;
|
|
974
|
+
const ref = env.ASTROC4_REF || options.ref || DEFAULT_REF;
|
|
975
|
+
const offline = options.offline ?? isTruthy(env.ASTROC4_OFFLINE);
|
|
976
|
+
const onError = options.onError ?? "fail";
|
|
977
|
+
const logger = options.logger;
|
|
978
|
+
validateRef(ref);
|
|
979
|
+
if (offline) {
|
|
980
|
+
return bundled("offline: true", options);
|
|
981
|
+
}
|
|
982
|
+
const cache = await readCache(options, { repo, ref });
|
|
983
|
+
if (cache?.fresh && !options.refresh) {
|
|
984
|
+
return derive(cache.langium, { repo, ref, source: "cache", fetchedAt: cache.fetchedAt, etag: cache.etag });
|
|
985
|
+
}
|
|
986
|
+
let fetched;
|
|
987
|
+
try {
|
|
988
|
+
fetched = await fetchLangiumSource({ ...options, repo, ref, etag: cache?.etag });
|
|
989
|
+
} catch (error) {
|
|
990
|
+
if (!(error instanceof Error))
|
|
991
|
+
throw error;
|
|
992
|
+
if (error instanceof GrammarRefError)
|
|
993
|
+
throw error;
|
|
994
|
+
if (onError === "fail")
|
|
995
|
+
throw error;
|
|
996
|
+
if (onError === "warn")
|
|
997
|
+
logger?.warn?.(`[astroc4] ${error.message}`);
|
|
998
|
+
if (cache) {
|
|
999
|
+
if (onError === "warn") {
|
|
1000
|
+
logger?.warn?.(`[astroc4] using the grammar cached at ${new Date(cache.fetchedAt).toISOString()}`);
|
|
1001
|
+
}
|
|
1002
|
+
return derive(cache.langium, {
|
|
1003
|
+
repo,
|
|
1004
|
+
ref,
|
|
1005
|
+
source: "cache",
|
|
1006
|
+
fetchedAt: cache.fetchedAt,
|
|
1007
|
+
etag: cache.etag
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
return bundled(error.message, options);
|
|
1011
|
+
}
|
|
1012
|
+
if ("notModified" in fetched) {
|
|
1013
|
+
if (!cache) {
|
|
1014
|
+
throw new GrammarFetchError("the grammar host answered 304 for a grammar that is not cached — retry with `refresh: true`");
|
|
1015
|
+
}
|
|
1016
|
+
await writeCache(options, { repo, ref }, cache);
|
|
1017
|
+
return derive(cache.langium, { repo, ref, source: "cache", fetchedAt: cache.fetchedAt, etag: cache.etag });
|
|
1018
|
+
}
|
|
1019
|
+
const result = derive(fetched.text, { repo, ref, source: "network", etag: fetched.etag });
|
|
1020
|
+
await writeCache(options, { repo, ref }, { langium: fetched.text, etag: fetched.etag });
|
|
1021
|
+
return result;
|
|
1022
|
+
}
|
|
1023
|
+
function derive(langium, info) {
|
|
1024
|
+
let vocabulary;
|
|
1025
|
+
try {
|
|
1026
|
+
vocabulary = parseLangium(langium);
|
|
1027
|
+
} catch (cause) {
|
|
1028
|
+
throw new GrammarFetchError(`${info.repo}@${info.ref}: the grammar could not be read — ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
1029
|
+
}
|
|
1030
|
+
const sha = grammarSha(langium);
|
|
1031
|
+
return {
|
|
1032
|
+
grammar: buildTmLanguage(vocabulary, { repo: info.repo, ref: info.ref, sha }),
|
|
1033
|
+
langium,
|
|
1034
|
+
meta: {
|
|
1035
|
+
repo: info.repo,
|
|
1036
|
+
ref: info.ref,
|
|
1037
|
+
source: info.source,
|
|
1038
|
+
fetchedAt: info.fetchedAt ?? Date.now(),
|
|
1039
|
+
etag: info.etag,
|
|
1040
|
+
sha
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
async function bundled(reason, options) {
|
|
1045
|
+
const path = options.bundledLangiumPath ?? fileURLToPath(BUNDLED_LANGIUM);
|
|
1046
|
+
const snapshot = await bundledProvenance();
|
|
1047
|
+
const source = derive(await readFile(path, "utf8"), {
|
|
1048
|
+
repo: snapshot.repo ?? DEFAULT_REPO,
|
|
1049
|
+
ref: snapshot.ref ?? DEFAULT_REF,
|
|
1050
|
+
source: "bundled"
|
|
1051
|
+
});
|
|
1052
|
+
source.meta.reason = reason;
|
|
1053
|
+
return source;
|
|
1054
|
+
}
|
|
1055
|
+
async function bundledProvenance() {
|
|
1056
|
+
try {
|
|
1057
|
+
const { $generated } = JSON.parse(await readFile(fileURLToPath(BUNDLED_GRAMMAR), "utf8"));
|
|
1058
|
+
return { repo: $generated?.repo, ref: $generated?.ref };
|
|
1059
|
+
} catch {
|
|
1060
|
+
return {};
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
function grammarSha(source) {
|
|
1064
|
+
return createHash("sha256").update(source).digest("hex").slice(0, 12);
|
|
1065
|
+
}
|
|
1066
|
+
function validateRef(ref) {
|
|
1067
|
+
if (typeof ref !== "string" || ref.length === 0) {
|
|
1068
|
+
throw new GrammarRefError("the LikeC4 grammar `ref` must be a branch, tag or commit SHA");
|
|
1069
|
+
}
|
|
1070
|
+
if (!REF_PATTERN.test(ref) || ref.includes("..") || ref.endsWith("/") || ref.includes("//")) {
|
|
1071
|
+
throw new GrammarRefError(`"${ref}" is not a usable LikeC4 grammar ref — expected a branch, tag or commit SHA such as "main" or "v1.59.4"`);
|
|
1072
|
+
}
|
|
1073
|
+
return ref;
|
|
1074
|
+
}
|
|
1075
|
+
async function fetchLangiumSource(options) {
|
|
1076
|
+
const repo = options.repo;
|
|
1077
|
+
const ref = options.ref;
|
|
1078
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
1079
|
+
const url = `${baseUrl}/${repo}/${ref}/${LANGIUM_PATH}`;
|
|
1080
|
+
const timeout = options.timeout ?? 15000;
|
|
1081
|
+
const request = options.fetchImpl ?? fetch;
|
|
1082
|
+
const headers = { "user-agent": "astroc4", accept: "text/plain" };
|
|
1083
|
+
if (options.etag)
|
|
1084
|
+
headers["if-none-match"] = options.etag;
|
|
1085
|
+
let response;
|
|
1086
|
+
try {
|
|
1087
|
+
response = await request(url, { headers, redirect: "follow", signal: AbortSignal.timeout(timeout) });
|
|
1088
|
+
} catch (cause) {
|
|
1089
|
+
throw new GrammarFetchError(`could not fetch the LikeC4 grammar from ${url} — ${cause instanceof Error ? cause.message : String(cause)}. ` + "Builds are strict by default: pass `highlighting: { offline: true }` to use the bundled grammar instead.", { cause });
|
|
1090
|
+
}
|
|
1091
|
+
if (response.status === 304)
|
|
1092
|
+
return { notModified: true };
|
|
1093
|
+
if (response.status === 404) {
|
|
1094
|
+
throw new GrammarRefError(`${repo} has no "${ref}", or it no longer has ${LANGIUM_PATH} (${url}). ` + 'Use a tag such as "v1.59.4", a commit SHA, or "main".');
|
|
1095
|
+
}
|
|
1096
|
+
if (!response.ok) {
|
|
1097
|
+
throw new GrammarFetchError(`${url} responded ${response.status} ${response.statusText} — set ` + '`highlighting: { onError: "warn" }` to fall back to the bundled grammar.');
|
|
1098
|
+
}
|
|
1099
|
+
const text = await response.text();
|
|
1100
|
+
if (text.trim().length === 0) {
|
|
1101
|
+
throw new GrammarFetchError(`${url} returned an empty file — the grammar is not usable`);
|
|
1102
|
+
}
|
|
1103
|
+
return { text, etag: response.headers.get("etag") ?? undefined };
|
|
1104
|
+
}
|
|
1105
|
+
function cachePath(options, identity) {
|
|
1106
|
+
const dir = options.cacheDir ?? join(options.root ?? process.cwd(), "node_modules", ".cache", "astroc4");
|
|
1107
|
+
const key = createHash("sha256").update(`${identity.repo}@${identity.ref}`).digest("hex").slice(0, 16);
|
|
1108
|
+
return join(dir, `grammar-${key}.json`);
|
|
1109
|
+
}
|
|
1110
|
+
async function readCache(options, identity) {
|
|
1111
|
+
let raw;
|
|
1112
|
+
try {
|
|
1113
|
+
raw = await readFile(cachePath(options, identity), "utf8");
|
|
1114
|
+
} catch {
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
try {
|
|
1118
|
+
const entry = JSON.parse(raw);
|
|
1119
|
+
if (entry?.version !== CACHE_VERSION || entry.repo !== identity.repo || entry.ref !== identity.ref || typeof entry.langium !== "string" || typeof entry.fetchedAt !== "number") {
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
const ttl = options.cacheTtl ?? 3600;
|
|
1123
|
+
return { ...entry, fresh: Date.now() - entry.fetchedAt < ttl * 1000 };
|
|
1124
|
+
} catch {
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
async function writeCache(options, identity, entry) {
|
|
1129
|
+
const path = cachePath(options, identity);
|
|
1130
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
1131
|
+
try {
|
|
1132
|
+
await mkdir(join(path, ".."), { recursive: true });
|
|
1133
|
+
await writeFile(temporary, JSON.stringify({
|
|
1134
|
+
version: CACHE_VERSION,
|
|
1135
|
+
repo: identity.repo,
|
|
1136
|
+
ref: identity.ref,
|
|
1137
|
+
etag: entry.etag,
|
|
1138
|
+
fetchedAt: Date.now(),
|
|
1139
|
+
langium: entry.langium
|
|
1140
|
+
}), "utf8");
|
|
1141
|
+
await rename(temporary, path);
|
|
1142
|
+
} catch (error) {
|
|
1143
|
+
options.logger?.warn?.(`[astroc4] could not cache the LikeC4 grammar in ${path} — ${error instanceof Error ? error.message : String(error)}`);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
function isTruthy(value) {
|
|
1147
|
+
return typeof value === "string" && /^(1|true|yes|on)$/i.test(value.trim());
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// src/expressive-code.ts
|
|
1151
|
+
function registration(grammar) {
|
|
1152
|
+
return {
|
|
1153
|
+
...grammar,
|
|
1154
|
+
name: LANGUAGE_NAME,
|
|
1155
|
+
aliases: [...LANGUAGE_ALIASES]
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
var likec4Language = registration(likec4_tmLanguage_default);
|
|
1159
|
+
async function loadLikeC4Language(options) {
|
|
1160
|
+
const { grammar: resolved } = await resolveLikeC4Grammar(options);
|
|
1161
|
+
return registration(resolved);
|
|
1162
|
+
}
|
|
1163
|
+
var expressive_code_default = likec4Language;
|
|
1164
|
+
export {
|
|
1165
|
+
expressive_code_default as default,
|
|
1166
|
+
likec4Language,
|
|
1167
|
+
loadLikeC4Language
|
|
1168
|
+
};
|
|
1169
|
+
|
|
1170
|
+
//# debugId=AA3350C3BE6D0C0664756E2164756E21
|
|
1171
|
+
//# sourceMappingURL=expressive-code.js.map
|