Skip to content

SimpleDoc language grammar

Doug Torrance edited this page Aug 12, 2026 · 1 revision

Note: This grammar was AI-generated by Claude Opus 5. 🤖

SimpleDoc Grammar

EBNF description of the docstring language accepted by the SimpleDoc package — the format used by doc, multidoc, and document(String). It is a companion to the Macaulay2 language grammar, which describes the expression syntax that this language embeds.

Everything below is derived from the parser in M2/Macaulay2/packages/SimpleDoc.m2; the function names appearing in the notes (splitByIndent, markup, render, items, submenu, …) refer to that file.

Overview

A docstring is plain text, supplied either directly or as the name of a file to read (doc reads the file when fileExists is true of its argument). It is line-oriented and indentation-sensitive — it follows the off-side rule. A section begins with a line holding a keyword and nothing else; the section's body is the following, more deeply indented lines. Which keywords are legal depends on the enclosing section, and is fixed by one of four keyword tables.

The language has two layers:

  • an outer block layer, driven by indentation and the keyword tables, which determines the tree of sections; and
  • an inner inline layer, in which prose is TeX and @…@ escapes to an arbitrary Macaulay2 expression.

The inline layer runs only inside certain sections; others take their bodies verbatim, or evaluate them as Macaulay2 code. The Inline Layer section lists which is which.

Notation

The terminals of the block grammar are whole lines, not characters. INDENT and DEDENT are not produced by a lexer — they are a way of writing down the result of splitByIndent. Unlike the corresponding tokens in most off-side-rule languages, they carry two caveats: a single DEDENT may close several blocks at once, and inconsistent indentation is never itself an error, so a misindented line is silently reinterpreted rather than rejected. See Block Structure.

Character-level productions are used only for the inline layer and for the parts of a line that have internal structure.

m2_expression means a complete Macaulay2 expression, as described by the Macaulay2 language grammar. Wherever it appears, the text really is handed to value.

Lexical Rules

source       ::= line*
line         ::= indentation content? line_end
line_end     ::= "\n" | EOF
content      ::= any_char*             (* anything after the indentation *)
indentation  ::= indent_ws*
blank_line   ::= indent_ws* line_end
comment_line ::= strip_ws* "--" any_char*

indent_ws    ::= " " | "\t" | "\r"     (* what indent measurement recognises *)
strip_ws     ::= [[:space:]]           (* what comment matching and stripping use *)

The two whitespace classes differ, and the difference is observable: a line beginning with a vertical tab or form feed has indent 0, with that character stripped from its text, so a line containing only a vertical tab is a non-blank line at indent 0 whose text is empty.

Preprocessing, before any parsing (toDoc, makeTextline):

  1. The source is split into lines.
  2. Every line matching ^[[:space:]]*-- is deleted outright.
  3. Each surviving line becomes a triple (text, indent, linenum), where text is the line with leading and trailing whitespace stripped.

Indentation

indent is the column of the first character that is not a space, tab, or carriage return, computed by scanning the leading whitespace:

Character Effect on the running column
" " column + 1
"\t" the smallest multiple of 8 strictly greater than the column
"\r" column := 0
anything else stop; that column is the indent
end of line indent is infinity — the line is blank

So "\tx", " \tx", and " \tx" all have indent 8, while " \tx" has indent 16; " \r x" has indent 1. A line containing only spaces, tabs, and carriage returns has indent infinity, which is what makes blank lines behave specially rather than as indent 0.

Keyword lines

keyword_line ::= keyword       (* the entire stripped text of the line *)
keyword      ::= (* a name drawn from the table in force; see below *)

Dispatch is an exact hash lookup of the stripped text in the keyword table in force, so a keyword line may hold nothing else: Headline is a keyword line, Headline blah is an error. Keyword matching is case-sensitive.

Block Structure

splitByIndent divides a list of lines into consecutive blocks. It keeps a running indent m, initially infinity, and for each line:

  • if m + 1 ≤ indent, the line continues the current block;
  • otherwise the line starts a new block, and m is set to that line's indent.

INDENT body DEDENT in the productions below means "the lines of one such block, after its first line".

The predicate is written m + 1 ≤ indent rather than indent > m because the two differ when both are infinity, and that case matters: it is why blank lines before the first block are absorbed rather than starting one.

The crucial consequence is that m only ever decreases: a less indented line permanently lowers the nesting floor for everything that follows it. Given these six lines,

  a          indent 2   -- starts a block, m := 2
    b        indent 4   -- continues a
  c          indent 2   -- starts a block, m := 2
d            indent 0   -- starts a block, m := 0
  e          indent 2   -- continues d  (!)
    f        indent 4   -- continues d

splitByIndent returns three blocks — a b, c, and d e f — not four. Once d has pulled the floor down to 0, the indent-2 line e is a continuation rather than a sibling of a and c. In practice this means a keyword indented less than its siblings silently swallows them.

Blank lines

splitByIndent has two modes, and blank lines are the only thing that distinguishes them.

  • Section splitting (the default) leaves blank lines at indent infinity, so a blank line always continues the current block. Blank lines before the first block are discarded.
  • Paragraph splitting, used by prose sections and nowhere else, treats a blank line as indent −1, so a blank line always ends the current block. This is what separates paragraphs.

Because comment lines are deleted before indentation is ever computed, a -- line can never break a block, whatever its indentation.

Keyword Tables

Four tables, each governing one context. The body column names the production used for the section's body; those productions are defined in Body Forms.

Node keywords

Legal at the top level of a docstring and inside Node (NodeFunctions).

Keyword Body Inline layer
Node node_body
Key key_list no — evaluated
Headline single_line no
Usage multi_line no
Inputs item_list yes, in descriptions
Outputs item_list yes, in descriptions
Consequences consequences_body
Description description_body
Synopsis synopsis_body
Acknowledgement prose yes
Contributors prose yes
References prose yes
Citation verbatim no — raw text
Caveat prose yes
SeeAlso key_list no — evaluated
Subnodes submenu yes, per entry
SourceCode key_list no — evaluated
ExampleFiles file_list no

Synopsis keywords

Legal inside Synopsis (SynopsisFunctions).

Keyword Body Notes
Heading single_line
BaseFunction single_line evaluated
Usage multi_line same handler as the node keyword
Inputs item_list same handler
Outputs item_list same handler
Consequences consequences_body same handler
Description description_body same handler

Synopsis is not itself a Synopsis keyword, so synopses do not nest.

Description keywords

Legal inside Description (DescriptionFunctions).

Keyword Body Inline layer
Text prose yes
Tree menu yes, per entry
Example example_block no — Macaulay2 input
CannedExample verbatim no
Pre verbatim no
Code m2_code no — evaluated

Consequences keywords

Legal inside Consequences (ConsequencesFunctions).

Keyword Body Inline layer
Item item_prose yes

Item is the only one; anything else in a Consequences section is an error.

Grammar

Top Level

docstring ::= node+
            | node_body

node      ::= "Node" INDENT node_body DEDENT

A docstring is either a sequence of Node sections, describing several documentation nodes, or the body of a single unnamed node. Mixing the two — some top-level sections Node and some not — is rejected by doc, though not by the block parser itself. The test is applied to the parsed result rather than to the source, and an empty docstring takes the second branch.


Sections

node_body         ::= node_section*

node_section      ::= "Node"            INDENT node_body         DEDENT
                    | "Key"             INDENT key_list          DEDENT
                    | "Headline"        INDENT single_line       DEDENT
                    | "Usage"           INDENT multi_line        DEDENT
                    | "Inputs"          INDENT item_list         DEDENT
                    | "Outputs"         INDENT item_list         DEDENT
                    | "Consequences"    INDENT consequences_body DEDENT
                    | "Description"     INDENT description_body  DEDENT
                    | "Synopsis"        INDENT synopsis_body     DEDENT
                    | "Acknowledgement" INDENT prose             DEDENT
                    | "Contributors"    INDENT prose             DEDENT
                    | "References"      INDENT prose             DEDENT
                    | "Citation"        INDENT verbatim          DEDENT
                    | "Caveat"          INDENT prose             DEDENT
                    | "SeeAlso"         INDENT key_list          DEDENT
                    | "Subnodes"        INDENT submenu           DEDENT
                    | "SourceCode"      INDENT key_list          DEDENT
                    | "ExampleFiles"    INDENT file_list         DEDENT

synopsis_body     ::= synopsis_section*

synopsis_section  ::= "Heading"         INDENT single_line       DEDENT
                    | "BaseFunction"    INDENT single_line       DEDENT
                    | "Usage"           INDENT multi_line        DEDENT
                    | "Inputs"          INDENT item_list         DEDENT
                    | "Outputs"         INDENT item_list         DEDENT
                    | "Consequences"    INDENT consequences_body DEDENT
                    | "Description"     INDENT description_body  DEDENT

description_body  ::= description_section*

description_section ::= "Text"          INDENT prose             DEDENT
                    | "Tree"            INDENT menu              DEDENT
                    | "Example"         INDENT example_block     DEDENT
                    | "CannedExample"   INDENT verbatim          DEDENT
                    | "Pre"             INDENT verbatim          DEDENT
                    | "Code"            INDENT m2_code           DEDENT

consequences_body ::= ("Item" INDENT item_prose DEDENT)*

The * quantifiers are deliberate: an empty body is accepted almost everywhere. Only Key, Headline, Heading, BaseFunction, Usage, and Synopsis genuinely require content; the rest either yield an empty result or crash, as listed under Failure modes. A linter built from this grammar should not reject an empty section outright.

Sections may appear in any order. Whether one may be repeated depends on which table it comes from:

  • Among node keywords, only Node, Description, and Synopsis may repeat. Those three contribute bare items; every other node keyword becomes an option of document, which rejects a duplicate with option X encountered twice. The block parser accepts the repetition either way, so the error surfaces later, at document time.
  • Inside Description and Consequences, every keyword repeats freely. A Description is normally a sequence of alternating Text and Example subsections, and several Item sections are the usual way to write several consequences.
  • Inside Synopsis, a repeated keyword is not an error either, but only Description is cumulative — it is a bare item, which SYNOPSIS splices into its body. Heading, BaseFunction, Usage, Inputs, Outputs, and Consequences are options of SYNOPSIS, an ordinary method with options, so for those the last occurrence silently wins.

A Description yields a sequence of items rather than a single item. At the top level of a docstring toDoc's deepSplice flattens it immediately, and inside a Synopsis SYNOPSIS splices it; inside an explicit Node it stays nested until document calls fixup, which flattens it then. Either way, several Description sections end up equivalent to one.

Node appears in its own body table, so nodes nest structurally. Nothing consumes a nested node, so this is best regarded as an accident of the table rather than a feature.


Body Forms

key_list      ::= m2_expression*       (* one per non-blank line *)

single_line   ::= text_line

multi_line    ::= text_line+           (* joined with newlines *)

verbatim      ::= any_line*            (* common indentation removed *)

m2_code       ::= any_line*            (* common indentation removed,
                                          wrapped in ( ) and evaluated *)

example_block ::= example*

example       ::= any_line more_indented_line*

file_list     ::= any_line*

prose         ::= "Tree" submenu
                | paragraph (blank_line paragraph)*

item_prose    ::= prose                (* paragraph structure discarded *)

paragraph     ::= inline_line*         (* joined with single spaces *)

(* any_lineany line of the section, blank ones included
   more_indented_linea line indented more than the block's first line
   text_line          — any line, taken as a string
   inline_line        — any line, contributing to the paragraph's inline text *)
  • key_list — used by Key, SeeAlso, and SourceCode. Blank lines are dropped and each remaining line is evaluated as a Macaulay2 expression, so a string key must carry its quotation marks. Key requires at least one entry.

  • single_line — used by Headline, Heading, and BaseFunction. The value is the first line; it is an error for any later line to be non-blank. Trailing blank lines are harmless, but a blank line immediately after the keyword is not: it becomes the value, and the intended text becomes an illegal second line. BaseFunction's line is then evaluated.

  • multi_line — used by Usage. At least one line is required; the lines are joined with newlines, giving one usage per line.

  • verbatim — used by Pre, CannedExample, and Citation, which take their text raw with a common indentation removed. Pre and Code subtract the minimum indentation over the section; CannedExample and Citation subtract the indentation of the section's first line, as does Example. Blank lines are kept. Indentation is rebuilt as spaces, so tabs are normalised to their column, and a line shallower than the reference is flushed left rather than kept relatively indented.

    The three differ sharply in what they produce, despite sharing a body form. Pre becomes a preformatted block, and Citation is stored as a raw string. CannedExample is wrapped in EXAMPLE and renders into the same <table class="examples"> that a real Example does, but its content is a preformatted transcript rather than an example item, so it is never extracted into the package's example inputs and never run.

  • m2_code — used by Code. The dedented lines are wrapped in parentheses and evaluated, so the body is one parenthesised Macaulay2 expression, not a statement sequence: two expressions on consecutive lines need a comma or a semicolon between them, exactly as inside ( … ). Only the comma contributes two items — ; sequences for effect and yields just the last value. The result is spliced into the surrounding list of documentation items, and both sequences and lists are flattened, the latter by document's fixup rather than by SimpleDoc, so (PARA "a", PARA "b") and {PARA "a", PARA "b"} both contribute two items.

  • example_block — used by Example. Each block found by splitByIndent becomes one example, so each line at the section's base indentation starts a new example and more deeply indented lines continue it. Indentation is measured against the section's first line. The text is not evaluated by SimpleDoc at all; it is captured and run later by installPackage.

  • file_list — used by ExampleFiles. Every line is taken literally as a file name, with no evaluation and, unlike key_list, no dropping of blank lines.

  • prose — used by Text, Caveat, Acknowledgement, Contributors, and References. Blank lines separate paragraphs; the lines of a paragraph are joined with single spaces and passed through the inline layer as a unit. A paragraph that renders to a single hypertext container or paragraph is used as-is; anything else is wrapped in a paragraph. This is why @SUBSECTION "…"@ alone on a line works while @SUBSECTION "…"@ tail does not: the latter parses without complaint, but when installPackage renders the node it prints warning: … PARA, may not contain an element of type … HEADER2 and then aborts with validation failed: PARA{HEADER2{…}, …}. As a special case, if the section's very first line is exactly Tree, the rest of the section is parsed as a submenu instead.

  • item_prose — used by Item. Paragraphs are computed as for prose and then flattened: each paragraph's outermost wrapper is discarded and the pieces are concatenated. Blank lines therefore do not produce visible paragraph breaks inside an Item, and an Item consisting of @SUBSECTION "S"@ is reduced to the bare string S. Use one Item per paragraph.

Note that paragraph may be empty, and empty paragraphs are emitted rather than discarded. The underlying rule is that markup prepends a synthetic blank line, splits on blank lines, and emits an empty paragraph for any block consisting of a blank line alone. So n leading or trailing blank lines yield n empty paragraphs and an interior run of n yields n − 1. A body of nothing but blank lines is the degenerate case: n of them yield n + 1, since the synthetic line's own block is empty too.

The trailing case is the common one, and it is easy to miss: because blank lines belong to the preceding section body, the ordinary habit of leaving a blank line before the next keyword puts an empty paragraph at the end of nearly every Text section.


Inputs and Outputs

item_list ::= item*

item      ::= item_head description_line*

item_head ::= name? separator type? ("--" abbreviation)?

separator ::= ":" | "=>"

(* namea string, or a symbol to evaluate; see below
   type             — m2_expression
   abbreviation     — free text, passed through the inline layer
   description_line — a line indented more than the head *)

Each block found by splitByIndent is one item: its first line is the head, and the more deeply indented lines that follow are its description, joined with spaces and passed through the inline layer. Unlike prose, a blank line does not end an item description — it merely contributes an extra space.

The entire head, abbreviation included, is split on [[:space:]]*(:|=>)[[:space:]]*, and the split must yield exactly two parts. Both parts may be empty, so :ZZ (an anonymous value of type ZZ) and n: are legal, but a head with no separator, or with a second one, is an error — including a second one that only appears in the abbreviation, so n:ZZ -- see: below does not parse.

  • With :, the name is kept as a string: n:ZZ documents an argument named n.
  • With =>, the name is evaluated, yielding an optional argument: Limit => ZZ documents the option Limit.
  • The type is evaluated as Macaulay2 code. That is also why the -- abbreviation tail survives: it is not stripped by SimpleDoc before evaluation, but Macaulay2's own lexer treats it as a comment. The abbreviation is separately recovered as the second ---delimited fragment of the type text, so with two --s the remainder is dropped, and it is prepended to the description followed by "; " — which appears even when the description is empty.
  • If the type evaluates to a String rather than a type, it is not treated as a type at all: it is joined to the description with ", ", which is the supported idiom for describing a type in prose, as in n:"a list of things".
  • An abbreviation with no type before it is legal but rarely intended: in n: -- small, the type text is -- small, which evaluates to null, so the item is documented as having type null.

The head is not checked for sensibility, so Matrix:a parses happily and documents an argument named Matrix of the type given by the symbol a.


Menus and Trees

Subnodes, the Tree subsection of a Description, and a prose section whose first line is Tree all share this sublanguage. Blank lines are discarded throughout it.

submenu    ::= entry*

entry      ::= entry_line (INDENT submenu DEDENT)?

entry_line ::= ":" inline                (* heading *)
             | at_inline                 (* raw markup *)
             | "* " entry_target         (* link *)
             | "> " entry_target         (* subnode link *)
             | "- " inline               (* free text *)
             | m2_expression             (* documentation key *)

entry_target ::= at_inline
               | m2_expression

at_inline  ::= inline                    (* constrained to begin with "@" *)

menu       ::= menu_section+

menu_section ::= heading (INDENT submenu DEDENT)?

heading    ::= entry_line

The alternatives are tried in the order written, so a line beginning with : or @ is a heading or a raw markup line regardless of what follows; the prefix forms are recognised by their first two characters, the space included, so *matrix is a documentation key rather than a link.

  • * marks a link, > marks a link to a subnode, and - marks free text.
  • * and > take a documentation key, evaluated and rendered with its headline, unless the text begins with @, in which case the whole rest of the line goes through the inline layer instead. - always uses the inline layer.
  • A line with none of these prefixes is a documentation key, rendered as after * but without the wrapper described below.
  • Deeper indentation nests one menu inside another, to any depth.

Outside Subnodes, * and > are not cosmetic variants. They emit spans of class link and subnode respectively, and installPackage harvests every subnode span out of a node's Description, Acknowledgement, Contributors, References, and Caveat, adding those tags to the documentation tree as genuine children just as if they had been listed under Subnodes. A * link in those sections is only a link.

Inside Subnodes the distinction is invisible to the tree builder, which deep-selects every documentation tag in the section with no regard for class. A key there becomes a child whichever prefix it carries, or none.

menu — the Tree subsection of a Description — differs from submenu in two ways: it renders the first line of each block as a heading, and if the section's first line begins with neither : nor @ it synthesises a Menu heading to put the entries under. A prose section led by Tree uses plain submenu and does neither; in particular its : lines get no heading element, so "heading" in the submenu production above means only "not an entry".

A menu of exactly one entry is a special case: it is not wrapped in a UL. Be warned that it is not uniform either — five of the six entry forms yield a one-element list, while the bare-key form yields the link object itself, so a consumer must handle three shapes in all. This is why both menu and submenu's recursive call re-wrap a one-element result before use.


Inline Layer

Within prose, item descriptions, and menu entries, text is a mixture of TeX and Macaulay2:

inline    ::= (tex_run | at_block)*

at_block  ::= "@" m2_expression "@"

tex_run   ::= (any_char_except_at | "\\@")+
  • Text outside @…@ is TeX. It may use $…$ and \(…\) for inline math and $$…$$ and \[…\] for display math, which are left for KaTeX. Five constructs are converted to hypertext outright — {\bf …}, {\em …}, {\it …}, {\tt …}, and \url{…} — and the text is also rewritten for typography: --- becomes an em dash unconditionally, and then any remaining -- with a word character immediately on both sides becomes an en dash.
  • Text inside @…@ is wrapped in parentheses and evaluated. It is therefore a full Macaulay2 expression, not a restricted markup dialect: surrounding spaces are harmless, @TT "a", TT "b"@ is a sequence by virtue of the parentheses, and the hypertext constructors exported by the Text package (TO, TO2, TOH, TT, EM, HREF, UL, …) together with SimpleDoc's own arXiv, wikipedia, and stacksProject helpers are all in scope.
  • \@ is an escaped literal @; it does not delimit a block, and the backslash is removed in both layers.
  • An unmatched @ is an error, including a single stray @ at the end of a line.

The unit the inline layer operates on is the whole paragraph or the whole item description, after its lines have been joined — not the individual line. So an @…@ block may be broken across lines within one paragraph, but never across a paragraph break, and never in a menu, whose entries are rendered one line at a time.

Sections that do not use the inline layer fall into three groups:

Treatment Sections
Taken as a raw string Pre, CannedExample, Citation, ExampleFiles, Headline, Heading, Usage
Evaluated while parsing Key, SeeAlso, SourceCode, Code, BaseFunction, item types, menu keys
Deferred to install time Example

Static Checks

Raised while parsing, with a line number as a line #N: prefix:

Condition Message
Key with no non-blank lines expected at least one key
Headline, Heading, or BaseFunction with no body, or with a second non-blank line expected single indented line after …
Usage with no lines expected at least one indented line after Usage
An item head without exactly one : or => expected line containing a colon or a double arrow
Inputs or Outputs without Usage, inside a Node Inputs or Outputs specified, but Usage not provided

Raised while parsing, with the line number embedded in the message instead:

Condition Message
A keyword line not in the table in force unrecognized keyword on line #N: "…"; expected: …
An unmatched @ in a rendered line unmatched @ near line #N:

The keyword error is the most helpful diagnostic SimpleDoc produces, since it also prints the full list of keywords legal in that context.

Raised with no line information at all:

Condition Message Raised by
Inputs or Outputs without Usage, in a docstring with no explicit Node document: Inputs or Outputs specified, but Usage not provided document
A Synopsis with no Usage at all Usage: expected content SYNOPSIS, during parsing
A docstring mixing Node and non-Node top-level sections expected either a documentation node or a list of documentation nodes doc
A node with no Key missing Key document
A node keyword other than Node, Description, or Synopsis used twice in one node option X encountered twice document

The three Usage requirements are not one rule. nodeCheck runs from the Node handler, so it never sees a docstring written without an explicit Node keyword, and even inside a Node it only fires when Inputs or Outputs is present. That case is not unchecked, though: document applies the same requirement to every node later on, without a line number and with a document: prefix. A Synopsis, by contrast, is expanded eagerly by SYNOPSIS during parsing and requires Usage unconditionally — a Synopsis containing only a Heading, or nothing at all, is an error.

Line numbering in diagnostics

The line #N in these messages is not always what a reader expects. For a docstring read from a file, N is relative to that file. For an inline doc /// … ///, it is an approximate absolute line in the .m2 file, back-computed from the current row number — the source's own comment describes it as "hopefully the doc line, but may be off by one". Line numbers are assigned before comment lines are deleted, so they do count deleted -- lines. Two diagnostics are further off. The "expected single indented line" error reports the second body line rather than the offending one. The unmatched-@ error reports the line preceding the paragraph — the blank line that started it, or the keyword line if it is the section's first paragraph — not the line the @ is on.

Failure modes that are not checked

Several malformed docstrings produce an internal Macaulay2 error rather than a SimpleDoc diagnostic. The known cases:

  • An empty CannedExample or Citation, or an empty Tree subsection of a Description, raises array index 0 out of bounds. An empty Example raises EXAMPLE: empty list of examples encountered. An empty Synopsis raises Usage: expected content — the Static Checks rule above, since an empty Synopsis has no Usage. Apart from those and the keywords whose emptiness is diagnosed above (Key, Headline, Heading, BaseFunction, Usage), an empty body is silently accepted and degenerate, including Node, Subnodes, Pre, Code, Inputs, Outputs, Consequences, Item, SeeAlso, SourceCode, ExampleFiles, a prose-leading Tree, every prose section, and a Description outside an explicit Node.
  • An empty Description inside an explicit Node also raises array index 0 out of bounds, unless an Inputs or Outputs section and a Usage section both precede it. nodeCheck makes two scans over the parsed items and both must short-circuit before reaching the empty sequence, so either one alone is not enough.
  • A blank first line in an Example, CannedExample, or Citation raises no method for binary operator : applied to … — the section takes its base indentation from that line, which is infinity.
  • A failure inside an @…@ block is reported by re-evaluating the text after printing in the evaluation of: … to stderr. No other evaluated text gets this treatment: a failure in a Key, SeeAlso, SourceCode, Code, an item's type, or a menu key surfaces as a bare interpreter error naming neither the docstring line nor the offending section.

Notes

Comment lines vanish before indentation matters. Lines matching ^[[:space:]]*-- are deleted at the very start, so a comment can never break a block, end a paragraph, or separate two examples, however it is indented. The consequence authors actually hit is in Example: a whole-line -- comment is removed from the example source, so it is never sent to Macaulay2 and never appears in the transcript. An end-of-line -- inside prose, by contrast, is not stripped and appears in the rendered text verbatim. TeX's typographic rewriting does not save you: it converts a -- only when a word character sits immediately on both sides, so foo--bar becomes an en dash but a trailing -- comment is left alone. A trailing --- comment does get an em dash, since --- is rewritten unconditionally.

The indentation floor only ever drops. See Block Structure. A section keyword accidentally indented one column less than its siblings will absorb them instead of standing beside them, and no error is reported.

Blank lines mean opposite things at the two levels. They are invisible when splitting a section into subsections, significant when splitting prose into paragraphs — where they produce empty paragraphs, including one at the end of almost every Text section — and discarded entirely inside menus. A blank line immediately after Headline, Heading, BaseFunction, Example, CannedExample, or Citation is a hazard, since those forms take their value or their base indentation from the section's first line.

Key requires quotation marks around string keys. Every line of a Key, SeeAlso, or SourceCode section is evaluated, so "Macaulay2Doc :: matrix" is a string key while matrix is a symbol.

SeeAlso drops blank lines but ExampleFiles does not. An ExampleFiles section containing a blank line yields an empty file name.

Description splices, eventually. Its subsections end up inserted individually into the enclosing node, so splitting a description across several Description sections changes nothing — but inside an explicit Node the flattening is deferred to document, so the intermediate parse tree still shows a nested sequence.

Tabs are real tab stops. A tab advances to the next multiple of 8, so a line indented with a tab and a line indented with eight spaces are at the same level — but a tab following seven spaces advances only one column.

Adding a keyword means editing two files. Keywords that are not already exported Macaulay2 symbols must also be registered for syntax highlighting in M2/Macaulay2/packages/Style.m2, which currently lists Node, Synopsis, CannedExample, Code, Example, Pre, Tree, and Item. That list is a useful cross-check for anyone writing a highlighter.

Clone this wiki locally