@markup-carve/carve-grammars 0.1.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.
package/prism/carve.js ADDED
@@ -0,0 +1,471 @@
1
+ /**
2
+ * Prism.js grammar for the Carve markup language.
3
+ *
4
+ * Mirrors the canonical token set in markup-carve/carve
5
+ * (`resources/grammar.ebnf`) and the TextMate grammar in vscode-carve
6
+ * (`syntaxes/carve.tmLanguage.json`). Carve's inline delimiters differ from
7
+ * Markdown/Djot: emphasis is `/italic/`, `*bold*`, `_underline_`,
8
+ * `~strike~`, `=highlight=`, `^sup^`, `,sub,` (the doubled forms `==x==` /
9
+ * `,,x,,` are literal in Carve; the forced brace forms `{=x=}` / `{,x,}` /
10
+ * `{^x^}` render intraword).
11
+ *
12
+ * Usage (ESM):
13
+ *
14
+ * ```js
15
+ * import Prism from 'prismjs';
16
+ * import 'carve-grammars/prism/carve.js'; // registers Prism.languages.carve
17
+ *
18
+ * const html = Prism.highlight(src, Prism.languages.carve, 'carve');
19
+ * ```
20
+ *
21
+ * Usage (browser / bundler): load `prismjs` first (which sets the global
22
+ * `Prism`), then import this file for its side effect - it reads the global
23
+ * `Prism` (`globalThis` / `window` / `global`) and registers the grammar.
24
+ *
25
+ * @module carve-grammars/prism/carve
26
+ */
27
+ (function (Prism) {
28
+ if (!Prism) {
29
+ return;
30
+ }
31
+
32
+ // Inline attribute block: {#id .class key="val"} - reused by spans, divs,
33
+ // headings and extension calls.
34
+ // The payload is STRICT (spec PART 9 S14): a class/id/key identifier may not
35
+ // start with a digit, so `{2=v}` stays literal text rather than scoping as
36
+ // an attribute block. An unquoted value may contain dots and colons.
37
+ var attrItem = /(?:[.#][A-Za-z_][\w-]*|[A-Za-z_][\w:-]*(?:=(?:"(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'|[^\s"'{}]+))?)/.source;
38
+ // An EMPTY block is valid only glued to a preceding `]` (`[x]{}` ->
39
+ // <span>x</span>); a bare `{}` in prose is literal text (corpus 123).
40
+ // An EMPTY attribute block is valid only where it is glued to a preceding
41
+ // `]` (`[x]{}` -> <span>x</span>); a bare `{}` in prose is literal text
42
+ // (corpus 123). Prism tokenizes left to right, so the span and its empty
43
+ // block have to be ONE rule -- a lookbehind would lose its `]` to the span.
44
+ var spanEmptyAttrs = {
45
+ pattern: /\[[^\^\]][^\]]*\]\{\s*\}/,
46
+ inside: {
47
+ 'attr-value': { pattern: /\{\s*\}/, inside: { 'punctuation': /[{}]/ } },
48
+ 'string': /\[[^\]]*\]/,
49
+ },
50
+ };
51
+ var attributes = {
52
+ pattern: RegExp('\\{\\s*' + attrItem + '(?:\\s+' + attrItem + ')*\\s*\\}'),
53
+ alias: 'attr-value',
54
+ inside: {
55
+ 'id': /#[A-Za-z_][\w-]*/,
56
+ 'class-name': /\.[A-Za-z_][\w-]*/,
57
+ 'attr-name': /[A-Za-z_:][\w:-]*(?==)/,
58
+ 'string': /"[^"]*"|'[^']*'/,
59
+ 'punctuation': /[{}=]/,
60
+ },
61
+ };
62
+
63
+ // Shared inline emphasis/markup, referenced from block tokens that contain
64
+ // running text (headings, list items, table cells, quotes).
65
+ var inline = {
66
+ 'bold-italic': {
67
+ pattern: /\/\*(?=\S)[^*]+\*\//,
68
+ alias: 'important',
69
+ },
70
+ // The "no leading/trailing space" rule is expressed without JS
71
+ // lookbehind (unsupported on Safari < 16.4 and some engines): the first
72
+ // and last content chars are required to be non-space directly.
73
+ // Forced intraword family (PART 9 S22): `{*x*}` `{/x/}` `{_x_}` `{~x~}`.
74
+ // Content may contain the delimiter -- `{/a/b/}` is <em>a/b</em> -- so the
75
+ // run ends at the closing `X}`. These MUST precede 'attributes', or
76
+ // `{_path_}` reads as a boolean attribute instead of <u>path</u>.
77
+ 'forced-bold': {
78
+ pattern: /\{\*(?=\S)[^\n]*?\*\}/,
79
+ alias: 'bold',
80
+ },
81
+ 'forced-italic': {
82
+ pattern: /\{\/(?=\S)[^\n]*?\/\}/,
83
+ alias: 'italic',
84
+ },
85
+ 'forced-underline': {
86
+ pattern: /\{_(?=\S)[^\n]*?_\}/,
87
+ alias: 'underline',
88
+ },
89
+ 'forced-strike': {
90
+ pattern: /\{~(?=\S)(?:(?!~>)[^\n])*?~\}/,
91
+ alias: 'deleted',
92
+ },
93
+ 'bold': {
94
+ pattern: /\*[^*\s\n](?:[^*\n]*?[^*\s\n])?\*/,
95
+ alias: 'bold',
96
+ },
97
+ 'italic': {
98
+ // leading guard via Prism lookbehind (avoids URLs, paths); the
99
+ // trailing `(?![\w/])` lookahead is fine (lookahead is universal).
100
+ pattern: /(^|[^\w/])\/[^/\s\n](?:[^/\n]*?[^/\s\n])?\/(?![\w/])/,
101
+ lookbehind: true,
102
+ alias: 'italic',
103
+ },
104
+ 'underline': {
105
+ pattern: /(^|[^\w_])_[^_\s\n](?:[^_\n]*?[^_\s\n])?_(?![\w_])/,
106
+ lookbehind: true,
107
+ alias: 'underline',
108
+ },
109
+ 'strike': {
110
+ pattern: /~[^~\s\n](?:[^~\n]*?[^~\s\n])?~/,
111
+ alias: 'deleted',
112
+ },
113
+ 'highlight': {
114
+ pattern: /\{=(?=\S)[^\n]*?=\}|(?<![\w=])=(?=\S)[^=\n]+?(?<=\S)=(?![\w=])/,
115
+ alias: 'important',
116
+ },
117
+ // Braced-only: a bare `^` / `,` is literal text (no bare sup/sub).
118
+ 'superscript': {
119
+ pattern: /\{\^(?=\S)[^\n]*?\^\}/,
120
+ alias: 'important',
121
+ },
122
+ 'subscript': {
123
+ pattern: /\{,(?=\S)[^\n]*?,\}/,
124
+ alias: 'important',
125
+ },
126
+ };
127
+
128
+ Prism.languages.carve = {
129
+ // Block comments %%% ... %%% and line comments %% ...
130
+ // The comment fence is a *bare* %%% line; a `%%% format` opener is a
131
+ // raw passthrough block instead (handled by #raw-block below).
132
+ 'comment': [
133
+ {
134
+ pattern: /^[ \t]*%%%[ \t]*\n[\s\S]*?^[ \t]*%%%[ \t]*$/m,
135
+ greedy: true,
136
+ },
137
+ {
138
+ pattern: /^[ \t]*%%(?!%).*$/m,
139
+ greedy: true,
140
+ },
141
+ {
142
+ // trailing comment after whitespace (Prism lookbehind, no JS
143
+ // lookbehind: the leading space is captured and excluded).
144
+ pattern: /([ \t])%%.*$/m,
145
+ lookbehind: true,
146
+ greedy: true,
147
+ },
148
+ ],
149
+
150
+ // YAML/typed front matter delimited by --- at the very top of the file.
151
+ // `^` (no `m` flag) anchors to the start of the document; the close is
152
+ // matched at end-of-line so a document body may follow.
153
+ 'front-matter': {
154
+ pattern: /^---[ \t]*[A-Za-z0-9_-]*[ \t]*\n[\s\S]*?\n---[ \t]*(?:\n|$)/,
155
+ alias: 'comment',
156
+ greedy: true,
157
+ inside: {
158
+ 'punctuation': /---/,
159
+ },
160
+ },
161
+
162
+ // Fenced code blocks: ``` lang ... ``` or ~~~ lang ... ~~~
163
+ 'code-block': {
164
+ pattern: /^(`{3,}|~{3,})[ \t]*[^\n]*\n[\s\S]*?^\1[ \t]*$/m,
165
+ greedy: true,
166
+ inside: {
167
+ 'punctuation': /^(?:`{3,}|~{3,})|(?:`{3,}|~{3,})$/,
168
+ 'language': {
169
+ pattern: /(^(?:`{3,}|~{3,})[ \t]*)[^\s`~]+/,
170
+ lookbehind: true,
171
+ alias: 'class-name',
172
+ },
173
+ },
174
+ },
175
+
176
+ // Raw passthrough blocks: %%% format ... %%% (rendered verbatim)
177
+ 'raw-block': {
178
+ pattern: /^%{3,}[ \t]*\S*[ \t]*\n[\s\S]*?^%{3,}[ \t]*$/m,
179
+ greedy: true,
180
+ alias: 'string',
181
+ inside: {
182
+ 'punctuation': /^%{3,}|%{3,}$/,
183
+ 'language': {
184
+ pattern: /(^%{3,}[ \t]*)\S+/,
185
+ lookbehind: true,
186
+ alias: 'class-name',
187
+ },
188
+ },
189
+ },
190
+
191
+ // ATX headings # .. ######
192
+ 'title': {
193
+ pattern: /^#{1,6}[ \t]+.+$/m,
194
+ alias: 'important',
195
+ inside: Object.assign({
196
+ 'punctuation': /^#{1,6}/,
197
+ }, inline),
198
+ },
199
+
200
+ // Container divs ::: class / :::
201
+ // Strict opener shapes only: type word, optional "title" (straight
202
+ // quotes), optional [label], the | / \ layout tokens, or a typeless
203
+ // [label]. Trailing junk makes the line a paragraph, so it must not
204
+ // highlight as a fence.
205
+ 'div': {
206
+ pattern: /^[ \t]*:{3,}(?:[ \t]*(?:\||\\)|[ \t]*[a-zA-Z_][\w-]*(?:[ \t]+"[^"\n]*")?(?:[ \t]+\[[^\]\n]*\])?|[ \t]*\[[^\]\n]*\])?[ \t]*$/m,
207
+ alias: 'tag',
208
+ inside: {
209
+ 'punctuation': /:{3,}/,
210
+ 'string': /"[^"\n]*"/,
211
+ 'symbol': /\[[^\]\n]*\]/,
212
+ 'class-name': {
213
+ pattern: /(^[ \t]*:{3,}[ \t]*)(?:[a-zA-Z_][\w-]*|\||\\)/,
214
+ lookbehind: true,
215
+ },
216
+ },
217
+ },
218
+
219
+ // Table rows: | a | b | (plus header `|=`, caption `^`, span markers)
220
+ 'table': {
221
+ pattern: /^[ \t]*\|.*$/m,
222
+ inside: Object.assign({
223
+ // rowspan `^` / colspan `<` markers - must precede `punctuation`
224
+ // so the surrounding `|` is not consumed first.
225
+ 'operator': {
226
+ pattern: /(\|)[ \t]*[\^<](?=[ \t]*\|)/,
227
+ lookbehind: true,
228
+ },
229
+ 'punctuation': /\|=|\|/,
230
+ 'attributes': attributes,
231
+ 'url': /\[[^\]]+\]\([^\s)]+\)/,
232
+ }, inline),
233
+ },
234
+
235
+ // Table continuation / list continuation: a lone `+`
236
+ 'table-continuation': {
237
+ pattern: /^[ \t]*\+[ \t]*$/m,
238
+ alias: 'punctuation',
239
+ },
240
+
241
+ // Blockquotes: leading > (possibly nested >>)
242
+ 'blockquote': {
243
+ pattern: /^[ \t]*>+[ \t]?.*$/m,
244
+ inside: Object.assign({
245
+ 'punctuation': /^[ \t]*>+/,
246
+ }, inline),
247
+ },
248
+
249
+ // List markers: -, *, ordered (1. a) i.), task [ ]/[x], definition `: `
250
+ 'list': {
251
+ pattern: /^[ \t]*(?:(?:[-*][ \t]+)*[-*][ \t]+(?:\[[ xX]\][ \t]+)?|(?:[0-9]+|[A-Za-z]|[ivxlcdmIVXLCDM]+)[.)][ \t]+|:[ \t]+)/m,
252
+ alias: 'punctuation',
253
+ inside: {
254
+ 'constant': /\[[ xX]\]/,
255
+ },
256
+ },
257
+
258
+ // Reference link / abbreviation definitions
259
+ 'reference-definition': {
260
+ pattern: /^[ \t]*\[[^\]]+\]:[ \t]+\S+.*$/m,
261
+ alias: 'url',
262
+ inside: {
263
+ 'constant': /^[ \t]*\[[^\]]+\]:/,
264
+ },
265
+ },
266
+ 'abbreviation-definition': {
267
+ pattern: /^[ \t]*\*\[[A-Z][A-Z0-9]*\]:[ \t]+.*$/m,
268
+ inside: {
269
+ 'punctuation': /^[ \t]*\*|\[|\]|:/,
270
+ 'symbol': /[A-Z][A-Z0-9]*/,
271
+ },
272
+ },
273
+
274
+ // Display + inline math: $$`...`$$ and $`...`$
275
+ 'math': [
276
+ {
277
+ pattern: /\$\$(`+)[\s\S]*?\1\$\$/,
278
+ greedy: true,
279
+ alias: 'string',
280
+ },
281
+ {
282
+ pattern: /\$(`+)[\s\S]*?\1\$/,
283
+ greedy: true,
284
+ alias: 'string',
285
+ },
286
+ ],
287
+
288
+ // Raw inline passthrough: `code`{=format}
289
+ 'raw-inline': {
290
+ pattern: /(`+)(?:[^`]|[^`][\s\S]*?[^`])\1\{=[A-Za-z_][\w-]*\}/,
291
+ greedy: true,
292
+ alias: 'string',
293
+ },
294
+
295
+ // Inline code spans
296
+ 'code': {
297
+ pattern: /(`+)(?:[^`]|[^`][\s\S]*?[^`])\1/,
298
+ greedy: true,
299
+ },
300
+
301
+ // Images: ![alt](src "title"); the title may contain
302
+ // backslash-escaped quotes like the link title.
303
+ 'image': {
304
+ pattern: /!\[[^\]]*\]\([^\s)]+(?:[ \t]+"(?:[^"\\]|\\[\s\S])*")?\)/,
305
+ greedy: true,
306
+ alias: 'url',
307
+ inside: {
308
+ 'string': /"(?:[^"\\]|\\[\s\S])*"/,
309
+ 'punctuation': /!\[|\]\(|\)/,
310
+ },
311
+ },
312
+
313
+ // Footnote references: [^label]
314
+ 'footnote': {
315
+ pattern: /\[\^[^\]]+\]/,
316
+ alias: 'symbol',
317
+ },
318
+
319
+ // Citations (Tier-2 §22): [@key], [+@key], [@key, p.10], [@a; @b; @c]
320
+ // Distinguished from links/spans/refs by the absence of a trailing
321
+ // `(url)`, `[ref]`, or `{attrs}` suffix. The bracket MUST contain at
322
+ // least one `@key` item.
323
+ 'citation': {
324
+ pattern: /\[\+?(?:[^\]@]*@[A-Za-z0-9_][A-Za-z0-9_.:#$%&+?<>~\/-]*(?:[^\]]*)?)\](?!\(|\[|\{)/,
325
+ greedy: true,
326
+ alias: 'string',
327
+ inside: {
328
+ // Integral marker `+` and suppress-author `-`
329
+ 'operator': /(?<=^\[)\+|(?<=[@;]\s*)-(?=@)/,
330
+ // The `@key` itself
331
+ 'function': /@[A-Za-z0-9_][A-Za-z0-9_.:#$%&+?<>~\/-]*/,
332
+ // Separators and locator punctuation
333
+ 'punctuation': /\[|\]|;|,/,
334
+ },
335
+ },
336
+
337
+ // Code callouts (Tier-2 §10): <n> markers trailing a code-fence line
338
+ // or leading a callout-list item. Only pure-digit content.
339
+ 'code-callout': {
340
+ pattern: /<\d+>/,
341
+ alias: 'symbol',
342
+ },
343
+
344
+ // Inline links: [text](url "title") and reference [text][id]
345
+ 'url': [
346
+ {
347
+ // The link text may be empty ([](url), spec corpus 03-links-8)
348
+ // and the title may contain backslash-escaped quotes:
349
+ // [t](/url "ti\"tle") (spec corpus 03-links-4).
350
+ pattern: /\[[^\]]*\]\([^\s)]+(?:[ \t]+"(?:[^"\\]|\\[\s\S])*")?\)/,
351
+ greedy: true,
352
+ inside: {
353
+ 'string': /"(?:[^"\\]|\\[\s\S])*"/,
354
+ 'punctuation': /\[|\]\(|\)/,
355
+ },
356
+ },
357
+ {
358
+ pattern: /\[[^\]]+\]\[[^\]]*\]/,
359
+ greedy: true,
360
+ inside: {
361
+ 'punctuation': /\[|\]\[|\]/,
362
+ },
363
+ },
364
+ {
365
+ // autolink <https://...> and <mailto-ish>
366
+ pattern: /<[a-zA-Z][a-zA-Z0-9+.-]*:[^>\s]+>|<[^>\s@]+@[^>\s]+>/,
367
+ greedy: true,
368
+ },
369
+ ],
370
+
371
+ // An EMPTY attribute block, only where it is glued to a preceding `]`
372
+ // (`[x]{}` -> <span>x</span>). A bare `{}` in prose is literal text
373
+ // (corpus 123). Must precede 'span', which would otherwise consume the
374
+ // `]` this rule anchors on.
375
+ 'span-empty-attrs': spanEmptyAttrs,
376
+
377
+ // Bracketed span with attributes: [text]{.class}
378
+ 'span': {
379
+ pattern: /\[[^\^\]][^\]]*\](?=\{)/,
380
+ alias: 'string',
381
+ },
382
+
383
+ // Extension inline call: :name[content]{attrs}
384
+ 'extension': {
385
+ pattern: /:[a-zA-Z][\w-]*\[[^\]]*\](?:\{[^}]*\})?/,
386
+ alias: 'function',
387
+ inside: {
388
+ 'function': /:[a-zA-Z][\w-]*/,
389
+ 'attributes': attributes,
390
+ 'punctuation': /\[|\]/,
391
+ },
392
+ },
393
+
394
+ // CriticMarkup: {+ins+} {-del-} {~old~>new~} {#comment#}
395
+ 'inserted': {
396
+ pattern: /\{\+[^}]*\+\}/,
397
+ alias: 'inserted',
398
+ },
399
+ 'deleted': {
400
+ pattern: /\{-[^}]*-\}/,
401
+ alias: 'deleted',
402
+ },
403
+ 'changed': {
404
+ pattern: /\{~[^~]*~>[^~]*~\}/,
405
+ alias: 'important',
406
+ },
407
+ 'critic-comment': {
408
+ pattern: /\{#[^}]*#\}/,
409
+ alias: 'comment',
410
+ },
411
+
412
+ // Forced brace emphasis must beat the attribute rule (`{_path_}`).
413
+ 'forced-bold': inline['forced-bold'],
414
+ 'forced-italic': inline['forced-italic'],
415
+ 'forced-underline': inline['forced-underline'],
416
+ 'forced-strike': inline['forced-strike'],
417
+
418
+ // Attribute blocks attached to the preceding element
419
+ 'attributes': attributes,
420
+
421
+ // Inline emphasis family (must come after code/links/attributes)
422
+ 'bold-italic': inline['bold-italic'],
423
+ 'bold': inline['bold'],
424
+ 'italic': inline['italic'],
425
+ 'underline': inline['underline'],
426
+ 'strike': inline['strike'],
427
+ 'highlight': inline['highlight'],
428
+ 'superscript': inline['superscript'],
429
+ 'subscript': inline['subscript'],
430
+
431
+ // Mentions @name, tags #tag, symbols :name:
432
+ 'mention': {
433
+ pattern: /(^|[^\w.])@[A-Za-z0-9_][\w-]*/,
434
+ lookbehind: true,
435
+ alias: 'variable',
436
+ },
437
+ 'tag': {
438
+ pattern: /(^|[^\w])#[A-Za-z0-9_][\w-]*/,
439
+ lookbehind: true,
440
+ alias: 'variable',
441
+ },
442
+ 'symbol': {
443
+ // Symbol shortcode (e.g. emoji). Parser shape: name starts
444
+ // alphanumeric, then word chars, `+` or `-` (`:+1:` stays literal).
445
+ pattern: /(^|[^\w]):[A-Za-z0-9+-][\w+-]*:/,
446
+ lookbehind: true,
447
+ alias: 'constant',
448
+ },
449
+
450
+ // Escapes and smart typography
451
+ 'escape': {
452
+ pattern: /\\[\\`*_{}[\]()#+\-.!~^/<>@%|=,]/,
453
+ alias: 'constant',
454
+ },
455
+ 'typography': {
456
+ pattern: /\.\.\.|---|--|<->|<-|->|=>|!=|<=|>=|\+-|\(c\)|\(r\)|\(tm\)/,
457
+ alias: 'constant',
458
+ },
459
+ };
460
+
461
+ // Allow Carve to be embedded and to embed itself (e.g. inside ```carve).
462
+ Prism.languages.carvemd = Prism.languages.carve;
463
+ })(
464
+ (typeof globalThis !== 'undefined' && globalThis.Prism)
465
+ ? globalThis.Prism
466
+ : (typeof window !== 'undefined' && window.Prism)
467
+ ? window.Prism
468
+ : (typeof global !== 'undefined' && global.Prism)
469
+ ? global.Prism
470
+ : undefined
471
+ );
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Companion CSS for carve-grammars/shiki.
3
+ *
4
+ * Applies the Shiki dual-theme CSS variables (VitePress emits them but
5
+ * only applies color by default) and styles the data-carve-* attributes
6
+ * emitted by the carveStylingTransformer.
7
+ */
8
+
9
+ /* Apply Shiki font-style, font-weight and text-decoration CSS variables */
10
+ .vp-code span {
11
+ font-style: var(--shiki-light-font-style, inherit);
12
+ font-weight: var(--shiki-light-font-weight, inherit);
13
+ text-decoration: var(--shiki-light-text-decoration, inherit);
14
+ }
15
+
16
+ .dark .vp-code span {
17
+ font-style: var(--shiki-dark-font-style, inherit);
18
+ font-weight: var(--shiki-dark-font-weight, inherit);
19
+ text-decoration: var(--shiki-dark-text-decoration, inherit);
20
+ }
21
+
22
+ /* Carve token styling the Shiki HTML emitter can't express directly.
23
+ Use html prefix to outrank `html.dark .vp-code span` (specificity 0,2,2). */
24
+ html .vp-code span[data-carve-strike] {
25
+ text-decoration: line-through;
26
+ }
27
+
28
+ html .vp-code span[data-carve-super] {
29
+ vertical-align: super;
30
+ font-size: 0.75em;
31
+ }
32
+
33
+ html .vp-code span[data-carve-sub] {
34
+ vertical-align: sub;
35
+ font-size: 0.75em;
36
+ }
37
+
38
+ html .vp-code span[data-carve-highlight] {
39
+ background: rgba(255, 213, 0, 0.35);
40
+ padding: 0 2px;
41
+ border-radius: 2px;
42
+ }
43
+
44
+ html.dark .vp-code span[data-carve-highlight] {
45
+ background: rgba(255, 213, 0, 0.18);
46
+ }