atdoc-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,915 @@
1
+ # @Doc Inline Syntax Specification v1.4
2
+
3
+ > ๐ŸŒ Other languages: [Traditional Chinese](https://github.com/WEDC-Studio-Official/AtDoc/blob/main/docs/zh-tw/Inline-Syntax-Specification.md) ใƒป [Simplified Chinese](https://github.com/WEDC-Studio-Official/AtDoc/blob/main/docs/zh-cn/Inline-Syntax-Specification.md) ใƒป [Japanese (AI translation; may contain errors)](https://github.com/WEDC-Studio-Official/AtDoc/blob/main/docs/ja/Inline-Syntax-Specification.md) ใƒป [Korean (AI translation; may contain errors)](https://github.com/WEDC-Studio-Official/AtDoc/blob/main/docs/ko/Inline-Syntax-Specification.md)
4
+
5
+ ## 0. Table of Contents
6
+
7
+ * [1. Design Philosophy](#1-design-philosophy)
8
+ * [2. Lexer Behavior Definition](#2-lexer-behavior-definition)
9
+ * [3. Ambiguity Resolution Rule](#3-ambiguity-resolution-rule)
10
+ * [4. Complete EBNF Grammar Definition](#4-complete-ebnf-grammar-definition)
11
+ * [5. Escape Rule](#5-escape-rule)
12
+ * [6. Unknown Command Fallback](#6-unknown-command-fallback)
13
+ * [7. @mark / @color / @bordered Styles Semantics](#7-mark--color--bordered-styles-semantics)
14
+ * [8. @link URI Semantics](#8-link-uri-semantics)
15
+ * [9. @raw Opaque Domain](#9-raw-opaque-domain)
16
+ * [10. Nested Parsing](#10-nested-parsing)
17
+ * [11. Parser Recovery Strategy](#11-parser-recovery-strategy)
18
+ * [12. Architecture](#12-architecture)
19
+ * [13. Core Principle](#13-core-principle)
20
+ * [14. Simplified Syntax Aliases](#14-simplified-syntax-aliases)
21
+
22
+ ---
23
+
24
+
25
+ ## 1. Design Philosophy
26
+
27
+ @Doc adopts:
28
+
29
+ > **Only Known Commands Trigger Parsing**
30
+
31
+ Only known commands carry grammatical meaning.
32
+
33
+ Unknown commands are always treated as plain text.
34
+
35
+ The goals of this design:
36
+
37
+ * Lower the learning cost
38
+ * Avoid conflicts with email and mention systems
39
+ * Improve AI parsing stability
40
+ * Improve editor fault tolerance
41
+ * Preserve the DSL's extensibility
42
+ * Establish a stable and predictable AST
43
+
44
+ ---
45
+
46
+ ## 2. Lexer Behavior Definition
47
+
48
+ ### Command Parsing Rules
49
+
50
+ When the Lexer scans an `@`, it should process it in the following priority order:
51
+
52
+ 1. If followed by `@@`
53
+
54
+ * Parse it as a single literal `@` character
55
+
56
+ 2. If what follows matches a registered command name
57
+
58
+ * Enter the corresponding grammar-parsing flow
59
+
60
+ 3. If it matches no known command
61
+
62
+ * Output the entire span as plain text
63
+
64
+ ---
65
+
66
+ ### Examples
67
+
68
+ | Input | Result |
69
+ | ------------------ | ------------- |
70
+ | `@mark[hello]` | Parsed as a `mark` node |
71
+ | `@@mark` | Outputs `@mark` |
72
+ | `test@example.com` | Plain text |
73
+ | `@GitHub` | Plain text |
74
+ | `@unknown` | Plain text |
75
+
76
+ ---
77
+
78
+ ## 3. Ambiguity Resolution Rule
79
+
80
+ Since @Doc adopts:
81
+
82
+ > Known Command Recognition
83
+
84
+ the Lexer must first attempt to recognize known commands before falling back to plain-text mode.
85
+
86
+ In other words:
87
+
88
+ > `inline-node` takes precedence over `plain-text-char`.
89
+
90
+ The Lexer must follow:
91
+
92
+ ```text
93
+ Starts with @
94
+ โ†“
95
+ Is it @@ ?
96
+ โ†“
97
+ Does it exist in the Command Registry ?
98
+ โ†“
99
+ Yes โ†’ Inline Node
100
+ No โ†’ Plain Text
101
+ ```
102
+
103
+ Therefore:
104
+
105
+ ```text
106
+ @mark[hello]
107
+ ```
108
+
109
+ must be parsed as:
110
+
111
+ ```text
112
+ InlineNode(mark)
113
+ ```
114
+
115
+ rather than:
116
+
117
+ ```text
118
+ Text('@')
119
+ Text('m')
120
+ Text('a')
121
+ Text('r')
122
+ Text('k')
123
+ ...
124
+ ```
125
+
126
+ ---
127
+
128
+ ## 4. Complete EBNF Grammar Definition
129
+
130
+ ```ebnf
131
+ (* ==========================================================================
132
+ Entry Point
133
+ ========================================================================== *)
134
+
135
+ inline-stream =
136
+ { inline-node | plain-text-char } ;
137
+
138
+ inline-node =
139
+ mark
140
+ | color
141
+ | bordered
142
+ | bold
143
+ | italic
144
+ | underline
145
+ | del
146
+ | raw
147
+ | sup
148
+ | sub
149
+ | fn
150
+ | defn
151
+ | kbd
152
+ | link
153
+ | br
154
+ | escape ;
155
+
156
+ (* ==========================================================================
157
+ Inline Nodes
158
+ ========================================================================== *)
159
+
160
+ mark = "@mark" , [ styles ] , content ;
161
+ color = "@color" , [ styles ] , content ;
162
+ (* @bordered shares @color's exact {styles} slot and swatch (see ยง7),
163
+ applied as a text border instead of a foreground color. *)
164
+ bordered = "@bordered" , [ styles ] , content ;
165
+ bold = ( "@bold" | "@b" ) , content ;
166
+ italic = ( "@italic" | "@i" ) , content ;
167
+ underline = ( "@underline" | "@u" ) , content ;
168
+ del = "@del" , content ;
169
+
170
+ raw = "@raw" , raw-content ;
171
+
172
+ sup = "@sup" , content ;
173
+ sub = "@sub" , content ;
174
+
175
+ (* Footnotes:
176
+ fn = the in-text reference marker (superscript), carries only the number
177
+ defn = the footnote definition body, carries the number and the actual content
178
+ *)
179
+ fn = "@fn" , "[" , integer , "]" ;
180
+ defn = "@defn" , modifier , content ;
181
+
182
+ kbd = "@kbd" , "[" , key , "]" ;
183
+
184
+ link = "@link" , uri , content ;
185
+
186
+ br = "@n" ;
187
+
188
+ escape = "@@" ;
189
+
190
+ (* ==========================================================================
191
+ Shared Components
192
+ ========================================================================== *)
193
+
194
+ content =
195
+ "[" ,
196
+ { content-element } ,
197
+ "]" ;
198
+
199
+ content-element =
200
+ inline-node
201
+ | plain-text-char ;
202
+
203
+ (* The actual termination rule for raw-content is "bracket-depth counting,"
204
+ not "reaching the first unescaped ]" โ€” balanced-bracket-group uses a
205
+ recursive production to express "as long as brackets are paired inside,
206
+ they can nest freely with no escaping needed at all"; only truly
207
+ unpaired brackets need to be escaped. See 9. @raw Opaque Domain for
208
+ details. *)
209
+ raw-content =
210
+ "[" , { raw-unit } , "]" ;
211
+
212
+ raw-unit =
213
+ escaped-at-close-bracket (* "@@]" โ†’ literal "@]" *)
214
+ | escaped-at-open-bracket (* "@@[" โ†’ literal "@[" *)
215
+ | escaped-close-bracket (* "@]" โ†’ literal "]" (only used for an unpaired ]) *)
216
+ | escaped-open-bracket (* "@[" โ†’ literal "[" (only used for an unpaired [) *)
217
+ | balanced-bracket-group (* paired, nestable literal brackets, content unrestricted *)
218
+ | raw-char ;
219
+
220
+ balanced-bracket-group =
221
+ "[" , { raw-unit } , "]" ;
222
+
223
+ escaped-at-close-bracket = "@@]" ;
224
+ escaped-at-open-bracket = "@@[" ;
225
+ escaped-close-bracket = "@]" ;
226
+ escaped-open-bracket = "@[" ;
227
+
228
+ raw-char =
229
+ any-unicode-char - "]" - "[" ;
230
+
231
+ uri =
232
+ "(" ,
233
+ { text-char - ")" } ,
234
+ ")" ;
235
+
236
+ modifier =
237
+ "(" ,
238
+ { text-char - ")" } ,
239
+ ")" ;
240
+
241
+ (* Additional Lexer restriction: `text-char` itself includes newlines, so a
242
+ literal reading would mean "an unclosed "{" can swallow everything all
243
+ the way to any "}" later in the document" โ€” a "{" the author is still
244
+ typing would swallow every node in between (e.g. everything before the
245
+ curly brace in the @code block below) whole into styles, silently
246
+ vanishing from the AST. The actual semantics of styles are a short,
247
+ comma-separated list of tokens with no example ever spanning multiple
248
+ lines, and the editor's Monarch rule (/\{[^}]*\}/, matched line by line)
249
+ doesn't support spanning lines either โ€” so the Lexer stops scanning at
250
+ whichever of "}", end of line, or "[" (start of a content slot) comes
251
+ first; both end-of-line and "[" are treated as unclosed. See
252
+ scanStylesEnd() in src/Lexer.ts. *)
253
+ styles =
254
+ "{" ,
255
+ { text-char - "}" - newline - "[" } ,
256
+ "}" ;
257
+
258
+ key =
259
+ { text-char - "]" } ;
260
+
261
+ (* @color's semantic constraint on its {styles} content โ€” see ยง7 for the
262
+ full validation rule (must match /^#[0-9a-fA-F]{6}$/); the terminal itself
263
+ is grammar-level only, exact digit-count/case validation is semantic-level,
264
+ same split as `styles` below. *)
265
+ hex-color =
266
+ "#" , hex-digit , hex-digit , hex-digit , hex-digit , hex-digit , hex-digit ;
267
+
268
+ hex-digit =
269
+ digit
270
+ | "a" | "b" | "c" | "d" | "e" | "f"
271
+ | "A" | "B" | "C" | "D" | "E" | "F" ;
272
+
273
+ integer =
274
+ digit ,
275
+ { digit } ;
276
+
277
+ digit =
278
+ "0" | "1" | "2" | "3" | "4"
279
+ | "5" | "6" | "7" | "8" | "9" ;
280
+
281
+ (* ==========================================================================
282
+ Character Sets
283
+ ========================================================================== *)
284
+
285
+ (* Note:
286
+ plain-text-char has lower precedence than inline-node.
287
+
288
+ The lexer MUST always attempt known command recognition
289
+ before falling back to plain text.
290
+ *)
291
+
292
+ plain-text-char =
293
+ any-unicode-char ;
294
+
295
+ text-char =
296
+ any-unicode-char ;
297
+
298
+ letter =
299
+ Unicode Letter ;
300
+
301
+ symbol =
302
+ Unicode Symbol ;
303
+ ```
304
+
305
+ ---
306
+
307
+ ## 5. Escape Rule
308
+
309
+ ### Syntax
310
+
311
+ ```text
312
+ @@
313
+ ```
314
+
315
+ ### Output
316
+
317
+ ```text
318
+ @
319
+ ```
320
+
321
+ ### Purpose
322
+
323
+ Used when the user needs to output a syntax keyword itself.
324
+
325
+ > This is a **global escape rule**, applicable in the general inline-stream context.
326
+ > `@raw` has its own independent escaping rules internally; see [9. @raw Opaque Domain](#9-raw-opaque-domain).
327
+
328
+ ---
329
+
330
+ ### Examples
331
+
332
+ Input:
333
+
334
+ ```text
335
+ @@mark
336
+ ```
337
+
338
+ Output:
339
+
340
+ ```text
341
+ @mark
342
+ ```
343
+
344
+ ---
345
+
346
+ Input:
347
+
348
+ ```text
349
+ @@bold[hello]
350
+ ```
351
+
352
+ Output:
353
+
354
+ ```text
355
+ @bold[hello]
356
+ ```
357
+
358
+ ---
359
+
360
+ Input:
361
+
362
+ ```text
363
+ Email: test@@example.com
364
+ ```
365
+
366
+ Output:
367
+
368
+ ```text
369
+ Email: test@example.com
370
+ ```
371
+
372
+ Although this form is valid, since:
373
+
374
+ ```text
375
+ example
376
+ ```
377
+
378
+ is not a known command, in practice you can simply write:
379
+
380
+ ```text
381
+ Email: test@example.com
382
+ ```
383
+
384
+ without needing to escape it.
385
+
386
+ ---
387
+
388
+ ## 6. Unknown Command Fallback
389
+
390
+ If what follows `@` is not a valid command name, the parser must fall back to plain-text mode.
391
+
392
+ Example:
393
+
394
+ ```text
395
+ @github
396
+ ```
397
+
398
+ Output:
399
+
400
+ ```text
401
+ @github
402
+ ```
403
+
404
+ ---
405
+
406
+ ```text
407
+ test@example.com
408
+ ```
409
+
410
+ Output:
411
+
412
+ ```text
413
+ test@example.com
414
+ ```
415
+
416
+ ---
417
+
418
+ ```text
419
+ @my_custom_tag
420
+ ```
421
+
422
+ Output:
423
+
424
+ ```text
425
+ @my_custom_tag
426
+ ```
427
+
428
+ ---
429
+
430
+ This rule effectively prevents conflicts with:
431
+
432
+ * Email
433
+ * Social media handles
434
+ * Discord Mention
435
+ * GitHub Username
436
+ * Chat Mention System
437
+
438
+ from occurring.
439
+
440
+ ---
441
+
442
+ ## 7. @mark / @color / @bordered Styles Semantics
443
+
444
+ `@mark` supports an optional `styles` modifier syntax:
445
+
446
+ ```text
447
+ @mark{style}[content]
448
+ ```
449
+
450
+ where:
451
+
452
+ * `style` is a comma-separated style token string (style token list).
453
+ * `content` is the text content being marked.
454
+ * `styles` is **optional** syntax; when omitted, it is equivalent to a plain highlight mark:
455
+
456
+ ```text
457
+ @mark[important content]
458
+ ```
459
+
460
+ ---
461
+
462
+ ### Style Token Semantics
463
+
464
+ The content of `style` is a comma-separated **Color Token** string, representing the highlight color; the renderer maps it semantically to an actual color value. Two forms are supported, either of which may be used:
465
+
466
+ * **Named tokens** (the renderer defines the actual color values):
467
+
468
+ ```text
469
+ yellow / red / green / blue / orange / purple / gray
470
+ ```
471
+
472
+ * **Hexadecimal tokens** (starting with `#`, followed by 6 hex digits, case-insensitive; the renderer MUST use the specified value directly and MUST NOT remap it):
473
+
474
+ ```text
475
+ #ff0000 / #3366FF / #00c896
476
+ ```
477
+
478
+ A token that does not match `/^#[0-9a-fA-F]{6}$/` (e.g. `#f00`, `#gggggg`) is not considered a valid hex token,
479
+ and follows the general fault-tolerant spirit of Unknown Command Fallback (see Renderer Behavior below).
480
+
481
+ > **Changelog**: An earlier version separately defined three modifier tokens, `underline`/`strikethrough`/`bordered`, which have been removed. `underline` duplicated the semantics of the `@underline` node; `strikethrough` duplicated the semantics of the `@del` node; `bordered` has been promoted to its own independent node, `@bordered` (see below). `style` now only carries color semantics, and no longer mixes in modifier semantics.
482
+
483
+ ---
484
+
485
+ ### Examples
486
+
487
+ ```text
488
+ @mark[default highlight]
489
+ @mark{yellow}[yellow highlight]
490
+ @mark{red}[red highlight]
491
+ @mark{#3366ff}[hex background color]
492
+ ```
493
+
494
+ ---
495
+
496
+ ### @color โ€” Changing Text Color
497
+
498
+ `@mark` changes the **background** (highlight) and cannot change the color of the text itself. `@color` fills this gap:
499
+
500
+ ```text
501
+ @color{#ff0000}[This text is red]
502
+ ```
503
+
504
+ `@color` shares the same `{styles}` field as `@mark` (see the EBNF above), and is itself **optional** โ€”
505
+ when omitted, the renderer falls back to a default color, behaving the same way `@mark[content]` does when `{styles}` is omitted.
506
+
507
+ ```text
508
+ @color{blue}[This text is dark blue]
509
+ ```
510
+
511
+ `@color` accepts the same seven named color tokens as `@mark` (`yellow`/`red`/`green`/`blue`/
512
+ `orange`/`purple`/`gray`), and also accepts a single hexadecimal token (`/^#[0-9a-fA-F]{6}$/`).
513
+ Both share the same set of token names syntactically, but **the actual color values they map to
514
+ are independent**: `@mark`'s color scale is tuned for light highlight backgrounds, and using it
515
+ directly as a text foreground color would have insufficient contrast and be hard to read, so
516
+ renderers typically maintain a separate, darker-toned lookup table specifically for `@color`
517
+ (rather than reusing `@mark`'s). The renderer MUST ignore malformed or unrecognized values and
518
+ fall back to some default value, rather than throwing an error:
519
+
520
+ ```text
521
+ @color{not-a-color}[This text has no color specified, and gracefully falls back to the default color]
522
+ ```
523
+
524
+ > [!IMPORTANT]
525
+ > **The old syntax has been deprecated**: an earlier version of `@color` used a required
526
+ > `(hex-color)` parenthesized form (`@color(#ff0000)[...]`). That syntax has been removed โ€”
527
+ > and not in the sense of "ignore and fall back to the default"; instead, the Parser MUST throw
528
+ > a syntax error directly (Strict Mode) or flag it as a diagnostic (Editor Mode) โ€” because the
529
+ > parenthesized form would make the author believe the color had taken effect, when it had
530
+ > actually silently applied the default color. This "looks like it succeeded but actually
531
+ > didn't" gap is more dangerous than failing loudly, so it does not fall under the
532
+ > fault-tolerant spirit of [6. Unknown Command Fallback](#6-unknown-command-fallback).
533
+
534
+ ---
535
+
536
+ ### @bordered โ€” Text Border
537
+
538
+ `@bordered` adds a border around text, sharing exactly the same `{styles}` field as `@color` โ€”
539
+ the same braces, equally optional, the same seven named tokens plus hex, and the same
540
+ color-swatch lookup table (in implementation, `@color`'s resolver can be reused directly) โ€” the
541
+ only difference is that it's applied to the **border** rather than the text color:
542
+
543
+ ```text
544
+ @bordered[default border]
545
+ @bordered{blue}[blue border]
546
+ @bordered{#3366ff}[hex border color]
547
+ ```
548
+
549
+ When `{styles}` is omitted or given an unrecognized value, the renderer falls back to its
550
+ default border style rather than throwing an error, echoing the fault-tolerant spirit of ยง6
551
+ Unknown Command Fallback. This node replaces the `bordered` modifier token that previously
552
+ lived in `@mark`'s `{styles}`, becoming its own independent first-class node, playing the same
553
+ role as `underline` (now `@underline`) and `strikethrough` (now `@del`).
554
+
555
+ ---
556
+
557
+ ### Renderer Behavior
558
+
559
+ * The renderer MUST at least support the default highlight style (`@mark`) / default border style (`@bordered`) when `styles` is omitted.
560
+ * The renderer MAY decide for itself the actual color value each named color token maps to (e.g. `yellow` may differ between dark mode and light mode; `@mark` and `@color`/`@bordered` may, and usually should, each maintain their own separate lookup tables, for the reasons given above); a hexadecimal token, however, MUST be used directly as specified and MUST NOT be remapped.
561
+ * The renderer MUST ignore unrecognized tokens (including malformed hex tokens), and SHOULD fall back to some default value rather than throwing an error โ€” this behavior is consistent with the fault-tolerant spirit of "unknown commands fall back to plain text" in [6. Unknown Command Fallback](#6-unknown-command-fallback), but its scope is limited to inside `styles`; `@mark[content]`/`@color[content]`/`@bordered[content]` themselves are still parsed normally as their corresponding nodes. The exact shape of the fallback is up to the renderer to decide: it could be "no extra color" (falling back to the default text color/border color), or it could reuse `@mark`'s default highlight color when `styles` is omitted (since all three share the same field shape, this is visually natural).
562
+ * The delimiter between tokens is a fixed half-width comma `,`, with any amount of whitespace allowed before and after (the Parser should auto-trim). This rule applies to `@mark`'s `styles`; the `{}` of `@color`/`@bordered` only allows a single token (a color token or a hex value) and does not use comma-separation.
563
+
564
+ ---
565
+
566
+ ### EBNF Supplementary Notes
567
+
568
+ Corresponding to the following in [4. Complete EBNF Grammar Definition](#4-complete-ebnf-grammar-definition):
569
+
570
+ ```ebnf
571
+ (* Additional Lexer restriction: `text-char` itself includes newlines, so a
572
+ literal reading would mean "an unclosed "{" can swallow everything all
573
+ the way to any "}" later in the document" โ€” a "{" the author is still
574
+ typing would swallow every node in between (e.g. everything before the
575
+ curly brace in the @code block below) whole into styles, silently
576
+ vanishing from the AST. The actual semantics of styles are a short,
577
+ comma-separated list of tokens with no example ever spanning multiple
578
+ lines, and the editor's Monarch rule (/\{[^}]*\}/, matched line by line)
579
+ doesn't support spanning lines either โ€” so the Lexer stops scanning at
580
+ whichever of "}", end of line, or "[" (start of a content slot) comes
581
+ first; both end-of-line and "[" are treated as unclosed. See
582
+ scanStylesEnd() in src/Lexer.ts. *)
583
+ styles =
584
+ "{" ,
585
+ { text-char - "}" - newline - "[" } ,
586
+ "}" ;
587
+ ```
588
+
589
+ At the lexical level, `styles` itself is only defined as "an arbitrary character sequence wrapped in curly braces"; the actual token splitting (comma-separated, recognizing color tokens and modifier tokens) belongs to **semantic-level** processing โ€” it is not the grammar-level responsibility of the Lexer/Parser, but is left to the renderer or a later semantic analysis stage. This design ensures that:
590
+
591
+ * Adding new style tokens (e.g. `italic`, `bold` in the future) does not require modifying the EBNF grammar definition itself.
592
+ * Different renderers can extend or trim their supported token set on their own, consistent with the "preserve the DSL's extensibility" goal in [1. Design Philosophy](#1-design-philosophy).
593
+
594
+ ---
595
+
596
+ ## 8. @link URI Semantics
597
+
598
+ `@link` accepts any valid URI or URI-like identifier.
599
+
600
+ ```text
601
+ @link(uri)[content]
602
+ ```
603
+
604
+ where:
605
+
606
+ * `uri` is the identifier of the target resource.
607
+ * `content` is the display text.
608
+
609
+ ---
610
+
611
+ ### Renderer URI Inference
612
+
613
+ The renderer MAY automatically infer the URI scheme based on the content of `uri`.
614
+
615
+ For example:
616
+
617
+ | Input | Actual renderer URI |
618
+ | ------------------------------ | ------------------------- |
619
+ | `@link(example.com)[Official Website]` | `https://example.com` |
620
+ | `@link(test@example.com)[Contact me]` | `mailto:test@example.com` |
621
+ | `@link(+886912345678)[Customer Service Phone]` | `tel:+886912345678` |
622
+
623
+ ---
624
+
625
+ If `uri` already explicitly specifies a scheme:
626
+
627
+ ```text
628
+ @link(https://example.com)[Official Website]
629
+ @link(mailto:test@example.com)[Contact me]
630
+ @link(tel:+886912345678)[Customer Service Phone]
631
+ ```
632
+
633
+ The renderer MUST use the specified value directly, and MUST NOT infer or modify it.
634
+
635
+ ---
636
+
637
+ ### Supported URI Examples
638
+
639
+ The following are all valid `uri` values:
640
+
641
+ ```text
642
+ https://example.com
643
+ mailto:test@example.com
644
+ tel:+886912345678
645
+ ftp://example.com/file.zip
646
+ discord://channel/123
647
+ vscode://file/path
648
+ file:///tmp/test.txt
649
+ ```
650
+
651
+ @Doc itself does not restrict the type of URI.
652
+
653
+ The actual support for a given URI is determined by the renderer.
654
+
655
+ ---
656
+
657
+ ## 9. @raw Opaque Domain
658
+
659
+ `@raw` belongs to:
660
+
661
+ > Opaque Domain
662
+
663
+ Once the parser enters:
664
+
665
+ ```text
666
+ @raw[
667
+ ```
668
+
669
+ afterward:
670
+
671
+ * No internal syntax is parsed.
672
+ * All keywords such as `@mark`, `@bold`, `@link`, etc. are treated as plain text.
673
+ * The global `@@` escape rule ([5. Escape Rule](#5-escape-rule)) no longer applies; the raw domain has its own independent, local set of rules (see below).
674
+
675
+ ### Termination Rule: Bracket-Depth Counting, Not "Reaching the First `]`"
676
+
677
+ The implementation model for `@raw[...]` is **bracket-depth counting**, and this must be made clear up front, because it directly determines when the escaping rules should and should not be used:
678
+
679
+ * `[` increases the depth by +1, `]` decreases it by -1; the `]` that brings the depth back to zero is the true end.
680
+ * In other words, **as long as the brackets are paired, you can copy them verbatim, with no escaping needed at all** โ€” the `@mark[hello]` inside `@raw[@mark[hello]]` itself has paired left and right brackets, so the Parser correctly ends at the outermost `]`, outputting `@mark[hello]` as literal text.
681
+ * The escaping rules exist specifically for **unpaired** brackets โ€” for example, when you just want to write a single, standalone literal `]`, or quote a content fragment whose own brackets are unbalanced. If you add unnecessary escaping to a bracket pair that is already balanced (e.g. writing the end of `@mark[hello]` as `@mark[hello@]`), the `]` consumed by the escape **does not** bring the depth count back to zero, so the +1 depth caused by the earlier `[` in `@mark[` can never find a matching `]` to cancel it out โ€” the Parser can only keep searching forward, swallowing more and more of the outer content (potentially the entire document) before it finally errors out. **Write balanced brackets as-is; always escape unbalanced brackets; escape characters do not participate in depth counting.**
682
+
683
+ The escaping rules are symmetric โ€” both `]` and `[` each have a "single-character escape" and an "escape the `@` itself followed by that character" form, four rules in total; when scanning, they are matched in the following priority order (longer, more specific sequences first):
684
+
685
+ | Priority | Input | Output | Description |
686
+ | ------ | ------ | ------ | ----------------------------- |
687
+ | 1 | `@@]` | `@]` | Outputs the two literal characters `@]` |
688
+ | 2 | `@@[` | `@[` | Outputs the two literal characters `@[` |
689
+ | 3 | `@]` | `]` | Outputs an **unpaired** literal `]` (does not affect depth counting) |
690
+ | 4 | `@[` | `[` | Outputs an **unpaired** literal `[` (does not affect depth counting) |
691
+
692
+ ---
693
+
694
+ ### Examples
695
+
696
+ Input:
697
+
698
+ ```text
699
+ @raw[@mark[hello]]
700
+ ```
701
+
702
+ Output:
703
+
704
+ ```text
705
+ @mark[hello]
706
+ ```
707
+
708
+ > Explanation: `@mark[hello]` itself has paired brackets, so the depth count goes 1โ†’2โ†’1, and only the outermost `]` brings the depth back to zero and ends `@raw`. **No escaping is needed at all** โ€” this is the most common usage (demonstrating a complete, bracket-balanced piece of @Doc syntax inside raw content).
709
+
710
+ ---
711
+
712
+ Input:
713
+
714
+ ```text
715
+ @raw[@@]
716
+ ```
717
+
718
+ Output:
719
+
720
+ ```text
721
+ @@
722
+ ```
723
+
724
+ > Explanation: here the `]` immediately following `@@` is the closing bracket of raw-content itself, so the `@@]` special case is not triggered (`@@]` must be three consecutive characters); it should be parsed as: the literal characters `@@` (output as-is, since the global escape rule is disabled) + the closing `]`.
725
+
726
+ ---
727
+
728
+ Input:
729
+
730
+ ```text
731
+ @raw[Today I'm afraid the @] will be detected]
732
+ ```
733
+
734
+ Output:
735
+
736
+ ```text
737
+ Today I'm afraid the ] will be detected
738
+ ```
739
+
740
+ > Explanation: here `@]` is an **unpaired** literal `]` (there is no matching `[` before it), so it must be escaped โ€” otherwise it would be treated as the end of `@raw` itself, causing the following "will be detected" to fall outside the raw content.
741
+
742
+ ---
743
+
744
+ Input:
745
+
746
+ ```text
747
+ @raw[Today I'm afraid the @@] will be detected]
748
+ ```
749
+
750
+ Output:
751
+
752
+ ```text
753
+ Today I'm afraid the @] will be detected
754
+ ```
755
+
756
+ > [!TIP]
757
+ > **TIP**: If the user wants to output the two characters `@]` inside raw content, simply add one more `@` in front (i.e. `@@]`) โ€” this is a local escaping special case within the raw domain, independent of and unaffected by the global `@@` escape rule in ยง5. `@[`/`@@[` form a completely symmetric counterpart, with the same rules but the opposite direction (handling an unpaired literal `[`).
758
+
759
+ ---
760
+
761
+ Input (escaping used where it shouldn't be โ€” **counter-example**):
762
+
763
+ ```text
764
+ @raw[Here, @mark[hello@] stays as-is]
765
+ ```
766
+
767
+ > [!WARNING]
768
+ > **Don't write it this way**: the `[` in `@mark[hello` has already increased the depth by +1; the author's original intent was simply to have `@mark[hello]` output as-is (just like the first example above, where the brackets are already paired and no escaping is needed at all), but an extra escape character `@]` was typed at the end. The escape consumes that `]` without participating in depth counting, so the +1 depth caused by `@mark[` can never find a matching `]` to cancel it out โ€” the Parser keeps searching forward, swallowing more and more of the outer content (potentially the entire document) before it finally errors out. The correct way to write it is to remove the extra `@` and simply write `@raw[Here, @mark[hello] stays as-is]` โ€” the brackets are paired, and the Parser can match them correctly on its own, with no manual intervention needed.
769
+
770
+ ### Rendering semantics: inline code
771
+
772
+ The rules above define **Parser behavior** (content is not parsed; it is kept verbatim). The corresponding rendering semantics are **inline code** โ€” the same thing Markdown's backticks `` `code` `` express:
773
+
774
+ * A Renderer **SHOULD** present `@raw` as a monospace inline element. The HTML Route uses `<code>`.
775
+ * This is distinct from `@code`, which is a **block** (`<pre><code>` in HTML); `@raw` is **inline**.
776
+ * It is also distinct from `@kbd`, which denotes a physical key and conventionally carries a border and key-cap look; `@raw` is code text and needs only a monospace font and a tint.
777
+ * This does not license a Renderer to parse the content โ€” adding rendering semantics does not change the opaque domain's parsing rules.
778
+
779
+ > Why `@raw` rather than a separate node: `raw-escaped` is the only content mode in the language with a real escape mechanism (`@]` / `@[` plus bracket-depth counting), and "the content must not be parsed" is precisely the defining requirement of inline code. The two use cases overlap almost entirely โ€” every example above in this section is showing code.
780
+
781
+ ---
782
+
783
+ ## 10. Nested Parsing
784
+
785
+ Because:
786
+
787
+ ```ebnf
788
+ content-element =
789
+ inline-node
790
+ | plain-text-char ;
791
+ ```
792
+
793
+ @Doc supports full recursive nesting.
794
+
795
+ For example:
796
+
797
+ ```text
798
+ @bold[
799
+ This is bold text,
800
+ inside there is
801
+ @mark{yellow}[an important highlight]
802
+ and
803
+ @underline[an underline]
804
+ ]
805
+ ```
806
+
807
+ Its AST structure is:
808
+
809
+ ```text
810
+ Bold
811
+ โ”œโ”€โ”€ Text
812
+ โ”œโ”€โ”€ Mark
813
+ โ””โ”€โ”€ Underline
814
+ ```
815
+
816
+ ---
817
+
818
+ ## 11. Parser Recovery Strategy
819
+
820
+ When the Parser encounters an unclosed structure:
821
+
822
+ ```text
823
+ @bold[hello
824
+ ```
825
+
826
+ or:
827
+
828
+ ```text
829
+ @mark{red}[hello
830
+ ```
831
+
832
+ Two suggested modes are provided:
833
+
834
+ ### Strict Mode
835
+
836
+ Throws a syntax error directly:
837
+
838
+ ```text
839
+ Unexpected EOF while parsing @bold
840
+ ```
841
+
842
+ > [!TIP]
843
+ > **TIP**: AtDoc chose to throw a syntax error directly, and uses an asynchronous error-breakpoint recovery mechanism
844
+
845
+ ---
846
+
847
+ ### Editor Mode
848
+
849
+ Allows the editor to automatically complete missing closing symbols:
850
+
851
+ ```text
852
+ ]
853
+ ```
854
+
855
+ to improve the real-time editing experience.
856
+
857
+ ---
858
+
859
+ ## 12. Architecture
860
+
861
+ Recommended parsing pipeline:
862
+
863
+ ```text
864
+ Source Text
865
+ โ†“
866
+ Lexer
867
+ โ†“
868
+ Token Stream
869
+ โ†“
870
+ Parser
871
+ โ†“
872
+ AST
873
+ โ†“
874
+ Renderer
875
+ ```
876
+
877
+ The renderer can freely output:
878
+
879
+ * HTML
880
+ * React
881
+ * PDF
882
+ * DOCX
883
+ * Markdown
884
+ * Discord
885
+ * Terminal
886
+ * Custom UI
887
+
888
+ ---
889
+
890
+ ## 13. Core Principle
891
+
892
+ The core goal of @Doc is not to replace Markdown.
893
+
894
+ but rather to establish:
895
+
896
+ > Human Editable
897
+ > Machine Deterministic
898
+ > AI Friendly
899
+ > Cross Platform
900
+
901
+ a next-generation document interchange format.
902
+
903
+ ---
904
+
905
+ ## 14. Simplified Syntax Aliases
906
+
907
+ `@bold`/`@italic`/`@underline` provide the simplified aliases `@b`/`@i`/`@u` โ€” purely a shorthand at input time. The Parser normalizes it to the canonical name before creating the AST node (`node.type` is always the canonical name); the renderer never needs to, and never does, distinguish which form the author actually typed.
908
+
909
+ | Canonical | Alias |
910
+ |---|---|
911
+ | `@bold` | `@b` |
912
+ | `@italic` | `@i` |
913
+ | `@underline` | `@u` |
914
+
915
+ (The Block Syntax aliases `@h`/`@p` for `@heading`/`@paragraph` are defined in Block Syntax Specification ยง11.)