2015-05-05 01:22:45 +00:00
|
|
|
Low level format
|
|
|
|
================
|
|
|
|
|
|
|
|
Lean can export .lean and .hlean files in a low-level format that is easy to parse and process.
|
|
|
|
The exported file contains only fully elaborated terms.
|
|
|
|
The file describes hierarchical names, universe levels and expressions.
|
|
|
|
These objects are used to declare inductive datatypes, definitions and axioms.
|
|
|
|
|
|
|
|
Hierarchical names
|
|
|
|
------------------
|
|
|
|
|
|
|
|
A hierarchical name is essentially a list of strings and integers.
|
|
|
|
Each hierarchical name has a unique identifier: a unsigned integer.
|
|
|
|
The unsigned integer 0 denotes the _anonymous_ hierarchical name.
|
|
|
|
We can also view it as the empty name.
|
|
|
|
The following commands are used to define hierarchical names in the export file.
|
|
|
|
|
|
|
|
```
|
2015-07-27 20:47:18 +00:00
|
|
|
<nidx'> #NS <nidx> <string>
|
|
|
|
<nidx'> #NI <nidx> <integer>
|
2015-05-05 01:22:45 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
In both commands, `nidx` is the unique identifier of an existing hierarchical name,
|
|
|
|
and `nidx'` is the identifier for the hierarchical name being defined.
|
|
|
|
The first command defines a hierarchical name by appending the given string,
|
|
|
|
and the second by appending the given integer.
|
|
|
|
The hierarchical name `foo.bla.1.boo` may be defined using the following sequence of commands
|
|
|
|
|
|
|
|
```
|
2015-07-27 20:47:18 +00:00
|
|
|
1 #NS 0 foo
|
|
|
|
2 #NS 1 bla
|
|
|
|
3 #NI 2 1
|
|
|
|
4 #NS 3 boo
|
2015-05-05 01:22:45 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
Universe terms
|
|
|
|
---------------
|
|
|
|
|
|
|
|
Lean supports universe polymorphism.
|
|
|
|
That is, declaration in Lean can be parametrized by one or more universe level parameters.
|
|
|
|
The declarations can then be instantiated with universe level expressions.
|
|
|
|
In the standard Lean front-end, universe levels can be omitted, and the Lean elaborator (tries) to infer them automatically for users.
|
|
|
|
In this section, we describe the commands for create universe terms.
|
|
|
|
Each universe term has a unique identifier: a unsigned integer.
|
|
|
|
Note that the identifiers assigned to universe terms and hierarchical names are not disjoint.
|
|
|
|
The unsigned integer 0 is used to denote the universe 0.
|
|
|
|
|
|
|
|
The following commands are used to create universe terms in the export file.
|
|
|
|
|
|
|
|
```
|
2015-07-27 17:24:00 +00:00
|
|
|
<uidx'> #US <uidx>
|
|
|
|
<uidx'> #UM <uidx_1> <uidx_2>
|
|
|
|
<uidx'> #UIM <uidx_1> <uidx_2>
|
|
|
|
<uidx'> #UP <nidx>
|
|
|
|
<uidx'> #UG <nidx>
|
2015-05-05 01:22:45 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
In the commands above, `uidx`, `uidx_1` and `uidx_2` denote the unique identifier of existing universe terms,
|
|
|
|
`nidx` the unique identifier of existing hierarchical names, and `nidx'` is the identifier for the universe
|
2015-07-27 17:24:00 +00:00
|
|
|
term being defined. The command `#US` defines the _successor_ universe for `uidx`, the `#UM` the maximum universe for `uidx_1` and `uidx_2`,
|
|
|
|
and `#UIM` is the "impredicative" maximum. It is defined as zero if `uidx_2` evaluates to zero, and `#UM` otherwise.
|
|
|
|
The command `#UP` defines the universe parameter with name `nidx`, and `#UG` the global universe with name `nidx`.
|
2015-05-05 01:22:45 +00:00
|
|
|
Here is the sequence of commands for creating the universe term `imax (max 2 l1) l2`.
|
|
|
|
```
|
2015-07-27 20:47:18 +00:00
|
|
|
1 #NS 0 l1
|
|
|
|
2 #NS 0 l2
|
2015-07-27 17:24:00 +00:00
|
|
|
1 #US 0
|
|
|
|
2 #US 1
|
|
|
|
3 #UP 1
|
|
|
|
4 #UP 2
|
|
|
|
5 #UM 2 3
|
|
|
|
6 #UIM 5 4
|
2015-05-05 01:22:45 +00:00
|
|
|
```
|
|
|
|
Thus, the unique identifier for term `imax (max 2 l1) l2` is `6`. The unique identifier for term `l1` is `3`.
|
|
|
|
|
|
|
|
Expressions
|
|
|
|
-----------
|
|
|
|
|
|
|
|
In Lean, we have the following kind of expressions:
|
|
|
|
variables, sorts (aka Type), constants, constants, function applications, lambdas, and dependent function spaces (aka Pis).
|
|
|
|
Each expression has a unique identifier: a unsigned integer.
|
|
|
|
Again, the expression unique identifiers are not disjoint from the universe term and hierarchical name ones.
|
|
|
|
The following command are used to create expressions in the export file.
|
|
|
|
```
|
2015-07-27 20:47:18 +00:00
|
|
|
<eidx'> #EV <integer>
|
|
|
|
<eidx'> #ES <uidx>
|
|
|
|
<eidx'> #EC <nidx> <uidx>*
|
|
|
|
<eidx'> #EA <eidx_1> <eidx_2>
|
|
|
|
<eidx'> #EL <info> <nidx> <eidx_1> <eidx_2>
|
|
|
|
<eidx'> #EP <info> <nidx> <eidx_1> <eidx_2>
|
2015-05-05 01:22:45 +00:00
|
|
|
```
|
|
|
|
In the commands above, `uidx` denotes the unique identifier of existing universe terms,
|
|
|
|
`nidx` the unique identifier of existing hierarchical names, `eidx_1` and `eidx_2` the unique
|
|
|
|
identifier of existing expressions, `info` is an annotation (explained later), and
|
|
|
|
`eidx'` is the identifier for the expression being defined.
|
2015-07-27 20:47:18 +00:00
|
|
|
The command `#EV` defines a bound variable with de Bruijn index `<integer>`.
|
|
|
|
The command `#ES` defines a sort using the given universe term.
|
|
|
|
The command `#EC` defines a constant with hierarchical name `nidx` and instantiated with 0 or more
|
2015-05-05 01:22:45 +00:00
|
|
|
universe terms `<uidx>*`.
|
2015-07-27 20:47:18 +00:00
|
|
|
The command `#EA` defines function application where `eidx_1` is the function, and `eidx_2` is the argument.
|
2015-05-05 01:22:45 +00:00
|
|
|
The binders of lambda and Pi abstractions are decorated with `info`.
|
|
|
|
This information has no semantic value for fully elaborated terms, but it is useful for pretty printing.
|
2015-07-27 20:47:18 +00:00
|
|
|
`info` can be one of the following annotations: `#BD`, `#BI`, `#BS` and `#BC`. The annotation `#BD` corresponds to
|
|
|
|
the default binder annotation `(...)` used in `.lean` files, and `#BI` to `{...}`, `#BS` to `{{...}}`, and
|
|
|
|
`#BC` to `[...]`.
|
|
|
|
The command `#EL` defines a lambda abstraction where `nidx` is the binder name, `eidx_1` the type, and
|
|
|
|
`eidx_2` the body. The command `#EP` is similar to `#EL`, but defines a Pi abstraction.
|
2015-05-05 01:22:45 +00:00
|
|
|
Here is the sequence of commands for creating the term `fun {A : Type.{1}} (a : A), a`
|
|
|
|
```
|
2015-07-27 20:47:18 +00:00
|
|
|
1 #NS 0 A
|
|
|
|
2 #NS 1 a
|
2015-07-27 17:24:00 +00:00
|
|
|
1 #US 0
|
2015-07-27 20:47:18 +00:00
|
|
|
1 #ES 1
|
|
|
|
2 #EV 0
|
|
|
|
3 #EL #BD 2 2
|
|
|
|
4 #EL #BI 1 3
|
2015-05-05 01:22:45 +00:00
|
|
|
```
|
|
|
|
Now, assume the environment contains the following constant declarations:
|
|
|
|
`nat : Type.{1}`, `nat.zero : nat`, `nat.succ : nat -> nat`, and `vector.{l} : Type.{l} -> nat -> Type.{max 1 l}`.
|
|
|
|
Then, the following sequence of commands can be used to create the term `vector.{1} nat 3`.
|
|
|
|
We annotate some commands with comments of the form `-- ...` to make the example easier to understand.
|
|
|
|
```
|
2015-07-27 20:47:18 +00:00
|
|
|
1 #NS 0 nat
|
|
|
|
2 #NS 1 zero
|
|
|
|
3 #NS 1 succ
|
|
|
|
4 #NS 0 vector
|
2015-07-27 17:24:00 +00:00
|
|
|
1 #US 0
|
2015-07-27 20:47:18 +00:00
|
|
|
1 #EC 2 -- nat.zero
|
|
|
|
2 #EC 3 -- nat.succ
|
|
|
|
3 #EA 2 1 -- nat.succ nat.zero
|
|
|
|
4 #EA 2 3 -- nat.succ (nat.succ nat.zero)
|
|
|
|
5 #EA 2 4 -- nat.succ (nat.succ (nat.succ nat.zero))
|
|
|
|
6 #EC 4 1 -- vector.{1}
|
|
|
|
7 #EC 1 -- nat
|
|
|
|
8 #EA 6 7 -- vector.{1} nat
|
|
|
|
9 #EA 8 5 -- vector.{1} nat (nat.succ (nat.succ (nat.succ nat.zero)))
|
2015-05-05 01:22:45 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
Imported files
|
|
|
|
--------------
|
|
|
|
|
|
|
|
As `.lean` and `.hlean` files, the exported files may import other exported files.
|
|
|
|
The _import_ commands can be relative or absolute paths (with respect to the `LEAN_PATH` environment variable).
|
|
|
|
```
|
2015-07-27 17:24:00 +00:00
|
|
|
#DI <nidx>
|
|
|
|
#RI <integer> <nidx>
|
2015-05-05 01:22:45 +00:00
|
|
|
```
|
|
|
|
Paths are described using hierarchical names. The hierarchical name `foo.bla.boo` corresponds to the path `foo/bla/boo`.
|
2015-07-27 17:24:00 +00:00
|
|
|
The command `#DI` is the direct import, it instructs the reader to import the file at the location corresponding to
|
|
|
|
the hierarchical name `nidx`. The command `#RI` is the relative import, the integer represents how many `../` should be added the path
|
2015-05-05 01:22:45 +00:00
|
|
|
represented by the hierarchical name `nidx`.
|
|
|
|
|
|
|
|
Global universe level declaration
|
|
|
|
---------------------------------
|
|
|
|
|
|
|
|
The command
|
|
|
|
```
|
2015-07-27 17:24:00 +00:00
|
|
|
#UNI <nidx>
|
2015-05-05 01:22:45 +00:00
|
|
|
```
|
|
|
|
declares a global universe with name `nidx`.
|
|
|
|
|
|
|
|
Definitions and Axioms
|
|
|
|
----------------------
|
|
|
|
|
|
|
|
The command
|
|
|
|
```
|
2015-07-27 17:24:00 +00:00
|
|
|
#DEF <nidx> <nidx>* | <eidx_1> <edix_2>
|
2015-05-05 01:22:45 +00:00
|
|
|
```
|
|
|
|
declares a definition with name `nidx` with zero or more universe parameters named `<nidx>*`.
|
|
|
|
The type is given by the expression `eidx_1` and the value by `eidx_2`.
|
|
|
|
Axioms are declared in a similar way
|
|
|
|
```
|
2015-07-27 17:24:00 +00:00
|
|
|
#AX <nidx> <nidx>* | <eidx>
|
2015-05-05 01:22:45 +00:00
|
|
|
```
|
|
|
|
We are postulating the existence of an element with the given type.
|
|
|
|
The following command declare the `definition id.{l} {A : Type.{l}} (a : A) : A := a`.
|
|
|
|
```
|
2015-07-27 20:47:18 +00:00
|
|
|
2 #NS 0 id
|
|
|
|
3 #NS 0 l
|
|
|
|
4 #NS 0 A
|
2015-07-27 17:24:00 +00:00
|
|
|
1 #UP 3
|
2015-07-27 20:47:18 +00:00
|
|
|
0 #ES 1
|
|
|
|
5 #NS 0 a
|
|
|
|
1 #EV 0
|
|
|
|
2 #EV 1
|
|
|
|
3 #EP #BD 5 1 2
|
|
|
|
4 #EP #BI 4 0 3
|
|
|
|
5 #EL #BD 5 1 1
|
|
|
|
6 #EL #BD 4 0 5
|
2015-07-27 17:24:00 +00:00
|
|
|
#DEF 2 3 | 4 6
|
2015-05-05 01:22:45 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
Inductive definitions
|
|
|
|
---------------------
|
|
|
|
|
|
|
|
Mutually inductive datatype declarations are slightly more complicated.
|
2015-07-27 17:24:00 +00:00
|
|
|
They are declared by a block of commands delimited by the command `#BIND` and `#EIND`.
|
|
|
|
The command `#BIND` has the following form:
|
2015-05-05 01:22:45 +00:00
|
|
|
```
|
2015-07-27 17:24:00 +00:00
|
|
|
#BIND <integer> <integer> <nidx>*
|
2015-05-05 01:22:45 +00:00
|
|
|
```
|
|
|
|
where the first integer are the number of parameters, the second is the number of
|
|
|
|
mutually recursive types being declared by the block, and `nidx*` is the sequence
|
|
|
|
of universe parameter _names_.
|
2015-07-27 17:24:00 +00:00
|
|
|
The command `#EIND` is just a delimiter and does not have arguments.
|
|
|
|
The block is composed by commands `#IND` and `#INTRO`.
|
2015-05-05 01:22:45 +00:00
|
|
|
```
|
2015-07-27 17:24:00 +00:00
|
|
|
#IND <nidx> <eidx>
|
|
|
|
#INTRO <nidx> <eidx>
|
2015-05-05 01:22:45 +00:00
|
|
|
```
|
2015-07-27 17:24:00 +00:00
|
|
|
The command `#IND` declares an inductive type with name `nidx` and type `eidx`.
|
|
|
|
The command `#INTRO` declares an introduction rule (aka constructor) with name
|
|
|
|
`nidx` and type `eidx`. The first command in a block is always an `#IND`,
|
|
|
|
the subsequent `#INTRO` commands are declaring the introduction rules for this
|
2015-05-05 01:22:45 +00:00
|
|
|
inductive type.
|
|
|
|
|
|
|
|
For example, the following mutually recursive declaration
|
|
|
|
```lean
|
|
|
|
inductive tree.{l} (A : Type.{l}) : Type.{max 1 l} :=
|
|
|
|
| node : tree_list.{l} A → tree.{l} A
|
|
|
|
| empty : tree.{l} A
|
|
|
|
with tree_list : Type.{max 1 l} :=
|
|
|
|
| nil : tree_list.{l} A
|
|
|
|
| cons : tree.{l} A → tree_list.{l} A → tree_list.{l} A
|
|
|
|
```
|
|
|
|
is encoded by the following sequence of commands
|
|
|
|
```
|
2015-07-27 20:47:18 +00:00
|
|
|
2 #NS 0 l
|
|
|
|
3 #NS 0 tree
|
|
|
|
4 #NS 0 A
|
2015-07-27 17:24:00 +00:00
|
|
|
1 #UP 2
|
2015-07-27 20:47:18 +00:00
|
|
|
0 #ES 1
|
2015-07-27 17:24:00 +00:00
|
|
|
2 #US 0
|
|
|
|
3 #UM 2 1
|
2015-07-27 20:47:18 +00:00
|
|
|
1 #ES 3
|
|
|
|
2 #EP #D 4 0 1
|
|
|
|
5 #NS 3 node
|
|
|
|
6 #NS 0 a
|
|
|
|
7 #NS 0 tree_list
|
|
|
|
3 #EC 7 1
|
|
|
|
4 #EV 0
|
|
|
|
5 #EA 3 4
|
|
|
|
6 #EC 3 1
|
|
|
|
7 #EV 1
|
|
|
|
8 #EA 6 7
|
|
|
|
9 #EP #BD 6 5 8
|
|
|
|
10 #EP #BI 4 0 9
|
|
|
|
8 #NS 3 empty
|
|
|
|
11 #EA 6 4
|
|
|
|
12 #EP #BD 4 0 11
|
|
|
|
9 #NS 7 nil
|
|
|
|
13 #EP #BD 4 0 5
|
|
|
|
10 #NS 7 cons
|
|
|
|
14 #EA 3 7
|
|
|
|
15 #EV 2
|
|
|
|
16 #EA 3 15
|
|
|
|
17 #EP #BD 6 14 16
|
|
|
|
18 #EP #BD 6 11 17
|
|
|
|
19 #EP #BI 4 0 18
|
2015-07-27 17:24:00 +00:00
|
|
|
#BIND 1 2 2
|
|
|
|
#IND 3 2
|
|
|
|
#INTRO 5 10
|
|
|
|
#INTRO 8 12
|
|
|
|
#IND 7 2
|
|
|
|
#INTRO 9 13
|
|
|
|
#INTRO 10 19
|
|
|
|
#EIND
|
2015-05-05 01:22:45 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
Exporting declarations
|
|
|
|
----------------------
|
|
|
|
|
|
|
|
The command line option `-E filename` is used to export declarations
|
|
|
|
in the format described above.
|