Documentation with Verso

4. Extensions🔗

Verso's markup language features four extension points:

These can be used to extend Verso to support new documentation features.

4.1. Syntax🔗

All four extension points share a common syntax. They are invoked by name, with a sequence of arguments. These arguments may be positional or by name, and their values may be identifiers, string literals, or numbers. Boolean flags may be passed by preceding their name with - or + for false or true, respectively.

In this example, the directive syntax is invoked with the positional argument term and the named argument title set to "Example". The flag check is set to false. It contains a descriptive paragraph and the code block grammar, which is invoked with no arguments:

:::syntax term (title := example) -check
This is an example grammar:
```grammar
term ::= term "<+-+>" term
```
:::

More formally, an invocation of an extension should match this grammar:

CALL := IDENT ARG*
ARG := VAL | "(" IDENT ":=" VAL ")" | "+" IDENT | "-" IDENT
VAL := IDENT | STRING | NUM

A CALL may occur after an opening fence on a code block. It is mandatory after the opening colons of a directive, in the opening curly braces of a role, or in a command.

4.2. Elaborating Extensions🔗

Each kind of extension has a table that maps names to expanders. An expander converts Verso's syntax to Lean terms. When the elaborator encounters a code block, role, directive, or command invocation, it resolves the name and looks up an expander in the table. Expanders are attempted until one of them either throws an error or succeeds. Expanders use the monad DocElabM, which is an extension of Lean's term elaboration monad TermElabM with document-specific features. Expanders first parse their arguments into a suitable configuration type, typically via a FromArgs instance, after which they return Lean syntax.

There are two ways to associate an expander with a name: the @[code_block], @[role], @[directive], and @[block_command] attributes (preferred) or the @[code_block_expander], @[role_expander], and @[directive_expander] attributes. Using the former attributes results in an expander that invokes the argument parser automatically, and they enable Verso to automatically compute usage information from a FromArgs instance. The latter are lower-level, and require manual parsing of arguments.

4.2.1. Parsing Arguments🔗

This grammar is fairly restrictive, so each extension is responsible for parsing their arguments in order to afford sufficient flexibility. Arguments are parsed via instances of FromArgs:

🔗type class
Verso.ArgParse.FromArgs (α : Type) (m : TypeType) : Type 1
Verso.ArgParse.FromArgs (α : Type) (m : TypeType) : Type 1

A canonical way to convert a sequence of Verso arguments into a given type.

Instance Constructor

Verso.ArgParse.FromArgs.mk

Methods

fromArgs : ArgParse m α

Converts a sequence of arguments into a value.

Implementations of FromArgs.fromArgs specify parsers written using ArgParse:

🔗inductive type
Verso.ArgParse (m : TypeType) : TypeType 1
Verso.ArgParse (m : TypeType) : TypeType 1

A parser for arguments in some underlying monad.

Constructors

fail : ArgParse m α

Fails with the provided error message.

pure : ArgParse m α

Returns a value without parsing any arguments.

lift : ArgParse m α

Provides an argument value by lifting an action from the underlying monad.

positional : ArgParse m α

Matches a positional argument.

named : ArgParse m (if optional = true then Option α else α)

Matches an argument with the provided name.

anyNamed : ArgParse m (Lean.Ident × α)

Matches any named argument.

flag : ArgParse m Bool

Matches a flag with the provided name.

flagM : ArgParse m Bool

Matches a flag with the provided name, deriving a default value from the monad

done : ArgParse m Unit

No further arguments are allowed.

orElse : ArgParse m α

Error recovery.

seq : ArgParse m β

The sequencing operation of an applicative functor.

many : ArgParse m αArgParse m (List α)

Zero or more repetitions.

remaining : ArgParse m (Array Doc.Arg)

Returns all remaining arguments. This is useful for consuming some, then forwarding the rest.

Individual argument values are matched using ValDesc:

🔗structure
Verso.ArgParse.ValDesc (m : TypeType) (α : Type) : Type
Verso.ArgParse.ValDesc (m : TypeType) (α : Type) : Type

A means of transforming a Verso argument value into a given type.

Constructor

Verso.ArgParse.ValDesc.mk

Fields

description : SigDoc

How should this argument be documented in automatically-generated signatures?

signature : CanMatch

Which of the three kinds of values can match this argument?

get : Doc.ArgValm α

How to transform the value into the given type.

A canonical value description for a Lean type can be registered via an instance of FromArgVal:

🔗type class
Verso.ArgParse.FromArgVal (α : Type) (m : TypeType) : Type
Verso.ArgParse.FromArgVal (α : Type) (m : TypeType) : Type

A canonical way to convert a Verso argument into a given type.

Instance Constructor

Verso.ArgParse.FromArgVal.mk

Methods

fromArgVal : ValDesc m α

A canonical way to convert a Verso argument into a given type.

In addition to the constructors of ArgParse, the Applicative and Functor instances are important, as well as the following helpers:

🔗def
Verso.ArgParse.namedD {α : Type} {m : TypeType} (name : Lean.Name) (val : ValDesc m α) (default : α) : ArgParse m α
Verso.ArgParse.namedD {α : Type} {m : TypeType} (name : Lean.Name) (val : ValDesc m α) (default : α) : ArgParse m α

Matches an argument with the provided name. If the argument is not present, default is returned.

🔗def
Verso.ArgParse.positional' {α : Type} {m : TypeType} [FromArgVal α m] (nameHint : Lean.Name) (doc? : Option SigDoc := none) : ArgParse m α
Verso.ArgParse.positional' {α : Type} {m : TypeType} [FromArgVal α m] (nameHint : Lean.Name) (doc? : Option SigDoc := none) : ArgParse m α

Matches a positional argument using the type's FromArgVal instance.

🔗def
Verso.ArgParse.named' {α : Type} {m : TypeType} [FromArgVal α m] (name : Lean.Name) (optional : Bool) (doc? : Option SigDoc := none) : ArgParse m (if optional = true then Option α else α)
Verso.ArgParse.named' {α : Type} {m : TypeType} [FromArgVal α m] (name : Lean.Name) (optional : Bool) (doc? : Option SigDoc := none) : ArgParse m (if optional = true then Option α else α)

Matches a named argument using the type's FromArgVal instance.

🔗def
Verso.ArgParse.namedD' {α : Type} {m : TypeType} [FromArgVal α m] (name : Lean.Name) (default : α) : ArgParse m α
Verso.ArgParse.namedD' {α : Type} {m : TypeType} [FromArgVal α m] (name : Lean.Name) (default : α) : ArgParse m α

Matches an argument with the provided name using the type's FromArgVal instance. If the argument is not present, default is returned.