lean4-htt/src/Lean/Parser/Term/Basic.lean
David Thrane Christiansen f122454ef6
chore: cleanup and better docs for #10479 (#10504)
This PR cleans up a half-reverted refactor and adds documentation to
#10479.
2025-09-23 09:02:07 +00:00

297 lines
14 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Sebastian Ullrich, Mario Carneiro
-/
module
prelude
public import Lean.Parser.Attr
public import Lean.Parser.Level
public import Lean.Parser.Term.Doc
meta import Lean.Parser.Basic
/-!
This module contains the bare minimum of term syntax that's required to get documentation syntax to
parse, namely structure initializers and their dependencies.
This matters because some term syntax (such as `let rec`) includes docstrings, but docstrings
include metadata blocks that themselves include a structure initializer. Separating these layers
prevents an import cycle.
The remaining term syntax is found in `Lean.Parser.Term`. It may freely make use of documentation
comments.
-/
public section
namespace Lean
namespace Parser
builtin_initialize
registerBuiltinParserAttribute `builtin_tactic_parser ``Category.tactic .both
registerBuiltinDynamicParserAttribute `tactic_parser `tactic
@[inline] def tacticParser (rbp : Nat := 0) : Parser :=
categoryParser `tactic rbp
@[inline] def convParser (rbp : Nat := 0) : Parser :=
categoryParser `conv rbp
namespace Tactic
/-- `sepByIndentSemicolon(p)` parses a sequence of `p` optionally followed by `;`,
similar to `manyIndent(p ";"?)`, except that if two occurrences of `p` occur on the same line,
the `;` is mandatory. This is used by tactic parsing, so that
```
example := by
skip
skip
```
is legal, but `by skip skip` is not - it must be written as `by skip; skip`. -/
@[run_builtin_parser_attribute_hooks, builtin_doc]
def sepByIndentSemicolon (p : Parser) : Parser :=
sepByIndent p "; " (allowTrailingSep := true)
/-- `sepBy1IndentSemicolon(p)` parses a (nonempty) sequence of `p` optionally followed by `;`,
similar to `many1Indent(p ";"?)`, except that if two occurrences of `p` occur on the same line,
the `;` is mandatory. This is used by tactic parsing, so that
```
example := by
skip
skip
```
is legal, but `by skip skip` is not - it must be written as `by skip; skip`. -/
@[run_builtin_parser_attribute_hooks, builtin_doc]
def sepBy1IndentSemicolon (p : Parser) : Parser :=
sepBy1Indent p "; " (allowTrailingSep := true)
builtin_initialize
register_parser_alias sepByIndentSemicolon
register_parser_alias sepBy1IndentSemicolon
@[builtin_doc] def tacticSeq1Indented : Parser := leading_parser
sepBy1IndentSemicolon tacticParser
/-- The syntax `{ tacs }` is an alternative syntax for `· tacs`.
It runs the tactics in sequence, and fails if the goal is not solved. -/
@[builtin_doc] def tacticSeqBracketed : Parser := leading_parser
"{ " >> sepByIndentSemicolon tacticParser >> ppDedent ppLine >> "}"
/-- A sequence of tactics in brackets, or a delimiter-free indented sequence of tactics.
Delimiter-free indentation is determined by the *first* tactic of the sequence. -/
@[builtin_doc, run_builtin_parser_attribute_hooks] def tacticSeq := leading_parser
tacticSeqBracketed <|> tacticSeq1Indented
/-- Same as [`tacticSeq`] but requires delimiter-free tactic sequence to have strict indentation.
The strict indentation requirement only apply to *nested* `by`s, as top-level `by`s do not have a
position set. -/
@[builtin_doc, run_builtin_parser_attribute_hooks]
def tacticSeqIndentGt := withAntiquot (mkAntiquot "tacticSeq" ``tacticSeq) <| node ``tacticSeq <|
tacticSeqBracketed <|> (checkColGt "indented tactic sequence" >> tacticSeq1Indented)
/- Raw sequence for quotation and grouping -/
@[run_builtin_parser_attribute_hooks]
def seq1 :=
node `Lean.Parser.Tactic.seq1 $ sepBy1 tacticParser ";\n" (allowTrailingSep := true)
end Tactic
namespace Term
/--
A *hole* (or *placeholder term*), which stands for an unknown term that is expected to be inferred based on context.
For example, in `@id _ Nat.zero`, the `_` must be the type of `Nat.zero`, which is `Nat`.
The way this works is that holes create fresh metavariables.
The elaborator is allowed to assign terms to metavariables while it is checking definitional equalities.
This is often known as *unification*.
Normally, all holes must be solved for. However, there are a few contexts where this is not necessary:
* In `match` patterns, holes are catch-all patterns.
* In some tactics, such as `refine'` and `apply`, unsolved-for placeholders become new goals.
Related concept: implicit parameters are automatically filled in with holes during the elaboration process.
See also `?m` syntax (synthetic holes).
-/
@[builtin_term_parser] def hole := leading_parser
"_"
/--
A *synthetic hole* (or *synthetic placeholder*), which stands for an unknown term that should be synthesized using tactics.
- `?_` creates a fresh metavariable with an auto-generated name.
- `?m` either refers to a pre-existing metavariable named `m` or creates a fresh metavariable with that name.
In particular, the synthetic hole syntax creates "synthetic opaque metavariables",
the same kind of metavariable used to represent goals in the tactic state.
Synthetic holes are similar to holes in that `_` also creates metavariables,
but synthetic opaque metavariables have some different properties:
- In tactics such as `refine`, only synthetic holes yield new goals.
- During elaboration, unification will not solve for synthetic opaque metavariables, they are "opaque".
This is to prevent counterintuitive behavior such as disappearing goals.
- When synthetic holes appear under binders, they capture local variables using a more complicated mechanism known as delayed assignment.
## Delayed assigned metavariables
This section gives an overview of some technical details of synthetic holes, which you should feel free to skip.
Understanding delayed assignments is mainly useful for those who are working on tactics and other metaprogramming.
It is included here until there is a suitable place for it in the reference manual.
When a synthetic hole appears under a binding construct, such as for example `fun (x : α) (y : β) => ?s`,
the system creates a *delayed assignment*. This consists of
1. A metavariable `?m` of type `(x : α) → (y : β) → γ x y` whose local context is the local context outside the `fun`,
where `γ x y` is the type of `?s`. Recall that `x` and `y` appear in the local context of `?s`.
2. A delayed assignment record associating `?m` to `?s` and the variables `#[x, y]` in the local context of `?s`
Then, this function elaborates as `fun (x : α) (y : β) => ?m x y`, where one should understand `x` and `y` here
as being De Bruijn indexes, since Lean uses the locally nameless encoding of lambda calculus.
Once `?s` is fully solved for, in the sense that after metavariable instantiation it is a metavariable-free term `e`,
then we can make the assignment `?m := fun (x' : α) (y' : β) => e[x := x', y := y']`.
(Implementation note: Lean only instantiates full applications `?m x' y'` of delayed assigned metavariables, to skip forming this function.)
This delayed assignment mechanism is essential to the operation of basic tactics like `intro`,
and a good mental model is that it is a way to "apply" the metavariable `?s` by substituting values in for some of its local variables.
While it would be easier to immediately assign `?s := ?m x y`,
delayed assignment preserves `?s` as an unsolved-for metavariable with a local context that still contains `x` and `y`,
which is exactly what tactics like `intro` need.
By default, delayed assigned metavariables pretty print with what they are delayed assigned to.
The delayed assigned metavariables themselves can be pretty printed using `set_option pp.mvars.delayed true`.
For more information, see the "Gruesome details" module docstrings in `Lean.MetavarContext`.
-/
@[builtin_term_parser] def syntheticHole := leading_parser
"?" >> (ident <|> "_")
/--
The `⋯` term denotes a term that was omitted by the pretty printer.
The presence of `⋯` in pretty printer output is controlled by the `pp.deepTerms` and `pp.proofs` options,
and these options can be further adjusted using `pp.deepTerms.threshold` and `pp.proofs.threshold`.
It is only meant to be used for pretty printing.
However, in case it is copied and pasted from the Infoview, `⋯` logs a warning and elaborates like `_`.
-/
@[builtin_term_parser] def omission := leading_parser
"⋯"
@[run_builtin_parser_attribute_hooks]
def binderIdent : Parser := ident <|> hole
@[run_builtin_parser_attribute_hooks]
def binderType (requireType := false) : Parser :=
if requireType then node nullKind (" : " >> termParser) else optional (" : " >> termParser)
@[run_builtin_parser_attribute_hooks]
def binderTactic := leading_parser
atomic (symbol " := " >> " by ") >> Tactic.tacticSeq
def binderDefault := leading_parser
" := " >> termParser
open Lean.PrettyPrinter Parenthesizer Syntax.MonadTraverser in
@[combinator_parenthesizer Lean.Parser.Term.binderDefault, expose] def binderDefault.parenthesizer : Parenthesizer := do
let prec := match (← getCur) with
-- must parenthesize to distinguish from `binderTactic`
| `(binderDefault| := by $_) => maxPrec
| _ => 0
visitArgs do
term.parenthesizer prec
visitToken
/--
Explicit binder, like `(x y : A)` or `(x y)`.
Default values can be specified using `(x : A := v)` syntax, and tactics using `(x : A := by tac)`.
-/
@[builtin_doc] def explicitBinder (requireType := false) := leading_parser ppGroup <|
"(" >> withoutPosition (many1 binderIdent >> binderType requireType >> optional (binderTactic <|> binderDefault)) >> ")"
/--
Implicit binder, like `{x y : A}` or `{x y}`.
In regular applications, whenever all parameters before it have been specified,
then a `_` placeholder is automatically inserted for this parameter.
Implicit parameters should be able to be determined from the other arguments and the return type
by unification.
In `@` explicit mode, implicit binders behave like explicit binders.
-/
@[builtin_doc] def implicitBinder (requireType := false) := leading_parser ppGroup <|
"{" >> withoutPosition (many1 binderIdent >> binderType requireType) >> "}"
def strictImplicitLeftBracket := atomic (group (symbol "{" >> "{")) <|> "⦃"
def strictImplicitRightBracket := atomic (group (symbol "}" >> "}")) <|> "⦄"
/--
Strict-implicit binder, like `⦃x y : A⦄` or `⦃x y⦄`.
In contrast to `{ ... }` implicit binders, strict-implicit binders do not automatically insert
a `_` placeholder until at least one subsequent explicit parameter is specified.
Do *not* use strict-implicit binders unless there is a subsequent explicit parameter.
Assuming this rule is followed, for fully applied expressions implicit and strict-implicit binders have the same behavior.
Example: If `h : ∀ ⦃x : A⦄, x ∈ s → p x` and `hs : y ∈ s`,
then `h` by itself elaborates to itself without inserting `_` for the `x : A` parameter,
and `h hs` has type `p y`.
In contrast, if `h' : ∀ {x : A}, x ∈ s → p x`, then `h` by itself elaborates to have type `?m ∈ s → p ?m`
with `?m` a fresh metavariable.
-/
@[builtin_doc] def strictImplicitBinder (requireType := false) := leading_parser ppGroup <|
strictImplicitLeftBracket >> many1 binderIdent >>
binderType requireType >> strictImplicitRightBracket
def optIdent : Parser :=
optional (atomic (ident >> " : "))
/--
Instance-implicit binder, like `[C]` or `[inst : C]`.
In regular applications without `@` explicit mode, it is automatically inserted
and solved for by typeclass inference for the specified class `C`.
In `@` explicit mode, if `_` is used for an instance-implicit parameter, then it is still solved for by typeclass inference;
use `(_)` to inhibit this and have it be solved for by unification instead, like an implicit argument.
-/
@[builtin_doc] def instBinder := leading_parser ppGroup <|
"[" >> withoutPosition (optIdent >> termParser) >> "]"
/-- A `bracketedBinder` matches any kind of binder group that uses some kind of brackets:
* An explicit binder like `(x y : A)`
* An implicit binder like `{x y : A}`
* A strict implicit binder, `⦃y z : A⦄` or its ASCII alternative `{{y z : A}}`
* An instance binder `[A]` or `[x : A]` (multiple variables are not allowed here)
-/
@[builtin_doc, run_builtin_parser_attribute_hooks] def bracketedBinder (requireType := false) :=
withAntiquot (mkAntiquot "bracketedBinder" decl_name% (isPseudoKind := true)) <|
explicitBinder requireType <|> strictImplicitBinder requireType <|>
implicitBinder requireType <|> instBinder
@[run_builtin_parser_attribute_hooks]
def typeSpec := leading_parser " : " >> termParser
@[run_builtin_parser_attribute_hooks]
def optType : Parser := optional typeSpec
/-
Syntax category for structure instance notation fields.
Does not initialize `registerBuiltinDynamicParserAttribute` since this category is not meant to be user-extensible.
-/
builtin_initialize
registerBuiltinParserAttribute `builtin_structInstFieldDecl_parser ``Category.structInstFieldDecl
@[inline] def structInstFieldDeclParser (rbp : Nat := 0) : Parser :=
categoryParser `structInstFieldDecl rbp
@[run_builtin_parser_attribute_hooks]
def optEllipsis := leading_parser
optional " .."
@[run_builtin_parser_attribute_hooks]
def structInstArrayRef := leading_parser
"[" >> withoutPosition termParser >> "]"
@[run_builtin_parser_attribute_hooks]
def structInstLVal := leading_parser
(ident <|> fieldIdx <|> structInstArrayRef) >>
many (group ("." >> (ident <|> fieldIdx)) <|> structInstArrayRef)
@[run_builtin_parser_attribute_hooks]
def structInstFieldBinder :=
withAntiquot (mkAntiquot "structInstFieldBinder" decl_name% (isPseudoKind := true)) <|
binderIdent <|> bracketedBinder
@[run_builtin_parser_attribute_hooks]
def optTypeForStructInst : Parser := optional (atomic (typeSpec >> notFollowedBy "}" "}"))
/- `x` is an abbreviation for `x := x` -/
@[run_builtin_parser_attribute_hooks]
def structInstField := ppGroup <| leading_parser
structInstLVal >> optional (many (checkColGt >> ppSpace >> structInstFieldBinder) >> optTypeForStructInst >> ppDedent structInstFieldDeclParser)
/-
Tags the structure instance field syntax with a `Lean.Parser.Term.structInstFields` syntax node.
This node is used to enable structure instance field completion in the whitespace
of a structure instance notation.
-/
@[run_builtin_parser_attribute_hooks]
def structInstFields (p : Parser) : Parser := node `Lean.Parser.Term.structInstFields p