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.
- package/LICENSE +21 -0
- package/README.md +33 -0
- package/configs/atdoc-setting.json +30 -0
- package/dist/Adapters.d.ts +18 -0
- package/dist/Adapters.d.ts.map +1 -0
- package/dist/Adapters.js +484 -0
- package/dist/Adapters.js.map +1 -0
- package/dist/Lexer.d.ts +23 -0
- package/dist/Lexer.d.ts.map +1 -0
- package/dist/Lexer.js +292 -0
- package/dist/Lexer.js.map +1 -0
- package/dist/Parser.d.ts +136 -0
- package/dist/Parser.d.ts.map +1 -0
- package/dist/Parser.js +797 -0
- package/dist/Parser.js.map +1 -0
- package/dist/Serializer.d.ts +97 -0
- package/dist/Serializer.d.ts.map +1 -0
- package/dist/Serializer.js +753 -0
- package/dist/Serializer.js.map +1 -0
- package/dist/editor/monarch.d.ts +17 -0
- package/dist/editor/monarch.d.ts.map +1 -0
- package/dist/editor/monarch.js +163 -0
- package/dist/editor/monarch.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/registry.d.ts +104 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +265 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +55 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/docs/en/Block-Syntax-Specification.md +992 -0
- package/docs/en/Inline-Syntax-Specification.md +915 -0
- package/docs/en/README.md +235 -0
- package/package.json +36 -0
|
@@ -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.)
|