lean4-htt/library/init/lean/expander.lean

224 lines
9.5 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) 2018 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Sebastian Ullrich
Macro expander for the Lean language
-/
prelude
import init.lean.parser.module
import init.lean.expr
namespace lean
namespace expander
open parser
open parser.term
open parser.command
open parser.command.notation_spec
structure expander_config :=
(filename : string)
@[derive monad monad_reader monad_except]
def transform_m := reader_t expander_config $ except_t message id
abbreviation transformer := syntax → transform_m syntax
def error {m : Type → Type} [monad m] [monad_reader expander_config m] [monad_except message m] {α : Type}
(context : syntax) (text : string) : m α :=
do cfg ← read,
-- TODO(Sebastian): convert position
throw {filename := cfg.filename, pos := /-context.get_pos.get_or_else-/ ⟨1,0⟩, text := text}
instance coe_name_ident : has_coe name syntax_ident :=
⟨λ n, {val := n, raw_val := substring.of_string n.to_string}⟩
instance coe_ident_ident_univs : has_coe syntax_ident ident_univs.view :=
⟨λ id, {id := id}⟩
instance coe_ident_binder_id : has_coe syntax_ident binder_ident.view :=
⟨binder_ident.view.id⟩
instance coe_binders {α : Type} [has_coe_t α binder_ident.view] : has_coe (list α) term.binders.view :=
⟨λ ids, {leading_ids := ids.map coe}⟩
def mk_app (fn : syntax_ident) (args : list syntax) : syntax :=
args.foldl (λ fn arg, syntax.mk_node app [fn, arg]) (syntax.ident fn)
def mk_simple_lambda (id : syntax_ident) (bi : binder_info) (dom body : syntax) : syntax :=
let bc : binder_content.view := {ids := [id], type := some {type := dom}} in
review lambda {
binders := {leading_ids := [], remainder := binders_remainder.view.mixed [
mixed_binder.view.bracketed $ match bi with
| binder_info.default := bracketed_binder.view.explicit {content := explicit_binder_content.view.other bc}
| binder_info.implicit := bracketed_binder.view.implicit {content := bc}
| binder_info.strict_implicit := bracketed_binder.view.strict_implicit {content := bc}
| binder_info.inst_implicit := bracketed_binder.view.inst_implicit
{content := inst_implicit_binder_content.view.named {id := id, type := dom}}
| binder_info.aux_decl := /- should not happen -/
bracketed_binder.view.explicit {content := explicit_binder_content.view.other bc}
]},
body := body
}
-- TODO(Sebastian): share this with other forms taking binders? Using local_expand?
def lambda.transform : transformer :=
λ stx, do
let lam := view lambda stx,
let to_ident (bid : binder_ident.view) : syntax_ident :=
match bid with
| binder_ident.view.id id := id
| binder_ident.view.hole _ := "a",
let r := lam.body,
(r, ty) ← match lam.binders.remainder with
| none := pure (r, none)
| binders_remainder.view.type brt := pure (r, some brt.type)
| binders_remainder.view.mixed brms := do {
r ← brms.mfoldr (λ brm r, match brm with
| mixed_binder.view.bracketed (bracketed_binder.view.anonymous_constructor ctor) :=
pure $ mk_simple_lambda "x" binder_info.default (review hole {}) $ review «match» {
scrutinees := [(syntax.ident "x", none)],
equations := [({lhs := [(review anonymous_constructor ctor, none)], rhs := r}, none)]
}
-- local notation: erase
| mixed_binder.view.bracketed
(bracketed_binder.view.explicit {content := explicit_binder_content.view.notation _}) := pure r
| mixed_binder.view.bracketed mbb := do
let (bi, bc) : binder_info × binder_content.view := (match mbb with
| bracketed_binder.view.explicit {content := bc} := (match bc with
| explicit_binder_content.view.other bc := (binder_info.default, bc)
| _ := (binder_info.default, {ids := []}) /- unreachable, see above -/)
| bracketed_binder.view.implicit {content := bc} := (binder_info.implicit, bc)
| bracketed_binder.view.strict_implicit {content := bc} := (binder_info.strict_implicit, bc)
| bracketed_binder.view.inst_implicit {content := bc} :=
prod.mk binder_info.inst_implicit $ (match bc with
| inst_implicit_binder_content.view.anonymous bca :=
{ids := ["_inst_"], type := some {type := bca.type}}
| inst_implicit_binder_content.view.named bcn :=
{ids := [bcn.id], type := some {type := bcn.type}})
| bracketed_binder.view.anonymous_constructor _ := (binder_info.default, {ids := []}) /- unreachable -/),
let type := (binder_content_type.view.type <$> bc.type).get_or_else $ review hole {},
type ← match bc.default with
| none := pure type
| some (binder_default.view.val bdv) := pure $ mk_app "_root_.opt_param" [type, bdv.term]
| some bdv@(binder_default.view.tac bdt) := match bc.type with
| none := pure $ mk_app "_root_.auto_param" [bdt.term]
| some _ := error (review binder_default bdv) "unexpected auto param after type annotation",
pure $ bc.ids.foldr (λ bid r, mk_simple_lambda (to_ident bid) bi type r) r
| mixed_binder.view.id bid := pure $ mk_simple_lambda (to_ident bid) binder_info.default (review hole {}) r
) r,
pure (r, none)
},
let r := lam.binders.leading_ids.foldr (λ bid r,
mk_simple_lambda (to_ident bid) binder_info.default (ty.get_or_else $ review hole {}) r) r,
pure r
def mixfix_to_notation_spec (k : mixfix.kind.view) (sym : notation_symbol.view) : transform_m notation_spec.view :=
let prec := match sym with
| notation_symbol.view.quoted q := q.prec
/-| _ := none -/ in
-- `notation` allows more syntax after `:` than mixfix commands, so we have to do a small conversion
let prec_to_action := λ prec, {action.view . kind := action_kind.view.prec prec} in
match k with
| mixfix.kind.view.prefix _ :=
-- `prefix sym:prec` ~> `notation sym:prec b:prec`
pure {
rules := [{
symbol := sym,
transition := transition.view.arg {id := `b,
action := prec_to_action <$> precedence.view.term <$> prec}}]}
| mixfix.kind.view.postfix _ :=
-- `postfix tk:prec` ~> `notation a tk:prec`
pure {
prefix_arg := `a,
rules := [{symbol := sym}]}
| mixfix.kind.view.infixr _ := do
-- `infixr tk:prec` ~> `notation a tk:prec b:(prec-1)`
act ← match prec with
| some prec := if prec.term.to_nat = 0
then error (review «precedence» prec) "invalid `infixr` declaration, given precedence must greater than zero"
else pure $ some $ prec_to_action $ precedence_term.view.lit $ precedence_lit.view.num $ number.view.of_nat $ prec.term.to_nat - 1
| none := pure none,
pure {
prefix_arg := `a,
rules := [{
symbol := sym,
transition := transition.view.arg {id := `b,
action := act}}]}
| _ :=
-- `infix/infixl tk:prec` ~> `notation a tk:prec b:prec`
pure {
prefix_arg := `a,
rules := [{
symbol := sym,
transition := transition.view.arg {id := `b,
action := prec_to_action <$> precedence.view.term <$> prec}}]}
def mixfix.transform : transformer :=
λ stx, do
let v := view mixfix stx,
let nota_sym := match v.symbol with
| mixfix_symbol.view.quoted q := notation_symbol.view.quoted q
| mixfix_symbol.view.unquoted u := notation_symbol.view.quoted {symbol := u},
spec ← mixfix_to_notation_spec v.kind nota_sym,
let term := match v.kind with
| mixfix.kind.view.prefix _ :=
-- `prefix tk:prec? := e` ~> `notation tk:prec? b:prec? := e b`
review app {fn := v.term, arg := review ident_univs `b}
| mixfix.kind.view.postfix _ :=
-- `postfix tk:prec? := e` ~> `notation tk:prec? b:prec? := e b`
review app {fn := v.term, arg := review ident_univs `a}
| _ :=
review app {fn := review app {fn := v.term, arg := review ident_univs `a}, arg := review ident_univs `b},
pure $ review «notation» {«local» := v.local, spec := spec, term := term}
def reserve_mixfix.transform : transformer :=
λ stx, do
let v := view reserve_mixfix stx,
spec ← mixfix_to_notation_spec v.kind v.symbol,
pure $ review reserve_notation {spec := spec}
local attribute [instance] name.has_lt_quick
-- TODO(Sebastian): replace with attribute
def transformers : rbmap name transformer (<) := rbmap.from_list [
(mixfix.name, mixfix.transform),
(reserve_mixfix.name, reserve_mixfix.transform),
(lambda.name, lambda.transform)
] _
structure expander_state :=
(next_scope : macro_scope)
@[reducible] def expander_m := state_t expander_state $ reader_t expander_config $ except_t message id
section
local attribute [reducible] macro_scope
def expander_state.new : expander_state := ⟨0⟩
def mk_scope : expander_m macro_scope :=
do st ← get,
put {st with next_scope := st.next_scope + 1},
pure st.next_scope
end
private def expand_core : nat → syntax → expander_m syntax
| 0 stx := error stx "macro expansion limit exceeded"
| (fuel + 1) stx :=
do some n ← pure stx.as_node | pure stx,
cfg ← read,
some t ← pure $ transformers.find n.kind.name
| syntax.mk_node n.kind <$> n.args.mmap (expand_core fuel),
sc ← mk_scope,
let n' := syntax.mk_node n.kind $ n.args.map (syntax.flip_scopes [sc]),
stx' ← state_t.lift $ t n',
---- expand recursively
--expand_core fuel $ stx'.flip_scopes [sc]
let stx' := stx'.flip_scopes [sc],
some n ← pure stx'.as_node | pure stx',
syntax.mk_node n.kind <$> n.args.mmap (expand_core fuel)
def expand (stx : syntax) : reader_t expander_config (except message) syntax :=
-- TODO(Sebastian): persist macro scopes across commands/files
prod.fst <$> expand_core 1000 stx expander_state.new
end expander
end lean