#doc.lang: en
#//: PicoDoc Language Reference
#//: Authoritative documentation of every language feature.
#doc.title: PicoDoc Language Reference
#doc.content type=main id=content
#doc.heading.number level=4
#doc.heading.anchor level=4
#doc.link rel=stylesheet href=style/picodoc.css
#doc.script src=style/tocbot.min.js
#doc.script src=style/init.js
#doc.script src=style/prism.min.js
#doc.script src=style/prism-picodoc.js
#doc.body class=toc-sidebar
#nav id=nav: [#include literal=true: style/nav.html]
#header:
#-: PicoDoc Language Reference
#nav id=toc class=toc-sidebar:
#div id=tocfile: Table of Contents
#doc.toc
#--: Overview
PicoDoc is a macro-based markup language that compiles to HTML. Every construct
in the language (headings, bold text, links, user-defined templates) is a macro
call. This regularity means there is only one syntax to learn.
Design principles:
[#ul :
[#* : [#b : One abstraction] : macros are the only construct.]
[#* : [#b : Named arguments] : all arguments are named, eliminating
positional ambiguity.]
[#* : [#b : Strict parsing] : invalid input is rejected with clear error
messages rather than silently guessed at.]
[#* : [#b : UTF-8 only] : no encoding detection or fallback.]
[#* : [#b : HTML target] : the primary output format is HTML.]
]
The file extension is [#~ : .pdoc].
#--: Lexical Structure
#---: Identifiers
An identifier consists of one or more letters, digits, dots, or special
characters:
[#code : """
! $ % & * + - / < > @ ^ _ ~ |
"""]
Dots allow namespacing (e.g. [#~ : env.mode], [#~ : doc.lang]). The
identifier terminates at the first character that is not in the set above, such
as whitespace, colons, quotes, brackets, equals signs, hash, or backslash.
#---: Escape Sequences
Backslash is the escape prefix. Backslash followed by any character not on the
valid list for the current context is a syntax error.
#p: [#b : Prose escapes] (in body text and barewords):
#table:
Sequence | Produces
[#~"\\\\"] | Literal backslash
[#~"\\#"] | Literal \#
[#~"\\["] | Literal \[
[#~"\\]"] | Literal \]
[#~"\\\""] | Literal \"
[#~"\\xHH"] | Unicode codepoint U+0000 to U+00FF
[#~"\\UHHHHHHHH"] | Unicode codepoint (8 hex digits)
Double quotes are literal in prose — they are only parsed as string
delimiters in value positions (after [#~"="], after a colon, or
directly after a macro name). Use [#~"\\\""] when the body of a macro
must begin with a literal double quote character.
#p: [#b : String escapes] (inside interpreted string literals):
#table:
Sequence | Produces
[#~"\\\\"] | Literal backslash
[#~"\\\""] | Literal double quote
[#~"\\["] | Enter code mode (macro expansion)
[#~"\\n"] | Newline
[#~"\\t"] | Tab
[#~"\\xHH"] | Unicode codepoint U+0000 to U+00FF
[#~"\\UHHHHHHHH"] | Unicode codepoint (8 hex digits)
Note that [#~"\\["] has different meaning in the two contexts: in prose it
produces a literal \[, while in strings it enters code mode.
#---: Comments
The \#// macro (also \#comment) removes its body from the output entirely.
Three forms are available:
[#ol :
[#* : [#b : Inline] (colon body to end of line): [#~"#//: This text will not appear."]]
[#* : [#b : Bracketed] (multi-line): [#~"[#// : Multi-line comment here.]"]]
[#* : [#b : Paragraph body] (colon + following paragraph):]
]
#code language=picodoc: """
#//: This text will not appear.
[#// : Multi-line comments
use the bracketed form.]
#//:
This entire paragraph
is a comment.
"""
#--: Macro Calls
There are two forms of macro call: unbracketed and bracketed. The parser does
not need to consult macro definitions to parse either form.
#---: Unbracketed Form
A macro call begins with \# followed immediately by the macro identifier.
After the identifier, the parser examines the next token:
[#ol :
[#* : If the token matches [#~"identifier="] (no whitespace before
the equals sign), it enters argument mode and consumes name=value pairs.]
[#* : If the token is a colon, it enters body mode.]
[#* : If the token is a string literal opening, it enters body mode
with the string as body.]
[#* : Otherwise the call is complete with no arguments and no body.
Remaining text on the line is prose.]
]
#code language=picodoc: """
#doc.title: Document Title
#-: Visible Heading
#hr
#p"Explicit paragraph."
"""
In argument mode, the parser consumes name=value pairs. If the next token is
not a name=value pair, a colon, or a string literal, the call is complete and
the remaining text on the line is prose. After a colon body (to end of line) or
a string literal body, the call is also complete and any remaining text is
prose.
#code language=picodoc: """
#code language=python : print("hello")
#doc.meta name=viewport content="width=device-width"
"""
#---: Bracketed Form
A macro call enclosed in square brackets: \[#identifier ...\]. Named
arguments may appear as name=value pairs separated by whitespace. A colon or
string literal introduces body content. Everything until the matching closing
bracket is body, which may span multiple lines and paragraphs.
#code language=picodoc: """"
[#b : bold text]
[#link to="https://example.com" : Click here]
[#code language=python : """
def hello():
print("world")
"""]
""""
Bare text inside brackets that is not a name=value pair and not preceded by
a colon or string literal is a syntax error.
#---: Named Arguments
Named arguments use the syntax [#~"name=value"] with no whitespace before
the equals sign. Whitespace after the equals sign is permitted.
Argument values can be:
[#ul :
[#* : [#b : Bareword] (a simple word with no syntactic characters):
[#~"language=python"]]
[#* : [#b : String literal] (interpreted or raw):
[#~"to=\"https://example.com\""]]
[#* : [#b : Macro reference] (a zero-argument macro):
[#~"to=#site-url"]]
[#* : [#b : Bracketed call] (a macro call in brackets):
[#~"style=[#get-style]"]]
]
#---: The Body Argument
Body content is introduced by a colon or by a string literal:
#p: [#b : Colon, inline body]: text follows the colon on the same line.
#code language=picodoc: """
#-: Document Heading
"""
#p: [#b : Colon, paragraph body]:
nothing follows the colon on the line. The next paragraph (contiguous non-blank
lines) becomes the body.
#code language=picodoc: """
#p:
This paragraph spans
multiple lines.
"""
#p: [#b : String literal body]:
a string literal serves as body. A colon before the string is optional.
#code language=picodoc: """"
#p"A string literal paragraph."
#**"bold text"
[#code language=python """
def hello():
pass
"""]
""""
#p: [#b : Bracketed body]:
in bracketed calls, body extends to the closing bracket and may span multiple
lines and paragraphs.
#code language=picodoc: """
[#ul :
#*: First item
#*: Second item
#*: Third item
]
"""
If no colon and no string literal is present, the macro call has no body.
#---: Body Whitespace Stripping
Multiline body content (both paragraph and bracketed forms) is
[#b : dedented]: the longest common leading whitespace shared by all
non-blank lines is stripped. This lets you indent body content to match the
surrounding markup without that indentation leaking into the output.
#code language=picodoc: """
#code:
def hello():
print("hi")
"""
The four spaces before [#~"def"] and [#~"print"] are stripped because they
are the common leading whitespace. The relative indentation (four extra spaces
on [#~"print"]) is preserved.
Blank lines inside the body do not affect the common prefix calculation.
Inline bodies (content on the same line as the colon) have no newlines and
are unaffected.
#--: String Literals
#---: Interpreted Strings
String literals are only recognised in value positions: after [#~"="] (argument
value), directly after a macro name with no whitespace (inline body), or after
[#~":"] (colon body). A double quote in any other position is literal prose
text.
Delimited by a single double quote on each end. Escape sequences are processed
but macro calls are NOT automatically scanned for.
#code language=picodoc: """
"A simple string."
"Contains a tab:\there."
"Contains a newline:\nSecond line."
"A literal quote: \" inside."
"""
To embed macro calls within an interpreted string, use [#~"\\["] to enter
code mode. Code mode ends at the matching closing \]. Normal bracketed macro
call syntax applies inside [#~"\\[...\\]"].
#code language=picodoc: """
"Hello, \[#name]!"
"Dear \[#target], welcome to \[#place]."
"""
#---: Raw Strings
Delimited by three or more double quote characters. The closing delimiter must
have exactly the same number of quotes as the opening. Contents are completely
opaque (no escapes, no macro processing).
#code language=picodoc: """""
"""This is raw: \n is literal and #name is not expanded."""
""""Contains """ three quotes inside.""""
"""""
#---: Empty String
The empty string is always [#~"\"\""] (two double quotes). An empty raw
string is not possible because the opening quotes run into the closing.
#---: Whitespace Stripping
Both interpreted and raw strings apply the same whitespace rules:
[#ol :
[#* : If the remainder of the opening delimiter's line is blank (whitespace
only), that remainder is discarded.]
[#* : If the beginning of the closing delimiter's line is blank (whitespace
only), that whitespace is discarded.]
[#* : If the closing delimiter's leading whitespace appears identically on
ALL other lines of the string, that common prefix is stripped from every
line (indentation stripping).]
]
This allows indenting string content to match the surrounding markup without
affecting the result:
#code language=picodoc: """"
[#code language=python : """
def hello():
print("world")
"""]
""""
The four spaces before [#~"def"] and [#~"print"] are stripped because
they match the indentation of the closing delimiter.
Colon body content (paragraph and bracketed) applies analogous whitespace
stripping: the longest common whitespace prefix across all non-blank content
lines is removed. The difference from strings is that strings use the closing
delimiter's indentation as the reference prefix, while bodies compute the
minimum common prefix directly.
#---: Adjacent String Restriction
After a string literal closes, the next character must not be a double quote.
At least one non-quote character must separate consecutive string literals.
This prevents a mismatched quote from silently cascading through the file.
#--: Builtin Macros
PicoDoc has 44 builtin macros in 8 categories, plus 11 alternate forms.
#---: Structural Macros
#----: \#-
Also: \#h1. Top-level heading. Renders as [#~""] in the document body.
Takes body, no parameters.
#code language=picodoc: """
#-: Main Heading
#h1: Alternate form
"""
#----: \#--
Also: \#h2. Section heading. Renders as [#~""]. Takes body, no
parameters.
#----: \#---
Also: \#h3. Subsection heading. Renders as [#~""]. Takes body, no
parameters.
#----: \#----
Also: \#h4. Lower-level heading. Renders as [#~""]. Takes body, no
parameters.
#----: \#-----
Also: \#h5. Lower-level heading. Renders as [#~""]. Takes body, no
parameters.
#----: \#------
Also: \#h6. Lower-level heading. Renders as [#~""]. Takes body, no
parameters.
#----: \#p
Paragraph. Renders as [#~"
"]. Takes body, no parameters. Bare text
paragraphs (not inside a macro call) are implicitly wrapped in \#p.
#code language=picodoc: """
#p: An explicit paragraph.
This bare text is also a paragraph.
"""
#----: \#hr
Horizontal rule. Renders as [#~"
"]. No parameters, no body.
#---: Inline Macros
#----: \#**
Also: \#b. Bold text. Renders as [#~""]. Takes body, no
parameters.
#code language=picodoc: """
This is #**"bold" text.
This is [#** : also bold] text.
"""
#----: \#__
Also: \#i. Italic text. Renders as [#~""]. Takes body, no
parameters.
#code language=picodoc: """
This is #__"italic" text.
This is [#__ : also italic] text.
"""
#----: \#*_
Bold italic. Renders as [#~""] (strong wrapping em). Takes body,
no parameters.
#code language=picodoc: """
This is [#*_: bold italic] text.
This is #*_"also bold italic" text.
"""
#----: \#_*
Italic bold. Renders as [#~""] (em wrapping strong). Takes body,
no parameters.
#code language=picodoc: """
This is [#_*: italic bold] text.
This is #_*"also italic bold" text.
"""
#----: \#>
Also: \#link. Hyperlink. Renders as [#~""]. Parameters:
#table:
Parameter | Required | Description
to | No | Link target (URL, path, or fragment name)
Takes optional body (the link text). At least one of [#~ : to] or body
must be present. When [#~ : to] is omitted, the body text is used as the
link target. For external links, if no body is provided the [#~ : to]
value is used as the link text.
Fragment reference behavior: if the link target does not contain [#~"://"]
and does not contain [#~"/"], it is treated as a fragment reference and
\# is prepended (e.g. [#~"to=section1"] produces [#~"href=\"#section1\""]).
For fragment references, two additional rules apply:
[#ul :
[#* : [#b : Auto-text] : if no body is provided, the link text defaults
to the heading text of the target anchor. The text is always the plain
heading text without section numbers.]
[#* : [#b : Validation] : the target must match a heading anchor in the
document. A broken internal link is a render error.]
]
#code language=picodoc: """
[#> to="https://example.com" : Click here]
[#link to="https://example.com" : Alternate form]
[#> to=section1 : Jump to section]
[#> to=section1]
[#> to=page/about : About page]
[#> : https://example.com]
[#> : section1]
"""
#---: Code and Literal Macros
#----: \#code
Block code. Renders as [#~ : ]. Parameters:
#table:
Parameter | Required | Description
language | No | Programming language for syntax class
Takes body. Always renders as a block [#~ : ] element regardless
of body type. For inline code, use \#~ instead.
#code language=picodoc: """"
[#code language=python : """
def hello():
print("world")
"""]
""""
With a paragraph body, body dedenting strips the common indentation
automatically:
#code language=picodoc: """
#code language=python:
def hello():
print("world")
"""
#----: \#~
Inline code. Renders as [#~ : ]. Parameters:
#table:
Parameter | Required | Description
language | No | Programming language for syntax class
Takes body. Always renders as an inline [#~ : ] element regardless
of body type. For block code, use \#code instead.
#code language=picodoc: """
Use the [#~ : print()] function to output text.
The [#~ language=python : def] keyword starts a function.
"""
#----: \#literal
Prevents re-expansion of its body. Any macro calls or escapes within the body
are passed through as literal text. Useful for documenting PicoDoc syntax or
for output from external filters. Takes body, no parameters.
#code language=picodoc: """"
#literal"""
The #b macro makes text bold.
Use [#link to="..." : text] for links.
"""
""""
#---: List Macros
#----: \#ul
Unordered list. Renders as [#~""]. Takes body, no parameters. Body must
contain only \#* (list item) elements.
#----: \#ol
Ordered list. Renders as [#~""]. Takes body, no parameters. Body must
contain only \#* (list item) elements.
#----: \#*
Also: \#li. List item. Renders as [#~"- "]. Takes body, no parameters.
Must appear inside \#ul or \#ol.
#code language=picodoc: """
[#ul :
#*: First item
#*: Second with #**"bold"
[#* : Third with a sublist
[#ul :
#*: Nested A
#*: Nested B
]
]
]
[#ol :
#*: Step one
#*: Step two
]
"""
#---: Table Macros
#----: \#table
Table container. Renders as [#~"
"]. Takes body. Parameters:
#table:
Parameter | Required | Description
cols | No | Column widths and alignment spec
The [#~ : cols] parameter specifies relative column widths and optional
alignment, using a space-separated list of integers. Prefix a column with
[#~ : >] for right-alignment or [#~ : <] for left-alignment (default).
Example: [#~ : cols="1 >2 1"] means 3 columns where the middle column is
twice as wide and right-aligned.
When [#~ : cols] is present, a [#~""] element is emitted with
percentage-based widths (integer division: width*100/total). The column
count must match every row's cell count or rendering fails with an error.
#code language=picodoc: """
[#table cols="1 >2 1" :
Name | Score | Status
Alice | 95 | Active
Bob | 87 | Active
]
"""
#p: This produces:
#code language=html: """
"""
Supports two forms:
#p: [#b : Pipe-delimited form]: the body is parsed as pipe-separated rows.
The first row becomes headers (\#th), subsequent rows become data (\#td).
#code language=picodoc: """
#table:
Name | Age | Status
Alice | 30 | Active
Bob | 25 | Inactive
"""
#p: [#b : Explicit form]: the body contains \#tr, \#th, and \#td calls
directly.
#code language=picodoc: """
[#table :
[#tr : [#th: Name] [#th: Age]]
[#tr : [#td: Alice] [#td: 30]]
]
"""
#----: \#tr
Table row. Renders as [#~""]. Takes body, no parameters.
#----: \#td
Table data cell. Renders as [#~""]. Parameters:
#table:
Parameter | Required | Description
span | No | Column span (colspan attribute)
Takes body.
#----: \#th
Table header cell. Renders as [#~" "]. Same parameters as \#td. Takes
body.
#code language=picodoc: """
[#table :
[#tr : [#th: Name] [#th: Age]]
[#tr : [#td: Alice] [#td: 30]]
[#tr : [#td span=2 : Total: 1 person]]
]
"""
#---: Wrapper / Container Macros
PicoDoc provides 9 wrapper macros for grouping content in HTML container
elements. All wrapper macros accept optional [#~"class"] and [#~"id"]
parameters and take a body.
#table:
Macro | HTML element | Display
\#div | [#~""] | Block
\#section | [#~""] | Block
\#nav | [#~"