From 3e2124bb489dd98025427015ae5b3c5a17d14aad Mon Sep 17 00:00:00 2001 From: David Thrane Christiansen Date: Wed, 10 Sep 2025 09:03:57 +0200 Subject: [PATCH] feat: docstrings with Verso syntax (#10307) This PR upstreams the Verso parser and adds preliminary support for Verso in docstrings. This will allow the compiler to check examples and cross-references in documentation. After a `stage0` update, a follow-up PR will add the appropriate attributes that allow the feature to be used. The parser tests from Verso also remain to be upstreamed, and user-facing documentation will be added once the feature has been used on more internals. --- src/Lean/BuiltinDocAttr.lean | 1 + src/Lean/Data/EditDistance.lean | 55 + src/Lean/DocString/Add.lean | 143 +- src/Lean/DocString/Extension.lean | 93 +- src/Lean/DocString/Links.lean | 49 +- src/Lean/DocString/Markdown.lean | 295 +++ src/Lean/DocString/Parser.lean | 1164 +++++++++ src/Lean/DocString/Syntax.lean | 172 ++ src/Lean/DocString/Types.lean | 181 ++ src/Lean/Elab.lean | 2 + src/Lean/Elab/Binders.lean | 1 + src/Lean/Elab/BuiltinCommand.lean | 2 +- src/Lean/Elab/Command.lean | 70 +- src/Lean/Elab/Command/Scope.lean | 82 + src/Lean/Elab/DeclModifiers.lean | 14 +- src/Lean/Elab/Declaration.lean | 9 +- src/Lean/Elab/DefView.lean | 19 +- src/Lean/Elab/DocString.lean | 1186 ++++++++++ src/Lean/Elab/DocString/Builtin.lean | 985 ++++++++ src/Lean/Elab/ErrorExplanation.lean | 2 +- src/Lean/Elab/Inductive.lean | 7 +- src/Lean/Elab/InfoTree/Main.lean | 5 + src/Lean/Elab/InheritDoc.lean | 20 +- src/Lean/Elab/LetRec.lean | 5 +- src/Lean/Elab/MutualDef.lean | 14 +- src/Lean/Elab/MutualInductive.lean | 16 + src/Lean/Elab/Open.lean | 12 +- src/Lean/Elab/PreDefinition/Basic.lean | 56 +- src/Lean/Elab/PreDefinition/Main.lean | 42 +- src/Lean/Elab/PreDefinition/Mutual.lean | 8 +- .../PreDefinition/PartialFixpoint/Main.lean | 6 +- .../Elab/PreDefinition/Structural/Main.lean | 11 +- .../Structural/SmartUnfolding.lean | 6 +- src/Lean/Elab/PreDefinition/WF/Main.lean | 6 +- src/Lean/Elab/SetOption.lean | 56 +- src/Lean/Elab/Structure.lean | 16 +- src/Lean/Elab/Tactic/Doc.lean | 6 +- src/Lean/Elab/Term.lean | 2052 +--------------- src/Lean/Elab/Term/TermElabM.lean | 2081 +++++++++++++++++ src/Lean/Elab/Util.lean | 2 +- src/Lean/Exception.lean | 2 +- src/Lean/KeyedDeclsAttribute.lean | 6 +- src/Lean/Linter.lean | 1 + src/Lean/Linter/DocsOnAlt.lean | 62 + src/Lean/Parser/Basic.lean | 123 +- src/Lean/Parser/StrInterpolation.lean | 4 +- src/Lean/Parser/Tactic/Doc.lean | 30 +- .../Server/FileWorker/RequestHandling.lean | 1 - src/shell/CMakeLists.txt | 11 + stage0/src/stdlib_flags.h | 8 +- tests/lean/docparse/arg_0001 | 1 + tests/lean/docparse/arg_0001.expected.out | 4 + tests/lean/docparse/arg_0002 | 1 + tests/lean/docparse/arg_0002.expected.out | 6 + tests/lean/docparse/arg_0003 | 1 + tests/lean/docparse/arg_0003.expected.out | 8 + tests/lean/docparse/arg_0004 | 2 + tests/lean/docparse/arg_0004.expected.out | 4 + tests/lean/docparse/arg_0005 | 1 + tests/lean/docparse/arg_0005.expected.out | 3 + tests/lean/docparse/arg_0006 | 1 + tests/lean/docparse/arg_0006.expected.out | 3 + tests/lean/docparse/arg_0007 | 1 + tests/lean/docparse/arg_0007.expected.out | 4 + tests/lean/docparse/arg_0008 | 1 + tests/lean/docparse/arg_0008.expected.out | 9 + tests/lean/docparse/arg_0009 | 1 + tests/lean/docparse/arg_0009.expected.out | 6 + tests/lean/docparse/arg_0010 | 1 + tests/lean/docparse/arg_0010.expected.out | 8 + tests/lean/docparse/arg_0011 | 1 + tests/lean/docparse/arg_0011.expected.out | 6 + tests/lean/docparse/arg_0012 | 1 + tests/lean/docparse/arg_0012.expected.out | 7 + tests/lean/docparse/arg_0013 | 1 + tests/lean/docparse/arg_0013.expected.out | 13 + tests/lean/docparse/arg_0014 | 1 + tests/lean/docparse/arg_0014.expected.out | 4 + tests/lean/docparse/arg_0015 | 1 + tests/lean/docparse/arg_0015.expected.out | 17 + tests/lean/docparse/arg_0016 | 1 + tests/lean/docparse/arg_0016.expected.out | 15 + tests/lean/docparse/arg_0017 | 2 + tests/lean/docparse/arg_0017.expected.out | 9 + tests/lean/docparse/arg_0018 | 2 + tests/lean/docparse/arg_0018.expected.out | 9 + tests/lean/docparse/arg_val_0001 | 1 + tests/lean/docparse/arg_val_0001.expected.out | 4 + tests/lean/docparse/arg_val_0002 | 1 + tests/lean/docparse/arg_val_0002.expected.out | 4 + tests/lean/docparse/arg_val_0003 | 0 tests/lean/docparse/arg_val_0003.expected.out | 4 + tests/lean/docparse/arg_val_0004 | 1 + tests/lean/docparse/arg_val_0004.expected.out | 5 + tests/lean/docparse/arg_val_0005 | 1 + tests/lean/docparse/arg_val_0005.expected.out | 5 + tests/lean/docparse/arg_val_0006 | 2 + tests/lean/docparse/arg_val_0006.expected.out | 5 + tests/lean/docparse/arg_val_0007 | 1 + tests/lean/docparse/arg_val_0007.expected.out | 4 + tests/lean/docparse/args_0001 | 2 + tests/lean/docparse/args_0001.expected.out | 5 + tests/lean/docparse/args_0002 | 2 + tests/lean/docparse/args_0002.expected.out | 9 + tests/lean/docparse/args_0003 | 2 + tests/lean/docparse/args_0003.expected.out | 9 + tests/lean/docparse/blockOpener_0001 | 1 + .../docparse/blockOpener_0001.expected.out | 4 + tests/lean/docparse/blockOpener_0002 | 1 + .../docparse/blockOpener_0002.expected.out | 4 + tests/lean/docparse/blockOpener_0003 | 1 + .../docparse/blockOpener_0003.expected.out | 4 + tests/lean/docparse/block_0001 | 8 + tests/lean/docparse/block_0001.expected.out | 36 + tests/lean/docparse/block_0002 | 2 + tests/lean/docparse/block_0002.expected.out | 4 + tests/lean/docparse/block_0003 | 2 + tests/lean/docparse/block_0003.expected.out | 4 + tests/lean/docparse/block_0004 | 2 + tests/lean/docparse/block_0004.expected.out | 4 + tests/lean/docparse/block_0005 | 2 + tests/lean/docparse/block_0005.expected.out | 4 + tests/lean/docparse/block_0006 | 6 + tests/lean/docparse/block_0006.expected.out | 4 + tests/lean/docparse/block_0007 | 3 + tests/lean/docparse/block_0007.expected.out | 15 + tests/lean/docparse/block_0008 | 3 + tests/lean/docparse/block_0008.expected.out | 15 + tests/lean/docparse/block_0009 | 4 + tests/lean/docparse/block_0009.expected.out | 15 + tests/lean/docparse/block_0010 | 4 + tests/lean/docparse/block_0010.expected.out | 15 + tests/lean/docparse/block_0011 | 6 + tests/lean/docparse/block_0011.expected.out | 15 + tests/lean/docparse/block_0012 | 15 + tests/lean/docparse/block_0012.expected.out | 84 + tests/lean/docparse/blocks_0001 | 3 + tests/lean/docparse/blocks_0001.expected.out | 12 + tests/lean/docparse/blocks_0002 | 2 + tests/lean/docparse/blocks_0002.expected.out | 12 + tests/lean/docparse/blocks_0003 | 4 + tests/lean/docparse/blocks_0003.expected.out | 23 + tests/lean/docparse/blocks_0004 | 5 + tests/lean/docparse/blocks_0004.expected.out | 47 + tests/lean/docparse/blocks_0005 | 5 + tests/lean/docparse/blocks_0005.expected.out | 38 + tests/lean/docparse/blocks_0006 | 5 + tests/lean/docparse/blocks_0006.expected.out | 38 + tests/lean/docparse/blocks_0007 | 2 + tests/lean/docparse/blocks_0007.expected.out | 11 + tests/lean/docparse/blocks_0008 | 4 + tests/lean/docparse/blocks_0008.expected.out | 15 + tests/lean/docparse/blocks_0009 | 4 + tests/lean/docparse/blocks_0009.expected.out | 15 + tests/lean/docparse/blocks_0010 | 2 + tests/lean/docparse/blocks_0010.expected.out | 20 + tests/lean/docparse/blocks_0011 | 1 + tests/lean/docparse/blocks_0011.expected.out | 13 + tests/lean/docparse/blocks_0012 | 1 + tests/lean/docparse/blocks_0012.expected.out | 16 + tests/lean/docparse/blocks_0013 | 4 + tests/lean/docparse/blocks_0013.expected.out | 22 + tests/lean/docparse/blocks_0014 | 4 + tests/lean/docparse/blocks_0014.expected.out | 23 + tests/lean/docparse/blocks_0015 | 4 + tests/lean/docparse/blocks_0015.expected.out | 19 + tests/lean/docparse/blocks_0016 | 4 + tests/lean/docparse/blocks_0016.expected.out | 25 + tests/lean/docparse/blocks_0017 | 4 + tests/lean/docparse/blocks_0017.expected.out | 22 + tests/lean/docparse/blocks_0018 | 2 + tests/lean/docparse/blocks_0018.expected.out | 17 + tests/lean/docparse/blocks_0019 | 2 + tests/lean/docparse/blocks_0019.expected.out | 20 + tests/lean/docparse/blocks_0020 | 3 + tests/lean/docparse/blocks_0020.expected.out | 20 + tests/lean/docparse/blocks_0021 | 9 + tests/lean/docparse/blocks_0021.expected.out | 36 + tests/lean/docparse/blocks_0022 | 3 + tests/lean/docparse/blocks_0022.expected.out | 28 + tests/lean/docparse/blocks_0023 | 2 + tests/lean/docparse/blocks_0023.expected.out | 18 + tests/lean/docparse/blocks_0024 | 3 + tests/lean/docparse/blocks_0024.expected.out | 15 + tests/lean/docparse/blocks_0025 | 6 + tests/lean/docparse/blocks_0025.expected.out | 21 + tests/lean/docparse/blocks_0026 | 10 + tests/lean/docparse/blocks_0026.expected.out | 24 + tests/lean/docparse/blocks_0027 | 7 + tests/lean/docparse/blocks_0027.expected.out | 23 + tests/lean/docparse/blocks_0028 | 5 + tests/lean/docparse/blocks_0028.expected.out | 21 + tests/lean/docparse/blocks_0029 | 3 + tests/lean/docparse/blocks_0029.expected.out | 20 + tests/lean/docparse/blocks_0030 | 1 + tests/lean/docparse/blocks_0030.expected.out | 14 + tests/lean/docparse/blocks_0031 | 3 + tests/lean/docparse/blocks_0031.expected.out | 26 + tests/lean/docparse/blocks_0032 | 3 + tests/lean/docparse/blocks_0032.expected.out | 20 + tests/lean/docparse/blocks_0033 | 1 + tests/lean/docparse/blocks_0033.expected.out | 11 + tests/lean/docparse/blocks_0034 | 1 + tests/lean/docparse/blocks_0034.expected.out | 6 + tests/lean/docparse/blocks_0035 | 1 + tests/lean/docparse/blocks_0035.expected.out | 17 + tests/lean/docparse/blocks_0036 | 1 + tests/lean/docparse/blocks_0036.expected.out | 9 + tests/lean/docparse/blocks_0037 | 1 + tests/lean/docparse/blocks_0037.expected.out | 9 + tests/lean/docparse/blocks_0038 | 2 + tests/lean/docparse/blocks_0038.expected.out | 14 + tests/lean/docparse/blocks_0039 | 7 + tests/lean/docparse/blocks_0039.expected.out | 29 + tests/lean/docparse/blocks_0040 | 9 + tests/lean/docparse/blocks_0040.expected.out | 36 + tests/lean/docparse/blocks_0041 | 8 + tests/lean/docparse/blocks_0041.expected.out | 36 + tests/lean/docparse/blocks_0042 | 10 + tests/lean/docparse/blocks_0042.expected.out | 41 + tests/lean/docparse/blocks_0043 | 3 + tests/lean/docparse/blocks_0043.expected.out | 14 + tests/lean/docparse/blocks_0044 | 3 + tests/lean/docparse/blocks_0044.expected.out | 14 + tests/lean/docparse/blocks_0045 | 2 + tests/lean/docparse/blocks_0045.expected.out | 8 + tests/lean/docparse/blocks_0046 | 6 + tests/lean/docparse/blocks_0046.expected.out | 19 + tests/lean/docparse/blocks_0047 | 2 + tests/lean/docparse/blocks_0047.expected.out | 4 + tests/lean/docparse/blocks_0048 | 4 + tests/lean/docparse/blocks_0048.expected.out | 20 + tests/lean/docparse/blocks_0049 | 1 + tests/lean/docparse/blocks_0049.expected.out | 24 + tests/lean/docparse/blocks_0050 | 3 + tests/lean/docparse/blocks_0050.expected.out | 18 + tests/lean/docparse/blocks_0051 | 2 + tests/lean/docparse/blocks_0051.expected.out | 22 + tests/lean/docparse/blocks_0052 | 4 + tests/lean/docparse/blocks_0052.expected.out | 22 + tests/lean/docparse/blocks_0053 | 3 + tests/lean/docparse/blocks_0053.expected.out | 16 + tests/lean/docparse/blocks_0054 | 26 + tests/lean/docparse/blocks_0054.expected.out | 180 ++ tests/lean/docparse/codeBlock_0001 | 4 + .../lean/docparse/codeBlock_0001.expected.out | 8 + tests/lean/docparse/codeBlock_0002 | 4 + .../lean/docparse/codeBlock_0002.expected.out | 8 + tests/lean/docparse/codeBlock_0003 | 4 + .../lean/docparse/codeBlock_0003.expected.out | 8 + tests/lean/docparse/codeBlock_0004 | 4 + .../lean/docparse/codeBlock_0004.expected.out | 8 + tests/lean/docparse/codeBlock_0005 | 4 + .../lean/docparse/codeBlock_0005.expected.out | 8 + tests/lean/docparse/codeBlock_0006 | 4 + .../lean/docparse/codeBlock_0006.expected.out | 8 + tests/lean/docparse/codeBlock_0007 | 5 + .../lean/docparse/codeBlock_0007.expected.out | 9 + tests/lean/docparse/codeBlock_0008 | 4 + .../lean/docparse/codeBlock_0008.expected.out | 9 + tests/lean/docparse/codeBlock_0009 | 4 + .../lean/docparse/codeBlock_0009.expected.out | 9 + tests/lean/docparse/codeBlock_0010 | 4 + .../lean/docparse/codeBlock_0010.expected.out | 9 + tests/lean/docparse/codeBlock_0011 | 4 + .../lean/docparse/codeBlock_0011.expected.out | 9 + tests/lean/docparse/codeBlock_0012 | 4 + .../lean/docparse/codeBlock_0012.expected.out | 15 + tests/lean/docparse/codeBlock_0013 | 4 + .../lean/docparse/codeBlock_0013.expected.out | 15 + tests/lean/docparse/codeBlock_0014 | 4 + .../lean/docparse/codeBlock_0014.expected.out | 16 + tests/lean/docparse/code_0001 | 1 + tests/lean/docparse/code_0001.expected.out | 6 + tests/lean/docparse/code_0002 | 1 + tests/lean/docparse/code_0002.expected.out | 3 + tests/lean/docparse/code_0003 | 1 + tests/lean/docparse/code_0003.expected.out | 3 + tests/lean/docparse/code_0004 | 1 + tests/lean/docparse/code_0004.expected.out | 3 + tests/lean/docparse/code_0005 | 1 + tests/lean/docparse/code_0005.expected.out | 3 + tests/lean/docparse/code_0006 | 1 + tests/lean/docparse/code_0006.expected.out | 6 + tests/lean/docparse/code_0007 | 1 + tests/lean/docparse/code_0007.expected.out | 7 + tests/lean/docparse/code_0008 | 1 + tests/lean/docparse/code_0008.expected.out | 3 + tests/lean/docparse/code_0009 | 1 + tests/lean/docparse/code_0009.expected.out | 3 + tests/lean/docparse/code_0010 | 2 + tests/lean/docparse/code_0010.expected.out | 6 + tests/lean/docparse/code_0011 | 1 + tests/lean/docparse/code_0011.expected.out | 3 + tests/lean/docparse/directive_0001 | 3 + .../lean/docparse/directive_0001.expected.out | 12 + tests/lean/docparse/directive_0002 | 5 + .../lean/docparse/directive_0002.expected.out | 12 + tests/lean/docparse/directive_0003 | 3 + .../lean/docparse/directive_0003.expected.out | 15 + tests/lean/docparse/directive_0004 | 7 + .../lean/docparse/directive_0004.expected.out | 22 + tests/lean/docparse/directive_0005 | 7 + .../lean/docparse/directive_0005.expected.out | 23 + tests/lean/docparse/directive_0006 | 5 + .../lean/docparse/directive_0006.expected.out | 22 + tests/lean/docparse/document_0001 | 3 + .../lean/docparse/document_0001.expected.out | 19 + tests/lean/docparse/emph_0001 | 1 + tests/lean/docparse/emph_0001.expected.out | 6 + tests/lean/docparse/emph_0002 | 1 + tests/lean/docparse/emph_0002.expected.out | 7 + tests/lean/docparse/emph_0003 | 1 + tests/lean/docparse/emph_0003.expected.out | 4 + tests/lean/docparse/header_0001 | 1 + tests/lean/docparse/header_0001.expected.out | 9 + tests/lean/docparse/inlineTextChar_0001 | 0 .../docparse/inlineTextChar_0001.expected.out | 4 + tests/lean/docparse/inlineTextChar_0002 | 1 + .../docparse/inlineTextChar_0002.expected.out | 3 + tests/lean/docparse/inlineTextChar_0003 | 1 + .../docparse/inlineTextChar_0003.expected.out | 4 + tests/lean/docparse/inlineTextChar_0004 | 1 + .../docparse/inlineTextChar_0004.expected.out | 4 + tests/lean/docparse/inlineTextChar_0005 | 1 + .../docparse/inlineTextChar_0005.expected.out | 4 + .../lookaheadOrderedListIndicator_0001 | 1 + ...headOrderedListIndicator_0001.expected.out | 6 + .../lookaheadOrderedListIndicator_0002 | 1 + ...headOrderedListIndicator_0002.expected.out | 6 + .../lookaheadOrderedListIndicator_0003 | 1 + ...headOrderedListIndicator_0003.expected.out | 4 + .../lookaheadOrderedListIndicator_0004 | 1 + ...headOrderedListIndicator_0004.expected.out | 6 + .../lookaheadOrderedListIndicator_0005 | 1 + ...headOrderedListIndicator_0005.expected.out | 4 + .../lookaheadOrderedListIndicator_0006 | 1 + ...headOrderedListIndicator_0006.expected.out | 4 + .../lookaheadOrderedListIndicator_0007 | 1 + ...headOrderedListIndicator_0007.expected.out | 4 + .../lookaheadUnorderedListIndicator_0001 | 1 + ...adUnorderedListIndicator_0001.expected.out | 4 + .../lookaheadUnorderedListIndicator_0002 | 1 + ...adUnorderedListIndicator_0002.expected.out | 4 + .../lookaheadUnorderedListIndicator_0003 | 1 + ...adUnorderedListIndicator_0003.expected.out | 4 + .../lookaheadUnorderedListIndicator_0004 | 1 + ...adUnorderedListIndicator_0004.expected.out | 4 + .../lookaheadUnorderedListIndicator_0005 | 1 + ...adUnorderedListIndicator_0005.expected.out | 4 + .../lookaheadUnorderedListIndicator_0006 | 1 + ...adUnorderedListIndicator_0006.expected.out | 5 + tests/lean/docparse/manyInlineTextChar_0001 | 1 + .../manyInlineTextChar_0001.expected.out | 4 + tests/lean/docparse/manyInlineTextChar_0002 | 1 + .../manyInlineTextChar_0002.expected.out | 4 + tests/lean/docparse/manyInlineTextChar_0003 | 1 + .../manyInlineTextChar_0003.expected.out | 4 + tests/lean/docparse/metadataBlock_0001 | 2 + .../docparse/metadataBlock_0001.expected.out | 6 + tests/lean/docparse/metadataBlock_0002 | 4 + .../docparse/metadataBlock_0002.expected.out | 12 + tests/lean/docparse/metadataBlock_0003 | 4 + .../docparse/metadataBlock_0003.expected.out | 16 + tests/lean/docparse/nameAndArgs_0001 | 1 + .../docparse/nameAndArgs_0001.expected.out | 8 + tests/lean/docparse/nameAndArgs_0002 | 1 + .../docparse/nameAndArgs_0002.expected.out | 11 + tests/lean/docparse/nameAndArgs_0003 | 1 + .../docparse/nameAndArgs_0003.expected.out | 12 + tests/lean/docparse/nameAndArgs_0004 | 1 + .../docparse/nameAndArgs_0004.expected.out | 13 + tests/lean/docparse/nameAndArgs_0005 | 1 + .../docparse/nameAndArgs_0005.expected.out | 13 + tests/lean/docparse/nameAndArgs_0006 | 2 + .../docparse/nameAndArgs_0006.expected.out | 12 + tests/lean/docparse/nameAndArgs_0007 | 1 + .../docparse/nameAndArgs_0007.expected.out | 6 + tests/lean/docparse/nameAndArgs_0008 | 1 + .../docparse/nameAndArgs_0008.expected.out | 8 + tests/lean/docparse/nameAndArgs_0009 | 1 + .../docparse/nameAndArgs_0009.expected.out | 11 + tests/lean/docparse/nameAndArgs_0010 | 3 + .../docparse/nameAndArgs_0010.expected.out | 12 + tests/lean/docparse/oneInline_0001 | 1 + .../lean/docparse/oneInline_0001.expected.out | 6 + tests/lean/docparse/oneInline_0002 | 1 + .../lean/docparse/oneInline_0002.expected.out | 6 + tests/lean/docparse/oneInline_0003 | 1 + .../lean/docparse/oneInline_0003.expected.out | 7 + tests/lean/docparse/oneInline_0004 | 1 + .../lean/docparse/oneInline_0004.expected.out | 3 + tests/lean/docparse/oneInline_0005 | 1 + .../lean/docparse/oneInline_0005.expected.out | 3 + tests/lean/docparse/oneInline_0006 | 2 + .../lean/docparse/oneInline_0006.expected.out | 6 + tests/lean/docparse/oneInline_0007 | 2 + .../lean/docparse/oneInline_0007.expected.out | 7 + tests/lean/docparse/oneInline_0008 | 2 + .../lean/docparse/oneInline_0008.expected.out | 10 + tests/lean/docparse/oneInline_0009 | 1 + .../lean/docparse/oneInline_0009.expected.out | 4 + tests/lean/docparse/oneInline_0010 | 1 + .../lean/docparse/oneInline_0010.expected.out | 10 + tests/lean/docparse/oneInline_0011 | 1 + .../lean/docparse/oneInline_0011.expected.out | 10 + tests/lean/docparse/oneInline_0012 | 2 + .../lean/docparse/oneInline_0012.expected.out | 11 + tests/lean/docparse/oneInline_0013 | 2 + .../lean/docparse/oneInline_0013.expected.out | 7 + tests/lean/docparse/oneInline_0014 | 1 + .../lean/docparse/oneInline_0014.expected.out | 10 + tests/lean/docparse/oneInline_0015 | 1 + .../lean/docparse/oneInline_0015.expected.out | 8 + tests/lean/docparse/oneInline_0016 | 1 + .../lean/docparse/oneInline_0016.expected.out | 6 + tests/lean/docparse/oneInline_0017 | 1 + .../lean/docparse/oneInline_0017.expected.out | 8 + tests/lean/docparse/oneInline_0018 | 6 + .../lean/docparse/oneInline_0018.expected.out | 8 + tests/lean/docparse/oneInline_0019 | 1 + .../lean/docparse/oneInline_0019.expected.out | 8 + tests/lean/docparse/oneInline_0020 | 1 + .../lean/docparse/oneInline_0020.expected.out | 3 + tests/lean/docparse/oneInline_0021 | 1 + .../lean/docparse/oneInline_0021.expected.out | 4 + tests/lean/docparse/oneInline_0022 | 1 + .../lean/docparse/oneInline_0022.expected.out | 3 + tests/lean/docparse/recoverBlock_0001 | 3 + .../docparse/recoverBlock_0001.expected.out | 46 + tests/lean/docparse/recoverBlocks_0001 | 11 + .../docparse/recoverBlocks_0001.expected.out | 44 + tests/lean/docparse/role_0001 | 1 + tests/lean/docparse/role_0001.expected.out | 13 + tests/lean/docparse/role_0002 | 1 + tests/lean/docparse/role_0002.expected.out | 10 + tests/lean/docparse/role_0003 | 1 + tests/lean/docparse/role_0003.expected.out | 22 + tests/lean/docparse/role_0004 | 1 + tests/lean/docparse/role_0004.expected.out | 13 + tests/lean/docparse/role_0005 | 1 + tests/lean/docparse/role_0005.expected.out | 24 + tests/lean/docparse/run.lean | 116 + tests/lean/docparse/test_single.sh | 9 + tests/lean/docparse/text_0001 | 1 + tests/lean/docparse/text_0001.expected.out | 4 + .../lean/interactive/hover.lean.expected.out | 3 +- tests/lean/moduleOf.lean.expected.out | 2 +- tests/lean/run/3497.lean | 8 +- tests/lean/run/info_trees.lean | 2 +- tests/lean/run/levenshtein.lean | 260 ++ tests/lean/run/tacticDoc.lean | 16 +- tests/lean/run/versoDocs.lean | 383 +++ 453 files changed, 10719 insertions(+), 2364 deletions(-) create mode 100644 src/Lean/Data/EditDistance.lean create mode 100644 src/Lean/DocString/Markdown.lean create mode 100644 src/Lean/DocString/Parser.lean create mode 100644 src/Lean/DocString/Syntax.lean create mode 100644 src/Lean/DocString/Types.lean create mode 100644 src/Lean/Elab/Command/Scope.lean create mode 100644 src/Lean/Elab/DocString.lean create mode 100644 src/Lean/Elab/DocString/Builtin.lean create mode 100644 src/Lean/Elab/Term/TermElabM.lean create mode 100644 src/Lean/Linter/DocsOnAlt.lean create mode 100644 tests/lean/docparse/arg_0001 create mode 100644 tests/lean/docparse/arg_0001.expected.out create mode 100644 tests/lean/docparse/arg_0002 create mode 100644 tests/lean/docparse/arg_0002.expected.out create mode 100644 tests/lean/docparse/arg_0003 create mode 100644 tests/lean/docparse/arg_0003.expected.out create mode 100644 tests/lean/docparse/arg_0004 create mode 100644 tests/lean/docparse/arg_0004.expected.out create mode 100644 tests/lean/docparse/arg_0005 create mode 100644 tests/lean/docparse/arg_0005.expected.out create mode 100644 tests/lean/docparse/arg_0006 create mode 100644 tests/lean/docparse/arg_0006.expected.out create mode 100644 tests/lean/docparse/arg_0007 create mode 100644 tests/lean/docparse/arg_0007.expected.out create mode 100644 tests/lean/docparse/arg_0008 create mode 100644 tests/lean/docparse/arg_0008.expected.out create mode 100644 tests/lean/docparse/arg_0009 create mode 100644 tests/lean/docparse/arg_0009.expected.out create mode 100644 tests/lean/docparse/arg_0010 create mode 100644 tests/lean/docparse/arg_0010.expected.out create mode 100644 tests/lean/docparse/arg_0011 create mode 100644 tests/lean/docparse/arg_0011.expected.out create mode 100644 tests/lean/docparse/arg_0012 create mode 100644 tests/lean/docparse/arg_0012.expected.out create mode 100644 tests/lean/docparse/arg_0013 create mode 100644 tests/lean/docparse/arg_0013.expected.out create mode 100644 tests/lean/docparse/arg_0014 create mode 100644 tests/lean/docparse/arg_0014.expected.out create mode 100644 tests/lean/docparse/arg_0015 create mode 100644 tests/lean/docparse/arg_0015.expected.out create mode 100644 tests/lean/docparse/arg_0016 create mode 100644 tests/lean/docparse/arg_0016.expected.out create mode 100644 tests/lean/docparse/arg_0017 create mode 100644 tests/lean/docparse/arg_0017.expected.out create mode 100644 tests/lean/docparse/arg_0018 create mode 100644 tests/lean/docparse/arg_0018.expected.out create mode 100644 tests/lean/docparse/arg_val_0001 create mode 100644 tests/lean/docparse/arg_val_0001.expected.out create mode 100644 tests/lean/docparse/arg_val_0002 create mode 100644 tests/lean/docparse/arg_val_0002.expected.out create mode 100644 tests/lean/docparse/arg_val_0003 create mode 100644 tests/lean/docparse/arg_val_0003.expected.out create mode 100644 tests/lean/docparse/arg_val_0004 create mode 100644 tests/lean/docparse/arg_val_0004.expected.out create mode 100644 tests/lean/docparse/arg_val_0005 create mode 100644 tests/lean/docparse/arg_val_0005.expected.out create mode 100644 tests/lean/docparse/arg_val_0006 create mode 100644 tests/lean/docparse/arg_val_0006.expected.out create mode 100644 tests/lean/docparse/arg_val_0007 create mode 100644 tests/lean/docparse/arg_val_0007.expected.out create mode 100644 tests/lean/docparse/args_0001 create mode 100644 tests/lean/docparse/args_0001.expected.out create mode 100644 tests/lean/docparse/args_0002 create mode 100644 tests/lean/docparse/args_0002.expected.out create mode 100644 tests/lean/docparse/args_0003 create mode 100644 tests/lean/docparse/args_0003.expected.out create mode 100644 tests/lean/docparse/blockOpener_0001 create mode 100644 tests/lean/docparse/blockOpener_0001.expected.out create mode 100644 tests/lean/docparse/blockOpener_0002 create mode 100644 tests/lean/docparse/blockOpener_0002.expected.out create mode 100644 tests/lean/docparse/blockOpener_0003 create mode 100644 tests/lean/docparse/blockOpener_0003.expected.out create mode 100644 tests/lean/docparse/block_0001 create mode 100644 tests/lean/docparse/block_0001.expected.out create mode 100644 tests/lean/docparse/block_0002 create mode 100644 tests/lean/docparse/block_0002.expected.out create mode 100644 tests/lean/docparse/block_0003 create mode 100644 tests/lean/docparse/block_0003.expected.out create mode 100644 tests/lean/docparse/block_0004 create mode 100644 tests/lean/docparse/block_0004.expected.out create mode 100644 tests/lean/docparse/block_0005 create mode 100644 tests/lean/docparse/block_0005.expected.out create mode 100644 tests/lean/docparse/block_0006 create mode 100644 tests/lean/docparse/block_0006.expected.out create mode 100644 tests/lean/docparse/block_0007 create mode 100644 tests/lean/docparse/block_0007.expected.out create mode 100644 tests/lean/docparse/block_0008 create mode 100644 tests/lean/docparse/block_0008.expected.out create mode 100644 tests/lean/docparse/block_0009 create mode 100644 tests/lean/docparse/block_0009.expected.out create mode 100644 tests/lean/docparse/block_0010 create mode 100644 tests/lean/docparse/block_0010.expected.out create mode 100644 tests/lean/docparse/block_0011 create mode 100644 tests/lean/docparse/block_0011.expected.out create mode 100644 tests/lean/docparse/block_0012 create mode 100644 tests/lean/docparse/block_0012.expected.out create mode 100644 tests/lean/docparse/blocks_0001 create mode 100644 tests/lean/docparse/blocks_0001.expected.out create mode 100644 tests/lean/docparse/blocks_0002 create mode 100644 tests/lean/docparse/blocks_0002.expected.out create mode 100644 tests/lean/docparse/blocks_0003 create mode 100644 tests/lean/docparse/blocks_0003.expected.out create mode 100644 tests/lean/docparse/blocks_0004 create mode 100644 tests/lean/docparse/blocks_0004.expected.out create mode 100644 tests/lean/docparse/blocks_0005 create mode 100644 tests/lean/docparse/blocks_0005.expected.out create mode 100644 tests/lean/docparse/blocks_0006 create mode 100644 tests/lean/docparse/blocks_0006.expected.out create mode 100644 tests/lean/docparse/blocks_0007 create mode 100644 tests/lean/docparse/blocks_0007.expected.out create mode 100644 tests/lean/docparse/blocks_0008 create mode 100644 tests/lean/docparse/blocks_0008.expected.out create mode 100644 tests/lean/docparse/blocks_0009 create mode 100644 tests/lean/docparse/blocks_0009.expected.out create mode 100644 tests/lean/docparse/blocks_0010 create mode 100644 tests/lean/docparse/blocks_0010.expected.out create mode 100644 tests/lean/docparse/blocks_0011 create mode 100644 tests/lean/docparse/blocks_0011.expected.out create mode 100644 tests/lean/docparse/blocks_0012 create mode 100644 tests/lean/docparse/blocks_0012.expected.out create mode 100644 tests/lean/docparse/blocks_0013 create mode 100644 tests/lean/docparse/blocks_0013.expected.out create mode 100644 tests/lean/docparse/blocks_0014 create mode 100644 tests/lean/docparse/blocks_0014.expected.out create mode 100644 tests/lean/docparse/blocks_0015 create mode 100644 tests/lean/docparse/blocks_0015.expected.out create mode 100644 tests/lean/docparse/blocks_0016 create mode 100644 tests/lean/docparse/blocks_0016.expected.out create mode 100644 tests/lean/docparse/blocks_0017 create mode 100644 tests/lean/docparse/blocks_0017.expected.out create mode 100644 tests/lean/docparse/blocks_0018 create mode 100644 tests/lean/docparse/blocks_0018.expected.out create mode 100644 tests/lean/docparse/blocks_0019 create mode 100644 tests/lean/docparse/blocks_0019.expected.out create mode 100644 tests/lean/docparse/blocks_0020 create mode 100644 tests/lean/docparse/blocks_0020.expected.out create mode 100644 tests/lean/docparse/blocks_0021 create mode 100644 tests/lean/docparse/blocks_0021.expected.out create mode 100644 tests/lean/docparse/blocks_0022 create mode 100644 tests/lean/docparse/blocks_0022.expected.out create mode 100644 tests/lean/docparse/blocks_0023 create mode 100644 tests/lean/docparse/blocks_0023.expected.out create mode 100644 tests/lean/docparse/blocks_0024 create mode 100644 tests/lean/docparse/blocks_0024.expected.out create mode 100644 tests/lean/docparse/blocks_0025 create mode 100644 tests/lean/docparse/blocks_0025.expected.out create mode 100644 tests/lean/docparse/blocks_0026 create mode 100644 tests/lean/docparse/blocks_0026.expected.out create mode 100644 tests/lean/docparse/blocks_0027 create mode 100644 tests/lean/docparse/blocks_0027.expected.out create mode 100644 tests/lean/docparse/blocks_0028 create mode 100644 tests/lean/docparse/blocks_0028.expected.out create mode 100644 tests/lean/docparse/blocks_0029 create mode 100644 tests/lean/docparse/blocks_0029.expected.out create mode 100644 tests/lean/docparse/blocks_0030 create mode 100644 tests/lean/docparse/blocks_0030.expected.out create mode 100644 tests/lean/docparse/blocks_0031 create mode 100644 tests/lean/docparse/blocks_0031.expected.out create mode 100644 tests/lean/docparse/blocks_0032 create mode 100644 tests/lean/docparse/blocks_0032.expected.out create mode 100644 tests/lean/docparse/blocks_0033 create mode 100644 tests/lean/docparse/blocks_0033.expected.out create mode 100644 tests/lean/docparse/blocks_0034 create mode 100644 tests/lean/docparse/blocks_0034.expected.out create mode 100644 tests/lean/docparse/blocks_0035 create mode 100644 tests/lean/docparse/blocks_0035.expected.out create mode 100644 tests/lean/docparse/blocks_0036 create mode 100644 tests/lean/docparse/blocks_0036.expected.out create mode 100644 tests/lean/docparse/blocks_0037 create mode 100644 tests/lean/docparse/blocks_0037.expected.out create mode 100644 tests/lean/docparse/blocks_0038 create mode 100644 tests/lean/docparse/blocks_0038.expected.out create mode 100644 tests/lean/docparse/blocks_0039 create mode 100644 tests/lean/docparse/blocks_0039.expected.out create mode 100644 tests/lean/docparse/blocks_0040 create mode 100644 tests/lean/docparse/blocks_0040.expected.out create mode 100644 tests/lean/docparse/blocks_0041 create mode 100644 tests/lean/docparse/blocks_0041.expected.out create mode 100644 tests/lean/docparse/blocks_0042 create mode 100644 tests/lean/docparse/blocks_0042.expected.out create mode 100644 tests/lean/docparse/blocks_0043 create mode 100644 tests/lean/docparse/blocks_0043.expected.out create mode 100644 tests/lean/docparse/blocks_0044 create mode 100644 tests/lean/docparse/blocks_0044.expected.out create mode 100644 tests/lean/docparse/blocks_0045 create mode 100644 tests/lean/docparse/blocks_0045.expected.out create mode 100644 tests/lean/docparse/blocks_0046 create mode 100644 tests/lean/docparse/blocks_0046.expected.out create mode 100644 tests/lean/docparse/blocks_0047 create mode 100644 tests/lean/docparse/blocks_0047.expected.out create mode 100644 tests/lean/docparse/blocks_0048 create mode 100644 tests/lean/docparse/blocks_0048.expected.out create mode 100644 tests/lean/docparse/blocks_0049 create mode 100644 tests/lean/docparse/blocks_0049.expected.out create mode 100644 tests/lean/docparse/blocks_0050 create mode 100644 tests/lean/docparse/blocks_0050.expected.out create mode 100644 tests/lean/docparse/blocks_0051 create mode 100644 tests/lean/docparse/blocks_0051.expected.out create mode 100644 tests/lean/docparse/blocks_0052 create mode 100644 tests/lean/docparse/blocks_0052.expected.out create mode 100644 tests/lean/docparse/blocks_0053 create mode 100644 tests/lean/docparse/blocks_0053.expected.out create mode 100644 tests/lean/docparse/blocks_0054 create mode 100644 tests/lean/docparse/blocks_0054.expected.out create mode 100644 tests/lean/docparse/codeBlock_0001 create mode 100644 tests/lean/docparse/codeBlock_0001.expected.out create mode 100644 tests/lean/docparse/codeBlock_0002 create mode 100644 tests/lean/docparse/codeBlock_0002.expected.out create mode 100644 tests/lean/docparse/codeBlock_0003 create mode 100644 tests/lean/docparse/codeBlock_0003.expected.out create mode 100644 tests/lean/docparse/codeBlock_0004 create mode 100644 tests/lean/docparse/codeBlock_0004.expected.out create mode 100644 tests/lean/docparse/codeBlock_0005 create mode 100644 tests/lean/docparse/codeBlock_0005.expected.out create mode 100644 tests/lean/docparse/codeBlock_0006 create mode 100644 tests/lean/docparse/codeBlock_0006.expected.out create mode 100644 tests/lean/docparse/codeBlock_0007 create mode 100644 tests/lean/docparse/codeBlock_0007.expected.out create mode 100644 tests/lean/docparse/codeBlock_0008 create mode 100644 tests/lean/docparse/codeBlock_0008.expected.out create mode 100644 tests/lean/docparse/codeBlock_0009 create mode 100644 tests/lean/docparse/codeBlock_0009.expected.out create mode 100644 tests/lean/docparse/codeBlock_0010 create mode 100644 tests/lean/docparse/codeBlock_0010.expected.out create mode 100644 tests/lean/docparse/codeBlock_0011 create mode 100644 tests/lean/docparse/codeBlock_0011.expected.out create mode 100644 tests/lean/docparse/codeBlock_0012 create mode 100644 tests/lean/docparse/codeBlock_0012.expected.out create mode 100644 tests/lean/docparse/codeBlock_0013 create mode 100644 tests/lean/docparse/codeBlock_0013.expected.out create mode 100644 tests/lean/docparse/codeBlock_0014 create mode 100644 tests/lean/docparse/codeBlock_0014.expected.out create mode 100644 tests/lean/docparse/code_0001 create mode 100644 tests/lean/docparse/code_0001.expected.out create mode 100644 tests/lean/docparse/code_0002 create mode 100644 tests/lean/docparse/code_0002.expected.out create mode 100644 tests/lean/docparse/code_0003 create mode 100644 tests/lean/docparse/code_0003.expected.out create mode 100644 tests/lean/docparse/code_0004 create mode 100644 tests/lean/docparse/code_0004.expected.out create mode 100644 tests/lean/docparse/code_0005 create mode 100644 tests/lean/docparse/code_0005.expected.out create mode 100644 tests/lean/docparse/code_0006 create mode 100644 tests/lean/docparse/code_0006.expected.out create mode 100644 tests/lean/docparse/code_0007 create mode 100644 tests/lean/docparse/code_0007.expected.out create mode 100644 tests/lean/docparse/code_0008 create mode 100644 tests/lean/docparse/code_0008.expected.out create mode 100644 tests/lean/docparse/code_0009 create mode 100644 tests/lean/docparse/code_0009.expected.out create mode 100644 tests/lean/docparse/code_0010 create mode 100644 tests/lean/docparse/code_0010.expected.out create mode 100644 tests/lean/docparse/code_0011 create mode 100644 tests/lean/docparse/code_0011.expected.out create mode 100644 tests/lean/docparse/directive_0001 create mode 100644 tests/lean/docparse/directive_0001.expected.out create mode 100644 tests/lean/docparse/directive_0002 create mode 100644 tests/lean/docparse/directive_0002.expected.out create mode 100644 tests/lean/docparse/directive_0003 create mode 100644 tests/lean/docparse/directive_0003.expected.out create mode 100644 tests/lean/docparse/directive_0004 create mode 100644 tests/lean/docparse/directive_0004.expected.out create mode 100644 tests/lean/docparse/directive_0005 create mode 100644 tests/lean/docparse/directive_0005.expected.out create mode 100644 tests/lean/docparse/directive_0006 create mode 100644 tests/lean/docparse/directive_0006.expected.out create mode 100644 tests/lean/docparse/document_0001 create mode 100644 tests/lean/docparse/document_0001.expected.out create mode 100644 tests/lean/docparse/emph_0001 create mode 100644 tests/lean/docparse/emph_0001.expected.out create mode 100644 tests/lean/docparse/emph_0002 create mode 100644 tests/lean/docparse/emph_0002.expected.out create mode 100644 tests/lean/docparse/emph_0003 create mode 100644 tests/lean/docparse/emph_0003.expected.out create mode 100644 tests/lean/docparse/header_0001 create mode 100644 tests/lean/docparse/header_0001.expected.out create mode 100644 tests/lean/docparse/inlineTextChar_0001 create mode 100644 tests/lean/docparse/inlineTextChar_0001.expected.out create mode 100644 tests/lean/docparse/inlineTextChar_0002 create mode 100644 tests/lean/docparse/inlineTextChar_0002.expected.out create mode 100644 tests/lean/docparse/inlineTextChar_0003 create mode 100644 tests/lean/docparse/inlineTextChar_0003.expected.out create mode 100644 tests/lean/docparse/inlineTextChar_0004 create mode 100644 tests/lean/docparse/inlineTextChar_0004.expected.out create mode 100644 tests/lean/docparse/inlineTextChar_0005 create mode 100644 tests/lean/docparse/inlineTextChar_0005.expected.out create mode 100644 tests/lean/docparse/lookaheadOrderedListIndicator_0001 create mode 100644 tests/lean/docparse/lookaheadOrderedListIndicator_0001.expected.out create mode 100644 tests/lean/docparse/lookaheadOrderedListIndicator_0002 create mode 100644 tests/lean/docparse/lookaheadOrderedListIndicator_0002.expected.out create mode 100644 tests/lean/docparse/lookaheadOrderedListIndicator_0003 create mode 100644 tests/lean/docparse/lookaheadOrderedListIndicator_0003.expected.out create mode 100644 tests/lean/docparse/lookaheadOrderedListIndicator_0004 create mode 100644 tests/lean/docparse/lookaheadOrderedListIndicator_0004.expected.out create mode 100644 tests/lean/docparse/lookaheadOrderedListIndicator_0005 create mode 100644 tests/lean/docparse/lookaheadOrderedListIndicator_0005.expected.out create mode 100644 tests/lean/docparse/lookaheadOrderedListIndicator_0006 create mode 100644 tests/lean/docparse/lookaheadOrderedListIndicator_0006.expected.out create mode 100644 tests/lean/docparse/lookaheadOrderedListIndicator_0007 create mode 100644 tests/lean/docparse/lookaheadOrderedListIndicator_0007.expected.out create mode 100644 tests/lean/docparse/lookaheadUnorderedListIndicator_0001 create mode 100644 tests/lean/docparse/lookaheadUnorderedListIndicator_0001.expected.out create mode 100644 tests/lean/docparse/lookaheadUnorderedListIndicator_0002 create mode 100644 tests/lean/docparse/lookaheadUnorderedListIndicator_0002.expected.out create mode 100644 tests/lean/docparse/lookaheadUnorderedListIndicator_0003 create mode 100644 tests/lean/docparse/lookaheadUnorderedListIndicator_0003.expected.out create mode 100644 tests/lean/docparse/lookaheadUnorderedListIndicator_0004 create mode 100644 tests/lean/docparse/lookaheadUnorderedListIndicator_0004.expected.out create mode 100644 tests/lean/docparse/lookaheadUnorderedListIndicator_0005 create mode 100644 tests/lean/docparse/lookaheadUnorderedListIndicator_0005.expected.out create mode 100644 tests/lean/docparse/lookaheadUnorderedListIndicator_0006 create mode 100644 tests/lean/docparse/lookaheadUnorderedListIndicator_0006.expected.out create mode 100644 tests/lean/docparse/manyInlineTextChar_0001 create mode 100644 tests/lean/docparse/manyInlineTextChar_0001.expected.out create mode 100644 tests/lean/docparse/manyInlineTextChar_0002 create mode 100644 tests/lean/docparse/manyInlineTextChar_0002.expected.out create mode 100644 tests/lean/docparse/manyInlineTextChar_0003 create mode 100644 tests/lean/docparse/manyInlineTextChar_0003.expected.out create mode 100644 tests/lean/docparse/metadataBlock_0001 create mode 100644 tests/lean/docparse/metadataBlock_0001.expected.out create mode 100644 tests/lean/docparse/metadataBlock_0002 create mode 100644 tests/lean/docparse/metadataBlock_0002.expected.out create mode 100644 tests/lean/docparse/metadataBlock_0003 create mode 100644 tests/lean/docparse/metadataBlock_0003.expected.out create mode 100644 tests/lean/docparse/nameAndArgs_0001 create mode 100644 tests/lean/docparse/nameAndArgs_0001.expected.out create mode 100644 tests/lean/docparse/nameAndArgs_0002 create mode 100644 tests/lean/docparse/nameAndArgs_0002.expected.out create mode 100644 tests/lean/docparse/nameAndArgs_0003 create mode 100644 tests/lean/docparse/nameAndArgs_0003.expected.out create mode 100644 tests/lean/docparse/nameAndArgs_0004 create mode 100644 tests/lean/docparse/nameAndArgs_0004.expected.out create mode 100644 tests/lean/docparse/nameAndArgs_0005 create mode 100644 tests/lean/docparse/nameAndArgs_0005.expected.out create mode 100644 tests/lean/docparse/nameAndArgs_0006 create mode 100644 tests/lean/docparse/nameAndArgs_0006.expected.out create mode 100644 tests/lean/docparse/nameAndArgs_0007 create mode 100644 tests/lean/docparse/nameAndArgs_0007.expected.out create mode 100644 tests/lean/docparse/nameAndArgs_0008 create mode 100644 tests/lean/docparse/nameAndArgs_0008.expected.out create mode 100644 tests/lean/docparse/nameAndArgs_0009 create mode 100644 tests/lean/docparse/nameAndArgs_0009.expected.out create mode 100644 tests/lean/docparse/nameAndArgs_0010 create mode 100644 tests/lean/docparse/nameAndArgs_0010.expected.out create mode 100644 tests/lean/docparse/oneInline_0001 create mode 100644 tests/lean/docparse/oneInline_0001.expected.out create mode 100644 tests/lean/docparse/oneInline_0002 create mode 100644 tests/lean/docparse/oneInline_0002.expected.out create mode 100644 tests/lean/docparse/oneInline_0003 create mode 100644 tests/lean/docparse/oneInline_0003.expected.out create mode 100644 tests/lean/docparse/oneInline_0004 create mode 100644 tests/lean/docparse/oneInline_0004.expected.out create mode 100644 tests/lean/docparse/oneInline_0005 create mode 100644 tests/lean/docparse/oneInline_0005.expected.out create mode 100644 tests/lean/docparse/oneInline_0006 create mode 100644 tests/lean/docparse/oneInline_0006.expected.out create mode 100644 tests/lean/docparse/oneInline_0007 create mode 100644 tests/lean/docparse/oneInline_0007.expected.out create mode 100644 tests/lean/docparse/oneInline_0008 create mode 100644 tests/lean/docparse/oneInline_0008.expected.out create mode 100644 tests/lean/docparse/oneInline_0009 create mode 100644 tests/lean/docparse/oneInline_0009.expected.out create mode 100644 tests/lean/docparse/oneInline_0010 create mode 100644 tests/lean/docparse/oneInline_0010.expected.out create mode 100644 tests/lean/docparse/oneInline_0011 create mode 100644 tests/lean/docparse/oneInline_0011.expected.out create mode 100644 tests/lean/docparse/oneInline_0012 create mode 100644 tests/lean/docparse/oneInline_0012.expected.out create mode 100644 tests/lean/docparse/oneInline_0013 create mode 100644 tests/lean/docparse/oneInline_0013.expected.out create mode 100644 tests/lean/docparse/oneInline_0014 create mode 100644 tests/lean/docparse/oneInline_0014.expected.out create mode 100644 tests/lean/docparse/oneInline_0015 create mode 100644 tests/lean/docparse/oneInline_0015.expected.out create mode 100644 tests/lean/docparse/oneInline_0016 create mode 100644 tests/lean/docparse/oneInline_0016.expected.out create mode 100644 tests/lean/docparse/oneInline_0017 create mode 100644 tests/lean/docparse/oneInline_0017.expected.out create mode 100644 tests/lean/docparse/oneInline_0018 create mode 100644 tests/lean/docparse/oneInline_0018.expected.out create mode 100644 tests/lean/docparse/oneInline_0019 create mode 100644 tests/lean/docparse/oneInline_0019.expected.out create mode 100644 tests/lean/docparse/oneInline_0020 create mode 100644 tests/lean/docparse/oneInline_0020.expected.out create mode 100644 tests/lean/docparse/oneInline_0021 create mode 100644 tests/lean/docparse/oneInline_0021.expected.out create mode 100644 tests/lean/docparse/oneInline_0022 create mode 100644 tests/lean/docparse/oneInline_0022.expected.out create mode 100644 tests/lean/docparse/recoverBlock_0001 create mode 100644 tests/lean/docparse/recoverBlock_0001.expected.out create mode 100644 tests/lean/docparse/recoverBlocks_0001 create mode 100644 tests/lean/docparse/recoverBlocks_0001.expected.out create mode 100644 tests/lean/docparse/role_0001 create mode 100644 tests/lean/docparse/role_0001.expected.out create mode 100644 tests/lean/docparse/role_0002 create mode 100644 tests/lean/docparse/role_0002.expected.out create mode 100644 tests/lean/docparse/role_0003 create mode 100644 tests/lean/docparse/role_0003.expected.out create mode 100644 tests/lean/docparse/role_0004 create mode 100644 tests/lean/docparse/role_0004.expected.out create mode 100644 tests/lean/docparse/role_0005 create mode 100644 tests/lean/docparse/role_0005.expected.out create mode 100644 tests/lean/docparse/run.lean create mode 100755 tests/lean/docparse/test_single.sh create mode 100644 tests/lean/docparse/text_0001 create mode 100644 tests/lean/docparse/text_0001.expected.out create mode 100644 tests/lean/run/levenshtein.lean create mode 100644 tests/lean/run/versoDocs.lean diff --git a/src/Lean/BuiltinDocAttr.lean b/src/Lean/BuiltinDocAttr.lean index f34fb52815..684c6c891b 100644 --- a/src/Lean/BuiltinDocAttr.lean +++ b/src/Lean/BuiltinDocAttr.lean @@ -34,6 +34,7 @@ builtin_initialize add := fun decl stx _ => do Attribute.Builtin.ensureNoArgs stx declareBuiltinDocStringAndRanges decl + applicationTime := AttributeApplicationTime.afterCompilation } end Lean diff --git a/src/Lean/Data/EditDistance.lean b/src/Lean/Data/EditDistance.lean new file mode 100644 index 0000000000..980fed5550 --- /dev/null +++ b/src/Lean/Data/EditDistance.lean @@ -0,0 +1,55 @@ +/- +Copyright (c) 2024-2025 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: David Thrane Christiansen +-/ +module +prelude +public import Init + +set_option linter.missingDocs true + +namespace Lean.EditDistance + +/-- +Computes the Levenshtein distance between two strings, up to some cutoff. + +If the return value is `none`, then the distance is certainly greater than the cutoff value, but a +returned `some` does not necessarily indicate that the edit distance is less than or equal to the +cutoff. +-/ +public def levenshtein (str1 str2 : String) (cutoff : Nat) : Option Nat := Id.run do + let len1 := str1.length + let len2 := str2.length + + -- The lower bound on the Levenshtein distance is the difference in lengths + if max len1 len2 - min len1 len2 > cutoff then return none + + let mut v0 := Vector.replicate (len2 + 1) 0 + let mut v1 := v0 + + for h : i in [0:v0.size] do + v0 := v0.set i i + let mut iter1 := str1.iter + let mut i := 0 + while h1 : iter1.hasNext do + v1 := v1.set 0 (i+1) + let mut iter2 := str2.iter + let mut j : Fin (len2 + 1) := 0 + while h2 : iter2.hasNext do + let j' : Fin _ := j + 1 + let deletionCost := v0[j'] + 1 + let insertionCost := v1[j] + 1 + let substCost := + if iter1.curr' h1 == iter2.curr' h2 then v0[j] + else v0[j] + 1 + let cost := min (min deletionCost insertionCost) substCost + v1 := v1.set j' cost + iter2 := iter2.next' h2 + j := j + 1 + iter1 := iter1.next' h1 + i := i + 1 + -- Terminate early if it's impossible that the result is below the cutoff + if v1.all (· > cutoff) then return none + v0 := v1 + some v0[len2] diff --git a/src/Lean/DocString/Add.lean b/src/Lean/DocString/Add.lean index 3f629f87e5..c10e5bd3ca 100644 --- a/src/Lean/DocString/Add.lean +++ b/src/Lean/DocString/Add.lean @@ -7,11 +7,17 @@ Authors: David Thrane Christiansen module prelude -public import Lean.Environment -public import Lean.Exception -public import Lean.Log -public import Lean.DocString.Extension -public import Lean.DocString.Links +import Lean.Environment +import Lean.Exception +import Lean.Log +import Lean.Elab.DocString +import Lean.DocString.Extension +import Lean.DocString.Links +import Lean.Parser.Types +import Lean.DocString.Parser +import Lean.ResolveName +public import Lean.Elab.Term.TermElabM +import Std.Data.HashMap public section @@ -19,6 +25,8 @@ set_option linter.missingDocs true namespace Lean +open Lean.Elab.Term (TermElabM) + /-- Validates all links to the Lean reference manual in `docstring`. @@ -26,9 +34,8 @@ This is intended to be used before saving a docstring that is later subject to r `rewriteManualLinks`. -/ def validateDocComment - [Monad m] [MonadLog m] [AddMessageContext m] [MonadOptions m] [MonadLiftT IO m] (docstring : TSyntax `Lean.Parser.Command.docComment) : - m Unit := do + TermElabM Unit := do let str := docstring.getDocString let pos? := docstring.raw[1].getHeadInfo? >>= (·.getPos?) @@ -42,27 +49,131 @@ def validateDocComment else logError err + +open Lean.Doc in +open Parser in /-- -Adds a docstring to the environment, validating documentation links. +Adds a Verso docstring to the specified declaration, which should already be present in the +environment. -/ -def addDocString - [Monad m] [MonadError m] [MonadEnv m] [MonadLog m] [AddMessageContext m] [MonadOptions m] [MonadLiftT IO m] - (declName : Name) (docComment : TSyntax `Lean.Parser.Command.docComment) : m Unit := do +def versoDocString + (declName : Name) (binders : Syntax) (docComment : TSyntax `Lean.Parser.Command.docComment) : + TermElabM (Array (Doc.Block ElabInline ElabBlock) × Array (Doc.Part ElabInline ElabBlock Empty)) := do + + let text ← getFileMap + -- TODO fallback to string version without nice interactivity + let some startPos := docComment.raw[1].getPos? (canonicalOnly := true) + | throwErrorAt docComment m!"Documentation comment has no source location, cannot parse" + let some endPos := docComment.raw[1].getTailPos? (canonicalOnly := true) + | throwErrorAt docComment m!"Documentation comment has no source location, cannot parse" + + -- Skip trailing `-/` + let endPos := text.source.prev <| text.source.prev endPos + let endPos := if endPos ≤ text.source.endPos then endPos else text.source.endPos + have endPos_valid : endPos ≤ text.source.endPos := by + unfold endPos + split <;> simp [*] + + let env ← getEnv + let ictx : InputContext := + .mk text.source (← getFileName) (fileMap := text) + (endPos := endPos) (endPos_valid := endPos_valid) + let pmctx : ParserModuleContext := { + env, + options := ← getOptions, + currNamespace := (← getCurrNamespace), + openDecls := (← getOpenDecls) + } + let s := mkParserState text.source |>.setPos startPos + -- TODO parse one block at a time for error recovery purposes + let s := (Doc.Parser.document).run ictx pmctx (getTokenTable env) s + + if !s.allErrors.isEmpty then + for (pos, _, err) in s.allErrors do + logMessage { + fileName := (← getFileName), + pos := text.toPosition pos, + -- TODO end position + data := err.toString + } + return (#[], #[]) + else + let stx := s.stxStack.back + let stx := stx.getArgs + Doc.elabBlocks (stx.map (⟨·⟩)) |>.exec declName binders + +/-- +Adds a Markdown docstring to the environment, validating documentation links. +-/ +def addMarkdownDocString (declName : Name) (docComment : TSyntax `Lean.Parser.Command.docComment) : TermElabM Unit := do if declName.isAnonymous then -- This case might happen on partial elaboration; ignore instead of triggering any panics below return + let throwImported {α} : TermElabM α := + throwError m!"invalid doc string, declaration `{.ofConstName declName}` is in an imported module" unless (← getEnv).getModuleIdxFor? declName |>.isNone do - throwError "invalid doc string, declaration `{.ofConstName declName}` is in an imported module" + throwImported validateDocComment docComment let docString : String ← getDocStringText docComment modifyEnv fun env => docStringExt.insert env declName docString.removeLeadingSpaces /-- -Adds a docstring to the environment, validating documentation links. +Adds an elaborated Verso docstring to the environment. +-/ +def addVersoDocStringCore [Monad m] [MonadEnv m] [MonadLiftT BaseIO m] [MonadError m] + (declName : Name) (docs : VersoDocString) : m Unit := do + let throwImported {α} : m α := + throwError s!"invalid doc string, declaration '{declName}' is in an imported module" + unless (← getEnv).getModuleIdxFor? declName |>.isNone do + throwImported + modifyEnv fun env => + versoDocStringExt.insert env declName docs --addEntry (asyncMode := .async .asyncEnv) + --env (declName, p) -- Using .insert env declName ⟨blocks, parts⟩ leads to panics + +/-- +Adds a Verso docstring to the environment. +-/ +def addVersoDocString (declName : Name) (binders : Syntax) (docComment : TSyntax `Lean.Parser.Command.docComment) : TermElabM Unit := do + unless (← getEnv).getModuleIdxFor? declName |>.isNone do + throwError s!"invalid doc string, declaration '{declName}' is in an imported module" + let (blocks, parts) ← versoDocString declName binders docComment + addVersoDocStringCore declName ⟨blocks, parts⟩ + +/-- +Adds a docstring to the environment. If `isVerso` is `false`, then the docstring is interpreted as +Markdown. +-/ +def addDocStringOf + (isVerso : Bool) (declName : Name) (binders : Syntax) (docComment : TSyntax `Lean.Parser.Command.docComment) : + TermElabM Unit := do + if isVerso then + addVersoDocString declName binders docComment + else + addMarkdownDocString declName docComment + +/-- +Adds a docstring to the environment. + +If the option `doc.verso` is `true`, the docstring is processed as a Verso docstring. + +Otherwise, it is considered a Markdown docstring, and documentation links are validated. +-/ +def addDocString + (declName : Name) (binders : Syntax) (docComment : TSyntax `Lean.Parser.Command.docComment) : + TermElabM Unit := do + addDocStringOf (doc.verso.get (← getOptions)) declName binders docComment + +/-- +Adds a docstring to the environment, if it is provided. If no docstring is provided, nothing +happens. + +If the option `doc.verso` is `true`, the docstring is processed as a Verso docstring. + +Otherwise, it is considered a Markdown docstring, and documentation links are validated. -/ def addDocString' - [Monad m] [MonadError m] [MonadEnv m] [MonadLog m] [AddMessageContext m] [MonadOptions m] [MonadLiftT IO m] - (declName : Name) (docString? : Option (TSyntax `Lean.Parser.Command.docComment)) : m Unit := + (declName : Name) (binders : Syntax) (docString? : Option (TSyntax `Lean.Parser.Command.docComment)) : + TermElabM Unit := match docString? with - | some docString => addDocString declName docString + | some docString => addDocString declName binders docString | none => return () diff --git a/src/Lean/DocString/Extension.lean b/src/Lean/DocString/Extension.lean index 84235c9dee..e7bb786fac 100644 --- a/src/Lean/DocString/Extension.lean +++ b/src/Lean/DocString/Extension.lean @@ -7,9 +7,12 @@ module prelude public import Lean.DeclarationRange +public import Lean.Data.Options public import Lean.DocString.Links public import Lean.MonadEnv public import Init.Data.String.Extra +public import Lean.DocString.Types +import Lean.DocString.Markdown public section @@ -20,8 +23,48 @@ public section namespace Lean + +/-- +Saved data that describes the contents. The `name` should determine both the type of the value and +its interpretation; if in doubt, use the name of the elaborator that produces the data. +-/ +structure ElabInline where + name : Name + val : Dynamic + +private instance : Doc.MarkdownInline ElabInline where + -- TODO extensibility + toMarkdown go _i content := content.forM go + + +/-- +Saved data that describes the contents. The `name` should determine both the type of the value and +its interpretation; if in doubt, use the name of the elaborator that produces the data. +-/ +structure ElabBlock where + name : Name + value : Dynamic + +-- TODO extensible toMarkdown +private instance : Doc.MarkdownBlock ElabInline ElabBlock where + toMarkdown _goI goB _b content := content.forM goB + +structure VersoDocString where + text : Array (Doc.Block ElabInline ElabBlock) + subsections : Array (Doc.Part ElabInline ElabBlock Empty) +deriving Inhabited + +register_builtin_option doc.verso : Bool := { + defValue := false, + descr := "whether to use Verso syntax in docstrings" + group := "doc" +} + private builtin_initialize builtinDocStrings : IO.Ref (NameMap String) ← IO.mkRef {} -builtin_initialize docStringExt : MapDeclarationExtension String ← mkMapDeclarationExtension +builtin_initialize docStringExt : MapDeclarationExtension String ← mkMapDeclarationExtension (asyncMode := .async .asyncEnv) + +private builtin_initialize builtinVersoDocStrings : IO.Ref (NameMap VersoDocString) ← IO.mkRef {} +builtin_initialize versoDocStringExt : MapDeclarationExtension VersoDocString ← mkMapDeclarationExtension (asyncMode := .async .asyncEnv) /-- Adds a builtin docstring to the compiler. @@ -32,28 +75,56 @@ Links to the Lean manual aren't validated. def addBuiltinDocString (declName : Name) (docString : String) : IO Unit := do builtinDocStrings.modify (·.insert declName docString.removeLeadingSpaces) -def addDocStringCore [Monad m] [MonadError m] [MonadEnv m] (declName : Name) (docString : String) : m Unit := do +def addDocStringCore [Monad m] [MonadError m] [MonadEnv m] [MonadLiftT BaseIO m] (declName : Name) (docString : String) : m Unit := do unless (← getEnv).getModuleIdxFor? declName |>.isNone do - throwError "invalid doc string, declaration `{.ofConstName declName}` is in an imported module" + throwError m!"invalid doc string, declaration `{.ofConstName declName}` is in an imported module" modifyEnv fun env => docStringExt.insert env declName docString.removeLeadingSpaces -def addDocStringCore' [Monad m] [MonadError m] [MonadEnv m] (declName : Name) (docString? : Option String) : m Unit := +def addDocStringCore' [Monad m] [MonadError m] [MonadEnv m] [MonadLiftT BaseIO m] (declName : Name) (docString? : Option String) : m Unit := match docString? with | some docString => addDocStringCore declName docString | none => return () /-- Finds a docstring without performing any alias resolution or enrichment with extra metadata. +For Markdown docstrings, the result is a string; for Verso docstrings, it's a `VersoDocString`. Docstrings to be shown to a user should be looked up with `Lean.findDocString?` instead. -/ -def findSimpleDocString? (env : Environment) (declName : Name) (includeBuiltin := true) : IO (Option String) := - if let some docStr := docStringExt.find? env declName then - return some docStr - else if includeBuiltin then - return (← builtinDocStrings.get).find? declName - else - return none +def findInternalDocString? (env : Environment) (declName : Name) (includeBuiltin := true) : IO (Option (String ⊕ VersoDocString)) := do + match docStringExt.find? env declName with + | some md => return some (.inl md) + | none => pure () + match versoDocStringExt.find? env declName with + | some v => return some (.inr v) + | none => pure () + if includeBuiltin then + if let some docStr := (← builtinDocStrings.get).find? declName then + return some (.inl docStr) + else if let some doc := (← builtinVersoDocStrings.get).find? declName then + return some (.inr doc) + return none + +/-- +Finds a docstring without performing any alias resolution or enrichment with extra metadata. The +result is rendered as Markdown. + +Docstrings to be shown to a user should be looked up with `Lean.findDocString?` instead. +-/ +def findSimpleDocString? (env : Environment) (declName : Name) (includeBuiltin := true) : IO (Option String) := do + match (← findInternalDocString? env declName (includeBuiltin := includeBuiltin)) with + | some (.inl str) => return some str + | some (.inr verso) => return some (toMarkdown verso) + | none => return none + +where + toMarkdown : VersoDocString → String + | .mk bs ps => Doc.MarkdownM.run' do + for b in bs do + Doc.ToMarkdown.toMarkdown b + for p in ps do + Doc.ToMarkdown.toMarkdown p + structure ModuleDoc where doc : String diff --git a/src/Lean/DocString/Links.lean b/src/Lean/DocString/Links.lean index 307033b8f3..db4a477a9e 100644 --- a/src/Lean/DocString/Links.lean +++ b/src/Lean/DocString/Links.lean @@ -55,6 +55,38 @@ private def domainMap : Std.HashMap String String := ("errorExplanation", errorExplanationManualDomain) ] +/-- The valid domain abbreviations in the manual. -/ +def manualDomains : List String := domainMap.keys + +/-- +Constructs a link to the manual. +-/ +def manualLink (kind name : String) : Except String String := + if let some domain := domainMap.get? kind then + return manualRoot ++ s!"find/?domain={domain}&name={name}" + else + let acceptableKinds := ", ".intercalate <| domainMap.toList.map fun (k, _) => s!"`{k}`" + throw s!"Unknown documentation type `{kind}`. Expected one of the following: {acceptableKinds}" + +private def rw (path : String) : Except String String := do + match path.splitOn "/" with + | [] | [""] => + throw "Missing documentation type" + | kind :: args => + if let some domain := domainMap.get? kind then + if let [s] := args then + if s.isEmpty then + throw s!"Empty {kind} ID" + return s!"find/?domain={domain}&name={s}" + else + throw s!"Expected one item after `{kind}`, but got {args}" + else + let acceptableKinds := ", ".intercalate <| domainMap.toList.map fun (k, _) => s!"`{k}`" + throw s!"Unknown documentation type `{kind}`. Expected one of the following: {acceptableKinds}" + + + + /-- Rewrites links from the internal Lean manual syntax to the correct URL. This rewriting is an overapproximation: any parentheses containing the internal syntax of a Lean manual URL is rewritten. @@ -122,23 +154,6 @@ where lookingAt (goal : String) (iter : String.Iterator) : Bool := iter.s.substrEq iter.i goal 0 goal.endPos.byteIdx - rw (path : String) : Except String String := do - match path.splitOn "/" with - | [] | [""] => - throw "Missing documentation type" - | kind :: args => - if let some domain := domainMap.get? kind then - if let [s] := args then - if s.isEmpty then - throw s!"Empty {kind} ID" - return s!"find/?domain={domain}&name={s}" - else - throw s!"Expected one item after `{kind}`, but got {args}" - else - let acceptableKinds := ", ".intercalate <| domainMap.toList.map fun (k, _) => s!"`{k}`" - throw s!"Unknown documentation type `{kind}`. Expected one of the following: {acceptableKinds}" - - /-- Rewrites Lean reference manual links in `docstring` to point at the reference manual. diff --git a/src/Lean/DocString/Markdown.lean b/src/Lean/DocString/Markdown.lean new file mode 100644 index 0000000000..59efa2f10e --- /dev/null +++ b/src/Lean/DocString/Markdown.lean @@ -0,0 +1,295 @@ +/- +Copyright (c) 2023-2025 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: David Thrane Christiansen +-/ + +module + +prelude + +import Init.Data.Repr +import Init.Data.Ord +public import Lean.DocString.Types + +set_option linter.missingDocs true + +namespace Lean.Doc + +namespace MarkdownM + +/-- +The surrounding context of Markdown that's being generated, in order to prevent nestings that +Markdown doesn't allow. +-/ +public structure Context where + /-- The current code is inside emphasis. -/ + inEmph : Bool := false + /-- The current code is inside strong emphasis. -/ + inBold : Bool := false + /-- The current code is inside a link. -/ + inLink : Bool := false + /-- The prefix that should be added to each line (typically for indentation). -/ + linePrefix : String := "" + +/-- The state of a Markdown generation task. -/ +public structure State where + /-- The blocks prior to the one being generated. -/ + priorBlocks : String := "" + /-- The block being generated. -/ + currentBlock : String := "" + /-- Footnotes -/ + footnotes : Array (String × String) := #[] + +private def combineBlocks (prior current : String) := + if prior.isEmpty then current + else if current.isEmpty then prior + else if prior.endsWith "\n\n" then prior ++ current + else if prior.endsWith "\n" then prior ++ "\n" ++ current + else prior ++ "\n\n" ++ current + +private def State.endBlock (state : State) : State := + { state with + priorBlocks := + combineBlocks state.priorBlocks state.currentBlock ++ + (if state.footnotes.isEmpty then "" + else state.footnotes.foldl (init := "\n\n") fun s (n, txt) => s ++ s!"[^{n}]:{txt}\n\n"), + currentBlock := "", + footnotes := #[] + } + +private def State.render (state : State) : String := + state.endBlock.priorBlocks + +private def State.push (state : State) (txt : String) : State := + { state with currentBlock := state.currentBlock ++ txt } + +end MarkdownM + +open MarkdownM in +/-- +The monad for generating Markdown output. +-/ +public abbrev MarkdownM := ReaderT Context (StateM State) + +/-- +Generates Markdown, rendering the result from the final state. +-/ +public def MarkdownM.run (act : MarkdownM α) (context : Context := {}) (state : State := {}) : (α × String) := + let (val, state) := act context state + (val, state.render) + +/-- +Generates Markdown, rendering the result from the final state, without producing a value. +-/ +public def MarkdownM.run' (act : MarkdownM Unit) (context : Context := {}) (state : State := {}) : String := + act.run context state |>.2 + +private def MarkdownM.push (txt : String) : MarkdownM Unit := modify (·.push txt) + +private def MarkdownM.endBlock : MarkdownM Unit := modify (·.endBlock) + +private def MarkdownM.indent: MarkdownM α → MarkdownM α := + withReader fun st => { st with linePrefix := st.linePrefix ++ " " } + +/-- +A means of transforming values to Markdown representations. +-/ +public class ToMarkdown (α : Type u) where + /-- + A function that transforms an `α` into a Markdown representation. + -/ + toMarkdown : α → MarkdownM Unit + +/-- +A way to transform inline elements extended with `i` into Markdown. +-/ +public class MarkdownInline (i : Type u) where + /-- + A function that transforms an `i` and its contents into Markdown, given a way to transform the + contents. + -/ + toMarkdown : (Inline i → MarkdownM Unit) → i → Array (Inline i) → MarkdownM Unit + +public instance : MarkdownInline Empty where + toMarkdown := nofun + +/-- +A way to transform block elements extended with `b` that contain inline elements extended with `i` +into Markdown. +-/ +public class MarkdownBlock (i : Type u) (b : Type v) where + /-- + A function that transforms a `b` and its contents into Markdown, given a way to transform the + contents. + -/ + toMarkdown : + (Inline i → MarkdownM Unit) → (Block i b → MarkdownM Unit) → + b → Array (Block i b) → MarkdownM Unit + +public instance : MarkdownBlock i Empty where + toMarkdown := nofun + +private def escape (s : String) : String := Id.run do + let mut s' := "" + let mut iter := s.iter + while h : iter.hasNext do + let c := iter.curr' h + iter := iter.next' h + if isSpecial c then + s' := s'.push '\\' + s' := s'.push c + return s' +where + isSpecial c := "*_`-+.!<>[]{}()#".any (· == c) + +private def quoteCode (str : String) : String := Id.run do + let mut longest := 0 + let mut current := 0 + let mut iter := str.iter + while h : iter.hasNext do + let c := iter.curr' h + iter := iter.next' h + if c == '`' then + current := current + 1 + else + longest := max longest current + current := 0 + let backticks := "".pushn '`' (max longest current + 1) + let str := if str.startsWith "`" || str.endsWith "`" then " " ++ str ++ " " else str + backticks ++ str ++ backticks + +open MarkdownM in +private partial def inlineMarkdown [MarkdownInline i] : Inline i → MarkdownM Unit + | .text s => + push (escape s) + | .linebreak s => do + push <| s.replace "\n" ("\n" ++ (← read).linePrefix ) + | .emph xs => do + unless (← read).inEmph do + push "*" + withReader (fun ρ => { ρ with inEmph := true }) do + for i in xs do inlineMarkdown i + unless (← read).inEmph do + push "*" + | .bold xs => do + unless (← read).inEmph do + push "**" + withReader (fun ρ => { ρ with inEmph := true }) do + for i in xs do inlineMarkdown i + unless (← read).inEmph do + push "**" + | .concat xs => + for i in xs do inlineMarkdown i + | .link content url => do + if (← read).inLink then + for i in content do inlineMarkdown i + else + push "[" + for i in content do inlineMarkdown i + push "](" + push url + push ")" + | .image alt url => + push s!"![{escape alt}]({url})" + | .footnote name content => do + push s!"[ˆ^{name}]" + let footnoteContent := (content.forM inlineMarkdown) {} {} |>.2.render + modify fun st => { st with footnotes := st.footnotes.push (name, footnoteContent) } + | .code str => + push (quoteCode str) + | .math .display m => push s!"$${m}$$" + | .math .inline m => push s!"${m}$" + | .other container content => do + MarkdownInline.toMarkdown inlineMarkdown container content + +public instance [MarkdownInline i] : ToMarkdown (Inline i) where + toMarkdown inline := private inlineMarkdown inline + +private def quoteCodeBlock (indent : Nat) (str : String) : String := Id.run do + let mut longest := 2 + let mut current := 0 + let mut iter := str.iter + let mut out := "" + while h : iter.hasNext do + let c := iter.curr' h + iter := iter.next' h + if c == '`' then + current := current + 1 + else + longest := max longest current + current := 0 + out := out.push c + if c == '\n' then + out := out.pushn ' ' indent + let backticks := "" |>.pushn ' ' indent |>.pushn '`' (max longest current + 1) + backticks ++ "\n" ++ out ++ "\n" ++ backticks ++ "\n" + +open MarkdownM in +private partial def blockMarkdown [MarkdownInline i] [MarkdownBlock i b] : Block i b → MarkdownM Unit + | .para xs => do + for i in xs do + ToMarkdown.toMarkdown i + endBlock + | .concat bs => + for b in bs do + blockMarkdown b + | .blockquote bs => do + withReader (fun ρ => { ρ with linePrefix := ρ.linePrefix ++ "> " }) + for b in bs do + blockMarkdown b + endBlock + | .ul items => do + for item in items do + push <| (← read).linePrefix ++ "* " + withReader (fun ρ => { ρ with linePrefix := ρ.linePrefix ++ " " }) do + for b in item.contents do + blockMarkdown b + endBlock + | .ol start items => do + let mut n := max 1 start.toNat + for item in items do + push <| (← read).linePrefix ++ s!"{n}. " + withReader (fun ρ => { ρ with linePrefix := ρ.linePrefix ++ " " }) do + for b in item.contents do + blockMarkdown b + n := n + 1 + endBlock + | .dl items => do + for item in items do + push <| (← read).linePrefix ++ "* " + withReader (fun ρ => { ρ with linePrefix := ρ.linePrefix ++ " " }) do + inlineMarkdown (.bold item.term) + inlineMarkdown (.text ": " : Inline i) + push "\n" + push (← read).linePrefix + blockMarkdown (.concat item.desc) + endBlock + | .code str => do + unless (← get).currentBlock.isEmpty || (← get).currentBlock.endsWith "\n" do + push "\n" + push <| quoteCodeBlock (← read).linePrefix.length str + endBlock + | .other container content => + MarkdownBlock.toMarkdown (i := i) (b := b) inlineMarkdown blockMarkdown container content + + +public instance [MarkdownInline i] [MarkdownBlock i b] : ToMarkdown (Block i b) where + toMarkdown block := private blockMarkdown block + +open MarkdownM in +open ToMarkdown in +private partial def partMarkdown [MarkdownInline i] [MarkdownBlock i b] (level : Nat) (part : Part i b p) : MarkdownM Unit := do + push ("".pushn '#' (level + 1)) + push " " + for i in part.title do + toMarkdown i + endBlock + for b in part.content do + toMarkdown b + endBlock + for p in part.subParts do + partMarkdown (level + 1) p + +public instance [MarkdownInline i] [MarkdownBlock i b] : ToMarkdown (Part i b p) where + toMarkdown part := private partMarkdown 0 part diff --git a/src/Lean/DocString/Parser.lean b/src/Lean/DocString/Parser.lean new file mode 100644 index 0000000000..b4f86e60fe --- /dev/null +++ b/src/Lean/DocString/Parser.lean @@ -0,0 +1,1164 @@ +/- +Copyright (c) 2023-2025 Lean FRO LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Author: David Thrane Christiansen +-/ +module +prelude +public import Lean.Parser.Types +public import Lean.DocString.Syntax +meta import Lean.DocString.Syntax + +set_option linter.missingDocs true + +namespace Lean.Doc.Parser + +open Lean Parser +open Lean.Doc.Syntax + +local instance : Coe Char ParserFn where + coe := chFn + +private partial def atLeastAux (n : Nat) (p : ParserFn) : ParserFn := fun c s => Id.run do + let iniSz := s.stackSize + let iniPos := s.pos + let mut s := p c s + if s.hasError then + return if iniPos == s.pos && n == 0 then s.restore iniSz iniPos else s + if iniPos == s.pos then + return s.mkUnexpectedError "invalid 'atLeast' parser combinator application, parser did not consume anything" + if s.stackSize > iniSz + 1 then + s := s.mkNode nullKind iniSz + atLeastAux (n - 1) p c s + +private def atLeastFn (n : Nat) (p : ParserFn) : ParserFn := fun c s => + let iniSz := s.stackSize + let s := atLeastAux n p c s + s.mkNode nullKind iniSz + +/-- +A parser that does nothing. +-/ +public def skipFn : ParserFn := fun _ s => s + +private def eatSpaces := takeWhileFn (· == ' ') + +private def repFn : Nat → ParserFn → ParserFn + | 0, _ => skipFn + | n+1, p => p >> repFn n p + +/-- Like `satisfyFn`, but no special handling of EOI -/ +partial def satisfyFn' (p : Char → Bool) + (errorMsg : String := "unexpected character") : + ParserFn := fun c s => + let i := s.pos + if h : c.atEnd i then s.mkUnexpectedError errorMsg + else if p (c.get' i h) then s.next' c i h + else s.mkUnexpectedError errorMsg + +private partial def atMostAux (n : Nat) (p : ParserFn) (msg : String) : ParserFn := + fun c s => Id.run do + let iniSz := s.stackSize + let iniPos := s.pos + if n == 0 then return notFollowedByFn p msg c s + let mut s := p c s + if s.hasError then + return if iniPos == s.pos then s.restore iniSz iniPos else s + if iniPos == s.pos then + return s.mkUnexpectedError "invalid 'atMost' parser combinator application, parser did not \ + consume anything" + if s.stackSize > iniSz + 1 then + s := s.mkNode nullKind iniSz + atMostAux (n - 1) p msg c s + +private def atMostFn (n : Nat) (p : ParserFn) (msg : String) : ParserFn := fun c s => + let iniSz := s.stackSize + let s := atMostAux n p msg c s + s.mkNode nullKind iniSz + +/-- Like `satisfyFn`, but allows any escape sequence through -/ +private partial def satisfyEscFn (p : Char → Bool) + (errorMsg : String := "unexpected character") : + ParserFn := fun c s => + let i := s.pos + if h : c.atEnd i then s.mkEOIError + else if c.get' i h == '\\' then + let s := s.next' c i h + let i := s.pos + if h : c.atEnd i then s.mkEOIError + else s.next' c i h + else if p (c.get' i h) then s.next' c i h + else s.mkUnexpectedError errorMsg + +private partial def takeUntilEscFn (p : Char → Bool) : ParserFn := fun c s => + let i := s.pos + if h : c.atEnd i then s + else if c.get' i h == '\\' then + let s := s.next' c i h + let i := s.pos + if h : c.atEnd i then s.mkEOIError + else takeUntilEscFn p c (s.next' c i h) + else if p (c.get' i h) then s + else takeUntilEscFn p c (s.next' c i h) + +private partial def takeWhileEscFn (p : Char → Bool) : ParserFn := takeUntilEscFn (not ∘ p) + +/-- +Parses as `p`, but discards the result. +-/ +public def ignoreFn (p : ParserFn) : ParserFn := fun c s => + let iniSz := s.stxStack.size + let s' := p c s + s'.shrinkStack iniSz + + +private def withInfoSyntaxFn (p : ParserFn) (infoP : SourceInfo → ParserFn) : ParserFn := fun c s => + let iniSz := s.stxStack.size + let startPos := s.pos + let s := p c s + let stopPos := s.pos + let leading := c.mkEmptySubstringAt startPos + let trailing := c.mkEmptySubstringAt stopPos + let info := SourceInfo.original leading startPos trailing stopPos + infoP info c (s.shrinkStack iniSz) + +private def unescapeStr (str : String) : String := Id.run do + let mut out := "" + let mut iter := str.iter + while !iter.atEnd do + let c := iter.curr + iter := iter.next + if c == '\\' then + if !iter.atEnd then + out := out.push iter.curr + iter := iter.next + else + out := out.push c + out + +private def asStringAux (quoted : Bool) (startPos : String.Pos) (transform : String → String) : + ParserFn := fun c s => + let stopPos := s.pos + let leading := c.mkEmptySubstringAt startPos + let val := c.extract startPos stopPos + let val := transform val + let trailing := c.mkEmptySubstringAt stopPos + let atom := + .atom (SourceInfo.original leading startPos trailing stopPos) <| + if quoted then val.quote else val + s.pushSyntax atom + +/-- Match an arbitrary Parser and return the consumed String in a `Syntax.atom`. -/ +public def asStringFn (p : ParserFn) (quoted := false) (transform : String → String := id ) : + ParserFn := fun c s => + let startPos := s.pos + let iniSz := s.stxStack.size + let s := p c s + if s.hasError then s + else asStringAux quoted startPos transform c (s.shrinkStack iniSz) + +private def checkCol0Fn (errorMsg : String) : ParserFn := fun c s => + let pos := c.fileMap.toPosition s.pos + if pos.column = 1 then s + else s.mkError errorMsg + +private def _root_.Lean.Parser.ParserContext.currentColumn + (c : ParserContext) (s : ParserState) : Nat := + c.fileMap.toPosition s.pos |>.column + +private def pushColumn : ParserFn := fun c s => + let col := c.fileMap.toPosition s.pos |>.column + s.pushSyntax <| Syntax.mkLit `column (toString col) (SourceInfo.synthetic s.pos s.pos) + +private def guardColumn (p : Nat → Bool) (message : String) : ParserFn := fun c s => + if p (c.currentColumn s) then s else s.mkErrorAt message s.pos + +private def guardMinColumn (min : Nat) : ParserFn := + guardColumn (· ≥ min) s!"expected column at least {min}" + +private def withCurrentColumn (p : Nat → ParserFn) : ParserFn := fun c s => + p (c.currentColumn s) c s + +private def bol : ParserFn := fun c s => + let position := c.fileMap.toPosition s.pos + let col := position |>.column + if col == 0 then s else s.mkErrorAt s!"beginning of line at {position}" s.pos + +private def bolThen (p : ParserFn) (description : String) : ParserFn := fun c s => + let position := c.fileMap.toPosition s.pos + let col := position |>.column + if col == 0 then + let s := p c s + if s.hasError then + s.mkErrorAt description s.pos + else s + else s.mkErrorAt description s.pos + +/-- +We can only start a nestable block if we're immediately after a newline followed by a sequence of +nestable block openers +-/ +private def onlyBlockOpeners : ParserFn := fun c s => + let position := c.fileMap.toPosition s.pos + let lineStart := c.fileMap.lineStart position.line + let ok : Bool := Id.run do + let mut iter := {c.inputString.iter with i := lineStart} + while iter.i < s.pos && iter.hasNext && iter.i < c.endPos do + if iter.curr.isDigit then + while iter.curr.isDigit && iter.i < s.pos && iter.hasNext do + iter := iter.next + if !iter.hasNext then return false + else if iter.curr == '.' || iter.curr == ')' then iter := iter.next + else if iter.curr == ' ' then iter := iter.next + else if iter.curr == '>' then iter := iter.next + else if iter.curr == '*' then iter := iter.next + else if iter.curr == '+' then iter := iter.next + else if iter.curr == '-' then iter := iter.next + else return false + true + + if ok then s + else s.mkErrorAt s!"beginning of line or sequence of nestable block openers at {position}" s.pos + +private def nl := satisfyFn (· == '\n') "newline" + +/-- +Construct a “fake” atom with the given string content and source information. + +Normally, atoms are always substrings of the original input; however, Verso's concrete syntax +is different enough from Lean's that this isn't always a good match. +-/ +public def fakeAtom (str : String) (info : SourceInfo := SourceInfo.none) : ParserFn := fun _c s => + let atom := .atom info str + s.pushSyntax atom + +private def pushMissing : ParserFn := fun _c s => + s.pushSyntax .missing + +private def strFn (str : String) : ParserFn := asStringFn <| fun c s => + let rec go (iter : String.Iterator) (s : ParserState) := + if iter.atEnd then s + else + let ch := iter.curr + go iter.next <| satisfyFn (· == ch) ch.toString c s + let iniPos := s.pos + let iniSz := s.stxStack.size + let s := go str.iter s + if s.hasError then s.mkErrorAt s!"'{str}'" iniPos (some iniSz) else s + +/-- +Ordered lists may have two styles of indicator, with trailing dots or parentheses. +-/ +public inductive OrderedListType where + /-- Items like 1. -/ + | numDot + /-- Items like 1) -/ + | parenAfter +deriving Repr, BEq, DecidableEq + +public instance : Ord OrderedListType where + compare + | .numDot, .numDot => .eq + | .numDot, .parenAfter => .lt + | .parenAfter, .numDot => .gt + | .parenAfter, .parenAfter => .eq + +private def OrderedListType.all : List OrderedListType := + [.numDot, .parenAfter] + +private theorem OrderedListType.all_complete : ∀ x : OrderedListType, x ∈ all := by + unfold all; intro x; cases x <;> repeat constructor + +/-- +Unordered lists may have three indicators: asterisks, dashes, or pluses. +-/ +public inductive UnorderedListType where + /-- Items like * -/ + | asterisk + /-- Items like - -/ + | dash + /-- Items like + -/ + | plus +deriving Repr, BEq, DecidableEq + +public instance : Ord UnorderedListType where + compare + | .asterisk, .asterisk => .eq + | .asterisk, _ => .lt + | .dash, .asterisk => .gt + | .dash, .dash => .eq + | .dash, .plus => .lt + | .plus, .plus => .eq + | .plus, _ => .gt + +private def UnorderedListType.all : List UnorderedListType := + [.asterisk, .dash, .plus] + +private theorem UnorderedListType.all_complete : ∀ x : UnorderedListType, x ∈ all := by + unfold all; intro x; cases x <;> repeat constructor + +private def unorderedListIndicator (type : UnorderedListType) : ParserFn := + asStringFn <| + match type with + | .asterisk => chFn '*' + | .dash => chFn '-' + | .plus => chFn '+' + +private def orderedListIndicator (type : OrderedListType) : ParserFn := + asStringFn <| + takeWhile1Fn (·.isDigit) "digits" >> + match type with + | .numDot => chFn '.' + | .parenAfter => chFn ')' + +private def blankLine : ParserFn := + nodeFn `blankLine <| atomicFn <| asStringFn <| takeWhileFn (· == ' ') >> nl + +private def bullet := atomicFn (go UnorderedListType.all) +where + go + | [] => fun _ s => s.mkError "no list type" + | [x] => atomicFn (unorderedListIndicator x) + | x :: xs => atomicFn (unorderedListIndicator x) <|> go xs + +private def numbering := atomicFn (go OrderedListType.all) +where + go + | [] => fun _ s => s.mkError "no list type" + | [x] => atomicFn (orderedListIndicator x) + | x :: xs => atomicFn (orderedListIndicator x) <|> go xs + +/-- +Parses a character that's allowed as part of inline text. This resolves escaped characters and +performs limited lookahead for characters that only begin a different inline as part of a sequence. +-/ +public def inlineTextChar : ParserFn := fun c s => + let i := s.pos + if h : c.atEnd i then s.mkEOIError + else + let curr := c.get' i h + match curr with + | '\\' => + let s := s.next' c i h + let i := s.pos + if h : c.atEnd i then s.mkEOIError + else s.next' c i h + | '*' | '_' | '\n' | '[' | ']' | '{' | '}' | '`' => s.mkUnexpectedErrorAt s!"'{curr}'" i + | '!' => + let s := s.next' c i h + let i' := s.pos + if h : c.atEnd i' then s + else if c.get' i' h == '[' + then s.mkUnexpectedErrorAt "![" i + else s + | '$' => + let s := s.next' c i h + let i' := s.pos + if h : c.atEnd i' then + s + else if c.get' i' h == '`' then + s.mkUnexpectedErrorAt "$`" i + else if c.get' i' h == '$' then + let s := s.next' c i' h + let i' := s.pos + if h : c.atEnd i' then + s + else if c.get' i' h == '`' then + s.mkUnexpectedErrorAt "$$`" i + else s + else s + | _ => s.next' c i h + +/-- Return some inline text up to the next inline opener or the end of +the line, whichever is first. Always consumes at least one +logical character on success, taking escaping into account. -/ +private def inlineText : ParserFn := + asStringFn (transform := unescapeStr) <| atomicFn inlineTextChar >> manyFn inlineTextChar + +/-- +Parses block opener prefixes. At the beginning of the line, if this parser succeeds, then a special +block is beginning. +-/ +public def blockOpener := atomicFn <| + takeWhileEscFn (· == ' ') >> + (atomicFn ((bullet >> chFn ' ')) <|> -- Unordered list + atomicFn ((numbering >> chFn ' ')) <|> -- Ordered list + atomicFn (strFn ": ") <|> -- Description list item + atomicFn (atLeastFn 3 (chFn ':')) <|> -- Directive + atomicFn (atLeastFn 3 (chFn '`')) <|> -- Code block + atomicFn (strFn "%%%") <|> -- Metadata + atomicFn (chFn '>')) -- Block quote + +/-- Parses an argument value, which may be a string literal, identifier, or numeric literal. -/ +public def val : ParserFn := fun c s => + if h : c.atEnd s.pos then + s.mkEOIError + else + let ch := c.get' s.pos h + let i := s.stackSize + if ch == '\"' then + let s := strLitFnAux s.pos false c (s.next' c s.pos h) + s.mkNode ``arg_str i + else if isIdFirst ch || isIdBeginEscape ch then + let s := rawIdentFn (includeWhitespace := false) c s + s.mkNode ``arg_ident i + else if ch.isDigit then + let s := numberFnAux false c s + s.mkNode ``arg_num i + else + s.mkError "expected identifier, string, or number" + +private def withCurrentStackSize (p : Nat → ParserFn) : ParserFn := fun c s => + p s.stxStack.size c s + +/-- Match the character indicated, pushing nothing to the stack in case of success -/ +private def skipChFn (c : Char) : ParserFn := + satisfyFn (· == c) c.toString + +private def skipToNewline : ParserFn := + takeUntilFn (· == '\n') + +private def skipToSpace : ParserFn := + takeUntilFn (· == ' ') + +private def skipRestOfLine : ParserFn := + skipToNewline >> (eoiFn <|> nl) + +private def skipBlock : ParserFn := + skipToNewline >> manyFn nonEmptyLine >> takeWhileFn (· == '\n') +where + nonEmptyLine : ParserFn := + atomicFn <| + chFn '\n' >> + takeWhileFn (fun c => c.isWhitespace && c != '\n') >> + satisfyFn (!·.isWhitespace) "non-whitespace" >> skipToNewline + +/-- +Recovers from a parse error by skipping input until one or more complete blank lines has been +skipped. +-/ +public def recoverBlock (p : ParserFn) (final : ParserFn := skipFn) : ParserFn := + recoverFn p fun _ => + ignoreFn skipBlock >> final + +private def recoverBlockWith (stxs : Array Syntax) (p : ParserFn) : ParserFn := + recoverFn p fun rctx => + ignoreFn skipBlock >> + show ParserFn from + fun _ s => stxs.foldl (init := s.shrinkStack rctx.initialSize) (·.pushSyntax ·) + +private def recoverLine (p : ParserFn) : ParserFn := + recoverFn p fun _ => + ignoreFn skipRestOfLine + +private def recoverWs (p : ParserFn) : ParserFn := + recoverFn p fun _ => + ignoreFn <| takeUntilFn (fun c => c == ' ' || c == '\n') + +private def recoverNonSpace (p : ParserFn) : ParserFn := + recoverFn p fun rctx => + ignoreFn (takeUntilFn (fun c => c != ' ')) >> + show ParserFn from + fun _ s => s.shrinkStack rctx.initialSize + +private def recoverWsWith (stxs : Array Syntax) (p : ParserFn) : ParserFn := + recoverFn p fun rctx => + ignoreFn <| takeUntilFn (fun c => c == ' ' || c == '\n') >> + show ParserFn from + fun _ s => stxs.foldl (init := s.shrinkStack rctx.initialSize) (·.pushSyntax ·) + +private def recoverEol (p : ParserFn) : ParserFn := + recoverFn p fun _ => ignoreFn <| skipToNewline + +private def recoverEolWith (stxs : Array Syntax) (p : ParserFn) : ParserFn := + recoverFn p fun rctx => + ignoreFn skipToNewline >> + show ParserFn from + fun _ s => stxs.foldl (init := s.shrinkStack rctx.initialSize) (·.pushSyntax ·) + +private def recoverSkip (p : ParserFn) : ParserFn := + recoverFn p fun _ => skipFn + +private def recoverSkipWith (stxs : Array Syntax) (p : ParserFn) : ParserFn := + recoverFn p fun rctx => + show ParserFn from + fun _ s => stxs.foldl (init := s.shrinkStack rctx.initialSize) (·.pushSyntax ·) + +/-- Recovers from an error by pushing the provided syntax items, without adjusting the position. -/ +def recoverHereWith (stxs : Array Syntax) (p : ParserFn) : ParserFn := + recoverFn p fun rctx => + show ParserFn from + fun _ s => stxs.foldl (init := s.restore rctx.initialSize rctx.initialPos) (·.pushSyntax ·) + +private def recoverHereWithKeeping (stxs : Array Syntax) (keep : Nat) (p : ParserFn) : ParserFn := + recoverFn p fun rctx => + show ParserFn from + fun _ s => stxs.foldl (init := s.restore (rctx.initialSize + keep) rctx.initialPos) (·.pushSyntax ·) + +/-- +Parses an argument to a role, directive, command, or code block, which may be named or positional or +a flag. +-/ +public def arg : ParserFn := + withCurrentStackSize fun iniSz => + flag <|> withParens iniSz <|> potentiallyNamed iniSz <|> (val >> mkAnon iniSz) +where + mkNamed (iniSz : Nat) : ParserFn := fun _ s => s.mkNode ``Syntax.named iniSz + mkNamedNoParen (iniSz : Nat) : ParserFn := fun _ s => s.mkNode ``Syntax.named_no_paren iniSz + mkAnon (iniSz : Nat) : ParserFn := fun _ s => s.mkNode ``Syntax.anon iniSz + mkIdent (iniSz : Nat) : ParserFn := fun _ s => s.mkNode ``Syntax.arg_ident iniSz + flag : ParserFn := + nodeFn ``Doc.Syntax.flag_on + (asStringFn (strFn "+") >> recoverNonSpace noSpace >> + recoverWs (rawIdentFn (includeWhitespace := false))) <|> + nodeFn ``Doc.Syntax.flag_off + (asStringFn (strFn "-") >> recoverNonSpace noSpace >> + recoverWs (rawIdentFn (includeWhitespace := false))) + noSpace : ParserFn := fun c s => + if h : c.atEnd s.pos then s + else + let ch := c.get' s.pos h + if ch == ' ' then + s.mkError "no space before" + else s + potentiallyNamed iniSz := + atomicFn (rawIdentFn (includeWhitespace := false)) >> eatSpaces >> + ((atomicFn (asStringFn <| strFn ":=") >> eatSpaces >> val >> eatSpaces >> mkNamedNoParen iniSz) <|> (mkIdent iniSz >> mkAnon iniSz)) + withParens iniSz := + atomicFn (asStringFn <| strFn "(") >> eatSpaces >> + recoverWs (rawIdentFn (includeWhitespace := false)) >> eatSpaces >> + recoverWs (asStringFn <| strFn ":=") >> eatSpaces >> + recoverWs val >> eatSpaces >> + recoverEol (asStringFn <| strFn ")") >> eatSpaces >> + mkNamed iniSz + +/-- + +Skip whitespace for name and arguments. If the argument is `none`, +it's in a single-line context and whitespace may only be the space +character. If it's `some N`, then newlines are allowed, but `N` is the +minimum indentation column. +-/ +private def nameArgWhitespace : (multiline : Option Nat) → ParserFn + | none => eatSpaces + | some n => takeWhileFn (fun c => c == ' ' || c == '\n') >> guardMinColumn n + +/-- Parses zero or more arguments to a role, directive, command, or code block. -/ +public def args (multiline : Option Nat := none) : ParserFn := + sepByFn true arg (nameArgWhitespace multiline) + +/-- Parses a name and zero or more arguments to a role, directive, command, or code block. -/ +public def nameAndArgs (multiline : Option Nat := none) : ParserFn := + nameArgWhitespace multiline >> rawIdentFn (includeWhitespace := false) >> + nameArgWhitespace multiline >> args (multiline := multiline) + +/-- +The context within which a newline element is parsed. +-/ +public structure InlineCtxt where + /-- Are newlines allowed here? -/ + allowNewlines := true + /-- + The minimum indentation of a continuation line for the current paragraph + -/ + minIndent : Nat := 0 + /-- + How many asterisks introduced the current level of boldness? `none` means no bold here. + -/ + boldDepth : Option Nat := none + /-- + How many underscores introduced the current level of emphasis? `none` means no emphasis here. + -/ + emphDepth : Option Nat := none + /-- Are we in a link? -/ + inLink : Bool := false +deriving Inhabited + +/- Parsing inlines: + * Inline parsers may not consume trailing whitespace, and must be robust in the face of leading whitespace +-/ + +/-- +A linebreak that isn't a block break (that is, there's non-space content on the next line) +-/ +def linebreak (ctxt : InlineCtxt) : ParserFn := + if ctxt.allowNewlines then + nodeFn ``linebreak <| + andthenFn (withInfoSyntaxFn skip.fn (fun info => fakeAtom "line!" info)) <| + nodeFn strLitKind <| + asStringFn (quoted := true) <| + atomicFn (chFn '\n' >> lookaheadFn (manyFn (chFn ' ') >> notFollowedByFn (chFn '\n' <|> blockOpener) "newline")) + else + errorFn "Newlines not allowed here" + +private partial def notInLink (ctxt : InlineCtxt) : ParserFn := fun _ s => + if ctxt.inLink then s.mkError "Already in a link" else s + +mutual + private partial def emphLike + (name : SyntaxNodeKind) (char : Char) (what plural : String) + (getter : InlineCtxt → Option Nat) (setter : InlineCtxt → Option Nat → InlineCtxt) + (ctxt : InlineCtxt) : ParserFn := + nodeFn name <| + withCurrentColumn fun c => + atomicFn (asStringFn (asStringFn (opener ctxt) >> notFollowedByFn (chFn ' ' false <|> chFn '\n' false) "space or newline after opener")) >> + (recoverSkip <| + withCurrentColumn fun c' => + let count := c' - c + manyFn (inline (setter ctxt (some count))) >> + asStringFn (atomicFn (noSpaceBefore >> repFn count (satisfyFn (· == char) s!"'{tok count}'")))) + + where + tok (count : Nat) : String := ⟨List.replicate count char⟩ + opener (ctxt : InlineCtxt) : ParserFn := + match getter ctxt with + | none => many1Fn (satisfyFn (· == char) s!"any number of {char}s") + | some 1 | some 0 => fun _ s => s.mkError s!"Can't {what} here" + | some d => atMostFn (d - 1) (satisfyFn (· == char) s!"{char}") s!"at most {d} {plural}" + noSpaceBefore : ParserFn := fun c s => + if s.pos == 0 then s + else + let prior := c.get (c.prev s.pos) + if prior.isWhitespace then + s.mkError s!"'{char}' without preceding space" + else s + + /-- + Parses emphasis: a matched pair of one or more `_`. + -/ + public partial def emph := + emphLike ``emph '_' "emphasize" "underscores" (·.emphDepth) ({· with emphDepth := ·}) + + /-- + Parses bold: a matched pair of one or more `*`. + -/ + public partial def bold := + emphLike ``bold '*' "bold" "asterisks" (·.boldDepth) ({· with boldDepth := ·}) + + /-- + Parses inline code. + -/ + public partial def code : ParserFn := + nodeFn ``code <| + withCurrentColumn fun c => + atomicFn opener >> + ( atomicFn <| + withCurrentColumn fun c' => + let count := c' - c + recoverCode <| + nodeFn strLitKind + (asStringFn (many1Fn <| codeContentsFn (count - 1)) (quoted := true) >> + normFn) >> + closer count) + where + opener : ParserFn := asStringFn (many1Fn (satisfyFn (· == '`') s!"any number of backticks")) + closer (count : Nat) : ParserFn := + asStringFn (atomicFn (repFn count (satisfyFn' (· == '`') s!"expected '{String.mk (.replicate count '`')}' to close inline code"))) >> + notFollowedByFn (satisfyFn (· == '`') "`") "backtick" + takeBackticksFn : Nat → ParserFn + | 0 => satisfyFn (fun _ => false) + | n+1 => optionalFn (chFn '`' >> takeBackticksFn n) + recoverCode (p : ParserFn) : ParserFn := + recoverFn p fun rctx => + (show ParserFn from fun _ s => s.restore rctx.initialSize rctx.initialPos) >> + atomicFn (nodeFn strLitKind (asStringFn (takeWhileFn (· ≠ '\n')) true) >> ignoreFn (chFn '\n' <|> eoiFn) >> pushMissing) + codeContentsFn (maxCount : Nat) : ParserFn := + atomicFn (asStringFn (satisfyFn (maxCount > 0 && · == '`') >> atMostFn (maxCount - 1) (chFn '`') s!"at most {maxCount} backticks")) <|> + satisfyFn (· != '`') "expected character other than backtick ('`')" + normFn : ParserFn := fun _c s => Id.run <| do + let str := s.stxStack.back + if let .atom info str := str then + if str.startsWith "\" " && str.endsWith " \"" then + let core := str.drop 2 |>.dropRight 2 + if core.any (· != ' ') then + let str := "\"" ++ core ++ "\"" + let info : SourceInfo := + match info with + | .none => .none + | .synthetic start stop c => .synthetic (start + ⟨1⟩) (stop - ⟨1⟩) c + | .original leading start trailing stop => + .original + {leading with stopPos := leading.stopPos + ⟨1⟩} (start + ⟨1⟩) + {trailing with startPos := trailing.startPos - ⟨1⟩} (stop - ⟨1⟩) + return s.popSyntax.pushSyntax (.atom info str) + return s + + takeContentsFn (maxCount : Nat) : ParserFn := fun c s => + let i := s.pos + if h : c.atEnd i then s.mkEOIError + else + let ch := c.get' i h + let s := s.next' c i h + let i := s.pos + if ch == '\\' then + if h : c.atEnd i then s.mkEOIError + else + let ch := c.get' i h + let s := s.next' c i h + if ch ∈ ['`', '\\'] then takeContentsFn maxCount c s + else + s.mkError "expected 'n', '\\', or '`'" + else if ch == '`' then + optionalFn (atomicFn (takeBackticksFn maxCount) >> takeContentsFn maxCount) c s + else if ch == '\n' then + s.mkError "unexpected newline" + else takeContentsFn maxCount c s + + /-- + Parses mathematics. + -/ + public partial def math : ParserFn := + atomicFn (nodeFn ``display_math <| strFn "$$" >> code) <|> + atomicFn (nodeFn ``inline_math <| strFn "$" >> code) + + /-- Reads a prefix of a line of text, stopping at a text-mode special character. -/ + public partial def text := + nodeFn ``text <| + nodeFn strLitKind <| + asStringFn (transform := unescapeStr) (quoted := true) <| + many1Fn inlineTextChar + + /-- Parses a link. -/ + public partial def link (ctxt : InlineCtxt) := + nodeFn ``link <| + (atomicFn (notInLink ctxt >> strFn "[" >> notFollowedByFn (chFn '^') "'^'" )) >> + (recoverEol <| + many1Fn (inline {ctxt with inLink := true}) >> + strFn "]" >> linkTarget) + + /-- Parses a footnote. -/ + public partial def footnote (ctxt : InlineCtxt) := + nodeFn ``footnote <| + (atomicFn (notInLink ctxt >> strFn "[^" )) >> + (recoverLine <| + nodeFn `str (asStringFn (quoted := true) (many1Fn (satisfyEscFn (fun c => c != ']' && c != '\n') "other than ']' or newline"))) >> + strFn "]") + + private partial def linkTarget := ref <|> url + where + notUrlEnd := satisfyEscFn (· ∉ ")\n".toList) "not ')' or newline" >> takeUntilEscFn (· ∈ ")\n".toList) + notRefEnd := satisfyEscFn (· ∉ "]\n".toList) "not ']' or newline" >> takeUntilEscFn (· ∈ "]\n".toList) + ref : ParserFn := + nodeFn ``Syntax.ref <| + (atomicFn <| strFn "[") >> + recoverEol (nodeFn strLitKind (asStringFn notRefEnd (quoted := true)) >> strFn "]") + url : ParserFn := + nodeFn ``Syntax.url <| + (atomicFn <| strFn "(") >> + recoverEol (nodeFn strLitKind (asStringFn notUrlEnd (quoted := true)) >> strFn ")") + + /-- Parses an image. -/ + public partial def image : ParserFn := + nodeFn ``image <| + atomicFn (strFn "![") >> + (recoverSkip <| + nodeFn strLitKind (asStringFn (takeUntilEscFn (· ∈ "]\n".toList)) (quoted := true)) >> + strFn "]" >> + linkTarget) + + /-- Parses a role. -/ + public partial def role (ctxt : InlineCtxt) : ParserFn := + nodeFn ``role <| + intro >> (bracketed <|> atomicFn nonBracketed) + where + intro := atomicFn (chFn '{') >> recoverBlock (eatSpaces >> nameAndArgs >> eatSpaces >> chFn '}') + bracketed := atomicFn (chFn '[') >> recoverBlock (manyFn (inline ctxt) >> chFn ']') + fakeOpen := .atom SourceInfo.none "[" + fakeClose := .atom SourceInfo.none "]" + nonBracketed : ParserFn := fun c s => + let s := s.pushSyntax fakeOpen + let s := nodeFn nullKind (delimitedInline ctxt) c s + s.pushSyntax fakeClose + + /-- + Parses an inline that is self-delimiting (that is, with well-defined start and stop characters). + -/ + public partial def delimitedInline (ctxt : InlineCtxt) : ParserFn := + emph ctxt <|> bold ctxt <|> code <|> math <|> role ctxt <|> image <|> + link ctxt <|> footnote ctxt + + /-- + Parses any inline element. + -/ + public partial def inline (ctxt : InlineCtxt) : ParserFn := + text <|> linebreak ctxt <|> delimitedInline ctxt +end + +/-- +Parses a line of text (that is, one or more inline elements). +-/ +def textLine (allowNewlines := true) : ParserFn := many1Fn (inline { allowNewlines }) + +open Lean.Parser.Term in +/-- +Parses a metadata block, which contains the contents of a Lean structure initialization but is +surrounded by `%%%` on each side. +-/ +public meta def metadataBlock : ParserFn := + nodeFn ``metadata_block <| + opener >> + metadataContents.fn >> + takeWhileFn (·.isWhitespace) >> + closer +where + opener := atomicFn (bolThen (eatSpaces >> strFn "%%%") "%%% (at line beginning)") >> eatSpaces >> ignoreFn (chFn '\n') + closer := bolThen (eatSpaces >> strFn "%%%") "%%% (at line beginning)" >> eatSpaces >> ignoreFn (chFn '\n' <|> eoiFn) + +/-- +Records that the parser is presently parsing a list. +-/ +public structure InList where + /-- The indentation of list indicators. -/ + indentation : Nat + /-- The specific list type and its indicator style -/ + type : OrderedListType ⊕ UnorderedListType +deriving Repr + +/-- +The context within which a block should be valid. +-/ +public structure BlockCtxt where + /-- + The block's minimum indentation. + -/ + minIndent : Nat := 0 + /-- + The block's maximal directive size (that is, the greatest number of allowed colons). + -/ + maxDirective : Option Nat := none + /-- + The nested list context, innermost first. + -/ + inLists : List InList := [] +deriving Inhabited, Repr + +/-- +Succeeds when the parser is looking at an ordered list indicator. +-/ +public def lookaheadOrderedListIndicator (ctxt : BlockCtxt) (p : OrderedListType → Int → ParserFn) : + ParserFn := fun c s => + let iniPos := s.pos + let iniSz := s.stxStack.size + let s := (onlyBlockOpeners >> takeWhileFn (· == ' ') >> guardMinColumn ctxt.minIndent) c s + if s.hasError then s.setPos iniPos |>.shrinkStack iniSz + else + let numPos := s.pos + let s := ignoreFn (takeWhile1Fn (·.isDigit) "digits") c s + if s.hasError then {s with pos := iniPos}.shrinkStack iniSz else + let digits := c.extract numPos s.pos + match digits.toNat? with + | none => {s.mkError s!"digits, got '{digits}'" with pos := iniPos} + | some n => + let i := s.pos + if h : c.atEnd i then {s.mkEOIError with pos := iniPos} + else + let (s, next, type) := match c.get' i h with + | '.' => (s.next' c i h, (chFn ' ' <|> chFn '\n'), OrderedListType.numDot) + | ')' => (s.next' c i h, (chFn ' ' <|> chFn '\n'), OrderedListType.parenAfter) + | other => + (s.setError { unexpected := s!"unexpected '{other}'", expected := ["'.'", "')'"] }, + skipFn, + .numDot) + if s.hasError then {s with pos := iniPos} + else + let s := next c s + if s.hasError then {s with pos := iniPos} + else + let leading := c.mkEmptySubstringAt numPos + let trailing := c.mkEmptySubstringAt i + let num := Syntax.mkNumLit digits (info := .original leading numPos trailing i) + p type n c (s.shrinkStack iniSz |>.setPos numPos |>.pushSyntax num) + +/-- +Succeeds when the parser is looking at an unordered list indicator. +-/ +public def lookaheadUnorderedListIndicator (ctxt : BlockCtxt) (p : UnorderedListType → ParserFn) : + ParserFn := fun c s => + let iniPos := s.pos + let iniSz := s.stxStack.size + let s := (onlyBlockOpeners >> takeWhileFn (· == ' ') >> guardMinColumn ctxt.minIndent) c s + let bulletPos := s.pos + if s.hasError then s.setPos iniPos |>.shrinkStack iniSz + else if h : c.atEnd s.pos then s.mkEOIError.setPos iniPos |>.shrinkStack iniSz + else let (s, type) : (_ × UnorderedListType) := match c.get' s.pos h with + | '*' => (s.next' c s.pos h, .asterisk) + | '-' => (s.next' c s.pos h, .dash) + | '+' => (s.next' c s.pos h, .plus) + | other => (s.setError {expected := ["*", "-", "+"], unexpected := s!"'{other}'"}, .plus) + if s.hasError then s.setPos iniPos + else + let s := (chFn ' ' <|> chFn '\n') c s + if s.hasError then s.setPos iniPos + else p type c (s.shrinkStack iniSz |>.setPos bulletPos) + +private def skipUntilDedent (indent : Nat) : ParserFn := + skipRestOfLine >> + manyFn (chFn ' ' >> takeWhileFn (· == ' ') >> guardColumn (· ≥ indent) s!"indentation at {indent}" >> skipRestOfLine) + +private def recoverUnindent (indent : Nat) (p : ParserFn) (finish : ParserFn := skipFn) : + ParserFn := + recoverFn p (fun _ => ignoreFn (skipUntilDedent indent) >> finish) + +mutual + /-- Parses a list item according to the current nesting context. -/ + public partial def listItem (ctxt : BlockCtxt) : ParserFn := + nodeFn ``li <| + bulletFn >> + withCurrentColumn fun c => + ignoreFn (manyFn (chFn ' ' <|> chFn '\n')) >> blocks1 {ctxt with minIndent := c} + where + bulletFn := + match ctxt.inLists.head? with + | none => fun _ s => s.mkError "not in a list" + | some ⟨col, .inr type⟩ => + atomicFn <| + takeWhileFn (· == ' ') >> + guardColumn (· == col) s!"indentation at {col}" >> + unorderedListIndicator type >> ignoreFn (lookaheadFn (chFn ' ' <|> chFn '\n')) + | some ⟨col, .inl type⟩ => + atomicFn <| + takeWhileFn (· == ' ') >> + guardColumn (· == col) s!"indentation at {col}" >> + orderedListIndicator type >> ignoreFn (lookaheadFn (chFn ' ' <|> chFn '\n')) + + /-- Parses an item from a description list. -/ + public partial def descItem (ctxt : BlockCtxt) : ParserFn := + nodeFn ``desc <| + colonFn >> + withCurrentColumn fun c => textLine >> ignoreFn (manyFn blankLine) >> + fakeAtom "=>" >> + takeWhileFn (· == ' ') >> + recoverSkip (guardColumn (· ≥ c) s!"indentation at least {c}" >> + blocks1 { ctxt with minIndent := c}) >> + ignoreFn (manyFn blankLine) + where + colonFn := atomicFn <| + takeWhileFn (· == ' ') >> + guardColumn (· == ctxt.minIndent) s!"indentation at {ctxt.minIndent}" >> + asStringFn (chFn ':' false) >> ignoreFn (lookaheadFn (chFn ' ')) + + /-- Parses a block quote. -/ + public partial def blockquote (ctxt : BlockCtxt) : ParserFn := + atomicFn <| nodeFn ``blockquote <| + takeWhileFn (· == ' ') >> guardMinColumn ctxt.minIndent >> chFn '>' >> + withCurrentColumn fun c => blocks { ctxt with minIndent := c } + + /-- Parses an unordered list. -/ + public partial def unorderedList (ctxt : BlockCtxt) : ParserFn := + nodeFn ``ul <| + lookaheadUnorderedListIndicator ctxt fun type => + withCurrentColumn fun c => + fakeAtom "ul{" >> + many1Fn (listItem {ctxt with minIndent := c + 1 , inLists := ⟨c, .inr type⟩ :: ctxt.inLists}) >> + fakeAtom "}" + + /-- Parses an ordered list. -/ + public partial def orderedList (ctxt : BlockCtxt) : ParserFn := + nodeFn ``ol <| + fakeAtom "ol(" >> + lookaheadOrderedListIndicator ctxt fun type _start => -- TODO? Validate list numbering? + withCurrentColumn fun c => + fakeAtom ")" >> fakeAtom "{" >> + many1Fn (listItem {ctxt with minIndent := c + 1 , inLists := ⟨c, .inl type⟩ :: ctxt.inLists}) >> + fakeAtom "}" + + /-- Parses a definition list. -/ + public partial def definitionList (ctxt : BlockCtxt) : ParserFn := + nodeFn ``dl <| + atomicFn (onlyBlockOpeners >> takeWhileFn (· == ' ') >> ignoreFn (lookaheadFn (chFn ':' >> chFn ' ')) >> guardMinColumn ctxt.minIndent) >> + withInfoSyntaxFn skip.fn (fun info => fakeAtom "dl{" info) >> + withCurrentColumn (fun c => many1Fn (descItem {ctxt with minIndent := c})) >> + withInfoSyntaxFn skip.fn (fun info => fakeAtom "}" info) + + /-- Parses a paragraph (that is, a sequence of otherwise-undecorated inlines). -/ + public partial def para (ctxt : BlockCtxt) : ParserFn := + nodeFn ``para <| + atomicFn (takeWhileFn (· == ' ') >> notFollowedByFn blockOpener "block opener" >> guardMinColumn ctxt.minIndent) >> + withInfoSyntaxFn skip.fn (fun info => fakeAtom "para{" (info := info)) >> + textLine >> + withInfoSyntaxFn skip.fn (fun info => fakeAtom "}" (info := info)) + + /-- Parses a header. -/ + public partial def header (ctxt : BlockCtxt) : ParserFn := + nodeFn ``header <| + guardMinColumn ctxt.minIndent >> + atomicFn (bol >> + withCurrentColumn fun c => + withInfoSyntaxFn (many1Fn (skipChFn '#')) (fun info => fakeAtom "header(" (info := info)) >> + withCurrentColumn fun c' => + skipChFn ' ' >> takeWhileFn (· == ' ') >> lookaheadFn (satisfyFn (· != '\n') "non-newline") >> + (show ParserFn from fun _ s => s.pushSyntax <| Syntax.mkNumLit (toString <| c' - c - 1)) >> + fakeAtom ")") >> + fakeAtom "{" >> + textLine (allowNewlines := false) >> + fakeAtom "}" + + /-- + Parses a code block. The resulting string literal has already had the fences' leading indentation + stripped. + -/ + public partial def codeBlock (ctxt : BlockCtxt) : ParserFn := + nodeFn ``codeblock <| + -- Opener - leaves indent info and open token on the stack + atomicFn (takeWhileFn (· == ' ') >> guardMinColumn ctxt.minIndent >> pushColumn >> asStringFn (atLeastFn 3 (skipChFn '`'))) >> + withIndentColumn fun c => + recoverUnindent c <| + withCurrentColumn fun c' => + let fenceWidth := c' - c + takeWhileFn (· == ' ') >> + optionalFn nameAndArgs >> + asStringFn (satisfyFn (· == '\n') "newline") >> + nodeFn strLitKind (asStringFn (manyFn (atomicFn blankLine <|> codeFrom c fenceWidth)) (transform := deIndent c) (quoted := true)) >> + closeFence c fenceWidth + where + withIndentColumn (p : Nat → ParserFn) : ParserFn := fun c s => + let colStx := s.stxStack.get! (s.stxStack.size - 2) + match colStx with + | .node _ `column #[.atom _ col] => + if let some colNat := col.toNat? then + let opener := s.stxStack.get! (s.stxStack.size - 1) + p colNat c (s.popSyntax.popSyntax.pushSyntax opener) + else + s.mkError s!"Internal error - not a Nat {col}" + | other => s.mkError s!"Internal error - not a column node {other}" + + deIndent (n : Nat) (str : String) : String := Id.run do + let str := if str != "" && str.back == '\n' then str.dropRight 1 else str + let mut out := "" + for line in str.splitOn "\n" do + out := out ++ line.drop n ++ "\n" + out + + codeFrom (col width : Nat) := + atomicFn (bol >> takeWhileFn (· == ' ') >> guardMinColumn col >> + notFollowedByFn (atLeastFn width (skipChFn '`')) "ending fence") >> + manyFn (satisfyFn (· != '\n') "non-newline") >> satisfyFn (· == '\n') "newline" + + closeFence (col width : Nat) := + bol >> takeWhileFn (· == ' ') >> guardColumn (· == col) s!"column {col}" >> + atomicFn (asStringFn (repFn width (skipChFn '`'))) >> + notFollowedByFn (skipChFn '`') "extra `" >> + takeWhileFn (· == ' ') >> (satisfyFn (· == '\n') "newline" <|> eoiFn) + + /-- Parses a directive. -/ + public partial def directive (ctxt : BlockCtxt) : ParserFn := + nodeFn ``directive <| + -- Opener - leaves indent info and open token on the stack + atomicFn + (eatSpaces >> guardMinColumn ctxt.minIndent >> + asStringFn (atLeastFn 3 (skipChFn ':')) >> + guardOpenerSize >> + eatSpaces >> + recoverEolWith #[.missing, .node .none nullKind #[]] (nameAndArgs >> satisfyFn (· == '\n') "newline")) >> + fakeAtom "\n" >> + ignoreFn (manyFn blankLine) >> + (withFencePos 3 fun ⟨l, col⟩ => + withFenceSize 3 fun fenceWidth => + blocks {ctxt with minIndent := col, maxDirective := fenceWidth} >> + recoverHereWith #[.missing] + (closeFence l fenceWidth >> + withFence 0 fun info _ c s => + if (c.fileMap.toPosition info.getPos?.get!).column != col then + s.mkErrorAt s!"closing '{String.mk <| List.replicate fenceWidth ':'}' from directive on line {l} at column {col}, but it's at column {(c.fileMap.toPosition info.getPos?.get!).column}" info.getPos?.get! + else + s)) + + where + withFence (atDepth : Nat) (p : SourceInfo → String → ParserFn) : ParserFn := fun c s => + match s.stxStack.get! (s.stxStack.size - (atDepth + 1)) with + | .atom info str => + if str.all (· == ':') then + p info str c s + else + s.mkError s!"Internal error - index {atDepth} wasn't the directive fence - it was the atom {str}" + | .missing => s.pushSyntax .missing + | stx => + s.mkError s!"Internal error - index {atDepth} wasn't the directive fence - it was {stx} in {s.stxStack.back}, {s.stxStack.pop.back}, {s.stxStack.pop.pop.back}, {s.stxStack.pop.pop.pop.back}" + + withFenceSize (atDepth : Nat) (p : Nat → ParserFn) : ParserFn := + withFence atDepth fun _ str => p str.length + + withFencePos (atDepth : Nat) (p : Position → ParserFn) : ParserFn := + withFence atDepth fun info _ c s => p (c.fileMap.toPosition info.getPos?.get!) c s + + withIndentColumn (atDepth : Nat) (p : Nat → ParserFn) : ParserFn := + withFence atDepth fun info _ c s => + let col := c.fileMap.toPosition info.getPos?.get! |>.column + p col c s + + guardOpenerSize : ParserFn := withFenceSize 0 fun x => + if let some m := ctxt.maxDirective then + if x < m then skipFn else fun _ s => s.mkError "Too many ':'s here" + else skipFn + + closeFence (line width : Nat) := + let str := String.mk (.replicate width ':') + bolThen (description := s!"closing '{str}' for directive from line {line}") + (eatSpaces >> + asStringFn (strFn str) >> notFollowedByFn (chFn ':') "':'" >> + eatSpaces >> + (ignoreFn <| atomicFn (satisfyFn (· == '\n') "newline") <|> eoiFn)) + + /-- + Parses a block command. + -/ + -- This low-level definition is to get exactly the right amount of lookahead + -- together with column tracking + public partial def block_command (ctxt : BlockCtxt) : ParserFn := fun c s => + let iniPos := s.pos + let iniSz := s.stxStack.size + let restorePosOnErr : ParserState → ParserState + | ⟨stack, lhsPrec, _, cache, some msg, errs⟩ => ⟨stack, lhsPrec, iniPos, cache, some msg, errs⟩ + | other => other + let s := eatSpaces c s + if s.hasError then restorePosOnErr s + else + let s := (intro >> eatSpaces >> ignoreFn (satisfyFn (· == '\n') "newline" <|> eoiFn)) c s + if s.hasError then restorePosOnErr s + else + s.mkNode ``Syntax.command iniSz + where + eatSpaces := takeWhileFn (· == ' ') + intro := guardMinColumn (ctxt.minIndent) >> atomicFn (chFn '{') >> nameAndArgs >> nameArgWhitespace none >> chFn '}' + + /-- + Parses a link reference target. + -/ + public partial def linkRef (c : BlockCtxt) : ParserFn := + nodeFn ``link_ref <| + atomicFn (ignoreFn (bol >> eatSpaces >> guardMinColumn c.minIndent) >> chFn '[' >> nodeFn strLitKind (asStringFn (quoted := true) (nameStart >> manyFn (satisfyEscFn (· != ']') "not ']'"))) >> strFn "]:") >> + eatSpaces >> + nodeFn strLitKind (asStringFn (quoted := true) (takeWhileFn (· != '\n'))) >> + ignoreFn (satisfyFn (· == '\n') "newline" <|> eoiFn) + where nameStart := satisfyEscFn (fun c => c != ']' && c != '^') "not ']' or '^'" + + /-- + Parses a footnote reference target. + -/ + public partial def footnoteRef (c : BlockCtxt) : ParserFn := + nodeFn ``footnote_ref <| + atomicFn (ignoreFn (bol >> eatSpaces >> guardMinColumn c.minIndent) >> strFn "[^" >> nodeFn strLitKind (asStringFn (quoted := true) (many1Fn (satisfyEscFn (· != ']') "not ']'"))) >> strFn "]:") >> + eatSpaces >> + notFollowedByFn blockOpener "block opener" >> guardMinColumn c.minIndent >> textLine + + /-- + Parses a block. + -/ + public partial def block (c : BlockCtxt) : ParserFn := + block_command c <|> unorderedList c <|> orderedList c <|> definitionList c <|> header c <|> codeBlock c <|> directive c <|> blockquote c <|> linkRef c <|> footnoteRef c <|> para c <|> metadataBlock + + /-- + Parses zero or more blocks. + -/ + public partial def blocks (c : BlockCtxt) : ParserFn := sepByFn true (block c) (ignoreFn (manyFn blankLine)) + + /-- + Parses one or more blocks. + -/ + public partial def blocks1 (c : BlockCtxt) : ParserFn := sepBy1Fn true (block c) (ignoreFn (manyFn blankLine)) + + /-- + Parses some number of blank lines followed by zero or more blocks. + -/ + public partial def document (blockContext : BlockCtxt := {}) : ParserFn := ignoreFn (manyFn blankLine) >> blocks blockContext +end diff --git a/src/Lean/DocString/Syntax.lean b/src/Lean/DocString/Syntax.lean new file mode 100644 index 0000000000..8037270d2c --- /dev/null +++ b/src/Lean/DocString/Syntax.lean @@ -0,0 +1,172 @@ +/- +Copyright (c) 2023-2025 Lean FRO LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Author: David Thrane Christiansen +-/ + +module + +prelude + +import Init.Prelude +import Init.Notation +public import Lean.Parser.Types +import Lean.Syntax +import Lean.Parser.Extra +public import Lean.Parser.Term +meta import Lean.Parser.Term + + +/-! + +This module contains an internal syntax that's used to represent documents. + +Ordinarily, a syntax declaration is used to extend the Lean parser. The parser produces `Syntax`, +which is flexible enough to represent essentially anything. However, each syntax declaration will +produce parsed syntax trees with a predictable form, and these syntax trees can be matched using +quasiquotation patterns. In other words, syntax declarations really do all of the following: + + * They extend Lean's parser + * They establish expectations for valid subsets of `Syntax` + * They provide a way to pattern-match against the valid `Syntax` that they induce + +The syntax declarations in this module are used somewhat differently. They're not generally intended +for direct use with the Lean parser, because the concrete syntax of Verso documents falls outside +what can be implemented with Lean's parsing framework. Thus, Verso has a separate parser, written +using the lower-level parts of Lean's parser. These syntax declarations are, however, a +specification for the syntax trees produced by said parser. The Verso parser is in the module +`Lean.DocString.Parser`. Specifying the Verso document syntax as is done here also allows +quasiquotation patterns that match against the output of the Verso parser. + +Importantly, Lean quasiquotation patterns do not match the string contents of atoms. This means that +the Verso parser may produce a node of kind `` `Lean.Doc.Syntax.li `` in which the first atom is +`"1."` rather than `"*'` when parsing an ordered list. + +Parsed Verso documents are transformed into Lean syntax that represents Verso document ASTs (see +module `Lean.DocString.Types`). This process potentially invokes user-written metaprograms - while +Verso's concrete syntax is not extensible, roles, directives and code blocks all contain explicit +hooks for extensibility. This translation step is defined in the module `Lean.DocString.Elab`. + +-/ + +open Lean.Parser (rawIdent) + +namespace Lean.Doc.Syntax + +public section + +/-- Argument values -/ +declare_syntax_cat arg_val +scoped syntax (name:=arg_str) str : arg_val +scoped syntax (name:=arg_ident) ident : arg_val +scoped syntax (name:=arg_num) num : arg_val + +/-- Arguments -/ +declare_syntax_cat doc_arg +/-- Anonymous positional arguments -/ +scoped syntax (name:=anon) arg_val : doc_arg +/-- Named arguments -/ +scoped syntax (name:=named) "(" ident " := " arg_val ")": doc_arg +/-- Named arguments, without parentheses. -/ +scoped syntax (name:=named_no_paren) ident " := " arg_val : doc_arg +/-- Boolean flags, turned on -/ +scoped syntax (name:=flag_on) "+" ident : doc_arg +/-- Boolean flags, turned off -/ +scoped syntax (name:=flag_off) "-" ident : doc_arg + +/-- Link targets, which may be URLs or named references -/ +declare_syntax_cat link_target +/-- A reference to a URL -/ +scoped syntax (name:=url) "(" str ")" : link_target +/-- A named reference -/ +scoped syntax (name:=ref) "[" str "]" : link_target + +/-- +Verso inline objects. These are part of the ordinary text flow of a paragraph. + +This syntax uses the following conventions: + * Sequences of inline items are in square brackets + * Literal data, like strings or numbers, are in parentheses + * Verso metaprogram names and arguments are in curly braces +-/ +declare_syntax_cat inline +scoped syntax (name:=text) str : inline +/-- Emphasis (often rendered as italics) -/ +scoped syntax (name:=emph) "_[" inline* "]" : inline +/-- Bold emphasis -/ +scoped syntax (name:=bold) "*[" inline* "]" : inline +/-- Link -/ +scoped syntax (name:=link) "link[" inline* "]" link_target : inline +/-- Image -/ +scoped syntax (name:=image) "image(" str ")" link_target : inline +/-- A footnote use -/ +scoped syntax (name:=footnote) "footnote(" str ")" : inline +/-- Line break -/ +scoped syntax (name:=linebreak) "line!" str : inline +/-- Literal code. If the first and last characters are space, and it contains at least one non-space + character, then the resulting string has a single space stripped from each end.-/ +scoped syntax (name:=code) "code(" str ")" : inline +/-- A _role_: an extension to the Verso document language in an inline position -/ +scoped syntax (name:=role) "role{" ident doc_arg* "}" "[" inline* "]" : inline +/-- Inline mathematical notation (equivalent to LaTeX's `$` notation) -/ +scoped syntax (name:=inline_math) "\\math" code : inline +/-- Display-mode mathematical notation -/ +scoped syntax (name:=display_math) "\\displaymath" code : inline + +/-- +Block-level elements, such as paragraphs, headers, and lists. + +Conventions: + * When there's concrete syntax that can be written as Lean atoms, do so (code blocks are ` ``` `, + directives `:::`) + * When Verso's syntax requires a newline, use `|` because `"\n"` is not a valid Lean token + * Directive bodies are in `{` and `}` to avoid quotation parsing issues with `:::` ... `:::` + * If there's no concrete syntax per se, such as for paragraphs or lists, use a name with brackets + and braces + * Use parentheses around required literals, such as the starting number of an ordered list + * Use square brackets around sequences of literals + * Use curly braces around blocks or lists items (because names and arguments a la roles are always + newline-separated for directives and code) +-/ +declare_syntax_cat block + +/-- Items from both ordered and unordered lists -/ +declare_syntax_cat list_item +/-- List item -/ +syntax (name:=li) "*" block* : list_item + +/-- A description of an item -/ +declare_syntax_cat desc_item +/-- A description of an item -/ +scoped syntax (name:=desc) ":" inline* "=>" block* : desc_item + +scoped syntax (name:=para) "para[" inline+ "]" : block +/-- Unordered List -/ +scoped syntax (name:=ul) "ul{" list_item* "}" : block +/-- Definition list -/ +scoped syntax (name:=dl) "dl{" desc_item* "}" : block +/-- Ordered list -/ +scoped syntax (name:=ol) "ol(" num ")" "{" list_item* "}" : block +/-- Literal code -/ +scoped syntax (name:=codeblock) "```" (ident doc_arg*)? "|" str "```" : block +/-- Quotation -/ +scoped syntax (name:=blockquote) ">" block* : block +/-- A link reference definition -/ +scoped syntax (name:=link_ref) "[" str "]:" str : block +/-- A footnote definition -/ +scoped syntax (name:=footnote_ref) "[^" str "]:" inline* : block +/-- Custom directive -/ +scoped syntax (name:=directive) ":::" rawIdent doc_arg* "{" block:max* "}" : block +/-- A header -/ +scoped syntax (name:=header) "header(" num ")" "{" inline+ "}" : block +open Lean.Parser.Term in + +open Lean.Parser Term in +meta def metadataContents : Parser := + structInstFields (sepByIndent structInstField ", " (allowTrailingSep := true)) + +/-- Metadata for this section, defined by the current genre -/ +scoped syntax (name:=metadata_block) "%%%" metadataContents "%%%" : block + +/-- A block-level command -/ +scoped syntax (name:=command) "command{" rawIdent doc_arg* "}" : block diff --git a/src/Lean/DocString/Types.lean b/src/Lean/DocString/Types.lean new file mode 100644 index 0000000000..a3d4e29369 --- /dev/null +++ b/src/Lean/DocString/Types.lean @@ -0,0 +1,181 @@ +/- +Copyright (c) 2023-2025 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: David Thrane Christiansen +-/ + +module + +prelude + +public import Init.Data.Repr +public import Init.Data.Ord + +set_option linter.missingDocs true + +namespace Lean.Doc + +public section + +/-- +How to render mathematical content. +-/ +inductive MathMode where + /-- The math content is part of the text flow. -/ + | inline + /-- The math content is set apart from the text flow, with more space. -/ + | display +deriving Repr, BEq, Hashable, Ord + +/-- +Inline content that is part of the text flow. +-/ +inductive Inline (i : Type u) : Type u where + /-- + Textual content. + -/ + | text (string : String) + /-- + Emphasis, typically rendered using italic text. + -/ + | emph (content : Array (Inline i)) + /-- + Strong emphasis, typically rendered using bold text. + -/ + | bold (content : Array (Inline i)) + /-- + Inline literal code, typically rendered in a monospace font. + -/ + | code (string : String) + /-- + Embedded TeX math, to be rendered by an engine such as TeX or KaTeX. The `mode` determines whether + it is rendered in inline mode or display mode; even display-mode math is an inline element for + purposes of document structure. + -/ + | math (mode : MathMode) (string : String) + /-- + A user's line break. These are typically ignored when rendering, but don't need to be. + -/ + | linebreak (string : String) + /-- + A link to some URL. + -/ + | link (content : Array (Inline i)) (url : String) + /-- + A footnote. In Verso's concrete syntax, their contents are specified elsewhere, but elaboration + places the contents at the use site. + -/ + | footnote (name : String) (content : Array (Inline i)) + /-- + An image. `alt` should be displayed if the image can't be shown. + -/ + | image (alt : String) (url : String) + /-- + A sequence of inline elements. + -/ + | concat (content : Array (Inline i)) + /-- + A genre-specific inline element. `container` specifies what kind of element it is, and `content` + specifies the contained elements. + -/ + | other (container : i) (content : Array (Inline i)) +deriving BEq, Ord, Repr, Inhabited + +/-- Rewrites using a proof that two inline element types are equal. -/ +def Inline.cast (inlines_eq : i = i') (x : Inline i) : Inline i' := + inlines_eq ▸ x + +instance : Append (Inline i) where + append + | .concat #[], x => x + | x, .concat #[] => x + | .concat xs, .concat ys => .concat (xs ++ ys) + | .concat xs, x => .concat (xs.push x) + | x, .concat xs => .concat (#[x] ++ xs) + | x, y => .concat #[x, y] + +/-- No inline content. -/ +def Inline.empty : Inline i := .concat #[] + +/-- An item in either an ordered or unordered list. -/ +structure ListItem (α : Type u) where + /-- The contents of the list item. -/ + contents : Array α +deriving Repr, BEq, Ord, Inhabited + +/-- An item in a description list. -/ +structure DescItem (α : Type u) (β : Type v) where + /-- The term being described. -/ + term : Array α + /-- The description itself. -/ + desc : Array β +deriving Repr, BEq, Ord, Inhabited + +/-- +Block-level content in a document. +-/ +inductive Block (i : Type u) (b : Type v) : Type (max u v) where + /-- + A paragraph. + -/ + | para (contents : Array (Inline i)) + /-- + A code block. + -/ + | code (content : String) + /-- + An unordered list. + -/ + | ul (items : Array (ListItem (Block i b))) + /-- + An ordered list. + -/ + | ol (start : Int) (items : Array (ListItem (Block i b))) + /-- + A description list that associates explanatory text with shorter items. + -/ + | dl (items : Array (DescItem (Inline i) (Block i b))) + /-- + A quotation. + -/ + | blockquote (items : Array (Block i b)) + /-- + Multiple blocks, merged. + -/ + | concat (content : Array (Block i b)) + /-- + A genre-specific block. `container` specifies what kind of block it is, while `content` specifies + the content within the block. + -/ + | other (container : b) (content : Array (Block i b)) +deriving BEq, Ord, Repr, Inhabited + +/-- An empty block with no content. -/ +def Block.empty : Block i b := .concat #[] + +/-- Rewrites using proofs that two inline element types and two block types are equal. -/ +def Block.cast (inlines_eq : i = i') (blocks_eq : b = b') (x : Block i b) : Block i' b' := + inlines_eq ▸ blocks_eq ▸ x + +/-- +A logical division of a document. +-/ +structure Part (i : Type u) (b : Type v) (p : Type w) : Type (max u v w) where + /-- The part's title -/ + title : Array (Inline i) + /-- + A string approximation of the part's title, for use in contexts where formatted text is invalid. + -/ + titleString : String + /-- Genre-specific metadata -/ + metadata : Option p + /-- The part's textual content -/ + content : Array (Block i b) + /-- Sub-parts (e.g. subsections of a section, sections of a chapter) -/ + subParts : Array (Part i b p) +deriving BEq, Ord, Repr, Inhabited + +/-- Rewrites using proofs that inline element types, block types, and metadata types are equal. -/ +def Part.cast (inlines_eq : i = i') (blocks_eq : b = b') (metadata_eq : p = p') + (x : Part i b p) : Part i' b' p' := + inlines_eq ▸ blocks_eq ▸ metadata_eq ▸ x diff --git a/src/Lean/Elab.lean b/src/Lean/Elab.lean index 613c70bab9..b99ffa35f8 100644 --- a/src/Lean/Elab.lean +++ b/src/Lean/Elab.lean @@ -60,5 +60,7 @@ public import Lean.Elab.Time public import Lean.Elab.RecommendedSpelling public import Lean.Elab.InfoTrees public import Lean.Elab.ErrorExplanation +public import Lean.Elab.DocString +public import Lean.Elab.DocString.Builtin public section diff --git a/src/Lean/Elab/Binders.lean b/src/Lean/Elab/Binders.lean index 4d3c76728a..ad7abd0ce2 100644 --- a/src/Lean/Elab/Binders.lean +++ b/src/Lean/Elab/Binders.lean @@ -14,6 +14,7 @@ public import Lean.Elab.PreDefinition.TerminationHint public import Lean.Elab.Match public import Lean.Compiler.MetaAttr meta import Lean.Parser.Term +meta import Lean.Parser.Tactic import Lean.Linter.Basic public section diff --git a/src/Lean/Elab/BuiltinCommand.lean b/src/Lean/Elab/BuiltinCommand.lean index 6356f50254..400bea77b2 100644 --- a/src/Lean/Elab/BuiltinCommand.lean +++ b/src/Lean/Elab/BuiltinCommand.lean @@ -505,7 +505,7 @@ open Lean.Parser.Command.InternalSyntax in -- this is only relevant for declarations added without a declaration range -- in particular `Quot.mk` et al which are added by `init_quot` addDeclarationRangesFromSyntax declName stx id - addDocString declName doc + runTermElabM fun _ => addDocString declName (mkNullNode #[]) doc | _ => throwUnsupportedSyntax @[builtin_command_elab Lean.Parser.Command.include] def elabInclude : CommandElab diff --git a/src/Lean/Elab/Command.lean b/src/Lean/Elab/Command.lean index 7b2159c6e9..0182de52e9 100644 --- a/src/Lean/Elab/Command.lean +++ b/src/Lean/Elab/Command.lean @@ -9,6 +9,7 @@ prelude public import Init.Data.Range.Polymorphic.Stream public import Lean.Meta.Diagnostics public import Lean.Elab.Binders +public import Lean.Elab.Command.Scope public import Lean.Elab.SyntheticMVars public import Lean.Elab.SetOption public import Lean.Language.Basic @@ -19,75 +20,6 @@ public section namespace Lean.Elab.Command -/-- -A `Scope` records the part of the `CommandElabM` state that respects scoping, -such as the data for `universe`, `open`, and `variable` declarations, the current namespace, -and currently enabled options. -The `CommandElabM` state contains a stack of scopes, and only the top `Scope` -on the stack is read from or modified. There is always at least one `Scope` on the stack, -even outside any `section` or `namespace`, and each new pushed `Scope` -starts as a modified copy of the previous top scope. --/ -structure Scope where - /-- - The component of the `namespace` or `section` that this scope is associated to. - For example, `section a.b.c` and `namespace a.b.c` each create three scopes with headers - named `a`, `b`, and `c`. - This is used for checking the `end` command. The "base scope" has `""` as its header. - -/ - header : String - /-- - The current state of all set options at this point in the scope. Note that this is the - full current set of options and does *not* simply contain the options set - while this scope has been active. - -/ - opts : Options := {} - /-- The current namespace. The top-level namespace is represented by `Name.anonymous`. -/ - currNamespace : Name := Name.anonymous - /-- All currently `open`ed namespaces and names. -/ - openDecls : List OpenDecl := [] - /-- The current list of names for universe level variables to use for new declarations. This is managed by the `universe` command. -/ - levelNames : List Name := [] - /-- - The current list of binders to use for new declarations. - This is managed by the `variable` command. - Each binder is represented in `Syntax` form, and it is re-elaborated - within each command that uses this information. - - This is also used by commands, such as `#check`, to create an initial local context, - even if they do not work with binders per se. - -/ - varDecls : Array (TSyntax ``Parser.Term.bracketedBinder) := #[] - /-- - Globally unique internal identifiers for the `varDecls`. - There is one identifier per variable introduced by the binders - (recall that a binder such as `(a b c : Ty)` can produce more than one variable), - and each identifier is the user-provided variable name with a macro scope. - This is used by `TermElabM` in `Lean.Elab.Term.Context` to help with processing macros - that capture these variables. - -/ - varUIds : Array Name := #[] - /-- `include`d section variable names (from `varUIds`) -/ - includedVars : List Name := [] - /-- `omit`ted section variable names (from `varUIds`) -/ - omittedVars : List Name := [] - /-- - If true (default: false), all declarations that fail to compile - automatically receive the `noncomputable` modifier. - A scope with this flag set is created by `noncomputable section`. - - Recall that a new scope inherits all values from its parent scope, - so all sections and namespaces nested within a `noncomputable` section also have this flag set. - -/ - isNoncomputable : Bool := false - isPublic : Bool := false - /-- - Attributes that should be applied to all matching declaration in the section. Inherited from - parent scopes. - -/ - attrs : List (TSyntax ``Parser.Term.attrInstance) := [] - deriving Inhabited - structure State where env : Environment messages : MessageLog := {} diff --git a/src/Lean/Elab/Command/Scope.lean b/src/Lean/Elab/Command/Scope.lean new file mode 100644 index 0000000000..c069cd48b8 --- /dev/null +++ b/src/Lean/Elab/Command/Scope.lean @@ -0,0 +1,82 @@ +/- +Copyright (c) 2019 Microsoft Corporation. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Leonardo de Moura, Gabriel Ebner +-/ +module + +prelude +public import Lean.Data.Options +public import Lean.Parser.Term + +public section + +namespace Lean.Elab.Command +/-- +A `Scope` records the part of the `CommandElabM` state that respects scoping, +such as the data for `universe`, `open`, and `variable` declarations, the current namespace, +and currently enabled options. +The `CommandElabM` state contains a stack of scopes, and only the top `Scope` +on the stack is read from or modified. There is always at least one `Scope` on the stack, +even outside any `section` or `namespace`, and each new pushed `Scope` +starts as a modified copy of the previous top scope. +-/ +structure Scope where + /-- + The component of the `namespace` or `section` that this scope is associated to. + For example, `section a.b.c` and `namespace a.b.c` each create three scopes with headers + named `a`, `b`, and `c`. + This is used for checking the `end` command. The "base scope" has `""` as its header. + -/ + header : String + /-- + The current state of all set options at this point in the scope. Note that this is the + full current set of options and does *not* simply contain the options set + while this scope has been active. + -/ + opts : Options := {} + /-- The current namespace. The top-level namespace is represented by `Name.anonymous`. -/ + currNamespace : Name := Name.anonymous + /-- All currently `open`ed namespaces and names. -/ + openDecls : List OpenDecl := [] + /-- The current list of names for universe level variables to use for new declarations. This is managed by the `universe` command. -/ + levelNames : List Name := [] + /-- + The current list of binders to use for new declarations. + This is managed by the `variable` command. + Each binder is represented in `Syntax` form, and it is re-elaborated + within each command that uses this information. + + This is also used by commands, such as `#check`, to create an initial local context, + even if they do not work with binders per se. + -/ + varDecls : Array (TSyntax ``Parser.Term.bracketedBinder) := #[] + /-- + Globally unique internal identifiers for the `varDecls`. + There is one identifier per variable introduced by the binders + (recall that a binder such as `(a b c : Ty)` can produce more than one variable), + and each identifier is the user-provided variable name with a macro scope. + This is used by `TermElabM` in `Lean.Elab.Term.Context` to help with processing macros + that capture these variables. + -/ + varUIds : Array Name := #[] + /-- `include`d section variable names (from `varUIds`) -/ + includedVars : List Name := [] + /-- `omit`ted section variable names (from `varUIds`) -/ + omittedVars : List Name := [] + /-- + If true (default: false), all declarations that fail to compile + automatically receive the `noncomputable` modifier. + A scope with this flag set is created by `noncomputable section`. + + Recall that a new scope inherits all values from its parent scope, + so all sections and namespaces nested within a `noncomputable` section also have this flag set. + -/ + isNoncomputable : Bool := false + isPublic : Bool := false + /-- + Attributes that should be applied to all matching declaration in the section. Inherited from + parent scopes. + -/ + attrs : List (TSyntax ``Parser.Term.attrInstance) := [] + deriving Inhabited diff --git a/src/Lean/Elab/DeclModifiers.lean b/src/Lean/Elab/DeclModifiers.lean index e088ed6fec..4eed4a0f10 100644 --- a/src/Lean/Elab/DeclModifiers.lean +++ b/src/Lean/Elab/DeclModifiers.lean @@ -85,7 +85,7 @@ inductive ComputeKind where structure Modifiers where /-- Input syntax, used for adjusting declaration range (unless missing) -/ stx : TSyntax ``Parser.Command.declModifiers := ⟨.missing⟩ - docString? : Option (TSyntax ``Parser.Command.docComment) := none + docString? : Option (TSyntax ``Parser.Command.docComment × Bool) := none visibility : Visibility := Visibility.regular isProtected : Bool := false computeKind : ComputeKind := .regular @@ -187,7 +187,7 @@ def elabModifiers (stx : TSyntax ``Parser.Command.declModifiers) : m Modifiers : RecKind.partial else RecKind.nonrec - let docString? := docCommentStx.getOptional?.map TSyntax.mk + let docString? := docCommentStx.getOptional?.map (TSyntax.mk ·, doc.verso.get (← getOptions)) let visibility ← match visibilityStx.getOptional? with | none => pure .regular | some v => @@ -276,6 +276,10 @@ structure ExpandDeclIdResult where declName : Name /-- Universe parameter names provided using the `universe` command and `.{...}` notation. -/ levelNames : List Name + /-- The docstring, and whether it's Verso -/ + docString? : Option (TSyntax ``Parser.Command.docComment × Bool) + +open Lean.Elab.Term (TermElabM) /-- Given a declaration identifier (e.g., `ident (".{" ident,+ "}")?`) that may contain explicit universe parameters @@ -287,7 +291,7 @@ The result also contains the universe parameters provided using `universe` comma This commands also stores the doc string stored in `modifiers`. -/ -def expandDeclId (currNamespace : Name) (currLevelNames : List Name) (declId : Syntax) (modifiers : Modifiers) : m ExpandDeclIdResult := do +def expandDeclId (currNamespace : Name) (currLevelNames : List Name) (declId : Syntax) (modifiers : Modifiers) : TermElabM ExpandDeclIdResult := do -- ident >> optional (".{" >> sepBy1 ident ", " >> "}") let (shortName, optUnivDeclStx) := expandDeclIdCore declId let levelNames ← if optUnivDeclStx.isNone then @@ -303,8 +307,8 @@ def expandDeclId (currNamespace : Name) (currLevelNames : List Name) (declId : S pure (id :: levelNames)) currLevelNames let (declName, shortName) ← withRef declId <| mkDeclName currNamespace modifiers shortName - addDocString' declName modifiers.docString? - return { shortName := shortName, declName := declName, levelNames := levelNames } + let docString? := modifiers.docString? + return { shortName, declName, levelNames, docString? } end Methods diff --git a/src/Lean/Elab/Declaration.lean b/src/Lean/Elab/Declaration.lean index aa52af3e9b..cd44fc887e 100644 --- a/src/Lean/Elab/Declaration.lean +++ b/src/Lean/Elab/Declaration.lean @@ -109,7 +109,7 @@ def elabAxiom (modifiers : Modifiers) (stx : Syntax) : CommandElabM Unit := do let (binders, typeStx) := expandDeclSig stx[2] runTermElabM fun vars => do let scopeLevelNames ← Term.getLevelNames - let ⟨shortName, declName, allUserLevelNames⟩ ← Term.expandDeclId (← getCurrNamespace) scopeLevelNames declId modifiers + let ⟨shortName, declName, allUserLevelNames, docString?⟩ ← Term.expandDeclId (← getCurrNamespace) scopeLevelNames declId modifiers addDeclarationRangesForBuiltin declName modifiers.stx stx Term.withAutoBoundImplicit do Term.withAutoBoundImplicitForbiddenPred (fun n => shortName == n) do @@ -136,12 +136,15 @@ def elabAxiom (modifiers : Modifiers) (stx : Syntax) : CommandElabM Unit := do trace[Elab.axiom] "{declName} : {type}" Term.ensureNoUnassignedMVars decl addDecl decl - withSaveInfoContext do -- save new env - Term.addTermInfo' declId (← mkConstWithLevelParams declName) (isBinder := true) + Term.applyAttributesAt declName modifiers.attrs AttributeApplicationTime.afterTypeChecking if isExtern (← getEnv) declName then compileDecl decl + if let some (doc, isVerso) := docString? then + addDocStringOf isVerso declName binders doc Term.applyAttributesAt declName modifiers.attrs AttributeApplicationTime.afterCompilation + withSaveInfoContext do -- save new env with docstring and decl + Term.addTermInfo' declId (← mkConstWithLevelParams declName) (isBinder := true) open Lean.Parser.Command.InternalSyntax in /-- Macro that expands a declaration with a complex name into an explicit `namespace` block. diff --git a/src/Lean/Elab/DefView.lean b/src/Lean/Elab/DefView.lean index f08a581ea2..656621ca7d 100644 --- a/src/Lean/Elab/DefView.lean +++ b/src/Lean/Elab/DefView.lean @@ -118,6 +118,8 @@ structure DefView where binders : Syntax type? : Option Syntax value : Syntax + /-- The docstring, if present, and whether it's Verso -/ + docString? : Option (TSyntax ``Parser.Command.docComment × Bool) /-- Snapshot for incremental processing of this definition. @@ -145,20 +147,22 @@ def mkDefViewOfAbbrev (modifiers : Modifiers) (stx : Syntax) : DefView := let modifiers := modifiers.addAttr { name := `inline } let modifiers := modifiers.addAttr { name := `reducible } { ref := stx, headerRef := mkNullNode stx.getArgs[*...3], kind := DefKind.abbrev, modifiers, - declId := stx[1], binders, type? := type, value := stx[3] } + declId := stx[1], binders, type? := type, value := stx[3], docString? := modifiers.docString? } def mkDefViewOfDef (modifiers : Modifiers) (stx : Syntax) : DefView := -- leading_parser "def " >> declId >> optDeclSig >> declVal >> optDefDeriving let (binders, type) := expandOptDeclSig stx[2] let deriving? := if stx[4].isNone then none else some stx[4][1].getSepArgs { ref := stx, headerRef := mkNullNode stx.getArgs[*...3], kind := DefKind.def, modifiers, - declId := stx[1], binders, type? := type, value := stx[3], deriving? } + declId := stx[1], binders, type? := type, value := stx[3], deriving?, + docString? := modifiers.docString? } def mkDefViewOfTheorem (modifiers : Modifiers) (stx : Syntax) : DefView := -- leading_parser "theorem " >> declId >> declSig >> declVal let (binders, type) := expandDeclSig stx[2] { ref := stx, headerRef := mkNullNode stx.getArgs[*...3], kind := DefKind.theorem, modifiers, - declId := stx[1], binders, type? := some type, value := stx[3] } + declId := stx[1], binders, type? := some type, value := stx[3], + docString? := modifiers.docString? } def mkDefViewOfInstance (modifiers : Modifiers) (stx : Syntax) : CommandElabM DefView := do -- leading_parser Term.attrKind >> "instance " >> optNamedPrio >> optional declId >> declSig >> declVal @@ -179,7 +183,8 @@ def mkDefViewOfInstance (modifiers : Modifiers) (stx : Syntax) : CommandElabM De pure <| mkNode ``Parser.Command.declId #[mkIdentFrom stx[1] id (canonical := true), mkNullNode] return { ref := stx, headerRef := mkNullNode stx.getArgs[*...5], kind := DefKind.instance, modifiers := modifiers, - declId := declId, binders := binders, type? := type, value := stx[5] + declId := declId, binders := binders, type? := type, value := stx[5], + docString? := modifiers.docString? } def mkDefViewOfOpaque (modifiers : Modifiers) (stx : Syntax) : CommandElabM DefView := do @@ -192,7 +197,8 @@ def mkDefViewOfOpaque (modifiers : Modifiers) (stx : Syntax) : CommandElabM DefV `(Parser.Command.declValSimple| := $val) return { ref := stx, headerRef := mkNullNode stx.getArgs[*...3], kind := DefKind.opaque, modifiers := modifiers, - declId := stx[1], binders := binders, type? := some type, value := val + declId := stx[1], binders := binders, type? := some type, value := val, + docString? := modifiers.docString? } def mkDefViewOfExample (modifiers : Modifiers) (stx : Syntax) : DefView := @@ -201,7 +207,8 @@ def mkDefViewOfExample (modifiers : Modifiers) (stx : Syntax) : DefView := let id := mkIdentFrom stx[0] `_example (canonical := true) let declId := mkNode ``Parser.Command.declId #[id, mkNullNode] { ref := stx, headerRef := mkNullNode stx.getArgs[*...2], kind := DefKind.example, modifiers := modifiers, - declId := declId, binders := binders, type? := type, value := stx[2] } + declId := declId, binders := binders, type? := type, value := stx[2], + docString? := modifiers.docString? } def isDefLike (stx : Syntax) : Bool := let declKind := stx.getKind diff --git a/src/Lean/Elab/DocString.lean b/src/Lean/Elab/DocString.lean new file mode 100644 index 0000000000..e7a2603184 --- /dev/null +++ b/src/Lean/Elab/DocString.lean @@ -0,0 +1,1186 @@ +/- +Copyright (c) 2025 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: David Thrane Christiansen +-/ +module +prelude +public import Lean.ScopedEnvExtension +import Std.Data.HashMap +public import Lean.DocString.Types +public import Lean.Elab.Term.TermElabM +public import Lean.Elab.Command.Scope +import Lean.DocString.Syntax +import Lean.Meta.Hint + +set_option linter.missingDocs true + +namespace Lean.Doc + +open Lean Elab Term +open Std +open scoped Lean.Doc.Syntax + + + +/-- Environment extension for code suggestions -/ +builtin_initialize codeSuggestionExt : SimpleScopedEnvExtension Name NameSet ← + registerSimpleScopedEnvExtension { + addEntry := fun xs suggester => xs.insert suggester + initial := {} + } + +/-- +Built-in code suggestions, for bootstrapping +-/ +builtin_initialize builtinCodeSuggestions : IO.Ref NameSet ← IO.mkRef {} + +/-- Environment extension for code block suggestions -/ +builtin_initialize codeBlockSuggestionExt : SimpleScopedEnvExtension Name NameSet ← + registerSimpleScopedEnvExtension { + addEntry := fun xs suggester => xs.insert suggester + initial := {} + } + +/-- +Built-in code block suggestions, for bootstrapping +-/ +builtin_initialize builtinCodeBlockSuggestions : IO.Ref NameSet ← IO.mkRef {} + +/-- Environment extension for docstring roles -/ +builtin_initialize docRoleExt : SimpleScopedEnvExtension (Name × Name) (NameMap (Array Name)) ← + registerSimpleScopedEnvExtension { + addEntry := fun xs (roleName, expander) => xs.alter roleName fun v? => + v?.getD #[] |>.push expander + initial := {} + } + +/-- +Built-in docstring roles, for bootstrapping. +-/ +builtin_initialize builtinDocRoles : IO.Ref (NameMap (Array Name)) ← IO.mkRef {} + +/-- Environment extension for docstring roles -/ +builtin_initialize docCodeBlockExt : SimpleScopedEnvExtension (Name × Name) (NameMap (Array Name)) ← + registerSimpleScopedEnvExtension { + addEntry := fun xs (roleName, expander) => xs.alter roleName fun v? => + v?.getD #[] |>.push expander + initial := {} + } + +/-- +Built-in docstring code blocks, for bootstrapping. +-/ +builtin_initialize builtinDocCodeBlocks : IO.Ref (NameMap (Array Name)) ← IO.mkRef {} + +/-- Environment extension for docstring directives -/ +builtin_initialize docDirectiveExt : SimpleScopedEnvExtension (Name × Name) (NameMap (Array Name)) ← + registerSimpleScopedEnvExtension { + addEntry := fun xs (roleName, expander) => xs.alter roleName fun v? => + v?.getD #[] |>.push expander + initial := {} + } + +/-- +Built-in docstring directives, for bootstrapping. +-/ +builtin_initialize builtinDocDirectives : IO.Ref (NameMap (Array Name)) ← IO.mkRef {} + +/-- Environment extension for docstring commands -/ +builtin_initialize docCommandExt : SimpleScopedEnvExtension (Name × Name) (NameMap (Array Name)) ← + registerSimpleScopedEnvExtension { + addEntry := fun xs (roleName, expander) => xs.alter roleName fun v? => + v?.getD #[] |>.push expander + initial := {} + } + +/-- +Built-in docstring commands, for bootstrapping. +-/ +builtin_initialize builtinDocCommands : IO.Ref (NameMap (Array Name)) ← IO.mkRef {} + +public section + +private structure ElabLink where + name : StrLit +deriving TypeName + +private def delayLink (name : StrLit) : ElabInline where + name := decl_name% + val := .mk (ElabLink.mk name) + +private structure ElabImage where + alt : String + name : StrLit +deriving TypeName + +private def delayImage (alt : String) (name : StrLit) : ElabInline where + name := decl_name% + val := .mk (ElabImage.mk alt name) + +private structure ElabFootnote where + name : StrLit +deriving TypeName + +private def delayFootnote (name : StrLit) : ElabInline where + name := decl_name% + val := .mk (ElabFootnote.mk name) + +private structure Ref (α) where + content : α + location : Syntax + seen := false + +/-- The internal state used by docstring elaboration -/ +structure InternalState where + private footnotes : HashMap String (Ref (Inline ElabInline)) := {} + private urls : HashMap String (Ref String) := {} + +/-- +The state used by `DocM`. +-/ +structure State where + /-- + The command elaboration scope stack. + + These scopes are used when running commands inside of documentation. To keep examples + self-contained, these scopes are initialized for each doc comment as if it were the beginning + of a Lean file. + -/ + scopes : List Elab.Command.Scope + /-- + The set of open declarations presently in force. + + The `MonadLift TermElabM DocM` instance runs the lifted action in a context where these open + declarations are used, so elaboration commands that mutate this state cause it to take effect in + subsequent commands. + -/ + openDecls : List OpenDecl + /-- + The local context. + + The `MonadLift TermElabM DocM` instance runs the lifted action in this context, so elaboration + commands that mutate this state cause it to take effect in subsequent commands. + -/ + lctx : LocalContext + /-- + The options. + + The `MonadLift TermElabM DocM` instance runs the lifted action with these options, so elaboration + commands that mutate this state cause it to take effect in subsequent commands. + -/ + options : Options + +/-- +The monad in which documentation is elaborated. +-/ +abbrev DocM := StateRefT InternalState (StateRefT Lean.Doc.State TermElabM) + +private def DocM.mk (act : IO.Ref InternalState → IO.Ref State → TermElabM α) : DocM α := act + +instance : MonadStateOf InternalState DocM := + inferInstanceAs <| MonadStateOf InternalState (StateRefT InternalState (StateRefT Lean.Doc.State TermElabM)) + +instance : MonadStateOf State DocM := + inferInstanceAs <| MonadStateOf State (StateRefT InternalState (StateRefT Lean.Doc.State TermElabM)) + + +instance : MonadLift TermElabM DocM where + monadLift act := private DocM.mk fun _ st' => do + let {openDecls, lctx, options, ..} := (← st'.get) + let v ← + withTheReader Core.Context (fun ρ => { ρ with openDecls, options }) <| + withTheReader Meta.Context (fun ρ => { ρ with lctx }) <| + act + return v + +open Lean.Parser.Term in +/-- +Runs a documentation elaborator, discarding changes made to the environment. +-/ +def DocM.exec (declName : Name) (binders : Syntax) (act : DocM α) : + TermElabM α := withoutModifyingEnv do + let some ci := (← getEnv).constants.find? declName + | throwError "Unknown constant {declName} when building docstring" + let (lctx, localInstances) ← buildContext ci.type binders + let sc ← scopedEnvExtensionsRef.get + try + let openDecls ← getOpenDecls + let options ← getOptions + let scopes := [{header := ""}] + let ((v, _), _) ← withTheReader Meta.Context (fun ρ => { ρ with localInstances }) <| + act.run {} |>.run { scopes, openDecls, lctx, options } + pure v + finally + scopedEnvExtensionsRef.set sc +where + buildContext (type : Expr) (binders : Syntax) : TermElabM (LocalContext × LocalInstances) := do + -- Create a local context with all binders + let mut type := type + let mut localInstances := (← readThe Meta.Context).localInstances + let mut lctx := ← getLCtx + for b in binders.getArgs do + for x in (← binderNames b) do + -- Consume parameters until we find one that matches or run out + repeat + type ← Meta.whnf type + match type with + | .forallE y ty body bi => + let fv ← mkFreshFVarId + if let some c := ← Meta.isClass? ty then + localInstances := localInstances.push {className := c, fvar := .fvar fv} + if let some x' := x then + if x'.getId == y then + lctx := lctx.mkLocalDecl fv y ty + addTermInfo' x' (.fvar fv) (lctx? := some lctx) (expectedType? := ty) + type := body.instantiate1 (.fvar fv) + break + else + if bi == .instImplicit then + lctx := lctx.mkLocalDecl fv y ty + type := body.instantiate1 (.fvar fv) + break + lctx := lctx.mkLocalDecl fv y ty + type := body.instantiate1 (.fvar fv) + | .mdata _ t => type := t + | _ => break + return (lctx, localInstances) + + binderNames (binderStx : Syntax) : TermElabM (Array (Option Syntax)) := + match binderStx.getKind with + | ``explicitBinder | ``implicitBinder | ``strictImplicitBinder => + getNames binderStx[1] + | ``instBinder => + let x := binderStx[1][0] + if x.isMissing then pure #[none] else pure #[some x] + | _ => throwErrorAt binderStx "Couldn't interpret binder {binderStx}" + getNames (ids : Syntax) : TermElabM (Array (Option Syntax)) := + ids.getArgs.mapM fun x => + if x.getKind == identKind || x.getKind == ``hole then + pure (some x) + else throwErrorAt x "identifer or `_` expected" + + +set_option linter.unusedVariables false in +/-- +Gadget that indicates that a function's parameter should be treated as a Boolean flag when used in +a docstring extension. +-/ +abbrev flag (default : Bool) : Type := Bool + +/-- +Gadget that indicates that a function's parameter should be treated as a repeated (and thus +optional) named argument when used in a docstring extension. +-/ +@[expose] +abbrev many (α : Type u) : Type u := Array α + + +/-- An argument provided to a docstring extension -/ +inductive DocArg where + /-- An identifier -/ + | ident (val : Ident) + /-- A number -/ + | num (val : NumLit) + /-- A string -/ + | str (val : StrLit) + +instance : ToMessageData DocArg where + toMessageData + | .ident x => toMessageData x + | .num n => toMessageData n + | .str s => toMessageData s + +/-- +Returns the syntax from which a documentation argument was drawn, typically used to report errors. +-/ +def DocArg.syntax : DocArg → Syntax + | .ident x => x + | .num x => x + | .str x => x + +/-- +Converts the syntax of a documentation argument into a suitable value. +-/ +def DocArg.ofSyntax : TSyntax `arg_val → TermElabM DocArg + | `(arg_val| $x:ident ) => pure <| .ident x + | `(arg_val| $x:num ) => pure <| .num x + | `(arg_val| $x:str ) => pure <| .str x + | other => throwErrorAt other "Failed to parse argument value" + +/-- +A value paired with the syntax it is derived from. + +This can be used to provide hints and code actions. +-/ +structure WithSyntax (α : Type u) where + /-- The parsed value. -/ + val : α + /-- The syntax that the value was derived from. -/ + stx : Syntax + +/-- +A canonical way to convert a documentation extension's argument into a Lean value of type `α`. +-/ +class FromDocArg α where + /-- + Converts a documentation extension's argument into a Lean value. + -/ + fromDocArg : DocArg → TermElabM α + +instance [FromDocArg α] : FromDocArg (Option α) where + fromDocArg v := private some <$> FromDocArg.fromDocArg v + +instance [FromDocArg α] : FromDocArg (WithSyntax α) where + fromDocArg v := private (WithSyntax.mk · v.syntax) <$> FromDocArg.fromDocArg v + +instance : FromDocArg Ident where + fromDocArg v := private + match v with + | .ident x => pure x + | other => throwErrorAt other.syntax "Expected a string" + +instance : FromDocArg String where + fromDocArg v := private + match v with + | .str s => pure s.getString + | other => throwErrorAt other.syntax "Expected a string" + +instance : FromDocArg StrLit where + fromDocArg v := private + match v with + | .str s => pure s + | other => throwErrorAt other.syntax "Expected a string" + +instance : FromDocArg Nat where + fromDocArg v := private + match v with + | .num x => pure x.getNat + | other => throwErrorAt other.syntax "Expected a number" + +instance : FromDocArg NumLit where + fromDocArg v := private + match v with + | .num x => pure x + | other => throwErrorAt other.syntax "Expected a number" + +instance : FromDocArg DataValue where + fromDocArg v := private + match v with + | .num x => pure <| .ofNat x.getNat + | .ident x => do + let y ← realizeGlobalConstNoOverloadWithInfo x + if y == ``true then pure <| .ofBool true + else if y == ``false then pure <| .ofBool false + else + let bools ← #[``true, ``false] |>.mapM unresolveNameGlobalAvoidingLocals + let h ← MessageData.hint m!"Use a Boolean:" (bools.map fun x => s!"{x}") (ref? := some x) + throwErrorAt x m!"Expected a string, number, or Boolean.{h}" + | .str s => pure <| .ofString s.getString + +instance : FromDocArg Bool where + fromDocArg v := private + match v with + | .ident x => do + let x' ← realizeGlobalConstNoOverloadWithInfo x + if x' == ``true then return true + else if x' == ``false then return false + else throwErrorAt x m!"Expected {.ofConstName ``true} or {.ofConstName ``false} but got {.ofConstName x'}" + | other => throwErrorAt other.syntax "Expected a Boolean" + +open MessageSeverity in +private def severityHint (ref : Syntax) : TermElabM MessageData := do + let suggestions ← #[``information, ``warning, ``error].mapM unresolveNameGlobalAvoidingLocals + let suggestions:= suggestions.map ({suggestion := ·.toString}) + MessageData.hint m!"Use a message severity:" suggestions (ref? := ref) + +open MessageSeverity in +instance : FromDocArg MessageSeverity where + fromDocArg v := private + match v with + | .ident x => do + let x' ← + try realizeGlobalConstNoOverloadWithInfo x + catch + | e => throwErrorAt x m!"{e.toMessageData}{← severityHint x}" + match x' with + | ``error => return error + | ``warning => return warning + | ``information => return information + | _ => + let expected := [``information, ``warning, ``error].map (MessageData.ofConstName) + throwErrorAt x m!"Expected {.orList expected} but got {.ofConstName x'}{← severityHint x}" + | other => do + throwErrorAt other.syntax "Expected a message severity{← severityHint other.syntax}" + +/-- +Retrieves the next positional argument from the arguments to a documentation extension. Throws +an error if no positional arguments remain. +-/ +protected def getPositional [FromDocArg α] (name : Name) : + StateT (Array (TSyntax `doc_arg)) DocM α := do + let args ← get + for h : i in [0:args.size] do + if let `(doc_arg|$v:arg_val) := args[i] then + set (σ := Array (TSyntax `doc_arg)) (args[:i] ++ args[i+1:]) + let v ← DocArg.ofSyntax v + return (← FromDocArg.fromDocArg v) + throwError "Missing positional argument `{name}`" + +private def asNamed : Syntax → Option (Ident × TSyntax `arg_val) + | `(doc_arg|$x:ident := $v:arg_val) => some (x, v) + | `(doc_arg|($x:ident := $v:arg_val)) => some (x, v) + | _ => none + +/-- +Retrieves a named argument from the arguments to a documentation extension. Returns `default` if no +such named argument was provided. +-/ +protected def getNamed [FromDocArg α] (name : Name) (default : α) : + StateT (Array (TSyntax `doc_arg)) DocM α := do + let name := name.eraseMacroScopes + let args ← get + for h : i in [0:args.size] do + if let some (x, v) := asNamed args[i] then + if x.getId.eraseMacroScopes == name then + set (σ := Array (TSyntax `doc_arg)) (args[:i] ++ args[i+1:]) + let v ← DocArg.ofSyntax v + return (← FromDocArg.fromDocArg v) + return default + +/-- +Retrieves a repeated named argument from the arguments to a documentation extension. +-/ +protected def getMany [FromDocArg α] (name : Name) : + StateT (Array (TSyntax `doc_arg)) DocM (Array α) := do + let name := name.eraseMacroScopes + let args ← get + let mut thisArg := #[] + let mut others := #[] + for arg in args do + if let some (x, v) := asNamed arg then + if x.getId.eraseMacroScopes == name then + let v ← DocArg.ofSyntax v + thisArg := thisArg.push v + continue + others := others.push arg + set others + thisArg.mapM (FromDocArg.fromDocArg ·) + +/-- +Retrieves a flag from the arguments to a documentation extension. Returns `default` if the flag is +not explicit set. +-/ +protected def getFlag (name : Name) (default : Bool) : StateT (Array (TSyntax `doc_arg)) DocM Bool := do + let name := name.eraseMacroScopes + let args ← get + for h : i in [0:args.size] do + if let some (x, v) := asFlag args[i] then + if x.getId.eraseMacroScopes == name then + set (σ := Array (TSyntax `doc_arg)) (args[:i] ++ args[i+1:]) + return v + return default +where + asFlag + | `(doc_arg|+$x:ident) => some (x, true) + | `(doc_arg|-$x:ident) => some (x, false) + | _ => none + +/-- +Asserts that there are no further arguments to a documentation language extension. +-/ +protected def done : StateT (Array (TSyntax `doc_arg)) DocM Unit := do + for arg in (← get) do + logErrorAt arg m!"Extra argument" + return + +private inductive ArgSpec where + | positional (name : Name) (type : Expr) + | named (name : Name) (type : Expr) (default : Expr) + | many (name : Name) (type : Expr) + | flag (name : Name) (default : Bool) +deriving Repr + +open Meta in +private def genWrapper (declName : Name) (argType : Option Expr) (retType : Expr) : TermElabM Name := do + if let some c := (← getEnv).constants.find? declName then + let argSpec ← forallTelescope c.type fun args ret => do + let mut argSpec : Array ArgSpec := #[] + + for arg in (if argType.isSome then (args[:args.size-1] : Array _) else args) do + let localDecl ← arg.fvarId!.getDecl + let name := localDecl.userName + let argType := localDecl.type + if argType.isAppOfArity' ``optParam 2 then + argSpec := argSpec.push (.named name (argType.getArg! 0) (argType.getArg! 1)) + else if argType.isAppOfArity' ``many 1 then + argSpec := argSpec.push (.many name (argType.getArg!' 0)) + else if argType.isAppOfArity' ``flag 1 then + let e ← whnf (argType.getArg!' 0) + match_expr e with + | true => argSpec := argSpec.push (.flag name true) + | false => argSpec := argSpec.push (.flag name false) + | _ => throwError m!"Couldn't determine default flag value from {e}" + else + argSpec := argSpec.push (.positional name argType) + if h : args.size < 1 then + throwError "Expected at least one parameter" + else + if let some expected := argType then + let final := args[args.size-1] + let localDecl ← final.fvarId!.getDecl + unless ← isDefEq localDecl.type expected do + throwError "Expected last parameter type to be {expected} but got {localDecl.type}" + + let expected ← mkAppM ``DocM #[retType] + unless ← isDefEq ret expected do + throwError "Expected return type to be {expected} but got {ret}" + + pure argSpec + let inls ← mkAppM ``TSyntaxArray #[← mkListLit (.const ``SyntaxNodeKind []) [toExpr `inline]] + let parser ← + if let some argType := argType then + withLocalDecl (← mkFreshBinderName) .default argType fun i => do + mkLambdaFVars #[i] (← build 0 argSpec #[] (some i)) + else build 0 argSpec #[] none + let parserTy ← inferType parser + let name ← mkFreshUserName (declName ++ `getArgs) + let name := declName ++ `getArgs + addAndCompile <| .defnDecl { + name + levelParams := [] + type := parserTy + value := parser + hints := .regular 0 + safety := .safe + } + return name + else + throwError m!"`{MessageData.ofConstName declName}` not found" +where + build (i : Nat) (argSpec : Array ArgSpec) (args : Array Expr) (body : Option Expr): MetaM Expr := do + if h : i < argSpec.size then + match argSpec[i] with + | .positional name type => + let arg ← mkAppOptM ``Lean.Doc.getPositional #[type, none, toExpr name] + let k ← withLocalDecl name .default type fun v => do + mkLambdaFVars #[v] (← build (i + 1) argSpec (args.push v) body) + mkAppM ``Bind.bind #[arg, k] + | .named name type default => + let arg ← mkAppOptM ``Lean.Doc.getNamed #[type, none, toExpr name, default] + let k ← withLocalDecl name .default type fun v => do + mkLambdaFVars #[v] (← build (i + 1) argSpec (args.push v) body) + mkAppM ``Bind.bind #[arg, k] + | .many name type => + let arg ← mkAppOptM ``Lean.Doc.getMany #[type, none, toExpr name] + let k ← withLocalDecl name .default (← mkAppM ``Array #[type]) fun v => do + mkLambdaFVars #[v] (← build (i + 1) argSpec (args.push v) body) + mkAppM ``Bind.bind #[arg, k] + | .flag name default => + let arg ← mkAppM ``Lean.Doc.getFlag #[toExpr name, toExpr default] + let k ← withLocalDecl name .default (.const ``Bool []) fun v => do + mkLambdaFVars #[v] (← build (i + 1) argSpec (args.push v) body) + mkAppM ``Bind.bind #[arg, k] + else + let last ← mkAppM ``Lean.Doc.done #[] + let m ← mkAppM ``StateT #[← mkAppM ``Array #[← mkAppM ``TSyntax #[← mkListLit (.const ``SyntaxNodeKind []) [toExpr `doc_arg]]], ← mkAppM ``DocM #[]] + let k ← withLocalDecl (← mkFreshBinderName) .default (.const ``Unit []) fun u => do + let args := body.map (args.push ·) |>.getD args + mkLambdaFVars #[u] (← mkAppOptM ``liftM #[none, some m, none, none, (← mkAppM declName args)]) + mkAppM ``Bind.bind #[last, k] + +/-- A suggestion about an applicable role -/ +structure CodeSuggestion where + /-- The name of the role to suggest. -/ + role : Name + /-- The arguments it should receive, as a string. -/ + args : Option String := none + /-- More information to show users -/ + moreInfo : Option String := none + +builtin_initialize registerBuiltinAttribute { + name := `doc_code_suggestions + descr := "docstring code element suggestion provider" + applicationTime := .afterCompilation + add := fun decl stx kind => do + if let some d := (← getEnv).find? decl then + if d.type matches (.forallE _ (.const ``StrLit _) + (.app (.const ``DocM _) (.app (.const ``Array _) (.const ``CodeSuggestion _))) + .default) then + codeSuggestionExt.add decl + else + throwError "Wrong type for {.ofConstName decl}: {indentD <| repr d.type}" + else + throwError "{.ofConstName decl} is not defined" +} + +/-- +Adds a builtin documentation code suggestion provider. + +Should be run during initialization. +-/ +def addBuiltinCodeSuggestion (decl : Name) : IO Unit := + builtinCodeSuggestions.modify (·.insert decl) + +builtin_initialize registerBuiltinAttribute { + name := `builtin_doc_code_suggestions + descr := "docstring code element suggestion provider" + applicationTime := .afterCompilation + add := fun decl stx kind => do + if let some d := (← getEnv).find? decl then + if d.type matches (.forallE _ (.const ``StrLit _) + (.app (.const ``DocM _) (.app (.const ``Array _) (.const ``CodeSuggestion _))) + .default) then + declareBuiltin decl <| .app (.const ``addBuiltinCodeSuggestion []) (toExpr decl) + else + throwError "Wrong type for {.ofConstName decl}: {indentD <| repr d.type}" + else + throwError "{.ofConstName decl} is not defined" +} + +builtin_initialize registerBuiltinAttribute { + name := `doc_role + descr := "docstring role expander" + applicationTime := .afterCompilation + add := fun decl stx kind => do + let roleName ← + if let `(attr|doc_role $x) := stx then + realizeGlobalConstNoOverloadWithInfo x + else + pure decl + let argTy : Expr := + .app (.const ``TSyntaxArray []) + (mkApp3 (.const ``List.cons [0]) (.const ``SyntaxNodeKind []) (toExpr `inline) (.app (.const ``List.nil [0]) (.const ``SyntaxNodeKind []))) + let ret := .app (.const ``Inline [0]) (.const ``ElabInline []) + let ((wrapper, _), _) ← genWrapper decl (some argTy) ret |>.run {} {} |>.run {} {} + docRoleExt.add (roleName, wrapper) +} + +/-- +Adds a builtin documentation role. + +Should be run during initialization. +-/ +def addBuiltinDocRole (roleName wrapper : Name) : IO Unit := + builtinDocRoles.modify (·.alter roleName fun x? => x?.getD #[] |>.push wrapper) + +builtin_initialize registerBuiltinAttribute { + name := `builtin_doc_role + descr := "docstring role expander" + applicationTime := .afterCompilation + add := fun decl stx kind => do + let roleName ← + if let `(attr|builtin_doc_role $x) := stx then + realizeGlobalConstNoOverloadWithInfo x + else + pure decl + let argTy : Expr := + .app (.const ``TSyntaxArray []) + (mkApp3 (.const ``List.cons [0]) (.const ``SyntaxNodeKind []) (toExpr `inline) (.app (.const ``List.nil [0]) (.const ``SyntaxNodeKind []))) + let ret := .app (.const ``Inline [0]) (.const ``ElabInline []) + let ((wrapper, _), _) ← genWrapper decl (some argTy) ret |>.run {} {} |>.run {} {} + declareBuiltin roleName <| mkApp2 (.const ``addBuiltinDocRole []) (toExpr roleName) (toExpr wrapper) +} + +builtin_initialize registerBuiltinAttribute { + name := `doc_code_block + descr := "docstring code block expander" + applicationTime := .afterCompilation + add := fun decl stx kind => do + let blockName ← + if let `(attr|doc_code_block $x) := stx then + realizeGlobalConstNoOverloadWithInfo x + else + pure decl + let ret := mkApp2 (.const ``Block [0, 0]) (.const ``ElabInline []) (.const ``ElabBlock []) + let ((wrapper, _), _) ← genWrapper decl (some (.const ``StrLit [])) ret |>.run {} {} |>.run {} {} + docCodeBlockExt.add (blockName, wrapper) +} + +/-- +Adds a builtin documentation code block. + +Should be run during initialization. +-/ +def addBuiltinDocCodeBlock (blockName wrapper : Name) : IO Unit := + builtinDocCodeBlocks.modify (·.alter blockName fun x? => x?.getD #[] |>.push wrapper) + +builtin_initialize registerBuiltinAttribute { + name := `builtin_doc_code_block + descr := "docstring code block expander" + applicationTime := .afterCompilation + add := fun decl stx kind => do + let blockName ← + if let `(attr|builtin_doc_code_block $x) := stx then + realizeGlobalConstNoOverloadWithInfo x + else + pure decl + let ret := mkApp2 (.const ``Block [0, 0]) (.const ``ElabInline []) (.const ``ElabBlock []) + let ((wrapper, _), _) ← genWrapper decl (some (.const ``StrLit [])) ret |>.run {} {} |>.run {} {} + declareBuiltin blockName <| mkApp2 (.const ``addBuiltinDocCodeBlock []) (toExpr blockName) (toExpr wrapper) +} + +builtin_initialize registerBuiltinAttribute { + name := `doc_directive + descr := "docstring directive expander" + applicationTime := .afterCompilation + add := fun decl stx kind => do + let directiveName ← + if let `(attr|doc_directive $x) := stx then + realizeGlobalConstNoOverloadWithInfo x + else + pure decl + let argTy : Expr := + .app (.const ``TSyntaxArray []) + (mkApp3 (.const ``List.cons [0]) (.const ``SyntaxNodeKind []) (toExpr `block) (.app (.const ``List.nil [0]) (.const ``SyntaxNodeKind []))) + let ret := mkApp2 (.const ``Block [0, 0]) (.const ``ElabInline []) (.const ``ElabBlock []) + let ((wrapper, _), _) ← genWrapper decl (some argTy) ret |>.run {} {} |>.run {} {} + docCodeBlockExt.add (directiveName, wrapper) +} + +/-- +Adds a builtin documentation directive. + +Should be run during initialization. +-/ +def addBuiltinDocDirective (directiveName wrapper : Name) : IO Unit := + builtinDocCodeBlocks.modify (·.alter directiveName fun x? => x?.getD #[] |>.push wrapper) + +builtin_initialize registerBuiltinAttribute { + name := `builtin_doc_directive + descr := "docstring directive expander" + applicationTime := .afterCompilation + add := fun decl stx kind => do + let directiveName ← + if let `(attr|builtin_doc_directive $x) := stx then + realizeGlobalConstNoOverloadWithInfo x + else + pure decl + let argTy : Expr := + .app (.const ``TSyntaxArray []) + (mkApp3 (.const ``List.cons [0]) (.const ``SyntaxNodeKind []) (toExpr `block) (.app (.const ``List.nil [0]) (.const ``SyntaxNodeKind []))) + let ret := mkApp2 (.const ``Block [0, 0]) (.const ``ElabInline []) (.const ``ElabBlock []) + let ((wrapper, _), _) ← genWrapper decl (some argTy) ret |>.run {} {} |>.run {} {} + declareBuiltin directiveName <| mkApp2 (.const ``addBuiltinDocCodeBlock []) (toExpr directiveName) (toExpr wrapper) +} + +builtin_initialize registerBuiltinAttribute { + name := `doc_command + descr := "docstring command expander" + applicationTime := .afterCompilation + add := fun decl stx kind => do + let commandName ← + if let `(attr|doc_command $x) := stx then + realizeGlobalConstNoOverloadWithInfo x + else + pure decl + + let ret := mkApp2 (.const ``Block [0, 0]) (.const ``ElabInline []) (.const ``ElabBlock []) + let ((wrapper, _), _) ← genWrapper decl none ret |>.run {} {} |>.run {} {} + docCommandExt.add (commandName, wrapper) +} + +/-- +Adds a builtin documentation command. + +Should be run during initialization. +-/ +def addBuiltinDocCommand (commandName wrapper : Name) : IO Unit := + builtinDocCommands.modify (·.alter commandName fun x? => x?.getD #[] |>.push wrapper) + +builtin_initialize registerBuiltinAttribute { + name := `builtin_doc_command + descr := "builtin docstring command expander" + applicationTime := .afterCompilation + add := fun decl stx kind => do + let commandName ← + if let `(attr|builtin_doc_command $x) := stx then + realizeGlobalConstNoOverloadWithInfo x + else + pure decl + + let ret := mkApp2 (.const ``Block [0, 0]) (.const ``ElabInline []) (.const ``ElabBlock []) + let ((wrapper, _), _) ← genWrapper decl none ret |>.run {} {} |>.run {} {} + declareBuiltin commandName <| mkApp2 (.const ``addBuiltinDocCommand []) (toExpr commandName) (toExpr wrapper) + +} +end + +private unsafe def codeSuggestionsUnsafe : TermElabM (Array (StrLit → DocM (Array CodeSuggestion))) := do + let names := (codeSuggestionExt.getState (← getEnv)) ++ (← builtinCodeSuggestions.get) |>.toArray + names.mapM (evalConst _) + +@[implemented_by codeSuggestionsUnsafe] +private opaque codeSuggestions : TermElabM (Array (StrLit → DocM (Array CodeSuggestion))) + +private unsafe def codeBlockSuggestionsUnsafe : TermElabM (Array (StrLit → DocM (Array CodeSuggestion))) := do + let names := (codeBlockSuggestionExt.getState (← getEnv)) ++ (← builtinCodeBlockSuggestions.get) |>.toArray + names.mapM (evalConst _) + +@[implemented_by codeSuggestionsUnsafe] +private opaque codeBlockSuggestions : TermElabM (Array (StrLit → DocM (Array CodeSuggestion))) + + +private unsafe def roleExpandersForUnsafe (roleName : Name) : TermElabM (Array (TSyntaxArray `inline → StateT (Array (TSyntax `doc_arg)) DocM (Inline ElabInline))) := do + let names := (docRoleExt.getState (← getEnv)).get? roleName |>.getD #[] + let names' := (← builtinDocRoles.get).get? roleName |>.getD #[] + (names ++ names').mapM (evalConst _) + +@[implemented_by roleExpandersForUnsafe] +private opaque roleExpandersFor (roleName : Name) : TermElabM (Array (TSyntaxArray `inline → StateT (Array (TSyntax `doc_arg)) DocM (Inline ElabInline))) + +private unsafe def codeBlockExpandersForUnsafe (roleName : Name) : TermElabM (Array (StrLit → StateT (Array (TSyntax `doc_arg)) DocM (Block ElabInline ElabBlock))) := do + let names := (docCodeBlockExt.getState (← getEnv)).get? roleName |>.getD #[] + let names' := (← builtinDocCodeBlocks.get).get? roleName |>.getD #[] + (names ++ names').mapM (evalConst _) + +@[implemented_by codeBlockExpandersForUnsafe] +private opaque codeBlockExpandersFor (roleName : Name) : TermElabM (Array (StrLit → StateT (Array (TSyntax `doc_arg)) DocM (Block ElabInline ElabBlock))) + +private unsafe def directiveExpandersForUnsafe (roleName : Name) : TermElabM (Array (TSyntaxArray `block → StateT (Array (TSyntax `doc_arg)) DocM (Block ElabInline ElabBlock))) := do + let names := (docCodeBlockExt.getState (← getEnv)).get? roleName |>.getD #[] + let names' := (← builtinDocCodeBlocks.get).get? roleName |>.getD #[] + (names ++ names').mapM (evalConst _) + +@[implemented_by directiveExpandersForUnsafe] +private opaque directiveExpandersFor (roleName : Name) : TermElabM (Array (TSyntaxArray `block → StateT (Array (TSyntax `doc_arg)) DocM (Block ElabInline ElabBlock))) + +private unsafe def commandExpandersForUnsafe (roleName : Name) : TermElabM (Array (StateT (Array (TSyntax `doc_arg)) DocM (Block ElabInline ElabBlock))) := do + let names := (docCommandExt.getState (← getEnv)).get? roleName |>.getD #[] + let names' := (← builtinDocCommands.get).get? roleName |>.getD #[] + (names ++ names').mapM (evalConst _) + +@[implemented_by commandExpandersForUnsafe] +private opaque commandExpandersFor (roleName : Name) : TermElabM (Array (StateT (Array (TSyntax `doc_arg)) DocM (Block ElabInline ElabBlock))) + + +private def mkArgVal (arg : TSyntax `arg_val) : DocM Term := + match arg with + | `(arg_val|$n:ident) => pure n + | `(arg_val|$n:num) => pure n + | `(arg_val|$s:str) => pure s + | _ => throwErrorAt arg "Didn't understand as argument value" + +private def mkArg (arg : TSyntax `doc_arg) : DocM (TSyntax ``Parser.Term.argument) := do + match arg with + | `(doc_arg|$x:arg_val) => + let x ← mkArgVal x + `(Parser.Term.argument| $x:term) + | `(doc_arg|+$x) => + `(Parser.Term.argument| ($x := true)) + | `(doc_arg|-$x) => + `(Parser.Term.argument| ($x := false)) + | `(doc_arg|($x := $v)) => + let v ← mkArgVal v + `(Parser.Term.argument| ($x := $v)) + | `(doc_arg|$x:ident := $v) => + logWarningAt arg "Obsolete syntax" -- TODO suggestion + let v ← mkArgVal v + `(Parser.Term.argument| ($x := $v)) + | _ => throwErrorAt arg "Didn't understand as argument" + +private def mkAppStx (name : Ident) (args : TSyntaxArray `doc_arg) : DocM Term := do + return ⟨mkNode ``Parser.Term.app #[name, mkNullNode (← args.mapM mkArg)]⟩ + +/-- +If `true`, suggestions are provided for code elements. +-/ +register_builtin_option doc.verso.suggestions : Bool := { + defValue := true + descr := "whether to provide suggestions for code elements" + group := "doc" +} + +/-- +Elaborates the syntax of an inline document element to an actual inline document element. +-/ +public partial def elabInline (stx : TSyntax `inline) : DocM (Inline ElabInline) := + withRef stx do + match stx with + | `(inline|$s:str) => + return .text s.getString + | `(inline|_[$inl*]) => + return .emph (← inl.mapM elabInline) + | `(inline|*[$inl*]) => + return .bold (← inl.mapM elabInline) + | `(inline|link[$inl*]($url)) => + return .link (← inl.mapM elabInline) url.getString + | `(inline|link[$inl*][$name]) => + return .other (delayLink name) (← inl.mapM elabInline) + | `(inline|image($alt)($url)) => + -- TODO forward ref to URL + return .image alt.getString url.getString + | `(inline|image($alt)[$name]) => + return .other (delayImage alt.getString name) #[] + | `(inline|footnote($ref)) => + return .other (delayFootnote ref) #[] + | `(inline|line!$s) => + return .linebreak s.getString + | `(inline|code($s)) => + if doc.verso.suggestions.get (← getOptions) then + if let some ⟨b, e⟩ := stx.raw.getRange? then + let suggesters ← codeSuggestions + let mut suggestions := #[] + for suggest in suggesters do + try suggestions := suggestions ++ (← suggest s) + catch | _ => pure () + unless suggestions.isEmpty do + let text ← getFileMap + let str := text.source.extract b e + let ss : Array Meta.Hint.Suggestion ← suggestions.mapM fun {role, args, moreInfo} => do + pure { + suggestion := + "{" ++ (← unresolveNameGlobalAvoidingLocals role).toString ++ + (args.map (" " ++ ·)).getD "" ++ "}" ++ str, + postInfo? := moreInfo.map withSpace + } + let ss : Array Meta.Hint.Suggestion := sortSuggestions ss + let hint ← m!"Insert a role to document it:".hint ss (ref? := some stx) + logWarning m!"Code element could be marked up.{hint}" + return .code s.getString + | `(inline|\math code($s)) => + return .math .inline s.getString + | `(inline|\displaymath code($s)) => + return .math .display s.getString + | `(inline|role{$name $args*}[$inl*]) => + let x ← realizeGlobalConstNoOverloadWithInfo name + let expanders ← roleExpandersFor x + for ex in expanders do + try + let res ← ex inl args <&> (·.1) + return res + catch + | e@(.internal id _) => + if id == unsupportedSyntaxExceptionId then + continue + else throw e + | e => throw e + throwErrorAt name "No expander for `{name}`" + | other => + throwErrorAt other "Unsupported syntax {other}" + +where + withSpace (s : String) : String := + if s.startsWith " " then s else " " ++ s + + sortSuggestions (ss : Array Meta.Hint.Suggestion) : Array Meta.Hint.Suggestion := + let cmp : (x y : Meta.Tactic.TryThis.SuggestionText) → Bool + | .string s1, .string s2 => s1 < s2 + | .string _, _ => true + | .tsyntax _, .string _ => false + | .tsyntax s1, .tsyntax s2 => toString s1.raw < toString s2.raw + ss.qsort (cmp ·.suggestion ·.suggestion) + +/-- +Elaborates the syntax of an block-level document element to an actual block-level document element. +-/ +public partial def elabBlock (stx : TSyntax `block) : DocM (Block ElabInline ElabBlock) := + withRef stx do + match stx with + | `(block|para[$inls*]) => + .para <$> inls.mapM elabInline + | `(block|ul{$[* $itemss*]*}) => + .ul <$> itemss.mapM fun items => + .mk <$> items.mapM elabBlock + | `(block|ol($n){$[* $itemss*]*}) => + .ol n.getNat <$> itemss.mapM fun items => + .mk <$> items.mapM elabBlock + | `(block|dl{$items*}) => + .dl <$> items.mapM fun itemStx => + withRef itemStx do + match itemStx with + | `(desc_item|: $term* => $desc*) => + return .mk (← term.mapM elabInline) (← desc.mapM elabBlock) + | _ => throwUnsupportedSyntax + | `(block|[^$ref]: $content*) => + let refStr := ref.getString + if (← getThe InternalState).footnotes.contains refStr then + throwErrorAt ref m!"Reference already found" + else + let content ← content.mapM elabInline + modifyThe InternalState fun st => + { st with + footnotes := st.footnotes.insert refStr { content := .concat content, location := ref } } + return .empty + | `(block|[$ref]: $url) => + let refStr := ref.getString + if (← getThe InternalState).urls.contains refStr then + throwErrorAt ref m!"Reference already found" + else + modifyThe InternalState fun st => + { st with + urls := st.urls.insert refStr { content := url.getString, location := ref } } + return .empty + | `(block| ::: $name $args* { $content*}) => + let x ← realizeGlobalConstNoOverloadWithInfo name + let expanders ← directiveExpandersFor x + for ex in expanders do + try + let res ← ex content args <&> (·.1) + return res + catch + | e@(.internal id _) => + if id == unsupportedSyntaxExceptionId then + continue + else throw e + | e => throw e + throwErrorAt name "No directive expander for `{name}`" + | `(block| ```$name $args* | $s ```) => + let x ← realizeGlobalConstNoOverloadWithInfo name + let expanders ← codeBlockExpandersFor x + for ex in expanders do + try + let res ← ex s args <&> (·.1) + return res + catch + | e@(.internal id _) => + if id == unsupportedSyntaxExceptionId then + continue + else throw e + | e => throw e + throwErrorAt name "No code block expander for `{name}`" + | `(block| command{$name $args*}) => + let x ← realizeGlobalConstNoOverloadWithInfo name + let expanders ← commandExpandersFor x + for ex in expanders do + try + let res ← ex args <&> (·.1) + return res + catch + | e@(.internal id _) => + if id == unsupportedSyntaxExceptionId then + continue + else throw e + | e => throw e + throwErrorAt name "No document command elaborator for `{name}`" + | _ => throwUnsupportedSyntax + +private def takeFirst? (xs : Array α) : Option (α × Array α) := + if h : xs.size > 0 then + some (xs[0], xs.extract 1) + else none + +private partial def elabBlocks' (level : Nat) : + StateT (TSyntaxArray `block) DocM (Array (Block ElabInline ElabBlock) × Array (Part ElabInline ElabBlock Empty)) := do + let mut pre := #[] + let mut sub := #[] + repeat + if let some (x, xs) := takeFirst? (← getThe (TSyntaxArray `block)) then + if let `(block|header($n){$name*}) := x then + let n := n.getNat + if n < level then return (pre, sub) + else if n = level then + set xs + let (content, subParts) ← elabBlocks' (level + 1) + let title ← liftM <| name.mapM elabInline + sub := sub.push { + title, + titleString := "" -- TODO get string from filemap? + metadata := none + content, subParts + } + else + logErrorAt x m!"Expected a header no deeper than `{"".pushn '#' <| level + 1}`" + set xs + else + set xs + try + pre := pre.push (← elabBlock x) + catch + | e => + logErrorAt e.getRef e.toMessageData + else break + return (pre, sub) + +private partial def fixupBlocks : (Array (Block ElabInline ElabBlock) × Array (Part ElabInline ElabBlock Empty)) → DocM (Array (Block ElabInline ElabBlock) × Array (Part ElabInline ElabBlock Empty)) + | (bs, ps) => do + let bs ← bs.mapM fixB + let ps ← ps.mapM fixP + return (bs, ps) +where + fixI (inl : Inline ElabInline) : DocM (Inline ElabInline) := do + match inl with + | .concat xs => .concat <$> xs.mapM fixI + | .emph xs => .emph <$> xs.mapM fixI + | .bold xs => .bold <$> xs.mapM fixI + | .link content url => (.link · url) <$> content.mapM fixI + | .footnote name content => .footnote name <$> content.mapM fixI + | .text s => pure (.text s) + | .image alt url => pure (.image alt url) + | .code s => pure (.code s) + | .math mode s => pure (.math mode s) + | .linebreak s => pure (.linebreak s) + | .other i@{ name, val } xs => + match name with + | ``delayLink => + let some {name} := val.get? ElabLink + | throwError "Wrong value for {name}: {val.typeName}" + let nameStr := name.getString + if let some r@{content := url, seen, .. } := (← getThe InternalState).urls[nameStr]? then + unless seen do modifyThe InternalState fun st => { st with urls := st.urls.insert nameStr { r with seen := true } } + return .link (← xs.mapM fixI) url + else + logErrorAt name "Reference not found" + return .concat (← xs.mapM fixI) + | ``delayImage => + let some {alt, name} := val.get? ElabImage + | throwError "Wrong value for {name}: {val.typeName}" + let nameStr := name.getString + if let some r@{content := url, seen, ..} := (← getThe InternalState).urls[nameStr]? then + unless seen do modifyThe InternalState fun st => { st with urls := st.urls.insert nameStr { r with seen := true } } + return .image alt url + else + logErrorAt name "Reference not found" + return .empty + | ``delayFootnote => + let some {name} := val.get? ElabFootnote + | throwError "Wrong value for {name}: {val.typeName}" + let nameStr := name.getString + if let some r@{content, seen, ..} := (← getThe InternalState).footnotes[nameStr]? then + unless seen do modifyThe InternalState fun st => + { st with footnotes := st.footnotes.insert nameStr { r with seen := true } } + return .footnote nameStr #[← fixI content] + else + logErrorAt name "Footnote not found" + return .empty + | _ => .other i <$> xs.mapM fixI + + fixB (block : Block ElabInline ElabBlock) : DocM (Block ElabInline ElabBlock) := do + match block with + | .para xs => .para <$> xs.mapM fixI + | .concat xs => .concat <$> xs.mapM fixB + | .blockquote xs => .blockquote <$> xs.mapM fixB + | .dl xs => .dl <$> xs.mapM fun { term, desc } => do + let term ← term.mapM fixI + let desc ← desc.mapM fixB + pure { term, desc } + | .ul xs => .ul <$> xs.mapM fun ⟨bs⟩ => do return ⟨← bs.mapM fixB⟩ + | .ol n xs => .ol n <$> xs.mapM fun ⟨bs⟩ => do return ⟨← bs.mapM fixB⟩ + | .code s => pure (.code s) + | .other i xs => .other i <$> xs.mapM fixB + + fixP (part : Part ElabInline ElabBlock Empty) : DocM (Part ElabInline ElabBlock Empty) := do + return { part with + title := ← part.title.mapM fixI + content := ← part.content.mapM fixB, + subParts := ← part.subParts.mapM fixP + } + +/-- +After fixing up the references, check to see which were not used and emit a suitable warning. +-/ +private def warnUnusedRefs : DocM Unit := do + for (_, {location, seen, ..}) in (← getThe InternalState).urls do + unless seen do + logWarningAt location "Unused URL" + for (_, {location, seen, ..}) in (← getThe InternalState).footnotes do + unless seen do + logWarningAt location "Unused footnote" + +/-- Elaborates a sequence of blocks into a document -/ +public def elabBlocks (blocks : TSyntaxArray `block) : + DocM (Array (Block ElabInline ElabBlock) × Array (Part ElabInline ElabBlock Empty)) := do + let (v, _) ← elabBlocks' 0 |>.run blocks + let res ← fixupBlocks v + warnUnusedRefs + return res diff --git a/src/Lean/Elab/DocString/Builtin.lean b/src/Lean/Elab/DocString/Builtin.lean new file mode 100644 index 0000000000..e356d6ca81 --- /dev/null +++ b/src/Lean/Elab/DocString/Builtin.lean @@ -0,0 +1,985 @@ +/- +Copyright (c) 2025 Lean FRO LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Author: David Thrane Christiansen +-/ + +module +prelude +public import Lean.Elab.DocString +import Lean.DocString.Links +public import Lean.DocString.Syntax +public import Lean.Elab.InfoTree +public import Lean.Elab.Term.TermElabM +import Lean.Elab.Open +public import Lean.Parser +import Lean.Meta.Hint +import Lean.Elab.Tactic.Doc +import Lean.Data.EditDistance + + +namespace Lean.Doc +open Lean Elab Term +open Lean.Parser +open Lean.EditDistance +open scoped Lean.Doc.Syntax + +set_option linter.missingDocs true + +public section + +/-- Create an identifier while directly copying info -/ +private def mkIdentFrom' (src : Syntax) (val : Name) : Ident := + ⟨Syntax.ident src.getHeadInfo (toString val).toSubstring val []⟩ + +/-- The code represents a global constant. -/ +structure Data.Const where + /-- The constant's name. -/ + name : Name +deriving TypeName + +/-- The code represents a local variable. -/ +structure Data.Local where + /-- The local variable's name. -/ + name : Name + /-- The local variable's ID. -/ + fvarId : FVarId + /-- The local variable's context. -/ + lctx : LocalContext + /-- The local variable's type. -/ + type : Expr +deriving TypeName + +/-- The code represents a tactic. -/ +structure Data.Tactic where + /-- The name of the tactic's syntax kind. -/ + name : Name +deriving TypeName + +/-- The code represents a conv tactic. -/ +structure Data.ConvTactic where + /-- The name of the tactic's syntax kind. -/ + name : Name +deriving TypeName + +/-- The code represents an attribute application `@[...]`. -/ +structure Data.Attributes where + /-- The attribute syntax. -/ + stx : Syntax +deriving TypeName + +/-- The code represents a single attribute. -/ +structure Data.Attribute where + /-- The attribute syntax. -/ + stx : Syntax +deriving TypeName + +/-- The code represents syntax to set an option. -/ +structure Data.SetOption where + /-- The `set_option ...` syntax. -/ + stx : Syntax +deriving TypeName + +/-- The code represents an option. -/ +structure Data.Option where + /-- The option's name. -/ + name : Name +deriving TypeName + +/-- The code represents an atom drawn from some syntax. -/ +structure Data.Atom where + /-- The syntax kind's name. -/ + name : Name + /-- The syntax category -/ + category : Name +deriving TypeName + +/-- The code represents a syntax category name. -/ +structure Data.SyntaxCat where + /-- The syntax category. -/ + name : Name +deriving TypeName + +/-- The code represents syntax in a given category. -/ +structure Data.Syntax where + /-- The syntax category. -/ + category : Name + /-- The parsed syntax. -/ + stx : Lean.Syntax +deriving TypeName + +private def onlyCode (xs : TSyntaxArray `inline) : DocM StrLit := do + if h : xs.size = 1 then + match xs[0] with + | `(inline|code($s)) => return s + | other => throwErrorAt other "Expected code" + else + throwError "Expected precisely 1 code argument" + +/-- +Displays a name, without attempting to elaborate implicit arguments. +-/ +--@[builtin_doc_role] +def name (xs : TSyntaxArray `inline) : DocM (Inline ElabInline) := do + let s ← onlyCode xs + let x := s.getString.toName + let n := mkIdentFrom' s x + if let some (e, fields) := (← resolveLocalName n.getId) then + let t ← Meta.inferType e + if fields.isEmpty then + pushInfoLeaf <| .ofTermInfo { + elaborator := .anonymous + stx := n + lctx := ← getLCtx + expr := e + expectedType? := some t + } + let data : Data.Local := {name := x, lctx := ← getLCtx, type := t, fvarId := e.fvarId!} + return .other { + name := ``Data.Local, val := .mk data + } #[.code s.getString] + let x ← realizeGlobalConstNoOverloadWithInfo n + return .other (.mk ``Data.Const (.mk (Data.Const.mk x))) #[.code s.getString] + +private def parseStrLit (p : ParserFn) (s : StrLit) : DocM Syntax := do + let text ← getFileMap + let env ← getEnv + let endPos := s.raw.getTailPos? true |>.get! + let endPos := if endPos ≤ text.source.endPos then endPos else text.source.endPos + let ictx := + mkInputContext text.source (← getFileName) + (endPos := endPos) (endPos_valid := by simp only [endPos]; split <;> simp [*]) + -- TODO fallback for non-original syntax + let s := (mkParserState text.source).setPos (s.raw.getPos? (canonicalOnly := true)).get! + let s := p.run ictx { env, options := ← getOptions } (getTokenTable env) s + + if !s.allErrors.isEmpty then + throwError (s.toErrorMsg ictx) + else if ictx.atEnd s.pos then + pure s.stxStack.back + else + throwError ((s.mkError "end of input").toErrorMsg ictx) + +private def parseStrLit' (p : ParserFn) (s : StrLit) : DocM (Syntax × Bool) := do + let text ← getFileMap + let env ← getEnv + let endPos := s.raw.getTailPos? true |>.get! + let endPos := if endPos ≤ text.source.endPos then endPos else text.source.endPos + let ictx := + mkInputContext text.source (← getFileName) + (endPos := endPos) (endPos_valid := by simp only [endPos]; split <;> simp [*]) + -- TODO fallback for non-original syntax + let s := (mkParserState text.source).setPos (s.raw.getPos? (canonicalOnly := true)).get! + let s := p.run ictx { env, options := ← getOptions } (getTokenTable env) s + + let err ← + if !s.allErrors.isEmpty then + logError (s.toErrorMsg ictx) + pure true + else if !ictx.atEnd s.pos then + logError ((s.mkError "end of input").toErrorMsg ictx) + pure true + else pure false + pure (s.stxStack.back, err) + +private def introduceAntiquotes (stx : Syntax) : DocM Unit := + discard <| stx.rewriteBottomUpM fun stx' => + match stx' with + | .node _ (.str k "antiquot") #[_dollar, _, name, _] => do + let k := if let .str k' "pseudo" := k then k' else k + let ty ← Meta.mkAppM ``TSyntax #[← Meta.mkListLit (.const ``SyntaxNodeKind []) [toExpr k]] + let lctx ← do + let lctx ← getLCtx + let fv ← mkFreshFVarId + let lctx := lctx.mkLocalDecl fv name.getId ty + addTermInfo' name (.fvar fv) (lctx? := some lctx) (isBinder := true) (expectedType? := some ty) + pure lctx + modify (fun st => { st with lctx }) + pure stx' + | _ => pure stx' + +/-- +A reference to a tactic. + +In `` {tactic}`T` ``, `T` can be any of the following: + * The name of a tactic syntax kind (e.g. `Lean.Parser.Tactic.induction`) + * The first token of a tactic (e.g. `induction`) + * Valid tactic syntax, potentially including antiquotations (e.g. `intro $x*`) +-/ +--@[builtin_doc_role] +def tactic (xs : TSyntaxArray `inline) : DocM (Inline ElabInline) := do + let s ← onlyCode xs + withRef s do + let asString := s.getString + let asName := asString.toName + let allTactics ← Tactic.Doc.allTacticDocs + let found := allTactics.filter fun tac => tac.userName == asString || tac.internalName == asName + let mut exns := #[] + if h : found.size = 0 then + let (s, msg) ← AddErrorMessageContext.add s m!"Tactic `{asString}` not found" + exns := exns.push <| Exception.error s msg + else if h : found.size > 1 then + let found := found.map (MessageData.ofConstName ·.internalName) |>.toList + let (s, msg) ← + AddErrorMessageContext.add s m!"Tactic name `{asString}` matches {.andList found}" + exns := exns.push <| Exception.error s msg + else + let found := found[0] + addConstInfo s found.internalName + return .other { + name := ``Data.Tactic, val := .mk { name := found.internalName : Data.Tactic} + } #[.code s.getString] + try + let p := whitespace >> categoryParserFn `tactic + let stx ← parseStrLit p s + introduceAntiquotes stx + return .code s.getString + catch + | e => exns := exns.push e + if h : exns.size = 1 then + throw exns[0] + else + throwErrorWithNestedErrors m!"Couldn't resolve tactic" exns + +private def getConvTactic (name : StrLit) : DocM Name := do + let p := rawIdentFn + let stx ← parseStrLit p name + let name := stx.getId + let parserState := parserExtension.getState (← getEnv) + let some convs := parserState.categories.find? `conv + | throwError "Couldn't find conv tactic list" + let found := convs.kinds.toArray.filterMap fun ⟨x, _⟩ => + if name.isSuffixOf x then some x else none + if h : found.size = 0 then throwError m!"Conv tactic not found: `{name}`" + else if h : found.size > 1 then + let opts := (found.map MessageData.ofConstName).toList + throwError m!"Multiple matching conv tactics: {.andList opts}" + else + return found[0] + +private def throwOrNest (msg : MessageData) (exns : Array Exception) : DocM α := + if h : exns.size = 1 then + throw exns[0] + else + throwErrorWithNestedErrors msg exns + +/-- +A reference to a conv tactic. + +In `` {conv}`T` ``, `T` can be any of the following: + * The name of a conv tactic syntax kind (e.g. `Lean.Parser.Tactic.Conv.lhs`) + * Valid conv tactic syntax, potentially including antiquotations (e.g. `lhs`) +-/ +--@[builtin_doc_role] +def conv (xs : TSyntaxArray `inline) : DocM (Inline ElabInline) := do + let s ← onlyCode xs + withRef s do + let mut exns := #[] + try + let t ← getConvTactic s + addConstInfo s t + return .other { + name := ``Data.ConvTactic, val := .mk { name := t : Data.Tactic} + } #[.code s.getString] + catch + | e => exns := exns.push e + try + let p := whitespace >> categoryParserFn `conv + let stx ← parseStrLit p s + introduceAntiquotes stx + return .code s.getString + catch + | e => exns := exns.push e + throwOrNest m!"Couldn't resolve conv tactic" exns + +open Lean.Parser.Term in +/-- +A reference to an attribute or attribute-application syntax. +-/ +--@[builtin_doc_role] +def attr (xs : TSyntaxArray `inline) : DocM (Inline ElabInline) := do + let s ← onlyCode xs + withRef s do + let mut exns := #[] + + try + let stx ← parseStrLit attributes.fn s + let `(attributes|@[$_attrs,*]) := stx + | throwError "Not `@[attribute]` syntax" + return .other {name := ``Data.Attributes, val := .mk <| Data.Attributes.mk stx} #[ + .code s.getString + ] + catch + | e => exns := exns.push e + + try + let stx ← parseStrLit attrParser.fn s + if stx.getKind == ``Lean.Parser.Attr.simple then + let attrName := stx[0].getId.eraseMacroScopes + unless isAttribute (← getEnv) attrName do + let nameStr := attrName.toString + let threshold := max 2 (nameStr.length / 3) + let attrs := getAttributeNames (← getEnv) |>.toArray |>.filterMap fun x => + let x := x.toString + levenshtein x nameStr threshold |>.map (x, ·) + let attrs := attrs.qsort (fun (_, i) (_, j) => i < j) |>.map (·.1) + let hint ← + if attrs.isEmpty then pure m!"" + else m!"Use a known attribute:".hint attrs (ref? := s) + logErrorAt stx m!"Unknown attribute `{attrName}`{hint}" + + return .other {name := ``Data.Attribute, val := .mk <| Data.Attribute.mk stx} #[ + .code s.getString + ] + catch + | e => exns := exns.push e + + throwOrNest m!"Couldn't parse attributes" exns + +private def optionNameAndVal (stx : Syntax) : DocM (Ident × DataValue) := do + let id := stx[1] + let val := stx[3] + + let val ← + if let some s := val.isStrLit? then pure <| .ofString s + else if let some n := val.isNatLit? then pure <| .ofNat n + else if val matches .atom _ "true" then pure <| .ofBool true + else if val matches .atom _ "false" then pure <| .ofBool false + else throwErrorAt val m!"Invalid option value. Expected a string, a number, `true`, or `false`,\ + but got {val}." + return (⟨id⟩, val) + +open Lean.Parser.Command («set_option») in +/-- +A reference to an option. + +In `` {option}`O` ``, `O` can be either: + * The name of an option (e.g. `pp.all`) + * Syntax to set an option to a particular value (e.g. `set_option pp.all true`) +-/ +--@[builtin_doc_role] +def option (xs : TSyntaxArray `inline) : DocM (Inline ElabInline) := do + let s ← onlyCode xs + withRef s do + let spec : Syntax ⊕ Syntax ← + try + let stx ← parseStrLit «set_option».fn s + pure (Sum.inl stx) + catch + | e1 => + try + -- Here it's important to get the partial syntax in order to add completion info, + -- but then abort processing. + let (stx, err) ← parseStrLit' (nodeFn nullKind identWithPartialTrailingDot.fn) s + addCompletionInfo <| CompletionInfo.option stx[0] + if err then throw e1 else pure (Sum.inr stx[0]) + catch + | e2 => + throwOrNest m!"Expected an option name or a valid `set_option`" #[e1, e2] + match spec with + | .inl stx => + let (id, val) ← optionNameAndVal stx + -- For completion purposes, we discard `val` and any later arguments. We include the first + -- argument (the keyword) for position information in case `id` is `missing`. + addCompletionInfo <| CompletionInfo.option (stx.setArgs (stx.getArgs[*...3])) + let optionName := id.getId.eraseMacroScopes + try + let decl ← getOptionDecl optionName + pushInfoLeaf <| .ofOptionInfo { stx := id, optionName, declName := decl.declName } + validateOptionValue optionName decl val + + return .other {name := ``Data.SetOption, val := .mk <| Data.SetOption.mk stx} #[ + .code s.getString + ] + catch + | e => + let ref := e.getRef + let ref ← if ref.isMissing then getRef else pure ref + logErrorAt ref e.toMessageData + return .code s.getString + | .inr stx => + let optionName := stx.getId.eraseMacroScopes + try + let decl ← getOptionDecl optionName + pushInfoLeaf <| .ofOptionInfo { stx, optionName, declName := decl.declName } + + return .other {name := ``Data.Option, val := .mk <| Data.Option.mk optionName} #[ + .code s.getString + ] + catch + | e => + let ref := e.getRef + let ref ← if ref.isMissing then getRef else pure ref + logErrorAt ref e.toMessageData + return .code s.getString + +/-- +Checks whether a syntax descriptor's value contains the given atom. +-/ +private partial def containsAtom (e : Expr) (atom : String) : MetaM Bool := do + let rec attempt (p : Expr) (tryWhnf : Bool) : MetaM Bool := do + match p.getAppFnArgs with + | (``ParserDescr.node, #[_, _, p]) => containsAtom p atom + | (``ParserDescr.trailingNode, #[_, _, _, p]) => containsAtom p atom + | (``ParserDescr.unary, #[.app _ (.lit (.strVal _)), p]) => containsAtom p atom + | (``ParserDescr.binary, #[.app _ (.lit (.strVal "andthen")), p, _]) => containsAtom p atom + | (``ParserDescr.nonReservedSymbol, #[.lit (.strVal tk), _]) => pure (tk.trim == atom) + | (``ParserDescr.symbol, #[.lit (.strVal tk)]) => pure (tk.trim == atom) + | (``Parser.withAntiquot, #[_, p]) => containsAtom p atom + | (``Parser.leadingNode, #[_, _, p]) => containsAtom p atom + | (``HAndThen.hAndThen, #[_, _, _, _, p1, p2]) => + containsAtom p1 atom <||> containsAtom p2 atom + | (``Parser.nonReservedSymbol, #[.lit (.strVal tk), _]) => pure (tk.trim == atom) + | (``Parser.symbol, #[.lit (.strVal tk)]) => pure (tk.trim == atom) + | (``Parser.symbol, #[_nonlit]) => pure false + | (``Parser.withCache, #[_, p]) => containsAtom p atom + | _ => if tryWhnf then attempt (← Meta.whnf p) false else pure false + attempt e true + + +private def withAtom (cat : Name) (atom : String) : DocM (Array Name) := do + let env ← getEnv + let some catContents := (Lean.Parser.parserExtension.getState env).categories.find? cat + | return #[] + let kinds := catContents.kinds + let mut found := #[] + for (k, _) in kinds do + if let some d := env.find? k |>.bind (·.value?) then + if (← containsAtom d atom) then + found := found.push k + return found + +private def kwImpl (cat : Ident := mkIdent .anonymous) (of : Ident := mkIdent .anonymous) + (suggest : Bool) + (xs : TSyntaxArray `inline) : DocM (Inline ElabInline) := do + let s ← onlyCode xs + let atom := s.getString + let env ← getEnv + let parsers := Lean.Parser.parserExtension.getState env + let cat' := cat.getId + let of' ← do + let x := of.getId + if x.isAnonymous then pure x else realizeGlobalConstNoOverloadWithInfo of + let cats ← + if cat'.isAnonymous then + pure parsers.categories.toArray + else + if let some catImpl := parsers.categories.find? cat' then + pure #[(cat', catImpl)] + else throwError m!"Syntax category `{cat'}` not found" + + let mut candidates := #[] + for (catName, category) in cats do + if !of'.isAnonymous then + if category.kinds.contains of' then + if let some d := env.find? of' |>.bind (·.value?) then + if (← containsAtom d atom) then + candidates := candidates.push (catName, of') + else + let which ← withAtom catName atom + candidates := candidates ++ (which.map (catName, ·)) + + if h : candidates.size = 0 then + logErrorAt s m!"No syntax found with atom `{atom}`" + return .code atom + else if h : candidates.size > 1 then + let choices := .andList (candidates.map (fun (c, k) => m!"{.ofConstName k} : {c}")).toList + let catSuggs := categorySuggestions cat' candidates + let ofSuggs ← ofSuggestions of' candidates + let hintText := + if catSuggs.isEmpty then + if ofSuggs.isEmpty then m!"" + else m!"Specify the syntax kind:" + else + if ofSuggs.isEmpty then m!"Specify the category:" + else m!"Specify the category or syntax kind:" + + let range? := + match ← getRef with + | `(inline|role{$name $args*}[$_]) => + (mkNullNode (#[name] ++ args)).getRange? + | _ => none + + let hint ← makeHint hintText (ofSuggs ++ catSuggs) + + logErrorAt s m!"Multiple syntax entries found with atom `{atom}`: {choices}{hint.getD m!""}" + return .code atom + else + let (catName, k) := candidates[0] + addConstInfo s k + if of'.isAnonymous && suggest then + let k' ← unresolveNameGlobalAvoidingLocals k + if let some h ← makeHint m!"Specify the syntax kind:" #[s!" (of := {k'})"] then + logInfo h + + return .other {name := ``Data.Atom, val := .mk (Data.Atom.mk k catName)} #[.code atom] +where + categorySuggestions (c candidates) := Id.run do + if c.isAnonymous then + let mut counts : NameMap Nat := {} + for (cat, _) in candidates do + counts := counts.alter cat (some <| 1 + ·.getD 0) + counts := counts.filter fun _ n => n == 1 + let sorted := counts.keys.toArray.qsort (fun x y => x.toString < y.toString) + return sorted.map (s!" (cat := {·})") + else return #[] + ofSuggestions (o candidates) : DocM (Array String):= do + if o.isAnonymous then + let sorted := candidates |>.map (·.2) |>.qsort (fun x y => x.toString < y.toString) + sorted.mapM fun k => do + let k ← unresolveNameGlobalAvoidingLocals k + pure s!" (of := {k})" + else return #[] + makeHint (hintText) (suggestions : Array String) : DocM (Option MessageData) := do + let range? := + match ← getRef with + | `(inline|role{$name $args*}[$_]) => + (mkNullNode (#[name] ++ args)).getRange? + | _ => none + if let some ⟨b, e⟩ := range? then + let str := (← getFileMap).source.extract b e + let str := if str.startsWith "kw?" then "kw" ++ str.drop 3 else str + let stx := Syntax.mkStrLit str (info := .synthetic b e (canonical := true)) + let suggs := suggestions.map (fun (s : String) => {suggestion := str ++ s}) + some <$> MessageData.hint hintText suggs (ref? := some stx) + else pure none + +/-- +A reference to a particular syntax kind, via one of its atoms. + +It is an error if the syntax kind can't be automatically determined to contain the atom, or if +multiple syntax kinds contain it. If the parser for the syntax kind is sufficiently complex, +detection may fail. + +Specifying the category or kind using the named arguments `cat` and `of` can narrow down the +process. + +Use `kw?` to receive a suggestion of a specific kind. +-/ +--@[builtin_doc_role] +def kw (cat : Ident := mkIdent .anonymous) (of : Ident := mkIdent .anonymous) + (xs : TSyntaxArray `inline) : DocM (Inline ElabInline) := + kwImpl (cat := cat) (of := of) false xs + +@[inherit_doc kw /-, builtin_doc_role -/] +def kw? (cat : Ident := mkIdent .anonymous) (of : Ident := mkIdent .anonymous) + (xs : TSyntaxArray `inline) : DocM (Inline ElabInline) := + kwImpl (cat := cat) (of := of) true xs + +private def validateCat (x : Ident) : DocM Bool := do + let c := x.getId + let parsers := parserExtension.getState (← getEnv) + if parsers.categories.contains c then + return true + else + let allCats := parsers.categories.toArray.map (toString ·.1) |>.qsort + let allCats := allCats.map (fun c => { suggestion := c }) + let h ← MessageData.hint m!"Use a syntax category name:" allCats (ref? := x) + logErrorAt x m!"Unknown syntax category `{c}`{h}" + return false + +/-- +A reference to a syntax category. +-/ +--@[builtin_doc_role] +def syntaxCat (xs : TSyntaxArray `inline) : DocM (Inline ElabInline) := do + let s ← onlyCode xs + let x ← parseStrLit rawIdentFn s + let c := x.getId + if (← validateCat ⟨x⟩) then + return .other {name := ``Data.SyntaxCat, val := .mk (Data.SyntaxCat.mk c)} #[.code (toString c)] + else + return .code (toString c) + +/-- +A description of syntax in the provided category. +-/ +--@[builtin_doc_role] +def «syntax» (cat : Ident) (xs : TSyntaxArray `inline) : DocM (Inline ElabInline) := do + let s ← onlyCode xs + if (← validateCat cat) then + let cat := cat.getId + let stx ← parseStrLit (categoryParserFn cat) s + introduceAntiquotes stx + return .other {name := ``Data.Syntax, val := .mk (Data.Syntax.mk cat stx)} #[.code s.getString] + else + return .code s.getString + +/-- +A metavariable to be discussed in the remainder of the docstring. + +There are two syntaxes that can be used: + * `` {given}`x` `` establishes `x`'s type as a metavariable. + * `` {given}`x : A`` uses `A` as the type for metavariable `x`. +-/ +--@[builtin_doc_role] +def given (xs : TSyntaxArray `inline) : DocM (Inline ElabInline) := do + let s ← onlyCode xs + let p : ParserFn := whitespace >> nodeFn nullKind (identFn >> optionalFn (symbolFn ":" >> termParser.fn)) + let stx ← parseStrLit p s + let x := stx[0] + let ty := stx[1][1] + let ty' ← + if !ty.isMissing then elabType ty + else + withoutErrToSorry <| Meta.mkFreshExprMVar none + let lctx ← do + let lctx ← getLCtx + let fv ← mkFreshFVarId + let lctx := lctx.mkLocalDecl fv x.getId ty' + addTermInfo' x (.fvar fv) (lctx? := some lctx) (isBinder := true) (expectedType? := some ty') + pure lctx + modify (fun st => { st with lctx }) + pure .empty + +private def firstToken? (stx : Syntax) : Option Syntax := + stx.find? fun + | .ident info .. => usable info + | .atom info .. => usable info + | _ => false +where + usable + | .original .. => true + | _ => false + +private builtin_initialize + leanOutputExt : EnvExtension (NameMap (Array (MessageSeverity × String))) ← + registerEnvExtension (asyncMode := .local) (pure {}) + +/-- +Elaborates a sequence of Lean commands as examples. + +To make examples self-contained, elaboration ignores the surrouncing section scopes. Modifications +to the environment are preserved during a single documentation comment, and discarded afterwards. + +The named argument `name` allows a name to be assigned to the code block; any messages created by +elaboration are saved under this name. + +The flags `error` and `warning` indicate that an error or warning is expected in the code. +-/ +--@[builtin_doc_code_block] +def lean (name : Option Ident := none) (error warning : flag false) (code : StrLit) : DocM (Block ElabInline ElabBlock) := do + let text ← getFileMap + let env ← getEnv + let p := andthenFn whitespace (categoryParserFnImpl `command) + -- TODO fallback for non-original syntax + let pos := code.raw.getPos? true |>.get! + let endPos := code.raw.getTailPos? true |>.get! + let endPos := if endPos ≤ text.source.endPos then endPos else text.source.endPos + let ictx := + mkInputContext text.source (← getFileName) + (endPos := endPos) (endPos_valid := by simp only [endPos]; split <;> simp [*]) + let cctx : Command.Context := {fileName := ← getFileName, fileMap := text, snap? := none, cancelTk? := none} + let scopes := (← get).scopes + let mut cmdState : Command.State := { env, maxRecDepth := ← MonadRecDepth.getMaxRecDepth, scopes } + let mut pstate : Parser.ModuleParserState := {pos := pos, recovering := false} + let mut cmds := #[] + repeat + let scope := cmdState.scopes.head! + let pmctx := { env := cmdState.env, options := scope.opts, currNamespace := scope.currNamespace, openDecls := scope.openDecls } + let (cmd, ps', messages) := Parser.parseCommand ictx pmctx pstate cmdState.messages + cmds := cmds.push cmd + pstate := ps' + cmdState := { cmdState with messages := messages } + cmdState ← runCommand (Command.elabCommand cmd) cmd cctx cmdState + if Parser.isTerminalCommand cmd then break + setEnv cmdState.env + modify fun st => { st with scopes := cmdState.scopes } + for t in cmdState.infoState.trees do + pushInfoTree t + -- TODO Nice highlighted code + let mut output := #[] + for msg in cmdState.messages.toArray do + let b := text.ofPosition msg.pos + let e := msg.endPos |>.map text.ofPosition |>.getD (text.source.next b) + let msgStr := text.source.extract b e + let msgStx := Syntax.mkStrLit msgStr (info := .synthetic b e (canonical := true)) + unless msg.isSilent do + if name.isSome then output := output.push (msg.severity, ← msg.data.toString) + if msg.severity == .error && !error then + let hint ← flagHint m!"The `+error` flag indicates that errors are expected:" #[" +error"] + logErrorAt msgStx m!"Unexpected error:{indentD msg.data}{hint.getD m!""}" + if msg.severity == .warning && !warning then + let hint ← flagHint m!"The `+error` flag indicates that warnings are expected:" #[" +warning"] + logErrorAt msgStx m!"Unexpected warning:{indentD msg.data}{hint.getD m!""}" + else + withRef msgStx <| log msg.data (severity := .information) (isSilent := true) + if let some x := name then + modifyEnv (leanOutputExt.modifyState · (·.insert x.getId output)) + pure (Block.code (toString (mkNullNode cmds))) +where + runCommand (act : Command.CommandElabM Unit) (stx : Syntax) + (cctx : Command.Context) (cmdState : Command.State) : + DocM Command.State := do + let (output, cmdState) ← + match (← liftM <| IO.FS.withIsolatedStreams <| EIO.toIO' <| (act.run cctx).run cmdState) with + | (output, .error e) => Lean.logError e.toMessageData; pure (output, cmdState) + | (output, .ok ((), cmdState)) => pure (output, cmdState) + + if output.trim.isEmpty then return cmdState + + let log : MessageData → Command.CommandElabM Unit := + if let some tok := firstToken? stx then logInfoAt tok + else logInfo + + match (← liftM <| EIO.toIO' <| ((log output).run cctx).run cmdState) with + | .error _ => pure cmdState + | .ok ((), cmdState) => pure cmdState + + flagHint (hintText) (suggestions : Array String) : DocM (Option MessageData) := do + let range? := + match ← getRef with + | `(block|```$name $args* | $s ```) => + (mkNullNode (#[name] ++ args)).getRange? + | `(inline|role{$name $args*}[$_]) => + (mkNullNode (#[name] ++ args)).getRange? + | _ => none + if let some ⟨b, e⟩ := range? then + let str := (← getFileMap).source.extract b e + let str := if str.startsWith "kw?" then "kw" ++ str.drop 3 else str + let stx := Syntax.mkStrLit str (info := .synthetic b e (canonical := true)) + let suggs := suggestions.map (fun (s : String) => {suggestion := str ++ s}) + some <$> MessageData.hint hintText suggs (ref? := some stx) + else pure none + +/-- +Displays output from a named Lean code block. +-/ +--@[builtin_doc_code_block] +def output (name : Ident) (severity : Option (WithSyntax MessageSeverity) := none) (code : StrLit) : DocM (Block ElabInline ElabBlock) := do + let allOut := leanOutputExt.getState (← getEnv) + let some outs := allOut.find? name.getId + | let possible := allOut.keysArray.map ({suggestion := ·.toString}) + let h ← MessageData.hint m!"Use one of the named blocks:" possible + logErrorAt name m!"Output from block `{name.getId}` not found{h}" + return .code code.getString + let codeStr := code.getString + for (sev, out) in outs do + if out.trim == codeStr.trim then + if let some s := severity then + if s.val != sev then + let sevName := + match sev with + | .error => ``MessageSeverity.error + | .warning => ``MessageSeverity.warning + | .information => ``MessageSeverity.information + let sevName ← unresolveNameGlobalAvoidingLocals sevName + let h ← MessageData.hint m!"Update severity:" #[{suggestion := sevName.toString}] (ref? := some s.stx) + logErrorAt s.stx m!"Mismatched severity. Message has severity `{sev}`.{h}" + return .code codeStr + let outs := sortByDistance codeStr outs + let h ← m!"Use one of the outputs:".hint (outs.map (withNl ·.2)) (ref? := code) + logErrorAt code m!"Output not found.{h}" + return .code codeStr +where + withNl (s : String) := + if s.endsWith "\n" then s else s ++ "\n" + + sortByDistance {α} (target : String) (strings : Array (α × String)) : Array (α × String) := + let withDistance := strings.map fun (x, s) => + let d := levenshtein target s target.length + (x, s, d.getD target.length) + withDistance.qsort (fun (_, _, d1) (_, _, d2) => d1 < d2) |>.map fun (x, s, _) => (x, s) + + +/-- +Treats the provided term as Lean syntax in the documentation's scope. +-/ +--@[builtin_doc_role lean] +def leanTerm (xs : TSyntaxArray `inline) : DocM (Inline ElabInline) := do + let s ← onlyCode xs + let p : ParserFn := whitespace >> termParser.fn + let stx ← parseStrLit p s + discard <| withoutErrToSorry <| elabTerm stx none + pure (.code s.getString) + +/-- +Opens a namespace in the remainder of the documentation comment. + +The `+scoped` flag causes scoped instances and attributes to be activated, but no identifiers are +brought into scope. The named argument `only`, which can be repeated, specifies a subset of names to +bring into scope from the namespace. +-/ +--@[builtin_doc_command] +def «open» (n : Ident) («scoped» : flag false) («only» : many Ident) : DocM (Block ElabInline ElabBlock) := do + let nss ← resolveNamespace n + if only.isEmpty then + for ns in nss do + unless «scoped» do + let d := .simple ns [] + modify fun st => { st with openDecls := d :: st.openDecls } + activateScoped ns + else + if «scoped» then + throwError "`scoped` and `only` cannot be used together" + for idStx in only do + let declName ← OpenDecl.resolveNameUsingNamespacesCore nss idStx + let d := .explicit idStx.getId declName + modify fun st => { st with openDecls := d :: st.openDecls } + if (← getInfoState).enabled then + addConstInfo idStx declName + return .empty + +/-- +Sets the specified option to the specified value for the remainder of the comment. +-/ +--@[builtin_doc_command] +def «set_option» (option : Ident) (value : DataValue) : DocM (Block ElabInline ElabBlock) := do + addCompletionInfo <| CompletionInfo.option option + let optionName := option.getId + let decl ← withRef option <| getOptionDecl optionName + pushInfoLeaf <| .ofOptionInfo { stx := option, optionName, declName := decl.declName } + validateOptionValue optionName decl value + let o ← getOptions + modify fun s => { s with options := o.insert optionName value } + return .empty + +/-- +Constructs a link to the Lean langauge reference. Two positional arguments are expected: + * `domain` should be one of the valid domains, such as `section`. + * `name` should be the content's canonical name in the domain. +-/ +--@[builtin_doc_role] +def manual (domain : Ident) (name : String) (content : TSyntaxArray `inline) : DocM (Inline ElabInline) := do + let domStr := domain.getId.toString + if domStr ∉ manualDomains then + let h ← MessageData.hint "Use one of the valid documentation domains:" + (manualDomains.map ({ suggestion := · })).toArray + (ref? := some domain) + throwErrorAt domain m!"Invalid documentation domain.{h}" + match manualLink domStr name with + | .ok url => return .link (← content.mapM elabInline) url + | .error e => throwError e + +/-- +Suggests the `name` role, if applicable. +-/ +--@[builtin_doc_code_suggestions] +def suggestName (code : StrLit) : DocM (Array CodeSuggestion) := do + let stx ← parseStrLit identFn code + try + discard <| realizeGlobalConstNoOverload stx + return #[.mk ``name none none] + catch + | _ => + if let some (_, []) := (← resolveLocalName stx.getId) then + return #[.mk ``name none none] + return #[] + +/-- +Suggests the `lean` role, if applicable. +-/ +--@[builtin_doc_code_suggestions] +def suggestLean (code : StrLit) : DocM (Array CodeSuggestion) := do + let p : ParserFn := whitespace >> termParser.fn + try + let stx ← parseStrLit p code + discard <| withoutErrToSorry <| elabTerm stx none + return #[.mk ``lean none none] + catch | _ => return #[] + +/-- +Suggests the `tactic` role, if applicable. +-/ +--@[builtin_doc_code_suggestions] +def suggestTactic (code : StrLit) : DocM (Array CodeSuggestion) := do + let asString := code.getString + let asName := asString.toName + let allTactics ← Tactic.Doc.allTacticDocs + let found := allTactics.filter fun tac => tac.userName == asString || tac.internalName == asName + if found.size = 1 then return #[.mk ``tactic none none] + else + let p := whitespace >> categoryParserFn `tactic + try + discard <| parseStrLit p code + return #[.mk ``tactic none none] + catch | _ => return #[] + +open Lean.Parser.Term in +/-- +Suggests the `attr` role, if applicable. +-/ +--@[builtin_doc_code_suggestions] +def suggestAttr (code : StrLit) : DocM (Array CodeSuggestion) := do + try + let stx ← parseStrLit attributes.fn code + let `(attributes|@[$_attrs,*]) := stx + | return #[] + return #[.mk ``attr none none] + catch + | _ => pure () + try + discard <| parseStrLit attrParser.fn code + return #[.mk ``attr none none] + catch + | _ => pure () + return #[] + +open Lean.Parser.Command in +/-- +Suggests the `option` role, if applicable. +-/ +--@[builtin_doc_code_suggestions] +def suggestOption (code : StrLit) : DocM (Array CodeSuggestion) := do + try + discard <| parseStrLit Command.«set_option».fn code + return #[CodeSuggestion.mk ``option none none] + catch + | _ => + try + let stx ← parseStrLit rawIdentFn code + let name := stx.getId.eraseMacroScopes + discard <| getOptionDecl name + return #[CodeSuggestion.mk ``option none none] + catch + | _ => + return #[] + +/-- +Suggests the `kw` role, if applicable. +-/ +--@[builtin_doc_code_suggestions] +def suggestKw (code : StrLit) : DocM (Array CodeSuggestion) := do + let atom := code.getString + let env ← getEnv + let parsers := Lean.Parser.parserExtension.getState env + let cats := parsers.categories.toArray + + let mut candidates := #[] + for (catName, _) in cats do + let which ← withAtom catName atom + candidates := candidates ++ (which.map (catName, ·)) + + candidates.mapM fun (cat, of) => do + return .mk ``kw (some s!"(of := {of})") (some s!"(in `{cat}`)") + +/-- +Suggests the `syntaxCat` role, if applicable. +-/ +--@[builtin_doc_code_suggestions] +def suggestCat (code : StrLit) : DocM (Array CodeSuggestion) := do + let env ← getEnv + let parsers := Lean.Parser.parserExtension.getState env + if parsers.categories.contains code.getString.toName then + return #[.mk ``syntaxCat none none] + else + return #[] + +/-- +Suggests the `syntax` role, if applicable. +-/ +--@[builtin_doc_code_suggestions] +def suggestSyntax (code : StrLit) : DocM (Array CodeSuggestion) := do + let env ← getEnv + let parsers := Lean.Parser.parserExtension.getState env + let cats := parsers.categories.toArray + + let mut candidates := #[] + for (catName, _) in cats do + try + discard <| parseStrLit (whitespace >> (categoryParser catName 0).fn) code + candidates := candidates.push catName + catch | _ => pure () + + candidates.mapM fun cat => do + return .mk ``«syntax» (some s!"{cat}") none diff --git a/src/Lean/Elab/ErrorExplanation.lean b/src/Lean/Elab/ErrorExplanation.lean index 3b18b9a4b2..a79934e1e6 100644 --- a/src/Lean/Elab/ErrorExplanation.lean +++ b/src/Lean/Elab/ErrorExplanation.lean @@ -118,7 +118,7 @@ open Command in throwErrorAt id m!"Invalid name `{name}`: Error explanation names must have two components" ++ .note m!"The first component of an error explanation name identifies the package from \ which the error originates, and the second identifies the error itself." - validateDocComment docStx + runTermElabM fun _ => validateDocComment docStx let doc ← getDocStringText docStx if errorExplanationExt.getState (← getEnv) |>.contains name then throwErrorAt id m!"Cannot add explanation: An error explanation already exists for `{name}`" diff --git a/src/Lean/Elab/Inductive.lean b/src/Lean/Elab/Inductive.lean index 6bdb830505..e265b1a293 100644 --- a/src/Lean/Elab/Inductive.lean +++ b/src/Lean/Elab/Inductive.lean @@ -28,7 +28,7 @@ private def inductiveSyntaxToView (modifiers : Modifiers) (decl : Syntax) : Term let modifiers := if isClass then modifiers.addAttr { name := `class } else modifiers let (binders, type?) := expandOptDeclSig decl[2] let declId := decl[1] - let ⟨name, declName, levelNames⟩ ← Term.expandDeclId (← getCurrNamespace) (← Term.getLevelNames) declId modifiers + let ⟨name, declName, levelNames, docString?⟩ ← Term.expandDeclId (← getCurrNamespace) (← Term.getLevelNames) declId modifiers addDeclarationRangesForBuiltin declName modifiers.stx decl let ctors ← decl[4].getArgs.mapM fun ctor => withRef ctor do /- @@ -41,7 +41,8 @@ private def inductiveSyntaxToView (modifiers : Modifiers) (decl : Syntax) : Term if let some leadingDocComment := ctor[0].getOptional? then if ctorModifiers.docString?.isSome then logErrorAt leadingDocComment "Duplicate doc string" - ctorModifiers := { ctorModifiers with docString? := some ⟨leadingDocComment⟩ } + ctorModifiers := { ctorModifiers with + docString? := some (⟨leadingDocComment⟩, doc.verso.get (← getOptions)) } if ctorModifiers.isPrivate && modifiers.isPrivate then let hint ← do let .original .. := modifiersStx.getHeadInfo | pure .nil @@ -63,7 +64,6 @@ private def inductiveSyntaxToView (modifiers : Modifiers) (decl : Syntax) : Term let ctorName := declName ++ ctorName let ctorName ← withRef ctor[3] <| applyVisibility ctorModifiers ctorName let (binders, type?) := expandOptDeclSig ctor[4] - addDocString' ctorName ctorModifiers.docString? addDeclarationRangesFromSyntax ctorName ctor ctor[3] if modifiers.isMeta then modifyEnv (addMeta · ctorName) @@ -84,6 +84,7 @@ private def inductiveSyntaxToView (modifiers : Modifiers) (decl : Syntax) : Term declId, modifiers, isClass, declName, levelNames binders, type?, ctors computedFields + docString? } private def isInductiveFamily (numParams : Nat) (indFVar : Expr) : TermElabM Bool := do diff --git a/src/Lean/Elab/InfoTree/Main.lean b/src/Lean/Elab/InfoTree/Main.lean index b9f331472b..0e245aa192 100644 --- a/src/Lean/Elab/InfoTree/Main.lean +++ b/src/Lean/Elab/InfoTree/Main.lean @@ -273,6 +273,11 @@ def Info.updateContext? : Option ContextInfo → Info → Option ContextInfo | some ctx, ofTacticInfo i => some { ctx with mctx := i.mctxAfter } | ctx?, _ => ctx? +def PartialContextInfo.format (ctx : PartialContextInfo) : Format := + match ctx with + | .commandCtx _ => "command" + | .parentDeclCtx n => s!"parent[{n}]" + partial def InfoTree.format (tree : InfoTree) (ctx? : Option ContextInfo := none) : IO Format := do match tree with | hole id => return .nestD f!"• ?{toString id.name}" diff --git a/src/Lean/Elab/InheritDoc.lean b/src/Lean/Elab/InheritDoc.lean index 110f0f7ac2..3d39a6d714 100644 --- a/src/Lean/Elab/InheritDoc.lean +++ b/src/Lean/Elab/InheritDoc.lean @@ -6,10 +6,9 @@ Authors: Mario Carneiro module prelude -public import Lean.Elab.InfoTree.Main -public import Lean.DocString.Extension - -public section +import Lean.Elab.InfoTree.Main +import Lean.DocString.Extension +import Lean.DocString.Add namespace Lean @@ -19,7 +18,7 @@ Uses documentation from a specified declaration. `@[inherit_doc decl]` is used to inherit the documentation from the declaration `decl`. -/ @[builtin_doc] -builtin_initialize +public builtin_initialize registerBuiltinAttribute { name := `inherit_doc descr := "inherit documentation from a specified declaration" @@ -33,9 +32,14 @@ builtin_initialize let declName ← Elab.realizeGlobalConstNoOverloadWithInfo id if (← findSimpleDocString? (← getEnv) decl (includeBuiltin := false)).isSome then logWarning m!"{← mkConstWithLevelParams decl} already has a doc string" - let some doc ← findSimpleDocString? (← getEnv) declName + let some doc ← findInternalDocString? (← getEnv) declName | logWarningAt id m!"{← mkConstWithLevelParams declName} does not have a doc string" - -- This docstring comes from the environment, so documentation links have already been validated - addDocStringCore decl doc + match doc with + | .inl md => + -- This docstring comes from the environment, so documentation links have already been validated + addDocStringCore decl md + | .inr verso => + addVersoDocStringCore decl verso | _ => throwError "Invalid `[inherit_doc]` attribute syntax" + applicationTime := AttributeApplicationTime.afterCompilation } diff --git a/src/Lean/Elab/LetRec.lean b/src/Lean/Elab/LetRec.lean index f9ac720c42..048d048ab8 100644 --- a/src/Lean/Elab/LetRec.lean +++ b/src/Lean/Elab/LetRec.lean @@ -54,11 +54,12 @@ private def mkLetRecDeclView (letRec : Syntax) : TermElabM LetRecView := do if decls.any fun decl => decl.declName == declName then withRef declId do throwError "`{.ofConstName declName}` has already been declared" + let binders := decl[1] checkNotAlreadyDeclared declName applyAttributesAt declName attrs AttributeApplicationTime.beforeElaboration - addDocString' declName docStr? + addDocString' declName binders docStr? addDeclarationRangesFromSyntax declName decl declId - let binders := decl[1].getArgs + let binders := binders.getArgs let typeStx := expandOptType declId decl[2] let (type, binderIds) ← elabBindersEx binders fun xs => do let type ← elabType typeStx diff --git a/src/Lean/Elab/MutualDef.lean b/src/Lean/Elab/MutualDef.lean index 42004d2ff4..a65bd1566f 100644 --- a/src/Lean/Elab/MutualDef.lean +++ b/src/Lean/Elab/MutualDef.lean @@ -206,7 +206,7 @@ private def elabHeaders (views : Array DefView) (expandedDeclIds : Array ExpandD -- Can we reuse the result for a body? For starters, all headers (even those below the body) -- must be reusable let mut reuseBody := views.all (·.headerSnap?.any (·.old?.isSome)) - for view in views, ⟨shortDeclName, declName, levelNames⟩ in expandedDeclIds, + for view in views, ⟨shortDeclName, declName, levelNames, docString?⟩ in expandedDeclIds, tacPromise in tacPromises, bodyPromise in bodyPromises do let mut reusableResult? := none let mut oldBodySnap? := none @@ -1048,6 +1048,7 @@ def pushMain (preDefs : Array PreDefinition) (sectionVars : Array Expr) (mainHea ref := getDeclarationSelectionRef header.ref kind := header.kind declName := header.declName + binders := header.binders levelParams := [], -- we set it later modifiers := header.modifiers type, value, termination @@ -1071,6 +1072,7 @@ def pushLetRecs (preDefs : Array PreDefinition) (letRecClosures : List LetRecClo ref := c.ref declName := c.toLift.declName levelParams := [] -- we set it later + binders := mkNullNode -- No docstrings, so we don't need these modifiers := { modifiers with attrs := c.toLift.attrs } kind, type, value, termination := c.toLift.termination @@ -1297,8 +1299,6 @@ where headers.any (·.modifiers.attrs.any (·.name == `expose)))) do let headers := headers.map fun header => { header with modifiers.attrs := header.modifiers.attrs.filter (!·.name ∈ [`expose, `no_expose]) } - for view in views, funFVar in funFVars do - addLocalVarInfo view.declId funFVar let values ← try let values ← elabFunValues headers vars sc Term.synthesizeSyntheticMVarsNoPostponing @@ -1323,6 +1323,9 @@ where let whereFinally ← declValToWhereFinally header.value let exprsWithHoles := (exprsWithHoles.getD header.declName #[]).push { ref := header.ref, expr := value } fillHolesFromWhereFinally header.declName exprsWithHoles whereFinally + -- Compilation should take place without unused section vars, but all section vars should be + -- present when elaborating documentation. + let docCtx := (← getLCtx, ← getLocalInstances) (if headers.all (·.kind.isTheorem) && !deprecated.oldSectionVars.get (← getOptions) then -- do not repeat checks already done in `elabFunValues` withHeaderSecVars (check := false) vars sc headers @@ -1343,7 +1346,10 @@ where let preDefs ← fixLevelParams preDefs scopeLevelNames allUserLevelNames for preDef in preDefs do trace[Elab.definition] "after eraseAuxDiscr, {preDef.declName} : {preDef.type} :=\n{preDef.value}" - addPreDefinitions preDefs + addPreDefinitions docCtx preDefs + for view in views, funFVar in funFVars do + addLocalVarInfo view.declId funFVar + processDeriving (headers : Array DefViewElabHeader) := do for header in headers, view in views do if let some classStxs := view.deriving? then diff --git a/src/Lean/Elab/MutualInductive.lean b/src/Lean/Elab/MutualInductive.lean index 6c249981dc..5cbde20dd6 100644 --- a/src/Lean/Elab/MutualInductive.lean +++ b/src/Lean/Elab/MutualInductive.lean @@ -19,7 +19,9 @@ public import Lean.Elab.DefView public import Lean.Elab.DeclUtil public import Lean.Elab.Deriving.Basic public import Lean.Elab.DeclarationRange +public import Lean.Parser.Command import Lean.Elab.ComputedFields +import Lean.DocString.Extension import Lean.Meta.Constructions.CtorIdx import Lean.Meta.Constructions.CtorElim import Lean.Meta.IndPredBelow @@ -110,6 +112,8 @@ structure InductiveView where ctors : Array CtorView computedFields : Array ComputedFieldView derivingClasses : Array DerivingClassView + /-- The declaration docstring, and whether it's Verso -/ + docString? : Option (TSyntax ``Lean.Parser.Command.docComment × Bool) deriving Inhabited /-- Elaborated header for an inductive type before fvars for each inductive are added to the local context. -/ @@ -1071,6 +1075,18 @@ private def elabInductiveViewsPostprocessing (views : Array InductiveView) (res for view in views do withRef view.declId <| Term.applyAttributesAt view.declName view.modifiers.attrs .afterTypeChecking for elab' in finalizers do elab'.finalize applyDerivingHandlers views + -- Docstrings are added during postprocessing to allow them to have checked references to + -- the type and its constructors, but before attributes to enable e.g. `@[inherit_doc X]` + runTermElabM fun _ => Term.withDeclName view0.declName do withRef ref do + for view in views do + withRef view.declId do + if let some (doc, verso) := view.docString? then + addDocStringOf verso view.declName view.binders doc + for ctor in view.ctors do + withRef ctor.declId do + if let some (doc, verso) := ctor.modifiers.docString? then + addDocStringOf verso ctor.declName ctor.binders doc + runTermElabM fun _ => Term.withDeclName view0.declName do withRef ref do for view in views do withRef view.declId <| Term.applyAttributesAt view.declName view.modifiers.attrs .afterCompilation diff --git a/src/Lean/Elab/Open.lean b/src/Lean/Elab/Open.lean index 04326b8064..54149742e8 100644 --- a/src/Lean/Elab/Open.lean +++ b/src/Lean/Elab/Open.lean @@ -34,7 +34,7 @@ instance : MonadResolveName (M (m := m)) where getCurrNamespace := return (← get).currNamespace getOpenDecls := return (← get).openDecls -def resolveId (ns : Name) (idStx : Syntax) : M (m := m) Name := do +def resolveId [MonadResolveName m] (ns : Name) (idStx : Syntax) : m Name := do let declName := ns ++ idStx.getId if (← getEnv).contains declName then return declName @@ -44,7 +44,13 @@ def resolveId (ns : Name) (idStx : Syntax) : M (m := m) Name := do private def addOpenDecl (decl : OpenDecl) : M (m:=m) Unit := modify fun s => { s with openDecls := decl :: s.openDecls } -private def resolveNameUsingNamespacesCore (nss : List Name) (idStx : Syntax) : M (m:=m) Name := do +/-- +Uniquely resolves the identifier `idStx` in the provided namespaces `nss`. + +If the identifier does not indicate a name in exactly one of the namespaces, an exception is thrown. +-/ +def resolveNameUsingNamespacesCore [MonadResolveName m] + (nss : List Name) (idStx : Syntax) : m Name := do let mut exs := #[] let mut result := #[] for ns in nss do @@ -105,7 +111,7 @@ def elabOpenDecl [MonadResolveName m] [MonadInfoTree m] (stx : TSyntax ``Parser. def resolveNameUsingNamespaces [MonadResolveName m] (nss : List Name) (idStx : Ident) : m Name := do StateRefT'.run' (s := { openDecls := (← getOpenDecls), currNamespace := (← getCurrNamespace) }) do - resolveNameUsingNamespacesCore nss idStx + resolveNameUsingNamespacesCore (m := M) nss idStx end OpenDecl diff --git a/src/Lean/Elab/PreDefinition/Basic.lean b/src/Lean/Elab/PreDefinition/Basic.lean index e43e8f56b6..07ed22539f 100644 --- a/src/Lean/Elab/PreDefinition/Basic.lean +++ b/src/Lean/Elab/PreDefinition/Basic.lean @@ -41,6 +41,7 @@ structure PreDefinition where levelParams : List Name modifiers : Modifiers declName : Name + binders : Syntax type : Expr value : Expr termination : TerminationHints @@ -173,7 +174,28 @@ private def checkMeta (preDef : PreDefinition) : TermElabM Unit := do | _, _ => pure () return true -private def addNonRecAux (preDef : PreDefinition) (compile : Bool) (all : List Name) (applyAttrAfterCompilation := true) (cacheProofs := true) (cleanupValue := false) : TermElabM Unit := +/-- +Adds the docstring, if relevant. + +This should be done just after compilation so the predefinition can be executed in examples in its +docstring. If code generation will not occur, then it should be done after adding the declaration +to the environment. +-/ +def addPreDefDocs (docCtx : LocalContext × LocalInstances) (preDef : PreDefinition) : TermElabM Unit := do + if let some (doc, isVerso) := preDef.modifiers.docString? then + withLCtx docCtx.1 docCtx.2 do + addDocStringOf isVerso preDef.declName preDef.binders doc + +/-- +Adds constant info to the definition name. This should occur after executing post-compilation +attributes, in case they have an effect on hovers. +-/ +def addPreDefInfo (preDef : PreDefinition) : TermElabM Unit := do + withSaveInfoContext do -- save new env that includes docstring and constant + addTermInfo' preDef.ref (← mkConstWithLevelParams preDef.declName) (isBinder := true) + + +private def addNonRecAux (docCtx : LocalContext × LocalInstances) (preDef : PreDefinition) (compile : Bool) (all : List Name) (applyAttrAfterCompilation := true) (cacheProofs := true) (cleanupValue := false) : TermElabM Unit := withRef preDef.ref do let preDef ← abstractNestedProofs (cache := cacheProofs) preDef let preDef ← letToHaveType preDef @@ -207,8 +229,6 @@ private def addNonRecAux (preDef : PreDefinition) (compile : Bool) (all : List N | DefKind.def | DefKind.example => mkDefDecl | DefKind.«instance» => if ← Meta.isProp preDef.type then mkThmDecl else mkDefDecl addDecl decl - withSaveInfoContext do -- save new env - addTermInfo' preDef.ref (← mkConstWithLevelParams preDef.declName) (isBinder := true) applyAttributesOf #[preDef] AttributeApplicationTime.afterTypeChecking match preDef.modifiers.computeKind with | .meta => modifyEnv (addMeta · preDef.declName) @@ -220,13 +240,17 @@ private def addNonRecAux (preDef : PreDefinition) (compile : Bool) (all : List N if applyAttrAfterCompilation then enableRealizationsForConst preDef.declName generateEagerEqns preDef.declName + addPreDefDocs docCtx preDef + if applyAttrAfterCompilation then applyAttributesOf #[preDef] AttributeApplicationTime.afterCompilation + addPreDefInfo preDef -def addAndCompileNonRec (preDef : PreDefinition) (all : List Name := [preDef.declName]) (cleanupValue := false) : TermElabM Unit := do - addNonRecAux preDef (compile := true) (all := all) (cleanupValue := cleanupValue) -def addNonRec (preDef : PreDefinition) (applyAttrAfterCompilation := true) (all : List Name := [preDef.declName]) (cacheProofs := true) (cleanupValue := false) : TermElabM Unit := do - addNonRecAux preDef (compile := false) (applyAttrAfterCompilation := applyAttrAfterCompilation) (all := all) (cacheProofs := cacheProofs) (cleanupValue := cleanupValue) +def addAndCompileNonRec (docCtx : LocalContext × LocalInstances) (preDef : PreDefinition) (all : List Name := [preDef.declName]) (cleanupValue := false) : TermElabM Unit := do + addNonRecAux docCtx preDef (compile := true) (all := all) (cleanupValue := cleanupValue) + +def addNonRec (docCtx : LocalContext × LocalInstances) (preDef : PreDefinition) (applyAttrAfterCompilation := true) (all : List Name := [preDef.declName]) (cacheProofs := true) (cleanupValue := false) : TermElabM Unit := do + addNonRecAux docCtx preDef (compile := false) (applyAttrAfterCompilation := applyAttrAfterCompilation) (all := all) (cacheProofs := cacheProofs) (cleanupValue := cleanupValue) /-- Eliminate recursive application annotations containing syntax. These annotations are used by the well-founded recursion module @@ -240,7 +264,10 @@ def eraseRecAppSyntaxExpr (e : Expr) : CoreM Expr := do def eraseRecAppSyntax (preDef : PreDefinition) : CoreM PreDefinition := return { preDef with value := (← eraseRecAppSyntaxExpr preDef.value) } -def addAndCompileUnsafe (preDefs : Array PreDefinition) (safety := DefinitionSafety.unsafe) : TermElabM Unit := do +def addAndCompileUnsafe + (docCtx : LocalContext × LocalInstances) (preDefs : Array PreDefinition) + (safety := DefinitionSafety.unsafe) : + TermElabM Unit := do let preDefs ← preDefs.mapM fun d => eraseRecAppSyntax d withRef preDefs[0]!.ref do let all := preDefs.toList.map (·.declName) @@ -253,18 +280,21 @@ def addAndCompileUnsafe (preDefs : Array PreDefinition) (safety := DefinitionSaf safety, all } addDecl decl - withSaveInfoContext do -- save new env - for preDef in preDefs do - addTermInfo' preDef.ref (← mkConstWithLevelParams preDef.declName) (isBinder := true) applyAttributesOf preDefs AttributeApplicationTime.afterTypeChecking compileDecl decl + for preDef in preDefs do + addPreDefDocs docCtx preDef applyAttributesOf preDefs AttributeApplicationTime.afterCompilation + for preDef in preDefs do + addPreDefInfo preDef return () -def addAndCompilePartialRec (preDefs : Array PreDefinition) : TermElabM Unit := do +def addAndCompilePartialRec + (docCtx : LocalContext × LocalInstances) (preDefs : Array PreDefinition) : + TermElabM Unit := do if preDefs.all shouldGenCodeFor then withEnableInfoTree false do - addAndCompileUnsafe (safety := DefinitionSafety.partial) <| preDefs.map fun preDef => + addAndCompileUnsafe docCtx (safety := DefinitionSafety.partial) <| preDefs.map fun preDef => { preDef with declName := Compiler.mkUnsafeRecName preDef.declName value := preDef.value.replace fun e => match e with diff --git a/src/Lean/Elab/PreDefinition/Main.lean b/src/Lean/Elab/PreDefinition/Main.lean index 427ba06961..141628104e 100644 --- a/src/Lean/Elab/PreDefinition/Main.lean +++ b/src/Lean/Elab/PreDefinition/Main.lean @@ -19,7 +19,9 @@ namespace Lean.Elab open Meta open Term -private def addAndCompilePartial (preDefs : Array PreDefinition) (useSorry := false) : TermElabM Unit := do +private def addAndCompilePartial + (docCtx : LocalContext × LocalInstances) (preDefs : Array PreDefinition) (useSorry := false) : + TermElabM Unit := do for preDef in preDefs do trace[Elab.definition] "processing {preDef.declName}" let all := preDefs.toList.map (·.declName) @@ -29,11 +31,11 @@ private def addAndCompilePartial (preDefs : Array PreDefinition) (useSorry := fa else let msg := m!"failed to compile 'partial' definition `{preDef.declName}`" liftM <| mkInhabitantFor msg xs type - addNonRec { preDef with + addNonRec docCtx { preDef with kind := DefKind.«opaque» value } (all := all) - addAndCompilePartialRec preDefs + addAndCompilePartialRec docCtx preDefs private def isNonRecursive (preDef : PreDefinition) : Bool := Option.isNone $ preDef.value.find? fun @@ -139,7 +141,8 @@ private def betaReduceLetRecApps (preDefs : Array PreDefinition) : MetaM (Array else return preDef -private def addSorried (preDefs : Array PreDefinition) : TermElabM Unit := do +private def addSorried (docCtx : LocalContext × LocalInstances) (preDefs : Array PreDefinition) : + TermElabM Unit := do for preDef in preDefs do unless (← hasConst preDef.declName) do let value ← mkSorry (synthetic := true) preDef.type @@ -160,10 +163,10 @@ private def addSorried (preDefs : Array PreDefinition) : TermElabM Unit := do value } addDecl decl - withSaveInfoContext do -- save new env - addTermInfo' preDef.ref (← mkConstWithLevelParams preDef.declName) (isBinder := true) applyAttributesOf #[preDef] AttributeApplicationTime.afterTypeChecking + addPreDefDocs docCtx preDef applyAttributesOf #[preDef] AttributeApplicationTime.afterCompilation + addPreDefInfo preDef def ensureFunIndReservedNamesAvailable (preDefs : Array PreDefinition) : MetaM Unit := do preDefs.forM fun preDef => @@ -301,7 +304,8 @@ def shouldUseWF (preDefs : Array PreDefinition) : Bool := preDef.termination.decreasingBy?.isSome -def addPreDefinitions (preDefs : Array PreDefinition) : TermElabM Unit := withLCtx {} {} do +def addPreDefinitions (docCtx : LocalContext × LocalInstances) (preDefs : Array PreDefinition) : + TermElabM Unit := withLCtx {} {} do profileitM Exception "process pre-definitions" (← getOptions) do withTraceNode `Elab.def.processPreDef (fun _ => return m!"process pre-definitions") do for preDef in preDefs do @@ -320,12 +324,12 @@ def addPreDefinitions (preDefs : Array PreDefinition) : TermElabM Unit := withLC let preDef ← eraseRecAppSyntax preDefs[0]! ensureEqnReservedNamesAvailable preDef.declName if preDef.modifiers.isNoncomputable then - addNonRec preDef (cleanupValue := true) + addNonRec docCtx preDef (cleanupValue := true) else - addAndCompileNonRec preDef (cleanupValue := true) + addAndCompileNonRec docCtx preDef (cleanupValue := true) preDef.termination.ensureNone "not recursive" else if preDefs.any (·.modifiers.isUnsafe) then - addAndCompileUnsafe preDefs + addAndCompileUnsafe docCtx preDefs preDefs.forM (·.termination.ensureNone "unsafe") else if preDefs.any (·.modifiers.isInferredPartial) then @@ -339,7 +343,7 @@ def addPreDefinitions (preDefs : Array PreDefinition) : TermElabM Unit := withLC isPartial := false if isPartial then - addAndCompilePartial preDefs + addAndCompilePartial docCtx preDefs preDefs.forM (·.termination.ensureNone "partial") continue @@ -349,16 +353,16 @@ def addPreDefinitions (preDefs : Array PreDefinition) : TermElabM Unit := withLC checkTerminationByHints preDefs let termMeasures?s ← elabTerminationByHints preDefs if shouldUseStructural preDefs then - structuralRecursion preDefs termMeasures?s + structuralRecursion docCtx preDefs termMeasures?s else if shouldUsePartialFixpoint preDefs then - partialFixpoint preDefs + partialFixpoint docCtx preDefs else if shouldUseWF preDefs then - wfRecursion preDefs termMeasures?s + wfRecursion docCtx preDefs termMeasures?s else withRef (preDefs[0]!.ref) <| mapError (orelseMergeErrors - (structuralRecursion preDefs termMeasures?s) - (wfRecursion preDefs termMeasures?s)) + (structuralRecursion docCtx preDefs termMeasures?s) + (wfRecursion docCtx preDefs termMeasures?s)) (fun msg => let preDefMsgs := preDefs.toList.map (MessageData.ofExpr $ mkConst ·.declName) m!"fail to show termination for{indentD (MessageData.joinSep preDefMsgs Format.line)}\nwith errors\n{msg}") @@ -370,13 +374,13 @@ def addPreDefinitions (preDefs : Array PreDefinition) : TermElabM Unit := withLC -- try to add as partial definition withOptions (Elab.async.set · false) do try - addAndCompilePartial preDefs (useSorry := true) + addAndCompilePartial docCtx preDefs (useSorry := true) catch _ => -- Compilation failed try again just as axiom s.restore - addSorried preDefs + addSorried docCtx preDefs else if preDefs.all fun preDef => preDef.kind == DefKind.theorem then - addSorried preDefs + addSorried docCtx preDefs catch _ => s.restore builtin_initialize diff --git a/src/Lean/Elab/PreDefinition/Mutual.lean b/src/Lean/Elab/PreDefinition/Mutual.lean index dd5afb5f43..07abc14af3 100644 --- a/src/Lean/Elab/PreDefinition/Mutual.lean +++ b/src/Lean/Elab/PreDefinition/Mutual.lean @@ -30,7 +30,7 @@ where withLocalDecl vals[0]!.bindingName! vals[0]!.binderInfo vals[0]!.bindingDomain! fun x => go (fvars.push x) (vals.map fun val => val.bindingBody!.instantiate1 x) -def addPreDefsFromUnary (preDefs : Array PreDefinition) (preDefsNonrec : Array PreDefinition) +def addPreDefsFromUnary (docCtx : LocalContext × LocalInstances) (preDefs : Array PreDefinition) (preDefsNonrec : Array PreDefinition) (unaryPreDefNonRec : PreDefinition) (cacheProofs := true) : TermElabM Unit := do /- We must remove `implemented_by` attributes from the auxiliary application because @@ -45,11 +45,11 @@ def addPreDefsFromUnary (preDefs : Array PreDefinition) (preDefsNonrec : Array P -- we recognize that below and then do not set @[irreducible] withOptions (allowUnsafeReducibility.set · true) do if unaryPreDefNonRec.declName = preDefs[0]!.declName then - addNonRec preDefNonRec (applyAttrAfterCompilation := false) (cacheProofs := cacheProofs) + addNonRec docCtx preDefNonRec (applyAttrAfterCompilation := false) (cacheProofs := cacheProofs) else withEnableInfoTree false do - addNonRec preDefNonRec (applyAttrAfterCompilation := false) (cacheProofs := cacheProofs) - preDefsNonrec.forM (addNonRec · (applyAttrAfterCompilation := false) (all := declNames) (cacheProofs := cacheProofs)) + addNonRec docCtx preDefNonRec (applyAttrAfterCompilation := false) (cacheProofs := cacheProofs) + preDefsNonrec.forM (addNonRec docCtx · (applyAttrAfterCompilation := false) (all := declNames) (cacheProofs := cacheProofs)) /-- Cleans the right-hand-sides of the predefinitions, to prepare for inclusion in the EqnInfos: diff --git a/src/Lean/Elab/PreDefinition/PartialFixpoint/Main.lean b/src/Lean/Elab/PreDefinition/PartialFixpoint/Main.lean index fe0ce420be..89284ff0fb 100644 --- a/src/Lean/Elab/PreDefinition/PartialFixpoint/Main.lean +++ b/src/Lean/Elab/PreDefinition/PartialFixpoint/Main.lean @@ -78,7 +78,7 @@ private def mkMonoPProd : (hmono₁ hmono₂ : Expr × Expr) → MetaM (Expr × let hmonoProof ← mkAppOptM ``PProd.monotone_mk #[none, none, none, inst₁, inst₂, inst, none, none, hmono1Proof, hmono2Proof] return (← inferType hmonoProof, hmonoProof) -def partialFixpoint (preDefs : Array PreDefinition) : TermElabM Unit := do +def partialFixpoint (docCtx : LocalContext × LocalInstances) (preDefs : Array PreDefinition) : TermElabM Unit := do -- We expect all functions in the clique to have `partial_fixpoint`, `inductive_fixpoint` or `coinductive_fixpoint` syntax let hints := preDefs.filterMap (·.termination.partialFixpoint?) assert! preDefs.size = hints.size @@ -217,8 +217,8 @@ def partialFixpoint (preDefs : Array PreDefinition) : TermElabM Unit := do let value ← mkLambdaFVars (etaReduce := true) params value pure { preDef with value } - Mutual.addPreDefsFromUnary preDefs preDefsNonrec preDefNonRec - addAndCompilePartialRec preDefs + Mutual.addPreDefsFromUnary docCtx preDefs preDefsNonrec preDefNonRec + addAndCompilePartialRec docCtx preDefs let preDefs ← preDefs.mapM (Mutual.cleanPreDef ·) PartialFixpoint.registerEqnsInfo preDefs preDefNonRec.declName fixedParamPerms (hints.map (·.fixpointType)) Mutual.addPreDefAttributes preDefs diff --git a/src/Lean/Elab/PreDefinition/Structural/Main.lean b/src/Lean/Elab/PreDefinition/Structural/Main.lean index 668e3f9380..3f433a25ee 100644 --- a/src/Lean/Elab/PreDefinition/Structural/Main.lean +++ b/src/Lean/Elab/PreDefinition/Structural/Main.lean @@ -183,7 +183,10 @@ def reportTermMeasure (preDef : PreDefinition) (recArgPos : Nat) : MetaM Unit := Tactic.TryThis.addSuggestion ref stx -def structuralRecursion (preDefs : Array PreDefinition) (termMeasure?s : Array (Option TerminationMeasure)) : TermElabM Unit := do +def structuralRecursion + (docCtx : LocalContext × LocalInstances) (preDefs : Array PreDefinition) + (termMeasure?s : Array (Option TerminationMeasure)) : + TermElabM Unit := do let names := preDefs.map (·.declName) let ((recArgPoss, preDefsNonRec, fixedParamPerms), state) ← run <| inferRecArgPos preDefs termMeasure?s for recArgPos in recArgPoss, preDef in preDefs do @@ -194,9 +197,9 @@ def structuralRecursion (preDefs : Array PreDefinition) (termMeasure?s : Array ( prependError m!"structural recursion failed, produced type incorrect term" do -- We create the `_unsafe_rec` before we abstract nested proofs. -- Reason: the nested proofs may be referring to the _unsafe_rec. - addNonRec preDefNonRec (applyAttrAfterCompilation := false) (all := names.toList) + addNonRec docCtx preDefNonRec (applyAttrAfterCompilation := false) (all := names.toList) let preDefs ← preDefs.mapM (eraseRecAppSyntax ·) - addAndCompilePartialRec preDefs + addAndCompilePartialRec docCtx preDefs for preDef in preDefs, recArgPos in recArgPoss do let mut preDef := preDef unless preDef.kind.isTheorem do @@ -208,7 +211,7 @@ def structuralRecursion (preDefs : Array PreDefinition) (termMeasure?s : Array ( See issue #2327 -/ registerEqnsInfo preDef (preDefs.map (·.declName)) recArgPos fixedParamPerms - addSmartUnfoldingDef preDef recArgPos + addSmartUnfoldingDef docCtx preDef recArgPos markAsRecursive preDef.declName for preDef in preDefs do -- must happen in separate loop so realizations can see eqnInfos of all other preDefs diff --git a/src/Lean/Elab/PreDefinition/Structural/SmartUnfolding.lean b/src/Lean/Elab/PreDefinition/Structural/SmartUnfolding.lean index cd524175c9..fd08f51287 100644 --- a/src/Lean/Elab/PreDefinition/Structural/SmartUnfolding.lean +++ b/src/Lean/Elab/PreDefinition/Structural/SmartUnfolding.lean @@ -64,12 +64,14 @@ where | _ => processApp e | _ => return e -partial def addSmartUnfoldingDef (preDef : PreDefinition) (recArgPos : Nat) : TermElabM Unit := do +partial def addSmartUnfoldingDef + (docCtx : LocalContext × LocalInstances) (preDef : PreDefinition) (recArgPos : Nat) : + TermElabM Unit := do if (← isProp preDef.type) then return () else withEnableInfoTree false do let preDefSUnfold ← addSmartUnfoldingDefAux preDef recArgPos - addNonRec preDefSUnfold (cleanupValue := true) + addNonRec docCtx preDefSUnfold (cleanupValue := true) end Lean.Elab.Structural diff --git a/src/Lean/Elab/PreDefinition/WF/Main.lean b/src/Lean/Elab/PreDefinition/WF/Main.lean index a16decc55e..29a7e37914 100644 --- a/src/Lean/Elab/PreDefinition/WF/Main.lean +++ b/src/Lean/Elab/PreDefinition/WF/Main.lean @@ -23,7 +23,7 @@ namespace Lean.Elab open WF open Meta -def wfRecursion (preDefs : Array PreDefinition) (termMeasure?s : Array (Option TerminationMeasure)) : TermElabM Unit := do +def wfRecursion (docCtx : LocalContext × LocalInstances) (preDefs : Array PreDefinition) (termMeasure?s : Array (Option TerminationMeasure)) : TermElabM Unit := do let termMeasures? := termMeasure?s.mapM id -- Either all or none, checked by `elabTerminationByHints` let preDefs ← preDefs.mapM fun preDef => return { preDef with value := (← floatRecApp preDef.value) } @@ -75,8 +75,8 @@ def wfRecursion (preDefs : Array PreDefinition) (termMeasure?s : Array (Option T trace[Elab.definition.wf] ">> {preDefNonRec.declName} :=\n{preDefNonRec.value}" let preDefsNonrec ← preDefsFromUnaryNonRec fixedParamPerms argsPacker preDefs preDefNonRec - Mutual.addPreDefsFromUnary (cacheProofs := false) preDefs preDefsNonrec preDefNonRec - addAndCompilePartialRec preDefs + Mutual.addPreDefsFromUnary (cacheProofs := false) docCtx preDefs preDefsNonrec preDefNonRec + addAndCompilePartialRec docCtx preDefs let unaryPreDef ← Mutual.cleanPreDef (cacheProofs := false) unaryPreDef let preDefs ← preDefs.mapM (Mutual.cleanPreDef (cacheProofs := false) ·) registerEqnsInfo preDefs preDefNonRec.declName fixedParamPerms argsPacker diff --git a/src/Lean/Elab/SetOption.lean b/src/Lean/Elab/SetOption.lean index b7ebe3bcea..7d02cced74 100644 --- a/src/Lean/Elab/SetOption.lean +++ b/src/Lean/Elab/SetOption.lean @@ -14,6 +14,35 @@ namespace Lean.Elab variable [Monad m] [MonadOptions m] [MonadError m] [MonadLiftT (EIO Exception) m] [MonadInfoTree m] +private def throwUnconfigurable {α} (optionName : Name) : m α := + throwError "Invalid `set_option` command: The option `{optionName}` cannot be configured using \ + `set_option`" + +/-- +Returns the type corresponding to the given `DataValue`, or `none` if the corresponding type +cannot be specified using `set_option` notation. +-/ +private def ctorType? : DataValue → Option Expr + | .ofString .. => mkConst ``String + | .ofNat .. => mkConst ``Nat + | .ofBool .. => mkConst ``Bool + | .ofInt .. => none + | .ofName .. => none + | .ofSyntax .. => none + +def validateOptionValue (optionName : Name) (decl : OptionDecl) (val : DataValue) : m Unit := do + unless decl.defValue.sameCtor val do + throwMistypedOptionValue val decl.defValue +where + throwMistypedOptionValue (found defVal : DataValue) := do + match ctorType? defVal with + | some defValType => + let foundType := ctorType? found |>.get! + throwError "set_option value type mismatch: The value{indentD (toMessageData found)}\nhas type\ + {indentD (toMessageData foundType)}\nbut the option `{optionName}` expects a value of type\ + {indentExpr defValType}" + | _ => throwUnconfigurable optionName + def elabSetOption (id : Syntax) (val : Syntax) : m Options := do let ref ← getRef -- For completion purposes, we discard `val` and any later arguments. @@ -23,7 +52,7 @@ def elabSetOption (id : Syntax) (val : Syntax) : m Options := do let decl ← IO.toEIO (fun (ex : IO.Error) => Exception.error ref ex.toString) (getOptionDecl optionName) pushInfoLeaf <| .ofOptionInfo { stx := id, optionName, declName := decl.declName } let rec setOption (val : DataValue) : m Options := do - unless decl.defValue.sameCtor val do throwMistypedOptionValue optionName val decl.defValue + validateOptionValue optionName decl val return (← getOptions).insert optionName val match val.isStrLit? with | some str => setOption (DataValue.ofString str) @@ -39,30 +68,5 @@ def elabSetOption (id : Syntax) (val : Syntax) : m Options := do throwError "Unexpected set_option value `{val}`; expected a literal of type `{ctorType}`" else throwUnconfigurable optionName -where - throwMistypedOptionValue (optionName : Name) (found defVal : DataValue) := do - match ctorType? defVal with - | some defValType => - let foundType := ctorType? found |>.get! - throwError "set_option value type mismatch: The value{indentD (toMessageData found)}\nhas type\ - {indentD (toMessageData foundType)}\nbut the option `{optionName}` expects a value of type\ - {indentExpr defValType}" - | _ => throwUnconfigurable optionName - - throwUnconfigurable {α} (optionName : Name) : m α := - throwError "Invalid `set_option` command: The option `{optionName}` cannot be configured using \ - `set_option`" - - /-- - Returns the type corresponding to the given `DataValue`, or `none` if the corresponding type - cannot be specified using `set_option` notation. - -/ - ctorType? : DataValue → Option Expr - | .ofString .. => mkConst ``String - | .ofNat .. => mkConst ``Nat - | .ofBool .. => mkConst ``Bool - | .ofInt .. => none - | .ofName .. => none - | .ofSyntax .. => none end Lean.Elab diff --git a/src/Lean/Elab/Structure.lean b/src/Lean/Elab/Structure.lean index 3496b4c0e7..aac1f01a20 100644 --- a/src/Lean/Elab/Structure.lean +++ b/src/Lean/Elab/Structure.lean @@ -281,7 +281,6 @@ private def expandCtor (structStx : Syntax) (structModifiers : Modifiers) (struc let declName ← applyVisibility ctorModifiers declName -- `binders` is type parameter binder overrides; this will be validated when the constructor is created in `Structure.mkCtor`. let binders := ctor[2] - addDocString' declName ctorModifiers.docString? addDeclarationRangesFromSyntax declName ctor[1] if structModifiers.isMeta then modifyEnv (addMeta · declName) @@ -387,7 +386,6 @@ private def expandFields (structStx : Syntax) (structModifiers : Modifiers) (str throwErrorAt ident "Invalid field name `{name.eraseMacroScopes}`: Field names must be atomic" let declName := structDeclName ++ name let declName ← applyVisibility fieldModifiers declName - addDocString' declName fieldModifiers.docString? return views.push { ref := ident modifiers := fieldModifiers @@ -417,7 +415,7 @@ def structureSyntaxToView (modifiers : Modifiers) (stx : Syntax) : TermElabM Str let isClass := stx[0].getKind == ``Parser.Command.classTk let modifiers := if isClass then modifiers.addAttr { name := `class } else modifiers let declId := stx[1] - let ⟨name, declName, levelNames⟩ ← Term.expandDeclId (← getCurrNamespace) (← Term.getLevelNames) declId modifiers + let ⟨name, declName, levelNames, docString?⟩ ← Term.expandDeclId (← getCurrNamespace) (← Term.getLevelNames) declId modifiers addDeclarationRangesForBuiltin declName modifiers.stx stx let (binders, type?) := expandOptDeclSig stx[2] let exts := stx[3] @@ -463,6 +461,7 @@ def structureSyntaxToView (modifiers : Modifiers) (stx : Syntax) : TermElabM Str fields computedFields := #[] derivingClasses + docString? } @@ -1538,6 +1537,17 @@ def elabStructureCommand : InductiveElabDescr where withOptions (warn.sorry.set · false) do mkRemainingProjections levelParams params view setStructureParents view.declName parentInfos + + if let some (doc, isVerso) := view.docString? then + addDocStringOf isVerso view.declName view.binders doc + if let some (doc, isVerso) := view.ctor.modifiers.docString? then + addDocStringOf isVerso view.ctor.declName view.ctor.binders doc + for field in view.fields do + -- may not exist if overriding inherited field + if (← getEnv).contains field.declName then + if let some (doc, isVerso) := field.modifiers.docString? then + addDocStringOf isVerso field.declName field.binders doc + withSaveInfoContext do -- save new env for field in view.fields do -- may not exist if overriding inherited field diff --git a/src/Lean/Elab/Tactic/Doc.lean b/src/Lean/Elab/Tactic/Doc.lean index 210f630d6f..1e69b41ce8 100644 --- a/src/Lean/Elab/Tactic/Doc.lean +++ b/src/Lean/Elab/Tactic/Doc.lean @@ -6,7 +6,7 @@ Authors: David Thrane Christiansen module prelude -public import Lean.DocString +import Lean.DocString public import Lean.Elab.Command public section @@ -23,9 +23,9 @@ open Lean.Parser.Command let tacName ← liftTermElabM <| realizeGlobalConstNoOverloadWithInfo tac if let some tgt' := alternativeOfTactic (← getEnv) tacName then - throwErrorAt tac "`{tacName}` is an alternative form of `{tgt'}`" + throwErrorAt tac "`{.ofConstName tacName}` is an alternative form of `{.ofConstName tgt'}`" if !(isTactic (← getEnv) tacName) then - throwErrorAt tac "`{tacName}` is not a tactic" + throwErrorAt tac "`{.ofConstName tacName}` is not a tactic" modifyEnv (tacticDocExtExt.addEntry · (tacName, docs.getDocString)) pure () diff --git a/src/Lean/Elab/Term.lean b/src/Lean/Elab/Term.lean index 74ae6017a6..07e760c09f 100644 --- a/src/Lean/Elab/Term.lean +++ b/src/Lean/Elab/Term.lean @@ -21,2068 +21,20 @@ public import Lean.Elab.WhereFinally public import Lean.Language.Basic public import Lean.Elab.InfoTree.InlayHints public meta import Lean.Parser.Term +public import Lean.Elab.Term.TermElabM public section namespace Lean.Elab -namespace Term - -/-- Saved context for postponed terms and tactics to be executed. -/ -structure SavedContext where - declName? : Option Name - options : Options - openDecls : List OpenDecl - macroStack : MacroStack - errToSorry : Bool - levelNames : List Name - -/-- The kind of a tactic metavariable, used for additional error reporting. -/ -inductive TacticMVarKind - /-- Standard tactic metavariable, arising from `by ...` syntax. -/ - | term - /-- Tactic metavariable arising from an autoparam for a function application. -/ - | autoParam (argName : Name) - /-- Tactic metavariable arising from an autoparam for a structure field. -/ - | fieldAutoParam (fieldName structName : Name) - -/-- We use synthetic metavariables as placeholders for pending elaboration steps. -/ -inductive SyntheticMVarKind where - /-- - Use typeclass resolution to synthesize value for metavariable. - If `extraErrorMsg?` is `some msg`, `msg` contains additional information to include in error messages - regarding type class synthesis failure. - -/ - | typeClass (extraErrorMsg? : Option MessageData) - /-- - Use coercion to synthesize value for the metavariable. - If synthesis fails, then throws an error. - - If `mkErrorMsg?` is provided, then the error `mkErrorMsg expectedType e` is thrown. - The `mkErrorMsg` function is allowed to throw an error itself. - - Otherwise, throws a default type mismatch error message. - If `header?` is not provided, the default header is "type mismatch". - If `f?` is provided, then throws an application type mismatch error. - -/ - | coe (header? : Option String) (expectedType : Expr) (e : Expr) (f? : Option Expr) - (mkErrorMsg? : Option (MVarId → Expr → Expr → MetaM MessageData)) - /-- - Use tactic to synthesize value for metavariable. - - If `delayOnMVars` is true, the tactic will not be executed until the goal is free of unassigned - expr metavariables. - -/ - | tactic (tacticCode : Syntax) (ctx : SavedContext) (kind : TacticMVarKind) (delayOnMVars := false) - /-- Metavariable represents a hole whose elaboration has been postponed. -/ - | postponed (ctx : SavedContext) - deriving Inhabited - -/-- -Convert an "extra" optional error message into a message `"\n{msg}"` (if `some msg`) and `MessageData.nil` (if `none`) --/ -def extraMsgToMsg (extraErrorMsg? : Option MessageData) : MessageData := - if let some msg := extraErrorMsg? then m!"\n{msg}" else .nil - -instance : ToString SyntheticMVarKind where - toString - | .typeClass .. => "typeclass" - | .coe .. => "coe" - | .tactic .. => "tactic" - | .postponed .. => "postponed" - -structure SyntheticMVarDecl where - stx : Syntax - kind : SyntheticMVarKind - deriving Inhabited - -/-- - We can optionally associate an error context with a metavariable (see `MVarErrorInfo`). - We have three different kinds of error context. --/ -inductive MVarErrorKind where - /-- Metavariable for implicit arguments. `ctx` is the parent application, - `lctx` is a local context where it is valid (necessary for eta feature for named arguments). -/ - | implicitArg (lctx : LocalContext) (ctx : Expr) - /-- Metavariable for explicit holes provided by the user (e.g., `_` and `?m`) -/ - | hole - /-- "Custom", `msgData` stores the additional error messages. -/ - | custom (msgData : MessageData) - deriving Inhabited - -instance : ToString MVarErrorKind where - toString - | .implicitArg _ _ => "implicitArg" - | .hole => "hole" - | .custom _ => "custom" - -/-- - We can optionally associate an error context with metavariables. --/ -structure MVarErrorInfo where - mvarId : MVarId - ref : Syntax - kind : MVarErrorKind - deriving Inhabited - -/-- -When reporting unexpected universe level metavariables, it is useful to localize the errors -to particular terms, especially at `let` bindings and function binders, -where universe polymorphism is not permitted. --/ -structure LevelMVarErrorInfo where - lctx : LocalContext - expr : Expr - ref : Syntax - msgData? : Option MessageData := none - deriving Inhabited - -/-- - Nested `let rec` expressions are eagerly lifted by the elaborator. - We store the information necessary for performing the lifting here. --/ -structure LetRecToLift where - ref : Syntax - fvarId : FVarId - attrs : Array Attribute - shortDeclName : Name - declName : Name - parentName? : Option Name - lctx : LocalContext - localInstances : LocalInstances - type : Expr - val : Expr - mvarId : MVarId - termination : TerminationHints - deriving Inhabited - -/-- - State of the `TermElabM` monad. --/ -structure State where - levelNames : List Name := [] - syntheticMVars : MVarIdMap SyntheticMVarDecl := {} - pendingMVars : List MVarId := {} - /-- List of errors associated to a metavariable that are shown to the user if the metavariable could not be fully instantiated -/ - mvarErrorInfos : List MVarErrorInfo := [] - /-- List of data to be able to localize universe level metavariable errors to particular expressions. -/ - levelMVarErrorInfos : List LevelMVarErrorInfo := [] - /-- - `mvarArgNames` stores the argument names associated to metavariables. - These are used in combination with `mvarErrorInfos` for throwing errors about metavariables that could not be fully instantiated. - For example when elaborating `List _`, the argument name of the placeholder will be `α`. - - While elaborating an application, `mvarArgNames` is set for each metavariable argument, using the available argument name. - This may happen before or after the `mvarErrorInfos` is set for the same metavariable. - - We used to store the argument names in `mvarErrorInfos`, updating the `MVarErrorInfos` to add the argument name when it is available, - but this doesn't work if the argument name is available _before_ the `mvarErrorInfos` is set for that metavariable. - -/ - mvarArgNames : MVarIdMap Name := {} - letRecsToLift : List LetRecToLift := [] - deriving Inhabited - -/-- - Backtrackable state for the `TermElabM` monad. --/ -structure SavedState where - «meta» : Meta.SavedState - «elab» : State - deriving Nonempty - -end Term - -namespace Tactic - -/-- - State of the `TacticM` monad. --/ -structure State where - goals : List MVarId - deriving Inhabited - -/-- - Snapshots are used to implement the `save` tactic. - This tactic caches the state of the system, and allows us to "replay" - expensive proofs efficiently. This is only relevant implementing the - LSP server. --/ -structure Snapshot where - core : Core.State - «meta» : Meta.State - term : Term.State - tactic : Tactic.State - stx : Syntax - -/-- - Key for the cache used to implement the `save` tactic. --/ -structure CacheKey where - mvarId : MVarId -- TODO: should include all goals - pos : String.Pos - deriving BEq, Hashable, Inhabited - -/-- - Cache for the `save` tactic. --/ -structure Cache where - pre : PHashMap CacheKey Snapshot := {} - post : PHashMap CacheKey Snapshot := {} - deriving Inhabited - -section Snapshot -open Language - -structure SavedState where - term : Term.SavedState - tactic : State - -/-- Snapshot after finishing execution of a tactic. -/ -structure TacticFinishedSnapshot extends Language.Snapshot where - /-- State saved for reuse, if no fatal exception occurred. -/ - state? : Option SavedState - /-- Untyped snapshots from `logSnapshotTask`, saved at this level for cancellation. -/ - moreSnaps : Array (SnapshotTask SnapshotTree) -deriving Inhabited -instance : ToSnapshotTree TacticFinishedSnapshot where - toSnapshotTree s := ⟨s.toSnapshot, s.moreSnaps⟩ - -/-- Snapshot just before execution of a tactic. -/ -structure TacticParsedSnapshot extends Language.Snapshot where - /-- Syntax tree of the tactic, stored and compared for incremental reuse. -/ - stx : Syntax - /-- Task for nested incrementality, if enabled for tactic. -/ - inner? : Option (SnapshotTask TacticParsedSnapshot) := none - /-- Task for state after tactic execution. -/ - finished : SnapshotTask TacticFinishedSnapshot - /-- Tasks for subsequent, potentially parallel, tactic steps. -/ - next : Array (SnapshotTask TacticParsedSnapshot) := #[] -deriving Inhabited -partial instance : ToSnapshotTree TacticParsedSnapshot where - toSnapshotTree := go where - go := fun s => ⟨s.toSnapshot, - s.inner?.toArray.map (·.map (sync := true) go) ++ - #[s.finished.map (sync := true) toSnapshotTree] ++ - s.next.map (·.map (sync := true) go)⟩ - -end Snapshot -end Tactic - -namespace Term - -structure Context where - declName? : Option Name := none - macroStack : MacroStack := [] - /-- - When `mayPostpone == true`, an elaboration function may interrupt its execution by throwing `Exception.postpone`. - The function `elabTerm` catches this exception and creates fresh synthetic metavariable `?m`, stores `?m` in - the list of pending synthetic metavariables, and returns `?m`. -/ - mayPostpone : Bool := true - /-- - When `errToSorry` is set to true, the method `elabTerm` catches - exceptions and converts them into synthetic `sorry`s. - The implementation of choice nodes and overloaded symbols rely on the fact - that when `errToSorry` is set to false for an elaboration function `F`, then - `errToSorry` remains `false` for all elaboration functions invoked by `F`. - That is, it is safe to transition `errToSorry` from `true` to `false`, but - we must not set `errToSorry` to `true` when it is currently set to `false`. -/ - errToSorry : Bool := true - /-- - When `autoBoundImplicit` is set to true, instead of producing - an "unknown identifier" error for unbound variables, we generate an - internal exception. This exception is caught at `withAutoBoundImplicit` - which adds an implicit declaration for the unbound variable and tries again. -/ - autoBoundImplicit : Bool := false - autoBoundImplicits : PArray Expr := {} - /-- - A name `n` is only eligible to be an auto implicit name if `autoBoundImplicitForbidden n = false`. - We use this predicate to disallow `f` to be considered an auto implicit name in a definition such - as - ``` - def f : f → Bool := fun _ => true - ``` - -/ - autoBoundImplicitForbidden : Name → Bool := fun _ => false - /-- Map from user name to internal unique name -/ - sectionVars : NameMap Name := {} - /-- Map from internal name to fvar -/ - sectionFVars : NameMap Expr := {} - /-- Enable/disable implicit lambdas feature. -/ - implicitLambda : Bool := true - /-- Heed `elab_as_elim` attribute. -/ - heedElabAsElim : Bool := true - /-- Noncomputable sections automatically add the `noncomputable` modifier to any declaration we cannot generate code for. -/ - isNoncomputableSection : Bool := false - /-- When `true` we skip TC failures. We use this option when processing patterns. -/ - ignoreTCFailures : Bool := false - /-- `true` when elaborating patterns. It affects how we elaborate named holes. -/ - inPattern : Bool := false - /-- - Snapshot for incremental processing of current tactic, if any. - - Invariant: if the bundle's `old?` is set, then the state *up to the start* of the tactic is - unchanged, i.e. reuse is possible. - -/ - tacSnap? : Option (Language.SnapshotBundle Tactic.TacticParsedSnapshot) := none - /-- - If `true`, we store in the `Expr` the `Syntax` for recursive applications (i.e., applications - of free variables tagged with `isAuxDecl`). We store the `Syntax` using `mkRecAppWithSyntax`. - We use the `Syntax` object to produce better error messages at `Structural.lean` and `WF.lean`. -/ - saveRecAppSyntax : Bool := true - /-- - If `holesAsSyntheticOpaque` is `true`, then we mark metavariables associated - with `_`s as `syntheticOpaque` if they do not occur in patterns. - This option is useful when elaborating terms in tactics such as `refine'` where - we want holes there to become new goals. See issue #1681, we have - `refine' (fun x => _) - -/ - holesAsSyntheticOpaque : Bool := false - /-- - If `checkDeprecated := true`, then `Linter.checkDeprecated` when creating constants. - -/ - checkDeprecated : Bool := true - -abbrev TermElabM := ReaderT Context $ StateRefT State MetaM -abbrev TermElab := Syntax → Option Expr → TermElabM Expr - -/- -Make the compiler generate specialized `pure`/`bind` so we do not have to optimize through the -whole monad stack at every use site. May eventually be covered by `deriving`. --/ -@[always_inline] -instance : Monad TermElabM := - let i := inferInstanceAs (Monad TermElabM) - { pure := i.pure, bind := i.bind } - open Meta -instance : Inhabited (TermElabM α) where - default := throw default - -protected def saveState : TermElabM SavedState := - return { «meta» := (← Meta.saveState), «elab» := (← get) } - -def SavedState.restore (s : SavedState) (restoreInfo : Bool := false) : TermElabM Unit := do - let traceState ← getTraceState -- We never backtrack trace message - let infoState ← getInfoState -- We also do not backtrack the info nodes when `restoreInfo == false` - s.meta.restore - set s.elab - setTraceState traceState - unless restoreInfo do - setInfoState infoState - -/-- -Like `Meta.withRestoreOrSaveFull` for `TermElabM`, but also takes a `tacSnap?` that -* when running `act`, is set as `Context.tacSnap?` -* otherwise (i.e. on restore) is used to update the new snapshot promise to the old task's - value. -This extra restore step is necessary because while `reusableResult?` can be used to replay any -effects on `State`, `Context.tacSnap?` is not part of it but changed via an `IO` side effect, so -it needs to be replayed separately. - -We use an explicit parameter instead of accessing `Context.tacSnap?` directly because this prevents -`withRestoreOrSaveFull` and `withReader` from being used in the wrong order. --/ -@[specialize] -def withRestoreOrSaveFull (reusableResult? : Option (α × SavedState)) - (tacSnap? : Option (Language.SnapshotBundle Tactic.TacticParsedSnapshot)) (act : TermElabM α) : - TermElabM (α × SavedState) := do - if let some (_, state) := reusableResult? then - set state.elab - if let some snap := tacSnap? then - let some old := snap.old? - | throwError "withRestoreOrSaveFull: expected old snapshot in `tacSnap?`" - snap.new.resolve old.val.get - - let reusableResult? := reusableResult?.map (fun (val, state) => (val, state.meta)) - let (a, «meta») ← withReader ({ · with tacSnap? }) do - controlAt MetaM fun runInBase => do - Meta.withRestoreOrSaveFull reusableResult? <| runInBase act - return (a, { «meta», «elab» := (← get) }) - -instance : MonadBacktrack SavedState TermElabM where - saveState := Term.saveState - restoreState b := b.restore - -/-- -Incremental elaboration helper. Avoids leakage of data from outside syntax via the monadic context -when running `act` on `stx` by -* setting `stx` as the `ref` and -* deactivating `suppressElabErrors` if `stx` is `missing`-free, which also helps with not hiding - useful errors in this part of the input. Note that if `stx` has `missing`, this should always be - true for the outer syntax as well, so taking the old value of `suppressElabErrors` into account - should not introduce data leakage. - -This combinator should always be used when narrowing reuse to a syntax subtree, usually (in the case -of tactics, to be generalized) via `withNarrowed(Arg)TacticReuse`. --/ -def withReuseContext [Monad m] [MonadWithReaderOf Core.Context m] (stx : Syntax) (act : m α) : - m α := do - withTheReader Core.Context (fun ctx => { ctx with - ref := stx - suppressElabErrors := ctx.suppressElabErrors && stx.hasMissing }) act - -/-- -Manages reuse information for nested tactics by `split`ting given syntax into an outer and inner -part. `act` is then run on the inner part but with reuse information adjusted as following: -* If the old (from `tacSnap?`'s `SyntaxGuarded.stx`) and new (from `stx`) outer syntax are not - identical according to `Syntax.eqWithInfo`, reuse is disabled. -* Otherwise, the old syntax as stored in `tacSnap?` is updated to the old *inner* syntax. -* In any case, `withReuseContext` is used on the new inner syntax to further prepare the monadic - context. - -For any tactic that participates in reuse, `withNarrowedTacticReuse` should be applied to the -tactic's syntax and `act` should be used to do recursive tactic evaluation of nested parts. Also, -after this function, `getAndEmptySnapshotTasks` should be called and the result stored in a snapshot -so that the tasks don't end up in a snapshot further up and are cancelled together with it; see -note [Incremental Cancellation]. --/ -def withNarrowedTacticReuse [Monad m] [MonadReaderOf Context m] [MonadLiftT BaseIO m] - [MonadWithReaderOf Core.Context m] [MonadWithReaderOf Context m] [MonadOptions m] - (split : Syntax → Syntax × Syntax) (act : Syntax → m α) (stx : Syntax) : m α := do - let (outer, inner) := split stx - let opts ← getOptions - let ctx ← readThe Term.Context - withTheReader Term.Context (fun ctx => { ctx with tacSnap? := ctx.tacSnap?.map fun tacSnap => - { tacSnap with old? := tacSnap.old?.bind fun old => do - let (oldOuter, oldInner) := split old.stx - guard <| outer.eqWithInfoAndTraceReuse opts oldOuter - return { old with stx := oldInner } - } - }) do - if let some oldOuter := ctx.tacSnap?.bind (·.old?) then - if (← read).tacSnap?.bind (·.old?) |>.isNone then - oldOuter.val.cancelRec - withReuseContext inner (act inner) - -/-- -A variant of `withNarrowedTacticReuse` that uses `stx[argIdx]` as the inner syntax and all `stx` -child nodes before that as the outer syntax, i.e. reuse is disabled if there was any change before -`argIdx`. - -NOTE: child nodes after `argIdx` are not tested (which would almost always disable reuse as they are -necessarily shifted by changes at `argIdx`) so it must be ensured that the result of `arg` does not -depend on them (i.e. they should not be inspected beforehand). --/ -def withNarrowedArgTacticReuse [Monad m] [MonadReaderOf Context m] [MonadLiftT BaseIO m] - [MonadWithReaderOf Core.Context m] [MonadWithReaderOf Context m] [MonadOptions m] - (argIdx : Nat) (act : Syntax → m α) (stx : Syntax) : m α := - withNarrowedTacticReuse (fun stx => (mkNullNode stx.getArgs[*...argIdx], stx[argIdx])) act stx - -/-- -Disables incremental tactic reuse *and* reporting for `act` if `cond` is true by setting `tacSnap?` -to `none`. This should be done for tactic blocks that are run multiple times as otherwise the -reported progress will jump back and forth (and partial reuse for these kinds of tact blocks is -similarly questionable). --/ -def withoutTacticIncrementality [Monad m] [MonadWithReaderOf Context m] [MonadOptions m] - (cond : Bool) (act : m α) : m α := do - let opts ← getOptions - withTheReader Term.Context (fun ctx => { ctx with tacSnap? := ctx.tacSnap?.filter fun tacSnap => Id.run do - if let some old := tacSnap.old? then - if cond && opts.getBool `trace.Elab.reuse then - dbg_trace "reuse stopped: guard failed at {old.stx}" - return !cond - }) act - -/-- Disables incremental tactic reuse for `act` if `cond` is true. -/ -def withoutTacticReuse [Monad m] [MonadWithReaderOf Context m] [MonadOptions m] - (cond : Bool) (act : m α) : m α := do - let opts ← getOptions - withTheReader Term.Context (fun ctx => { ctx with tacSnap? := ctx.tacSnap?.map fun tacSnap => - { tacSnap with old? := tacSnap.old?.filter fun old => Id.run do - if cond && opts.getBool `trace.Elab.reuse then - dbg_trace "reuse stopped: guard failed at {old.stx}" - return !cond } - }) act - -@[inherit_doc Core.wrapAsyncAsSnapshot] -def wrapAsyncAsSnapshot {α : Type} (act : α → TermElabM Unit) (cancelTk? : Option IO.CancelToken) - (desc : String := by exact decl_name%.toString) : - TermElabM (α → BaseIO Language.SnapshotTree) := do - let ctx ← read - let st ← get - let metaCtx ← readThe Meta.Context - let metaSt ← getThe Meta.State - Core.wrapAsyncAsSnapshot (cancelTk? := cancelTk?) (desc := desc) fun a => - act a |>.run ctx |>.run' st |>.run' metaCtx metaSt - -abbrev TermElabResult (α : Type) := EStateM.Result Exception SavedState α - -/-- - Execute `x`, save resulting expression and new state. - We remove any `Info` created by `x`. - The info nodes are committed when we execute `applyResult`. - We use `observing` to implement overloaded notation and decls. - We want to save `Info` nodes for the chosen alternative. --/ -def observing (x : TermElabM α) : TermElabM (TermElabResult α) := do - let s ← saveState - try - let e ← x - let sNew ← saveState - s.restore (restoreInfo := true) - return EStateM.Result.ok e sNew - catch - | ex@(.error ..) => - let sNew ← saveState - s.restore (restoreInfo := true) - return .error ex sNew - | ex@(.internal id _) => - if id == postponeExceptionId then - s.restore (restoreInfo := true) - throw ex - -/-- - Apply the result/exception and state captured with `observing`. - We use this method to implement overloaded notation and symbols. -/ -def applyResult (result : TermElabResult α) : TermElabM α := do - match result with - | .ok a r => r.restore (restoreInfo := true); return a - | .error ex r => r.restore (restoreInfo := true); throw ex - -/-- - Execute `x`, but keep state modifications only if `x` did not postpone. - This method is useful to implement elaboration functions that cannot decide whether - they need to postpone or not without updating the state. -/ -def commitIfDidNotPostpone (x : TermElabM α) : TermElabM α := do - -- We just reuse the implementation of `observing` and `applyResult`. - let r ← observing x - applyResult r - -/-- - Return the universe level names explicitly provided by the user. --/ -def getLevelNames : TermElabM (List Name) := - return (← get).levelNames - -/-- - Given a free variable `fvar`, return its declaration. - This function panics if `fvar` is not a free variable. --/ -def getFVarLocalDecl! (fvar : Expr) : TermElabM LocalDecl := do - match (← getLCtx).find? fvar.fvarId! with - | some d => pure d - | none => unreachable! - -instance : AddErrorMessageContext TermElabM where - add ref msg := do - let ctx ← read - let ref := getBetterRef ref ctx.macroStack - let msg ← addMessageContext msg - let msg ← addMacroStack msg ctx.macroStack - pure (ref, msg) - -/-- - Execute `x` without storing `Syntax` for recursive applications. See `saveRecAppSyntax` field at `Context`. --/ -def withoutSavingRecAppSyntax (x : TermElabM α) : TermElabM α := - withReader (fun ctx => { ctx with saveRecAppSyntax := false }) x - -unsafe def mkTermElabAttributeUnsafe (ref : Name) : IO (KeyedDeclsAttribute TermElab) := - mkElabAttribute TermElab `builtin_term_elab `term_elab `Lean.Parser.Term `Lean.Elab.Term.TermElab "term" ref - -@[implemented_by mkTermElabAttributeUnsafe] -opaque mkTermElabAttribute (ref : Name) : IO (KeyedDeclsAttribute TermElab) - -/-- -Registers a term elaborator for the given syntax node kind. - -A term elaborator should have type `Lean.Elab.Term.TermElab` (which is -`Lean.Syntax → Option Lean.Expr → Lean.Elab.Term.TermElabM Lean.Expr`), i.e. should take syntax of -the given syntax node kind and an optional expected type as parameters and produce an expression. - -The `elab_rules` and `elab` commands should usually be preferred over using this attribute -directly. --/ -@[builtin_doc] -builtin_initialize termElabAttribute : KeyedDeclsAttribute TermElab ← mkTermElabAttribute decl_name% - -/-- - Auxiliary datatype for presenting a Lean lvalue modifier. - We represent an unelaborated lvalue as a `Syntax` (or `Expr`) and `List LVal`. - Example: `a.foo.1` is represented as the `Syntax` `a` and the list - `[LVal.fieldName "foo", LVal.fieldIdx 1]`. --/ -inductive LVal where - | fieldIdx (ref : Syntax) (i : Nat) - /-- Field `suffix?` is for producing better error messages because `x.y` may be a field access or a hierarchical/composite name. - `ref` is the syntax object representing the field. `fullRef` includes the LHS. -/ - | fieldName (ref : Syntax) (name : String) (suffix? : Option Name) (fullRef : Syntax) - -def LVal.getRef : LVal → Syntax - | .fieldIdx ref _ => ref - | .fieldName ref .. => ref - -def LVal.isFieldName : LVal → Bool - | .fieldName .. => true - | _ => false - -instance : ToString LVal where - toString - | .fieldIdx _ i => toString i - | .fieldName _ n .. => n - -/-- Return the name of the declaration being elaborated if available. -/ -def getDeclName? : TermElabM (Option Name) := return (← read).declName? -/-- Return the list of nested `let rec` declarations that need to be lifted. -/ -def getLetRecsToLift : TermElabM (List LetRecToLift) := return (← get).letRecsToLift -/-- Return the declaration of the given metavariable -/ -def getMVarDecl (mvarId : MVarId) : TermElabM MetavarDecl := return (← getMCtx).getDecl mvarId - -instance : MonadParentDecl TermElabM where - getParentDeclName? := getDeclName? - -/-- -Executes `x` in the context of the given declaration name. Ensures that the info tree is set up -correctly and adjusts the declaration name generator to generate names below this name, resetting -the nested counter. --/ -def withDeclName (name : Name) (x : TermElabM α) : TermElabM α := - withReader (fun ctx => { ctx with declName? := name }) do - withSaveParentDeclInfoContext do - withDeclNameForAuxNaming name do - x - -/-- Update the universe level parameter names. -/ -def setLevelNames (levelNames : List Name) : TermElabM Unit := - modify fun s => { s with levelNames := levelNames } - -/-- Execute `x` using `levelNames` as the universe level parameter names. See `getLevelNames`. -/ -def withLevelNames (levelNames : List Name) (x : TermElabM α) : TermElabM α := do - let levelNamesSaved ← getLevelNames - setLevelNames levelNames - try x finally setLevelNames levelNamesSaved - -def withoutErrToSorryImp (x : TermElabM α) : TermElabM α := - withReader (fun ctx => { ctx with errToSorry := false }) x - -/-- - Execute `x` without converting errors (i.e., exceptions) to `sorry` applications. - Recall that when `errToSorry = true`, the method `elabTerm` catches exceptions and converts them into `sorry` applications. --/ -def withoutErrToSorry [MonadFunctorT TermElabM m] : m α → m α := - monadMap (m := TermElabM) withoutErrToSorryImp - -def withoutHeedElabAsElimImp (x : TermElabM α) : TermElabM α := - withReader (fun ctx => { ctx with heedElabAsElim := false }) x - -/-- - Execute `x` without heeding the `elab_as_elim` attribute. Useful when there is - no expected type (so `elabAppArgs` would fail), but expect that the user wants - to use such constants. --/ -def withoutHeedElabAsElim [MonadFunctorT TermElabM m] : m α → m α := - monadMap (m := TermElabM) withoutHeedElabAsElimImp - -/-- -Execute `x` but discard changes performed at `Term.State` and `Meta.State`. -Recall that the `Environment`, `InfoState` and messages are at `Core.State`. Thus, any updates to -it will be preserved. -This method is useful for performing computations where all metavariable must be resolved or -discarded. -The `InfoTree`s are not discarded, however, and wrapped in `InfoTree.Context` -to store their metavariable context. --/ -def withoutModifyingElabMetaStateWithInfo (x : TermElabM α) : TermElabM α := do - let s ← get - let sMeta ← getThe Meta.State - try - withSaveInfoContext x - finally - set s - set sMeta - -/-- - Execute `x` but discard changes performed to the state. - However, the info trees and messages are not discarded. -/ -private def withoutModifyingStateWithInfoAndMessagesImpl (x : TermElabM α) : TermElabM α := do - let saved ← saveState - try - withSaveInfoContext x - finally - let saved := { saved with meta.core.infoState := (← getInfoState), meta.core.messages := (← getThe Core.State).messages } - restoreState saved - -/-- -Wraps the trees returned from `getInfoTrees`, if any, in an `InfoTree.context` node based on the -current monadic context and state. This is mainly used to report info trees early via -`Snapshot.infoTree?`. The trees are not removed from the `getInfoTrees` state as the final info tree -of the elaborated command should be complete and not depend on whether parts have been reported -early. - -As `InfoTree.context` can have only one child, this function panics if `trees` contains more than 1 -tree. Also, `PartialContextInfo.parentDeclCtx` is not currently generated as that information is not -available in the monadic context and only needed for the final info tree. --/ -def getInfoTreeWithContext? : TermElabM (Option InfoTree) := do - let st ← getInfoState - if st.trees.size > 1 then - return panic! "getInfoTreeWithContext: overfull tree" - let some t := st.trees[0]? | - return none - let t := t.substitute st.assignment - let ctx ← readThe Core.Context - let s ← getThe Core.State - let ctx := PartialContextInfo.commandCtx { - env := s.env, fileMap := ctx.fileMap, mctx := {}, currNamespace := ctx.currNamespace, - openDecls := ctx.openDecls, options := ctx.options, ngen := s.ngen - } - return InfoTree.context ctx t - -/-- For testing `TermElabM` methods. The #eval command will sign the error. -/ -def throwErrorIfErrors : TermElabM Unit := do - if (← MonadLog.hasErrors) then - throwError "Error(s)" - -def traceAtCmdPos (cls : Name) (msg : Unit → MessageData) : TermElabM Unit := - withRef Syntax.missing <| trace cls msg - -def ppGoal (mvarId : MVarId) : TermElabM Format := - Meta.ppGoal mvarId - -open Level (LevelElabM) - -def liftLevelM (x : LevelElabM α) : TermElabM α := do - let ctx ← read - let mctx ← getMCtx - let ngen ← getNGen - let lvlCtx : Level.Context := { options := (← getOptions), ref := (← getRef), autoBoundImplicit := ctx.autoBoundImplicit } - match (x lvlCtx).run { ngen := ngen, mctx := mctx, levelNames := (← getLevelNames) } with - | .ok a newS => setMCtx newS.mctx; setNGen newS.ngen; setLevelNames newS.levelNames; pure a - | .error ex _ => throw ex - -def elabLevel (stx : Syntax) : TermElabM Level := - liftLevelM <| Level.elabLevel stx - -/-- Elaborate `x` with `stx` on the macro stack -/ -def withPushMacroExpansionStack (beforeStx afterStx : Syntax) (x : TermElabM α) : TermElabM α := - withReader (fun ctx => { ctx with macroStack := { before := beforeStx, after := afterStx } :: ctx.macroStack }) x - -/-- Elaborate `x` with `stx` on the macro stack and produce macro expansion info -/ -def withMacroExpansion (beforeStx afterStx : Syntax) (x : TermElabM α) : TermElabM α := - withMacroExpansionInfo beforeStx afterStx do - withPushMacroExpansionStack beforeStx afterStx x - -/-- - Add the given metavariable to the list of pending synthetic metavariables. - The method `synthesizeSyntheticMVars` is used to process the metavariables on this list. -/ -def registerSyntheticMVar (stx : Syntax) (mvarId : MVarId) (kind : SyntheticMVarKind) : TermElabM Unit := do - modify fun s => { s with syntheticMVars := s.syntheticMVars.insert mvarId { stx, kind }, pendingMVars := mvarId :: s.pendingMVars } - -def registerSyntheticMVarWithCurrRef (mvarId : MVarId) (kind : SyntheticMVarKind) : TermElabM Unit := do - registerSyntheticMVar (← getRef) mvarId kind - -def registerMVarErrorInfo (mvarErrorInfo : MVarErrorInfo) : TermElabM Unit := - modify fun s => { s with mvarErrorInfos := mvarErrorInfo :: s.mvarErrorInfos } - -def registerMVarErrorHoleInfo (mvarId : MVarId) (ref : Syntax) : TermElabM Unit := - registerMVarErrorInfo { mvarId, ref, kind := .hole } - -def registerMVarErrorImplicitArgInfo (mvarId : MVarId) (ref : Syntax) (app : Expr) : TermElabM Unit := do - registerMVarErrorInfo { mvarId, ref, kind := .implicitArg (← getLCtx) app } - -def registerMVarErrorCustomInfo (mvarId : MVarId) (ref : Syntax) (msgData : MessageData) : TermElabM Unit := do - registerMVarErrorInfo { mvarId, ref, kind := .custom msgData } - -def registerCustomErrorIfMVar (e : Expr) (ref : Syntax) (msgData : MessageData) : TermElabM Unit := - match e.getAppFn with - | Expr.mvar mvarId => registerMVarErrorCustomInfo mvarId ref msgData - | _ => pure () - -def registerMVarArgName (mvarId : MVarId) (argName : Name) : TermElabM Unit := - modify fun s => { s with mvarArgNames := s.mvarArgNames.insert mvarId argName } - -/-- - Auxiliary method for reporting errors of the form "... contains metavariables ...". - This kind of error is thrown, for example, at `Match.lean` where elaboration - cannot continue if there are metavariables in patterns. - We only want to log it if we haven't logged any errors so far. -/ -def throwMVarError (m : MessageData) : TermElabM α := do - if (← MonadLog.hasErrors) then - throwAbortTerm - else - throwError m - -def MVarErrorInfo.logError (mvarErrorInfo : MVarErrorInfo) (extraMsg? : Option MessageData) : TermElabM Unit := do - match mvarErrorInfo.kind with - | MVarErrorKind.implicitArg lctx app => withLCtx lctx {} do - let app ← instantiateMVars app - let msg ← addArgName "don't know how to synthesize implicit argument" - let msg := msg ++ m!"{indentExpr app.setAppPPExplicitForExposingMVars}" ++ Format.line ++ "context:" ++ Format.line ++ MessageData.ofGoal mvarErrorInfo.mvarId - logErrorAt mvarErrorInfo.ref (appendExtra msg) - | MVarErrorKind.hole => do - let msg ← addArgName "don't know how to synthesize placeholder" " for argument" - let msg := msg ++ Format.line ++ "context:" ++ Format.line ++ MessageData.ofGoal mvarErrorInfo.mvarId - logErrorAt mvarErrorInfo.ref (MessageData.tagged `Elab.synthPlaceholder <| appendExtra msg) - | MVarErrorKind.custom msg => - logErrorAt mvarErrorInfo.ref (appendExtra msg) -where - /-- Append the argument name (if available) to the message. - Remark: if the argument name contains macro scopes we do not append it. -/ - addArgName (msg : MessageData) (extra : String := "") : TermElabM MessageData := do - match (← get).mvarArgNames.get? mvarErrorInfo.mvarId with - | none => return msg - | some argName => return if argName.hasMacroScopes then msg else msg ++ extra ++ m!" `{argName}`" - - appendExtra (msg : MessageData) : MessageData := - match extraMsg? with - | none => msg - | some extraMsg => msg.composePreservingKind extraMsg - -/-- - Try to log errors for the unassigned metavariables `pendingMVarIds`. - - Return `true` if there were "unfilled holes", and we should "abort" declaration. - TODO: try to fill "all" holes using synthetic "sorry's" - - Remark: We only log the "unfilled holes" as new errors if no error has been logged so far. -/ -def logUnassignedUsingErrorInfos (pendingMVarIds : Array MVarId) (extraMsg? : Option MessageData := none) : TermElabM Bool := do - if pendingMVarIds.isEmpty then - return false - else - let hasOtherErrors ← MonadLog.hasErrors - let mut hasNewErrors := false - let mut alreadyVisited : MVarIdSet := {} - let mut errors : Array MVarErrorInfo := #[] - for mvarErrorInfo in (← get).mvarErrorInfos do - let mvarId := mvarErrorInfo.mvarId - unless alreadyVisited.contains mvarId do - alreadyVisited := alreadyVisited.insert mvarId - /- The metavariable `mvarErrorInfo.mvarId` may have been assigned or - delayed assigned to another metavariable that is unassigned. -/ - let mvarDeps ← getMVars (mkMVar mvarId) - if mvarDeps.any pendingMVarIds.contains then do - unless hasOtherErrors do - errors := errors.push mvarErrorInfo - hasNewErrors := true - -- To sort the errors by position use - -- let sortedErrors := errors.qsort fun e₁ e₂ => e₁.ref.getPos?.getD 0 < e₂.ref.getPos?.getD 0 - for error in errors do - error.mvarId.withContext do - error.logError extraMsg? - return hasNewErrors - -def registerLevelMVarErrorInfo (levelMVarErrorInfo : LevelMVarErrorInfo) : TermElabM Unit := - modify fun s => { s with levelMVarErrorInfos := levelMVarErrorInfo :: s.levelMVarErrorInfos } - -def registerLevelMVarErrorExprInfo (expr : Expr) (ref : Syntax) (msgData? : Option MessageData := none) : TermElabM Unit := do - registerLevelMVarErrorInfo { lctx := (← getLCtx), expr, ref, msgData? } - -def exposeLevelMVars (e : Expr) : MetaM Expr := - Core.transform e - (post := fun e => do - match e with - | .const _ us => return .done <| if us.any (·.isMVar) then e.setPPUniverses true else e - | .sort u => return .done <| if u.isMVar then e.setPPUniverses true else e - | .lam _ t _ _ => return .done <| if t.hasLevelMVar then e.setOption `pp.funBinderTypes true else e - | .letE _ t _ _ _ => return .done <| if t.hasLevelMVar then e.setOption `pp.letVarTypes true else e - | _ => return .done e) - -def LevelMVarErrorInfo.logError (levelMVarErrorInfo : LevelMVarErrorInfo) : TermElabM Unit := - Meta.withLCtx levelMVarErrorInfo.lctx {} do - let e' ← exposeLevelMVars (← instantiateMVars levelMVarErrorInfo.expr) - let msg := levelMVarErrorInfo.msgData?.getD m!"don't know how to synthesize universe level metavariables" - let msg := m!"{msg}{indentExpr e'}" - logErrorAt levelMVarErrorInfo.ref msg - -/-- -Try to log errors for unassigned level metavariables `pendingLevelMVarIds`. - -Returns `true` if there are any relevant `LevelMVarErrorInfo`s and we should "abort" the declaration. - -Remark: we only log unassigned level metavariables as new errors if no error has been logged so far. --/ -def logUnassignedLevelMVarsUsingErrorInfos (pendingLevelMVarIds : Array LMVarId) : TermElabM Bool := do - if pendingLevelMVarIds.isEmpty then - return false - else - let hasOtherErrors ← MonadLog.hasErrors - let mut hasNewErrors := false - let mut errors : Array LevelMVarErrorInfo := #[] - for levelMVarErrorInfo in (← get).levelMVarErrorInfos do - let e ← instantiateMVars levelMVarErrorInfo.expr - let lmvars := (collectLevelMVars {} e).result - if lmvars.any pendingLevelMVarIds.contains then do - unless hasOtherErrors do - errors := errors.push levelMVarErrorInfo - hasNewErrors := true - for error in errors do - error.logError - return hasNewErrors - -/-- Ensure metavariables registered using `registerMVarErrorInfos` (and used in the given declaration) have been assigned. -/ -def ensureNoUnassignedMVars (decl : Declaration) : TermElabM Unit := do - let pendingMVarIds ← getMVarsAtDecl decl - if (← logUnassignedUsingErrorInfos pendingMVarIds) then - throwAbortCommand - -/-- - Execute `x` without allowing it to postpone elaboration tasks. - That is, `tryPostpone` is a noop. -/ -def withoutPostponing (x : TermElabM α) : TermElabM α := - withReader (fun ctx => { ctx with mayPostpone := false }) x - -/-- Creates syntax for `(` `:` `)` -/ -def mkExplicitBinder (ident : Syntax) (type : Syntax) : Syntax := - mkNode ``Lean.Parser.Term.explicitBinder #[mkAtom "(", mkNullNode #[ident], mkNullNode #[mkAtom ":", type], mkNullNode, mkAtom ")"] - -/-- - Convert unassigned universe level metavariables into parameters. - The new parameter names are fresh names of the form `u_i` with regard to `ctx.levelNames`, which is updated with the new names. -/ -def levelMVarToParam (e : Expr) (except : LMVarId → Bool := fun _ => false) : TermElabM Expr := do - let levelNames ← getLevelNames - let r := (← getMCtx).levelMVarToParam (fun n => levelNames.elem n) except e `u 1 - -- Recall that the most recent universe is the first element of the field `levelNames`. - setLevelNames (r.newParamNames.reverse.toList ++ levelNames) - setMCtx r.mctx - return r.expr - -/-- -Creates a fresh inaccessible binder name based on `x`. -Equivalent to ``Lean.Core.mkFreshUserName `x``. - -Do not confuse with `Lean.mkFreshId`, for creating fresh free variable and metavariable ids. --/ -def mkFreshBinderName [Monad m] [MonadQuotation m] : m Name := - withFreshMacroScope <| MonadQuotation.addMacroScope `x - -/-- - Auxiliary method for creating a `Syntax.ident` containing - a fresh name. This method is intended for creating fresh binder names. - It is just a thin layer on top of `mkFreshUserName`. -/ -def mkFreshIdent [Monad m] [MonadQuotation m] (ref : Syntax) (canonical := false) : m Ident := - return mkIdentFrom ref (← mkFreshBinderName) canonical - -private def applyAttributesCore - (declName : Name) (attrs : Array Attribute) - (applicationTime? : Option AttributeApplicationTime) : TermElabM Unit := do profileitM Exception "attribute application" (← getOptions) do - /- - Remark: if the declaration has syntax errors, `declName` may be `.anonymous` see issue #4309 - In this case, we skip attribute application. - -/ - if declName == .anonymous then - return - withDeclName declName do - for attr in attrs do - withTraceNode `Elab.attribute (fun _ => pure m!"applying [{attr.stx}]") do - withRef attr.stx do withLogging do - let env ← getEnv - match getAttributeImpl env attr.name with - | Except.error errMsg => throwError errMsg - | Except.ok attrImpl => - let runAttr := attrImpl.add declName attr.stx attr.kind - let runAttr := do - -- not truly an elaborator, but a sensible target for go-to-definition - let elaborator := attrImpl.ref - if (← getInfoState).enabled then - withInfoContext (mkInfo := return .ofCommandInfo { elaborator, stx := attr.stx }) do - try runAttr - finally if attr.stx[0].isIdent || attr.stx[0].isAtom then - -- Add an additional node over the leading identifier if there is one to make it look more function-like. - -- Do this last because we want user-created infos to take precedence - pushInfoLeaf <| .ofCommandInfo { elaborator, stx := attr.stx[0] } - else - runAttr - match applicationTime? with - | none => runAttr - | some applicationTime => - if applicationTime == attrImpl.applicationTime then - runAttr - -/-- Apply given attributes **at** a given application time -/ -def applyAttributesAt (declName : Name) (attrs : Array Attribute) (applicationTime : AttributeApplicationTime) : TermElabM Unit := - applyAttributesCore declName attrs applicationTime - -def applyAttributes (declName : Name) (attrs : Array Attribute) : TermElabM Unit := - applyAttributesCore declName attrs none - -def mkTypeMismatchError (header? : Option MessageData) (e : Expr) (eType : Expr) (expectedType : Expr) : MetaM MessageData := do - let header : MessageData := match header? with - | some header => m!"{header} " - | none => m!"Type mismatch{indentExpr e}\n" - return m!"{header}{← mkHasTypeButIsExpectedMsg eType expectedType}" - -def throwTypeMismatchError (header? : Option MessageData) (expectedType : Expr) (eType : Expr) (e : Expr) - (f? : Option Expr := none) (_extraMsg? : Option MessageData := none) : MetaM α := do - /- - We ignore `extraMsg?` for now. In all our tests, it contained no useful information. It was - always of the form: - ``` - failed to synthesize instance - CoeT - ``` - We should revisit this decision in the future and decide whether it may contain useful information - or not. -/ - let extraMsg := Format.nil - /- - let extraMsg : MessageData := match extraMsg? with - | none => Format.nil - | some extraMsg => Format.line ++ extraMsg; - -/ - match f? with - | none => throwError "{← mkTypeMismatchError header? e eType expectedType}{extraMsg}" - | some f => Meta.throwAppTypeMismatch f e - -def withoutMacroStackAtErr (x : TermElabM α) : TermElabM α := - withTheReader Core.Context (fun (ctx : Core.Context) => { ctx with options := pp.macroStack.set ctx.options false }) x - -namespace ContainsPendingMVar - -abbrev M := MonadCacheT Expr Unit (OptionT MetaM) - -/-- See `containsPostponedTerm` -/ -partial def visit (e : Expr) : M Unit := do - checkCache e fun _ => do - match e with - | .forallE _ d b _ => visit d; visit b - | .lam _ d b _ => visit d; visit b - | .letE _ t v b _ => visit t; visit v; visit b - | .app f a => visit f; visit a - | .mdata _ b => visit b - | .proj _ _ b => visit b - | .fvar fvarId .. => - match (← fvarId.getDecl) with - | .cdecl .. => return () - | .ldecl (value := v) .. => visit v - | .mvar mvarId .. => - let e' ← instantiateMVars e - if e' != e then - visit e' - else - match (← getDelayedMVarAssignment? mvarId) with - | some d => visit (mkMVar d.mvarIdPending) - | none => failure - | _ => return () - -end ContainsPendingMVar - -/-- Return `true` if `e` contains a pending metavariable. Remark: it also visits let-declarations. -/ -def containsPendingMVar (e : Expr) : MetaM Bool := do - match (← ContainsPendingMVar.visit e |>.run.run) with - | some _ => return false - | none => return true - -/-- - Try to synthesize metavariable using type class resolution. - This method assumes the local context and local instances of `instMVar` coincide - with the current local context and local instances. - Return `true` if the instance was synthesized successfully, and `false` if - the instance contains unassigned metavariables that are blocking the type class - resolution procedure. Throw an exception if resolution or assignment irrevocably fails. - - If `extraErrorMsg?` is not none, it contains additional information that should be attached - to type class synthesis failures. --/ -def synthesizeInstMVarCore (instMVar : MVarId) (maxResultSize? : Option Nat := none) (extraErrorMsg? : Option MessageData := none): TermElabM Bool := do - let extraErrorMsg := extraMsgToMsg extraErrorMsg? - let instMVarDecl ← getMVarDecl instMVar - let type := instMVarDecl.type - let type ← instantiateMVars type - let result ← trySynthInstance type maxResultSize? - match result with - | LOption.some val => - if (← instMVar.isAssigned) then - let oldVal ← instantiateMVars (mkMVar instMVar) - unless (← isDefEq oldVal val) do - if (← containsPendingMVar oldVal <||> containsPendingMVar val) then - /- If `val` or `oldVal` contains metavariables directly or indirectly (e.g., in a let-declaration), - we return `false` to indicate we should try again later. This is very coarse grain since - the metavariable may not be responsible for the failure. We should refine the test in the future if needed. - This check has been added to address dependencies between postponed metavariables. The following - example demonstrates the issue fixed by this test. - ``` - structure Point where - x : Nat - y : Nat - - def Point.compute (p : Point) : Point := - let p := { p with x := 1 } - let p := { p with y := 0 } - if (p.x - p.y) > p.x then p else p - ``` - The `isDefEq` test above fails for `Decidable (p.x - p.y ≤ p.x)` when the structure instance assigned to - `p` has not been elaborated yet. - -/ - return false -- we will try again later - let oldValType ← inferType oldVal - let valType ← inferType val - unless (← isDefEq oldValType valType) do - let (oldValType, valType) ← addPPExplicitToExposeDiff oldValType valType - throwError "synthesized type class instance type is not definitionally equal to expected type, synthesized{indentExpr val}\nhas type{indentExpr valType}\nexpected{indentExpr oldValType}{extraErrorMsg}" - let (oldVal, val) ← addPPExplicitToExposeDiff oldVal val - throwError "synthesized type class instance is not definitionally equal to expression inferred by typing rules, synthesized{indentExpr val}\ninferred{indentExpr oldVal}{extraErrorMsg}" - else - unless (← isDefEq (mkMVar instMVar) val) do - throwError "failed to assign synthesized type class instance{indentExpr val}{extraErrorMsg}" - return true - | .undef => return false -- we will try later - | .none => - if (← read).ignoreTCFailures then - return false - else - throwError "failed to synthesize{indentExpr type}{extraErrorMsg}{useDiagnosticMsg}" - -def mkCoe (expectedType : Expr) (e : Expr) (f? : Option Expr := none) (errorMsgHeader? : Option String := none) - (mkErrorMsg? : Option (MVarId → (expectedType e : Expr) → MetaM MessageData) := none) - (mkImmedErrorMsg? : Option ((errorMsg? : Option MessageData) → (expectedType e : Expr) → MetaM MessageData) := none) : TermElabM Expr := do - withTraceNode `Elab.coe (fun _ => return m!"adding coercion for {e} : {← inferType e} =?= {expectedType}") do - try - withoutMacroStackAtErr do - match ← coerce? e expectedType with - | .some eNew => return eNew - | .none => failure - | .undef => - let mvarAux ← mkFreshExprMVar expectedType MetavarKind.syntheticOpaque - registerSyntheticMVarWithCurrRef mvarAux.mvarId! (.coe errorMsgHeader? expectedType e f? mkErrorMsg?) - return mvarAux - catch - | .error _ msg => - if let some mkImmedErrorMsg := mkImmedErrorMsg? then - throwError (← mkImmedErrorMsg msg expectedType e) - else - throwTypeMismatchError errorMsgHeader? expectedType (← inferType e) e f? msg - | _ => - if let some mkImmedErrorMsg := mkImmedErrorMsg? then - throwError (← mkImmedErrorMsg none expectedType e) - else - throwTypeMismatchError errorMsgHeader? expectedType (← inferType e) e f? - -def mkCoeWithErrorMsgs (expectedType : Expr) (e : Expr) - (mkImmedErrorMsg : (errorMsg? : Option MessageData) → (expectedType e : Expr) → MetaM MessageData) - (mkErrorMsg : MVarId → (expectedType e : Expr) → MetaM MessageData) : TermElabM Expr := do - mkCoe expectedType e (mkImmedErrorMsg? := mkImmedErrorMsg) (mkErrorMsg? := mkErrorMsg) - -/-- -If `expectedType?` is `some t`, then ensures `t` and `eType` are definitionally equal by inserting a coercion if necessary. - -Argument `f?` is used only for generating error messages when inserting coercions fails. --/ -def ensureHasType (expectedType? : Option Expr) (e : Expr) - (errorMsgHeader? : Option String := none) (f? : Option Expr := none) : TermElabM Expr := do - let some expectedType := expectedType? | return e - if (← isDefEq (← inferType e) expectedType) then - return e - else - mkCoe expectedType e f? errorMsgHeader? - -def ensureHasTypeWithErrorMsgs (expectedType? : Option Expr) (e : Expr) - (mkImmedErrorMsg : (errorMsg? : Option MessageData) → (expectedType e : Expr) → MetaM MessageData) - (mkErrorMsg : MVarId → (expectedType e : Expr) → MetaM MessageData) : TermElabM Expr := do - let some expectedType := expectedType? | return e - if (← isDefEq (← inferType e) expectedType) then - return e - else - mkCoeWithErrorMsgs expectedType e mkImmedErrorMsg mkErrorMsg - -/-- - Create a synthetic sorry for the given expected type. If `expectedType? = none`, then a fresh - metavariable is created to represent the type. --/ -private def mkSyntheticSorryFor (expectedType? : Option Expr) : TermElabM Expr := do - let expectedType ← match expectedType? with - | none => mkFreshTypeMVar - | some expectedType => pure expectedType - mkLabeledSorry expectedType (synthetic := true) (unique := false) - -/-- - Log the given exception, and create a synthetic sorry for representing the failed - elaboration step with exception `ex`. --/ -def exceptionToSorry (ex : Exception) (expectedType? : Option Expr) : TermElabM Expr := do - logException ex - mkSyntheticSorryFor expectedType? - -/-- If `mayPostpone == true`, throw `Exception.postpone`. -/ -def tryPostpone : TermElabM Unit := do - if (← read).mayPostpone then - throwPostpone - -/-- Return `true` if `e` reduces (by unfolding only `[reducible]` declarations) to `?m ...` -/ -def isMVarApp (e : Expr) : TermElabM Bool := - return (← whnfR e).getAppFn.isMVar - -/-- If `mayPostpone == true` and `e`'s head is a metavariable, throw `Exception.postpone`. -/ -def tryPostponeIfMVar (e : Expr) : TermElabM Unit := do - if (← isMVarApp e) then - tryPostpone - -/-- If `e? = some e`, then `tryPostponeIfMVar e`, otherwise it is just `tryPostpone`. -/ -def tryPostponeIfNoneOrMVar (e? : Option Expr) : TermElabM Unit := - match e? with - | some e => tryPostponeIfMVar e - | none => tryPostpone - -/-- - Throws `Exception.postpone`, if `expectedType?` contains unassigned metavariables. - It is a noop if `mayPostpone == false`. --/ -def tryPostponeIfHasMVars? (expectedType? : Option Expr) : TermElabM (Option Expr) := do - tryPostponeIfNoneOrMVar expectedType? - let some expectedType := expectedType? | return none - let expectedType ← instantiateMVars expectedType - if expectedType.hasExprMVar then - tryPostpone - return none - return some expectedType - -/-- - Throws `Exception.postpone`, if `expectedType?` contains unassigned metavariables. - If `mayPostpone == false`, it throws error `msg`. --/ -def tryPostponeIfHasMVars (expectedType? : Option Expr) (msg : String) : TermElabM Expr := do - let some expectedType ← tryPostponeIfHasMVars? expectedType? | - throwError "{msg}, expected type contains metavariables{indentD expectedType?}" - return expectedType - -def withExpectedType (expectedType? : Option Expr) (x : Expr → TermElabM Expr) : TermElabM Expr := do - tryPostponeIfNoneOrMVar expectedType? - let some expectedType ← pure expectedType? - | throwError "expected type must be known" - x expectedType - -/-- - Save relevant context for term elaboration postponement. --/ -def saveContext : TermElabM SavedContext := - return { - macroStack := (← read).macroStack - declName? := (← read).declName? - options := (← getOptions) - openDecls := (← getOpenDecls) - errToSorry := (← read).errToSorry - levelNames := (← get).levelNames - } - -/-- - Execute `x` with the context saved using `saveContext`. --/ -def withSavedContext (savedCtx : SavedContext) (x : TermElabM α) : TermElabM α := do - withReader (fun ctx => { ctx with declName? := savedCtx.declName?, macroStack := savedCtx.macroStack, errToSorry := savedCtx.errToSorry }) <| - withTheReader Core.Context (fun ctx => { ctx with options := savedCtx.options, openDecls := savedCtx.openDecls }) <| - withLevelNames savedCtx.levelNames x - -/-- -Delay the elaboration of `stx`, and return a fresh metavariable that works a placeholder. -Remark: the caller is responsible for making sure the info tree is properly updated. -This method is used only at `elabUsingElabFnsAux`. --/ -private def postponeElabTermCore (stx : Syntax) (expectedType? : Option Expr) : TermElabM Expr := do - trace[Elab.postpone] "{stx} : {expectedType?}" - let mvar ← mkFreshExprMVar expectedType? MetavarKind.syntheticOpaque - registerSyntheticMVar stx mvar.mvarId! (SyntheticMVarKind.postponed (← saveContext)) - return mvar - -def getSyntheticMVarDecl? (mvarId : MVarId) : TermElabM (Option SyntheticMVarDecl) := - return (← get).syntheticMVars.get? mvarId - -register_builtin_option debug.byAsSorry : Bool := { - defValue := false - group := "debug" - descr := "replace `by ..` blocks with `sorry` IF the expected type is a proposition" -} - -/-- -Creates a new metavariable of type `type` that will be synthesized using the tactic code. -The `tacticCode` syntax is the full `by ..` syntax. --/ -def mkTacticMVar (type : Expr) (tacticCode : Syntax) (kind : TacticMVarKind) - (delayOnMVars := false) : TermElabM Expr := do - if ← pure (debug.byAsSorry.get (← getOptions)) <&&> isProp type then - withRef tacticCode <| mkLabeledSorry type false (unique := true) - else - let mvar ← mkFreshExprMVar type MetavarKind.syntheticOpaque - let mvarId := mvar.mvarId! - let ref ← getRef - registerSyntheticMVar ref mvarId <| .tactic tacticCode (← saveContext) kind delayOnMVars - return mvar - -/-- - Create an auxiliary annotation to make sure we create an `Info` even if `e` is a metavariable. - See `mkTermInfo`. - - We use this function because some elaboration functions elaborate subterms that may not be immediately - part of the resulting term. Example: - ``` - let_mvar% ?m := b; wait_if_type_mvar% ?m; body - ``` - If the type of `b` is not known, then `wait_if_type_mvar% ?m; body` is postponed and just returns a fresh - metavariable `?n`. The elaborator for - ``` - let_mvar% ?m := b; wait_if_type_mvar% ?m; body - ``` - returns `mkSaveInfoAnnotation ?n` to make sure the info nodes created when elaborating `b` are "saved". - This is a bit hackish, but elaborators like `let_mvar%` are rare. --/ -def mkSaveInfoAnnotation (e : Expr) : Expr := - if e.isMVar then - mkAnnotation `save_info e - else - e - -def isSaveInfoAnnotation? (e : Expr) : Option Expr := - annotation? `save_info e - -partial def removeSaveInfoAnnotation (e : Expr) : Expr := - match isSaveInfoAnnotation? e with - | some e => removeSaveInfoAnnotation e - | _ => e - -/-- - Return `some mvarId` if `e` corresponds to a hole that is going to be filled "later" by executing a tactic or resuming elaboration. - - We do not save `ofTermInfo` for this kind of node in the `InfoTree`. --/ -def isTacticOrPostponedHole? (e : Expr) : TermElabM (Option MVarId) := do - match e with - | Expr.mvar mvarId => - match (← getSyntheticMVarDecl? mvarId) with - | some { kind := .tactic .., .. } => return mvarId - | some { kind := .postponed .., .. } => return mvarId - | _ => return none - | _ => pure none - -def mkTermInfo (elaborator : Name) (stx : Syntax) (e : Expr) (expectedType? : Option Expr := none) - (lctx? : Option LocalContext := none) (isBinder := false) : - TermElabM (Sum Info MVarId) := do - match (← isTacticOrPostponedHole? e) with - | some mvarId => return Sum.inr mvarId - | none => - let e := removeSaveInfoAnnotation e - return Sum.inl <| Info.ofTermInfo { elaborator, lctx := lctx?.getD (← getLCtx), expr := e, stx, expectedType?, isBinder } - -def mkPartialTermInfo (elaborator : Name) (stx : Syntax) (expectedType? : Option Expr := none) - (lctx? : Option LocalContext := none) : - TermElabM Info := do - return Info.ofPartialTermInfo { elaborator, lctx := lctx?.getD (← getLCtx), stx, expectedType? } - -/-- -Pushes a new leaf node to the info tree associating the expression `e` to the syntax `stx`. -As a result, when the user hovers over `stx` they will see the type of `e`, and if `e` -is a constant they will see the constant's doc string. - -* `expectedType?`: the expected type of `e` at the point of elaboration, if available -* `lctx?`: the local context in which to interpret `e` (otherwise it will use `← getLCtx`) -* `elaborator`: a declaration name used as an alternative target for go-to-definition -* `isBinder`: if true, this will be treated as defining `e` (which should be a local constant) - for the purpose of go-to-definition on local variables -* `force`: In patterns, the effect of `addTermInfo` is usually suppressed and replaced - by a `patternWithRef?` annotation which will be turned into a term info on the - post-match-elaboration expression. This flag overrides that behavior and adds the term - info immediately. (See https://github.com/leanprover/lean4/pull/1664.) --/ -def addTermInfo (stx : Syntax) (e : Expr) (expectedType? : Option Expr := none) - (lctx? : Option LocalContext := none) (elaborator := Name.anonymous) - (isBinder := false) (force := false) : TermElabM Expr := do - if (← read).inPattern && !force then - return mkPatternWithRef e stx - else - discard <| withInfoContext' - (pure ()) - (fun _ => mkTermInfo elaborator stx e expectedType? lctx? isBinder) - (mkPartialTermInfo elaborator stx expectedType? lctx?) - return e - -def addTermInfo' (stx : Syntax) (e : Expr) (expectedType? : Option Expr := none) (lctx? : Option LocalContext := none) (elaborator := Name.anonymous) (isBinder := false) : TermElabM Unit := - discard <| addTermInfo stx e expectedType? lctx? elaborator isBinder - -def withInfoContext' (stx : Syntax) (x : TermElabM Expr) - (mkInfo : Expr → TermElabM (Sum Info MVarId)) (mkInfoOnError : TermElabM Info) : - TermElabM Expr := do - if (← read).inPattern then - let e ← x - return mkPatternWithRef e stx - else - Elab.withInfoContext' x mkInfo mkInfoOnError - -/-- Info node capturing `def/let rec` bodies, used by the unused variables linter. -/ -structure BodyInfo where - /-- The body as a fully elaborated term. `none` if the body failed to elaborate. -/ - value? : Option Expr -deriving TypeName - -/-- Creates an `Info.ofCustomInfo` node backed by a `BodyInfo`. -/ -def mkBodyInfo (stx : Syntax) (value? : Option Expr) : Info := - .ofCustomInfo { stx, value := .mk { value? : BodyInfo } } - -/-- Extracts a `BodyInfo` custom info. -/ -def getBodyInfo? : Info → Option BodyInfo - | .ofCustomInfo { value, .. } => value.get? BodyInfo - | _ => none - -def withTermInfoContext' (elaborator : Name) (stx : Syntax) (x : TermElabM Expr) - (expectedType? : Option Expr := none) (lctx? : Option LocalContext := none) - (isBinder : Bool := false) : - TermElabM Expr := - withInfoContext' stx x - (mkTermInfo elaborator stx (expectedType? := expectedType?) (lctx? := lctx?) (isBinder := isBinder)) - (mkPartialTermInfo elaborator stx (expectedType? := expectedType?) (lctx? := lctx?)) - -/-- -Postpone the elaboration of `stx`, return a metavariable that acts as a placeholder, and -ensures the info tree is updated and a hole id is introduced. -When `stx` is elaborated, new info nodes are created and attached to the new hole id in the info tree. --/ -def postponeElabTerm (stx : Syntax) (expectedType? : Option Expr) : TermElabM Expr := do - withTermInfoContext' .anonymous stx (expectedType? := expectedType?) do - postponeElabTermCore stx expectedType? - -/-- - Helper function for `elabTerm` that tries the registered elaboration functions for `stxNode` kind until it finds one that supports the syntax or - an error is found. -/ -private def elabUsingElabFnsAux (s : SavedState) (stx : Syntax) (expectedType? : Option Expr) (catchExPostpone : Bool) - : List (KeyedDeclsAttribute.AttributeEntry TermElab) → TermElabM Expr - | [] => do throwError "unexpected syntax{indentD stx}" - | (elabFn::elabFns) => - try - -- record elaborator in info tree, but only when not backtracking to other elaborators (outer `try`) - withTermInfoContext' elabFn.declName stx (expectedType? := expectedType?) - (try - elabFn.value stx expectedType? - catch ex => match ex with - | .error .. => - if (← read).errToSorry then - exceptionToSorry ex expectedType? - else - throw ex - | .internal id _ => - if (← read).errToSorry && id == abortTermExceptionId then - exceptionToSorry ex expectedType? - else if id == unsupportedSyntaxExceptionId then - throw ex -- to outer try - else if catchExPostpone && id == postponeExceptionId then - /- If `elab` threw `Exception.postpone`, we reset any state modifications. - For example, we want to make sure pending synthetic metavariables created by `elab` before - it threw `Exception.postpone` are discarded. - Note that we are also discarding the messages created by `elab`. - - For example, consider the expression. - `((f.x a1).x a2).x a3` - Now, suppose the elaboration of `f.x a1` produces an `Exception.postpone`. - Then, a new metavariable `?m` is created. Then, `?m.x a2` also throws `Exception.postpone` - because the type of `?m` is not yet known. Then another, metavariable `?n` is created, and - finally `?n.x a3` also throws `Exception.postpone`. If we did not restore the state, we would - keep "dead" metavariables `?m` and `?n` on the pending synthetic metavariable list. This is - wasteful because when we resume the elaboration of `((f.x a1).x a2).x a3`, we start it from scratch - and new metavariables are created for the nested functions. -/ - s.restore - postponeElabTermCore stx expectedType? - else - throw ex) - catch ex => match ex with - | .internal id _ => - if id == unsupportedSyntaxExceptionId then - s.restore -- also removes the info tree created above - elabUsingElabFnsAux s stx expectedType? catchExPostpone elabFns - else - throw ex - | _ => throw ex - -private def elabUsingElabFns (stx : Syntax) (expectedType? : Option Expr) (catchExPostpone : Bool) : TermElabM Expr := do - let s ← saveState - let k := stx.getKind - match termElabAttribute.getEntries (← getEnv) k with - | [] => throwError "elaboration function for `{k}` has not been implemented{indentD stx}" - | elabFns => elabUsingElabFnsAux s stx expectedType? catchExPostpone elabFns - -instance : MonadMacroAdapter TermElabM where - getNextMacroScope := return (← getThe Core.State).nextMacroScope - setNextMacroScope next := modifyThe Core.State fun s => { s with nextMacroScope := next } - -private def isExplicit (stx : Syntax) : Bool := - match stx with - | `(@$_) => true - | _ => false - -private def isExplicitApp (stx : Syntax) : Bool := - stx.getKind == ``Lean.Parser.Term.app && isExplicit stx[0] - -/-- - Return true if `stx` is a lambda abstraction containing a `{}` or `[]` binder annotation. - Example: `fun {α} (a : α) => a` -/ -private def isLambdaWithImplicit (stx : Syntax) : Bool := - match stx with - | `(fun $binders* => $_) => binders.raw.any fun b => b.isOfKind ``Lean.Parser.Term.implicitBinder || b.isOfKind `Lean.Parser.Term.instBinder - | _ => false - -private partial def dropTermParens : Syntax → Syntax := fun stx => - match stx with - | `(($stx)) => dropTermParens stx - | _ => stx - -private def isHole (stx : Syntax) : Bool := - stx.isOfKind ``Lean.Parser.Term.hole || stx.isOfKind ``Lean.Parser.Term.syntheticHole - -private def isTacticBlock (stx : Syntax) : Bool := - match stx with - | `(by $_:tacticSeq) => true - | _ => false - -private def isNoImplicitLambda (stx : Syntax) : Bool := - match stx with - | `(no_implicit_lambda% $_:term) => true - | _ => false - -private def isTypeAscription (stx : Syntax) : Bool := - stx.isOfKind ``Parser.Term.typeAscription - -def hasNoImplicitLambdaAnnotation (type : Expr) : Bool := - annotation? `noImplicitLambda type |>.isSome - -def mkNoImplicitLambdaAnnotation (type : Expr) : Expr := - if hasNoImplicitLambdaAnnotation type then - type - else - mkAnnotation `noImplicitLambda type - -/-- Block usage of implicit lambdas if `stx` is `@f` or `@f arg1 ...` or `fun` with an implicit binder annotation. -/ -def blockImplicitLambda (stx : Syntax) : Bool := - let stx := dropTermParens stx - -- TODO: make it extensible - isExplicit stx || isExplicitApp stx || isLambdaWithImplicit stx || isHole stx || isTacticBlock stx || - isNoImplicitLambda stx || isTypeAscription stx - -/-- Return true iff `stx` is a `Syntax.ident`, and it is a local variable. -/ -def isLocalIdent? (stx : Syntax) : TermElabM (Option Expr) := - match stx with - | Syntax.ident _ _ val _ => do - let r? ← resolveLocalName val - match r? with - | some (fvar, []) => return some fvar - | _ => return none - | _ => return none - -inductive UseImplicitLambdaResult where - | no - | yes (expectedType : Expr) - | postpone - -/-- - Return normalized expected type if it is of the form `{a : α} → β` or `[a : α] → β` and - `blockImplicitLambda stx` is not true, else return `none`. - - Remark: implicit lambdas are not triggered by the strict implicit binder annotation `{{a : α}} → β` --/ -private def useImplicitLambda (stx : Syntax) (expectedType? : Option Expr) : TermElabM UseImplicitLambdaResult := do - if blockImplicitLambda stx then - return .no - let some expectedType := expectedType? | return .no - if hasNoImplicitLambdaAnnotation expectedType then - return .no - let expectedType ← whnfForall expectedType - let .forallE _ _ _ c := expectedType | return .no - unless c.isImplicit || c.isInstImplicit do - return .no - if let some x ← isLocalIdent? stx then - if (← isMVarApp (← inferType x)) then - /- - If `stx` is a local variable without type information, then adding implicit lambdas makes elaboration fail. - We should try to postpone elaboration until the type of the local variable becomes available, or disable - implicit lambdas if we cannot postpone anymore. - Here is an example where this special case is useful. - ``` - def foo2mk (_ : ∀ {α : Type} (a : α), a = a) : nat := 37 - example (x) : foo2mk x = foo2mk x := rfl - ``` - The example about would fail without this special case. - The expected type would be `(a : α✝) → a = a`, where `α✝` is a new free variable introduced by the implicit lambda. - Now, let `?m` be the type of `x`. Then, the constraint `?m =?= (a : α✝) → a = a` cannot be solved using the - assignment `?m := (a : α✝) → a = a` since `α✝` is not in the scope of `?m`. - - Note that, this workaround does not prevent the following example from failing. - ``` - example (x) : foo2mk (id x) = 37 := rfl - ``` - The user can write - ``` - example (x) : foo2mk (id @x) = 37 := rfl - ``` - -/ - return .postpone - return .yes expectedType - -private def decorateErrorMessageWithLambdaImplicitVars (ex : Exception) (impFVars : Array Expr) : TermElabM Exception := do - match ex with - | .error ref msg => - if impFVars.isEmpty then - return Exception.error ref msg - else - let mut msg := m!"{msg}\nthe following variables have been introduced by the implicit lambda feature" - for impFVar in impFVars do - let auxMsg := m!"{impFVar} : {← inferType impFVar}" - let auxMsg ← addMessageContext auxMsg - msg := m!"{msg}{indentD auxMsg}" - msg := m!"{msg}\nyou can disable implicit lambdas using `@` or writing a lambda expression with `\{}` or `[]` binder annotations." - return Exception.error ref msg - | _ => return ex - -private def elabImplicitLambdaAux (stx : Syntax) (catchExPostpone : Bool) (expectedType : Expr) (impFVars : Array Expr) : TermElabM Expr := do - let body ← elabUsingElabFns stx expectedType catchExPostpone - try - let body ← ensureHasType expectedType body - let r ← mkLambdaFVars impFVars body - trace[Elab.implicitForall] r - return r - catch ex => - throw (← decorateErrorMessageWithLambdaImplicitVars ex impFVars) - -private partial def elabImplicitLambda (stx : Syntax) (catchExPostpone : Bool) (type : Expr) : TermElabM Expr := - loop type #[] -where - loop (type : Expr) (fvars : Array Expr) : TermElabM Expr := do - match (← whnfForall type) with - | .forallE n d b c => - if c.isExplicit then - elabImplicitLambdaAux stx catchExPostpone type fvars - else withFreshMacroScope do - let n ← MonadQuotation.addMacroScope n - withLocalDecl n c d fun fvar => do - let type := b.instantiate1 fvar - loop type (fvars.push fvar) - | _ => - elabImplicitLambdaAux stx catchExPostpone type fvars - -/-- Main loop for `elabTerm` -/ -private partial def elabTermAux (expectedType? : Option Expr) (catchExPostpone : Bool) (implicitLambda : Bool) : Syntax → TermElabM Expr - | .missing => mkSyntheticSorryFor expectedType? - | stx => withFreshMacroScope <| withIncRecDepth do - withTraceNode `Elab.step (fun _ => return m!"expected type: {expectedType?}, term\n{stx}") - (tag := stx.getKind.toString) do - checkSystem "elaborator" - let env ← getEnv - let result ← match (← liftMacroM (expandMacroImpl? env stx)) with - | some (decl, stxNew?) => - let stxNew ← liftMacroM <| liftExcept stxNew? - withTermInfoContext' decl stx (expectedType? := expectedType?) <| - withMacroExpansion stx stxNew <| - withRef stxNew <| - elabTermAux expectedType? catchExPostpone implicitLambda stxNew - | _ => - let useImplicitResult ← if implicitLambda && (← read).implicitLambda then useImplicitLambda stx expectedType? else pure .no - match useImplicitResult with - | .yes expectedType => elabImplicitLambda stx catchExPostpone expectedType - | .no => elabUsingElabFns stx expectedType? catchExPostpone - | .postpone => - /- - Try to postpone elaboration, and if we cannot postpone anymore disable implicit lambdas. - See comment at `useImplicitLambda`. - -/ - if (← read).mayPostpone then - if catchExPostpone then - postponeElabTerm stx expectedType? - else - throwPostpone - else - elabUsingElabFns stx expectedType? catchExPostpone - trace[Elab.step.result] result - pure result - -/-- Store in the `InfoTree` that `e` is a "dot"-completion target. `stx` should cover the entire term. -/ -def addDotCompletionInfo (stx : Syntax) (e : Expr) (expectedType? : Option Expr) : TermElabM Unit := do - addCompletionInfo <| CompletionInfo.dot { expr := e, stx, lctx := (← getLCtx), elaborator := .anonymous, expectedType? } (expectedType? := expectedType?) - -/-- - Main function for elaborating terms. - It extracts the elaboration methods from the environment using the node kind. - Recall that the environment has a mapping from `SyntaxNodeKind` to `TermElab` methods. - It creates a fresh macro scope for executing the elaboration method. - All unlogged trace messages produced by the elaboration method are logged using - the position information at `stx`. If the elaboration method throws an `Exception.error` and `errToSorry == true`, - the error is logged and a synthetic sorry expression is returned. - If the elaboration throws `Exception.postpone` and `catchExPostpone == true`, - a new synthetic metavariable of kind `SyntheticMVarKind.postponed` is created, registered, - and returned. - The option `catchExPostpone == false` is used to implement `resumeElabTerm` - to prevent the creation of another synthetic metavariable when resuming the elaboration. - - If `implicitLambda == false`, then disable implicit lambdas feature for the given syntax, but not for its subterms. - We use this flag to implement, for example, the `@` modifier. If `Context.implicitLambda == false`, then this parameter has no effect. - -/ -def elabTerm (stx : Syntax) (expectedType? : Option Expr) (catchExPostpone := true) (implicitLambda := true) : TermElabM Expr := - withRef stx <| elabTermAux expectedType? catchExPostpone implicitLambda stx - -/-- -Similar to `Lean.Elab.Term.elabTerm`, but ensures that the type of the elaborated term is `expectedType?` -by inserting coercions if necessary. - -If `errToSorry` is true, then if coercion insertion fails, this function returns `sorry` and logs the error. -Otherwise, it throws the error. --/ -def elabTermEnsuringType (stx : Syntax) (expectedType? : Option Expr) (catchExPostpone := true) (implicitLambda := true) (errorMsgHeader? : Option String := none) : TermElabM Expr := do - let e ← elabTerm stx expectedType? catchExPostpone implicitLambda - try - withRef stx <| ensureHasType expectedType? e errorMsgHeader? - catch ex => - if (← read).errToSorry && ex matches .error .. then - withRef stx <| exceptionToSorry ex expectedType? - else - throw ex - -/-- Execute `x` and return `some` if no new errors were recorded or exceptions were thrown. Otherwise, return `none`. -/ -def commitIfNoErrors? (x : TermElabM α) : TermElabM (Option α) := do - let saved ← saveState - Core.resetMessageLog - try - let a ← x - if (← MonadLog.hasErrors) then - restoreState saved - return none - else - Core.setMessageLog (saved.meta.core.messages ++ (← Core.getMessageLog)) - return a - catch _ => - restoreState saved - return none - -/-- Adapt a syntax transformation to a regular, term-producing elaborator. -/ -def adaptExpander (exp : Syntax → TermElabM Syntax) : TermElab := fun stx expectedType? => do - let stx' ← exp stx - withMacroExpansion stx stx' <| elabTerm stx' expectedType? - -/-- - Create a new metavariable with the given type, and try to synthesize it. - If type class resolution cannot be executed (e.g., it is stuck because of metavariables in `type`), - register metavariable as a pending one. --/ -def mkInstMVar (type : Expr) (extraErrorMsg? : Option MessageData := none) : TermElabM Expr := do - let mvar ← mkFreshExprMVar type MetavarKind.synthetic - let mvarId := mvar.mvarId! - unless (← synthesizeInstMVarCore mvarId (extraErrorMsg? := extraErrorMsg?)) do - registerSyntheticMVarWithCurrRef mvarId (.typeClass extraErrorMsg?) - return mvar - -/-- - Make sure `e` is a type by inferring its type and making sure it is an `Expr.sort` - or is unifiable with `Expr.sort`, or can be coerced into one. -/ -def ensureType (e : Expr) : TermElabM Expr := do - if (← isType e) then - return e - else - let eType ← inferType e - let u ← mkFreshLevelMVar - if (← isDefEq eType (mkSort u)) then - return e - else if let some coerced ← coerceToSort? e then - return coerced - else - if (← instantiateMVars e).hasSyntheticSorry then - throwAbortTerm - throwError "type expected, got\n ({← instantiateMVars e} : {← instantiateMVars eType})" - -/-- Elaborate `stx` and ensure result is a type. -/ -def elabType (stx : Syntax) : TermElabM Expr := do - let u ← mkFreshLevelMVar - let type ← elabTerm stx (mkSort u) - withRef stx <| ensureType type - -/-- - Enable auto-bound implicits, and execute `k` while catching auto bound implicit exceptions. When an exception is caught, - a new local declaration is created, registered, and `k` is tried to be executed again. -/ -partial def withAutoBoundImplicit (k : TermElabM α) : TermElabM α := do - let flag := autoImplicit.get (← getOptions) - if flag then - withReader (fun ctx => { ctx with autoBoundImplicit := flag, autoBoundImplicits := {} }) do - let rec loop (s : SavedState) : TermElabM α := withIncRecDepth do - checkSystem "auto-implicit" - try - k - catch - | ex => match isAutoBoundImplicitLocalException? ex with - | some n => - -- Restore state, declare `n`, and try again - s.restore (restoreInfo := true) - withLocalDecl n .implicit (← mkFreshTypeMVar) fun x => - withReader (fun ctx => { ctx with autoBoundImplicits := ctx.autoBoundImplicits.push x } ) do - loop (← saveState) - | none => throw ex - loop (← saveState) - else - k - -def withoutAutoBoundImplicit (k : TermElabM α) : TermElabM α := do - withReader (fun ctx => { ctx with autoBoundImplicit := false, autoBoundImplicits := {} }) k - -partial def withAutoBoundImplicitForbiddenPred (p : Name → Bool) (x : TermElabM α) : TermElabM α := do - withReader (fun ctx => { ctx with autoBoundImplicitForbidden := fun n => p n || ctx.autoBoundImplicitForbidden n }) x - -/-- - Collect unassigned metavariables in `type` that are not already in `init` and not satisfying `except`. --/ -partial def collectUnassignedMVars (type : Expr) (init : Array Expr := #[]) (except : MVarId → Bool := fun _ => false) - : TermElabM (Array Expr) := do - let mvarIds ← getMVars type - if mvarIds.isEmpty then - return init - else - go mvarIds.toList init init -where - go (mvarIds : List MVarId) (result visited : Array Expr) : TermElabM (Array Expr) := do - match mvarIds with - | [] => return result - | mvarId :: mvarIds => do - let visited := visited.push (mkMVar mvarId) - if (← mvarId.isAssigned) then - go mvarIds result visited - else if result.contains (mkMVar mvarId) || except mvarId then - go mvarIds result visited - else - let mvarType := (← getMVarDecl mvarId).type - let mvarIdsNew ← getMVars mvarType - let mvarIdsNew := mvarIdsNew.filter fun mvarId => !visited.contains (mkMVar mvarId) - if mvarIdsNew.isEmpty then - go mvarIds (result.push (mkMVar mvarId)) visited - else - go (mvarIdsNew.toList ++ mvarId :: mvarIds) result visited - -/-- -Adds an `InlayHintInfo` for the fvar auto implicits in `autos` at `inlayHintPos`. -The inserted inlay hint has a hover that denotes the type of the auto-implicit (with meta-variables) -and can be inserted at `inlayHintPos`. --/ -def addAutoBoundImplicitsInlayHint (autos : Array Expr) (inlayHintPos : String.Pos) : TermElabM Unit := do - -- If the list of auto-implicits contains a non-type fvar, then the list of auto-implicits will - -- also contain an mvar that denotes the type of the non-type fvar. - -- For example, the auto-implicit `x` in a type `Foo x` for `Foo.{u} {α : Sort u} (x : α) : Type` - -- also comes with an auto-implicit mvar denoting the type of `x`. - -- We have no way of displaying this mvar to the user in an inlay hint, as it doesn't have a name, - -- so we filter it. - -- This also means that inserting the inlay hint with the syntax displayed in the inlay hint will - -- cause a "failed to infer binder type" error, since we don't have a name to insert in the code. - let autos := autos.filter (· matches .fvar ..) - if autos.isEmpty then - return - let autoNames ← autos.mapM (·.fvarId!.getUserName) - let formattedHint := s!" \{{" ".intercalate <| Array.toList <| autoNames.map toString}}" - let deferredResolution ih := do - let description := "Automatically-inserted implicit parameters:" - let codeBlockStart := "```lean" - let typeInfos ← autos.mapM fun auto => do - let name := toString <| ← auto.fvarId!.getUserName - let type := toString <| ← Meta.ppExpr <| ← instantiateMVars (← inferType auto) - return s!"{name} : {type}" - let codeBlockEnd := "```" - let tooltip := "\n".intercalate <| description :: codeBlockStart :: typeInfos.toList ++ [codeBlockEnd] - return { ih with tooltip? := tooltip } - pushInfoLeaf <| .ofCustomInfo { - position := inlayHintPos - label := .name formattedHint - textEdits := #[{ - range := ⟨inlayHintPos, inlayHintPos⟩, - newText := formattedHint - }] - kind? := some .parameter - lctx := ← getLCtx - deferredResolution - : InlayHint - }.toCustomInfo - -/-- - Return `autoBoundImplicits ++ xs` - This method throws an error if a variable in `autoBoundImplicits` depends on some `x` in `xs`. - The `autoBoundImplicits` may contain free variables created by the auto-implicit feature, and unassigned free variables. - It avoids the hack used at `autoBoundImplicitsOld`. - - If `inlayHintPos?` is set, this function also inserts an inlay hint denoting `autoBoundImplicits`. - See `addAutoBoundImplicitsInlayHint` for more information. - - Remark: we cannot simply replace every occurrence of `addAutoBoundImplicitsOld` with this one because a particular - use-case may not be able to handle the metavariables in the array being given to `k`. --/ -def addAutoBoundImplicits (xs : Array Expr) (inlayHintPos? : Option String.Pos) : TermElabM (Array Expr) := do - let autos := (← read).autoBoundImplicits - go autos.toList #[] -where - go (todo : List Expr) (autos : Array Expr) : TermElabM (Array Expr) := do - match todo with - | [] => - if let some inlayHintPos := inlayHintPos? then - addAutoBoundImplicitsInlayHint autos inlayHintPos - for auto in autos do - if auto.isFVar then - let localDecl ← auto.fvarId!.getDecl - for x in xs do - if (← localDeclDependsOn localDecl x.fvarId!) then - throwError "invalid auto implicit argument `{auto}`, it depends on explicitly provided argument `{x}`" - return autos ++ xs - | auto :: todo => - let autos ← collectUnassignedMVars (← inferType auto) autos - go todo (autos.push auto) - -/-- - Similar to `addAutoBoundImplicits`, but converts all metavariables into free variables. - - It uses `mkForallFVars` + `forallBoundedTelescope` to convert metavariables into free variables. - The type `type` is modified during the process if type depends on `xs`. - We use this method to simplify the conversion of code using `autoBoundImplicitsOld` to `autoBoundImplicits`. --/ -def addAutoBoundImplicits' (xs : Array Expr) (type : Expr) (k : Array Expr → Expr → TermElabM α) (inlayHintPos? : Option String.Pos := none) : TermElabM α := do - let xs ← addAutoBoundImplicits xs inlayHintPos? - if xs.all (·.isFVar) then - k xs type - else - forallBoundedTelescope (← mkForallFVars xs type) xs.size fun xs type => k xs type - -def mkAuxName (suffix : Name) : TermElabM Name := mkAuxDeclName (kind := suffix) - -builtin_initialize registerTraceClass `Elab.letrec - -/-- Return true if mvarId is an auxiliary metavariable created for compiling `let rec` or it - is delayed assigned to one. -/ -def isLetRecAuxMVar (mvarId : MVarId) : TermElabM Bool := do - trace[Elab.letrec] "mvarId: {mkMVar mvarId} letrecMVars: {(← get).letRecsToLift.map (mkMVar $ ·.mvarId)}" - let mvarId ← getDelayedMVarRoot mvarId - trace[Elab.letrec] "mvarId root: {mkMVar mvarId}" - return (← get).letRecsToLift.any (·.mvarId == mvarId) - -private def checkDeprecatedCore (constName : Name) : TermElabM Unit := do - if (← read).checkDeprecated then - Linter.checkDeprecated constName - -/-- - Create an `Expr.const` using the given name and explicit levels. - Remark: fresh universe metavariables are created if the constant has more universe - parameters than `explicitLevels`. - - If `checkDeprecated := true`, then `Linter.checkDeprecated` is invoked. --/ -def mkConst (constName : Name) (explicitLevels : List Level := []) : TermElabM Expr := do - checkDeprecatedCore constName - let cinfo ← getConstVal constName - if explicitLevels.length > cinfo.levelParams.length then - throwError "too many explicit universe levels for `{constName}`" - else - let numMissingLevels := cinfo.levelParams.length - explicitLevels.length - let us ← mkFreshLevelMVars numMissingLevels - return Lean.mkConst constName (explicitLevels ++ us) - -def checkDeprecated (ref : Syntax) (e : Expr) : TermElabM Unit := do - if let .const declName _ := e.getAppFn then - withRef ref do checkDeprecatedCore declName - -@[inline] def withoutCheckDeprecated [MonadWithReaderOf Context m] : m α → m α := - withTheReader Context (fun ctx => { ctx with checkDeprecated := false }) - -private def mkConsts (candidates : List (Name × List String)) (explicitLevels : List Level) : TermElabM (List (Expr × List String)) := do - candidates.foldlM (init := []) fun result (declName, projs) => do - -- TODO: better support for `mkConst` failure. We may want to cache the failures, and report them if all candidates fail. - /- - We disable `checkDeprecated` here because there may be many overloaded symbols. - Note that, this method and `resolveName` and `resolveName'` return a list of pairs instead of a list of `TermElabResult`s. - We perform the `checkDeprecated` test at `resolveId?` and `elabAppFnId`. - At `elabAppFnId`, we perform the check when converting the list returned by `resolveName'` into a list of - `TermElabResult`s. - -/ - let const ← withoutCheckDeprecated <| mkConst declName explicitLevels - return (const, projs) :: result - -def resolveName (stx : Syntax) (n : Name) (preresolved : List Syntax.Preresolved) (explicitLevels : List Level) (expectedType? : Option Expr := none) : TermElabM (List (Expr × List String)) := do - addCompletionInfo <| CompletionInfo.id stx stx.getId (danglingDot := false) (← getLCtx) expectedType? - if let some (e, projs) ← resolveLocalName n then - unless explicitLevels.isEmpty do - throwError "invalid use of explicit universe parameters, `{e}` is a local variable" - return [(e, projs)] - let preresolved := preresolved.filterMap fun - | .decl n projs => some (n, projs) - | _ => none - -- check for section variable capture by a quotation - let ctx ← read - if let some (e, projs) := preresolved.findSome? fun (n, projs) => ctx.sectionFVars.find? n |>.map (·, projs) then - return [(e, projs)] -- section variables should shadow global decls - if preresolved.isEmpty then - process (← realizeGlobalName n) - else - process preresolved -where - process (candidates : List (Name × List String)) : TermElabM (List (Expr × List String)) := do - if !candidates.isEmpty then - return (← mkConsts candidates explicitLevels) - let env ← getEnv - -- check for scope errors before trying auto implicits - if env.isExporting then - if let [(npriv, _)] ← withEnv (env.setExporting false) <| resolveGlobalName n then - throwUnknownIdentifierAt (declHint := npriv) stx m!"Unknown identifier `{.ofConstName n}`" - if (← read).autoBoundImplicit && - !(← read).autoBoundImplicitForbidden n && - isValidAutoBoundImplicitName n (relaxedAutoImplicit.get (← getOptions)) then - throwAutoBoundImplicitLocal n - throwUnknownIdentifierAt (declHint := n) stx m!"Unknown identifier `{.ofConstName n}`" - -/-- - Similar to `resolveName`, but creates identifiers for the main part and each projection with position information derived from `ident`. - Example: Assume resolveName `v.head.bla.boo` produces `(v.head, ["bla", "boo"])`, then this method produces - `(v.head, id, [f₁, f₂])` where `id` is an identifier for `v.head`, and `f₁` and `f₂` are identifiers for fields `"bla"` and `"boo"`. -/ -def resolveName' (ident : Syntax) (explicitLevels : List Level) (expectedType? : Option Expr := none) : TermElabM (List (Expr × Syntax × List Syntax)) := do - match ident with - | .ident _ _ n preresolved => - let r ← resolveName ident n preresolved explicitLevels expectedType? - r.mapM fun (c, fields) => do - let ids := ident.identComponents (nFields? := fields.length) - return (c, ids.head!, ids.tail!) - | _ => throwError "identifier expected" - -def resolveId? (stx : Syntax) (kind := "term") (withInfo := false) : TermElabM (Option Expr) := withRef stx do - match stx with - | .ident _ _ val preresolved => - let rs ← try resolveName stx val preresolved [] catch _ => pure [] - let rs := rs.filter fun ⟨_, projs⟩ => projs.isEmpty - let fs := rs.map fun (f, _) => f - match fs with - | [] => return none - | [f] => - let f ← if withInfo then addTermInfo stx f else pure f - checkDeprecated stx f - return some f - | _ => throwError "ambiguous {kind}, use fully qualified name, possible interpretations {fs}" - | _ => throwError "identifier expected" - -def TermElabM.run (x : TermElabM α) (ctx : Context := {}) (s : State := {}) : MetaM (α × State) := - withConfig setElabConfig (x ctx |>.run s) - -@[inline] def TermElabM.run' (x : TermElabM α) (ctx : Context := {}) (s : State := {}) : MetaM α := - (·.1) <$> x.run ctx s - -def TermElabM.toIO (x : TermElabM α) - (ctxCore : Core.Context) (sCore : Core.State) - (ctxMeta : Meta.Context) (sMeta : Meta.State) - (ctx : Context) (s : State) : IO (α × Core.State × Meta.State × State) := do - let ((a, s), sCore, sMeta) ← (x.run ctx s).toIO ctxCore sCore ctxMeta sMeta - return (a, sCore, sMeta, s) - -/-- - Execute `x` and then tries to solve pending universe constraints. - Note that, stuck constraints will not be discarded. --/ -def universeConstraintsCheckpoint (x : TermElabM α) : TermElabM α := do - let a ← x - discard <| processPostponed (mayPostpone := true) (exceptionOnFailure := true) - return a - -def expandDeclId (currNamespace : Name) (currLevelNames : List Name) (declId : Syntax) (modifiers : Modifiers) : TermElabM ExpandDeclIdResult := do +def Term.expandDeclId (currNamespace : Name) (currLevelNames : List Name) (declId : Syntax) (modifiers : Modifiers) : TermElabM ExpandDeclIdResult := do let r ← Elab.expandDeclId currNamespace currLevelNames declId modifiers if (← read).sectionVars.contains r.shortName then throwError "invalid declaration name `{r.shortName}`, there is a section variable with the same name" return r -/-- - Helper function for "embedding" an `Expr` in `Syntax`. - It creates a named hole `?m` and immediately assigns `e` to it. - Examples: - ```lean - let e := mkConst ``Nat.zero - `(Nat.succ $(← exprToSyntax e)) - ``` --/ -def exprToSyntax (e : Expr) : TermElabM Term := withFreshMacroScope do - let result ← `(?m) - let eType ← inferType e - let mvar ← elabTerm result eType - mvar.mvarId!.assign e - return result - -end Term - -open Term in -def withoutModifyingStateWithInfoAndMessages [MonadControlT TermElabM m] [Monad m] (x : m α) : m α := do - controlAt TermElabM fun runInBase => withoutModifyingStateWithInfoAndMessagesImpl <| runInBase x - builtin_initialize registerTraceClass `Elab.postpone registerTraceClass `Elab.coe diff --git a/src/Lean/Elab/Term/TermElabM.lean b/src/Lean/Elab/Term/TermElabM.lean new file mode 100644 index 0000000000..e338a56076 --- /dev/null +++ b/src/Lean/Elab/Term/TermElabM.lean @@ -0,0 +1,2081 @@ +/- +Copyright (c) 2019-2025 Microsoft Corporation. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Leonardo de Moura, Sebastian Ullrich +-/ +module + +prelude +public import Lean.ReservedNameAction +public import Lean.Meta.AppBuilder +public import Lean.Meta.CollectMVars +public import Lean.Meta.Coe +public import Lean.Util.CollectLevelMVars +public import Lean.Linter.Deprecated +public import Lean.Elab.Attributes +public import Lean.Elab.Config +public import Lean.Elab.Level +public import Lean.Elab.PreDefinition.TerminationHint +public import Lean.Elab.DeclarationRange +public import Lean.Elab.WhereFinally +public import Lean.Elab.Util +public import Lean.Language.Basic +public import Lean.Elab.InfoTree.InlayHints +public meta import Lean.Parser.Term + + + +public section + +namespace Lean.Elab + +namespace Term + + +/-- Saved context for postponed terms and tactics to be executed. -/ +structure SavedContext where + declName? : Option Name + options : Options + openDecls : List OpenDecl + macroStack : MacroStack + errToSorry : Bool + levelNames : List Name + +/-- The kind of a tactic metavariable, used for additional error reporting. -/ +inductive TacticMVarKind + /-- Standard tactic metavariable, arising from `by ...` syntax. -/ + | term + /-- Tactic metavariable arising from an autoparam for a function application. -/ + | autoParam (argName : Name) + /-- Tactic metavariable arising from an autoparam for a structure field. -/ + | fieldAutoParam (fieldName structName : Name) + +/-- We use synthetic metavariables as placeholders for pending elaboration steps. -/ +inductive SyntheticMVarKind where + /-- + Use typeclass resolution to synthesize value for metavariable. + If `extraErrorMsg?` is `some msg`, `msg` contains additional information to include in error messages + regarding type class synthesis failure. + -/ + | typeClass (extraErrorMsg? : Option MessageData) + /-- + Use coercion to synthesize value for the metavariable. + If synthesis fails, then throws an error. + - If `mkErrorMsg?` is provided, then the error `mkErrorMsg expectedType e` is thrown. + The `mkErrorMsg` function is allowed to throw an error itself. + - Otherwise, throws a default type mismatch error message. + If `header?` is not provided, the default header is "type mismatch". + If `f?` is provided, then throws an application type mismatch error. + -/ + | coe (header? : Option String) (expectedType : Expr) (e : Expr) (f? : Option Expr) + (mkErrorMsg? : Option (MVarId → Expr → Expr → MetaM MessageData)) + /-- + Use tactic to synthesize value for metavariable. + + If `delayOnMVars` is true, the tactic will not be executed until the goal is free of unassigned + expr metavariables. + -/ + | tactic (tacticCode : Syntax) (ctx : SavedContext) (kind : TacticMVarKind) (delayOnMVars := false) + /-- Metavariable represents a hole whose elaboration has been postponed. -/ + | postponed (ctx : SavedContext) + deriving Inhabited + +/-- +Convert an "extra" optional error message into a message `"\n{msg}"` (if `some msg`) and `MessageData.nil` (if `none`) +-/ +def extraMsgToMsg (extraErrorMsg? : Option MessageData) : MessageData := + if let some msg := extraErrorMsg? then m!"\n{msg}" else .nil + +instance : ToString SyntheticMVarKind where + toString + | .typeClass .. => "typeclass" + | .coe .. => "coe" + | .tactic .. => "tactic" + | .postponed .. => "postponed" + +structure SyntheticMVarDecl where + stx : Syntax + kind : SyntheticMVarKind + deriving Inhabited + +/-- + We can optionally associate an error context with a metavariable (see `MVarErrorInfo`). + We have three different kinds of error context. +-/ +inductive MVarErrorKind where + /-- Metavariable for implicit arguments. `ctx` is the parent application, + `lctx` is a local context where it is valid (necessary for eta feature for named arguments). -/ + | implicitArg (lctx : LocalContext) (ctx : Expr) + /-- Metavariable for explicit holes provided by the user (e.g., `_` and `?m`) -/ + | hole + /-- "Custom", `msgData` stores the additional error messages. -/ + | custom (msgData : MessageData) + deriving Inhabited + +instance : ToString MVarErrorKind where + toString + | .implicitArg _ _ => "implicitArg" + | .hole => "hole" + | .custom _ => "custom" + +/-- + We can optionally associate an error context with metavariables. +-/ +structure MVarErrorInfo where + mvarId : MVarId + ref : Syntax + kind : MVarErrorKind + deriving Inhabited + +/-- +When reporting unexpected universe level metavariables, it is useful to localize the errors +to particular terms, especially at `let` bindings and function binders, +where universe polymorphism is not permitted. +-/ +structure LevelMVarErrorInfo where + lctx : LocalContext + expr : Expr + ref : Syntax + msgData? : Option MessageData := none + deriving Inhabited + +/-- + Nested `let rec` expressions are eagerly lifted by the elaborator. + We store the information necessary for performing the lifting here. +-/ +structure LetRecToLift where + ref : Syntax + fvarId : FVarId + attrs : Array Attribute + shortDeclName : Name + declName : Name + parentName? : Option Name + lctx : LocalContext + localInstances : LocalInstances + type : Expr + val : Expr + mvarId : MVarId + termination : TerminationHints + deriving Inhabited + +/-- + State of the `TermElabM` monad. +-/ +structure State where + levelNames : List Name := [] + syntheticMVars : MVarIdMap SyntheticMVarDecl := {} + pendingMVars : List MVarId := {} + /-- List of errors associated to a metavariable that are shown to the user if the metavariable could not be fully instantiated -/ + mvarErrorInfos : List MVarErrorInfo := [] + /-- List of data to be able to localize universe level metavariable errors to particular expressions. -/ + levelMVarErrorInfos : List LevelMVarErrorInfo := [] + /-- + `mvarArgNames` stores the argument names associated to metavariables. + These are used in combination with `mvarErrorInfos` for throwing errors about metavariables that could not be fully instantiated. + For example when elaborating `List _`, the argument name of the placeholder will be `α`. + + While elaborating an application, `mvarArgNames` is set for each metavariable argument, using the available argument name. + This may happen before or after the `mvarErrorInfos` is set for the same metavariable. + + We used to store the argument names in `mvarErrorInfos`, updating the `MVarErrorInfos` to add the argument name when it is available, + but this doesn't work if the argument name is available _before_ the `mvarErrorInfos` is set for that metavariable. + -/ + mvarArgNames : MVarIdMap Name := {} + letRecsToLift : List LetRecToLift := [] + deriving Inhabited + +/-- + Backtrackable state for the `TermElabM` monad. +-/ +structure SavedState where + «meta» : Meta.SavedState + «elab» : State + deriving Nonempty + +end Term + +namespace Tactic + +/-- + State of the `TacticM` monad. +-/ +structure State where + goals : List MVarId + deriving Inhabited + +/-- + Snapshots are used to implement the `save` tactic. + This tactic caches the state of the system, and allows us to "replay" + expensive proofs efficiently. This is only relevant implementing the + LSP server. +-/ +structure Snapshot where + core : Core.State + «meta» : Meta.State + term : Term.State + tactic : Tactic.State + stx : Syntax + +/-- + Key for the cache used to implement the `save` tactic. +-/ +structure CacheKey where + mvarId : MVarId -- TODO: should include all goals + pos : String.Pos + deriving BEq, Hashable, Inhabited + +/-- + Cache for the `save` tactic. +-/ +structure Cache where + pre : PHashMap CacheKey Snapshot := {} + post : PHashMap CacheKey Snapshot := {} + deriving Inhabited + +section Snapshot +open Language + +structure SavedState where + term : Term.SavedState + tactic : State + +/-- Snapshot after finishing execution of a tactic. -/ +structure TacticFinishedSnapshot extends Language.Snapshot where + /-- State saved for reuse, if no fatal exception occurred. -/ + state? : Option SavedState + /-- Untyped snapshots from `logSnapshotTask`, saved at this level for cancellation. -/ + moreSnaps : Array (SnapshotTask SnapshotTree) +deriving Inhabited +instance : ToSnapshotTree TacticFinishedSnapshot where + toSnapshotTree s := ⟨s.toSnapshot, s.moreSnaps⟩ + +/-- Snapshot just before execution of a tactic. -/ +structure TacticParsedSnapshot extends Language.Snapshot where + /-- Syntax tree of the tactic, stored and compared for incremental reuse. -/ + stx : Syntax + /-- Task for nested incrementality, if enabled for tactic. -/ + inner? : Option (SnapshotTask TacticParsedSnapshot) := none + /-- Task for state after tactic execution. -/ + finished : SnapshotTask TacticFinishedSnapshot + /-- Tasks for subsequent, potentially parallel, tactic steps. -/ + next : Array (SnapshotTask TacticParsedSnapshot) := #[] +deriving Inhabited +partial instance : ToSnapshotTree TacticParsedSnapshot where + toSnapshotTree := go where + go := fun s => ⟨s.toSnapshot, + s.inner?.toArray.map (·.map (sync := true) go) ++ + #[s.finished.map (sync := true) toSnapshotTree] ++ + s.next.map (·.map (sync := true) go)⟩ + +end Snapshot +end Tactic + +namespace Term + +structure Context where + declName? : Option Name := none + macroStack : MacroStack := [] + /-- + When `mayPostpone == true`, an elaboration function may interrupt its execution by throwing `Exception.postpone`. + The function `elabTerm` catches this exception and creates fresh synthetic metavariable `?m`, stores `?m` in + the list of pending synthetic metavariables, and returns `?m`. -/ + mayPostpone : Bool := true + /-- + When `errToSorry` is set to true, the method `elabTerm` catches + exceptions and converts them into synthetic `sorry`s. + The implementation of choice nodes and overloaded symbols rely on the fact + that when `errToSorry` is set to false for an elaboration function `F`, then + `errToSorry` remains `false` for all elaboration functions invoked by `F`. + That is, it is safe to transition `errToSorry` from `true` to `false`, but + we must not set `errToSorry` to `true` when it is currently set to `false`. -/ + errToSorry : Bool := true + /-- + When `autoBoundImplicit` is set to true, instead of producing + an "unknown identifier" error for unbound variables, we generate an + internal exception. This exception is caught at `withAutoBoundImplicit` + which adds an implicit declaration for the unbound variable and tries again. -/ + autoBoundImplicit : Bool := false + autoBoundImplicits : PArray Expr := {} + /-- + A name `n` is only eligible to be an auto implicit name if `autoBoundImplicitForbidden n = false`. + We use this predicate to disallow `f` to be considered an auto implicit name in a definition such + as + ``` + def f : f → Bool := fun _ => true + ``` + -/ + autoBoundImplicitForbidden : Name → Bool := fun _ => false + /-- Map from user name to internal unique name -/ + sectionVars : NameMap Name := {} + /-- Map from internal name to fvar -/ + sectionFVars : NameMap Expr := {} + /-- Enable/disable implicit lambdas feature. -/ + implicitLambda : Bool := true + /-- Heed `elab_as_elim` attribute. -/ + heedElabAsElim : Bool := true + /-- Noncomputable sections automatically add the `noncomputable` modifier to any declaration we cannot generate code for. -/ + isNoncomputableSection : Bool := false + /-- When `true` we skip TC failures. We use this option when processing patterns. -/ + ignoreTCFailures : Bool := false + /-- `true` when elaborating patterns. It affects how we elaborate named holes. -/ + inPattern : Bool := false + /-- + Snapshot for incremental processing of current tactic, if any. + + Invariant: if the bundle's `old?` is set, then the state *up to the start* of the tactic is + unchanged, i.e. reuse is possible. + -/ + tacSnap? : Option (Language.SnapshotBundle Tactic.TacticParsedSnapshot) := none + /-- + If `true`, we store in the `Expr` the `Syntax` for recursive applications (i.e., applications + of free variables tagged with `isAuxDecl`). We store the `Syntax` using `mkRecAppWithSyntax`. + We use the `Syntax` object to produce better error messages at `Structural.lean` and `WF.lean`. -/ + saveRecAppSyntax : Bool := true + /-- + If `holesAsSyntheticOpaque` is `true`, then we mark metavariables associated + with `_`s as `syntheticOpaque` if they do not occur in patterns. + This option is useful when elaborating terms in tactics such as `refine'` where + we want holes there to become new goals. See issue #1681, we have + `refine' (fun x => _) + -/ + holesAsSyntheticOpaque : Bool := false + /-- + If `checkDeprecated := true`, then `Linter.checkDeprecated` when creating constants. + -/ + checkDeprecated : Bool := true + +abbrev TermElabM := ReaderT Context $ StateRefT State MetaM +abbrev TermElab := Syntax → Option Expr → TermElabM Expr + +/- +Make the compiler generate specialized `pure`/`bind` so we do not have to optimize through the +whole monad stack at every use site. May eventually be covered by `deriving`. +-/ +@[always_inline] +instance : Monad TermElabM := + let i := inferInstanceAs (Monad TermElabM) + { pure := i.pure, bind := i.bind } + +open Meta +instance : Inhabited (TermElabM α) where + default := throw default + +protected def saveState : TermElabM SavedState := + return { «meta» := (← Meta.saveState), «elab» := (← get) } + +def SavedState.restore (s : SavedState) (restoreInfo : Bool := false) : TermElabM Unit := do + let traceState ← getTraceState -- We never backtrack trace message + let infoState ← getInfoState -- We also do not backtrack the info nodes when `restoreInfo == false` + s.meta.restore + set s.elab + setTraceState traceState + unless restoreInfo do + setInfoState infoState + +/-- +Like `Meta.withRestoreOrSaveFull` for `TermElabM`, but also takes a `tacSnap?` that +* when running `act`, is set as `Context.tacSnap?` +* otherwise (i.e. on restore) is used to update the new snapshot promise to the old task's + value. +This extra restore step is necessary because while `reusableResult?` can be used to replay any +effects on `State`, `Context.tacSnap?` is not part of it but changed via an `IO` side effect, so +it needs to be replayed separately. + +We use an explicit parameter instead of accessing `Context.tacSnap?` directly because this prevents +`withRestoreOrSaveFull` and `withReader` from being used in the wrong order. +-/ +@[specialize] +def withRestoreOrSaveFull (reusableResult? : Option (α × SavedState)) + (tacSnap? : Option (Language.SnapshotBundle Tactic.TacticParsedSnapshot)) (act : TermElabM α) : + TermElabM (α × SavedState) := do + if let some (_, state) := reusableResult? then + set state.elab + if let some snap := tacSnap? then + let some old := snap.old? + | throwError "withRestoreOrSaveFull: expected old snapshot in `tacSnap?`" + snap.new.resolve old.val.get + + let reusableResult? := reusableResult?.map (fun (val, state) => (val, state.meta)) + let (a, «meta») ← withReader ({ · with tacSnap? }) do + controlAt MetaM fun runInBase => do + Meta.withRestoreOrSaveFull reusableResult? <| runInBase act + return (a, { «meta», «elab» := (← get) }) + +instance : MonadBacktrack SavedState TermElabM where + saveState := Term.saveState + restoreState b := b.restore + +/-- +Incremental elaboration helper. Avoids leakage of data from outside syntax via the monadic context +when running `act` on `stx` by +* setting `stx` as the `ref` and +* deactivating `suppressElabErrors` if `stx` is `missing`-free, which also helps with not hiding + useful errors in this part of the input. Note that if `stx` has `missing`, this should always be + true for the outer syntax as well, so taking the old value of `suppressElabErrors` into account + should not introduce data leakage. + +This combinator should always be used when narrowing reuse to a syntax subtree, usually (in the case +of tactics, to be generalized) via `withNarrowed(Arg)TacticReuse`. +-/ +def withReuseContext [Monad m] [MonadWithReaderOf Core.Context m] (stx : Syntax) (act : m α) : + m α := do + withTheReader Core.Context (fun ctx => { ctx with + ref := stx + suppressElabErrors := ctx.suppressElabErrors && stx.hasMissing }) act + +/-- +Manages reuse information for nested tactics by `split`ting given syntax into an outer and inner +part. `act` is then run on the inner part but with reuse information adjusted as following: +* If the old (from `tacSnap?`'s `SyntaxGuarded.stx`) and new (from `stx`) outer syntax are not + identical according to `Syntax.eqWithInfo`, reuse is disabled. +* Otherwise, the old syntax as stored in `tacSnap?` is updated to the old *inner* syntax. +* In any case, `withReuseContext` is used on the new inner syntax to further prepare the monadic + context. + +For any tactic that participates in reuse, `withNarrowedTacticReuse` should be applied to the +tactic's syntax and `act` should be used to do recursive tactic evaluation of nested parts. Also, +after this function, `getAndEmptySnapshotTasks` should be called and the result stored in a snapshot +so that the tasks don't end up in a snapshot further up and are cancelled together with it; see +note [Incremental Cancellation]. +-/ +def withNarrowedTacticReuse [Monad m] [MonadReaderOf Context m] [MonadLiftT BaseIO m] + [MonadWithReaderOf Core.Context m] [MonadWithReaderOf Context m] [MonadOptions m] + (split : Syntax → Syntax × Syntax) (act : Syntax → m α) (stx : Syntax) : m α := do + let (outer, inner) := split stx + let opts ← getOptions + let ctx ← readThe Term.Context + withTheReader Term.Context (fun ctx => { ctx with tacSnap? := ctx.tacSnap?.map fun tacSnap => + { tacSnap with old? := tacSnap.old?.bind fun old => do + let (oldOuter, oldInner) := split old.stx + guard <| outer.eqWithInfoAndTraceReuse opts oldOuter + return { old with stx := oldInner } + } + }) do + if let some oldOuter := ctx.tacSnap?.bind (·.old?) then + if (← read).tacSnap?.bind (·.old?) |>.isNone then + oldOuter.val.cancelRec + withReuseContext inner (act inner) + +/-- +A variant of `withNarrowedTacticReuse` that uses `stx[argIdx]` as the inner syntax and all `stx` +child nodes before that as the outer syntax, i.e. reuse is disabled if there was any change before +`argIdx`. + +NOTE: child nodes after `argIdx` are not tested (which would almost always disable reuse as they are +necessarily shifted by changes at `argIdx`) so it must be ensured that the result of `arg` does not +depend on them (i.e. they should not be inspected beforehand). +-/ +def withNarrowedArgTacticReuse [Monad m] [MonadReaderOf Context m] [MonadLiftT BaseIO m] + [MonadWithReaderOf Core.Context m] [MonadWithReaderOf Context m] [MonadOptions m] + (argIdx : Nat) (act : Syntax → m α) (stx : Syntax) : m α := + withNarrowedTacticReuse (fun stx => (mkNullNode stx.getArgs[*...argIdx], stx[argIdx])) act stx + +/-- +Disables incremental tactic reuse *and* reporting for `act` if `cond` is true by setting `tacSnap?` +to `none`. This should be done for tactic blocks that are run multiple times as otherwise the +reported progress will jump back and forth (and partial reuse for these kinds of tact blocks is +similarly questionable). +-/ +def withoutTacticIncrementality [Monad m] [MonadWithReaderOf Context m] [MonadOptions m] + (cond : Bool) (act : m α) : m α := do + let opts ← getOptions + withTheReader Term.Context (fun ctx => { ctx with tacSnap? := ctx.tacSnap?.filter fun tacSnap => Id.run do + if let some old := tacSnap.old? then + if cond && opts.getBool `trace.Elab.reuse then + dbg_trace "reuse stopped: guard failed at {old.stx}" + return !cond + }) act + +/-- Disables incremental tactic reuse for `act` if `cond` is true. -/ +def withoutTacticReuse [Monad m] [MonadWithReaderOf Context m] [MonadOptions m] + (cond : Bool) (act : m α) : m α := do + let opts ← getOptions + withTheReader Term.Context (fun ctx => { ctx with tacSnap? := ctx.tacSnap?.map fun tacSnap => + { tacSnap with old? := tacSnap.old?.filter fun old => Id.run do + if cond && opts.getBool `trace.Elab.reuse then + dbg_trace "reuse stopped: guard failed at {old.stx}" + return !cond } + }) act + +@[inherit_doc Core.wrapAsyncAsSnapshot] +def wrapAsyncAsSnapshot {α : Type} (act : α → TermElabM Unit) (cancelTk? : Option IO.CancelToken) + (desc : String := by exact decl_name%.toString) : + TermElabM (α → BaseIO Language.SnapshotTree) := do + let ctx ← read + let st ← get + let metaCtx ← readThe Meta.Context + let metaSt ← getThe Meta.State + Core.wrapAsyncAsSnapshot (cancelTk? := cancelTk?) (desc := desc) fun a => + act a |>.run ctx |>.run' st |>.run' metaCtx metaSt + +abbrev TermElabResult (α : Type) := EStateM.Result Exception SavedState α + +/-- + Execute `x`, save resulting expression and new state. + We remove any `Info` created by `x`. + The info nodes are committed when we execute `applyResult`. + We use `observing` to implement overloaded notation and decls. + We want to save `Info` nodes for the chosen alternative. +-/ +def observing (x : TermElabM α) : TermElabM (TermElabResult α) := do + let s ← saveState + try + let e ← x + let sNew ← saveState + s.restore (restoreInfo := true) + return EStateM.Result.ok e sNew + catch + | ex@(.error ..) => + let sNew ← saveState + s.restore (restoreInfo := true) + return .error ex sNew + | ex@(.internal id _) => + if id == postponeExceptionId then + s.restore (restoreInfo := true) + throw ex + +/-- + Apply the result/exception and state captured with `observing`. + We use this method to implement overloaded notation and symbols. -/ +def applyResult (result : TermElabResult α) : TermElabM α := do + match result with + | .ok a r => r.restore (restoreInfo := true); return a + | .error ex r => r.restore (restoreInfo := true); throw ex + +/-- + Execute `x`, but keep state modifications only if `x` did not postpone. + This method is useful to implement elaboration functions that cannot decide whether + they need to postpone or not without updating the state. -/ +def commitIfDidNotPostpone (x : TermElabM α) : TermElabM α := do + -- We just reuse the implementation of `observing` and `applyResult`. + let r ← observing x + applyResult r + +/-- + Return the universe level names explicitly provided by the user. +-/ +def getLevelNames : TermElabM (List Name) := + return (← get).levelNames + +/-- + Given a free variable `fvar`, return its declaration. + This function panics if `fvar` is not a free variable. +-/ +def getFVarLocalDecl! (fvar : Expr) : TermElabM LocalDecl := do + match (← getLCtx).find? fvar.fvarId! with + | some d => pure d + | none => unreachable! + +instance : AddErrorMessageContext TermElabM where + add ref msg := do + let ctx ← read + let ref := getBetterRef ref ctx.macroStack + let msg ← addMessageContext msg + let msg ← addMacroStack msg ctx.macroStack + pure (ref, msg) + +/-- + Execute `x` without storing `Syntax` for recursive applications. See `saveRecAppSyntax` field at `Context`. +-/ +def withoutSavingRecAppSyntax (x : TermElabM α) : TermElabM α := + withReader (fun ctx => { ctx with saveRecAppSyntax := false }) x + +unsafe def mkTermElabAttributeUnsafe (ref : Name) : IO (KeyedDeclsAttribute TermElab) := + mkElabAttribute TermElab `builtin_term_elab `term_elab `Lean.Parser.Term `Lean.Elab.Term.TermElab "term" ref + +@[implemented_by mkTermElabAttributeUnsafe] +opaque mkTermElabAttribute (ref : Name) : IO (KeyedDeclsAttribute TermElab) + +/-- +Registers a term elaborator for the given syntax node kind. + +A term elaborator should have type `Lean.Elab.Term.TermElab` (which is +`Lean.Syntax → Option Lean.Expr → Lean.Elab.Term.TermElabM Lean.Expr`), i.e. should take syntax of +the given syntax node kind and an optional expected type as parameters and produce an expression. + +The `elab_rules` and `elab` commands should usually be preferred over using this attribute +directly. +-/ +@[builtin_doc] +builtin_initialize termElabAttribute : KeyedDeclsAttribute TermElab ← mkTermElabAttribute decl_name% + +/-- + Auxiliary datatype for presenting a Lean lvalue modifier. + We represent an unelaborated lvalue as a `Syntax` (or `Expr`) and `List LVal`. + Example: `a.foo.1` is represented as the `Syntax` `a` and the list + `[LVal.fieldName "foo", LVal.fieldIdx 1]`. +-/ +inductive LVal where + | fieldIdx (ref : Syntax) (i : Nat) + /-- Field `suffix?` is for producing better error messages because `x.y` may be a field access or a hierarchical/composite name. + `ref` is the syntax object representing the field. `fullRef` includes the LHS. -/ + | fieldName (ref : Syntax) (name : String) (suffix? : Option Name) (fullRef : Syntax) + +def LVal.getRef : LVal → Syntax + | .fieldIdx ref _ => ref + | .fieldName ref .. => ref + +def LVal.isFieldName : LVal → Bool + | .fieldName .. => true + | _ => false + +instance : ToString LVal where + toString + | .fieldIdx _ i => toString i + | .fieldName _ n .. => n + +/-- Return the name of the declaration being elaborated if available. -/ +def getDeclName? : TermElabM (Option Name) := return (← read).declName? +/-- Return the list of nested `let rec` declarations that need to be lifted. -/ +def getLetRecsToLift : TermElabM (List LetRecToLift) := return (← get).letRecsToLift +/-- Return the declaration of the given metavariable -/ +def getMVarDecl (mvarId : MVarId) : TermElabM MetavarDecl := return (← getMCtx).getDecl mvarId + +instance : MonadParentDecl TermElabM where + getParentDeclName? := getDeclName? + +/-- +Executes `x` in the context of the given declaration name. Ensures that the info tree is set up +correctly and adjusts the declaration name generator to generate names below this name, resetting +the nested counter. +-/ +def withDeclName (name : Name) (x : TermElabM α) : TermElabM α := + withReader (fun ctx => { ctx with declName? := name }) do + withSaveParentDeclInfoContext do + withDeclNameForAuxNaming name do + x + +/-- Update the universe level parameter names. -/ +def setLevelNames (levelNames : List Name) : TermElabM Unit := + modify fun s => { s with levelNames := levelNames } + +/-- Execute `x` using `levelNames` as the universe level parameter names. See `getLevelNames`. -/ +def withLevelNames (levelNames : List Name) (x : TermElabM α) : TermElabM α := do + let levelNamesSaved ← getLevelNames + setLevelNames levelNames + try x finally setLevelNames levelNamesSaved + +def withoutErrToSorryImp (x : TermElabM α) : TermElabM α := + withReader (fun ctx => { ctx with errToSorry := false }) x + +/-- + Execute `x` without converting errors (i.e., exceptions) to `sorry` applications. + Recall that when `errToSorry = true`, the method `elabTerm` catches exceptions and converts them into `sorry` applications. +-/ +def withoutErrToSorry [MonadFunctorT TermElabM m] : m α → m α := + monadMap (m := TermElabM) withoutErrToSorryImp + +def withoutHeedElabAsElimImp (x : TermElabM α) : TermElabM α := + withReader (fun ctx => { ctx with heedElabAsElim := false }) x + +/-- + Execute `x` without heeding the `elab_as_elim` attribute. Useful when there is + no expected type (so `elabAppArgs` would fail), but expect that the user wants + to use such constants. +-/ +def withoutHeedElabAsElim [MonadFunctorT TermElabM m] : m α → m α := + monadMap (m := TermElabM) withoutHeedElabAsElimImp + +/-- +Execute `x` but discard changes performed at `Term.State` and `Meta.State`. +Recall that the `Environment`, `InfoState` and messages are at `Core.State`. Thus, any updates to +it will be preserved. +This method is useful for performing computations where all metavariable must be resolved or +discarded. +The `InfoTree`s are not discarded, however, and wrapped in `InfoTree.Context` +to store their metavariable context. +-/ +def withoutModifyingElabMetaStateWithInfo (x : TermElabM α) : TermElabM α := do + let s ← get + let sMeta ← getThe Meta.State + try + withSaveInfoContext x + finally + set s + set sMeta + +/-- + Execute `x` but discard changes performed to the state. + However, the info trees and messages are not discarded. -/ +private def withoutModifyingStateWithInfoAndMessagesImpl (x : TermElabM α) : TermElabM α := do + let saved ← saveState + try + withSaveInfoContext x + finally + let saved := { saved with meta.core.infoState := (← getInfoState), meta.core.messages := (← getThe Core.State).messages } + restoreState saved + +/-- +Wraps the trees returned from `getInfoTrees`, if any, in an `InfoTree.context` node based on the +current monadic context and state. This is mainly used to report info trees early via +`Snapshot.infoTree?`. The trees are not removed from the `getInfoTrees` state as the final info tree +of the elaborated command should be complete and not depend on whether parts have been reported +early. + +As `InfoTree.context` can have only one child, this function panics if `trees` contains more than 1 +tree. Also, `PartialContextInfo.parentDeclCtx` is not currently generated as that information is not +available in the monadic context and only needed for the final info tree. +-/ +def getInfoTreeWithContext? : TermElabM (Option InfoTree) := do + let st ← getInfoState + if st.trees.size > 1 then + return panic! "getInfoTreeWithContext: overfull tree" + let some t := st.trees[0]? | + return none + let t := t.substitute st.assignment + let ctx ← readThe Core.Context + let s ← getThe Core.State + let ctx := PartialContextInfo.commandCtx { + env := s.env, fileMap := ctx.fileMap, mctx := {}, currNamespace := ctx.currNamespace, + openDecls := ctx.openDecls, options := ctx.options, ngen := s.ngen + } + return InfoTree.context ctx t + +/-- For testing `TermElabM` methods. The #eval command will sign the error. -/ +def throwErrorIfErrors : TermElabM Unit := do + if (← MonadLog.hasErrors) then + throwError "Error(s)" + +def traceAtCmdPos (cls : Name) (msg : Unit → MessageData) : TermElabM Unit := + withRef Syntax.missing <| trace cls msg + +def ppGoal (mvarId : MVarId) : TermElabM Format := + Meta.ppGoal mvarId + +open Level (LevelElabM) + +def liftLevelM (x : LevelElabM α) : TermElabM α := do + let ctx ← read + let mctx ← getMCtx + let ngen ← getNGen + let lvlCtx : Level.Context := { options := (← getOptions), ref := (← getRef), autoBoundImplicit := ctx.autoBoundImplicit } + match (x lvlCtx).run { ngen := ngen, mctx := mctx, levelNames := (← getLevelNames) } with + | .ok a newS => setMCtx newS.mctx; setNGen newS.ngen; setLevelNames newS.levelNames; pure a + | .error ex _ => throw ex + +def elabLevel (stx : Syntax) : TermElabM Level := + liftLevelM <| Level.elabLevel stx + +/-- Elaborate `x` with `stx` on the macro stack -/ +def withPushMacroExpansionStack (beforeStx afterStx : Syntax) (x : TermElabM α) : TermElabM α := + withReader (fun ctx => { ctx with macroStack := { before := beforeStx, after := afterStx } :: ctx.macroStack }) x + +/-- Elaborate `x` with `stx` on the macro stack and produce macro expansion info -/ +def withMacroExpansion (beforeStx afterStx : Syntax) (x : TermElabM α) : TermElabM α := + withMacroExpansionInfo beforeStx afterStx do + withPushMacroExpansionStack beforeStx afterStx x + +/-- + Add the given metavariable to the list of pending synthetic metavariables. + The method `synthesizeSyntheticMVars` is used to process the metavariables on this list. -/ +def registerSyntheticMVar (stx : Syntax) (mvarId : MVarId) (kind : SyntheticMVarKind) : TermElabM Unit := do + modify fun s => { s with syntheticMVars := s.syntheticMVars.insert mvarId { stx, kind }, pendingMVars := mvarId :: s.pendingMVars } + +def registerSyntheticMVarWithCurrRef (mvarId : MVarId) (kind : SyntheticMVarKind) : TermElabM Unit := do + registerSyntheticMVar (← getRef) mvarId kind + +def registerMVarErrorInfo (mvarErrorInfo : MVarErrorInfo) : TermElabM Unit := + modify fun s => { s with mvarErrorInfos := mvarErrorInfo :: s.mvarErrorInfos } + +def registerMVarErrorHoleInfo (mvarId : MVarId) (ref : Syntax) : TermElabM Unit := + registerMVarErrorInfo { mvarId, ref, kind := .hole } + +def registerMVarErrorImplicitArgInfo (mvarId : MVarId) (ref : Syntax) (app : Expr) : TermElabM Unit := do + registerMVarErrorInfo { mvarId, ref, kind := .implicitArg (← getLCtx) app } + +def registerMVarErrorCustomInfo (mvarId : MVarId) (ref : Syntax) (msgData : MessageData) : TermElabM Unit := do + registerMVarErrorInfo { mvarId, ref, kind := .custom msgData } + +def registerCustomErrorIfMVar (e : Expr) (ref : Syntax) (msgData : MessageData) : TermElabM Unit := + match e.getAppFn with + | Expr.mvar mvarId => registerMVarErrorCustomInfo mvarId ref msgData + | _ => pure () + +def registerMVarArgName (mvarId : MVarId) (argName : Name) : TermElabM Unit := + modify fun s => { s with mvarArgNames := s.mvarArgNames.insert mvarId argName } + +/-- + Auxiliary method for reporting errors of the form "... contains metavariables ...". + This kind of error is thrown, for example, at `Match.lean` where elaboration + cannot continue if there are metavariables in patterns. + We only want to log it if we haven't logged any errors so far. -/ +def throwMVarError (m : MessageData) : TermElabM α := do + if (← MonadLog.hasErrors) then + throwAbortTerm + else + throwError m + +def MVarErrorInfo.logError (mvarErrorInfo : MVarErrorInfo) (extraMsg? : Option MessageData) : TermElabM Unit := do + match mvarErrorInfo.kind with + | MVarErrorKind.implicitArg lctx app => withLCtx lctx {} do + let app ← instantiateMVars app + let msg ← addArgName "don't know how to synthesize implicit argument" + let msg := msg ++ m!"{indentExpr app.setAppPPExplicitForExposingMVars}" ++ Format.line ++ "context:" ++ Format.line ++ MessageData.ofGoal mvarErrorInfo.mvarId + logErrorAt mvarErrorInfo.ref (appendExtra msg) + | MVarErrorKind.hole => do + let msg ← addArgName "don't know how to synthesize placeholder" " for argument" + let msg := msg ++ Format.line ++ "context:" ++ Format.line ++ MessageData.ofGoal mvarErrorInfo.mvarId + logErrorAt mvarErrorInfo.ref (MessageData.tagged `Elab.synthPlaceholder <| appendExtra msg) + | MVarErrorKind.custom msg => + logErrorAt mvarErrorInfo.ref (appendExtra msg) +where + /-- Append the argument name (if available) to the message. + Remark: if the argument name contains macro scopes we do not append it. -/ + addArgName (msg : MessageData) (extra : String := "") : TermElabM MessageData := do + match (← get).mvarArgNames.get? mvarErrorInfo.mvarId with + | none => return msg + | some argName => return if argName.hasMacroScopes then msg else msg ++ extra ++ m!" `{argName}`" + + appendExtra (msg : MessageData) : MessageData := + match extraMsg? with + | none => msg + | some extraMsg => msg.composePreservingKind extraMsg + +/-- + Try to log errors for the unassigned metavariables `pendingMVarIds`. + + Return `true` if there were "unfilled holes", and we should "abort" declaration. + TODO: try to fill "all" holes using synthetic "sorry's" + + Remark: We only log the "unfilled holes" as new errors if no error has been logged so far. -/ +def logUnassignedUsingErrorInfos (pendingMVarIds : Array MVarId) (extraMsg? : Option MessageData := none) : TermElabM Bool := do + if pendingMVarIds.isEmpty then + return false + else + let hasOtherErrors ← MonadLog.hasErrors + let mut hasNewErrors := false + let mut alreadyVisited : MVarIdSet := {} + let mut errors : Array MVarErrorInfo := #[] + for mvarErrorInfo in (← get).mvarErrorInfos do + let mvarId := mvarErrorInfo.mvarId + unless alreadyVisited.contains mvarId do + alreadyVisited := alreadyVisited.insert mvarId + /- The metavariable `mvarErrorInfo.mvarId` may have been assigned or + delayed assigned to another metavariable that is unassigned. -/ + let mvarDeps ← getMVars (mkMVar mvarId) + if mvarDeps.any pendingMVarIds.contains then do + unless hasOtherErrors do + errors := errors.push mvarErrorInfo + hasNewErrors := true + -- To sort the errors by position use + -- let sortedErrors := errors.qsort fun e₁ e₂ => e₁.ref.getPos?.getD 0 < e₂.ref.getPos?.getD 0 + for error in errors do + error.mvarId.withContext do + error.logError extraMsg? + return hasNewErrors + +def registerLevelMVarErrorInfo (levelMVarErrorInfo : LevelMVarErrorInfo) : TermElabM Unit := + modify fun s => { s with levelMVarErrorInfos := levelMVarErrorInfo :: s.levelMVarErrorInfos } + +def registerLevelMVarErrorExprInfo (expr : Expr) (ref : Syntax) (msgData? : Option MessageData := none) : TermElabM Unit := do + registerLevelMVarErrorInfo { lctx := (← getLCtx), expr, ref, msgData? } + +def exposeLevelMVars (e : Expr) : MetaM Expr := + Core.transform e + (post := fun e => do + match e with + | .const _ us => return .done <| if us.any (·.isMVar) then e.setPPUniverses true else e + | .sort u => return .done <| if u.isMVar then e.setPPUniverses true else e + | .lam _ t _ _ => return .done <| if t.hasLevelMVar then e.setOption `pp.funBinderTypes true else e + | .letE _ t _ _ _ => return .done <| if t.hasLevelMVar then e.setOption `pp.letVarTypes true else e + | _ => return .done e) + +def LevelMVarErrorInfo.logError (levelMVarErrorInfo : LevelMVarErrorInfo) : TermElabM Unit := + Meta.withLCtx levelMVarErrorInfo.lctx {} do + let e' ← exposeLevelMVars (← instantiateMVars levelMVarErrorInfo.expr) + let msg := levelMVarErrorInfo.msgData?.getD m!"don't know how to synthesize universe level metavariables" + let msg := m!"{msg}{indentExpr e'}" + logErrorAt levelMVarErrorInfo.ref msg + +/-- +Try to log errors for unassigned level metavariables `pendingLevelMVarIds`. + +Returns `true` if there are any relevant `LevelMVarErrorInfo`s and we should "abort" the declaration. + +Remark: we only log unassigned level metavariables as new errors if no error has been logged so far. +-/ +def logUnassignedLevelMVarsUsingErrorInfos (pendingLevelMVarIds : Array LMVarId) : TermElabM Bool := do + if pendingLevelMVarIds.isEmpty then + return false + else + let hasOtherErrors ← MonadLog.hasErrors + let mut hasNewErrors := false + let mut errors : Array LevelMVarErrorInfo := #[] + for levelMVarErrorInfo in (← get).levelMVarErrorInfos do + let e ← instantiateMVars levelMVarErrorInfo.expr + let lmvars := (collectLevelMVars {} e).result + if lmvars.any pendingLevelMVarIds.contains then do + unless hasOtherErrors do + errors := errors.push levelMVarErrorInfo + hasNewErrors := true + for error in errors do + error.logError + return hasNewErrors + +/-- Ensure metavariables registered using `registerMVarErrorInfos` (and used in the given declaration) have been assigned. -/ +def ensureNoUnassignedMVars (decl : Declaration) : TermElabM Unit := do + let pendingMVarIds ← getMVarsAtDecl decl + if (← logUnassignedUsingErrorInfos pendingMVarIds) then + throwAbortCommand + +/-- + Execute `x` without allowing it to postpone elaboration tasks. + That is, `tryPostpone` is a noop. -/ +def withoutPostponing (x : TermElabM α) : TermElabM α := + withReader (fun ctx => { ctx with mayPostpone := false }) x + +/-- Creates syntax for `(` `:` `)` -/ +def mkExplicitBinder (ident : Syntax) (type : Syntax) : Syntax := + mkNode ``Lean.Parser.Term.explicitBinder #[mkAtom "(", mkNullNode #[ident], mkNullNode #[mkAtom ":", type], mkNullNode, mkAtom ")"] + +/-- + Convert unassigned universe level metavariables into parameters. + The new parameter names are fresh names of the form `u_i` with regard to `ctx.levelNames`, which is updated with the new names. -/ +def levelMVarToParam (e : Expr) (except : LMVarId → Bool := fun _ => false) : TermElabM Expr := do + let levelNames ← getLevelNames + let r := (← getMCtx).levelMVarToParam (fun n => levelNames.elem n) except e `u 1 + -- Recall that the most recent universe is the first element of the field `levelNames`. + setLevelNames (r.newParamNames.reverse.toList ++ levelNames) + setMCtx r.mctx + return r.expr + +/-- +Creates a fresh inaccessible binder name based on `x`. +Equivalent to ``Lean.Core.mkFreshUserName `x``. + +Do not confuse with `Lean.mkFreshId`, for creating fresh free variable and metavariable ids. +-/ +def mkFreshBinderName [Monad m] [MonadQuotation m] : m Name := + withFreshMacroScope <| MonadQuotation.addMacroScope `x + +/-- + Auxiliary method for creating a `Syntax.ident` containing + a fresh name. This method is intended for creating fresh binder names. + It is just a thin layer on top of `mkFreshUserName`. -/ +def mkFreshIdent [Monad m] [MonadQuotation m] (ref : Syntax) (canonical := false) : m Ident := + return mkIdentFrom ref (← mkFreshBinderName) canonical + +private def applyAttributesCore + (declName : Name) (attrs : Array Attribute) + (applicationTime? : Option AttributeApplicationTime) : TermElabM Unit := do profileitM Exception "attribute application" (← getOptions) do + /- + Remark: if the declaration has syntax errors, `declName` may be `.anonymous` see issue #4309 + In this case, we skip attribute application. + -/ + if declName == .anonymous then + return + withDeclName declName do + for attr in attrs do + withTraceNode `Elab.attribute (fun _ => pure m!"applying [{attr.stx}]") do + withRef attr.stx do withLogging do + let env ← getEnv + match getAttributeImpl env attr.name with + | Except.error errMsg => throwError errMsg + | Except.ok attrImpl => + let runAttr := attrImpl.add declName attr.stx attr.kind + let runAttr := do + -- not truly an elaborator, but a sensible target for go-to-definition + let elaborator := attrImpl.ref + if (← getInfoState).enabled then + withInfoContext (mkInfo := return .ofCommandInfo { elaborator, stx := attr.stx }) do + try runAttr + finally if attr.stx[0].isIdent || attr.stx[0].isAtom then + -- Add an additional node over the leading identifier if there is one to make it look more function-like. + -- Do this last because we want user-created infos to take precedence + pushInfoLeaf <| .ofCommandInfo { elaborator, stx := attr.stx[0] } + else + runAttr + match applicationTime? with + | none => runAttr + | some applicationTime => + if applicationTime == attrImpl.applicationTime then + runAttr + +/-- Apply given attributes **at** a given application time -/ +def applyAttributesAt (declName : Name) (attrs : Array Attribute) (applicationTime : AttributeApplicationTime) : TermElabM Unit := + applyAttributesCore declName attrs applicationTime + +def applyAttributes (declName : Name) (attrs : Array Attribute) : TermElabM Unit := + applyAttributesCore declName attrs none + +def mkTypeMismatchError (header? : Option MessageData) (e : Expr) (eType : Expr) (expectedType : Expr) : MetaM MessageData := do + let header : MessageData := match header? with + | some header => m!"{header} " + | none => m!"Type mismatch{indentExpr e}\n" + return m!"{header}{← mkHasTypeButIsExpectedMsg eType expectedType}" + +def throwTypeMismatchError (header? : Option MessageData) (expectedType : Expr) (eType : Expr) (e : Expr) + (f? : Option Expr := none) (_extraMsg? : Option MessageData := none) : MetaM α := do + /- + We ignore `extraMsg?` for now. In all our tests, it contained no useful information. It was + always of the form: + ``` + failed to synthesize instance + CoeT + ``` + We should revisit this decision in the future and decide whether it may contain useful information + or not. -/ + let extraMsg := Format.nil + /- + let extraMsg : MessageData := match extraMsg? with + | none => Format.nil + | some extraMsg => Format.line ++ extraMsg; + -/ + match f? with + | none => throwError "{← mkTypeMismatchError header? e eType expectedType}{extraMsg}" + | some f => Meta.throwAppTypeMismatch f e + +def withoutMacroStackAtErr (x : TermElabM α) : TermElabM α := + withTheReader Core.Context (fun (ctx : Core.Context) => { ctx with options := pp.macroStack.set ctx.options false }) x + +namespace ContainsPendingMVar + +abbrev M := MonadCacheT Expr Unit (OptionT MetaM) + +/-- See `containsPostponedTerm` -/ +partial def visit (e : Expr) : M Unit := do + checkCache e fun _ => do + match e with + | .forallE _ d b _ => visit d; visit b + | .lam _ d b _ => visit d; visit b + | .letE _ t v b _ => visit t; visit v; visit b + | .app f a => visit f; visit a + | .mdata _ b => visit b + | .proj _ _ b => visit b + | .fvar fvarId .. => + match (← fvarId.getDecl) with + | .cdecl .. => return () + | .ldecl (value := v) .. => visit v + | .mvar mvarId .. => + let e' ← instantiateMVars e + if e' != e then + visit e' + else + match (← getDelayedMVarAssignment? mvarId) with + | some d => visit (mkMVar d.mvarIdPending) + | none => failure + | _ => return () + +end ContainsPendingMVar + +/-- Return `true` if `e` contains a pending metavariable. Remark: it also visits let-declarations. -/ +def containsPendingMVar (e : Expr) : MetaM Bool := do + match (← ContainsPendingMVar.visit e |>.run.run) with + | some _ => return false + | none => return true + +/-- + Try to synthesize metavariable using type class resolution. + This method assumes the local context and local instances of `instMVar` coincide + with the current local context and local instances. + Return `true` if the instance was synthesized successfully, and `false` if + the instance contains unassigned metavariables that are blocking the type class + resolution procedure. Throw an exception if resolution or assignment irrevocably fails. + + If `extraErrorMsg?` is not none, it contains additional information that should be attached + to type class synthesis failures. +-/ +def synthesizeInstMVarCore (instMVar : MVarId) (maxResultSize? : Option Nat := none) (extraErrorMsg? : Option MessageData := none): TermElabM Bool := do + let extraErrorMsg := extraMsgToMsg extraErrorMsg? + let instMVarDecl ← getMVarDecl instMVar + let type := instMVarDecl.type + let type ← instantiateMVars type + let result ← trySynthInstance type maxResultSize? + match result with + | LOption.some val => + if (← instMVar.isAssigned) then + let oldVal ← instantiateMVars (mkMVar instMVar) + unless (← isDefEq oldVal val) do + if (← containsPendingMVar oldVal <||> containsPendingMVar val) then + /- If `val` or `oldVal` contains metavariables directly or indirectly (e.g., in a let-declaration), + we return `false` to indicate we should try again later. This is very coarse grain since + the metavariable may not be responsible for the failure. We should refine the test in the future if needed. + This check has been added to address dependencies between postponed metavariables. The following + example demonstrates the issue fixed by this test. + ``` + structure Point where + x : Nat + y : Nat + + def Point.compute (p : Point) : Point := + let p := { p with x := 1 } + let p := { p with y := 0 } + if (p.x - p.y) > p.x then p else p + ``` + The `isDefEq` test above fails for `Decidable (p.x - p.y ≤ p.x)` when the structure instance assigned to + `p` has not been elaborated yet. + -/ + return false -- we will try again later + let oldValType ← inferType oldVal + let valType ← inferType val + unless (← isDefEq oldValType valType) do + let (oldValType, valType) ← addPPExplicitToExposeDiff oldValType valType + throwError "synthesized type class instance type is not definitionally equal to expected type, synthesized{indentExpr val}\nhas type{indentExpr valType}\nexpected{indentExpr oldValType}{extraErrorMsg}" + let (oldVal, val) ← addPPExplicitToExposeDiff oldVal val + throwError "synthesized type class instance is not definitionally equal to expression inferred by typing rules, synthesized{indentExpr val}\ninferred{indentExpr oldVal}{extraErrorMsg}" + else + unless (← isDefEq (mkMVar instMVar) val) do + throwError "failed to assign synthesized type class instance{indentExpr val}{extraErrorMsg}" + return true + | .undef => return false -- we will try later + | .none => + if (← read).ignoreTCFailures then + return false + else + throwError "failed to synthesize{indentExpr type}{extraErrorMsg}{useDiagnosticMsg}" + +def mkCoe (expectedType : Expr) (e : Expr) (f? : Option Expr := none) (errorMsgHeader? : Option String := none) + (mkErrorMsg? : Option (MVarId → (expectedType e : Expr) → MetaM MessageData) := none) + (mkImmedErrorMsg? : Option ((errorMsg? : Option MessageData) → (expectedType e : Expr) → MetaM MessageData) := none) : TermElabM Expr := do + withTraceNode `Elab.coe (fun _ => return m!"adding coercion for {e} : {← inferType e} =?= {expectedType}") do + try + withoutMacroStackAtErr do + match ← coerce? e expectedType with + | .some eNew => return eNew + | .none => failure + | .undef => + let mvarAux ← mkFreshExprMVar expectedType MetavarKind.syntheticOpaque + registerSyntheticMVarWithCurrRef mvarAux.mvarId! (.coe errorMsgHeader? expectedType e f? mkErrorMsg?) + return mvarAux + catch + | .error _ msg => + if let some mkImmedErrorMsg := mkImmedErrorMsg? then + throwError (← mkImmedErrorMsg msg expectedType e) + else + throwTypeMismatchError errorMsgHeader? expectedType (← inferType e) e f? msg + | _ => + if let some mkImmedErrorMsg := mkImmedErrorMsg? then + throwError (← mkImmedErrorMsg none expectedType e) + else + throwTypeMismatchError errorMsgHeader? expectedType (← inferType e) e f? + +def mkCoeWithErrorMsgs (expectedType : Expr) (e : Expr) + (mkImmedErrorMsg : (errorMsg? : Option MessageData) → (expectedType e : Expr) → MetaM MessageData) + (mkErrorMsg : MVarId → (expectedType e : Expr) → MetaM MessageData) : TermElabM Expr := do + mkCoe expectedType e (mkImmedErrorMsg? := mkImmedErrorMsg) (mkErrorMsg? := mkErrorMsg) + +/-- +If `expectedType?` is `some t`, then ensures `t` and `eType` are definitionally equal by inserting a coercion if necessary. + +Argument `f?` is used only for generating error messages when inserting coercions fails. +-/ +def ensureHasType (expectedType? : Option Expr) (e : Expr) + (errorMsgHeader? : Option String := none) (f? : Option Expr := none) : TermElabM Expr := do + let some expectedType := expectedType? | return e + if (← isDefEq (← inferType e) expectedType) then + return e + else + mkCoe expectedType e f? errorMsgHeader? + +def ensureHasTypeWithErrorMsgs (expectedType? : Option Expr) (e : Expr) + (mkImmedErrorMsg : (errorMsg? : Option MessageData) → (expectedType e : Expr) → MetaM MessageData) + (mkErrorMsg : MVarId → (expectedType e : Expr) → MetaM MessageData) : TermElabM Expr := do + let some expectedType := expectedType? | return e + if (← isDefEq (← inferType e) expectedType) then + return e + else + mkCoeWithErrorMsgs expectedType e mkImmedErrorMsg mkErrorMsg + +/-- + Create a synthetic sorry for the given expected type. If `expectedType? = none`, then a fresh + metavariable is created to represent the type. +-/ +private def mkSyntheticSorryFor (expectedType? : Option Expr) : TermElabM Expr := do + let expectedType ← match expectedType? with + | none => mkFreshTypeMVar + | some expectedType => pure expectedType + mkLabeledSorry expectedType (synthetic := true) (unique := false) + +/-- + Log the given exception, and create a synthetic sorry for representing the failed + elaboration step with exception `ex`. +-/ +def exceptionToSorry (ex : Exception) (expectedType? : Option Expr) : TermElabM Expr := do + logException ex + mkSyntheticSorryFor expectedType? + +/-- If `mayPostpone == true`, throw `Exception.postpone`. -/ +def tryPostpone : TermElabM Unit := do + if (← read).mayPostpone then + throwPostpone + +/-- Return `true` if `e` reduces (by unfolding only `[reducible]` declarations) to `?m ...` -/ +def isMVarApp (e : Expr) : TermElabM Bool := + return (← whnfR e).getAppFn.isMVar + +/-- If `mayPostpone == true` and `e`'s head is a metavariable, throw `Exception.postpone`. -/ +def tryPostponeIfMVar (e : Expr) : TermElabM Unit := do + if (← isMVarApp e) then + tryPostpone + +/-- If `e? = some e`, then `tryPostponeIfMVar e`, otherwise it is just `tryPostpone`. -/ +def tryPostponeIfNoneOrMVar (e? : Option Expr) : TermElabM Unit := + match e? with + | some e => tryPostponeIfMVar e + | none => tryPostpone + +/-- + Throws `Exception.postpone`, if `expectedType?` contains unassigned metavariables. + It is a noop if `mayPostpone == false`. +-/ +def tryPostponeIfHasMVars? (expectedType? : Option Expr) : TermElabM (Option Expr) := do + tryPostponeIfNoneOrMVar expectedType? + let some expectedType := expectedType? | return none + let expectedType ← instantiateMVars expectedType + if expectedType.hasExprMVar then + tryPostpone + return none + return some expectedType + +/-- + Throws `Exception.postpone`, if `expectedType?` contains unassigned metavariables. + If `mayPostpone == false`, it throws error `msg`. +-/ +def tryPostponeIfHasMVars (expectedType? : Option Expr) (msg : String) : TermElabM Expr := do + let some expectedType ← tryPostponeIfHasMVars? expectedType? | + throwError "{msg}, expected type contains metavariables{indentD expectedType?}" + return expectedType + +def withExpectedType (expectedType? : Option Expr) (x : Expr → TermElabM Expr) : TermElabM Expr := do + tryPostponeIfNoneOrMVar expectedType? + let some expectedType ← pure expectedType? + | throwError "expected type must be known" + x expectedType + +/-- + Save relevant context for term elaboration postponement. +-/ +def saveContext : TermElabM SavedContext := + return { + macroStack := (← read).macroStack + declName? := (← read).declName? + options := (← getOptions) + openDecls := (← getOpenDecls) + errToSorry := (← read).errToSorry + levelNames := (← get).levelNames + } + +/-- + Execute `x` with the context saved using `saveContext`. +-/ +def withSavedContext (savedCtx : SavedContext) (x : TermElabM α) : TermElabM α := do + withReader (fun ctx => { ctx with declName? := savedCtx.declName?, macroStack := savedCtx.macroStack, errToSorry := savedCtx.errToSorry }) <| + withTheReader Core.Context (fun ctx => { ctx with options := savedCtx.options, openDecls := savedCtx.openDecls }) <| + withLevelNames savedCtx.levelNames x + +/-- +Delay the elaboration of `stx`, and return a fresh metavariable that works a placeholder. +Remark: the caller is responsible for making sure the info tree is properly updated. +This method is used only at `elabUsingElabFnsAux`. +-/ +private def postponeElabTermCore (stx : Syntax) (expectedType? : Option Expr) : TermElabM Expr := do + trace[Elab.postpone] "{stx} : {expectedType?}" + let mvar ← mkFreshExprMVar expectedType? MetavarKind.syntheticOpaque + registerSyntheticMVar stx mvar.mvarId! (SyntheticMVarKind.postponed (← saveContext)) + return mvar + +def getSyntheticMVarDecl? (mvarId : MVarId) : TermElabM (Option SyntheticMVarDecl) := + return (← get).syntheticMVars.get? mvarId + +register_builtin_option debug.byAsSorry : Bool := { + defValue := false + group := "debug" + descr := "replace `by ..` blocks with `sorry` IF the expected type is a proposition" +} + +/-- +Creates a new metavariable of type `type` that will be synthesized using the tactic code. +The `tacticCode` syntax is the full `by ..` syntax. +-/ +def mkTacticMVar (type : Expr) (tacticCode : Syntax) (kind : TacticMVarKind) + (delayOnMVars := false) : TermElabM Expr := do + if ← pure (debug.byAsSorry.get (← getOptions)) <&&> isProp type then + withRef tacticCode <| mkLabeledSorry type false (unique := true) + else + let mvar ← mkFreshExprMVar type MetavarKind.syntheticOpaque + let mvarId := mvar.mvarId! + let ref ← getRef + registerSyntheticMVar ref mvarId <| .tactic tacticCode (← saveContext) kind delayOnMVars + return mvar + +/-- + Create an auxiliary annotation to make sure we create an `Info` even if `e` is a metavariable. + See `mkTermInfo`. + + We use this function because some elaboration functions elaborate subterms that may not be immediately + part of the resulting term. Example: + ``` + let_mvar% ?m := b; wait_if_type_mvar% ?m; body + ``` + If the type of `b` is not known, then `wait_if_type_mvar% ?m; body` is postponed and just returns a fresh + metavariable `?n`. The elaborator for + ``` + let_mvar% ?m := b; wait_if_type_mvar% ?m; body + ``` + returns `mkSaveInfoAnnotation ?n` to make sure the info nodes created when elaborating `b` are "saved". + This is a bit hackish, but elaborators like `let_mvar%` are rare. +-/ +def mkSaveInfoAnnotation (e : Expr) : Expr := + if e.isMVar then + mkAnnotation `save_info e + else + e + +def isSaveInfoAnnotation? (e : Expr) : Option Expr := + annotation? `save_info e + +partial def removeSaveInfoAnnotation (e : Expr) : Expr := + match isSaveInfoAnnotation? e with + | some e => removeSaveInfoAnnotation e + | _ => e + +/-- + Return `some mvarId` if `e` corresponds to a hole that is going to be filled "later" by executing a tactic or resuming elaboration. + + We do not save `ofTermInfo` for this kind of node in the `InfoTree`. +-/ +def isTacticOrPostponedHole? (e : Expr) : TermElabM (Option MVarId) := do + match e with + | Expr.mvar mvarId => + match (← getSyntheticMVarDecl? mvarId) with + | some { kind := .tactic .., .. } => return mvarId + | some { kind := .postponed .., .. } => return mvarId + | _ => return none + | _ => pure none + +def mkTermInfo (elaborator : Name) (stx : Syntax) (e : Expr) (expectedType? : Option Expr := none) + (lctx? : Option LocalContext := none) (isBinder := false) : + TermElabM (Sum Info MVarId) := do + match (← isTacticOrPostponedHole? e) with + | some mvarId => return Sum.inr mvarId + | none => + let e := removeSaveInfoAnnotation e + return Sum.inl <| Info.ofTermInfo { elaborator, lctx := lctx?.getD (← getLCtx), expr := e, stx, expectedType?, isBinder } + +def mkPartialTermInfo (elaborator : Name) (stx : Syntax) (expectedType? : Option Expr := none) + (lctx? : Option LocalContext := none) : + TermElabM Info := do + return Info.ofPartialTermInfo { elaborator, lctx := lctx?.getD (← getLCtx), stx, expectedType? } + +/-- +Pushes a new leaf node to the info tree associating the expression `e` to the syntax `stx`. +As a result, when the user hovers over `stx` they will see the type of `e`, and if `e` +is a constant they will see the constant's doc string. + +* `expectedType?`: the expected type of `e` at the point of elaboration, if available +* `lctx?`: the local context in which to interpret `e` (otherwise it will use `← getLCtx`) +* `elaborator`: a declaration name used as an alternative target for go-to-definition +* `isBinder`: if true, this will be treated as defining `e` (which should be a local constant) + for the purpose of go-to-definition on local variables +* `force`: In patterns, the effect of `addTermInfo` is usually suppressed and replaced + by a `patternWithRef?` annotation which will be turned into a term info on the + post-match-elaboration expression. This flag overrides that behavior and adds the term + info immediately. (See https://github.com/leanprover/lean4/pull/1664.) +-/ +def addTermInfo (stx : Syntax) (e : Expr) (expectedType? : Option Expr := none) + (lctx? : Option LocalContext := none) (elaborator := Name.anonymous) + (isBinder := false) (force := false) : TermElabM Expr := do + if (← read).inPattern && !force then + return mkPatternWithRef e stx + else + discard <| withInfoContext' + (pure ()) + (fun _ => mkTermInfo elaborator stx e expectedType? lctx? isBinder) + (mkPartialTermInfo elaborator stx expectedType? lctx?) + return e + +def addTermInfo' (stx : Syntax) (e : Expr) (expectedType? : Option Expr := none) (lctx? : Option LocalContext := none) (elaborator := Name.anonymous) (isBinder := false) : TermElabM Unit := + discard <| addTermInfo stx e expectedType? lctx? elaborator isBinder + +def withInfoContext' (stx : Syntax) (x : TermElabM Expr) + (mkInfo : Expr → TermElabM (Sum Info MVarId)) (mkInfoOnError : TermElabM Info) : + TermElabM Expr := do + if (← read).inPattern then + let e ← x + return mkPatternWithRef e stx + else + Elab.withInfoContext' x mkInfo mkInfoOnError + +/-- Info node capturing `def/let rec` bodies, used by the unused variables linter. -/ +structure BodyInfo where + /-- The body as a fully elaborated term. `none` if the body failed to elaborate. -/ + value? : Option Expr +deriving TypeName + +/-- Creates an `Info.ofCustomInfo` node backed by a `BodyInfo`. -/ +def mkBodyInfo (stx : Syntax) (value? : Option Expr) : Info := + .ofCustomInfo { stx, value := .mk { value? : BodyInfo } } + +/-- Extracts a `BodyInfo` custom info. -/ +def getBodyInfo? : Info → Option BodyInfo + | .ofCustomInfo { value, .. } => value.get? BodyInfo + | _ => none + +def withTermInfoContext' (elaborator : Name) (stx : Syntax) (x : TermElabM Expr) + (expectedType? : Option Expr := none) (lctx? : Option LocalContext := none) + (isBinder : Bool := false) : + TermElabM Expr := + withInfoContext' stx x + (mkTermInfo elaborator stx (expectedType? := expectedType?) (lctx? := lctx?) (isBinder := isBinder)) + (mkPartialTermInfo elaborator stx (expectedType? := expectedType?) (lctx? := lctx?)) + +/-- +Postpone the elaboration of `stx`, return a metavariable that acts as a placeholder, and +ensures the info tree is updated and a hole id is introduced. +When `stx` is elaborated, new info nodes are created and attached to the new hole id in the info tree. +-/ +def postponeElabTerm (stx : Syntax) (expectedType? : Option Expr) : TermElabM Expr := do + withTermInfoContext' .anonymous stx (expectedType? := expectedType?) do + postponeElabTermCore stx expectedType? + +/-- + Helper function for `elabTerm` that tries the registered elaboration functions for `stxNode` kind until it finds one that supports the syntax or + an error is found. -/ +private def elabUsingElabFnsAux (s : SavedState) (stx : Syntax) (expectedType? : Option Expr) (catchExPostpone : Bool) + : List (KeyedDeclsAttribute.AttributeEntry TermElab) → TermElabM Expr + | [] => do throwError "unexpected syntax{indentD stx}" + | (elabFn::elabFns) => + try + -- record elaborator in info tree, but only when not backtracking to other elaborators (outer `try`) + withTermInfoContext' elabFn.declName stx (expectedType? := expectedType?) + (try + elabFn.value stx expectedType? + catch ex => match ex with + | .error .. => + if (← read).errToSorry then + exceptionToSorry ex expectedType? + else + throw ex + | .internal id _ => + if (← read).errToSorry && id == abortTermExceptionId then + exceptionToSorry ex expectedType? + else if id == unsupportedSyntaxExceptionId then + throw ex -- to outer try + else if catchExPostpone && id == postponeExceptionId then + /- If `elab` threw `Exception.postpone`, we reset any state modifications. + For example, we want to make sure pending synthetic metavariables created by `elab` before + it threw `Exception.postpone` are discarded. + Note that we are also discarding the messages created by `elab`. + + For example, consider the expression. + `((f.x a1).x a2).x a3` + Now, suppose the elaboration of `f.x a1` produces an `Exception.postpone`. + Then, a new metavariable `?m` is created. Then, `?m.x a2` also throws `Exception.postpone` + because the type of `?m` is not yet known. Then another, metavariable `?n` is created, and + finally `?n.x a3` also throws `Exception.postpone`. If we did not restore the state, we would + keep "dead" metavariables `?m` and `?n` on the pending synthetic metavariable list. This is + wasteful because when we resume the elaboration of `((f.x a1).x a2).x a3`, we start it from scratch + and new metavariables are created for the nested functions. -/ + s.restore + postponeElabTermCore stx expectedType? + else + throw ex) + catch ex => match ex with + | .internal id _ => + if id == unsupportedSyntaxExceptionId then + s.restore -- also removes the info tree created above + elabUsingElabFnsAux s stx expectedType? catchExPostpone elabFns + else + throw ex + | _ => throw ex + +private def elabUsingElabFns (stx : Syntax) (expectedType? : Option Expr) (catchExPostpone : Bool) : TermElabM Expr := do + let s ← saveState + let k := stx.getKind + match termElabAttribute.getEntries (← getEnv) k with + | [] => throwError "elaboration function for `{k}` has not been implemented{indentD stx}" + | elabFns => elabUsingElabFnsAux s stx expectedType? catchExPostpone elabFns + +instance : MonadMacroAdapter TermElabM where + getNextMacroScope := return (← getThe Core.State).nextMacroScope + setNextMacroScope next := modifyThe Core.State fun s => { s with nextMacroScope := next } + +private def isExplicit (stx : Syntax) : Bool := + match stx with + | `(@$_) => true + | _ => false + +private def isExplicitApp (stx : Syntax) : Bool := + stx.getKind == ``Lean.Parser.Term.app && isExplicit stx[0] + +/-- + Return true if `stx` is a lambda abstraction containing a `{}` or `[]` binder annotation. + Example: `fun {α} (a : α) => a` -/ +private def isLambdaWithImplicit (stx : Syntax) : Bool := + match stx with + | `(fun $binders* => $_) => binders.raw.any fun b => b.isOfKind ``Lean.Parser.Term.implicitBinder || b.isOfKind `Lean.Parser.Term.instBinder + | _ => false + +private partial def dropTermParens : Syntax → Syntax := fun stx => + match stx with + | `(($stx)) => dropTermParens stx + | _ => stx + +private def isHole (stx : Syntax) : Bool := + stx.isOfKind ``Lean.Parser.Term.hole || stx.isOfKind ``Lean.Parser.Term.syntheticHole + +private def isTacticBlock (stx : Syntax) : Bool := + match stx with + | `(by $_:tacticSeq) => true + | _ => false + +private def isNoImplicitLambda (stx : Syntax) : Bool := + match stx with + | `(no_implicit_lambda% $_:term) => true + | _ => false + +private def isTypeAscription (stx : Syntax) : Bool := + stx.isOfKind ``Parser.Term.typeAscription + +def hasNoImplicitLambdaAnnotation (type : Expr) : Bool := + annotation? `noImplicitLambda type |>.isSome + +def mkNoImplicitLambdaAnnotation (type : Expr) : Expr := + if hasNoImplicitLambdaAnnotation type then + type + else + mkAnnotation `noImplicitLambda type + +/-- Block usage of implicit lambdas if `stx` is `@f` or `@f arg1 ...` or `fun` with an implicit binder annotation. -/ +def blockImplicitLambda (stx : Syntax) : Bool := + let stx := dropTermParens stx + -- TODO: make it extensible + isExplicit stx || isExplicitApp stx || isLambdaWithImplicit stx || isHole stx || isTacticBlock stx || + isNoImplicitLambda stx || isTypeAscription stx + +/-- Return true iff `stx` is a `Syntax.ident`, and it is a local variable. -/ +def isLocalIdent? (stx : Syntax) : TermElabM (Option Expr) := + match stx with + | Syntax.ident _ _ val _ => do + let r? ← resolveLocalName val + match r? with + | some (fvar, []) => return some fvar + | _ => return none + | _ => return none + +inductive UseImplicitLambdaResult where + | no + | yes (expectedType : Expr) + | postpone + +/-- + Return normalized expected type if it is of the form `{a : α} → β` or `[a : α] → β` and + `blockImplicitLambda stx` is not true, else return `none`. + + Remark: implicit lambdas are not triggered by the strict implicit binder annotation `{{a : α}} → β` +-/ +private def useImplicitLambda (stx : Syntax) (expectedType? : Option Expr) : TermElabM UseImplicitLambdaResult := do + if blockImplicitLambda stx then + return .no + let some expectedType := expectedType? | return .no + if hasNoImplicitLambdaAnnotation expectedType then + return .no + let expectedType ← whnfForall expectedType + let .forallE _ _ _ c := expectedType | return .no + unless c.isImplicit || c.isInstImplicit do + return .no + if let some x ← isLocalIdent? stx then + if (← isMVarApp (← inferType x)) then + /- + If `stx` is a local variable without type information, then adding implicit lambdas makes elaboration fail. + We should try to postpone elaboration until the type of the local variable becomes available, or disable + implicit lambdas if we cannot postpone anymore. + Here is an example where this special case is useful. + ``` + def foo2mk (_ : ∀ {α : Type} (a : α), a = a) : nat := 37 + example (x) : foo2mk x = foo2mk x := rfl + ``` + The example about would fail without this special case. + The expected type would be `(a : α✝) → a = a`, where `α✝` is a new free variable introduced by the implicit lambda. + Now, let `?m` be the type of `x`. Then, the constraint `?m =?= (a : α✝) → a = a` cannot be solved using the + assignment `?m := (a : α✝) → a = a` since `α✝` is not in the scope of `?m`. + + Note that, this workaround does not prevent the following example from failing. + ``` + example (x) : foo2mk (id x) = 37 := rfl + ``` + The user can write + ``` + example (x) : foo2mk (id @x) = 37 := rfl + ``` + -/ + return .postpone + return .yes expectedType + +private def decorateErrorMessageWithLambdaImplicitVars (ex : Exception) (impFVars : Array Expr) : TermElabM Exception := do + match ex with + | .error ref msg => + if impFVars.isEmpty then + return Exception.error ref msg + else + let mut msg := m!"{msg}\nthe following variables have been introduced by the implicit lambda feature" + for impFVar in impFVars do + let auxMsg := m!"{impFVar} : {← inferType impFVar}" + let auxMsg ← addMessageContext auxMsg + msg := m!"{msg}{indentD auxMsg}" + msg := m!"{msg}\nyou can disable implicit lambdas using `@` or writing a lambda expression with `\{}` or `[]` binder annotations." + return Exception.error ref msg + | _ => return ex + +private def elabImplicitLambdaAux (stx : Syntax) (catchExPostpone : Bool) (expectedType : Expr) (impFVars : Array Expr) : TermElabM Expr := do + let body ← elabUsingElabFns stx expectedType catchExPostpone + try + let body ← ensureHasType expectedType body + let r ← mkLambdaFVars impFVars body + trace[Elab.implicitForall] r + return r + catch ex => + throw (← decorateErrorMessageWithLambdaImplicitVars ex impFVars) + +private partial def elabImplicitLambda (stx : Syntax) (catchExPostpone : Bool) (type : Expr) : TermElabM Expr := + loop type #[] +where + loop (type : Expr) (fvars : Array Expr) : TermElabM Expr := do + match (← whnfForall type) with + | .forallE n d b c => + if c.isExplicit then + elabImplicitLambdaAux stx catchExPostpone type fvars + else withFreshMacroScope do + let n ← MonadQuotation.addMacroScope n + withLocalDecl n c d fun fvar => do + let type := b.instantiate1 fvar + loop type (fvars.push fvar) + | _ => + elabImplicitLambdaAux stx catchExPostpone type fvars + +/-- Main loop for `elabTerm` -/ +private partial def elabTermAux (expectedType? : Option Expr) (catchExPostpone : Bool) (implicitLambda : Bool) : Syntax → TermElabM Expr + | .missing => mkSyntheticSorryFor expectedType? + | stx => withFreshMacroScope <| withIncRecDepth do + withTraceNode `Elab.step (fun _ => return m!"expected type: {expectedType?}, term\n{stx}") + (tag := stx.getKind.toString) do + checkSystem "elaborator" + let env ← getEnv + let result ← match (← liftMacroM (expandMacroImpl? env stx)) with + | some (decl, stxNew?) => + let stxNew ← liftMacroM <| liftExcept stxNew? + withTermInfoContext' decl stx (expectedType? := expectedType?) <| + withMacroExpansion stx stxNew <| + withRef stxNew <| + elabTermAux expectedType? catchExPostpone implicitLambda stxNew + | _ => + let useImplicitResult ← if implicitLambda && (← read).implicitLambda then useImplicitLambda stx expectedType? else pure .no + match useImplicitResult with + | .yes expectedType => elabImplicitLambda stx catchExPostpone expectedType + | .no => elabUsingElabFns stx expectedType? catchExPostpone + | .postpone => + /- + Try to postpone elaboration, and if we cannot postpone anymore disable implicit lambdas. + See comment at `useImplicitLambda`. + -/ + if (← read).mayPostpone then + if catchExPostpone then + postponeElabTerm stx expectedType? + else + throwPostpone + else + elabUsingElabFns stx expectedType? catchExPostpone + trace[Elab.step.result] result + pure result + +/-- Store in the `InfoTree` that `e` is a "dot"-completion target. `stx` should cover the entire term. -/ +def addDotCompletionInfo (stx : Syntax) (e : Expr) (expectedType? : Option Expr) : TermElabM Unit := do + addCompletionInfo <| CompletionInfo.dot { expr := e, stx, lctx := (← getLCtx), elaborator := .anonymous, expectedType? } (expectedType? := expectedType?) + +/-- + Main function for elaborating terms. + It extracts the elaboration methods from the environment using the node kind. + Recall that the environment has a mapping from `SyntaxNodeKind` to `TermElab` methods. + It creates a fresh macro scope for executing the elaboration method. + All unlogged trace messages produced by the elaboration method are logged using + the position information at `stx`. If the elaboration method throws an `Exception.error` and `errToSorry == true`, + the error is logged and a synthetic sorry expression is returned. + If the elaboration throws `Exception.postpone` and `catchExPostpone == true`, + a new synthetic metavariable of kind `SyntheticMVarKind.postponed` is created, registered, + and returned. + The option `catchExPostpone == false` is used to implement `resumeElabTerm` + to prevent the creation of another synthetic metavariable when resuming the elaboration. + + If `implicitLambda == false`, then disable implicit lambdas feature for the given syntax, but not for its subterms. + We use this flag to implement, for example, the `@` modifier. If `Context.implicitLambda == false`, then this parameter has no effect. + -/ +def elabTerm (stx : Syntax) (expectedType? : Option Expr) (catchExPostpone := true) (implicitLambda := true) : TermElabM Expr := + withRef stx <| elabTermAux expectedType? catchExPostpone implicitLambda stx + +/-- +Similar to `Lean.Elab.Term.elabTerm`, but ensures that the type of the elaborated term is `expectedType?` +by inserting coercions if necessary. + +If `errToSorry` is true, then if coercion insertion fails, this function returns `sorry` and logs the error. +Otherwise, it throws the error. +-/ +def elabTermEnsuringType (stx : Syntax) (expectedType? : Option Expr) (catchExPostpone := true) (implicitLambda := true) (errorMsgHeader? : Option String := none) : TermElabM Expr := do + let e ← elabTerm stx expectedType? catchExPostpone implicitLambda + try + withRef stx <| ensureHasType expectedType? e errorMsgHeader? + catch ex => + if (← read).errToSorry && ex matches .error .. then + withRef stx <| exceptionToSorry ex expectedType? + else + throw ex + +/-- Execute `x` and return `some` if no new errors were recorded or exceptions were thrown. Otherwise, return `none`. -/ +def commitIfNoErrors? (x : TermElabM α) : TermElabM (Option α) := do + let saved ← saveState + Core.resetMessageLog + try + let a ← x + if (← MonadLog.hasErrors) then + restoreState saved + return none + else + Core.setMessageLog (saved.meta.core.messages ++ (← Core.getMessageLog)) + return a + catch _ => + restoreState saved + return none + +/-- Adapt a syntax transformation to a regular, term-producing elaborator. -/ +def adaptExpander (exp : Syntax → TermElabM Syntax) : TermElab := fun stx expectedType? => do + let stx' ← exp stx + withMacroExpansion stx stx' <| elabTerm stx' expectedType? + +/-- + Create a new metavariable with the given type, and try to synthesize it. + If type class resolution cannot be executed (e.g., it is stuck because of metavariables in `type`), + register metavariable as a pending one. +-/ +def mkInstMVar (type : Expr) (extraErrorMsg? : Option MessageData := none) : TermElabM Expr := do + let mvar ← mkFreshExprMVar type MetavarKind.synthetic + let mvarId := mvar.mvarId! + unless (← synthesizeInstMVarCore mvarId (extraErrorMsg? := extraErrorMsg?)) do + registerSyntheticMVarWithCurrRef mvarId (.typeClass extraErrorMsg?) + return mvar + +/-- + Make sure `e` is a type by inferring its type and making sure it is an `Expr.sort` + or is unifiable with `Expr.sort`, or can be coerced into one. -/ +def ensureType (e : Expr) : TermElabM Expr := do + if (← isType e) then + return e + else + let eType ← inferType e + let u ← mkFreshLevelMVar + if (← isDefEq eType (mkSort u)) then + return e + else if let some coerced ← coerceToSort? e then + return coerced + else + if (← instantiateMVars e).hasSyntheticSorry then + throwAbortTerm + throwError "type expected, got\n ({← instantiateMVars e} : {← instantiateMVars eType})" + +/-- Elaborate `stx` and ensure result is a type. -/ +def elabType (stx : Syntax) : TermElabM Expr := do + let u ← mkFreshLevelMVar + let type ← elabTerm stx (mkSort u) + withRef stx <| ensureType type + +/-- + Enable auto-bound implicits, and execute `k` while catching auto bound implicit exceptions. When an exception is caught, + a new local declaration is created, registered, and `k` is tried to be executed again. -/ +partial def withAutoBoundImplicit (k : TermElabM α) : TermElabM α := do + let flag := autoImplicit.get (← getOptions) + if flag then + withReader (fun ctx => { ctx with autoBoundImplicit := flag, autoBoundImplicits := {} }) do + let rec loop (s : SavedState) : TermElabM α := withIncRecDepth do + checkSystem "auto-implicit" + try + k + catch + | ex => match isAutoBoundImplicitLocalException? ex with + | some n => + -- Restore state, declare `n`, and try again + s.restore (restoreInfo := true) + withLocalDecl n .implicit (← mkFreshTypeMVar) fun x => + withReader (fun ctx => { ctx with autoBoundImplicits := ctx.autoBoundImplicits.push x } ) do + loop (← saveState) + | none => throw ex + loop (← saveState) + else + k + +def withoutAutoBoundImplicit (k : TermElabM α) : TermElabM α := do + withReader (fun ctx => { ctx with autoBoundImplicit := false, autoBoundImplicits := {} }) k + +partial def withAutoBoundImplicitForbiddenPred (p : Name → Bool) (x : TermElabM α) : TermElabM α := do + withReader (fun ctx => { ctx with autoBoundImplicitForbidden := fun n => p n || ctx.autoBoundImplicitForbidden n }) x + +/-- + Collect unassigned metavariables in `type` that are not already in `init` and not satisfying `except`. +-/ +partial def collectUnassignedMVars (type : Expr) (init : Array Expr := #[]) (except : MVarId → Bool := fun _ => false) + : TermElabM (Array Expr) := do + let mvarIds ← getMVars type + if mvarIds.isEmpty then + return init + else + go mvarIds.toList init init +where + go (mvarIds : List MVarId) (result visited : Array Expr) : TermElabM (Array Expr) := do + match mvarIds with + | [] => return result + | mvarId :: mvarIds => do + let visited := visited.push (mkMVar mvarId) + if (← mvarId.isAssigned) then + go mvarIds result visited + else if result.contains (mkMVar mvarId) || except mvarId then + go mvarIds result visited + else + let mvarType := (← getMVarDecl mvarId).type + let mvarIdsNew ← getMVars mvarType + let mvarIdsNew := mvarIdsNew.filter fun mvarId => !visited.contains (mkMVar mvarId) + if mvarIdsNew.isEmpty then + go mvarIds (result.push (mkMVar mvarId)) visited + else + go (mvarIdsNew.toList ++ mvarId :: mvarIds) result visited + +/-- +Adds an `InlayHintInfo` for the fvar auto implicits in `autos` at `inlayHintPos`. +The inserted inlay hint has a hover that denotes the type of the auto-implicit (with meta-variables) +and can be inserted at `inlayHintPos`. +-/ +def addAutoBoundImplicitsInlayHint (autos : Array Expr) (inlayHintPos : String.Pos) : TermElabM Unit := do + -- If the list of auto-implicits contains a non-type fvar, then the list of auto-implicits will + -- also contain an mvar that denotes the type of the non-type fvar. + -- For example, the auto-implicit `x` in a type `Foo x` for `Foo.{u} {α : Sort u} (x : α) : Type` + -- also comes with an auto-implicit mvar denoting the type of `x`. + -- We have no way of displaying this mvar to the user in an inlay hint, as it doesn't have a name, + -- so we filter it. + -- This also means that inserting the inlay hint with the syntax displayed in the inlay hint will + -- cause a "failed to infer binder type" error, since we don't have a name to insert in the code. + let autos := autos.filter (· matches .fvar ..) + if autos.isEmpty then + return + let autoNames ← autos.mapM (·.fvarId!.getUserName) + let formattedHint := s!" \{{" ".intercalate <| Array.toList <| autoNames.map toString}}" + let deferredResolution ih := do + let description := "Automatically-inserted implicit parameters:" + let codeBlockStart := "```lean" + let typeInfos ← autos.mapM fun auto => do + let name := toString <| ← auto.fvarId!.getUserName + let type := toString <| ← Meta.ppExpr <| ← instantiateMVars (← inferType auto) + return s!"{name} : {type}" + let codeBlockEnd := "```" + let tooltip := "\n".intercalate <| description :: codeBlockStart :: typeInfos.toList ++ [codeBlockEnd] + return { ih with tooltip? := tooltip } + pushInfoLeaf <| .ofCustomInfo { + position := inlayHintPos + label := .name formattedHint + textEdits := #[{ + range := ⟨inlayHintPos, inlayHintPos⟩, + newText := formattedHint + }] + kind? := some .parameter + lctx := ← getLCtx + deferredResolution + : InlayHint + }.toCustomInfo + +/-- + Return `autoBoundImplicits ++ xs` + This method throws an error if a variable in `autoBoundImplicits` depends on some `x` in `xs`. + The `autoBoundImplicits` may contain free variables created by the auto-implicit feature, and unassigned free variables. + It avoids the hack used at `autoBoundImplicitsOld`. + + If `inlayHintPos?` is set, this function also inserts an inlay hint denoting `autoBoundImplicits`. + See `addAutoBoundImplicitsInlayHint` for more information. + + Remark: we cannot simply replace every occurrence of `addAutoBoundImplicitsOld` with this one because a particular + use-case may not be able to handle the metavariables in the array being given to `k`. +-/ +def addAutoBoundImplicits (xs : Array Expr) (inlayHintPos? : Option String.Pos) : TermElabM (Array Expr) := do + let autos := (← read).autoBoundImplicits + go autos.toList #[] +where + go (todo : List Expr) (autos : Array Expr) : TermElabM (Array Expr) := do + match todo with + | [] => + if let some inlayHintPos := inlayHintPos? then + addAutoBoundImplicitsInlayHint autos inlayHintPos + for auto in autos do + if auto.isFVar then + let localDecl ← auto.fvarId!.getDecl + for x in xs do + if (← localDeclDependsOn localDecl x.fvarId!) then + throwError "invalid auto implicit argument `{auto}`, it depends on explicitly provided argument `{x}`" + return autos ++ xs + | auto :: todo => + let autos ← collectUnassignedMVars (← inferType auto) autos + go todo (autos.push auto) + +/-- + Similar to `addAutoBoundImplicits`, but converts all metavariables into free variables. + + It uses `mkForallFVars` + `forallBoundedTelescope` to convert metavariables into free variables. + The type `type` is modified during the process if type depends on `xs`. + We use this method to simplify the conversion of code using `autoBoundImplicitsOld` to `autoBoundImplicits`. +-/ +def addAutoBoundImplicits' (xs : Array Expr) (type : Expr) (k : Array Expr → Expr → TermElabM α) (inlayHintPos? : Option String.Pos := none) : TermElabM α := do + let xs ← addAutoBoundImplicits xs inlayHintPos? + if xs.all (·.isFVar) then + k xs type + else + forallBoundedTelescope (← mkForallFVars xs type) xs.size fun xs type => k xs type + +def mkAuxName (suffix : Name) : TermElabM Name := mkAuxDeclName (kind := suffix) + +builtin_initialize registerTraceClass `Elab.letrec + +/-- Return true if mvarId is an auxiliary metavariable created for compiling `let rec` or it + is delayed assigned to one. -/ +def isLetRecAuxMVar (mvarId : MVarId) : TermElabM Bool := do + trace[Elab.letrec] "mvarId: {mkMVar mvarId} letrecMVars: {(← get).letRecsToLift.map (mkMVar $ ·.mvarId)}" + let mvarId ← getDelayedMVarRoot mvarId + trace[Elab.letrec] "mvarId root: {mkMVar mvarId}" + return (← get).letRecsToLift.any (·.mvarId == mvarId) + +private def checkDeprecatedCore (constName : Name) : TermElabM Unit := do + if (← read).checkDeprecated then + Linter.checkDeprecated constName + +/-- + Create an `Expr.const` using the given name and explicit levels. + Remark: fresh universe metavariables are created if the constant has more universe + parameters than `explicitLevels`. + + If `checkDeprecated := true`, then `Linter.checkDeprecated` is invoked. +-/ +def mkConst (constName : Name) (explicitLevels : List Level := []) : TermElabM Expr := do + checkDeprecatedCore constName + let cinfo ← getConstVal constName + if explicitLevels.length > cinfo.levelParams.length then + throwError "too many explicit universe levels for `{constName}`" + else + let numMissingLevels := cinfo.levelParams.length - explicitLevels.length + let us ← mkFreshLevelMVars numMissingLevels + return Lean.mkConst constName (explicitLevels ++ us) + +def checkDeprecated (ref : Syntax) (e : Expr) : TermElabM Unit := do + if let .const declName _ := e.getAppFn then + withRef ref do checkDeprecatedCore declName + +@[inline] def withoutCheckDeprecated [MonadWithReaderOf Context m] : m α → m α := + withTheReader Context (fun ctx => { ctx with checkDeprecated := false }) + +private def mkConsts (candidates : List (Name × List String)) (explicitLevels : List Level) : TermElabM (List (Expr × List String)) := do + candidates.foldlM (init := []) fun result (declName, projs) => do + -- TODO: better support for `mkConst` failure. We may want to cache the failures, and report them if all candidates fail. + /- + We disable `checkDeprecated` here because there may be many overloaded symbols. + Note that, this method and `resolveName` and `resolveName'` return a list of pairs instead of a list of `TermElabResult`s. + We perform the `checkDeprecated` test at `resolveId?` and `elabAppFnId`. + At `elabAppFnId`, we perform the check when converting the list returned by `resolveName'` into a list of + `TermElabResult`s. + -/ + let const ← withoutCheckDeprecated <| mkConst declName explicitLevels + return (const, projs) :: result + +def resolveName (stx : Syntax) (n : Name) (preresolved : List Syntax.Preresolved) (explicitLevels : List Level) (expectedType? : Option Expr := none) : TermElabM (List (Expr × List String)) := do + addCompletionInfo <| CompletionInfo.id stx stx.getId (danglingDot := false) (← getLCtx) expectedType? + if let some (e, projs) ← resolveLocalName n then + unless explicitLevels.isEmpty do + throwError "invalid use of explicit universe parameters, `{e}` is a local variable" + return [(e, projs)] + let preresolved := preresolved.filterMap fun + | .decl n projs => some (n, projs) + | _ => none + -- check for section variable capture by a quotation + let ctx ← read + if let some (e, projs) := preresolved.findSome? fun (n, projs) => ctx.sectionFVars.find? n |>.map (·, projs) then + return [(e, projs)] -- section variables should shadow global decls + if preresolved.isEmpty then + process (← realizeGlobalName n) + else + process preresolved +where + process (candidates : List (Name × List String)) : TermElabM (List (Expr × List String)) := do + if !candidates.isEmpty then + return (← mkConsts candidates explicitLevels) + let env ← getEnv + -- check for scope errors before trying auto implicits + if env.isExporting then + if let [(npriv, _)] ← withEnv (env.setExporting false) <| resolveGlobalName n then + throwUnknownIdentifierAt (declHint := npriv) stx m!"Unknown identifier `{.ofConstName n}`" + if (← read).autoBoundImplicit && + !(← read).autoBoundImplicitForbidden n && + isValidAutoBoundImplicitName n (relaxedAutoImplicit.get (← getOptions)) then + throwAutoBoundImplicitLocal n + throwUnknownIdentifierAt (declHint := n) stx m!"Unknown identifier `{.ofConstName n}`" + +/-- + Similar to `resolveName`, but creates identifiers for the main part and each projection with position information derived from `ident`. + Example: Assume resolveName `v.head.bla.boo` produces `(v.head, ["bla", "boo"])`, then this method produces + `(v.head, id, [f₁, f₂])` where `id` is an identifier for `v.head`, and `f₁` and `f₂` are identifiers for fields `"bla"` and `"boo"`. -/ +def resolveName' (ident : Syntax) (explicitLevels : List Level) (expectedType? : Option Expr := none) : TermElabM (List (Expr × Syntax × List Syntax)) := do + match ident with + | .ident _ _ n preresolved => + let r ← resolveName ident n preresolved explicitLevels expectedType? + r.mapM fun (c, fields) => do + let ids := ident.identComponents (nFields? := fields.length) + return (c, ids.head!, ids.tail!) + | _ => throwError "identifier expected" + +def resolveId? (stx : Syntax) (kind := "term") (withInfo := false) : TermElabM (Option Expr) := withRef stx do + match stx with + | .ident _ _ val preresolved => + let rs ← try resolveName stx val preresolved [] catch _ => pure [] + let rs := rs.filter fun ⟨_, projs⟩ => projs.isEmpty + let fs := rs.map fun (f, _) => f + match fs with + | [] => return none + | [f] => + let f ← if withInfo then addTermInfo stx f else pure f + checkDeprecated stx f + return some f + | _ => throwError "ambiguous {kind}, use fully qualified name, possible interpretations {fs}" + | _ => throwError "identifier expected" + +def TermElabM.run (x : TermElabM α) (ctx : Context := {}) (s : State := {}) : MetaM (α × State) := + withConfig setElabConfig (x ctx |>.run s) + +@[inline] def TermElabM.run' (x : TermElabM α) (ctx : Context := {}) (s : State := {}) : MetaM α := + (·.1) <$> x.run ctx s + +def TermElabM.toIO (x : TermElabM α) + (ctxCore : Core.Context) (sCore : Core.State) + (ctxMeta : Meta.Context) (sMeta : Meta.State) + (ctx : Context) (s : State) : IO (α × Core.State × Meta.State × State) := do + let ((a, s), sCore, sMeta) ← (x.run ctx s).toIO ctxCore sCore ctxMeta sMeta + return (a, sCore, sMeta, s) + +/-- + Execute `x` and then tries to solve pending universe constraints. + Note that, stuck constraints will not be discarded. +-/ +def universeConstraintsCheckpoint (x : TermElabM α) : TermElabM α := do + let a ← x + discard <| processPostponed (mayPostpone := true) (exceptionOnFailure := true) + return a + +/-- + Helper function for "embedding" an `Expr` in `Syntax`. + It creates a named hole `?m` and immediately assigns `e` to it. + Examples: + ```lean + let e := mkConst ``Nat.zero + `(Nat.succ $(← exprToSyntax e)) + ``` +-/ +def exprToSyntax (e : Expr) : TermElabM Term := withFreshMacroScope do + let result ← `(?m) + let eType ← inferType e + let mvar ← elabTerm result eType + mvar.mvarId!.assign e + return result + +end Term + +open Term in +def withoutModifyingStateWithInfoAndMessages [MonadControlT TermElabM m] [Monad m] (x : m α) : m α := do + controlAt TermElabM fun runInBase => withoutModifyingStateWithInfoAndMessagesImpl <| runInBase x diff --git a/src/Lean/Elab/Util.lean b/src/Lean/Elab/Util.lean index ff68e5171d..f367900727 100644 --- a/src/Lean/Elab/Util.lean +++ b/src/Lean/Elab/Util.lean @@ -123,7 +123,7 @@ unsafe def mkElabAttribute (γ) (attrBuiltinName attrName : Name) (parserNamespa if (← getEnv).contains kind && (← getInfoState).enabled then addConstInfo stx[1] kind none return kind - onAdded := fun builtin declName => do + onAdded := fun builtin declName kind => do if builtin then declareBuiltinDocStringAndRanges declName } attrDeclName diff --git a/src/Lean/Exception.lean b/src/Lean/Exception.lean index 2695f614d4..87620e1576 100644 --- a/src/Lean/Exception.lean +++ b/src/Lean/Exception.lean @@ -150,7 +150,7 @@ Throw an unknown constant error message. The end position of the range of `ref` should point at the unknown identifier. See also `mkUnknownIdentifierMessage`. -/ -def throwUnknownConstantAt [Monad m] [MonadEnv m] [MonadError m] (ref : Syntax) (constName : Name) : m α := do +def throwUnknownConstantAt [Monad m] [MonadEnv m] [MonadError m] (ref : Syntax) (constName : Name) : m α := throwUnknownIdentifierAt (declHint := constName) ref m!"Unknown constant `{.ofConstName constName}`" /-- diff --git a/src/Lean/KeyedDeclsAttribute.lean b/src/Lean/KeyedDeclsAttribute.lean index 4d1a443ce2..33c49effc0 100644 --- a/src/Lean/KeyedDeclsAttribute.lean +++ b/src/Lean/KeyedDeclsAttribute.lean @@ -46,7 +46,7 @@ structure Def (γ : Type) where if (← getEnv).contains kind && (← Elab.getInfoState).enabled then Elab.addConstInfo stx kind none pure kind) - onAdded (builtin : Bool) (declName : Name) : AttrM Unit := pure () + onAdded (builtin : Bool) (declName : Name) (key : Key) : AttrM Unit := pure () deriving Inhabited structure OLeanEntry where @@ -139,7 +139,7 @@ protected unsafe def init {γ} (df : Def γ) (attrDeclName : Name := by exact de /- builtin_initialize @addBuiltin $(mkConst valueTypeName) $(mkConst attrDeclName) $(key) $(declName) $(mkConst declName) -/ let val := mkAppN (mkConst ``addBuiltin) #[mkConst df.valueTypeName, mkConst attrDeclName, toExpr key, toExpr declName, mkConst declName] declareBuiltin declName val - df.onAdded true declName + df.onAdded true declName key | _ => throwUnexpectedType applicationTime := AttributeApplicationTime.afterCompilation } @@ -157,7 +157,7 @@ protected unsafe def init {γ} (df : Def γ) (attrDeclName : Name := by exact de | none => let val ← evalConstCheck γ df.valueTypeName declName ext.add { key := key, declName := declName, value := val } attrKind - df.onAdded false declName + df.onAdded false declName key | _ => -- If the declaration contains `sorry`, we skip `evalConstCheck` to avoid unnecessary bizarre error message pure () diff --git a/src/Lean/Linter.lean b/src/Lean/Linter.lean index 450c90a55e..8107dec49c 100644 --- a/src/Lean/Linter.lean +++ b/src/Lean/Linter.lean @@ -10,6 +10,7 @@ public import Lean.Linter.Util public import Lean.Linter.Builtin public import Lean.Linter.ConstructorAsVariable public import Lean.Linter.Deprecated +public import Lean.Linter.DocsOnAlt public import Lean.Linter.UnusedVariables public import Lean.Linter.MissingDocs public import Lean.Linter.Omit diff --git a/src/Lean/Linter/DocsOnAlt.lean b/src/Lean/Linter/DocsOnAlt.lean new file mode 100644 index 0000000000..007ab8dabe --- /dev/null +++ b/src/Lean/Linter/DocsOnAlt.lean @@ -0,0 +1,62 @@ +/- +Copyright (c) 2025 Lean FRO, LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: David Thrane Christiansen +-/ +module + +prelude +import Lean.Parser.Syntax +public import Lean.Data.Options +import Lean.Elab.Command +import Lean.Linter.Basic +import Lean.Server.InfoUtils + +public section + +namespace Lean.Linter +open Elab.Command Parser Command +open Parser.Term hiding «set_option» + +register_builtin_option linter.tactic.docsOnAlt : Bool := { + defValue := true + descr := "enable the 'documentation on tactic alternatives' linter" +} + +private def getLinterDocsOnAlt (o : LinterOptions) : Bool := + getLinterValue linter.tactic.docsOnAlt o + +namespace DocsOnAlt + + +private partial def docsOnAlt : Linter where + run stx := do + if getLinterDocsOnAlt (← getLinterOptions) then + if let some mods := stx.find? (·.getKind ∈ [``declModifiers, ``Command.syntax]) then + if mods.find? isAltAttr |>.isSome then + if let some doc := mods.find? (·.getKind == ``docComment) then + let msg := m!"Documentation is ignored on a tactic alternative." + logLint linter.tactic.docsOnAlt doc msg + else if let some cmd := stx.find? (·.getKind == ``Command.attribute) then + let attrs := cmd[2] + let names := cmd[4].getArgs + if (attrs.find? isAltAttr).isSome then + let infoTrees := (← get).infoState.trees.toArray + for tree in infoTrees do + tree.visitM' (postNode := fun ci info _ => do + match info with + | .ofTermInfo ti => + if names.contains ti.stx then + if let some (x, _) := ti.expr.const? then + if (← findInternalDocString? ci.env x).isSome then + let msg := m!"Documentation for `{.ofConstName x}` is ignored because it is \ + a tactic alternative." + logLint linter.tactic.docsOnAlt ti.stx msg + | _ => pure ()) +where + isAltAttr : Syntax → Bool + | `(attr|tactic_alt $_) => true + | _ => false + + +builtin_initialize addLinter docsOnAlt diff --git a/src/Lean/Parser/Basic.lean b/src/Lean/Parser/Basic.lean index 024cb8f739..dcc3b7b5a0 100644 --- a/src/Lean/Parser/Basic.lean +++ b/src/Lean/Parser/Basic.lean @@ -97,6 +97,9 @@ def andthenFn (p q : ParserFn) : ParserFn := fun c s => firstTokens := p.firstTokens.seq q.firstTokens } +instance : AndThen ParserFn where + andThen p1 p2 := andthenFn p1 (p2 ()) + /-- The `andthen(p, q)` combinator, usually written as adjacency in syntax declarations (`p q`), parses `p` followed by `q`. @@ -269,6 +272,9 @@ def orelseFn (p q : ParserFn) : ParserFn := firstTokens := p.firstTokens.merge q.firstTokens } +instance : OrElse ParserFn where + orElse p1 p2 := orelseFn p1 (p2 ()) + /-- Run `p`, falling back to `q` if `p` failed without consuming any input. @@ -314,21 +320,22 @@ Recover from errors in `p` using `recover` to consume input until a known-good s If `recover` fails itself, then no recovery is performed. `recover` is provided with information about the failing parser's effects , and it is run in the -state immediately after the failure. -/ +state immediately after the failure. +-/ def recoverFn (p : ParserFn) (recover : RecoveryContext → ParserFn) : ParserFn := fun c s => let iniPos := s.pos let iniSz := s.stxStack.size - let s' := p c s - if let some msg := s'.errorMsg then - let s' := recover ⟨iniPos, iniSz⟩ c {s' with errorMsg := none} - if s'.hasError then s' - else {s' with - pos := s'.pos, - lhsPrec := s'.lhsPrec, - cache := s'.cache, - errorMsg := none, - recoveredErrors := s'.recoveredErrors.push (s'.pos, s'.stxStack, msg) } - else s' + let s := p c s + if let some msg := s.errorMsg then + let s' := recover ⟨iniPos, iniSz⟩ c {s with errorMsg := none} + if s'.hasError then s + else {s with + pos := s'.pos, + errorMsg := none, + stxStack := s'.stxStack, + recoveredErrors := s.recoveredErrors.push (s'.pos, s'.stxStack, msg) } + else s + /-- Recover from errors in `parser` using `handler` to consume input until a known-good state has appeared. @@ -676,14 +683,19 @@ In particular, string gaps (`"\" newline whitespace*`). def quotedStringFn : ParserFn := quotedCharCoreFn isQuotableCharDefault true -/-- Push `(Syntax.node tk )` onto syntax stack if parse was successful. -/ -def mkNodeToken (n : SyntaxNodeKind) (startPos : String.Pos) : ParserFn := fun c s => Id.run do +/-- +Push `(Syntax.node tk )` onto syntax stack if parse was successful. + +If `includeWhitespace` is `false`, trailing whitespace is left behind. +-/ +def mkNodeToken (n : SyntaxNodeKind) (startPos : String.Pos) + (includeWhitespace := true) : ParserFn := fun c s => Id.run do if s.hasError then return s let stopPos := s.pos let leading := c.mkEmptySubstringAt startPos let val := c.extract startPos stopPos - let s := whitespace c s + let s := if includeWhitespace then whitespace c s else s let wsStopPos := s.pos let trailing := c.substring (startPos := stopPos) (stopPos := wsStopPos) let info := SourceInfo.original leading startPos trailing stopPos @@ -701,19 +713,19 @@ def charLitFnAux (startPos : String.Pos) : ParserFn := fun c s => let i := s.pos let curr := c.get i let s := s.setPos (c.next i) - if curr == '\'' then mkNodeToken charLitKind startPos c s + if curr == '\'' then mkNodeToken charLitKind startPos true c s else s.mkUnexpectedError "missing end of character literal" -partial def strLitFnAux (startPos : String.Pos) : ParserFn := fun c s => +partial def strLitFnAux (startPos : String.Pos) (includeWhitespace := false) : ParserFn := fun c s => let i := s.pos if h : c.atEnd i then s.mkUnexpectedErrorAt "unterminated string literal" startPos else let curr := c.get' i h let s := s.setPos (c.next' i h) if curr == '\"' then - mkNodeToken strLitKind startPos c s + mkNodeToken strLitKind startPos true c s else if curr == '\\' then andthenFn quotedStringFn (strLitFnAux startPos) c s - else strLitFnAux startPos c s + else strLitFnAux startPos includeWhitespace c s /-- Raw strings have the syntax `r##...#"..."#...##` with zero or more `#`'s. @@ -770,7 +782,7 @@ where let s := s.setPos (c.next' i h) if curr == '\"' then if num == 0 then - mkNodeToken strLitKind startPos c s + mkNodeToken strLitKind startPos true c s else closingState num 0 c s else @@ -788,7 +800,7 @@ where let s := s.setPos (c.next' i h) if curr == '#' then if closingNum + 1 == num then - mkNodeToken strLitKind startPos c s + mkNodeToken strLitKind startPos true c s else closingState num (closingNum + 1) c s else if curr == '\"' then @@ -816,25 +828,26 @@ partial def takeDigitsFn (isDigit : Char → Bool) (expecting : String) (needDig else if needDigit then s.mkUnexpectedError "unexpected character" (expected := [expecting]) else s -def decimalNumberFn (startPos : String.Pos) (c : ParserContext) : ParserState → ParserState := fun s => +def decimalNumberFn (startPos : String.Pos) (includeWhitespace := true) + (c : ParserContext) (s : ParserState) : ParserState := let s := takeDigitsFn (fun c => c.isDigit) "decimal number" false c s let i := s.pos if h : c.atEnd i then - mkNodeToken numLitKind startPos c s + mkNodeToken numLitKind startPos true c s else let curr := c.get' i h let j := c.next i if ∃ hj : ¬ c.atEnd j, curr = '.' && c.get' j hj = '.' then - mkNodeToken numLitKind startPos c s + mkNodeToken numLitKind startPos includeWhitespace c s else if curr == '.' || curr == 'e' || curr == 'E' then parseScientific s else - mkNodeToken numLitKind startPos c s + mkNodeToken numLitKind startPos includeWhitespace c s where parseScientific s := let s := parseOptDot s let s := parseOptExp s - mkNodeToken scientificLitKind startPos c s + mkNodeToken scientificLitKind startPos includeWhitespace c s parseOptDot s := let i := s.pos @@ -863,19 +876,23 @@ where else s -def binNumberFn (startPos : String.Pos) : ParserFn := fun c s => +def binNumberFn (startPos : String.Pos) (includeWhitespace := true) : ParserFn := fun c s => let s := takeDigitsFn (fun c => c == '0' || c == '1') "binary number" true c s - mkNodeToken numLitKind startPos c s + mkNodeToken numLitKind startPos includeWhitespace c s -def octalNumberFn (startPos : String.Pos) : ParserFn := fun c s => +def octalNumberFn (startPos : String.Pos) (includeWhitespace := true) : ParserFn := fun c s => let s := takeDigitsFn (fun c => '0' ≤ c && c ≤ '7') "octal number" true c s - mkNodeToken numLitKind startPos c s + mkNodeToken numLitKind startPos includeWhitespace c s -def hexNumberFn (startPos : String.Pos) : ParserFn := fun c s => - let s := takeDigitsFn (fun c => ('0' ≤ c && c ≤ '9') || ('a' ≤ c && c ≤ 'f') || ('A' ≤ c && c ≤ 'F')) "hexadecimal number" true c s - mkNodeToken numLitKind startPos c s +@[inline] +private def isHexDigit (c : Char) : Bool := + ('0' ≤ c && c ≤ '9') || ('a' ≤ c && c ≤ 'f') || ('A' ≤ c && c ≤ 'F') -def numberFnAux : ParserFn := fun c s => +def hexNumberFn (startPos : String.Pos) (includeWhitespace := true) : ParserFn := fun c s => + let s := takeDigitsFn isHexDigit "hexadecimal number" true c s + mkNodeToken numLitKind startPos includeWhitespace c s + +def numberFnAux (includeWhitespace := true) : ParserFn := fun c s => let startPos := s.pos if h : c.atEnd startPos then s.mkEOIError else @@ -884,15 +901,15 @@ def numberFnAux : ParserFn := fun c s => let i := c.next' startPos h let curr := c.get i if curr == 'b' || curr == 'B' then - binNumberFn startPos c (s.next c i) + binNumberFn startPos includeWhitespace c (s.next c i) else if curr == 'o' || curr == 'O' then - octalNumberFn startPos c (s.next c i) + octalNumberFn startPos includeWhitespace c (s.next c i) else if curr == 'x' || curr == 'X' then - hexNumberFn startPos c (s.next c i) + hexNumberFn startPos includeWhitespace c (s.next c i) else - decimalNumberFn startPos c (s.setPos i) + decimalNumberFn startPos includeWhitespace c (s.setPos i) else if curr.isDigit then - decimalNumberFn startPos c (s.next c startPos) + decimalNumberFn startPos includeWhitespace c (s.next c startPos) else s.mkError "numeral" @@ -934,13 +951,15 @@ def mkTokenAndFixPos (startPos : String.Pos) (tk : Option Token) : ParserFn := f let atom := mkAtom (SourceInfo.original leading startPos trailing stopPos) tk s.pushSyntax atom -def mkIdResult (startPos : String.Pos) (tk : Option Token) (val : Name) : ParserFn := fun c s => +def mkIdResult (startPos : String.Pos) (tk : Option Token) (val : Name) + (includeWhitespace : Bool := true) : + ParserFn := fun c s => let stopPos := s.pos if isToken startPos stopPos tk then mkTokenAndFixPos startPos tk c s else let rawVal := c.substring startPos stopPos - let s := whitespace c s + let s := if includeWhitespace then whitespace c s else s let trailingStopPos := s.pos let leading := c.mkEmptySubstringAt startPos let trailing := c.substring (startPos := stopPos) (stopPos := trailingStopPos) @@ -948,7 +967,9 @@ def mkIdResult (startPos : String.Pos) (tk : Option Token) (val : Name) : Parser let atom := mkIdent info rawVal val s.pushSyntax atom -partial def identFnAux (startPos : String.Pos) (tk : Option Token) (r : Name) : ParserFn := +partial def identFnAux (startPos : String.Pos) (tk : Option Token) (r : Name) + (includeWhitespace : Bool := true) : + ParserFn := let rec parse (r : Name) (c s) := let i := s.pos if h : c.atEnd i then @@ -968,7 +989,7 @@ partial def identFnAux (startPos : String.Pos) (tk : Option Token) (r : Name) : let s := s.next c s.pos parse r c s else - mkIdResult startPos tk r c s + mkIdResult startPos tk r includeWhitespace c s else if isIdFirst curr then let startPart := i let s := takeWhileFn isIdRest c (s.next c i) @@ -978,7 +999,7 @@ partial def identFnAux (startPos : String.Pos) (tk : Option Token) (r : Name) : let s := s.next c s.pos parse r c s else - mkIdResult startPos tk r c s + mkIdResult startPos tk r includeWhitespace c s else mkTokenAndFixPos startPos tk c s parse r @@ -987,7 +1008,7 @@ private def isIdFirstOrBeginEscape (c : Char) : Bool := isIdFirst c || isIdBeginEscape c private def nameLitAux (startPos : String.Pos) : ParserFn := fun c s => - let s := identFnAux startPos none .anonymous c (s.next c startPos) + let s := identFnAux startPos none .anonymous (includeWhitespace := true) c (s.next c startPos) if s.hasError then s else @@ -1002,11 +1023,11 @@ private def tokenFnAux : ParserFn := fun c s => let i := s.pos let curr := c.get i if curr == '\"' then - strLitFnAux i c (s.next c i) + strLitFnAux i true c (s.next c i) else if curr == '\'' && c.getNext i != '\'' then charLitFnAux i c (s.next c i) else if curr.isDigit then - numberFnAux c s + numberFnAux true c s else if curr == '`' && isIdFirstOrBeginEscape (c.getNext i) then nameLitAux i c s else if curr == 'r' && isRawStrLitStart c (c.next i) then @@ -1016,7 +1037,7 @@ private def tokenFnAux : ParserFn := fun c s => have := c.endPos_valid rw [String.endPos] at this omega - identFnAux i tk .anonymous c s + identFnAux i tk .anonymous (includeWhitespace := true) c s private def updateTokenCache (startPos : String.Pos) (s : ParserState) : ParserState := -- do not cache token parsing errors, which are rare and usually fatal and thus not worth an extra field in `TokenCache` @@ -1057,10 +1078,10 @@ def peekToken (c : ParserContext) (s : ParserState) : ParserState × Except Pars peekTokenAux c s /-- Treat keywords as identifiers. -/ -def rawIdentFn : ParserFn := fun c s => +def rawIdentFn (includeWhitespace := true) : ParserFn := fun c s => let i := s.pos if c.atEnd i then s.mkEOIError - else identFnAux i none .anonymous c s + else identFnAux i none .anonymous (includeWhitespace := includeWhitespace) c s def satisfySymbolFn (p : String → Bool) (expected : List String) : ParserFn := fun c s => Id.run do let iniPos := s.pos @@ -1933,7 +1954,7 @@ def fieldIdxFn : ParserFn := fun c s => let curr := c.get iniPos if curr.isDigit && curr != '0' then let s := takeWhileFn (fun c => c.isDigit) c s - mkNodeToken fieldIdxKind iniPos c s + mkNodeToken fieldIdxKind iniPos true c s else s.mkErrorAt "field index" iniPos initStackSz diff --git a/src/Lean/Parser/StrInterpolation.lean b/src/Lean/Parser/StrInterpolation.lean index f8906f1dec..59283251f9 100644 --- a/src/Lean/Parser/StrInterpolation.lean +++ b/src/Lean/Parser/StrInterpolation.lean @@ -25,12 +25,12 @@ partial def interpolatedStrFn (p : ParserFn) : ParserFn := fun c s => let curr := c.get i let s := s.setPos (c.next i) if curr == '\"' then - let s := mkNodeToken interpolatedStrLitKind startPos c s + let s := mkNodeToken interpolatedStrLitKind startPos true c s s.mkNode interpolatedStrKind stackSize else if curr == '\\' then andthenFn (quotedCharCoreFn isQuotableCharForStrInterpolant true) (parse startPos) c s else if curr == '{' then - let s := mkNodeToken interpolatedStrLitKind startPos c s + let s := mkNodeToken interpolatedStrLitKind startPos true c s let s := p c s if s.hasError then s else diff --git a/src/Lean/Parser/Tactic/Doc.lean b/src/Lean/Parser/Tactic/Doc.lean index 3288a44f5f..c500c2d6e2 100644 --- a/src/Lean/Parser/Tactic/Doc.lean +++ b/src/Lean/Parser/Tactic/Doc.lean @@ -6,11 +6,12 @@ Authors: David Thrane Christiansen module prelude -public import Lean.Attributes -public import Lean.DocString.Extension -public import Lean.Elab.InfoTree.Main +public import Lean.Environment +import Lean.Attributes +import Lean.DocString.Extension +import Lean.Elab.InfoTree.Main meta import Lean.Parser.Attr -public import Lean.Parser.Extension +import Lean.Parser.Extension public section @@ -25,9 +26,7 @@ open Lean.Parser.Attr def isTactic (env : Environment) (kind : Name) : Bool := Id.run do let some tactics := (Lean.Parser.parserExtension.getState env).categories.find? `tactic | return false - for (tac, _) in tactics.kinds do - if kind == tac then return true - return false + return tactics.kinds.contains kind /-- Stores a collection of *tactic alternatives*, to track which new syntax rules represent new forms of @@ -83,15 +82,18 @@ builtin_initialize let tgtName ← Lean.Elab.realizeGlobalConstNoOverloadWithInfo tgt if !(isTactic (← getEnv) tgtName) then throwErrorAt tgt "`{tgtName}` is not a tactic" - -- If this condition is true, then we're in an `attribute` command and can validate here. - if (← getEnv).find? decl |>.isSome then - if !(isTactic (← getEnv) decl) then throwError "`{decl}` is not a tactic" + -- If the target is a known syntax kind, ensure that it's a tactic + let mut cats := #[] + for (catName, cat) in parserExtension.getState (← getEnv) |>.categories do + if cat.kinds.contains decl then cats := cats.push catName + if !cats.isEmpty && cats.all (· ≠ `tactic) then + let catNames := cats.map fun c => m!"`{c}`" + let s := if catNames.size > 1 then m!"ies" else m!"y" + throwError "`{decl}` is not a tactic (it is in the categor{s} {.andList catNames.toList})" if let some tgt' := alternativeOfTactic (← getEnv) tgtName then throwError "`{tgtName}` is itself an alternative for `{tgt'}`" modifyEnv fun env => tacticAlternativeExt.addEntry env (decl, tgtName) - if (← findSimpleDocString? (← getEnv) decl).isSome then - logWarningAt stx m!"Docstring for `{decl}` will be ignored because it is an alternative" descr := "Register a tactic parser as an alternative form of an existing tactic, so they " ++ @@ -101,7 +103,7 @@ builtin_initialize -- when the attribute is applied after definition, using an `attribute` command (error checking -- for the `@[tactic_alt TAC]` syntax is performed by the parser attribute hook). If this -- attribute ran later, then the decl would already be present. - applicationTime := .beforeElaboration + applicationTime := .afterCompilation --.beforeElaboration } @@ -278,7 +280,7 @@ where Validates that a tactic alternative is actually a tactic and that syntax tagged as tactics are tactics. -/ -def tacticDocsOnTactics : ParserAttributeHook where +private def tacticDocsOnTactics : ParserAttributeHook where postAdd (catName declName : Name) (_builtIn : Bool) := do if catName == `tactic then return diff --git a/src/Lean/Server/FileWorker/RequestHandling.lean b/src/Lean/Server/FileWorker/RequestHandling.lean index a079a494d6..e8b95e71c5 100644 --- a/src/Lean/Server/FileWorker/RequestHandling.lean +++ b/src/Lean/Server/FileWorker/RequestHandling.lean @@ -100,7 +100,6 @@ def handleHover (p : HoverParams) let docStr ← findDocString? snap.env kind return docStr.map (·, stx.getRange?.get!) | none => pure none - -- now try info tree if let some result := snap.infoTree.hoverableInfoAtM? (m := Id) hoverPos then let ctx := result.ctx diff --git a/src/shell/CMakeLists.txt b/src/shell/CMakeLists.txt index e218124ac7..63b2444734 100644 --- a/src/shell/CMakeLists.txt +++ b/src/shell/CMakeLists.txt @@ -180,6 +180,17 @@ FOREACH(T ${LEANTESTS}) endif() ENDFOREACH(T) +# LEAN DOCSTRING PARSER TESTS +file(GLOB_RECURSE LEANTESTS "${LEAN_SOURCE_DIR}/../tests/lean/docparse/*_[0-9][0-9][0-9][0-9]") +FOREACH(T ${LEANTESTS}) + if(NOT T MATCHES "\\.#" AND NOT T MATCHES "run.lean") + GET_FILENAME_COMPONENT(T_NAME ${T} NAME) + add_test(NAME "leandocparsetest_${T_NAME}" + WORKING_DIRECTORY "${LEAN_SOURCE_DIR}/../tests/lean/docparse" + COMMAND bash -c "${TEST_VARS} ./test_single.sh ${T_NAME}") + endif() +ENDFOREACH(T) + # Create a lake test for each test and examples subdirectory of `lake` # which contains a `test.sh` file, excluding the following test(s): # bootstrap: too slow diff --git a/stage0/src/stdlib_flags.h b/stage0/src/stdlib_flags.h index fc33b085a4..598d1db367 100644 --- a/stage0/src/stdlib_flags.h +++ b/stage0/src/stdlib_flags.h @@ -1,5 +1,7 @@ #include "util/options.h" -// Please update stage0 + +// Dear CI, won't you please update stage0? + namespace lean { options get_default_options() { options opts; @@ -11,12 +13,12 @@ options get_default_options() { opts = opts.update({"debug", "terminalTacticsAsSorry"}, false); // switch to `true` for ABI-breaking changes affecting meta code; // see also next option! - opts = opts.update({"interpreter", "prefer_native"}, false); + opts = opts.update({"interpreter", "prefer_native"}, true); // switch to `false` when enabling `prefer_native` should also affect use // of built-in parsers in quotations; this is usually the case, but setting // both to `true` may be necessary for handling non-builtin parsers with // builtin elaborators - opts = opts.update({"internal", "parseQuotWithCurrentStage"}, true); + opts = opts.update({"internal", "parseQuotWithCurrentStage"}, false); // changes to builtin parsers may also require toggling the following option if macros/syntax // with custom precheck hooks were affected opts = opts.update({"quotPrecheck"}, true); diff --git a/tests/lean/docparse/arg_0001 b/tests/lean/docparse/arg_0001 new file mode 100644 index 0000000000..c1b0730e01 --- /dev/null +++ b/tests/lean/docparse/arg_0001 @@ -0,0 +1 @@ +x \ No newline at end of file diff --git a/tests/lean/docparse/arg_0001.expected.out b/tests/lean/docparse/arg_0001.expected.out new file mode 100644 index 0000000000..e3baa4533d --- /dev/null +++ b/tests/lean/docparse/arg_0001.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + (Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_ident `x)) +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/arg_0002 b/tests/lean/docparse/arg_0002 new file mode 100644 index 0000000000..a07f3e4684 --- /dev/null +++ b/tests/lean/docparse/arg_0002 @@ -0,0 +1 @@ +x:=1 \ No newline at end of file diff --git a/tests/lean/docparse/arg_0002.expected.out b/tests/lean/docparse/arg_0002.expected.out new file mode 100644 index 0000000000..415949d8d4 --- /dev/null +++ b/tests/lean/docparse/arg_0002.expected.out @@ -0,0 +1,6 @@ +Success! Final stack: + (Lean.Doc.Syntax.named_no_paren + `x + ":=" + (Lean.Doc.Syntax.arg_num (num "1"))) +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/arg_0003 b/tests/lean/docparse/arg_0003 new file mode 100644 index 0000000000..3ee9ce4052 --- /dev/null +++ b/tests/lean/docparse/arg_0003 @@ -0,0 +1 @@ +(x:=1) \ No newline at end of file diff --git a/tests/lean/docparse/arg_0003.expected.out b/tests/lean/docparse/arg_0003.expected.out new file mode 100644 index 0000000000..2f1c0fd65b --- /dev/null +++ b/tests/lean/docparse/arg_0003.expected.out @@ -0,0 +1,8 @@ +Success! Final stack: + (Lean.Doc.Syntax.named + "(" + `x + ":=" + (Lean.Doc.Syntax.arg_num (num "1")) + ")") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/arg_0004 b/tests/lean/docparse/arg_0004 new file mode 100644 index 0000000000..938e7b08b8 --- /dev/null +++ b/tests/lean/docparse/arg_0004 @@ -0,0 +1,2 @@ + +(x:=1) \ No newline at end of file diff --git a/tests/lean/docparse/arg_0004.expected.out b/tests/lean/docparse/arg_0004.expected.out new file mode 100644 index 0000000000..5587f74c9c --- /dev/null +++ b/tests/lean/docparse/arg_0004.expected.out @@ -0,0 +1,4 @@ +Failure @0 (⟨1, 0⟩): expected '(', '+', '-', expected identifier, string, or number or token +Final stack: + +Remaining: "\n(x:=1)" \ No newline at end of file diff --git a/tests/lean/docparse/arg_0005 b/tests/lean/docparse/arg_0005 new file mode 100644 index 0000000000..debda609a2 --- /dev/null +++ b/tests/lean/docparse/arg_0005 @@ -0,0 +1 @@ ++foo \ No newline at end of file diff --git a/tests/lean/docparse/arg_0005.expected.out b/tests/lean/docparse/arg_0005.expected.out new file mode 100644 index 0000000000..8b83361a0f --- /dev/null +++ b/tests/lean/docparse/arg_0005.expected.out @@ -0,0 +1,3 @@ +Success! Final stack: + (Lean.Doc.Syntax.flag_on "+" `foo) +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/arg_0006 b/tests/lean/docparse/arg_0006 new file mode 100644 index 0000000000..5fff7bd978 --- /dev/null +++ b/tests/lean/docparse/arg_0006 @@ -0,0 +1 @@ +-other \ No newline at end of file diff --git a/tests/lean/docparse/arg_0006.expected.out b/tests/lean/docparse/arg_0006.expected.out new file mode 100644 index 0000000000..8e2455ac9b --- /dev/null +++ b/tests/lean/docparse/arg_0006.expected.out @@ -0,0 +1,3 @@ +Success! Final stack: + (Lean.Doc.Syntax.flag_off "-" `other) +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/arg_0007 b/tests/lean/docparse/arg_0007 new file mode 100644 index 0000000000..88dc55e4a5 --- /dev/null +++ b/tests/lean/docparse/arg_0007 @@ -0,0 +1 @@ +- other \ No newline at end of file diff --git a/tests/lean/docparse/arg_0007.expected.out b/tests/lean/docparse/arg_0007.expected.out new file mode 100644 index 0000000000..35abac62bb --- /dev/null +++ b/tests/lean/docparse/arg_0007.expected.out @@ -0,0 +1,4 @@ +Failure @2 (⟨1, 2⟩): expected no space before +Final stack: + (Lean.Doc.Syntax.flag_off "-" `other) +Remaining: "other" \ No newline at end of file diff --git a/tests/lean/docparse/arg_0008 b/tests/lean/docparse/arg_0008 new file mode 100644 index 0000000000..0970bcbc5a --- /dev/null +++ b/tests/lean/docparse/arg_0008 @@ -0,0 +1 @@ +(x:=1) diff --git a/tests/lean/docparse/arg_0008.expected.out b/tests/lean/docparse/arg_0008.expected.out new file mode 100644 index 0000000000..271999b4ac --- /dev/null +++ b/tests/lean/docparse/arg_0008.expected.out @@ -0,0 +1,9 @@ +Success! Final stack: + (Lean.Doc.Syntax.named + "(" + `x + ":=" + (Lean.Doc.Syntax.arg_num (num "1")) + ")") +Remaining: +"\n" \ No newline at end of file diff --git a/tests/lean/docparse/arg_0009 b/tests/lean/docparse/arg_0009 new file mode 100644 index 0000000000..14d0b3f1a3 --- /dev/null +++ b/tests/lean/docparse/arg_0009 @@ -0,0 +1 @@ +x:=y \ No newline at end of file diff --git a/tests/lean/docparse/arg_0009.expected.out b/tests/lean/docparse/arg_0009.expected.out new file mode 100644 index 0000000000..53aa69cfea --- /dev/null +++ b/tests/lean/docparse/arg_0009.expected.out @@ -0,0 +1,6 @@ +Success! Final stack: + (Lean.Doc.Syntax.named_no_paren + `x + ":=" + (Lean.Doc.Syntax.arg_ident `y)) +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/arg_0010 b/tests/lean/docparse/arg_0010 new file mode 100644 index 0000000000..0d61547708 --- /dev/null +++ b/tests/lean/docparse/arg_0010 @@ -0,0 +1 @@ +(x:=y) \ No newline at end of file diff --git a/tests/lean/docparse/arg_0010.expected.out b/tests/lean/docparse/arg_0010.expected.out new file mode 100644 index 0000000000..84a495bdf2 --- /dev/null +++ b/tests/lean/docparse/arg_0010.expected.out @@ -0,0 +1,8 @@ +Success! Final stack: + (Lean.Doc.Syntax.named + "(" + `x + ":=" + (Lean.Doc.Syntax.arg_ident `y) + ")") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/arg_0011 b/tests/lean/docparse/arg_0011 new file mode 100644 index 0000000000..e905623855 --- /dev/null +++ b/tests/lean/docparse/arg_0011 @@ -0,0 +1 @@ +x:="y" \ No newline at end of file diff --git a/tests/lean/docparse/arg_0011.expected.out b/tests/lean/docparse/arg_0011.expected.out new file mode 100644 index 0000000000..1d893bf953 --- /dev/null +++ b/tests/lean/docparse/arg_0011.expected.out @@ -0,0 +1,6 @@ +Success! Final stack: + (Lean.Doc.Syntax.named_no_paren + `x + ":=" + (Lean.Doc.Syntax.arg_str (str "\"y\""))) +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/arg_0012 b/tests/lean/docparse/arg_0012 new file mode 100644 index 0000000000..a949832515 --- /dev/null +++ b/tests/lean/docparse/arg_0012 @@ -0,0 +1 @@ +x:="y \ No newline at end of file diff --git a/tests/lean/docparse/arg_0012.expected.out b/tests/lean/docparse/arg_0012.expected.out new file mode 100644 index 0000000000..f18b9e2ca2 --- /dev/null +++ b/tests/lean/docparse/arg_0012.expected.out @@ -0,0 +1,7 @@ +Failure @3 (⟨1, 3⟩): unterminated string literal +Final stack: + • `x + • ":=" + • (Lean.Doc.Syntax.arg_str ) + +Remaining: "\"y" \ No newline at end of file diff --git a/tests/lean/docparse/arg_0013 b/tests/lean/docparse/arg_0013 new file mode 100644 index 0000000000..45cb12c927 --- /dev/null +++ b/tests/lean/docparse/arg_0013 @@ -0,0 +1 @@ +(x:="y) \ No newline at end of file diff --git a/tests/lean/docparse/arg_0013.expected.out b/tests/lean/docparse/arg_0013.expected.out new file mode 100644 index 0000000000..272771fa9d --- /dev/null +++ b/tests/lean/docparse/arg_0013.expected.out @@ -0,0 +1,13 @@ +2 failures: + @7 (⟨1, 7⟩): expected ')' + "" + @7 (⟨1, 7⟩): unterminated string literal + "" + +Final stack: + (Lean.Doc.Syntax.named + "(" + `x + ":=" + (Lean.Doc.Syntax.arg_str ) + ) \ No newline at end of file diff --git a/tests/lean/docparse/arg_0014 b/tests/lean/docparse/arg_0014 new file mode 100644 index 0000000000..f70d7bba4a --- /dev/null +++ b/tests/lean/docparse/arg_0014 @@ -0,0 +1 @@ +42 \ No newline at end of file diff --git a/tests/lean/docparse/arg_0014.expected.out b/tests/lean/docparse/arg_0014.expected.out new file mode 100644 index 0000000000..fa5d18aee4 --- /dev/null +++ b/tests/lean/docparse/arg_0014.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + (Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_num (num "42"))) +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/arg_0015 b/tests/lean/docparse/arg_0015 new file mode 100644 index 0000000000..93022ef3e3 --- /dev/null +++ b/tests/lean/docparse/arg_0015 @@ -0,0 +1 @@ +(42) \ No newline at end of file diff --git a/tests/lean/docparse/arg_0015.expected.out b/tests/lean/docparse/arg_0015.expected.out new file mode 100644 index 0000000000..c7c2062b46 --- /dev/null +++ b/tests/lean/docparse/arg_0015.expected.out @@ -0,0 +1,17 @@ +4 failures: + @4 (⟨1, 4⟩): expected ')' + "" + @4 (⟨1, 4⟩): expected ':=' + "" + @4 (⟨1, 4⟩): expected token + "" + @4 (⟨1, 4⟩): unexpected end of input + "" + +Final stack: + (Lean.Doc.Syntax.named + "(" + + + + ) \ No newline at end of file diff --git a/tests/lean/docparse/arg_0016 b/tests/lean/docparse/arg_0016 new file mode 100644 index 0000000000..b4e4f95540 --- /dev/null +++ b/tests/lean/docparse/arg_0016 @@ -0,0 +1 @@ +(x 42) \ No newline at end of file diff --git a/tests/lean/docparse/arg_0016.expected.out b/tests/lean/docparse/arg_0016.expected.out new file mode 100644 index 0000000000..8abd72b466 --- /dev/null +++ b/tests/lean/docparse/arg_0016.expected.out @@ -0,0 +1,15 @@ +3 failures: + @6 (⟨1, 6⟩): expected ')' + "" + @6 (⟨1, 6⟩): expected ':=' + "" + @6 (⟨1, 6⟩): unexpected end of input + "" + +Final stack: + (Lean.Doc.Syntax.named + "(" + `x + + + ) \ No newline at end of file diff --git a/tests/lean/docparse/arg_0017 b/tests/lean/docparse/arg_0017 new file mode 100644 index 0000000000..f4bad7dba1 --- /dev/null +++ b/tests/lean/docparse/arg_0017 @@ -0,0 +1,2 @@ +(x := 42 +) \ No newline at end of file diff --git a/tests/lean/docparse/arg_0017.expected.out b/tests/lean/docparse/arg_0017.expected.out new file mode 100644 index 0000000000..166a8bb2da --- /dev/null +++ b/tests/lean/docparse/arg_0017.expected.out @@ -0,0 +1,9 @@ +Failure @8 (⟨1, 8⟩): expected ')' +Final stack: + (Lean.Doc.Syntax.named + "(" + `x + ":=" + (Lean.Doc.Syntax.arg_num (num "42")) + ) +Remaining: "\n)" \ No newline at end of file diff --git a/tests/lean/docparse/arg_0018 b/tests/lean/docparse/arg_0018 new file mode 100644 index 0000000000..50b0fa2b52 --- /dev/null +++ b/tests/lean/docparse/arg_0018 @@ -0,0 +1,2 @@ +(x := 42 +a \ No newline at end of file diff --git a/tests/lean/docparse/arg_0018.expected.out b/tests/lean/docparse/arg_0018.expected.out new file mode 100644 index 0000000000..c59c669b85 --- /dev/null +++ b/tests/lean/docparse/arg_0018.expected.out @@ -0,0 +1,9 @@ +Failure @8 (⟨1, 8⟩): expected ')' +Final stack: + (Lean.Doc.Syntax.named + "(" + `x + ":=" + (Lean.Doc.Syntax.arg_num (num "42")) + ) +Remaining: "\na" \ No newline at end of file diff --git a/tests/lean/docparse/arg_val_0001 b/tests/lean/docparse/arg_val_0001 new file mode 100644 index 0000000000..56a6051ca2 --- /dev/null +++ b/tests/lean/docparse/arg_val_0001 @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/tests/lean/docparse/arg_val_0001.expected.out b/tests/lean/docparse/arg_val_0001.expected.out new file mode 100644 index 0000000000..81408dde38 --- /dev/null +++ b/tests/lean/docparse/arg_val_0001.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + (Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_num (num "1"))) +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/arg_val_0002 b/tests/lean/docparse/arg_val_0002 new file mode 100644 index 0000000000..e440e5c842 --- /dev/null +++ b/tests/lean/docparse/arg_val_0002 @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/tests/lean/docparse/arg_val_0002.expected.out b/tests/lean/docparse/arg_val_0002.expected.out new file mode 100644 index 0000000000..184e36a987 --- /dev/null +++ b/tests/lean/docparse/arg_val_0002.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + (Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_num (num "3"))) +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/arg_val_0003 b/tests/lean/docparse/arg_val_0003 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/lean/docparse/arg_val_0003.expected.out b/tests/lean/docparse/arg_val_0003.expected.out new file mode 100644 index 0000000000..dc78cc8b51 --- /dev/null +++ b/tests/lean/docparse/arg_val_0003.expected.out @@ -0,0 +1,4 @@ +Failure @0 (⟨1, 0⟩): unexpected end of input; expected '(', '+' or '-' +Final stack: + +Remaining: "" \ No newline at end of file diff --git a/tests/lean/docparse/arg_val_0004 b/tests/lean/docparse/arg_val_0004 new file mode 100644 index 0000000000..101b1a7155 --- /dev/null +++ b/tests/lean/docparse/arg_val_0004 @@ -0,0 +1 @@ +"a b c d" \ No newline at end of file diff --git a/tests/lean/docparse/arg_val_0004.expected.out b/tests/lean/docparse/arg_val_0004.expected.out new file mode 100644 index 0000000000..e5c4c0b416 --- /dev/null +++ b/tests/lean/docparse/arg_val_0004.expected.out @@ -0,0 +1,5 @@ +Success! Final stack: + (Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_str + (str "\"a b c\t d\""))) +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/arg_val_0005 b/tests/lean/docparse/arg_val_0005 new file mode 100644 index 0000000000..54fb7822c1 --- /dev/null +++ b/tests/lean/docparse/arg_val_0005 @@ -0,0 +1 @@ +"a b c d" diff --git a/tests/lean/docparse/arg_val_0005.expected.out b/tests/lean/docparse/arg_val_0005.expected.out new file mode 100644 index 0000000000..e5c4c0b416 --- /dev/null +++ b/tests/lean/docparse/arg_val_0005.expected.out @@ -0,0 +1,5 @@ +Success! Final stack: + (Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_str + (str "\"a b c\t d\""))) +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/arg_val_0006 b/tests/lean/docparse/arg_val_0006 new file mode 100644 index 0000000000..4c37dc6405 --- /dev/null +++ b/tests/lean/docparse/arg_val_0006 @@ -0,0 +1,2 @@ +43 +"foo" \ No newline at end of file diff --git a/tests/lean/docparse/arg_val_0006.expected.out b/tests/lean/docparse/arg_val_0006.expected.out new file mode 100644 index 0000000000..1476d75eed --- /dev/null +++ b/tests/lean/docparse/arg_val_0006.expected.out @@ -0,0 +1,5 @@ +Success! Final stack: + (Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_num (num "43"))) +Remaining: +"\n\"foo\"" \ No newline at end of file diff --git a/tests/lean/docparse/arg_val_0007 b/tests/lean/docparse/arg_val_0007 new file mode 100644 index 0000000000..d45772e3c5 --- /dev/null +++ b/tests/lean/docparse/arg_val_0007 @@ -0,0 +1 @@ +"foo \ No newline at end of file diff --git a/tests/lean/docparse/arg_val_0007.expected.out b/tests/lean/docparse/arg_val_0007.expected.out new file mode 100644 index 0000000000..f939c23c0d --- /dev/null +++ b/tests/lean/docparse/arg_val_0007.expected.out @@ -0,0 +1,4 @@ +Failure @0 (⟨1, 0⟩): unterminated string literal; expected '(', '+', '-' or token +Final stack: + (Lean.Doc.Syntax.arg_str ) +Remaining: "\"foo" \ No newline at end of file diff --git a/tests/lean/docparse/args_0001 b/tests/lean/docparse/args_0001 new file mode 100644 index 0000000000..4c37dc6405 --- /dev/null +++ b/tests/lean/docparse/args_0001 @@ -0,0 +1,2 @@ +43 +"foo" \ No newline at end of file diff --git a/tests/lean/docparse/args_0001.expected.out b/tests/lean/docparse/args_0001.expected.out new file mode 100644 index 0000000000..8cf69af3bb --- /dev/null +++ b/tests/lean/docparse/args_0001.expected.out @@ -0,0 +1,5 @@ +Success! Final stack: + [(Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_num (num "43")))] +Remaining: +"\n\"foo\"" \ No newline at end of file diff --git a/tests/lean/docparse/args_0002 b/tests/lean/docparse/args_0002 new file mode 100644 index 0000000000..d504d62b75 --- /dev/null +++ b/tests/lean/docparse/args_0002 @@ -0,0 +1,2 @@ +dialect:="chicken" 43 +foo \ No newline at end of file diff --git a/tests/lean/docparse/args_0002.expected.out b/tests/lean/docparse/args_0002.expected.out new file mode 100644 index 0000000000..81bd7bc6f2 --- /dev/null +++ b/tests/lean/docparse/args_0002.expected.out @@ -0,0 +1,9 @@ +Success! Final stack: + [(Lean.Doc.Syntax.named_no_paren + `dialect + ":=" + (Lean.Doc.Syntax.arg_str (str "\"chicken\""))) + (Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_num (num "43")))] +Remaining: +"\nfoo" \ No newline at end of file diff --git a/tests/lean/docparse/args_0003 b/tests/lean/docparse/args_0003 new file mode 100644 index 0000000000..cc89499331 --- /dev/null +++ b/tests/lean/docparse/args_0003 @@ -0,0 +1,2 @@ +dialect:="chicken" 43 +(foo) \ No newline at end of file diff --git a/tests/lean/docparse/args_0003.expected.out b/tests/lean/docparse/args_0003.expected.out new file mode 100644 index 0000000000..6edb3eea02 --- /dev/null +++ b/tests/lean/docparse/args_0003.expected.out @@ -0,0 +1,9 @@ +Success! Final stack: + [(Lean.Doc.Syntax.named_no_paren + `dialect + ":=" + (Lean.Doc.Syntax.arg_str (str "\"chicken\""))) + (Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_num (num "43")))] +Remaining: +"\n(foo)" \ No newline at end of file diff --git a/tests/lean/docparse/blockOpener_0001 b/tests/lean/docparse/blockOpener_0001 new file mode 100644 index 0000000000..1198a6c908 --- /dev/null +++ b/tests/lean/docparse/blockOpener_0001 @@ -0,0 +1 @@ ++ abc \ No newline at end of file diff --git a/tests/lean/docparse/blockOpener_0001.expected.out b/tests/lean/docparse/blockOpener_0001.expected.out new file mode 100644 index 0000000000..08f596a60c --- /dev/null +++ b/tests/lean/docparse/blockOpener_0001.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + empty +Remaining: +"abc" \ No newline at end of file diff --git a/tests/lean/docparse/blockOpener_0002 b/tests/lean/docparse/blockOpener_0002 new file mode 100644 index 0000000000..1c98dec5da --- /dev/null +++ b/tests/lean/docparse/blockOpener_0002 @@ -0,0 +1 @@ +* abc \ No newline at end of file diff --git a/tests/lean/docparse/blockOpener_0002.expected.out b/tests/lean/docparse/blockOpener_0002.expected.out new file mode 100644 index 0000000000..08f596a60c --- /dev/null +++ b/tests/lean/docparse/blockOpener_0002.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + empty +Remaining: +"abc" \ No newline at end of file diff --git a/tests/lean/docparse/blockOpener_0003 b/tests/lean/docparse/blockOpener_0003 new file mode 100644 index 0000000000..1198a6c908 --- /dev/null +++ b/tests/lean/docparse/blockOpener_0003 @@ -0,0 +1 @@ ++ abc \ No newline at end of file diff --git a/tests/lean/docparse/blockOpener_0003.expected.out b/tests/lean/docparse/blockOpener_0003.expected.out new file mode 100644 index 0000000000..08f596a60c --- /dev/null +++ b/tests/lean/docparse/blockOpener_0003.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + empty +Remaining: +"abc" \ No newline at end of file diff --git a/tests/lean/docparse/block_0001 b/tests/lean/docparse/block_0001 new file mode 100644 index 0000000000..4df9d39f7f --- /dev/null +++ b/tests/lean/docparse/block_0001 @@ -0,0 +1,8 @@ + * Here's a bullet + * and another with some code in it + ````lean + hey + + there + ```` + * and another one diff --git a/tests/lean/docparse/block_0001.expected.out b/tests/lean/docparse/block_0001.expected.out new file mode 100644 index 0000000000..755ffd97cf --- /dev/null +++ b/tests/lean/docparse/block_0001.expected.out @@ -0,0 +1,36 @@ +Success! Final stack: + (Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"Here's a bullet\""))] + "}")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str + "\"and another with some code in it\""))] + "}") + (Lean.Doc.Syntax.codeblock + "````" + [`lean []] + "\n" + (str "\"hey\\n\\nthere\\n\"") + "````")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"and another one\"")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\""))] + "}")])] + "}") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/block_0002 b/tests/lean/docparse/block_0002 new file mode 100644 index 0000000000..fd9bf99e3c --- /dev/null +++ b/tests/lean/docparse/block_0002 @@ -0,0 +1,2 @@ +{test} +Here's a paragraph. \ No newline at end of file diff --git a/tests/lean/docparse/block_0002.expected.out b/tests/lean/docparse/block_0002.expected.out new file mode 100644 index 0000000000..f1b9ec73bc --- /dev/null +++ b/tests/lean/docparse/block_0002.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + (Lean.Doc.Syntax.command "{" `test [] "}") +Remaining: +"Here's a paragraph." \ No newline at end of file diff --git a/tests/lean/docparse/block_0003 b/tests/lean/docparse/block_0003 new file mode 100644 index 0000000000..abea64d71b --- /dev/null +++ b/tests/lean/docparse/block_0003 @@ -0,0 +1,2 @@ +{test} + Here's a paragraph. \ No newline at end of file diff --git a/tests/lean/docparse/block_0003.expected.out b/tests/lean/docparse/block_0003.expected.out new file mode 100644 index 0000000000..7af7884e3e --- /dev/null +++ b/tests/lean/docparse/block_0003.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + (Lean.Doc.Syntax.command "{" `test [] "}") +Remaining: +" Here's a paragraph." \ No newline at end of file diff --git a/tests/lean/docparse/block_0004 b/tests/lean/docparse/block_0004 new file mode 100644 index 0000000000..abea64d71b --- /dev/null +++ b/tests/lean/docparse/block_0004 @@ -0,0 +1,2 @@ +{test} + Here's a paragraph. \ No newline at end of file diff --git a/tests/lean/docparse/block_0004.expected.out b/tests/lean/docparse/block_0004.expected.out new file mode 100644 index 0000000000..7af7884e3e --- /dev/null +++ b/tests/lean/docparse/block_0004.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + (Lean.Doc.Syntax.command "{" `test [] "}") +Remaining: +" Here's a paragraph." \ No newline at end of file diff --git a/tests/lean/docparse/block_0005 b/tests/lean/docparse/block_0005 new file mode 100644 index 0000000000..fd9bf99e3c --- /dev/null +++ b/tests/lean/docparse/block_0005 @@ -0,0 +1,2 @@ +{test} +Here's a paragraph. \ No newline at end of file diff --git a/tests/lean/docparse/block_0005.expected.out b/tests/lean/docparse/block_0005.expected.out new file mode 100644 index 0000000000..f1b9ec73bc --- /dev/null +++ b/tests/lean/docparse/block_0005.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + (Lean.Doc.Syntax.command "{" `test [] "}") +Remaining: +"Here's a paragraph." \ No newline at end of file diff --git a/tests/lean/docparse/block_0006 b/tests/lean/docparse/block_0006 new file mode 100644 index 0000000000..64bb700afd --- /dev/null +++ b/tests/lean/docparse/block_0006 @@ -0,0 +1,6 @@ +{test} +> Here's a blockquote + + with multiple paras + +that ends \ No newline at end of file diff --git a/tests/lean/docparse/block_0006.expected.out b/tests/lean/docparse/block_0006.expected.out new file mode 100644 index 0000000000..44c01444dc --- /dev/null +++ b/tests/lean/docparse/block_0006.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + (Lean.Doc.Syntax.command "{" `test [] "}") +Remaining: +"> Here's a blockquote\n\n with multiple paras\n\nthat ends" \ No newline at end of file diff --git a/tests/lean/docparse/block_0007 b/tests/lean/docparse/block_0007 new file mode 100644 index 0000000000..90abe7906a --- /dev/null +++ b/tests/lean/docparse/block_0007 @@ -0,0 +1,3 @@ +{ +test} +Here's a modified paragraph. \ No newline at end of file diff --git a/tests/lean/docparse/block_0007.expected.out b/tests/lean/docparse/block_0007.expected.out new file mode 100644 index 0000000000..da8e1f2e94 --- /dev/null +++ b/tests/lean/docparse/block_0007.expected.out @@ -0,0 +1,15 @@ +2 failures: + @36 (⟨3, 28⟩): expected token + "" + @36 (⟨3, 28⟩): unexpected end of input; expected '![', '$$', '$', '[' or '[^' + "" + +Final stack: + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.role + "{" + + "[" + [(Lean.Doc.Syntax.footnote )] + "]")]) \ No newline at end of file diff --git a/tests/lean/docparse/block_0008 b/tests/lean/docparse/block_0008 new file mode 100644 index 0000000000..997a2d0499 --- /dev/null +++ b/tests/lean/docparse/block_0008 @@ -0,0 +1,3 @@ +{ + test} +Here's a modified paragraph. \ No newline at end of file diff --git a/tests/lean/docparse/block_0008.expected.out b/tests/lean/docparse/block_0008.expected.out new file mode 100644 index 0000000000..e16be66609 --- /dev/null +++ b/tests/lean/docparse/block_0008.expected.out @@ -0,0 +1,15 @@ +2 failures: + @37 (⟨3, 28⟩): expected token + "" + @37 (⟨3, 28⟩): unexpected end of input; expected '![', '$$', '$', '[' or '[^' + "" + +Final stack: + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.role + "{" + + "[" + [(Lean.Doc.Syntax.footnote )] + "]")]) \ No newline at end of file diff --git a/tests/lean/docparse/block_0009 b/tests/lean/docparse/block_0009 new file mode 100644 index 0000000000..261c6bc86e --- /dev/null +++ b/tests/lean/docparse/block_0009 @@ -0,0 +1,4 @@ +{ + test +arg} +Here's a modified paragraph. \ No newline at end of file diff --git a/tests/lean/docparse/block_0009.expected.out b/tests/lean/docparse/block_0009.expected.out new file mode 100644 index 0000000000..593ab41540 --- /dev/null +++ b/tests/lean/docparse/block_0009.expected.out @@ -0,0 +1,15 @@ +2 failures: + @44 (⟨4, 28⟩): expected token + "" + @44 (⟨4, 28⟩): unexpected end of input; expected '![', '$$', '$', '[' or '[^' + "" + +Final stack: + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.role + "{" + + "[" + [(Lean.Doc.Syntax.footnote )] + "]")]) \ No newline at end of file diff --git a/tests/lean/docparse/block_0010 b/tests/lean/docparse/block_0010 new file mode 100644 index 0000000000..a6bccc014b --- /dev/null +++ b/tests/lean/docparse/block_0010 @@ -0,0 +1,4 @@ +{ + test + arg} +Here's a modified paragraph. \ No newline at end of file diff --git a/tests/lean/docparse/block_0010.expected.out b/tests/lean/docparse/block_0010.expected.out new file mode 100644 index 0000000000..9b01691411 --- /dev/null +++ b/tests/lean/docparse/block_0010.expected.out @@ -0,0 +1,15 @@ +2 failures: + @45 (⟨4, 28⟩): expected token + "" + @45 (⟨4, 28⟩): unexpected end of input; expected '![', '$$', '$', '[' or '[^' + "" + +Final stack: + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.role + "{" + + "[" + [(Lean.Doc.Syntax.footnote )] + "]")]) \ No newline at end of file diff --git a/tests/lean/docparse/block_0011 b/tests/lean/docparse/block_0011 new file mode 100644 index 0000000000..7594e9a0ac --- /dev/null +++ b/tests/lean/docparse/block_0011 @@ -0,0 +1,6 @@ +{ + test + arg} + + +Here's a paragraph. \ No newline at end of file diff --git a/tests/lean/docparse/block_0011.expected.out b/tests/lean/docparse/block_0011.expected.out new file mode 100644 index 0000000000..98d23d6625 --- /dev/null +++ b/tests/lean/docparse/block_0011.expected.out @@ -0,0 +1,15 @@ +2 failures: + @19 (⟨6, 0⟩): '{'; expected '![', '$$', '$', '[' or '[^' + "Here's a paragraph." + @19 (⟨6, 0⟩): expected token + "Here's a paragraph." + +Final stack: + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.role + "{" + + "[" + [(Lean.Doc.Syntax.footnote )] + "]")]) \ No newline at end of file diff --git a/tests/lean/docparse/block_0012 b/tests/lean/docparse/block_0012 new file mode 100644 index 0000000000..0262cfe751 --- /dev/null +++ b/tests/lean/docparse/block_0012 @@ -0,0 +1,15 @@ +* `structure` and `inductive` commands + * [#5842](https://github.com/leanprover/lean4/pull/5842) and [#5783](https://github.com/leanprover/lean4/pull/5783) implement a feature where the `structure` command can now define recursive inductive types: + ```lean + structure Tree where + n : Nat + children : Fin n → Tree + + def Tree.size : Tree → Nat + | {n, children} => Id.run do + let mut s := 0 + for h : i in [0 : n] do + s := s + (children ⟨i, h.2⟩).size + pure s + ``` + * [#5814](https://github.com/leanprover/lean4/pull/5814) \ No newline at end of file diff --git a/tests/lean/docparse/block_0012.expected.out b/tests/lean/docparse/block_0012.expected.out new file mode 100644 index 0000000000..5ae56c0c7a --- /dev/null +++ b/tests/lean/docparse/block_0012.expected.out @@ -0,0 +1,84 @@ +Success! Final stack: + (Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.code + "`" + (str "\"structure\"") + "`") + (Lean.Doc.Syntax.text (str "\" and \"")) + (Lean.Doc.Syntax.code + "`" + (str "\"inductive\"") + "`") + (Lean.Doc.Syntax.text + (str "\" commands\""))] + "}") + (Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.link + "[" + [(Lean.Doc.Syntax.text + (str "\"#5842\""))] + "]" + (Lean.Doc.Syntax.url + "(" + (str + "\"https://github.com/leanprover/lean4/pull/5842\"") + ")")) + (Lean.Doc.Syntax.text + (str "\" and \"")) + (Lean.Doc.Syntax.link + "[" + [(Lean.Doc.Syntax.text + (str "\"#5783\""))] + "]" + (Lean.Doc.Syntax.url + "(" + (str + "\"https://github.com/leanprover/lean4/pull/5783\"") + ")")) + (Lean.Doc.Syntax.text + (str + "\" implement a feature where the \"")) + (Lean.Doc.Syntax.code + "`" + (str "\"structure\"") + "`") + (Lean.Doc.Syntax.text + (str + "\" command can now define recursive inductive types:\""))] + "}") + (Lean.Doc.Syntax.codeblock + "```" + [`lean []] + "\n" + (str + "\"structure Tree where\\n n : Nat\\n children : Fin n → Tree\\n\\ndef Tree.size : Tree → Nat\\n | {n, children} => Id.run do\\n let mut s := 0\\n for h : i in [0 : n] do\\n s := s + (children ⟨i, h.2⟩).size\\n pure s\\n\"") + "```")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.link + "[" + [(Lean.Doc.Syntax.text + (str "\"#5814\""))] + "]" + (Lean.Doc.Syntax.url + "(" + (str + "\"https://github.com/leanprover/lean4/pull/5814\"") + ")")) + (Lean.Doc.Syntax.text (str "\" \""))] + "}")])] + "}")])] + "}") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0001 b/tests/lean/docparse/blocks_0001 new file mode 100644 index 0000000000..969555e232 --- /dev/null +++ b/tests/lean/docparse/blocks_0001 @@ -0,0 +1,3 @@ +This is just some textual content. How is it? + +More? \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0001.expected.out b/tests/lean/docparse/blocks_0001.expected.out new file mode 100644 index 0000000000..f9979cd6fc --- /dev/null +++ b/tests/lean/docparse/blocks_0001.expected.out @@ -0,0 +1,12 @@ +Success! Final stack: + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str + "\"This is just some textual content. How is it?\""))] + "}") + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"More?\""))] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0002 b/tests/lean/docparse/blocks_0002 new file mode 100644 index 0000000000..2990456732 --- /dev/null +++ b/tests/lean/docparse/blocks_0002 @@ -0,0 +1,2 @@ +Newlines are preserved +in paragraphs. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0002.expected.out b/tests/lean/docparse/blocks_0002.expected.out new file mode 100644 index 0000000000..5c9f66c66a --- /dev/null +++ b/tests/lean/docparse/blocks_0002.expected.out @@ -0,0 +1,12 @@ +Success! Final stack: + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"Newlines are preserved\"")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\"")) + (Lean.Doc.Syntax.text + (str "\"in paragraphs.\""))] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0003 b/tests/lean/docparse/blocks_0003 new file mode 100644 index 0000000000..d6a59d7b09 --- /dev/null +++ b/tests/lean/docparse/blocks_0003 @@ -0,0 +1,4 @@ +I can describe lists like this one: + +* a +* b \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0003.expected.out b/tests/lean/docparse/blocks_0003.expected.out new file mode 100644 index 0000000000..e332c41af0 --- /dev/null +++ b/tests/lean/docparse/blocks_0003.expected.out @@ -0,0 +1,23 @@ +Success! Final stack: + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str + "\"I can describe lists like this one:\""))] + "}") + (Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"a\""))] + "}")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"b\""))] + "}")])] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0004 b/tests/lean/docparse/blocks_0004 new file mode 100644 index 0000000000..01fd606d6a --- /dev/null +++ b/tests/lean/docparse/blocks_0004 @@ -0,0 +1,5 @@ +* * A1 + * A2 +* B + * B1 + * B2 \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0004.expected.out b/tests/lean/docparse/blocks_0004.expected.out new file mode 100644 index 0000000000..2d0cd8c73b --- /dev/null +++ b/tests/lean/docparse/blocks_0004.expected.out @@ -0,0 +1,47 @@ +Success! Final stack: + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"A1\""))] + "}")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"A2\""))] + "}")])] + "}")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"B\""))] + "}") + (Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"B1\""))] + "}")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"B2\""))] + "}")])] + "}")])] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0005 b/tests/lean/docparse/blocks_0005 new file mode 100644 index 0000000000..ec098bb685 --- /dev/null +++ b/tests/lean/docparse/blocks_0005 @@ -0,0 +1,5 @@ +* + * A1 + * A2 +* + * B1 \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0005.expected.out b/tests/lean/docparse/blocks_0005.expected.out new file mode 100644 index 0000000000..af10e3c5e0 --- /dev/null +++ b/tests/lean/docparse/blocks_0005.expected.out @@ -0,0 +1,38 @@ +Success! Final stack: + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"A1\""))] + "}")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"A2\"")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\""))] + "}")])] + "}")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"B1\""))] + "}")])] + "}")])] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0006 b/tests/lean/docparse/blocks_0006 new file mode 100644 index 0000000000..922dac70e0 --- /dev/null +++ b/tests/lean/docparse/blocks_0006 @@ -0,0 +1,5 @@ +* + * A1 + * A2 +* + * B1 \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0006.expected.out b/tests/lean/docparse/blocks_0006.expected.out new file mode 100644 index 0000000000..af10e3c5e0 --- /dev/null +++ b/tests/lean/docparse/blocks_0006.expected.out @@ -0,0 +1,38 @@ +Success! Final stack: + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"A1\""))] + "}")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"A2\"")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\""))] + "}")])] + "}")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"B1\""))] + "}")])] + "}")])] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0007 b/tests/lean/docparse/blocks_0007 new file mode 100644 index 0000000000..c869ff73c5 --- /dev/null +++ b/tests/lean/docparse/blocks_0007 @@ -0,0 +1,2 @@ +* +abc \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0007.expected.out b/tests/lean/docparse/blocks_0007.expected.out new file mode 100644 index 0000000000..863b919e9c --- /dev/null +++ b/tests/lean/docparse/blocks_0007.expected.out @@ -0,0 +1,11 @@ +Failure @2 (⟨2, 0⟩): ':'; expected %%% (at line beginning) or expected column at least 1 +Final stack: + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.metadata_block + + ) + ])])] +Remaining: "abc" \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0008 b/tests/lean/docparse/blocks_0008 new file mode 100644 index 0000000000..3f9863c171 --- /dev/null +++ b/tests/lean/docparse/blocks_0008 @@ -0,0 +1,4 @@ +* + abc + + def \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0008.expected.out b/tests/lean/docparse/blocks_0008.expected.out new file mode 100644 index 0000000000..84bad49bb2 --- /dev/null +++ b/tests/lean/docparse/blocks_0008.expected.out @@ -0,0 +1,15 @@ +Success! Final stack: + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"abc\""))] + "}") + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"def\""))] + "}")])] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0009 b/tests/lean/docparse/blocks_0009 new file mode 100644 index 0000000000..c5c569ead3 --- /dev/null +++ b/tests/lean/docparse/blocks_0009 @@ -0,0 +1,4 @@ +* + abc + +def \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0009.expected.out b/tests/lean/docparse/blocks_0009.expected.out new file mode 100644 index 0000000000..63bb41db6f --- /dev/null +++ b/tests/lean/docparse/blocks_0009.expected.out @@ -0,0 +1,15 @@ +Success! Final stack: + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"abc\""))] + "}")])] + "}") + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"def\""))] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0010 b/tests/lean/docparse/blocks_0010 new file mode 100644 index 0000000000..0beb3ff0d7 --- /dev/null +++ b/tests/lean/docparse/blocks_0010 @@ -0,0 +1,2 @@ +* foo +* bar diff --git a/tests/lean/docparse/blocks_0010.expected.out b/tests/lean/docparse/blocks_0010.expected.out new file mode 100644 index 0000000000..1df295971a --- /dev/null +++ b/tests/lean/docparse/blocks_0010.expected.out @@ -0,0 +1,20 @@ +Success! Final stack: + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"foo\""))] + "}")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"bar\"")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\""))] + "}")])] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0011 b/tests/lean/docparse/blocks_0011 new file mode 100644 index 0000000000..64c346a42a --- /dev/null +++ b/tests/lean/docparse/blocks_0011 @@ -0,0 +1 @@ +> * foo \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0011.expected.out b/tests/lean/docparse/blocks_0011.expected.out new file mode 100644 index 0000000000..83fdaf55d8 --- /dev/null +++ b/tests/lean/docparse/blocks_0011.expected.out @@ -0,0 +1,13 @@ +Success! Final stack: + [(Lean.Doc.Syntax.blockquote + ">" + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"foo\""))] + "}")])] + "}")])] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0012 b/tests/lean/docparse/blocks_0012 new file mode 100644 index 0000000000..52b2693d08 --- /dev/null +++ b/tests/lean/docparse/blocks_0012 @@ -0,0 +1 @@ +> 1. foo \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0012.expected.out b/tests/lean/docparse/blocks_0012.expected.out new file mode 100644 index 0000000000..20e4875474 --- /dev/null +++ b/tests/lean/docparse/blocks_0012.expected.out @@ -0,0 +1,16 @@ +Success! Final stack: + [(Lean.Doc.Syntax.blockquote + ">" + [(Lean.Doc.Syntax.ol + "ol(" + (num "1") + ")" + "{" + [(Lean.Doc.Syntax.li + "1." + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"foo\""))] + "}")])] + "}")])] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0013 b/tests/lean/docparse/blocks_0013 new file mode 100644 index 0000000000..d8da766848 --- /dev/null +++ b/tests/lean/docparse/blocks_0013 @@ -0,0 +1,4 @@ +> * foo + + + * foo \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0013.expected.out b/tests/lean/docparse/blocks_0013.expected.out new file mode 100644 index 0000000000..f156ea4e23 --- /dev/null +++ b/tests/lean/docparse/blocks_0013.expected.out @@ -0,0 +1,22 @@ +Success! Final stack: + [(Lean.Doc.Syntax.blockquote + ">" + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"foo\""))] + "}")])] + "}") + (Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"foo\""))] + "}")])] + "}")])] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0014 b/tests/lean/docparse/blocks_0014 new file mode 100644 index 0000000000..d79b1d8073 --- /dev/null +++ b/tests/lean/docparse/blocks_0014 @@ -0,0 +1,4 @@ +> I like quotes + * Also with lists in them + +Abc \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0014.expected.out b/tests/lean/docparse/blocks_0014.expected.out new file mode 100644 index 0000000000..f5aedb1ea4 --- /dev/null +++ b/tests/lean/docparse/blocks_0014.expected.out @@ -0,0 +1,23 @@ +Success! Final stack: + [(Lean.Doc.Syntax.blockquote + ">" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"I like quotes\""))] + "}") + (Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"Also with lists in them\""))] + "}")])] + "}")]) + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"Abc\""))] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0015 b/tests/lean/docparse/blocks_0015 new file mode 100644 index 0000000000..01bc81c9dc --- /dev/null +++ b/tests/lean/docparse/blocks_0015 @@ -0,0 +1,4 @@ +> * foo + + + * foo \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0015.expected.out b/tests/lean/docparse/blocks_0015.expected.out new file mode 100644 index 0000000000..e29e740c8a --- /dev/null +++ b/tests/lean/docparse/blocks_0015.expected.out @@ -0,0 +1,19 @@ +Success! Final stack: + [(Lean.Doc.Syntax.blockquote + ">" + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"foo\""))] + "}")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"foo\""))] + "}")])] + "}")])] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0016 b/tests/lean/docparse/blocks_0016 new file mode 100644 index 0000000000..463d4206b7 --- /dev/null +++ b/tests/lean/docparse/blocks_0016 @@ -0,0 +1,4 @@ +> > * foo + + + * foo \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0016.expected.out b/tests/lean/docparse/blocks_0016.expected.out new file mode 100644 index 0000000000..13ff9a0f2a --- /dev/null +++ b/tests/lean/docparse/blocks_0016.expected.out @@ -0,0 +1,25 @@ +Success! Final stack: + [(Lean.Doc.Syntax.blockquote + ">" + [(Lean.Doc.Syntax.blockquote + ">" + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"foo\""))] + "}")])] + "}")]) + (Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"foo\""))] + "}")])] + "}")])] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0017 b/tests/lean/docparse/blocks_0017 new file mode 100644 index 0000000000..3c07cef113 --- /dev/null +++ b/tests/lean/docparse/blocks_0017 @@ -0,0 +1,4 @@ +> * foo + + +* foo \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0017.expected.out b/tests/lean/docparse/blocks_0017.expected.out new file mode 100644 index 0000000000..b2ef0b8f8e --- /dev/null +++ b/tests/lean/docparse/blocks_0017.expected.out @@ -0,0 +1,22 @@ +Success! Final stack: + [(Lean.Doc.Syntax.blockquote + ">" + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"foo\""))] + "}")])] + "}")]) + (Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"foo\""))] + "}")])] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0018 b/tests/lean/docparse/blocks_0018 new file mode 100644 index 0000000000..d4e4b0971b --- /dev/null +++ b/tests/lean/docparse/blocks_0018 @@ -0,0 +1,2 @@ +* foo + thing* bar diff --git a/tests/lean/docparse/blocks_0018.expected.out b/tests/lean/docparse/blocks_0018.expected.out new file mode 100644 index 0000000000..dde2132959 --- /dev/null +++ b/tests/lean/docparse/blocks_0018.expected.out @@ -0,0 +1,17 @@ +Success! Final stack: + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"foo\"")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\"")) + (Lean.Doc.Syntax.text + (str "\" thing\""))] + "}")])] + "}")] +Remaining: +"* bar\n" \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0019 b/tests/lean/docparse/blocks_0019 new file mode 100644 index 0000000000..85270e40a9 --- /dev/null +++ b/tests/lean/docparse/blocks_0019 @@ -0,0 +1,2 @@ ++ foo ++ bar diff --git a/tests/lean/docparse/blocks_0019.expected.out b/tests/lean/docparse/blocks_0019.expected.out new file mode 100644 index 0000000000..822d174bc8 --- /dev/null +++ b/tests/lean/docparse/blocks_0019.expected.out @@ -0,0 +1,20 @@ +Success! Final stack: + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "+" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"foo\""))] + "}")]) + (Lean.Doc.Syntax.li + "+" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"bar\"")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\""))] + "}")])] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0020 b/tests/lean/docparse/blocks_0020 new file mode 100644 index 0000000000..d642871e52 --- /dev/null +++ b/tests/lean/docparse/blocks_0020 @@ -0,0 +1,3 @@ +* foo + +* bar diff --git a/tests/lean/docparse/blocks_0020.expected.out b/tests/lean/docparse/blocks_0020.expected.out new file mode 100644 index 0000000000..1df295971a --- /dev/null +++ b/tests/lean/docparse/blocks_0020.expected.out @@ -0,0 +1,20 @@ +Success! Final stack: + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"foo\""))] + "}")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"bar\"")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\""))] + "}")])] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0021 b/tests/lean/docparse/blocks_0021 new file mode 100644 index 0000000000..f19eabdd34 --- /dev/null +++ b/tests/lean/docparse/blocks_0021 @@ -0,0 +1,9 @@ +* foo + stuff + + > more + + abc + + +* bar diff --git a/tests/lean/docparse/blocks_0021.expected.out b/tests/lean/docparse/blocks_0021.expected.out new file mode 100644 index 0000000000..7bc24e6563 --- /dev/null +++ b/tests/lean/docparse/blocks_0021.expected.out @@ -0,0 +1,36 @@ +Success! Final stack: + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"foo\"")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\"")) + (Lean.Doc.Syntax.text + (str "\" stuff\""))] + "}") + (Lean.Doc.Syntax.blockquote + ">" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"more \""))] + "}")]) + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"abc\""))] + "}")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"bar\"")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\""))] + "}")])] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0022 b/tests/lean/docparse/blocks_0022 new file mode 100644 index 0000000000..54bc72892c --- /dev/null +++ b/tests/lean/docparse/blocks_0022 @@ -0,0 +1,3 @@ +* foo + * bar +* more outer \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0022.expected.out b/tests/lean/docparse/blocks_0022.expected.out new file mode 100644 index 0000000000..2b538eabf0 --- /dev/null +++ b/tests/lean/docparse/blocks_0022.expected.out @@ -0,0 +1,28 @@ +Success! Final stack: + [(Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"foo\""))] + "}") + (Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"bar\""))] + "}")])] + "}")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"more outer\""))] + "}")])] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0023 b/tests/lean/docparse/blocks_0023 new file mode 100644 index 0000000000..4b00c1d1ac --- /dev/null +++ b/tests/lean/docparse/blocks_0023 @@ -0,0 +1,2 @@ +: an excellent idea +Let's say more! \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0023.expected.out b/tests/lean/docparse/blocks_0023.expected.out new file mode 100644 index 0000000000..6fafea3f2f --- /dev/null +++ b/tests/lean/docparse/blocks_0023.expected.out @@ -0,0 +1,18 @@ +Failure @35 (⟨2, 15⟩): unexpected end of input; expected %%% (at line beginning), '![', '$$', '$', '[', '[^', beginning of line at ⟨2, 15⟩ or beginning of line or sequence of nestable block openers at ⟨2, 15⟩ +Final stack: + [(Lean.Doc.Syntax.dl + "dl{" + [(Lean.Doc.Syntax.desc + ":" + [(Lean.Doc.Syntax.text + (str "\" an excellent idea\"")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\"")) + (Lean.Doc.Syntax.text + (str "\"Let's say more!\""))] + "=>" + [(Lean.Doc.Syntax.metadata_block ) + ])] + "}")] +Remaining: "" \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0024 b/tests/lean/docparse/blocks_0024 new file mode 100644 index 0000000000..87fb7f1ced --- /dev/null +++ b/tests/lean/docparse/blocks_0024 @@ -0,0 +1,3 @@ +: an excellent idea + + Let's say more! \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0024.expected.out b/tests/lean/docparse/blocks_0024.expected.out new file mode 100644 index 0000000000..de0467157e --- /dev/null +++ b/tests/lean/docparse/blocks_0024.expected.out @@ -0,0 +1,15 @@ +Success! Final stack: + [(Lean.Doc.Syntax.dl + "dl{" + [(Lean.Doc.Syntax.desc + ":" + [(Lean.Doc.Syntax.text + (str "\" an excellent idea\""))] + "=>" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"Let's say more!\""))] + "}")])] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0025 b/tests/lean/docparse/blocks_0025 new file mode 100644 index 0000000000..853f9c9f10 --- /dev/null +++ b/tests/lean/docparse/blocks_0025 @@ -0,0 +1,6 @@ +: an excellent idea + + Let's say more! + +: more + diff --git a/tests/lean/docparse/blocks_0025.expected.out b/tests/lean/docparse/blocks_0025.expected.out new file mode 100644 index 0000000000..875fa0646b --- /dev/null +++ b/tests/lean/docparse/blocks_0025.expected.out @@ -0,0 +1,21 @@ +Failure @47 (⟨7, 0⟩): expected indentation at least 1 +Final stack: + [(Lean.Doc.Syntax.dl + "dl{" + [(Lean.Doc.Syntax.desc + ":" + [(Lean.Doc.Syntax.text + (str "\" an excellent idea\""))] + "=>" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"Let's say more!\""))] + "}")]) + (Lean.Doc.Syntax.desc + ":" + [(Lean.Doc.Syntax.text (str "\" more\""))] + "=>" + )] + "}")] +Remaining: "" \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0026 b/tests/lean/docparse/blocks_0026 new file mode 100644 index 0000000000..565be32b5f --- /dev/null +++ b/tests/lean/docparse/blocks_0026 @@ -0,0 +1,10 @@ +: an excellent idea + + Let's say more! + + + + +: more + + even more! \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0026.expected.out b/tests/lean/docparse/blocks_0026.expected.out new file mode 100644 index 0000000000..22b3cd79ee --- /dev/null +++ b/tests/lean/docparse/blocks_0026.expected.out @@ -0,0 +1,24 @@ +Success! Final stack: + [(Lean.Doc.Syntax.dl + "dl{" + [(Lean.Doc.Syntax.desc + ":" + [(Lean.Doc.Syntax.text + (str "\" an excellent idea\""))] + "=>" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"Let's say more!\""))] + "}")]) + (Lean.Doc.Syntax.desc + ":" + [(Lean.Doc.Syntax.text (str "\" more\""))] + "=>" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"even more!\""))] + "}")])] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0027 b/tests/lean/docparse/blocks_0027 new file mode 100644 index 0000000000..246e961dcd --- /dev/null +++ b/tests/lean/docparse/blocks_0027 @@ -0,0 +1,7 @@ +: an excellent idea + + Let's say more! + +: more + + stuff \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0027.expected.out b/tests/lean/docparse/blocks_0027.expected.out new file mode 100644 index 0000000000..c7cc6f676a --- /dev/null +++ b/tests/lean/docparse/blocks_0027.expected.out @@ -0,0 +1,23 @@ +Success! Final stack: + [(Lean.Doc.Syntax.dl + "dl{" + [(Lean.Doc.Syntax.desc + ":" + [(Lean.Doc.Syntax.text + (str "\" an excellent idea\""))] + "=>" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"Let's say more!\""))] + "}")]) + (Lean.Doc.Syntax.desc + ":" + [(Lean.Doc.Syntax.text (str "\" more\""))] + "=>" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"stuff\""))] + "}")])] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0028 b/tests/lean/docparse/blocks_0028 new file mode 100644 index 0000000000..b57d0e425e --- /dev/null +++ b/tests/lean/docparse/blocks_0028 @@ -0,0 +1,5 @@ +: an excellent idea + +Let's say more! + +hello \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0028.expected.out b/tests/lean/docparse/blocks_0028.expected.out new file mode 100644 index 0000000000..6387eca664 --- /dev/null +++ b/tests/lean/docparse/blocks_0028.expected.out @@ -0,0 +1,21 @@ +Failure @21 (⟨3, 0⟩): expected indentation at least 1 +Final stack: + [(Lean.Doc.Syntax.dl + "dl{" + [(Lean.Doc.Syntax.desc + ":" + [(Lean.Doc.Syntax.text + (str "\" an excellent idea\""))] + "=>" + )] + "}") + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"Let's say more!\""))] + "}") + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"hello\""))] + "}")] +Remaining: "Let's say more!\n\nhello" \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0029 b/tests/lean/docparse/blocks_0029 new file mode 100644 index 0000000000..0300bf11f6 --- /dev/null +++ b/tests/lean/docparse/blocks_0029 @@ -0,0 +1,3 @@ +1. Hello + +2. World \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0029.expected.out b/tests/lean/docparse/blocks_0029.expected.out new file mode 100644 index 0000000000..b0e31ac375 --- /dev/null +++ b/tests/lean/docparse/blocks_0029.expected.out @@ -0,0 +1,20 @@ +Success! Final stack: + [(Lean.Doc.Syntax.ol + "ol(" + (num "1") + ")" + "{" + [(Lean.Doc.Syntax.li + "1." + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"Hello\""))] + "}")]) + (Lean.Doc.Syntax.li + "2." + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"World\""))] + "}")])] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0030 b/tests/lean/docparse/blocks_0030 new file mode 100644 index 0000000000..2a0b5fc7a1 --- /dev/null +++ b/tests/lean/docparse/blocks_0030 @@ -0,0 +1 @@ +1. Hello \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0030.expected.out b/tests/lean/docparse/blocks_0030.expected.out new file mode 100644 index 0000000000..72c6335008 --- /dev/null +++ b/tests/lean/docparse/blocks_0030.expected.out @@ -0,0 +1,14 @@ +Success! Final stack: + [(Lean.Doc.Syntax.ol + "ol(" + (num "1") + ")" + "{" + [(Lean.Doc.Syntax.li + "1." + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"Hello\""))] + "}")])] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0031 b/tests/lean/docparse/blocks_0031 new file mode 100644 index 0000000000..67c8df2828 --- /dev/null +++ b/tests/lean/docparse/blocks_0031 @@ -0,0 +1,3 @@ +1. Hello + + 2. World \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0031.expected.out b/tests/lean/docparse/blocks_0031.expected.out new file mode 100644 index 0000000000..ac12388814 --- /dev/null +++ b/tests/lean/docparse/blocks_0031.expected.out @@ -0,0 +1,26 @@ +Success! Final stack: + [(Lean.Doc.Syntax.ol + "ol(" + (num "1") + ")" + "{" + [(Lean.Doc.Syntax.li + "1." + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"Hello\""))] + "}")])] + "}") + (Lean.Doc.Syntax.ol + "ol(" + (num "2") + ")" + "{" + [(Lean.Doc.Syntax.li + "2." + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"World\""))] + "}")])] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0032 b/tests/lean/docparse/blocks_0032 new file mode 100644 index 0000000000..78893485c4 --- /dev/null +++ b/tests/lean/docparse/blocks_0032 @@ -0,0 +1,3 @@ + 1. Hello + + 2. World \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0032.expected.out b/tests/lean/docparse/blocks_0032.expected.out new file mode 100644 index 0000000000..b0e31ac375 --- /dev/null +++ b/tests/lean/docparse/blocks_0032.expected.out @@ -0,0 +1,20 @@ +Success! Final stack: + [(Lean.Doc.Syntax.ol + "ol(" + (num "1") + ")" + "{" + [(Lean.Doc.Syntax.li + "1." + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"Hello\""))] + "}")]) + (Lean.Doc.Syntax.li + "2." + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"World\""))] + "}")])] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0033 b/tests/lean/docparse/blocks_0033 new file mode 100644 index 0000000000..5111d8b080 --- /dev/null +++ b/tests/lean/docparse/blocks_0033 @@ -0,0 +1 @@ +> hey diff --git a/tests/lean/docparse/blocks_0033.expected.out b/tests/lean/docparse/blocks_0033.expected.out new file mode 100644 index 0000000000..8845597f03 --- /dev/null +++ b/tests/lean/docparse/blocks_0033.expected.out @@ -0,0 +1,11 @@ +Success! Final stack: + [(Lean.Doc.Syntax.blockquote + ">" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"hey\"")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\""))] + "}")])] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0034 b/tests/lean/docparse/blocks_0034 new file mode 100644 index 0000000000..9885d3e1cd --- /dev/null +++ b/tests/lean/docparse/blocks_0034 @@ -0,0 +1 @@ +n\*k \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0034.expected.out b/tests/lean/docparse/blocks_0034.expected.out new file mode 100644 index 0000000000..bf7af691d6 --- /dev/null +++ b/tests/lean/docparse/blocks_0034.expected.out @@ -0,0 +1,6 @@ +Success! Final stack: + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"n*k \""))] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0035 b/tests/lean/docparse/blocks_0035 new file mode 100644 index 0000000000..7ffdc2355f --- /dev/null +++ b/tests/lean/docparse/blocks_0035 @@ -0,0 +1 @@ +*This is _strong* not regular_ emphasis \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0035.expected.out b/tests/lean/docparse/blocks_0035.expected.out new file mode 100644 index 0000000000..2a2d253a47 --- /dev/null +++ b/tests/lean/docparse/blocks_0035.expected.out @@ -0,0 +1,17 @@ +Failure @16 (⟨1, 16⟩): '_' +Final stack: + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.bold + "*" + [(Lean.Doc.Syntax.text (str "\"This is \"")) + (Lean.Doc.Syntax.emph + "_" + [(Lean.Doc.Syntax.text + (str "\"strong\""))] + )] + "*") + (Lean.Doc.Syntax.text + (str "\" not regular\""))] + "}")] +Remaining: "* not regular_ emphasis" \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0036 b/tests/lean/docparse/blocks_0036 new file mode 100644 index 0000000000..db23e08dad --- /dev/null +++ b/tests/lean/docparse/blocks_0036 @@ -0,0 +1 @@ +# Header! \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0036.expected.out b/tests/lean/docparse/blocks_0036.expected.out new file mode 100644 index 0000000000..44e76edec1 --- /dev/null +++ b/tests/lean/docparse/blocks_0036.expected.out @@ -0,0 +1,9 @@ +Success! Final stack: + [(Lean.Doc.Syntax.header + "header(" + (num "0") + ")" + "{" + [(Lean.Doc.Syntax.text (str "\"Header!\""))] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0037 b/tests/lean/docparse/blocks_0037 new file mode 100644 index 0000000000..38c82f4c93 --- /dev/null +++ b/tests/lean/docparse/blocks_0037 @@ -0,0 +1 @@ +## Header! \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0037.expected.out b/tests/lean/docparse/blocks_0037.expected.out new file mode 100644 index 0000000000..bc03584f02 --- /dev/null +++ b/tests/lean/docparse/blocks_0037.expected.out @@ -0,0 +1,9 @@ +Success! Final stack: + [(Lean.Doc.Syntax.header + "header(" + (num "1") + ")" + "{" + [(Lean.Doc.Syntax.text (str "\"Header!\""))] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0038 b/tests/lean/docparse/blocks_0038 new file mode 100644 index 0000000000..2d642bff64 --- /dev/null +++ b/tests/lean/docparse/blocks_0038 @@ -0,0 +1,2 @@ +> Quotation +and contained \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0038.expected.out b/tests/lean/docparse/blocks_0038.expected.out new file mode 100644 index 0000000000..56b8a94595 --- /dev/null +++ b/tests/lean/docparse/blocks_0038.expected.out @@ -0,0 +1,14 @@ +Success! Final stack: + [(Lean.Doc.Syntax.blockquote + ">" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"Quotation\"")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\"")) + (Lean.Doc.Syntax.text + (str "\"and contained\""))] + "}")])] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0039 b/tests/lean/docparse/blocks_0039 new file mode 100644 index 0000000000..d1af6cc9b7 --- /dev/null +++ b/tests/lean/docparse/blocks_0039 @@ -0,0 +1,7 @@ +Attention: + +Here is a paragraph with an unterminated `code block +that would be super annoying without error recovery in the +parser. + +Yep. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0039.expected.out b/tests/lean/docparse/blocks_0039.expected.out new file mode 100644 index 0000000000..9599c03b89 --- /dev/null +++ b/tests/lean/docparse/blocks_0039.expected.out @@ -0,0 +1,29 @@ +Failure @65 (⟨4, 0⟩): expected '`' to close inline code +Final stack: + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"Attention:\""))] + "}") + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str + "\"Here is a paragraph with an unterminated \"")) + (Lean.Doc.Syntax.code + "`" + (str "\"code block\"") + ) + (Lean.Doc.Syntax.text + (str + "\"that would be super annoying without error recovery in the\"")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\"")) + (Lean.Doc.Syntax.text (str "\"parser.\""))] + "}") + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"Yep.\""))] + "}")] +Remaining: "that would be super annoying without error recovery in the\nparser.\n\nYep." \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0040 b/tests/lean/docparse/blocks_0040 new file mode 100644 index 0000000000..4fe94fa8ec --- /dev/null +++ b/tests/lean/docparse/blocks_0040 @@ -0,0 +1,9 @@ +Here's some error recovery for directives. + +::::foo + +Indeed. + +It is. + +::: diff --git a/tests/lean/docparse/blocks_0040.expected.out b/tests/lean/docparse/blocks_0040.expected.out new file mode 100644 index 0000000000..57eb7295b4 --- /dev/null +++ b/tests/lean/docparse/blocks_0040.expected.out @@ -0,0 +1,36 @@ +3 failures: + @73 (⟨9, 3⟩): expected token + "\n" + @74 (⟨10, 0⟩): expected closing ':::' for directive from line 9 + "" + @74 (⟨10, 0⟩): expected closing '::::' for directive from line 3 + "" + +Final stack: + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str + "\"Here's some error recovery for directives.\""))] + "}") + (Lean.Doc.Syntax.directive + "::::" + `foo + [] + "\n" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"Indeed.\""))] + "}") + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"It is.\""))] + "}") + (Lean.Doc.Syntax.directive + ":::" + + [] + "\n" + [] + )] + )] \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0041 b/tests/lean/docparse/blocks_0041 new file mode 100644 index 0000000000..0086cc6f8c --- /dev/null +++ b/tests/lean/docparse/blocks_0041 @@ -0,0 +1,8 @@ +Here's some error recovery for directives. + +:::foo + +Indeed. + +It is. + ::: diff --git a/tests/lean/docparse/blocks_0041.expected.out b/tests/lean/docparse/blocks_0041.expected.out new file mode 100644 index 0000000000..c16cc06882 --- /dev/null +++ b/tests/lean/docparse/blocks_0041.expected.out @@ -0,0 +1,36 @@ +3 failures: + @68 (⟨8, 0⟩): expected closing ':::' from directive on line 3 at column 0, but it's at column 1 + " :::\n" + @72 (⟨8, 4⟩): expected token + "\n" + @73 (⟨9, 0⟩): expected closing ':::' for directive from line 8 + "" + +Final stack: + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str + "\"Here's some error recovery for directives.\""))] + "}") + (Lean.Doc.Syntax.directive + ":::" + `foo + [] + "\n" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"Indeed.\""))] + "}") + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"It is.\""))] + "}")] + ) + (Lean.Doc.Syntax.directive + ":::" + + [] + "\n" + [] + )] \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0042 b/tests/lean/docparse/blocks_0042 new file mode 100644 index 0000000000..91852404e2 --- /dev/null +++ b/tests/lean/docparse/blocks_0042 @@ -0,0 +1,10 @@ +Here's some error recovery for directives. + +::::foo 5 + +Indeed. + +It is. +:::: a + +x diff --git a/tests/lean/docparse/blocks_0042.expected.out b/tests/lean/docparse/blocks_0042.expected.out new file mode 100644 index 0000000000..e814adc214 --- /dev/null +++ b/tests/lean/docparse/blocks_0042.expected.out @@ -0,0 +1,41 @@ +2 failures: + @71 (⟨8, 0⟩): expected closing '::::' for directive from line 3 + ":::: a\n\nx\n" + @81 (⟨11, 0⟩): expected closing '::::' for directive from line 8 + "" + +Final stack: + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str + "\"Here's some error recovery for directives.\""))] + "}") + (Lean.Doc.Syntax.directive + "::::" + `foo + [(Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_num (num "5")))] + "\n" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"Indeed.\""))] + "}") + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"It is.\""))] + "}")] + ) + (Lean.Doc.Syntax.directive + "::::" + `a + [] + "\n" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"x\"")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\""))] + "}")] + )] \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0043 b/tests/lean/docparse/blocks_0043 new file mode 100644 index 0000000000..eb68de8fb4 --- /dev/null +++ b/tests/lean/docparse/blocks_0043 @@ -0,0 +1,3 @@ +> Quotation + + and contained \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0043.expected.out b/tests/lean/docparse/blocks_0043.expected.out new file mode 100644 index 0000000000..0e48c54b60 --- /dev/null +++ b/tests/lean/docparse/blocks_0043.expected.out @@ -0,0 +1,14 @@ +Success! Final stack: + [(Lean.Doc.Syntax.blockquote + ">" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"Quotation\""))] + "}") + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"and contained\""))] + "}")])] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0044 b/tests/lean/docparse/blocks_0044 new file mode 100644 index 0000000000..d3369a58e5 --- /dev/null +++ b/tests/lean/docparse/blocks_0044 @@ -0,0 +1,3 @@ +> Quotation + +and not contained \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0044.expected.out b/tests/lean/docparse/blocks_0044.expected.out new file mode 100644 index 0000000000..e74ce250bb --- /dev/null +++ b/tests/lean/docparse/blocks_0044.expected.out @@ -0,0 +1,14 @@ +Success! Final stack: + [(Lean.Doc.Syntax.blockquote + ">" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"Quotation\""))] + "}")]) + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"and not contained\""))] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0045 b/tests/lean/docparse/blocks_0045 new file mode 100644 index 0000000000..fd9bf99e3c --- /dev/null +++ b/tests/lean/docparse/blocks_0045 @@ -0,0 +1,2 @@ +{test} +Here's a paragraph. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0045.expected.out b/tests/lean/docparse/blocks_0045.expected.out new file mode 100644 index 0000000000..3d4c9d1d11 --- /dev/null +++ b/tests/lean/docparse/blocks_0045.expected.out @@ -0,0 +1,8 @@ +Success! Final stack: + [(Lean.Doc.Syntax.command "{" `test [] "}") + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"Here's a paragraph.\""))] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0046 b/tests/lean/docparse/blocks_0046 new file mode 100644 index 0000000000..64bb700afd --- /dev/null +++ b/tests/lean/docparse/blocks_0046 @@ -0,0 +1,6 @@ +{test} +> Here's a blockquote + + with multiple paras + +that ends \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0046.expected.out b/tests/lean/docparse/blocks_0046.expected.out new file mode 100644 index 0000000000..cafc29d7c2 --- /dev/null +++ b/tests/lean/docparse/blocks_0046.expected.out @@ -0,0 +1,19 @@ +Success! Final stack: + [(Lean.Doc.Syntax.command "{" `test [] "}") + (Lean.Doc.Syntax.blockquote + ">" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"Here's a blockquote\""))] + "}") + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"with multiple paras\""))] + "}")]) + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"that ends\""))] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0047 b/tests/lean/docparse/blocks_0047 new file mode 100644 index 0000000000..b94927cf98 --- /dev/null +++ b/tests/lean/docparse/blocks_0047 @@ -0,0 +1,2 @@ +{abc} +{def} \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0047.expected.out b/tests/lean/docparse/blocks_0047.expected.out new file mode 100644 index 0000000000..8bfe1f7c88 --- /dev/null +++ b/tests/lean/docparse/blocks_0047.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + [(Lean.Doc.Syntax.command "{" `abc [] "}") + (Lean.Doc.Syntax.command "{" `def [] "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0048 b/tests/lean/docparse/blocks_0048 new file mode 100644 index 0000000000..abb9bab817 --- /dev/null +++ b/tests/lean/docparse/blocks_0048 @@ -0,0 +1,4 @@ +%%% +foo := 53 +%%% +Text/paragraph! \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0048.expected.out b/tests/lean/docparse/blocks_0048.expected.out new file mode 100644 index 0000000000..57c0dcb005 --- /dev/null +++ b/tests/lean/docparse/blocks_0048.expected.out @@ -0,0 +1,20 @@ +Success! Final stack: + [(Lean.Doc.Syntax.metadata_block + "%%%" + (Term.structInstFields + [(Term.structInstField + (Term.structInstLVal `foo []) + [[] + [] + (Term.structInstFieldDef + ":=" + [] + (num "53"))]) + []]) + "%%%") + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"Text/paragraph!\""))] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0049 b/tests/lean/docparse/blocks_0049 new file mode 100644 index 0000000000..d3e16272d0 --- /dev/null +++ b/tests/lean/docparse/blocks_0049 @@ -0,0 +1 @@ +[\[link A\]](https://example.com) [\[link B\]](https://more.example.com) \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0049.expected.out b/tests/lean/docparse/blocks_0049.expected.out new file mode 100644 index 0000000000..6c761e9f8e --- /dev/null +++ b/tests/lean/docparse/blocks_0049.expected.out @@ -0,0 +1,24 @@ +Success! Final stack: + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.link + "[" + [(Lean.Doc.Syntax.text + (str "\"[link A]\""))] + "]" + (Lean.Doc.Syntax.url + "(" + (str "\"https://example.com\"") + ")")) + (Lean.Doc.Syntax.text (str "\" \"")) + (Lean.Doc.Syntax.link + "[" + [(Lean.Doc.Syntax.text + (str "\"[link B]\""))] + "]" + (Lean.Doc.Syntax.url + "(" + (str "\"https://more.example.com\"") + ")"))] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0050 b/tests/lean/docparse/blocks_0050 new file mode 100644 index 0000000000..47813a544b --- /dev/null +++ b/tests/lean/docparse/blocks_0050 @@ -0,0 +1,3 @@ +[My link][lean] + +[lean]: https://lean-lang.org \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0050.expected.out b/tests/lean/docparse/blocks_0050.expected.out new file mode 100644 index 0000000000..5b90de9585 --- /dev/null +++ b/tests/lean/docparse/blocks_0050.expected.out @@ -0,0 +1,18 @@ +Success! Final stack: + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.link + "[" + [(Lean.Doc.Syntax.text (str "\"My link\""))] + "]" + (Lean.Doc.Syntax.ref + "[" + (str "\"lean\"") + "]"))] + "}") + (Lean.Doc.Syntax.link_ref + "[" + (str "\"lean\"") + "]:" + (str "\"https://lean-lang.org\""))] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0051 b/tests/lean/docparse/blocks_0051 new file mode 100644 index 0000000000..e1709ab1ec --- /dev/null +++ b/tests/lean/docparse/blocks_0051 @@ -0,0 +1,2 @@ +[My link][lean] +[lean]: https://lean-lang.org \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0051.expected.out b/tests/lean/docparse/blocks_0051.expected.out new file mode 100644 index 0000000000..2a79d1aa86 --- /dev/null +++ b/tests/lean/docparse/blocks_0051.expected.out @@ -0,0 +1,22 @@ +Failure @45 (⟨2, 29⟩): expected '(' or '[' +Final stack: + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.link + "[" + [(Lean.Doc.Syntax.text (str "\"My link\""))] + "]" + (Lean.Doc.Syntax.ref + "[" + (str "\"lean\"") + "]")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\"")) + (Lean.Doc.Syntax.link + "[" + [(Lean.Doc.Syntax.text (str "\"lean\""))] + "]" + (Lean.Doc.Syntax.url ))] + "}")] +Remaining: "" \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0052 b/tests/lean/docparse/blocks_0052 new file mode 100644 index 0000000000..03650fc125 --- /dev/null +++ b/tests/lean/docparse/blocks_0052 @@ -0,0 +1,4 @@ +[My link][lean] + +[lean]: https://lean-lang.org +hello \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0052.expected.out b/tests/lean/docparse/blocks_0052.expected.out new file mode 100644 index 0000000000..ef4f90aae8 --- /dev/null +++ b/tests/lean/docparse/blocks_0052.expected.out @@ -0,0 +1,22 @@ +Success! Final stack: + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.link + "[" + [(Lean.Doc.Syntax.text (str "\"My link\""))] + "]" + (Lean.Doc.Syntax.ref + "[" + (str "\"lean\"") + "]"))] + "}") + (Lean.Doc.Syntax.link_ref + "[" + (str "\"lean\"") + "]:" + (str "\"https://lean-lang.org\"")) + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"hello\""))] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0053 b/tests/lean/docparse/blocks_0053 new file mode 100644 index 0000000000..a2c99c13cb --- /dev/null +++ b/tests/lean/docparse/blocks_0053 @@ -0,0 +1,3 @@ +Blah blah[^1] + +[^1]: More can be said \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0053.expected.out b/tests/lean/docparse/blocks_0053.expected.out new file mode 100644 index 0000000000..dc22d30e47 --- /dev/null +++ b/tests/lean/docparse/blocks_0053.expected.out @@ -0,0 +1,16 @@ +Success! Final stack: + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"Blah blah\"")) + (Lean.Doc.Syntax.footnote + "[^" + (str "\"1\"") + "]")] + "}") + (Lean.Doc.Syntax.footnote_ref + "[^" + (str "\"1\"") + "]:" + [(Lean.Doc.Syntax.text + (str "\"More can be said\""))])] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/blocks_0054 b/tests/lean/docparse/blocks_0054 new file mode 100644 index 0000000000..de5a45d35b --- /dev/null +++ b/tests/lean/docparse/blocks_0054 @@ -0,0 +1,26 @@ + +Error recovery tests: + +* [busted link + +* [busted + link + +* [busted + _italics + link + + +* [busted destination](hey + +* ![busted image alt text + +* ![busted image link](image.png + +* a *bold choice + +* very _italic *and bold, onto many + lines is OK* but don't forget... + +a paragraph with [a bad link syntax](http://example.com +is OK. The rest *works*. diff --git a/tests/lean/docparse/blocks_0054.expected.out b/tests/lean/docparse/blocks_0054.expected.out new file mode 100644 index 0000000000..3639662ae7 --- /dev/null +++ b/tests/lean/docparse/blocks_0054.expected.out @@ -0,0 +1,180 @@ +10 failures: + @38 (⟨4, 14⟩): expected ']' + "\n\n* [busted\n link\n\n* [busted\n _italics\n link\n\n\n* [busted destination](hey\n\n* ![busted image alt text\n\n* ![busted image link](image.png\n\n* a *bold choice\n\n* very _italic *and bold, onto many\n lines is OK* but don't forget...\n\na paragraph with [a bad link syntax](http://example.com\nis OK. The rest *works*.\n" + @57 (⟨7, 7⟩): expected ']' + "\n\n* [busted\n _italics\n link\n\n\n* [busted destination](hey\n\n* ![busted image alt text\n\n* ![busted image link](image.png\n\n* a *bold choice\n\n* very _italic *and bold, onto many\n lines is OK* but don't forget...\n\na paragraph with [a bad link syntax](http://example.com\nis OK. The rest *works*.\n" + @88 (⟨11, 7⟩): '_' + "\n\n\n* [busted destination](hey\n\n* ![busted image alt text\n\n* ![busted image link](image.png\n\n* a *bold choice\n\n* very _italic *and bold, onto many\n lines is OK* but don't forget...\n\na paragraph with [a bad link syntax](http://example.com\nis OK. The rest *works*.\n" + @88 (⟨11, 7⟩): expected ']' + "\n\n\n* [busted destination](hey\n\n* ![busted image alt text\n\n* ![busted image link](image.png\n\n* a *bold choice\n\n* very _italic *and bold, onto many\n lines is OK* but don't forget...\n\na paragraph with [a bad link syntax](http://example.com\nis OK. The rest *works*.\n" + @117 (⟨14, 26⟩): expected ')' + "\n\n* ![busted image alt text\n\n* ![busted image link](image.png\n\n* a *bold choice\n\n* very _italic *and bold, onto many\n lines is OK* but don't forget...\n\na paragraph with [a bad link syntax](http://example.com\nis OK. The rest *works*.\n" + @144 (⟨16, 25⟩): expected ']' + "\n\n* ![busted image link](image.png\n\n* a *bold choice\n\n* very _italic *and bold, onto many\n lines is OK* but don't forget...\n\na paragraph with [a bad link syntax](http://example.com\nis OK. The rest *works*.\n" + @178 (⟨18, 32⟩): expected ')' + "\n\n* a *bold choice\n\n* very _italic *and bold, onto many\n lines is OK* but don't forget...\n\na paragraph with [a bad link syntax](http://example.com\nis OK. The rest *works*.\n" + @196 (⟨20, 16⟩): '*' + "\n\n* very _italic *and bold, onto many\n lines is OK* but don't forget...\n\na paragraph with [a bad link syntax](http://example.com\nis OK. The rest *works*.\n" + @269 (⟨23, 35⟩): '_' + "\n\na paragraph with [a bad link syntax](http://example.com\nis OK. The rest *works*.\n" + @326 (⟨25, 55⟩): expected ')' + "\nis OK. The rest *works*.\n" + +Final stack: + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\"")) + (Lean.Doc.Syntax.text + (str "\"Error recovery tests:\""))] + "}") + (Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.link + "[" + [(Lean.Doc.Syntax.text + (str "\"busted link\""))] + )] + "}")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.link + "[" + [(Lean.Doc.Syntax.text + (str "\"busted\"")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\"")) + (Lean.Doc.Syntax.text + (str "\" link\""))] + )] + "}")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.link + "[" + [(Lean.Doc.Syntax.text + (str "\"busted\"")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\"")) + (Lean.Doc.Syntax.text (str "\" \"")) + (Lean.Doc.Syntax.emph + "_" + [(Lean.Doc.Syntax.text + (str "\"italics\"")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\"")) + (Lean.Doc.Syntax.text + (str "\" link\""))] + )] + )] + "}")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.link + "[" + [(Lean.Doc.Syntax.text + (str "\"busted destination\""))] + "]" + (Lean.Doc.Syntax.url + "(" + (str "\"hey\"") + ))] + "}")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.image + "![" + (str "\"busted image alt text\"") + )] + "}")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.image + "![" + (str "\"busted image link\"") + "]" + (Lean.Doc.Syntax.url + "(" + (str "\"image.png\"") + ))] + "}")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"a \"")) + (Lean.Doc.Syntax.bold + "*" + [(Lean.Doc.Syntax.text + (str "\"bold choice\""))] + )] + "}")]) + (Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"very \"")) + (Lean.Doc.Syntax.emph + "_" + [(Lean.Doc.Syntax.text + (str "\"italic \"")) + (Lean.Doc.Syntax.bold + "*" + [(Lean.Doc.Syntax.text + (str "\"and bold, onto many\"")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\"")) + (Lean.Doc.Syntax.text + (str "\" lines is OK\""))] + "*") + (Lean.Doc.Syntax.text + (str "\" but don't forget...\""))] + )] + "}")])] + "}") + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"a paragraph with \"")) + (Lean.Doc.Syntax.link + "[" + [(Lean.Doc.Syntax.text + (str "\"a bad link syntax\""))] + "]" + (Lean.Doc.Syntax.url + "(" + (str "\"http://example.com\"") + )) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\"")) + (Lean.Doc.Syntax.text + (str "\"is OK. The rest \"")) + (Lean.Doc.Syntax.bold + "*" + [(Lean.Doc.Syntax.text (str "\"works\""))] + "*") + (Lean.Doc.Syntax.text (str "\".\"")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\""))] + "}")] \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0001 b/tests/lean/docparse/codeBlock_0001 new file mode 100644 index 0000000000..dc36ef62e2 --- /dev/null +++ b/tests/lean/docparse/codeBlock_0001 @@ -0,0 +1,4 @@ + ``` scheme + (define x 4) + x + ``` diff --git a/tests/lean/docparse/codeBlock_0001.expected.out b/tests/lean/docparse/codeBlock_0001.expected.out new file mode 100644 index 0000000000..c53fe6fcf3 --- /dev/null +++ b/tests/lean/docparse/codeBlock_0001.expected.out @@ -0,0 +1,8 @@ +Success! Final stack: + (Lean.Doc.Syntax.codeblock + "```" + [`scheme []] + "\n" + (str "\"(define x 4)\\nx\\n\"") + "```") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0002 b/tests/lean/docparse/codeBlock_0002 new file mode 100644 index 0000000000..f07886f27d --- /dev/null +++ b/tests/lean/docparse/codeBlock_0002 @@ -0,0 +1,4 @@ +``` scheme + (define x 4) + x +``` \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0002.expected.out b/tests/lean/docparse/codeBlock_0002.expected.out new file mode 100644 index 0000000000..c043b30191 --- /dev/null +++ b/tests/lean/docparse/codeBlock_0002.expected.out @@ -0,0 +1,8 @@ +Success! Final stack: + (Lean.Doc.Syntax.codeblock + "```" + [`scheme []] + "\n" + (str "\" (define x 4)\\n x\\n\"") + "```") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0003 b/tests/lean/docparse/codeBlock_0003 new file mode 100644 index 0000000000..f1530288c2 --- /dev/null +++ b/tests/lean/docparse/codeBlock_0003 @@ -0,0 +1,4 @@ + ``` + (define x 4) + x + ``` diff --git a/tests/lean/docparse/codeBlock_0003.expected.out b/tests/lean/docparse/codeBlock_0003.expected.out new file mode 100644 index 0000000000..9092953fa6 --- /dev/null +++ b/tests/lean/docparse/codeBlock_0003.expected.out @@ -0,0 +1,8 @@ +Success! Final stack: + (Lean.Doc.Syntax.codeblock + "```" + [] + "\n" + (str "\"(define x 4)\\nx\\n\"") + "```") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0004 b/tests/lean/docparse/codeBlock_0004 new file mode 100644 index 0000000000..b68d97987b --- /dev/null +++ b/tests/lean/docparse/codeBlock_0004 @@ -0,0 +1,4 @@ + ``` + (define x 4) + x + ``` diff --git a/tests/lean/docparse/codeBlock_0004.expected.out b/tests/lean/docparse/codeBlock_0004.expected.out new file mode 100644 index 0000000000..9092953fa6 --- /dev/null +++ b/tests/lean/docparse/codeBlock_0004.expected.out @@ -0,0 +1,8 @@ +Success! Final stack: + (Lean.Doc.Syntax.codeblock + "```" + [] + "\n" + (str "\"(define x 4)\\nx\\n\"") + "```") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0005 b/tests/lean/docparse/codeBlock_0005 new file mode 100644 index 0000000000..f07886f27d --- /dev/null +++ b/tests/lean/docparse/codeBlock_0005 @@ -0,0 +1,4 @@ +``` scheme + (define x 4) + x +``` \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0005.expected.out b/tests/lean/docparse/codeBlock_0005.expected.out new file mode 100644 index 0000000000..c043b30191 --- /dev/null +++ b/tests/lean/docparse/codeBlock_0005.expected.out @@ -0,0 +1,8 @@ +Success! Final stack: + (Lean.Doc.Syntax.codeblock + "```" + [`scheme []] + "\n" + (str "\" (define x 4)\\n x\\n\"") + "```") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0006 b/tests/lean/docparse/codeBlock_0006 new file mode 100644 index 0000000000..c91cfd1f68 --- /dev/null +++ b/tests/lean/docparse/codeBlock_0006 @@ -0,0 +1,4 @@ +``` scheme +(define x 4) +x +``` \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0006.expected.out b/tests/lean/docparse/codeBlock_0006.expected.out new file mode 100644 index 0000000000..c53fe6fcf3 --- /dev/null +++ b/tests/lean/docparse/codeBlock_0006.expected.out @@ -0,0 +1,8 @@ +Success! Final stack: + (Lean.Doc.Syntax.codeblock + "```" + [`scheme []] + "\n" + (str "\"(define x 4)\\nx\\n\"") + "```") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0007 b/tests/lean/docparse/codeBlock_0007 new file mode 100644 index 0000000000..8b124f80a9 --- /dev/null +++ b/tests/lean/docparse/codeBlock_0007 @@ -0,0 +1,5 @@ +``` scheme +(define x 4) +x + ```` +more \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0007.expected.out b/tests/lean/docparse/codeBlock_0007.expected.out new file mode 100644 index 0000000000..4109389b53 --- /dev/null +++ b/tests/lean/docparse/codeBlock_0007.expected.out @@ -0,0 +1,9 @@ +Failure @32 (⟨5, 0⟩): expected column 0 +Final stack: + (Lean.Doc.Syntax.codeblock + "```" + [`scheme []] + "\n" + (str "\"(define x 4)\\nx\\n\"") + ) +Remaining: "more" \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0008 b/tests/lean/docparse/codeBlock_0008 new file mode 100644 index 0000000000..036053708f --- /dev/null +++ b/tests/lean/docparse/codeBlock_0008 @@ -0,0 +1,4 @@ + ``` scheme +(define x 4) +x +``` \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0008.expected.out b/tests/lean/docparse/codeBlock_0008.expected.out new file mode 100644 index 0000000000..5bb5a39139 --- /dev/null +++ b/tests/lean/docparse/codeBlock_0008.expected.out @@ -0,0 +1,9 @@ +Failure @25 (⟨3, 0⟩): expected column 1 +Final stack: + (Lean.Doc.Syntax.codeblock + "```" + [`scheme []] + "\n" + (str "\"\\n\"") + ) +Remaining: "x\n```" \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0009 b/tests/lean/docparse/codeBlock_0009 new file mode 100644 index 0000000000..946bd58168 --- /dev/null +++ b/tests/lean/docparse/codeBlock_0009 @@ -0,0 +1,4 @@ + ``` scheme + (define x 4) + x +``` \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0009.expected.out b/tests/lean/docparse/codeBlock_0009.expected.out new file mode 100644 index 0000000000..2246124614 --- /dev/null +++ b/tests/lean/docparse/codeBlock_0009.expected.out @@ -0,0 +1,9 @@ +Failure @32 (⟨4, 3⟩): expected column 1 +Final stack: + (Lean.Doc.Syntax.codeblock + "```" + [`scheme []] + "\n" + (str "\"(define x 4)\\nx\\n\"") + ) +Remaining: "" \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0010 b/tests/lean/docparse/codeBlock_0010 new file mode 100644 index 0000000000..9aaa5b01f7 --- /dev/null +++ b/tests/lean/docparse/codeBlock_0010 @@ -0,0 +1,4 @@ + ``` + (define x 4) + x +``` \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0010.expected.out b/tests/lean/docparse/codeBlock_0010.expected.out new file mode 100644 index 0000000000..ac290ed14b --- /dev/null +++ b/tests/lean/docparse/codeBlock_0010.expected.out @@ -0,0 +1,9 @@ +Failure @25 (⟨4, 3⟩): expected column 1 +Final stack: + (Lean.Doc.Syntax.codeblock + "```" + [] + "\n" + (str "\"(define x 4)\\nx\\n\"") + ) +Remaining: "" \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0011 b/tests/lean/docparse/codeBlock_0011 new file mode 100644 index 0000000000..25afaa302d --- /dev/null +++ b/tests/lean/docparse/codeBlock_0011 @@ -0,0 +1,4 @@ + ``` + (define x 4) + x +``` \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0011.expected.out b/tests/lean/docparse/codeBlock_0011.expected.out new file mode 100644 index 0000000000..c71422ee63 --- /dev/null +++ b/tests/lean/docparse/codeBlock_0011.expected.out @@ -0,0 +1,9 @@ +Failure @28 (⟨4, 3⟩): expected column 1 +Final stack: + (Lean.Doc.Syntax.codeblock + "```" + [] + "\n" + (str "\"(define x 4)\\nx\\n\"") + ) +Remaining: "" \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0012 b/tests/lean/docparse/codeBlock_0012 new file mode 100644 index 0000000000..1776232fc5 --- /dev/null +++ b/tests/lean/docparse/codeBlock_0012 @@ -0,0 +1,4 @@ +``` scheme dialect:="chicken" 43 +(define x 4) +x +``` \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0012.expected.out b/tests/lean/docparse/codeBlock_0012.expected.out new file mode 100644 index 0000000000..2d816c0be7 --- /dev/null +++ b/tests/lean/docparse/codeBlock_0012.expected.out @@ -0,0 +1,15 @@ +Success! Final stack: + (Lean.Doc.Syntax.codeblock + "```" + [`scheme + [(Lean.Doc.Syntax.named_no_paren + `dialect + ":=" + (Lean.Doc.Syntax.arg_str + (str "\"chicken\""))) + (Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_num (num "43")))]] + "\n" + (str "\"(define x 4)\\nx\\n\"") + "```") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0013 b/tests/lean/docparse/codeBlock_0013 new file mode 100644 index 0000000000..f11acdb958 --- /dev/null +++ b/tests/lean/docparse/codeBlock_0013 @@ -0,0 +1,4 @@ +``` scheme (dialect:="chicken") +(define x 4) +x +``` \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0013.expected.out b/tests/lean/docparse/codeBlock_0013.expected.out new file mode 100644 index 0000000000..d2496c6ef3 --- /dev/null +++ b/tests/lean/docparse/codeBlock_0013.expected.out @@ -0,0 +1,15 @@ +Success! Final stack: + (Lean.Doc.Syntax.codeblock + "```" + [`scheme + [(Lean.Doc.Syntax.named + "(" + `dialect + ":=" + (Lean.Doc.Syntax.arg_str + (str "\"chicken\"")) + ")")]] + "\n" + (str "\"(define x 4)\\nx\\n\"") + "```") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0014 b/tests/lean/docparse/codeBlock_0014 new file mode 100644 index 0000000000..bd497fdc2f --- /dev/null +++ b/tests/lean/docparse/codeBlock_0014 @@ -0,0 +1,4 @@ +``` scheme (dialect:="chicken" +(define x 4) +x +``` \ No newline at end of file diff --git a/tests/lean/docparse/codeBlock_0014.expected.out b/tests/lean/docparse/codeBlock_0014.expected.out new file mode 100644 index 0000000000..337678035e --- /dev/null +++ b/tests/lean/docparse/codeBlock_0014.expected.out @@ -0,0 +1,16 @@ +Failure @43 (⟨2, 12⟩): expected ')' +Final stack: + (Lean.Doc.Syntax.codeblock + "```" + [`scheme + [(Lean.Doc.Syntax.named + "(" + `dialect + ":=" + (Lean.Doc.Syntax.arg_str + (str "\"chicken\"")) + )]] + "\n" + (str "\"x\\n\"") + "```") +Remaining: "\nx\n```" \ No newline at end of file diff --git a/tests/lean/docparse/code_0001 b/tests/lean/docparse/code_0001 new file mode 100644 index 0000000000..26c2351ecb --- /dev/null +++ b/tests/lean/docparse/code_0001 @@ -0,0 +1 @@ +``foo bar`` \ No newline at end of file diff --git a/tests/lean/docparse/code_0001.expected.out b/tests/lean/docparse/code_0001.expected.out new file mode 100644 index 0000000000..6faadc05a7 --- /dev/null +++ b/tests/lean/docparse/code_0001.expected.out @@ -0,0 +1,6 @@ +Success! Final stack: + (Lean.Doc.Syntax.code + "``" + (str "\"foo bar\"") + "``") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/code_0002 b/tests/lean/docparse/code_0002 new file mode 100644 index 0000000000..bac7389bb8 --- /dev/null +++ b/tests/lean/docparse/code_0002 @@ -0,0 +1 @@ +` ` \ No newline at end of file diff --git a/tests/lean/docparse/code_0002.expected.out b/tests/lean/docparse/code_0002.expected.out new file mode 100644 index 0000000000..cd7d7a06a8 --- /dev/null +++ b/tests/lean/docparse/code_0002.expected.out @@ -0,0 +1,3 @@ +Success! Final stack: + (Lean.Doc.Syntax.code "`" (str "\" \"") "`") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/code_0003 b/tests/lean/docparse/code_0003 new file mode 100644 index 0000000000..e6ba24d95d --- /dev/null +++ b/tests/lean/docparse/code_0003 @@ -0,0 +1 @@ +` ` \ No newline at end of file diff --git a/tests/lean/docparse/code_0003.expected.out b/tests/lean/docparse/code_0003.expected.out new file mode 100644 index 0000000000..549b305dea --- /dev/null +++ b/tests/lean/docparse/code_0003.expected.out @@ -0,0 +1,3 @@ +Success! Final stack: + (Lean.Doc.Syntax.code "`" (str "\" \"") "`") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/code_0004 b/tests/lean/docparse/code_0004 new file mode 100644 index 0000000000..a3ece3eadf --- /dev/null +++ b/tests/lean/docparse/code_0004 @@ -0,0 +1 @@ +` ` \ No newline at end of file diff --git a/tests/lean/docparse/code_0004.expected.out b/tests/lean/docparse/code_0004.expected.out new file mode 100644 index 0000000000..b121bb5035 --- /dev/null +++ b/tests/lean/docparse/code_0004.expected.out @@ -0,0 +1,3 @@ +Success! Final stack: + (Lean.Doc.Syntax.code "`" (str "\" \"") "`") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/code_0005 b/tests/lean/docparse/code_0005 new file mode 100644 index 0000000000..8cc1e2f989 --- /dev/null +++ b/tests/lean/docparse/code_0005 @@ -0,0 +1 @@ +` x ` \ No newline at end of file diff --git a/tests/lean/docparse/code_0005.expected.out b/tests/lean/docparse/code_0005.expected.out new file mode 100644 index 0000000000..1af0b73d36 --- /dev/null +++ b/tests/lean/docparse/code_0005.expected.out @@ -0,0 +1,3 @@ +Success! Final stack: + (Lean.Doc.Syntax.code "`" (str "\"x\"") "`") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/code_0006 b/tests/lean/docparse/code_0006 new file mode 100644 index 0000000000..6f340fa8d5 --- /dev/null +++ b/tests/lean/docparse/code_0006 @@ -0,0 +1 @@ +``foo `stuff` bar`` \ No newline at end of file diff --git a/tests/lean/docparse/code_0006.expected.out b/tests/lean/docparse/code_0006.expected.out new file mode 100644 index 0000000000..32c839c11b --- /dev/null +++ b/tests/lean/docparse/code_0006.expected.out @@ -0,0 +1,6 @@ +Success! Final stack: + (Lean.Doc.Syntax.code + "``" + (str "\"foo `stuff` bar\"") + "``") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/code_0007 b/tests/lean/docparse/code_0007 new file mode 100644 index 0000000000..8814874d88 --- /dev/null +++ b/tests/lean/docparse/code_0007 @@ -0,0 +1 @@ +`foo \ No newline at end of file diff --git a/tests/lean/docparse/code_0007.expected.out b/tests/lean/docparse/code_0007.expected.out new file mode 100644 index 0000000000..35297485f3 --- /dev/null +++ b/tests/lean/docparse/code_0007.expected.out @@ -0,0 +1,7 @@ +Failure @4 (⟨1, 4⟩): expected '`' to close inline code +Final stack: + (Lean.Doc.Syntax.code + "`" + (str "\"foo\"") + ) +Remaining: "" \ No newline at end of file diff --git a/tests/lean/docparse/code_0008 b/tests/lean/docparse/code_0008 new file mode 100644 index 0000000000..87e9555ea8 --- /dev/null +++ b/tests/lean/docparse/code_0008 @@ -0,0 +1 @@ +` foo` \ No newline at end of file diff --git a/tests/lean/docparse/code_0008.expected.out b/tests/lean/docparse/code_0008.expected.out new file mode 100644 index 0000000000..aff92ea2bb --- /dev/null +++ b/tests/lean/docparse/code_0008.expected.out @@ -0,0 +1,3 @@ +Success! Final stack: + (Lean.Doc.Syntax.code "`" (str "\" foo\"") "`") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/code_0009 b/tests/lean/docparse/code_0009 new file mode 100644 index 0000000000..cfc7ed5fe5 --- /dev/null +++ b/tests/lean/docparse/code_0009 @@ -0,0 +1 @@ +` foo ` \ No newline at end of file diff --git a/tests/lean/docparse/code_0009.expected.out b/tests/lean/docparse/code_0009.expected.out new file mode 100644 index 0000000000..2e0a05c543 --- /dev/null +++ b/tests/lean/docparse/code_0009.expected.out @@ -0,0 +1,3 @@ +Success! Final stack: + (Lean.Doc.Syntax.code "`" (str "\"foo\"") "`") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/code_0010 b/tests/lean/docparse/code_0010 new file mode 100644 index 0000000000..018a5bfe12 --- /dev/null +++ b/tests/lean/docparse/code_0010 @@ -0,0 +1,2 @@ +` fo +o ` \ No newline at end of file diff --git a/tests/lean/docparse/code_0010.expected.out b/tests/lean/docparse/code_0010.expected.out new file mode 100644 index 0000000000..cccc91e0a6 --- /dev/null +++ b/tests/lean/docparse/code_0010.expected.out @@ -0,0 +1,6 @@ +Success! Final stack: + (Lean.Doc.Syntax.code + "`" + (str "\"fo\\no\"") + "`") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/code_0011 b/tests/lean/docparse/code_0011 new file mode 100644 index 0000000000..6b1d3de5b1 --- /dev/null +++ b/tests/lean/docparse/code_0011 @@ -0,0 +1 @@ +`` `x `` \ No newline at end of file diff --git a/tests/lean/docparse/code_0011.expected.out b/tests/lean/docparse/code_0011.expected.out new file mode 100644 index 0000000000..f4be83ef09 --- /dev/null +++ b/tests/lean/docparse/code_0011.expected.out @@ -0,0 +1,3 @@ +Success! Final stack: + (Lean.Doc.Syntax.code "``" (str "\"`x\"") "``") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/directive_0001 b/tests/lean/docparse/directive_0001 new file mode 100644 index 0000000000..b6878d6071 --- /dev/null +++ b/tests/lean/docparse/directive_0001 @@ -0,0 +1,3 @@ +:::: multiPara +foo +:::: \ No newline at end of file diff --git a/tests/lean/docparse/directive_0001.expected.out b/tests/lean/docparse/directive_0001.expected.out new file mode 100644 index 0000000000..5c3341cce0 --- /dev/null +++ b/tests/lean/docparse/directive_0001.expected.out @@ -0,0 +1,12 @@ +Success! Final stack: + (Lean.Doc.Syntax.directive + "::::" + `multiPara + [] + "\n" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"foo\""))] + "}")] + "::::") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/directive_0002 b/tests/lean/docparse/directive_0002 new file mode 100644 index 0000000000..07b217bbbc --- /dev/null +++ b/tests/lean/docparse/directive_0002 @@ -0,0 +1,5 @@ +:::: multiPara + + +foo +:::: \ No newline at end of file diff --git a/tests/lean/docparse/directive_0002.expected.out b/tests/lean/docparse/directive_0002.expected.out new file mode 100644 index 0000000000..5c3341cce0 --- /dev/null +++ b/tests/lean/docparse/directive_0002.expected.out @@ -0,0 +1,12 @@ +Success! Final stack: + (Lean.Doc.Syntax.directive + "::::" + `multiPara + [] + "\n" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"foo\""))] + "}")] + "::::") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/directive_0003 b/tests/lean/docparse/directive_0003 new file mode 100644 index 0000000000..847d73799e --- /dev/null +++ b/tests/lean/docparse/directive_0003 @@ -0,0 +1,3 @@ + ::: multiPara greatness:="amazing!" + foo + ::: diff --git a/tests/lean/docparse/directive_0003.expected.out b/tests/lean/docparse/directive_0003.expected.out new file mode 100644 index 0000000000..69e31fc0a5 --- /dev/null +++ b/tests/lean/docparse/directive_0003.expected.out @@ -0,0 +1,15 @@ +Success! Final stack: + (Lean.Doc.Syntax.directive + ":::" + `multiPara + [(Lean.Doc.Syntax.named_no_paren + `greatness + ":=" + (Lean.Doc.Syntax.arg_str + (str "\"amazing!\""))) + (Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_ident `foo))] + "\n" + [] + ":::") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/directive_0004 b/tests/lean/docparse/directive_0004 new file mode 100644 index 0000000000..9ab71c6ee2 --- /dev/null +++ b/tests/lean/docparse/directive_0004 @@ -0,0 +1,7 @@ + ::: multiPara + foo + + + + * List item + ::: diff --git a/tests/lean/docparse/directive_0004.expected.out b/tests/lean/docparse/directive_0004.expected.out new file mode 100644 index 0000000000..54c523dad5 --- /dev/null +++ b/tests/lean/docparse/directive_0004.expected.out @@ -0,0 +1,22 @@ +Success! Final stack: + (Lean.Doc.Syntax.directive + ":::" + `multiPara + [] + "\n" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"foo\""))] + "}") + (Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"List item \""))] + "}")])] + "}")] + ":::") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/directive_0005 b/tests/lean/docparse/directive_0005 new file mode 100644 index 0000000000..2d7abfb0bb --- /dev/null +++ b/tests/lean/docparse/directive_0005 @@ -0,0 +1,7 @@ + ::: multiPara thing + foo + + + + * List item + ::: diff --git a/tests/lean/docparse/directive_0005.expected.out b/tests/lean/docparse/directive_0005.expected.out new file mode 100644 index 0000000000..b59c7b7167 --- /dev/null +++ b/tests/lean/docparse/directive_0005.expected.out @@ -0,0 +1,23 @@ +Success! Final stack: + (Lean.Doc.Syntax.directive + ":::" + `multiPara + [(Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_ident `thing))] + "\n" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"foo\""))] + "}") + (Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"List item \""))] + "}")])] + "}")] + ":::") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/directive_0006 b/tests/lean/docparse/directive_0006 new file mode 100644 index 0000000000..e281f1f5b4 --- /dev/null +++ b/tests/lean/docparse/directive_0006 @@ -0,0 +1,5 @@ + ::: multiPara + foo + + * List item + ::: diff --git a/tests/lean/docparse/directive_0006.expected.out b/tests/lean/docparse/directive_0006.expected.out new file mode 100644 index 0000000000..54c523dad5 --- /dev/null +++ b/tests/lean/docparse/directive_0006.expected.out @@ -0,0 +1,22 @@ +Success! Final stack: + (Lean.Doc.Syntax.directive + ":::" + `multiPara + [] + "\n" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text (str "\"foo\""))] + "}") + (Lean.Doc.Syntax.ul + "ul{" + [(Lean.Doc.Syntax.li + "*" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"List item \""))] + "}")])] + "}")] + ":::") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/document_0001 b/tests/lean/docparse/document_0001 new file mode 100644 index 0000000000..ceb60ff23c --- /dev/null +++ b/tests/lean/docparse/document_0001 @@ -0,0 +1,3 @@ +![Lean logo](/static/lean_logo.svg) + +This is an example website/blog, for testing purposes. \ No newline at end of file diff --git a/tests/lean/docparse/document_0001.expected.out b/tests/lean/docparse/document_0001.expected.out new file mode 100644 index 0000000000..fb006f0ba6 --- /dev/null +++ b/tests/lean/docparse/document_0001.expected.out @@ -0,0 +1,19 @@ +Success! Final stack: + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.image + "![" + (str "\"Lean logo\"") + "]" + (Lean.Doc.Syntax.url + "(" + (str "\"/static/lean_logo.svg\"") + ")"))] + "}") + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str + "\"This is an example website/blog, for testing purposes.\""))] + "}")] +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/emph_0001 b/tests/lean/docparse/emph_0001 new file mode 100644 index 0000000000..e48a4bf507 --- /dev/null +++ b/tests/lean/docparse/emph_0001 @@ -0,0 +1 @@ +_aa_ \ No newline at end of file diff --git a/tests/lean/docparse/emph_0001.expected.out b/tests/lean/docparse/emph_0001.expected.out new file mode 100644 index 0000000000..13f6c44a3b --- /dev/null +++ b/tests/lean/docparse/emph_0001.expected.out @@ -0,0 +1,6 @@ +Success! Final stack: + (Lean.Doc.Syntax.emph + "_" + [(Lean.Doc.Syntax.text (str "\"aa\""))] + "_") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/emph_0002 b/tests/lean/docparse/emph_0002 new file mode 100644 index 0000000000..07c26144ba --- /dev/null +++ b/tests/lean/docparse/emph_0002 @@ -0,0 +1 @@ +_aa _ \ No newline at end of file diff --git a/tests/lean/docparse/emph_0002.expected.out b/tests/lean/docparse/emph_0002.expected.out new file mode 100644 index 0000000000..c9eb95adb4 --- /dev/null +++ b/tests/lean/docparse/emph_0002.expected.out @@ -0,0 +1,7 @@ +Failure @4 (⟨1, 4⟩): expected '_' without preceding space +Final stack: + (Lean.Doc.Syntax.emph + "_" + [(Lean.Doc.Syntax.text (str "\"aa \""))] + ) +Remaining: "_" \ No newline at end of file diff --git a/tests/lean/docparse/emph_0003 b/tests/lean/docparse/emph_0003 new file mode 100644 index 0000000000..c09ead4ebc --- /dev/null +++ b/tests/lean/docparse/emph_0003 @@ -0,0 +1 @@ +_ aa_ \ No newline at end of file diff --git a/tests/lean/docparse/emph_0003.expected.out b/tests/lean/docparse/emph_0003.expected.out new file mode 100644 index 0000000000..a86956ca6e --- /dev/null +++ b/tests/lean/docparse/emph_0003.expected.out @@ -0,0 +1,4 @@ +Failure @0 (⟨1, 0⟩): unexpected space or newline after opener +Final stack: + (Lean.Doc.Syntax.emph "_" ) +Remaining: "_ aa_" \ No newline at end of file diff --git a/tests/lean/docparse/header_0001 b/tests/lean/docparse/header_0001 new file mode 100644 index 0000000000..db23e08dad --- /dev/null +++ b/tests/lean/docparse/header_0001 @@ -0,0 +1 @@ +# Header! \ No newline at end of file diff --git a/tests/lean/docparse/header_0001.expected.out b/tests/lean/docparse/header_0001.expected.out new file mode 100644 index 0000000000..0170d48605 --- /dev/null +++ b/tests/lean/docparse/header_0001.expected.out @@ -0,0 +1,9 @@ +Success! Final stack: + (Lean.Doc.Syntax.header + "header(" + (num "0") + ")" + "{" + [(Lean.Doc.Syntax.text (str "\"Header!\""))] + "}") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/inlineTextChar_0001 b/tests/lean/docparse/inlineTextChar_0001 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/lean/docparse/inlineTextChar_0001.expected.out b/tests/lean/docparse/inlineTextChar_0001.expected.out new file mode 100644 index 0000000000..948a3a6300 --- /dev/null +++ b/tests/lean/docparse/inlineTextChar_0001.expected.out @@ -0,0 +1,4 @@ +Failure @0 (⟨1, 0⟩): unexpected end of input +Final stack: + +Remaining: "" \ No newline at end of file diff --git a/tests/lean/docparse/inlineTextChar_0002 b/tests/lean/docparse/inlineTextChar_0002 new file mode 100644 index 0000000000..2e65efe2a1 --- /dev/null +++ b/tests/lean/docparse/inlineTextChar_0002 @@ -0,0 +1 @@ +a \ No newline at end of file diff --git a/tests/lean/docparse/inlineTextChar_0002.expected.out b/tests/lean/docparse/inlineTextChar_0002.expected.out new file mode 100644 index 0000000000..58b7cea6a3 --- /dev/null +++ b/tests/lean/docparse/inlineTextChar_0002.expected.out @@ -0,0 +1,3 @@ +Success! Final stack: + empty +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/inlineTextChar_0003 b/tests/lean/docparse/inlineTextChar_0003 new file mode 100644 index 0000000000..f2ba8f84ab --- /dev/null +++ b/tests/lean/docparse/inlineTextChar_0003 @@ -0,0 +1 @@ +abc \ No newline at end of file diff --git a/tests/lean/docparse/inlineTextChar_0003.expected.out b/tests/lean/docparse/inlineTextChar_0003.expected.out new file mode 100644 index 0000000000..c3fd7fc416 --- /dev/null +++ b/tests/lean/docparse/inlineTextChar_0003.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + empty +Remaining: +"bc" \ No newline at end of file diff --git a/tests/lean/docparse/inlineTextChar_0004 b/tests/lean/docparse/inlineTextChar_0004 new file mode 100644 index 0000000000..b8d5035c12 --- /dev/null +++ b/tests/lean/docparse/inlineTextChar_0004 @@ -0,0 +1 @@ +[abc \ No newline at end of file diff --git a/tests/lean/docparse/inlineTextChar_0004.expected.out b/tests/lean/docparse/inlineTextChar_0004.expected.out new file mode 100644 index 0000000000..52f3b75f41 --- /dev/null +++ b/tests/lean/docparse/inlineTextChar_0004.expected.out @@ -0,0 +1,4 @@ +Failure @0 (⟨1, 0⟩): '[' +Final stack: + +Remaining: "[abc" \ No newline at end of file diff --git a/tests/lean/docparse/inlineTextChar_0005 b/tests/lean/docparse/inlineTextChar_0005 new file mode 100644 index 0000000000..78e3ae40b4 --- /dev/null +++ b/tests/lean/docparse/inlineTextChar_0005 @@ -0,0 +1 @@ +!abc \ No newline at end of file diff --git a/tests/lean/docparse/inlineTextChar_0005.expected.out b/tests/lean/docparse/inlineTextChar_0005.expected.out new file mode 100644 index 0000000000..08f596a60c --- /dev/null +++ b/tests/lean/docparse/inlineTextChar_0005.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + empty +Remaining: +"abc" \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadOrderedListIndicator_0001 b/tests/lean/docparse/lookaheadOrderedListIndicator_0001 new file mode 100644 index 0000000000..1f2b81dae0 --- /dev/null +++ b/tests/lean/docparse/lookaheadOrderedListIndicator_0001 @@ -0,0 +1 @@ +1. \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadOrderedListIndicator_0001.expected.out b/tests/lean/docparse/lookaheadOrderedListIndicator_0001.expected.out new file mode 100644 index 0000000000..8365344f54 --- /dev/null +++ b/tests/lean/docparse/lookaheadOrderedListIndicator_0001.expected.out @@ -0,0 +1,6 @@ +Success! Final stack: + • (num "1") + • "Lean.Doc.Parser.OrderedListType.numDot 1" + +Remaining: +"1. " \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadOrderedListIndicator_0002 b/tests/lean/docparse/lookaheadOrderedListIndicator_0002 new file mode 100644 index 0000000000..03b8252c57 --- /dev/null +++ b/tests/lean/docparse/lookaheadOrderedListIndicator_0002 @@ -0,0 +1 @@ +2. \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadOrderedListIndicator_0002.expected.out b/tests/lean/docparse/lookaheadOrderedListIndicator_0002.expected.out new file mode 100644 index 0000000000..d6e4b50183 --- /dev/null +++ b/tests/lean/docparse/lookaheadOrderedListIndicator_0002.expected.out @@ -0,0 +1,6 @@ +Success! Final stack: + • (num "2") + • "Lean.Doc.Parser.OrderedListType.numDot 2" + +Remaining: +"2. " \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadOrderedListIndicator_0003 b/tests/lean/docparse/lookaheadOrderedListIndicator_0003 new file mode 100644 index 0000000000..c5aa09bb18 --- /dev/null +++ b/tests/lean/docparse/lookaheadOrderedListIndicator_0003 @@ -0,0 +1 @@ +2. \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadOrderedListIndicator_0003.expected.out b/tests/lean/docparse/lookaheadOrderedListIndicator_0003.expected.out new file mode 100644 index 0000000000..3aaa5a63ed --- /dev/null +++ b/tests/lean/docparse/lookaheadOrderedListIndicator_0003.expected.out @@ -0,0 +1,4 @@ +Failure @0 (⟨1, 0⟩): unexpected end of input +Final stack: + +Remaining: "2." \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadOrderedListIndicator_0004 b/tests/lean/docparse/lookaheadOrderedListIndicator_0004 new file mode 100644 index 0000000000..75d671965b --- /dev/null +++ b/tests/lean/docparse/lookaheadOrderedListIndicator_0004 @@ -0,0 +1 @@ +2) \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadOrderedListIndicator_0004.expected.out b/tests/lean/docparse/lookaheadOrderedListIndicator_0004.expected.out new file mode 100644 index 0000000000..6bbdaa5c6c --- /dev/null +++ b/tests/lean/docparse/lookaheadOrderedListIndicator_0004.expected.out @@ -0,0 +1,6 @@ +Success! Final stack: + • (num "2") + • "Lean.Doc.Parser.OrderedListType.parenAfter 2" + +Remaining: +"2) " \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadOrderedListIndicator_0005 b/tests/lean/docparse/lookaheadOrderedListIndicator_0005 new file mode 100644 index 0000000000..289c4b4567 --- /dev/null +++ b/tests/lean/docparse/lookaheadOrderedListIndicator_0005 @@ -0,0 +1 @@ +-23) \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadOrderedListIndicator_0005.expected.out b/tests/lean/docparse/lookaheadOrderedListIndicator_0005.expected.out new file mode 100644 index 0000000000..4ea9f206e0 --- /dev/null +++ b/tests/lean/docparse/lookaheadOrderedListIndicator_0005.expected.out @@ -0,0 +1,4 @@ +Failure @0 (⟨1, 0⟩): digits +Final stack: + empty +Remaining: "-23) " \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadOrderedListIndicator_0006 b/tests/lean/docparse/lookaheadOrderedListIndicator_0006 new file mode 100644 index 0000000000..0e09270fe0 --- /dev/null +++ b/tests/lean/docparse/lookaheadOrderedListIndicator_0006 @@ -0,0 +1 @@ +a-23) \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadOrderedListIndicator_0006.expected.out b/tests/lean/docparse/lookaheadOrderedListIndicator_0006.expected.out new file mode 100644 index 0000000000..6e8b27cf5b --- /dev/null +++ b/tests/lean/docparse/lookaheadOrderedListIndicator_0006.expected.out @@ -0,0 +1,4 @@ +Failure @0 (⟨1, 0⟩): digits +Final stack: + empty +Remaining: "a-23) " \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadOrderedListIndicator_0007 b/tests/lean/docparse/lookaheadOrderedListIndicator_0007 new file mode 100644 index 0000000000..120456cae0 --- /dev/null +++ b/tests/lean/docparse/lookaheadOrderedListIndicator_0007 @@ -0,0 +1 @@ +23 ) \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadOrderedListIndicator_0007.expected.out b/tests/lean/docparse/lookaheadOrderedListIndicator_0007.expected.out new file mode 100644 index 0000000000..9242c5806a --- /dev/null +++ b/tests/lean/docparse/lookaheadOrderedListIndicator_0007.expected.out @@ -0,0 +1,4 @@ +Failure @0 (⟨1, 0⟩): unexpected ' '; expected ')' or '.' +Final stack: + empty +Remaining: "23 ) " \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadUnorderedListIndicator_0001 b/tests/lean/docparse/lookaheadUnorderedListIndicator_0001 new file mode 100644 index 0000000000..e48d96ff92 --- /dev/null +++ b/tests/lean/docparse/lookaheadUnorderedListIndicator_0001 @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadUnorderedListIndicator_0001.expected.out b/tests/lean/docparse/lookaheadUnorderedListIndicator_0001.expected.out new file mode 100644 index 0000000000..874a81e62a --- /dev/null +++ b/tests/lean/docparse/lookaheadUnorderedListIndicator_0001.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + "Lean.Doc.Parser.UnorderedListType.asterisk" +Remaining: +"* " \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadUnorderedListIndicator_0002 b/tests/lean/docparse/lookaheadUnorderedListIndicator_0002 new file mode 100644 index 0000000000..0d439ac8b6 --- /dev/null +++ b/tests/lean/docparse/lookaheadUnorderedListIndicator_0002 @@ -0,0 +1 @@ +- \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadUnorderedListIndicator_0002.expected.out b/tests/lean/docparse/lookaheadUnorderedListIndicator_0002.expected.out new file mode 100644 index 0000000000..9dfee4b1ae --- /dev/null +++ b/tests/lean/docparse/lookaheadUnorderedListIndicator_0002.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + "Lean.Doc.Parser.UnorderedListType.dash" +Remaining: +"- " \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadUnorderedListIndicator_0003 b/tests/lean/docparse/lookaheadUnorderedListIndicator_0003 new file mode 100644 index 0000000000..2ca484d629 --- /dev/null +++ b/tests/lean/docparse/lookaheadUnorderedListIndicator_0003 @@ -0,0 +1 @@ ++ \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadUnorderedListIndicator_0003.expected.out b/tests/lean/docparse/lookaheadUnorderedListIndicator_0003.expected.out new file mode 100644 index 0000000000..02578f3c5f --- /dev/null +++ b/tests/lean/docparse/lookaheadUnorderedListIndicator_0003.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + "Lean.Doc.Parser.UnorderedListType.plus" +Remaining: +"+ " \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadUnorderedListIndicator_0004 b/tests/lean/docparse/lookaheadUnorderedListIndicator_0004 new file mode 100644 index 0000000000..e48d96ff92 --- /dev/null +++ b/tests/lean/docparse/lookaheadUnorderedListIndicator_0004 @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadUnorderedListIndicator_0004.expected.out b/tests/lean/docparse/lookaheadUnorderedListIndicator_0004.expected.out new file mode 100644 index 0000000000..874a81e62a --- /dev/null +++ b/tests/lean/docparse/lookaheadUnorderedListIndicator_0004.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + "Lean.Doc.Parser.UnorderedListType.asterisk" +Remaining: +"* " \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadUnorderedListIndicator_0005 b/tests/lean/docparse/lookaheadUnorderedListIndicator_0005 new file mode 100644 index 0000000000..42780ecb17 --- /dev/null +++ b/tests/lean/docparse/lookaheadUnorderedListIndicator_0005 @@ -0,0 +1 @@ + * \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadUnorderedListIndicator_0005.expected.out b/tests/lean/docparse/lookaheadUnorderedListIndicator_0005.expected.out new file mode 100644 index 0000000000..54ab873ab9 --- /dev/null +++ b/tests/lean/docparse/lookaheadUnorderedListIndicator_0005.expected.out @@ -0,0 +1,4 @@ +Failure @0 (⟨1, 0⟩): unexpected end of input +Final stack: + +Remaining: " *" \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadUnorderedListIndicator_0006 b/tests/lean/docparse/lookaheadUnorderedListIndicator_0006 new file mode 100644 index 0000000000..0ca974ea52 --- /dev/null +++ b/tests/lean/docparse/lookaheadUnorderedListIndicator_0006 @@ -0,0 +1 @@ +** \ No newline at end of file diff --git a/tests/lean/docparse/lookaheadUnorderedListIndicator_0006.expected.out b/tests/lean/docparse/lookaheadUnorderedListIndicator_0006.expected.out new file mode 100644 index 0000000000..78f0098758 --- /dev/null +++ b/tests/lean/docparse/lookaheadUnorderedListIndicator_0006.expected.out @@ -0,0 +1,5 @@ +Failure @0 (⟨1, 0⟩): ' +' +Final stack: + +Remaining: "** " \ No newline at end of file diff --git a/tests/lean/docparse/manyInlineTextChar_0001 b/tests/lean/docparse/manyInlineTextChar_0001 new file mode 100644 index 0000000000..baace3026f --- /dev/null +++ b/tests/lean/docparse/manyInlineTextChar_0001 @@ -0,0 +1 @@ +!!![abc \ No newline at end of file diff --git a/tests/lean/docparse/manyInlineTextChar_0001.expected.out b/tests/lean/docparse/manyInlineTextChar_0001.expected.out new file mode 100644 index 0000000000..141eb87878 --- /dev/null +++ b/tests/lean/docparse/manyInlineTextChar_0001.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + "!!" +Remaining: +"![abc" \ No newline at end of file diff --git a/tests/lean/docparse/manyInlineTextChar_0002 b/tests/lean/docparse/manyInlineTextChar_0002 new file mode 100644 index 0000000000..f59ec20aab --- /dev/null +++ b/tests/lean/docparse/manyInlineTextChar_0002 @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/tests/lean/docparse/manyInlineTextChar_0002.expected.out b/tests/lean/docparse/manyInlineTextChar_0002.expected.out new file mode 100644 index 0000000000..86af371c44 --- /dev/null +++ b/tests/lean/docparse/manyInlineTextChar_0002.expected.out @@ -0,0 +1,4 @@ +Failure @0 (⟨1, 0⟩): '*' +Final stack: + [] +Remaining: "*" \ No newline at end of file diff --git a/tests/lean/docparse/manyInlineTextChar_0003 b/tests/lean/docparse/manyInlineTextChar_0003 new file mode 100644 index 0000000000..1333988995 --- /dev/null +++ b/tests/lean/docparse/manyInlineTextChar_0003 @@ -0,0 +1 @@ +!!!\[abc] \ No newline at end of file diff --git a/tests/lean/docparse/manyInlineTextChar_0003.expected.out b/tests/lean/docparse/manyInlineTextChar_0003.expected.out new file mode 100644 index 0000000000..69756435a3 --- /dev/null +++ b/tests/lean/docparse/manyInlineTextChar_0003.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + "!!!\\[abc" +Remaining: +"]" \ No newline at end of file diff --git a/tests/lean/docparse/metadataBlock_0001 b/tests/lean/docparse/metadataBlock_0001 new file mode 100644 index 0000000000..157458a32c --- /dev/null +++ b/tests/lean/docparse/metadataBlock_0001 @@ -0,0 +1,2 @@ +%%% +%%% \ No newline at end of file diff --git a/tests/lean/docparse/metadataBlock_0001.expected.out b/tests/lean/docparse/metadataBlock_0001.expected.out new file mode 100644 index 0000000000..606e773fc7 --- /dev/null +++ b/tests/lean/docparse/metadataBlock_0001.expected.out @@ -0,0 +1,6 @@ +Success! Final stack: + (Lean.Doc.Syntax.metadata_block + "%%%" + (Term.structInstFields []) + "%%%") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/metadataBlock_0002 b/tests/lean/docparse/metadataBlock_0002 new file mode 100644 index 0000000000..894b79e406 --- /dev/null +++ b/tests/lean/docparse/metadataBlock_0002 @@ -0,0 +1,4 @@ +%%% +foo := bar + +%%% \ No newline at end of file diff --git a/tests/lean/docparse/metadataBlock_0002.expected.out b/tests/lean/docparse/metadataBlock_0002.expected.out new file mode 100644 index 0000000000..c09d89257a --- /dev/null +++ b/tests/lean/docparse/metadataBlock_0002.expected.out @@ -0,0 +1,12 @@ +Success! Final stack: + (Lean.Doc.Syntax.metadata_block + "%%%" + (Term.structInstFields + [(Term.structInstField + (Term.structInstLVal `foo []) + [[] + [] + (Term.structInstFieldDef ":=" [] `bar)]) + []]) + "%%%") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/metadataBlock_0003 b/tests/lean/docparse/metadataBlock_0003 new file mode 100644 index 0000000000..05625731c9 --- /dev/null +++ b/tests/lean/docparse/metadataBlock_0003 @@ -0,0 +1,4 @@ +%%% +foo := bar +x +%%% \ No newline at end of file diff --git a/tests/lean/docparse/metadataBlock_0003.expected.out b/tests/lean/docparse/metadataBlock_0003.expected.out new file mode 100644 index 0000000000..aa689840f0 --- /dev/null +++ b/tests/lean/docparse/metadataBlock_0003.expected.out @@ -0,0 +1,16 @@ +Success! Final stack: + (Lean.Doc.Syntax.metadata_block + "%%%" + (Term.structInstFields + [(Term.structInstField + (Term.structInstLVal `foo []) + [[] + [] + (Term.structInstFieldDef ":=" [] `bar)]) + [] + (Term.structInstField + (Term.structInstLVal `x []) + []) + []]) + "%%%") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/nameAndArgs_0001 b/tests/lean/docparse/nameAndArgs_0001 new file mode 100644 index 0000000000..0ca118b32c --- /dev/null +++ b/tests/lean/docparse/nameAndArgs_0001 @@ -0,0 +1 @@ +leanExample context := 2 \ No newline at end of file diff --git a/tests/lean/docparse/nameAndArgs_0001.expected.out b/tests/lean/docparse/nameAndArgs_0001.expected.out new file mode 100644 index 0000000000..c9820cb0c5 --- /dev/null +++ b/tests/lean/docparse/nameAndArgs_0001.expected.out @@ -0,0 +1,8 @@ +Success! Final stack: + • `leanExample + • [(Lean.Doc.Syntax.named_no_paren + `context + ":=" + (Lean.Doc.Syntax.arg_num (num "2")))] + +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/nameAndArgs_0002 b/tests/lean/docparse/nameAndArgs_0002 new file mode 100644 index 0000000000..0997600671 --- /dev/null +++ b/tests/lean/docparse/nameAndArgs_0002 @@ -0,0 +1 @@ +scheme dialect:="chicken" 43 \ No newline at end of file diff --git a/tests/lean/docparse/nameAndArgs_0002.expected.out b/tests/lean/docparse/nameAndArgs_0002.expected.out new file mode 100644 index 0000000000..523e10673e --- /dev/null +++ b/tests/lean/docparse/nameAndArgs_0002.expected.out @@ -0,0 +1,11 @@ +Success! Final stack: + • `scheme + • [(Lean.Doc.Syntax.named_no_paren + `dialect + ":=" + (Lean.Doc.Syntax.arg_str + (str "\"chicken\""))) + (Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_num (num "43")))] + +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/nameAndArgs_0003 b/tests/lean/docparse/nameAndArgs_0003 new file mode 100644 index 0000000000..4be9889fa6 --- /dev/null +++ b/tests/lean/docparse/nameAndArgs_0003 @@ -0,0 +1 @@ +scheme dialect:="chicken" 43 +foo \ No newline at end of file diff --git a/tests/lean/docparse/nameAndArgs_0003.expected.out b/tests/lean/docparse/nameAndArgs_0003.expected.out new file mode 100644 index 0000000000..bdc7273f5a --- /dev/null +++ b/tests/lean/docparse/nameAndArgs_0003.expected.out @@ -0,0 +1,12 @@ +Success! Final stack: + • `scheme + • [(Lean.Doc.Syntax.named_no_paren + `dialect + ":=" + (Lean.Doc.Syntax.arg_str + (str "\"chicken\""))) + (Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_num (num "43"))) + (Lean.Doc.Syntax.flag_on "+" `foo)] + +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/nameAndArgs_0004 b/tests/lean/docparse/nameAndArgs_0004 new file mode 100644 index 0000000000..e14f14d53c --- /dev/null +++ b/tests/lean/docparse/nameAndArgs_0004 @@ -0,0 +1 @@ +scheme dialect:="chicken" +43 99 \ No newline at end of file diff --git a/tests/lean/docparse/nameAndArgs_0004.expected.out b/tests/lean/docparse/nameAndArgs_0004.expected.out new file mode 100644 index 0000000000..84890dc63e --- /dev/null +++ b/tests/lean/docparse/nameAndArgs_0004.expected.out @@ -0,0 +1,13 @@ +Failure @29 (⟨1, 29⟩): expected token +Final stack: + • `scheme + • [(Lean.Doc.Syntax.named_no_paren + `dialect + ":=" + (Lean.Doc.Syntax.arg_str + (str "\"chicken\""))) + (Lean.Doc.Syntax.flag_on "+" ) + (Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_num (num "99")))] + +Remaining: " 99" \ No newline at end of file diff --git a/tests/lean/docparse/nameAndArgs_0005 b/tests/lean/docparse/nameAndArgs_0005 new file mode 100644 index 0000000000..f1f619282e --- /dev/null +++ b/tests/lean/docparse/nameAndArgs_0005 @@ -0,0 +1 @@ +scheme dialect:="chicken" + x 99 \ No newline at end of file diff --git a/tests/lean/docparse/nameAndArgs_0005.expected.out b/tests/lean/docparse/nameAndArgs_0005.expected.out new file mode 100644 index 0000000000..6ccf4f279b --- /dev/null +++ b/tests/lean/docparse/nameAndArgs_0005.expected.out @@ -0,0 +1,13 @@ +Failure @28 (⟨1, 28⟩): expected no space before +Final stack: + • `scheme + • [(Lean.Doc.Syntax.named_no_paren + `dialect + ":=" + (Lean.Doc.Syntax.arg_str + (str "\"chicken\""))) + (Lean.Doc.Syntax.flag_on "+" `x) + (Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_num (num "99")))] + +Remaining: "x 99" \ No newline at end of file diff --git a/tests/lean/docparse/nameAndArgs_0006 b/tests/lean/docparse/nameAndArgs_0006 new file mode 100644 index 0000000000..47faa6abd8 --- /dev/null +++ b/tests/lean/docparse/nameAndArgs_0006 @@ -0,0 +1,2 @@ +scheme dialect:="chicken" 43 +(foo) \ No newline at end of file diff --git a/tests/lean/docparse/nameAndArgs_0006.expected.out b/tests/lean/docparse/nameAndArgs_0006.expected.out new file mode 100644 index 0000000000..0fd9fbb323 --- /dev/null +++ b/tests/lean/docparse/nameAndArgs_0006.expected.out @@ -0,0 +1,12 @@ +Success! Final stack: + • `scheme + • [(Lean.Doc.Syntax.named_no_paren + `dialect + ":=" + (Lean.Doc.Syntax.arg_str + (str "\"chicken\""))) + (Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_num (num "43")))] + +Remaining: +"\n(foo)" \ No newline at end of file diff --git a/tests/lean/docparse/nameAndArgs_0007 b/tests/lean/docparse/nameAndArgs_0007 new file mode 100644 index 0000000000..fb8c24572d --- /dev/null +++ b/tests/lean/docparse/nameAndArgs_0007 @@ -0,0 +1 @@ +leanExample context \ No newline at end of file diff --git a/tests/lean/docparse/nameAndArgs_0007.expected.out b/tests/lean/docparse/nameAndArgs_0007.expected.out new file mode 100644 index 0000000000..5a0d8a8b30 --- /dev/null +++ b/tests/lean/docparse/nameAndArgs_0007.expected.out @@ -0,0 +1,6 @@ +Success! Final stack: + • `leanExample + • [(Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_ident `context))] + +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/nameAndArgs_0008 b/tests/lean/docparse/nameAndArgs_0008 new file mode 100644 index 0000000000..dd713f29fa --- /dev/null +++ b/tests/lean/docparse/nameAndArgs_0008 @@ -0,0 +1 @@ +leanExample context more \ No newline at end of file diff --git a/tests/lean/docparse/nameAndArgs_0008.expected.out b/tests/lean/docparse/nameAndArgs_0008.expected.out new file mode 100644 index 0000000000..541147b6cd --- /dev/null +++ b/tests/lean/docparse/nameAndArgs_0008.expected.out @@ -0,0 +1,8 @@ +Success! Final stack: + • `leanExample + • [(Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_ident `context)) + (Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_ident `more))] + +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/nameAndArgs_0009 b/tests/lean/docparse/nameAndArgs_0009 new file mode 100644 index 0000000000..1ddaeb97b3 --- /dev/null +++ b/tests/lean/docparse/nameAndArgs_0009 @@ -0,0 +1 @@ +leanExample context more:="stuff" \ No newline at end of file diff --git a/tests/lean/docparse/nameAndArgs_0009.expected.out b/tests/lean/docparse/nameAndArgs_0009.expected.out new file mode 100644 index 0000000000..9369dc2c4f --- /dev/null +++ b/tests/lean/docparse/nameAndArgs_0009.expected.out @@ -0,0 +1,11 @@ +Success! Final stack: + • `leanExample + • [(Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_ident `context)) + (Lean.Doc.Syntax.named_no_paren + `more + ":=" + (Lean.Doc.Syntax.arg_str + (str "\"stuff\"")))] + +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/nameAndArgs_0010 b/tests/lean/docparse/nameAndArgs_0010 new file mode 100644 index 0000000000..b455b56aca --- /dev/null +++ b/tests/lean/docparse/nameAndArgs_0010 @@ -0,0 +1,3 @@ +leanExample context more:="stuff" + +abc \ No newline at end of file diff --git a/tests/lean/docparse/nameAndArgs_0010.expected.out b/tests/lean/docparse/nameAndArgs_0010.expected.out new file mode 100644 index 0000000000..6241ef2513 --- /dev/null +++ b/tests/lean/docparse/nameAndArgs_0010.expected.out @@ -0,0 +1,12 @@ +Success! Final stack: + • `leanExample + • [(Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_ident `context)) + (Lean.Doc.Syntax.named_no_paren + `more + ":=" + (Lean.Doc.Syntax.arg_str (str "\"stuff\""))) + (Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_ident `abc))] + +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0001 b/tests/lean/docparse/oneInline_0001 new file mode 100644 index 0000000000..26c2351ecb --- /dev/null +++ b/tests/lean/docparse/oneInline_0001 @@ -0,0 +1 @@ +``foo bar`` \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0001.expected.out b/tests/lean/docparse/oneInline_0001.expected.out new file mode 100644 index 0000000000..6faadc05a7 --- /dev/null +++ b/tests/lean/docparse/oneInline_0001.expected.out @@ -0,0 +1,6 @@ +Success! Final stack: + (Lean.Doc.Syntax.code + "``" + (str "\"foo bar\"") + "``") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0002 b/tests/lean/docparse/oneInline_0002 new file mode 100644 index 0000000000..6f340fa8d5 --- /dev/null +++ b/tests/lean/docparse/oneInline_0002 @@ -0,0 +1 @@ +``foo `stuff` bar`` \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0002.expected.out b/tests/lean/docparse/oneInline_0002.expected.out new file mode 100644 index 0000000000..32c839c11b --- /dev/null +++ b/tests/lean/docparse/oneInline_0002.expected.out @@ -0,0 +1,6 @@ +Success! Final stack: + (Lean.Doc.Syntax.code + "``" + (str "\"foo `stuff` bar\"") + "``") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0003 b/tests/lean/docparse/oneInline_0003 new file mode 100644 index 0000000000..8814874d88 --- /dev/null +++ b/tests/lean/docparse/oneInline_0003 @@ -0,0 +1 @@ +`foo \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0003.expected.out b/tests/lean/docparse/oneInline_0003.expected.out new file mode 100644 index 0000000000..35297485f3 --- /dev/null +++ b/tests/lean/docparse/oneInline_0003.expected.out @@ -0,0 +1,7 @@ +Failure @4 (⟨1, 4⟩): expected '`' to close inline code +Final stack: + (Lean.Doc.Syntax.code + "`" + (str "\"foo\"") + ) +Remaining: "" \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0004 b/tests/lean/docparse/oneInline_0004 new file mode 100644 index 0000000000..87e9555ea8 --- /dev/null +++ b/tests/lean/docparse/oneInline_0004 @@ -0,0 +1 @@ +` foo` \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0004.expected.out b/tests/lean/docparse/oneInline_0004.expected.out new file mode 100644 index 0000000000..aff92ea2bb --- /dev/null +++ b/tests/lean/docparse/oneInline_0004.expected.out @@ -0,0 +1,3 @@ +Success! Final stack: + (Lean.Doc.Syntax.code "`" (str "\" foo\"") "`") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0005 b/tests/lean/docparse/oneInline_0005 new file mode 100644 index 0000000000..cfc7ed5fe5 --- /dev/null +++ b/tests/lean/docparse/oneInline_0005 @@ -0,0 +1 @@ +` foo ` \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0005.expected.out b/tests/lean/docparse/oneInline_0005.expected.out new file mode 100644 index 0000000000..2e0a05c543 --- /dev/null +++ b/tests/lean/docparse/oneInline_0005.expected.out @@ -0,0 +1,3 @@ +Success! Final stack: + (Lean.Doc.Syntax.code "`" (str "\"foo\"") "`") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0006 b/tests/lean/docparse/oneInline_0006 new file mode 100644 index 0000000000..018a5bfe12 --- /dev/null +++ b/tests/lean/docparse/oneInline_0006 @@ -0,0 +1,2 @@ +` fo +o ` \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0006.expected.out b/tests/lean/docparse/oneInline_0006.expected.out new file mode 100644 index 0000000000..cccc91e0a6 --- /dev/null +++ b/tests/lean/docparse/oneInline_0006.expected.out @@ -0,0 +1,6 @@ +Success! Final stack: + (Lean.Doc.Syntax.code + "`" + (str "\"fo\\no\"") + "`") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0007 b/tests/lean/docparse/oneInline_0007 new file mode 100644 index 0000000000..5a0c67cc41 --- /dev/null +++ b/tests/lean/docparse/oneInline_0007 @@ -0,0 +1,2 @@ +`` fo +o ` \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0007.expected.out b/tests/lean/docparse/oneInline_0007.expected.out new file mode 100644 index 0000000000..e8271e5410 --- /dev/null +++ b/tests/lean/docparse/oneInline_0007.expected.out @@ -0,0 +1,7 @@ +Failure @6 (⟨2, 0⟩): expected '``' to close inline code +Final stack: + (Lean.Doc.Syntax.code + "``" + (str "\" fo\"") + ) +Remaining: "o `" \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0008 b/tests/lean/docparse/oneInline_0008 new file mode 100644 index 0000000000..d52eda6546 --- /dev/null +++ b/tests/lean/docparse/oneInline_0008 @@ -0,0 +1,2 @@ +**aa +bb** \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0008.expected.out b/tests/lean/docparse/oneInline_0008.expected.out new file mode 100644 index 0000000000..3c2384895f --- /dev/null +++ b/tests/lean/docparse/oneInline_0008.expected.out @@ -0,0 +1,10 @@ +Success! Final stack: + (Lean.Doc.Syntax.bold + "**" + [(Lean.Doc.Syntax.text (str "\"aa\"")) + (Lean.Doc.Syntax.linebreak + "line!" + (str "\"\\n\"")) + (Lean.Doc.Syntax.text (str "\"bb\""))] + "**") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0009 b/tests/lean/docparse/oneInline_0009 new file mode 100644 index 0000000000..8415737ae7 --- /dev/null +++ b/tests/lean/docparse/oneInline_0009 @@ -0,0 +1 @@ +a * b \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0009.expected.out b/tests/lean/docparse/oneInline_0009.expected.out new file mode 100644 index 0000000000..ff7a12a235 --- /dev/null +++ b/tests/lean/docparse/oneInline_0009.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + (Lean.Doc.Syntax.text (str "\"a \"")) +Remaining: +"* b" \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0010 b/tests/lean/docparse/oneInline_0010 new file mode 100644 index 0000000000..35ac928905 --- /dev/null +++ b/tests/lean/docparse/oneInline_0010 @@ -0,0 +1 @@ +[Wikipedia](https://en.wikipedia.org) \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0010.expected.out b/tests/lean/docparse/oneInline_0010.expected.out new file mode 100644 index 0000000000..6633c3fb0d --- /dev/null +++ b/tests/lean/docparse/oneInline_0010.expected.out @@ -0,0 +1,10 @@ +Success! Final stack: + (Lean.Doc.Syntax.link + "[" + [(Lean.Doc.Syntax.text (str "\"Wikipedia\""))] + "]" + (Lean.Doc.Syntax.url + "(" + (str "\"https://en.wikipedia.org\"") + ")")) +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0011 b/tests/lean/docparse/oneInline_0011 new file mode 100644 index 0000000000..67f7512622 --- /dev/null +++ b/tests/lean/docparse/oneInline_0011 @@ -0,0 +1 @@ +![](logo.png) \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0011.expected.out b/tests/lean/docparse/oneInline_0011.expected.out new file mode 100644 index 0000000000..ee49cae647 --- /dev/null +++ b/tests/lean/docparse/oneInline_0011.expected.out @@ -0,0 +1,10 @@ +Success! Final stack: + (Lean.Doc.Syntax.image + "![" + (str "\"\"") + "]" + (Lean.Doc.Syntax.url + "(" + (str "\"logo.png\"") + ")")) +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0012 b/tests/lean/docparse/oneInline_0012 new file mode 100644 index 0000000000..5e8cc6b464 --- /dev/null +++ b/tests/lean/docparse/oneInline_0012 @@ -0,0 +1,2 @@ +![](logo.png +abc \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0012.expected.out b/tests/lean/docparse/oneInline_0012.expected.out new file mode 100644 index 0000000000..eb0441996f --- /dev/null +++ b/tests/lean/docparse/oneInline_0012.expected.out @@ -0,0 +1,11 @@ +Failure @12 (⟨1, 12⟩): expected ')' +Final stack: + (Lean.Doc.Syntax.image + "![" + (str "\"\"") + "]" + (Lean.Doc.Syntax.url + "(" + (str "\"logo.png\"") + )) +Remaining: "\nabc" \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0013 b/tests/lean/docparse/oneInline_0013 new file mode 100644 index 0000000000..018259b0fc --- /dev/null +++ b/tests/lean/docparse/oneInline_0013 @@ -0,0 +1,2 @@ +![abc +123 \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0013.expected.out b/tests/lean/docparse/oneInline_0013.expected.out new file mode 100644 index 0000000000..419d9fa04f --- /dev/null +++ b/tests/lean/docparse/oneInline_0013.expected.out @@ -0,0 +1,7 @@ +Failure @5 (⟨1, 5⟩): expected ']' +Final stack: + (Lean.Doc.Syntax.image + "![" + (str "\"abc\"") + ) +Remaining: "\n123" \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0014 b/tests/lean/docparse/oneInline_0014 new file mode 100644 index 0000000000..9e92e39d84 --- /dev/null +++ b/tests/lean/docparse/oneInline_0014 @@ -0,0 +1 @@ +![alt text is good](logo.png) \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0014.expected.out b/tests/lean/docparse/oneInline_0014.expected.out new file mode 100644 index 0000000000..5e7d1406e3 --- /dev/null +++ b/tests/lean/docparse/oneInline_0014.expected.out @@ -0,0 +1,10 @@ +Success! Final stack: + (Lean.Doc.Syntax.image + "![" + (str "\"alt text is good\"") + "]" + (Lean.Doc.Syntax.url + "(" + (str "\"logo.png\"") + ")")) +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0015 b/tests/lean/docparse/oneInline_0015 new file mode 100644 index 0000000000..e4b9149371 --- /dev/null +++ b/tests/lean/docparse/oneInline_0015 @@ -0,0 +1 @@ +![alt text is good]](logo.png) \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0015.expected.out b/tests/lean/docparse/oneInline_0015.expected.out new file mode 100644 index 0000000000..f30b4eae19 --- /dev/null +++ b/tests/lean/docparse/oneInline_0015.expected.out @@ -0,0 +1,8 @@ +Failure @19 (⟨1, 19⟩): expected '(' or '[' +Final stack: + (Lean.Doc.Syntax.image + "![" + (str "\"alt text is good\"") + "]" + (Lean.Doc.Syntax.url )) +Remaining: "](logo.png)" \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0016 b/tests/lean/docparse/oneInline_0016 new file mode 100644 index 0000000000..bea07b734c --- /dev/null +++ b/tests/lean/docparse/oneInline_0016 @@ -0,0 +1 @@ +[^1] \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0016.expected.out b/tests/lean/docparse/oneInline_0016.expected.out new file mode 100644 index 0000000000..876a08e91f --- /dev/null +++ b/tests/lean/docparse/oneInline_0016.expected.out @@ -0,0 +1,6 @@ +Success! Final stack: + (Lean.Doc.Syntax.footnote + "[^" + (str "\"1\"") + "]") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0017 b/tests/lean/docparse/oneInline_0017 new file mode 100644 index 0000000000..ce2f47e824 --- /dev/null +++ b/tests/lean/docparse/oneInline_0017 @@ -0,0 +1 @@ +$`\frac{x}{4}` \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0017.expected.out b/tests/lean/docparse/oneInline_0017.expected.out new file mode 100644 index 0000000000..5c46fd5dde --- /dev/null +++ b/tests/lean/docparse/oneInline_0017.expected.out @@ -0,0 +1,8 @@ +Success! Final stack: + (Lean.Doc.Syntax.inline_math + "$" + (Lean.Doc.Syntax.code + "`" + (str "\"\\\\frac{x}{4}\"") + "`")) +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0018 b/tests/lean/docparse/oneInline_0018 new file mode 100644 index 0000000000..564e267b2b --- /dev/null +++ b/tests/lean/docparse/oneInline_0018 @@ -0,0 +1,6 @@ +$`\frac{ + x +}{ + 4 +} +` \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0018.expected.out b/tests/lean/docparse/oneInline_0018.expected.out new file mode 100644 index 0000000000..63cae14fcc --- /dev/null +++ b/tests/lean/docparse/oneInline_0018.expected.out @@ -0,0 +1,8 @@ +Success! Final stack: + (Lean.Doc.Syntax.inline_math + "$" + (Lean.Doc.Syntax.code + "`" + (str "\"\\\\frac{\\n x\\n}{\\n 4\\n}\\n\"") + "`")) +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0019 b/tests/lean/docparse/oneInline_0019 new file mode 100644 index 0000000000..05ccf75c7d --- /dev/null +++ b/tests/lean/docparse/oneInline_0019 @@ -0,0 +1 @@ +$$`\frac{x}{4}` \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0019.expected.out b/tests/lean/docparse/oneInline_0019.expected.out new file mode 100644 index 0000000000..cc8fe55b0e --- /dev/null +++ b/tests/lean/docparse/oneInline_0019.expected.out @@ -0,0 +1,8 @@ +Success! Final stack: + (Lean.Doc.Syntax.display_math + "$$" + (Lean.Doc.Syntax.code + "`" + (str "\"\\\\frac{x}{4}\"") + "`")) +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0020 b/tests/lean/docparse/oneInline_0020 new file mode 100644 index 0000000000..ade1569bb5 --- /dev/null +++ b/tests/lean/docparse/oneInline_0020 @@ -0,0 +1 @@ +$35.23 \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0020.expected.out b/tests/lean/docparse/oneInline_0020.expected.out new file mode 100644 index 0000000000..83045d754c --- /dev/null +++ b/tests/lean/docparse/oneInline_0020.expected.out @@ -0,0 +1,3 @@ +Success! Final stack: + (Lean.Doc.Syntax.text (str "\"$35.23\"")) +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0021 b/tests/lean/docparse/oneInline_0021 new file mode 100644 index 0000000000..a4ac796274 --- /dev/null +++ b/tests/lean/docparse/oneInline_0021 @@ -0,0 +1 @@ +\$`code` \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0021.expected.out b/tests/lean/docparse/oneInline_0021.expected.out new file mode 100644 index 0000000000..45d204a4a2 --- /dev/null +++ b/tests/lean/docparse/oneInline_0021.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + (Lean.Doc.Syntax.text (str "\"$\"")) +Remaining: +"`code`" \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0022 b/tests/lean/docparse/oneInline_0022 new file mode 100644 index 0000000000..7de3ef4a72 --- /dev/null +++ b/tests/lean/docparse/oneInline_0022 @@ -0,0 +1 @@ +$$$ \ No newline at end of file diff --git a/tests/lean/docparse/oneInline_0022.expected.out b/tests/lean/docparse/oneInline_0022.expected.out new file mode 100644 index 0000000000..f92e87787b --- /dev/null +++ b/tests/lean/docparse/oneInline_0022.expected.out @@ -0,0 +1,3 @@ +Success! Final stack: + (Lean.Doc.Syntax.text (str "\"$$$\"")) +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/recoverBlock_0001 b/tests/lean/docparse/recoverBlock_0001 new file mode 100644 index 0000000000..14718cb343 --- /dev/null +++ b/tests/lean/docparse/recoverBlock_0001 @@ -0,0 +1,3 @@ +{tactic}`rw`{lit}` [t] `{kw}`at`{lit} h` applies th + +test \ No newline at end of file diff --git a/tests/lean/docparse/recoverBlock_0001.expected.out b/tests/lean/docparse/recoverBlock_0001.expected.out new file mode 100644 index 0000000000..c92769b4ef --- /dev/null +++ b/tests/lean/docparse/recoverBlock_0001.expected.out @@ -0,0 +1,46 @@ +Failure @55 (⟨3, 0⟩): '{'; expected '![', '$$', '$', '[' or '[^' +Final stack: + (Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.role + "{" + `tactic + [] + "}" + "[" + [(Lean.Doc.Syntax.code + "`" + (str "\"rw\"") + "`")] + "]") + (Lean.Doc.Syntax.role + "{" + `lit + [] + "}" + "[" + [(Lean.Doc.Syntax.code + "`" + (str "\" [t] \"") + "`")] + "]") + (Lean.Doc.Syntax.role + "{" + `kw + [] + "}" + "[" + [(Lean.Doc.Syntax.code + "`" + (str "\"at\"") + "`")] + "]") + (Lean.Doc.Syntax.role + "{" + `lit + [] + "}" + "[" + [(Lean.Doc.Syntax.footnote )] + "]")]) +Remaining: "test" \ No newline at end of file diff --git a/tests/lean/docparse/recoverBlocks_0001 b/tests/lean/docparse/recoverBlocks_0001 new file mode 100644 index 0000000000..96a14844ce --- /dev/null +++ b/tests/lean/docparse/recoverBlocks_0001 @@ -0,0 +1,11 @@ +: {ref defs}[Basic definitions] + + The def is something + +: {ref large}{Sufficiently large} + + More text + +: `foo` + + Thing diff --git a/tests/lean/docparse/recoverBlocks_0001.expected.out b/tests/lean/docparse/recoverBlocks_0001.expected.out new file mode 100644 index 0000000000..e2c00cb0db --- /dev/null +++ b/tests/lean/docparse/recoverBlocks_0001.expected.out @@ -0,0 +1,44 @@ +Failure @92 (⟨7, 0⟩): '{'; expected '![', '$$', '$', '[' or '[^' +Final stack: + [(Lean.Doc.Syntax.dl + "dl{" + [(Lean.Doc.Syntax.desc + ":" + [(Lean.Doc.Syntax.text (str "\" \"")) + (Lean.Doc.Syntax.role + "{" + `ref + [(Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_ident `defs))] + "}" + "[" + [(Lean.Doc.Syntax.text + (str "\"Basic definitions\""))] + "]")] + "=>" + [(Lean.Doc.Syntax.para + "para{" + [(Lean.Doc.Syntax.text + (str "\"The def is something\""))] + "}")]) + (Lean.Doc.Syntax.desc + ":" + [(Lean.Doc.Syntax.text (str "\" \"")) + (Lean.Doc.Syntax.role + "{" + `ref + [(Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_ident `large))] + "}" + "[" + [(Lean.Doc.Syntax.role + "{" + `Sufficiently + [(Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_ident `large))] + "}" + "[" + [(Lean.Doc.Syntax.footnote )] + "]")] + "]")])])] +Remaining: " More text\n\n: `foo`\n\n Thing\n" \ No newline at end of file diff --git a/tests/lean/docparse/role_0001 b/tests/lean/docparse/role_0001 new file mode 100644 index 0000000000..b5657de551 --- /dev/null +++ b/tests/lean/docparse/role_0001 @@ -0,0 +1 @@ +{hello}*there* \ No newline at end of file diff --git a/tests/lean/docparse/role_0001.expected.out b/tests/lean/docparse/role_0001.expected.out new file mode 100644 index 0000000000..c1dba89e86 --- /dev/null +++ b/tests/lean/docparse/role_0001.expected.out @@ -0,0 +1,13 @@ +Success! Final stack: + (Lean.Doc.Syntax.role + "{" + `hello + [] + "}" + "[" + [(Lean.Doc.Syntax.bold + "*" + [(Lean.Doc.Syntax.text (str "\"there\""))] + "*")] + "]") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/role_0002 b/tests/lean/docparse/role_0002 new file mode 100644 index 0000000000..82d9b0c5ae --- /dev/null +++ b/tests/lean/docparse/role_0002 @@ -0,0 +1 @@ +{hello}[there] \ No newline at end of file diff --git a/tests/lean/docparse/role_0002.expected.out b/tests/lean/docparse/role_0002.expected.out new file mode 100644 index 0000000000..f6f692160d --- /dev/null +++ b/tests/lean/docparse/role_0002.expected.out @@ -0,0 +1,10 @@ +Success! Final stack: + (Lean.Doc.Syntax.role + "{" + `hello + [] + "}" + "[" + [(Lean.Doc.Syntax.text (str "\"there\""))] + "]") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/role_0003 b/tests/lean/docparse/role_0003 new file mode 100644 index 0000000000..f9a43ff305 --- /dev/null +++ b/tests/lean/docparse/role_0003 @@ -0,0 +1 @@ +{ref other}[{leanKw}`cmd` is great] \ No newline at end of file diff --git a/tests/lean/docparse/role_0003.expected.out b/tests/lean/docparse/role_0003.expected.out new file mode 100644 index 0000000000..83c54ca255 --- /dev/null +++ b/tests/lean/docparse/role_0003.expected.out @@ -0,0 +1,22 @@ +Success! Final stack: + (Lean.Doc.Syntax.role + "{" + `ref + [(Lean.Doc.Syntax.anon + (Lean.Doc.Syntax.arg_ident `other))] + "}" + "[" + [(Lean.Doc.Syntax.role + "{" + `leanKw + [] + "}" + "[" + [(Lean.Doc.Syntax.code + "`" + (str "\"cmd\"") + "`")] + "]") + (Lean.Doc.Syntax.text (str "\" is great\""))] + "]") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/role_0004 b/tests/lean/docparse/role_0004 new file mode 100644 index 0000000000..f0aed6081e --- /dev/null +++ b/tests/lean/docparse/role_0004 @@ -0,0 +1 @@ +{hello world:=gaia}[there] \ No newline at end of file diff --git a/tests/lean/docparse/role_0004.expected.out b/tests/lean/docparse/role_0004.expected.out new file mode 100644 index 0000000000..759bf63896 --- /dev/null +++ b/tests/lean/docparse/role_0004.expected.out @@ -0,0 +1,13 @@ +Success! Final stack: + (Lean.Doc.Syntax.role + "{" + `hello + [(Lean.Doc.Syntax.named_no_paren + `world + ":=" + (Lean.Doc.Syntax.arg_ident `gaia))] + "}" + "[" + [(Lean.Doc.Syntax.text (str "\"there\""))] + "]") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/role_0005 b/tests/lean/docparse/role_0005 new file mode 100644 index 0000000000..f7a96975a1 --- /dev/null +++ b/tests/lean/docparse/role_0005 @@ -0,0 +1 @@ +{hello world:=gaia}[there *is* _a meaning!_ ] \ No newline at end of file diff --git a/tests/lean/docparse/role_0005.expected.out b/tests/lean/docparse/role_0005.expected.out new file mode 100644 index 0000000000..68f43c0095 --- /dev/null +++ b/tests/lean/docparse/role_0005.expected.out @@ -0,0 +1,24 @@ +Success! Final stack: + (Lean.Doc.Syntax.role + "{" + `hello + [(Lean.Doc.Syntax.named_no_paren + `world + ":=" + (Lean.Doc.Syntax.arg_ident `gaia))] + "}" + "[" + [(Lean.Doc.Syntax.text (str "\"there \"")) + (Lean.Doc.Syntax.bold + "*" + [(Lean.Doc.Syntax.text (str "\"is\""))] + "*") + (Lean.Doc.Syntax.text (str "\" \"")) + (Lean.Doc.Syntax.emph + "_" + [(Lean.Doc.Syntax.text + (str "\"a meaning!\""))] + "_") + (Lean.Doc.Syntax.text (str "\" \""))] + "]") +All input consumed. \ No newline at end of file diff --git a/tests/lean/docparse/run.lean b/tests/lean/docparse/run.lean new file mode 100644 index 0000000000..8dd7cc974e --- /dev/null +++ b/tests/lean/docparse/run.lean @@ -0,0 +1,116 @@ +/- +Copyright (c) 2025 Lean FRO. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. + +Author: David Thrane Christiansen +-/ +import Lean.DocString.Parser + +/-! +This file tests the Verso parser. + +Input files are expected to be snippets of code; their filename picks which parser is used. +-/ + +open Lean Doc Parser + +def ppSyntax (stx : Syntax) : Std.Format := .nest 2 <| stx.formatStx (some 50) false + +open Std Format in +def ppStack (elts : Array Syntax) (number : Bool := false) : Format := Id.run do + let mut stk : Format := .nil + if h : elts.size = 0 then + stk := " empty" + else if elts.size = 1 then + stk := " " ++ ppSyntax elts[0] + else + for h : i in [0:elts.size] do + let tm := ppSyntax (elts[i]) + let num := if number then .text s!"[{i}] " else .nil + stk := stk ++ .group (" • " ++ num ++ nest 2 (.group tm)) ++ line + pure stk + +def test (p : ParserFn) (input : String) : IO String := do + let ictx := mkInputContext input "" + let env : Environment ← mkEmptyEnvironment + let pmctx : ParserModuleContext := {env := env, options := {}} + let s' := p.run ictx pmctx (getTokenTable env) (mkParserState input) + let stk := ppStack <| s'.stxStack.extract 0 s'.stxStack.size + + let remaining : String := + if s'.pos ≥ input.endPos then "All input consumed." + else s!"Remaining:\n{repr (input.extract s'.pos input.endPos)}" + + if s'.allErrors.isEmpty then + return s!"Success! Final stack:\n{stk.pretty 50}\n{remaining}" + else if let #[(p, _, err)] := s'.allErrors then + return s!"Failure @{p} ({ictx.fileMap.toPosition p}): {toString err}\n\ + Final stack:\n\ + {stk.pretty 50}\n\ + Remaining: {repr $ input.extract p input.endPos}" + else + let mut errors := "" + for (p, _, e) in s'.allErrors.qsort errLt do + errors := + errors ++ + s!" @{p} ({ictx.fileMap.toPosition p}): {toString e}\n" ++ + s!" {repr <| input.extract p input.endPos}\n" + return s!"{s'.allErrors.size} failures:\n{errors}\nFinal stack:\n{stk.pretty 50}" +where + errLt (x y : String.Pos × SyntaxStack × Error) : Bool := + let (p1, _, e1) := x + let (p2, _, e2) := y + p1 < p2 || p1 == p2 && toString e1 < toString e2 + +/-- +The test case's filename determines which parser will be used to test it. +-/ +def testConfigs : List (String × ParserFn):= [ + ("metadataBlock", metadataBlock), + ("arg_val", val), + ("arg", arg), + ("args", args), + ("nameAndArgs", nameAndArgs), + ("inlineTextChar", inlineTextChar), + ("manyInlineTextChar", (asStringFn (many1Fn inlineTextChar))), + ("text", text), + ("emph", (emph {})), + ("code", code), + ("role", (role {})), + ("oneInline", (inline {})), + ("codeBlock", (codeBlock {})), + ("header", (header {})), + ("blocks", (blocks {})), + ("recoverBlock", (recoverBlock (block {}))), + ("recoverBlocks", (recoverBlock (blocks {}))), + ("directive", (directive {})), + ("blockOpener", (ignoreFn blockOpener)), + ("lookaheadUnorderedListIndicator", + lookaheadUnorderedListIndicator {} (fun type => fakeAtom s! "{repr type}")), + ("lookaheadOrderedListIndicator", + lookaheadOrderedListIndicator {} (fun type i => fakeAtom s! "{repr type} {i}")), + ("block", (block {})), + ("document", document), +] + +def main : List String → IO UInt32 + | [inputFile] => do + let inputFile : System.FilePath := inputFile + if inputFile.isAbsolute then + IO.eprintln s!"Expected a relative path, got {inputFile}" + return 2 + unless (← inputFile.pathExists) do + IO.eprintln s!"File not found: {inputFile}" + return 3 + let [file] := inputFile.components + | IO.eprintln "Expected file in current directory" + return 4 + let kind := file.takeWhile (· != '_') + let some p := testConfigs.lookup kind + | IO.eprintln s!"Not found in test configs: {kind}" + return 5 + IO.print <| ← test p (← IO.FS.readFile inputFile) + return 0 + | args => do + IO.eprintln s!"Expected precisely one argument, got {args}" + return 1 diff --git a/tests/lean/docparse/test_single.sh b/tests/lean/docparse/test_single.sh new file mode 100755 index 0000000000..66355a303a --- /dev/null +++ b/tests/lean/docparse/test_single.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +source ../../common.sh + +# IO.Process.exit (used by the file worker) seems to be incompatible with LSAN +# TODO: investigate or work around +export ASAN_OPTIONS=detect_leaks=0 + +exec_capture lean -Dlinter.all=false --run run.lean "$f" +diff_produced diff --git a/tests/lean/docparse/text_0001 b/tests/lean/docparse/text_0001 new file mode 100644 index 0000000000..214d3fb0c1 --- /dev/null +++ b/tests/lean/docparse/text_0001 @@ -0,0 +1 @@ + [\[link\]](https://link.com) \ No newline at end of file diff --git a/tests/lean/docparse/text_0001.expected.out b/tests/lean/docparse/text_0001.expected.out new file mode 100644 index 0000000000..d5fd8568d9 --- /dev/null +++ b/tests/lean/docparse/text_0001.expected.out @@ -0,0 +1,4 @@ +Success! Final stack: + (Lean.Doc.Syntax.text (str "\" \"")) +Remaining: +"[\\[link\\]](https://link.com)" \ No newline at end of file diff --git a/tests/lean/interactive/hover.lean.expected.out b/tests/lean/interactive/hover.lean.expected.out index dd62516d2f..c23b82bd6e 100644 --- a/tests/lean/interactive/hover.lean.expected.out +++ b/tests/lean/interactive/hover.lean.expected.out @@ -112,7 +112,8 @@ {"range": {"start": {"line": 73, "character": 18}, "end": {"line": 73, "character": 25}}, "contents": - {"value": "```lean\nmyInfix : Lean.TrailingParserDescr\n```", + {"value": + "```lean\nmyInfix : Lean.TrailingParserDescr\n```\n***\nAddition of natural numbers, typically used via the `+` operator.\n\nThis function is overridden in both the kernel and the compiler to efficiently evaluate using the\narbitrary-precision arithmetic library. The definition provided here is the logical model.\n", "kind": "markdown"}} {"textDocument": {"uri": "file:///hover.lean"}, "position": {"line": 73, "character": 39}} diff --git a/tests/lean/moduleOf.lean.expected.out b/tests/lean/moduleOf.lean.expected.out index f7bb16eec0..0583876e36 100644 --- a/tests/lean/moduleOf.lean.expected.out +++ b/tests/lean/moduleOf.lean.expected.out @@ -1,6 +1,6 @@ (some Init.Prelude) (some Lean.CoreM) -(some Lean.Elab.Term) +(some Lean.Elab.Term.TermElabM) (some Std.Data.HashMap.Basic) none none diff --git a/tests/lean/run/3497.lean b/tests/lean/run/3497.lean index 9dae8d1933..5bdf8ddd7b 100644 --- a/tests/lean/run/3497.lean +++ b/tests/lean/run/3497.lean @@ -1,5 +1,9 @@ import Lean -/-- error: invalid doc string, declaration `Nat.mul` is in an imported module -/ +open Lean Elab Term + +/-- +error: invalid doc string, declaration `Nat.mul` is in an imported module +-/ #guard_msgs (error) in -#eval show Lean.MetaM Unit from do Lean.addDocString `Nat.mul (← `(docComment| /-- oh no -/)) +#eval show TermElabM Unit from do addDocString `Nat.mul mkNullNode (← `(docComment| /-- oh no -/)) diff --git a/tests/lean/run/info_trees.lean b/tests/lean/run/info_trees.lean index 262c03a0e0..f3621cd5d2 100644 --- a/tests/lean/run/info_trees.lean +++ b/tests/lean/run/info_trees.lean @@ -21,7 +21,6 @@ info: • [Command] @ ⟨77, 0⟩-⟨77, 40⟩ @ Lean.Elab.Command.elabDeclarati • [Completion-Id] n : none @ ⟨77, 26⟩-⟨77, 27⟩ • [Term] n : Nat @ ⟨77, 26⟩-⟨77, 27⟩ • [CustomInfo(Lean.Elab.Term.AsyncBodyInfo)] - • [Term] t (isBinder := true) : ∀ (n : Nat), 0 ≤ n @ ⟨77, 8⟩-⟨77, 9⟩ • [Term] n (isBinder := true) : Nat @ ⟨77, 11⟩-⟨77, 12⟩ • [CustomInfo(Lean.Elab.Term.BodyInfo)] • [Tactic] @ ⟨77, 31⟩-⟨77, 40⟩ @@ -68,6 +67,7 @@ info: • [Command] @ ⟨77, 0⟩-⟨77, 40⟩ @ Lean.Elab.Command.elabDeclarati • [Term] n : Nat @ ⟨1, 5⟩†-⟨1, 5⟩† • [CustomInfo(Lean.Meta.Tactic.TryThis.TryThisInfo)] • [Term] t (isBinder := true) : ∀ (n : Nat), 0 ≤ n @ ⟨77, 8⟩-⟨77, 9⟩ + • [Term] t (isBinder := true) : ∀ (n : Nat), 0 ≤ n @ ⟨77, 8⟩-⟨77, 9⟩ --- info: Try this: exact Nat.zero_le n diff --git a/tests/lean/run/levenshtein.lean b/tests/lean/run/levenshtein.lean new file mode 100644 index 0000000000..bda6806875 --- /dev/null +++ b/tests/lean/run/levenshtein.lean @@ -0,0 +1,260 @@ +import Lean.Data.EditDistance + +open Lean.EditDistance + +/-! +Tests the implementation of Levenshtein distances by constructing a number of strings with known +edit distances (or known bounds), and comparing the results. +-/ + +def strings := #["", "a", "aa", "ab", "supercalifragilisticexpialidocious", "𝒫(𝒜)"] + +/-! +# Infrastructure +-/ + +structure Stats where + passed : Nat := 0 + failed : Array (String × String × Nat × Option Nat) := #[] + +def report : StateT Stats IO Unit := do + if (← get).failed.isEmpty then + IO.println s!"All {(← get).passed} tests passed" + else + IO.println s!"While {(← get).passed} tests passed, {(← get).failed.size} failed:" + for (str, del, expected, actual) in (← get).failed do + IO.println s!" • {str.quote} and {del.quote}: expected {expected}, got {actual}" + +/-! +# Testing Individual Operations + +These tests check whether individual operations yield the expected result. +-/ + +def deletions (n : Nat) (s : String) : Array String := + match n with + | 0 => #[s] + | n' + 1 => Id.run do + let mut out := #[] + for s' in deletions n' s do + if s'.isEmpty then break + for i in [0:s'.length] do + let d := s'.take i ++ s'.drop (i + 1) + if !out.contains d then out := out.push d + return out.reverse + +-- Quick check to make sure that the modifications are as expected + +/-- info: #["abc", "abd", "acd", "bcd"] -/ +#guard_msgs in +#eval deletions 1 "abcd" + +/-- info: #["cd", "ad", "bd", "ab", "ac", "bc"] -/ +#guard_msgs in +#eval deletions 2 "abcd" + +/-- info: #["b", "a", "c", "d"] -/ +#guard_msgs in +#eval deletions 3 "abcd" + +/-- info: #[""] -/ +#guard_msgs in +#eval deletions 4 "abcd" + +/-- info: #["aaaa"] -/ +#guard_msgs in +#eval deletions 1 "aaaaa" + + +def testDeletions (s : String) : StateT Stats IO Unit := do + for i in [0:min s.length 4] do -- This generates O(2^n) tests, so a limit is needed + let dels := deletions i s + for del in dels do + if let some d := levenshtein s del s.length then + if d != i then + modify fun st => { st with failed := st.failed.push (s, del, i, some d) } + else + modify fun st => { st with passed := st.passed + 1 } + else + modify fun st => { st with failed := st.failed.push (s, del, i, none) } + +/-- info: All 6566 tests passed -/ +#guard_msgs in +#eval show IO Unit from do + (strings.forM testDeletions *> report).run {} <&> (·.1) + +def insertions (toInsert : String) (s : String) : Array String := Id.run do + let mut out := #[s] + let mut iter := toInsert.iter + while h : iter.hasNext do + let c := iter.curr' h + iter := iter.next' h + let mut next := #[] + for s' in out do + for i in [0:s'.length + 1] do + next := next.push ((s'.take i).push c ++ s'.drop i) + out := next + return out + +/-- +info: #["baxyz", "abxyz", "axbyz", "axybz", "axyzb", "bxayz", "xbayz", "xabyz", "xaybz", "xayzb", "bxyaz", "xbyaz", "xybaz", + "xyabz", "xyazb", "bxyza", "xbyza", "xybza", "xyzba", "xyzab"] +-/ +#guard_msgs in +#eval insertions "ab" "xyz" + +def testInsertions (s : String) : StateT Stats IO Unit := do + for i in #["", "X", "ab", "•𝒜▼"] do + let inss := insertions i s + for ins in inss do + if let some d := levenshtein s ins (s.length + i.length) then + if d != i.length then + modify fun st => { st with failed := st.failed.push (s, ins, i.length, some d) } + else + modify fun st => { st with passed := st.passed + 1 } + else + modify fun st => { st with failed := st.failed.push (s, ins, i.length, none) } + +/-- info: All 48357 tests passed -/ +#guard_msgs in +#eval show IO Unit from do + (strings.forM testInsertions *> report).run {} <&> (·.1) + +def substs (toSubst : String) (s : String) : Array String := Id.run do + let mut out := #[s] + let mut iter := toSubst.iter + while h : iter.hasNext do + let c := iter.curr' h + iter := iter.next' h + let mut next := #[] + for s' in out do + let mut iter2 := s'.iter + while h2 : iter2.hasNext do + let c2 := iter2.curr' h2 + let i := iter2.i + iter2 := iter2.next' h2 + if c ≠ c2 then + next := next.push <| s'.set i c + out := next + return out + +/-- info: #[] -/ +#guard_msgs in +#eval substs "X" "" + +/-- info: #["Xbc", "aXc", "abX"] -/ +#guard_msgs in +#eval substs "X" "abc" + +/-- info: #["Ybc", "XYc", "XbY", "YXc", "aYc", "aXY", "YbX", "aYX", "abY"] -/ +#guard_msgs in +#eval substs "XY" "abc" + +def testSubsts (s : String) : StateT Stats IO Unit := do + for i in #["", "X", "ab", "•𝒜▼"] do + let toCheck := substs i s + for modified in toCheck do + if let some d := levenshtein s modified s.length then + if d > i.length then + modify fun st => { st with failed := st.failed.push (s, modified, i.length, some d) } + else + modify fun st => { st with passed := st.passed + 1 } + else + modify fun st => { st with failed := st.failed.push (s, modified, i.length, none) } + +/-- info: #["ayz", "xaz", "xya"] -/ +#guard_msgs in +#eval substs "a" "xyz" + +/-- info: #["byz", "abz", "ayb", "baz", "xbz", "xab", "bya", "xba", "xyb"] -/ +#guard_msgs in +#eval substs "ab" "xyz" + +/-- info: All 40494 tests passed -/ +#guard_msgs in +#eval show IO Unit from do + (strings.forM testSubsts *> report).run {} <&> (·.1) + +/-! +# Testing Sequenced Operations + +These tests check whether sequences of operations yield the expected results. +-/ + +inductive Spec where + | ins (toInsert : String) + | del (howMany : Nat) + | subst (toSubst : String) + +def Spec.maxDistance : Spec → Nat + | .ins toInsert => toInsert.length + | .del howMany => howMany + | .subst toSubst => toSubst.length + +def maxDistance (spec : List Spec) : Nat := spec.map (·.maxDistance) |>.sum + +def Spec.apply : Spec → String → Array String + | .ins toInsert, s => insertions toInsert s + | .del howMany, s => deletions howMany s + | .subst toSubst, s => substs toSubst s + +def applySpec (spec : List Spec) (s : String) : Array String := + spec.foldl (init := #[s]) fun ss spec' => + ss.flatMap (spec'.apply) + +def specs : List (List Spec) := + [[], [.ins "ab", .del 1], [.subst "a", .del 2]] + +def testSpec (spec : List Spec) (s : String) : StateT Stats IO Unit := do + for modified in applySpec spec s do + let max := maxDistance spec + if let some d := levenshtein s modified max then + if d > max then + modify fun st => { st with failed := st.failed.push (s, modified, max, some d) } + else + modify fun st => { st with passed := st.passed + 1 } + else + modify fun st => { st with failed := st.failed.push (s, modified, max, none) } + +/-- info: All 2610 tests passed -/ +#guard_msgs in +#eval show IO Unit from do + Prod.fst <$> StateT.run (s := {}) + (((#["hello", "abcdefg", "abcdedcba", "𝒫(𝒜)"]).forM fun str => + specs.forM (testSpec · str)) *> report) + +/-! +# Comparison Against Reference Implementation + +This section compares against a slow-but-clear implementation with some chosen examples. +-/ + +/-- Naïve Levenshtein distance -/ +def slow : (s1 s2 : List Char) → Nat + | [], ys => ys.length + | xs, [] => xs.length + | (x :: xs), (y :: ys) => + if x = y then slow xs ys + else 1 + min (min (slow xs (y :: ys)) (slow (x :: xs) ys)) (slow xs ys) + +def tests := [ + ("kitten", "sitting"), ("Lean", "L∃∀N"), ("abc", "xyz"), ("", "ABC "), ("hello", "quake"), + ("", ""), ("aaaaaaa", "aaaaa"), ("aba", "aa"), ("aba", "ab"), ("abc", "ab"), ("abc", "zbc"), + ("abcde", "abcdz"), ("abcde", "abXde") + ] + +def testPairs : StateT Stats IO Unit := do + for (s1, s2) in tests do + let expected := slow s1.toList s2.toList + if let some d := levenshtein s1 s2 (s1.length + s2.length) then + if d ≠ expected then + modify fun st => { st with failed := st.failed.push (s1, s2, expected, some d) } + else + modify fun st => { st with passed := st.passed + 1 } + else + modify fun st => { st with failed := st.failed.push (s1, s2, expected, none) } + +/-- info: All 13 tests passed -/ +#guard_msgs in +#eval show IO Unit from do + (testPairs *> report).run {} <&> Prod.fst diff --git a/tests/lean/run/tacticDoc.lean b/tests/lean/run/tacticDoc.lean index 95fcd35c4a..081ad4fbdf 100644 --- a/tests/lean/run/tacticDoc.lean +++ b/tests/lean/run/tacticDoc.lean @@ -7,7 +7,7 @@ We don't expect to modify the default tactics often, and it should be a matter o from #guard_msgs. -/ -set_option guard_msgs.diff true +set_option linter.tactic.docsOnAlt true /-- Finishing tactics that are intended to completely close a goal -/ register_tactic_tag finishing "finishing" @@ -51,7 +51,7 @@ syntax (name := nonTacticTm) "nonTactic" : term syntax (name := nonTacticTm') "nonTactic'" : term -/-- error: `nonTacticTm'` is not a tactic -/ +/-- error: `nonTacticTm'` is not a tactic (it is in the category `term`) -/ #guard_msgs in attribute [tactic_alt my_trivial] nonTacticTm' @@ -106,7 +106,11 @@ tactic_extension very_trivial /-! Check that warnings are issued if alternatives have their own docstrings -/ -/-- warning: Docstring for `tacticAnother` will be ignored because it is an alternative -/ +/-- +warning: Documentation is ignored on a tactic alternative. + +Note: This linter can be disabled with `set_option linter.tactic.docsOnAlt false` +-/ #guard_msgs in /-- Docs -/ @[tactic_alt my_trivial] @@ -115,7 +119,11 @@ syntax "another" : tactic /-- Docs -/ syntax (name := yetAnother) "yetAnother" : tactic -/-- warning: Docstring for `yetAnother` will be ignored because it is an alternative -/ +/-- +warning: Documentation for `yetAnother` is ignored because it is a tactic alternative. + +Note: This linter can be disabled with `set_option linter.tactic.docsOnAlt false` +-/ #guard_msgs in attribute [tactic_alt my_trivial] «yetAnother» diff --git a/tests/lean/run/versoDocs.lean b/tests/lean/run/versoDocs.lean new file mode 100644 index 0000000000..df67ee0174 --- /dev/null +++ b/tests/lean/run/versoDocs.lean @@ -0,0 +1,383 @@ + +import Lean + + +open Lean Doc Elab Term + +set_option doc.verso true + + + +attribute [doc_role] Lean.Doc.name +attribute [doc_role] Lean.Doc.kw +attribute [doc_role] Lean.Doc.kw? +attribute [doc_role] Lean.Doc.syntaxCat +attribute [doc_role] Lean.Doc.option +attribute [doc_role] Lean.Doc.attr +attribute [doc_role] Lean.Doc.tactic +attribute [doc_role] Lean.Doc.conv +attribute [doc_code_block] Lean.Doc.lean +attribute [doc_code_block] Lean.Doc.output +attribute [doc_command] Lean.Doc.«open» +attribute [doc_command] Lean.Doc.«set_option» +attribute [doc_role] Lean.Doc.given +attribute [doc_role lean] Lean.Doc.leanTerm +attribute [doc_role] Lean.Doc.manual +attribute [doc_role] Lean.Doc.syntax +attribute [doc_code_suggestions] Lean.Doc.suggestName +attribute [doc_code_suggestions] Lean.Doc.suggestLean +attribute [doc_code_suggestions] Lean.Doc.suggestTactic +attribute [doc_code_suggestions] Lean.Doc.suggestAttr +attribute [doc_code_suggestions] Lean.Doc.suggestOption +attribute [doc_code_suggestions] Lean.Doc.suggestKw +attribute [doc_code_suggestions] Lean.Doc.suggestCat +attribute [doc_code_suggestions] Lean.Doc.suggestSyntax + + + +@[doc_code_block] +def c (s : StrLit) : DocM (Block ElabInline ElabBlock) := pure (Block.code (s.getString.toList.reverse |> String.mk)) + +@[doc_directive] +def d (s : TSyntaxArray `block) : DocM (Block ElabInline ElabBlock) := do + .concat <$> s.reverse.mapM elabBlock + + +/-- +x +yz + +[W][wikipedia] + +[wikipedia]: https://en.wikipedia.org + +{name}`Nat` + +{given}`n : Nat` + +{given}`k` + +{lean}`k = n` + +{name}`n` + +{open Nat} + +{name}`succ` + +{name}`x` + +{name}`y` + +# foo + +blah + +# bar + +## baz + +:::d + +```c +blah +``` + +::: + +```lean +#check x +``` +-/ +def x (y : Nat) : Nat := y * 5 + +#eval show TermElabM Unit from do (← findDocString? (← getEnv) ``x).forM (IO.println ·.quote) + + +/-- +{name}`inst` +-/ +def blah [inst : ToString α] (x : α) : String := inst.toString x + +/-- +Rotates an array {name}`xs` by {name}`n` places. + +If {lean}`xs.size ≤ n`, then {lean}`rot n xs = rot (n % xs.size) xs`. + +Read more about {manual section "Array"}[arrays] in the Lean language reference. +-/ +def rot (n : Nat) (xs : Array α) : Array α := + xs[n:] ++ xs[:n] + +#eval rot 2 #[1, 2, 3] + +#eval rot 5 #[1, 2, 3] + + +/-- +Given {given}`xs : List α`, finds lists {given}`ys` and {given}`zs` such that {lean}`xs = ys ++ zs` +and {lean}`∀x ∈ xs, p x` and {lean}`zs.head?.all (¬p ·)`. +-/ +def splitSuchThat (p : α → Prop) [DecidablePred p] : List α → (List α × List α) + | [] => ([], []) + | x :: xs => + if p x then + let (pre, post) := splitSuchThat p xs + (x :: pre, post) + else + ([], x :: xs) + +/-- +An induction principle for {name}`Nat.gcd`'s reference implementation via Euclid's algorithm. + +{open Nat} + +To prove that a relation {name}`P` holds universally for the natural numbers (that is, for any +{name}`Nat`s {given}`j` and {given}`k`, we have {lean}`P j k`), it suffices to show: + +: Base case + + {lean}`P` relates {lean}`0` to all {lean}`Nat`s. + +: Inductive step + + {lean}`P` relates non-zero {given}`m` to {given}`n` if it relates {lean}`n % m` to {lean}`m`. + +This follows the computational behavior of {name}`gcd`. +-/ +@[elab_as_elim] theorem Nat.gcd.induction' {P : Nat → Nat → Prop} (m n : Nat) + (H0 : ∀n, P 0 n) (H1 : ∀ m n, 0 < m → P (n % m) m → P m n) : P m n := + Nat.strongRecOn (motive := fun m => ∀ n, P m n) m + (fun + | 0, _ => H0 + | _+1, IH => fun _ => H1 _ _ (succ_pos _) (IH _ (mod_lt _ (succ_pos _)) _) ) + n + +#check Nat.gcd.induction' + + +open MessageSeverity in +/-- +Prints {name}`s` twice. + +```lean +error (name := twice) +#eval printTwice A +``` +```output twice (severity := error) +Unknown identifier `A` +``` + +```lean +error (name := blah) +def blah2 : Nat := "glah" +``` +```output blah +Type mismatch + "glah" +has type + String +but is expected to have type + Nat +``` +-/ +def printTwice (s : String) : IO Unit := do + IO.print s + IO.print s + +section +/-! +This section tests that section variables work as expected. They should be in scope for docstrings, +but they should not be added as parameters only due to being mentioned in the docstring. +-/ +variable (howMany : Nat) + +/-- +Returns how many there are (that is, {name}`howMany`) +-/ +def f := howMany + +/-- +Returns its argument (but not {name}`howMany`) +-/ +def g (x : Nat) := x + +/-- +{name}`f` and {name}`g` are the same function (there's no extra parameter on {name}`g`) +-/ +theorem f_eq_g : f = g := rfl + +end + +section +/-! +This tests the rules for {name}`open`. +-/ + +namespace A +def a := "a" +def b := "b" +end A + +/-- error: Unknown constant `a` -/ +#guard_msgs in +/-- +{name}`a` +-/ +def testDef := 15 + +#guard_msgs in +/-- +{open A} + +{name}`a` and {name}`b` +-/ +def testDef' := 15 + +#guard_msgs in +/-- +{open A only:=a} + +{name}`a` +-/ +def testDef'' := 15 + +/-- error: Unknown constant `b` -/ +#guard_msgs in +/-- +{open A only:=a} + +{name}`b` +-/ +def testDef''' := 15 + +#guard_msgs in +/-- +{open A (only:=a) (only := b)} + +{name}`b` +-/ +def testDef'''' := 15 + + +end + +section +/-! +This section tests tactic references. +-/ + +namespace W +/-- +Completely unlike {tactic}`grind` +-/ +syntax (name := wiggleTac) "wiggle" (term)? term,*: tactic +end W + +/-- +The {tactic}`wiggle` tactic is not very powerful. + +It can be referred to as {tactic}`W.wiggleTac`. + +It can take a parameter! {tactic}`wiggle $t` where {name}`t` is some term. +Or even more: {tactic}`wiggle $t $[$t2],*`. + +Conv tactics can be used similarly: + * {conv}`arg` + * {conv}`arg 1` + * {conv}`ext $x` +-/ +def something := () + +#check something + +end + + +/-- +Attributes are great! +Examples: + * {attr}`grind` + * {attr}`@[grind ←, simp]` + * {attr}`init` +-/ +def somethingElse := () + +/-- +Options control Lean. +Examples: + * Use the {option}`pp.all` to control showing all the details + * {option}`set_option pp.all true` to set it +-/ +def somethingElseAgain := () + + +/-- +error: Unknown option `pp.alll` +--- +error: set_option value type mismatch: The value + "true" +has type + String +but the option `pp.all` expects a value of type + Bool +-/ +#guard_msgs in +/-- +Options control Lean. +Examples: + * Use the {option}`pp.alll` to control showing all the details + * {option}`set_option pp.all "true"` to set it +-/ +def somethingElseAgain' := () + + +/-- +{kw (cat := term)}`Type` {kw (of := termIfLet)}`if` +-/ +def somethingElseAgain'' := () + +/-- +info: + +Hint: Specify the syntax kind: + kw?̵ ̲(̲o̲f̲ ̲:̲=̲ ̲P̲a̲r̲s̲e̲r̲.̲T̲e̲r̲m̲.̲t̲y̲p̲e̲)̲ +-/ +#guard_msgs in +/-- +{kw?}`Type` +-/ +def somethingElseAgain''' := () + + +/-- +{syntaxCat}`term` +-/ +def stxDoc := () + +/-- +{syntaxCat}`thing` +-/ +declare_syntax_cat thing + + +syntax (name := here) "here " "{" num "}" : thing + +/-- +This is a thing: {syntax thing}`here{$n}` where {name}`n` is a numeral +-/ +add_decl_doc «here» + +/-- +{syntax thing}`here{$n}` +-/ +def yetMore := () + +@[inherit_doc yetMore] +def yetMore' := () + +#check yetMore' + +/- +TODO test: +* Scope rules for all operators + +-/