12 Commits
Author SHA1 Message Date
msyds d1588bd917 fix runtime parsing lol
build / build (push) Failing after 1m35s
2026-09-05 20:22:07 -06:00
msyds c495fc064a arith prims 2026-09-05 20:22:07 -06:00
msyds ac39175767 eval agian 2026-09-05 20:22:07 -06:00
msyds 31c610db34 superfuck 2026-09-05 20:22:07 -06:00
msyds 1ec3d35282 arith 2026-09-05 20:22:07 -06:00
msyds 9f37d10e4f ughhh evaluate cps 2026-09-05 20:22:07 -06:00
msyds ba5dc401d9 okay it's time for a hard reset and some thinking </3 2026-09-05 20:22:07 -06:00
msyds bc599df65f shared closures maybe 2026-09-05 20:22:07 -06:00
msyds f26ac50d4e hoist 2026-09-05 20:22:07 -06:00
msyds 3196d8db84 kexp 2026-09-05 20:22:07 -06:00
msyds 75e6c963c7 stupid 2026-09-05 20:22:07 -06:00
msyds 25f1f008bd wip: call/cc = capture/cc × invoke/cc 2026-09-05 20:22:06 -06:00
28 changed files with 982 additions and 475 deletions
+3
View File
@@ -9,6 +9,9 @@
. (progn (defun apply-cabal-fmt-h ()
(haskell-mode-buffer-apply-command "cabal-fmt"))
(add-hook 'before-save-hook #'apply-cabal-fmt-h nil t)))))
(scheme-mode
. ((eval . (dolist (s '(kappa κ prim))
(put s 'scheme-indent-function 1)))))
(nil
. ((eval
. (progn (defun display-ansi ()
+31
View File
@@ -132,3 +132,34 @@ multiple ~env-ref~ calls could probably be replaced with a primitive that loads
$code)
1))))
#+end_src
** example
#+begin_src scheme
(λ (n m ktail)
(letrec ((f (λ (x ktail-0) (+ x n ktail-0)))
(g (λ (y ktail-1) (+ y g ktail-1))))
(prim (cons f g) ktail)))
#+end_src
#+begin_src scheme
(λ (n m ktail)
(letrec ((f-code (λ (x ktail-0)
(prim (env-get 2)
(κ (n)
(+ x n ktail-0)))))
(g-code (λ (y ktail-1)
(prim (env-get 3)
(κ (m)
(+ y m ktail-1))))))
(letrec ((with-closure-code
(κ (f g)
(prim (get-env 0)
(κ (ktail)
(prim cons f g ktail))))))
(prim (make-shared-closure (with-closure-code)
ktail)
(κ (with-closure)
(prim (make-shared-closure (f-code g-code) n m)
with-closure))))))
#+end_src
+2
View File
@@ -0,0 +1,2 @@
(let ((p (cons 123 456)))
(cons (cdr p) (car p)))
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > 123
+1
View File
@@ -0,0 +1 @@
123
+7 -3
View File
@@ -58,14 +58,16 @@ library
-- cabal-fmt: expand src
exposed-modules:
Gyehoek.CPS.Close
Gyehoek.CPS.Contify
Gyehoek.CPS.Convert
Gyehoek.CPS.Eval
Gyehoek.CPS.Hoist
Gyehoek.CPS.Stackify
Gyehoek.CPS.Syntax
Gyehoek.Driver
Gyehoek.Language
Gyehoek.GenSym
Gyehoek.Jalmot
Gyehoek.Language
Gyehoek.Lift1
Gyehoek.Options
Gyehoek.Prelude
@@ -100,6 +102,7 @@ library
, hashable
, invertible-grammar
, lens
, lucid
, megaparsec
, mtl
, optparse-applicative
@@ -107,6 +110,7 @@ library
, pretty-simple
, prettyprinter
, prettyprinter-ansi-terminal
, prettyprinter-lucid
, process
, recursion-schemes
, scientific
@@ -117,8 +121,7 @@ library
, typed-process
, unordered-containers
, vector
, lucid
, prettyprinter-lucid
, tardis
hs-source-dirs: src
default-language: GHC2024
@@ -171,6 +174,7 @@ test-suite doctest
build-depends:
, base
, gyehoek
default-extensions: CPP
main-is: doctest.hs
+38 -29
View File
@@ -7,40 +7,49 @@ import Gyehoek.CPS.Syntax
import Data.List (nub)
import Gyehoek.GenSym
import Gyehoek.Prelude
import Debug.Pretty.Simple
import Gyehoek.Sexp qualified as S
import Data.HashSet.Lens
import Data.Traversable
close :: GenSym :> es => Exp -> Eff es Exp
close = transformM \case
ExpLetRec [(f, AbsLambda lam@(MkLambda bs kb m))] e -> do
f_code <- gensym' @Name $ f ^. _Wrapped' . to (<> "-code")
-- it would probably be most sane to generate a symbol for `env`,
-- but we're reusing the lambda binding so we don't have to
-- explicitly substitute recursive calls.
let frees = nub $ free' lam
let m' = ifoldr
(\n x q ->
let p = if x == f then PrimEnv @Val else PrimEnvRef n
in [cps|
(prim #{p}
(κ (#{x}) #{q}))
|])
m frees
pure [cps|
(letrec ((#{f_code} (λ (##{bs} #{kb})
#{m'})))
(prim (make-closure #{f_code} ##{frees})
(κ (#{f}) #{e})))
|]
genCodeName :: GenSym :> es => Name -> Eff es Name
genCodeName f = gensym' @Name $ f ^. _Wrapped' . to (<> "-code")
-- ExpApply f xs ktail -> do
-- code <- gensym' @Name "code"
-- pure [cps|
-- (prim (env-code #{f})
-- (κ (#{code})
-- (#{code} #{f} ##{xs} #{ktail})))
-- |]
bindEnv :: List Name -> Exp -> Exp
bindEnv frees m = [cps|
(prim (get-env) (κ #{frees} #{m}))
|]
close1 :: forall es. GenSym :> es => Exp -> Eff es Exp
close1 = \case
lr@(ExpLetRec bs e) -> do
let boundNames = bs ^.. each . _1
let boundNames' = setOf each boundNames
let frees = bs
& foldMapOf
(each . _2)
(freeWithBound' boundNames')
& nub
env_cont_l <- gensym' @Name "env-cont"
e_l <- gensym' @Name "letrec-body-cont"
bs' <- for bs \(f,ab) -> do
f_code_l <- genCodeName f
pure ( f_code_l
, ab & absBody %~ bindEnv (boundNames ++ frees)
)
let codes = bs' ^.. each . _1 . to MkLabel
pure [cps|
(letrec #{bs'}
(prim (make-shared-closure #{codes} #{frees})
(κ #{boundNames}
#{e})))
|]
e -> pure e
close :: forall es. GenSym :> es => Exp -> Eff es Exp
close = transformM close1
closeProgram :: GenSym :> es => Program -> Eff es Program
closeProgram = traverseOf (#body . #body) close
+67
View File
@@ -0,0 +1,67 @@
{-# LANGUAGE ApplicativeDo #-}
module Gyehoek.CPS.Contify
( contifyProgram
) where
import Control.Monad.Tardis
import Gyehoek.CPS.Syntax
import Gyehoek.Prelude
import qualified Data.HashSet as HS
import Control.Lens.Unsound (adjoin)
import Debug.Pretty.Simple
import qualified Data.HashMap.Strict as H
import Control.Monad.Writer.Lazy
import Control.Monad.Trans.Tardis (liftTardisT)
-- | ain't no way...
-- type T = WriterT (HashSet Name) (Tardis (HashSet Name) (HashSet Name))
type T = TardisT (HashSet Name) (HashSet Name) (Writer (HashSet Name))
evalT :: T a -> a
-- evalT = (`evalTardis` (mempty,mempty)) . fmap fst . runWriterT
evalT = fst . runWriter . (`evalTardisT` (mempty,mempty))
runT :: T a -> (a, HashSet Name)
-- runT = (`evalTardis` (mempty,mempty)) . runWriterT
runT = runWriter . (`evalTardisT` (mempty,mempty))
-- | inline function if it hasn't been used in the past, and won't
-- be used in the future.
tryInline :: Name -> Kappa -> T Kexp
tryInline kname kap = do
modifyBackwards (HS.insert kname)
p <- getsPast (HS.member kname)
modifyForwards (HS.insert kname)
q <- getsFuture (HS.member kname)
let c = p || q
liftTardisT . tell $ if c then HS.singleton kname else mempty
pure $ if c
then KexpVar kname
else KexpKappa kap
getKap :: HashMap Name Abs -> Name -> Maybe Kappa
getKap g kname = g ^? ix kname . #AbsKappa
contify :: HashMap Name Abs -> Exp -> T Exp
contify g = transformM \case
ExpApply f xs (KexpVar kname) | Just kap <- getKap g kname
-> ExpApply f xs <$> tryInline kname kap
ExpPrim p (KexpVar kname) | Just kap <- getKap g kname
-> ExpPrim p <$> tryInline kname kap
e -> pure e
contifyProgram :: HoistedProgram -> Eff es HoistedProgram
contifyProgram p = do
let g = p.bindings
let (p',contifiedVars) =
runT $
traverseOf
(adjoin
(#bindings . each . body)
(#body . body))
(contify g)
p
pTraceShowM contifiedVars
-- pure $ p' & #bindings %~ H.filterWithKey \k _ -> HS.member k contifiedVars
pure p'
+22 -22
View File
@@ -46,26 +46,18 @@ convert (Scm.ExpLit l) k = k . one . ValImm $ case l of
LitBool b -> ImmBool b
_ -> _
-- special case: call/cc is desugared during cps-conversion...
-- convert (Scm.ExpPrim (PrimCallCC withcc)) k = do
-- convert1 withcc \withcc' -> do
-- cc <- gensym' @Name "cc"
-- r <- gensym' "r"
-- m <- k . one $ ValVar r
-- ccish <- gensym' @Name "cc-ish"
-- x <- gensym' @Name "x"
-- pure [cps|
-- (letrec ((#{cc} (κ (#{r}) #{m})))
-- (letrec ((#{ccish} (λ (#{x} _) (continue #{cc} #{x}))))
-- (#{withcc'} #{ccish} #{cc})))
-- |]
-- ...while all other prims are left as-is for later stages to
-- handle..
convert (Scm.ExpPrim p) k =
telescope (convert1 @es) p \p' -> do
r <- gensym' "r"
ExpPrim p' . MkKappa [r] <$> k [ValVar r]
r_l <- gensym' "r"
-- k_l <- gensym' @Name "prim-k"
m <- k [ValVar r_l]
pure [cps|
(prim #{p'} (κ (#{r_l}) #{m}))
|]
-- pure [cps|
-- (letrec ((#{k_l} (κ (#{r_l}) #{m})))
-- (prim #{p'} #{k_l}))
-- |]
convert (Scm.ExpLambda xs e) k = do
f <- gensym' "lambda-body"
@@ -78,17 +70,25 @@ convert (Scm.ExpLambda xs e) k = do
convert (Scm.ExpApply f xs) k =
telescope (convert1 @es) (f:|xs) \(f':|xs') -> do
r <- gensym' "r"
r <- gensym' @Name "r"
x <- gensym' "x"
m <- k [ValVar x]
pure $ ExpLetRec [(r, AbsKappa' [x] m)] $
ExpApply f' xs' r
ExpApply f' xs' (KexpVar r)
convert (Scm.ExpBegin xs) k = telescope (convert @es) xs (k . NE.last)
convert (Scm.ExpIf c t f) k =
convert1 c \c' ->
ExpIf c' <$> convert t k <*> convert f k
convert1 c \c' -> do
t_l <- gensym' @Name "truthy-cont"
f_l <- gensym' @Name "falsey-cont"
t' <- convert t k
f' <- convert f k
pure [cps|
(letrec ((#{t_l} (κ () #{t'}))
(#{f_l} (κ () #{f'})))
(if #{c'} #{t_l} #{f_l}))
|]
-- let-bindings are desugared into continuation calls whose parameters
-- are the left-hand sides and whose arguments are the right-hand
+237 -82
View File
@@ -1,104 +1,259 @@
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE OverloadedLists #-}
module Gyehoek.CPS.Eval
( evalProgram
, module Gyehoek.CPS.Syntax
, evalExp
, eGrammar
) where
import Gyehoek.CPS.Syntax
import Control.Lens
import Gyehoek.CPS.Syntax hiding (Hob(..), Obj(..), cont)
import Gyehoek.Sexp qualified as S
import Control.Lens hiding (assign)
import Data.Maybe (fromMaybe)
import Text.Show.Functions ()
import qualified Data.HashMap.Strict as H
import Gyehoek.Prelude
import Gyehoek.Prelude hiding (assign)
import Debug.Pretty.Simple
import Gyehoek.Jalmot
import Control.Monad.Cont
import Gyehoek.Sexp qualified as S
import GHC.Generics (Generically(..))
import Gyehoek.Sexp ((:-)(..))
import Data.List (nub, mapAccumR, compareLength)
import Data.HashSet.Lens (setOf)
import Data.IntMap.Strict (IntMap)
import Data.IntMap.Strict qualified as IM
import Data.Monoid
import Control.Monad.State
import Data.Traversable (for)
import Data.Foldable (traverse_)
data Env = MkEnv
{ vars :: HashMap Name Obj
, labels :: HashMap Name (Env, Abs)
newtype Loc = MkLoc { getLoc :: Int }
deriving stock (Generic, Data)
deriving newtype (Show, Eq, Ord, Enum)
data Store = MkStore
{ nextLoc :: Loc
, heap :: IntMap E
}
deriving (Show, Generic)
deriving stock (Show, Generic)
eval :: Env -> Exp -> List Obj
type instance Index Store = Loc
type instance IxValue Store = E
eval g (Halt xs) = evalVal g <$> xs
instance Ixed Store where ix (MkLoc j) = #heap . ix j
instance At Store where at (MkLoc j) = #heap . at j
eval g (ExpContinue k xs) =
case g ^. #labels . at k' of
Just (h, AbsKappa' bs m) -> eval h' m
where
h' = h & #vars <>~ envOfBinds bs (evalVal g <$> xs)
_ -> error [i|not a kappa: #{k}|]
where
k' = case evalVal g k of
ObjImm (ImmLabel x) -> x
x -> error [i|expected label, got #{x}|]
emptyStore :: Store
emptyStore = MkStore
{ nextLoc = MkLoc 0
, heap = mempty
}
eval g (ExpApply f xs ktail) =
case g ^?! #labels . at f' of
Just (h,AbsLambda' bs kb m) -> eval h' m
where h' = h & #vars <>~ envOfBinds bs (evalVal g <$> xs)
& #labels . at kb .~ (g ^. #labels . at ktail)
Nothing -> error [i|undefined label: #{f}|]
where
f' = case evalVal g f of
ObjImm (ImmLabel x) -> x
x -> error [i|expected label, got #{x}|]
eval g (ExpLetRec [(b, ab)] e) = eval g' e
where g' = g & #labels . at b ?~ ab
eval g (ExpPrim p (MkKappa bs e)) = case evalVal g <$> p of
PrimAdd x y -> arithBinop (+) x y
PrimMul x y -> arithBinop (*) x y
PrimSub x y -> arithBinop (-) x y
PrimDiv x y -> arithBinop div x y
PrimMakeClosure x env -> ret [ObjHob (HobClosure lbl env) ]
where
lbl = case x of
ObjImm (ImmLabel l) -> l
_ -> error [i|expected label, got #{x}|]
PrimEnvCode x -> ret . (:[]) . ObjImm . ImmLabel $ code
where
code = case x of
ObjHob (HobClosure lbl _) -> lbl
_ -> error [i|expected closure, got #{x}|]
_ -> error [i|unhandled prim: #{p}|]
where
ret rs = eval
(g & #vars <>~ envOfBinds bs rs)
e
arithBinop f (ObjImm (ImmInt x)) (ObjImm (ImmInt y)) =
ret [ObjImm . ImmInt $ f x y]
arithBinop _ x y = error [i|bad arith: #{x}, #{y}|]
eval _ e = error [i|unimplemented case: #{e}|]
envOfBinds bs xs = foldMap (uncurry H.singleton) (zip bs xs)
evalVal :: Env -> Val -> Obj
evalVal g = \case
ValVar x -> fromMaybe (error [i|unbound: #{x}|]) $ g ^?! #vars . at x
ValImm x -> ObjImm x
newtype Env = MkEnv { getEnv :: HashMap Name Loc }
deriving stock (Show, Generic, Data)
deriving newtype (Semigroup, Monoid)
emptyEnv :: Env
emptyEnv = MkEnv
{ vars = mempty
-- a kinda silly hack to make sure `halt` is handled correctly when
-- it appears as the tail continuation of an application. the
-- special case of `eval` responsible for `halt` only covers terms
-- of the form `(continue $halt xs …)`; other terms such as
-- `($some-fn xs $halt)` just see an undefined label `$halt`.
, labels = H.singleton "halt" $
AbsKappa' ["h0"] $ Halt [ValVar "h0"]
}
emptyEnv = mempty
evalExp :: Exp -> List Obj
evalExp = eval emptyEnv
type instance Index Env = Name
type instance IxValue Env = Loc
evalProgram :: Program -> List Obj
evalProgram (MkProgram lam) = eval emptyEnv [cps|
(letrec ((start #{lam}))
(start halt))
|]
instance Ixed Env where ix j = #getEnv . ix j
instance At Env where at j = #getEnv . at j
update :: Loc -> E -> Store -> Store
update (MkLoc loc) v = #heap %~ IM.alter f loc
where
f (Just _) = Just v
f Nothing = error "segfault lol"
updates :: Foldable f => f (Loc, E) -> Store -> Store
updates = alaf Endo foldMap (uncurry update)
fetch :: Loc -> M r E
fetch (MkLoc loc) = gets (^?! #heap . ix loc)
new :: M r Loc
new = state \st -> (st.nextLoc, st & #nextLoc %~ succ)
new' :: E -> M r Loc
new' e = state \st ->
( st.nextLoc
, st & #nextLoc %~ succ & at st.nextLoc ?~ e
)
defines :: Traversable t => t (Name, E) -> M Answer Env
defines = alaf Ap foldMap \(name,e) -> do
l <- new' e
pure $ bind name l
var :: HasCallStack => Env -> Name -> M Answer Loc
var g x = case g ^. at x of
Just l -> pure l
Nothing -> wrong [i|unbound variable #{x}|]
type CmdCont = Store -> Answer
type ExpCont = List E -> CmdCont
type M r = ContT r (State Store)
data Answer
= AnswerValues (List E)
| AnswerError AJalmot
deriving (Show, Generic)
data Mutability
= Mut
| NoMut
deriving (Show, Generic, Data, Eq)
wrong :: Text -> M Answer a
wrong s = ContT \_ -> pure . AnswerError . EvalError $ s
bind :: Name -> Loc -> Env
bind k = MkEnv . H.singleton k
extends :: Foldable f => f (Name, Loc) -> Env -> Env
extends xs g = g <> foldMap (uncurry bind) xs
assign :: Loc -> E -> M Answer ()
assign l e = do
use (at l) >>= \case
Just _ -> at l ?= e
Nothing -> wrong [i|#{e} #{l} |]
-- | The denotation of an expressed value.
data E
= ESymbol Text
| ECharacter Char
| EInt Int
| EBool Bool
| EUndefined
| EUnspecified
| ENull
| EPair Loc Loc Mutability
| EVec (List Loc) Mutability
| EString (List Loc) Mutability
| EProcedure Procedure
deriving stock (Show, Generic)
type Procedure = List E -> DynPoints -> M Answer (List E)
eGrammar :: Store -> S.DatumGrammar E
eGrammar st = S.partialOsi (const . Left $ mempty) go
where
gofetch x = go $ st ^?! ix x
go = \case
ESymbol s -> S.Symbol s
ECharacter c -> S.Character c
EInt n -> S.Number (fromIntegral n)
EBool b -> S.Boolean b
EUndefined -> S.Unreadable "#<undefined>"
EUnspecified -> S.Unreadable "#<unspecified>"
ENull -> S.List []
EPair car cdr _mut -> S.DotList [gofetch car] (gofetch cdr)
EVec xs _mut -> S.Vector . fmap gofetch $ xs
EString xs _mut -> S.String _
data DynPoints = MkDynPoints
deriving (Generic, Data)
evalVal :: Env -> Val -> M Answer E
evalVal g (ValVar x) = var g x >>= fetch
evalVal g (ValImm imm) = pure case imm of
ImmLabel l -> error [i|#{l}|]
ImmInt n -> EInt n
ImmBool b -> EBool b
ImmUndefined -> EUndefined
evalKexp :: Env -> Kexp -> M Answer E
evalKexp g (KexpVar x) = var g x >>= fetch
evalKexp g (KexpKappa kap) = evalAbs g (AbsKappa kap)
evalAbs :: Env -> Abs -> M Answer E
evalAbs g (MkAbs formals ktail e) = pure . EProcedure $ \xs dps ->
let
formals' = formals ++ foldMap (:[]) ktail
lformals = length formals'
lxs = length xs
in if lformals /= lxs
then wrong [i| #{lformals} #{lxs} .|]
else do
ls <- xs & traverse new'
let g' = g & extends (zip formals' ls)
eval g' dps e
eval :: Env -> DynPoints -> Exp -> M Answer (List E)
eval g dps (ExpJump f xs ktail) = do
f' <- evalVal g f
xs' <- traverse (evalVal g) xs
ktail' <- traverse (evalKexp g) (ktail ^.. _Just)
case f' of
EProcedure p -> p (xs' ++ ktail') dps
_ -> wrong "bad procedure"
eval g dps (ExpLetRec bs e) = do
ls <- for bs . const $ new' EUndefined
let g' = g & extends (zip (bs ^.. each . _1) ls)
bs' <- forOf (each . _2) bs (evalAbs g')
traverse_ (uncurry assign) $ zip ls (bs' ^.. each . _2)
eval g' dps e
eval g dps (ExpPrim p k) = do
p' <- evalPrim g =<< traverse (evalVal g) p
evalKexp g k >>= \case
EProcedure fp -> fp p' dps
_ -> wrong [i|prim(#{p}) |]
eval g dps e = error [i|unimplemented #{e}|]
evalPrim :: Env -> Prim E -> M Answer (List E)
evalPrim g = \case
PrimAdd x y -> arith2 (+) x y
PrimMul x y -> arith2 (*) x y
PrimSub x y -> arith2 (-) x y
PrimDiv x y -> arith2 div x y
PrimValues xs -> pure xs
p -> wrong [i|prim(#{p}) |]
where
arith2 f (EInt x) (EInt y) = pure [EInt $ f x y]
arith2 f x y = wrong [i| : #{x}, #{y}|]
evalExp :: Jalmot :> es => Exp -> Eff es _
evalExp e = _
evalProgram :: Jalmot :> es => Program -> Eff es (List S.Datum)
evalProgram (MkProgram lam) = case run (pure . AnswerValues) of
(AnswerError jm, _) -> throwError jm
(AnswerValues vs, st) -> traverse (S.toDatum $ eGrammar st) vs
where
run f = (`runState` emptyStore) . (`runContT` f) $ do
g <- setup
eval g MkDynPoints (ExpLetRec
[("_start",AbsLambda lam)]
(ExpApply (ValVar "_start") [] (KexpVar "halt")))
setup :: M Answer Env
setup = defines @List
[ ("halt", EProcedure prim_halt)
]
prim_halt :: Procedure
prim_halt xs _dps = ContT \_ -> pure $ AnswerValues xs
+24
View File
@@ -0,0 +1,24 @@
module Gyehoek.CPS.Hoist
( hoistProgram
) where
import Gyehoek.CPS.Syntax
import Gyehoek.Prelude
import qualified Data.HashMap.Strict as H
import Effectful.Writer.Static.Local
import Data.Foldable
type Hoist = Writer (HashMap Label Abs)
hoist :: Hoist :> es => Exp -> Eff es Exp
hoist = transformM \case
ExpLetRec bs m -> do
traverse_ (\(k,v) -> tell $ H.singleton (MkLabel k) v) bs
pure m
e -> pure e
hoistProgram :: Program -> Eff es HoistedProgram
hoistProgram p = do
(body,bindings) <- runWriter $ traverseOf #body hoist p.body
pure $ MkHoistedProgram {body,bindings}
+114 -139
View File
@@ -12,24 +12,14 @@ import Gyehoek.GenSym
import Effectful.Writer.Static.Shared
import Data.Foldable
import qualified Data.HashMap.Strict as H
import Data.List (elemIndex, nub)
import Data.List (elemIndex, nub, intersect)
import Data.Text qualified as T
import Gyehoek.Prelude
import Debug.Pretty.Simple
import qualified Gyehoek.Sexp as S
import Data.Monoid
type Stackify = Writer Stk.Program
runStackify :: Eff (Stackify : es) a -> Eff es (a, Stk.Program)
runStackify = runWriter
live :: Free a => Env -> a -> List Name
-- TODO: free' should return an OSet lol
live g e = nub (free' e) & filter \x ->
x `elem` g.bound
-- && not (x `elem` g.contStack)
data BlockBuilder
= Code (List Stk.Instr) BlockBuilder
| Tail Stk.Tail
@@ -40,152 +30,137 @@ buildBlock = go [] where
go acc (Code xs bb) = go (acc ++ xs) bb
go acc (Tail t) = Stk.MkBlock acc t
emitRoutine :: Stackify :> es => Stk.Routine -> Eff es ()
emitRoutine rt = tell [rt]
stackify
:: (GenSym :> es, Stackify :> es)
=> Env -> Exp -> Eff es BlockBuilder
stackify g (ExpLetRec [(f, AbsKappa kap)] e) = do
kap' <- stackifyKappa g kap
emitRoutine (Stk.MkRoutine (MkLabel f) . buildBlock $ kap')
stackify g e
stackify g (ExpLetRec [(f, AbsLambda lam)] e) = do
lam' <- stackifyLambda g (MkLabel f) lam
emitRoutine lam'
stackify g e
stackify g (ExpIf c t f) = do
let c' = stackifyVal g c
t' <- buildBlock <$> stackify g t
f' <- buildBlock <$> stackify g f
pure . Tail $ Stk.If c' t' f'
stackify g (ExpApply f xs ktail) = do
pure $
Code [ Stk.Push $ stackifyVal g (ValVar ktail)
, Stk.Push $ stackifyVal g f
] $
Code (pushArgs g xs) $
Tail (Stk.Call (length xs))
-- assume that `k` is the continuation on top of the stack lol.
stackify g e@(ExpContinue k xs)
| isn't (#_ValVar . only g.tail) k = pure $
Code [ Stk.Push (stackifyVal g k) ] $
Code (pushArgs g xs) $
Tail $ Stk.TailCall (length xs)
| otherwise = pure $
Code (pushArgs g xs) $
Tail (Stk.Return (length xs))
stackify g (ExpPrim p kap) = do
kap' <- stackifyKappa g kap
pure $ Code [ Stk.Prim (stackifyVal g <$> p) ] kap'
stackify _ e = error [i|unimplemented exp: #{e}|]
loadArgs :: List Name -> List Stk.Instr
loadArgs = imapOf itraversed \n x -> Stk.Load (MkReg x) n
pushArgs :: Env -> List Val -> List Stk.Instr
pushArgs g args = [ Stk.Push $ stackifyVal g x | x <- reverse args ]
-- affine
_ValName :: Traversal' Val Name
_ValName = failing #_ValVar (#_ValImm . #_ImmLabel . #_MkLabel)
stackifyKappa
:: (Stackify :> es, GenSym :> es)
=> Env -> Kappa
-> Eff es BlockBuilder
stackifyKappa g (MkKappa xs m) = do
let g' = g & #bound <>:~ xs
Code (loadArgs g'.bound)
<$> stackify g' m
stackify
:: forall es. (GenSym :> es)
=> Env -> Exp -> Eff es BlockBuilder
stackifyLambda
:: (Stackify :> es, GenSym :> es)
=> Env -> Label -> Lambda
-> Eff es Stk.Routine
stackifyLambda g name (MkLambda xs k m) = do
m' <- stackify (g & #bound .~ xs & #tail .~ k) m
pure $
Stk.MkRoutine name . buildBlock $
Code (loadArgs xs) $
Code [Stk.Load (MkReg k) (length xs + 1)] m'
stackify _ (ExpContinue (ValVar k) xs) =
Code [ ] _
stackifyVal :: Env -> Val -> Stk.Val
stackifyVal g = \case
ValImm imm -> Stk.ValImm imm
ValVar v -> case regOf g v of
Just r -> Stk.ValReg r
Nothing -> Stk.ValLabel (MkLabel v)
v -> error [i|unimplemented val: #{v}|]
stackify _ (ExpPrim p k) = _
regOf :: Env -> Name -> Maybe Reg
regOf g x
| x `elem` g.bound || x == g.tail = Just . MkReg $ x
| otherwise = Nothing
stackify _ e = error [i|unimplemented exp: #{e}|]
stackifyAbs :: (GenSym :> es) => Env -> Label -> Abs -> Eff es Stk.Routine
stackifyAbs g lbl (MkAbs xs mtail e) =
Stk.MkRoutine lbl . buildBlock . preamble <$> stackify g e
where
preamble = Code (popArgs $ (mtail ^.. _Just) ++ xs)
popArgs :: List Name -> List Stk.Instr
popArgs = fmap (Stk.Pop . MkReg) . reverse
pushArgs :: List Name -> List Stk.Instr
pushArgs = _
data Env = MkEnv
-- | `bound` tracks the stack lifetime of bound variables.
{ bound :: List Name
-- | for each locally-bound continuation @k@, @liveness@ has an
-- entry @(k,ls)@ where @ls@ is the sequence of registers @k@
-- expects to find saved on the stack.
, liveness :: HashMap Label (List Name)
, tail :: Name
{
}
deriving (Show, Generic)
emptyEnv :: Env
emptyEnv = MkEnv
{ bound = mempty
, liveness = mempty
, tail = "halt"
{
}
stackifyProgram :: GenSym :> es => Program -> Eff es Stk.Program
stackifyProgram (MkProgram lam) = do
let g = emptyEnv
(_,p) <- runStackify $ emitRoutine =<< stackifyLambda g "start" lam
pure p
stackifyProgram
:: forall es. GenSym :> es
=> HoistedProgram -> Eff es Stk.Program
stackifyProgram p = p
& ifoldMapOf
((#bindings . itraversed)
<> (#body . to (H.singleton "start" . AbsLambda) . itraversed))
(\l -> Ap . stackifyBinding l)
& getAp
where
g = emptyEnv
stackifyBinding lbl ab =
Stk.MkProgram . H.singleton lbl <$> stackifyAbs @es g lbl ab
letfn :: Program
letfn = [cps|
(λ (start-ktail0)
(letrec ((lambda-body1
(λ (x lambda-tail2)
(prim (* x x) (κ (r3) (continue lambda-tail2 r3))))))
(letrec ((let-body6
(κ (square)
(letrec ((r4 (κ (x5) (continue start-ktail0 x5))))
(square 4 r4)))))
(continue let-body6 lambda-body1))))
|]
blah :: Program
blah = [cps|
(λ (ktail0)
(letrec ((fac (λ (n ktail)
(prim (zero? n)
(κ (x0)
(if x0
(continue ktail 1)
(prim (- n 1)
(κ (x1)
(letrec ((fac-k0
(κ (x2)
(prim (* n x2)
(κ (x3)
(continue ktail x3))))))
(fac x1 fac-k0))))))))))
(fac 6 halt)))
p :: HoistedProgram
p = [cps|
(letrec (($r12-code32
(κ (x13)
(prim
(get-env)
(κ (r12 start-ktail0)
(continue start-ktail0 x13)))))
($prim-k7-code22
(κ (r6)
(prim
(get-env)
(κ (prim-k7 lambda-tail1 n fac)
(prim
(make-shared-closure ($r8-code19) (lambda-tail1 n))
(κ (r8)
(fac r6 r8)))))))
($prim-k11-code16
(κ (r10)
(prim
(get-env)
(κ (prim-k11 lambda-tail1)
(continue lambda-tail1 r10)))))
($falsey-cont5-code26
(κ ()
(prim
(get-env)
(κ (truthy-cont4 falsey-cont5 lambda-tail1 n fac)
(prim
(make-shared-closure ($prim-k7-code22) (lambda-tail1 n fac))
(κ (prim-k7)
(prim (- n 1) prim-k7)))))))
($r8-code19
(κ (x9)
(prim
(get-env)
(κ (r8 lambda-tail1 n)
(prim
(make-shared-closure ($prim-k11-code16) (lambda-tail1))
(κ (prim-k11)
(prim (* n x9) prim-k11)))))))
($truthy-cont4-code25
(κ ()
(prim
(get-env)
(κ (truthy-cont4 falsey-cont5 lambda-tail1 n fac)
(continue lambda-tail1 1)))))
($fac-code35
(λ (n lambda-tail1)
(prim
(get-env)
(κ (fac)
(prim
(make-shared-closure ($prim-k3-code29) (lambda-tail1 n fac))
(κ (prim-k3)
(prim (zero? n) prim-k3)))))))
($prim-k3-code29
(κ (r2)
(prim
(get-env)
(κ (prim-k3 lambda-tail1 n fac)
(prim
(make-shared-closure
($truthy-cont4-code25 $falsey-cont5-code26)
(lambda-tail1 n fac))
(κ (truthy-cont4 falsey-cont5)
(if r2
truthy-cont4
falsey-cont5))))))))
(λ (start-ktail0)
(prim
(make-shared-closure ($fac-code35) ())
(κ (fac)
(prim
(make-shared-closure ($r12-code32) (start-ktail0))
(κ (r12)
(fac 20 r12)))))))
|]
+147 -26
View File
@@ -10,10 +10,12 @@ module Gyehoek.CPS.Syntax
, Kappa(..)
, Lambda(..)
, Exp(..)
, Kexp(..)
, ExpF(..)
, Name(..)
, Prim(..)
, Program(..)
, HoistedProgram(..)
, Lit(..)
, Imm(..)
, Obj(..)
@@ -39,6 +41,12 @@ module Gyehoek.CPS.Syntax
, Free(..)
, pattern ValLabel
, pattern ObjLabel
, absBody
, pattern MkAbs
, _MkAbs
, unhoist
, pattern ExpJump
, _ExpJump
)
where
@@ -56,6 +64,10 @@ import Gyehoek.Sexp (G, (:-)(..))
import qualified Data.InvertibleGrammar.Base as IG
import Gyehoek.GenSym (Gen)
import Data.String (IsString)
import Control.Applicative
import qualified Data.HashMap.Strict as H
import GHC.Records (HasField (..))
import Data.Bifunctor
-- Data types
@@ -96,6 +108,8 @@ pattern ObjLabel l = ObjImm (ImmLabel l)
-- | a heap object.
data Hob
= HobClosure { label :: Label, env :: List Obj }
-- should a continuation have a label, or an Obj?
| HobContinuation { cont :: Obj, stack :: NonEmpty (List Obj) }
| HobPair Obj Obj
deriving stock (Show, Generic, Data, Eq)
deriving anyclass (NFData)
@@ -111,24 +125,60 @@ data Abs
| AbsLambda Lambda
deriving (Show, Generic, Data, Eq)
pattern AbsKappa' :: [Name] -> Exp -> Abs
pattern AbsKappa' :: List Name -> Exp -> Abs
pattern AbsKappa' xs e = AbsKappa (MkKappa xs e)
pattern AbsLambda' :: [Name] -> Name -> Exp -> Abs
pattern AbsLambda' :: List Name -> Name -> Exp -> Abs
pattern AbsLambda' xs e ktail = AbsLambda (MkLambda xs e ktail)
{-# COMPLETE AbsKappa', AbsLambda' #-}
_MkAbs :: Iso' Abs (List Name, Maybe Name, Exp)
_MkAbs = iso
(\case
AbsKappa' xs e -> (xs,Nothing,e)
AbsLambda' xs ktail e -> (xs,Just ktail,e))
(\(xs,ktail,e) -> case ktail of
Just k -> AbsLambda' xs k e
Nothing -> AbsKappa' xs e)
pattern MkAbs :: List Name -> Maybe Name -> Exp -> Abs
pattern MkAbs xs ktail body <- (view _MkAbs -> (xs,ktail,body))
where MkAbs xs ktail body = review _MkAbs (xs,ktail,body)
{-# COMPLETE MkAbs #-}
_ExpJump :: Prism' Exp (Val, List Val, Maybe Kexp)
_ExpJump = prism'
(\(f,xs,ktail) -> case ktail of
Just k -> ExpApply f xs k
Nothing -> ExpContinue f xs)
\case
ExpApply f xs ktail -> Just (f,xs,Just ktail)
ExpContinue f xs -> Just (f,xs,Nothing)
_ -> Nothing
pattern ExpJump :: Val -> List Val -> Maybe Kexp -> Exp
pattern ExpJump f xs ktail <- (preview _ExpJump -> Just (f,xs,ktail))
where ExpJump f xs ktail = review _ExpJump (f,xs,ktail)
data Exp
= ExpPrim (Prim Val) Kappa
= ExpPrim (Prim Val) Kexp
| ExpLetRec { binders :: List (Name, Abs), body :: Exp }
| ExpContinue Val (List Val)
| ExpIf Val Exp Exp
| ExpIf Val Name Name
| ExpApply
{ op :: Val
, args :: List Val
, cont :: Name
, cont :: Kexp
}
deriving (Show, Generic, Data, Eq)
data Kexp
= KexpVar Name
| KexpKappa Kappa
deriving (Show, Generic, Data, Eq)
pattern Halt :: List Val -> Exp
pattern Halt xs = ExpContinue (ValLabel "halt") xs
@@ -143,6 +193,22 @@ data Program = MkProgram
}
deriving (Show, Generic, Data)
data HoistedProgram = MkHoistedProgram
{ bindings :: HashMap Label Abs
, body :: Lambda
}
deriving stock (Show, Generic, Data)
type instance Index HoistedProgram = Label
type instance IxValue HoistedProgram = Abs
instance Ixed HoistedProgram where ix j = #bindings . ix j
instance At HoistedProgram where at j = #bindings . at j
instance Each HoistedProgram HoistedProgram Abs Abs where
each = #bindings . each
makePrisms ''Kappa
makePrisms ''Exp
makeFieldsId ''Exp
@@ -166,6 +232,21 @@ _AbsLambda' = prism'
instance Plated Exp where plate = uniplate
absBody :: Lens' Abs Exp
absBody = lens
(\case
AbsLambda lam -> lam.body
AbsKappa kap -> kap.body)
(\cases
(AbsLambda lam) b -> AbsLambda $ lam & #body .~ b
(AbsKappa kap) b -> AbsKappa $ kap & #body .~ b)
unhoist :: HoistedProgram -> Program
unhoist p =
MkProgram $ p.body & body %~ ExpLetRec
(p ^.. #bindings . itraversed . withIndex
. to (\(MkLabel l, ab) -> (l,ab)))
-- DatumIso instances
@@ -208,6 +289,7 @@ instance S.DatumIso Reg where
instance S.DatumIso Hob where
datumIso = S.match
$ S.With (. closure)
$ S.With (. cont)
$ S.With (. conspair)
$ S.End
where
@@ -215,7 +297,13 @@ instance S.DatumIso Hob where
-- closures can be printed, but not parsed.
closure :: G (Datum :- t) (List Obj :- Label :- t)
closure = IG.Flip $ IG.PartialIso
(\(env:-code:-t) -> S.Unreadable "#<procedure>" :- t)
(\(env:-code:-t) -> S.Unreadable [i|\#<procedure $#{code}>|] :- t)
(const . Left $ mempty)
cont :: G (Datum :- t) (NonEmpty (List Obj) :- _ :- t)
cont = IG.Flip $ IG.PartialIso
(\(_ :- l :- t) ->
let x = S.encodeOrShow' @Text S.datumIso l
in S.Unreadable [i|\#<continuation #{x}>|] :- t)
(const . Left $ mempty)
instance S.DatumIso Lambda where
@@ -262,30 +350,51 @@ instance S.DatumIso Exp where
if_ = S.ifLike "if"
S.datumIso S.datumIso S.datumIso
app :: forall t.
G (Datum :- t) (Name :- ([Val] :- (Val :- t)))
app = S.list $ S.el (S.datumIso @Val)
-- >>> S.flipped Gyehoek.Datum.nonEmptyGrammar
G (Datum :- t) (Kexp :- List Val :- Val :- t)
app = S.list $
S.flipped (S.PartialIso
(\(S.MkListContext ctx :- t) ->
case ctx of
f:kexp:xs -> S.MkListContext (f : snoc xs kexp) :- t
_ -> error "unreachable")
(\(S.MkListContext ctx :- t) ->
case unsnoc ctx of
Just (f:xs,kexp) -> Right $ S.MkListContext (f:kexp:xs) :- t
_ -> Left $ S.expected "continuation arg"))
>>> S.el (S.datumIso @Val)
>>> S.el (S.datumIso @Kexp)
>>> S.rest (S.datumIso @Val)
-- >>> _
>>> S.onTail (S.flipped $ IG.PartialIso
(\(karg :- args :- op :- t) ->
(args ++ [ValVar karg]) :- op :- t)
(\(xs :- op :- t) -> case xs ^? _Snoc of
Just (args,preview #ValVar -> Just karg) ->
Right $ karg:- args :- op :- t
_ -> Left $ S.expected "continuation arg"
))
-- prim = S.headTagged2 "prim"
-- (primDatumIso id (S.datumIso @Val))
-- (S.datumIso @Kappa)
>>> S.onTail S.swap
prim = S.list $
S.el (S.decorate S.SynBuiltin >>> S.sym "prim")
>>> S.el (primDatumIso id (S.datumIso @Val))
>>> S.el S.datumIso
instance S.DatumIso Kexp where
datumIso = S.match
$ S.With (S.datumIso @Name >>>)
$ S.With (S.datumIso @Kappa >>>)
$ S.End
instance S.DatumIso Program where
datumIso = S.with \prog -> S.datumIso @Lambda >>> prog
-- the printed representation is pretty dishonest in its current
-- state. consider the following hoisted program:
--
-- (letrec ((k (κ () (continue start-ktail 123))))
-- (λ (start-ktail)
-- (continue k)))
--
-- here, `start-ktail` is bound in `k`, but the printed representation
-- fails to reflect that.
instance S.DatumIso HoistedProgram where
datumIso = S.with \prog ->
S.letLike "letrec"
(S.datumIso @Label) (S.datumIso @Abs) (S.datumIso @Lambda)
>>> S.onTail (S.iso H.fromList H.toList)
>>> prog
-- quasiquoters
@@ -298,6 +407,7 @@ instance CPS Kappa where toCPS = S.fromDatumUnsafe S.datumIso
instance CPS Lambda where toCPS = S.fromDatumUnsafe S.datumIso
instance CPS Abs where toCPS = S.fromDatumUnsafe S.datumIso
instance CPS Program where toCPS = S.fromDatumUnsafe S.datumIso
instance CPS HoistedProgram where toCPS = S.fromDatumUnsafe S.datumIso
cps :: S.QuasiQuoter
cps = S.makeSx' [| toCPS |]
@@ -320,7 +430,8 @@ class Free a where
freeWithBound :: HashSet Name -> a -> HashSet Name
freeWithBound bound = HS.fromList . freeWithBound' bound
-- | Free variables given in the order of their appearance.
-- | Free variables given in the same left-to-right order they
-- appear.
free' :: a -> List Name
free' = freeWithBound' mempty
@@ -330,11 +441,21 @@ instance Free Abs where
freeWithBound' bound (AbsKappa kap) = freeWithBound' bound kap
freeWithBound' bound (AbsLambda lam) = freeWithBound' bound lam
mif :: Alternative f => (a -> Bool) -> a -> f a
mif p a
| p a = pure a
| otherwise = empty
instance Free Kexp where
freeWithBound' bound = \case
KexpVar x -> mif (`notElem` bound) x
KexpKappa kap -> freeWithBound' bound kap
instance Free Exp where
freeWithBound' bound = \case
ExpPrim p k ->
p & toListOf (folded . #ValVar . filtered (`notElem` bound))
& (<> freeWithBound' bound k)
(p ^.. folded . #ValVar . filtered (`notElem` bound))
++ freeWithBound' bound k
ExpLetRec bs m ->
foldMapOf (each . _2) (freeWithBound' bound') bs
<> freeWithBound' bound' m
@@ -342,10 +463,10 @@ instance Free Exp where
ExpContinue k xs -> filter (`notElem` bound) ((k:xs) ^.. each . #ValVar)
ExpIf c t f ->
(c ^.. #ValVar . filtered (`notElem` bound))
<> freeWithBound' bound t <> freeWithBound' bound f
<> mif (`notElem` bound) t <> mif (`notElem` bound) f
ExpApply f xs k ->
(f:xs) ^.. (each . #ValVar . filtered (`notElem` bound))
<> (k ^.. filtered (`notElem` bound))
<> freeWithBound' bound k
instance Free Kappa where
freeWithBound' bound (MkKappa xs m) =
+34 -10
View File
@@ -1,5 +1,5 @@
module Gyehoek.Driver
(main, lower_e2e, convert_e2e, parse_e2e, readScm, eval_e2e)
(main, lower_e2e, convert_e2e, parse_e2e, readScm, eval_e2e, eval_cps_e2e, eval_cps2_e2e)
where
import Gyehoek.Options
@@ -35,6 +35,8 @@ import Control.Arrow ((>>>))
import Gyehoek.Prelude
import Gyehoek.Jalmot
import qualified Gyehoek.Sexp as S
import Gyehoek.CPS.Hoist (hoistProgram)
import Gyehoek.CPS.Contify (contifyProgram)
main :: IO ()
@@ -118,29 +120,35 @@ driver opts = do
hPutStrLn FS.stdout . view strict . pShowNoColor $ scm
cps <- convertProgram scm
when opts.dumpCPS do
hPutStrLn FS.stdout =<< S.encodeWith S.datumIso cps
S.writeDatum cps
closedCps <- closeProgram cps
when opts.dumpClosed do
hPutStrLn FS.stdout =<< S.encodeWith S.datumIso closedCps
S.writeDatum closedCps
hoistedCps <- hoistProgram closedCps
when opts.dumpHoisted do
S.writeDatum hoistedCps
-- contifiedCps <- contifyProgram hoistedCps
-- when opts.dumpContified do
-- hPutStrLn FS.stdout =<< S.encodeWith S.datumIso contifiedCps
let rt_is p = is (_Just . p) opts.runtime
dumpOrRun opts.dumpStackified (rt_is #Stackify)
(stackifyProgram closedCps)
(stackifyProgram hoistedCps)
(hPutStrLn FS.stdout <=< S.encodeDataWith S.dataIso)
(eval >=> fmap writeObj
>>> T.unwords
>>> hPutStrLn FS.stdout)
when (rt_is #HigherOrderCPS) do
CPS.evalProgram cps
>>= S.writeData
when (rt_is #CPS) do
closedCps
& CPS.evalProgram
& fmap writeObj
& T.unwords
& hPutStrLn FS.stdout
CPS.evalProgram closedCps
>>= S.writeData
-- dumpOrRun opts.inspectWasm (rt_is #Wasm)
-- (lowerProgram cps)
-- inspectWasm
-- (\wat -> withFile opts.output FS.WriteMode \h -> hPutStrLn h wat)
when opts.traceStackified do
stackifyProgram closedCps >>= traceEval
stackifyProgram hoistedCps >>= traceEval
parse_e2e :: FilePath -> IO Scm.Program
parse_e2e = runJalmotIO . runFileSystem . readScm
@@ -158,3 +166,19 @@ eval_e2e :: FilePath -> IO (List Obj)
eval_e2e fp = runJalmotIO . runFileSystem . runGenSym $ do
stk <- stackifyProgram <=< closeProgram <=< convertProgram <=< readScm $ fp
eval stk
eval_cps_e2e :: FilePath -> IO Text
eval_cps_e2e fp = runJalmotIO . runFileSystem . runGenSym $
readScm fp
>>= convertProgram
>>= closeProgram
>>= CPS.evalProgram
>>= pure . S.encodeOrShowData' S.dataIso
eval_cps2_e2e :: FilePath -> IO Text
eval_cps2_e2e fp = runJalmotIO . runFileSystem . runGenSym $
readScm fp
>>= convertProgram
-- >>= closeProgram
>>= CPS.evalProgram
>>= pure . S.encodeOrShowData' S.dataIso
+2
View File
@@ -31,6 +31,7 @@ data AJalmot
= ReaderError (ParseErrorBundle Text Void)
| GrammarError (Grammar.ErrorMessage Ann)
| VMError Text
| EvalError Text
deriving (Show, Generic, Data)
data AJalmotCS = MkAJalmotCS !CallStack !AJalmot
@@ -66,6 +67,7 @@ instance Exception AJalmot where
& layoutPretty defaultLayoutOptions
& renderString
VMError err -> [i|#{err}|]
EvalError err -> [i|#{err}|]
instance Exception AJalmotCS where
backtraceDesired = const False
+14 -4
View File
@@ -13,7 +13,7 @@ import Data.Foldable
import Gyehoek.Prelude hiding (argument)
data Runtime = Stackify | Wasm | CPS
data Runtime = Stackify | Wasm | CPS | HigherOrderCPS
deriving (Show, Generic, Eq)
data Language
@@ -29,7 +29,10 @@ data Options = MkOptions
, dumpCPS :: Bool
, dumpParsed :: Bool
, dumpStackified :: Bool
, dumpHoisted :: Bool
, dumpContified :: Bool
, traceStackified :: Bool
, noColour :: Bool
, runtime :: Maybe Runtime
, inspectWasm :: Bool
, output :: FilePath
@@ -51,7 +54,8 @@ runtimeValues = ["stackify","wasm","cps","none"]
runtimeReader = maybeReader \case
"stackify" -> Just (Just Stackify)
"wasm" -> Just (Just Wasm)
"cps" -> Just (Just CPS)
"cps1" -> Just (Just CPS)
("cps";"higher-order-cps") -> Just (Just HigherOrderCPS)
"none" -> Just Nothing
_ -> Nothing
@@ -61,14 +65,20 @@ parser = do
dumpCPS <- switch (long "dump-cps")
dumpStackified <- switch (long "dump-stackified")
dumpParsed <- switch (long "dump-parsed")
dumpHoisted <- switch (long "dump-hoisted")
dumpContified <- switch (long "dump-contified")
traceStackified <- switch (long "trace-stackified")
noColour <- switch . fold $
[ long "no-colour"
, long "no-color"
]
inspectWasm <- switch $ long "inspect-wasm" <> short 'p'
runtime <- option runtimeReader . fold $
[ long "runtime"
, short 'R'
, value (Just Stackify)
, value (Just HigherOrderCPS)
, completeWith runtimeValues
, showDefaultWith $ const "stackify"
, showDefaultWith $ const "higher-order-cps"
, metavar "RUNTIME"
]
sourceLanguage <- option languageReader . fold $
+8 -2
View File
@@ -81,11 +81,13 @@ data Prim e
| PrimZeroP e
| PrimNewline
| PrimMakeClosure { code :: e, env :: List e }
| PrimMakeSharedClosure { codes :: List e, env :: List e }
| PrimGetEnv
| PrimEnv
| PrimEnvRef Int
| PrimEnvCode e
| PrimCallCC e
| PrimCaptureCC
| PrimInvokeCC e (List e)
| PrimValues (List e)
| PrimCallWithValues e e
deriving stock (Show, Generic, Functor, Foldable, Traversable, Data, Eq)
@@ -168,11 +170,15 @@ primDatumIso namefn a = S.match
$ S.With (. ht1 "zero?")
$ S.With (. ht0 "newline")
$ S.With (. ht1' "make-closure")
$ S.With (. S.headTagged2 (namefn "make-shared-closure")
(S.list $ S.rest a)
(S.list $ S.rest a))
$ S.With (. ht0 "get-env")
$ S.With (. ht0 "env")
$ S.With (. S.headTagged1 (namefn "env-ref") S.int)
$ S.With (. ht1 "env-code")
$ S.With (. ht1 "call/cc")
$ S.With (. ht0 "capture/cc")
$ S.With (. ht1' "invoke/cc")
$ S.With (. ht0' "values")
$ S.With (. ht2 "call-with-values")
$ S.End
+37
View File
@@ -15,6 +15,7 @@ module Gyehoek.Sexp.Grammar
, encodeDataTest
, encodeDataTestColour
, encodeOrShow'
, encodeOrShowData'
, decodeDataWith
, encodeDataWith'
, decodeTest
@@ -28,6 +29,8 @@ module Gyehoek.Sexp.Grammar
, fromDatumUnsafe
, Control.Category.id
, fromDataUnsafe
, writeDatum
, writeData
)
where
@@ -45,6 +48,7 @@ import qualified Control.Category
import qualified Data.Vector as V
import Data.String (IsString (fromString))
import qualified Data.Text as T
import System.Environment (lookupEnv)
toDatum :: Jalmot :> es => DatumGrammar a -> a -> Eff es Datum
@@ -126,6 +130,39 @@ encodeOrShow' g x = fromString $
Left _ -> show x
Right t -> T.unpack t
encodeOrShow :: (IsString s, Show a) => DatumGrammar a -> a -> s
encodeOrShow g x = fromString $
case runPureEff . runJalmot . encodeWith g $ x of
Left _ -> show x
Right t -> T.unpack t
encodeOrShowData' :: (IsString s, Show a) => DataGrammar a -> a -> s
encodeOrShowData' g x = fromString $
case runPureEff . runJalmot . encodeDataWith' g $ x of
Left _ -> show x
Right t -> T.unpack t
encodeOrShowData :: (IsString s, Show a) => DataGrammar a -> a -> s
encodeOrShowData g x = fromString $
case runPureEff . runJalmot . encodeDataWith g $ x of
Left _ -> show x
Right t -> T.unpack t
useColour :: IO Bool
useColour = maybe True (const False) <$> lookupEnv "NO_COLOR"
writeDatum :: (Show a, DatumIso a, MonadIO m) => a -> m ()
writeDatum x = do
c <- liftIO useColour
let f = if c then encodeOrShow else encodeOrShow'
liftIO . TIO.putStrLn . f datumIso $ x
writeData :: (Show a, DataIso a, MonadIO m) => a -> m ()
writeData x = do
c <- liftIO useColour
let f = if c then encodeOrShowData else encodeOrShowData'
liftIO . TIO.putStrLn . f dataIso $ x
class DatumIso a where
datumIso :: DatumGrammar a
+1 -1
View File
@@ -9,7 +9,7 @@ module Gyehoek.Sexp.Grammar.Base
, DatumGrammar
, DataGrammar
, Grammar
, ListContext
, ListContext(..)
, (:-)((:-))
-- * lists
, list
+4 -2
View File
@@ -68,6 +68,7 @@ data Tail
| Call Int
| If Val Block Block
| Return Int
| CallCC
deriving stock (Show, Generic, Data)
deriving anyclass (NFData)
@@ -75,7 +76,7 @@ data Instr
= Pop Reg
| Push Val
| Load Reg Int
| Prim (Prim Val)
| Prim Reg (Prim Val)
deriving stock (Show, Generic, Data)
deriving anyclass (NFData)
@@ -98,7 +99,7 @@ instance S.DatumIso Instr where
$ S.With (S.headTagged1 "pop!" S.datumIso >>>)
$ S.With (S.headTagged1 "push!" S.datumIso >>>)
$ S.With (S.headTagged2 "load" S.datumIso S.datumIso >>>)
$ S.With (S.headTagged1 "prim" S.datumIso >>>)
$ S.With (S.headTagged2 "prim" S.datumIso S.datumIso >>>)
$ S.End
where
@@ -116,6 +117,7 @@ instance S.DatumIso Tail where
$ S.With (S.headTagged1 "call" S.datumIso >>>)
$ S.With (if_ >>>)
$ S.With (S.headTagged1 "return" S.datumIso >>>)
$ S.With (S.headTagged0 "call/cc" >>>)
$ S.End
where
-- if_ = S.ifLike "if" (S.datumIso @Val) S.datumIso S.datumIso
+31 -20
View File
@@ -112,36 +112,26 @@ vmerror = throwError . VMError
stepI :: Jalmot :> es => Env -> VM -> Instr -> Eff es VM
stepI e vm (Load r j) = do
x <- expectOf [i|object at index #{j}|] (activeFrame . ix j) vm
pure $ vm & #registers . at r ?~ x
stepI e vm (Push v) = traverseOf activeFrame push vm
where push xs = cons <$> evalVal e vm v <*> pure xs
stepI g vm (Prim p) = stepP g vm p
stepI e vm (Pop r) = case vm ^? activeFrame . _Cons of
Nothing -> vmerror "empty stack"
Just (x,xs) -> pure $ vm & #registers . at r ?~ x
& activeFrame .~ xs
stepI e vm ins = vmerror [i|unimplemented instruction: #{ins}|]
stepT :: Jalmot :> es => Env -> VM -> Tail -> Eff es VM
stepT g vm tc@(Call nargs) = do
(args,f,ret,frm) <- parseCall nargs (vm ^. activeFrame)
& expectOf [i|bad call: #{show tc}|] _Just
& expectOf [i|bad call: #{show tc}|] _Just
rt <- getRoutine g f
let newFrame = MkFrame $ args ++ [f,ret]
pure $ vm
& jumpToRoutine rt
& activeFrame .~ frm
& #stack %~ pushFrame newFrame
-- it is not essential we clear the registers, but it'll
-- make bugs more obvious.
& #registers .~ mempty
& #stack %~ \stk ->
case f of
ObjHob (HobContinuation {stack}) ->
coerce $ stack & _NonEmpty . _1 <>:~ (args ++ [f])
_ -> pushFrame newFrame stk
stepT g vm tc@(Return nret) = do
(xs,_) <- splitAtExact nret (vm ^. activeFrame . #locals)
@@ -179,6 +169,21 @@ stepT g vm (If c t f) = do
_ -> t
pure $ jumpToBlock branch vm
stepT g vm CallCC = do
(cc,withcc,frm) <- parseCallCC (vm ^. activeFrame)
& expectOf "bad call/cc" _Just
let stk = vm.stack & #frames . _NonEmpty . _1 .~ frm
let reified_cc = ObjHob $ HobContinuation cc (coerce stk)
let newFrame = MkFrame [reified_cc, withcc, cc]
rt <- getRoutine g withcc
pure $ vm
& jumpToRoutine rt
-- replace the active frame; don't push a new one.
& activeFrame .~ newFrame
-- it is not essential we clear the registers, but it'll make
-- bugs more obvious.
& #registers .~ mempty
stepP :: Jalmot :> es => Env -> VM -> Prim Val -> Eff es VM
stepP g vm p = traverse (evalVal g vm) p >>= \case
PrimZeroP x -> case x of
@@ -192,10 +197,6 @@ stepP g vm p = traverse (evalVal g vm) p >>= \case
case f of
ObjImm (ImmLabel l) -> ret1 . ObjHob $ HobClosure l env
_ -> vmerror [i|expected label, got #{f}|]
PrimEnvCode env ->
case env of
ObjHob (HobClosure l _) -> ret1 . ObjImm . ImmLabel $ l
_ -> vmerror [i|expected closure, got #{env}|]
PrimEnv -> do
x <- vm & expectOf "expected closure" (activeFrame . activeProcedure)
ret1 x
@@ -211,6 +212,10 @@ stepP g vm p = traverse (evalVal g vm) p >>= \case
PrimCdr x -> case x of
ObjHob (HobPair _ cdr) -> ret1 cdr
_ -> vmerror [i|expected pair, got ${x}|]
-- PrimCaptureCC -> do
-- label <- vm & expectOf [i|bad stack, no return addr|]
-- (activeFrame . returnAddress . #_ObjImm . #_ImmLabel)
-- ret1 . ObjHob $ HobContinuation { label }
x -> vmerror [i|unimplemented prim: #{p}|]
where
ret vs = pure $ vm & activeFrame . #locals <>:~ vs
@@ -239,6 +244,7 @@ jumpToRoutine rt vm = vm
getLabel :: Obj -> Maybe Label
getLabel = \case
ObjHob (HobClosure {label}) -> Just label
ObjHob (HobContinuation {cont}) -> getLabel cont
ObjImm (ImmLabel label) -> Just label
x -> Nothing
@@ -277,6 +283,11 @@ takeExact n xs = case compareLength xs n of
(EQ;GT) -> Just $ take n xs
LT -> Nothing
parseCallCC :: Frame -> Maybe (Obj, Obj, Frame)
parseCallCC frm = do
([cc,withcc],ys) <- splitAtExact 2 (frm ^. #locals)
pure (cc,withcc,MkFrame ys)
parseCall :: Int -> Frame -> Maybe (List Obj, Obj, Obj, Frame)
parseCall nargs frm = do
(xs,ys) <- splitAtExact (nargs+2) (frm ^. #locals)
BIN
View File
Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
((λ ()
(* 2 (call/cc
(λ (k)
(begin (k 6)
3))))))
+68 -46
View File
@@ -5,53 +5,75 @@ import Test.Tasty.HUnit
import Gyehoek.CPS.Syntax (cps, Obj(..), Hob(..), Imm(..))
import Gyehoek.CPS.Eval qualified as Sut
import Data.List (List)
import Test.Tasty.ExpectedFailure (ignoreTestBecause)
import Test.Tasty.ExpectedFailure (ignoreTestBecause, expectFail)
import System.Directory (listDirectory)
import Test.Tasty.Silver
import System.FilePath
import Control.Exception
import qualified Gyehoek.Driver as Driver
import Control.DeepSeq (($!!))
import System.Exit (ExitCode(..))
import qualified Data.Text as T
import Data.Function (applyWhen)
import Gyehoek.Prelude
test_cpsInterpreter =
ignoreTestBecause "i forgorrrr" $
testGroup "cps interpreter" $
[ primitives
, testCase "halt with constant" do
evalsTo [ObjImm (ImmInt 123)] [cps|
(continue halt 123)
|]
, testCase "identity cont" do
evalsTo [ObjImm (ImmInt 154)] [cps|
(letrec ((id (κ (x)
(continue halt x))))
(continue id 154))
|]
, testCase "identity function" do
evalsTo [ObjImm (ImmInt 456)] [cps|
(letrec ((id (λ (x ktail)
(continue ktail x))))
(id 456 halt))
|]
, testCase "square" do
evalsTo [ObjImm (ImmInt 81)] [cps|
(letrec ((square (λ (x ktail)
(prim (* x x)
(κ (r) (continue ktail r))))))
(square 9 halt))
|]
]
brokenEvalTests :: List String
brokenEvalTests =
[]
-- [ "adder"
-- , "apply2"
-- , "apply-twice"
-- , "arith"
-- , "begin-1"
-- , "callcc-constant"
-- , "callcc-discard"
-- , "callcc-early-exit-1"
-- , "callcc-early-exit-2"
-- , "callcc-early-exit-3"
-- , "callcc-early-exit-4"
-- , "callcc-early-exit-5"
-- , "callcc-early-exit-6"
-- , "callcc-nested-1"
-- , "callcc-nested-2"
-- , "complicated-1"
-- , "cons-1"
-- , "factorial"
-- , "false"
-- , "fn-of-fn"
-- , "if-false"
-- , "if-number"
-- , "if-true"
-- , "lambda"
-- , "letrec-fn"
-- , "let-fn"
-- , "lit-int"
-- , "square"
-- , "true"
-- ]
evalsTo :: HasCallStack => List Obj -> Sut.Exp -> Assertion
evalsTo rs e = Sut.evalExp e @?= rs
primitives = testGroup "primitives"
[ testGroup "arith"
[ testCase "basic 1" do
evalsTo [ObjImm (ImmInt 20)] [cps|
(prim (* 4 5)
(κ (x) (continue halt x)))
|]
, testCase "basic 2" do
evalsTo [ObjImm (ImmInt 35)] [cps|
(prim (* 2 16)
(κ (x) (prim (+ x 3)
(κ (r) (continue halt r)))))
|]
test_eval :: IO TestTree
test_eval = do
cs <- listDirectory "golden/exec"
<&> fmap ("golden/exec" </>)
pure $ testGroup "cps interpreter"
[ testGroup "higher-order" $ cpsCase Driver.eval_cps2_e2e <$> cs
, testGroup "first-order" $ cpsCase Driver.eval_cps_e2e <$> cs
]
]
maybeBroken name broken = applyWhen (name `elem` broken) expectFail
cpsCase :: (FilePath -> IO Text) -> FilePath -> TestTree
cpsCase f test =
maybeBroken testName brokenEvalTests $
goldenVsAction testName resultFile action printProcResult
where
testName = takeFileName test
resultFile = test </> "exec"
sourceFile = test </> "source.scm"
action = catch @SomeException
(do r <- f sourceFile
pure $!! ( ExitSuccess
, r
, "" ))
\e -> pure (ExitFailure 1, "", T.pack $ displayException e)
+76 -75
View File
@@ -10,86 +10,87 @@ import Gyehoek.GenSym (runGenSym)
import Effectful
import Gyehoek.Prelude
import Gyehoek.Jalmot
import Test.Tasty.ExpectedFailure (expectFail, ignoreTestBecause)
test_stackify =
[ trivialReturn
, tailCall
, prim
, condition
, procedure
]
-- test_stackify =
-- [ trivialReturn
-- , tailCall
-- , prim
-- , condition
-- , procedure
-- ]
evalsTo :: HasCallStack => List Obj -> Sut.Program -> Assertion
evalsTo rs e = runJalmotUnsafe (Stk.eval e') @?= rs
where
e' = e & Sut.stackifyProgram & runGenSym & runPureEff
-- evalsTo :: HasCallStack => List Obj -> Sut.Program -> Assertion
-- evalsTo rs e = runJalmotUnsafe (Stk.eval e') @?= rs
-- where
-- e' = e & Sut.stackifyProgram & runGenSym & runPureEff
trivialReturn = testGroup "trivial return"
[ testCase "return int" do
evalsTo [ObjImm (ImmInt 4)]
[cps|(λ (ktail) (continue ktail 4))|]
, testCase "return bool" do
evalsTo [ObjImm (ImmBool True)]
[cps|(λ (ktail) (continue ktail #t))|]
evalsTo [ObjImm (ImmBool False)]
[cps|(λ (ktail) (continue ktail #f))|]
]
-- trivialReturn = testGroup "trivial return"
-- [ testCase "return int" do
-- evalsTo [ObjImm (ImmInt 4)]
-- [cps|(λ (ktail) (continue ktail 4))|]
-- , testCase "return bool" do
-- evalsTo [ObjImm (ImmBool True)]
-- [cps|(λ (ktail) (continue ktail #t))|]
-- evalsTo [ObjImm (ImmBool False)]
-- [cps|(λ (ktail) (continue ktail #f))|]
-- ]
tailCall = testGroup "tail call"
[ testCase "square" do
evalsTo [ObjImm (ImmInt 16)] [cps|
(λ (ktail0)
(letrec ((square (λ (x ktail)
(prim (* x x)
(κ (x0) (continue ktail x0))))))
(square 4 halt)))
|]
]
-- tailCall = testGroup "tail call"
-- [ testCase "square" do
-- evalsTo [ObjImm (ImmInt 16)] [cps|
-- (λ (ktail0)
-- (letrec ((square (λ (x ktail)
-- (prim (* x x)
-- (κ (x0) (continue ktail x0))))))
-- (square 4 halt)))
-- |]
-- ]
prim = testGroup "prim"
[ testCase "multiply" do
evalsTo [ObjImm (ImmInt 20)]
[cps|(λ (ktail0)
(prim (* 4 5)
(κ (x) (continue ktail0 x))))|]
, testCase "add" do
evalsTo [ObjImm (ImmInt 9)]
[cps|(λ (ktail0)
(prim (+ 4 5)
(κ (x) (continue ktail0 x))))|]
-- , testGroup "call/cc"
-- [ testCase "trivial" do
-- evalsTo [ObjImm (ImmInt 123)]
-- [cps|(letrec ((f (λ (cc ktail) (continue cc 123))))
-- (prim (call/cc f)))|]
-- ]
]
-- prim = testGroup "prim"
-- [ testCase "multiply" do
-- evalsTo [ObjImm (ImmInt 20)]
-- [cps|(λ (ktail0)
-- (prim (* 4 5)
-- (κ (x) (continue ktail0 x))))|]
-- , testCase "add" do
-- evalsTo [ObjImm (ImmInt 9)]
-- [cps|(λ (ktail0)
-- (prim (+ 4 5)
-- (κ (x) (continue ktail0 x))))|]
-- -- , testGroup "call/cc"
-- -- [ testCase "trivial" do
-- -- evalsTo [ObjImm (ImmInt 123)]
-- -- [cps|(letrec ((f (λ (cc ktail) (continue cc 123))))
-- -- (prim (call/cc f)))|]
-- -- ]
-- ]
condition = testCase "if" do
evalsTo [ObjImm (ImmInt 123)]
[cps|(λ (ktail0)
(if #t (continue ktail0 123) (continue ktail0 456)))|]
evalsTo [ObjImm (ImmInt 456)]
[cps|(λ (ktail0)
(if #f (continue ktail0 123) (continue ktail0 456)))|]
-- condition = testCase "if" do
-- evalsTo [ObjImm (ImmInt 123)]
-- [cps|(λ (ktail0)
-- (if #t (continue ktail0 123) (continue ktail0 456)))|]
-- evalsTo [ObjImm (ImmInt 456)]
-- [cps|(λ (ktail0)
-- (if #f (continue ktail0 123) (continue ktail0 456)))|]
procedure = testGroup "procedure"
[ testCase "factorial" do
evalsTo [ObjImm (ImmInt 720)]
[cps|(λ (ktail0)
(letrec ((fac (λ (n ktail)
(prim (zero? n)
(κ (x0)
(if x0
(continue ktail 1)
(prim (- n 1)
(κ (x1)
(letrec ((fac-k0
(κ (x2)
(prim (* n x2)
(κ (x3)
(continue ktail x3))))))
(fac x1 fac-k0))))))))))
(fac 6 halt)))|]
]
-- procedure = testGroup "procedure"
-- [ testCase "factorial" do
-- evalsTo [ObjImm (ImmInt 720)]
-- [cps|(λ (ktail0)
-- (letrec ((fac (λ (n ktail)
-- (prim (zero? n)
-- (κ (x0)
-- (if x0
-- (continue ktail 1)
-- (prim (- n 1)
-- (κ (x1)
-- (letrec ((fac-k0
-- (κ (x2)
-- (prim (* n x2)
-- (κ (x3)
-- (continue ktail x3))))))
-- (fac x1 fac-k0))))))))))
-- (fac 6 halt)))|]
-- ]
+2 -2
View File
@@ -46,9 +46,9 @@ qq = testGroup "parser"
, testCase "application" do
assertEqual "" (Sut.ExpApply (Sut.ValVar "f")
[Sut.ValVar "x",Sut.ValVar "y"]
"k")
(Sut.KexpVar "k"))
[cps|(f x y k)|]
assertEqual "" (Sut.ExpApply (Sut.ValVar "f")
[] "k")
[] (Sut.KexpVar "k"))
[cps|(f k)|]
]
+3 -11
View File
@@ -29,16 +29,7 @@ brokenWasmTests =
brokenStackifyTests :: List String
brokenStackifyTests =
[ "callcc-early-exit-1"
, "callcc-early-exit-2"
, "callcc-early-exit-3"
, "callcc-early-exit-4"
, "callcc-early-exit-5"
, "callcc-early-exit-6"
, "callcc-nested-1"
, "callcc-nested-2"
, "callcc-discard"
, "callcc-constant"
[
]
test_root :: IO TestTree
@@ -49,7 +40,8 @@ test_root = do
testGroup "execution" <$> sequenceA
[ ignoreTestBecause "wasm codegen is on the backburner"
<$> wasmTests tests
, stackifyTests tests
, ignoreTestBecause "i'm killing myself"
<$> stackifyTests tests
]
maybeBroken name broken = applyWhen (name `elem` broken) expectFail
+2 -1
View File
@@ -8,12 +8,13 @@ import Gyehoek.Stack.VM qualified as Sut
import Data.List (List)
import Gyehoek.Jalmot
import Gyehoek.Prelude (i)
import Test.Tasty.ExpectedFailure (expectFail, ignoreTestBecause)
evalsTo :: List Obj -> Program -> Assertion
evalsTo rs p = runJalmotUnsafe (Sut.eval p) @?= rs
test_root = testGroup "stack machine"
test_root = ignoreTestBecause "i'm super-killing myself" $ testGroup "stack machine"
[ testCase "immediate halt" do
evalsTo [] [stkP|
(define $start