lean4-htt/src/Lean/ParserCompiler.lean
Leonardo de Moura 91dca53274 refactor: remove MonadIO
There is no reason for having `MonadIO` anymore. The `MonadLift` type
class is well behaved in the new frontend, the `MonadFinally` solves
the problem at monad stacks such as `ExcepT e IO`.

This commit also changes the type of the IO printing functions.
For example, the type of `IO.println` was
```
def IO.println {m} [MonadIO m] {α} [ToString α] (s : α) : m Unit
```
and now it is just
```
def IO.println {α} [ToString α] (s : α) : IO Unit
```
We rely on the new frontend auto-lifting feature.
That is, if there is an instance `[MonadLiftT IO m]`, then
a term of type `IO a` is automatically coerced to `m a`

We also want a simpler `IO.println` for writing tests.
For example,
```
```
doesn't work because there isn't sufficient information for inferring
the parameter `m` in the previous `IO.println`.
The shortest workaround looked very weird
```
```

I considered adding `IO` as a default value for `m` when we have
`MonadIO m`, as we use `Nat` as the default for `ofNat a`, but it felt
like uncessary complexity.

@Kha The commit seems to work well. The auto-lifting featuring has
been working great for me. There is still room for improvement.
For example, given `MonadLiftT m n`, it doesn't automatically lift
`a -> m b` into `a -> n b`. So, code such as
`foo >>= IO.println`
had to be rewritten as
`foo >>= fun x => IO.println x`
I will add this feature later.
If you have time, please try to play with this feature and figure out
if it is stable enough for making it the default.
That is, if it roboust enough, we can stop using the following idiom
for writing functions that can be lifted automatically.
```
def instantiateLevelMVarsImp (u : Level) : MetaM Level :=
  ...

def instantiateLevelMVars {m} [MonadLiftT MetaM m] (u : Level) : m Level :=
  liftMetaM $ instantiateLevelMVarsImp u
```
I think we only need this idiom when using `MonadControlT` which is
not as common as `MonadLiftT`.
2020-11-18 18:47:22 -08:00

147 lines
6.9 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
-/
/-!
Gadgets for compiling parser declarations into other programs, such as pretty printers.
-/
import Lean.Util.ReplaceExpr
import Lean.Meta.Basic
import Lean.Meta.WHNF
import Lean.ParserCompiler.Attribute
import Lean.Parser.Extension
namespace Lean
namespace ParserCompiler
structure Context (α : Type) :=
(varName : Name)
(categoryAttr : KeyedDeclsAttribute α)
(combinatorAttr : CombinatorAttribute)
def Context.tyName {α} (ctx : Context α) : Name := ctx.categoryAttr.defn.valueTypeName
-- replace all references of `Parser` with `tyName` as a first approximation
def preprocessParserBody {α} (ctx : Context α) (e : Expr) : Expr :=
e.replace fun e => if e.isConstOf `Lean.Parser.Parser then mkConst ctx.tyName else none
section
open Meta
variables {α} (ctx : Context α) (force : Bool := false) 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
| e@(Expr.lam _ _ _ _) => lambdaLetTelescope e fun xs b => compileParserExpr b >>= mkLambdaFVars xs
| e@(Expr.fvar _ _) => pure e
| _ => do
let fn := e.getAppFn
let Expr.const c _ _ ← pure fn
| throwError! "call of unknown parser at '{e}'"
let args := e.getAppArgs
-- 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
pure 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 $ preprocessParserBody ctx value
let ty ← forallTelescope cinfo.type fun params _ =>
params.foldrM (init := mkConst ctx.tyName) fun param ty => do
let paramTy ← inferType param;
let paramTy ← forallTelescope paramTy fun _ b => pure $
if b.isConstOf `Lean.Parser.Parser then mkConst ctx.tyName
else b
pure $ mkForall `_ BinderInfo.default paramTy ty
let decl := Declaration.defnDecl {
name := c', lparams := [],
type := ty, value := value, hints := ReducibilityHints.opaque, isUnsafe := false }
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'
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
open Core
/-- Compile the given declaration into a `[(builtin)categoryAttr declName]` -/
def compileCategoryParser {α} (ctx : Context α) (declName : Name) (builtin : Bool) : AttrM Unit := do
-- This will also tag the declaration as a `[combinatorParenthesizer declName]` in case the parser is used by other parsers.
-- Note that simply having `[(builtin)Parenthesizer]` imply `[combinatorParenthesizer]` is not ideal since builtin
-- attributes are active only in the next stage, while `[combinatorParenthesizer]` is active immediately (since we never
-- call them at compile time but only reference them).
let (Expr.const c' _ _) ← (compileParserExpr ctx (mkConst declName) (force := false)).run'
| unreachable!
-- We assume that for tagged parsers, the kind is equal to the declaration name. This is automatically true for parsers
-- using `parser!` or `syntax`.
let kind := declName
addAttribute c' (if builtin then ctx.categoryAttr.defn.builtinName else ctx.categoryAttr.defn.name) (mkNullNode #[mkIdent kind])
variables {α} (ctx : Context α) 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) (force := false)
| ParserDescr.node _ _ d => compileEmbeddedParsers d
| ParserDescr.nodeWithAntiquot _ _ d => compileEmbeddedParsers d
| 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 $.run'
else
if catName.isAnonymous then
-- `[runBuiltinParserAttributeHooks]` => force compilation even if imported, do not apply `ctx.categoryAttr`.
discard (compileParserExpr ctx (mkConst constName) (force := true)).run'
else
compileCategoryParser ctx constName builtin
}
end ParserCompiler
end Lean