14.4. 新しい構文の定義(Defining New Syntax)🔗

Lean の統一された構文表現は非常に汎用的で柔軟です。これは Lean のパーサを拡張しても、パースされた構文の表現を拡張する必要はないことを意味します。

14.4.1. 構文モデル(Syntax Model)

Lean のパーサは Lean.Syntax 型の解析木を生成します。 Lean.Syntax はコマンド・項・タクティク・カスタム拡張を含む Lean のすべての構文を表す帰納型です。これらは全て、いくつかの基本的なビルディングブロックで表されます。

アトム (atom)

アトムは文法の基本的な終端で、リテラル(文字や数値)・括弧・演算子・キーワードを含みます。

識別子 (identifier)

識別子は xNatNat.add などのような名前を表します。識別子の構文には、識別子が参照する可能性のある事前解決された名前のリストが含まれています。

ノード (node)

ノードは非終端のパースを表します。ノードは 構文種別 (syntax kind)と子として Syntax 値の配列を含みます。構文種別はノードの結果となる構文規則を識別します。

欠落した構文

パーサがエラーに遭遇すると、パーサは部分的な結果を返すため、Lean は部分的に書かれたプログラムや間違いを含むプログラムについて何らかのフィードバックを提供することができます。部分的な結果には、1つ以上の構文の欠落が含まれます。

アトムと識別子をまとめて トークン (token)と呼びます。

🔗inductive type
Lean.Syntax : Type

Syntax objects used by the parser, macro expander, delaborator, etc.

Constructors

missing : Lean.Syntax

A missing syntax corresponds to a portion of the syntax tree that is missing because of a parse error. The indexing operator on Syntax also returns missing for indexing out of bounds.

node (info : Lean.SourceInfo) (kind : Lean.SyntaxNodeKind)
    (args : Array Lean.Syntax) : Lean.Syntax

Node in the syntax tree.

The info field is used by the delaborator to store the position of the subexpression corresponding to this node. The parser sets the info field to none. The parser sets the info field to none, with position retrieval continuing recursively. Nodes created by quotations use the result from SourceInfo.fromRef so that they are marked as synthetic even when the leading/trailing token is not. The delaborator uses the info field to store the position of the subexpression corresponding to this node.

(Remark: the node constructor did not have an info field in previous versions. This caused a bug in the interactive widgets, where the popup for a + b was the same as for a. The delaborator used to associate subexpressions with pretty-printed syntax by setting the (string) position of the first atom/identifier to the (expression) position of the subexpression. For example, both a and a + b have the same first identifier, and so their infos got mixed up.)

atom (info : Lean.SourceInfo) (val : String) : Lean.Syntax

An atom corresponds to a keyword or piece of literal unquoted syntax. These correspond to quoted strings inside syntax declarations. For example, in (x + y), "(", "+" and ")" are atom and x and y are ident.

ident (info : Lean.SourceInfo) (rawVal : Substring)
    (val : Lean.Name)
    (preresolved : List Lean.Syntax.Preresolved)
    : Lean.Syntax

An ident corresponds to an identifier as parsed by the ident or rawIdent parsers.

  • rawVal is the literal substring from the input file

  • val is the parsed identifier (with hygiene)

  • preresolved is the list of possible declarations this could refer to

🔗inductive type
Lean.Syntax.Preresolved : Type

Binding information resolved and stored at compile time of a syntax quotation. Note: We do not statically know whether a syntax expects a namespace or term name, so a Syntax.ident may contain both preresolution kinds.

Constructors

namespace (ns : Lean.Name) : Lean.Syntax.Preresolved

A potential namespace reference

decl (n : Lean.Name) (fields : List String)
    : Lean.Syntax.Preresolved

A potential global constant or section variable reference, with additional field accesses

14.4.2. 構文ノード種別(Syntax Node Kinds)

構文ノード種別は通常、そのノードを生成したパーサを識別します。これは演算子や記法に与えられた名前(または自動的に生成された内部的な名前)が出現する場所の1つです。ノードだけが種別を識別するフィールドを持ちますが、識別子は規約により identKind 種別を、アトムは規約により内部文字列を種別として持ちます。Lean のパーサは各キーワードアトム KW​`token.KW という種類を持つ子を1つだけ持つノードにラップします。構文の値は Syntax.getKind を使って取り出すことができます。

🔗def
Lean.SyntaxNodeKind : Type

A SyntaxNodeKind classifies Syntax.node values. It is an abbreviation for Name, and you can use name literals to construct SyntaxNodeKinds, but they need not refer to declarations in the environment. Conventionally, a SyntaxNodeKind will correspond to the Parser or ParserDesc declaration that parses it.

🔗def
Lean.Syntax.isOfKind (stx : Lean.Syntax)
    (k : Lean.SyntaxNodeKind) : Bool

Is this a syntax with node kind k?

🔗def
Lean.Syntax.getKind (stx : Lean.Syntax)
    : Lean.SyntaxNodeKind

Gets the kind of a Syntax node. For non-node syntax, we use "pseudo kinds": identKind for ident, missing for missing, and the atom's string literal for atoms.

🔗def
Lean.Syntax.setKind (stx : Lean.Syntax)
    (k : Lean.SyntaxNodeKind) : Lean.Syntax

Changes the kind at the root of a Syntax node to k. Does nothing for non-node nodes.

14.4.3. トークンとリテラル種別(Token and Literal Kinds)

パーサによって生成される基本的なトークンには多くの名前付き種別が関連付けられます。通常、単一トークンの構文生成物は atom を1つ含む node で構成されます;ノードに保存された種別によって値を判別できるようになります。リテラル用のアトムはパーサによって解釈されません:文字列アトムはその先頭と末尾のダブルクォート文字とその中に含まれるエスケープシーケンスを含み、16進数は "0x" から始まる文字列として保存されます。 Lean.TSyntax.getString のような 補助関数 は要求に応じてこうしたデコードを行うために提供されています。

🔗def
Lean.identKind : Lean.SyntaxNodeKind

ident is not actually used as a node kind, but it is returned by getKind in the ident case so that things that handle different node kinds can also handle ident.

🔗def
Lean.strLitKind : Lean.SyntaxNodeKind

str is the node kind of string literals like "foo".

🔗def
Lean.interpolatedStrKind : Lean.SyntaxNodeKind

interpolatedStrKind is the node kind of an interpolated string literal like "value = {x}" in s!"value = {x}".

🔗def
Lean.interpolatedStrLitKind
    : Lean.SyntaxNodeKind

interpolatedStrLitKind is the node kind of interpolated string literal fragments like "value = { and }" in s!"value = {x}".

🔗def
Lean.charLitKind : Lean.SyntaxNodeKind

char is the node kind of character literals like 'A'.

🔗def
Lean.numLitKind : Lean.SyntaxNodeKind

num is the node kind of number literals like 42.

🔗def
Lean.scientificLitKind : Lean.SyntaxNodeKind

scientific is the node kind of floating point literals like 1.23e-3.

🔗def
Lean.nameLitKind : Lean.SyntaxNodeKind

name is the node kind of name literals like `foo.

🔗def
Lean.fieldIdxKind : Lean.SyntaxNodeKind

fieldIdx is the node kind of projection indices like the 2 in x.2.

14.4.4. 内部的な種別(Internal Kinds)

🔗def
Lean.groupKind : Lean.SyntaxNodeKind

The group kind is by the group parser, to avoid confusing with the null kind when used inside optional.

🔗def
Lean.nullKind : Lean.SyntaxNodeKind

The null kind is used for raw list parsers like many.

🔗def
Lean.choiceKind : Lean.SyntaxNodeKind

The choice kind is used when a piece of syntax has multiple parses, and the determination of which to use is deferred until typing information is available.

🔗def
Lean.hygieneInfoKind : Lean.SyntaxNodeKind

hygieneInfo is the node kind of the hygieneInfo parser, which is an "invisible token" which captures the hygiene information at the current point without parsing anything.

They can be used to generate identifiers (with Lean.HygieneInfo.mkIdent) as if they were introduced by the calling context, not the called macro.

14.4.5. ソース位置(Source Positions)🔗

アトム・識別子・ノードは元のファイルとの対応を追跡する ソース情報 (source information)をオプションで含みます。パーサはすべてのトークンのソース情報を保存しますが、ノードのソース情報は保存しません;パースされたノードの位置情報は最初と最後のトークンから再構築されます。すべての Syntax データがパーサから得られるわけではありません: マクロ展開 の結果である場合もあり、その場合は通常、生成された構文と解析された構文が混在しています。また内部的な項をユーザに表示するために delaborating した結果である場合もあります。これらの使用例では、ノード自体にソース情報が含まれることがあります。

ソース情報には2種類存在します:

オリジナル (original)

もとのソース情報はパーサから得られます。もとのソース位置に加えて、パーサによってスキップされた先頭と末尾の空白も含まれるため、元の文字列を再構築することができます。この空白は、部分文字列のコピーを割り当てる必要が内容に、もとのソースコードの文字列表現のオフセットとして(つまり Substring として)保存されます。

統合的 (synthetic)

統合されたソース情報はメタプログラム(マクロを含む)または Lean の内部から得られます。再構築されるもとの文字列がないため、先頭と末尾の空白は保存されません。統合されたソース位置は、項が自動的に変換された場合でも正確なフィードバックを提供し、エラボレートされた式と Lean の出力における表現の対応を追跡するために使用されます。統合された位置には 標準的 (canonical)というマークが付けられることがありますが、この場合、通常は統合された位置を無視する操作があたかもそうではないかのように扱われます。

🔗inductive type
Lean.SourceInfo : Type

Source information of tokens.

Constructors

original (leading : Substring) (pos : String.Pos)
    (trailing : Substring) (endPos : String.Pos)
    : Lean.SourceInfo

Token from original input with whitespace and position information. leading will be inferred after parsing by Syntax.updateLeading. During parsing, it is not at all clear what the preceding token was, especially with backtracking.

Lean.SourceInfo.synthetic (pos endPos : String.Pos)
  (canonical : Bool := false) : Lean.SourceInfo

Synthesized syntax (e.g. from a quotation) annotated with a span from the original source. In the delaborator, we "misuse" this constructor to store synthetic positions identifying subterms.

The canonical flag on synthetic syntax is enabled for syntax that is not literally part of the original input syntax but should be treated "as if" the user really wrote it for the purpose of hovers and error messages. This is usually used on identifiers, to connect the binding site to the user's original syntax even if the name of the identifier changes during expansion, as well as on tokens where we will attach targeted messages.

The syntax token%$stx in a syntax quotation will annotate the token token with the span from stx and also mark it as canonical.

As a rough guide, a macro expansion should only use a given piece of input syntax in a single canonical token, although this is sometimes violated when the same identifier is used to declare two binders, as in the macro expansion for dependent if:

`(if $h : $cond then $t else $e) ~>
`(dite $cond (fun $h => $t) (fun $h => $t))

In these cases if the user hovers over h they will see information about both binding sites.

none : Lean.SourceInfo

Synthesized token without position information.

14.4.6. 構文の検査(Inspecting Syntax)

Syntax 値の検査には3つの方法があります:

Repr インスタンス

Repr Syntax インスタンスは Syntax 型のコンストラクタを用いて、構文の非常に詳細な表現を生成します。

ToString インスタンス

ToString インスタンスはコンパクトな可視化を行い、特定の構文の種類を特定の規則で表現し、ひと目で読みやすくします。このインスタンスはソースの位置情報を抑制します。

プリティプリンタ

Lean のプリティプリンタは、構文をソースファイルで表示されるようにレンダリングしようとします。ただし、構文の入れ子構造が期待される形と一致しない場合は失敗します。

コンストラクタによる構文表現

Lean.Parser.Command.eval : command`#eval e` evaluates the expression `e` by compiling and evaluating it. * The command attempts to use `ToExpr`, `Repr`, or `ToString` instances to print the result. * If `e` is a monadic value of type `m ty`, then the command tries to adapt the monad `m` to one of the monads that `#eval` supports, which include `IO`, `CoreM`, `MetaM`, `TermElabM`, and `CommandElabM`. Users can define `MonadEval` instances to extend the list of supported monads. The `#eval` command gracefully degrades in capability depending on what is imported. Importing the `Lean.Elab.Command` module provides full capabilities. Due to unsoundness, `#eval` refuses to evaluate expressions that depend on `sorry`, even indirectly, since the presence of `sorry` can lead to runtime instability and crashes. This check can be overridden with the `#eval! e` command. Options: * If `eval.pp` is true (default: true) then tries to use `ToExpr` instances to make use of the usual pretty printer. Otherwise, only tries using `Repr` and `ToString` instances. * If `eval.type` is true (default: false) then pretty prints the type of the evaluated value. * If `eval.derive.repr` is true (default: true) then attempts to auto-derive a `Repr` instance when there is no other way to print the result. See also: `#reduce e` for evaluation by term reduction. #eval のコンテキストにて quote することで、 Repr インスタンスの構文表現を検査することができます。これはコマンドエラボレーションモナド CommandElabM でアクションが実行されます。出力例のサイズを小さくするために、補助関数 removeSourceInfo を使用して、表示前にソース情報を削除しています。

partial def removeSourceInfo : Syntax Syntax | .atom _ str => .atom .none str | .ident _ str x pre => .ident .none str x pre | .node _ k children => .node .none k (children.map removeSourceInfo) | .missing => .missing Lean.Syntax.node (Lean.SourceInfo.none) `«term_+_» #[Lean.Syntax.node (Lean.SourceInfo.none) `num #[Lean.Syntax.atom (Lean.SourceInfo.none) "2"], Lean.Syntax.atom (Lean.SourceInfo.none) "+", Lean.Syntax.missing]#eval do let stx `(2 + $(.missing)) logInfo (repr (removeSourceInfo stx.raw))
Lean.Syntax.node
  (Lean.SourceInfo.none)
  `«term_+_»
  #[Lean.Syntax.node (Lean.SourceInfo.none) `num #[Lean.Syntax.atom (Lean.SourceInfo.none) "2"],
    Lean.Syntax.atom (Lean.SourceInfo.none) "+", Lean.Syntax.missing]

2つ目の例では、quotation で囲まれた マクロスコープList.length の呼び出しで見えるようになっています。

Lean.Syntax.node (Lean.SourceInfo.none) `Lean.Parser.Term.app #[Lean.Syntax.ident (Lean.SourceInfo.none) "List.length".toSubstring (Lean.Name.mkNum `List.length._@.Manual.NotationsMacros.SyntaxDef._hyg 2) [Lean.Syntax.Preresolved.decl `List.length [], Lean.Syntax.Preresolved.namespace `List.length], Lean.Syntax.node (Lean.SourceInfo.none) `null #[Lean.Syntax.node (Lean.SourceInfo.none) `«term[_]» #[Lean.Syntax.atom (Lean.SourceInfo.none) "[", Lean.Syntax.node (Lean.SourceInfo.none) `null #[Lean.Syntax.node (Lean.SourceInfo.none) `str #[Lean.Syntax.atom (Lean.SourceInfo.none) "\"Rose\""], Lean.Syntax.atom (Lean.SourceInfo.none) ",", Lean.Syntax.node (Lean.SourceInfo.none) `str #[Lean.Syntax.atom (Lean.SourceInfo.none) "\"Daffodil\""], Lean.Syntax.atom (Lean.SourceInfo.none) ",", Lean.Syntax.node (Lean.SourceInfo.none) `str #[Lean.Syntax.atom (Lean.SourceInfo.none) "\"Lily\""]], Lean.Syntax.atom (Lean.SourceInfo.none) "]"]]]#eval do let stx `(List.length ["Rose", "Daffodil", "Lily"]) logInfo (repr (removeSourceInfo stx.raw))

事前解決された識別子 List.length の内容は以下のように表示されます:

Lean.Syntax.node
  (Lean.SourceInfo.none)
  `Lean.Parser.Term.app
  #[Lean.Syntax.ident
      (Lean.SourceInfo.none)
      "List.length".toSubstring
      (Lean.Name.mkNum `List.length._@.Manual.NotationsMacros.SyntaxDef._hyg 2)
      [Lean.Syntax.Preresolved.decl `List.length [], Lean.Syntax.Preresolved.namespace `List.length],
    Lean.Syntax.node
      (Lean.SourceInfo.none)
      `null
      #[Lean.Syntax.node
          (Lean.SourceInfo.none)
          `«term[_]»
          #[Lean.Syntax.atom (Lean.SourceInfo.none) "[",
            Lean.Syntax.node
              (Lean.SourceInfo.none)
              `null
              #[Lean.Syntax.node (Lean.SourceInfo.none) `str #[Lean.Syntax.atom (Lean.SourceInfo.none) "\"Rose\""],
                Lean.Syntax.atom (Lean.SourceInfo.none) ",",
                Lean.Syntax.node (Lean.SourceInfo.none) `str #[Lean.Syntax.atom (Lean.SourceInfo.none) "\"Daffodil\""],
                Lean.Syntax.atom (Lean.SourceInfo.none) ",",
                Lean.Syntax.node (Lean.SourceInfo.none) `str #[Lean.Syntax.atom (Lean.SourceInfo.none) "\"Lily\""]],
            Lean.Syntax.atom (Lean.SourceInfo.none) "]"]]]

ToString インスタンスは Syntax のコンストラクタを以下のように表現します:

  • ident コンストラクタはベースになっている名前として表現されます。ソース情報と事前解決された名前は表示されません。

  • atom コンストラクタは文字列として表現されます。

  • missing コンストラクタは <missing> で表現されます。

  • node コンストラクタの表現は種別に依存します。もし種別が ​`null の場合、ノードは大括弧で囲まれた子ノードで表現されます。そうでない場合、ノードはその種別とその子ノードで表現され、どちらも括弧で囲まれます。

文字列による構文

Lean.Parser.Command.eval : command`#eval e` evaluates the expression `e` by compiling and evaluating it. * The command attempts to use `ToExpr`, `Repr`, or `ToString` instances to print the result. * If `e` is a monadic value of type `m ty`, then the command tries to adapt the monad `m` to one of the monads that `#eval` supports, which include `IO`, `CoreM`, `MetaM`, `TermElabM`, and `CommandElabM`. Users can define `MonadEval` instances to extend the list of supported monads. The `#eval` command gracefully degrades in capability depending on what is imported. Importing the `Lean.Elab.Command` module provides full capabilities. Due to unsoundness, `#eval` refuses to evaluate expressions that depend on `sorry`, even indirectly, since the presence of `sorry` can lead to runtime instability and crashes. This check can be overridden with the `#eval! e` command. Options: * If `eval.pp` is true (default: true) then tries to use `ToExpr` instances to make use of the usual pretty printer. Otherwise, only tries using `Repr` and `ToString` instances. * If `eval.type` is true (default: false) then pretty prints the type of the evaluated value. * If `eval.derive.repr` is true (default: true) then attempts to auto-derive a `Repr` instance when there is no other way to print the result. See also: `#reduce e` for evaluation by term reduction. #eval のコンテキストにて quote することで、文字列による構文表現を検査することができます。これはコマンドエラボレーションモナド CommandElabM でアクションが実行されます。

(«term_+_» (num "2") "+" <missing>)#eval do let stx `(2 + $(.missing)) logInfo (toString stx)
(«term_+_» (num "2") "+" <missing>)

2つ目の例では、quotation で囲まれた マクロスコープList.length の呼び出しで見えるようになっています。

(Term.app `List.length._@.Manual.NotationsMacros.SyntaxDef._hyg.2 [(«term[_]» "[" [(str "\"Rose\"") "," (str "\"Daffodil\"") "," (str "\"Lily\"")] "]")])#eval do let stx `(List.length ["Rose", "Daffodil", "Lily"]) logInfo (toString stx)
(Term.app
 `List.length._@.Manual.NotationsMacros.SyntaxDef._hyg.2
 [(«term[_]» "[" [(str "\"Rose\"") "," (str "\"Daffodil\"") "," (str "\"Lily\"")] "]")])

構文のプリティプリントは通常、ユーザへのメッセージに含める場合に最も便利です。通常、Lean は必要に応じて自動的にプリティプリンタを起動します。しかし、必要に応じて ppTerm を明示的に呼び出すことができます。

プリティプリントされた構文

Lean.Parser.Command.eval : command`#eval e` evaluates the expression `e` by compiling and evaluating it. * The command attempts to use `ToExpr`, `Repr`, or `ToString` instances to print the result. * If `e` is a monadic value of type `m ty`, then the command tries to adapt the monad `m` to one of the monads that `#eval` supports, which include `IO`, `CoreM`, `MetaM`, `TermElabM`, and `CommandElabM`. Users can define `MonadEval` instances to extend the list of supported monads. The `#eval` command gracefully degrades in capability depending on what is imported. Importing the `Lean.Elab.Command` module provides full capabilities. Due to unsoundness, `#eval` refuses to evaluate expressions that depend on `sorry`, even indirectly, since the presence of `sorry` can lead to runtime instability and crashes. This check can be overridden with the `#eval! e` command. Options: * If `eval.pp` is true (default: true) then tries to use `ToExpr` instances to make use of the usual pretty printer. Otherwise, only tries using `Repr` and `ToString` instances. * If `eval.type` is true (default: false) then pretty prints the type of the evaluated value. * If `eval.derive.repr` is true (default: true) then attempts to auto-derive a `Repr` instance when there is no other way to print the result. See also: `#reduce e` for evaluation by term reduction. #eval のコンテキストにて quote することで、文字列による構文表現を検査することができます。これはコマンドエラボレーションモナド CommandElabM でアクションが実行されます。新しい構文宣言は、それらを表示させるための命令をプリティプリンタに備えさせるため、プリティプリンタは設定オブジェクトを必要とします。このオブジェクトは補助関数で構築できます:

def getPPContext : CommandElabM PPContext := do return { env := ( getEnv), opts := ( getOptions), currNamespace := ( getCurrNamespace), openDecls := ( getOpenDecls) } 2 + 5#eval show CommandElabM Unit from do let stx `(2 + 5) let fmt ppTerm ( getPPContext) stx logInfo fmt
2 + 5

2つ目の例では、quotation によって List.length に挿入された マクロスコープ がダガー()付きで表示されます。

List.length✝ ["Rose", "Daffodil", "Lily"]#eval do let stx `(List.length ["Rose", "Daffodil", "Lily"]) let fmt ppTerm ( getPPContext) stx logInfo fmt
List.length✝ ["Rose", "Daffodil", "Lily"]

プリティプリントは行を折り返し、自動的にインデントを挿入します。 coercion は通常、デフォルトのレイアウト幅を使用して、プリティプリンタの出力を logInfo が期待する型に変換します。この幅は pretty を名前付き引数で明示的に呼び出すことで制御できます。

List.length✝ ["Rose", "Daffodil", "Lily", "Rose", "Daffodil", "Lily", "Rose", "Daffodil", "Lily"]#eval do let flowers := #["Rose", "Daffodil", "Lily"] let manyFlowers := flowers ++ flowers ++ flowers let stx `(List.length [$(manyFlowers.map (quote (k := `term))),*]) let fmt ppTerm ( getPPContext) stx logInfo (fmt.pretty (width := 40))
List.length✝
  ["Rose", "Daffodil", "Lily", "Rose",
    "Daffodil", "Lily", "Rose",
    "Daffodil", "Lily"]

14.4.7. 型付き構文(Typed Syntax)

構文は追加でどの 構文カテゴリ に属するかを指定する型を注釈することができます。 TSyntax 構造体には、構文木とともに、構文カテゴリの型レベルのリストが含まれます。構文カテゴリのリストは通常、ちょうど1つの要素を含み、その場合リスト構造自体は表示されません。

🔗structure
Lean.TSyntax (ks : Lean.SyntaxNodeKinds) : Type

A Syntax value of one of the given syntax kinds. Note that while syntax quotations produce/expect TSyntax values of the correct kinds, this is not otherwise enforced and can easily be circumvented by direct use of the constructor. The namespace TSyntax.Compat can be opened to expose a general coercion from Syntax to any TSyntax ks for porting older code.

Constructor

Lean.TSyntax.mk

Fields

raw : Lean.Syntax

The underlying Syntax value.

Quasiquotations は正しい構文カテゴリに由来しない型付き構文の置換を防ぎます。Lean の組み込み構文カテゴリの多くには、ある種別の構文を別のカテゴリに適切にラップする coercions のセットがあります。これには例えば文字列リテラルの構文から項の構文への強制などが含まれます。さらに、ある構文カテゴリに対してのみ有効な補助関数の多くは、適切な型付き構文に対してのみ定義されています。

TSyntax のコンストラクタはパブリックであり、ユーザが内部制約を破る値を構築することを妨げるものはありません。 TSyntax の使用はよくあるミスを完全に排除するためではなく、減らすための方法と考えるべきです。

TSyntax の他に、セパレータの有無に関わらず、構文の配列を表す型があります。これらは構文宣言、または antiquotation の繰り返し要素に対応します。

🔗def
Lean.TSyntaxArray (ks : Lean.SyntaxNodeKinds)
    : Type

An array of syntaxes of kind ks.

🔗structure
Lean.Syntax.TSepArray
    (ks : Lean.SyntaxNodeKinds) (sep : String)
    : Type

A typed version of SepArray.

Constructor

Lean.Syntax.TSepArray.mk

Fields

elemsAndSeps : Array Lean.Syntax

The array of elements and separators, ordered like #[el1, sep1, el2, sep2, el3].

14.4.8. エイリアス(Aliases)

よく使われる型付き構文のために、多くのエイリアスが用意されています。これらのエイリアスによって、より抽象度の高いコードを書くことができます。

🔗def
Lean.Syntax.Term : Type
🔗def
Lean.Syntax.Command : Type
🔗def
Lean.Syntax.Level : Type
🔗def
Lean.Syntax.Tactic : Type
🔗def
Lean.Syntax.Prec : Type
🔗def
Lean.Syntax.Prio : Type
🔗def
Lean.Syntax.Ident : Type
🔗def
Lean.Syntax.StrLit : Type
🔗def
Lean.Syntax.CharLit : Type
🔗def
Lean.Syntax.NameLit : Type
🔗def
Lean.Syntax.NumLit : Type
🔗def
Lean.Syntax.ScientificLit : Type
🔗def
Lean.Syntax.HygieneInfo : Type

14.4.9. 型付き構文のための補助関数(Helpers for Typed Syntax)🔗

リテラルの場合、Lean のパーサは atom を1つ含むノードを生成します。内部のアトムは、ソース情報を持つ文字列を含み、ノードの種別はアトムがどのように解釈されるかを指定します。これには文字列のエスケープシーケンスのデコードや、16進数リテラルの解釈などがふくまれます。本節の補助案数は正しい解釈を行います。

🔗def
Lean.TSyntax.getId (s : Lean.Ident) : Lean.Name
🔗def
Lean.TSyntax.getName (s : Lean.NameLit)
    : Lean.Name
🔗def
Lean.TSyntax.getNat (s : Lean.NumLit) : Nat
🔗def
Lean.TSyntax.getScientific
    (s : Lean.ScientificLit) : Nat × Bool × Nat
🔗def
Lean.TSyntax.getString (s : Lean.StrLit)
    : String
🔗def
Lean.TSyntax.getChar (s : Lean.CharLit) : Char
🔗def
Lean.TSyntax.getHygieneInfo
    (s : Lean.HygieneInfo) : Lean.Name

14.4.10. 構文カテゴリ(Syntax Categories)🔗

Lean のパーサは 構文カテゴリ (syntax category)のテーブルを保持しています。構文カテゴリはコンテキストに依存しない文法における非終端に対応します。最も重要なカテゴリは項・コマンド・宇宙レベル・優先度 訳注:インスタンスの優先度(priority)のこと ・優先順位 訳注:構文解析においての優先順位のこと(precedence) ・リテラルなどのトークンを表すカテゴリです。通常、各 構文種別 はカテゴリに対応します。新しいカテゴリは Lean.Parser.Command.syntaxCat : commanddeclare_syntax_cat を使って宣言できます。

syntax

新しい構文カテゴリを宣言します。

command ::= ...
    | A `docComment` parses a "documentation comment" like `/-- foo -/`. This is not treated like
a regular comment (that is, as whitespace); it is parsed and forms part of the syntax tree structure.

A `docComment` node contains a `/--` atom and then the remainder of the comment, `foo -/` in this
example. Use `TSyntax.getDocString` to extract the body text from a doc string syntax node. docComment?
      declare_syntax_cat ident ((behavior := (catBehaviorBoth | catBehaviorSymbol)))?

識別子の後ろの動作指定は高度な機能で、通常は変更する必要はありません。これはパーサが識別子に遭遇した時の動作を制御し、時には識別子を非予約キーワードとして代わりに扱うようにすることができます。これはすべての tactic の名前が予約キーワードになるのを避けるために使用されています。

🔗inductive type
Lean.Parser.LeadingIdentBehavior : Type

The type LeadingIdentBehavior specifies how the parsing table lookup function behaves for identifiers. The function prattParser uses two tables leadingTable and trailingTable. They map tokens to parsers.

We use LeadingIdentBehavior.symbol and LeadingIdentBehavior.both and nonReservedSymbol parser to implement the tactic parsers. The idea is to avoid creating a reserved symbol for each builtin tactic (e.g., apply, assumption, etc.). That is, users may still use these symbols as identifiers (e.g., naming a function).

Constructors

default : Lean.Parser.LeadingIdentBehavior

LeadingIdentBehavior.default: if the leading token is an identifier, then prattParser just executes the parsers associated with the auxiliary token "ident".

symbol : Lean.Parser.LeadingIdentBehavior

LeadingIdentBehavior.symbol: if the leading token is an identifier <foo>, and there are parsers P associated with the token <foo>, then it executes P. Otherwise, it executes only the parsers associated with the auxiliary token "ident".

both : Lean.Parser.LeadingIdentBehavior

LeadingIdentBehavior.both: if the leading token an identifier <foo>, the it executes the parsers associated with token <foo> and parsers associated with the auxiliary token "ident".

14.4.11. 構文規則(Syntax Rules)🔗

構文カテゴリ構文規則 (syntax rule)のセットに関連づけられています。構文規則はコンテキストに依存しない文法の生成物に対応します。構文規則は Lean.Parser.Command.syntax : commandsyntax コマンドで定義できます。

syntax
command ::= ...
    | A `docComment` parses a "documentation comment" like `/-- foo -/`. This is not treated like
a regular comment (that is, as whitespace); it is parsed and forms part of the syntax tree structure.

A `docComment` node contains a `/--` atom and then the remainder of the comment, `foo -/` in this
example. Use `TSyntax.getDocString` to extract the body text from a doc string syntax node. docComment?
      attributes?
      `attrKind` matches `("scoped" <|> "local")?`, used before an attribute like `@[local simp]`. attrKind
      syntax(:prec)? ((name := ident))? ((priority := prio))? `p*` is shorthand for `many(p)`. It uses parser `p` 0 or more times, and produces a
`nullNode` containing the array of parsed results. This parser has arity 1.

If `p` has arity more than 1, it is auto-grouped in the items generated by the parser.
stx* : ident

演算子や記法宣言と同様に、ドキュメントコメントの内容はユーザが新しい構文を操作している間に表示されます。コンパイル時にメタプログラムを呼び出すために、結果の定義に属性を追加することができます。

構文属性は属性・演算子・記法と同じように セクションスコープ と相互作用します。デフォルトでは、構文規則はそれが確立されたパーサをインポートした任意のモジュールで遷移的に利用可能です。しかし、scoped または local と宣言することで、それぞれ現在の名前空間が開かれているコンテキストか、現在の セクションスコープ に限定することができます。

あるカテゴリの複数の構文規則が現在の入力にマッチする場合、 ローカル最長一致規則 が使用され、そのうちの1つが選択されます。記法や演算子と同様に、最長一致が複数ある場合は、宣言された優先度が使用され、どちらの構文解析結果が適用されるかが決定されます。それでもあいまいさが解消されない場合、同店の結果はすべて保存されます。エラボレータはそれらすべての試行し、1つだけがエラボレートできたときに成功することが期待されます。

Lean.Parser.Command.syntax : commandsyntax キーワードの直後に記述された構文規則の優先順位は、優先順位コンテキストが指定された値以上の場合にのみ、この新しい構文を使用するようにパーサを制限します。演算子や記法と同様に、構文規則も手動で名前を指定することができます;指定されない場合、他では使われない名前が生成されます。名前が指定された場合でも生成された場合でも、この名前は結果の node の構文種別として使用されます。

構文宣言の本体は記法よりもさらに柔軟です。文字列リテラルはマッチするアトムを指定します。部分項は項だけでなく、任意の構文カテゴリから引き出すことができ、それらはカンマの有無に関わらずオプション的であったり繰り返されたりします。構文規則における識別子は、記法の場合のように部分項を指定するのではなく、構文カテゴリを示します。

最後に、構文規則はどのカテゴリを拡張するかを指定します。存在しないカテゴリで構文規則を宣言するのはエラーになります。

syntax

構文カテゴリ stxLean.Parser.Command.syntax : commandsyntax コマンドのボディに出現する可能性のある指定子についての文法です。

文字列リテラルは アトムif#evalwhereなどのキーワードを含む)としてパースされます:

stx ::=
    str

文字列の先頭と末尾の空白はパースに影響しませんが、空白を入れた場合 証明状態 やエラーメッセージで構文を表示する時に Lean が対応する位置に空白が挿入されます。通常、構文規則のアトムとして出現する有効な識別子は予約キーワードになります。文字列リテラルの前にアンパサンド(&)を置くとこの動作が抑制されます:

stx ::= ...
    | &str

識別子は、指定された位置で期待される構文カテゴリを指定し、オプションで優先順位を指定できます:

stx ::= ...
    | ident(:prec)?

修飾子 * はクリーネスターで、直前の構文の0回以上の繰り返しにマッチします。これは many と書くこともできます。

stx ::= ...
    | `p*` is shorthand for `many(p)`. It uses parser `p` 0 or more times, and produces a
`nullNode` containing the array of parsed results. This parser has arity 1.

If `p` has arity more than 1, it is auto-grouped in the items generated by the parser.
stx *

修飾子 + は直前の構文の1回以上の繰り返しにマッチします。これは many1 と書くこともできます。

stx ::= ...
    | `p+` is shorthand for `many1(p)`. It uses parser `p` 1 or more times, and produces a
`nullNode` containing the array of parsed results. This parser has arity 1.

If `p` has arity more than 1, it is auto-grouped in the items generated by the parser.
stx +

修飾性 ? は部分項をオプショナルにし、直前の構文の繰り返しに0回または1回(それ以上は繰り返さない)にマッチします。これは optional と書くこともできます。

stx ::= ...
    | `(p)?` is shorthand for `optional(p)`. It uses parser `p` 0 or 1 times, and produces a
`nullNode` containing the array of parsed results. This parser has arity 1.

`p` is allowed to have arity n > 1 (in which case the node will have either 0 or n children),
but if it has arity 0 then the result will be ambiguous.

Because `?` is an identifier character, `ident?` will not work as intended.
You have to write either `ident ?` or `(ident)?` for it to parse as the `?` combinator
applied to the `ident` parser.
stx ?
stx ::= ...
    | Returns `some x` if `f` succeeds with value `x`, else returns `none`.
optional(stx)

修飾子 ,* はカンマで区切られた直前の構文の0回以上の繰り返しにマッチします。これは sepBy と書くこともできます。

stx ::= ...
    | `p,*` is shorthand for `sepBy(p, ",")`. It parses 0 or more occurrences of
`p` separated by `,`, that is: `empty | p | p,p | p,p,p | ...`.

It produces a `nullNode` containing a `SepArray` with the interleaved parser
results. It has arity 1, and auto-groups its component parser if needed.
stx ,*

修飾子 ,+ はカンマで区切られた直前の構文の1回以上の繰り返しにマッチします。これは sepBy1 と書くこともできます。

stx ::= ...
    | `p,+` is shorthand for `sepBy(p, ",")`. It parses 1 or more occurrences of
`p` separated by `,`, that is: `p | p,p | p,p,p | ...`.

It produces a `nullNode` containing a `SepArray` with the interleaved parser
results. It has arity 1, and auto-groups its component parser if needed.
stx ,+

修飾子 ,*,? はカンマで区切られた直前の構文の0回以上の繰り返しにマッチし、繰り返しの最後のカンマを任意にすることができます。これは allowTrailingSep を伴った sepBy と書くこともできます。

stx ::= ...
    | `p,*,?` is shorthand for `sepBy(p, ",", allowTrailingSep)`.
It parses 0 or more occurrences of `p` separated by `,`, possibly including
a trailing `,`, that is: `empty | p | p, | p,p | p,p, | p,p,p | ...`.

It produces a `nullNode` containing a `SepArray` with the interleaved parser
results. It has arity 1, and auto-groups its component parser if needed.
stx ,*,?

修飾子 ,+,? はカンマで区切られた直前の構文の1回以上の繰り返しにマッチし、繰り返しの最後のカンマを任意にすることができます。これは allowTrailingSep を伴った sepBy1 と書くこともできます。

stx ::= ...
    | `p,+,?` is shorthand for `sepBy1(p, ",", allowTrailingSep)`.
It parses 1 or more occurrences of `p` separated by `,`, possibly including
a trailing `,`, that is: `p | p, | p,p | p,p, | p,p,p | ...`.

It produces a `nullNode` containing a `SepArray` with the interleaved parser
results. It has arity 1, and auto-groups its component parser if needed.
stx ,+,?

演算子 <|>orelse と書くこともでき、どちらかの構文にマッチします。しかし、最初の分岐で何らかのトークンが消費された場合、その分岐がコミットされ、失敗はバックトラックされません:

stx ::= ...
    | `p1 <|> p2` is shorthand for `orelse(p1, p2)`, and parses either `p1` or `p2`.
It does not backtrack, meaning that if `p1` consumes at least one token then
`p2` will not be tried. Therefore, the parsers should all differ in their first
token. The `atomic(p)` parser combinator can be used to locally backtrack a parser.
(For full backtracking, consider using extensible syntax classes instead.)

On success, if the inner parser does not generate exactly one node, it will be
automatically wrapped in a `group` node, so the result will always be arity 1.

The `<|>` combinator does not generate a node of its own, and in particular
does not tag the inner parsers to distinguish them, which can present a problem
when reconstructing the parse. A well formed `<|>` parser should use disjoint
node kinds for `p1` and `p2`.
stx <|> stx
stx ::= ...
    | orelse(stx, stx)

演算子 ! は引数の補集合にマッチします。引数が失敗した場合に成功し、パース状態がリセットされます。

stx ::= ...
    | `!p` parses the negation of `p`. That is, it fails if `p` succeeds, and
otherwise parses nothing. It has arity 0.
! stx

構文指定子は括弧を使ってグループ化することができます。

stx ::= ...
    | (stx)

繰り返しは manymany1 を使って定義することができます。後者は繰り返される構文のインスタンスが少なくとも1つ必要です。

stx ::= ...
    | many(stx)
stx ::= ...
    | many1(stx)

区切り文字を使った繰り返しは sepBysepBy1 を使って定義することができ、それぞれ0個以上・1個以上の出現にマッチし、他の構文で区切られます。使い方は3種類あります:

  • パラメータが2つの場合は、文字列リテラルで指定されたアトムを使用して区切り文字をパースし、末尾の区切り文字を許容しません。

  • パラメータが3つの場合は、3番目のパラメータを使用して区切り文字をパースし、プリティプリンタのためにアトムを使用します。

  • パラメータが4つの場合、オプションとしてシーケンスの最後にセパレータを追加することができます。第4引数は常にキーワード allowTrailingSep を指定しなければなりません。

stx ::= ...
    | sepBy(stx, str)
stx ::= ...
    | sepBy(stx, str, stx)
stx ::= ...
    | sepBy(stx, str, stx, allowTrailingSep)
stx ::= ...
    | sepBy1(stx, str)
stx ::= ...
    | sepBy1(stx, str, stx)
stx ::= ...
    | sepBy1(stx, str, stx, allowTrailingSep)
一致した丸括弧と大括弧のパース

一致する丸括弧と大括弧で構成される言語は、構文規則を使って定義することができます。最初のステップは新しい 構文カテゴリ を宣言することです:

declare_syntax_cat balanced

次に、丸括弧と大括弧の規則を追加することができます。空の文字列を除外するために、基本ケースは空のペアで構成されます。

syntax "(" ")" : balanced syntax "[" "]" : balanced syntax "(" balanced ")" : balanced syntax "[" balanced "]" : balanced syntax balanced balanced : balanced

これらの規則に対して Lean のパーサを呼び出すには、新しい構文カテゴリから、すでにパースされている構文カテゴリへの埋め込みも必要です:

syntax (name := termBalanced) "balanced " balanced : term

これらの項はエラボレートできませんが、エラボレートエラーになったということは、パースが成功したということです:

/-- error: elaboration function for 'termBalanced' has not been implemented balanced () -/ #guard_msgs in example := balanced () /-- error: elaboration function for 'termBalanced' has not been implemented balanced [] -/ #guard_msgs in example := balanced [] /-- error: elaboration function for 'termBalanced' has not been implemented balanced [[]()([])] -/ #guard_msgs in example := balanced [[] () ([])]

同様に、括弧が一致しない場合はパースに失敗します:

example := balanced [() (expected ')' or balanced]]
<example>:1:25: expected ')' or balanced
カンマ区切りの繰り返しのパース

二重の大括弧を必要とし、末尾のカンマを許容するリストリテラルの亜種は、以下の構文で追加できます:

syntax "[[" term,*,? "]]" : term

マクロ (macro)を追加して、通常のリストリテラルに変換する方法を記述することで、テストで使用できるようになります。

macro_rules | `(term|[[$e:term,*]]) => `([$e,*]) ["Dandelion", "Thistle"]#eval [["Dandelion", "Thistle",]]
["Dandelion", "Thistle"]

14.4.12. インデント(Indentation)🔗

内部的に、パーサは保存されたソース位置を保持します。構文規則には、これらの保存された位置と相互作用し、条件が満たされない場合にパースを失敗させる命令を含めることができます。 Lean.Parser.Term.do : termdo のようなインデントに依存する構文はソース位置を保存し、この保存された位置を考慮しながら一連の部分をパースし、もとの位置に戻します。

特に、インデントへの依存は withPosition または withPositionAfterLinebreakcolGtcolGecolEq を組み合わせることで指定します。最初の2つは他の構文をパースし始める際のソース位置を保存します。後ろの3つは現在の列と最近保存された位置の列を比較します。また、 lineEq は2つの位置がソースファイルの同じ行にあることを確認するために使用できます。

🔗parser alias
withPosition(p)

Arity is sum of arguments' arities

withPosition(p) runs p while setting the "saved position" to the current position. This has no effect on its own, but various other parsers access this position to achieve some composite effect:

  • colGt, colGe, colEq compare the column of the saved position to the current position, used to implement Python-style indentation sensitive blocks

  • lineEq ensures that the current position is still on the same line as the saved position, used to implement composite tokens

The saved position is only available in the read-only state, which is why this is a scoping parser: after the withPosition(..) block the saved position will be restored to its original value.

This parser has the same arity as p - it just forwards the results of p.

🔗parser alias
withoutPosition(p)

Arity is sum of arguments' arities

withoutPosition(p) runs p without the saved position, meaning that position-checking parsers like colGt will have no effect. This is usually used by bracketing constructs like (...) so that the user can locally override whitespace sensitivity.

This parser has the same arity as p - it just forwards the results of p.

🔗parser alias
withPositionAfterLinebreak
  • Arity: 1
  • Automatically wraps arguments in a null node unless there's exactly one
🔗parser alias
colGt
  • Arity: 0
  • Automatically wraps arguments in a null node unless there's exactly one

The colGt parser requires that the next token starts a strictly greater column than the saved position (see withPosition). This can be used for whitespace sensitive syntax for the arguments to a tactic, to ensure that the following tactic is not interpreted as an argument.

example (x : False) : False := by
  revert x
  exact id

Here, the revert tactic is followed by a list of colGt ident, because otherwise it would interpret exact as an identifier and try to revert a variable named exact.

This parser has arity 0 - it does not capture anything.

🔗parser alias
colGe
  • Arity: 0
  • Automatically wraps arguments in a null node unless there's exactly one

The colGe parser requires that the next token starts from at least the column of the saved position (see withPosition), but allows it to be more indented. This can be used for whitespace sensitive syntax to ensure that a block does not go outside a certain indentation scope. For example it is used in the lean grammar for else if, to ensure that the else is not less indented than the if it matches with.

This parser has arity 0 - it does not capture anything.

🔗parser alias
colEq
  • Arity: 0
  • Automatically wraps arguments in a null node unless there's exactly one

The colEq parser ensures that the next token starts at exactly the column of the saved position (see withPosition). This can be used to do whitespace sensitive syntax like a by block or do block, where all the lines have to line up.

This parser has arity 0 - it does not capture anything.

🔗parser alias
lineEq
  • Arity: 0
  • Automatically wraps arguments in a null node unless there's exactly one

The lineEq parser requires that the current token is on the same line as the saved position (see withPosition). This can be used to ensure that composite tokens are not "broken up" across different lines. For example, else if is parsed using lineEq to ensure that the two tokens are on the same line.

This parser has arity 0 - it does not capture anything.

列をそろえる

箇条書きの項目リストを取るメモ保存のための以下の構文では、各項目は同じ列にそろえなければなりません。

syntax "note " ppLine withPosition((colEq "• " str ppLine)+) : term

この構文に関連するエラボレータやマクロはありませんが、以下の例はパーサに受け入れられます:

#check elaboration function for '«termNote__•__»' has not been implemented note • "One" • "Two" note "One" "Two"
elaboration function for '«termNote__•__»' has not been implemented
  note
    • "One"
    • "Two"
    

この構文では、リストが開始トークンに対してインデントされている必要はありません。それを達成するには追加で withPositioncolGt が必要です。

#check elaboration function for '«termNote__•__»' has not been implemented note • "One" • "Two" note "One" "Two"
elaboration function for '«termNote__•__»' has not been implemented
  note
    • "One"
    • "Two"
    

以下の例は、箇条書きの列が一致していないために、構文的に妥当ではありません。

#check  note    • "One"   expected end of input• "Two"
<example>:4:3: expected end of input
#check  note   • "One"     expected end of input• "Two"
<example>:4:5: expected end of input