Files
newt/src/Lib/CompileScheme.newt

387 lines
16 KiB
Agda
Raw Blame History

module Lib.CompileScheme
import Prelude
import Lib.Common
import Lib.Types
import Lib.Eval
import Lib.Prettier
import Lib.CompileExp
import Lib.TopContext
import Lib.LiftWhere
import Lib.LiftLambda
import Lib.TCO
import Lib.Ref2
import Lib.Erasure
import Data.String
import Data.Int
import Data.SortedMap
import Data.IORef
import Data.SnocList
-- FIXME SnocList
SCEnv : U
SCEnv = List String
getEnv : Int SCEnv String
getEnv ix env = case getAt' ix env of
Just e => e
Nothing => fatalError "Bad bounds \{show ix}"
scbind : String → SCEnv → (String × SCEnv)
scbind nm env = let
x = length' env
nm' = "nm-\{show x}"
in (nm', nm' :: env)
-- TODO
scmKeywords : List String
scmKeywords = "lambda" :: Nil
scmIdent : String String
scmIdent id = if elem id scmKeywords then "$" ++ id else pack $ fix (unpack id)
where
fix : List Char -> List Char
fix Nil = Nil
fix (x :: xs) =
if isAlphaNum x || x == '_' || x == '.' then
x :: fix xs
-- make qualified names more readable
else if x == ',' then '_' :: fix xs
else if x == ' ' then '_' :: fix xs
else if x == '$' then
'$' :: '$' :: fix xs
else
'$' :: (toHex (cast x)) ++ fix xs
scmLit : Literal String
scmLit (LString str) = quoteString str
scmLit (LInt n) = show n
scmLit (LChar c) = pack $ '#' :: '\\' :: c :: Nil -- FIXME
scmLit (LBool b) = ite b "#t" "#f"
scmName : QName String
scmName qn = scmIdent $ show qn
cexpToScm : SCEnv CExp String
argsToScm : SCEnv List CExp List Quant List String
argsToScm env Nil Nil = Nil
argsToScm env (e :: es) (Many :: qs) = cexpToScm env e :: argsToScm env es qs
argsToScm env (e :: es) (Zero :: qs) = argsToScm env es qs
argsToScm env _ _ = fatalError "Arg count mismatch"
withVar : SCEnv CExp (String String) String
-- don't rebind a variable
withVar env (CBnd n) f = f $ getEnv n env
withVar env t f = let nm = "wv$\{show $ length' env}"
in "(let ((\{nm} \{cexpToScm env t})) \{f nm})"
cexpToScm env (CBnd n) = getEnv n env
cexpToScm env (CLam nm t) = case scbind nm env of
(nm', env') => "(lambda (\{nm'}) \{cexpToScm env' t})"
cexpToScm env (CFun args body) = case bindAll args Lin env of
(nms,env') => "(lambda (\{joinBy " " nms}) \{cexpToScm env' body})"
where
bindAll : List (Quant × String) SnocList String SCEnv List String × SCEnv
bindAll Nil acc env = (acc <>> Nil, env)
bindAll ((Many,nm) :: rest) acc env = case scbind nm env of
(nm', env') => bindAll rest (acc :< nm') env'
bindAll ((Zero,nm) :: rest) acc env = bindAll rest acc ("#f" :: env)
cexpToScm env (CApp t u) = "(\{cexpToScm env t} \{cexpToScm env u})"
cexpToScm env (CAppRef nm args Nil) = go (scmName nm) $ map (cexpToScm env) args
where
go : String List String String
go acc Nil = acc
go acc (arg :: args) = go "(\{acc} \{arg})" args
-- If we're short, we need to eta expand
cexpToScm env (CAppRef nm args quants) =
go env "(\{scmName nm}" (map (cexpToScm env) args) quants
where
-- here `acc` is always missing a closing paren
go : SCEnv String List String List Quant String
go env acc Nil Nil = acc ++ ")"
-- extra args are applied
go env acc (arg :: args) Nil = go env "(\{acc}) \{arg}" args Nil
-- missing args get eta expanded
go env acc Nil (q :: qs) = case scbind "_" env of
(nm, env') => let acc = "\{acc} \{nm}" in "(lambda (\{nm}) \{go env' acc Nil qs})"
-- TODO / REVIEW Only for Many?
go env acc (arg :: args) (Many :: qs) = go env "\{acc} \{arg}" args qs
go env acc (arg :: args) (Zero :: qs) = go env acc args qs
-- go env acc (arg :: args) (q :: qs) = go env acc args qs
-- so... we're not giving scrutinee a deBruijn index, but we may
-- need to let it so we can pull data off for the CConAlt
cexpToScm env (CCase sc alts) = do
-- assign sc, might need to do more to make the name unique, we only
-- get one of these per env at the moment
-- Not vector-ref for CLitAlt...
withVar env sc $ \ nm => doCase nm alts
where
-- add a bunch of lets then do body
conAlt : SCEnv String Int List String List Quant CExp String
conAlt env nm ix Nil Nil body = cexpToScm env body
conAlt env nm ix (arg :: args) (Many :: qs) body =
conAlt ("(vector-ref \{nm} \{show ix})" :: env) nm (ix + 1) args qs body
conAlt env nm ix (arg :: args) (Zero :: qs) body = conAlt ("#f" :: env) nm ix args qs body
conAlt env nm ix _ _ body = fatalError "arg/qs mismatch in conAlt"
nilAlt : SCEnv List Quant CExp String
nilAlt env Nil body = cexpToScm env body
nilAlt env (Zero :: qs) body = nilAlt ("#f" :: env) qs body
nilAlt env (Many :: qs) body = fatalError "Non-empty field on nil constructor"
consAlt' : SCEnv List String List Quant CExp String
consAlt' env nms Nil body = cexpToScm env body
consAlt' env nms (Zero :: qs) body = consAlt' ("#f" :: env) nms qs body
consAlt' env Nil (Many :: qs) body = fatalError "Too many fields on cons constructor"
consAlt' env (nm :: nms) (Many :: qs) body = consAlt' (nm :: env) nms qs body
consAlt : SCEnv String List Quant CExp String
consAlt env nm qs body = consAlt' env ("(car \{nm})" :: "(cdr \{nm})" :: Nil) qs body
-- an alt branch in a `case` statement
-- for the Nil/Cons case, we're not inside a `case`.
doAlt : String CAlt String
doAlt nm (CConAlt tag cname _ args qs body) = "((\{show tag}) \{conAlt env nm 1 args qs body})"
doAlt nm (CDefAlt body) = "(else \{cexpToScm env body})"
doAlt nm (CLitAlt lit body) = "((\{scmLit lit}) \{cexpToScm env body})"
-- doCase decides the top level path - case/cond/if/...
doCase : String List CAlt String
doCase nm (CDefAlt body :: Nil) = cexpToScm env body
doCase nm (cons@(CConAlt tag cname ConsCon args qs body ) :: rest) =
let consBranch = consAlt env nm qs body in
case rest of
Nil => consBranch
(CDefAlt body :: Nil) => "(if (null? \{nm}) \{cexpToScm env body} \{consBranch})"
(CConAlt _ _ NilCon _ qs body :: _) => "(if (null? \{nm}) \{nilAlt env qs body} \{consBranch})"
(CConAlt _ _ info _ _ _ :: _) => fatalError "\{show info} alt after cons"
(CLitAlt _ _ :: _) => fatalError "lit alt after cons"
_ => fatalError "too many alts after cons"
doCase nm (cons@(CConAlt tag cname NilCon args qs body ) :: rest) =
let nilBranch = consAlt env nm qs body in
case rest of
Nil => nilBranch
(CDefAlt body :: Nil) => "(if (null? \{nm}) \{nilBranch} \{cexpToScm env body})"
(CConAlt _ _ ConsCon _ qs body :: _) => "(if (null? \{nm}) \{nilBranch} \{consAlt env nm qs body})"
(CConAlt _ _ info _ _ _ :: _) => fatalError "\{show info} alt after nil"
(CLitAlt _ _ :: _) => fatalError "lit alt after nil"
_ => fatalError "too many alts after cons"
doCase nm (CConAlt tag cname _ args qs body :: Nil) = conAlt env nm 1 args qs body
doCase nm (CLitAlt _ body :: Nil) = cexpToScm env body
doCase nm (CDefAlt body :: Nil) = cexpToScm env body
doCase nm alts@(CLitAlt _ _ :: _) = "(case \{nm} \{joinBy " " $ map (doAlt nm) alts})"
doCase nm alts = "(case (vector-ref \{cexpToScm env sc} 0) \{joinBy " " $ map (doAlt nm) alts})"
cexpToScm env (CRef nm) = scmName nm
cexpToScm env (CMeta _) = fatalError "meta in code gen"
cexpToScm env (CLit lit) = scmLit lit
cexpToScm env (CLet nm t u) =
let (nm',env') = scbind nm env in
"(let ((\{nm'} \{cexpToScm env t})) \{cexpToScm env' u})"
cexpToScm env (CLetRec nm t u) =
let (nm',env') = scbind nm env in
"(let ((\{nm'} \{cexpToScm env' t})) \{cexpToScm env' u})"
cexpToScm env (CLetLoop _ _) = fatalError "CLetLoop in scheme codegen"
cexpToScm env (CLoop _ _) = fatalError "CLoop in scheme codegen"
cexpToScm env CErased = "#f"
cexpToScm env (CConstr tag nm args quants NilCon) = "'()"
cexpToScm env (CConstr tag nm args quants ConsCon) = case argsToScm env args quants of
(a :: b :: Nil) => "(cons \{a} \{b})"
_ => fatalError "Wrong number of args on a ConsCon"
cexpToScm env (CConstr tag nm args quants info) = "(vector \{show tag} \{unwords $ argsToScm env args quants})"
-- Should be handled by the caller
cexpToScm env (CRaw _ _) = fatalError "Stray CRaw"
-- TODO We still have a couple of these in CompileExp, for the magic Nat
cexpToScm env (CPrimOp op a b) = "(\{op} \{cexpToScm env a} \{cexpToScm env b})"
-- Collect the QNames used in a term
getNames : Tm -> List QName -> List QName
getNames (Ref x nm) acc = nm :: acc
getNames (Lam x str _ _ t) acc = getNames t acc
getNames (App x t u) acc = getNames u $ getNames t acc
getNames (Pi x str icit y t u) acc = getNames u $ getNames t $ QN primNS "PiType" :: acc
getNames (Let x str t u) acc = getNames u $ getNames t acc
getNames (LetRec x str _ t u) acc = getNames u $ getNames t acc
getNames (Case x t alts) acc = getNames t $ foldl getAltNames acc alts
where
getAltNames : List QName -> CaseAlt -> List QName
getAltNames acc (CaseDefault t) = getNames t acc
getAltNames acc (CaseCons name args t) = name :: getNames t acc
getAltNames acc (CaseLit lit t) = getNames t acc
getNames _ acc = acc
-- returns a QName -> Def of in-use entries
-- This will be what we work on for optimization passes
getEntries : SortedMap QName Def QName M (SortedMap QName Def)
getEntries acc name = do
top <- getTop
case lookup name top of
Nothing => do
putStrLn "bad name \{show name}"
pure acc
Just (MkEntry _ name type def@(Fn exp) _) => case lookupMap' name acc of
Just _ => pure acc
Nothing => do
top <- getTop
exp <- zonk top 0 Nil exp
let acc = updateMap name (Fn exp) acc
foldlM getEntries acc $ getNames exp Nil
Just (MkEntry _ name type def@(PrimFn _ _ used) _) =>
let acc = updateMap name def acc in
foldlM getEntries acc used
Just entry => pure $ updateMap name entry.def acc
/-
## Sort names by dependencies
This code is _way too subtle. The problem goes away if I wrap `() =>` around 0-ary top level functions. But I'm
stubborn, so I'm giving it a try. Changing codegen may repeatedly break this and require switching to `() =>`.
The idea here is to get a list of names to emit in order of dependencies. But top level 0-ary functions can reference
and call names at startup. They can't reference something that hasn't been declared yet and can't call something that
hasn't been defined.
As an example, a recursive show instance:
- References the `show` function
- `show` function references the instance under a lambda (not inlining this yet)
- We hit the bare function first, it depends on the instance (because of recursion), which depends on the
function, but loop prevention cuts.
We have main at the top, it is called so we visit it deep. We do a depth-first traversal, but will distinguish whether
we're visiting shallow or deep. We're trying to avoid hitting issues with indirect circular references.
- Anything we visit deep, we ensure is visited shallow first
- Shallow doesn't go into function bodies, but we do go into lambdas
- Anything invoked with arguments while shallow is visited deep, anything referenced or partially applied is still shallow.
- We keep track of both shallow and deep visits in our accumuulator
- Shallow represents the declaration, so we filter to those at the end
TODO this could be made faster by keeping a map of the done information
REVIEW could I avoid most of this by using `function` instead of arrow functions?
-/
-- TODO factor out to CompilerCommon
sortedNames : SortedMap QName CExp List QName List QName
sortedNames defs names =
map snd $ filter (not fst) $ foldl (go Nil) Nil $ map (True,) names
where
getBody : CAlt CExp
getBody (CConAlt _ _ _ _ _ t) = t
getBody (CLitAlt _ t) = t
getBody (CDefAlt t) = t
-- deep if this qn is being applied to something
getNames : (deep : Bool) List (Bool × QName) CExp List (Bool × QName)
-- liftIO calls a lambda statically
getNames deep acc (CLam _ t) = getNames deep acc t
getNames deep acc (CLetLoop _ t) = getNames deep acc t
-- top level 0-ary function, doesn't happen
getNames deep acc (CFun _ t) = if deep then getNames deep acc t else acc
-- REVIEW - True or deep?
getNames deep acc (CLoop args qs) = foldl (getNames True) acc args
getNames deep acc (CAppRef nm args qs) =
if length' args == length' qs
then case args of
Nil => (True, nm) :: acc
ts => foldl (getNames True) ((True, nm) :: acc) ts
else
foldl (getNames deep) ((deep, nm) :: acc) args
-- TODO look at which cases generate CApp
getNames deep acc (CApp t u) = getNames True (getNames deep acc u) t
getNames deep acc (CCase t alts) = foldl (getNames deep) acc $ t :: map getBody alts
-- we're not calling it
getNames deep acc (CRef qn) = (deep, qn) :: acc
getNames deep acc (CLet _ t u) = getNames deep (getNames deep acc t) u
getNames deep acc (CLetRec _ t u) = getNames deep (getNames deep acc t) u
getNames deep acc (CConstr _ _ ts _ info) = foldl (getNames deep) acc ts
-- if the CRaw is called, then the deps are called
getNames deep acc (CRaw _ deps) = acc -- map (_,_ deep) deps ++ acc
-- wrote these out so I get an error when I add a new constructor
getNames deep acc (CLit _) = acc
getNames deep acc (CMeta _) = acc
getNames deep acc (CBnd _) = acc
getNames deep acc CErased = acc
getNames deep acc (CPrimOp op t u) = getNames deep (getNames deep acc t) u
-- recurse on all dependencies, pushing onto acc
go : List (Bool × QName) List (Bool × QName) (Bool × QName) List (Bool × QName)
-- Need to force shallow if we're doing deep and haven't done shallow.
go loop acc this@(deep, qn) =
-- there is a subtle issue here with an existing (False, qn) vs (True, qn)
let acc = if deep && not (elem (False, qn) acc) && not (elem (False, qn) loop)
then go loop acc (False, qn)
else acc
in if elem this loop
then acc
else if elem this acc then acc
else
case lookupMap' qn defs of
Nothing => acc -- only `bouncer`
Just exp => this :: foldl (go $ this :: loop) acc (getNames deep Nil exp)
eraseEntries : {{Ref2 Defs St}} M Unit
eraseEntries = do
defs <- getRef Defs
ignore $ traverse go $ toList defs
where
go : {{Ref2 Defs St}} (QName × Def) M Unit
go (qn, Fn tm) = do
tm' <- erase Nil tm Nil
modifyRef Defs $ updateMap qn (Fn tm')
go _ = pure MkUnit
-- given a initial function, return a dependency-ordered list of javascript source
process : List QName M (List String)
process names = do
top <- getTop
entries <- foldlM getEntries emptyMap names
-- Maybe move this dance into liftWhere
ref <- newIORef entries
let foo = MkRef ref -- for the autos below
eraseEntries
liftWhere
entries <- readIORef ref
-- Now working with defs
exprs <- mapM defToCExp $ toList entries
let cexpMap = foldMap const emptyMap exprs
-- not needed for scheme
-- cexpMap <- tailCallOpt cexpMap
-- Not needed for scheme
-- cexpMap <- liftLambda cexpMap
let names = sortedNames cexpMap names
pure $ mapMaybe (go cexpMap) names
where
go : ExpMap QName Maybe String
go cexpMap name = do
(qn, cexp) <- lookupMap name cexpMap
case cexp of
CRaw _ _ => Nothing
_ => Just $ "(define \{scmName qn} \{cexpToScm Nil cexp})"
compileScheme : M (List String)
compileScheme = do
top <- getTop
let exports = getExports Nil $ listValues top.currentMod.modDefs
let mainName = (QN top.currentMod.modName "main")
let main = lookup mainName top
let todo = case main of
Nothing => exports
Just _ => mainName :: exports
defs <- process todo
case lookup mainName top of
Just _ => -- tack on call to main function
let exec = "(\{show mainName} 'world)"
in pure $ "(import (chezscheme))" :: "(include \"prim.ss\")" :: reverse (exec :: defs)
Nothing => pure $ reverse defs
where
getExports : List QName List TopEntry List QName
getExports acc Nil = acc
getExports acc ((MkEntry fc name@(QN ns nm) type def eflags) :: rest) =
let acc = if elem Export eflags then name :: acc else acc
in getExports acc rest