cli, cps interpreter, stack vm, closure-conversion, fixes, tests, LOL
build / build (push) Successful in 7m49s

This commit is contained in:
2026-08-19 01:32:48 -06:00
parent 94b1a5fb45
commit c5f9bf1850
23 changed files with 587 additions and 99 deletions
+3 -1
View File
@@ -4,4 +4,6 @@
(haskell-mode-buffer-apply-command "cabal-fmt"))
(add-hook 'before-save-hook #'apply-cabal-fmt-h nil t)
(add-to-list 'haskell-font-lock-quasi-quote-modes
'("cps" . scheme-mode)))))))
'("cps" . scheme-mode))
(add-to-list 'haskell-font-lock-quasi-quote-modes
'("scm" . scheme-mode)))))))
+51
View File
@@ -2,8 +2,59 @@
the closure-conversion phase makes closed-over variables explicit by addition of the primitive ~make-closure~, taking a code pointer (in the CPS language, bare lambda) and the environment.
nice testable properties of closure-converted code:
- code pointers only appear in function position
- no function has free variables
multiple ~env-ref~ calls could probably be replaced with a primitive that loads the entire environment at once, returning multiple variables.
* scratchpad
** example
#+caption: scheme source
#+begin_src scheme
(letrec ((curried-add (λ (n)
(λ (m)
(+ n m)))))
((curried-add 3) 4))
#+end_src
#+caption: cps
#+begin_src scheme
(letrec ((curried-add
(λ (n ktail0)
(letrec ((curried-add-in
(λ (m ktail1)
(prim (+ n m)
(κ (x0) (continue ktail1 x0))))))
(continue ktail0 curried-add-in)))))
(letrec ((k0 (κ (adder) (adder 4 halt))))
(curried-add 3 k0)))
#+end_src
#+caption: closure-converted
#+begin_src scheme
(letrec ((curried-add
(λ (n ktail0)
(letrec ((curried-add-in-code
(λ (env m ktail1)
(prim (env-ref 0 env)
(κ (n)
(prim (+ n m)
(κ (x0) (continue ktail1 x0))))))))
(prim (make-closure curried-add-in-code n)
(κ (curried-add-in)
(continue ktail0 curried-add-in)))))))
(letrec ((k0 (κ (adder-closure)
(prim (closure-code adder-closure)
(κ (adder)
(adder adder-closure 4 halt))))))
(curried-add 3 k0)))
#+end_src
** wasm
#+begin_src scheme
(letrec ((make-adder
(lambda (n)
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > 9
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > 456
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > 16
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > 16
+2
View File
@@ -0,0 +1,2 @@
(letrec ((square (λ (x) (* x x))))
(square 4))
+4
View File
@@ -54,6 +54,7 @@ library
exposed-modules:
Gyehoek.CPS.Close
Gyehoek.CPS.Convert
Gyehoek.CPS.Eval
Gyehoek.CPS.Lower
Gyehoek.CPS.Stackify
Gyehoek.CPS.Syntax
@@ -83,6 +84,7 @@ library
, megaparsec
, mtl
, optparse-applicative
, ordered-containers
, pretty-simple
, prettyprinter
, process
@@ -105,9 +107,11 @@ test-suite test
hs-source-dirs: test
main-is: Main.hs
other-modules:
Gyehoek.Test.CPS.Eval
Gyehoek.Test.CPS.Stackify
Gyehoek.Test.CPS.Syntax
Gyehoek.Test.Golden
Gyehoek.Test.Scheme.Syntax
Gyehoek.Test.Sexp
Gyehoek.Test.Stack.VM
+79 -9
View File
@@ -1,3 +1,4 @@
{-# LANGUAGE OverloadedLists #-}
module Gyehoek.CPS.Close
( closeProgram
) where
@@ -9,6 +10,12 @@ import Control.Monad ((>=>))
import Control.Lens
import Data.List.NonEmpty (NonEmpty)
import qualified Data.HashSet as HS
import qualified Data.Set.Ordered as O
import Data.Set.Ordered (OSet)
import Gyehoek.GenSym
import Data.String.Interpolate (i)
import Debug.Pretty.Simple
import Data.HashSet (HashSet)
cataM
@@ -16,14 +23,77 @@ cataM
=> (Base t a -> m a) -> t -> m a
cataM f = cata (sequenceA >=> f)
close :: Exp -> Exp
close = cata \case
ExpLetRecF {bindersF,bodyF} -> ExpLetRec binders bodyF
where
binders = bindersF & (each . _2 . _AbsLambda' . _3) %~ \e -> _
e -> embed e
close :: GenSym :> es => Exp -> Eff es Exp
-- let frees = freeWithBound' (HS.fromList $ ktail : bs) e'
close = transformM \case
closeProgram :: Program -> Eff es Program
closeProgram (MkProgram e) = pure . MkProgram . close $ e
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 for the sake of recursive
-- reverences.
-- env <- gensym' @Name "env"
let frees = freeWithBound' [f] lam
let m' = ifoldr
(\n x q -> [cps|(prim (env-ref #{f} #{n})
(κ (#{x}) #{q}))|])
m frees
pure [cps|
(letrec ((#{f_code} (λ (#{f} ##{bs} #{kb})
#{m'})))
(prim (make-closure ($ #{f_code}) ##{frees})
(κ (#{f}) #{e})))
|]
ExpApply f xs ktail -> do
code <- gensym' @Name "code"
pure [cps|
(prim (env-code #{f})
(κ (#{code})
(#{code} #{f} ##{xs} #{ktail})))
|]
e -> pure e
e -> error [i|unimplemented case: #{e}|]
-- lam@(ExpLambdaF bs m) -> do
-- env <- gensym' @Name "env"
-- let upvalBinds = free' (embed lam)
-- & itraversed %@~ \i x -> (x, [cps|(env-ref #{env} #{i})|])
-- let upvals = upvalBinds ^.. each . _1
-- pure [cps|
-- (make-closure (λ (#{env} ##{bs})
-- (let #{upvalBinds}
-- #{m}))
-- ##{upvals})
-- |]
-- ExpApplyF f xs ->
-- pure [scm|
-- (apply-closure #{f} ##{xs})
-- |]
closeProgram :: GenSym :> es => Program -> Eff es Program
closeProgram = traverseOf #body close
curriedadd :: Program
curriedadd = [cps|
(letrec ((curried-add
(λ (n ktail1)
(letrec ((curried-add-in
(λ (m ktail2)
(prim (+ n m)
(κ (x0) (continue ktail2 x0))))))
(continue ktail1 curried-add-in)))))
(letrec ((k0 (κ (adder) (adder 4 halt))))
(curried-add 5 k0)))
|]
square :: Program
square = [cps|
(letrec ((lambda-body0 (λ (x lambda-tail1)
(prim (* x x) (κ (r2) (continue lambda-tail1 r2))))))
(letrec
((r3 (κ (x4) (continue halt x4))))
(lambda-body0 5 r3)))
|]
+3 -2
View File
@@ -18,6 +18,7 @@ import Data.String.Interpolate (i)
import Data.Functor (unzip)
import Data.List (List)
import Prelude hiding (unzip)
import Debug.Pretty.Simple (pTraceShowMForceColor)
-- 뻘짓이어라
@@ -64,7 +65,7 @@ convert (Scm.ExpPrim p) k =
ExpPrim p' . MkKappa [r] <$> k (ValVar r)
convert (Scm.ExpLambda xs e) k = do
f <- gensym' "λ-body"
f <- gensym' "lambda-body"
lam <- convertLambda xs e
ke <- k $ ValVar f
pure [cps|
@@ -92,7 +93,7 @@ convert (Scm.ExpLet bs e) k =
let rhss = bs ^.. each . _2
in telescope (convert @es) rhss \rhss' -> do
e' <- convert e k
kbody <- gensym' @Name "letrec-body"
kbody <- gensym' @Name "let-body"
let bs' = bs ^.. each . _1
pure [cps|
(letrec ((#{kbody} (κ #{bs'} #{e'})))
+107
View File
@@ -0,0 +1,107 @@
{-# LANGUAGE ViewPatterns #-}
module Gyehoek.CPS.Eval
( evalProgram
, module Gyehoek.CPS.Syntax
) where
import Gyehoek.CPS.Syntax
import Data.String.Interpolate (i)
import Data.HashMap.Strict (HashMap)
import Control.Lens
import Data.Maybe (fromMaybe)
import Data.Generics.Labels ()
import GHC.Generics (Generic)
import Data.List (List)
import Text.Show.Functions ()
import qualified Data.HashMap.Strict as H
import Debug.Pretty.Simple (pTraceShowId)
import qualified Data.Text as T
data Code
= CodeKap (List Obj -> List Obj)
| CodeLam (List Obj -> Name -> List Obj)
deriving (Show, Generic)
data Env = MkEnv
{ vars :: HashMap Name Obj
, labels :: HashMap Name (Env, Abs)
}
deriving (Show, Generic)
eval :: Env -> Exp -> List Obj
eval g (Halt xs) = evalVal g <$> xs
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}|]
eval g (ExpApply ((^?! #ValVar) -> 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}|]
eval g (ExpLetRec [(b, ab)] e) = eval g' e
where g' = g & #labels . at b ?~ (g,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
_ -> 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
ValLit l -> ObjImm $ case l of
LitInt n -> ImmInt n
LitBool b -> ImmBool b
emptyEnv :: Env
emptyEnv = MkEnv
{ vars = mempty
, labels = H.singleton "halt" $
( emptyEnv
, AbsKappa' ["h0"] $ Halt [ValVar "h0"]
)
}
evalProgram :: Program -> List Obj
evalProgram (MkProgram e) = eval emptyEnv e
curriedadd :: Program
curriedadd = [cps|
(letrec ((curried-add
(λ (n ktail1)
(letrec ((curried-add-in
(λ (m ktail2)
(prim (+ n m)
(κ (x0) (continue ktail2 x0))))))
(continue ktail1 curried-add-in)))))
(letrec ((k0 (κ (adder) (adder 4 halt))))
(curried-add 5 k0)))
|]
idfn = [cps|
(letrec ((id (λ (x ktail)
(continue ktail x))))
(id 456 halt))
|] :: Program
+20 -15
View File
@@ -8,6 +8,7 @@ module Gyehoek.CPS.Stackify
import Gyehoek.CPS.Syntax
import Gyehoek.Stack.Syntax qualified as Stk
import Data.Sequence (Seq)
import Data.Sequence qualified as Seq
import Effectful
import Gyehoek.GenSym
import Effectful.Writer.Static.Shared
@@ -18,7 +19,7 @@ import GHC.Generics (Generic)
import Data.Foldable
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as H
import Data.List (List)
import Data.List (List, elemIndex)
import GHC.Exts (IsList(fromList))
@@ -48,12 +49,10 @@ stackify g (ExpLetRec [(f, kap@(AbsKappa' xs m))] e) = do
stackify g (ExpLetRec [(f, AbsLambda' xs k m)] e) = do
let vs = (k:xs) <&> \x -> (x, Stk.ValReg x)
lam_body <- gensym' "lambda-body"
m' <- stackify (g & #bound .~ H.fromList vs
& #bound . at f ?~ Stk.ValLabel lam_body
& #contStack %~ (k:)) m
tell [Stk.MkBlock lam_body xs . toList $ m']
stackify (g & #bound . at f ?~ Stk.ValLabel lam_body) e
tell [Stk.MkBlock f xs . toList $ m']
stackify g e
stackify g (ExpIf c t f) = do
t' <- stackify g t
@@ -70,16 +69,21 @@ stackify g (ExpApply f xs ktail) = do
ls = fold $ (k ^? #ValImm . #ImmLabel)
>>= \klbl -> g ^. #liveness . at klbl
-- this probably won't work for call/cc, for cps-converted code it'll
-- be fine i think. notice how, instead of calling `var g k`, we just
-- assume it's the return continuation on top of the stack.
stackify g (ExpContinue k xs) = do
ktail <- gensym' $ k ^. _Wrapped'
pure $
fromList [ Stk.PopCont "_" | _ <- takeWhile (/= k) g.contStack ]
<> [ Stk.PopCont ktail
, Stk.Call (Stk.ValReg ktail) (stackifyVal g <$> xs)
]
stackify g (ExpContinue k xs) =
-- return continuations require popping the stack. how do we know
-- when a continuation is a return continuation? is this a correct
-- test?
case elemIndex k g.contStack of
Nothing -> pure [ Stk.Call (Stk.ValLabel k) xs' ]
Just j -> do
ktail <- gensym' $ k ^. _Wrapped'
pure $
Seq.replicate j (Stk.PopCont "_")
<> [ Stk.PopCont ktail
, Stk.Call (Stk.ValReg ktail) (stackifyVal g <$> xs)
]
where xs' = stackifyVal g <$> xs
stackify g (ExpPrim p (MkKappa [x] e)) = do
e' <- stackify (g & #bound . at x ?~ Stk.ValReg x) e
@@ -91,6 +95,7 @@ stackifyVal :: Env -> Val -> Stk.Val
stackifyVal g = \case
ValLit (LitInt n) -> Stk.ValImm (ImmInt n)
ValLit (LitBool b) -> Stk.ValImm (ImmBool b)
ValImm imm -> Stk.ValImm imm
ValVar v -> var g v
v -> error [i|unimplemented val: #{v}|]
+78 -8
View File
@@ -4,6 +4,7 @@
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE DeriveAnyClass #-}
module Gyehoek.CPS.Syntax
( Val(..)
, Kappa(..)
@@ -14,6 +15,9 @@ module Gyehoek.CPS.Syntax
, Prim(..)
, Program(..)
, Lit(..)
, Imm(..)
, Obj(..)
, Hob(..)
, pattern Void
, pattern Halt
, pattern Halt1
@@ -34,6 +38,8 @@ module Gyehoek.CPS.Syntax
, Free(..)
, Vars(..)
, Subst(..)
, pattern ValLabel
, labelName -- don't like that this is part of the api
)
where
@@ -61,14 +67,41 @@ import Data.Monoid (Endo)
import Data.Containers.ListUtils (nubOrd)
import Data.Functor.Foldable.TH
import Data.Functor.Foldable (Recursive(..), Corecursive (..))
import Control.DeepSeq (NFData)
import qualified Gyehoek.Sexp as GS
import qualified Language.Sexp.Located as SL
import Data.Data.Lens (uniplate)
-- Data types
data Val
= ValVar Name
= ValImm Imm
| ValLit Lit
| ValVar Name
deriving (Show, Generic, Data, Eq)
pattern ValLabel :: Name -> Val
pattern ValLabel x = ValImm (ImmLabel x)
data Imm
= ImmInt Int
| ImmBool Bool
| ImmLabel Name
deriving stock (Show, Generic, Data, Eq)
deriving anyclass (NFData)
data Obj
= ObjImm Imm
| ObjHob Hob
deriving stock (Show, Generic, Data, Eq)
deriving anyclass (NFData)
-- | a heap object.
data Hob
= HobClosure { label :: Name, env :: List Obj }
deriving stock (Show, Generic, Data, Eq)
deriving anyclass (NFData)
data Kappa = MkKappa { binders :: List Name, body :: Exp }
deriving (Show, Generic, Data, Eq)
@@ -88,7 +121,7 @@ pattern AbsLambda' xs e ktail = AbsLambda (MkLambda xs e ktail)
data Exp
= ExpPrim (Prim Val) Kappa
| ExpLetRec { binders :: NonEmpty (Name, Abs), body :: Exp }
| ExpLetRec { binders :: List (Name, Abs), body :: Exp }
| ExpContinue Name (List Val)
| ExpIf Val Exp Exp
| ExpApply
@@ -139,15 +172,57 @@ _AbsLambda' = prism'
(\case AbsLambda' bs ktail e -> Just (bs,ktail,e)
_ -> Nothing)
instance Plated Exp where
plate = uniplate
-- plate k = \case
-- ExpPrim p kap -> ExpPrim p <$> body k kap
-- ExpLetRec bs e -> ExpLetRec <$> (each . _2 . body) k bs <*> k e
-- ExpContinue c xs -> pure $ ExpContinue c xs
-- ExpIf c t f -> ExpIf c <$> k t <*> k f
-- ExpApply f xs ktail -> pure $ ExpApply f xs ktail
-- SexpIso instances
instance S.SexpIso Val where
sexpIso = match
$ With (\var -> var . S.sexpIso)
$ With (\lit -> lit . S.sexpIso)
$ With (\imm -> imm . S.sexpIso)
$ With (\var -> var . S.sexpIso)
$ End
instance S.SexpIso Obj where
sexpIso = match
$ With (\imm -> imm . S.sexpIso)
$ With (\hob -> hob . S.sexpIso)
$ End
instance S.SexpIso Imm where
sexpIso = match
$ With (. S.int)
$ With (. GS.schemeBool)
$ With (. labelName)
$ End
labelName :: S.SexpGrammar Name
labelName = S.coproduct
[ S.sexpIso @Name >>> Gyehoek.Sexp.prismIso
(S.expected "label")
(prefixed @Name "$")
, S.list $ S.el (S.sym "$") >>> S.el (S.sexpIso @Name)
]
instance S.SexpIso Hob where
sexpIso = match
$ With (. closure)
$ End
where
-- closures can be printed, but not parsed.
closure :: S.Grammar S.Position (Sexp :- t) (List Obj :- Name :- t)
closure = IG.Flip $ IG.PartialIso
(\(env:-code:-t) -> SL.Modified SL.Hash [GS.sx|(#{code} ##{env})|] :- t)
(const . Left $ mempty)
instance S.SexpIso Lambda where
sexpIso = match
$ With (. lambda)
@@ -359,8 +434,3 @@ instance Scoped Exp where
class Subst a where
substWith :: (Name -> Maybe Val) -> a -> a
instance Subst Exp where
substWith f = go HS.empty where
go bound e = case scope e of
Use xs ss -> _
+42 -19
View File
@@ -19,7 +19,7 @@ import System.IO (Handle)
import System.IO qualified as IO
import Gyehoek.CPS.Convert
import Gyehoek.CPS.Lower
import Gyehoek.CPS.Syntax qualified as Cps
import Gyehoek.CPS.Eval qualified as CPS
import Control.Monad
import Text.Pretty.Simple (pShowNoColor)
import System.Process.Typed
@@ -33,8 +33,11 @@ import Text.Pretty.Simple (pShow)
import Gyehoek.Stack.VM (eval, writeObj, Obj)
import qualified Data.Text as T
import Data.List (List)
import Gyehoek.Stack.Syntax (encodeProgram)
import Gyehoek.Stack.Syntax qualified as Stk
import Effectful.Exception
import Gyehoek.CPS.Close (closeProgram)
import Control.Lens.Extras (is)
import Control.Arrow ((>>>))
main :: IO ()
@@ -98,6 +101,18 @@ inspectWasm wat = do
IO.hFlush (getStdin pager)
IO.hClose (getStdin pager)
dumpOrRun
:: Monad m
=> Bool -> Bool
-> m a
-> (a -> m ()) -> (a -> m ())
-> m ()
dumpOrRun dump run acquire do_dump do_run =
when (dump || run) do
x <- acquire
when dump (do_dump x)
when run (do_run x)
driver
:: (GenSym :> es, FileSystem :> es, IOE :> es)
=> Options -> Eff es ()
@@ -108,32 +123,40 @@ driver opts = do
cps <- convertProgram scm
when opts.dumpCPS do
hPutStrLn FS.stdout $ Sexp.encodePretty cps ^?! _Right
stk <- stackifyProgram cps
if opts.dumpStackified then do
hPutStrLn FS.stdout . encodeProgram $ stk
else if opts.stackify then do
eval stk & fmap writeObj
& T.unwords
& hPutStrLn FS.stdout
else do
wat <- lowerProgram cps
withFile opts.output FS.WriteMode \h ->
hPutStrLn h wat
when opts.inspectWasm do
inspectWasm wat
closedCps <- closeProgram cps
when opts.dumpClosed do
hPutStrLn FS.stdout $ Sexp.encodePretty closedCps ^?! _Right
let rt_is p = is (_Just . p) opts.runtime
dumpOrRun opts.dumpStackified (rt_is #Stackify)
(stackifyProgram closedCps)
(hPutStrLn FS.stdout . Stk.encodeProgram)
(eval >>> fmap writeObj
>>> T.unwords
>>> hPutStrLn FS.stdout)
dumpOrRun False (rt_is #CPS)
(pure closedCps)
(const $ pure ())
(CPS.evalProgram >>> fmap writeObj
>>> T.unwords
>>> hPutStrLn FS.stdout)
dumpOrRun opts.inspectWasm (rt_is #Wasm)
(lowerProgram cps)
inspectWasm
(\wat -> withFile opts.output FS.WriteMode \h -> hPutStrLn h wat)
parse_e2e :: FilePath -> IO Scm.Program
parse_e2e = runEff . runFileSystem . readScm
convert_e2e :: FilePath -> IO Cps.Program
convert_e2e = runEff . runFileSystem . runGenSym . (convertProgram <=< readScm)
convert_e2e :: FilePath -> IO CPS.Program
convert_e2e = runEff . runFileSystem . runGenSym
. (closeProgram <=< convertProgram <=< readScm)
lower_e2e :: FilePath -> IO Text
lower_e2e =
runEff . runFileSystem . runGenSym
. (lowerProgram <=< convertProgram <=< readScm)
. (lowerProgram <=< closeProgram <=< convertProgram <=< readScm)
eval_e2e :: FilePath -> IO (List Obj)
eval_e2e fp = runEff . runFileSystem . runGenSym $ do
stk <- stackifyProgram <=< convertProgram <=< readScm $ fp
stk <- stackifyProgram <=< closeProgram <=< convertProgram <=< readScm $ fp
pure . eval $ stk
+25 -5
View File
@@ -1,6 +1,7 @@
{-# LANGUAGE NoFieldSelectors #-}
module Gyehoek.Options
( Options(..)
, Runtime(..)
, parser
)
where
@@ -12,13 +13,18 @@ import System.FilePath
import qualified Data.HashSet as HS
import Control.Lens hiding (argument)
import GHC.Generics (Generic)
import Data.Foldable
data Runtime = Stackify | Wasm | CPS
deriving (Show, Generic)
data Options = MkOptions
{ dumpCPS :: Bool
{ dumpClosed :: Bool
, dumpCPS :: Bool
, dumpParsed :: Bool
, dumpStackified :: Bool
, stackify :: Bool
, runtime :: Maybe Runtime
, inspectWasm :: Bool
, output :: FilePath
, sourceFile :: FilePath
@@ -48,18 +54,32 @@ parseOutput = strOption
<> value "-"
)
parseRuntime = option rdr . fold $
[ long "runtime"
, short 'R'
, value Nothing
]
where
rdr = maybeReader \case
"stackify" -> Just (Just Stackify)
"wasm" -> Just (Just Wasm)
"cps" -> Just (Just CPS)
"none" -> Just Nothing
_ -> Nothing
parseDumpClosed = switch (long "dump-closed")
parseDumpCPS = switch (long "dump-cps")
parseDumpStackified = switch (long "dump-stackified")
parseStackify = switch (long "stackify")
parseDumpParsed = switch (long "dump-parsed")
parseInspectWasm = switch $ long "inspect-wasm" <> short 'p'
parser :: Parser Options
parser = MkOptions
<$> parseDumpCPS
<$> parseDumpClosed
<*> parseDumpCPS
<*> parseDumpParsed
<*> parseDumpStackified
<*> parseStackify
<*> parseRuntime
<*> parseInspectWasm
<*> parseOutput
<*> argument str (metavar "FILE")
+52 -4
View File
@@ -1,4 +1,5 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE TypeFamilies #-}
@@ -15,6 +16,7 @@ module Gyehoek.Scheme.Syntax
, Lit(..)
, Def(..)
, Exp(..)
, ExpF(..)
, Sexp(..)
, Program(..)
, CommandOrDef(..)
@@ -26,11 +28,15 @@ module Gyehoek.Scheme.Syntax
, scm
, readExp
, readProgram
, free'
, freeWithBound'
, freeO
, encodeProgram
)
where
import Data.Text (Text)
import Data.List (List)
import Data.List (List, intersperse)
import Language.SexpGrammar
( SexpIso(..), list, el, rest, sym, symbol )
import Language.SexpGrammar qualified as Sexp
@@ -51,13 +57,15 @@ import Data.Functor.Foldable.TH (makeBaseFunctor)
import Data.Functor.Foldable hiding (fold)
import Data.HashSet (HashSet)
import qualified Data.HashSet as HS
import Data.Foldable (fold)
import Data.Foldable (fold, toList)
import Language.Haskell.TH.Quote (QuasiQuoter)
import Effectful.FileSystem (runFileSystem)
import qualified Effectful.FileSystem.IO as FS
import qualified Data.Text.Encoding as T
import qualified Effectful.FileSystem.IO.ByteString as FB
import Control.DeepSeq (NFData)
import qualified Data.Set.Ordered as O
import Data.Sequence (Seq)
newtype Name = MkName { inner :: Text }
@@ -87,6 +95,7 @@ data Prim e
| PrimNewline
| PrimMakeClosure { code :: e, env :: List e }
| PrimEnvRef e Int
| PrimEnvCode e
| PrimCallCC e
deriving stock (Show, Generic, Functor, Foldable, Traversable, Data, Eq)
deriving anyclass (NFData)
@@ -112,8 +121,8 @@ data Def
deriving anyclass (NFData)
data Exp
= ExpLet (NonEmpty (Name, Exp)) Exp
| ExpLetRec (NonEmpty (Name, Exp)) Exp
= ExpLet (List (Name, Exp)) Exp
| ExpLetRec (List (Name, Exp)) Exp
| ExpPrim (Prim Exp)
| ExpBegin (List Exp)
| ExpIf Exp Exp Exp
@@ -180,6 +189,7 @@ primSexpIso namefn a = match
$ With (. nullop "newline")
$ With (. ht1' "make-closure")
$ With (. GS.headTagged2 (namefn "env-ref") a Sexp.int)
$ With (. ht1 "env-code")
$ With (. ht1 "call/cc")
$ End
where
@@ -253,6 +263,35 @@ instance SexpIso CommandOrDef where
scm :: QuasiQuoter
scm = GS.makeSx [|| GS.fromSexp @Exp ||]
freeWithBound' :: Foldable f => f Name -> Exp -> List Name
freeWithBound' bound = filter (`elem` bound) . free'
freeO :: Exp -> O.OSet Name
freeO = O.unbiased . cata \case
ExpVarF x -> O.Bias @O.L $ O.singleton x
ExpLetF bs e ->
foldOf (each . _2) bs
<> (e & coerced %~ deleteFromO (bs ^.. each . _1))
ExpLetRecF bs e ->
(foldOf (each . _2) bs & coerced %~ deleteFromO binds)
<> (e & coerced %~ deleteFromO binds)
where binds = bs ^.. each . _1
ExpLambdaF bs e -> e & coerced %~ deleteFromO bs
e -> fold e
free' :: Exp -> List Name
free' = toList @O.OSet . O.unbiased . cata \case
ExpVarF x -> O.Bias @O.L $ O.singleton x
ExpLetF bs e ->
foldOf (each . _2) bs
<> (e & coerced %~ deleteFromO (bs ^.. each . _1))
ExpLetRecF bs e ->
(foldOf (each . _2) bs & coerced %~ deleteFromO binds)
<> (e & coerced %~ deleteFromO binds)
where binds = bs ^.. each . _1
ExpLambdaF bs e -> e & coerced %~ deleteFromO bs
e -> fold e
free :: Exp -> HashSet Name
free = cata \case
ExpVarF x -> HS.singleton x
@@ -260,6 +299,9 @@ free = cata \case
ExpLambdaF binders vs -> deleteFrom binders vs
e -> fold e
deleteFromO :: (Foldable f, Ord a) => f a -> O.OSet a -> O.OSet a
deleteFromO = flip $ foldr O.delete
deleteFrom :: (Foldable f, Hashable a) => f a -> HashSet a -> HashSet a
deleteFrom = flip $ foldr HS.delete
@@ -292,3 +334,9 @@ readProgram fp = runFileSystem $
readExp :: IOE :> es => FilePath -> Eff es Exp
readExp fp = readProgram fp <&> (^?! #commandsAndDefs . _head . #Command)
encodeProgram :: Program -> Text
encodeProgram p = p.commandsAndDefs
& fmap ((^?! _Right) . GS.encodePretty)
& intersperse "\n\n"
& mconcat
+3 -6
View File
@@ -170,12 +170,12 @@ let_
:: Text
-> (forall t. Grammar Position (Sexp :- t) (a :- t))
-> (forall t. Grammar Position (Sexp :- t) (b :- t))
-> Grammar Position (Sexp :- (NonEmpty (a, b) :- t1)) t2
-> Grammar Position (Sexp :- (List (a, b) :- t1)) t2
-> Grammar Position (Sexp :- t1) t2
let_ kw name rhs e = list (el (sym kw) >>> el bindings >>> el e)
where
-- bindings :: Grammar Position (Sexp :- _) (List (_, _) :- _)
bindings = nonempty binding
bindings = list $ rest binding
binding :: Grammar Position (Sexp :- t) ((_, _) :- t)
binding = list (el name >>> el rhs) >>> pair
@@ -304,6 +304,7 @@ instance Each Sexp Sexp Sexp Sexp where
each k (SL.ParenList xs) = SL.ParenList <$> traverse k xs
each k (SL.BracketList xs) = SL.BracketList <$> traverse k xs
each k (SL.BraceList xs) = SL.BraceList <$> traverse k xs
-- each k (SL.Modified m e) = SL.Modified m <$> each k e
each _ e@(SL.Atom _; SL.Modified _ _) = pure e
stripLocation :: Sexp -> Sexp
@@ -348,10 +349,6 @@ unquoteSplicingRecursive xs = [| mconcat $(spans) |]
_ (UnquoteSplicing _) -> False
_ _ -> True
& fmap \case
-- [e@(Unquote _)] ->
-- case unquote e of
-- Just x -> [| [$(x)] |]
-- Nothing -> error "unreachable"
[UnquoteSplicing x] ->
[| spliceSexp $(varE (mkName (T.unpack x))) |]
es -> listE $ unquoteRecursive <$> es
+2 -24
View File
@@ -10,6 +10,7 @@ module Gyehoek.Stack.Syntax
, Lit(..)
, Obj(..)
, Imm(..)
, Hob(..)
, Prim(..)
, Name
, pattern ValLabel
@@ -34,6 +35,7 @@ import Gyehoek.Scheme.Syntax (Name(..), Lit(..), Prim(..))
import GHC.Exts (IsList(..))
import Data.List (intersperse)
import Control.DeepSeq (NFData)
import Gyehoek.CPS.Syntax (Imm(..), Obj(..), Hob(..), labelName)
newtype Program = MkProgram
@@ -79,18 +81,6 @@ data Val
pattern ValLabel :: Name -> Val
pattern ValLabel x = ValImm (ImmLabel x)
data Imm
= ImmInt Int
| ImmBool Bool
| ImmLabel Name
deriving stock (Show, Generic, Data, Eq)
deriving anyclass (NFData)
data Obj
= ObjImm Imm
deriving stock (Show, Generic, Data, Eq)
deriving anyclass (NFData)
--- sexp work
@@ -118,13 +108,6 @@ instance SexpIso Val where
$ With (S.sexpIso >>>)
$ End
instance SexpIso Imm where
sexpIso = match
$ With (S.sexpIso @Int >>>)
$ With (Gyehoek.Sexp.schemeBool >>>)
$ With (labelName >>>)
$ End
instance SexpIso Block where
sexpIso = with (block >>>)
where
@@ -143,8 +126,3 @@ regName :: S.SexpGrammar Name
regName = S.sexpIso @Name >>> Gyehoek.Sexp.prismIso
(S.expected "register")
(prefixed @Name "%")
labelName :: S.SexpGrammar Name
labelName = S.sexpIso @Name >>> Gyehoek.Sexp.prismIso
(S.expected "label")
(prefixed @Name "$")
+16 -2
View File
@@ -57,6 +57,18 @@ stepI e vm (Prim r p) = case evalVal e vm <$> p of
PrimMul x y -> arith_binop (*) x y
PrimSub x y -> arith_binop (-) x y
PrimDiv x y -> arith_binop div x y
PrimMakeClosure f env ->
case f of
ObjImm (ImmLabel l) -> ret . ObjHob $ HobClosure l env
_ -> error [i|expected label, got #{f}|]
PrimEnvCode env ->
case env of
ObjHob (HobClosure l _) -> ret . ObjImm . ImmLabel $ l
_ -> error [i|expected closure, got #{env}|]
PrimEnvRef env n ->
case env of
ObjHob (HobClosure _ xs) -> ret $ xs ^?! ix n
_ -> error [i|expected closure, got #{env}|]
x -> error [i|unimplemented prim: #{p}|]
where
ret v = vm & #registers . at r ?~ v
@@ -69,8 +81,8 @@ stepI e vm (Pop r) = case vm ^. #stack of
(x:xs) -> vm & #registers . at r ?~ x
& #stack .~ xs
stepI e vm (PopCont r) = case vm ^. #kstack of
[] -> error "empty stack"
stepI e vm ins@(PopCont r) = case vm ^. #kstack of
[] -> error [i|empty cont stack: #{ins}|]
(x:xs) -> vm & #registers . at r ?~ ObjImm (ImmLabel x)
& #kstack .~ xs
@@ -141,3 +153,5 @@ writeObj (ObjImm im) = case im of
ImmBool True -> "#t"
ImmBool False -> "#f"
ImmLabel l -> "#<procedure>"
writeObj (ObjHob h) = case h of
HobClosure code env -> "#<procedure>"
+56
View File
@@ -0,0 +1,56 @@
module Gyehoek.Test.CPS.Eval (root) where
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit
import Language.SexpGrammar ()
import Gyehoek.CPS.Syntax (cps, Obj(..), Hob(..), Imm(..))
import Gyehoek.CPS.Eval qualified as Sut
import Data.List (List)
root :: IO TestTree
root = pure . testGroup "cps interpreter" $
[ prim
, 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))
|]
]
evalsTo :: HasCallStack => List Obj -> Sut.Program -> Assertion
evalsTo rs p = Sut.evalProgram p @?= rs
prim = 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)))))
|]
]
]
+5 -4
View File
@@ -25,10 +25,11 @@ brokenWasmTests =
brokenStackifyTests :: List String
brokenStackifyTests =
[ "adder"
, "let-fn"
, "callcc-nested1" -- requires closure-conversion
]
[]
-- [ "adder"
-- , "let-fn"
-- , "callcc-nested1" -- requires closure-conversion
-- ]
root :: IO TestTree
root = do
+27
View File
@@ -0,0 +1,27 @@
module Gyehoek.Test.Scheme.Syntax (root) where
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit
import Language.SexpGrammar ()
import Gyehoek.Scheme.Syntax (scm)
import Gyehoek.Scheme.Syntax qualified as Sut
root :: IO TestTree
root = pure . testGroup "scheme syntax" $
[ freeTree
]
freeTree :: TestTree
freeTree = testGroup "free"
[ testCase "lambda" do
Sut.free' [scm|
(lambda (x y z k) (f x b a))
|] @=? ["f","b","a"]
, testCase "exp" do
Sut.free' [scm|
(letrec ((x (lambda (r) (f a y)))
(y (lambda (r b) (f b x))))
(g x y z))
|] @=? ["f","a","g","z"]
]
+4
View File
@@ -5,8 +5,10 @@ import Test.Tasty.Silver.Interactive (defaultMain)
import qualified Gyehoek.Test.Golden
import qualified Gyehoek.Test.Sexp
import qualified Gyehoek.Test.CPS.Syntax
import qualified Gyehoek.Test.Scheme.Syntax
import qualified Gyehoek.Test.Stack.VM
import qualified Gyehoek.Test.CPS.Stackify
import qualified Gyehoek.Test.CPS.Eval
main :: IO ()
@@ -17,7 +19,9 @@ root = testGroup "test" <$> sequenceA
[ Gyehoek.Test.Golden.root
, Gyehoek.Test.Sexp.root
, Gyehoek.Test.CPS.Syntax.root
, Gyehoek.Test.Scheme.Syntax.root
, Gyehoek.Test.Stack.VM.root
, Gyehoek.Test.CPS.Stackify.root
, Gyehoek.Test.CPS.Eval.root
]