diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index ba1ea6e..1211525 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -8,6 +8,15 @@ jobs: steps: - uses: actions/checkout@v3 + - name: install elan + run: | + curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh -s -- -y --default-toolchain $(cat lean-toolchain) + echo "$HOME/.elan/bin" >> $GITHUB_PATH + + # note: `lake run build` raise an error in GitHub Action + - name: build markdown files by mdgen + run: lake exe mdgen lean md + - name: Install Dependencies run: sudo apt update && sudo apt install -y pandoc texlive-latex-base texlive-latex-extra texlive-latex-recommended texlive-luatex fonts-dejavu python3-pygments diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..110d52d --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,56 @@ +name: Deploy to github pages + +on: [push, pull_request] + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: install elan + run: | + curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh -s -- -y --default-toolchain $(cat lean-toolchain) + echo "$HOME/.elan/bin" >> $GITHUB_PATH + + # note: `lake run build` raise an error in GitHub Action + - name: build markdown files by mdgen + run: lake exe mdgen lean md + + - name: setup mdBook + uses: peaceiris/actions-mdbook@v1 + with: + mdbook-version: '0.4.32' + + - name: build html from markdown + run: mdbook build + + - name: upload artifact + uses: actions/upload-pages-artifact@v2 + with: + path: ./book + + deploy: + if: github.ref == 'refs/heads/master' + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v3 diff --git a/.gitignore b/.gitignore index 88f1874..3247634 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ /build -.lake/ \ No newline at end of file +.lake/ +book +md/ +!md/SUMMARY.md \ No newline at end of file diff --git a/book.toml b/book.toml new file mode 100644 index 0000000..5a46334 --- /dev/null +++ b/book.toml @@ -0,0 +1,9 @@ +[book] +authors = ["Arthur Paulino, Damiano Testa, Edward Ayers, Evgenia Karunus, Henrik Böving, Jannis Limperg, Siddhartha Gadgil, Siddharth Bhat"] +language = "en" +multilingual = false +src = "md" +title = "Metaprogramming in Lean 4" + +[output.html] +git-repository-url = "https://github.com/leanprover-community/lean4-metaprogramming-book" \ No newline at end of file diff --git a/md/SUMMARY.md b/md/SUMMARY.md new file mode 100644 index 0000000..e382e16 --- /dev/null +++ b/md/SUMMARY.md @@ -0,0 +1,18 @@ +# Summary + +- [Introduction](./main/01_intro.md) +- [Overview](./main/02_overview.md) +- [Expressions](./main/03_expressions.md) +- [MetaM](./main/04_metam.md) +- [Syntax](./main/05_syntax.md) +- [Macros](./main/06_macros.md) +- [Elaboration](./main/07_elaboration.md) +- [Embedding DSLs By Elaboration](./main/08_dsls.md) +- [Tactics](./main/09_tactics.md) +- [Lean4 Cheat-sheet](./main/10_cheat-sheet.md) + +# Extra + +- [Options](./extra/01_options.md) +- [Attributes]() +- [Pretty Printing](./extra/03_pretty-printing.md) \ No newline at end of file diff --git a/md/cover.md b/md/cover.md deleted file mode 100644 index 43f9e93..0000000 --- a/md/cover.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: "Metaprogramming in Lean 4" -author: [Arthur Paulino, Damiano Testa, Edward Ayers, Evgenia Karunus, - Henrik Böving, Jannis Limperg, Siddhartha Gadgil, Siddharth Bhat] -# abusing this field just because the template puts it at a decent right-sized spot -date: "Formatted for PDF by Julian Berman using [Pascal Wagler's Template](https://github.com/Wandmalfarbe/pandoc-latex-template)" -mainfont: "DejaVu Serif" -monofont: "DejaVu Sans Mono" -book: true -header-right: "." -titlepage: true -titlepage-color: "FFFFFF" -titlepage-text-color: "5F5F5F" -titlepage-rule-color: "435488" -keywords: [Lean, theorem proving, mathematics, math, maths, tutorial] -... diff --git a/md/extra/01_options.md b/md/extra/01_options.md deleted file mode 100644 index 307ab81..0000000 --- a/md/extra/01_options.md +++ /dev/null @@ -1,88 +0,0 @@ -# Extra: Options -Options are a way to communicate some special configuration to both -your meta programs and the Lean compiler itself. Basically it's just -a [`KVMap`](https://github.com/leanprover/lean4/blob/master/src/Lean/Data/KVMap.lean) -which is a simple map from `Name` to a `Lean.DataValue`. Right now there -are 6 kinds of data values: -- `String` -- `Bool` -- `Name` -- `Nat` -- `Int` -- `Syntax` - -Setting an option to tell the Lean compiler to do something different -with your program is quite simple with the `set_option` command: - -```lean -import Lean -open Lean - -#check 1 + 1 -- 1 + 1 : Nat - -set_option pp.explicit true -- No custom syntax in pretty printing - -#check 1 + 1 -- @HAdd.hAdd Nat Nat Nat (@instHAdd Nat instAddNat) 1 1 : Nat - -set_option pp.explicit false -``` - -You can furthermore limit an option value to just the next command or term: - -```lean -set_option pp.explicit true in -#check 1 + 1 -- @HAdd.hAdd Nat Nat Nat (@instHAdd Nat instAddNat) 1 1 : Nat - -#check 1 + 1 -- 1 + 1 : Nat - -#check set_option trace.Meta.synthInstance true in 1 + 1 -- the trace of the type class synthesis for `OfNat` and `HAdd` -``` - -If you want to know which options are available out of the Box right now -you can simply write out the `set_option` command and move your cursor -to where the name is written, it should give you a list of them as auto -completion suggestions. The most useful group of options when you are -debugging some meta thing is the `trace.` one. - -## Options in meta programming -Now that we know how to set options, let's take a look at how a meta program -can get access to them. The most common way to do this is via the `MonadOptions` -type class, an extension to `Monad` that provides a function `getOptions : m Options`. -As of now, it is implemented by: -- `CoreM` -- `CommandElabM` -- `LevelElabM` -- all monads to which you can lift operations of one of the above (e.g. `MetaM` from `CoreM`) - -Once we have an `Options` object, we can query the information via `Options.get`. -To show this, let's write a command that prints the value of `pp.explicit`. - -```lean -elab "#getPPExplicit" : command => do - let opts ← getOptions - -- defValue = default value - logInfo s!"pp.explicit : {opts.get pp.explicit.name pp.explicit.defValue}" - -#getPPExplicit -- pp.explicit : false - -set_option pp.explicit true in -#getPPExplicit -- pp.explicit : true -``` - -Note that the real implementation of getting `pp.explicit`, `Lean.getPPExplicit`, -uses whether `pp.all` is set as a default value instead. - -## Making our own -Declaring our own option is quite easy as well. The Lean compiler provides -a macro `register_option` for this. Let's see it in action: - -```lean -register_option book.myGreeting : String := { - defValue := "Hello World" - group := "pp" - descr := "just a friendly greeting" -} -``` - -However, we cannot just use an option that we just declared in the same file -it was declared in because of initialization restrictions. diff --git a/md/extra/02_attributes.md b/md/extra/02_attributes.md deleted file mode 100644 index 8b13789..0000000 --- a/md/extra/02_attributes.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/md/extra/03_pretty-printing.md b/md/extra/03_pretty-printing.md deleted file mode 100644 index bec0313..0000000 --- a/md/extra/03_pretty-printing.md +++ /dev/null @@ -1,223 +0,0 @@ -# Extra: Pretty Printing -The pretty printer is what Lean uses to present terms that have been -elaborated to the user. This is done by converting the `Expr`s back into -`Syntax` and then even higher level pretty printing datastructures. This means -Lean does not actually recall the `Syntax` it used to create some `Expr`: -there has to be code that tells it how to do that. -In the big picture, the pretty printer consists of three parts run in the -order they are listed in: - -- the **[delaborator](https://github.com/leanprover/lean4/tree/master/src/Lean/PrettyPrinter/Delaborator)** - this will be our main interest since we can easily extend it with our own code. - Its job is to turn `Expr` back into `Syntax`. -- the **[parenthesizer](https://github.com/leanprover/lean4/blob/master/src/Lean/PrettyPrinter/Parenthesizer.lean)** - responsible for adding parenthesis into the `Syntax` tree, where it thinks they would be useful -- the **[formatter](https://github.com/leanprover/lean4/blob/master/src/Lean/PrettyPrinter/Formatter.lean)** - responsible for turning the parenthesized `Syntax` tree into a `Format` object that contains - more pretty printing information like explicit whitespaces - -## Delaboration -As its name suggests, the delaborator is in a sense the opposite of the -elaborator. The job of the delaborator is to take an `Expr` produced by -the elaborator and turn it back into a `Syntax` which, if elaborated, -should produce an `Expr` that behaves equally to the input one. - -Delaborators have the type `Lean.PrettyPrinter.Delaborator.Delab`. This -is an alias for `DelabM Syntax`, where `DelabM` is the delaboration monad. -All of this machinery is defined [here](https://github.com/leanprover/lean4/blob/master/src/Lean/PrettyPrinter/Delaborator/Basic.lean). -`DelabM` provides us with quite a lot of options you can look up in the documentation -(TODO: Docs link). We will merely highlight the most relevant parts here. - -- It has a `MonadQuotation` instance which allows us to declare `Syntax` objects - using the familiar quotation syntax. -- It can run `MetaM` code. -- It has a `MonadExcept` instance for throwing errors. -- It can interact with `pp` options using functions like `whenPPOption`. -- You can obtain the current subexpression using `SubExpr.getExpr`. There is - also an entire API defined around this concept in the `SubExpr` module. - -### Making our own -Like so many things in metaprogramming the elaborator is based on an attribute, -in this case the `delab` one. `delab` expects a `Name` as an argument, -this name has to start with the name of an `Expr` constructor, most commonly -`const` or `app`. This constructor name is then followed by the name of the -constant we want to delaborate. For example, if we want to delaborate a function -`foo` in a special way we would use `app.foo`. Let's see this in action: - -```lean -import Lean - -open Lean PrettyPrinter Delaborator SubExpr - -def foo : Nat → Nat := fun x => 42 - -@[delab app.foo] -def delabFoo : Delab := do - `(1) - -#check foo -- 1 : Nat → Nat -#check foo 13 -- 1 : Nat, full applications are also pretty printed this way -``` - -This is obviously not a good delaborator since reelaborating this `Syntax` -will not yield the same `Expr`. Like with many other metaprogramming -attributes we can also overload delaborators: - -```lean -@[delab app.foo] -def delabfoo2 : Delab := do - `(2) - -#check foo -- 2 : Nat → Nat -``` - -The mechanism for figuring out which one to use is the same as well. The -delaborators are tried in order, in reverse order of registering, until one -does not throw an error, indicating that it "feels unresponsible for the `Expr`". -In the case of delaborators, this is done using `failure`: - -```lean -@[delab app.foo] -def delabfoo3 : Delab := do - failure - `(3) - -#check foo -- 2 : Nat → Nat, still 2 since 3 failed -``` - -In order to write a proper delaborator for `foo`, we will have to use some -slightly more advanced machinery though: - -```lean -@[delab app.foo] -def delabfooFinal : Delab := do - let e ← getExpr - guard $ e.isAppOfArity' `foo 1 -- only delab full applications this way - let fn := mkIdent `fooSpecial - let arg ← withAppArg delab - `($fn $arg) - -#check foo 42 -- fooSpecial 42 : Nat -#check foo -- 2 : Nat → Nat, still 2 since 3 failed -``` - -Can you extend `delabFooFinal` to also account for non full applications? - -## Unexpanders -While delaborators are obviously quite powerful it is quite often not necessary -to use them. If you look in the Lean compiler for `@[delab` or rather `@[builtin_delab` -(a special version of the `delab` attribute for compiler use, we don't care about it), -you will see there are quite few occurrences of it. This is because the majority -of pretty printing is in fact done by so called unexpanders. Unlike delaborators -they are of type `Lean.PrettyPrinter.Unexpander` which in turn is an alias for -`Syntax → Lean.PrettyPrinter.UnexpandM Syntax`. As you can see, they are -`Syntax` to `Syntax` translations, quite similar to macros, except that they -are supposed to be the inverse of macros. The `UnexpandM` monad is quite a lot -weaker than `DelabM` but it still has: - -- `MonadQuotation` for syntax quotations -- The ability to throw errors, although not very informative ones: `throw ()` - is the only valid one - -Unexpanders are always specific to applications of one constant. They are registered -using the `app_unexpander` attribute, followed by the name of said constant. The unexpander -is passed the entire application of the constant after the `Expr` has been delaborated, -without implicit arguments. Let's see this in action: - -```lean -def myid {α : Type} (x : α) := x - -@[app_unexpander myid] -def unexpMyId : Unexpander - -- hygiene disabled so we can actually return `id` without macro scopes etc. - | `(myid $arg) => set_option hygiene false in `(id $arg) - | `(myid) => pure $ mkIdent `id - | _ => throw () - -#check myid 12 -- id 12 : Nat -#check myid -- id : ?m.3870 → ?m.3870 -``` - -For a few nice examples of unexpanders you can take a look at -[NotationExtra](https://github.com/leanprover/lean4/blob/master/src/Init/NotationExtra.lean) - -### Mini project -As per usual, we will tackle a little mini project at the end of the chapter. -This time we build our own unexpander for a mini programming language. -Note that many ways to define syntax already have generation of the required -pretty printer code built-in, e.g. `infix`, and `notation` (however not `macro_rules`). -So, for easy syntax, you will never have to do this yourself. - -```lean -declare_syntax_cat lang -syntax num : lang -syntax ident : lang -syntax "let " ident " := " lang " in " lang: lang -syntax "[Lang| " lang "]" : term - -inductive LangExpr - | numConst : Nat → LangExpr - | ident : String → LangExpr - | letE : String → LangExpr → LangExpr → LangExpr - -macro_rules - | `([Lang| $x:num ]) => `(LangExpr.numConst $x) - | `([Lang| $x:ident]) => `(LangExpr.ident $(Lean.quote (toString x.getId))) - | `([Lang| let $x:ident := $v:lang in $b:lang]) => `(LangExpr.letE $(Lean.quote (toString x.getId)) [Lang| $v] [Lang| $b]) - -instance : Coe NumLit (TSyntax `lang) where - coe s := ⟨s.raw⟩ - -instance : Coe Ident (TSyntax `lang) where - coe s := ⟨s.raw⟩ - --- LangExpr.letE "foo" (LangExpr.numConst 12) --- (LangExpr.letE "bar" (LangExpr.ident "foo") (LangExpr.ident "foo")) : LangExpr -#check [Lang| - let foo := 12 in - let bar := foo in - foo -] -``` - -As you can see, the pretty printing output right now is rather ugly to look at. -We can do better with an unexpander: - -```lean -@[app_unexpander LangExpr.numConst] -def unexpandNumConst : Unexpander - | `(LangExpr.numConst $x:num) => `([Lang| $x]) - | _ => throw () - -@[app_unexpander LangExpr.ident] -def unexpandIdent : Unexpander - | `(LangExpr.ident $x:str) => - let str := x.getString - let name := mkIdent $ Name.mkSimple str - `([Lang| $name]) - | _ => throw () - -@[app_unexpander LangExpr.letE] -def unexpandLet : Unexpander - | `(LangExpr.letE $x:str [Lang| $v:lang] [Lang| $b:lang]) => - let str := x.getString - let name := mkIdent $ Name.mkSimple str - `([Lang| let $name := $v in $b]) - | _ => throw () - --- [Lang| let foo := 12 in foo] : LangExpr -#check [Lang| - let foo := 12 in foo -] - --- [Lang| let foo := 12 in let bar := foo in foo] : LangExpr -#check [Lang| - let foo := 12 in - let bar := foo in - foo -] -``` - -That's much better! As always, we encourage you to extend the language yourself -with things like parenthesized expressions, more data values, quotations for -`term` or whatever else comes to your mind. diff --git a/md/main/01_intro.md b/md/main/01_intro.md deleted file mode 100644 index ace87e6..0000000 --- a/md/main/01_intro.md +++ /dev/null @@ -1,258 +0,0 @@ -# Introduction - -## What's the goal of this book? - -This book aims to build up enough knowledge about metaprogramming in Lean 4 so -you can be comfortable enough to: - -* Start building your own meta helpers (defining new Lean notation such as `∑`, -building new Lean commands such as `#check`, writing tactics such as `use`, etc.) -* Read and discuss metaprogramming APIs like the ones in Lean 4 core and -Mathlib4 - -We by no means intend to provide an exhaustive exploration/explanation of the -entire Lean 4 metaprogramming API. We also don't cover the topic of monadic -programming in Lean 4. However, we hope that the examples provided will be -simple enough for the reader to follow and comprehend without a super deep -understanding of monadic programming. The book -[Functional Programming in Lean](https://leanprover.github.io/functional_programming_in_lean/) -is a highly recommended source on that subject. - -## Book structure - -The book is organized in a way to build up enough context for the chapters that -cover DSLs and tactics. Backtracking the pre-requisites for each chapter, the -dependency structure is as follows: - -* "Tactics" builds on top of "Macros" and "Elaboration" -* "DSLs" builds on top of "Elaboration" -* "Macros" builds on top of "`Syntax`" -* "Elaboration" builds on top of "`Syntax`" and "`MetaM`" -* "`MetaM`" builds on top of "Expressions" - -After the chapter on tactics, you find a cheat sheet containing a wrap-up of key -concepts and functions. And after that, there are some chapters with extra -content, showing other applications of metaprogramming in Lean 4. Most chapters contain exercises at the end of the chapter - and at the end of the book you will have full solutions to those exercises. - -The rest of this chapter is a gentle introduction to what metaprogramming is, -offering some small examples to serve as appetizers for what the book shall -cover. - -Note: the code snippets aren't self-contained. They are supposed to be run/read -incrementally, starting from the beginning of each chapter. - -## What does it mean to be in meta? - -When we write code in most programming languages such as Python, C, Java or -Scala, we usually have to stick to a pre-defined syntax otherwise the compiler -or the interpreter won't be able to figure out what we're trying to say. In -Lean, that would be defining an inductive type, implementing a function, proving -a theorem, etc. The compiler, then, has to parse the code, build an AST (abstract -syntax tree) and elaborate its syntax nodes into terms that can be processed by -the language kernel. We say that such activities performed by the compiler are -done in the __meta-level__, which will be studied throughout the book. And we -also say that the common usage of the language syntax is done in the -__object-level__. - -In most systems, the meta-level activities are done in a different language to -the one that we use to write code. In Isabelle, the meta-level language is ML -and Scala. In Coq, it's OCaml. In Agda, it's Haskell. In Lean 4, the meta code is -mostly written in Lean itself, with a few components written in C++. - -One cool thing about Lean, though, is that it allows us to define custom syntax -nodes and implement meta-level routines to elaborate them in the -very same development environment that we use to perform object-level -activities. So for example, one can write notation to instantiate a -term of a certain type and use it right away, in the same file! This concept is -generally called -[__reflection__](https://en.wikipedia.org/wiki/Reflective_programming). We can -say that, in Lean, the meta-level is _reflected_ to the object-level. - -If you have done some metaprogramming in languages such as Ruby, Python or Javascript, -it probably took the form of making use of predefined metaprogramming methods to define -something on the fly. For example, in Ruby you can use `Class.new` and `define_method` -to define a new class and its new method (with new code inside!) on the fly, as your program is executing. - -We don't usually need to define new commands or tactics "on the fly" in Lean, but spiritually -similar feats are possible with Lean metaprogramming and are equally straightforward, e.g. you can define -a new Lean command using a simple one-liner `elab "#help" : command => do ...normal Lean code...`. - -In Lean, however, we will frequently want to directly manipulate -Lean's CST (Concrete Syntax Tree, Lean's `Syntax` type) and -Lean's AST (Abstract Syntax Tree, Lean's `Expr` type) instead of using "normal Lean code", -especially when we're writing tactics. So Lean metaprogramming is more challenging to master - -a large chunk of this book is contributed to studying these types and how we can manipulate them. - -## Metaprogramming examples - -Next, we introduce several examples of Lean metaprogramming, so that you start getting a taste for -what metaprogramming in Lean is, and what it will enable you to achieve. These examples are meant as -mere illustrations - do not worry if you don't understand the details for now. - -### Introducing notation (defining new syntax) - -Often one wants to introduce new notation, for example one more suitable for (a branch of) mathematics. For instance, in mathematics one would write the function adding `2` to a natural number as `x : Nat ↦ x + 2` or simply `x ↦ x + 2` if the domain can be inferred to be the natural numbers. The corresponding lean definitions `fun x : Nat => x + 2` and `fun x => x + 2` use `=>` which in mathematics means _implication_, so may be confusing to some. - -We can introduce notation using a `macro` which transforms our syntax to Lean's syntax (or syntax we previously defined). Here we introduce the `↦` notation for functions. - -```lean -import Lean - -macro x:ident ":" t:term " ↦ " y:term : term => do - `(fun $x : $t => $y) - -#eval (x : Nat ↦ x + 2) 2 -- 4 - -macro x:ident " ↦ " y:term : term => do - `(fun $x => $y) - -#eval (x ↦ x + 2) 2 -- 4 -``` - -### Building a command - -Suppose we want to build a helper command `#assertType` which tells whether a -given term is of a certain type. The usage will be: - -`#assertType : ` - -Let's see the code: - -```lean -elab "#assertType " termStx:term " : " typeStx:term : command => - open Lean Lean.Elab Command Term in - liftTermElabM - try - let tp ← elabType typeStx - discard $ elabTermEnsuringType termStx tp - synthesizeSyntheticMVarsNoPostponing - logInfo "success" - catch | _ => throwError "failure" - -#assertType 5 : Nat -- success -#assertType [] : Nat -- failure -``` - -We started by using `elab` to define a `command` syntax. When parsed -by the compiler, it will trigger the incoming computation. - -At this point, the code should be running in the `CommandElabM` monad. We then -use `liftTermElabM` to access the `TermElabM` monad, which allows us to use -`elabType` and `elabTermEnsuringType` to build expressions out of the -syntax nodes `typeStx` and `termStx`. - -First, we elaborate the expected type `tp : Expr`, then we use it to elaborate -the term expression. The term should have the type `tp` otherwise an error will be -thrown. We then discard the resulting term expression, since it doesn't matter to us here - we're calling -`elabTermEnsuringType` as a sanity check. - -We also add `synthesizeSyntheticMVarsNoPostponing`, which forces Lean to -elaborate metavariables right away. Without that line, `#assertType [] : ?_` -would result in `success`. - -If no error is thrown until now then the elaboration succeeded and we can use -`logInfo` to output "success". If, instead, some error is caught, then we use -`throwError` with the appropriate message. - -### Building a DSL and a syntax for it - -Let's parse a classic grammar, the grammar of arithmetic expressions with -addition, multiplication, naturals, and variables. We'll define an AST -(Abstract Syntax Tree) to encode the data of our expressions, and use operators -`+` and `*` to denote building an arithmetic AST. Here's the AST that we will be -parsing: - -```lean -inductive Arith : Type where - | add : Arith → Arith → Arith -- e + f - | mul : Arith → Arith → Arith -- e * f - | nat : Nat → Arith -- constant - | var : String → Arith -- variable -``` - -Now we declare a syntax category to describe the grammar that we will be -parsing. Notice that we control the precedence of `+` and `*` by giving a lower -precedence weight to the `+` syntax than to the `*` syntax indicating that -multiplication binds tighter than addition (the higher the number, the tighter -the binding). This allows us to declare _precedence_ when defining new syntax. - -```lean -declare_syntax_cat arith -syntax num : arith -- nat for Arith.nat -syntax str : arith -- strings for Arith.var -syntax:50 arith:50 " + " arith:51 : arith -- Arith.add -syntax:60 arith:60 " * " arith:61 : arith -- Arith.mul -syntax " ( " arith " ) " : arith -- bracketed expressions - --- Auxiliary notation for translating `arith` into `term` -syntax " ⟪ " arith " ⟫ " : term - --- Our macro rules perform the "obvious" translation: -macro_rules - | `(⟪ $s:str ⟫) => `(Arith.var $s) - | `(⟪ $num:num ⟫) => `(Arith.nat $num) - | `(⟪ $x:arith + $y:arith ⟫) => `(Arith.add ⟪ $x ⟫ ⟪ $y ⟫) - | `(⟪ $x:arith * $y:arith ⟫) => `(Arith.mul ⟪ $x ⟫ ⟪ $y ⟫) - | `(⟪ ( $x ) ⟫) => `( ⟪ $x ⟫ ) - -#check ⟪ "x" * "y" ⟫ --- Arith.mul (Arith.var "x") (Arith.var "y") : Arith - -#check ⟪ "x" + "y" ⟫ --- Arith.add (Arith.var "x") (Arith.var "y") : Arith - -#check ⟪ "x" + 20 ⟫ --- Arith.add (Arith.var "x") (Arith.nat 20) : Arith - -#check ⟪ "x" + "y" * "z" ⟫ -- precedence --- Arith.add (Arith.var "x") (Arith.mul (Arith.var "y") (Arith.var "z")) : Arith - -#check ⟪ "x" * "y" + "z" ⟫ -- precedence --- Arith.add (Arith.mul (Arith.var "x") (Arith.var "y")) (Arith.var "z") : Arith - -#check ⟪ ("x" + "y") * "z" ⟫ -- brackets --- Arith.mul (Arith.add (Arith.var "x") (Arith.var "y")) (Arith.var "z") -``` - -### Writing our own tactic - -Let's create a tactic that adds a new hypothesis to the context with a given -name and postpones the need for its proof to the very end. It's similar to -the `suffices` tactic from Lean 3, except that we want to make sure that the new -goal goes to the bottom of the goal list. - -It's going to be called `suppose` and is used like this: - -`suppose : ` - -So let's see the code: - -```lean -open Lean Meta Elab Tactic Term in -elab "suppose " n:ident " : " t:term : tactic => do - let n : Name := n.getId - let mvarId ← getMainGoal - mvarId.withContext do - let t ← elabType t - let p ← mkFreshExprMVar t MetavarKind.syntheticOpaque n - let (_, mvarIdNew) ← MVarId.intro1P $ ← mvarId.assert n t p - replaceMainGoal [p.mvarId!, mvarIdNew] - evalTactic $ ← `(tactic|rotate_left) - -example : 0 + a = a := by - suppose add_comm : 0 + a = a + 0 - rw [add_comm]; rfl -- closes the initial main goal - rw [Nat.zero_add]; rfl -- proves `add_comm` -``` - -We start by storing the main goal in `mvarId` and using it as a parameter of -`withMVarContext` to make sure that our elaborations will work with types that -depend on other variables in the context. - -This time we're using `mkFreshExprMVar` to create a metavariable expression for -the proof of `t`, which we can introduce to the context using `intro1P` and -`assert`. - -To require the proof of the new hypothesis as a goal, we call `replaceMainGoal` -passing a list with `p.mvarId!` in the head. And then we can use the -`rotate_left` tactic to move the recently added top goal to the bottom. diff --git a/md/main/02_overview.md b/md/main/02_overview.md deleted file mode 100644 index 16fe099..0000000 --- a/md/main/02_overview.md +++ /dev/null @@ -1,187 +0,0 @@ -# Overview - -In this chapter, we will provide an overview of the primary steps involved in the Lean compilation process, including parsing, elaboration, and evaluation. As alluded to in the introduction, metaprogramming in Lean involves plunging into the heart of this process. We will explore the fundamental objects involved, `Expr` and `Syntax`, learn what they signify, and discover how one can be turned into another (and back!). - -In the next chapters, you will learn the particulars. As you read on, you might want to return to this chapter occasionally to remind yourself of how it all fits together. - -## Connection to compilers - -Metaprogramming in Lean is deeply connected to the compilation steps - parsing, syntactic analysis, transformation, and code generation. - -> Lean 4 is a reimplementation of the Lean theorem prover in Lean itself. The new compiler produces C code, and users can now implement efficient proof automation in Lean, compile it into efficient C code, and load it as a plugin. In Lean 4, users can access all internal data structures used to implement Lean by merely importing the Lean package. -> -> Leonardo de Moura, Sebastian Ullrich ([The Lean 4 Theorem Prover and Programming Language](https://pp.ipd.kit.edu/uploads/publikationen/demoura21lean4.pdf)) - -The Lean compilation process can be summed up in the following diagram: - -

- -

- -First, we will start with Lean code as a string. Then we'll see it become a `Syntax` object, and then an `Expr` object. Then finally we can execute it. - -So, the compiler sees a string of Lean code, say `"let a := 2"`, and the following process unfolds: - -1. **apply a relevant syntax rule** (`"let a := 2"` ➤ `Syntax`) - - During the parsing step, Lean tries to match a string of Lean code to one of the declared **syntax rules** in order to turn that string into a `Syntax` object. **Syntax rules** are basically glorified regular expressions - when you write a Lean string that matches a certain **syntax rule**'s regex, that rule will be used to handle subsequent steps. - -2. **apply all macros in a loop** (`Syntax` ➤ `Syntax`) - - During the elaboration step, each **macro** simply turns the existing `Syntax` object into some new `Syntax` object. Then, the new `Syntax` is processed similarly (repeating steps 1 and 2), until there are no more **macros** to apply. - -3. **apply a single elab** (`Syntax` ➤ `Expr`) - - Finally, it's time to infuse your syntax with meaning - Lean finds an **elab** that's matched to the appropriate **syntax rule** by the `name` argument (**syntax rules**, **macros** and **elabs** all have this argument, and they must match). The newfound **elab** returns a particular `Expr` object. - This completes the elaboration step. - -The expression (`Expr`) is then converted into executable code during the evaluation step - we don't have to specify that in any way, the Lean compiler will handle doing so for us. - -## Elaboration and delaboration - -Elaboration is an overloaded term in Lean. For example, you might encounter the following usage of the word "elaboration", wherein the intention is *"taking a partially-specified expression and inferring what is left implicit"*: - - -> When you enter an expression like `λ x y z, f (x + y) z` for Lean to process, you are leaving information implicit. For example, the types of `x`, `y`, and `z` have to be inferred from the context, the notation `+` may be overloaded, and there may be implicit arguments to `f` that need to be filled in as well. -> -> The process of *taking a partially-specified expression and inferring what is left implicit* is known as **elaboration**. Lean's **elaboration** algorithm is powerful, but at the same time, subtle and complex. Working in a system of dependent type theory requires knowing what sorts of information the **elaborator** can reliably infer, as well as knowing how to respond to error messages that are raised when the elaborator fails. To that end, it is helpful to have a general idea of how Lean's **elaborator** works. -> -> When Lean is parsing an expression, it first enters a preprocessing phase. First, Lean inserts "holes" for implicit arguments. If term t has type `Π {x : A}, P x`, then t is replaced by `@t _` everywhere. Then, the holes — either the ones inserted in the previous step or the ones explicitly written by the user — in a term are instantiated by metavariables `?M1`, `?M2`, `?M3`, .... Each overloaded notation is associated with a list of choices, that is, the possible interpretations. Similarly, Lean tries to detect the points where a coercion may need to be inserted in an application `s t`, to make the inferred type of t match the argument type of `s`. These become choice points too. If one possible outcome of the elaboration procedure is that no coercion is needed, then one of the choices on the list is the identity. -> -> ([Theorem Proving in Lean 2](http://leanprover.github.io/tutorial/08_Building_Theories_and_Proofs.html)) - -We, on the other hand, just defined elaboration as the process of turning `Syntax` objects into `Expr` objects. - -These definitions are not mutually exclusive - elaboration is, indeed, the transformation of `Syntax` into `Expr`s - it's just so that for this transformation to happen we need a lot of trickery - we need to infer implicit arguments, instantiate metavariables, perform unification, resolve identifiers, etc. etc. - and these actions can be referred to as "elaboration" on their own; similarly to how "checking if you turned off the lights in your apartment" (metavariable instantiation) can be referred to as "going to school" (elaboration). - -There also exists a process opposite to elaboration in Lean - it's called, appropriately enough, delaboration. During delaboration, an `Expr` is turned into a `Syntax` object; and then the formatter turns it into a `Format` object, which can be displayed in Lean's infoview. Every time you log something to the screen, or see some output upon hovering over `#check`, it's the work of the delaborator. - -Throughout this book you will see references to the elaborator; and in the "Extra: Pretty Printing" chapter you can read about delaborators. - -## 3 essential commands and their syntax sugars - -Now, when you're reading Lean source code, you will see 11(+?) commands specifying the **parsing**/**elaboration**/**evaluation** process: - -

- -

- -In the image above, you see `notation`, `prefix`, `infix`, and `postfix` - all of these are combinations of `syntax` and `@[macro xxx] def ourMacro`, just like `macro`. These commands differ from `macro` in that you can only define syntax of a particular form with them. - -All of these commands are used in Lean and Mathlib source code extensively, so it's well worth memorizing them. Most of them are syntax sugars, however, and you can understand their behaviour by studying the behaviour of the following 3 low-level commands: `syntax` (a **syntax rule**), `@[macro xxx] def ourMacro` (a **macro**), and `@[command_elab xxx] def ourElab` (an **elab**). - -To give a more concrete example, imagine we're implementing a `#help` command, that can also be written as `#h`. Then we can write our **syntax rule**, **macro**, and **elab** as follows: - -

- -

- -This image is not supposed to be read row by row - it's perfectly fine to use `macro_rules` together with `elab`. Suppose, however, that we used the 3 low-level commands to specify our `#help` command (the first row). After we've done this, we can write `#help "#explode"` or `#h "#explode"`, both of which will output a rather parsimonious documentation for the `#explode` command - *"Displays proof in a Fitch table"*. - -If we write `#h "#explode"`, Lean will travel the `syntax (name := shortcut_h)` ➤ `@[macro shortcut_h] def helpMacro` ➤ `syntax (name := default_h)` ➤ `@[command_elab default_h] def helpElab` route. -If we write `#help "#explode"`, Lean will travel the `syntax (name := default_h)` ➤ `@[command_elab default_h] def helpElab` route. - -Note how the matching between **syntax rules**, **macros**, and **elabs** is done via the `name` argument. If we used `macro_rules` or other syntax sugars, Lean would figure out the appropriate `name` arguments on its own. - -If we were defining something other than a command, instead of `: command` we could write `: term`, or `: tactic`, or any other syntax category. -The elab function can also be of different types - the `CommandElab` we used to implement `#help` - but also `TermElab` and `Tactic`: - -- `TermElab` stands for **Syntax → Option Expr → TermElabM Expr**, so the elab function is expected to return the **Expr** object. -- `CommandElab` stands for **Syntax → CommandElabM Unit**, so it shouldn't return anything. -- `Tactic` stands for **Syntax → TacticM Unit**, so it shouldn't return anything either. - -This corresponds to our intuitive understanding of terms, commands and tactics in Lean - terms return a particular value upon execution, commands modify the environment or print something out, and tactics modify the proof state. - -## Order of execution: syntax rule, macro, elab - -We have hinted at the flow of execution of these three essential commands here and there, however let's lay it out explicitly. The order of execution follows the following pseudocodey template: `syntax (macro; syntax)* elab`. - -Consider the following example. - -```lean -import Lean -open Lean Elab Command - -syntax (name := xxx) "red" : command -syntax (name := yyy) "green" : command -syntax (name := zzz) "blue" : command - -@[macro xxx] def redMacro : Macro := λ stx => - match stx with - | _ => `(green) - -@[macro yyy] def greenMacro : Macro := λ stx => - match stx with - | _ => `(blue) - -@[command_elab zzz] def blueElab : CommandElab := λ stx => - Lean.logInfo "finally, blue!" - -red -- finally, blue! -``` - -The process is as follows: - -- match appropriate `syntax` rule (happens to have `name := xxx`) ➤ - apply `@[macro xxx]` ➤ - -- match appropriate `syntax` rule (happens to have `name := yyy`) ➤ - apply `@[macro yyy]` ➤ - -- match appropriate `syntax` rule (happens to have `name := zzz`) ➤ - can't find any macros with name `zzz` to apply, - so apply `@[command_elab zzz]`. 🎉. - -The behaviour of syntax sugars (`elab`, `macro`, etc.) can be understood from these first principles. - -## Manual conversions between `Syntax`/`Expr`/executable-code - -Lean will execute the aforementioned **parsing**/**elaboration**/**evaluation** steps for you automatically if you use `syntax`, `macro` and `elab` commands, however, when you're writing your tactics, you will also frequently need to perform these transitions manually. You can use the following functions for that: - -

- -

- -Note how all functions that let us turn `Syntax` into `Expr` start with "elab", short for "elaboration"; and all functions that let us turn `Expr` (or `Syntax`) into `actual code` start with "eval", short for "evaluation". - -## Assigning meaning: macro VS elaboration? - -In principle, you can do with a `macro` (almost?) anything you can do with the `elab` function. Just write what you would have in the body of your `elab` as a syntax within `macro`. However, the rule of thumb here is to only use `macro`s when the conversion is simple and truly feels elementary to the point of aliasing. As Henrik Böving puts it: "as soon as types or control flow is involved a macro is probably not reasonable anymore" ([Zulip thread](https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/The.20line.20between.20term.20elaboration.20and.20macro/near/280951290)). - -So - use `macro`s for creating syntax sugars, notations, and shortcuts, and prefer `elab`s for writing out code with some programming logic, even if it's potentially implementable in a `macro`. - -## Additional comments - -Finally - some notes that should clarify a few things as you read the coming chapters. - -### Printing Messages - -In the `#assertType` example, we used `logInfo` to make our command print -something. If, instead, we just want to perform a quick debug, we can use -`dbg_trace`. - -They behave a bit differently though, as we can see below: - -```lean -elab "traces" : tactic => do - let array := List.replicate 2 (List.range 3) - Lean.logInfo m!"logInfo: {array}" - dbg_trace f!"dbg_trace: {array}" - -example : True := by -- `example` is underlined in blue, outputting: - -- dbg_trace: [[0, 1, 2], [0, 1, 2]] - traces -- now `traces` is underlined in blue, outputting - -- logInfo: [[0, 1, 2], [0, 1, 2]] - trivial -``` - -### Type correctness - -Since the objects defined in the meta-level are not the ones we're most -interested in proving theorems about, it can sometimes be overly tedious to -prove that they are type correct. For example, we don't care about proving that -a recursive function to traverse an expression is well-founded. Thus, we can -use the `partial` keyword if we're convinced that our function terminates. In -the worst-case scenario, our function gets stuck in a loop, causing the Lean server to crash -in VSCode, but the soundness of the underlying type theory implemented in the kernel -isn't affected. diff --git a/md/main/03_expressions.md b/md/main/03_expressions.md deleted file mode 100644 index a9f2081..0000000 --- a/md/main/03_expressions.md +++ /dev/null @@ -1,325 +0,0 @@ -# Expressions - -Expressions (terms of type `Expr`) are an abstract syntax tree for Lean programs. -This means that each term which can be written in Lean has a corresponding `Expr`. -For example, the application `f e` is represented by the expression `Expr.app ⟦f⟧ ⟦e⟧`, -where `⟦f⟧` is a representation of `f` and `⟦e⟧` a representation of `e`. -Similarly, the term `Nat` is represented by the expression ``Expr.const `Nat []``. -(The backtick and empty list are discussed below.) - -The ultimate purpose of a Lean tactic block -is to generate a term which serves as a proof of the theorem we want to prove. -Thus, the purpose of a tactic is to produce (part of) an `Expr` of the right type. -Much metaprogramming therefore comes down to manipulating expressions: -constructing new ones and taking apart existing ones. - -Once a tactic block is finished, the `Expr` is sent to the kernel, -which checks whether it is well-typed and whether it really has the type claimed by the theorem. -As a result, tactic bugs are not fatal: -if you make a mistake, the kernel will ultimately catch it. -However, many internal Lean functions also assume that expressions are well-typed, -so you may crash Lean before the expression ever reaches the kernel. -To avoid this, Lean provides many functions which help with the manipulation of expressions. -This chapter and the next survey the most important ones. - -Let's get concrete and look at the -[`Expr`](https://github.com/leanprover/lean4/blob/master/src/Lean/Expr.lean) -type: - -```lean -import Lean - -namespace Playground - -inductive Expr where - | bvar : Nat → Expr -- bound variables - | fvar : FVarId → Expr -- free variables - | mvar : MVarId → Expr -- meta variables - | sort : Level → Expr -- Sort - | const : Name → List Level → Expr -- constants - | app : Expr → Expr → Expr -- application - | lam : Name → Expr → Expr → BinderInfo → Expr -- lambda abstraction - | forallE : Name → Expr → Expr → BinderInfo → Expr -- (dependent) arrow - | letE : Name → Expr → Expr → Expr → Bool → Expr -- let expressions - -- less essential constructors: - | lit : Literal → Expr -- literals - | mdata : MData → Expr → Expr -- metadata - | proj : Name → Nat → Expr → Expr -- projection - -end Playground -``` - -What is each of these constructors doing? - -- `bvar` is a __bound variable__. - For example, the `x` in `fun x => x + 2` or `∑ x, x²`. - This is any occurrence of a variable in an expression where there is a binder above it. - Why is the argument a `Nat`? This is called a de Bruijn index and will be explained later. - You can figure out the type of a bound variable by looking at its binder, - since the binder always has the type information for it. -- `fvar` is a __free variable__. - These are variables which are not bound by a binder. - An example is `x` in `x + 2`. - Note that you can't just look at a free variable `x` and tell what its type is, - there needs to be a context which contains a declaration for `x` and its type. - A free variable has an ID that tells you where to look for it in a `LocalContext`. - In Lean 3, free variables were called "local constants" or "locals". -- `mvar` is a __metavariable__. - There will be much more on these later, - but you can think of it as a placeholder or a 'hole' in an expression - that needs to be filled at a later point. -- `sort` is used for `Type u`, `Prop` etc. -- `const` is a constant that has been defined earlier in the Lean document. -- `app` is a function application. - Multiple arguments are done using _partial application_: `f x y ↝ app (app f x) y`. -- `lam n t b` is a lambda expression (`fun ($n : $t) => $b`). - The `b` argument is called the __body__. - Note that you have to give the type of the variable you are binding. -- `forallE n t b` is a dependent arrow expression (`($n : $t) → $b`). - This is also sometimes called a Π-type or Π-expression - and is often written `∀ $n : $t, $b`. - Note that the non-dependent arrow `α → β` is a special case of `(a : α) → β` - where `β` doesn't depend on `a`. - The `E` on the end of `forallE` is to distinguish it from the `forall` keyword. -- `letE n t v b` is a __let binder__ (`let ($n : $t) := $v in $b`). -- `lit` is a __literal__, this is a number or string literal like `4` or `"hello world"`. - Literals help with performance: - we don't want to represent the expression `(10000 : Nat)` - as `Nat.succ $ ... $ Nat.succ Nat.zero`. -- `mdata` is just a way of storing extra information on expressions that might be useful, - without changing the nature of the expression. -- `proj` is for projection. - Suppose you have a structure such as `p : α × β`, - rather than storing the projection `π₁ p` as `app π₁ p`, it is expressed as `proj Prod 0 p`. - This is for efficiency reasons ([todo] find link to docstring explaining this). - -You've probably noticed -that you can write many Lean programs which do not have an obvious corresponding `Expr`. -For example, what about `match` statements, `do` blocks or `by` blocks? -These constructs, and many more, must indeed first be translated into expressions. -The part of Lean which performs this (substantial) task is called the elaborator -and is discussed in its own chapter. -The benefit of this setup is that once the translation to `Expr` is done, -we have a relatively simple structure to work with. -(The downside is that going back from `Expr` to a high-level Lean program can be challenging.) - -The elaborator also fills in any implicit or typeclass instance arguments -which you may have omitted from your Lean program. -Thus, at the `Expr` level, constants are always applied to all their arguments, implicit or not. -This is both a blessing -(because you get a lot of information which is not obvious from the source code) -and a curse -(because when you build an `Expr`, you must supply any implicit or instance arguments yourself). - -## De Bruijn Indexes - -Consider the following lambda expression `(λ f x => f x x) (λ x y => x + y) 5`, -we have to be very careful when we reduce this, -because we get a clash in the variable `x`. - -To avoid variable name-clash carnage, -`Expr`s use a nifty trick called __de Bruijn indexes__. -In de Bruijn indexing, -each variable bound by a `lam` or a `forallE` is converted into a number `#n`. -The number says how many binders up the `Expr` tree we should look -to find the binder which binds this variable. -So our above example would become -(putting wildcards `_` in the type arguments for now for brevity): -``app (app (lam `f _ (lam `x _ (app (app #1 #0) #0))) (lam `x _ (lam `y _ (app (app plus #1) #0)))) five`` -Now we don't need to rename variables when we perform β-reduction. -We also really easily check if two `Expr`s containing bound expressions are equal. -This is why the signature of the `bvar` case is `Nat → Expr` and not `Name → Expr`. - -If a de Bruijn index is too large for the number of binders preceding it, -we say it is a __loose `bvar`__; -otherwise we say it is __bound__. -For example, in the expression ``lam `x _ (app #0 #1)`` -the `bvar` `#0` is bound by the preceding binder and `#1` is loose. -The fact that Lean calls all de Bruijn indexes `bvar`s ("bound variables") -points to an important invariant: -outside of some very low-level functions, -Lean expects that expressions do not contain any loose `bvar`s. -Instead, whenever we would be tempted to introduce a loose `bvar`, -we immediately convert it into an `fvar` ("free variable"). -Precisely how that works is discussed in the next chapter. - -If there are no loose `bvar`s in an expression, we say that the expression is __closed__. -The process of replacing all instances of a loose `bvar` with an `Expr` -is called __instantiation__. -Going the other way is called __abstraction__. - -If you are familiar with the standard terminology around variables, -Lean's terminology may be confusing, -so here's a map: -Lean's "bvars" are usually called just "variables"; -Lean's "loose" is usually called "free"; -and Lean's "fvars" might be called "local hypotheses". - -## Universe Levels - -Some expressions involve universe levels, represented by the `Lean.Level` type. -A universe level is a natural number, -a universe parameter (introduced with a `universe` declaration), -a universe metavariable or the maximum of two universes. -They are relevant for two kinds of expressions. - -First, sorts are represented by `Expr.sort u`, where `u` is a `Level`. -`Prop` is `sort Level.zero`; `Type` is `sort (Level.succ Level.zero)`. - -Second, universe-polymorphic constants have universe arguments. -A universe-polymorphic constant is one whose type contains universe parameters. -For example, the `List.map` function is universe-polymorphic, -as the `pp.universes` pretty-printing option shows: - -```lean -set_option pp.universes true in -#check @List.map -``` - -The `.{u_1,u_2}` suffix after `List.map` -means that `List.map` has two universe arguments, `u_1` and `u_2`. -The `.{u_1}` suffix after `List` (which is itself a universe-polymorphic constant) -means that `List` is applied to the universe argument `u_1`, and similar for `.{u_2}`. - -In fact, whenever you use a universe-polymorphic constant, -you must apply it to the correct universe arguments. -This application is represented by the `List Level` argument of `Expr.const`. -When we write regular Lean code, Lean infers the universes automatically, -so we do not need think about them much. -But when we construct `Expr`s, -we must be careful to apply each universe-polymorphic constant to the right universe arguments. - -## Constructing Expressions - -The simplest expressions we can construct are constants. -We use the `const` constructor and give it a name and a list of universe levels. -Most of our examples only involve non-universe-polymorphic constants, -in which case the list is empty. - -We also show a second form where we write the name with double backticks. -This checks that the name in fact refers to a defined constant, -which is useful to avoid typos. - -```lean -open Lean - -def z' := Expr.const `Nat.zero [] -#eval z' -- Lean.Expr.const `Nat.zero [] - -def z := Expr.const ``Nat.zero [] -#eval z -- Lean.Expr.const `Nat.zero [] -``` - -The double-backtick variant also resolves the given name, making it fully-qualified. -To illustrate this mechanism, here are two further examples. -The first expression, `z₁`, is unsafe: -if we use it in a context where the `Nat` namespace is not open, -Lean will complain that there is no constant called `zero` in the environment. -In contrast, the second expression, `z₂`, -contains the fully-qualified name `Nat.zero` and does not have this problem. - -```lean -open Nat - -def z₁ := Expr.const `zero [] -#eval z₁ -- Lean.Expr.const `zero [] - -def z₂ := Expr.const ``zero [] -#eval z₂ -- Lean.Expr.const `Nat.zero [] -``` - -The next class of expressions we consider are function applications. -These can be built using the `app` constructor, -with the first argument being an expression for the function -and the second being an expression for the argument. - -Here are two examples. -The first is simply a constant applied to another. -The second is a recursive definition giving an expression as a function of a natural number. - -```lean -def one := Expr.app (.const ``Nat.succ []) z -#eval one --- Lean.Expr.app (Lean.Expr.const `Nat.succ []) (Lean.Expr.const `Nat.zero []) - -def natExpr: Nat → Expr -| 0 => z -| n + 1 => .app (.const ``Nat.succ []) (natExpr n) -``` - -Next we use the variant `mkAppN` which allows application with multiple arguments. - -```lean -def sumExpr : Nat → Nat → Expr -| n, m => mkAppN (.const ``Nat.add []) #[natExpr n, natExpr m] -``` - -As you may have noticed, we didn't show `#eval` outputs for the two last functions. -That's because the resulting expressions can grow so large -that it's hard to make sense of them. - -We next use the constructor `lam` -to construct a simple function which takes any natural number `x` and returns `Nat.zero`. -The argument `BinderInfo.default` says that `x` is an explicit argument -(rather than an implicit or typeclass argument). - -```lean -def constZero : Expr := - .lam `x (.const ``Nat []) (.const ``Nat.zero []) BinderInfo.default - -#eval constZero --- Lean.Expr.lam `x (Lean.Expr.const `Nat []) (Lean.Expr.const `Nat.zero []) --- (Lean.BinderInfo.default) -``` - -As a more elaborate example which also involves universe levels, -here is the `Expr` that represents `List.map (λ x => Nat.add x 1) []` -(broken up into several definitions to make it somewhat readable): - -```lean -def nat : Expr := .const ``Nat [] - -def addOne : Expr := - .lam `x nat - (mkAppN (.const ``Nat.add []) #[.bvar 0, mkNatLit 1]) - BinderInfo.default - -def mapAddOneNil : Expr := - mkAppN (.const ``List.map [levelOne, levelOne]) - #[nat, nat, addOne, .app (.const ``List.nil [levelOne]) nat] -``` - -With a little trick (more about which in the Elaboration chapter), -we can turn our `Expr` into a Lean term, which allows us to inspect it more easily. - -```lean -elab "mapAddOneNil" : term => return mapAddOneNil - -#check mapAddOneNil --- List.map (fun x => Nat.add x 1) [] : List Nat - -set_option pp.universes true in -set_option pp.explicit true in -#check mapAddOneNil --- @List.map.{1, 1} Nat Nat (fun x => Nat.add x 1) (@List.nil.{1} Nat) : List.{1} Nat - -#reduce mapAddOneNil --- [] -``` - -In the next chapter we explore the `MetaM` monad, -which, among many other things, -allows us to more conveniently construct and destruct larger expressions. - -## Exercises - -1. Create expression `1 + 2` with `Expr.app`. -2. Create expression `1 + 2` with `Lean.mkAppN`. -3. Create expression `fun x => 1 + x`. -4. [**De Bruijn Indexes**] Create expression `fun a, fun b, fun c, (b * a) + c`. -5. Create expression `fun x y => x + y`. -6. Create expression `fun x, String.append "hello, " x`. -7. Create expression `∀ x : Prop, x ∧ x`. -8. Create expression `Nat → String`. -9. Create expression `fun (p : Prop) => (λ hP : p => hP)`. -10. [**Universe levels**] Create expression `Type 6`. diff --git a/md/main/04_metam.md b/md/main/04_metam.md deleted file mode 100644 index 5ba10bd..0000000 --- a/md/main/04_metam.md +++ /dev/null @@ -1,1328 +0,0 @@ -# `MetaM` - -The Lean 4 metaprogramming API is organised around a small zoo of monads. The -four main ones are: - -- `CoreM` gives access to the *environment*, i.e. the set of things that - have been declared or imported at the current point in the program. -- `MetaM` gives access to the *metavariable context*, i.e. the set of - metavariables that are currently declared and the values assigned to them (if - any). -- `TermElabM` gives access to various information used during elaboration. -- `TacticM` gives access to the list of current goals. - -These monads extend each other, so a `MetaM` operation also has access to the -environment and a `TermElabM` computation can use metavariables. There are also -other monads which do not neatly fit into this hierarchy, e.g. `CommandElabM` -extends `MetaM` but neither extends nor is extended by `TermElabM`. - -This chapter demonstrates a number of useful operations in the `MetaM` monad. -`MetaM` is of particular importance because it allows us to give meaning to -every expression: the environment (from `CoreM`) gives meaning to constants like -`Nat.zero` or `List.map` and the metavariable context gives meaning to both -metavariables and local hypotheses. - -```lean -import Lean - -open Lean Lean.Expr Lean.Meta -``` - -## Metavariables - -### Overview - -The 'Meta' in `MetaM` refers to metavariables, so we should talk about these -first. Lean users do not usually interact much with metavariables -- at least -not consciously -- but they are used all over the place in metaprograms. There -are two ways to view them: as holes in an expression or as goals. - -Take the goal perspective first. When we prove things in Lean, we always operate -on goals, such as - -```lean -n m : Nat -⊢ n + m = m + n -``` - -These goals are internally represented by metavariables. Accordingly, each -metavariable has a *local context* containing hypotheses (here `[n : Nat, m : -Nat]`) and a *target type* (here `n + m = m + n`). Metavariables also have a -unique name, say `m`, and we usually render them as `?m`. - -To close a goal, we must give an expression `e` of the target type. The -expression may contain fvars from the metavariable's local context, but no -others. Internally, closing a goal in this way corresponds to *assigning* the -metavariable; we write `?m := e` for this assignment. - -The second, complementary view of metavariables is that they represent holes -in an expression. For instance, an application of `Eq.trans` may generate two -goals which look like this: - -```lean -n m : Nat -⊢ n = ?x - -n m : Nat -⊢ ?x = m -``` - -Here `?x` is another metavariable -- a hole in the target types of both goals, -to be filled in later during the proof. The type of `?x` is `Nat` and its local -context is `[n : Nat, m : Nat]`. Now, if we solve the first goal by reflexivity, -then `?x` must be `n`, so we assign `?x := n`. Crucially, this also affects the -second goal: it is "updated" (not really, as we will see) to have target `n = -m`. The metavariable `?x` represents the same expression everywhere it occurs. - - -### Tactic Communication via Metavariables - -Tactics use metavariables to communicate the current goals. To see how, consider -this simple (and slightly artificial) proof: - -```lean -example {α} (a : α) (f : α → α) (h : ∀ a, f a = a) : f (f a) = a := by - apply Eq.trans - apply h - apply h -``` - -After we enter tactic mode, our ultimate goal is to generate an expression of -type `f (f a) = a` which may involve the hypotheses `α`, `a`, `f` and `h`. So -Lean generates a metavariable `?m1` with target `f (f a) = a` and a local -context containing these hypotheses. This metavariable is passed to the first -`apply` tactic as the current goal. - -The `apply` tactic then tries to apply `Eq.trans` and succeeds, generating three -new metavariables: - -```lean -... -⊢ f (f a) = ?b - -... -⊢ ?b = a - -... -⊢ α -``` - -Call these metavariables `?m2`, `?m3` and `?b`. The last one, `?b`, stands for -the intermediate element of the transitivity proof and occurs in `?m2` and -`?m3`. The local contexts of all metavariables in this proof are the same, so -we omit them. - -Having created these metavariables, `apply` assigns - -```lean -?m1 := @Eq.trans α (f (f a)) ?b a ?m2 ?m3 -``` - -and reports that `?m2`, `?m3` and `?b` are now the current goals. - -At this point the second `apply` tactic takes over. It receives `?m2` as the -current goal and applies `h` to it. This succeeds and the tactic assigns `?m2 := -h (f a)`. This assignment implies that `?b` must be `f a`, so the tactic also -assigns `?b := f a`. Assigned metavariables are not considered open goals, so -the only goal that remains is `?m3`. - -Now the third `apply` comes in. Since `?b` has been assigned, the target of -`?m3` is now `f (f a) = a`. Again, the application of `h` succeeds and the -tactic assigns `?m3 := h a`. - -At this point, all metavariables are assigned as follows: - -```lean -?m1 := @Eq.trans α (f (f a)) ?b a ?m2 ?m3 -?m2 := h (f a) -?m3 := h a -?b := f a -``` - -Exiting the `by` block, Lean constructs the final proof term by taking the -assignment of `?m1` and replacing each metavariable with its assignment. This -yields - -```lean -@Eq.trans α (f (f a)) (f a) a (h (f a)) (h a) -``` - -The example also shows how the two views of metavariables -- as holes in an -expression or as goals -- are related: the goals we get are holes in the final -proof term. - - -### Basic Operations - -Let us make these concepts concrete. When we operate in the `MetaM` monad, we -have read-write access to a `MetavarContext` structure containing information -about the currently declared metavariables. Each metavariable is identified by -an `MVarId` (a unique `Name`). To create a new metavariable, we use -`Lean.Meta.mkFreshExprMVar` with type - -```lean -mkFreshExprMVar (type? : Option Expr) (kind := MetavarKind.natural) - (userName := Name.anonymous) : MetaM Expr -``` - -Its arguments are: - -- `type?`: the target type of the new metavariable. If `none`, the target type - is `Sort ?u`, where `?u` is a universe level metavariable. (This is a special - class of metavariables for universe levels, distinct from the expression - metavariables which we have been calling simply "metavariables".) -- `kind`: the metavariable kind. See the [Metavariable Kinds - section](#metavariable-kinds) (but the default is usually correct). -- `userName`: the new metavariable's user-facing name. This is what gets printed - when the metavariable appears in a goal. Unlike the `MVarId`, this name does - not need to be unique. - -The returned `Expr` is always a metavariable. We can use `Lean.Expr.mvarId!` to -extract the `MVarId`, which is guaranteed to be unique. (Arguably -`mkFreshExprMVar` should just return the `MVarId`.) - -The local context of the new metavariable is inherited from the current local -context, more about which in the next section. If you want to give a different -local context, use `Lean.Meta.mkFreshExprMVarAt`. - -Metavariables are initially unassigned. To assign them, use -`Lean.MVarId.assign` with type - -```lean -assign (mvarId : MVarId) (val : Expr) : MetaM Unit -``` - -This updates the `MetavarContext` with the assignment `?mvarId := val`. You must -make sure that `mvarId` is not assigned yet (or that the old assignment is -definitionally equal to the new assignment). You must also make sure that the -assigned value, `val`, has the right type. This means (a) that `val` must have -the target type of `mvarId` and (b) that `val` must only contain fvars from the -local context of `mvarId`. - -If you `#check Lean.MVarId.assign`, you will see that its real type is more -general than the one we showed above: it works in any monad that has access to a -`MetavarContext`. But `MetaM` is by far the most important such monad, so in -this chapter, we specialise the types of `assign` and similar functions. - -To get information about a declared metavariable, use `Lean.MVarId.getDecl`. -Given an `MVarId`, this returns a `MetavarDecl` structure. (If no metavariable -with the given `MVarId` is declared, the function throws an exception.) The -`MetavarDecl` contains information about the metavariable, e.g. its type, local -context and user-facing name. This function has some convenient variants, such -as `Lean.MVarId.getType`. - -To get the current assignment of a metavariable (if any), use -`Lean.getExprMVarAssignment?`. To check whether a metavariable is assigned, use -`Lean.MVarId.isAssigned`. However, these functions are relatively rarely -used in tactic code because we usually prefer a more powerful operation: -`Lean.Meta.instantiateMVars` with type - -```lean -instantiateMVars : Expr → MetaM Expr -``` - -Given an expression `e`, `instantiateMVars` replaces any assigned metavariable -`?m` in `e` with its assigned value. Unassigned metavariables remain as they -are. - -This operation should be used liberally. When we assign a metavariable, existing -expressions containing this metavariable are not immediately updated. This is a -problem when, for example, we match on an expression to check whether it is an -equation. Without `instantiateMVars`, we might miss the fact that the expression -`?m`, where `?m` happens to be assigned to `0 = n`, represents an equation. In -other words, `instantiateMVars` brings our expressions up to date with the -current metavariable state. - -Instantiating metavariables requires a full traversal of the input expression, -so it can be somewhat expensive. But if the input expression does not contain -any metavariables, `instantiateMVars` is essentially free. Since this is the -common case, liberal use of `instantiateMVars` is fine in most situations. - -Before we go on, here is a synthetic example demonstrating how the basic -metavariable operations are used. More natural examples appear in the following -sections. - -```lean -#eval show MetaM Unit from do - -- Create two fresh metavariables of type `Nat`. - let mvar1 ← mkFreshExprMVar (Expr.const ``Nat []) (userName := `mvar1) - let mvar2 ← mkFreshExprMVar (Expr.const ``Nat []) (userName := `mvar2) - -- Create a fresh metavariable of type `Nat → Nat`. The `mkArrow` function - -- creates a function type. - let mvar3 ← mkFreshExprMVar (← mkArrow (.const ``Nat []) (.const ``Nat [])) - (userName := `mvar3) - - -- Define a helper function that prints each metavariable. - let printMVars : MetaM Unit := do - IO.println s!" meta1: {← instantiateMVars mvar1}" - IO.println s!" meta2: {← instantiateMVars mvar2}" - IO.println s!" meta3: {← instantiateMVars mvar3}" - - IO.println "Initially, all metavariables are unassigned:" - printMVars - - -- Assign `mvar1 : Nat := ?mvar3 ?mvar2`. - mvar1.mvarId!.assign (.app mvar3 mvar2) - IO.println "After assigning mvar1:" - printMVars - - -- Assign `mvar2 : Nat := 0`. - mvar2.mvarId!.assign (.const ``Nat.zero []) - IO.println "After assigning mvar2:" - printMVars - - -- Assign `mvar3 : Nat → Nat := Nat.succ`. - mvar3.mvarId!.assign (.const ``Nat.succ []) - IO.println "After assigning mvar3:" - printMVars --- Initially, all metavariables are unassigned: --- meta1: ?_uniq.1 --- meta2: ?_uniq.2 --- meta3: ?_uniq.3 --- After assigning mvar1: --- meta1: ?_uniq.3 ?_uniq.2 --- meta2: ?_uniq.2 --- meta3: ?_uniq.3 --- After assigning mvar2: --- meta1: ?_uniq.3 Nat.zero --- meta2: Nat.zero --- meta3: ?_uniq.3 --- After assigning mvar3: --- meta1: Nat.succ Nat.zero --- meta2: Nat.zero --- meta3: Nat.succ -``` - -### Local Contexts - -Consider the expression `e` which refers to the free variable with unique name -`h`: - -```lean -e := .fvar (FVarId.mk `h) -``` - -What is the type of this expression? The answer depends on the local context in -which `e` is interpreted. One local context may declare that `h` is a local -hypothesis of type `Nat`; another local context may declare that `h` is a local -definition with value `List.map`. - -Thus, expressions are only meaningful if they are interpreted in the local -context for which they were intended. And as we saw, each metavariable has its -own local context. So in principle, functions which manipulate expressions -should have an additional `MVarId` argument specifying the goal in which the -expression should be interpreted. - -That would be cumbersome, so Lean goes a slightly different route. In `MetaM`, -we always have access to an ambient `LocalContext`, obtained with `Lean.getLCtx` -of type - -```lean -getLCtx : MetaM LocalContext -``` - -All operations involving fvars use this ambient local context. - -The downside of this setup is that we always need to update the ambient local -context to match the goal we are currently working on. To do this, we use -`Lean.MVarId.withContext` of type - -```lean -withContext (mvarId : MVarId) (c : MetaM α) : MetaM α -``` - -This function takes a metavariable `mvarId` and a `MetaM` computation `c` and -executes `c` with the ambient context set to the local context of `mvarId`. A -typical use case looks like this: - -```lean -def someTactic (mvarId : MVarId) ... : ... := - mvarId.withContext do - ... -``` - -The tactic receives the current goal as the metavariable `mvarId` and -immediately sets the current local context. Any operations within the `do` block -then use the local context of `mvarId`. - -Once we have the local context properly set, we can manipulate fvars. Like -metavariables, fvars are identified by an `FVarId` (a unique `Name`). Basic -operations include: - -- `Lean.FVarId.getDecl : FVarId → MetaM LocalDecl` retrieves the declaration - of a local hypothesis. As with metavariables, a `LocalDecl` contains all - information pertaining to the local hypothesis, e.g. its type and its - user-facing name. -- `Lean.Meta.getLocalDeclFromUserName : Name → MetaM LocalDecl` retrieves the - declaration of the local hypothesis with the given user-facing name. If there - are multiple such hypotheses, the bottommost one is returned. If there is - none, an exception is thrown. - -We can also iterate over all hypotheses in the local context, using the `ForIn` -instance of `LocalContext`. A typical pattern is this: - -```lean -for ldecl in ← getLCtx do - if ldecl.isImplementationDetail then - continue - -- do something with the ldecl -``` - -The loop iterates over every `LocalDecl` in the context. The -`isImplementationDetail` check skips local hypotheses which are 'implementation -details', meaning they are introduced by Lean or by tactics for bookkeeping -purposes. They are not shown to users and tactics are expected to ignore them. - -At this point, we can build the `MetaM` part of an `assumption` tactic: - -```lean -def myAssumption (mvarId : MVarId) : MetaM Bool := do - -- Check that `mvarId` is not already assigned. - mvarId.checkNotAssigned `myAssumption - -- Use the local context of `mvarId`. - mvarId.withContext do - -- The target is the type of `mvarId`. - let target ← mvarId.getType - -- For each hypothesis in the local context: - for ldecl in ← getLCtx do - -- If the hypothesis is an implementation detail, skip it. - if ldecl.isImplementationDetail then - continue - -- If the type of the hypothesis is definitionally equal to the target - -- type: - if ← isDefEq ldecl.type target then - -- Use the local hypothesis to prove the goal. - mvarId.assign ldecl.toExpr - -- Stop and return true. - return true - -- If we have not found any suitable local hypothesis, return false. - return false -``` - -The `myAssumption` tactic contains three functions we have not seen before: - -- `Lean.MVarId.checkNotAssigned` checks that a metavariable is not already - assigned. The 'myAssumption' argument is the name of the current tactic. It is - used to generate a nicer error message. -- `Lean.Meta.isDefEq` checks whether two definitions are definitionally equal. - See the [Definitional Equality section](#definitional-equality). -- `Lean.LocalDecl.toExpr` is a helper function which constructs the `fvar` - expression corresponding to a local hypothesis. - - -### Delayed Assignments - -The above discussion of metavariable assignment contains a lie by omission: -there are actually two ways to assign a metavariable. We have seen the regular -way; the other way is called a *delayed assignment*. - -We do not discuss delayed assignments in any detail here since they are rarely -useful for tactic writing. If you want to learn more about them, see the -comments in `MetavarContext.lean` in the Lean standard library. But they create -two complications which you should be aware of. - -First, delayed assignments make `Lean.MVarId.isAssigned` and -`getExprMVarAssignment?` medium-calibre footguns. These functions only check for -regular assignments, so you may need to use `Lean.MVarId.isDelayedAssigned` -and `Lean.Meta.getDelayedMVarAssignment?` as well. - -Second, delayed assignments break an intuitive invariant. You may have assumed -that any metavariable which remains in the output of `instantiateMVars` is -unassigned, since the assigned metavariables have been substituted. But delayed -metavariables can only be substituted once their assigned value contains no -unassigned metavariables. So delayed-assigned metavariables can appear in an -expression even after `instantiateMVars`. - - -### Metavariable Depth - -Metavariable depth is also a niche feature, but one that is occasionally useful. -Any metavariable has a *depth* (a natural number), and a `MetavarContext` has a -corresponding depth as well. Lean only assigns a metavariable if its depth is -equal to the depth of the current `MetavarContext`. Newly created metavariables -inherit the `MetavarContext`'s depth, so by default every metavariable is -assignable. - -This setup can be used when a tactic needs some temporary metavariables and also -needs to make sure that other, non-temporary metavariables will not be assigned. -To ensure this, the tactic proceeds as follows: - -1. Save the current `MetavarContext`. -2. Increase the depth of the `MetavarContext`. -3. Perform whatever computation is necessary, possibly creating and assigning - metavariables. Newly created metavariables are at the current depth of the - `MetavarContext` and so can be assigned. Old metavariables are at a lower - depth, so cannot be assigned. -4. Restore the saved `MetavarContext`, thereby erasing all the temporary - metavariables and resetting the `MetavarContext` depth. - -This pattern is encapsulated in `Lean.Meta.withNewMCtxDepth`. - - -## Computation - -Computation is a core concept of dependent type theory. The terms `2`, `Nat.succ -1` and `1 + 1` are all "the same" in the sense that they compute the same value. -We call them *definitionally equal*. The problem with this, from a -metaprogramming perspective, is that definitionally equal terms may be -represented by entirely different expressions, but our users would usually -expect that a tactic which works for `2` also works for `1 + 1`. So when we -write our tactics, we must do additional work to ensure that definitionally -equal terms are treated similarly. - -### Full Normalisation - -The simplest thing we can do with computation is to bring a term into normal -form. With some exceptions for numeric types, the normal form of a term `t` of -type `T` is a sequence of applications of `T`'s constructors. E.g. the normal -form of a list is a sequence of applications of `List.cons` and `List.nil`. - -The function that normalises a term (i.e. brings it into normal form) is -`Lean.Meta.reduce` with type signature - -```lean -reduce (e : Expr) (explicitOnly skipTypes skipProofs := true) : MetaM Expr -``` - -We can use it like this: - -```lean -def someNumber : Nat := (· + 2) $ 3 - -#eval Expr.const ``someNumber [] --- Lean.Expr.const `someNumber [] - -#eval reduce (Expr.const ``someNumber []) --- Lean.Expr.lit (Lean.Literal.natVal 5) -``` - -Incidentally, this shows that the normal form of a term of type `Nat` is not -always an application of the constructors of `Nat`; it can also be a literal. -Also note that `#eval` can be used not only to evaluate a term, but also to -execute a `MetaM` program. - -The optional arguments of `reduce` allow us to skip certain parts of an -expression. E.g. `reduce e (explicitOnly := true)` does not normalise any -implicit arguments in the expression `e`. This yields better performance: since -normal forms can be very big, it may be a good idea to skip parts of an -expression that the user is not going to see anyway. - -The `#reduce` command is essentially an application of `reduce`: - -```lean -#reduce someNumber --- 5 -``` - -### Transparency - -An ugly but important detail of Lean 4 metaprogramming is that any given -expression does not have a single normal form. Rather, it has a normal form up -to a given *transparency*. - -A transparency is a value of `Lean.Meta.TransparencyMode`, an enumeration with -four values: `reducible`, `instances`, `default` and `all`. Any `MetaM` -computation has access to an ambient `TransparencyMode` which can be obtained -with `Lean.Meta.getTransparency`. - -The current transparency determines which constants get unfolded during -normalisation, e.g. by `reduce`. (To unfold a constant means to replace it with -its definition.) The four settings unfold progressively more constants: - -- `reducible`: unfold only constants tagged with the `@[reducible]` attribute. - Note that `abbrev` is a shorthand for `@[reducible] def`. -- `instances`: unfold reducible constants and constants tagged with the - `@[instance]` attribute. Again, the `instance` command is a shorthand for - `@[instance] def`. -- `default`: unfold all constants except those tagged as `@[irreducible]`. -- `all`: unfold all constants, even those tagged as `@[irreducible]`. - -The ambient transparency is usually `default`. To execute an operation with a -specific transparency, use `Lean.Meta.withTransparency`. There are also -shorthands for specific transparencies, e.g. `Lean.Meta.withReducible`. - -Putting everything together for an example (where we use `Lean.Meta.ppExpr` to -pretty-print an expression): - -```lean -def traceConstWithTransparency (md : TransparencyMode) (c : Name) : - MetaM Format := do - ppExpr (← withTransparency md $ reduce (.const c [])) - -@[irreducible] def irreducibleDef : Nat := 1 -def defaultDef : Nat := irreducibleDef + 1 -abbrev reducibleDef : Nat := defaultDef + 1 -``` - -We start with `reducible` transparency, which only unfolds `reducibleDef`: - -```lean -#eval traceConstWithTransparency .reducible ``reducibleDef --- defaultDef + 1 -``` - -If we repeat the above command but let Lean print implicit arguments as well, -we can see that the `+` notation amounts to an application of the `hAdd` -function, which is a member of the `HAdd` typeclass: - -```lean -set_option pp.explicit true -#eval traceConstWithTransparency .reducible ``reducibleDef --- @HAdd.hAdd Nat Nat Nat (@instHAdd Nat instAddNat) defaultDef 1 -``` - -When we reduce with `instances` transparency, this applications is unfolded and -replaced by `Nat.add`: - -```lean -#eval traceConstWithTransparency .instances ``reducibleDef --- Nat.add defaultDef 1 -``` - -With `default` transparency, `Nat.add` is unfolded as well: - -```lean -#eval traceConstWithTransparency .default ``reducibleDef --- Nat.succ (Nat.succ irreducibleDef) -``` - -And with `TransparencyMode.all`, we're finally able to unfold `irreducibleDef`: - -```lean -#eval traceConstWithTransparency .all ``reducibleDef --- 3 -``` - -The `#eval` commands illustrate that the same term, `reducibleDef`, can have a -different normal form for each transparency. - -Why all this ceremony? Essentially for performance: if we allowed normalisation -to always unfold every constant, operations such as type class search would -become prohibitively expensive. The tradeoff is that we must choose the -appropriate transparency for each operation that involves normalisation. - - -### Weak Head Normalisation - -Transparency addresses some of the performance issues with normalisation. But -even more important is to recognise that for many purposes, we don't need to -fully normalise terms at all. Suppose we are building a tactic that -automatically splits hypotheses of the type `P ∧ Q`. We might want this tactic -to recognise a hypothesis `h : X` if `X` reduces to `P ∧ Q`. But if `P` -additionally reduces to `Y ∨ Z`, the specific `Y` and `Z` do not concern us. -Reducing `P` would be unnecessary work. - -This situation is so common that the fully normalising `reduce` is in fact -rarely used. Instead, the normalisation workhorse of Lean is `whnf`, which -reduces an expression to *weak head normal form* (WHNF). - -Roughly speaking, an expression `e` is in weak-head normal form when it has the -form - -```text -e = f x₁ ... xₙ (n ≥ 0) -``` - -and `f` cannot be reduced (at the current transparency). To conveniently check -the WHNF of an expression, we define a function `whnf'`, using some functions -that will be discussed in the Elaboration chapter. - -```lean -open Lean.Elab.Term in -def whnf' (e : TermElabM Syntax) : TermElabM Format := do - let e ← elabTermAndSynthesize (← e) none - ppExpr (← whnf e) -``` - -Now, here are some examples of expressions in WHNF. - -Constructor applications are in WHNF (with some exceptions for numeric types): - -```lean -#eval whnf' `(List.cons 1 []) --- [1] -``` - -The *arguments* of an application in WHNF may or may not be in WHNF themselves: - -```lean -#eval whnf' `(List.cons (1 + 1) []) --- [1 + 1] -``` - -Applications of constants are in WHNF if the current transparency does not -allow us to unfold the constants: - -```lean -#eval withTransparency .reducible $ whnf' `(List.append [1] [2]) --- List.append [1] [2] -``` - -Lambdas are in WHNF: - -```lean -#eval whnf' `(λ x : Nat => x) --- fun x => x -``` - -Foralls are in WHNF: - -```lean -#eval whnf' `(∀ x, x > 0) --- ∀ (x : Nat), x > 0 -``` - -Sorts are in WHNF: - -```lean -#eval whnf' `(Type 3) --- Type 3 -``` - -Literals are in WHNF: - -```lean -#eval whnf' `((15 : Nat)) --- 15 -``` - -Here are some more expressions in WHNF which are a bit tricky to test: - -```lean -?x 0 1 -- Assuming the metavariable `?x` is unassigned, it is in WHNF. -h 0 1 -- Assuming `h` is a local hypothesis, it is in WHNF. -``` - -On the flipside, here are some expressions that are not in WHNF. - -Applications of constants are not in WHNF if the current transparency allows us -to unfold the constants: - -```lean -#eval whnf' `(List.append [1]) --- fun x => 1 :: List.append [] x -``` - -Applications of lambdas are not in WHNF: - -```lean -#eval whnf' `((λ x y : Nat => x + y) 1) --- `fun y => 1 + y` -``` - -`let` bindings are not in WHNF: - -```lean -#eval whnf' `(let x : Nat := 1; x) --- 1 -``` - -And again some tricky examples: - -```lean -?x 0 1 -- Assuming `?x` is assigned (e.g. to `Nat.add`), its application is not - in WHNF. -h 0 1 -- Assuming `h` is a local definition (e.g. with value `Nat.add`), its - application is not in WHNF. -``` - -Returning to the tactic that motivated this section, let us write a function -that matches a type of the form `P ∧ Q`, avoiding extra computation. WHNF -makes it easy: - -```lean -def matchAndReducing (e : Expr) : MetaM (Option (Expr × Expr)) := do - match ← whnf e with - | (.app (.app (.const ``And _) P) Q) => return some (P, Q) - | _ => return none -``` - -By using `whnf`, we ensure that if `e` evaluates to something of the form `P -∧ Q`, we'll notice. But at the same time, we don't perform any unnecessary -computation in `P` or `Q`. - -However, our 'no unnecessary computation' mantra also means that if we want to -perform deeper matching on an expression, we need to use `whnf` multiple times. -Suppose we want to match a type of the form `P ∧ Q ∧ R`. The correct way to do -this uses `whnf` twice: - -```lean -def matchAndReducing₂ (e : Expr) : MetaM (Option (Expr × Expr × Expr)) := do - match ← whnf e with - | (.app (.app (.const ``And _) P) e') => - match ← whnf e' with - | (.app (.app (.const ``And _) Q) R) => return some (P, Q, R) - | _ => return none - | _ => return none -``` - -This sort of deep matching up to computation could be automated. But until -someone builds this automation, we have to figure out the necessary `whnf`s -ourselves. - - -### Definitional Equality - -As mentioned, definitional equality is equality up to computation. Two -expressions `t` and `s` are definitionally equal or *defeq* (at the current -transparency) if their normal forms (at the current transparency) are equal. - -To check whether two expressions are defeq, use `Lean.Meta.isDefEq` with type -signature - -```lean -isDefEq : Expr → Expr → MetaM Bool -``` - -Even though definitional equality is defined in terms of normal forms, `isDefEq` -does not actually compute the normal forms of its arguments, which would be very -expensive. Instead, it tries to "match up" `t` and `s` using as few reductions -as possible. This is a necessarily heuristic endeavour and when the heuristics -misfire, `isDefEq` can become very expensive. In the worst case, it may have to -reduce `s` and `t` so often that they end up in normal form anyway. But usually -the heuristics are good and `isDefEq` is reasonably fast. - -If expressions `t` and `u` contain assignable metavariables, `isDefEq` may -assign these metavariables to make `t` defeq to `u`. We also say that `isDefEq` -*unifies* `t` and `u`; such unification queries are sometimes written `t =?= u`. -For instance, the unification `List ?m =?= List Nat` succeeds and assigns `?m := -Nat`. The unification `Nat.succ ?m =?= n + 1` succeeds and assigns `?m := n`. -The unification `?m₁ + ?m₂ + ?m₃ =?= m + n - k` fails and no metavariables are -assigned (even though there is a 'partial match' between the expressions). - -Whether `isDefEq` considers a metavariable assignable is determined by two -factors: - -1. The metavariable's depth must be equal to the current `MetavarContext` depth. - See the [Metavariable Depth section](#metavariable-depth). -2. Each metavariable has a *kind* (a value of type `MetavarKind`) whose sole - purpose is to modify the behaviour of `isDefEq`. Possible kinds are: - - Natural: `isDefEq` may freely assign the metavariable. This is the default. - - Synthetic: `isDefEq` may assign the metavariable, but avoids doing so if - possible. For example, suppose `?n` is a natural metavariable and `?s` is a - synthetic metavariable. When faced with the unification problem - `?s =?= ?n`, `isDefEq` assigns `?n` rather than `?s`. - - Synthetic opaque: `isDefEq` never assigns the metavariable. - - -## Constructing Expressions - -In the previous chapter, we saw some primitive functions for building -expressions: `Expr.app`, `Expr.const`, `mkAppN` and so on. There is nothing -wrong with these functions, but the additional facilities of `MetaM` often -provide more convenient ways. - - -### Applications - -When we write regular Lean code, Lean helpfully infers many implicit arguments -and universe levels. If it did not, our code would look rather ugly: - -```lean -def appendAppend (xs ys : List α) := (xs.append ys).append xs - -set_option pp.all true in -set_option pp.explicit true in -#print appendAppend --- def appendAppend.{u_1} : {α : Type u_1} → List.{u_1} α → List.{u_1} α → List.{u_1} α := --- fun {α : Type u_1} (xs ys : List.{u_1} α) => @List.append.{u_1} α (@List.append.{u_1} α xs ys) xs -``` - -The `.{u_1}` suffixes are universe levels, which must be given for every -polymorphic constant. And of course the type `α` is passed around everywhere. - -Exactly the same problem occurs during metaprogramming when we construct -expressions. A hand-made expression representing the right-hand side of the -above definition looks like this: - -```lean -def appendAppendRHSExpr₁ (u : Level) (α xs ys : Expr) : Expr := - mkAppN (.const ``List.append [u]) - #[α, mkAppN (.const ``List.append [u]) #[α, xs, ys], xs] -``` - -Having to specify the implicit arguments and universe levels is annoying and -error-prone. So `MetaM` provides a helper function which allows us to omit -implicit information: `Lean.Meta.mkAppM` of type - -```lean -mkAppM : Name → Array Expr → MetaM Expr -``` - -Like `mkAppN`, `mkAppM` constructs an application. But while `mkAppN` requires -us to give all universe levels and implicit arguments ourselves, `mkAppM` infers -them. This means we only need to provide the explicit arguments, which makes for -a much shorter example: - -```lean -def appendAppendRHSExpr₂ (xs ys : Expr) : MetaM Expr := do - mkAppM ``List.append #[← mkAppM ``List.append #[xs, ys], xs] -``` - -Note the absence of any `α`s and `u`s. There is also a variant of `mkAppM`, -`mkAppM'`, which takes an `Expr` instead of a `Name` as the first argument, -allowing us to construct applications of expressions which are not constants. - -However, `mkAppM` is not magic: if you write `mkAppM ``List.append #[]`, you -will get an error at runtime. This is because `mkAppM` tries to determine what -the type `α` is, but with no arguments given to `append`, `α` could be anything, -so `mkAppM` fails. - -Another occasionally useful variant of `mkAppM` is `Lean.Meta.mkAppOptM` of type - -```lean -mkAppOptM : Name → Array (Option Expr) → MetaM Expr -``` - -Whereas `mkAppM` always infers implicit and instance arguments and always -requires us to give explicit arguments, `mkAppOptM` lets us choose freely which -arguments to provide and which to infer. With this, we can, for example, give -instances explicitly, which we use in the following example to give a -non-standard `Ord` instance. - -```lean -def revOrd : Ord Nat where - compare x y := compare y x - -def ordExpr : MetaM Expr := do - mkAppOptM ``compare #[none, Expr.const ``revOrd [], mkNatLit 0, mkNatLit 1] - -#eval format <$> ordExpr --- Ord.compare.{0} Nat revOrd --- (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0)) --- (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)) -``` - -Like `mkAppM`, `mkAppOptM` has a primed variant `Lean.Meta.mkAppOptM'` which -takes an `Expr` instead of a `Name` as the first argument. The file which -contains `mkAppM` also contains various other helper functions, e.g. for making -list literals or `sorry`s. - - -### Lambdas and Foralls - -Another common task is to construct expressions involving `λ` or `∀` binders. -Suppose we want to create the expression `λ (x : Nat), Nat.add x x`. One way is -to write out the lambda directly: - -```lean -def doubleExpr₁ : Expr := - .lam `x (.const ``Nat []) (mkAppN (.const ``Nat.add []) #[.bvar 0, .bvar 0]) - BinderInfo.default - -#eval ppExpr doubleExpr₁ --- fun x => Nat.add x x -``` - -This works, but the use of `bvar` is highly unidiomatic. Lean uses a so-called -*locally closed* variable representation. This means that all but the -lowest-level functions in the Lean API expect expressions not to contain 'loose -`bvar`s', where a `bvar` is loose if it is not bound by a binder in the same -expression. (Outside of Lean, such variables are usually called 'free'. The name -`bvar` -- 'bound variable' -- already indicates that `bvar`s are never supposed -to be free.) - -As a result, if in the above example we replace `mkAppN` with the slightly -higher-level `mkAppM`, we get a runtime error. Adhering to the locally closed -convention, `mkAppM` expects any expressions given to it to have no loose bound -variables, and `.bvar 0` is precisely that. - -So instead of using `bvar`s directly, the Lean way is to construct expressions -with bound variables in two steps: - -1. Construct the body of the expression (in our example: the body of the - lambda), using temporary local hypotheses (`fvar`s) to stand in for the bound - variables. -2. Replace these `fvar`s with `bvar`s and, at the same time, add the - corresponding lambda binders. - -This process ensures that we do not need to handle expressions with loose -`bvar`s at any point (except during step 2, which is performed 'atomically' by a -bespoke function). Applying the process to our example: - -```lean -def doubleExpr₂ : MetaM Expr := - withLocalDecl `x BinderInfo.default (.const ``Nat []) λ x => do - let body ← mkAppM ``Nat.add #[x, x] - mkLambdaFVars #[x] body - -#eval show MetaM _ from do - ppExpr (← doubleExpr₂) --- fun x => Nat.add x x -``` - -There are two new functions. First, `Lean.Meta.withLocalDecl` has type - -```lean -withLocalDecl (name : Name) (bi : BinderInfo) (type : Expr) (k : Expr → MetaM α) : MetaM α -``` - -Given a variable name, binder info and type, `withLocalDecl` constructs a new -`fvar` and passes it to the computation `k`. The `fvar` is available in the local -context during the execution of `k` but is deleted again afterwards. - -The second new function is `Lean.Meta.mkLambdaFVars` with type (ignoring some -optional arguments) - -``` -mkLambdaFVars : Array Expr → Expr → MetaM Expr -``` - -This function takes an array of `fvar`s and an expression `e`. It then adds one -lambda binder for each `fvar` `x` and replaces every occurrence of `x` in `e` -with a bound variable corresponding to the new lambda binder. The returned -expression does not contain the `fvar`s any more, which is good since they -disappear after we leave the `withLocalDecl` context. (Instead of `fvar`s, we -can also give `mvar`s to `mkLambdaFVars`, despite its name.) - -Some variants of the above functions may be useful: - -- `withLocalDecls` declares multiple temporary `fvar`s. -- `mkForallFVars` creates `∀` binders instead of `λ` binders. `mkLetFVars` - creates `let` binders. -- `mkArrow` is the non-dependent version of `mkForallFVars` which construcs - a function type `X → Y`. Since the type is non-dependent, there is no need - for temporary `fvar`s. - -Using all these functions, we can construct larger expressions such as this one: - -```lean -λ (f : Nat → Nat), ∀ (n : Nat), f n = f (n + 1) -``` - -```lean -def somePropExpr : MetaM Expr := do - let funcType ← mkArrow (.const ``Nat []) (.const ``Nat []) - withLocalDecl `f BinderInfo.default funcType fun f => do - let feqn ← withLocalDecl `n BinderInfo.default (.const ``Nat []) fun n => do - let lhs := .app f n - let rhs := .app f (← mkAppM ``Nat.succ #[n]) - let eqn ← mkEq lhs rhs - mkForallFVars #[n] eqn - mkLambdaFVars #[f] feqn -``` - -The next line registers `someProp` as a name for the expression we've just -constructed, allowing us to play with it more easily. The mechanisms behind this -are discussed in the Elaboration chapter. - -```lean -elab "someProp" : term => somePropExpr - -#check someProp --- fun f => ∀ (n : Nat), f n = f (Nat.succ n) : (Nat → Nat) → Prop -#reduce someProp Nat.succ --- ∀ (n : Nat), Nat.succ n = Nat.succ (Nat.succ n) -``` - -### Deconstructing Expressions - -Just like we can construct expressions more easily in `MetaM`, we can also -deconstruct them more easily. Particularly useful is a family of functions for -deconstructing expressions which start with `λ` and `∀` binders. - -When we are given a type of the form `∀ (x₁ : T₁) ... (xₙ : Tₙ), U`, we are -often interested in doing something with the conclusion `U`. For instance, the -`apply` tactic, when given an expression `e : ∀ ..., U`, compares `U` with the -current target to determine whether `e` can be applied. - -To do this, we could repeatedly match on the type expression, removing `∀` -binders until we get to `U`. But this would leave us with an `U` containing -unbound `bvar`s, which, as we saw, is bad. Instead, we use -`Lean.Meta.forallTelescope` of type - -``` -forallTelescope (type : Expr) (k : Array Expr → Expr → MetaM α) : MetaM α -``` - -Given `type = ∀ (x₁ : T₁) ... (xₙ : Tₙ), U x₁ ... xₙ`, this function creates one -fvar `fᵢ` for each `∀`-bound variable `xᵢ` and replaces each `xᵢ` with `fᵢ` in -the conclusion `U`. It then calls the computation `k`, passing it the `fᵢ` and -the conclusion `U f₁ ... fₙ`. Within this computation, the `fᵢ` are registered -in the local context; afterwards, they are deleted again (similar to -`withLocalDecl`). - -There are many useful variants of `forallTelescope`: - -- `forallTelescopeReducing`: like `forallTelescope` but matching is performed up - to computation. This means that if you have an expression `X` which is - different from but defeq to `∀ x, P x`, `forallTelescopeReducing X` will - deconstruct `X` into `x` and `P x`. The non-reducing `forallTelescope` would - not recognise `X` as a quantified expression. The matching is performed by - essentially calling `whnf` repeatedly, using the ambient transparency. -- `forallBoundedTelescope`: like `forallTelescopeReducing` (even though there is - no "reducing" in the name) but stops after a specified number of `∀` binders. -- `forallMetaTelescope`, `forallMetaTelescopeReducing`, - `forallMetaBoundedTelescope`: like the corresponding non-`meta` functions, but - the bound variables are replaced by new `mvar`s instead of `fvar`s. Unlike the - non-`meta` functions, the `meta` functions do not delete the new metavariables - after performing some computation, so the metavariables remain in the - environment indefinitely. -- `lambdaTelescope`, `lambdaTelescopeReducing`, `lambdaBoundedTelescope`, - `lambdaMetaTelescope`: like the corresponding `forall` functions, but for - `λ` binders instead of `∀`. - -Using one of the telescope functions, we can implement our own `apply` tactic: - -```lean -def myApply (goal : MVarId) (e : Expr) : MetaM (List MVarId) := do - -- Check that the goal is not yet assigned. - goal.checkNotAssigned `myApply - -- Operate in the local context of the goal. - goal.withContext do - -- Get the goal's target type. - let target ← goal.getType - -- Get the type of the given expression. - let type ← inferType e - -- If `type` has the form `∀ (x₁ : T₁) ... (xₙ : Tₙ), U`, introduce new - -- metavariables for the `xᵢ` and obtain the conclusion `U`. (If `type` does - -- not have this form, `args` is empty and `conclusion = type`.) - let (args, _, conclusion) ← forallMetaTelescopeReducing type - -- If the conclusion unifies with the target: - if ← isDefEq target conclusion then - -- Assign the goal to `e x₁ ... xₙ`, where the `xᵢ` are the fresh - -- metavariables in `args`. - goal.assign (mkAppN e args) - -- `isDefEq` may have assigned some of the `args`. Report the rest as new - -- goals. - let newGoals ← args.filterMapM λ mvar => do - let mvarId := mvar.mvarId! - if ! (← mvarId.isAssigned) && ! (← mvarId.isDelayedAssigned) then - return some mvarId - else - return none - return newGoals.toList - -- If the conclusion does not unify with the target, throw an error. - else - throwTacticEx `myApply goal m!"{e} is not applicable to goal with target {target}" -``` - -The real `apply` does some additional pre- and postprocessing, but the core -logic is what we show here. To test our tactic, we need an elaboration -incantation, more about which in the Elaboration chapter. - -```lean -elab "myApply" e:term : tactic => do - let e ← Elab.Term.elabTerm e none - Elab.Tactic.liftMetaTactic (myApply · e) - -example (h : α → β) (a : α) : β := by - myApply h - myApply a -``` - -## Backtracking - -Many tactics naturally require backtracking: the ability to go back to a -previous state, as if the tactic had never been executed. A few examples: - -- `first | t | u` first executes `t`. If `t` fails, it backtracks and executes - `u`. -- `try t` executes `t`. If `t` fails, it backtracks to the initial state, - erasing any changes made by `t`. -- `trivial` attempts to solve the goal using a number of simple tactics - (e.g. `rfl` or `contradiction`). After each unsuccessful application of such a - tactic, `trivial` backtracks. - -Good thing, then, that Lean's core data structures are designed to enable easy -and efficient backtracking. The corresponding API is provided by the -`Lean.MonadBacktrack` class. `MetaM`, `TermElabM` and `TacticM` are all -instances of this class. (`CoreM` is not but could be.) - -`MonadBacktrack` provides two fundamental operations: - -- `Lean.saveState : m s` returns a representation of the current state, where - `m` is the monad we are in and `s` is the state type. E.g. for `MetaM`, - `saveState` returns a `Lean.Meta.SavedState` containing the current - environment, the current `MetavarContext` and various other pieces of - information. -- `Lean.restoreState : s → m Unit` takes a previously saved state and restores - it. This effectively resets the compiler state to the previous point. - -With this, we can roll our own `MetaM` version of the `try` tactic: - -```lean -def tryM (x : MetaM Unit) : MetaM Unit := do - let s ← saveState - try - x - catch _ => - restoreState s -``` - -We first save the state, then execute `x`. If `x` fails, we backtrack the state. - -The standard library defines many combinators like `tryM`. Here are the most -useful ones: - -- `Lean.withoutModifyingState (x : m α) : m α` executes the action `x`, then - resets the state and returns `x`'s result. You can use this, for example, to - check for definitional equality without assigning metavariables: - ```lean - withoutModifyingState $ isDefEq x y - ``` - If `isDefEq` succeeds, it may assign metavariables in `x` and `y`. Using - `withoutModifyingState`, we can make sure this does not happen. -- `Lean.observing? (x : m α) : m (Option α)` executes the action `x`. If `x` - succeeds, `observing?` returns its result. If `x` fails (throws an exception), - `observing?` backtracks the state and returns `none`. This is a more - informative version of our `tryM` combinator. -- `Lean.commitIfNoEx (x : α) : m α` executes `x`. If `x` succeeds, - `commitIfNoEx` returns its result. If `x` throws an exception, `commitIfNoEx` - backtracks the state and rethrows the exception. - -Note that the builtin `try ... catch ... finally` does not perform any -backtracking. So code which looks like this is probably wrong: - -```lean -try - doSomething -catch e => - doSomethingElse -``` - -The `catch` branch, `doSomethingElse`, is executed in a state containing -whatever modifications `doSomething` made before it failed. Since we probably -want to erase these modifications, we should write instead: - -```lean -try - commitIfNoEx doSomething -catch e => - doSomethingElse -``` - -Another `MonadBacktrack` gotcha is that `restoreState` does not backtrack the -*entire* state. Caches, trace messages and the global name generator, among -other things, are not backtracked, so changes made to these parts of the state -are not reset by `restoreState`. This is usually what we want: if a tactic -executed by `observing?` produces some trace messages, we want to see them even -if the tactic fails. See `Lean.Meta.SavedState.restore` and `Lean.Core.restore` -for details on what is and is not backtracked. - -In the next chapter, we move towards the topic of elaboration, of which -you've already seen several glimpses in this chapter. We start by discussing -Lean's syntax system, which allows you to add custom syntactic constructs to the -Lean parser. - -## Exercises - -1. [**Metavariables**] Create a metavariable with type `Nat`, and assign to it value `3`. -Notice that changing the type of the metavariable from `Nat` to, for example, `String`, doesn't raise any errors - that's why, as was mentioned, we must make sure *"(a) that `val` must have the target type of `mvarId` and (b) that `val` must only contain `fvars` from the local context of `mvarId`"*. -2. [**Metavariables**] What would `instantiateMVars (Lean.mkAppN (Expr.const 'Nat.add []) #[mkNatLit 1, mkNatLit 2])` output? -3. [**Metavariables**] Fill in the missing lines in the following code. - - ```lean - #eval show MetaM Unit from do - let oneExpr := Expr.app (Expr.const `Nat.succ []) (Expr.const ``Nat.zero []) - let twoExpr := Expr.app (Expr.const `Nat.succ []) oneExpr - - -- Create `mvar1` with type `Nat` - -- let mvar1 ← ... - -- Create `mvar2` with type `Nat` - -- let mvar2 ← ... - -- Create `mvar3` with type `Nat` - -- let mvar3 ← ... - - -- Assign `mvar1` to `2 + ?mvar2 + ?mvar3` - -- ... - - -- Assign `mvar3` to `1` - -- ... - - -- Instantiate `mvar1`, which should result in expression `2 + ?mvar2 + 1` - ... - ``` -4. [**Metavariables**] Consider the theorem `red`, and tactic `explore` below. - **a)** What would be the `type` and `userName` of metavariable `mvarId`? - **b)** What would be the `type`s and `userName`s of all local declarations in this metavariable's local context? - Print them all out. - - ```lean - elab "explore" : tactic => do - let mvarId : MVarId ← Lean.Elab.Tactic.getMainGoal - let metavarDecl : MetavarDecl ← mvarId.getDecl - - IO.println "Our metavariable" - -- ... - - IO.println "All of its local declarations" - -- ... - - theorem red (hA : 1 = 1) (hB : 2 = 2) : 2 = 2 := by - explore - sorry - ``` -5. [**Metavariables**] Write a tactic `solve` that proves the theorem `red`. -6. [**Computation**] What is the normal form of the following expressions: - **a)** `fun x => x` of type `Bool → Bool` - **b)** `(fun x => x) ((true && false) || true)` of type `Bool` - **c)** `800 + 2` of type `Nat` -7. [**Computation**] Show that `1` created with `Expr.lit (Lean.Literal.natVal 1)` is definitionally equal to an expression created with `Expr.app (Expr.const ``Nat.succ []) (Expr.const ``Nat.zero [])`. -8. [**Computation**] Determine whether the following expressions are definitionally equal. If `Lean.Meta.isDefEq` succeeds, and it leads to metavariable assignment, write down the assignments. - **a)** `5 =?= (fun x => 5) ((fun y : Nat → Nat => y) (fun z : Nat => z))` - **b)** `2 + 1 =?= 1 + 2` - **c)** `?a =?= 2`, where `?a` has a type `String` - **d)** `?a + Int =?= "hi" + ?b`, where `?a` and `?b` don't have a type - **e)** `2 + ?a =?= 3` - **f)** `2 + ?a =?= 2 + 1` -9. [**Computation**] Write down what you expect the following code to output. - - ```lean - @[reducible] def reducibleDef : Nat := 1 -- same as `abbrev` - @[instance] def instanceDef : Nat := 2 -- same as `instance` - def defaultDef : Nat := 3 - @[irreducible] def irreducibleDef : Nat := 4 - - @[reducible] def sum := [reducibleDef, instanceDef, defaultDef, irreducibleDef] - - #eval show MetaM Unit from do - let constantExpr := Expr.const `sum [] - - Meta.withTransparency Meta.TransparencyMode.reducible do - let reducedExpr ← Meta.reduce constantExpr - dbg_trace (← ppExpr reducedExpr) -- ... - - Meta.withTransparency Meta.TransparencyMode.instances do - let reducedExpr ← Meta.reduce constantExpr - dbg_trace (← ppExpr reducedExpr) -- ... - - Meta.withTransparency Meta.TransparencyMode.default do - let reducedExpr ← Meta.reduce constantExpr - dbg_trace (← ppExpr reducedExpr) -- ... - - Meta.withTransparency Meta.TransparencyMode.all do - let reducedExpr ← Meta.reduce constantExpr - dbg_trace (← ppExpr reducedExpr) -- ... - - let reducedExpr ← Meta.reduce constantExpr - dbg_trace (← ppExpr reducedExpr) -- ... - ``` -10. [**Constructing Expressions**] Create expression `fun x, 1 + x` in two ways: - **a)** not idiomatically, with loose bound variables - **b)** idiomatically. - In what version can you use `Lean.mkAppN`? In what version can you use `Lean.Meta.mkAppM`? -11. [**Constructing Expressions**] Create expression `∀ (yellow: Nat), yellow`. -12. [**Constructing Expressions**] Create expression `∀ (n : Nat), n = n + 1` in two ways: - **a)** not idiomatically, with loose bound variables - **b)** idiomatically. - In what version can you use `Lean.mkApp3`? In what version can you use `Lean.Meta.mkEq`? -13. [**Constructing Expressions**] Create expression `fun (f : Nat → Nat), ∀ (n : Nat), f n = f (n + 1)` idiomatically. -14. [**Constructing Expressions**] What would you expect the output of the following code to be? - - ```lean - #eval show Lean.Elab.Term.TermElabM _ from do - let stx : Syntax ← `(∀ (a : Prop) (b : Prop), a ∨ b → b → a ∧ a) - let expr ← Elab.Term.elabTermAndSynthesize stx none - - let (_, _, conclusion) ← forallMetaTelescope expr - dbg_trace conclusion -- ... - - let (_, _, conclusion) ← forallMetaBoundedTelescope expr 2 - dbg_trace conclusion -- ... - - let (_, _, conclusion) ← lambdaMetaTelescope expr - dbg_trace conclusion -- ... - ``` -15. [**Backtracking**] Check that the expressions `?a + Int` and `"hi" + ?b` are definitionally equal with `isDefEq` (make sure to use the proper types or `Option.none` for the types of your metavariables!). -Use `saveState` and `restoreState` to revert metavariable assignments. diff --git a/md/main/05_syntax.md b/md/main/05_syntax.md deleted file mode 100644 index 968c014..0000000 --- a/md/main/05_syntax.md +++ /dev/null @@ -1,622 +0,0 @@ -# Syntax - -This chapter is concerned with the means to declare and operate on syntax -in Lean. Since there are a multitude of ways to operate on it, we will -not go into great detail about this yet and postpone quite a bit of this to -later chapters. - -## Declaring Syntax - -### Declaration helpers - -Some readers might be familiar with the `infix` or even the `notation` -commands, for those that are not here is a brief recap: - -```lean -import Lean - --- XOR, denoted \oplus -infixl:60 " ⊕ " => fun l r => (!l && r) || (l && !r) - -#eval true ⊕ true -- false -#eval true ⊕ false -- true -#eval false ⊕ true -- true -#eval false ⊕ false -- false - --- with `notation`, "left XOR" -notation:10 l:10 " LXOR " r:11 => (!l && r) - -#eval true LXOR true -- false -#eval true LXOR false -- false -#eval false LXOR true -- true -#eval false LXOR false -- false -``` - -As we can see the `infixl` command allows us to declare a notation for -a binary operation that is infix, meaning that the operator is in between -the operands (as opposed to e.g. before which would be done using the `prefix` command). -The `l` at the end of `infixl` means that the notation is left associative so `a ⊕ b ⊕ c` -gets parsed as `(a ⊕ b) ⊕ c` as opposed to `a ⊕ (b ⊕ c)` (which would be achieved by `infixr`). -On the right hand side, it expects a function that operates on these two parameters -and returns some value. The `notation` command, on the other hand, allows us some more -freedom: we can just "mention" the parameters right in the syntax definition -and operate on them on the right hand side. It gets even better though, we can -in theory create syntax with 0 up to as many parameters as we wish using the -`notation` command, it is hence also often referred to as "mixfix" notation. - -The two unintuitive parts about these two are: -- The fact that we are leaving spaces around our operators: " ⊕ ", " LXOR ". - This is so that, when Lean pretty prints our syntax later on, it also - uses spaces around the operators, otherwise the syntax would just be presented - as `l⊕r` as opposed to `l ⊕ r`. -- The `60` and `10` right after the respective commands -- these denote the operator - precedence, meaning how strong they bind to their arguments, let's see this in action: - -```lean -#eval true ⊕ false LXOR false -- false -#eval (true ⊕ false) LXOR false -- false -#eval true ⊕ (false LXOR false) -- true -``` - -As we can see, the Lean interpreter analyzed the first term without parentheses -like the second instead of the third one. This is because the `⊕` notation -has higher precedence than `LXOR` (`60 > 10` after all) and is thus evaluated before it. -This is also how you might implement rules like `*` being evaluated before `+`. - -Lastly at the `notation` example there are also these `:precedence` bindings -at the arguments: `l:10` and `r:11`. This conveys that the left argument must have -precedence at least 10 or greater, and the right argument must have precedence at 11 -or greater. The way the arguments are assigned their respective precedence is by looking at -the precedence of the rule that was used to parse them. Consider for example -`a LXOR b LXOR c`. Theoretically speaking this could be parsed in two ways: -1. `(a LXOR b) LXOR c` -2. `a LXOR (b LXOR c)` - -Since the arguments in parentheses are parsed by the `LXOR` rule with precedence -10 they will appear as arguments with precedence 10 to the outer `LXOR` rule: -1. `(a LXOR b):10 LXOR c` -2. `a LXOR (b LXOR c):10` - -However if we check the definition of `LXOR`: `notation:10 l:10 " LXOR " r:11` -we can see that the right hand side argument requires a precedence of at least 11 -or greater, thus the second parse is invalid and we remain with: `(a LXOR b) LXOR c` -assuming that: -- `a` has precedence 10 or higher -- `b` has precedence 11 or higher -- `c` has precedence 11 or higher - -Thus `LXOR` is a left associative notation. Can you make it right associative? - -NOTE: If parameters of a notation are not explicitly given a precedence they will implicitly be tagged with precedence 0. - -As a last remark for this section: Lean will always attempt to obtain the longest -matching parse possible, this has three important implications. -First a very intuitive one, if we have a right associative operator `^` -and Lean sees something like `a ^ b ^ c`, it will first parse the `a ^ b` -and then attempt to keep parsing (as long as precedence allows it) until -it cannot continue anymore. Hence Lean will parse this expression as `a ^ (b ^ c)` -(as we would expect it to). - -Secondly, if we have a notation where precedence does not allow to figure -out how the expression should be parenthesized, for example: - -```lean -notation:65 lhs:65 " ~ " rhs:65 => (lhs - rhs) -``` - -An expression like `a ~ b ~ c` will be parsed as `a ~ (b ~ c)` because -Lean attempts to find the longest parse possible. As a general rule of thumb: -If precedence is ambiguous Lean will default to right associativity. - -```lean -#eval 5 ~ 3 ~ 3 -- 5 because this is parsed as 5 - (3 - 3) -``` - -Lastly, if we define overlapping notation such as: - -```lean --- define `a ~ b mod rel` to mean that a and b are equivalent with respect to some equivalence relation rel -notation:65 a:65 " ~ " b:65 " mod " rel:65 => rel a b -``` - -Lean will prefer this notation over parsing `a ~ b` as defined above and -then erroring because it doesn't know what to do with `mod` and the -relation argument: - -```lean -#check 0 ~ 0 mod Eq -- 0 = 0 : Prop -``` - -This is again because it is looking for the longest possible parser which -in this case involves also consuming `mod` and the relation argument. - -### Free form syntax declarations -With the above `infix` and `notation` commands, you can get quite far with -declaring ordinary mathematical syntax already. Lean does however allow you to -introduce arbitrarily complex syntax as well. This is done using two main commands -`syntax` and `declare_syntax_cat`. A `syntax` command allows you add a new -syntax rule to an already existing so-called "syntax category". The most common syntax -categories are: -- `term`, this category will be discussed in detail in the elaboration chapter, - for now you can think of it as "the syntax of everything that has a value" -- `command`, this is the category for top-level commands like `#check`, `def` etc. -- TODO: ... - -Let's see this in action: - -```lean -syntax "MyTerm" : term -``` - -We can now write `MyTerm` in place of things like `1 + 1` and it will be -*syntactically* valid, this does not mean the code will compile yet, -it just means that the Lean parser can understand it: - -```lean -def Playground1.test := MyTerm --- elaboration function for 'termMyTerm' has not been implemented --- MyTerm -``` - -Implementing this so-called "elaboration function", which will actually -give meaning to this syntax in terms of Lean's fundamental `Expr` type, -is topic of the elaboration chapter. - -The `notation` and `infix` commands are utilities that conveniently bundle syntax declaration -with macro definition (for more on macros, see the macro chapter), -where the contents left of the `=>` declare the syntax. -All the previously mentioned principles from `notation` and `infix` regarding precedence -fully apply to `syntax` as well. - -We can, of course, also involve other syntax into our own declarations -in order to build up syntax trees. For example, we could try to build our -own little boolean expression language: - -```lean -namespace Playground2 - --- The scoped modifier makes sure the syntax declarations remain in this `namespace` --- because we will keep modifying this along the chapter -scoped syntax "⊥" : term -- ⊥ for false -scoped syntax "⊤" : term -- ⊤ for true -scoped syntax:40 term " OR " term : term -scoped syntax:50 term " AND " term : term -#check ⊥ OR (⊤ AND ⊥) -- elaboration function hasn't been implemented but parsing passes - -end Playground2 -``` - -While this does work, it allows arbitrary terms to the left and right of our -`AND` and `OR` operation. If we want to write a mini language that only accepts -our boolean language on a syntax level we will have to declare our own -syntax category on top. This is done using the `declare_syntax_cat` command: - -```lean -declare_syntax_cat boolean_expr -syntax "⊥" : boolean_expr -- ⊥ for false -syntax "⊤" : boolean_expr -- ⊤ for true -syntax:40 boolean_expr " OR " boolean_expr : boolean_expr -syntax:50 boolean_expr " AND " boolean_expr : boolean_expr -``` - -Now that we are working in our own syntax category, we are completely -disconnected from the rest of the system. And these cannot be used in place of -terms anymore: - -```lean -#check ⊥ AND ⊤ -- expected term -``` - -In order to integrate our syntax category into the rest of the system we will -have to extend an already existing one with new syntax, in this case we -will re-embed it into the `term` category: - -```lean -syntax "[Bool|" boolean_expr "]" : term -#check [Bool| ⊥ AND ⊤] -- elaboration function hasn't been implemented but parsing passes -``` - -### Syntax combinators -In order to declare more complex syntax, it is often very desirable to have -some basic operations on syntax already built-in, these include: - -- helper parsers without syntax categories (i.e. not extendable) -- alternatives -- repetitive parts -- optional parts - -While all of these do have an encoding based on syntax categories, this -can make things quite ugly at times, so Lean provides an easier way to do all -of these. - -In order to see all of these in action, we will briefly define a simple -binary expression syntax. -First things first, declaring named parsers that don't belong to a syntax -category is quite similar to ordinary `def`s: - -```lean -syntax binOne := "O" -syntax binZero := "Z" -``` - -These named parsers can be used in the same positions as syntax categories -from above, their only difference to them is, that they are not extensible. -That is, they are directly expanded within syntax declarations, -and we cannot define new patterns for them as we would with proper syntax categories. -There does also exist a number of built-in named parsers that are generally useful, -most notably: -- `str` for string literals -- `num` for number literals -- `ident` for identifiers -- ... TODO: better list or link to compiler docs - -Next up we want to declare a parser that understands digits, a binary digit is -either 0 or 1 so we can write: - -```lean -syntax binDigit := binZero <|> binOne -``` - -Where the `<|>` operator implements the "accept the left or the right" behaviour. -We can also chain them to achieve parsers that accept arbitrarily many, arbitrarily complex -other ones. Now we will define the concept of a binary number, usually this would be written -as digits directly after each other but we will instead use comma separated ones to showcase -the repetition feature: - -```lean --- the "+" denotes "one or many", in order to achieve "zero or many" use "*" instead --- the "," denotes the separator between the `binDigit`s, if left out the default separator is a space -syntax binNumber := binDigit,+ -``` - -Since we can just use named parsers in place of syntax categories, we can now easily -add this to the `term` category: - -```lean -syntax "bin(" binNumber ")" : term -#check bin(Z, O, Z, Z, O) -- elaboration function hasn't been implemented but parsing passes -#check bin() -- fails to parse because `binNumber` is "one or many": expected 'O' or 'Z' - -syntax binNumber' := binDigit,* -- note the * -syntax "emptyBin(" binNumber' ")" : term -#check emptyBin() -- elaboration function hasn't been implemented but parsing passes -``` - -Note that nothing is limiting us to only using one syntax combinator per parser, -we could also have written all of this inline: - -```lean -syntax "binCompact(" ("Z" <|> "O"),+ ")" : term -#check binCompact(Z, O, Z, Z, O) -- elaboration function hasn't been implemented but parsing passes -``` - -As a final feature, let's add an optional string comment that explains the binary -literal being declared: - -```lean --- The (...)? syntax means that the part in parentheses is optional -syntax "binDoc(" (str ";")? binNumber ")" : term -#check binDoc(Z, O, Z, Z, O) -- elaboration function hasn't been implemented but parsing passes -#check binDoc("mycomment"; Z, O, Z, Z, O) -- elaboration function hasn't been implemented but parsing passes -``` - -## Operating on Syntax -As explained above, we will not go into detail in this chapter on how to teach -Lean about the meaning you want to give your syntax. We will, however, take a look -at how to write functions that operate on it. Like all things in Lean, syntax is -represented by the inductive type `Lean.Syntax`, on which we can operate. It does -contain quite some information, but most of what we are interested in, we can -condense in the following simplified view: - -```lean -namespace Playground2 - -inductive Syntax where - | missing : Syntax - | node (kind : Lean.SyntaxNodeKind) (args : Array Syntax) : Syntax - | atom : String -> Syntax - | ident : Lean.Name -> Syntax - -end Playground2 -``` - -Lets go through the definition one constructor at a time: -- `missing` is used when there is something the Lean compiler cannot parse, - it is what allows Lean to have a syntax error in one part of the file but - recover from it and try to understand the rest of it. This also means we pretty - much don't care about this constructor. -- `node` is, as the name suggests, a node in the syntax tree. It has a so called - `kind : SyntaxNodeKind` where `SyntaxNodeKind` is just a `Lean.Name`. Basically, - each of our `syntax` declarations receives an automatically generated `SyntaxNodeKind` - (we can also explicitly specify the name with `syntax (name := foo) ... : cat`) so - we can tell Lean "this function is responsible for processing this specific syntax construct". - Furthermore, like all nodes in a tree, it has children, in this case in the form of - an `Array Syntax`. -- `atom` represents (with the exception of one) every syntax object that is at the bottom of the - hierarchy. For example, our operators ` ⊕ ` and ` LXOR ` from above will be represented as - atoms. -- `ident` is the mentioned exception to this rule. The difference between `ident` and `atom` - is also quite obvious: an identifier has a `Lean.Name` instead of a `String` that represents it. - Why a `Lean.Name` is not just a `String` is related to a concept called macro hygiene - that will be discussed in detail in the macro chapter. For now, you can consider them - basically equivalent. - -### Constructing new `Syntax` -Now that we know how syntax is represented in Lean, we could of course write programs that -generate all of these inductive trees by hand, which would be incredibly tedious and is something -we most definitely want to avoid. Luckily for us there is quite an extensive API hidden inside the -`Lean.Syntax` namespace we can explore: - -```lean -open Lean -#check Syntax -- Syntax. autocomplete -``` - -The interesting functions for creating `Syntax` are the `Syntax.mk*` ones that allow us to create -both very basic `Syntax` objects like `ident`s but also more complex ones like `Syntax.mkApp` -which we can use to create the `Syntax` object that would amount to applying the function -from the first argument to the argument list (all given as `Syntax`) in the second one. -Let's see a few examples: - -```lean --- Name literals are written with this little ` in front of the name -#eval Syntax.mkApp (mkIdent `Nat.add) #[Syntax.mkNumLit "1", Syntax.mkNumLit "1"] -- is the syntax of `Nat.add 1 1` -#eval mkNode `«term_+_» #[Syntax.mkNumLit "1", mkAtom "+", Syntax.mkNumLit "1"] -- is the syntax for `1 + 1` - --- note that the `«term_+_» is the auto-generated SyntaxNodeKind for the + syntax -``` - -If you don't like this way of creating `Syntax` at all you are not alone. -However, there are a few things involved with the machinery of doing this in -a pretty and correct (the machinery is mostly about the correct part) way -which will be explained in the macro chapter. - -### Matching on `Syntax` -Just like constructing `Syntax` is an important topic, especially -with macros, matching on syntax is equally (or in fact even more) interesting. -Luckily we don't have to match on the inductive type itself either: we can -instead use so-called "syntax patterns". They are quite simple, their syntax is just -`` `(the syntax I want to match on) ``. Let's see one in action: - -```lean -def isAdd11 : Syntax → Bool - | `(Nat.add 1 1) => true - | _ => false - -#eval isAdd11 (Syntax.mkApp (mkIdent `Nat.add) #[Syntax.mkNumLit "1", Syntax.mkNumLit "1"]) -- true -#eval isAdd11 (Syntax.mkApp (mkIdent `Nat.add) #[mkIdent `foo, Syntax.mkNumLit "1"]) -- false -``` - -The next level with matches is to capture variables from the input instead -of just matching on literals, this is done with a slightly fancier-looking syntax: - -```lean -def isAdd : Syntax → Option (Syntax × Syntax) - | `(Nat.add $x $y) => some (x, y) - | _ => none - -#eval isAdd (Syntax.mkApp (mkIdent `Nat.add) #[Syntax.mkNumLit "1", Syntax.mkNumLit "1"]) -- some ... -#eval isAdd (Syntax.mkApp (mkIdent `Nat.add) #[mkIdent `foo, Syntax.mkNumLit "1"]) -- some ... -#eval isAdd (Syntax.mkApp (mkIdent `Nat.add) #[mkIdent `foo]) -- none -``` - -### Typed Syntax -Note that `x` and `y` in this example are of type `` TSyntax `term ``, not `Syntax`. -Even though we are pattern matching on `Syntax` which, as we can see in the constructors, -is purely composed of types that are not `TSyntax`, so what is going on? -Basically the `` `() `` Syntax is smart enough to figure out the most general -syntax category the syntax we are matching might be coming from (in this case `term`). -It will then use the typed syntax type `TSyntax` which is parameterized -by the `Name` of the syntax category it came from. This is not only more -convenient for the programmer to see what is going on, it also has other -benefits. For Example if we limit the syntax category to just `num` -in the next example Lean will allow us to call `getNat` on the resulting -`` TSyntax `num `` directly without pattern matching or the option to panic: - -```lean --- Now we are also explicitly marking the function to operate on term syntax -def isLitAdd : TSyntax `term → Option Nat - | `(Nat.add $x:num $y:num) => some (x.getNat + y.getNat) - | _ => none - -#eval isLitAdd (Syntax.mkApp (mkIdent `Nat.add) #[Syntax.mkNumLit "1", Syntax.mkNumLit "1"]) -- some 2 -#eval isLitAdd (Syntax.mkApp (mkIdent `Nat.add) #[mkIdent `foo, Syntax.mkNumLit "1"]) -- none -``` - -If you want to access the `Syntax` behind a `TSyntax` you can do this using -`TSyntax.raw` although the coercion machinery should just work most of the time. -We will see some further benefits of the `TSyntax` system in the macro chapter. - -One last important note about the matching on syntax: In this basic -form it only works on syntax from the `term` category. If you want to use -it to match on your own syntax categories you will have to use `` `(category| ...)``. - -### Mini Project -As a final mini project for this chapter we will declare the syntax of a mini -arithmetic expression language and a function of type `Syntax → Nat` to evaluate -it. We will see more about some of the concepts presented below in future -chapters. - -```lean -declare_syntax_cat arith - -syntax num : arith -syntax arith "-" arith : arith -syntax arith "+" arith : arith -syntax "(" arith ")" : arith - -partial def denoteArith : TSyntax `arith → Nat - | `(arith| $x:num) => x.getNat - | `(arith| $x:arith + $y:arith) => denoteArith x + denoteArith y - | `(arith| $x:arith - $y:arith) => denoteArith x - denoteArith y - | `(arith| ($x:arith)) => denoteArith x - | _ => 0 - --- You can ignore Elab.TermElabM, what is important for us is that it allows --- us to use the ``(arith| (12 + 3) - 4)` notation to construct `Syntax` --- instead of only being able to match on it like this. -def test : Elab.TermElabM Nat := do - let stx ← `(arith| (12 + 3) - 4) - pure (denoteArith stx) - -#eval test -- 11 -``` - -Feel free to play around with this example and extend it in whatever way -you want to. The next chapters will mostly be about functions that operate -on `Syntax` in some way. - -## More elaborate examples -### Using type classes for notations -We can use type classes in order to add notation that is extensible via -the type instead of the syntax system, this is for example how `+` -using the typeclasses `HAdd` and `Add` and other common operators in -Lean are generically defined. - -For example, we might want to have a generic notation for subset notation. -The first thing we have to do is define a type class that captures -the function we want to build notation for. - -```lean -class Subset (α : Type u) where - subset : α → α → Prop -``` - -The second step is to define the notation, what we can do here is simply -turn every instance of a `⊆` appearing in the code to a call to `Subset.subset` -because the type class resolution should be able to figure out which `Subset` -instance is referred to. Thus the notation will be a simple: - -```lean --- precedence is arbitrary for this example -infix:50 " ⊆ " => Subset.subset -``` - -Let's define a simple theory of sets to test it: - -```lean --- a `Set` is defined by the elements it contains --- -> a simple predicate on the type of its elements -def Set (α : Type u) := α → Prop - -def Set.mem (x : α) (X : Set α) : Prop := X x - --- Integrate into the already existing typeclass for membership notation -instance : Membership α (Set α) where - mem := Set.mem - -def Set.empty : Set α := λ _ => False - -instance : Subset (Set α) where - subset X Y := ∀ (x : α), x ∈ X → x ∈ Y - -example : ∀ (X : Set α), Set.empty ⊆ X := by - intro X x - -- ⊢ x ∈ Set.empty → x ∈ X - intro h - exact False.elim h -- empty set has no members -``` - -### Binders -Because declaring syntax that uses variable binders used to be a rather -unintuitive thing to do in Lean 3, we'll take a brief look at how naturally -this can be done in Lean 4. - -For this example we will define the well-known notation for the set -that contains all elements `x` such that some property holds: -`{x ∈ ℕ | x < 10}` for example. - -First things first we need to extend the theory of sets from above slightly: - -```lean --- the basic "all elements such that" function for the notation -def setOf {α : Type} (p : α → Prop) : Set α := p -``` - -Equipped with this function, we can now attempt to intuitively define a -basic version of our notation: - -```lean -notation "{ " x " | " p " }" => setOf (fun x => p) - -#check { (x : Nat) | x ≤ 1 } -- { x | x ≤ 1 } : Set Nat - -example : 1 ∈ { (y : Nat) | y ≤ 1 } := by simp[Membership.mem, Set.mem, setOf] -example : 2 ∈ { (y : Nat) | y ≤ 3 ∧ 1 ≤ y } := by simp[Membership.mem, Set.mem, setOf] -``` - -This intuitive notation will indeed deal with what we could throw at -it in the way we would expect it. - -As to how one might extend this notation to allow more set-theoretic -things such as `{x ∈ X | p x}` and leave out the parentheses around -the bound variables, we refer the reader to the macro chapter. - - -## Exercises - -1. Create an "urgent minus 💀" notation such that `5 * 8 💀 4` returns `20`, and `8 💀 6 💀 1` returns `3`. - - **a)** Using `notation` command. - **b)** Using `infix` command. - **c)** Using `syntax` command. - - Hint: multiplication in Lean 4 is defined as `infixl:70 " * " => HMul.hMul`. - -2. Consider the following syntax categories: `term`, `command`, `tactic`; and 3 syntax rules given below. Make use of each of these newly defined syntaxes. - - ``` - syntax "good morning" : term - syntax "hello" : command - syntax "yellow" : tactic - ``` - -3. Create a `syntax` rule that would accept the following commands: - - - `red red red 4` - - `blue 7` - - `blue blue blue blue blue 18` - - (So, either all `red`s followed by a number; or all `blue`s followed by a number; `red blue blue 5` - shouldn't work.) - - Use the following code template: - - ```lean - syntax (name := colors) ... - -- our "elaboration function" that infuses syntax with semantics - @[command_elab colors] def elabColors : CommandElab := λ stx => Lean.logInfo "success!" - ``` - -4. Mathlib has a `#help option` command that displays all options available in the current environment, and their descriptions. `#help option pp.r` will display all options starting with a "pp.r" substring. - - Create a `syntax` rule that would accept the following commands: - - - `#better_help option` - - `#better_help option pp.r` - - `#better_help option some.other.name` - - Use the following template: - - ```lean - syntax (name := help) ... - -- our "elaboration function" that infuses syntax with semantics - @[command_elab help] def elabHelp : CommandElab := λ stx => Lean.logInfo "success!" - ``` - -5. Mathlib has a ∑ operator. Create a `syntax` rule that would accept the following terms: - - - `∑ x in { 1, 2, 3 }, x^2` - - `∑ x in { "apple", "banana", "cherry" }, x.length` - - Use the following template: - - ```lean - import Std.Classes.SetNotation - import Std.Util.ExtendedBinder - syntax (name := bigsumin) ... - -- our "elaboration function" that infuses syntax with semantics - @[term_elab bigsumin] def elabSum : TermElab := λ stx tp => return mkNatLit 666 - ``` - - Hint: use the `Std.ExtendedBinder.extBinder` parser. - Hint: you need Std4 installed in your Lean project for these imports to work. diff --git a/md/main/06_macros.md b/md/main/06_macros.md deleted file mode 100644 index c72a971..0000000 --- a/md/main/06_macros.md +++ /dev/null @@ -1,523 +0,0 @@ -# Macros - -## What is a macro -Macros in Lean are `Syntax → MacroM Syntax` functions. `MacroM` is the macro -monad which allows macros to have some static guarantees we will discuss in the -next section, you can mostly ignore it for now. - -Macros are registered as handlers for a specific syntax declaration using the -`macro` attribute. The compiler will take care of applying these function -to the syntax for us before performing actual analysis of the input. This -means that the only thing we have to do is declare our syntax with a specific -name and bind a function of type `Lean.Macro` to it. Let's try to reproduce -the `LXOR` notation from the `Syntax` chapter: - -```lean -import Lean - -open Lean - -syntax:10 (name := lxor) term:10 " LXOR " term:11 : term - -@[macro lxor] def lxorImpl : Macro - | `($l:term LXOR $r:term) => `(!$l && $r) -- we can use the quotation mechanism to create `Syntax` in macros - | _ => Macro.throwUnsupported - -#eval true LXOR true -- false -#eval true LXOR false -- false -#eval false LXOR true -- true -#eval false LXOR false -- false -``` - -That was quite easy! The `Macro.throwUnsupported` function can be used by a macro -to indicate that "it doesn't feel responsible for this syntax". In this case -it's merely used to fill a wildcard pattern that should never be reached anyways. - -However we can in fact register multiple macros for the same syntax this way -if we desire, they will be tried one after another (the later registered ones have -higher priority) -- is "higher" correct? -until one throws either a real error using `Macro.throwError` or succeeds, that -is it does not `Macro.throwUnsupported`. Let's see this in action: - -```lean -@[macro lxor] def lxorImpl2 : Macro - -- special case that changes behaviour of the case where the left and - -- right hand side are these specific identifiers - | `(true LXOR true) => `(true) - | _ => Macro.throwUnsupported - -#eval true LXOR true -- true, handled by new macro -#eval true LXOR false -- false, still handled by the old -``` - -This capability is obviously *very* powerful! It should not be used -lightly and without careful thinking since it can introduce weird -behaviour while writing code later on. The following example illustrates -this weird behaviour: - -```lean -#eval true LXOR true -- true, handled by new macro - -def foo := true -#eval foo LXOR foo -- false, handled by old macro, after all the identifiers have a different name -``` - -Without knowing exactly how this macro is implemented this behaviour -will be very confusing to whoever might be debugging an issue based on this. -The rule of thumb for when to use a macro vs. other mechanisms like -elaboration is that as soon as you are building real logic like in the 2nd -macro above, it should most likely not be a macro but an elaborator -(explained in the elaboration chapter). This means ideally we want to -use macros for simple syntax to syntax translations, that a human could -easily write out themselves as well but is too lazy to. - -## Simplifying macro declaration -Now that we know the basics of what a macro is and how to register it -we can take a look at slightly more automated ways to do this (in fact -all of the ways about to be presented are implemented as macros themselves). - -First things first there is `macro_rules` which basically desugars to -functions like the ones we wrote above, for example: - -```lean -syntax:10 term:10 " RXOR " term:11 : term - -macro_rules - | `($l:term RXOR $r:term) => `($l && !$r) -``` - -As you can see, it figures out lot's of things on its own for us: -- the name of the syntax declaration -- the `macro` attribute registration -- the `throwUnsupported` wildcard - -apart from this it just works like a function that is using pattern -matching syntax, we can in theory encode arbitrarily complex macro -functions on the right hand side. - -If this is still not short enough for you, there is a next step using the -`macro` macro: - -```lean -macro l:term:10 " ⊕ " r:term:11 : term => `((!$l && $r) || ($l && !$r)) - -#eval true ⊕ true -- false -#eval true ⊕ false -- true -#eval false ⊕ true -- true -#eval false ⊕ false -- false -``` - -As you can see, `macro` is quite close to `notation` already: -- it performed syntax declaration for us -- it automatically wrote a `macro_rules` style function to match on it - -The are of course differences as well: -- `notation` is limited to the `term` syntax category -- `notation` cannot have arbitrary macro code on the right hand side - -## `Syntax` Quotations -### The basics -So far we've handwaved the `` `(foo $bar) `` syntax to both create and -match on `Syntax` objects but it's time for a full explanation since -it will be essential to all non trivial things that are syntax related. - -First things first we call the `` `() `` syntax a `Syntax` quotation. -When we plug variables into a syntax quotation like this: `` `($x) `` -we call the `$x` part an anti-quotation. When we insert `x` like this -it is required that `x` is of type `TSyntax x` where `x` is some `Name` -of a syntax category. The Lean compiler is actually smart enough to figure -the syntax categories that are allowed in this place out. Hence you might -sometimes see errors of the form: -``` -application type mismatch - x.raw -argument - x -has type - TSyntax `a : Type -but is expected to have type - TSyntax `b : Type -``` -If you are sure that your thing from the `a` syntax category can be -used as a `b` here you can declare a coercion of the form: - -```lean -instance : Coe (TSyntax `a) (TSyntax `b) where - coe s := ⟨s.raw⟩ -``` - -Which will allow Lean to perform the type cast automatically. If you -notice that your `a` can not be used in place of the `b` here congrats, -you just discovered a bug in your `Syntax` function. Similar to the Lean -compiler, you could also declare functions that are specific to certain -`TSyntax` variants. For example as we have seen in the syntax chapter -there exists the function: - -```lean -#check TSyntax.getNat -- TSyntax.getNat : TSyntax numLitKind → Nat -``` - -Which is guaranteed to not panic because we know that the `Syntax` that -the function is receiving is a numeric literal and can thus naturally -be converted to a `Nat`. - -If we use the antiquotation syntax in pattern matching it will, as discussed -in the syntax chapter, give us a a variable `x` of type `` TSyntax y `` where -`y` is the `Name` of the syntax category that fits in the spot where we pattern matched. -If we wish to insert a literal `$x` into the `Syntax` for some reason, -for example macro creating macros, we can escape the anti quotation using: `` `($$x) ``. - -If we want to specify the syntax kind we wish `x` to be interpreted as -we can make this explicit using: `` `($x:term) `` where `term` can be -replaced with any other valid syntax category (e.g. `command`) or parser -(e.g. `ident`). - -So far this is only a more formal explanation of the intuitive things -we've already seen in the syntax chapter and up to now in this chapter, -next we'll discuss some more advanced anti-quotations. - -### Advanced anti-quotations -For convenience we can also use anti-quotations in a way similar to -format strings: `` `($(mkIdent `c)) `` is the same as: `` let x := mkIdent `c; `($x) ``. - -Furthermore there are sometimes situations in which we are not working -with basic `Syntax` but `Syntax` wrapped in more complex datastructures, -most notably `Array (TSyntax c)` or `TSepArray c s`. Where `TSepArray c s`, is a -`Syntax` specific type, it is what we get if we pattern match on some -`Syntax` that users a separator `s` to separate things from the category `c`. -For example if we match using: `$xs,*`, `xs` will have type `TSepArray c ","`,. -With the special case of matching on no specific separator (i.e. whitespace): -`$xs*` in which we will receive an `Array (TSyntax c)`. - -If we are dealing with `xs : Array (TSyntax c)` and want to insert it into -a quotation we have two main ways to achieve this: -1. Insert it using a separator, most commonly `,`: `` `($xs,*) ``. - This is also the way to insert a `TSepArray c ",""` -2. Insert it point blank without a separator (TODO): `` `() `` - -For example: - -```lean --- syntactically cut away the first element of a tuple if possible -syntax "cut_tuple " "(" term ", " term,+ ")" : term - -macro_rules - -- cutting away one element of a pair isn't possible, it would not result in a tuple - | `(cut_tuple ($x, $y)) => `(($x, $y)) - | `(cut_tuple ($x, $y, $xs,*)) => `(($y, $xs,*)) - -#check cut_tuple (1, 2) -- (1, 2) : Nat × Nat -#check cut_tuple (1, 2, 3) -- (2, 3) : Nat × Nat -``` - -The last thing for this section will be so called "anti-quotation splices". -There are two kinds of anti quotation splices, first the so called optional -ones. For example we might declare a syntax with an optional argument, -say our own `let` (in real projects this would most likely be a `let` -in some functional language we are writing a theory about): - -```lean -syntax "mylet " ident (" : " term)? " := " term " in " term : term -``` - -There is this optional `(" : " term)?` argument involved which can let -the user define the type of the term to the left of it. With the methods -we know so far we'd have to write two `macro_rules` now, one for the case -with, one for the case without the optional argument. However the rest -of the syntactic translation works exactly the same with and without -the optional argument so what we can do using a splice here is to essentially -define both cases at once: - -```lean -macro_rules - | `(mylet $x $[: $ty]? := $val in $body) => `(let $x $[: $ty]? := $val; $body) -``` - -The `$[...]?` part is the splice here, it basically says "if this part of -the syntax isn't there, just ignore the parts on the right hand side that -involve anti quotation variables involved here". So now we can run -this syntax both with and without type ascription: - -```lean -#eval mylet x := 5 in x - 10 -- 0, due to subtraction behaviour of `Nat` -#eval mylet x : Int := 5 in x - 10 -- -5, after all it is an `Int` now -``` - -The second and last splice might remind readers of list comprehension -as seen for example in Python. We will demonstrate it using an implementation -of `map` as a macro: - -```lean --- run the function given at the end for each element of the list -syntax "foreach " "[" term,* "]" term : term - -macro_rules - | `(foreach [ $[$x:term],* ] $func:term) => `(let f := $func; [ $[f $x],* ]) - -#eval foreach [1,2,3,4] (Nat.add 2) -- [3, 4, 5, 6] -``` - -In this case the `$[...],*` part is the splice. On the match side it tries -to match the pattern we define inside of it repetitively (given the separator -we tell it to). However unlike regular separator matching it does not -give us an `Array` or `SepArray`, instead it allows us to write another -splice on the right hand side that gets evaluated for each time the -pattern we specified matched, with the specific values from the match -per iteration. - -## Hygiene issues and how to solve them -If you are familiar with macro systems in other languages like C you -probably know about so called macro hygiene issues already. -A hygiene issue is when a macro introduces an identifier that collides with an -identifier from some syntax that it is including. For example: - -```lean --- Applying this macro produces a function that binds a new identifier `x`. -macro "const" e:term : term => `(fun x => $e) - --- But `x` can also be defined by a user -def x : Nat := 42 - --- Which `x` should be used by the compiler in place of `$e`? -#eval (const x) 10 -- 42 -``` - -Given the fact that macros perform only syntactic translations one might -expect the above `eval` to return 10 instead of 42: after all, the resulting -syntax should be `(fun x => x) 10`. While this was of course not the intention -of the author, this is what would happen in more primitive macro systems like -the one of C. So how does Lean avoid these hygiene issues? You can read -about this in detail in the excellent [Beyond Notations](https://lmcs.episciences.org/9362/pdf) -paper which discusses the idea and implementation in Lean in detail. -We will merely give an overview of the topic, since the details are not -that interesting for practical uses. The idea described in Beyond Notations -comes down to a concept called "macro scopes". Whenever a new macro -is invoked, a new macro scope (basically a unique number) is added to -a list of all the macro scopes that are active right now. When the current -macro introduces a new identifier what is actually getting added is an -identifier of the form: -``` -._@.(.)*.._hyg. -``` -For example, if the module name is `Init.Data.List.Basic`, the name is -`foo.bla`, and macros scopes are [2, 5] we get: -``` -foo.bla._@.Init.Data.List.Basic._hyg.2.5 -``` -Since macro scopes are unique numbers the list of macro scopes appended in the end -of the name will always be unique across all macro invocations, hence macro hygiene -issues like the ones above are not possible. - -If you are wondering why there is more than just the macro scopes to this -name generation, that is because we may have to combine scopes from different files/modules. -The main module being processed is always the right most one. -This situation may happen when we execute a macro generated in a file -imported in the current file. -``` -foo.bla._@.Init.Data.List.Basic.2.1.Init.Lean.Expr_hyg.4 -``` -The delimiter `_hyg` at the end is used just to improve performance of -the function `Lean.Name.hasMacroScopes` -- the format could also work without it. - -This was a lot of technical details. You do not have to understand them -in order to use macros, if you want you can just keep in mind that Lean -will not allow name clashes like the one in the `const` example. - -Note that this extends to *all* names that are introduced using syntax -quotations, that is if you write a macro that produces: -`` `(def foo := 1) ``, the user will not be able to access `foo` -because the name will subject to hygiene. Luckily there is a way to -circumvent this. You can use `mkIdent` to generate a raw identifier, -for example: `` `(def $(mkIdent `foo) := 1) ``. In this case it won't -be subject to hygiene and accessible to the user. - -## `MonadQuotation` and `MonadRef` -Based on this description of the hygiene mechanism one interesting -question pops up, how do we know what the current list of macro scopes -actually is? After all in the macro functions that were defined above -there is never any explicit passing around of the scopes happening. -As is quite common in functional programming, as soon as we start -having some additional state that we need to bookkeep (like the macro scopes) -this is done with a monad, this is the case here as well with a slight twist. - -Instead of implementing this for only a single monad `MacroM` the general -concept of keeping track of macro scopes in monadic way is abstracted -away using a type class called `MonadQuotation`. This allows any other -monad to also easily provide this hygienic `Syntax` creation mechanism -by simply implementing this type class. - -This is also the reason that while we are able to use pattern matching on syntax -with `` `(syntax) `` we cannot just create `Syntax` with the same -syntax in pure functions: there is no `Monad` implementing `MonadQuotation` -involved in order to keep track of the macro scopes. - -Now let's take a brief look at the `MonadQuotation` type class: - -```lean -namespace Playground - -class MonadRef (m : Type → Type) where - getRef : m Syntax - withRef {α} : Syntax → m α → m α - -class MonadQuotation (m : Type → Type) extends MonadRef m where - getCurrMacroScope : m MacroScope - getMainModule : m Name - withFreshMacroScope {α : Type} : m α → m α - -end Playground -``` - -Since `MonadQuotation` is based on `MonadRef`, let's take a look at `MonadRef` -first. The idea here is quite simple: `MonadRef` is meant to be seen as an extension -to the `Monad` typeclass which -- gives us a reference to a `Syntax` value with `getRef` -- can evaluate a certain monadic action `m α` with a new reference to a `Syntax` - using `withRef` - -On it's own `MonadRef` isn't exactly interesting, but once it is combined with -`MonadQuotation` it makes sense. - -As you can see `MonadQuotation` extends `MonadRef` and adds 3 new functions: -- `getCurrMacroScope` which obtains the latest `MacroScope` that was created -- `getMainModule` which (obviously) obtains the name of the main module, - both of these are used to create these hygienic identifiers explained above -- `withFreshMacroScope` which will compute the next macro scope and run - some computation `m α` that performs syntax quotation with this new - macro scope in order to avoid name clashes. While this is mostly meant - to be used internally whenever a new macro invocation happens, it can sometimes - make sense to use this in our own macros, for example when we are generating - some syntax block repeatedly and want to avoid name clashes. - -How `MonadRef` comes into play here is that Lean requires a way to indicate -errors at certain positions to the user. One thing that wasn't introduced -in the `Syntax` chapter is that values of type `Syntax` actually carry their -position in the file around as well. When an error is detected, it is usually -bound to a `Syntax` value which tells Lean where to indicate the error in the file. -What Lean will do when using `withFreshMacroScope` is to apply the position of -the result of `getRef` to each introduced symbol, which then results in better -error positions than not applying any position. - -To see error positioning in action, we can write a little macro that makes use of it: - -```lean -syntax "error_position" ident : term - -macro_rules - | `(error_position all) => Macro.throwError "Ahhh" - -- the `%$tk` syntax gives us the Syntax of the thing before the %, - -- in this case `error_position`, giving it the name `tk` - | `(error_position%$tk first) => withRef tk (Macro.throwError "Ahhh") - -#eval error_position all -- the error is indicated at `error_position all` -#eval error_position first -- the error is only indicated at `error_position` -``` - -Obviously controlling the positions of errors in this way is quite important -for a good user experience. - -## Mini project -As a final mini project for this section we will re-build the arithmetic -DSL from the syntax chapter in a slightly more advanced way, using a macro -this time so we can actually fully integrate it into the Lean syntax. - -```lean -declare_syntax_cat arith - -syntax num : arith -syntax arith "-" arith : arith -syntax arith "+" arith : arith -syntax "(" arith ")" : arith -syntax "[Arith|" arith "]" : term - -macro_rules - | `([Arith| $x:num]) => `($x) - | `([Arith| $x:arith + $y:arith]) => `([Arith| $x] + [Arith| $y]) -- recursive macros are possible - | `([Arith| $x:arith - $y:arith]) => `([Arith| $x] - [Arith| $y]) - | `([Arith| ($x:arith)]) => `([Arith| $x]) - -#eval [Arith| (12 + 3) - 4] -- 11 -``` - -Again feel free to play around with it. If you want to build more complex -things, like expressions with variables, maybe consider building an inductive type -using macros instead. Once you got your arithmetic expression term -as an inductive, you could then write a function that takes some form of -variable assignment and evaluates the given expression for this -assignment. You could also try to embed arbitrary `term`s into your -arith language using some special syntax or whatever else comes to your mind. - -## More elaborate examples -### Binders 2.0 -As promised in the syntax chapter here is Binders 2.0. We'll start by -reintroducing our theory of sets: - -```lean -def Set (α : Type u) := α → Prop -def Set.mem (x : α) (X : Set α) : Prop := X x - --- Integrate into the already existing typeclass for membership notation -instance : Membership α (Set α) where - mem := Set.mem - -def Set.empty : Set α := λ _ => False - --- the basic "all elements such that" function for the notation -def setOf {α : Type} (p : α → Prop) : Set α := p -``` - -The goal for this section will be to allow for both `{x : X | p x}` -and `{x ∈ X, p x}` notations. In principle there are two ways to do this: -1. Define a syntax and macro for each way to bind a variable we might think of -2. Define a syntax category of binders that we could reuse across other - binder constructs such as `Σ` or `Π` as well and implement macros for - the `{ | }` case - -In this section we will use approach 2 because it is more easily reusable. - -```lean -declare_syntax_cat binder_construct -syntax "{" binder_construct "|" term "}" : term -``` - -Now let's define the two binders constructs we are interested in: - -```lean -syntax ident " : " term : binder_construct -syntax ident " ∈ " term : binder_construct -``` - -And finally the macros to expand our syntax: - -```lean -macro_rules - | `({ $var:ident : $ty:term | $body:term }) => `(setOf (fun ($var : $ty) => $body)) - | `({ $var:ident ∈ $s:term | $body:term }) => `(setOf (fun $var => $var ∈ $s ∧ $body)) - --- Old examples with better syntax: -#check { x : Nat | x ≤ 1 } -- setOf fun x => x ≤ 1 : Set Nat - -example : 1 ∈ { y : Nat | y ≤ 1 } := by simp[Membership.mem, Set.mem, setOf] -example : 2 ∈ { y : Nat | y ≤ 3 ∧ 1 ≤ y } := by simp[Membership.mem, Set.mem, setOf] - --- New examples: -def oneSet : Set Nat := λ x => x = 1 -#check { x ∈ oneSet | 10 ≤ x } -- setOf fun x => x ∈ oneSet ∧ 10 ≤ x : Set Nat - -example : ∀ x, ¬(x ∈ { y ∈ oneSet | y ≠ 1 }) := by - intro x h - -- h : x ∈ setOf fun y => y ∈ oneSet ∧ y ≠ 1 - -- ⊢ False - cases h - -- : x ∈ oneSet - -- : x ≠ 1 - contradiction -``` - -## Reading further -If you want to know more about macros you can read: -- the API docs: TODO link -- the source code: the lower parts of [Init.Prelude](https://github.com/leanprover/lean4/blob/master/src/Init/Prelude.lean) - as you can see they are declared quite early in Lean because of their importance - to building up syntax -- the aforementioned [Beyond Notations](https://lmcs.episciences.org/9362/pdf) paper diff --git a/md/main/07_elaboration.md b/md/main/07_elaboration.md deleted file mode 100644 index 92c94dd..0000000 --- a/md/main/07_elaboration.md +++ /dev/null @@ -1,396 +0,0 @@ -# Elaboration - -The elaborator is the component in charge of turning the user facing -`Syntax` into something with which the rest of the compiler can work. -Most of the time, this means translating `Syntax` into `Expr`s but -there are also other use cases such as `#check` or `#eval`. Hence the -elaborator is quite a large piece of code, it lives -[here](https://github.com/leanprover/lean4/blob/master/src/Lean/Elab). - -## Command elaboration -A command is the highest level of `Syntax`, a Lean file is made -up of a list of commands. The most commonly used commands are declarations, -for example: -- `def` -- `inductive` -- `structure` - -but there are also other ones, most notably `#check`, `#eval` and friends. -All commands live in the `command` syntax category so in order to declare -custom commands, their syntax has to be registered in that category. - -### Giving meaning to commands -The next step is giving some semantics to the syntax. With commands, this -is done by registering a so called command elaborator. - -Command elaborators have type `CommandElab` which is an alias for: -`Syntax → CommandElabM Unit`. What they do, is take the `Syntax` that -represents whatever the user wants to call the command and produce some -sort of side effect on the `CommandElabM` monad, after all the return -value is always `Unit`. The `CommandElabM` monad has 4 main kinds of -side effects: -1. Logging messages to the user via the `Monad` extensions - `MonadLog` and `AddMessageContext`, like `#check`. This is done via - functions that can be found in `Lean.Elab.Log`, the most notable ones - being: `logInfo`, `logWarning` and `logError`. -2. Interacting with the `Environment` via the `Monad` extension `MonadEnv`. - This is the place where all of the relevant information for the compiler - is stored, all known declarations, their types, doc-strings, values etc. - The current environment can be obtained via `getEnv` and set via `setEnv` - once it has been modified. Note that quite often wrappers around `setEnv` - like `addDecl` are the correct way to add information to the `Environment`. -3. Performing `IO`, `CommandElabM` is capable of running any `IO` operation. - For example reading from files and based on their contents perform - declarations. -4. Throwing errors, since it can run any kind of `IO`, it is only natural - that it can throw errors via `throwError`. - -Furthermore there are a bunch of other `Monad` extensions that are supported -by `CommandElabM`: -- `MonadRef` and `MonadQuotation` for `Syntax` quotations like in macros -- `MonadOptions` to interact with the options framework -- `MonadTrace` for debug trace information -- TODO: There are a few others though I'm not sure whether they are relevant, - see the instance in `Lean.Elab.Command` - -### Command elaboration -Now that we understand the type of command elaborators let's take a brief -look at how the elaboration process actually works: -1. Check whether any macros can be applied to the current `Syntax`. - If there is a macro that does apply and does not throw an error - the resulting `Syntax` is recursively elaborated as a command again. -2. If no macro can be applied, we search for all `CommandElab`s that have been - registered for the `SyntaxKind` of the `Syntax` we are elaborating, - using the `command_elab` attribute. -3. All of these `CommandElab` are then tried in order until one of them does not throw an - `unsupportedSyntaxException`, Lean's way of indicating that the elaborator - "feels responsible" - for this specific `Syntax` construct. Note that it can still throw a regular - error to indicate to the user that something is wrong. If no responsible - elaborator is found, then the command elaboration is aborted with an `unexpected syntax` - error message. - -As you can see the general idea behind the procedure is quite similar to ordinary macro expansion. - -### Making our own -Now that we know both what a `CommandElab` is and how they are used, we can -start looking into writing our own. The steps for this, as we learned above, are: -1. Declaring the syntax -2. Declaring the elaborator -3. Registering the elaborator as responsible for the syntax via the `command_elab` - attribute. - -Let's see how this is done: - -```lean -import Lean - -open Lean Elab Command Term Meta - -syntax (name := mycommand1) "#mycommand1" : command -- declare the syntax - -@[command_elab mycommand1] -def mycommand1Impl : CommandElab := fun stx => do -- declare and register the elaborator - logInfo "Hello World" - -#mycommand1 -- Hello World -``` - -You might think that this is a little boiler-platey and it turns out the Lean -devs did as well so they added a macro for this! - -```lean -elab "#mycommand2" : command => - logInfo "Hello World" - -#mycommand2 -- Hello World -``` - -Note that, due to the fact that command elaboration supports multiple -registered elaborators for the same syntax, we can in fact overload -syntax, if we want to. - -```lean -@[command_elab mycommand1] -def myNewImpl : CommandElab := fun stx => do - logInfo "new!" - -#mycommand1 -- new! -``` - -Furthermore it is also possible to only overload parts of syntax by -throwing an `unsupportedSyntaxException` in the cases we want the default -handler to deal with it or just letting the `elab` command handle it. - -In the following example, we are not extending the original `#check` syntax, -but adding a new `SyntaxKind` for this specific syntax construct. -However, from the point of view of the user, the effect is basically the same. - -```lean -elab "#check" "mycheck" : command => do - logInfo "Got ya!" -``` - -This is actually extending the original `#check` - -```lean -@[command_elab Lean.Parser.Command.check] def mySpecialCheck : CommandElab := fun stx => do - if let some str := stx[1].isStrLit? then - logInfo s!"Specially elaborated string literal!: {str} : String" - else - throwUnsupportedSyntax - -#check mycheck -- Got ya! -#check "Hello" -- Specially elaborated string literal!: Hello : String -#check Nat.add -- Nat.add : Nat → Nat → Nat -``` - -### Mini project -As a final mini project for this section let's build a command elaborator -that is actually useful. It will take a command and use the same mechanisms -as `elabCommand` (the entry point for command elaboration) to tell us -which macros or elaborators are relevant to the command we gave it. - -We will not go through the effort of actually reimplementing `elabCommand` though - -```lean -elab "#findCElab " c:command : command => do - let macroRes ← liftMacroM <| expandMacroImpl? (←getEnv) c - match macroRes with - | some (name, _) => logInfo s!"Next step is a macro: {name.toString}" - | none => - let kind := c.raw.getKind - let elabs := commandElabAttribute.getEntries (←getEnv) kind - match elabs with - | [] => logInfo s!"There is no elaborators for your syntax, looks like its bad :(" - | _ => logInfo s!"Your syntax may be elaborated by: {elabs.map (fun el => el.declName.toString)}" - -#findCElab def lala := 12 -- Your syntax may be elaborated by: [Lean.Elab.Command.elabDeclaration] -#findCElab abbrev lolo := 12 -- Your syntax may be elaborated by: [Lean.Elab.Command.elabDeclaration] -#findCElab #check foo -- even our own syntax!: Your syntax may be elaborated by: [mySpecialCheck, Lean.Elab.Command.elabCheck] -#findCElab open Hi -- Your syntax may be elaborated by: [Lean.Elab.Command.elabOpen] -#findCElab namespace Foo -- Your syntax may be elaborated by: [Lean.Elab.Command.elabNamespace] -#findCElab #findCElab open Bar -- even itself!: Your syntax may be elaborated by: [«_aux_lean_elaboration___elabRules_command#findCElab__1»] -``` - -TODO: Maybe we should also add a mini project that demonstrates a -non # style command aka a declaration, although nothing comes to mind right now. -TODO: Define a `conjecture` declaration, similar to `lemma/theorem`, except that -it is automatically sorried. The `sorry` could be a custom one, to reflect that -the "conjecture" might be expected to be true. - -## Term elaboration -A term is a `Syntax` object that represents some sort of `Expr`. -Term elaborators are the ones that do the work for most of the code we write. -Most notably they elaborate all the values of things like definitions, -types (since these are also just `Expr`) etc. - -All terms live in the `term` syntax category (which we have seen in action -in the macro chapter already). So, in order to declare custom terms, their -syntax needs to be registered in that category. - -### Giving meaning to terms -As with command elaboration, the next step is giving some semantics to the syntax. -With terms, this is done by registering a so called term elaborator. - -Term elaborators have type `TermElab` which is an alias for: -`Syntax → Option Expr → TermElabM Expr`. This type is already -quite different from command elaboration: -- As with command elaboration the `Syntax` is whatever the user used - to create this term -- The `Option Expr` is the expected type of the term, since this cannot - always be known it is only an `Option` argument -- Unlike command elaboration, term elaboration is not only executed - because of its side effects -- the `TermElabM Expr` return value does - actually contain something of interest, namely, the `Expr` that represents - the `Syntax` object. - -`TermElabM` is basically an upgrade of `CommandElabM` in every regard: -it supports all the capabilities we mentioned above, plus two more. -The first one is quite simple: On top of running `IO` code it is also -capable of running `MetaM` code, so `Expr`s can be constructed nicely. -The second one is very specific to the term elaboration loop. - -### Term elaboration -The basic idea of term elaboration is the same as command elaboration: -expand macros and recurse or run term elaborators that have been registered -for the `Syntax` via the `term_elab` attribute (they might in turn run term elaboration) -until we are done. There is, however, one special action that a term elaborator -can do during its execution. - -A term elaborator may throw `Except.postpone`. This indicates that -the term elaborator requires more -information to continue its work. In order to represent this missing information, -Lean uses so called synthetic metavariables. As you know from before, metavariables -are holes in `Expr`s that are waiting to be filled in. Synthetic metavariables are -different in that they have special methods that are used to solve them, -registered in `SyntheticMVarKind`. Right now, there are four of these: -- `typeClass`, the metavariable should be solved with typeclass synthesis -- `coe`, the metavariable should be solved via coercion (a special case of typeclass) -- `tactic`, the metavariable is a tactic term that should be solved by running a tactic -- `postponed`, the ones that are created at `Except.postpone` - -Once such a synthetic metavariable is created, the next higher level term elaborator will continue. -At some point, execution of postponed metavariables will be resumed by the term elaborator, -in hopes that it can now complete its execution. We can try to see this in -action with the following example: - -```lean -#check set_option trace.Elab.postpone true in List.foldr .add 0 [1,2,3] -- [Elab.postpone] .add : ?m.5695 → ?m.5696 → ?m.5696 -``` - -What happened here is that the elaborator for function applications started -at `List.foldr` which is a generic function so it created metavariables -for the implicit type parameters. Then, it attempted to elaborate the first argument `.add`. - -In case you don't know how `.name` works, the basic idea is that quite -often (like in this case) Lean should be able to infer the output type (in this case `Nat`) -of a function (in this case `Nat.add`). In such cases, the `.name` feature will then simply -search for a function named `name` in the namespace `Nat`. This is especially -useful when you want to use constructors of a type without referring to its -namespace or opening it, but can also be used like above. - -Now back to our example, while Lean does at this point already know that `.add` -needs to have type: `?m1 → ?m2 → ?m2` (where `?x` is notation for a metavariable) -the elaborator for `.add` does need to know the actual value of `?m2` so the -term elaborator postpones execution (by internally creating a synthetic metavariable -in place of `.add`), the elaboration of the other two arguments then yields the fact that -`?m2` has to be `Nat` so once the `.add` elaborator is continued it can work with -this information to complete elaboration. - -We can also easily provoke cases where this does not work out. For example: - -```lean -#check set_option trace.Elab.postpone true in List.foldr .add --- [Elab.postpone] .add : ?m.5808 → ?m.5809 → ?m.5809 --- invalid dotted identifier notation, expected type is not of the form (... → C ...) where C is a constant - -- ?m.5808 → ?m.5809 → ?m.5809 -``` - -In this case `.add` first postponed its execution, then got called again -but didn't have enough information to finish elaboration and thus failed. - -### Making our own -Adding new term elaborators works basically the same way as adding new -command elaborators so we'll only take a very brief look: - -```lean -syntax (name := myterm1) "myterm 1" : term - -def mytermValues := [1, 2] - -@[term_elab myterm1] -def myTerm1Impl : TermElab := fun stx type? => - mkAppM ``List.get! #[.const ``mytermValues [], mkNatLit 0] -- `MetaM` code - -#eval myterm 1 -- 1 - --- Also works with `elab` -elab "myterm 2" : term => do - mkAppM ``List.get! #[.const ``mytermValues [], mkNatLit 1] -- `MetaM` code - -#eval myterm 2 -- 2 -``` - -### Mini project -As a final mini project for this chapter we will recreate one of the most -commonly used Lean syntax sugars, the `⟨a,b,c⟩` notation as a short hand -for single constructor types: - -```lean --- slightly different notation so no ambiguity happens -syntax (name := myanon) "⟨⟨" term,* "⟩⟩" : term - -def getCtors (typ : Name) : MetaM (List Name) := do - let env ← getEnv - match env.find? typ with - | some (ConstantInfo.inductInfo val) => - pure val.ctors - | _ => pure [] - -@[term_elab myanon] -def myanonImpl : TermElab := fun stx typ? => do - -- Attempt to postpone execution if the type is not known or is a metavariable. - -- Metavariables are used by things like the function elaborator to fill - -- out the values of implicit parameters when they haven't gained enough - -- information to figure them out yet. - -- Term elaborators can only postpone execution once, so the elaborator - -- doesn't end up in an infinite loop. Hence, we only try to postpone it, - -- otherwise we may cause an error. - tryPostponeIfNoneOrMVar typ? - -- If we haven't found the type after postponing just error - let some typ := typ? | throwError "expected type must be known" - if typ.isMVar then - throwError "expected type must be known" - let Expr.const base .. := typ.getAppFn | throwError s!"type is not of the expected form: {typ}" - let [ctor] ← getCtors base | throwError "type doesn't have exactly one constructor" - let args := TSyntaxArray.mk stx[1].getSepArgs - let stx ← `($(mkIdent ctor) $args*) -- syntax quotations - elabTerm stx typ -- call term elaboration recursively - -#check (⟨⟨1, sorry⟩⟩ : Fin 12) -- { val := 1, isLt := (_ : 1 < 12) } : Fin 12 -#check ⟨⟨1, sorry⟩⟩ -- expected type must be known -#check (⟨⟨0⟩⟩ : Nat) -- type doesn't have exactly one constructor -#check (⟨⟨⟩⟩ : Nat → Nat) -- type is not of the expected form: Nat -> Nat -``` - -As a final note, we can shorten the postponing act by using an additional -syntax sugar of the `elab` syntax instead: - -```lean --- This `t` syntax will effectively perform the first two lines of `myanonImpl` -elab "⟨⟨" args:term,* "⟩⟩" : term <= t => do - sorry -``` - -## Exercises - -1. Consider the following code. Rewrite `syntax` + `@[term_elab hi]... : TermElab` combination using just `elab`. - - ```lean - syntax (name := hi) term " ♥ " " ♥ "? " ♥ "? : term - - @[term_elab hi] - def heartElab : TermElab := fun stx tp => - match stx with - | `($l:term ♥) => do - let nExpr ← elabTermEnsuringType l (mkConst `Nat) - return Expr.app (Expr.app (Expr.const `Nat.add []) nExpr) (mkNatLit 1) - | `($l:term ♥♥) => do - let nExpr ← elabTermEnsuringType l (mkConst `Nat) - return Expr.app (Expr.app (Expr.const `Nat.add []) nExpr) (mkNatLit 2) - | `($l:term ♥♥♥) => do - let nExpr ← elabTermEnsuringType l (mkConst `Nat) - return Expr.app (Expr.app (Expr.const `Nat.add []) nExpr) (mkNatLit 3) - | _ => - throwUnsupportedSyntax - ``` - -2. Here is some syntax taken from a real mathlib command `alias`. - - ``` - syntax (name := our_alias) (docComment)? "our_alias " ident " ← " ident* : command - ``` - - We want `alias hi ← hello yes` to print out the identifiers after `←` - that is, "hello" and "yes". - - Please add these semantics: - - **a)** using `syntax` + `@[command_elab alias] def elabOurAlias : CommandElab`. - **b)** using `syntax` + `elab_rules`. - **c)** using `elab`. - -3. Here is some syntax taken from a real mathlib tactic `nth_rewrite`. - - ```lean - open Parser.Tactic - syntax (name := nthRewriteSeq) "nth_rewrite " (config)? num rwRuleSeq (ppSpace location)? : tactic - ``` - - We want `nth_rewrite 5 [←add_zero a] at h` to print out `"rewrite location!"` if the user provided location, and `"rewrite target!"` if the user didn't provide location. - - Please add these semantics: - - **a)** using `syntax` + `@[tactic nthRewrite] def elabNthRewrite : Lean.Elab.Tactic.Tactic`. - **b)** using `syntax` + `elab_rules`. - **c)** using `elab`. diff --git a/md/main/08_dsls.md b/md/main/08_dsls.md deleted file mode 100644 index fd0c93a..0000000 --- a/md/main/08_dsls.md +++ /dev/null @@ -1,239 +0,0 @@ -# Embedding DSLs By Elaboration - -In this chapter we will learn how to use elaboration to build a DSL. We will not -explore the full power of `MetaM`, and simply gesture at how to get access to -this low-level machinery. - -More precisely, we shall enable Lean to understand the syntax of -[IMP](http://concrete-semantics.org/concrete-semantics.pdf), -which is a simple imperative language, often used for teaching operational and -denotational semantics. - -We are not going to define everything with the same encoding that the book does. -For instance, the book defines arithmetic expressions and boolean expressions. -We, will take a different path and just define generic expressions that take -unary or binary operators. - -This means that we will allow weirdnesses like `1 + true`! But it will simplify -the encoding, the grammar and consequently the metaprogramming didactic. - -## Defining our AST - -We begin by defining our atomic literal value. - -```lean -import Lean - -open Lean Elab Meta - -inductive IMPLit - | nat : Nat → IMPLit - | bool : Bool → IMPLit -``` - -This is our only unary operator - -```lean -inductive IMPUnOp - | not -``` - -These are our binary operations. - -```lean -inductive IMPBinOp - | and | add | less -``` - -Now we define the expressions that we want to handle. - -```lean -inductive IMPExpr - | lit : IMPLit → IMPExpr - | var : String → IMPExpr - | un : IMPUnOp → IMPExpr → IMPExpr - | bin : IMPBinOp → IMPExpr → IMPExpr → IMPExpr -``` - -And finally the commands of our language. Let's follow the book and say that -"each piece of a program is also a program": - -```lean -inductive IMPProgram - | Skip : IMPProgram - | Assign : String → IMPExpr → IMPProgram - | Seq : IMPProgram → IMPProgram → IMPProgram - | If : IMPExpr → IMPProgram → IMPProgram → IMPProgram - | While : IMPExpr → IMPProgram → IMPProgram -``` - -## Elaborating literals - -Now that we have our data types, let's elaborate terms of `Syntax` into -terms of `Expr`. We begin by defining the syntax and an elaboration function for -literals. - -```lean -declare_syntax_cat imp_lit -syntax num : imp_lit -syntax "true" : imp_lit -syntax "false" : imp_lit - -def elabIMPLit : Syntax → MetaM Expr - -- `mkAppM` creates an `Expr.app`, given the function `Name` and the args - -- `mkNatLit` creates an `Expr` from a `Nat` - | `(imp_lit| $n:num) => mkAppM ``IMPLit.nat #[mkNatLit n.getNat] - | `(imp_lit| true ) => mkAppM ``IMPLit.bool #[.const ``Bool.true []] - | `(imp_lit| false ) => mkAppM ``IMPLit.bool #[.const ``Bool.false []] - | _ => throwUnsupportedSyntax - -elab "test_elabIMPLit " l:imp_lit : term => elabIMPLit l - -#reduce test_elabIMPLit 4 -- IMPLit.nat 4 -#reduce test_elabIMPLit true -- IMPLit.bool true -#reduce test_elabIMPLit false -- IMPLit.bool true -``` - -## Elaborating expressions - -In order to elaborate expressions, we also need a way to elaborate our unary and -binary operators. - -Notice that these could very much be pure functions (`Syntax → Expr`), but we're -staying in `MetaM` because it allows us to easily throw an error for match -completion. - -```lean -declare_syntax_cat imp_unop -syntax "not" : imp_unop - -def elabIMPUnOp : Syntax → MetaM Expr - | `(imp_unop| not) => return .const ``IMPUnOp.not [] - | _ => throwUnsupportedSyntax - -declare_syntax_cat imp_binop -syntax "+" : imp_binop -syntax "and" : imp_binop -syntax "<" : imp_binop - -def elabIMPBinOp : Syntax → MetaM Expr - | `(imp_binop| +) => return .const ``IMPBinOp.add [] - | `(imp_binop| and) => return .const ``IMPBinOp.and [] - | `(imp_binop| <) => return .const ``IMPBinOp.less [] - | _ => throwUnsupportedSyntax -``` - -Now we define the syntax for expressions: - -```lean -declare_syntax_cat imp_expr -syntax imp_lit : imp_expr -syntax ident : imp_expr -syntax imp_unop imp_expr : imp_expr -syntax imp_expr imp_binop imp_expr : imp_expr -``` - -Let's also allow parentheses so the IMP programmer can denote their parsing -precedence. - -```lean -syntax "(" imp_expr ")" : imp_expr -``` - -Now we can elaborate our expressions. Note that expressions can be recursive. -This means that our `elabIMPExpr` function will need to be recursive! We'll need -to use `partial` because Lean can't prove the termination of `Syntax` -consumption alone. - -```lean -partial def elabIMPExpr : Syntax → MetaM Expr - | `(imp_expr| $l:imp_lit) => do - let l ← elabIMPLit l - mkAppM ``IMPExpr.lit #[l] - -- `mkStrLit` creates an `Expr` from a `String` - | `(imp_expr| $i:ident) => mkAppM ``IMPExpr.var #[mkStrLit i.getId.toString] - | `(imp_expr| $b:imp_unop $e:imp_expr) => do - let b ← elabIMPUnOp b - let e ← elabIMPExpr e -- recurse! - mkAppM ``IMPExpr.un #[b, e] - | `(imp_expr| $l:imp_expr $b:imp_binop $r:imp_expr) => do - let l ← elabIMPExpr l -- recurse! - let r ← elabIMPExpr r -- recurse! - let b ← elabIMPBinOp b - mkAppM ``IMPExpr.bin #[b, l, r] - | `(imp_expr| ($e:imp_expr)) => elabIMPExpr e - | _ => throwUnsupportedSyntax - -elab "test_elabIMPExpr " e:imp_expr : term => elabIMPExpr e - -#reduce test_elabIMPExpr a --- IMPExpr.var "a" - -#reduce test_elabIMPExpr a + 5 --- IMPExpr.bin IMPBinOp.add (IMPExpr.var "a") (IMPExpr.lit (IMPLit.nat 5)) - -#reduce test_elabIMPExpr 1 + true --- IMPExpr.bin IMPBinOp.add (IMPExpr.lit (IMPLit.nat 1)) (IMPExpr.lit (IMPLit.bool true)) -``` - -## Elaborating programs - -And now we have everything we need to elaborate our IMP programs! - -```lean -declare_syntax_cat imp_program -syntax "skip" : imp_program -syntax ident ":=" imp_expr : imp_program - -syntax imp_program ";;" imp_program : imp_program - -syntax "if" imp_expr "then" imp_program "else" imp_program "fi" : imp_program -syntax "while" imp_expr "do" imp_program "od" : imp_program - -partial def elabIMPProgram : Syntax → MetaM Expr - | `(imp_program| skip) => return .const ``IMPProgram.Skip [] - | `(imp_program| $i:ident := $e:imp_expr) => do - let i : Expr := mkStrLit i.getId.toString - let e ← elabIMPExpr e - mkAppM ``IMPProgram.Assign #[i, e] - | `(imp_program| $p₁:imp_program ;; $p₂:imp_program) => do - let p₁ ← elabIMPProgram p₁ - let p₂ ← elabIMPProgram p₂ - mkAppM ``IMPProgram.Seq #[p₁, p₂] - | `(imp_program| if $e:imp_expr then $pT:imp_program else $pF:imp_program fi) => do - let e ← elabIMPExpr e - let pT ← elabIMPProgram pT - let pF ← elabIMPProgram pF - mkAppM ``IMPProgram.If #[e, pT, pF] - | `(imp_program| while $e:imp_expr do $pT:imp_program od) => do - let e ← elabIMPExpr e - let pT ← elabIMPProgram pT - mkAppM ``IMPProgram.While #[e, pT] - | _ => throwUnsupportedSyntax -``` - -And we can finally test our full elaboration pipeline. Let's use the following -syntax: - -```lean -elab ">>" p:imp_program "<<" : term => elabIMPProgram p - -#reduce >> -a := 5;; -if not a and 3 < 4 then - c := 5 -else - a := a + 1 -fi;; -b := 10 -<< --- IMPProgram.Seq (IMPProgram.Assign "a" (IMPExpr.lit (IMPLit.nat 5))) --- (IMPProgram.Seq --- (IMPProgram.If --- (IMPExpr.un IMPUnOp.not --- (IMPExpr.bin IMPBinOp.and (IMPExpr.var "a") --- (IMPExpr.bin IMPBinOp.less (IMPExpr.lit (IMPLit.nat 3)) (IMPExpr.lit (IMPLit.nat 4))))) --- (IMPProgram.Assign "c" (IMPExpr.lit (IMPLit.nat 5))) --- (IMPProgram.Assign "a" (IMPExpr.bin IMPBinOp.add (IMPExpr.var "a") (IMPExpr.lit (IMPLit.nat 1))))) --- (IMPProgram.Assign "b" (IMPExpr.lit (IMPLit.nat 10)))) -``` diff --git a/md/main/09_tactics.md b/md/main/09_tactics.md deleted file mode 100644 index 5c0960a..0000000 --- a/md/main/09_tactics.md +++ /dev/null @@ -1,719 +0,0 @@ -# Tactics - -Tactics are Lean programs that manipulate a custom state. All tactics are, in -the end, of type `TacticM Unit`. This has the type: - -```lean --- from Lean/Elab/Tactic/Basic.lean -TacticM = ReaderT Context $ StateRefT State TermElabM -``` - -But before demonstrating how to use `TacticM`, we shall explore macro-based -tactics. - -## Tactics by Macro Expansion - -Just like many other parts of the Lean 4 infrastructure, tactics too can be -declared by lightweight macro expansion. - -For example, we build an example of a `custom_sorry_macro` that elaborates into -a `sorry`. We write this as a macro expansion, which expands the piece of syntax -`custom_sorry_macro` into the piece of syntax `sorry`: - -```lean -import Lean.Elab.Tactic - -macro "custom_sorry_macro" : tactic => `(tactic| sorry) - -example : 1 = 42 := by - custom_sorry_macro -``` - -### Implementing `trivial`: Extensible Tactics by Macro Expansion - -As more complex examples, we can write a tactic such as `custom_tactic`, which -is initially completely unimplemented, and can be extended with more tactics. -We start by simply declaring the tactic with no implementation: - -```lean -syntax "custom_tactic" : tactic - -example : 42 = 42 := by - custom_tactic --- tactic 'tacticCustom_tactic' has not been implemented - sorry -``` - -We will now add the `rfl` tactic into `custom_tactic`, which will allow us to -prove the previous theorem - -```lean -macro_rules -| `(tactic| custom_tactic) => `(tactic| rfl) - -example : 42 = 42 := by - custom_tactic --- Goals accomplished 🎉 -``` - -We can now try a harder problem, that cannot be immediately dispatched by `rfl`: - -```lean -example : 43 = 43 ∧ 42 = 42:= by - custom_tactic --- tactic 'rfl' failed, equality expected --- 43 = 43 ∧ 42 = 42 --- ⊢ 43 = 43 ∧ 42 = 42 -``` - -We extend the `custom_tactic` tactic with a tactic that tries to break `And` -down with `apply And.intro`, and then (recursively (!)) applies `custom_tactic` -to the two cases with `(<;> trivial)` to solve the generated subcases `43 = 43`, -`42 = 42`. - -```lean -macro_rules -| `(tactic| custom_tactic) => `(tactic| apply And.intro <;> custom_tactic) -``` - -The above declaration uses `<;>` which is a *tactic combinator*. Here, `a <;> b` -means "run tactic `a`, and apply "b" to each goal produced by `a`". Thus, -`And.intro <;> custom_tactic` means "run `And.intro`, and then run -`custom_tactic` on each goal". We test it out on our previous theorem and see -that we dispatch the theorem. - -```lean -example : 43 = 43 ∧ 42 = 42 := by - custom_tactic --- Goals accomplished 🎉 -``` - -In summary, we declared an extensible tactic called `custom_tactic`. It -initially had no elaboration at all. We added the `rfl` as an elaboration of -`custom_tactic`, which allowed it to solve the goal `42 = 42`. We then tried a -harder theorem, `43 = 43 ∧ 42 = 42` which `custom_tactic` was unable to solve. -We were then able to enrich `custom_tactic` to split "and" with `And.intro`, and -also *recursively* call `custom_tactic` in the two subcases. - -### Implementing `<;>`: Tactic Combinators by Macro Expansion - -Recall that in the previous section, we said that `a <;> b` meant "run `a`, and -then run `b` for all goals". In fact, `<;>` itself is a tactic macro. In this -section, we will implement the syntax `a and_then b` which will stand for -"run `a`, and then run `b` for all goals". - -```lean --- 1. We declare the syntax `and_then` -syntax tactic " and_then " tactic : tactic - --- 2. We write the expander that expands the tactic --- into running `a`, and then running `b` on all goals produced by `a`. -macro_rules -| `(tactic| $a:tactic and_then $b:tactic) => - `(tactic| $a:tactic; all_goals $b:tactic) - --- 3. We test this tactic. -theorem test_and_then: 1 = 1 ∧ 2 = 2 := by - apply And.intro and_then rfl - -#print test_and_then --- theorem test_and_then : 1 = 1 ∧ 2 = 2 := --- { left := Eq.refl 1, right := Eq.refl 2 } -``` - -## Exploring `TacticM` - -### The simplest tactic: `sorry` - -In this section, we wish to write a tactic that fills the proof with sorry: - -```lean -example : 1 = 2 := by - custom_sorry -``` - -We begin by declaring such a tactic: - -```lean -elab "custom_sorry_0" : tactic => do - return - -example : 1 = 2 := by - custom_sorry_0 --- unsolved goals: ⊢ 1 = 2 -``` - -This defines a syntax extension to Lean, where we are naming the piece of syntax -`custom_sorry_0` as living in `tactic` syntax category. This informs the -elaborator that, in the context of elaborating `tactic`s, the piece of syntax -`custom_sorry_0` must be elaborated as what we write to the right-hand-side of -the `=>` (the actual implementation of the tactic). - -Next, we write a term in `TacticM Unit` to fill in the goal with `sorryAx α`, -which can synthesize an artificial term of type `α`. To do this, we first access -the goal with `Lean.Elab.Tactic.getMainGoal : Tactic MVarId`, which returns the -main goal, represented as a metavariable. Recall that under -types-as-propositions, the type of our goal must be the proposition that `1 = 2`. -We check this by printing the type of `goal`. - -But first we need to start our tactic with `Lean.Elab.Tactic.withMainContext`, -which computes in `TacticM` with an updated context. - -```lean -elab "custom_sorry_1" : tactic => - Lean.Elab.Tactic.withMainContext do - let goal ← Lean.Elab.Tactic.getMainGoal - let goalDecl ← goal.getDecl - let goalType := goalDecl.type - dbg_trace f!"goal type: {goalType}" - -example : 1 = 2 := by - custom_sorry_1 --- goal type: Eq.{1} Nat (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)) (OfNat.ofNat.{0} Nat 2 (instOfNatNat 2)) --- unsolved goals: ⊢ 1 = 2 -``` - -To `sorry` the goal, we can use the helper `Lean.Elab.admitGoal`: - -```lean -elab "custom_sorry_2" : tactic => - Lean.Elab.Tactic.withMainContext do - let goal ← Lean.Elab.Tactic.getMainGoal - Lean.Elab.admitGoal goal - -theorem test_custom_sorry : 1 = 2 := by - custom_sorry_2 - -#print test_custom_sorry --- theorem test_custom_sorry : 1 = 2 := --- sorryAx (1 = 2) true -``` - -And we no longer have the error `unsolved goals: ⊢ 1 = 2`. - -### The `custom_assump` tactic: Accessing Hypotheses - -In this section, we will learn how to access the hypotheses to prove a goal. In -particular, we shall attempt to implement a tactic `custom_assump`, which looks -for an exact match of the goal among the hypotheses, and solves the theorem if -possible. - -In the example below, we expect `custom_assump` to use `(H2 : 2 = 2)` to solve -the goal `(2 = 2)`: - -```lean -theorem assump_correct (H1 : 1 = 1) (H2 : 2 = 2) : 2 = 2 := by - custom_assump - -#print assump_correct --- theorem assump_correct : 1 = 1 → 2 = 2 → 2 = 2 := --- fun H1 H2 => H2 -``` - -When we do not have a matching hypothesis to the goal, we expect the tactic -`custom_assump` to throw an error, telling us that we cannot find a hypothesis -of the type we are looking for: - -```lean -theorem assump_wrong (H1 : 1 = 1) : 2 = 2 := by - custom_assump - -#print assump_wrong --- tactic 'custom_assump' failed, unable to find matching hypothesis of type (2 = 2) --- H1 : 1 = 1 --- ⊢ 2 = 2 -``` - -We begin by accessing the goal and the type of the goal so we know what we -are trying to prove. The `goal` variable will soon be used to help us create -error messages. - -```lean -elab "custom_assump_0" : tactic => - Lean.Elab.Tactic.withMainContext do - let goalType ← Lean.Elab.Tactic.getMainTarget - dbg_trace f!"goal type: {goalType}" - -example (H1 : 1 = 1) (H2 : 2 = 2): 2 = 2 := by - custom_assump_0 --- goal type: Eq.{1} Nat (OfNat.ofNat.{0} Nat 2 (instOfNatNat 2)) (OfNat.ofNat.{0} Nat 2 (instOfNatNat 2)) --- unsolved goals --- H1 : 1 = 1 --- H2 : 2 = 2 --- ⊢ 2 = 2 - -example (H1 : 1 = 1): 2 = 2 := by - custom_assump_0 --- goal type: Eq.{1} Nat (OfNat.ofNat.{0} Nat 2 (instOfNatNat 2)) (OfNat.ofNat.{0} Nat 2 (instOfNatNat 2)) --- unsolved goals --- H1 : 1 = 1 --- ⊢ 2 = 2 -``` - -Next, we access the list of hypotheses, which are stored in a data structure -called `LocalContext`. This is accessed via `Lean.MonadLCtx.getLCtx`. The -`LocalContext` contains `LocalDeclaration`s, from which we can extract -information such as the name that is given to declarations (`.userName`), the -expression of the declaration (`.toExpr`). Let's write a tactic called -`list_local_decls` that prints the local declarations: - -```lean -elab "list_local_decls_1" : tactic => - Lean.Elab.Tactic.withMainContext do - let ctx ← Lean.MonadLCtx.getLCtx -- get the local context. - ctx.forM fun decl: Lean.LocalDecl => do - let declExpr := decl.toExpr -- Find the expression of the declaration. - let declName := decl.userName -- Find the name of the declaration. - dbg_trace f!"+ local decl: name: {declName} | expr: {declExpr}" - -example (H1 : 1 = 1) (H2 : 2 = 2): 1 = 1 := by - list_local_decls_1 --- + local decl: name: test_list_local_decls_1 | expr: _uniq.3339 --- + local decl: name: H1 | expr: _uniq.3340 --- + local decl: name: H2 | expr: _uniq.3341 - rfl -``` - -Recall that we are looking for a local declaration that has the same type as the -hypothesis. We get the type of `LocalDecl` by calling -`Lean.Meta.inferType` on the local declaration's expression. - -```lean -elab "list_local_decls_2" : tactic => - Lean.Elab.Tactic.withMainContext do - let ctx ← Lean.MonadLCtx.getLCtx -- get the local context. - ctx.forM fun decl: Lean.LocalDecl => do - let declExpr := decl.toExpr -- Find the expression of the declaration. - let declName := decl.userName -- Find the name of the declaration. - let declType ← Lean.Meta.inferType declExpr -- **NEW:** Find the type. - dbg_trace f!"+ local decl: name: {declName} | expr: {declExpr} | type: {declType}" - -example (H1 : 1 = 1) (H2 : 2 = 2): 1 = 1 := by - list_local_decls_2 - -- + local decl: name: test_list_local_decls_2 | expr: _uniq.4263 | type: (Eq.{1} Nat ...) - -- + local decl: name: H1 | expr: _uniq.4264 | type: Eq.{1} Nat ...) - -- + local decl: name: H2 | expr: _uniq.4265 | type: Eq.{1} Nat ...) - rfl -``` - -We check if the type of the `LocalDecl` is equal to the goal type with -`Lean.Meta.isExprDefEq`. See that we check if the types are equal at `eq?`, and -we print that `H1` has the same type as the goal -(`local decl[EQUAL? true]: name: H1`), and we print that `H2` does not have the -same type (`local decl[EQUAL? false]: name: H2 `): - -```lean -elab "list_local_decls_3" : tactic => - Lean.Elab.Tactic.withMainContext do - let goalType ← Lean.Elab.Tactic.getMainTarget - let ctx ← Lean.MonadLCtx.getLCtx -- get the local context. - ctx.forM fun decl: Lean.LocalDecl => do - let declExpr := decl.toExpr -- Find the expression of the declaration. - let declName := decl.userName -- Find the name of the declaration. - let declType ← Lean.Meta.inferType declExpr -- Find the type. - let eq? ← Lean.Meta.isExprDefEq declType goalType -- **NEW** Check if type equals goal type. - dbg_trace f!"+ local decl[EQUAL? {eq?}]: name: {declName}" - -example (H1 : 1 = 1) (H2 : 2 = 2): 1 = 1 := by - list_local_decls_3 --- + local decl[EQUAL? false]: name: test_list_local_decls_3 --- + local decl[EQUAL? true]: name: H1 --- + local decl[EQUAL? false]: name: H2 - rfl -``` - -Finally, we put all of these parts together to write a tactic that loops over -all declarations and finds one with the correct type. We loop over declarations -with `lctx.findDeclM?`. We infer the type of declarations with -`Lean.Meta.inferType`. We check that the declaration has the same type as the -goal with `Lean.Meta.isExprDefEq`: - -```lean -elab "custom_assump_1" : tactic => - Lean.Elab.Tactic.withMainContext do - let goalType ← Lean.Elab.Tactic.getMainTarget - let lctx ← Lean.MonadLCtx.getLCtx - -- Iterate over the local declarations... - let option_matching_expr ← lctx.findDeclM? fun ldecl: Lean.LocalDecl => do - let declExpr := ldecl.toExpr -- Find the expression of the declaration. - let declType ← Lean.Meta.inferType declExpr -- Find the type. - if (← Lean.Meta.isExprDefEq declType goalType) -- Check if type equals goal type. - then return some declExpr -- If equal, success! - else return none -- Not found. - dbg_trace f!"matching_expr: {option_matching_expr}" - -example (H1 : 1 = 1) (H2 : 2 = 2) : 2 = 2 := by - custom_assump_1 --- matching_expr: some _uniq.6241 - rfl - -example (H1 : 1 = 1) : 2 = 2 := by - custom_assump_1 --- matching_expr: none - rfl -``` - -Now that we are able to find the matching expression, we need to close the -theorem by using the match. We do this with `Lean.Elab.Tactic.closeMainGoal`. -When we do not have a matching expression, we throw an error with -`Lean.Meta.throwTacticEx`, which allows us to report an error corresponding to a -given goal. When throwing this error, we format the error using `m!"..."` which -builds a `MessageData`. This provides nicer error messages than using `f!"..."` -which builds a `Format`. This is because `MessageData` also runs *delaboration*, -which allows it to convert raw Lean terms like -`(Eq.{1} Nat (OfNat.ofNat.{0} Nat 2 (instOfNatNat 2)) (OfNat.ofNat.{0} Nat 2 (instOfNatNat 2)))` -into readable strings like`(2 = 2)`. The full code listing given below shows how -to do this: - -```lean -elab "custom_assump_2" : tactic => - Lean.Elab.Tactic.withMainContext do - let goal ← Lean.Elab.Tactic.getMainGoal - let goalType ← Lean.Elab.Tactic.getMainTarget - let ctx ← Lean.MonadLCtx.getLCtx - let option_matching_expr ← ctx.findDeclM? fun decl: Lean.LocalDecl => do - let declExpr := decl.toExpr - let declType ← Lean.Meta.inferType declExpr - if ← Lean.Meta.isExprDefEq declType goalType - then return Option.some declExpr - else return Option.none - match option_matching_expr with - | some e => Lean.Elab.Tactic.closeMainGoal e - | none => - Lean.Meta.throwTacticEx `custom_assump_2 goal - (m!"unable to find matching hypothesis of type ({goalType})") - -example (H1 : 1 = 1) (H2 : 2 = 2) : 2 = 2 := by - custom_assump_2 - -example (H1 : 1 = 1): 2 = 2 := by - custom_assump_2 --- tactic 'custom_assump_2' failed, unable to find matching hypothesis of type (2 = 2) --- H1 : 1 = 1 --- ⊢ 2 = 2 -``` - -### Tweaking the context - -Until now, we've only performed read-like operations with the context. But what -if we want to change it? In this section we will see how to change the order of -goals and how to add content to it (new hypotheses). - -Then, after elaborating our terms, we will need to use the helper function -`Lean.Elab.Tactic.liftMetaTactic`, which allows us to run computations in -`MetaM` while also giving us the goal `MVarId` for us to play with. In the end -of our computation, `liftMetaTactic` expects us to return a `List MVarId` as the -resulting list of goals. - -The only substantial difference between `custom_let` and `custom_have` is that -the former uses `Lean.MVarId.define` and the later uses `Lean.MVarId.assert`: - -```lean -open Lean.Elab.Tactic in -elab "custom_let " n:ident " : " t:term " := " v:term : tactic => - withMainContext do - let t ← elabTerm t none - let v ← elabTermEnsuringType v t - liftMetaTactic fun mvarId => do - let mvarIdNew ← mvarId.define n.getId t v - let (_, mvarIdNew) ← mvarIdNew.intro1P - return [mvarIdNew] - -open Lean.Elab.Tactic in -elab "custom_have " n:ident " : " t:term " := " v:term : tactic => - withMainContext do - let t ← elabTerm t none - let v ← elabTermEnsuringType v t - liftMetaTactic fun mvarId => do - let mvarIdNew ← mvarId.assert n.getId t v - let (_, mvarIdNew) ← mvarIdNew.intro1P - return [mvarIdNew] - -theorem test_faq_have : True := by - custom_let n : Nat := 5 - custom_have h : n = n := rfl --- n : Nat := 5 --- h : n = n --- ⊢ True - trivial -``` - -### "Getting" and "Setting" the list of goals - -To illustrate these, let's build a tactic that can reverse the list of goals. -We can use `Lean.Elab.Tactic.getGoals` and `Lean.Elab.Tactic.setGoals`: - -```lean -elab "reverse_goals" : tactic => - Lean.Elab.Tactic.withMainContext do - let goals : List Lean.MVarId ← Lean.Elab.Tactic.getGoals - Lean.Elab.Tactic.setGoals goals.reverse - -theorem test_reverse_goals : (1 = 2 ∧ 3 = 4) ∧ 5 = 6 := by - constructor - constructor --- case left.left --- ⊢ 1 = 2 --- case left.right --- ⊢ 3 = 4 --- case right --- ⊢ 5 = 6 - reverse_goals --- case right --- ⊢ 5 = 6 --- case left.right --- ⊢ 3 = 4 --- case left.left --- ⊢ 1 = 2 -``` - -## FAQ - -In this section, we collect common patterns that are used during writing tactics, -to make it easy to find common patterns. - -**Q: How do I use goals?** - -A: Goals are represented as metavariables. The module `Lean.Elab.Tactic.Basic` -has many functions to add new goals, switch goals, etc. - -**Q: How do I get the main goal?** - -A: Use `Lean.Elab.Tactic.getMainGoal`. - -```lean -elab "faq_main_goal" : tactic => - Lean.Elab.Tactic.withMainContext do - let goal ← Lean.Elab.Tactic.getMainGoal - dbg_trace f!"goal: {goal.name}" - -example : 1 = 1 := by - faq_main_goal --- goal: _uniq.9298 - rfl -``` - -**Q: How do I get the list of goals?** - -A: Use `getGoals`. - -```lean -elab "faq_get_goals" : tactic => - Lean.Elab.Tactic.withMainContext do - let goals ← Lean.Elab.Tactic.getGoals - goals.forM $ fun goal => do - let goalType ← goal.getType - dbg_trace f!"goal: {goal.name} | type: {goalType}" - -example (b : Bool) : b = true := by - cases b - faq_get_goals --- goal: _uniq.10067 | type: Eq.{1} Bool Bool.false Bool.true --- goal: _uniq.10078 | type: Eq.{1} Bool Bool.true Bool.true - sorry - rfl -``` - -**Q: How do I get the current hypotheses for a goal?** - -A: Use `Lean.MonadLCtx.getLCtx` which provides the local context, and then -iterate on the `LocalDeclaration`s of the `LocalContext` with accessors such as -`foldlM` and `forM`. - -```lean -elab "faq_get_hypotheses" : tactic => - Lean.Elab.Tactic.withMainContext do - let ctx ← Lean.MonadLCtx.getLCtx -- get the local context. - ctx.forM (fun (decl : Lean.LocalDecl) => do - let declExpr := decl.toExpr -- Find the expression of the declaration. - let declType := decl.type -- Find the type of the declaration. - let declName := decl.userName -- Find the name of the declaration. - dbg_trace f!" local decl: name: {declName} | expr: {declExpr} | type: {declType}" - ) - -example (H1 : 1 = 1) (H2 : 2 = 2): 3 = 3 := by - faq_get_hypotheses - -- local decl: name: _example | expr: _uniq.10814 | type: ... - -- local decl: name: H1 | expr: _uniq.10815 | type: ... - -- local decl: name: H2 | expr: _uniq.10816 | type: ... - rfl -``` - -**Q: How do I evaluate a tactic?** - -A: Use `Lean.Elab.Tactic.evalTactic: Syntax → TacticM Unit` which evaluates a -given tactic syntax. One can create tactic syntax using the macro -`` `(tactic| ⋯)``. - -For example, one could call `try rfl` with the piece of code: - -```lean -Lean.Elab.Tactic.evalTactic (← `(tactic| try rfl)) -``` - -**Q: How do I check if two expressions are equal?** - -A: Use `Lean.Meta.isExprDefEq `. - -```lean -#check Lean.Meta.isExprDefEq --- Lean.Meta.isExprDefEq : Lean.Expr → Lean.Expr → Lean.MetaM Bool -``` - -**Q: How do I throw an error from a tactic?** - -A: Use `throwTacticEx `. - -```lean -elab "faq_throw_error" : tactic => - Lean.Elab.Tactic.withMainContext do - let goal ← Lean.Elab.Tactic.getMainGoal - Lean.Meta.throwTacticEx `faq_throw_error goal "throwing an error at the current goal" - -example (b : Bool): b = true := by - cases b; - faq_throw_error - -- case true - -- ⊢ true = true - -- tactic 'faq_throw_error' failed, throwing an error at the current goal - -- case false - -- ⊢ false = true -``` - -**Q: What is the difference between `Lean.Elab.Tactic.*` and `Lean.Meta.Tactic.*`?** - -A: `Lean.Meta.Tactic.*` contains low level code that uses the `Meta` monad to -implement basic features such as rewriting. `Lean.Elab.Tactic.*` contains -high-level code that connects the low level development in `Lean.Meta` to the -tactic infrastructure and the parsing front-end. - -## Exercises - -1. Consider the theorem `p ∧ q ↔ q ∧ p`. We could either write its proof as a proof term, or construct it using the tactics. - When we are writing the proof of this theorem *as a proof term*, we're gradually filling up `_`s with certain expressions, step by step. Each such step corresponds to a tactic. - - There are many combinations of steps in which we could write this proof term - but consider the sequence of steps we wrote below. Please write each step as a tactic. - The tactic `step_1` is filled in, please do the same for the remaining tactics (for the sake of the exercise, try to use lower-level apis, such as `mkFreshExprMVar`, `mvarId.assign` and `modify fun _ => { goals := ~)`. - - ```lean - -- [this is the initial goal] - example : p ∧ q ↔ q ∧ p := - _ - - -- step_1 - example : p ∧ q ↔ q ∧ p := - Iff.intro _ _ - - -- step_2 - example : p ∧ q ↔ q ∧ p := - Iff.intro - ( - fun hA => - _ - ) - ( - fun hB => - (And.intro hB.right hB.left) - ) - - -- step_3 - example : p ∧ q ↔ q ∧ p := - Iff.intro - ( - fun hA => - (And.intro _ _) - ) - ( - fun hB => - (And.intro hB.right hB.left) - ) - - -- step_4 - example : p ∧ q ↔ q ∧ p := - Iff.intro - ( - fun hA => - (And.intro hA.right hA.left) - ) - ( - fun hB => - (And.intro hB.right hB.left) - ) - ``` - - ```lean - elab "step_1" : tactic => do - let mvarId ← getMainGoal - let goalType ← getMainTarget - - let Expr.app (Expr.app (Expr.const `Iff _) a) b := goalType | throwError "Goal type is not of the form `a ↔ b`" - - -- 1. Create new `_`s with appropriate types. - let mvarId1 ← mkFreshExprMVar (Expr.forallE `xxx a b .default) (userName := "red") - let mvarId2 ← mkFreshExprMVar (Expr.forallE `yyy b a .default) (userName := "blue") - - -- 2. Assign the main goal to the expression `Iff.intro _ _`. - mvarId.assign (mkAppN (Expr.const `Iff.intro []) #[a, b, mvarId1, mvarId2]) - - -- 3. Report the new `_`s to Lean as the new goals. - modify fun _ => { goals := [mvarId1.mvarId!, mvarId2.mvarId!] } - ``` - - ```lean - theorem gradual (p q : Prop) : p ∧ q ↔ q ∧ p := by - step_1 - step_2 - step_3 - step_4 - ``` - -2. In the first exercise, we used lower-level `modify` api to update our goals. - `liftMetaTactic`, `setGoals`, `appendGoals`, `replaceMainGoal`, `closeMainGoal`, etc. are all syntax sugars on top of `modify fun s : State => { s with goals := myMvarIds }`. - Please rewrite the `forker` tactic with: - - **a)** `liftMetaTactic` - **b)** `setGoals` - **c)** `replaceMainGoal` - - ```lean - elab "forker" : tactic => do - let mvarId ← getMainGoal - let goalType ← getMainTarget - - let (Expr.app (Expr.app (Expr.const `And _) p) q) := goalType | Lean.Meta.throwTacticEx `forker mvarId (m!"Goal is not of the form P ∧ Q") - - mvarId.withContext do - let mvarIdP ← mkFreshExprMVar p (userName := "red") - let mvarIdQ ← mkFreshExprMVar q (userName := "blue") - - let proofTerm := mkAppN (Expr.const `And.intro []) #[p, q, mvarIdP, mvarIdQ] - mvarId.assign proofTerm - - modify fun state => { goals := [mvarIdP.mvarId!, mvarIdQ.mvarId!] ++ state.goals.drop 1 } - ``` - - ```lean - example (A B C : Prop) : A → B → C → (A ∧ B) ∧ C := by - intro hA hB hC - forker - forker - assumption - assumption - assumption - ``` - -3. In the first exercise, you created your own `intro` in `step_2` (with a hardcoded hypothesis name, but the basics are the same). When writing tactics, we usually want to use functions such as `intro`, `intro1`, `intro1P`, `introN` or `introNP`. - - For each of the points below, create a tactic `introductor` (one per each point), that turns the goal `(ab: a = b) → (bc: b = c) → (a = c)`: - - **a)** into the goal `(a = c)` with hypotheses `(ab✝: a = b)` and `(bc✝: b = c)`. - **b)** into the goal `(bc: b = c) → (a = c)` with hypothesis `(ab: a = b)`. - **c)** into the goal `(bc: b = c) → (a = c)` with hypothesis `(hello: a = b)`. - - ```lean - example (a b c : Nat) : (ab: a = b) → (bc: b = c) → (a = c) := by - introductor - sorry - ``` - - Hint: **"P"** in `intro1P` and `introNP` stands for **"Preserve"**. diff --git a/md/main/10_cheat-sheet.md b/md/main/10_cheat-sheet.md deleted file mode 100644 index 2247a6a..0000000 --- a/md/main/10_cheat-sheet.md +++ /dev/null @@ -1,64 +0,0 @@ -# Lean4 Cheat-sheet - -## Extracting information - -* Extract the goal: `Lean.Elab.Tactic.getMainGoal` - - Use as `let goal ← Lean.Elab.Tactic.getMainGoal` -* Extract the declaration out of a metavariable: `mvarId.getDecl` - when `mvarId : Lean.MVarId` is in context. - For instance, `mvarId` could be the goal extracted using `getMainGoal` -* Extract the type of a metavariable: `mvarId.getType` - when `mvarId : Lean.MVarId` is in context. -* Extract the type of the main goal: `Lean.Elab.Tactic.getMainTarget` - - Use as `let goal_type ← Lean.Elab.Tactic.getMainTarget` - - Achieves the same as -```lean -let goal ← Lean.Elab.Tactic.getMainGoal -let goal_type ← goal.getType -``` -* Extract local context: `Lean.MonadLCtx.getLCtx` - - Use as `let lctx ← Lean.MonadLCtx.getLCtx` -* Extract the name of a declaration: `Lean.LocalDecl.userName ldecl` - when `ldecl : Lean.LocalDecl` is in context -* Extract the type of an expression: `Lean.Meta.inferType expr` - when `expr : Lean.Expr` is an expression in context - - Use as `let expr_type ← Lean.Meta.inferType expr` - -## Playing around with expressions - -* Convert a declaration into an expression: `Lean.LocalDecl.toExpr` - - Use as `ldecl.toExpr`, when `ldecl : Lean.LocalDecl` is in context - - For instance, `ldecl` could be `let ldecl ← Lean.MonadLCtx.getLCtx` -* Check whether two expressions are definitionally equal: `Lean.Meta.isDefEq ex1 ex2` - when `ex1 ex2 : Lean.Expr` are in context. Returns a `Lean.MetaM Bool` -* Close a goal: `Lean.Elab.Tactic.closeMainGoal expr` - when `expr : Lean.Expr` is in context - -## Further commands - -* meta-sorry: `Lean.Elab.admitGoal goal`, when `goal : Lean.MVarId` is the current goal - -## Printing and errors - -* Print a "permanent" message in normal usage: - - `Lean.logInfo f!"Hi, I will print\n{Syntax}"` -* Print a message while debugging: - - `dbg_trace f!"1) goal: {Syntax_that_will_be_interpreted}"`. -* Throw an error: `Lean.Meta.throwTacticEx name mvar message_data` - where `name : Lean.Name` is the name of a tactic and `mvar` contains error data. - - Use as `Lean.Meta.throwTacticEx `tac goal (m!"unable to find matching hypothesis of type ({goal_type})")` - where the `m!` formatting builds a `MessageData` for better printing of terms - -TODO: Add? -* Lean.LocalContext.forM -* Lean.LocalContext.findDeclM? diff --git a/md/solutions/03_expressions.md b/md/solutions/03_expressions.md deleted file mode 100644 index e095448..0000000 --- a/md/solutions/03_expressions.md +++ /dev/null @@ -1,155 +0,0 @@ -```lean -import Lean -open Lean Meta -``` - -# Solutions - -## Expressions: Solutions - -### 1. - -```lean -def one : Expr := - Expr.app (Expr.app (Expr.const `Nat.add []) (mkNatLit 1)) (mkNatLit 2) - -elab "one" : term => return one -#check one -- Nat.add 1 2 : Nat -#reduce one -- 3 -``` - -### 2. - -```lean -def two : Expr := - Lean.mkAppN (Expr.const `Nat.add []) #[mkNatLit 1, mkNatLit 2] - -elab "two" : term => return two -#check two -- Nat.add 1 2 : Nat -#reduce two -- 3 -``` - -### 3. - -```lean -def three : Expr := - Expr.lam `x (Expr.const `Nat []) - (Lean.mkAppN (Expr.const `Nat.add []) #[Lean.mkNatLit 1, Expr.bvar 0]) - BinderInfo.default - -elab "three" : term => return three -#check three -- fun x => Nat.add 1 x : Nat → Nat -#reduce three 6 -- 7 -``` - -### 4. - -```lean -def four : Expr := - Expr.lam `a (Expr.const `Nat []) - ( - Expr.lam `b (Expr.const `Nat []) - ( - Expr.lam `c (Expr.const `Nat []) - ( - Lean.mkAppN - (Expr.const `Nat.add []) - #[ - (Lean.mkAppN (Expr.const `Nat.mul []) #[Expr.bvar 1, Expr.bvar 2]), - (Expr.bvar 0) - ] - ) - BinderInfo.default - ) - BinderInfo.default - ) - BinderInfo.default - -elab "four" : term => return four -#check four -- fun a b c => Nat.add (Nat.mul b a) c : Nat → Nat → Nat → Nat -#reduce four 666 1 2 -- 668 -``` - -### 5. - -```lean -def five := - Expr.lam `x (Expr.const `Nat []) - ( - Expr.lam `y (Expr.const `Nat []) - (Lean.mkAppN (Expr.const `Nat.add []) #[Expr.bvar 1, Expr.bvar 0]) - BinderInfo.default - ) - BinderInfo.default - -elab "five" : term => return five -#check five -- fun x y => Nat.add x y : Nat → Nat → Nat -#reduce five 4 5 -- 9 -``` - -### 6. - -```lean -def six := - Expr.lam `x (Expr.const `String []) - (Lean.mkAppN (Expr.const `String.append []) #[Lean.mkStrLit "Hello, ", Expr.bvar 0]) - BinderInfo.default - -elab "six" : term => return six -#check six -- fun x => String.append "Hello, " x : String → String -#eval six "world" -- "Hello, world" -``` - -### 7. - -```lean -def seven : Expr := - Expr.forallE `x (Expr.sort Lean.Level.zero) - (Expr.app (Expr.app (Expr.const `And []) (Expr.bvar 0)) (Expr.bvar 0)) - BinderInfo.default - -elab "seven" : term => return seven -#check seven -- ∀ (x : Prop), x ∧ x : Prop -#reduce seven -- ∀ (x : Prop), x ∧ x -``` - -### 8. - -```lean -def eight : Expr := - Expr.forallE `notUsed - (Expr.const `Nat []) (Expr.const `String []) - BinderInfo.default - -elab "eight" : term => return eight -#check eight -- Nat → String : Type -#reduce eight -- Nat → String -``` - -### 9. - -```lean -def nine : Expr := - Expr.lam `p (Expr.sort Lean.Level.zero) - ( - Expr.lam `hP (Expr.bvar 0) - (Expr.bvar 0) - BinderInfo.default - ) - BinderInfo.default - -elab "nine" : term => return nine -#check nine -- fun p hP => hP : ∀ (p : Prop), p → p -#reduce nine -- fun p hP => hP -``` - -### 10. - -```lean -def ten : Expr := - Expr.sort (Nat.toLevel 7) - -elab "ten" : term => return ten -#check ten -- Type 6 : Type 7 -#reduce ten -- Type 6 -``` diff --git a/md/solutions/04_metam.md b/md/solutions/04_metam.md deleted file mode 100644 index eea169f..0000000 --- a/md/solutions/04_metam.md +++ /dev/null @@ -1,345 +0,0 @@ -```lean -import Lean -open Lean Meta -``` - -## `MetaM`: Solutions - -### 1. - -```lean -#eval show MetaM Unit from do - let hi ← Lean.Meta.mkFreshExprMVar (Expr.const `Nat []) (userName := `hi) - IO.println s!"value in hi: {← instantiateMVars hi}" -- ?_uniq.1 - - hi.mvarId!.assign (Expr.app (Expr.const `Nat.succ []) (Expr.const ``Nat.zero [])) - IO.println s!"value in hi: {← instantiateMVars hi}" -- Nat.succ Nat.zero -``` - -### 2. - -```lean --- It would output the same expression we gave it - there were no metavariables to instantiate. -#eval show MetaM Unit from do - let instantiatedExpr ← instantiateMVars (Expr.lam `x (Expr.const `Nat []) (Expr.bvar 0) BinderInfo.default) - IO.println instantiatedExpr -- fun (x : Nat) => x -``` - -### 3. - -```lean -#eval show MetaM Unit from do - let oneExpr := Expr.app (Expr.const `Nat.succ []) (Expr.const ``Nat.zero []) - let twoExpr := Expr.app (Expr.const `Nat.succ []) oneExpr - - -- Create `mvar1` with type `Nat` - let mvar1 ← Lean.Meta.mkFreshExprMVar (Expr.const `Nat []) (userName := `mvar1) - -- Create `mvar2` with type `Nat` - let mvar2 ← Lean.Meta.mkFreshExprMVar (Expr.const `Nat []) (userName := `mvar2) - -- Create `mvar3` with type `Nat` - let mvar3 ← Lean.Meta.mkFreshExprMVar (Expr.const `Nat []) (userName := `mvar3) - - -- Assign `mvar1` to `2 + ?mvar2 + ?mvar3` - mvar1.mvarId!.assign (Lean.mkAppN (Expr.const `Nat.add []) #[(Lean.mkAppN (Expr.const `Nat.add []) #[twoExpr, mvar2]), mvar3]) - - -- Assign `mvar3` to `1` - mvar3.mvarId!.assign oneExpr - - -- Instantiate `mvar1`, which should result in expression `2 + ?mvar2 + 1` - let instantiatedMvar1 ← instantiateMVars mvar1 - IO.println instantiatedMvar1 -- Nat.add (Nat.add 2 ?_uniq.2) 1 -``` - -### 4. - -```lean -elab "explore" : tactic => do - let mvarId : MVarId ← Lean.Elab.Tactic.getMainGoal - let metavarDecl : MetavarDecl ← mvarId.getDecl - - IO.println "Our metavariable" - -- [anonymous] : 2 = 2 - IO.println s!"\n{metavarDecl.userName} : {metavarDecl.type}" - - IO.println "\nAll of its local declarations" - let localContext : LocalContext := metavarDecl.lctx - for (localDecl : LocalDecl) in localContext do - if localDecl.isImplementationDetail then - -- (implementation detail) red : 1 = 1 → 2 = 2 → 2 = 2 - IO.println s!"\n(implementation detail) {localDecl.userName} : {localDecl.type}" - else - -- hA : 1 = 1 - -- hB : 2 = 2 - IO.println s!"\n{localDecl.userName} : {localDecl.type}" - -theorem red (hA : 1 = 1) (hB : 2 = 2) : 2 = 2 := by - explore - sorry -``` - -### 5. - -```lean --- The type of our metavariable `2 + 2`. We want to find a `localDecl` that has the same type, and `assign` our metavariable to that `localDecl`. -elab "solve" : tactic => do - let mvarId : MVarId ← Lean.Elab.Tactic.getMainGoal - let metavarDecl : MetavarDecl ← mvarId.getDecl - - let localContext : LocalContext := metavarDecl.lctx - for (localDecl : LocalDecl) in localContext do - if ← Lean.Meta.isDefEq localDecl.type metavarDecl.type then - mvarId.assign localDecl.toExpr - -theorem redSolved (hA : 1 = 1) (hB : 2 = 2) : 2 = 2 := by - solve -``` - -### 6. - -```lean -def sixA : Bool → Bool := fun x => x --- .lam `x (.const `Bool []) (.bvar 0) (Lean.BinderInfo.default) -#eval Lean.Meta.reduce (Expr.const `sixA []) - -def sixB : Bool := (fun x => x) ((true && false) || true) --- .const `Bool.true [] -#eval Lean.Meta.reduce (Expr.const `sixB []) - -def sixC : Nat := 800 + 2 --- .lit (Lean.Literal.natVal 802) -#eval Lean.Meta.reduce (Expr.const `sixC []) -``` - -### 7. - -```lean -#eval show MetaM Unit from do - let litExpr := Expr.lit (Lean.Literal.natVal 1) - let standardExpr := Expr.app (Expr.const ``Nat.succ []) (Expr.const ``Nat.zero []) - - let isEqual ← Lean.Meta.isDefEq litExpr standardExpr - IO.println isEqual -- true -``` - -### 8. - -```lean --- a) `5 =?= (fun x => 5) ((fun y : Nat → Nat => y) (fun z : Nat => z))` --- Definitionally equal. -def expr2 := (fun x => 5) ((fun y : Nat → Nat => y) (fun z : Nat => z)) -#eval show MetaM Unit from do - let expr1 := Lean.mkNatLit 5 - let expr2 := Expr.const `expr2 [] - let isEqual ← Lean.Meta.isDefEq expr1 expr2 - IO.println isEqual -- true - --- b) `2 + 1 =?= 1 + 2` --- Definitionally equal. -#eval show MetaM Unit from do - let expr1 := Lean.mkAppN (Expr.const `Nat.add []) #[Lean.mkNatLit 2, Lean.mkNatLit 1] - let expr2 := Lean.mkAppN (Expr.const `Nat.add []) #[Lean.mkNatLit 1, Lean.mkNatLit 2] - let isEqual ← Lean.Meta.isDefEq expr1 expr2 - IO.println isEqual -- true - --- c) `?a =?= 2`, where `?a` has a type `String` --- Not definitionally equal. -#eval show MetaM Unit from do - let expr1 ← Lean.Meta.mkFreshExprMVar (Expr.const `String []) (userName := `expr1) - let expr2 := Lean.mkNatLit 2 - let isEqual ← Lean.Meta.isDefEq expr1 expr2 - IO.println isEqual -- false - --- d) `?a + Int =?= "hi" + ?b`, where `?a` and `?b` don't have a type --- Definitionally equal. --- `?a` is assigned to `"hi"`, `?b` is assigned to `Int`. -#eval show MetaM Unit from do - let a ← Lean.Meta.mkFreshExprMVar Option.none (userName := `a) - let b ← Lean.Meta.mkFreshExprMVar Option.none (userName := `b) - let expr1 := Lean.mkAppN (Expr.const `Nat.add []) #[a, Expr.const `Int []] - let expr2 := Lean.mkAppN (Expr.const `Nat.add []) #[Lean.mkStrLit "hi", b] - let isEqual ← Lean.Meta.isDefEq expr1 expr2 - IO.println isEqual -- true - - IO.println s!"a: {← instantiateMVars a}" - IO.println s!"b: {← instantiateMVars b}" - --- e) `2 + ?a =?= 3` --- Not definitionally equal. -#eval show MetaM Unit from do - let a ← Lean.Meta.mkFreshExprMVar (Expr.const `Nat []) (userName := `a) - let expr1 := Lean.mkAppN (Expr.const `Nat.add []) #[Lean.mkNatLit 2, a] - let expr2 := Lean.mkNatLit 3 - let isEqual ← Lean.Meta.isDefEq expr1 expr2 - IO.println isEqual -- false - --- f) `2 + ?a =?= 2 + 1` --- Definitionally equal. --- `?a` is assigned to `1`. -#eval show MetaM Unit from do - let a ← Lean.Meta.mkFreshExprMVar (Expr.const `Nat []) (userName := `a) - let expr1 := Lean.mkAppN (Expr.const `Nat.add []) #[Lean.mkNatLit 2, a] - let expr2 := Lean.mkAppN (Expr.const `Nat.add []) #[Lean.mkNatLit 2, Lean.mkNatLit 1] - let isEqual ← Lean.Meta.isDefEq expr1 expr2 - IO.println isEqual -- true - - IO.println s!"a: {← instantiateMVars a}" -``` - -### 9. - -```lean -@[reducible] def reducibleDef : Nat := 1 -- same as `abbrev` -@[instance] def instanceDef : Nat := 2 -- same as `instance` -def defaultDef : Nat := 3 -@[irreducible] def irreducibleDef : Nat := 4 - -@[reducible] def sum := [reducibleDef, instanceDef, defaultDef, irreducibleDef] - -#eval show MetaM Unit from do - let constantExpr := Expr.const `sum [] - - Meta.withTransparency Meta.TransparencyMode.reducible do - let reducedExpr ← Meta.reduce constantExpr - dbg_trace (← ppExpr reducedExpr) -- [1, instanceDef, defaultDef, irreducibleDef] - - Meta.withTransparency Meta.TransparencyMode.instances do - let reducedExpr ← Meta.reduce constantExpr - dbg_trace (← ppExpr reducedExpr) -- [1, 2, defaultDef, irreducibleDef] - - Meta.withTransparency Meta.TransparencyMode.default do - let reducedExpr ← Meta.reduce constantExpr - dbg_trace (← ppExpr reducedExpr) -- [1, 2, 3, irreducibleDef] - - Meta.withTransparency Meta.TransparencyMode.all do - let reducedExpr ← Meta.reduce constantExpr - dbg_trace (← ppExpr reducedExpr) -- [1, 2, 3, 4] - - -- Note: if we don't set the transparency mode, we get a pretty strong `TransparencyMode.default`. - let reducedExpr ← Meta.reduce constantExpr - dbg_trace (← ppExpr reducedExpr) -- [1, 2, 3, irreducibleDef] -``` - -### 10. - -```lean --- Non-idiomatic: we can only use `Lean.mkAppN`. -def tenA : MetaM Expr := do - let body := Lean.mkAppN (Expr.const `Nat.add []) #[Lean.mkNatLit 1, Expr.bvar 0] - return Expr.lam `x (Expr.const `Nat []) body BinderInfo.default - --- Idiomatic: we can use both `Lean.mkAppN` and `Lean.Meta.mkAppM`. -def tenB : MetaM Expr := do - Lean.Meta.withLocalDecl `x .default (Expr.const `Nat []) (fun x => do - -- let body := Lean.mkAppN (Expr.const `Nat.add []) #[Lean.mkNatLit 1, x] - let body ← Lean.Meta.mkAppM `Nat.add #[Lean.mkNatLit 1, x] - Lean.Meta.mkLambdaFVars #[x] body - ) - -#eval show MetaM _ from do - ppExpr (← tenA) -- fun x => Nat.add 1 x -#eval show MetaM _ from do - ppExpr (← tenB) -- fun x => Nat.add 1 x -``` - -### 11. - -```lean -def eleven : MetaM Expr := - return Expr.forallE `yellow (Expr.const `Nat []) (Expr.bvar 0) BinderInfo.default - -#eval show MetaM _ from do - dbg_trace (← eleven) -- forall (yellow : Nat), yellow -``` - -### 12. - -```lean --- Non-idiomatic: we can only use `Lean.mkApp3`. -def twelveA : MetaM Expr := do - let nPlusOne := Expr.app (Expr.app (Expr.const `Nat.add []) (Expr.bvar 0)) (Lean.mkNatLit 1) - let forAllBody := Lean.mkApp3 (Expr.const ``Eq []) (Expr.const `Nat []) (Expr.bvar 0) nPlusOne - let forAll := Expr.forallE `n (Expr.const `Nat []) forAllBody BinderInfo.default - return forAll - --- Idiomatic: we can use both `Lean.mkApp3` and `Lean.Meta.mkEq`. -def twelveB : MetaM Expr := do - withLocalDecl `n BinderInfo.default (Expr.const `Nat []) (fun x => do - let nPlusOne := Expr.app (Expr.app (Expr.const `Nat.add []) x) (Lean.mkNatLit 1) - -- let forAllBody := Lean.mkApp3 (Expr.const ``Eq []) (Expr.const `Nat []) x nPlusOne - let forAllBody ← Lean.Meta.mkEq x nPlusOne - let forAll := mkForallFVars #[x] forAllBody - forAll - ) - -#eval show MetaM _ from do - ppExpr (← twelveA) -- (n : Nat) → Eq Nat n (Nat.add n 1) - -#eval show MetaM _ from do - ppExpr (← twelveB) -- ∀ (n : Nat), n = Nat.add n 1 -``` - -### 13. - -```lean -def thirteen : MetaM Expr := do - withLocalDecl `f BinderInfo.default (Expr.forallE `a (Expr.const `Nat []) (Expr.const `Nat []) .default) (fun y => do - let lamBody ← withLocalDecl `n BinderInfo.default (Expr.const `Nat []) (fun x => do - let fn := Expr.app y x - let fnPlusOne := Expr.app y (Expr.app (Expr.app (Expr.const `Nat.add []) (x)) (Lean.mkNatLit 1)) - let forAllBody := mkApp3 (mkConst ``Eq []) (Expr.const `Nat []) fn fnPlusOne - let forAll := mkForallFVars #[x] forAllBody - forAll - ) - let lam := mkLambdaFVars #[y] lamBody - lam - ) - -#eval show MetaM _ from do - ppExpr (← thirteen) -- fun f => (n : Nat) → Eq Nat (f n) (f (Nat.add n 1)) -``` - -### 14. - -```lean -#eval show Lean.Elab.Term.TermElabM _ from do - let stx : Syntax ← `(∀ (a : Prop) (b : Prop), a ∨ b → b → a ∧ a) - let expr ← Elab.Term.elabTermAndSynthesize stx none - - let (_, _, conclusion) ← forallMetaTelescope expr - dbg_trace conclusion -- And ?_uniq.10 ?_uniq.10 - - let (_, _, conclusion) ← forallMetaBoundedTelescope expr 2 - dbg_trace conclusion -- (Or ?_uniq.14 ?_uniq.15) -> ?_uniq.15 -> (And ?_uniq.14 ?_uniq.14) - - let (_, _, conclusion) ← lambdaMetaTelescope expr - dbg_trace conclusion -- forall (a.1 : Prop) (b.1 : Prop), (Or a.1 b.1) -> b.1 -> (And a.1 a.1) -``` - -### 15. - -```lean -#eval show MetaM Unit from do - let a ← Lean.Meta.mkFreshExprMVar (Expr.const `String []) (userName := `a) - let b ← Lean.Meta.mkFreshExprMVar (Expr.sort (Nat.toLevel 1)) (userName := `b) - -- ?a + Int - let c := Lean.mkAppN (Expr.const `Nat.add []) #[a, Expr.const `Int []] - -- "hi" + ?b - let d := Lean.mkAppN (Expr.const `Nat.add []) #[Lean.mkStrLit "hi", b] - - IO.println s!"value in c: {← instantiateMVars c}" -- Nat.add ?_uniq.1 Int - IO.println s!"value in d: {← instantiateMVars d}" -- Nat.add String ?_uniq.2 - - let state : SavedState ← saveState - IO.println "\nSaved state\n" - - if ← Lean.Meta.isDefEq c d then - IO.println true - IO.println s!"value in c: {← instantiateMVars c}" - IO.println s!"value in d: {← instantiateMVars d}" - - restoreState state - IO.println "\nRestored state\n" - - IO.println s!"value in c: {← instantiateMVars c}" - IO.println s!"value in d: {← instantiateMVars d}" -``` diff --git a/md/solutions/05_syntax.md b/md/solutions/05_syntax.md deleted file mode 100644 index 323da1f..0000000 --- a/md/solutions/05_syntax.md +++ /dev/null @@ -1,92 +0,0 @@ -```lean -import Lean -import Lean.Parser.Syntax - -open Lean Elab Command Term -``` - -## `Syntax`: Solutions - -### 1. - -```lean -namespace a - scoped notation:71 lhs:50 " 💀 " rhs:72 => lhs - rhs -end a - -namespace b - set_option quotPrecheck false - scoped infixl:71 " 💀 " => fun lhs rhs => lhs - rhs -end b - -namespace c - scoped syntax:71 term:50 " 💀 " term:72 : term - scoped macro_rules | `($l:term 💀 $r:term) => `($l - $r) -end c - -open a -#eval 5 * 8 💀 4 -- 20 -#eval 8 💀 6 💀 1 -- 1 -``` - -### 2. - -```lean -syntax "good morning" : term -syntax "hello" : command -syntax "yellow" : tactic - --- Note: the following are highlighted in red, however that's just because we haven't implemented the semantics ("elaboration function") yet - the syntax parsing stage works. - -#eval good morning -- works --- good morning -- error: `expected command` - -hello -- works --- #eval hello -- error: `expected term` - -example : 2 + 2 = 4 := by - yellow -- works --- yellow -- error: `expected command` --- #eval yellow -- error: `unknown identifier 'yellow'` -``` - -### 3. - -```lean -syntax (name := colors) (("blue"+) <|> ("red"+)) num : command - -@[command_elab colors] -def elabColors : CommandElab := fun stx => Lean.logInfo "success!" - -blue blue 443 -red red red 4 -``` - -### 4. - -```lean -syntax (name := help) "#better_help" "option" (ident)? : command - -@[command_elab help] -def elabHelp : CommandElab := fun stx => Lean.logInfo "success!" - -#better_help option -#better_help option pp.r -#better_help option some.other.name -``` - -### 5. - -```lean --- Note: std4 has to be in dependencies of your project for this to work. -syntax (name := bigsumin) "∑ " Std.ExtendedBinder.extBinder "in " term "," term : term - -@[term_elab bigsumin] -def elabSum : TermElab := fun stx tp => - return mkNatLit 666 - -#eval ∑ x in { 1, 2, 3 }, x^2 - -def hi := (∑ x in { "apple", "banana", "cherry" }, x.length) + 1 -#eval hi -``` diff --git a/md/solutions/07_elaboration.md b/md/solutions/07_elaboration.md deleted file mode 100644 index 49c3866..0000000 --- a/md/solutions/07_elaboration.md +++ /dev/null @@ -1,98 +0,0 @@ -```lean -import Lean -open Lean Elab Command Term Meta -``` - -## Elaboration: Solutions - -### 1. - -```lean -elab n:term "♥" a:"♥"? b:"♥"? : term => do - let nExpr : Expr ← elabTermEnsuringType n (mkConst `Nat) - if let some a := a then - if let some b := b then - return Expr.app (Expr.app (Expr.const `Nat.add []) nExpr) (mkNatLit 3) - else - return Expr.app (Expr.app (Expr.const `Nat.add []) nExpr) (mkNatLit 2) - else - return Expr.app (Expr.app (Expr.const `Nat.add []) nExpr) (mkNatLit 1) - -#eval 7 ♥ -- 8 -#eval 7 ♥♥ -- 9 -#eval 7 ♥♥♥ -- 10 -``` - -### 2. - -```lean --- a) using `syntax` + `@[command_elab alias] def elabOurAlias : CommandElab` -syntax (name := aliasA) (docComment)? "aliasA " ident " ← " ident* : command - -@[command_elab «aliasA»] -def elabOurAlias : CommandElab := λ stx => - match stx with - | `(aliasA $x:ident ← $ys:ident*) => - for y in ys do - Lean.logInfo y - | _ => - throwUnsupportedSyntax - -aliasA hi.hello ← d.d w.w nnn - --- b) using `syntax` + `elab_rules`. -syntax (name := aliasB) (docComment)? "aliasB " ident " ← " ident* : command - -elab_rules : command - | `(command | aliasB $m:ident ← $ys:ident*) => - for y in ys do - Lean.logInfo y - -aliasB hi.hello ← d.d w.w nnn - --- c) using `elab` -elab "aliasC " x:ident " ← " ys:ident* : command => - for y in ys do - Lean.logInfo y - -aliasC hi.hello ← d.d w.w nnn -``` - -### 3. - -```lean -open Parser.Tactic - --- a) using `syntax` + `@[tactic nthRewrite] def elabNthRewrite : Lean.Elab.Tactic.Tactic`. -syntax (name := nthRewriteA) "nth_rewriteA " (config)? num rwRuleSeq (ppSpace location)? : tactic - -@[tactic nthRewriteA] def elabNthRewrite : Lean.Elab.Tactic.Tactic := fun stx => do - match stx with - | `(tactic| nth_rewriteA $[$cfg]? $n $rules $_loc) => - Lean.logInfo "rewrite location!" - | `(tactic| nth_rewriteA $[$cfg]? $n $rules) => - Lean.logInfo "rewrite target!" - | _ => - throwUnsupportedSyntax - --- b) using `syntax` + `elab_rules`. -syntax (name := nthRewriteB) "nth_rewriteB " (config)? num rwRuleSeq (ppSpace location)? : tactic - -elab_rules (kind := nthRewriteB) : tactic - | `(tactic| nth_rewriteB $[$cfg]? $n $rules $_loc) => - Lean.logInfo "rewrite location!" - | `(tactic| nth_rewriteB $[$cfg]? $n $rules) => - Lean.logInfo "rewrite target!" - --- c) using `elab`. -elab "nth_rewriteC " (config)? num rwRuleSeq loc:(ppSpace location)? : tactic => - if let some loc := loc then - Lean.logInfo "rewrite location!" - else - Lean.logInfo "rewrite target!" - -example : 2 + 2 = 4 := by - nth_rewriteC 2 [← add_zero] at h - nth_rewriteC 2 [← add_zero] - sorry -``` diff --git a/md/solutions/09_tactics.md b/md/solutions/09_tactics.md deleted file mode 100644 index 17af738..0000000 --- a/md/solutions/09_tactics.md +++ /dev/null @@ -1,197 +0,0 @@ -```lean -import Lean.Elab.Tactic -open Lean Elab Tactic Meta -``` - -### 1. - -```lean -elab "step_1" : tactic => do - let mvarId ← getMainGoal - let goalType ← getMainTarget - - let Expr.app (Expr.app (Expr.const `Iff _) a) b := goalType | throwError "Goal type is not of the form `a ↔ b`" - - -- 1. Create new `_`s with appropriate types. - let mvarId1 ← mkFreshExprMVar (Expr.forallE `xxx a b .default) (userName := "red") - let mvarId2 ← mkFreshExprMVar (Expr.forallE `yyy b a .default) (userName := "blue") - - -- 2. Assign the main goal to the expression `Iff.intro _ _`. - mvarId.assign (mkAppN (Expr.const `Iff.intro []) #[a, b, mvarId1, mvarId2]) - - -- 3. Report the new `_`s to Lean as the new goals. - modify fun _ => { goals := [mvarId1.mvarId!, mvarId2.mvarId!] } - -elab "step_2" : tactic => do - let some redMvarId ← (← get).goals.findM? (fun mvarId => do - return (← mvarId.getDecl).userName == `red - ) | throwError "No mvar with username `red`" - let some blueMvarId ← (← get).goals.findM? (fun mvarId => do - return (← mvarId.getDecl).userName == `blue - ) | throwError "No mvar with username `blue`" - - ---- HANDLE `red` goal - let Expr.forallE _ redFrom redTo _ := (← redMvarId.getDecl).type | throwError "Goal type is not of the form `a → b`" - let handyRedMvarId ← withLocalDecl `hA BinderInfo.default redFrom (fun fvar => do - -- 1. Create new `_`s with appropriate types. - let mvarId1 ← mkFreshExprMVar redTo MetavarKind.syntheticOpaque `red - -- 2. Assign the main goal to the expression `fun hA => _`. - redMvarId.assign (← mkLambdaFVars #[fvar] mvarId1) - -- just a handy way to return a handyRedMvarId for the next code - return mvarId1.mvarId! - ) - -- 3. Report the new `_`s to Lean as the new goals. - modify fun _ => { goals := [handyRedMvarId, blueMvarId] } - - ---- HANDLE `blue` goal - let Expr.forallE _ blueFrom _ _ := (← blueMvarId.getDecl).type | throwError "Goal type is not of the form `a → b`" - -- 1. Create new `_`s with appropriate types. - -- None needed! - -- 2. Assign the main goal to the expression `fun hB : q ∧ p => (And.intro hB.right hB.left)`. - Lean.Meta.withLocalDecl `hB .default blueFrom (fun hB => do - let body ← Lean.Meta.mkAppM `And.intro #[← Lean.Meta.mkAppM `And.right #[hB], ← Lean.Meta.mkAppM `And.left #[hB]] - blueMvarId.assign (← Lean.Meta.mkLambdaFVars #[hB] body) - ) - -- 3. Report the new `_`s to Lean as the new goals. - modify fun _ => { goals := [handyRedMvarId] } - -elab "step_3" : tactic => do - let mvarId ← getMainGoal - let goalType ← getMainTarget - let mainDecl ← mvarId.getDecl - - let Expr.app (Expr.app (Expr.const `And _) q) p := goalType | throwError "Goal type is not of the form `And q p`" - - -- 1. Create new `_`s with appropriate types. - let mvarId1 ← mkFreshExprMVarAt mainDecl.lctx mainDecl.localInstances q (userName := "red1") - let mvarId2 ← mkFreshExprMVarAt mainDecl.lctx mainDecl.localInstances p (userName := "red2") - - -- 2. Assign the main goal to the expression `And.intro _ _`. - mvarId.assign (← mkAppM `And.intro #[mvarId1, mvarId2]) - - -- 3. Report the new `_`s to Lean as the new goals. - modify fun _ => { goals := [mvarId1.mvarId!, mvarId2.mvarId!] } - -elab "step_4" : tactic => do - let some red1MvarId ← (← get).goals.findM? (fun mvarId => do - return (← mvarId.getDecl).userName == `red1 - ) | throwError "No mvar with username `red1`" - let some red2MvarId ← (← get).goals.findM? (fun mvarId => do - return (← mvarId.getDecl).userName == `red2 - ) | throwError "No mvar with username `red2`" - - ---- HANDLE `red1` goal - -- 1. Create new `_`s with appropriate types. - -- None needed! - -- 2. Assign the main goal to the expression `hA.right`. - let some hA := (← red1MvarId.getDecl).lctx.findFromUserName? `hA | throwError "No hypothesis with name `hA` (in goal `red1`)" - red1MvarId.withContext do - red1MvarId.assign (← mkAppM `And.right #[hA.toExpr]) - -- 3. Report the new `_`s to Lean as the new goals. - modify fun _ => { goals := [red2MvarId] } - - ---- HANDLE `red2` goal - -- 1. Create new `_`s with appropriate types. - -- None needed! - -- 2. Assign the main goal to the expression `hA.left`. - let some hA := (← red2MvarId.getDecl).lctx.findFromUserName? `hA | throwError "No hypothesis with name `hA` (in goal `red2`)" - red2MvarId.withContext do - red2MvarId.assign (← mkAppM `And.left #[hA.toExpr]) - -- 3. Report the new `_`s to Lean as the new goals. - modify fun _ => { goals := [] } - -theorem gradual (p q : Prop) : p ∧ q ↔ q ∧ p := by - step_1 - step_2 - step_3 - step_4 -``` - -### 2. - -```lean -elab "forker_a" : tactic => do - liftMetaTactic fun mvarId => do - let (Expr.app (Expr.app (Expr.const `And _) p) q) ← mvarId.getType | Lean.Meta.throwTacticEx `forker mvarId ("Goal is not of the form P ∧ Q") - - let mvarIdP ← mkFreshExprMVar p (userName := "red") - let mvarIdQ ← mkFreshExprMVar q (userName := "blue") - - let proofTerm := mkAppN (Expr.const `And.intro []) #[p, q, mvarIdP, mvarIdQ] - mvarId.assign proofTerm - - return [mvarIdP.mvarId!, mvarIdQ.mvarId!] - -elab "forker_b" : tactic => do - let mvarId ← getMainGoal - let goalType ← getMainTarget - - let (Expr.app (Expr.app (Expr.const `And _) p) q) := goalType | Lean.Meta.throwTacticEx `forker mvarId ("Goal is not of the form P ∧ Q") - - mvarId.withContext do - let mvarIdP ← mkFreshExprMVar p (userName := "red") - let mvarIdQ ← mkFreshExprMVar q (userName := "blue") - - let proofTerm := mkAppN (Expr.const `And.intro []) #[p, q, mvarIdP, mvarIdQ] - mvarId.assign proofTerm - - setGoals ([mvarIdP.mvarId!, mvarIdQ.mvarId!] ++ (← getGoals).drop 1) - -elab "forker_c" : tactic => do - let mvarId ← getMainGoal - let goalType ← getMainTarget - - let (Expr.app (Expr.app (Expr.const `And _) p) q) := goalType | Lean.Meta.throwTacticEx `forker mvarId ("Goal is not of the form P ∧ Q") - - mvarId.withContext do - let mvarIdP ← mkFreshExprMVar p (userName := "red") - let mvarIdQ ← mkFreshExprMVar q (userName := "blue") - - let proofTerm := mkAppN (Expr.const `And.intro []) #[p, q, mvarIdP, mvarIdQ] - mvarId.assign proofTerm - - replaceMainGoal [mvarIdP.mvarId!, mvarIdQ.mvarId!] - -example (A B C : Prop) : A → B → C → (A ∧ B) ∧ C := by - intro hA hB hC - forker_a - forker_a - assumption - assumption - assumption -``` - -### 3. - -```lean -elab "introductor_a" : tactic => do - withMainContext do - liftMetaTactic fun mvarId => do - let (_, mvarIdNew) ← mvarId.introN 2 - return [mvarIdNew] - -elab "introductor_b" : tactic => do - withMainContext do - liftMetaTactic fun mvarId => do - let (_, mvarIdNew) ← mvarId.intro1P - return [mvarIdNew] - -elab "introductor_c" : tactic => do - withMainContext do - liftMetaTactic fun mvarId => do - let (_, mvarIdNew) ← mvarId.intro `wow - return [mvarIdNew] - --- So: --- `intro` - **intro**, specify the name manually --- `intro1` - **intro**, make the name inacessible --- `intro1P` - **intro**, preserve the original name --- `introN` - **intro many**, specify the names manually --- `introNP` - **intro many**, preserve the original names - -example (a b c : Nat) : (ab: a = b) → (bc: b = c) → (a = c) := by - introductor_a - -- introductor_b - -- introductor_c - sorry -``` diff --git a/theme/highlight.js b/theme/highlight.js new file mode 100644 index 0000000..1d08a72 --- /dev/null +++ b/theme/highlight.js @@ -0,0 +1,1209 @@ +/* + Highlight.js 10.3.2 (31e1fc40) + License: BSD-3-Clause + Copyright (c) 2006-2020, Ivan Sagalaev +*/ +var hljs=function(){"use strict";function e(n){Object.freeze(n) + ;var t="function"==typeof n + ;return Object.getOwnPropertyNames(n).forEach((function(r){ + !Object.hasOwnProperty.call(n,r)||null===n[r]||"object"!=typeof n[r]&&"function"!=typeof n[r]||t&&("caller"===r||"callee"===r||"arguments"===r)||Object.isFrozen(n[r])||e(n[r]) + })),n}class n{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data} + ignoreMatch(){this.ignore=!0}}function t(e){ + return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'") + }function r(e,...n){var t={};for(const n in e)t[n]=e[n] + ;return n.forEach((function(e){for(const n in e)t[n]=e[n]})),t}function a(e){ + return e.nodeName.toLowerCase()}var i=Object.freeze({__proto__:null, + escapeHTML:t,inherit:r,nodeStream:function(e){var n=[];return function e(t,r){ + for(var i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?r+=i.nodeValue.length:1===i.nodeType&&(n.push({ + event:"start",offset:r,node:i}),r=e(i,r),a(i).match(/br|hr|img|input/)||n.push({ + event:"stop",offset:r,node:i}));return r}(e,0),n},mergeStreams:function(e,n,r){ + var i=0,s="",o=[];function l(){ + return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function u(e){ + s+=""}function g(e){("start"===e.event?c:u)(e.node)} + for(;e.length||n.length;){var d=l() + ;if(s+=t(r.substring(i,d[0].offset)),i=d[0].offset,d===e){o.reverse().forEach(u) + ;do{g(d.splice(0,1)[0]),d=l()}while(d===e&&d.length&&d[0].offset===i) + ;o.reverse().forEach(c) + }else"start"===d[0].event?o.push(d[0].node):o.pop(),g(d.splice(0,1)[0])} + return s+t(r.substr(i))}});const s=e=>!!e.kind;class o{constructor(e,n){ + this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){ + this.buffer+=t(e)}openNode(e){if(!s(e))return;let n=e.kind + ;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){ + s(e)&&(this.buffer+="")}value(){return this.buffer}span(e){ + this.buffer+=``}}class l{constructor(){this.rootNode={ + children:[]},this.stack=[this.rootNode]}get top(){ + return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){ + this.top.children.push(e)}openNode(e){const n={kind:e,children:[]} + ;this.add(n),this.stack.push(n)}closeNode(){ + if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){ + for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)} + walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){ + return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n), + n.children.forEach((n=>this._walk(e,n))),e.closeNode(n)),e}static _collapse(e){ + "string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{ + l._collapse(e)})))}}class c extends l{constructor(e){super(),this.options=e} + addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())} + addText(e){""!==e&&this.add(e)}addSublanguage(e,n){const t=e.root + ;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){ + return new o(this,this.options).value()}finalize(){return!0}}function u(e){ + return e?"string"==typeof e?e:e.source:null} + const g="[a-zA-Z]\\w*",d="[a-zA-Z_]\\w*",h="\\b\\d+(\\.\\d+)?",f="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",p="\\b(0b[01]+)",m={ + begin:"\\\\[\\s\\S]",relevance:0},b={className:"string",begin:"'",end:"'", + illegal:"\\n",contains:[m]},v={className:"string",begin:'"',end:'"', + illegal:"\\n",contains:[m]},x={ + begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ + },E=function(e,n,t={}){var a=r({className:"comment",begin:e,end:n,contains:[] + },t);return a.contains.push(x),a.contains.push({className:"doctag", + begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),a + },_=E("//","$"),w=E("/\\*","\\*/"),N=E("#","$");var y=Object.freeze({ + __proto__:null,IDENT_RE:g,UNDERSCORE_IDENT_RE:d,NUMBER_RE:h,C_NUMBER_RE:f, + BINARY_NUMBER_RE:p, + RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~", + SHEBANG:(e={})=>{const n=/^#![ ]*\//;return e.binary&&(e.begin=function(...e){ + return e.map((e=>u(e))).join("")}(n,/.*\b/,e.binary,/\b.*/)),r({ + className:"meta",begin:n,end:/$/,relevance:0,"on:begin":(e,n)=>{ + 0!==e.index&&n.ignoreMatch()}},e)},BACKSLASH_ESCAPE:m,APOS_STRING_MODE:b, + QUOTE_STRING_MODE:v,PHRASAL_WORDS_MODE:x,COMMENT:E,C_LINE_COMMENT_MODE:_, + C_BLOCK_COMMENT_MODE:w,HASH_COMMENT_MODE:N,NUMBER_MODE:{className:"number", + begin:h,relevance:0},C_NUMBER_MODE:{className:"number",begin:f,relevance:0}, + BINARY_NUMBER_MODE:{className:"number",begin:p,relevance:0},CSS_NUMBER_MODE:{ + className:"number", + begin:h+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?", + relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp", + begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[m,{begin:/\[/,end:/\]/, + relevance:0,contains:[m]}]}]},TITLE_MODE:{className:"title",begin:g,relevance:0 + },UNDERSCORE_TITLE_MODE:{className:"title",begin:d,relevance:0},METHOD_GUARD:{ + begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:function(e){ + return Object.assign(e,{"on:begin":(e,n)=>{n.data._beginMatch=e[1]}, + "on:end":(e,n)=>{n.data._beginMatch!==e[1]&&n.ignoreMatch()}})} + }),R="of and for in not or if then".split(" ");function k(e){function n(n,t){ + return RegExp(u(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))}class t{ + constructor(){ + this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0} + addRule(e,n){ + n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]), + this.matchAt+=function(e){return RegExp(e.toString()+"|").exec("").length-1 + }(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null) + ;const e=this.regexes.map((e=>e[1]));this.matcherRe=n(function(e,n="|"){ + for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i0&&(a+=n),a+="(";o.length>0;){var l=t.exec(o) + ;if(null==l){a+=o;break} + a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length), + "\\"===l[0][0]&&l[1]?a+="\\"+(Number(l[1])+s):(a+=l[0],"("===l[0]&&r++)}a+=")"} + return a}(e),!0),this.lastIndex=0}exec(e){ + this.matcherRe.lastIndex=this.lastIndex;const n=this.matcherRe.exec(e) + ;if(!n)return null + ;const t=n.findIndex(((e,n)=>n>0&&void 0!==e)),r=this.matchIndexes[t] + ;return n.splice(0,t),Object.assign(n,r)}}class a{constructor(){ + this.rules=[],this.multiRegexes=[], + this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){ + if(this.multiRegexes[e])return this.multiRegexes[e];const n=new t + ;return this.rules.slice(e).forEach((([e,t])=>n.addRule(e,t))), + n.compile(),this.multiRegexes[e]=n,n}resumingScanAtSamePosition(){ + return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,n){ + this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){ + const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex + ;let t=n.exec(e) + ;if(this.resumingScanAtSamePosition())if(t&&t.index===this.lastIndex);else{ + const n=this.getMatcher(0);n.lastIndex=this.lastIndex+1,t=n.exec(e)} + return t&&(this.regexIndex+=t.position+1, + this.regexIndex===this.count&&this.considerAll()),t}}function i(e,n){ + const t=e.input[e.index-1],r=e.input[e.index+e[0].length] + ;"."!==t&&"."!==r||n.ignoreMatch()} + if(e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.") + ;return function t(s,o){const l=s;if(s.compiled)return l + ;s.compiled=!0,s.__beforeBegin=null,s.keywords=s.keywords||s.beginKeywords + ;let c=null + ;if("object"==typeof s.keywords&&(c=s.keywords.$pattern,delete s.keywords.$pattern), + s.keywords&&(s.keywords=function(e,n){var t={} + ;return"string"==typeof e?r("keyword",e):Object.keys(e).forEach((function(n){ + r(n,e[n])})),t;function r(e,r){ + n&&(r=r.toLowerCase()),r.split(" ").forEach((function(n){var r=n.split("|") + ;t[r[0]]=[e,O(r[0],r[1])]}))} + }(s.keywords,e.case_insensitive)),s.lexemes&&c)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ") + ;return l.keywordPatternRe=n(s.lexemes||c||/\w+/,!0), + o&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)", + s.__beforeBegin=i), + s.begin||(s.begin=/\B|\b/),l.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin), + s.end||s.endsWithParent||(s.end=/\B|\b/), + s.end&&(l.endRe=n(s.end)),l.terminator_end=u(s.end)||"", + s.endsWithParent&&o.terminator_end&&(l.terminator_end+=(s.end?"|":"")+o.terminator_end)), + s.illegal&&(l.illegalRe=n(s.illegal)), + void 0===s.relevance&&(s.relevance=1),s.contains||(s.contains=[]), + s.contains=[].concat(...s.contains.map((function(e){return function(e){ + return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){ + return r(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:M(e)?r(e,{ + starts:e.starts?r(e.starts):null}):Object.isFrozen(e)?r(e):e}("self"===e?s:e) + }))),s.contains.forEach((function(e){t(e,l) + })),s.starts&&t(s.starts,o),l.matcher=function(e){const n=new a + ;return e.contains.forEach((e=>n.addRule(e.begin,{rule:e,type:"begin" + }))),e.terminator_end&&n.addRule(e.terminator_end,{type:"end" + }),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(l),l}(e)}function M(e){ + return!!e&&(e.endsWithParent||M(e.starts))}function O(e,n){ + return n?Number(n):function(e){return R.includes(e.toLowerCase())}(e)?0:1} + const L={props:["language","code","autodetect"],data:function(){return{ + detectedLanguage:"",unknownLanguage:!1}},computed:{className(){ + return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){ + if(!this.autoDetect&&!hljs.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`), + this.unknownLanguage=!0,t(this.code);let e + ;return this.autoDetect?(e=hljs.highlightAuto(this.code), + this.detectedLanguage=e.language):(e=hljs.highlight(this.language,this.code,this.ignoreIllegals), + this.detectectLanguage=this.language),e.value},autoDetect(){ + return!(this.language&&(e=this.autodetect,!e&&""!==e));var e}, + ignoreIllegals:()=>!0},render(e){return e("pre",{},[e("code",{ + class:this.className,domProps:{innerHTML:this.highlighted}})])}},j={install(e){ + e.component("highlightjs",L)} + },I=t,T=r,{nodeStream:S,mergeStreams:A}=i,B=Symbol("nomatch") + ;return function(t){ + var r=[],a=Object.create(null),i=Object.create(null),s=[],o=!0,l=/(^(<[^>]+>|\t|)+|\n)/gm,u="Could not find the language '{}', did you forget to load/include a language module?" + ;const g={disableAutodetect:!0,name:"Plain text",contains:[]};var d={ + noHighlightRe:/^(no-?highlight)$/i, + languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-", + tabReplace:null,useBR:!1,languages:null,__emitter:c};function h(e){ + return d.noHighlightRe.test(e)}function f(e,n,t,r){var a={code:n,language:e} + ;N("before:highlight",a);var i=a.result?a.result:p(a.language,a.code,t,r) + ;return i.code=a.code,N("after:highlight",i),i}function p(e,t,r,i){var s=t + ;function l(e,n){var t=_.case_insensitive?n[0].toLowerCase():n[0] + ;return Object.prototype.hasOwnProperty.call(e.keywords,t)&&e.keywords[t]} + function c(){null!=y.subLanguage?function(){if(""!==O){var e=null + ;if("string"==typeof y.subLanguage){ + if(!a[y.subLanguage])return void M.addText(O) + ;e=p(y.subLanguage,O,!0,R[y.subLanguage]),R[y.subLanguage]=e.top + }else e=m(O,y.subLanguage.length?y.subLanguage:null) + ;y.relevance>0&&(L+=e.relevance),M.addSublanguage(e.emitter,e.language)} + }():function(){if(!y.keywords)return void M.addText(O);let e=0 + ;y.keywordPatternRe.lastIndex=0;let n=y.keywordPatternRe.exec(O),t="";for(;n;){ + t+=O.substring(e,n.index);const r=l(y,n);if(r){const[e,a]=r + ;M.addText(t),t="",L+=a,M.addKeyword(n[0],e)}else t+=n[0] + ;e=y.keywordPatternRe.lastIndex,n=y.keywordPatternRe.exec(O)} + t+=O.substr(e),M.addText(t)}(),O=""}function g(e){ + return e.className&&M.openNode(e.className),y=Object.create(e,{parent:{value:y} + })}function h(e,t,r){let a=function(e,n){var t=e&&e.exec(n) + ;return t&&0===t.index}(e.endRe,r);if(a){if(e["on:end"]){const r=new n(e) + ;e["on:end"](t,r),r.ignore&&(a=!1)}if(a){for(;e.endsParent&&e.parent;)e=e.parent + ;return e}}if(e.endsWithParent)return h(e.parent,t,r)}function f(e){ + return 0===y.matcher.regexIndex?(O+=e[0],1):(S=!0,0)}function b(e){ + var n=e[0],t=s.substr(e.index),r=h(y,e,t);if(!r)return B;var a=y + ;a.skip?O+=n:(a.returnEnd||a.excludeEnd||(O+=n),c(),a.excludeEnd&&(O=n));do{ + y.className&&M.closeNode(),y.skip||y.subLanguage||(L+=y.relevance),y=y.parent + }while(y!==r.parent) + ;return r.starts&&(r.endSameAsBegin&&(r.starts.endRe=r.endRe), + g(r.starts)),a.returnEnd?0:n.length}var v={};function x(t,a){var i=a&&a[0] + ;if(O+=t,null==i)return c(),0 + ;if("begin"===v.type&&"end"===a.type&&v.index===a.index&&""===i){ + if(O+=s.slice(a.index,a.index+1),!o){const n=Error("0 width match regex") + ;throw n.languageName=e,n.badRule=v.rule,n}return 1} + if(v=a,"begin"===a.type)return function(e){var t=e[0],r=e.rule + ;const a=new n(r),i=[r.__beforeBegin,r["on:begin"]] + ;for(const n of i)if(n&&(n(e,a),a.ignore))return f(t) + ;return r&&r.endSameAsBegin&&(r.endRe=RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")), + r.skip?O+=t:(r.excludeBegin&&(O+=t), + c(),r.returnBegin||r.excludeBegin||(O=t)),g(r),r.returnBegin?0:t.length}(a) + ;if("illegal"===a.type&&!r){ + const e=Error('Illegal lexeme "'+i+'" for mode "'+(y.className||"")+'"') + ;throw e.mode=y,e}if("end"===a.type){var l=b(a);if(l!==B)return l} + if("illegal"===a.type&&""===i)return 1 + ;if(T>1e5&&T>3*a.index)throw Error("potential infinite loop, way more iterations than matches") + ;return O+=i,i.length}var _=E(e) + ;if(!_)throw console.error(u.replace("{}",e)),Error('Unknown language: "'+e+'"') + ;var w=k(_),N="",y=i||w,R={},M=new d.__emitter(d);!function(){ + for(var e=[],n=y;n!==_;n=n.parent)n.className&&e.unshift(n.className) + ;e.forEach((e=>M.openNode(e)))}();var O="",L=0,j=0,T=0,S=!1;try{ + for(y.matcher.considerAll();;){ + T++,S?S=!1:y.matcher.considerAll(),y.matcher.lastIndex=j + ;const e=y.matcher.exec(s);if(!e)break;const n=x(s.substring(j,e.index),e) + ;j=e.index+n}return x(s.substr(j)),M.closeAllNodes(),M.finalize(),N=M.toHTML(),{ + relevance:L,value:N,language:e,illegal:!1,emitter:M,top:y}}catch(n){ + if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{ + msg:n.message,context:s.slice(j-100,j+100),mode:n.mode},sofar:N,relevance:0, + value:I(s),emitter:M};if(o)return{illegal:!1,relevance:0,value:I(s),emitter:M, + language:e,top:y,errorRaised:n};throw n}}function m(e,n){ + n=n||d.languages||Object.keys(a);var t=function(e){const n={relevance:0, + emitter:new d.__emitter(d),value:I(e),illegal:!1,top:g} + ;return n.emitter.addText(e),n}(e),r=t + ;return n.filter(E).filter(w).forEach((function(n){var a=p(n,e,!1);a.language=n, + a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a) + })),r.language&&(t.second_best=r),t}function b(e){ + return d.tabReplace||d.useBR?e.replace(l,(e=>"\n"===e?d.useBR?"
":e:d.tabReplace?e.replace(/\t/g,d.tabReplace):e)):e + }function v(e){let n=null;const t=function(e){var n=e.className+" " + ;n+=e.parentNode?e.parentNode.className:"";const t=d.languageDetectRe.exec(n) + ;if(t){var r=E(t[1]) + ;return r||(console.warn(u.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)), + r?t[1]:"no-highlight"}return n.split(/\s+/).find((e=>h(e)||E(e)))}(e) + ;if(h(t))return;N("before:highlightBlock",{block:e,language:t + }),d.useBR?(n=document.createElement("div"), + n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e + ;const r=n.textContent,a=t?f(t,r,!0):m(r),s=S(n);if(s.length){ + const e=document.createElement("div");e.innerHTML=a.value,a.value=A(s,S(e),r)} + a.value=b(a.value),N("after:highlightBlock",{block:e,result:a + }),e.innerHTML=a.value,e.className=function(e,n,t){var r=n?i[n]:t,a=[e.trim()] + ;return e.match(/\bhljs\b/)||a.push("hljs"), + e.includes(r)||a.push(r),a.join(" ").trim() + }(e.className,t,a.language),e.result={language:a.language,re:a.relevance, + relavance:a.relevance},a.second_best&&(e.second_best={ + language:a.second_best.language,re:a.second_best.relevance, + relavance:a.second_best.relevance})}const x=()=>{if(!x.called){x.called=!0 + ;var e=document.querySelectorAll("pre code");r.forEach.call(e,v)}} + ;function E(e){return e=(e||"").toLowerCase(),a[e]||a[i[e]]} + function _(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach((e=>{i[e]=n + }))}function w(e){var n=E(e);return n&&!n.disableAutodetect}function N(e,n){ + var t=e;s.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(t,{highlight:f, + highlightAuto:m,fixMarkup:function(e){ + return console.warn("fixMarkup is deprecated and will be removed entirely in v11.0"), + console.warn("Please see https://github.com/highlightjs/highlight.js/issues/2534"), + b(e)},highlightBlock:v,configure:function(e){ + e.useBR&&(console.warn("'useBR' option is deprecated and will be removed entirely in v11.0"), + console.warn("Please see https://github.com/highlightjs/highlight.js/issues/2559")), + d=T(d,e)},initHighlighting:x,initHighlightingOnLoad:function(){ + window.addEventListener("DOMContentLoaded",x,!1)}, + registerLanguage:function(e,n){var r=null;try{r=n(t)}catch(n){ + if(console.error("Language definition for '{}' could not be registered.".replace("{}",e)), + !o)throw n;console.error(n),r=g} + r.name||(r.name=e),a[e]=r,r.rawDefinition=n.bind(null,t), + r.aliases&&_(r.aliases,{languageName:e})},listLanguages:function(){ + return Object.keys(a)},getLanguage:E,registerAliases:_, + requireLanguage:function(e){var n=E(e);if(n)return n + ;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))}, + autoDetection:w,inherit:T,addPlugin:function(e){s.push(e)},vuePlugin:j + }),t.debugMode=function(){o=!1},t.safeMode=function(){o=!0 + },t.versionString="10.3.2";for(const n in y)"object"==typeof y[n]&&e(y[n]) + ;return Object.assign(t,y),t}({})}() + ;"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs); + hljs.registerLanguage("apache",function(){"use strict";return function(e){ + var n={className:"number", + begin:"\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?"};return{ + name:"Apache config",aliases:["apacheconf"],case_insensitive:!0, + contains:[e.HASH_COMMENT_MODE,{className:"section",begin:"", + contains:[n,{className:"number",begin:":\\d{1,5}" + },e.inherit(e.QUOTE_STRING_MODE,{relevance:0})]},{className:"attribute", + begin:/\w+/,relevance:0,keywords:{ + nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername" + },starts:{end:/$/,relevance:0,keywords:{literal:"on off all deny allow"}, + contains:[{className:"meta",begin:"\\s\\[",end:"\\]$"},{className:"variable", + begin:"[\\$%]\\{",end:"\\}",contains:["self",{className:"number", + begin:"[\\$%]\\d+"}]},n,{className:"number",begin:"\\d+"},e.QUOTE_STRING_MODE]} + }],illegal:/\S/}}}()); + hljs.registerLanguage("bash",function(){"use strict";return function(e){ + const s={};Object.assign(s,{className:"variable",variants:[{ + begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/, + contains:[s]}]}]});const n={className:"subst",begin:/\$\(/,end:/\)/, + contains:[e.BACKSLASH_ESCAPE]},t={begin:/<<-?\s*(?=\w+)/,starts:{ + contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,className:"string"})]} + },a={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,n]} + ;n.contains.push(a);const i={begin:/\$\(\(/,end:/\)\)/,contains:[{ + begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,s]},c=e.SHEBANG({ + binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10}),o={ + className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0, + contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{ + name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b[a-z._-]+\b/, + keyword:"if then else elif fi for while in do done case esac function", + literal:"true false", + built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp" + },contains:[c,e.SHEBANG(),o,i,e.COMMENT("(?")+")",i={ + className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},s={className:"string", + variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n", + contains:[e.BACKSLASH_ESCAPE]},{ + begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)", + end:"'",illegal:"."},e.END_SAME_AS_BEGIN({ + begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},o={ + className:"number",variants:[{begin:"\\b(0b[01']+)"},{ + begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{ + begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)" + }],relevance:0},c={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{ + "meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include" + },contains:[{begin:/\\\n/,relevance:0},e.inherit(s,{className:"meta-string"}),{ + className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n" + },n,e.C_BLOCK_COMMENT_MODE]},l={className:"title",begin:t(r)+e.IDENT_RE, + relevance:0},d=t(r)+e.IDENT_RE+"\\s*\\(",u={ + keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq", + built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary", + literal:"true false nullptr NULL"},m=[c,i,n,e.C_BLOCK_COMMENT_MODE,o,s],p={ + variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{ + beginKeywords:"new throw return else",end:/;/}],keywords:u,contains:m.concat([{ + begin:/\(/,end:/\)/,keywords:u,contains:m.concat(["self"]),relevance:0}]), + relevance:0},_={className:"function",begin:"("+a+"[\\*&\\s]+)+"+d, + returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:u,illegal:/[^\w\s\*&:<>]/, + contains:[{begin:"decltype\\(auto\\)",keywords:u,relevance:0},{begin:d, + returnBegin:!0,contains:[l],relevance:0},{className:"params",begin:/\(/, + end:/\)/,keywords:u,relevance:0,contains:[n,e.C_BLOCK_COMMENT_MODE,s,o,i,{ + begin:/\(/,end:/\)/,keywords:u,relevance:0, + contains:["self",n,e.C_BLOCK_COMMENT_MODE,s,o,i]}] + },i,n,e.C_BLOCK_COMMENT_MODE,c]};return{ + aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],keywords:u, + disableAutodetect:!0,illegal:"",keywords:u,contains:["self",i]},{begin:e.IDENT_RE+"::",keywords:u},{ + className:"class",beginKeywords:"enum class struct union",end:/[{;:<>=]/, + contains:[{beginKeywords:"final class struct"},e.TITLE_MODE]}]),exports:{ + preprocessor:c,strings:s,keywords:u}}}}()); + hljs.registerLanguage("c",function(){"use strict";return function(e){ + var n=e.requireLanguage("c-like").rawDefinition() + ;return n.name="C",n.aliases=["c","h"],n}}()); + hljs.registerLanguage("coffeescript",function(){"use strict" + ;const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]) + ;return function(r){var t,i={ + keyword:e.concat(["then","unless","until","loop","by","when","and","or","is","isnt","not"]).filter((t=["var","const","let","function","static"], + e=>!t.includes(e))).join(" "), + literal:n.concat(["yes","no","on","off"]).join(" "), + built_in:a.concat(["npm","print"]).join(" ")},s="[A-Za-z$_][0-9A-Za-z$_]*",o={ + className:"subst",begin:/#\{/,end:/}/,keywords:i + },c=[r.BINARY_NUMBER_MODE,r.inherit(r.C_NUMBER_MODE,{starts:{end:"(\\s*/)?", + relevance:0}}),{className:"string",variants:[{begin:/'''/,end:/'''/, + contains:[r.BACKSLASH_ESCAPE]},{begin:/'/,end:/'/,contains:[r.BACKSLASH_ESCAPE] + },{begin:/"""/,end:/"""/,contains:[r.BACKSLASH_ESCAPE,o]},{begin:/"/,end:/"/, + contains:[r.BACKSLASH_ESCAPE,o]}]},{className:"regexp",variants:[{begin:"///", + end:"///",contains:[o,r.HASH_COMMENT_MODE]},{begin:"//[gim]{0,3}(?=\\W)", + relevance:0},{begin:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{begin:"@"+s + },{subLanguage:"javascript",excludeBegin:!0,excludeEnd:!0,variants:[{ + begin:"```",end:"```"},{begin:"`",end:"`"}]}];o.contains=c + ;var l=r.inherit(r.TITLE_MODE,{begin:s}),d="(\\(.*\\))?\\s*\\B[-=]>",g={ + className:"params",begin:"\\([^\\(]",returnBegin:!0,contains:[{begin:/\(/, + end:/\)/,keywords:i,contains:["self"].concat(c)}]};return{name:"CoffeeScript", + aliases:["coffee","cson","iced"],keywords:i,illegal:/\/\*/, + contains:c.concat([r.COMMENT("###","###"),r.HASH_COMMENT_MODE,{ + className:"function",begin:"^\\s*"+s+"\\s*=\\s*"+d,end:"[-=]>",returnBegin:!0, + contains:[l,g]},{begin:/[:\(,=]\s*/,relevance:0,contains:[{className:"function", + begin:d,end:"[-=]>",returnBegin:!0,contains:[g]}]},{className:"class", + beginKeywords:"class",end:"$",illegal:/[:="\[\]]/,contains:[{ + beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[l]},l] + },{begin:s+":",end:":",returnBegin:!0,returnEnd:!0,relevance:0}])}}}()); + hljs.registerLanguage("cpp",function(){"use strict";return function(e){ + var i=e.requireLanguage("c-like").rawDefinition();return i.disableAutodetect=!1, + i.name="C++",i.aliases=["cc","c++","h++","hpp","hh","hxx","cxx"],i}}()); + hljs.registerLanguage("csharp",function(){"use strict";return function(e){ + var n={ + keyword:["abstract","as","base","break","case","class","const","continue","do","else","event","explicit","extern","finally","fixed","for","foreach","goto","if","implicit","in","interface","internal","is","lock","namespace","new","operator","out","override","params","private","protected","public","readonly","record","ref","return","sealed","sizeof","stackalloc","static","struct","switch","this","throw","try","typeof","unchecked","unsafe","using","virtual","void","volatile","while"].concat(["add","alias","and","ascending","async","await","by","descending","equals","from","get","global","group","init","into","join","let","nameof","not","notnull","on","or","orderby","partial","remove","select","set","unmanaged","value","var","when","where","with","yield"]).join(" "), + built_in:"bool byte char decimal delegate double dynamic enum float int long nint nuint object sbyte short string ulong unit ushort", + literal:"default false null true"},i=e.inherit(e.TITLE_MODE,{ + begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{ + begin:"\\b(0b[01']+)"},{ + begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{ + begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)" + }],relevance:0},s={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}] + },t=e.inherit(s,{illegal:/\n/}),r={className:"subst",begin:"{",end:"}", + keywords:n},l=e.inherit(r,{illegal:/\n/}),c={className:"string",begin:/\$"/, + end:'"',illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},e.BACKSLASH_ESCAPE,l] + },o={className:"string",begin:/\$@"/,end:'"',contains:[{begin:"{{"},{begin:"}}" + },{begin:'""'},r]},d=e.inherit(o,{illegal:/\n/,contains:[{begin:"{{"},{ + begin:"}}"},{begin:'""'},l]}) + ;r.contains=[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE], + l.contains=[d,c,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{ + illegal:/\n/})];var g={variants:[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE] + },E={begin:"<",end:">",contains:[{beginKeywords:"in out"},i] + },_=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",b={ + begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"], + keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0, + contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{ + begin:"\x3c!--|--\x3e"},{begin:""}]}] + }),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#", + end:"$",keywords:{ + "meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum" + }},g,a,{beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:,]/, + contains:[{beginKeywords:"where class" + },i,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace", + end:/[{;=]/,illegal:/[^\s:]/, + contains:[i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{ + beginKeywords:"record",end:/[{;=]/,illegal:/[^\s:]/, + contains:[i,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta", + begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{ + className:"meta-string",begin:/"/,end:/"/}]},{ + beginKeywords:"new return throw await else",relevance:0},{className:"function", + begin:"("+_+"\\s+)+"+e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0, + end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{ + beginKeywords:"public private protected static internal protected abstract async extern override unsafe virtual new sealed partial" + },{begin:e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0, + contains:[e.TITLE_MODE,E],relevance:0},{className:"params",begin:/\(/,end:/\)/, + excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0, + contains:[g,a,e.C_BLOCK_COMMENT_MODE] + },e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},b]}}}()); + hljs.registerLanguage("css",function(){"use strict";return function(e){var n={ + begin:/(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/,returnBegin:!0,end:";", + endsWithParent:!0,contains:[{className:"attribute",begin:/\S/,end:":", + excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,contains:[{ + begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in",begin:/[\w-]+/ + },{begin:/\(/,end:/\)/, + contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}] + },e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{ + className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}] + }}]};return{name:"CSS",case_insensitive:!0,illegal:/[=\/|'\$]/, + contains:[e.C_BLOCK_COMMENT_MODE,{className:"selector-id", + begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:/\.[A-Za-z0-9_-]+/},{ + className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$", + contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},{className:"selector-pseudo", + begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"@(page|font-face)", + lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]", + illegal:/:/,returnBegin:!0,contains:[{className:"keyword", + begin:/@\-?\w[\w]*(\-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0, + relevance:0,keywords:"and or not only",contains:[{begin:/[a-z-]+:/, + className:"attribute"},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE] + }]},{className:"selector-tag",begin:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{ + begin:"{",end:"}",illegal:/\S/,contains:[e.C_BLOCK_COMMENT_MODE,n]}]}}}()); + hljs.registerLanguage("diff",function(){"use strict";return function(e){return{ + name:"Diff",aliases:["patch"],contains:[{className:"meta",relevance:10, + variants:[{begin:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{ + begin:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{begin:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{ + className:"comment",variants:[{begin:/Index: /,end:/$/},{begin:/={3,}/,end:/$/ + },{begin:/^\-{3}/,end:/$/},{begin:/^\*{3} /,end:/$/},{begin:/^\+{3}/,end:/$/},{ + begin:/^\*{15}$/}]},{className:"addition",begin:"^\\+",end:"$"},{ + className:"deletion",begin:"^\\-",end:"$"},{className:"addition",begin:"^\\!", + end:"$"}]}}}()); + hljs.registerLanguage("go",function(){"use strict";return function(e){var n={ + keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune", + literal:"true false iota nil", + built_in:"append cap close complex copy imag len make new panic print println real recover delete" + };return{name:"Go",aliases:["golang"],keywords:n,illegal:"e(n))).join("")}return function(a){var s={className:"number", + relevance:0,variants:[{begin:/([\+\-]+)?[\d]+_[\d_]+/},{begin:a.NUMBER_RE}] + },i=a.COMMENT();i.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];var t={ + className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)}/}] + },r={className:"literal",begin:/\bon|off|true|false|yes|no\b/},l={ + className:"string",contains:[a.BACKSLASH_ESCAPE],variants:[{begin:"'''", + end:"'''",relevance:10},{begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"' + },{begin:"'",end:"'"}]},c={begin:/\[/,end:/\]/,contains:[i,r,t,l,s,"self"], + relevance:0 + },g="("+[/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/].map((n=>e(n))).join("|")+")" + ;return{name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/, + contains:[i,{className:"section",begin:/\[+/,end:/\]+/},{ + begin:n(g,"(\\s*\\.\\s*",g,")*",n("(?=",/\s*=\s*[^#\s]/,")")),className:"attr", + starts:{end:/$/,contains:[i,c,r,t,l,s]}}]}}}()); + hljs.registerLanguage("java",function(){"use strict";function e(e){ + return e?"string"==typeof e?e:e.source:null}function n(e){return a("(",e,")?")} + function a(...n){return n.map((n=>e(n))).join("")}function s(...n){ + return"("+n.map((n=>e(n))).join("|")+")"}return function(e){ + var r="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",i={ + className:"meta",begin:"@[\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*", + contains:[{begin:/\(/,end:/\)/,contains:["self"]}] + },t=e=>a("[",e,"]+([",e,"_]*[",e,"]+)?"),c={className:"number",variants:[{ + begin:`\\b(0[bB]${t("01")})[lL]?`},{begin:`\\b(0${t("0-7")})[dDfFlL]?`},{ + begin:a(/\b0[xX]/,s(a(t("a-fA-F0-9"),/\./,t("a-fA-F0-9")),a(t("a-fA-F0-9"),/\.?/),a(/\./,t("a-fA-F0-9"))),/([pP][+-]?(\d+))?/,/[fFdDlL]?/) + },{begin:a(/\b/,s(a(/\d*\./,t("\\d")),t("\\d")),/[eE][+-]?[\d]+[dDfF]?/)},{ + begin:a(/\b/,t(/\d/),n(/\.?/),n(t(/\d/)),/[dDfFlL]?/)}],relevance:0};return{ + name:"Java",aliases:["jsp"],keywords:r,illegal:/<\/|#/, + contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/, + relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}] + }),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{ + className:"class",beginKeywords:"class interface enum",end:/[{;=]/, + excludeEnd:!0,keywords:"class interface enum",illegal:/[:"\[\]]/,contains:[{ + beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{ + beginKeywords:"new throw return else",relevance:0},{className:"class", + begin:"record\\s+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,excludeEnd:!0, + end:/[{;=]/,keywords:r,contains:[{beginKeywords:"record"},{ + begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0, + contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/, + keywords:r,relevance:0,contains:[e.C_BLOCK_COMMENT_MODE] + },e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"function", + begin:"([\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*(<[\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*(\\s*,\\s*[\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(", + returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:r,contains:[{ + begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0, + contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/, + keywords:r,relevance:0, + contains:[i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE] + },e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},c,i]}}}()); + hljs.registerLanguage("javascript",function(){"use strict" + ;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],s=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]) + ;function r(e){return i("(?=",e,")")}function t(e){return i("(",e,")?")} + function i(...e){return e.map((e=>{ + return(n=e)?"string"==typeof n?n:n.source:null;var n})).join("")} + return function(c){const o=e,l={begin:/<[A-Za-z0-9\\._:-]+/, + end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{ + const a=e[0].length+e.index,s=e.input[a];"<"!==s?">"===s&&(((e,{after:n})=>{ + const a=e[0].replace("<","`\\b0[${e}][${n}]([${n}_]*[${n}])?n?`,b=/[1-9]([0-9_]*\d)?/,E=/\d([0-9_]*\d)?/,u=i(/[eE][+-]?/,E),_={ + className:"number",variants:[{begin:d("bB","01")},{begin:d("oO","0-7")},{ + begin:d("xX","0-9a-fA-F")},{begin:i(/\b/,b,"n")},{begin:i(/(\b0)?\./,E,t(u))},{ + begin:i(/\b/,b,t(i(/\./,t(E))),t(u))},{begin:/\b0[\.n]?/}],relevance:0},m={ + className:"subst",begin:"\\$\\{",end:"\\}",keywords:g,contains:[]},N={ + begin:"html`",end:"",starts:{end:"`",returnEnd:!1, + contains:[c.BACKSLASH_ESCAPE,m],subLanguage:"xml"}},y={begin:"css`",end:"", + starts:{end:"`",returnEnd:!1,contains:[c.BACKSLASH_ESCAPE,m],subLanguage:"css"} + },f={className:"string",begin:"`",end:"`",contains:[c.BACKSLASH_ESCAPE,m]},A={ + className:"comment",variants:[c.COMMENT("/\\*\\*","\\*/",{relevance:0, + contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type", + begin:"\\{",end:"\\}",relevance:0},{className:"variable", + begin:o+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/, + relevance:0}]}]}),c.C_BLOCK_COMMENT_MODE,c.C_LINE_COMMENT_MODE] + },p=[c.APOS_STRING_MODE,c.QUOTE_STRING_MODE,N,y,f,_,c.REGEXP_MODE] + ;m.contains=p.concat({begin:/{/,end:/}/,keywords:g,contains:["self"].concat(p)}) + ;const O=[].concat(A,m.contains),T=O.concat([{begin:/\(/,end:/\)/,keywords:g, + contains:["self"].concat(O)}]),R={className:"params",begin:/\(/,end:/\)/, + excludeBegin:!0,excludeEnd:!0,keywords:g,contains:T};return{name:"Javascript", + aliases:["js","jsx","mjs","cjs"],keywords:g,exports:{PARAMS_CONTAINS:T}, + illegal:/#(?![$_A-z])/,contains:[c.SHEBANG({label:"shebang",binary:"node", + relevance:5}),{label:"use_strict",className:"meta",relevance:10, + begin:/^\s*['"]use (strict|asm)['"]/ + },c.APOS_STRING_MODE,c.QUOTE_STRING_MODE,N,y,f,A,_,{ + begin:i(/[{,\n]\s*/,r(i(/(\/\/.*$)*/,/(\/\*(.|\n)*\*\/)*/,/\s*/,o+"\\s*:"))), + relevance:0,contains:[{className:"attr",begin:o+r("\\s*:"),relevance:0}]},{ + begin:"("+c.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*", + keywords:"return throw case",contains:[A,c.REGEXP_MODE,{className:"function", + begin:"(\\([^()]*(\\([^()]*(\\([^()]*\\))*[^()]*\\))*[^()]*\\)|"+c.UNDERSCORE_IDENT_RE+")\\s*=>", + returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{ + begin:c.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{ + begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:g,contains:T}]}]},{ + begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{ + begin:"<>",end:""},{begin:l.begin,"on:begin":l.isTrulyOpeningTag,end:l.end}], + subLanguage:"xml",contains:[{begin:l.begin,end:l.end,skip:!0,contains:["self"]}] + }],relevance:0},{className:"function",beginKeywords:"function",end:/[{;]/, + excludeEnd:!0,keywords:g,contains:["self",c.inherit(c.TITLE_MODE,{begin:o}),R], + illegal:/%/},{className:"function", + begin:c.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\))*[^()]*\\))*[^()]*\\)\\s*{", + returnBegin:!0,contains:[R,c.inherit(c.TITLE_MODE,{begin:o})]},{variants:[{ + begin:"\\."+o},{begin:"\\$"+o}],relevance:0},{className:"class", + beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{ + beginKeywords:"extends"},c.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/, + end:/[\{;]/,excludeEnd:!0,contains:[c.inherit(c.TITLE_MODE,{begin:o}),"self",R] + },{begin:"(get|set)\\s+(?="+o+"\\()",end:/{/,keywords:"get set", + contains:[c.inherit(c.TITLE_MODE,{begin:o}),{begin:/\(\)/},R]},{begin:/\$[(.]/}] + }}}()); + hljs.registerLanguage("json",function(){"use strict";return function(n){var e={ + literal:"true false null" + },i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],t=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],a={ + end:",",endsWithParent:!0,excludeEnd:!0,contains:t,keywords:e},l={begin:"{", + end:"}",contains:[{className:"attr",begin:/"/,end:/"/, + contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(a,{begin:/:/ + })].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(a)], + illegal:"\\S"};return t.push(l,s),i.forEach((function(n){t.push(n)})),{ + name:"JSON",contains:t,keywords:e,illegal:"\\S"}}}()); + hljs.registerLanguage("kotlin",function(){"use strict";return function(e){ + var n={ + keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual", + built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing", + literal:"true false null"},a={className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"@" + },i={className:"subst",begin:"\\${",end:"}",contains:[e.C_NUMBER_MODE]},s={ + className:"variable",begin:"\\$"+e.UNDERSCORE_IDENT_RE},t={className:"string", + variants:[{begin:'"""',end:'"""(?=[^"])',contains:[s,i]},{begin:"'",end:"'", + illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/, + contains:[e.BACKSLASH_ESCAPE,s,i]}]};i.contains.push(t);var r={className:"meta", + begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UNDERSCORE_IDENT_RE+")?" + },l={className:"meta",begin:"@"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/, + end:/\)/,contains:[e.inherit(t,{className:"meta-string"})]}] + },c=e.COMMENT("/\\*","\\*/",{contains:[e.C_BLOCK_COMMENT_MODE]}),o={variants:[{ + className:"type",begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/,contains:[]}] + },d=o;return d.variants[1].contains=[o],o.variants[1].contains=[d],{ + name:"Kotlin",aliases:["kt"],keywords:n,contains:[e.COMMENT("/\\*\\*","\\*/",{ + relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"}] + }),e.C_LINE_COMMENT_MODE,c,{className:"keyword", + begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol", + begin:/@\w+/}]}},a,r,l,{className:"function",beginKeywords:"fun",end:"[(]|$", + returnBegin:!0,excludeEnd:!0,keywords:n, + illegal:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,relevance:5,contains:[{ + begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0, + contains:[e.UNDERSCORE_TITLE_MODE]},{className:"type",begin://, + keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/, + endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\/]/, + endsWithParent:!0,contains:[o,e.C_LINE_COMMENT_MODE,c],relevance:0 + },e.C_LINE_COMMENT_MODE,c,r,l,t,e.C_NUMBER_MODE]},c]},{className:"class", + beginKeywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0, + illegal:"extends implements",contains:[{ + beginKeywords:"public protected internal private constructor" + },e.UNDERSCORE_TITLE_MODE,{className:"type",begin://,excludeBegin:!0, + excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,]|$/, + excludeBegin:!0,returnEnd:!0},r,l]},t,{className:"meta",begin:"^#!/usr/bin/env", + end:"$",illegal:"\n"},{className:"number", + begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?", + relevance:0}]}}}()); + hljs.registerLanguage("less",function(){"use strict";return function(e){ + var n="([\\w-]+|@{[\\w-]+})",a=[],s=[],t=function(e){return{className:"string", + begin:"~?"+e+".*?"+e}},r=function(e,n,a){return{className:e,begin:n,relevance:a} + },i={begin:"\\(",end:"\\)",contains:s,relevance:0} + ;s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t("'"),t('"'),e.CSS_NUMBER_MODE,{ + begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]", + excludeEnd:!0} + },r("number","#[0-9A-Fa-f]+\\b"),i,r("variable","@@?[\\w-]+",10),r("variable","@{[\\w-]+}"),r("built_in","~?`[^`]*?`"),{ + className:"attribute",begin:"[\\w-]+\\s*:",end:":",returnBegin:!0,excludeEnd:!0 + },{className:"meta",begin:"!important"});var c=s.concat({begin:"{",end:"}", + contains:a}),l={beginKeywords:"when",endsWithParent:!0,contains:[{ + beginKeywords:"and not"}].concat(s)},o={begin:n+"\\s*:",returnBegin:!0, + end:"[;}]",relevance:0,contains:[{className:"attribute",begin:n,end:":", + excludeEnd:!0,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s} + }]},g={className:"keyword", + begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b", + starts:{end:"[;{}]",returnEnd:!0,contains:s,relevance:0}},d={ + className:"variable",variants:[{begin:"@[\\w-]+\\s*:",relevance:15},{ + begin:"@[\\w-]+"}],starts:{end:"[;}]",returnEnd:!0,contains:c}},b={variants:[{ + begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:n,end:"{"}],returnBegin:!0, + returnEnd:!0,illegal:"[<='$\"]",relevance:0, + contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,l,r("keyword","all\\b"),r("variable","@{[\\w-]+}"),r("selector-tag",n+"%?",0),r("selector-id","#"+n),r("selector-class","\\."+n,0),r("selector-tag","&",0),{ + className:"selector-attr",begin:"\\[",end:"\\]"},{className:"selector-pseudo", + begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"\\(",end:"\\)",contains:c},{ + begin:"!important"}]} + ;return a.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,g,d,o,b),{ + name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:a}}}()); + hljs.registerLanguage("lua",function(){"use strict";return function(e){ + var t="\\[=*\\[",a="\\]=*\\]",n={begin:t,end:a,contains:["self"] + },o=[e.COMMENT("--(?!\\[=*\\[)","$"),e.COMMENT("--\\[=*\\[",a,{contains:[n], + relevance:10})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE, + literal:"true false nil", + keyword:"and break do else elseif end for goto if in local not or repeat return then until while", + built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove" + },contains:o.concat([{className:"function",beginKeywords:"function",end:"\\)", + contains:[e.inherit(e.TITLE_MODE,{ + begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params", + begin:"\\(",endsWithParent:!0,contains:o}].concat(o) + },e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string", + begin:t,end:a,contains:[n],relevance:5}])}}}()); + hljs.registerLanguage("makefile",function(){"use strict";return function(e){ + var i={className:"variable",variants:[{ + begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)",contains:[e.BACKSLASH_ESCAPE]},{ + begin:/\$[@%`]+/}]}]}]};return{name:"HTML, XML", + aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"], + case_insensitive:!0,contains:[{className:"meta",begin:"", + relevance:10,contains:[a,i,t,s,{begin:"\\[",end:"\\]",contains:[{ + className:"meta",begin:"",contains:[a,s,i,t]}]}] + },e.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[", + end:"\\]\\]>",relevance:10},n,{className:"meta",begin:/<\?xml/,end:/\?>/, + relevance:10},{className:"tag",begin:")",end:">",keywords:{ + name:"style"},contains:[c],starts:{end:"",returnEnd:!0, + subLanguage:["css","xml"]}},{className:"tag",begin:")",end:">", + keywords:{name:"script"},contains:[c],starts:{end:"<\/script>",returnEnd:!0, + subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:"",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},c]}]}} + }()); + hljs.registerLanguage("markdown",function(){"use strict";return function(n){ + const e={begin:"<",end:">",subLanguage:"xml",relevance:0},a={ + begin:"\\[.+?\\][\\(\\[].*?[\\)\\]]",returnBegin:!0,contains:[{ + className:"string",begin:"\\[",end:"\\]",excludeBegin:!0,returnEnd:!0, + relevance:0},{className:"link",begin:"\\]\\(",end:"\\)",excludeBegin:!0, + excludeEnd:!0},{className:"symbol",begin:"\\]\\[",end:"\\]",excludeBegin:!0, + excludeEnd:!0}],relevance:10},i={className:"strong",contains:[],variants:[{ + begin:/_{2}/,end:/_{2}/},{begin:/\*{2}/,end:/\*{2}/}]},s={className:"emphasis", + contains:[],variants:[{begin:/\*(?!\*)/,end:/\*/},{begin:/_(?!_)/,end:/_/, + relevance:0}]};i.contains.push(s),s.contains.push(i);var c=[e,a] + ;return i.contains=i.contains.concat(c),s.contains=s.contains.concat(c),{ + name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{className:"section", + variants:[{begin:"^#{1,6}",end:"$",contains:c=c.concat(i,s)},{ + begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n", + contains:c}]}]},e,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)", + end:"\\s+",excludeEnd:!0},i,s,{className:"quote",begin:"^>\\s+",contains:c, + end:"$"},{className:"code",variants:[{begin:"(`{3,})(.|\\n)*?\\1`*[ ]*"},{ + begin:"(~{3,})(.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{begin:"~~~", + end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",contains:[{ + begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{begin:"^[-\\*]{3,}",end:"$" + },a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{className:"symbol", + begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{className:"link", + begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}}()); + hljs.registerLanguage("nginx",function(){"use strict";return function(e){var n={ + className:"variable",variants:[{begin:/\$\d+/},{begin:/\$\{/,end:/}/},{ + begin:"[\\$\\@]"+e.UNDERSCORE_IDENT_RE}]},a={endsWithParent:!0,keywords:{ + $pattern:"[a-z/_]+", + literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll" + },relevance:0,illegal:"=>",contains:[e.HASH_COMMENT_MODE,{className:"string", + contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:/"/,end:/"/},{begin:/'/,end:/'/ + }]},{begin:"([a-z]+):/",end:"\\s",endsWithParent:!0,excludeEnd:!0,contains:[n] + },{className:"regexp",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:"\\s\\^", + end:"\\s|{|;",returnEnd:!0},{begin:"~\\*?\\s+",end:"\\s|{|;",returnEnd:!0},{ + begin:"\\*(\\.[a-z\\-]+)+"},{begin:"([a-z\\-]+\\.)+\\*"}]},{className:"number", + begin:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{ + className:"number",begin:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},n]};return{ + name:"Nginx config",aliases:["nginxconf"],contains:[e.HASH_COMMENT_MODE,{ + begin:e.UNDERSCORE_IDENT_RE+"\\s+{",returnBegin:!0,end:"{",contains:[{ + className:"section",begin:e.UNDERSCORE_IDENT_RE}],relevance:0},{ + begin:e.UNDERSCORE_IDENT_RE+"\\s",end:";|{",returnBegin:!0,contains:[{ + className:"attribute",begin:e.UNDERSCORE_IDENT_RE,starts:a}],relevance:0}], + illegal:"[^\\s\\}]"}}}()); + hljs.registerLanguage("objectivec",function(){"use strict";return function(e){ + var n=/[a-zA-Z@][a-zA-Z0-9_]*/,_={$pattern:n, + keyword:"@interface @class @protocol @implementation"};return{ + name:"Objective-C",aliases:["mm","objc","obj-c","obj-c++","objective-c++"], + keywords:{$pattern:n, + keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN", + literal:"false true FALSE TRUE nil YES NO NULL", + built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once" + },illegal:"/,end:/$/, + illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{ + className:"class",begin:"("+_.keyword.split(" ").join("|")+")\\b",end:"({|$)", + excludeEnd:!0,keywords:_,contains:[e.UNDERSCORE_TITLE_MODE]},{ + begin:"\\."+e.UNDERSCORE_IDENT_RE,relevance:0}]}}}()); + hljs.registerLanguage("perl",function(){"use strict";return function(e){var n={ + $pattern:/[\w.]+/, + keyword:"getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qq fileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmget sub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedir ioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when" + },t={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:n},s={begin:"->{", + end:"}"},r={variants:[{begin:/\$\d/},{ + begin:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{begin:/[\$%@][^\s\w{]/, + relevance:0}] + },i=[e.BACKSLASH_ESCAPE,t,r],a=[r,e.HASH_COMMENT_MODE,e.COMMENT("^\\=\\w","\\=cut",{ + endsWithParent:!0}),s,{className:"string",contains:i,variants:[{ + begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[", + end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{ + begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*\\<", + end:"\\>",relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'", + contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`", + contains:[e.BACKSLASH_ESCAPE]},{begin:"{\\w+}",contains:[],relevance:0},{ + begin:"-?\\w+\\s*\\=\\>",contains:[],relevance:0}]},{className:"number", + begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b", + relevance:0},{ + begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*", + keywords:"split return print reverse grep",relevance:0, + contains:[e.HASH_COMMENT_MODE,{className:"regexp", + begin:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",relevance:10},{ + className:"regexp",begin:"(m|qr)?/",end:"/[a-z]*",contains:[e.BACKSLASH_ESCAPE], + relevance:0}]},{className:"function",beginKeywords:"sub", + end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{ + begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$", + subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}] + }];return t.contains=a,s.contains=a,{name:"Perl",aliases:["pl","pm"],keywords:n, + contains:a}}}()); + hljs.registerLanguage("php",function(){"use strict";return function(e){var r={ + begin:"\\$+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*"},t={className:"meta", + variants:[{begin:/<\?php/,relevance:10},{begin:/<\?[=]?/},{begin:/\?>/}]},a={ + className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}] + },n=e.inherit(e.APOS_STRING_MODE,{illegal:null + }),i=e.inherit(e.QUOTE_STRING_MODE,{illegal:null, + contains:e.QUOTE_STRING_MODE.contains.concat(a)}),o=e.END_SAME_AS_BEGIN({ + begin:/<<<[ \t]*(\w+)\n/,end:/[ \t]*(\w+)\b/, + contains:e.QUOTE_STRING_MODE.contains.concat(a)}),l={className:"string", + contains:[e.BACKSLASH_ESCAPE,t],variants:[e.inherit(n,{begin:"b'",end:"'" + }),e.inherit(i,{begin:'b"',end:'"'}),i,n,o]},s={ + variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]},c={ + keyword:"__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ die echo exit include include_once print require require_once array abstract and as binary bool boolean break callable case catch class clone const continue declare default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval extends final finally float for foreach from global goto if implements instanceof insteadof int integer interface isset iterable list match new object or private protected public real return string switch throw trait try unset use var void while xor yield", + literal:"false null true", + built_in:"Error|0 AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Throwable Traversable WeakReference Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass" + };return{aliases:["php","php3","php4","php5","php6","php7","php8"], + case_insensitive:!0,keywords:c, + contains:[e.HASH_COMMENT_MODE,e.COMMENT("//","$",{contains:[t] + }),e.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}] + }),e.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0, + keywords:"__halt_compiler"}),t,{className:"keyword",begin:/\$this\b/},r,{ + begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function", + beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]", + contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)", + excludeBegin:!0,excludeEnd:!0,keywords:c, + contains:["self",r,e.C_BLOCK_COMMENT_MODE,l,s]}]},{className:"class", + beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/, + contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{ + beginKeywords:"namespace",end:";",illegal:/[\.']/, + contains:[e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",end:";", + contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"=>"},l,s]}}}()); + hljs.registerLanguage("php-template",function(){"use strict";return function(n){ + return{name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/, + end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{ + begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0 + },n.inherit(n.APOS_STRING_MODE,{illegal:null,className:null,contains:null, + skip:!0}),n.inherit(n.QUOTE_STRING_MODE,{illegal:null,className:null, + contains:null,skip:!0})]}]}}}()); + hljs.registerLanguage("plaintext",function(){"use strict";return function(t){ + return{name:"Plain text",aliases:["text","txt"],disableAutodetect:!0}}}()); + hljs.registerLanguage("properties",function(){"use strict";return function(e){ + var n="[ \\t\\f]*",t="("+n+"[:=]"+n+"|[ \\t\\f]+)",a="([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",s="([^\\\\:= \\t\\f\\n]|\\\\.)+",r={ + end:t,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{ + begin:"\\\\\\n"}]}};return{name:".properties",case_insensitive:!0,illegal:/\S/, + contains:[e.COMMENT("^\\s*[!#]","$"),{begin:a+t,returnBegin:!0,contains:[{ + className:"attr",begin:a,endsParent:!0,relevance:0}],starts:r},{begin:s+t, + returnBegin:!0,relevance:0,contains:[{className:"meta",begin:s,endsParent:!0, + relevance:0}],starts:r},{className:"attr",relevance:0,begin:s+n+"$"}]}}}()); + hljs.registerLanguage("python",function(){"use strict";return function(e){ + const n={ + keyword:"and as assert async await break class continue def del elif else except finally for from global if import in is lambda nonlocal|10 not or pass raise return try while with yield", + built_in:"__import__ abs all any ascii bin bool breakpoint bytearray bytes callable chr classmethod compile complex delattr dict dir divmod enumerate eval exec filter float format frozenset getattr globals hasattr hash help hex id input int isinstance issubclass iter len list locals map max memoryview min next object oct open ord pow print property range repr reversed round set setattr slice sorted staticmethod str sum super tuple type vars zip", + literal:"__debug__ Ellipsis False None NotImplemented True"},a={ + className:"meta",begin:/^(>>>|\.\.\.) /},s={className:"subst",begin:/\{/, + end:/\}/,keywords:n,illegal:/#/},i={begin:/\{\{/,relevance:0},r={ + className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{ + begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?'''/,end:/'''/, + contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{ + begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?"""/,end:/"""/, + contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{ + begin:/([fF][rR]|[rR][fF]|[fF])'''/,end:/'''/, + contains:[e.BACKSLASH_ESCAPE,a,i,s]},{begin:/([fF][rR]|[rR][fF]|[fF])"""/, + end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,i,s]},{begin:/([uU]|[rR])'/,end:/'/, + relevance:10},{begin:/([uU]|[rR])"/,end:/"/,relevance:10},{ + begin:/([bB]|[bB][rR]|[rR][bB])'/,end:/'/},{begin:/([bB]|[bB][rR]|[rR][bB])"/, + end:/"/},{begin:/([fF][rR]|[rR][fF]|[fF])'/,end:/'/, + contains:[e.BACKSLASH_ESCAPE,i,s]},{begin:/([fF][rR]|[rR][fF]|[fF])"/,end:/"/, + contains:[e.BACKSLASH_ESCAPE,i,s]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},t={ + className:"number",relevance:0,variants:[{begin:e.BINARY_NUMBER_RE+"[lLjJ]?"},{ + begin:"\\b(0o[0-7]+)[lLjJ]?"},{begin:e.C_NUMBER_RE+"[lLjJ]?"}]},l={ + className:"params",variants:[{begin:/\(\s*\)/,skip:!0,className:null},{ + begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n, + contains:["self",a,t,r,e.HASH_COMMENT_MODE]}]};return s.contains=[r,t,a],{ + name:"Python",aliases:["py","gyp","ipython"],keywords:n, + illegal:/(<\/|->|\?)|=>/,contains:[a,t,{beginKeywords:"if",relevance:0 + },r,e.HASH_COMMENT_MODE,{variants:[{className:"function",beginKeywords:"def"},{ + className:"class",beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/, + contains:[e.UNDERSCORE_TITLE_MODE,l,{begin:/->/,endsWithParent:!0, + keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,end:/$/},{ + begin:/\b(print|exec)\(/}]}}}()); + hljs.registerLanguage("python-repl",function(){"use strict";return function(n){ + return{aliases:["pycon"],contains:[{className:"meta",starts:{end:/ |$/,starts:{ + end:"$",subLanguage:"python"}},variants:[{begin:/^>>>(?=[ ]|$)/},{ + begin:/^\.\.\.(?=[ ]|$)/}]}]}}}()); + hljs.registerLanguage("ruby",function(){"use strict";return function(e){ + var n="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",a={ + keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor", + literal:"true false nil"},s={className:"doctag",begin:"@[A-Za-z]+"},i={ + begin:"#<",end:">"},r=[e.COMMENT("#","$",{contains:[s] + }),e.COMMENT("^\\=begin","^\\=end",{contains:[s],relevance:10 + }),e.COMMENT("^__END__","\\n$")],c={className:"subst",begin:"#\\{",end:"}", + keywords:a},t={className:"string",contains:[e.BACKSLASH_ESCAPE,c],variants:[{ + begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{ + begin:"%[qQwWx]?\\(",end:"\\)"},{begin:"%[qQwWx]?\\[",end:"\\]"},{ + begin:"%[qQwWx]?{",end:"}"},{begin:"%[qQwWx]?<",end:">"},{begin:"%[qQwWx]?/", + end:"/"},{begin:"%[qQwWx]?%",end:"%"},{begin:"%[qQwWx]?-",end:"-"},{ + begin:"%[qQwWx]?\\|",end:"\\|"},{ + begin:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{ + begin:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,returnBegin:!0,contains:[{ + begin:/<<[-~]?'?/},e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/, + contains:[e.BACKSLASH_ESCAPE,c]})]}]},b={className:"params",begin:"\\(", + end:"\\)",endsParent:!0,keywords:a},d=[t,i,{className:"class", + beginKeywords:"class module",end:"$|;",illegal:/=/, + contains:[e.inherit(e.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{ + begin:"<\\s*",contains:[{begin:"("+e.IDENT_RE+"::)?"+e.IDENT_RE}]}].concat(r)},{ + className:"function",beginKeywords:"def",end:"$|;", + contains:[e.inherit(e.TITLE_MODE,{begin:n}),b].concat(r)},{begin:e.IDENT_RE+"::" + },{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(\\!|\\?)?:",relevance:0},{ + className:"symbol",begin:":(?!\\s)",contains:[t,{begin:n}],relevance:0},{ + className:"number", + begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b", + relevance:0},{begin:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{className:"params", + begin:/\|/,end:/\|/,keywords:a},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*", + keywords:"unless",contains:[i,{className:"regexp", + contains:[e.BACKSLASH_ESCAPE,c],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*" + },{begin:"%r{",end:"}[a-z]*"},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!", + end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(r),relevance:0 + }].concat(r);c.contains=d,b.contains=d;var g=[{begin:/^\s*=>/,starts:{end:"$", + contains:d}},{className:"meta", + begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)", + starts:{end:"$",contains:d}}];return{name:"Ruby", + aliases:["rb","gemspec","podspec","thor","irb"],keywords:a,illegal:/\/\*/, + contains:r.concat(g).concat(d)}}}()); + hljs.registerLanguage("rust",function(){"use strict";return function(e){ + var n="([ui](8|16|32|64|128|size)|f(32|64))?",t="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!" + ;return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?", + keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield", + literal:"true false Some None Ok Err",built_in:t},illegal:""}]}}}()); + hljs.registerLanguage("scss",function(){"use strict";return function(e){ + var t="@[a-z-]+",i={className:"variable",begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b" + },r={className:"number",begin:"#[0-9A-Fa-f]+"} + ;return e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE, + e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{name:"SCSS",case_insensitive:!0, + illegal:"[=/|']",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{ + className:"selector-id",begin:"\\#[A-Za-z0-9_-]+",relevance:0},{ + className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0},{ + className:"selector-attr",begin:"\\[",end:"\\]",illegal:"$"},{ + className:"selector-tag", + begin:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b", + relevance:0},{className:"selector-pseudo", + begin:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)" + },{className:"selector-pseudo", + begin:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)" + },i,{className:"attribute", + begin:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b", + illegal:"[^\\s]"},{ + begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b" + },{begin:":",end:";", + contains:[i,r,e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{ + className:"meta",begin:"!important"}]},{begin:"@(page|font-face)",lexemes:t, + keywords:"@page @font-face"},{begin:"@",end:"[{;]",returnBegin:!0, + keywords:"and or not only",contains:[{begin:t,className:"keyword" + },i,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,r,e.CSS_NUMBER_MODE]}]}}}()); + hljs.registerLanguage("shell",function(){"use strict";return function(s){return{ + name:"Shell Session",aliases:["console"],contains:[{className:"meta", + begin:"^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]",starts:{end:"$",subLanguage:"bash"} + }]}}}()); + hljs.registerLanguage("sql",function(){"use strict";return function(e){ + var t=e.COMMENT("--","$");return{name:"SQL",case_insensitive:!0, + illegal:/[<>{}*]/,contains:[{ + beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with", + end:/;/,endsWithParent:!0,keywords:{$pattern:/[\w\.]+/, + keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek", + literal:"true false null unknown", + built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void" + },contains:[{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{ + className:"string",begin:'"',end:'"',contains:[{begin:'""'}]},{ + className:"string",begin:"`",end:"`" + },e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE] + },e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]}}}()); + hljs.registerLanguage("swift",function(){"use strict";return function(e){var i={ + keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet", + literal:"true false nil", + built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip" + },n=e.COMMENT("/\\*","\\*/",{contains:["self"]}),t={className:"subst", + begin:/\\\(/,end:"\\)",keywords:i,contains:[]},a={className:"string", + contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:/"""/,end:/"""/},{begin:/"/, + end:/"/}]},r={className:"number", + begin:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b", + relevance:0};return t.contains=[r],{name:"Swift",keywords:i, + contains:[a,e.C_LINE_COMMENT_MODE,n,{className:"type", + begin:"\\b[A-Z][\\w\xc0-\u02b8']*[!?]"},{className:"type", + begin:"\\b[A-Z][\\w\xc0-\u02b8']*",relevance:0},r,{className:"function", + beginKeywords:"func",end:"{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{ + begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{begin://},{className:"params", + begin:/\(/,end:/\)/,endsParent:!0,keywords:i, + contains:["self",r,a,e.C_BLOCK_COMMENT_MODE,{begin:":"}],illegal:/["']/}], + illegal:/\[|%/},{className:"class", + beginKeywords:"struct protocol class extension enum",keywords:i,end:"\\{", + excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{ + begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{className:"meta", + begin:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)\\b" + },{beginKeywords:"import",end:/$/,contains:[e.C_LINE_COMMENT_MODE,n]}]}}}()); + hljs.registerLanguage("typescript",function(){"use strict" + ;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],s=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]) + ;function t(e){return r("(?=",e,")")}function i(e){return r("(",e,")?")} + function r(...e){return e.map((e=>{ + return(n=e)?"string"==typeof n?n:n.source:null;var n})).join("")} + return function(c){const o={$pattern:e, + keyword:n.concat(["type","namespace","typedef","interface","public","private","protected","implements","declare","abstract","readonly"]).join(" "), + literal:a.join(" "), + built_in:s.concat(["any","void","number","boolean","string","object","never","enum"]).join(" ") + },l={className:"meta",begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},d=(e,n,a)=>{ + const s=e.contains.findIndex((e=>e.label===n)) + ;if(-1===s)throw Error("can not find mode to replace");e.contains.splice(s,1,a) + },g=function(c){const o=e,l={begin:/<[A-Za-z0-9\\._:-]+/, + end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{ + const a=e[0].length+e.index,s=e.input[a];"<"!==s?">"===s&&(((e,{after:n})=>{ + const a=e[0].replace("<","`\\b0[${e}][${n}]([${n}_]*[${n}])?n?`,b=/[1-9]([0-9_]*\d)?/,u=/\d([0-9_]*\d)?/,E=r(/[eE][+-]?/,u),m={ + className:"number",variants:[{begin:g("bB","01")},{begin:g("oO","0-7")},{ + begin:g("xX","0-9a-fA-F")},{begin:r(/\b/,b,"n")},{begin:r(/(\b0)?\./,u,i(E))},{ + begin:r(/\b/,b,i(r(/\./,i(u))),i(E))},{begin:/\b0[\.n]?/}],relevance:0},y={ + className:"subst",begin:"\\$\\{",end:"\\}",keywords:d,contains:[]},p={ + begin:"html`",end:"",starts:{end:"`",returnEnd:!1, + contains:[c.BACKSLASH_ESCAPE,y],subLanguage:"xml"}},_={begin:"css`",end:"", + starts:{end:"`",returnEnd:!1,contains:[c.BACKSLASH_ESCAPE,y],subLanguage:"css"} + },N={className:"string",begin:"`",end:"`",contains:[c.BACKSLASH_ESCAPE,y]},f={ + className:"comment",variants:[c.COMMENT("/\\*\\*","\\*/",{relevance:0, + contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type", + begin:"\\{",end:"\\}",relevance:0},{className:"variable", + begin:o+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/, + relevance:0}]}]}),c.C_BLOCK_COMMENT_MODE,c.C_LINE_COMMENT_MODE] + },A=[c.APOS_STRING_MODE,c.QUOTE_STRING_MODE,p,_,N,m,c.REGEXP_MODE] + ;y.contains=A.concat({begin:/{/,end:/}/,keywords:d,contains:["self"].concat(A)}) + ;const O=[].concat(f,y.contains),S=O.concat([{begin:/\(/,end:/\)/,keywords:d, + contains:["self"].concat(O)}]),T={className:"params",begin:/\(/,end:/\)/, + excludeBegin:!0,excludeEnd:!0,keywords:d,contains:S};return{name:"Javascript", + aliases:["js","jsx","mjs","cjs"],keywords:d,exports:{PARAMS_CONTAINS:S}, + illegal:/#(?![$_A-z])/,contains:[c.SHEBANG({label:"shebang",binary:"node", + relevance:5}),{label:"use_strict",className:"meta",relevance:10, + begin:/^\s*['"]use (strict|asm)['"]/ + },c.APOS_STRING_MODE,c.QUOTE_STRING_MODE,p,_,N,f,m,{ + begin:r(/[{,\n]\s*/,t(r(/(\/\/.*$)*/,/(\/\*(.|\n)*\*\/)*/,/\s*/,o+"\\s*:"))), + relevance:0,contains:[{className:"attr",begin:o+t("\\s*:"),relevance:0}]},{ + begin:"("+c.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*", + keywords:"return throw case",contains:[f,c.REGEXP_MODE,{className:"function", + begin:"(\\([^()]*(\\([^()]*(\\([^()]*\\))*[^()]*\\))*[^()]*\\)|"+c.UNDERSCORE_IDENT_RE+")\\s*=>", + returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{ + begin:c.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{ + begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:d,contains:S}]}]},{ + begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{ + begin:"<>",end:""},{begin:l.begin,"on:begin":l.isTrulyOpeningTag,end:l.end}], + subLanguage:"xml",contains:[{begin:l.begin,end:l.end,skip:!0,contains:["self"]}] + }],relevance:0},{className:"function",beginKeywords:"function",end:/[{;]/, + excludeEnd:!0,keywords:d,contains:["self",c.inherit(c.TITLE_MODE,{begin:o}),T], + illegal:/%/},{className:"function", + begin:c.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\))*[^()]*\\))*[^()]*\\)\\s*{", + returnBegin:!0,contains:[T,c.inherit(c.TITLE_MODE,{begin:o})]},{variants:[{ + begin:"\\."+o},{begin:"\\$"+o}],relevance:0},{className:"class", + beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{ + beginKeywords:"extends"},c.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/, + end:/[\{;]/,excludeEnd:!0,contains:[c.inherit(c.TITLE_MODE,{begin:o}),"self",T] + },{begin:"(get|set)\\s+(?="+o+"\\()",end:/{/,keywords:"get set", + contains:[c.inherit(c.TITLE_MODE,{begin:o}),{begin:/\(\)/},T]},{begin:/\$[(.]/}] + }}(c) + ;return Object.assign(g.keywords,o),g.exports.PARAMS_CONTAINS.push(l),g.contains=g.contains.concat([l,{ + beginKeywords:"namespace",end:/\{/,excludeEnd:!0},{beginKeywords:"interface", + end:/\{/,excludeEnd:!0,keywords:"interface extends" + }]),d(g,"shebang",c.SHEBANG()),d(g,"use_strict",{className:"meta",relevance:10, + begin:/^\s*['"]use strict['"]/ + }),g.contains.find((e=>"function"===e.className)).relevance=0,Object.assign(g,{ + name:"TypeScript",aliases:["ts"]}),g}}()); + hljs.registerLanguage("yaml",function(){"use strict";return function(e){ + var n="true false yes no null",a="[\\w#;/?:@&=+$,.~*\\'()[\\]]+",s={ + className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/ + },{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable", + variants:[{begin:"{{",end:"}}"},{begin:"%{",end:"}"}]}]},i=e.inherit(s,{ + variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={ + end:",",endsWithParent:!0,excludeEnd:!0,contains:[],keywords:n,relevance:0},t={ + begin:"{",end:"}",contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[", + end:"\\]",contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr", + variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{ + begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)" + }]},{className:"meta",begin:"^---s*$",relevance:10},{className:"string", + begin:"[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{ + begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0, + relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type", + begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a + },{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta", + begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"\\-(?=[ ]|$)", + relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{ + className:"number", + begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b" + },{className:"number",begin:e.C_NUMBER_RE+"\\b"},t,g,s],c=[...b];return c.pop(), + c.push(i),l.contains=c,{name:"YAML",case_insensitive:!0,aliases:["yml","YAML"], + contains:b}}}()); + + // original from https://github.com/leanprover-community/highlightjs-lean/blob/master/src/lean.js, BSD 3, (c) 2020 Patrick Massot + hljs.registerLanguage("lean", function(hljs) { + var LEAN_KEYWORDS = { + $pattern: /#?\w+/, + keyword: + 'theorem|10 def class structure instance set_option ' + + 'example inductive coinductive ' + + 'axiom constant ' + + 'partial unsafe private protected ' + + 'if then else ' + + 'universe variable ' + + 'import open export prelude renaming hiding ' + + 'calc match with do by let extends ' + + 'for in unless try catch finally mutual mut return continue break where rec ' + + 'syntax macro_rules macro deriving ' + + 'fun ' + + '#check #check_failure #eval #reduce #print ' + + 'section namespace end infix infixl infixr postfix prefix notation ', + built_in: + 'Type Prop|10 Sort rw|10 rewrite rwa erw subst substs ' + + 'simp dsimp simpa simp_intros finish using generalizing ' + + 'unfold unfold1 dunfold unfold_projs unfold_coes ' + + 'delta cc ac_rfl ' + + 'existsi|10 cases rcases intro intros introv by_cases ' + + 'refl rfl funext case focus propext exact exacts ' + + 'refine apply eapply fapply apply_with apply_instance ' + + 'induction rename assumption revert generalize specialize clear ' + + 'contradiction by_contradiction by_contra trivial exfalso ' + + 'symmetry transitivity destruct constructor econstructor ' + + 'left right split injection injections ' + + 'repeat skip swap solve1 abstract all_goals any_goals done ' + + 'fail_if_success success_if_fail guard_target guard_hyp ' + + 'have replace at suffices show from ' + + 'congr congr_n congr_arg norm_num ring ', + literal: + 'true false', + meta: + 'noncomputable|10 private protected mutual', + strong: + 'sorry admit', + }; + + var LEAN_IDENT_RE = /[A-Za-z_][\\w\u207F-\u209C\u1D62-\u1D6A\u2079\'0-9?]*/; + + var DASH_COMMENT = hljs.COMMENT('--', '$'); + var MULTI_LINE_COMMENT = hljs.COMMENT('/-[^-]', '-/'); + var DOC_COMMENT = { + className: 'doctag', + begin: '/-[-!]', + end: '-/' + }; + + var ATTRIBUTE_DECORATOR = { + className: 'meta', + begin: '@\\[', + end: '\\]' + }; + + var ATTRIBUTE_LINE = { + className: 'meta', + begin: '^attribute', + end: '$' + }; + + var LEAN_DEFINITION = { + className: 'theorem', + begin: '\\b(def|theorem|lemma|class|structure|(?