Repository: alecthomas/chroma (go)

Issue:
Add support for Lean
### Is there an existing issue for this?

- [X] I have searched the existing issues

### What is the missing lexer?

[Lean](https://lean-lang.org/about/) is a functional programming language and a proof assistant.

According to the [Lean Community Website](https://leanprover-community.github.io/), they are currently in the middle of switching from using Lean 3 to using Lean 4. Because of this, I propose to have the `lean3` and `lean4` lexers, and a `lean` lexer that will be a fallback to `lean4`.

In the [Pygments languages page](https://pygments.org/languages/), it says that it has support for Lean, but I don't know yet how easy it is to port from Pygments to Chroma.

The reference manual for the language can be found [here](https://lean-lang.org/reference/).

### Links to existing syntax definitions

- [Emacs plugin syntax](https://github.com/leanprover/lean-mode/blob/master/lean-syntax.el)
- [neovim plugin syntax](https://github.com/Julian/lean.nvim/tree/main/syntax)

Pull request description:
Add Lean lexer
Resolves #908 

Patch:
diff --git a/lexers/embedded/lean.xml b/lexers/embedded/lean.xml
new file mode 100644
index 0000000..6ac5151
--- /dev/null
+++ b/lexers/embedded/lean.xml
@@ -0,0 +1,56 @@
+<lexer>
+  <config>
+    <name>Lean4</name>
+    <alias>lean4</alias>
+    <alias>lean</alias>
+    <filename>*.lean</filename>
+    <mime_type>text/x-lean4</mime_type>
+    <mime_type>text/x-lean</mime_type>
+  </config>
+  <rules>
+    <state name="expression">
+      <rule pattern="\s+"><token type="TextWhitespace"/></rule>
+      <rule pattern="/--"><token type="LiteralStringDoc"/><push state="docstring"/></rule>
+      <rule pattern="/-"><token type="Comment"/><push state="comment"/></rule>
+      <rule pattern="--.*$"><token type="CommentSingle"/></rule>
+      <rule pattern="\b(Type|Prop|Sort)\b"><token type="KeywordType"/></rule>
+      <rule pattern="\b(sorry|admit)\b"><token type="GenericError"/></rule>
+      <rule pattern="(!=|\#|\&amp;|\&amp;\&amp;|\*|\+|\-|/|@|!|\-\.|\-&gt;|\.|\.\.|\.\.\.|::|:&gt;|;|;;|&lt;|&lt;\-|=|==|&gt;|_|\||\|\||\~|=&gt;|&lt;=|&gt;=|/\\|\\/|∀|Π|λ|↔|∧|∨|≠|≤|≥|¬|⁻¹|⬝|▸|→|∃|≈|×|⌞|⌟|≡|⟨|⟩|↦)"><token type="NameBuiltinPseudo"/></rule>
+      <rule pattern="(\(|\)|:|\{|\}|\[|\]|⦃|⦄|:=|,)"><token type="Operator"/></rule>
+      <rule pattern="(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟](?:(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟0-9&#x27;ⁿ-₉ₐ-ₜᵢ-ᵪ!?])*"><token type="Name"/></rule>
+      <rule pattern="``?(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟](?:(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟0-9&#x27;ⁿ-₉ₐ-ₜᵢ-ᵪ!?])*(\.(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟](?:(?![λΠΣ])[_a-zA-Zα-ωΑ-Ωϊ-ϻἀ-῾℀-⅏𝒜-𝖟0-9&#x27;ⁿ-₉ₐ-ₜᵢ-ᵪ!?])*)*"><token type="LiteralStringSymbol"/></rule>
+      <rule pattern="(?&lt;=\.)\d+"><token type="LiteralNumber"/></rule>
+      <rule pattern="(\d+\.\d*)([eE][+-]?[0-9]+)?"><token type="LiteralNumberFloat"/></rule>
+      <rule pattern="\d+"><token type="LiteralNumberInteger"/></rule>
+      <rule pattern="&quot;"><token type="LiteralStringDouble"/><push state="string"/></rule>
+      <rule pattern="[~?][a-z][\w\&#x27;]*:"><token type="NameVariable"/></rule>
+      <rule pattern="\S"><token type="NameBuiltinPseudo"/></rule>
+    </state>
+    <state name="root">
+      <rule pattern="\b(import|unif_hint|renaming|inline|hiding|lemma|variable|theorem|axiom|inductive|structure|universe|alias|\#help|precedence|postfix|prefix|infix|infixl|infixr|notation|\#eval|\#check|\#reduce|\#exit|end|private|using|namespace|instance|section|protected|export|set_option|extends|open|example|\#print|opaque|def|macro|elab|syntax|macro_rules|\#reduce|where|abbrev|noncomputable|class|attribute|\#synth|mutual|scoped|local)\b"><token type="KeywordNamespace"/></rule>
+      <rule pattern="\b(forall|fun|obtain|from|have|show|assume|let|if|else|then|by|in|with|calc|match|nomatch|do|at)\b"><token type="Keyword"/></rule>
+      <rule pattern="@\["><token type="KeywordDeclaration"/><push state="attribute"/></rule>
+      <rule><include state="expression"/></rule>
+    </state>
+    <state name="attribute">
+      <rule pattern="\]"><token type="KeywordDeclaration"/><pop depth="1"/></rule>
+      <rule><include state="expression"/></rule>
+    </state>
+    <state name="comment">
+      <rule pattern="[^/-]+"><token type="CommentMultiline"/></rule>
+      <rule pattern="/-"><token type="CommentMultiline"/><push/></rule>
+      <rule pattern="-/"><token type="CommentMultiline"/><pop depth="1"/></rule>
+      <rule pattern="[/-]"><token type="CommentMultiline"/></rule>
+    </state>
+    <state name="docstring">
+      <rule pattern="[^/-]+"><token type="LiteralStringDoc"/></rule>
+      <rule pattern="-/"><token type="LiteralStringDoc"/><pop depth="1"/></rule>
+      <rule pattern="[/-]"><token type="LiteralStringDoc"/></rule>
+    </state>
+    <state name="string">
+      <rule pattern="[^\\&quot;]+"><token type="LiteralStringDouble"/></rule>
+      <rule pattern="\\[n&quot;\\\n]"><token type="LiteralStringEscape"/></rule>
+      <rule pattern="&quot;"><token type="LiteralStringDouble"/><pop depth="1"/></rule>
+    </state>
+  </rules>
+</lexer>


Test patch:
diff --git a/lexers/testdata/lean.actual b/lexers/testdata/lean.actual
new file mode 100644
index 0000000..c78fa73
--- /dev/null
+++ b/lexers/testdata/lean.actual
@@ -0,0 +1,16 @@
+def f(x : Nat) : Nat := x + 1
+
+-- comment
+
+theorem thm (p q r : Prop) : p ∧ (q ∨ r) → (p ∧ q) ∨ (p ∧ r) := by
+  intro ⟨hp, hqr⟩
+  show (p ∧ q) ∨ (p ∧ r)
+  cases hqr with
+  | inl hq =>
+    have hpq : p ∧ q := And.intro hp hq
+    apply Or.inl
+    exact hpq
+  | inr hr =>
+    have hpr : p ∧ r := And.intro hp hr
+    apply Or.inr
+    exact hpr
diff --git a/lexers/testdata/lean.expected b/lexers/testdata/lean.expected
new file mode 100644
index 0000000..b707a47
--- /dev/null
+++ b/lexers/testdata/lean.expected
@@ -0,0 +1,200 @@
+[
+  {"type":"KeywordNamespace","value":"def"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"f"},
+  {"type":"Operator","value":"("},
+  {"type":"Name","value":"x"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Operator","value":":"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"Nat"},
+  {"type":"Operator","value":")"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Operator","value":":"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"Nat"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Operator","value":":"},
+  {"type":"NameBuiltinPseudo","value":"="},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"x"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameBuiltinPseudo","value":"+"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"LiteralNumberInteger","value":"1"},
+  {"type":"TextWhitespace","value":"\n\n"},
+  {"type":"CommentSingle","value":"-- comment"},
+  {"type":"TextWhitespace","value":"\n\n"},
+  {"type":"KeywordNamespace","value":"theorem"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"thm"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Operator","value":"("},
+  {"type":"Name","value":"p"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"q"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"r"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Operator","value":":"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"KeywordType","value":"Prop"},
+  {"type":"Operator","value":")"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Operator","value":":"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"p"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameBuiltinPseudo","value":"∧"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Operator","value":"("},
+  {"type":"Name","value":"q"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameBuiltinPseudo","value":"∨"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"r"},
+  {"type":"Operator","value":")"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameBuiltinPseudo","value":"→"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Operator","value":"("},
+  {"type":"Name","value":"p"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameBuiltinPseudo","value":"∧"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"q"},
+  {"type":"Operator","value":")"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameBuiltinPseudo","value":"∨"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Operator","value":"("},
+  {"type":"Name","value":"p"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameBuiltinPseudo","value":"∧"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"r"},
+  {"type":"Operator","value":")"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Operator","value":":"},
+  {"type":"NameBuiltinPseudo","value":"="},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Keyword","value":"by"},
+  {"type":"TextWhitespace","value":"\n  "},
+  {"type":"Name","value":"intro"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameBuiltinPseudo","value":"⟨"},
+  {"type":"Name","value":"hp"},
+  {"type":"Operator","value":","},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"hqr"},
+  {"type":"NameBuiltinPseudo","value":"⟩"},
+  {"type":"TextWhitespace","value":"\n  "},
+  {"type":"Keyword","value":"show"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Operator","value":"("},
+  {"type":"Name","value":"p"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameBuiltinPseudo","value":"∧"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"q"},
+  {"type":"Operator","value":")"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameBuiltinPseudo","value":"∨"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Operator","value":"("},
+  {"type":"Name","value":"p"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameBuiltinPseudo","value":"∧"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"r"},
+  {"type":"Operator","value":")"},
+  {"type":"TextWhitespace","value":"\n  "},
+  {"type":"Name","value":"cases"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"hqr"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Keyword","value":"with"},
+  {"type":"TextWhitespace","value":"\n  "},
+  {"type":"NameBuiltinPseudo","value":"|"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"inl"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"hq"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameBuiltinPseudo","value":"=\u003e"},
+  {"type":"TextWhitespace","value":"\n    "},
+  {"type":"Keyword","value":"have"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"hpq"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Operator","value":":"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"p"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameBuiltinPseudo","value":"∧"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"q"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Operator","value":":"},
+  {"type":"NameBuiltinPseudo","value":"="},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"And"},
+  {"type":"NameBuiltinPseudo","value":"."},
+  {"type":"Name","value":"intro"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"hp"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"hq"},
+  {"type":"TextWhitespace","value":"\n    "},
+  {"type":"Name","value":"apply"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"Or"},
+  {"type":"NameBuiltinPseudo","value":"."},
+  {"type":"Name","value":"inl"},
+  {"type":"TextWhitespace","value":"\n    "},
+  {"type":"Name","value":"exact"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"hpq"},
+  {"type":"TextWhitespace","value":"\n  "},
+  {"type":"NameBuiltinPseudo","value":"|"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"inr"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"hr"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameBuiltinPseudo","value":"=\u003e"},
+  {"type":"TextWhitespace","value":"\n    "},
+  {"type":"Keyword","value":"have"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"hpr"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Operator","value":":"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"p"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameBuiltinPseudo","value":"∧"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"r"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Operator","value":":"},
+  {"type":"NameBuiltinPseudo","value":"="},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"And"},
+  {"type":"NameBuiltinPseudo","value":"."},
+  {"type":"Name","value":"intro"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"hp"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"hr"},
+  {"type":"TextWhitespace","value":"\n    "},
+  {"type":"Name","value":"apply"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"Or"},
+  {"type":"NameBuiltinPseudo","value":"."},
+  {"type":"Name","value":"inr"},
+  {"type":"TextWhitespace","value":"\n    "},
+  {"type":"Name","value":"exact"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Name","value":"hpr"},
+  {"type":"TextWhitespace","value":"\n"}
+]