@entropicwarrior/sdoc 0.1.3 → 0.1.5

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,779 @@
1
+ # SDOC Specification v0.1 @sdoc-spec
2
+ {
3
+ # Meta @meta
4
+ {
5
+ type: doc
6
+ }
7
+
8
+ # About @about
9
+ {
10
+ The formal SDOC v0.1 specification. Defines syntax for scopes,
11
+ lists, tables, code blocks, inline formatting, references, and
12
+ the meta scope. Includes the formal EBNF grammar. Read for
13
+ edge cases and parser behaviour questions. For a friendlier
14
+ user-facing reference, see \`docs/reference/syntax.sdoc\`.
15
+ }
16
+
17
+ # Overview @overview
18
+ {
19
+ SDOC ("Simple/Smart Documentation") is a plain text documentation format with explicit scoping.
20
+
21
+ # Goals @goals
22
+ {
23
+ {[.]
24
+ - Explicit scoping with braces; whitespace and indentation are cosmetic
25
+ - Minimal syntax, easy to parse and render
26
+ - Automatic heading styles based on nesting depth
27
+ - Easy lists and easy references
28
+ - No hidden metadata: only user-provided IDs exist
29
+ }
30
+ }
31
+
32
+ # Core Concepts @core-concepts
33
+ {
34
+ {[.]
35
+ - A document is a tree of scopes
36
+ - A scope has a heading line and either a brace-delimited block (`{ ... }`) or braceless content terminated by the next heading, `}`, or EOF
37
+ - Headings are explicit and always start with `#`
38
+ - A scope may have an optional human ID (e.g., `@overview`)
39
+ - References use `@id` in text
40
+ - Paragraphs are separated by blank lines or by a new scope at the same level
41
+ - Whitespace (spaces/tabs) is ignored except where noted
42
+ }
43
+ }
44
+ }
45
+
46
+ # Syntax @syntax
47
+ {
48
+ # Heading Line @heading-line
49
+ {
50
+ ```
51
+ # Title text @id
52
+ ```
53
+
54
+ {[.]
55
+ - The line must start with `#` (after optional indentation)
56
+ - Multiple `#` characters are allowed but do not affect depth. Depth comes only from scope nesting
57
+ - The optional `@id` must appear at the end of the line, separated by whitespace
58
+ - If no `@id` is present, the scope has no ID
59
+ - If you need a literal `@` in the title, escape it (`\@`)
60
+ }
61
+ }
62
+
63
+ # Scope Block @scope-block
64
+ {
65
+ ```
66
+ # Title
67
+ {
68
+ ...
69
+ }
70
+ ```
71
+
72
+ `{` opens a scope block and `}` closes it. Indentation is cosmetic.
73
+
74
+ # Braceless Leaf Scopes @braceless-scopes
75
+ {
76
+ A heading not followed by `{` or a block opener creates a braceless scope. The scope's content runs until the next `#` heading at the same level, a closing `}`, or end of file:
77
+
78
+ ```
79
+ # Section A
80
+ This is content of Section A.
81
+ It can span multiple lines.
82
+
83
+ # Section B
84
+ Content of Section B.
85
+ ```
86
+
87
+ {[.]
88
+ - Braceless scopes can contain paragraphs, code blocks, blockquotes, implicit lists, horizontal rules, tables, and headingless scopes
89
+ - Braceless scopes cannot contain child `#` headings; encountering one ends the scope (the heading becomes a sibling)
90
+ - A closing `}` also terminates a braceless scope (used when braceless scopes appear inside an explicit parent)
91
+ - Braceless and explicit scopes can be freely mixed in the same document
92
+ }
93
+ }
94
+
95
+ # K&R Style Brace Placement @knr-braces
96
+ {
97
+ The opening brace (or list/table opener) may appear at the end of a heading or list-item line instead of on its own line:
98
+
99
+ ```
100
+ # Title {
101
+ ...
102
+ }
103
+
104
+ # My List {[.]
105
+ - Item 1
106
+ - Item 2
107
+ }
108
+
109
+ # Data {[table]
110
+ Name | Age
111
+ Alice | 30
112
+ }
113
+
114
+ # Title @id {
115
+ ...
116
+ }
117
+ ```
118
+
119
+ {[.]
120
+ - The opener must be the last token on the line (trailing whitespace is allowed)
121
+ - Applies to `{`, `{[.]`, `{[#]`, `{[table]`, and `{[table <flags>]`
122
+ - Also works on list-item shorthand lines (e.g., `- Item {`)
123
+ - Table options (e.g., `{[table borderless]`) work in K&R style: `# Data {[table borderless]`
124
+ - Escaped braces (`\{`) are not treated as openers
125
+ - The closing `}` must still appear on its own line
126
+ - Inline blocks (`{ content }`) are not affected; a line ending with `}` is not treated as K&R
127
+ - Headingless scopes (bare `{` on its own line) are not affected
128
+ }
129
+ }
130
+
131
+ # Headingless Scopes @headingless-scopes
132
+ {
133
+ A bare `{` block without a preceding heading creates a headingless scope:
134
+
135
+ ```
136
+ {
137
+ Grouped content here.
138
+ }
139
+ ```
140
+
141
+ {[.]
142
+ - Headingless scopes render as sections without a heading element
143
+ - They inherit the same nesting/indentation as headed scopes
144
+ - Useful for grouping paragraphs or creating visual indentation without a title
145
+ - Can be nested arbitrarily
146
+ }
147
+ }
148
+
149
+ # Inline Blocks @inline-blocks
150
+ {
151
+ For simple content, blocks can be written on a single line:
152
+
153
+ ```
154
+ # Name
155
+ { John Doe }
156
+
157
+ # Count
158
+ { 42 }
159
+ ```
160
+
161
+ {[.]
162
+ - Inline blocks must not contain unescaped `{` or `}` characters
163
+ - Multi-line blocks are always supported and sometimes clearer for longer content
164
+ }
165
+ }
166
+ }
167
+
168
+ # Lists @lists
169
+ {
170
+ Lists are declared by a list block type:
171
+
172
+ ```
173
+ # Bulleted List
174
+ {[.]
175
+ # Item 1
176
+ { ... }
177
+ # Item 2
178
+ { ... }
179
+ }
180
+
181
+ # Numbered List
182
+ {[#]
183
+ # Item 1
184
+ { ... }
185
+ # Item 2
186
+ { ... }
187
+ }
188
+ ```
189
+
190
+ {[.]
191
+ - `{[.]` creates a bulleted list
192
+ - `{[#]` creates a numbered list
193
+ - List items are scopes inside the list block
194
+ - Commas between list items are allowed but ignored
195
+ }
196
+
197
+ # List Item Shorthand @list-shorthand
198
+ {
199
+ Inside a list block, `- Title text` is shorthand for a list item scope:
200
+
201
+ ```
202
+ {[.]
203
+ - Item 1
204
+ - Item 2
205
+ }
206
+ ```
207
+
208
+ {[.]
209
+ - A shorthand item may optionally be followed by a block (`{ ... }` or list opener)
210
+ - If no block follows, the item has no body
211
+ - Outside list blocks, leading list-item lines form implicit lists (see @implicit-lists)
212
+ }
213
+
214
+ Numbered shorthand is also supported:
215
+
216
+ ```
217
+ {[#]
218
+ 1. Item 1
219
+ 2. Item 2
220
+ }
221
+ ```
222
+
223
+ # Multi-line List Item Shorthand @multi-line-list-items
224
+ {
225
+ Inside an explicit list block (`{[.]` or `{[#]`), a shorthand item's title may span multiple lines. Lines that follow the item marker and are not themselves a command token (heading, list marker, brace, fence, blockquote, horizontal rule, or blank line) are treated as continuation lines and joined to the item title with a single space:
226
+
227
+ ```
228
+ {[.]
229
+ - This is a long list item
230
+ that continues on the next line
231
+ - Short item
232
+ }
233
+ ```
234
+
235
+ {[.]
236
+ - Continuation lines are joined with a single space
237
+ - Continuation stops at: blank lines, list markers (`- `, `1. `), headings (`#`), block openers (`{`, `{[.]`, `{[#]`, `{[table]`), block closers (`}`), code fences, blockquotes, and horizontal rules
238
+ - A block (`{ ... }` or list/table opener) may still follow the completed multi-line title
239
+ - Multi-line continuation is only supported in explicit list blocks, not in implicit lists
240
+ - Indentation of continuation lines is cosmetic
241
+ }
242
+ }
243
+ }
244
+
245
+ # Implicit Lists @implicit-lists
246
+ {
247
+ Inside a normal scope block, a run of list-item lines is treated as a list:
248
+
249
+ ```
250
+ # Simple bulleted list
251
+ {
252
+ - Item 1
253
+ - Item 2
254
+ }
255
+
256
+ # Simple numbered list
257
+ {
258
+ 1. First
259
+ 2. Second
260
+ }
261
+ ```
262
+
263
+ {[.]
264
+ - The list type is chosen by the first item marker (`-` for bulleted, `1.` / `1)` for numbered)
265
+ - Mixed markers end the implicit list
266
+ - Each item may optionally be followed by a block (`{ ... }` or list opener)
267
+ }
268
+ }
269
+
270
+ # Anonymous List Items @anonymous-list-items
271
+ {
272
+ Inside a list block, a list item can start with a block directly:
273
+
274
+ ```
275
+ {[.]
276
+ {
277
+ This item has no heading line.
278
+ }
279
+ }
280
+ ```
281
+
282
+ The block contents are rendered as the list item body.
283
+ }
284
+
285
+ # Task Lists @task-lists
286
+ {
287
+ Inside a list block, task items use Markdown-style checkboxes:
288
+
289
+ ```
290
+ {[.]
291
+ - [ ] Pending task
292
+ - [x] Completed task
293
+ }
294
+ ```
295
+
296
+ Works in both bulleted and numbered list blocks.
297
+ }
298
+ }
299
+
300
+ # Tables @tables
301
+ {
302
+ Tables are declared with `{[table]`:
303
+
304
+ ```
305
+ {[table]
306
+ Name | Age | City
307
+ Alice | 30 | NYC
308
+ Bob | 25 | LA
309
+ }
310
+ ```
311
+
312
+ {[.]
313
+ - The first row is always the header (unless `headerless` is specified)
314
+ - Columns are separated by `|`
315
+ - Each row is a single line
316
+ - Cell contents support inline formatting
317
+ - Leading and trailing whitespace in cells is trimmed
318
+ }
319
+
320
+ # Table Options @table-options
321
+ {
322
+ Optional flags after `table` in the block opener control table rendering:
323
+
324
+ ```
325
+ {[table borderless]
326
+ Feature | Status
327
+ Parser | Complete
328
+ Renderer | Complete
329
+ }
330
+
331
+ {[table headerless]
332
+ Alice | 30 | New York
333
+ Bob | 25 | Los Angeles
334
+ }
335
+
336
+ {[table borderless headerless]
337
+ ![](a.png =100%) | ![](b.png =100%)
338
+ ![](c.png =100%) | ![](d.png =100%)
339
+ }
340
+ ```
341
+
342
+ {[.]
343
+ - `borderless` removes all table borders and row striping
344
+ - `headerless` treats the first row as data (no header row)
345
+ - Flags can be combined in any order
346
+ - Works with both Allman and K&R brace styles
347
+ }
348
+ }
349
+ }
350
+
351
+ # Paragraphs @paragraphs
352
+ {
353
+ {[.]
354
+ - Consecutive text lines are joined into a single paragraph
355
+ - A blank line ends the paragraph
356
+ - A new scope or list at the same level also ends the paragraph
357
+ }
358
+ }
359
+
360
+ # References @references
361
+ {
362
+ {[.]
363
+ - A reference is `@id` in text (unescaped)
364
+ - References link to the scope with that ID
365
+ - ID uniqueness is strongly recommended; tooling may warn on duplicates
366
+ }
367
+ }
368
+
369
+ # External Links @external-links
370
+ {
371
+ Markdown-style links:
372
+
373
+ ```
374
+ [label](https://example.com)
375
+ ```
376
+ }
377
+
378
+ # Autolinks @autolinks
379
+ {
380
+ Angle-bracket links:
381
+
382
+ ```
383
+ <https://example.com>
384
+ <mailto:hello@example.com>
385
+ ```
386
+
387
+ Only `http`, `https`, and `mailto` schemes are recognised.
388
+ }
389
+
390
+ # Images @images
391
+ {
392
+ Markdown-style images:
393
+
394
+ ```
395
+ ![Alt text](https://example.com/image.png)
396
+ ```
397
+
398
+ # Image Width and Alignment @image-width
399
+ {
400
+ An optional size and alignment suffix can follow the URL, separated by a space and introduced with `=`:
401
+
402
+ ```
403
+ ![Alt](image.png =50%)
404
+ ![Alt](image.png =200px)
405
+ ![Alt](image.png =50% center)
406
+ ![Alt](image.png =35% left)
407
+ ![Alt](image.png =35% right)
408
+ ```
409
+
410
+ {[.]
411
+ - `=<number>%` or `=<number>px` sets the image width
412
+ - An optional alignment keyword follows the width: `center`, `left`, or `right`
413
+ - `center` uses auto margins for horizontal centering
414
+ - `left` floats the image left with text wrapping on the right
415
+ - `right` floats the image right with text wrapping on the left
416
+ - Without an alignment keyword, images display inline (side by side when adjacent)
417
+ - Two images on the same line with explicit widths sit side by side naturally
418
+ }
419
+ }
420
+ }
421
+
422
+ # Blockquotes @blockquotes
423
+ {
424
+ ```
425
+ > A quoted line.
426
+ > Another line in the same quote.
427
+ ```
428
+
429
+ {[.]
430
+ - Consecutive `>` lines form a single blockquote
431
+ - A blank `>` line breaks paragraphs within the blockquote
432
+ }
433
+ }
434
+
435
+ # Horizontal Rules @horizontal-rules
436
+ {
437
+ A line of three or more `-`, `*`, or `_` characters:
438
+
439
+ ```
440
+ ---
441
+ ```
442
+ }
443
+
444
+ # Inline Formatting @inline-formatting
445
+ {
446
+ {[.]
447
+ - Emphasis: `*em*`
448
+ - Strong: `**strong**`
449
+ - Strikethrough: `~~strike~~`
450
+ - Inline code: `` `code` ``
451
+ }
452
+ }
453
+
454
+ # Escaping @escaping
455
+ {
456
+ In normal text (including headings and paragraphs), a backslash escapes: `\\` `\{` `\}` `\@` `\[` `\]` `\(` `\)` `\*` `\~` `\#` `\!` `\<` `\>` and `` \` ``.
457
+
458
+ Escapes are processed before reference detection.
459
+
460
+ If a line begins with `\#`, it is treated as a normal paragraph line (rendered with a literal `#`). If a line begins with `\>`, it is treated as a normal paragraph line (rendered with a literal `>`).
461
+ }
462
+
463
+ # Code Blocks @code-blocks
464
+ {
465
+ Fenced blocks for code or raw text:
466
+
467
+ `````
468
+ ```lang
469
+ raw text here { # @ } is not parsed
470
+ ```
471
+ `````
472
+
473
+ {[.]
474
+ - The opening and closing fences must be on their own lines
475
+ - Anything inside is treated as raw text (no parsing, no escapes)
476
+ - Optional language tag after the opening fence
477
+ }
478
+
479
+ # Include by Link @code-includes
480
+ {
481
+ A code fence can reference an external file using `src:` metadata on the opening fence line. The code block body is replaced by the file contents:
482
+
483
+ `````
484
+ ```json src:./data.json
485
+ ```
486
+
487
+ ```json src:./data.json lines:3-5
488
+ ```
489
+
490
+ ```src:https://example.com/schema.json
491
+ ```
492
+ `````
493
+
494
+ {[.]
495
+ - `src:<path>` references a file path (relative to the document) or URL
496
+ - `lines:<start>-<end>` optionally limits to a line range (1-based, inclusive)
497
+ - The language tag is optional and goes before `src:`
498
+ - Any body text in the code block is replaced by the resolved file contents
499
+ - If the file cannot be read, an error message is shown in the code block
500
+ - URL sources are fetched and cached; the cache is cleared on document save
501
+ }
502
+ }
503
+ }
504
+ }
505
+
506
+ # Styles, Header, and Footer @styling
507
+ {
508
+ # Hierarchical Config @hierarchical-config
509
+ {
510
+ Place `sdoc.config.json` in any folder. When rendering a file, configs are merged from the workspace root down to the file's folder.
511
+
512
+ ```json
513
+ {
514
+ "style": "styles/sdoc.custom.css",
515
+ "styleAppend": "styles/overrides.css",
516
+ "header": "My Project Docs",
517
+ "footer": "© 2026 My Company"
518
+ }
519
+ ```
520
+
521
+ {[.]
522
+ - The closest config to the file overrides parent configs
523
+ - `style` replaces the default stylesheet
524
+ - `styleAppend` is appended after the base stylesheet (string or array)
525
+ - `header` and `footer` are plain text with inline formatting supported
526
+ - Paths in `style` and `styleAppend` are resolved relative to the config file
527
+ }
528
+
529
+ A starter stylesheet template is provided at `examples/sdoc.template.css`.
530
+ }
531
+
532
+ # Per-File Overrides (Meta Scope) @meta-scope
533
+ {
534
+ A reserved meta scope overrides header/footer and styles:
535
+
536
+ ```
537
+ # Meta @meta
538
+ {
539
+ # Style
540
+ { styles/sdoc.custom.css }
541
+ # StyleAppend
542
+ { styles/overrides.css }
543
+ # Header
544
+ { My *custom* header }
545
+ # Footer
546
+ { Page-specific footer text }
547
+ }
548
+ ```
549
+
550
+ {[.]
551
+ - The `@meta` scope is not rendered in the document body
552
+ - The meta scope should appear at the top level of the document
553
+ - `Style` and `StyleAppend` are treated as file paths (relative to the SDOC file)
554
+ - `Header` and `Footer` render their scope contents at the top/bottom of the page
555
+ - Per-file meta settings override the merged `sdoc.config.json` values
556
+ - `@meta` is reserved and should not be used for normal references
557
+ }
558
+
559
+ # Key:Value Meta Syntax @kv-meta
560
+ {
561
+ Inside a `@meta` scope, paragraph lines matching `Key: value` are parsed as metadata. This provides a lighter-weight alternative to sub-scopes for simple values:
562
+
563
+ ```
564
+ # Meta @meta
565
+ {
566
+ style: styles/custom.css
567
+ header: My Header
568
+ footer: My Footer
569
+ author: Jane Smith
570
+ date: 2026-02-09
571
+ version: 1.0
572
+ status: Draft
573
+ }
574
+ ```
575
+
576
+ {[.]
577
+ - Key matching is case-insensitive
578
+ - The pattern requires at least one space after the colon (`key: value`, not `key:value`)
579
+ - Well-known keys: `style`, `styleappend`/`style-append`, `header`, `footer`
580
+ - All other keys are stored as custom properties (e.g., `author`, `date`, `version`, `status`, `tags`)
581
+ - Sub-scope syntax takes precedence: if both `# Style { path }` and `style: path` exist, the sub-scope value wins
582
+ - Key:value and sub-scope syntax can be mixed freely in the same meta scope
583
+ - Each key:value pair must be on its own paragraph line (separated by blank lines from other pairs)
584
+ }
585
+ }
586
+ }
587
+ }
588
+
589
+ # Interactive Preview @interactive-preview
590
+ {
591
+ The VSCode extension provides an interactive preview with the following features. These are preview-only behaviours and do not affect the SDOC format or static HTML export.
592
+
593
+ # Collapsible Scopes @collapsible-scopes
594
+ {
595
+ Scope headings that have children display a toggle triangle, visible on hover. Clicking the triangle collapses the scope's children (hides the content below the heading). Clicking again expands them.
596
+
597
+ {[.]
598
+ - The triangle points right when collapsed, down when expanded
599
+ - Collapse state is preserved across preview refreshes using webview state
600
+ - Scopes are identified by their `@id` if present, otherwise by source line number
601
+ - Only scopes with children show the toggle
602
+ }
603
+ }
604
+
605
+ # Click-to-Navigate @click-to-navigate
606
+ {
607
+ Clicking any rendered element in the preview (heading, paragraph, code block, blockquote, list item, table, horizontal rule) navigates the editor cursor to the corresponding source line in the SDOC file.
608
+
609
+ {[.]
610
+ - Each rendered element carries a `data-line` attribute with its 1-indexed source line number
611
+ - Clicking inside an editable paragraph or on a collapse toggle does not trigger navigation
612
+ }
613
+ }
614
+
615
+ # Inline Text Editing @inline-editing
616
+ {
617
+ Paragraphs in the preview are directly editable. Clicking a paragraph gives it focus with a visible outline. Typing changes the text, and pressing Enter or clicking away writes the change back to the source file.
618
+
619
+ {[.]
620
+ - Only paragraph elements are editable (not headings, code blocks, etc.)
621
+ - Inline formatting (`*bold*`, `@ref`) in the original source is lost if the user edits text that contained it
622
+ - Escape also blurs the paragraph (discarding focus without triggering a save of the current edit state)
623
+ }
624
+ }
625
+ }
626
+
627
+ # Document Formatting @document-formatting
628
+ {
629
+ The VSCode extension provides a built-in document formatter accessible via Format Document (Shift+Option+F). The formatter reindents the document based on brace depth.
630
+
631
+ # Formatting Rules @formatting-rules
632
+ {
633
+ The formatter operates line-by-line, tracking brace depth to compute indentation:
634
+
635
+ {[.]
636
+ - Blank lines are preserved as empty lines with no indentation
637
+ - Code block content (between ``` fences) is passed through raw with no reindentation
638
+ - Code fence lines are indented at the current depth
639
+ - Closing braces `}` decrement depth before indenting
640
+ - Standalone openers (`{`, `{[.]`, `{[#]`, `{[table]`) indent at current depth, then increment
641
+ - Inline blocks (`{ content }`) indent at current depth with no depth change
642
+ - K&R lines (heading or list item ending with an opener) indent at current depth, then increment
643
+ - All other lines (headings, paragraphs, list items, blockquotes, HRs) indent at current depth
644
+ }
645
+ }
646
+
647
+ # Behaviour @formatting-behaviour
648
+ {
649
+ {[.]
650
+ - The formatter respects the user's VS Code tab size and spaces/tabs preference
651
+ - Formatting is idempotent: formatting an already-formatted document produces identical output
652
+ - Structure is never changed; only indentation is adjusted
653
+ - The formatter does not reflow paragraph text or reorder content
654
+ }
655
+ }
656
+ }
657
+
658
+ # Implicit Root Scope @implicit-root
659
+ {
660
+ If the first non-blank line of a document is a `#` heading and the next non-blank line after it is NOT `{`, an inline block, or a list/table opener, the document is in implicit root mode:
661
+
662
+ ```
663
+ # My Document
664
+
665
+ # Section A
666
+ Content of Section A.
667
+
668
+ # Section B
669
+ {
670
+ Content of Section B.
671
+ }
672
+ ```
673
+
674
+ {[.]
675
+ - The first heading becomes the root scope title
676
+ - Everything after it until EOF becomes the root scope's children
677
+ - Works with both braceless and explicit child scopes
678
+ - If the first heading IS followed by `{` or a block opener, the document uses explicit root mode (existing behavior)
679
+ - K&R brace style on the first heading also triggers explicit mode
680
+ }
681
+ }
682
+
683
+ # Parsing Rules @parsing-rules
684
+ {
685
+ Command tokens are recognised only at the start of a line (after optional indentation):
686
+
687
+ {[.]
688
+ - `#` heading line
689
+ - `{` scope open
690
+ - `}` scope close
691
+ - `{[.]` / `{[#]` list open
692
+ - `{[table]` / `{[table <flags>]` table open
693
+ - `>` blockquote line
694
+ - `---` / `***` / `___` horizontal rule
695
+ - `` ``` `` code fence
696
+ }
697
+
698
+ Blank lines are allowed anywhere and are ignored.
699
+ }
700
+
701
+ # Formal Grammar @grammar
702
+ {
703
+ Informal EBNF grammar:
704
+
705
+ ```
706
+ document = implicit_root | block_body ;
707
+ implicit_root = heading block_body ;
708
+
709
+ scope = heading ws? block
710
+ | heading ws? braceless_body
711
+ | heading_with_opener block_body "}" ;
712
+ heading = "#" { "#" } ws title (ws id)? ;
713
+ heading_with_opener = "#" { "#" } ws title (ws id)? ws block_opener ;
714
+ id = "@" ident ;
715
+ block_opener = "{" | "{[.]" | "{[#]" | table_open ;
716
+ block = "{" ws? block_body "}" ;
717
+ braceless_body = { paragraph | code_block | blockquote | implicit_list
718
+ | horizontal_rule | headingless_scope | table_scope
719
+ | blank } ;
720
+
721
+ block_body = { blank | paragraph | scope | headingless_scope
722
+ | list_scope | table_scope
723
+ | implicit_list | blockquote | horizontal_rule
724
+ | code_block | comma_sep } ;
725
+ headingless_scope = "{" ws? block_body "}" ;
726
+ list_scope = list_open ws? list_body "}" ;
727
+ list_open = "{[.]" | "{[#]" ;
728
+ table_scope = table_open ws? table_body "}" ;
729
+ table_open = "{[table" { ws table_flag } "]" ;
730
+ table_flag = "borderless" | "headerless" ;
731
+ table_body = table_row { table_row } ;
732
+ table_row = cell { "|" cell } ;
733
+ list_body = { blank | comma_sep | scope | list_item_shorthand
734
+ | anonymous_item } ;
735
+ implicit_list = list_item_shorthand { list_item_shorthand } ;
736
+ list_item_shorthand = bullet_item | numbered_item ;
737
+ bullet_item = "-" ws title { continuation_line } [ws? block]?
738
+ | "-" ws title ws block_opener block_body "}" ;
739
+ numbered_item = number ("." | ")") ws title { continuation_line } [ws? block]?
740
+ | number ("." | ")") ws title ws block_opener block_body "}" ;
741
+ continuation_line = text_line ; (* only in explicit list blocks *)
742
+ anonymous_item = "{" ws? block_body "}" ;
743
+ number = DIGIT { DIGIT } ;
744
+
745
+ paragraph = text_line { ws? text_line } ;
746
+ text_line = line_not_starting_with_command ;
747
+
748
+ blockquote = quote_line { quote_line | blank } ;
749
+ quote_line = ">" text_line ;
750
+
751
+ horizontal_rule = "---" | "***" | "___" ;
752
+
753
+ code_block = fence_open raw_text fence_close ;
754
+ fence_open = "```" [lang] [ws "src:" path] [ws "lines:" range] newline ;
755
+ fence_close = "```" newline ;
756
+
757
+ comma_sep = "," ;
758
+ blank = newline ;
759
+
760
+ ident = (ALPHA | "_") { ALPHA | DIGIT | "_" | "-" } ;
761
+ ```
762
+
763
+ {[.]
764
+ - `title` is the remainder of the heading line, excluding the optional trailing `@id`
765
+ - If a line starts with a command token, it is not a paragraph line
766
+ - The grammar is line-oriented; practical parsers should operate on lines
767
+ }
768
+ }
769
+
770
+ # Open Questions @open-questions
771
+ {
772
+ {[.]
773
+ - Comment syntax (if any)
774
+ - Duplicate ID resolution (error vs warning vs nearest-scope)
775
+ - Additional list types (checkboxes, alpha, roman)
776
+ - Additional inline formatting (underline, highlight)
777
+ }
778
+ }
779
+ }