# scholatex

**Print-ready teaching worksheets, without writing LaTeX.** `scholatex` is a
small markup language for producing handouts, exercise sheets and short
assessments. It is not a general-purpose replacement for LaTeX — it is a
focused, consistent little language for the documents a teacher actually
makes: a few framed exercises, a table of results, some simple formulas, an
image or two, laid out cleanly on a page meant to be printed.

It compiles to LuaLaTeX, so the output has full LaTeX typesetting quality,
but the syntax is meant to be read and edited in minutes by someone who does
not know LaTeX. No `\begin{tabular}{|c|c|}`, no counting ampersands, no
remembering which package draws a coloured box — one tag syntax covers text,
tables, images, simple maths, framed boxes, lists and full-page named-area
layouts, the building blocks of a worksheet.

It is built for the people LaTeX usually loses: teachers preparing classroom
materials, anyone who wants a clean printable sheet without climbing the
LaTeX learning curve. The full power of LaTeX stays one escape hatch away —
any raw command still works — but for everyday teaching documents you rarely
need it.

You write a `.tex` file with a tiny, readable syntax between
`\begin{document}` and `\end{document}`. At compile time the `scholatex` class
reads that body, transpiles it to LaTeX, and typesets it. Nothing to install
beyond a LuaLaTeX distribution.

```latex
% !TeX program = lualatex
\documentclass[margins={20,15,20,15}, size=12, imgdir=IMG]{scholatex}
\begin{document}

<navy b 18pt c>My first sl document

This is a normal paragraph. <b>{Bold}, <i>{italic}, <red>{red}.

<box line:Navy fill:AliceBlue radius:3 title:{A framed note}>{
Boxes, tables, images and maths all use the same tag syntax.
}

\end{document}
```

Compare a term report — three lines of `scholatex` against the dozen lines of
`tabular`, `\multicolumn`, `\rowcolor` and `\hline` they replace:

```
<table [mc, ml, mc, mc] borders header fill:AliceBlue line:Navy headerfill:Navy headertext:White>{
<colspan:4 mc>{Term report}
Day | Subject | Mark | Coef.
<rowspan:2 mc>{Monday} | Maths | 15 | 4
. | French | 12 | 3
}
```

---

## The one rule

Everything is a **tag**: `<attributes>` followed either by `{content}`
(inline) or by a line that ends in `{` (a block). Attribute words follow a
single case convention:

| Form | Meaning | Examples |
|------|---------|----------|
| `lowercase` | a short keyword or base colour | `b`, `i`, `c`, `red`, `2tab` |
| `CamelCase` | an extended CSS colour (147 of them) | `SteelBlue`, `Crimson` |
| `UPPERCASE` | a font name | `DEJAVU SANS` |

The order of words inside a tag never matters — emission is always
normalised (page break, then vertical skips, then alignment, then tabs,
then styles).

---

## Class options

Set in `\documentclass[...]{scholatex}`:

| Option | Default | Meaning |
|--------|---------|---------|
| `margins` | `20` | `N` (all sides) or `{top,right,bottom,left}` in mm |
| `font` | `Latin Modern Roman` | main text font |
| `mathfont` | `Latin Modern Math` | math font |
| `size` | `11` | base font size in pt |
| `imgdir` | `img` | folder(s) searched for bare image names; accepts a comma-separated list, e.g. `{IMG, IMAGES/PNG}` |
| `tabwidth` | `8` | width of one tab, in mm (a large Seyès square) |
| `lineheight` | `8` | height of one skipped line, in mm |
| `scriptscale` | `100` | scale (%) of `up`/`down` scripts |
| `padding` | `2` | inner padding (mm) between a box/grid-area frame and its content; a box may override it locally with `sep:N` |
| `lang` | `fr` | decimal separator convention: `fr` (comma, the world-wide majority — all of continental Europe, Latin America, francophone Africa…) or `en` (point, used in anglophone countries, China, Japan, Mexico…). Affects both literal decimals typed in maths (`$3.5$`) and interpolated ones (`#x`), so the two always match. |
| `untrusted` | `false` | when `true`, runs the document's Lua (`let`, `#{…}`, loop conditions) in a restricted sandbox: no `os`/`io`/`require`/`load`, a runaway-loop ceiling, and capped `string.rep`/`format`. Hardens the `scholatex` layer only — see [Security](#security). |

Headings carry no extra vertical space of their own — only the normal
interline spacing separates them from surrounding text, identically at every
level. They have no built-in colour either: to style a heading (colour,
weight, size, or a line skip before it), fold a heading keyword into an alias
and use it, e.g. `let h = <line navy b section>` then `<h>My title` for a
numbered section, preceded by a blank line, in navy bold.

---

## Text attributes

**Inline styles** — `b` bold, `i` italic, `u` underline, `emph`,
`tt` typewriter, `sf` sans-serif, `sc` small caps.

**Colours** — short keywords (`red`, `blue`, `green`, `navy`, `orange`,
`purple`, `teal`, `brown`, `gray`, `pink`, …) or any of the 147 CSS
colours in CamelCase (`Tomato`, `SteelBlue`, `ForestGreen`, …).

**Fonts and sizes** — a font name in CAPITALS (`<DEJAVU SANS>{…}`);
sizes as `Npt` or `Npx` (`<14pt>{…}`).

**Alignment** — `l` left, `c` centre, `r` right, `j` justified
(the same letters as table columns).

**Tabs and skips** (the number is always a prefix):
`Ntab` indents the first line by N tabs; vertical skips follow singular /
plural agreement: `line` or `1line` skips one line, `2lines`, `3lines`, …
skip several. Bare `tab` = `1tab`.

**Scripts** — `upN` raises text by N mm (superscript-like),
`downN` lowers it (subscript-like): `x<up4>{2}`, `H<down2>{2}O`.

**Page break** — `nextpage`.

**Section headings** — `section`, `subsection`, `subsubsection` give the
three heading levels. LaTeX numbers them automatically and resets the
sub-counters on its own, so you never write a number by hand:

```
<section>First topic
<subsection>A detail
<subsubsection>A finer point
```

renders as `1 First topic`, `1.1 A detail`, `1.1.1 A finer point`.

A heading can also be written as a **block** carrying a `title:` option, with
its body in braces. Layout like `tab` goes on the body paragraphs (a heading
takes only colour/style words):

```
<subsection title:{A heading}>{
<tab>This body paragraph is indented on its first line.
}
```

The lightweight form `<section>Title` (heading only, no body braces) keeps
working unchanged for everyday use.

A **table of contents** is printed with `<tableofcontents>`; it lists every
heading with its number and page. Give it a title in braces — it is centred,
LaTeX's own default heading is suppressed (so nothing is duplicated), and the
document continues on a fresh page after the contents:

```
<tableofcontents>{Table of contents}
```

Combine anything in one tag:

```
<np 2lines 2tab j navy b>{A new page, a two-line skip, a two-tab indent,
justified, navy, bold — all before the first letter of the text.}
```

---

## Aliases and macros — the factoring tool

This is what makes `scholatex` scale. Define a style **once**, at the top of the
document, then name it everywhere instead of repeating its attributes. One
edit at the definition restyles the whole document.

```
let title = <navy b 18pt c>          % style alias
let h1    = <navy b section>         % a heading style, reusable
let p     = <tab>                    % a standard indented paragraph
<title>My heading
<h1>First topic
<p>{ A paragraph, indented and justified, named not described. }

let n = 7                            % value, usable in #{...}
Seven squared is #{n*n}.

let greet{name} = Hello #name!       % text macro with parameters
<...>{greet{World}}
```

Change `let h1 = <navy b section>` to `<ForestGreen b section>` once and
**every** first-level heading follows — the single point of control that
keeps a long worksheet consistent. (A justified paragraph is the default, so
`let p = <tab>` and `let p = <tab j>` are equivalent: `j` only restates what
LaTeX already does.) The same idea applies to whole components further down —
see [Block aliases](#block-aliases-the-factoring-tool).

---

## Tables

Columns are declared in brackets, **one two-letter placement code per
column** — there is no single-letter form. The first letter is vertical
(`t` top, `m` middle, `b` bottom), the second horizontal (`l`, `c`, `r`). So
`mc` is middle-centre, `br` is bottom-right, `tl` top-left. This is the only
placement syntax `scholatex` uses, in tables as in boxes and grids.

Columns share the page equally; `N:` before the code fixes a width in mm
(`[40:tl, 30:mc]`). Cells are separated by `|`, rows by line breaks, and
`\\` breaks a line inside a cell.

```
<table [mc, tl, br] borders header>{
Figure | Formula | Value
<img 25>{cat.png} | $1/2 + 1/3$ | $5/6$
}
```

This places each column independently: the figure centred in its cell, the
formula top-left, the value bottom-right. The vertical part is what lets a
short formula sit at the middle (or top, or bottom) of a row made tall by an
image, instead of always dropping to the baseline. When every cell in a row
is the same height, the vertical letter simply has nothing to redistribute,
so it shows only once a row holds something taller than its neighbours.

`borders` draws rules; `header` bolds the first row. `gap:N` sets the
horizontal spacing between columns, in mm.

A cell may span several columns with `colspan:N`, or several rows with
`rowspan:N` (`N` ≥ 2 in both cases). For a `rowspan`, each cell it covers
on the lines below is marked with a lone `.`, exactly like an empty cell in
a grid template. A spanning cell carries its own two-letter code (`mc` below)
to place its merged content, overriding the columns it covers.

```
<table [mc, ml, mc, mc, ml] borders header gap:3>{
<colspan:5 mc>{Term report}
Day | Subject | Mark | Coef. | Remark
<rowspan:2 mc>{Monday} | Maths | 15 | 4 | Good
. | French | 12 | 3 | Fair
}
```

Each row must cover exactly the declared number of columns once spans and
`.` markers are counted, or `scholatex` reports the mismatch with the source line.

Colour options act **inside** the table, on its cells and rules — no
wrapping box. `fill:` colours every body cell, `line:` colours the rules,
`text:` sets the text colour, and `headerfill:` / `headertext:` style the
header row:

```
<table [mc, ml, mc] borders header gap:3 fill:AliceBlue line:Navy headerfill:Navy headertext:White>{
Day | Subject | Mark
Monday | Maths | 15
}
```

These are not box options: a table has no `radius:`, `title:` or outer
frame of its own. To frame a table or round its corners, wrap it in a
`<box line:... radius:...>{ … }`. With no colour option, the table renders
exactly as before.

Vertical placement (`m`, `b`) needs a column that has a height to align
within — an auto-width column or a fixed-width `N:` column. The vertical
letter also anchors the content of `rowspan` cells in that column. A per-cell
`l/c/r` tag inside a spanning cell still wins over the column's own setting.

---

## Images

```
<img>{chat.png}        % fills the available width (the cell or line)
<img 25>{chat.png}     % 25 mm wide
<img 25x15>{chat.png}  % fits a 25×15 mm box, ratio preserved
```

With no size, the image is scaled to the full width available to it — the
column of a table cell, or the text width in a paragraph. Give a width in mm
to fix the size instead.

A bare name is searched in each folder of `imgdir` in turn, then at the
project root. `imgdir` accepts several folders as a comma-separated list,
so images may be spread across directories and still be referenced by name
alone:

```latex
\documentclass[imgdir={IMG, IMAGES/PNG}]{scholatex}
```

An explicit
path always works, with or without `./` (`<img 20>{IMG/PNG/chat.png}`).

---

## Maths

Wrap maths in `$…$`. A small mini-language keeps it light:

| You write | You get |
|-----------|---------|
| `*` | × |
| `+-` | ± |
| `<=` `>=` `!=` | ≤ ≥ ≠ |
| `a/b` | fraction (chained `a/b/c` reads as `(a/b)/c`) |
| `x^2` `x_i` | power / index |
| `sqrt(2)` | √2 |
| `sum(i=1, n)` | ∑ with bounds |
| `abs(x)` | \|x\| |
| `norm(v)` | ‖v‖ |
| `vec(AB)` | →AB (over-arrow vector) |
| `lim(x->0)` | limit, `->` becomes the arrow |
| `pi`, `alpha`, … | Greek letters |
| `inf` | ∞ |

The helpers nest, so the secondary-school staples come for free:
`norm(vec(AB))` is the norm of a vector, `vec(AB) + vec(BC) = vec(AC)` is
Chasles' relation, `abs(x - lim(x->0) f(x))` reads in one line.

Inject a computed value with `#{expr}` (or `#name`), including inside
maths: `$#k^2$`. Decimal numbers follow the `lang` option: with the default
`lang=fr` a literal `$3.5$` and an interpolated `#x` (`x = 3.5`) both render
as `3,5`; with `lang=en` both stay `3.5`. The two are always consistent —
the same separator is applied to typed and computed numbers alike.

### Maths blocks

Matrices and systems are blocks: **one line is one row**, and inside a
matrix `;` separates the entries. Every cell still goes through the
mini-language, so `2x+1` or `1/2` work in a cell.

```
<matrix>{
1 ; 2 ; 3
4 ; 5 ; 6
}
```

`<matrix>` draws parentheses, `<det>` the vertical bars of a determinant,
`<bmatrix>` square brackets. A single `|` inside a row draws the separation
bar of an **augmented matrix**, at the column where you type it (the same
column on every row); it is allowed on `matrix` and `bmatrix`, never on
`det`:

```
<bmatrix>{
2 ; 1 | 7
1 ; -1 | 1
}
```

A `<system>` stacks equations under a brace and aligns them on the first
relational operator, so equalities and inequalities mix freely — one
equation per line, no separator:

```
<system>{
2x + 3y = 7
x - y = 1
}
```

Every row of a matrix must hold the same number of cells, and a bar must sit
at the same column on every row, or `scholatex` reports the mismatch with the
source line.

---

## Lists

`<list:STYLE>{ … }` makes a list; the style is required and comes right
after the name, like a block. One item per non-empty line — no item tag. A
`<list:…>` written under an item becomes its sub-list, nested as deep as you
like, each level with its own style.

```
<list:disc>{
Fruits
<list:circle>{
pommes
poires
}
Légumes
<list:square>{
carottes
navets
}
}
```

Styles — bullets: `none` `disc` `circle` `square`; numbered: `decimal`
`alpha` `ALPHA` `roman` `ROMAN` (the case of the keyword is the case of the
letters/numerals); checkboxes: `check`.

```
<list:ROMAN>{
chapitre 1
chapitre 2
}

<list:check>{
Apporter sa règle
Coller la fiche
}
```

Text attributes follow the style on the same tag and wrap the whole list:
`<list:ROMAN TIMES NEW ROMAN 12pt i>{ … }` sets the items in Times 12 pt
italics. Writing `<list>` without a style is an error.

---

## Boxes

```
<box line:Crimson fill:MistyRose radius:4 title:{A note}>{
Content here.
}
```

Options: `line:` frame colour, `fill:` background, `text:` text colour,
`radius:N` rounded corners (mm), `width:N` or `width:N%`,
`boxrule:N`, `boxsep:N`, `break:yes` (allow page breaks), `title:{…}`,
`titlefill:`, `titletext:`. A line containing only `---` splits a box
into an upper and a lower region.

`<row gap:N>{ … }` lays its child boxes side by side, with equal widths
and equalised heights. A `row` accepts only `<box>` children, written either
on one line (`<box line:Navy>{ short text }`) or in multi-line block form
(`<box line:Navy>{` … `}`) — use the multi-line form when a child itself
contains blocks, tables, or several paragraphs.

A box also takes a **two-letter placement code** (`tl tc tr ml mc mr bl bc
br`, default `tl`) that positions its content: first letter vertical (`t`/
`m`/`b`), second horizontal (`l`/`c`/`r`). The vertical part needs a
`height:` to have room to act — without one the box hugs its content and
`tc`, `mc`, `bc` look the same. In a `row`, each child box carries its own
code, so columns can be aligned independently (`<box bc height:30>{…}`).

---

## Grid (named-area layout)

For full-page layouts — a worksheet header with a logo, a title bar, info
fields, and a body — `<grid>` borrows CSS Grid's named-area idea. A
`template:[ … ]` of quoted rows draws the layout; each word names the cell at
that position. A name repeated **horizontally** spans columns; repeated
**vertically** it spans rows. A dot `.` is an empty cell. Each name must form
a solid rectangle.

```
<grid template:[
  "title  title  logo"
  "intro  info   logo"
  "body   body   body"
] gap:4>{
  <area title>{ <red b 16pt>Maths assessment }
  <area logo >{
    <img>{blason.png}
  }
  <area intro>{ Instructions: no calculator. }
  <area info >{ Name: \\ First name: }
  <area body >{
    <s1>Exercise 1
    Solve the equation...
  }
}
```

Here `title` spans the first two columns of the top row, `logo` spans the
two right-hand rows, and `body` spans the full width. Columns share the text
width equally; `gap:N` sets the spacing between cells in mm. Each
`<area NAME>{ … }` supplies the content for one name, in any order; an area
may hold plain text, inline styles, or — written in multi-line block form —
boxes, headings, tables, and other grids.

An area can be framed like a box by giving it the **same options** as
`<box>` — `line:`, `fill:`, `radius:`, `title:` — right after its name:

```
<area body line:Navy fill:AliceBlue radius:2 title:{Exercise 1}>{
  Solve the equation...
}
```

An area with no options stays invisible (a bare cell); add options only to
the zones you want framed, so the same grid serves both a clean final
worksheet and a structure you can see while designing.

The grid itself takes `width:` and `height:`. `width:` is a percentage of
the text width (`width:90%`) or a millimetre value (`width:120`); the grid is
then that wide (default: full width). `height:` is a millimetre value
(`height:80`) fixing the total height; when given, the grid also reserves
that much vertical space, so if it would not fit at the bottom of a page it
moves to the next page as a whole instead of being split. Without `height:`
the grid takes a natural height from its content. A grid used as a page-top
worksheet header needs neither; `height:` helps when a grid sits in the
middle of flowing text.

An area also takes a **two-letter placement code** that positions its whole
content within the cell: the first letter is vertical (`t` top, `m` middle,
`b` bottom), the second horizontal (`l` left, `c` centre, `r` right) — `tl`
`tc` `tr` `ml` `mc` `mr` `bl` `bc` `br`. The default is `tl`. A code on the
`<grid>` itself sets the default for every area; a code on an `<area>`
overrides it. The vertical part only shows when the cell is taller than its
content (give the grid a `height:`, or the area sits in a tall row).

The text-alignment words `l` `c` `r` `j` are distinct: they align the text
*inside* the content, and combine freely with a placement code.

```
<grid template:[...] mc>{                  every area centred by default
<area logo>{ <img>{badge.png} }            inherits mc — centred badge
<area body bl j>{ ... }                    pinned bottom-left, text justified
}
```

An image with no size (`<img>{file.png}`) is scaled down to fit the width of
its area but never enlarged; give a size (`<img 30>{file.png}`,
`<img 40x25>{...}`) to fix it in millimetres.

---

## Block aliases (the factoring tool)

Define a reusable component once; `#param` placeholders are filled at the
call site, and the call-site body becomes the block content — so it may
contain sub-blocks of its own.

```
let card{title, frame} = <box title:{#title} line:#frame radius:2>

<card First, Crimson>{
Called with two arguments. No box options to repeat.
}
<card Second, Navy>{
Same component, different look.
}
```

The body can nest blocks freely:

```
let panel{title} = <box title:{#title} fill:AliceBlue line:SteelBlue>

<panel Worked example>{
Intro text.
<row gap:4>{
<box line:Crimson title:{Given}>{ A triangle, base 6, height 4. }
<box line:Green  title:{Find}>{ Area: $6 * 4 / 2 = 12$. }
}
}
```

---

## Control flow

```
for n in 1..3 {            % numeric range
<c navy b>Sheet #n
}

for f in [chat.png, chien.png, fig1.png] {   % explicit list
<img 16>{#f}
}

if score >= 10 {
<green>Passed.
} else {
<red>Try again.
}

while cond { … }
```

Loops and conditions work in the document body, inside boxes, and inside
table bodies. The loop variable interpolates everywhere via `#`.

---

## Escapes

Literal special characters: `\<` `\>` `\{` `\}` `\#`. The characters
`_ & % ~` are escaped automatically. A double backslash `\\` is a line
break (in text and in table cells). A lone `$` with no closing `$` on the
same paragraph is treated as a literal dollar sign (with a warning), so it
no longer swallows the rest of the line into a maths span.

A line whose first non-space character is `%` is a **comment** and is
dropped (the LaTeX convention). A `%` anywhere else on the line is ordinary
text and is escaped for you. To print a line that must *begin* with a
percent sign, write `\%` at its start (`\% de réussite`): the backslash is
removed and the rest, starting with `%`, is typeset — so a leading `%`
never silently deletes a line you meant to keep.

A bare `#` that is not followed by a name or `{…}` is a literal `#`
(`#3`, `C# majeur`): only `#name` and `#{expr}` interpolate. An
interpolation whose value is missing renders as empty rather than the word
`nil`.

---

## Security

`scholatex` evaluates `let name = expr`, `#{expr}` and the conditions of
`for`/`if`/`while` as Lua at compile time, so by default a document can run
arbitrary code — exactly like `\directlua`.

### The `untrusted` option

Setting `untrusted=true` in `\documentclass[...]{scholatex}` runs that Lua in a
restricted environment: only pure, side-effect-free names are visible — the
maths the document language needs plus `string`/`table` helpers — while
`os`, `io`, `package`, `require`, `load`, `debug`, `setmetatable` and the
other escape vectors are simply absent. A blocked access stops the compile
with a clear message (`scholatex: 'os' is not available in untrusted mode`). A
runaway loop is aborted by an instruction-count ceiling, and `string.rep` /
`string.format` are capped so a single call cannot allocate gigabytes.

```latex
\documentclass[untrusted=true]{scholatex}
```

```
% now an exercise pulled from an untrusted source can still do maths…
let note = 14.5
Moyenne : #note / 20.

% …but cannot touch the system:
#{os.execute("rm -rf ~")}     % -> scholatex: 'os' is not available in untrusted mode
```

### What `untrusted` does and does not protect

`untrusted` hardens **the `scholatex` expression layer only**. It does **not**
sandbox LuaLaTeX as a whole: a hostile `.tex` can still call `\directlua`,
`\write18` (with shell-escape), `\input`, and so on, entirely outside `scholatex`.

So the option is meaningful when the `scholatex` **body** comes from a semi-trusted
source — a form field, an exercise database, a generated or received `.sl`
fragment you inject — while the surrounding `.tex` (the `\documentclass`
line and anything around the body) is your own.

To compile a whole `.tex` you do **not** trust, do not rely on this flag
alone. Use the engine's own protections: run `lualatex` **without**
`--shell-escape`, and ideally inside a container or a restricted user
account. `untrusted=true` is a useful extra layer on top of that, not a
replacement for it.

## Other caveats

A multi-paragraph or block-containing `<box>` opens with the `{` alone on
its line (`<box ...>{` then the body on the following lines, then a closing
`}`). A box written entirely on one line — `<box ...>{ body }` — is read as
inline text in the document body, but **is** accepted as a child of `<row>`,
where the one-line form is the convenient way to place short boxes side by
side.

The body is extracted between the first `\begin{document}` and the first
`\end{document}`; an `\end{document}` appearing literally inside the body
(e.g. in a code sample) would cut it short.

---

## Examples

The `examples/` folder contains three self-contained, fully commented
documents that together exercise every feature:

| File | Covers |
|------|--------|
| `01-text-style.tex` | the case rule, styles, colours, fonts, sizes, alignment, tabs, skips, scripts; **factoring styles into aliases**; a table of contents from the heading keywords |
| `02-containers.tex` | tables, boxes and the named-area grid, each built up from its simplest form to a full worksheet header |
| `03-math.tex` | the inline mini-language, the helpers (`abs` `norm` `vec` `lim`), and the matrix / determinant / augmented-matrix / system blocks |

Compile any of them with `lualatex <file>.tex` from the `examples/` folder.
The image folders `IMG/` and `IMAGES/PNG/` ship alongside them; `02-containers.tex`
uses `imgdir={IMG, IMAGES/PNG}` to show that bare image names are resolved across
several directories.

---

## Project layout

```
scholatex.cls            LaTeX class: options, packages, reads & injects the body
scholatex.lua            transpiler core: tags, text, control flow, aliases
scholatex-style.lua      attribute resolution (colours, styles, sizes, alignment…)
scholatex-math.lua       the $…$ math mini-language
scholatex-util.lua       parsing primitives (groups, brace balance, comma split)
scholatex-table.lua      the <table> block
scholatex-img.lua        the <img> tag
scholatex-box.lua        the <box> and <row> blocks
scholatex-grid.lua       the <grid> named-area layout block
scholatex-section.lua    the <section>/<subsection>/<subsubsection> container blocks
scholatex-list.lua       the <list:STYLE> block
scholatex-matrix.lua     the <matrix>/<det>/<bmatrix> and <system> maths blocks
scholatex-toc.lua        the <tableofcontents> table-of-contents tag
examples/         three commented showcase documents
```

New tags and blocks register themselves via `scholatex.register_tag` /
`scholatex.register_block`; a name clash now raises an error rather than silently
overwriting, so modules stay independent.

---

## Diagnostics

Errors point at the source line, e.g. `scholatex: line 12: unknown tag attribute:
'xyz'`. Defining an alias whose name is a built-in (`let section = …`,
`let tab = …`) prints a warning: the built-in always wins, so the alias would
be silently dead — pick a different name (`let s1 = <line navy section>` and
use `<s1>`).

---

## License

Copyright © 2026 Gérard Dubard.

`scholatex` is free software: you can redistribute it and/or modify it under
the terms of the **GNU General Public License version 3** as published by the
Free Software Foundation. See the [`LICENSE`](LICENSE) file for the full text.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.
