alms-0.6.0: src/Syntax/Construction.hs
-- | Utilities for constructing syntax
module Syntax.Construction (
-- * Generic tuple building
ToTuple(..),
-- * Optimizing expression constructors
exLet', exLetVar', exAbs', exAbsVar',
-- * Substitution
substExpr,
) where
import Util
import AST
import Data.Loc
import Meta.Quasi
import Prelude ()
import Data.Map as M
import Data.Generics (Data, everywhere, mkT)
-- | Constructs a let expression, but with a special case:
--
-- @let x = e in x == e@
-- @let (x, y) = e in (x, y) == e@
-- @let x = v in e == [v/x]e@
--
-- This is always safe to do.
exLet' :: Tag i => Patt i -> Expr i -> Expr i -> Expr i
exLet' π e1 e2
| π -==+ e2 = e1
-- This case can cause code bloat:
| [pa| $vid:x |] ← π
, syntacticValue e1 = substExpr e1 (J [] x) e2
| [pa| $vid:x |] ← π
, qx ← J [] x
, nextRedex qx e2
, M.lookup qx (fv e2) == Just 1
= substExpr e1 (J [] x) e2
| otherwise = exLet π e1 e2
-- | Constructs a let expression whose pattern is a variable.
exLetVar' :: Tag i => VarId i -> Expr i -> Expr i -> Expr i
exLetVar' = exLet' . paVar
-- | Constructs a lambda expression, but with a special case:
--
-- @exAbs' x (exApp (exVar f) x) == exVar f@
-- @exAbs' (x,y) (exApp (exVar f) (x,y)) == exVar f@
--
-- This eta-contraction is always safe, because f has no effect
exAbs' :: Tag i => Patt i -> Expr i -> Expr i
exAbs' x e = case view e of
ExApp e1 e2 -> case view e1 of
ExVar (J p f) | x -==+ e2
-> exVar (J p f)
_ -> exAbs x e
_ -> exAbs x e
-- | Construct an abstraction whose pattern is just a variable.
exAbsVar' :: Tag i => VarId i -> Expr i -> Expr i
exAbsVar' = exAbs' . paVar
-- | Does a pattern exactly match an expression? That is, is
-- @let p = e1 in e@ equivalent to @e1@? Note that we cannot
-- safely handle data constructors, because they may fail to match.
(-==+) :: Tag i => Patt i -> Expr i -> Bool
p -==+ e = case (dataOf p, dataOf e) of
(PaVar l, ExVar (J [] l'))
-> l == l'
(PaCon (J [] (ConId (Uid _ "()"))) Nothing,
ExCon (J [] (ConId (Uid _ "()"))) Nothing)
-> True
(PaPair p1 p2, ExPair e1 e2)
-> p1 -==+ e1 && p2 -==+ e2
_ -> False
infix 4 -==+
-- Does the given variable appear where the next redex would be
-- for the expression, if a substitution were made? This, along with
-- linearity (a separate check) makes it safe to substitute any expression
-- for it.
nextRedex ∷ Tag i ⇒ QVarId i → Expr i → Bool
nextRedex x = loop where
e1 >*> b2 = loop e1
|| syntacticValue e1
&& x `M.notMember` fv e1
&& b2
loop e = case e of
[ex| $qvid:x' |] → x == x'
[ex| $lit:_ |] → False
[ex| $qcid:_ $opt:me |] → maybe False loop me
[ex| let $vid:x' = $e1 in $e2 |]
| J [] x' == x → loop e1
| otherwise → e1 >*> loop e2
[ex| let $_ = $e1 in $_ |] → loop e1
[ex| match $e0 with $list:_ |]
→ loop e0
[ex| let rec $list:bns in $e2 |]
→ x `M.notMember` fv bns
&& x ∉ qdv bns
&& loop e2
[ex| let $decl:_ in $_ |] → False
[ex| ($e1, $e2) |] → e1 >*> loop e2
[ex| λ $_ → $_ |] → False
[ex| $e1 $e2 |] → e1 >*> loop e2
[ex| `$uid:_ $opt:me2 |] → maybe False loop me2
[ex| #$uid:_ $e2 |] → loop e2
[ex| { $list:flds | $e2 } |]
→ foldr (>*>) (loop e2)
(fdexpr . view <$> flds)
[ex| {+ $list:_ | $e2 +} |] → loop e2
[ex| $e1.$uid:_ |] → loop e1
[ex| $e1 : $_ |] → loop e1
[ex| $e1 :> $_ |] → loop e1
[ex| $anti:a |] → $antierror
-- Substitute an expression for a variable
substExpr ∷ Tag i ⇒ Expr i → QVarId i → Expr i → Expr i
substExpr e' x' = loop where
fv_e' = [ x | J [] x ← M.keys (fv e') ]
loop e = case e of
[ex| $qvid:x |]
| x == x' → e'
| otherwise → e
[ex| $lit:_ |] → e
[ex| $qcid:c $opt:me |] → [ex| $qcid:c $opt:me' |]
where me' = loop <$> me
[ex| let $π = $e1 in $e2 |]
| x' ∈ qdv π → [ex| let $π = $e1' in $e2 |]
| otherwise → [ex| let $π' = $e1' in $e2' |]
where e1' = loop e1
(π', e2'0) = avoidCapture fv_e' x' π e2
e2' = loop e2'0
[ex| match $e0 with $list:cas |]
→ [ex| match $e0' with $list:cas' |]
where e0' = loop e0
cas' = substCaseAlt e' x' <$> cas
[ex| let rec $list:bns in $e2 |]
| x' ∈ (J [] <$> fs) → [ex| let rec $list:bns' in $e2 |]
where fs = [ fi | [bnQ|! $vid:fi = $_ |] ← bns ]
bns' = substBinding e' x' <$> bns
[ex| let rec $list:bns in $e2 |]
| otherwise → [ex| let rec $list:bns' in $e2' |]
where
(fs, es) = unzip [ (fi, ei) | [bnQ|! $vid:fi = $ei |] ← bns ]
(fs', renamer) = avoidCapture' fv_e' x' fs (e2:es)
bns' = reverse (fst (foldl' eachBn ([], fs') bns))
e2' = loop (renamer e2)
eachBn (acc, fi':rest) [bnQ| $vid:_ = $ei |]
= ([bnQ| $vid:fi' = $ei' |]:acc, rest)
where ei' = loop (renamer ei)
eachBn (acc, rest) bn@[bnQ| $antiB:_ |]
= (bn:acc, rest)
eachBn _ _ =
error "BUG in substExpr: Inconsistency in number of let rec bindings"
[ex| let $decl:d in $e2 |] → [ex| let $decl:d in $e2' |]
where e2' = loop e2 -- Doesn't handle d
[ex| ($e1, $e2) |] → [ex| ($e1', $e2') |]
where e1' = loop e1
e2' = loop e2
[ex| λ $π1 → $e2 |]
| x' ∈ qdv π1 → e
| otherwise → [ex| λ $π1' → $e2' |]
where (π1', e2') = second loop
$ avoidCapture fv_e' x' π1 e2
[ex| $e1 $e2 |] → [ex| $e1' $e2' |]
where e1' = loop e1
e2' = loop e2
[ex| `$uid:c $opt:me2 |] → [ex| `$uid:c $opt:me2' |]
where me2' = loop <$> me2
[ex| #$uid:c $e2 |] → [ex| #$uid:c $e2' |]
where e2' = loop e2
[ex| { $list:flds | $e2 } |]
→ [ex| { $list:flds' | $e2' } |]
where flds' = substField e' x' <$> flds
e2' = loop e2
[ex| {+ $list:flds | $e2 +} |]
→ [ex| {+ $list:flds' | $e2' +} |]
where flds' = substField e' x' <$> flds
e2' = loop e2
[ex| $e1.$uid:u |] → [ex| $e1'.$uid:u |]
where e1' = loop e1
[ex| $e1 : $annot |] → [ex| $e1' : $annot |]
where e1' = loop e1
[ex| $e1 :> $annot |] → [ex| $e1' : $annot |]
where e1' = loop e1
[ex| $anti:_ |] → e
substCaseAlt ∷ Tag i ⇒ Expr i → QVarId i → CaseAlt i → CaseAlt i
substCaseAlt e' x' ca = case ca of
[caQ| $π1 → $e2 |]
| x' ∈ qdv π1 → ca
| otherwise → [caQ| $π1' → $e2' |]
where (π1', e2') = second (substExpr e' x')
$ avoidCapture [ x | J [] x ← M.keys (fv e') ]
x' π1 e2
[caQ| #$uid:c → $e2 |] → [caQ| #$uid:c → $e2' |]
where e2' = substExpr e' x' e2
[caQ| #$uid:c $π1 → $e2 |]
| x' ∈ qdv π1 → ca
| otherwise → [caQ| #$uid:c $π1' → $e2' |]
where (π1', e2') = second (substExpr e' x')
$ avoidCapture [ x | J [] x ← M.keys (fv e') ]
x' π1 e2
[caQ| $antiC:_ |] → ca
substBinding ∷ Tag i ⇒ Expr i → QVarId i → Binding i → Binding i
substBinding e' x' bn = case bn of
[bnQ| $vid:f = $e2 |]
| x' == J [] f → bn
| otherwise → [bnQ| $vid:f' = $e2' |]
where
(f', e2') = second (substExpr e' x')
$ avoidCapture [ x | J [] x ← M.keys (fv e') ]
x' f e2
[bnQ| $antiB:_ |] → bn
substField ∷ Tag i ⇒ Expr i → QVarId i → Field i → Field i
substField e' x' fld = case fld of
[fdQ| $uid:u = $e2 |] → [fdQ| $uid:u = $e2' |]
where e2' = substExpr e' x' e2
[fdQ| $antiF:_ |] → fld
-- | Given a list of names not to capture, the variable being
-- substituted, a pattern, and an expression
-- in the scope of the pattern, rename the pattern and expression
-- together so that it's safe to substitute the names under the pattern.
avoidCapture ∷ (Data a, Dv a i, Tag i) ⇒
[VarId i] → QVarId i → a → Expr i → (a, Expr i)
avoidCapture fv_e' x' π e = second ($ e) (avoidCapture' fv_e' x' π e)
-- | Given a list of names not to capture, the variable being
-- substituted, a pattern, and an expression
-- in the scope of the pattern, rename the pattern and expression
-- together so that it's safe to substitute the names under the pattern.
avoidCapture' ∷ (Data a, Dv a i, Fv b i, Tag i) ⇒
[VarId i] → QVarId i → a → b → (a, Expr i → Expr i)
avoidCapture' fv_e' x' π e
| x' ∈ qdv π || x' `M.notMember` fv e = (π, id)
| otherwise = (π', r)
where
fv_e = [ idName x | J [] x ← M.keys (fv e) ]
vs = dv π ∩ fv_e'
vs' = ident <$> freshNames (Just . idName <$> vs)
(fv_e ++ (idName <$> fv_e')) []
π' = foldr2 (\v v' → renameGeneric v' v) π vs vs'
r e' = foldr2 (\v v' → substExpr (exBVar v') (J [] v)) e' vs vs'
-- | Rename a variable
renameGeneric ∷ (Tag i, Data a) ⇒ VarId i → VarId i → a → a
renameGeneric x' x = everywhere (mkT each) where
each y | x == y = x'
each a = a
---
--- GENERIC TUPLING
---
class ToTuple a b c where
(-*-) ∷ a → [b] → c
infixl 3 -*-
instance (Tag i, ToPatt a i, ToPatt b i) ⇒ ToTuple a b (Patt i) where
π -*- πs = foldl' paPair (toPatt π) (toPatt <$> πs)
instance (Tag i, ToExpr a i, ToExpr b i) ⇒ ToTuple a b (Expr i) where
π -*- πs = foldl' exPair (toExpr π) (toExpr <$> πs)