nattoppet 2.1.1 → 4.0.1
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/GUIDE.md +732 -0
- package/common.ymd +33 -13
- package/compiler.ts +169 -0
- package/form.css +87 -0
- package/form.js +64 -0
- package/form.ymd +36 -0
- package/katex.css +1 -0
- package/katex.ymd +13 -0
- package/koa.css +157 -0
- package/{koa/after.ymd → koa.ymd} +18 -1
- package/nattoppet-dev.ts +94 -0
- package/nattoppet-init.ts +142 -0
- package/nattoppet-native.ts +116 -0
- package/nattoppet.ts +35 -0
- package/package.json +54 -14
- package/ppt.css +304 -0
- package/ppt.js +83 -0
- package/{ppt/after.ymd → ppt.ymd} +17 -1
- package/readme.md +28 -15
- package/{stdlib.js → stdlib.ts} +48 -36
- package/tml.css +125 -0
- package/tml.js +3 -0
- package/{tml/after.ymd → tml.ymd} +19 -1
- package/vue.css +91 -0
- package/vue.ymd +23 -0
- package/.editorconfig +0 -15
- package/assets/katex.css +0 -1
- package/assets/manuscript.cls +0 -30
- package/compiler.js +0 -120
- package/koa/before.ymd +0 -12
- package/koa/koa.less +0 -179
- package/native/Cargo.toml +0 -13
- package/native/build.rs +0 -12
- package/native/src/main.rs +0 -22
- package/nattoppet-dev.js +0 -58
- package/nattoppet-native.js +0 -49
- package/nattoppet.js +0 -26
- package/ppt/before.ymd +0 -8
- package/ppt/ppt.coffee +0 -61
- package/ppt/ppt.less +0 -222
- package/tml/before.ymd +0 -10
- package/tml/tml.coffee +0 -1
- package/tml/tml.less +0 -145
- package/vue/after.ymd +0 -9
- package/vue/before.ymd +0 -9
- package/vue/vue.less +0 -101
package/GUIDE.md
ADDED
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
# Nattoppet Usage Guide
|
|
2
|
+
|
|
3
|
+
Nattoppet is a macro-based markup language that compiles to self-contained, minified HTML. You write `.ymd` files using a mix of raw HTML, plain text, and macros. The compiler inlines assets, renders math, and bundles everything into a single file.
|
|
4
|
+
|
|
5
|
+
This guide covers writing `.ymd` files from the perspective of an end user.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
1. [Quick Start](#quick-start)
|
|
12
|
+
2. [The Core Idea: Macros](#the-core-idea-macros)
|
|
13
|
+
3. [Paragraphs and Indentation](#paragraphs-and-indentation)
|
|
14
|
+
4. [Mixins](#mixins)
|
|
15
|
+
5. [Common Macros](#common-macros)
|
|
16
|
+
6. [Built-in Themes](#built-in-themes)
|
|
17
|
+
- [vue](#vue--articles-and-blog-posts)
|
|
18
|
+
- [form](#form--interactive-calculators)
|
|
19
|
+
- [katex](#katex--math-rendering)
|
|
20
|
+
- [koa](#koa--long-documents-with-navigation)
|
|
21
|
+
- [ppt](#ppt--slide-decks)
|
|
22
|
+
- [tml](#tml--timeline--horizontal-articles)
|
|
23
|
+
7. [Preprocessor Support](#preprocessor-support)
|
|
24
|
+
8. [Scoping and Name Resolution](#scoping-and-name-resolution)
|
|
25
|
+
9. [Traps and Workarounds](#traps-and-workarounds)
|
|
26
|
+
10. [Troubleshooting](#troubleshooting)
|
|
27
|
+
11. [Building and Deploying](#building-and-deploying)
|
|
28
|
+
12. [How It Works Under the Hood](#how-it-works-under-the-hood)
|
|
29
|
+
13. [Summary Cheat Sheet](#summary-cheat-sheet)
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
### Prerequisites
|
|
36
|
+
|
|
37
|
+
- [Bun](https://bun.sh) (≥1.0)
|
|
38
|
+
- Clone this repository and run `bun install` in the project root.
|
|
39
|
+
|
|
40
|
+
### Your first document
|
|
41
|
+
|
|
42
|
+
A minimal `.ymd` file looks like this:
|
|
43
|
+
|
|
44
|
+
```ymd
|
|
45
|
+
[mixin] vue
|
|
46
|
+
|
|
47
|
+
[title]: Hello World
|
|
48
|
+
|
|
49
|
+
[h2] First Section
|
|
50
|
+
|
|
51
|
+
This is a paragraph. It is automatically wrapped in `<p>` tags because it is indented by two spaces.
|
|
52
|
+
|
|
53
|
+
This line is not indented, so it passes through as raw HTML.
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Compile it:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
bun run nattoppet.ts input.ymd > output.html
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Preview with live reload:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
bun run nattoppet-dev.ts input.ymd
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The `--dev` flag disables minification and is useful for debugging. Both compilation modes write to stdout.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## The Core Idea: Macros
|
|
73
|
+
|
|
74
|
+
Everything dynamic in nattoppet is a **macro**. A macro is invoked by writing its name inside square brackets:
|
|
75
|
+
|
|
76
|
+
```ymd
|
|
77
|
+
[my_macro]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
There are two kinds of macros.
|
|
81
|
+
|
|
82
|
+
### Reference macros: `[name]:`
|
|
83
|
+
|
|
84
|
+
A reference macro is pure text substitution. When you write `[name]`, the compiler replaces it with the macro's body and interprets the result recursively.
|
|
85
|
+
|
|
86
|
+
```ymd
|
|
87
|
+
[site_name]: My Blog
|
|
88
|
+
|
|
89
|
+
Welcome to [site_name]!
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Reference macros are defined with a colon (`:`) after the closing bracket. The body extends until the next blank line or the next macro definition, whichever comes first. The body is **trimmed** (leading and trailing whitespace are removed).
|
|
93
|
+
|
|
94
|
+
```ymd
|
|
95
|
+
[site_name]: My Blog
|
|
96
|
+
[author]: Me
|
|
97
|
+
|
|
98
|
+
Welcome to [site_name] by [author]!
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Trap**: If you forget a blank line between a macro body and the next macro definition, the second definition terminates the first one immediately:
|
|
102
|
+
|
|
103
|
+
```ymd
|
|
104
|
+
[#] BAD: [author] is NOT inside [site_name]'s body
|
|
105
|
+
[site_name]: My Blog
|
|
106
|
+
[author]: Me
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Function macros: `[name]=`
|
|
110
|
+
|
|
111
|
+
A function macro is a snippet of JavaScript that runs at compile time. It is defined with an equals sign (`=`). The last evaluated expression in the block becomes the return value and is spliced into the output.
|
|
112
|
+
|
|
113
|
+
```ymd
|
|
114
|
+
[upper]=
|
|
115
|
+
const text = this.capture_until('\n')
|
|
116
|
+
text.toUpperCase()
|
|
117
|
+
|
|
118
|
+
[upper]hello world
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Function macros are executed inside a sandboxed context. They have access to the standard library (`read`, `render_markdown`, `render_katex`, etc.) and several special runtime values.
|
|
122
|
+
|
|
123
|
+
### Special values available inside function macros
|
|
124
|
+
|
|
125
|
+
| Name | Description |
|
|
126
|
+
|------|-------------|
|
|
127
|
+
| `this.remaining` | The unparsed text that appears **after** the macro invocation. |
|
|
128
|
+
| `this.interpret(str)` | Recursively compile a substring. Definitions visible at the **call site** are available (dynamic scoping). |
|
|
129
|
+
| `this.capture_until(delimiter)` | Consume `this.remaining` up to the first occurrence of `delimiter`, returning the text before it. The delimiter itself is also consumed from `remaining`. |
|
|
130
|
+
| `this.skip(n)` | Skip `n` characters from `this.remaining`. |
|
|
131
|
+
| `this.std_call(has_content?)` | Parse the standard option/argument DSL that many built-in macros use. See below. |
|
|
132
|
+
|
|
133
|
+
### The `std_call` convention
|
|
134
|
+
|
|
135
|
+
Most built-in macros use `std_call()` to parse a small DSL after the macro name:
|
|
136
|
+
|
|
137
|
+
```ymd
|
|
138
|
+
[macro].option1.option2(arg1)(arg2)>>block content<<
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`std_call()` returns an object with:
|
|
142
|
+
|
|
143
|
+
- `opts`: array of dot-prefixed options (e.g. `['option1', 'option2']`).
|
|
144
|
+
- `args`: array of parenthesized or braced arguments (e.g. `['arg1', 'arg2']`).
|
|
145
|
+
- `block`: the content between `>>` and a matching `<<` (or, if `has_content` is true, the rest of the line after a leading space).
|
|
146
|
+
|
|
147
|
+
Example usage inside a macro definition:
|
|
148
|
+
|
|
149
|
+
```ymd
|
|
150
|
+
[my_macro]=
|
|
151
|
+
const {opts, args, block} = std_call(true)
|
|
152
|
+
// opts -> ['center']
|
|
153
|
+
// args -> ['100px']
|
|
154
|
+
// block -> 'hello'
|
|
155
|
+
|
|
156
|
+
[my_macro].center(100px) hello
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Paragraphs and Indentation
|
|
162
|
+
|
|
163
|
+
Nattoppet automatically wraps indented prose in `<p>` tags.
|
|
164
|
+
|
|
165
|
+
- A line that begins with **two spaces** starts a paragraph.
|
|
166
|
+
- Paragraphs are separated by **blank lines** (`\n\n`).
|
|
167
|
+
- Macros inside indented text are expanded normally.
|
|
168
|
+
- Non-indented text passes through unchanged (useful for raw HTML).
|
|
169
|
+
|
|
170
|
+
```ymd
|
|
171
|
+
This becomes a paragraph.
|
|
172
|
+
|
|
173
|
+
This becomes a second paragraph.
|
|
174
|
+
|
|
175
|
+
<div>This is raw HTML, not a paragraph.</div>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Nesting behavior**: If every line in a block is indented, each line can open its own nested `<p>`. This is usually not what you want; keep paragraphs as contiguous indented blocks separated by blank lines.
|
|
179
|
+
|
|
180
|
+
**Trap**: Mixed indented and non-indented blocks can produce surprising nesting. When in doubt, separate prose from HTML with blank lines.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Mixins
|
|
185
|
+
|
|
186
|
+
A mixin inlines another file into the current token stream.
|
|
187
|
+
|
|
188
|
+
```ymd
|
|
189
|
+
[mixin] path/to/file.ymd
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Resolution rules**:
|
|
193
|
+
|
|
194
|
+
- If the path has an extension, it is used as-is.
|
|
195
|
+
- If the path has no extension and a file with that exact name exists, it is used.
|
|
196
|
+
- Otherwise, `.ymd` is appended (e.g. `[mixin] common` resolves to `common.ymd`).
|
|
197
|
+
- **All mixin paths are resolved relative to the nattoppet installation directory** (where `compiler.ts` lives). Paths starting with `./` are **not** treated specially; they also resolve relative to the compiler root.
|
|
198
|
+
|
|
199
|
+
### Non-`.ymd` mixins
|
|
200
|
+
|
|
201
|
+
If you mixin a `.css`, `.js`, or other text file, it is read as raw text and emitted verbatim.
|
|
202
|
+
|
|
203
|
+
**Trap**: `[mixin]` reads files with UTF-8 encoding. Do **not** use it for binary files (like `.wasm` or images); they will be corrupted. Use `[require]` or `[img]` instead, which read files as buffers.
|
|
204
|
+
|
|
205
|
+
### `[#slot]` — Content Injection
|
|
206
|
+
|
|
207
|
+
A mixin can contain a `[#slot]` marker. When the mixin is expanded, the caller's tokens that appear **after** the `[mixin]` directive replace the slot.
|
|
208
|
+
|
|
209
|
+
```ymd
|
|
210
|
+
[#] -- layout.ymd --
|
|
211
|
+
<!doctype html>
|
|
212
|
+
<title>[title]</title>
|
|
213
|
+
[#slot]
|
|
214
|
+
|
|
215
|
+
[#] -- page.ymd --
|
|
216
|
+
[mixin] layout.ymd
|
|
217
|
+
|
|
218
|
+
[title]: My Page
|
|
219
|
+
<h1>Hello</h1>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Trap**: A mixin may contain **at most one** `[#slot]`. Multiple slots raise a compilation error.
|
|
223
|
+
|
|
224
|
+
### `common.ymd`
|
|
225
|
+
|
|
226
|
+
`common.ymd` ships with nattoppet in the project root. Since mixin paths resolve relative to the compiler root, `[mixin] common` finds it automatically. All built-in themes include it, which is why macros like `[require]`, `[img]`, and `[code]` are available in themed documents.
|
|
227
|
+
|
|
228
|
+
**Trap**: If you write a bare `.ymd` without a theme and want `[require]`, you must add `[mixin] common.ymd` yourself. `[require]` is not a compiler built-in; it is a function macro defined in `common.ymd`.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Common Macros
|
|
233
|
+
|
|
234
|
+
These macros are available whenever you include `common.ymd` (all built-in themes do this automatically).
|
|
235
|
+
|
|
236
|
+
### `[require]` — Inline assets intelligently
|
|
237
|
+
|
|
238
|
+
```ymd
|
|
239
|
+
[require](style.less)
|
|
240
|
+
[require](app.coffee)
|
|
241
|
+
[require](app.js)
|
|
242
|
+
[require](data.json)
|
|
243
|
+
[require](module.wasm)
|
|
244
|
+
[require].my-id(lib/katex.css)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
`[require]` detects the file extension and wraps the content appropriately:
|
|
248
|
+
|
|
249
|
+
| Extension | Output |
|
|
250
|
+
|-----------|--------|
|
|
251
|
+
| `.less` | Compiles to CSS inside `<style>` |
|
|
252
|
+
| `.css` | Inlines inside `<style>` |
|
|
253
|
+
| `.coffee` | Compiles to JS inside `<script>` |
|
|
254
|
+
| `.js` | Inlines inside `<script>` |
|
|
255
|
+
| `.md` | Renders as Markdown HTML |
|
|
256
|
+
| `.wasm` | Inlines as compressed base64 with auto-decompress and `WebAssembly.instantiateStreaming` |
|
|
257
|
+
| `.json` | Inlines as compressed base64 with auto-decompress and `JSON.parse` |
|
|
258
|
+
| other | Raw text |
|
|
259
|
+
|
|
260
|
+
Options:
|
|
261
|
+
- `.name` sets `id="name"` on the generated `<style>` or `<script>` tag. The entire option string becomes the id.
|
|
262
|
+
|
|
263
|
+
**WASM/JSON specifics**:
|
|
264
|
+
- The file is compressed with raw deflate and encoded as base64. A small inline script decompresses it via the browser's `DecompressionStream("deflate-raw")`, then instantiates or parses it.
|
|
265
|
+
- The resulting exports/object is exposed on `window.<filename_without_extension>`.
|
|
266
|
+
- `window.wasm_ready` and `window.json_ready` promises coordinate multiple files so you can await them before accessing the data.
|
|
267
|
+
|
|
268
|
+
### `[img]` — Inline images
|
|
269
|
+
|
|
270
|
+
```ymd
|
|
271
|
+
[img].center(photo.png)(Alt text)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
- Arguments are separate parenthesized groups. Do **not** use commas.
|
|
275
|
+
- First argument is the file path.
|
|
276
|
+
- Second argument is alt text.
|
|
277
|
+
- Options become CSS classes.
|
|
278
|
+
- The image is inlined as a base64 data URI.
|
|
279
|
+
- `.center` wraps the image in a centered `<div>`.
|
|
280
|
+
|
|
281
|
+
### Headers
|
|
282
|
+
|
|
283
|
+
```ymd
|
|
284
|
+
[h2] Section Title
|
|
285
|
+
[h3] Subsection Title
|
|
286
|
+
[h4] Sub-subsection Title
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
These capture the rest of the line, interpret it, and wrap it in the corresponding HTML tag.
|
|
290
|
+
|
|
291
|
+
### `[quote]` — Blockquote
|
|
292
|
+
|
|
293
|
+
```ymd
|
|
294
|
+
[quote]>>
|
|
295
|
+
A famous saying.
|
|
296
|
+
<<
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Options become CSS classes on the `<blockquote>`.
|
|
300
|
+
|
|
301
|
+
### `[#]` — Comments
|
|
302
|
+
|
|
303
|
+
```ymd
|
|
304
|
+
[#] This line is ignored.
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
`[#]` consumes the rest of the line (or a `>>...<<` block) and produces nothing. It is a regular function macro, not special compiler syntax. It works by evaluating `std_call(true)` for its side effect (consuming input) and returning `''` via the comma operator.
|
|
308
|
+
|
|
309
|
+
### `[-]` — List items
|
|
310
|
+
|
|
311
|
+
```ymd
|
|
312
|
+
[-]>>
|
|
313
|
+
First item
|
|
314
|
+
<<
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Wraps content in `<li>`. Options become classes.
|
|
318
|
+
|
|
319
|
+
### `[file]` — Download links
|
|
320
|
+
|
|
321
|
+
```ymd
|
|
322
|
+
[file](report.pdf)(Download the report)
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Creates a download link with the file inlined as a base64 data URI. Arguments are separate parenthesized groups.
|
|
326
|
+
|
|
327
|
+
### `[code]` — Code blocks
|
|
328
|
+
|
|
329
|
+
```ymd
|
|
330
|
+
[code].js(console.log('hello'))
|
|
331
|
+
|
|
332
|
+
[code].js>>
|
|
333
|
+
function greet() {
|
|
334
|
+
return 'hi'
|
|
335
|
+
}
|
|
336
|
+
<<
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
- The **language is a dot-option** (e.g. `.js`), not a parenthesized argument.
|
|
340
|
+
- With a single-line argument, wraps in `<code class="language-<option>">`.
|
|
341
|
+
- Without an argument but with a block, wraps in `<pre><code>`.
|
|
342
|
+
|
|
343
|
+
**Trap**: Writing `[code](js) ...` puts `js` into the code content and loses the actual code. Always use the dot-option form.
|
|
344
|
+
|
|
345
|
+
### `[link]` — Links
|
|
346
|
+
|
|
347
|
+
```ymd
|
|
348
|
+
[link](https://example.com)(Click here)
|
|
349
|
+
[link](https://example.com)
|
|
350
|
+
The link text is captured from the next line if omitted.
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Arguments are separate parenthesized groups. Do not use commas.
|
|
354
|
+
|
|
355
|
+
### `[sup]` — Superscript
|
|
356
|
+
|
|
357
|
+
```ymd
|
|
358
|
+
[sup](2) -- produces <sup>2</sup>
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### `[eval]` / `[eval*]` — Run JavaScript at compile time
|
|
362
|
+
|
|
363
|
+
```ymd
|
|
364
|
+
[eval*]>> this.counter = 0 <<
|
|
365
|
+
|
|
366
|
+
[eval]>> this.counter++ <<
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
- `[eval]` runs the block and returns the result (inserted into output).
|
|
370
|
+
- `[eval*]` runs the block and discards the result (useful for side effects like initializing state).
|
|
371
|
+
- These macros call `std_call(true)`, so they require either a `>>...<<` block or a space followed by the rest of the line.
|
|
372
|
+
|
|
373
|
+
**Note**: Both execute in the same sandboxed context as other macros. Variables you set (e.g. `this.index = 0`) persist across macro calls but are scoped to a single compile.
|
|
374
|
+
|
|
375
|
+
### `[cn]` — CJK text wrapping helper
|
|
376
|
+
|
|
377
|
+
```ymd
|
|
378
|
+
[cn]>>
|
|
379
|
+
中
|
|
380
|
+
文
|
|
381
|
+
内
|
|
382
|
+
容
|
|
383
|
+
<<
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
The `[cn]` macro removes **single newlines** between CJK characters and between CJK and adjacent non-whitespace ASCII characters. This lets you write CJK prose with hard line breaks in the source without introducing unwanted spaces in the HTML output. The block content is recursively interpreted first, so macros inside `[cn]` are expanded normally.
|
|
387
|
+
|
|
388
|
+
**Trap**: Double newlines are preserved as paragraph breaks. Newlines between CJK and whitespace-leading ASCII are also preserved.
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## Built-in Themes
|
|
393
|
+
|
|
394
|
+
Themes are mixins that provide a complete page scaffold (doctype, meta tags, styles, scripts). You typically put `[mixin] theme_name` at the top of your file.
|
|
395
|
+
|
|
396
|
+
### `vue` — Articles and blog posts
|
|
397
|
+
|
|
398
|
+
```ymd
|
|
399
|
+
[mixin] vue
|
|
400
|
+
|
|
401
|
+
[title]: My Article
|
|
402
|
+
|
|
403
|
+
[h2] Introduction
|
|
404
|
+
Lorem ipsum...
|
|
405
|
+
|
|
406
|
+
[h3] Subsection
|
|
407
|
+
More text...
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Features:
|
|
411
|
+
- Generates `<!doctype html>`, viewport meta, and a title suffix (`| ylxdzsw's blog`).
|
|
412
|
+
- `[h2]` and `[h3]` are automatically wrapped in anchor tags with ids (`sec0`, `sec1`, ...), enabling deep-linking.
|
|
413
|
+
- Includes `vue.css` and `common.ymd`.
|
|
414
|
+
|
|
415
|
+
### `form` — Interactive calculators
|
|
416
|
+
|
|
417
|
+
```ymd
|
|
418
|
+
[mixin] form
|
|
419
|
+
|
|
420
|
+
[title]: Prime Spiral
|
|
421
|
+
|
|
422
|
+
[number].n N = 100
|
|
423
|
+
[checkbox].show_diag Show diagonals
|
|
424
|
+
|
|
425
|
+
[require](main.js)
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Features:
|
|
429
|
+
- Generates labeled input fields: `[text]`, `[number]`, `[checkbox]`.
|
|
430
|
+
- Each field takes an optional `.name` option. The inline content block becomes the label text.
|
|
431
|
+
- A **Run** button is automatically appended. When clicked, it gathers inputs into an object and calls `window.run(args)`.
|
|
432
|
+
- The result is displayed in a `<pre>` block.
|
|
433
|
+
- Input values are synced to the URL hash, making state shareable.
|
|
434
|
+
- Includes `form.css`, `form.js`, and `common.ymd`.
|
|
435
|
+
|
|
436
|
+
**Note**: `N = 100` in the example above is label text, not a default input value. The form theme does not set `value` attributes automatically.
|
|
437
|
+
|
|
438
|
+
### `katex` — Math rendering
|
|
439
|
+
|
|
440
|
+
```ymd
|
|
441
|
+
[mixin] katex
|
|
442
|
+
|
|
443
|
+
[$]E = mc^2$
|
|
444
|
+
|
|
445
|
+
[$$]>>
|
|
446
|
+
\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}
|
|
447
|
+
<<
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Features:
|
|
451
|
+
- `[$] ... $` — inline math. Terminates at the first `$`.
|
|
452
|
+
- `[$$]>> ... <<` — display math. Must use the block delimiter for multi-line equations.
|
|
453
|
+
- Includes vendored `katex.css` with all fonts inlined as base64. The output is fully offline-capable.
|
|
454
|
+
- Unicode math symbols (e.g. `α`, `β`, `∫`) and CJK inside `\text{}` work directly in the source.
|
|
455
|
+
|
|
456
|
+
**Trap**: `[$$]` without the `>>...<<` block form only captures until the end of the line. For any multi-line equation, always use the block form.
|
|
457
|
+
|
|
458
|
+
**Trap**: `[$]` terminates at the first bare `$`. You cannot use literal `$` characters (e.g. dollar amounts) inside inline math because there is no escaping mechanism.
|
|
459
|
+
|
|
460
|
+
### `koa` — Long documents with navigation
|
|
461
|
+
|
|
462
|
+
```ymd
|
|
463
|
+
[mixin] koa
|
|
464
|
+
|
|
465
|
+
[title]: Documentation
|
|
466
|
+
|
|
467
|
+
[section] Getting Started
|
|
468
|
+
Content here...
|
|
469
|
+
|
|
470
|
+
[section] Advanced Topics
|
|
471
|
+
More content...
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
Features:
|
|
475
|
+
- `[section]` creates a new section and auto-populates a hamburger-nav sidebar with a table of contents.
|
|
476
|
+
- Each section gets an id (`sec0`, `sec1`, ...).
|
|
477
|
+
- Includes `koa.css` and `common.ymd`.
|
|
478
|
+
|
|
479
|
+
**Note**: `[list]` is an internal macro that generates the navigation menu HTML. You do not invoke it directly.
|
|
480
|
+
|
|
481
|
+
### `ppt` — Slide decks
|
|
482
|
+
|
|
483
|
+
```ymd
|
|
484
|
+
[mixin] ppt
|
|
485
|
+
|
|
486
|
+
[title]: My Talk
|
|
487
|
+
|
|
488
|
+
[section] Slide One
|
|
489
|
+
- Bullet A
|
|
490
|
+
- Bullet B
|
|
491
|
+
|
|
492
|
+
[section].dark Slide Two
|
|
493
|
+
Dark-themed slide.
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
Features:
|
|
497
|
+
- `[section]` creates a slide.
|
|
498
|
+
- Options after `[section]` become CSS classes on the slide `<section>`. Only the **first** leading dot is replaced with a space, so `[section].dark.centered` produces `class="scen dark.centered"` rather than two separate classes. Use a single class name or write the classes as one option.
|
|
499
|
+
- Elements inside slides can have animation classes: `.click`, `.delay`, `.sim`, plus timing classes `.short`, `.long`, `.fast`, `.slow`.
|
|
500
|
+
- `[foot]` creates a `<footer>` element.
|
|
501
|
+
- `[span]` creates a vertical spacer `<div>` (takes a length argument).
|
|
502
|
+
- Includes `ppt.css`, `ppt.js`, and `common.ymd`.
|
|
503
|
+
|
|
504
|
+
### `tml` — Timeline / horizontal articles
|
|
505
|
+
|
|
506
|
+
```ymd
|
|
507
|
+
[mixin] tml
|
|
508
|
+
|
|
509
|
+
[title]: Timeline
|
|
510
|
+
|
|
511
|
+
[article] 2024-01
|
|
512
|
+
Event description...
|
|
513
|
+
|
|
514
|
+
[article] 2024-06
|
|
515
|
+
Another event...
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
Features:
|
|
519
|
+
- `[article]` wraps each entry in an `<article>` tag.
|
|
520
|
+
- The title becomes an `<h2>` with an auto-generated anchor id.
|
|
521
|
+
- Includes `tml.css`, `tml.js` (enables horizontal wheel scrolling), and `common.ymd`.
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## Preprocessor Support
|
|
526
|
+
|
|
527
|
+
`[require]` in `common.ymd` automatically handles several preprocessors:
|
|
528
|
+
|
|
529
|
+
- **Markdown** (`.md`) — parsed with `marked` and emitted as HTML.
|
|
530
|
+
- **CoffeeScript** (`.coffee`) — compiled to JavaScript with `bare: true`.
|
|
531
|
+
- **Less** (`.less`) — compiled to CSS synchronously.
|
|
532
|
+
|
|
533
|
+
No additional configuration is needed. Just require the file and the compiler handles the rest.
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
## Scoping and Name Resolution
|
|
538
|
+
|
|
539
|
+
Understanding how macros find each other is essential for non-trivial documents.
|
|
540
|
+
|
|
541
|
+
### Forward references
|
|
542
|
+
|
|
543
|
+
Reference macros (`[name]:`) can be used **before** they are defined:
|
|
544
|
+
|
|
545
|
+
```ymd
|
|
546
|
+
Before [title] after
|
|
547
|
+
|
|
548
|
+
[title]: My Title
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Self-reference is forbidden
|
|
552
|
+
|
|
553
|
+
A reference macro cannot call itself. Doing so produces `definition <name> not found` because the compiler excludes the macro from its own scope to prevent infinite recursion.
|
|
554
|
+
|
|
555
|
+
### Dynamic scoping for function macros
|
|
556
|
+
|
|
557
|
+
When a function macro calls `this.interpret(...)`, the substring is evaluated with access to **all macros defined at the call site**, even if those macros were not visible where the function macro itself was defined.
|
|
558
|
+
|
|
559
|
+
```ymd
|
|
560
|
+
Value: [caller]
|
|
561
|
+
|
|
562
|
+
[caller]=
|
|
563
|
+
this.interpret('[target]')
|
|
564
|
+
|
|
565
|
+
[target]: DYNAMIC
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
This dynamic scoping applies **only** to strings passed to `this.interpret()`. Function macro bodies are JavaScript, not nattoppet markup, so bare `[name]` references inside the JS code itself are not possible.
|
|
569
|
+
|
|
570
|
+
### Shadowing
|
|
571
|
+
|
|
572
|
+
If the same name is defined multiple times, the **earliest** definition wins.
|
|
573
|
+
|
|
574
|
+
```ymd
|
|
575
|
+
[wrapper] after
|
|
576
|
+
|
|
577
|
+
[wrapper]: [shadow]
|
|
578
|
+
|
|
579
|
+
[shadow]: FIRST
|
|
580
|
+
|
|
581
|
+
[shadow]: SECOND
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
This outputs `FIRST after`; `SECOND` is never used. This is a consequence of forward-reference support: the compiler resolves each macro name when it first encounters it, so the first definition takes precedence. Defining the same name twice is usually a mistake; the second definition is silently ignored.
|
|
585
|
+
|
|
586
|
+
### Preserving `remaining` across nested calls
|
|
587
|
+
|
|
588
|
+
`this.interpret` saves and restores `this.remaining`. This makes it safe to call `interpret` inside a function macro without losing your place in the input stream.
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
|
|
592
|
+
## Traps and Workarounds
|
|
593
|
+
|
|
594
|
+
Common pitfalls are listed below. For a quick-reference fix table, see [Troubleshooting](#troubleshooting).
|
|
595
|
+
|
|
596
|
+
### Square brackets in raw content
|
|
597
|
+
|
|
598
|
+
Because `[name]` is the macro invocation syntax, raw square brackets in JavaScript, Markdown, or HTML can be mistakenly interpreted as macro calls if the content inside happens to match a defined macro name.
|
|
599
|
+
|
|
600
|
+
**Workaround**: In practice this is rare because:
|
|
601
|
+
- Inside KaTeX math, the raw string is passed directly to the renderer without macro expansion.
|
|
602
|
+
- Inside `<script>` or `<style>` tags emitted by `[require]`, the content is raw.
|
|
603
|
+
- If you need literal square brackets in prose, define a passthrough macro or write them inside raw HTML tags.
|
|
604
|
+
|
|
605
|
+
### Array subscripts in inline math
|
|
606
|
+
|
|
607
|
+
Writing `a[i]` inside a paragraph can trigger macro lookup for `i`. If `i` is not defined, compilation fails with `definition i not found`.
|
|
608
|
+
|
|
609
|
+
**Workaround**: Use KaTeX math (`[$]a[i][$]`) for mathematical notation containing brackets, or escape the context by wrapping the expression in raw HTML.
|
|
610
|
+
|
|
611
|
+
### Paragraph nesting with all-indented lines
|
|
612
|
+
|
|
613
|
+
If every line in a region is indented by two spaces, each line opens a new nested `<p>`. Keep paragraphs as contiguous blocks, using blank lines to separate them.
|
|
614
|
+
|
|
615
|
+
### Mixin path ambiguity
|
|
616
|
+
|
|
617
|
+
If you have a file named `foo` (no extension) and also `foo.ymd`, `[mixin] foo` picks the bare file. To force the `.ymd` version, write `[mixin] foo.ymd` explicitly.
|
|
618
|
+
|
|
619
|
+
### `capture_until` does not include the delimiter
|
|
620
|
+
|
|
621
|
+
The delimiter passed to `capture_until` is consumed from `remaining` but is **not** included in the returned string. If you need to preserve it, append it manually.
|
|
622
|
+
|
|
623
|
+
### `[$$]` without space or block
|
|
624
|
+
|
|
625
|
+
`[$$]` uses `std_call(true)`. If there is **no space** after `[$$]` and no `>>...<<` block (e.g. `[$$]expr`), `std_call` falls through to the default case with no block, and `render_katex(undefined, true)` is called, which will likely throw an error. Always provide either a space and inline content or the `>>...<<` block form.
|
|
626
|
+
|
|
627
|
+
---
|
|
628
|
+
|
|
629
|
+
## Troubleshooting
|
|
630
|
+
|
|
631
|
+
| Symptom | Likely Cause | Fix |
|
|
632
|
+
|---------|--------------|-----|
|
|
633
|
+
| `definition X not found` | You invoked `[X]` but never defined `[X]:` or `[X]=`. | Define the macro, or check for typos. |
|
|
634
|
+
| `definition X not found` inside a self-referential ref | Reference macros cannot reference themselves. | Use a function macro if you need recursion, or restructure. |
|
|
635
|
+
| Multiple `[#slot]` error | A mixin contains more than one slot. | Remove extra slots, or split into multiple mixins. |
|
|
636
|
+
| `mixin not found: X` | The mixin path could not be resolved. | Check the path; remember all mixin paths resolve relative to the compiler root, not the input file. |
|
|
637
|
+
| Math does not render, or renders inline when it should be display | Missing `>>...<<` block delimiters around multi-line `[$$]`. | Use `[$$]>>...<<`. |
|
|
638
|
+
| CJK text has unwanted spaces | Hard line breaks between CJK characters are preserved as spaces by HTML. | Wrap the text in `[cn]>>...<<`. |
|
|
639
|
+
| Paragraphs wrap raw HTML | Indented HTML is treated as prose. | Remove the leading two spaces from HTML lines. |
|
|
640
|
+
| KaTeX fonts not loading | You are not using `[mixin] katex` and instead only included the JS. | Include the `katex` mixin (or manually inline `katex.css`) because the vendored CSS contains base64 fonts. |
|
|
641
|
+
| `less failed to compile synchronously` | The Less compiler returned an error. | Check your `.less` file for syntax errors. |
|
|
642
|
+
| `[code](js)` loses code content | Language must be a dot-option, not a parenthesized argument. | Use `[code].js(...)`. |
|
|
643
|
+
| `[img]` or `[link]` broken | Arguments must be separate parenthesized groups. | Use `[img].center(path)(alt)`, not `[img].center(path, alt)`. |
|
|
644
|
+
|
|
645
|
+
---
|
|
646
|
+
|
|
647
|
+
## Building and Deploying
|
|
648
|
+
|
|
649
|
+
### Native executable bundling
|
|
650
|
+
|
|
651
|
+
You can turn a `.ymd` file into a desktop application:
|
|
652
|
+
|
|
653
|
+
```bash
|
|
654
|
+
bun run nattoppet-native.ts init # create native/ Rust scaffold
|
|
655
|
+
bun run nattoppet-native.ts bundle file.ymd # compile HTML into native/
|
|
656
|
+
bun run nattoppet-native.ts build file.ymd # compile HTML and run cargo build --release
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
The result is a single native binary (Linux/macOS/Windows) that embeds the HTML and renders it with a web-view.
|
|
660
|
+
|
|
661
|
+
### Project scaffolding
|
|
662
|
+
|
|
663
|
+
```bash
|
|
664
|
+
bun run nattoppet-init.ts form
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
This generates a starter `main.ymd`, `main.js`, and optionally Rust/WASM boilerplate plus a GitHub Pages deployment workflow.
|
|
668
|
+
|
|
669
|
+
---
|
|
670
|
+
|
|
671
|
+
## How It Works Under the Hood
|
|
672
|
+
|
|
673
|
+
The nattoppet compiler processes a `.ymd` file in two passes:
|
|
674
|
+
|
|
675
|
+
1. **Tokenization**: The source is split into a sequence of tokens: raw text chunks (`code`), macro definitions (`ref`/`fn`), mixin directives (`mixin`), and slot markers (`slot`). During this phase, `[mixin] path` files are read and inlined recursively.
|
|
676
|
+
2. **Interpretation**: The token stream is walked left-to-right. Raw text is interpreted (paragraphs and nested macros are expanded); raw asset tokens are emitted as-is.
|
|
677
|
+
|
|
678
|
+
Function macros are executed via `vm.runInContext` in a sandboxed environment. The completion value of the JavaScript block (the value of the last expression statement) is converted to a string and spliced into the output.
|
|
679
|
+
|
|
680
|
+
You do not need to understand these internals to use nattoppet, but knowing the two-phase model helps explain why mixins are fully resolved before any macros run.
|
|
681
|
+
|
|
682
|
+
---
|
|
683
|
+
|
|
684
|
+
## Summary Cheat Sheet
|
|
685
|
+
|
|
686
|
+
```ymd
|
|
687
|
+
[#] Structure
|
|
688
|
+
[mixin] theme_name [#] include a theme
|
|
689
|
+
[mixin] common [#] include common macros
|
|
690
|
+
[#slot] [#] injection point inside a mixin
|
|
691
|
+
|
|
692
|
+
[#] Macro definitions
|
|
693
|
+
[ref]: text [#] reference macro
|
|
694
|
+
[fn]= js_code [#] function macro
|
|
695
|
+
|
|
696
|
+
[#] Common macros
|
|
697
|
+
[require](file.ext) [#] inline asset (auto-detect type)
|
|
698
|
+
[require].id(path) [#] with element id
|
|
699
|
+
[img].class(path)(alt) [#] inline image
|
|
700
|
+
[h2] Title [#] header
|
|
701
|
+
[quote]>>...<< [#] blockquote
|
|
702
|
+
[code].lang(code) [#] inline code
|
|
703
|
+
[code].lang>>...<< [#] code block
|
|
704
|
+
[link](url)(text) [#] hyperlink
|
|
705
|
+
[file](path)(text) [#] download link
|
|
706
|
+
[sup](text) [#] superscript
|
|
707
|
+
[#] comment [#] comment / ignore
|
|
708
|
+
[-]>>...<< [#] list item
|
|
709
|
+
[eval]>> expr << [#] compile-time JS (returns value)
|
|
710
|
+
[eval*]>> expr << [#] compile-time JS (side effect only)
|
|
711
|
+
[cn]>>...<< [#] CJK newline removal
|
|
712
|
+
|
|
713
|
+
[#] Math
|
|
714
|
+
[$] ... $ [#] inline math
|
|
715
|
+
[$$]>>...<< [#] display math
|
|
716
|
+
|
|
717
|
+
[#] Form theme
|
|
718
|
+
[text].name Label [#] text input
|
|
719
|
+
[number].name Label [#] number input
|
|
720
|
+
[checkbox].name Label [#] checkbox
|
|
721
|
+
|
|
722
|
+
[#] Koa theme
|
|
723
|
+
[section] Title [#] new section + TOC entry
|
|
724
|
+
|
|
725
|
+
[#] PPT theme
|
|
726
|
+
[section].class Title [#] new slide
|
|
727
|
+
[foot] text [#] footer
|
|
728
|
+
[span](length) [#] vertical spacer
|
|
729
|
+
|
|
730
|
+
[#] TML theme
|
|
731
|
+
[article] Title [#] new timeline article
|
|
732
|
+
```
|