- Holes are no longer allowed when building executables - Stack overflow in mapMaybe (Day15)
252 lines
8.9 KiB
Agda
252 lines
8.9 KiB
Agda
-- First pass of compilation
|
||
-- - work out arities and fully apply functions / constructors (currying)
|
||
-- currying is problemmatic because we need to insert lambdas (η-expand) and
|
||
-- it breaks all of the de Bruijn indices
|
||
-- - expand metas (this is happening earlier)
|
||
-- - erase stuff (there is another copy that essentially does the same thing)
|
||
-- I could make names unique (e.q. on lambdas), but I might want that to vary per backend?
|
||
module Lib.CompileExp
|
||
|
||
import Prelude
|
||
import Lib.Common
|
||
import Lib.Types -- Name / Tm
|
||
import Lib.TopContext
|
||
import Lib.Prettier
|
||
import Lib.Util
|
||
import Lib.Ref2
|
||
import Data.SortedMap
|
||
|
||
-- REVIEW Separate pass for constructor magic?
|
||
-- ConCase SuccCon will be replaced by Default CLet,
|
||
-- but we would need to fix up zero, since we collapse extra constructors into a default case.
|
||
-- But should be ok becaon CLitAlt doesn't bind.
|
||
|
||
CExp : U
|
||
|
||
data CAlt : U where
|
||
CConAlt : String → ConInfo → List String → CExp → CAlt
|
||
-- REVIEW keep var name?
|
||
CDefAlt : CExp -> CAlt
|
||
-- literal
|
||
CLitAlt : Literal -> CExp -> CAlt
|
||
|
||
data CExp : U where
|
||
CBnd : Int -> CExp
|
||
-- How is CLam different from CFun with one arg?
|
||
CLam : Name -> CExp -> CExp
|
||
CFun : List Name -> CExp -> CExp
|
||
CApp : CExp -> List CExp -> Int -> CExp
|
||
CCase : CExp -> List CAlt -> CExp
|
||
CRef : QName -> CExp
|
||
CMeta : Int -> CExp
|
||
CLit : Literal -> CExp
|
||
CLet : Name -> CExp -> CExp -> CExp
|
||
CLetRec : Name -> CExp -> CExp -> CExp
|
||
CErased : CExp
|
||
-- Data / type constructor
|
||
CConstr : Name -> List CExp -> CExp
|
||
-- Raw javascript for `pfunc`
|
||
CRaw : String -> List QName -> CExp
|
||
-- Need this for magic Nat
|
||
-- TODO - use for primitives too
|
||
CPrimOp : String → CExp → CExp -> CExp
|
||
|
||
-- I'm counting Lam in the term for arity. This matches what I need in
|
||
-- code gen.
|
||
|
||
lamArity : Tm -> Nat
|
||
lamArity (Lam _ _ _ _ t) = S (lamArity t)
|
||
lamArity _ = Z
|
||
|
||
|
||
|
||
-- This is how much we want to curry at top level
|
||
-- leading lambda Arity is used for function defs and metas
|
||
-- TODO - figure out how this will work with erasure
|
||
arityForName : {{Ref2 Defs St}} → FC -> QName -> M Nat
|
||
arityForName fc nm = do
|
||
defs <- getRef Defs
|
||
case lookupMap' nm defs of
|
||
Nothing => error fc "Name \{show nm} not in scope"
|
||
(Just Axiom) => pure Z
|
||
(Just (TCon arity strs)) => pure $ cast arity
|
||
(Just (DCon _ k str)) => pure $ cast k
|
||
(Just (Fn t)) => pure $ lamArity t
|
||
(Just (PrimTCon arity)) => pure $ cast arity
|
||
(Just (PrimFn t arity used)) => pure arity
|
||
|
||
|
||
any : ∀ a. (a → Bool) → List a → Bool
|
||
any f Nil = False
|
||
any f (x :: xs) = if f x then True else any f xs
|
||
|
||
-- need to eta out extra args, fill in the rest of the apps
|
||
-- NOW - maybe eta here instead of Compile.newt, drop number on CApp
|
||
-- The problem would be deBruijn. We have to put the app under CLam
|
||
-- which would mess up all of the deBruijn (unless we push it out)
|
||
|
||
apply : CExp -> List CExp -> SnocList CExp -> Nat -> M CExp
|
||
-- out of args, make one up (fix that last arg)
|
||
apply t Nil acc (S k) =
|
||
pure $ CApp t (acc <>> Nil) (1 + cast k)
|
||
apply t (x :: xs) acc (S k) = apply t xs (acc :< x) k
|
||
-- once we hit zero, we fold the rest
|
||
apply t ts acc Z = go (CApp t (acc <>> Nil) 0) ts
|
||
where
|
||
go : CExp -> List CExp -> M CExp
|
||
-- drop zero arg call
|
||
go (CApp t Nil 0) args = go t args
|
||
go t Nil = pure t
|
||
go t (arg :: args) = go (CApp t (arg :: Nil) 0) args
|
||
|
||
lookupDef : {{Ref2 Defs St}} → FC → QName → M Def
|
||
lookupDef fc nm = do
|
||
defs <- getRef Defs
|
||
case lookupMap' nm defs of
|
||
Nothing => error fc "\{show nm} not in scope"
|
||
Just def => pure def
|
||
|
||
compileTerm : {{Ref2 Defs St}} → Tm -> M CExp
|
||
compileTerm (Bnd _ k) = pure $ CBnd k
|
||
-- need to eta expand to arity
|
||
compileTerm t@(Ref fc nm@(QN _ tag)) = do
|
||
arity <- arityForName fc nm
|
||
defs <- getRef Defs
|
||
case arity of
|
||
-- we don't need to curry functions that take one argument
|
||
(S Z) => pure $ CRef nm
|
||
Z =>
|
||
case the (Maybe Def) $ lookupMap' nm defs of
|
||
Just (DCon EnumCon _ _) => pure $ CLit $ LString tag
|
||
Just (DCon ZeroCon _ _) => pure $ CLit $ LInt 0
|
||
Just (DCon SuccCon _ _) =>
|
||
pure $ CLam "x" $ CPrimOp "+" (CLit $ LInt 1) (CBnd 0)
|
||
_ => pure $ CRef nm
|
||
_ => apply (CRef nm) Nil Lin arity
|
||
|
||
compileTerm (Meta fc k) = error fc "Compiling meta \{show k}"
|
||
compileTerm (Lam _ nm _ _ t) = CLam nm <$> compileTerm t
|
||
compileTerm tm@(App _ _ _) = case funArgs tm of
|
||
(Meta _ k, args) => do
|
||
error (getFC tm) "Compiling an unsolved meta \{show tm}"
|
||
-- info (getFC tm) "Compiling an unsolved meta \{show tm}"
|
||
-- pure $ CApp (CRef "Meta\{show k}") Nil 0
|
||
(t@(Ref fc nm), args) => do
|
||
defs <- getRef Defs
|
||
args' <- traverse compileTerm args
|
||
arity <- arityForName fc nm
|
||
case the (Maybe Def) $ lookupMap' nm defs of
|
||
Just (DCon SuccCon _ _) => applySucc args'
|
||
_ => apply (CRef nm) args' Lin arity
|
||
(t, args) => do
|
||
debug $ \ _ => "apply other \{render 90 $ pprint Nil t}"
|
||
t' <- compileTerm t
|
||
args' <- traverse compileTerm args
|
||
apply t' args' Lin Z
|
||
where
|
||
applySucc : List CExp → M CExp
|
||
applySucc Nil = pure $ CLam "x" $ CPrimOp "+" (CLit $ LInt 1) (CBnd 0)
|
||
applySucc (t :: Nil) = pure $ CPrimOp "+" (CLit $ LInt 1) t
|
||
applySucc _ = error emptyFC "overapplied Succ \{show tm}"
|
||
compileTerm (UU _) = pure $ CRef (QN Nil "U")
|
||
compileTerm (Pi _ nm icit rig t u) = do
|
||
t' <- compileTerm t
|
||
u' <- compileTerm u
|
||
pure $ CApp (CRef (QN Nil "PiType")) (t' :: CLam nm u' :: Nil) 0
|
||
compileTerm (Case fc t alts) = do
|
||
t' <- compileTerm t
|
||
alts' <- for alts $ \case
|
||
CaseDefault tm => CDefAlt <$> compileTerm tm
|
||
-- we use the base name for the tag, some primitives assume this
|
||
CaseCons qn@(QN ns nm) args tm => do
|
||
defs <- getRef Defs
|
||
def <- lookupDef emptyFC qn
|
||
case def of
|
||
DCon info _ _ => CConAlt nm info args <$> compileTerm tm
|
||
_ => error fc "\{show nm} is not constructor"
|
||
|
||
CaseLit lit tm => CLitAlt lit <$> compileTerm tm
|
||
pure $ CCase t' $ fancyCons t' alts'
|
||
where
|
||
numAltP : CAlt → Bool
|
||
numAltP (CConAlt _ SuccCon _ _) = True
|
||
numAltP (CConAlt _ ZeroCon _ _) = True
|
||
numAltP _ = False
|
||
|
||
enumAlt : CAlt → CAlt
|
||
enumAlt (CConAlt nm EnumCon args tm) = CLitAlt (LString nm) tm
|
||
enumAlt alt = alt
|
||
|
||
isInfo : ConInfo → CAlt → Bool
|
||
isInfo needle (CConAlt _ info _ _) = needle == info
|
||
isInfo _ _ = False
|
||
|
||
isDef : CAlt → Bool
|
||
isDef (CDefAlt _) = True
|
||
isDef _ = False
|
||
|
||
getBody : CAlt → CExp
|
||
getBody (CConAlt _ _ _ t) = t
|
||
getBody (CLitAlt _ t) = t
|
||
getBody (CDefAlt t) = t
|
||
|
||
doNumCon : CExp → List CAlt → List CAlt
|
||
doNumCon sc alts =
|
||
let zeroAlt = case find (isInfo ZeroCon) alts of
|
||
Just (CConAlt _ _ _ tm) => CLitAlt (LInt 0) tm :: Nil
|
||
Just tm => fatalError "ERROR zeroAlt mismatch \{debugStr tm}"
|
||
_ => case find isDef alts of
|
||
Just (CDefAlt tm) => CLitAlt (LInt 0) tm :: Nil
|
||
-- This happens if the zero alt is impossible
|
||
_ => Nil
|
||
in
|
||
let succAlt = case find (isInfo SuccCon) alts of
|
||
Just (CConAlt _ _ _ tm) => CDefAlt (CLet "x" (CPrimOp "-" sc (CLit $ LInt 1)) tm) :: Nil
|
||
Just tm => fatalError "ERROR succAlt mismatch \{debugStr tm}"
|
||
_ => case find isDef alts of
|
||
Just alt => alt :: Nil
|
||
_ => Nil
|
||
in zeroAlt ++ succAlt
|
||
|
||
fancyCons : CExp → List CAlt → List CAlt
|
||
fancyCons sc alts =
|
||
if any numAltP alts
|
||
then doNumCon sc alts
|
||
else map enumAlt alts
|
||
|
||
compileTerm (Lit _ lit) = pure $ CLit lit
|
||
compileTerm (Let _ nm t u) = do
|
||
t' <- compileTerm t
|
||
u' <- compileTerm u
|
||
pure $ CLet nm t' u'
|
||
compileTerm (LetRec _ nm _ t u) = do
|
||
t' <- compileTerm t
|
||
u' <- compileTerm u
|
||
pure $ CLetRec nm t' u'
|
||
compileTerm (Erased _) = pure CErased
|
||
|
||
compileFun : {{Ref2 Defs St}} → Tm -> M CExp
|
||
compileFun tm = go tm Lin
|
||
where
|
||
go : Tm -> SnocList String -> M CExp
|
||
go (Lam _ nm _ _ t) acc = go t (acc :< nm)
|
||
go tm Lin = compileTerm tm
|
||
go tm args = CFun (args <>> Nil) <$> compileTerm tm
|
||
|
||
-- What are the Defs used for above? (Arity for name)
|
||
compileDCon : QName → ConInfo → Int → CExp
|
||
compileDCon (QN _ nm) EnumCon 0 = CLit $ LString nm
|
||
compileDCon (QN _ nm) info 0 = CConstr nm Nil
|
||
compileDCon (QN _ nm) info arity =
|
||
let args = map (\k => "h\{show k}") (range 0 arity) in
|
||
CFun args $ CConstr nm $ map (\k => CBnd $ arity - k - 1) (range 0 arity)
|
||
|
||
-- probably want to drop the Ref2 when we can
|
||
defToCExp : {{Ref2 Defs St}} → (QName × Def) -> M (QName × CExp)
|
||
defToCExp (qn, Axiom) = pure $ (qn, CErased)
|
||
defToCExp (qn, DCon info arity _) = pure $ (qn, compileDCon qn info arity)
|
||
defToCExp (qn, TCon arity _) = pure $ (qn, compileDCon qn NormalCon arity)
|
||
defToCExp (qn, PrimTCon arity) = pure $ (qn, compileDCon qn NormalCon arity)
|
||
defToCExp (qn, PrimFn src _ deps) = pure $ (qn, CRaw src deps)
|
||
defToCExp (qn, Fn tm) = (_,_ qn) <$> compileFun tm
|