lean4-htt/src/Lean/ParserCompiler.lean
2022-06-07 17:54:10 -07:00

157 lines
7.4 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

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

/-
Copyright (c) 2020 Sebastian Ullrich. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sebastian Ullrich
-/
import Lean.Util.ReplaceExpr
import Lean.Meta.Basic
import Lean.Meta.ReduceEval
import Lean.Meta.WHNF
import Lean.ParserCompiler.Attribute
import Lean.Parser.Extension
/-!
Gadgets for compiling parser declarations into other programs, such as pretty printers.
-/
namespace Lean
namespace ParserCompiler
structure Context (α : Type) where
varName : Name
categoryAttr : KeyedDeclsAttribute α
combinatorAttr : CombinatorAttribute
def Context.tyName {α} (ctx : Context α) : Name := ctx.categoryAttr.defn.valueTypeName
-- replace all references of `Parser` with `tyName`
def replaceParserTy {α} (ctx : Context α) (e : Expr) : Expr :=
e.replace fun e =>
-- strip `optParam`
let e := if e.isOptParam then e.appFn!.appArg! else e
if e.isConstOf `Lean.Parser.Parser then mkConst ctx.tyName else none
open Meta Parser in
/-- Takes an expression of type `Parser`, and determines the syntax kind of the root node it produces. -/
partial def parserNodeKind? (e : Expr) : MetaM (Option Name) := do
let reduceEval? e : MetaM (Option Name) := do
try pure <| some (← reduceEval e) catch _ => pure none
let e ← whnfCore e
if e matches Expr.lam .. then
lambdaLetTelescope e fun _ e => parserNodeKind? e
else if e.isAppOfArity ``nodeWithAntiquot 4 then
reduceEval? (e.getArg! 1)
else if e.isAppOfArity ``withAntiquot 2 then
parserNodeKind? (e.getArg! 1)
else if e.isAppOfArity ``leadingNode 3 || e.isAppOfArity ``trailingNode 4 || e.isAppOfArity ``node 2 then
reduceEval? (e.getArg! 0)
else
return none
section
open Meta
variable {α} (ctx : Context α) (builtin : Bool) (force : Bool) in
/--
Translate an expression of type `Parser` into one of type `tyName`, tagging intermediary constants with
`ctx.combinatorAttr`. If `force` is `false`, refuse to do so for imported constants. -/
partial def compileParserExpr (e : Expr) : MetaM Expr := do
let e ← whnfCore e
match e with
| .lam .. => lambdaLetTelescope e fun xs b => compileParserExpr b >>= mkLambdaFVars xs
| .fvar .. => return e
| _ => do
let fn := e.getAppFn
let .const c .. := fn | throwError "call of unknown parser at '{e}'"
-- call the translated `p` with (a prefix of) the arguments of `e`, recursing for arguments
-- of type `ty` (i.e. formerly `Parser`)
let mkCall (p : Name) := do
let ty ← inferType (mkConst p)
forallTelescope ty fun params _ => do
let mut p := mkConst p
let args := e.getAppArgs
for i in [:Nat.min params.size args.size] do
let param := params[i]
let arg := args[i]
let paramTy ← inferType param
let resultTy ← forallTelescope paramTy fun _ b => pure b
let arg ← if resultTy.isConstOf ctx.tyName then compileParserExpr arg else pure arg
p := mkApp p arg
return p
let env ← getEnv
match ctx.combinatorAttr.getDeclFor? env c with
| some p => mkCall p
| none =>
let c' := c ++ ctx.varName
let cinfo ← getConstInfo c
let resultTy ← forallTelescope cinfo.type fun _ b => pure b
if resultTy.isConstOf `Lean.Parser.TrailingParser || resultTy.isConstOf `Lean.Parser.Parser then do
-- synthesize a new `[combinatorAttr c]`
let some value ← pure cinfo.value?
| throwError "don't know how to generate {ctx.varName} for non-definition '{e}'"
unless (env.getModuleIdxFor? c).isNone || force do
throwError "refusing to generate code for imported parser declaration '{c}'; use `@[runParserAttributeHooks]` on its definition instead."
let value ← compileParserExpr <| replaceParserTy ctx value
let ty ← forallTelescope cinfo.type fun params _ =>
params.foldrM (init := mkConst ctx.tyName) fun param ty => do
let paramTy ← replaceParserTy ctx <$> inferType param
return mkForall `_ BinderInfo.default paramTy ty
let decl := Declaration.defnDecl {
name := c', levelParams := [],
type := ty, value := value, hints := ReducibilityHints.opaque, safety := DefinitionSafety.safe }
let env ← getEnv
let env ← match env.addAndCompile {} decl with
| Except.ok env => pure env
| Except.error kex => do throwError (← (kex.toMessageData {}).toString)
setEnv <| ctx.combinatorAttr.setDeclFor env c c'
if cinfo.type.isConst then
if let some kind ← parserNodeKind? cinfo.value! then
-- If the parser is parameter-less and produces a node of kind `kind`,
-- then tag the compiled definition as `[(builtin)Parenthesizer kind]`
-- (or `[(builtin)Formatter kind]`, resp.)
let attrName := if builtin then ctx.categoryAttr.defn.builtinName else ctx.categoryAttr.defn.name
-- Create syntax node for a simple attribute of the form
-- `def simple := leading_parser ident >> optional (ident <|> priorityParser)`
let stx := mkNode `Lean.Parser.Attr.simple #[mkIdent attrName, mkNullNode #[mkIdent kind]]
Attribute.add c' attrName stx
mkCall c'
else
-- if this is a generic function, e.g. `AndThen.andthen`, it's easier to just unfold it until we are
-- back to parser combinators
let some e' ← unfoldDefinition? e
| throwError "don't know how to generate {ctx.varName} for non-parser combinator '{e}'"
compileParserExpr e'
end
variable {α} (ctx : Context α) (builtin : Bool) in
def compileEmbeddedParsers : ParserDescr → MetaM Unit
| ParserDescr.const _ => pure ()
| ParserDescr.unary _ d => compileEmbeddedParsers d
| ParserDescr.binary _ d₁ d₂ => compileEmbeddedParsers d₁ *> compileEmbeddedParsers d₂
| ParserDescr.parser constName => discard <| compileParserExpr ctx (mkConst constName) (builtin := builtin) (force := false)
| ParserDescr.node _ _ d => compileEmbeddedParsers d
| ParserDescr.nodeWithAntiquot _ _ d => compileEmbeddedParsers d
| ParserDescr.sepBy p _ psep _ => compileEmbeddedParsers p *> compileEmbeddedParsers psep
| ParserDescr.sepBy1 p _ psep _ => compileEmbeddedParsers p *> compileEmbeddedParsers psep
| ParserDescr.trailingNode _ _ _ d => compileEmbeddedParsers d
| ParserDescr.symbol _ => pure ()
| ParserDescr.nonReservedSymbol _ _ => pure ()
| ParserDescr.cat _ _ => pure ()
/-- Precondition: `α` must match `ctx.tyName`. -/
unsafe def registerParserCompiler {α} (ctx : Context α) : IO Unit := do
Parser.registerParserAttributeHook {
postAdd := fun catName constName builtin => do
let info ← getConstInfo constName
if info.type.isConstOf `Lean.ParserDescr || info.type.isConstOf `Lean.TrailingParserDescr then
let d ← evalConstCheck ParserDescr `Lean.ParserDescr constName <|>
evalConstCheck TrailingParserDescr `Lean.TrailingParserDescr constName
compileEmbeddedParsers ctx d (builtin := builtin) |>.run'
else
-- `[runBuiltinParserAttributeHooks]` => force compilation even if imported, do not apply `ctx.categoryAttr`.
let force := catName.isAnonymous
discard (compileParserExpr ctx (mkConst constName) (builtin := builtin) (force := force)).run'
}
end ParserCompiler
end Lean