PicoDoc Language Reference — Source

#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 [#~"
    1. "]. 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 | [#~"