30 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
msyds 276c2c1249 fix: closure-conversion of recursive functions
build / build (push) Successful in 1m28s
2026-08-30 02:12:16 -06:00
msyds 03797d573b mark broken callcc tests 2026-08-30 02:12:16 -06:00
msyds 0df7280236 deconstruct closures only at the bytecode level 2026-08-30 02:12:16 -06:00
msyds a09c00badd works albeit comically inefficiently 2026-08-30 02:12:16 -06:00
msyds e7c0ae9161 return, pushcall
build / build (push) Failing after 1m23s
2026-08-29 07:25:48 -06:00
msyds 5ccb3f3e1a register & label newtypes 2026-08-28 11:39:37 -06:00
msyds 9cb169f9b8 new instrs, tail-call 2026-08-28 11:39:37 -06:00
msyds c0a44c89b4 wip: stack frames 2026-08-28 11:39:37 -06:00
msyds 49292d5d01 wip: call/cc primitives 2026-08-28 11:39:37 -06:00
msyds 679cc076ad fix html output
build / build (push) Successful in 1m21s
2026-08-27 02:16:52 -06:00
msyds 8048573cd8 fix tests
build / build (push) Successful in 1m19s
2026-08-27 02:01:32 -06:00
msyds bbb5d6e99f stack vm throws jalmot
build / build (push) Failing after 1m40s
2026-08-27 01:45:10 -06:00
msyds 1f40120740 dotted list and such 2026-08-27 01:43:42 -06:00
msyds 196dd0d1b3 blah 2026-08-27 01:02:16 -06:00
msyds 009a154a6e rrrg 2026-08-27 01:01:17 -06:00
msyds 21b9f0e69d allow multiple values in cps conversion 2026-08-27 00:57:09 -06:00
msyds 87baed9efc pass continuations as arguments, use normal stack
build / build (push) Successful in 28s
2026-08-24 23:41:52 -06:00
msyds 796b967686 continue takes var 2026-08-24 23:41:52 -06:00
59 changed files with 2004 additions and 1151 deletions
+3
View File
@@ -9,6 +9,9 @@
. (progn (defun apply-cabal-fmt-h () . (progn (defun apply-cabal-fmt-h ()
(haskell-mode-buffer-apply-command "cabal-fmt")) (haskell-mode-buffer-apply-command "cabal-fmt"))
(add-hook 'before-save-hook #'apply-cabal-fmt-h nil t))))) (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 (nil
. ((eval . ((eval
. (progn (defun display-ansi () . (progn (defun display-ansi ()
+2 -1
View File
@@ -8,4 +8,5 @@ dist-newstyle
*.tix *.tix
.direnv .direnv
result result
play/ play/
trace.html
+31
View File
@@ -132,3 +132,34 @@ multiple ~env-ref~ calls could probably be replaced with a primitive that loads
$code) $code)
1)))) 1))))
#+end_src #+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
+100
View File
@@ -0,0 +1,100 @@
* rationale?
previously, the VM's stack was used for storing local variables across blocks; a Scheme procedure was split into several low-level routines (one for the procedure itself and one for each continuation), and the stack was used as a communication channel for these separate routines. in contrast, registers were local to each routine. this aligns with Wasm's model of functions pretty well, with Wasm /locals/ acting as the VM's /registers/, and a global mutable stack serving as fallback.
this worked quite well until it became time to implement ~call/cc~.
we are considering making the following alterations to the VM:
- explicitly segment the stack into frames.
- passing procedures and return addresses on the stack.
- new instructions:
+ ~(tail-call /n/)~
+ ~(call /n/)~
+ ~(load /r/ /n/)~
+ ~(return /n/)~
* scratchpad
#+begin_src scheme
(letrec ((fac (λ (n)
(if (zero? n)
1
(* n (fac (- n 1)))))))
(fac 3))
#+end_src
#+begin_src scheme
(λ (ktail0)
(letrec ((fac
(λ (n ktail1)
(zero?
n
(κ (x0)
(if x0
(continue ktail1 1)
(- n 1
(κ (x1)
(fac x1
(κ (x2)
(* n x2 ktail1)))))))))))
(fac 3)))
#+end_src
#+begin_example
n ktail1
| |
| | x0
| | |
| | ^
| |
| | x1
| | |
| | ^
| |
| | x2
| | |
^ ^ ^
#+end_example
#+begin_src scheme
(define $fac-c0
(pop! %x0 0) ; [ x0 $fac-c0 n $fac ktail1 ]
(if %x0 ; [ $fac-c0 n $fac ktail1 ]
;; every variable but `ktail1' is dead so we pop them all.
;; this probably means that `if' should take two continuations
;; rather than two blocks.
(then (push! 1) ; [ $fac-c0 n $fac ktail1 ]
(return 1)) ; [ 1 $fac-c0 n $fac ktail1 ]
(else (load %n 1) ; [ $fac-c0 n $fac ktail1 ]
(prim %x1 (- %n 1)) ; [ $fac-c0 n $fac ktail1 ]
(push! $fac-c1) ; [ $fac-c0 n $fac ktail1 ]
(push! $fac) ; [ $fac-c1 $fac-c0 n $fac ktail1 ]
(push! %x1) ; [ $fac $fac-c1 $fac-c0 n $fac ktail1 ]
(call 1) ; [ x1 $fac $fac-c1 $fac-c0 n $fac ktail1 ]
)))
(define $fac-c1
(pop! %x2) ; [ x2 $fac-c1 $fac-c0 n $fac ktail1 ]
(load %n 3) ; [ $fac-c1 $fac-c0 n $fac ktail1 ]
(prim %x3 (* %n %x2))
(push! %x3) ; [ $fac-c1 $fac-c0 n $fac ktail1 ]
(return 1) ; [ x3 $fac-c1 $fac-c0 n $fac ktail1 ]
)
(define $fac
(load %ktail1 2) ; [ n $fac ktail1 ]
(load %n 0) ; [ n $fac ktail1 ]
(push! $fac-c0) ; [ n $fac ktail1 ]
(push! $zero?) ; [ $fac-c0 n $fac ktail1 ]
(push! %n) ; [ $zero? $fac-c0 n $fac ktail1 ]
(call 1) ; [ n $zero? $fac-c0 n $fac ktail1 ]
)
(define $start
(push! $fac) ; [ $start ktail0 ]
(push! 3) ; [ $fac $start ktail0 ]
(tail-call 1) ; [ 3 $fac $start ktail0 ]
;; ↑ `tail-call' knows how to dispose of the caller's stack frame.
)
#+end_src
+1
View File
@@ -0,0 +1 @@
(begin 123 456) ; => 456
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > #t
@@ -0,0 +1,12 @@
(letrec ((iter (λ (n f)
(if (zero? n)
#f
(begin (f n)
(iter (- n 1) f))))))
(call/cc
(λ (k)
(iter 10 (λ (n)
;; i don't feel like implementing (= n 5) right now lmfao
(if (zero? (- n 5))
(k #t)
#f))))))
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > #t
@@ -0,0 +1,4 @@
(call/cc
(λ (k)
(begin (k #t)
#f)))
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > #t
@@ -0,0 +1,5 @@
;; confer ../callcc-early-exit-4
(letrec ((app (λ (f x)
(begin (f x)
#f))))
(call/cc (λ (k) (app k #t))))
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > #t
@@ -0,0 +1,5 @@
;; confer ../callcc-early-exit-3
(letrec ((app (λ (f x)
(begin (f x)
#f))))
(call/cc (λ (k) (app (λ (x) (k x)) #t))))
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > #t
@@ -0,0 +1,4 @@
(call/cc
(λ (k)
(begin ((λ () (k #t)))
#f)))
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > 12
@@ -0,0 +1,4 @@
(* 2 (call/cc
(λ (k)
(begin (k 6)
3))))
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > 456
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > 155
+10
View File
@@ -0,0 +1,10 @@
(letrec ((factorial (λ (n)
(if (zero? n)
1
(* n (factorial (- n 1)))))))
(letrec ((sum-of-factorials
(λ (n)
(if (zero? n)
0
(+ (factorial n) (sum-of-factorials (- n 1)))))))
(+ 2 (sum-of-factorials 5))))
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > (6 . 7)
+1
View File
@@ -0,0 +1 @@
(cons 6 7)
+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
+22 -11
View File
@@ -14,9 +14,9 @@ build-type: Simple
-- extra-source-files: -- extra-source-files:
flag doctest flag doctest
description: enable the doctest suite description: enable the doctest suite
default: True default: True
manual: True manual: True
common ghcstuffs-dev common ghcstuffs-dev
ghc-options: ghc-options:
@@ -58,14 +58,16 @@ library
-- cabal-fmt: expand src -- cabal-fmt: expand src
exposed-modules: exposed-modules:
Gyehoek.CPS.Close Gyehoek.CPS.Close
Gyehoek.CPS.Contify
Gyehoek.CPS.Convert Gyehoek.CPS.Convert
Gyehoek.CPS.Eval Gyehoek.CPS.Eval
Gyehoek.CPS.Lower Gyehoek.CPS.Hoist
Gyehoek.CPS.Stackify Gyehoek.CPS.Stackify
Gyehoek.CPS.Syntax Gyehoek.CPS.Syntax
Gyehoek.Driver Gyehoek.Driver
Gyehoek.GenSym Gyehoek.GenSym
Gyehoek.Jalmot Gyehoek.Jalmot
Gyehoek.Language
Gyehoek.Lift1 Gyehoek.Lift1
Gyehoek.Options Gyehoek.Options
Gyehoek.Prelude Gyehoek.Prelude
@@ -77,6 +79,7 @@ library
Gyehoek.Sexp.QQ Gyehoek.Sexp.QQ
Gyehoek.Sexp.Read Gyehoek.Sexp.Read
Gyehoek.Sexp.Syntax Gyehoek.Sexp.Syntax
Gyehoek.Stack.Lower
Gyehoek.Stack.Syntax Gyehoek.Stack.Syntax
Gyehoek.Stack.VM Gyehoek.Stack.VM
Gyehoek.Wasm Gyehoek.Wasm
@@ -99,6 +102,7 @@ library
, hashable , hashable
, invertible-grammar , invertible-grammar
, lens , lens
, lucid
, megaparsec , megaparsec
, mtl , mtl
, optparse-applicative , optparse-applicative
@@ -106,6 +110,7 @@ library
, pretty-simple , pretty-simple
, prettyprinter , prettyprinter
, prettyprinter-ansi-terminal , prettyprinter-ansi-terminal
, prettyprinter-lucid
, process , process
, recursion-schemes , recursion-schemes
, scientific , scientific
@@ -116,6 +121,7 @@ library
, typed-process , typed-process
, unordered-containers , unordered-containers
, vector , vector
, tardis
hs-source-dirs: src hs-source-dirs: src
default-language: GHC2024 default-language: GHC2024
@@ -162,13 +168,18 @@ test-suite test
-- https://github.com/martijnbastiaan/doctest-parallel/pull/66 -- https://github.com/martijnbastiaan/doctest-parallel/pull/66
test-suite doctest test-suite doctest
import: ghcstuffs, ghcstuffs-dev import: ghcstuffs, ghcstuffs-dev
type: exitcode-stdio-1.0 type: exitcode-stdio-1.0
hs-source-dirs: test hs-source-dirs: test
build-depends: base build-depends:
, base
, gyehoek
default-extensions: CPP default-extensions: CPP
main-is: doctest.hs main-is: doctest.hs
if flag(doctest) if flag(doctest)
build-depends: doctest-parallel >=0.1 build-depends: doctest-parallel >=0.1
else else
cpp-options: "-DGYEHOEK_NO_DOCTEST" cpp-options: -DGYEHOEK_NO_DOCTEST
+38 -24
View File
@@ -4,38 +4,52 @@ module Gyehoek.CPS.Close
) where ) where
import Gyehoek.CPS.Syntax import Gyehoek.CPS.Syntax
import Data.List (nub)
import Gyehoek.GenSym import Gyehoek.GenSym
import Gyehoek.Prelude 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 genCodeName :: GenSym :> es => Name -> Eff es Name
close = transformM \case genCodeName f = gensym' @Name $ f ^. _Wrapped' . to (<> "-code")
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 = 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 bindEnv :: List Name -> Exp -> Exp
code <- gensym' @Name "code" 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| pure [cps|
(prim (env-code #{f}) (letrec #{bs'}
(κ (#{code}) (prim (make-shared-closure #{codes} #{frees})
(#{code} #{f} ##{xs} #{ktail}))) (κ #{boundNames}
#{e})))
|] |]
e -> pure e e -> pure e
close :: forall es. GenSym :> es => Exp -> Eff es Exp
close = transformM close1
closeProgram :: GenSym :> es => Program -> Eff es Program closeProgram :: GenSym :> es => Program -> Eff es Program
closeProgram = traverseOf #body close 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'
+51 -35
View File
@@ -12,6 +12,7 @@ import Data.List.NonEmpty (NonEmpty((:|)))
import Control.Monad.Cont qualified as Cont import Control.Monad.Cont qualified as Cont
import qualified Data.List.NonEmpty as NE import qualified Data.List.NonEmpty as NE
import Gyehoek.Prelude import Gyehoek.Prelude
import Debug.Pretty.Simple
-- 뻘짓이어라 -- 뻘짓이어라
@@ -23,66 +24,78 @@ telescope f = Cont.runCont . traverse (Cont.cont . f)
one :: a -> List a
one a = [a]
oneOrUndefined :: List Val -> Val
oneOrUndefined = \case
[x] -> x
_ -> ValImm ImmUndefined
convert1 :: (GenSym :> es) => Scm.Exp -> (Val -> Eff es Exp) -> Eff es Exp
convert1 e k = convert e (k . oneOrUndefined)
-- | Transform an expression with a meta-continuation. -- | Transform an expression with a meta-continuation.
convert convert
:: forall es. (GenSym :> es) :: forall es. (GenSym :> es)
=> Scm.Exp -> (Val -> Eff es Exp) -> Eff es Exp => Scm.Exp -> (List Val -> Eff es Exp) -> Eff es Exp
convert (Scm.ExpVar x) k = k $ ValVar x convert (Scm.ExpVar x) k = k [ValVar x]
convert (Scm.ExpLit l) k = k . ValImm $ case l of convert (Scm.ExpLit l) k = k . one . ValImm $ case l of
LitInt n -> ImmInt n LitInt n -> ImmInt n
LitBool b -> ImmBool b LitBool b -> ImmBool b
_ -> _ _ -> _
-- special case: call/cc is desugared during cps-conversion...
convert (Scm.ExpPrim (PrimCallCC withcc)) k = do
convert withcc \withcc' -> do
cc <- gensym' @Name "cc"
r <- gensym' "r"
m <- k $ 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 = convert (Scm.ExpPrim p) k =
telescope (convert @es) p \p' -> do telescope (convert1 @es) p \p' -> do
r <- gensym' "r" r_l <- gensym' "r"
ExpPrim p' . MkKappa [r] <$> k (ValVar 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 convert (Scm.ExpLambda xs e) k = do
f <- gensym' "lambda-body" f <- gensym' "lambda-body"
lam <- convertLambda xs e lam <- convertLambda xs e
ke <- k $ ValVar f ke <- k [ValVar f]
pure [cps| pure [cps|
(letrec ((#{f} #{lam})) (letrec ((#{f} #{lam}))
#{ke}) #{ke})
|] |]
convert (Scm.ExpApply f xs) k = convert (Scm.ExpApply f xs) k =
telescope (convert @es) (f:|xs) \(f':|xs') -> do telescope (convert1 @es) (f:|xs) \(f':|xs') -> do
r <- gensym' "r" r <- gensym' @Name "r"
x <- gensym' "x" x <- gensym' "x"
m <- k (ValVar x) m <- k [ValVar x]
pure $ ExpLetRec [(r, AbsKappa' [x] m)] $ ExpApply f' xs' r pure $ ExpLetRec [(r, AbsKappa' [x] m)] $
ExpApply f' xs' (KexpVar r)
convert (Scm.ExpBegin xs) k = _ convert (Scm.ExpBegin xs) k = telescope (convert @es) xs (k . NE.last)
convert (Scm.ExpIf c t f) k = convert (Scm.ExpIf c t f) k =
convert c \c' -> convert1 c \c' -> do
ExpIf c' <$> convert t k <*> convert f k 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 -- let-bindings are desugared into continuation calls whose parameters
-- are the left-hand sides and whose arguments are the right-hand -- are the left-hand sides and whose arguments are the right-hand
-- sides. -- sides.
convert (Scm.ExpLet bs e) k = convert (Scm.ExpLet bs e) k =
let rhss = bs ^.. each . _2 let rhss = bs ^.. each . _2
in telescope (convert @es) rhss \rhss' -> do in telescope (convert1 @es) rhss \rhss' -> do
e' <- convert e k e' <- convert e k
kbody <- gensym' @Name "let-body" kbody <- gensym' @Name "let-body"
let bs' = bs ^.. each . _1 let bs' = bs ^.. each . _1
@@ -105,12 +118,15 @@ convertLambda
=> List Name -> Scm.Exp -> Eff es Lambda => List Name -> Scm.Exp -> Eff es Lambda
convertLambda bs m = do convertLambda bs m = do
ktail <- gensym' "lambda-tail" ktail <- gensym' "lambda-tail"
m' <- convert m $ pure . ExpContinue ktail . (:[]) m' <- convert1 m $ pure . ExpContinue (ValVar ktail) . (:[])
pure [cps|(λ (##{bs} #{ktail}) #{m'})|] pure [cps|(λ (##{bs} #{ktail}) #{m'})|]
convertProgram :: forall es. (GenSym :> es) => Scm.Program -> Eff es Program convertProgram :: forall es. (GenSym :> es) => Scm.Program -> Eff es Program
convertProgram p = convertProgram p = do
MkProgram <$> telescope (convert @es) (p ^.. each . _Left) (pure . Halt) ktail <- gensym' "start-ktail"
m <- telescope (convert1 @es) (p ^.. each . _Left)
(pure . ExpContinue (ValVar ktail))
pure . MkProgram $ MkLambda [] ktail m
convertExp :: forall es. (GenSym :> es) => Scm.Exp -> Eff es Exp convertExp :: forall es. (GenSym :> es) => Scm.Exp -> Eff es Exp
convertExp e = convert e (pure . Halt1) convertExp e = convert e (pure . Halt)
+240 -60
View File
@@ -1,79 +1,259 @@
{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE OverloadedLists #-}
module Gyehoek.CPS.Eval module Gyehoek.CPS.Eval
( evalProgram ( evalProgram
, module Gyehoek.CPS.Syntax , module Gyehoek.CPS.Syntax
, evalExp
, eGrammar
) where ) where
import Gyehoek.CPS.Syntax import Gyehoek.CPS.Syntax hiding (Hob(..), Obj(..), cont)
import Control.Lens import Gyehoek.Sexp qualified as S
import Control.Lens hiding (assign)
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Text.Show.Functions () import Text.Show.Functions ()
import qualified Data.HashMap.Strict as H 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 newtype Loc = MkLoc { getLoc :: Int }
{ vars :: HashMap Name Obj deriving stock (Generic, Data)
, labels :: HashMap Name (Env, Abs) 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) = emptyStore :: Store
case g ^. #labels . at k of emptyStore = MkStore
Just (h, AbsKappa' bs m) -> eval h' m { nextLoc = MkLoc 0
where h' = h & #vars <>~ envOfBinds bs (evalVal g <$> xs) , heap = mempty
_ -> error [i|not a kappa: #{k}|] }
eval g (ExpApply ((^?! #ValVar) -> f) xs ktail) = newtype Env = MkEnv { getEnv :: HashMap Name Loc }
case g ^?! #labels . at f of deriving stock (Show, Generic, Data)
Just (h,AbsLambda' bs kb m) -> eval h' m deriving newtype (Semigroup, Monoid)
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
emptyEnv :: Env emptyEnv :: Env
emptyEnv = MkEnv emptyEnv = mempty
{ 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"
( emptyEnv
, AbsKappa' ["h0"] $ Halt [ValVar "h0"]
)
}
evalProgram :: Program -> List Obj type instance Index Env = Name
evalProgram (MkProgram e) = eval emptyEnv e type instance IxValue Env = Loc
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}
-325
View File
@@ -1,325 +0,0 @@
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE MultilineStrings #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE RecursiveDo #-}
{- HLINT ignore "Use camelCase" -}
module Gyehoek.CPS.Lower
(lower, lowerProgram) where
import Gyehoek.CPS.Syntax
import Data.Vector.Strict (Vector)
import Control.Lens hiding (op)
import Numeric.Natural
import qualified Data.Vector.Strict as V
import Gyehoek.Wasm qualified as Wasm
import Gyehoek.Wasm hiding (Expr)
import Control.Monad.Fix
import Data.Text qualified as T
import Data.Foldable (fold)
import Gyehoek.Jalmot
import Gyehoek.Sexp qualified as S
import Gyehoek.Prelude
data Env = MkEnv
{ vars :: Vector Name
, kvars :: Vector Name
}
deriving (Show, Generic)
type instance Index Env = Natural
type instance IxValue Env = Name
instance Ixed Env where
ix i = #vars . ix (fromIntegral i)
tonat :: Integral a => a -> Natural
tonat = fromIntegral
-- | @makeSmallFixnum@ emits an expression injecting the i32 on top
-- of the stack into the SCM unitype.
makeSmallFixnum :: Wasm.Expr
makeSmallFixnum = [expr|
(@gyehoek "construct small fixnum")
(i32.const 1)
i32.shl
ref.i31
|]
getArgRegister :: Natural -> S.Datum
getArgRegister n = S.Symbol [i|$arg#{n}|]
-- | Given an expression @e@ leaving a @ref eq@ atop the stack,
-- @pushArg rt n e@ sets the nth slot of the arg-passing array to the
-- result of @e@.
pushArg :: Natural -> Wasm.Expr -> Wasm.Expr
pushArg n e = [expr|
(@gyehoek begin pushArg)
##{e}
(global.set #{reg})
(@gyehoek end pushArg)
|]
where reg = getArgRegister n
-- | Pop the nth arg from the arg-passing array onto the stack.
popArg :: Natural -> Wasm.Expr
popArg n = [expr|
(@gyehoek begin popArg)
(global.get #{reg})
ref.as_non_null
(@gyehoek end popArg)
|]
where reg = getArgRegister n
lowerVal :: (HasCallStack, GenMod :> es) => Env -> Val -> Eff es Wasm.Expr
lowerVal g (ValImm imm) =
pure $ case imm of
ImmInt n -> [expr|
(i32.const #{n})
##{makeSmallFixnum}
|]
ImmBool b -> [expr|
(i32.const #{b'})
ref.i31
|]
where b' :: Int = if b then 0b11 else 0b01
_ -> _
lowerVal g (ValVar x) = do
pure [expr|(global.get #{l})|]
where
l = getArgRegister . fromIntegral . succ $ V.elemIndex x g.vars ^?! _Just
lower' :: (GenMod :> es) => Env -> Exp -> Eff es Wasm.Expr
lower' g (Halt [v]) = do
arg <- pushArg 0 <$> lowerVal g v
pure [expr|
##{arg}
(return_call $halt (i32.const 1))
|]
lower' g e@(ExpPrim p k) =
case p of
PrimAdd x y -> lowerBinOp "i32.add" g x y k
PrimMul x y -> lowerBinOp "i32.mul" g x y k
lower' g (ExpIf c t f) = do
c' <- lowerVal g c
t' <- lower' g t
f' <- lower' g f
pure [expr|
##{c'}
(call $gh-truthy?)
(if (then ##{t'})
(else ##{f'}))
|]
lower' g (ExpLetRec [(r,AbsKappa kap)] e) = do
idx <- lowerKappa g kap
let g' = g & #kvars <>~ [r]
e' <- lower' g' e
pure [expr|
(@gyehoek "push cont" :idx #{idx})
(array.set $cont-stack-type
(global.get $cont-stack)
(global.get $cont-stack-top)
(ref.func #{idx}))
(global.set $cont-stack-top
(i32.add (global.get $cont-stack-top)
(i32.const 1)))
##{e'}
|]
lower' g (ExpLetRec [(r,AbsLambda lam)] e) = do
idx <- lowerLambda g lam
let g' = g & #vars <>~ [r]
let n = succ $ length g.vars
e' <- lower' g' e
let reg = getArgRegister . fromIntegral $ n
pure [expr|
(i32.const 0)
(ref.func #{idx})
(struct.new $closure)
(global.set #{reg})
##{e'}
|]
lower' g e@(ExpApply f xs ktail) = do
let nargs = length xs
f' <- lowerVal g f
let l = succ $ V.elemIndex ktail g.kvars ^?! _Just
args <- fold <$>
itraverse (\i -> fmap (pushArg $ tonat i) . lowerVal g) xs
pure [expr|
(@gyehoek "load args")
##{args}
(i32.const 1)
##{f'}
(ref.cast (ref $closure))
(struct.get $closure $code)
(return_call_ref $cont-type)
(@gyehoek todo
(f' ##{f'})
(ktail #{l}))
|]
lower' g e@(ExpContinue k xs) = do
let nargs = length xs
args <- fold <$>
itraverse (\i -> fmap (pushArg $ tonat i) . lowerVal g) xs
pure [expr|
(@gyehoek "push args")
##{args}
(@gyehoek "nargs")
(i32.const #{nargs})
(@gyehoek "pop cont stack")
(global.get $cont-stack-top)
(i32.const #{l})
i32.sub
(global.set $cont-stack-top)
(global.get $cont-stack)
(global.get $cont-stack-top)
(array.get $cont-stack-type)
ref.as_non_null
(return_call_ref $cont-type)
|]
where
l = succ $ V.elemIndex k g.kvars ^?! _Just
lower' g e = error . S.encodeOrShow' S.datumIso $ e
lowerKappa :: GenMod :> es => Env -> Kappa -> Eff es Idx
lowerKappa g e@(MkKappa xs m) = do
let g' = g & #vars <>~ V.fromList xs
m' <- lower' g' m
idx <- Wasm.defineFunction [wat|
(func (param i32)
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
##{m'})
|]
Wasm.emit [wats|(elem declare funcref (ref.func #{idx}))|]
pure idx
lowerLambda :: GenMod :> es => Env -> Lambda -> Eff es Idx
lowerLambda g e@(MkLambda xs ktail m) = do
let g' = g & #vars .~ V.fromList xs
& #kvars <>~ [ktail]
m' <- lower' g' m
idx <- Wasm.defineFunction [wat|
(func (param i32)
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
##{m'})
|]
Wasm.emit [wats|(elem declare funcref (ref.func #{idx}))|]
pure idx
lowerBinOp
:: (GenMod :> es)
=> Text -> Env -> Val -> Val -> Kappa -> Eff es Wasm.Expr
lowerBinOp op g x y (MkKappa [r] e) = do
let op' = S.Symbol op
let g' = g & #vars <>~ [r]
let n = succ $ length (g ^. #vars)
let reg = getArgRegister . fromIntegral $ n
x' <- lowerVal g x
y' <- lowerVal g y
e' <- lower' g' e
pure [expr|
##{x'}
(i31.get_s (ref.cast (ref i31)))
(i32.const 1)
i32.shr_u
##{y'}
(i31.get_s (ref.cast (ref i31)))
(i32.const 1)
i32.shr_u
#{op'}
##{makeSmallFixnum}
(global.set #{reg})
##{e'}
|]
emitRuntime :: GenMod :> es => Eff es ()
emitRuntime = mfix \runtime -> do
Wasm.defineFunctions [wats|
(import "gyehoek" "write" (func $gh-write (param (ref eq))))
(import "gyehoek" "truthy?" (func $gh-truthy? (param (ref eq))
(result i32)))
|]
-- cont stack
Wasm.defineTypes [wats|
(type $heap-object (sub (struct (field $hash (mut i32)))))
(type $cont-type (func (param i32)))
(type $cont-stack-type (array (mut (ref null $cont-type))))
(type $closure (sub $heap-object
(struct (field $hash (mut i32))
(field $code (ref $cont-type)))))
|]
Wasm.defineGlobals [wats|
(global $cont-stack-top (mut i32) (i32.const 0))
(global $cont-stack (ref $cont-stack-type)
(array.new_default $cont-stack-type (i32.const 128)))
|]
-- arg registers
Wasm.defineGlobals [wats|
(global $arg0 (mut (ref null eq)) (ref.null eq))
(global $arg1 (mut (ref null eq)) (ref.null eq))
(global $arg2 (mut (ref null eq)) (ref.null eq))
(global $arg3 (mut (ref null eq)) (ref.null eq))
(global $arg4 (mut (ref null eq)) (ref.null eq))
(global $arg5 (mut (ref null eq)) (ref.null eq))
(global $arg6 (mut (ref null eq)) (ref.null eq))
(global $arg7 (mut (ref null eq)) (ref.null eq))
(global $arg8 (mut (ref null eq)) (ref.null eq))
(global $arg9 (mut (ref null eq)) (ref.null eq))
(global $arg10 (mut (ref null eq)) (ref.null eq))
(global $arg11 (mut (ref null eq)) (ref.null eq))
(global $arg12 (mut (ref null eq)) (ref.null eq))
(global $arg13 (mut (ref null eq)) (ref.null eq))
(global $arg14 (mut (ref null eq)) (ref.null eq))
(global $arg15 (mut (ref null eq)) (ref.null eq))
|]
-- other things 😼
Wasm.defineGlobal [wat|
(global $result (mut (ref null eq))
(ref.null eq))
|]
-- procedures
let arg = popArg 0
Wasm.defineFunction [wat|
(func $halt (param i32)
##{arg}
(global.set $result))
|]
pure ()
lower :: Exp -> Eff es Text
lower e = fmap Wasm.renderModule . Wasm.execGenMod $ do
runtime <- emitRuntime
let g = MkEnv mempty mempty
e' <- lower' g e
Wasm.defineFunction [wat|
(func $scm-entry (param i32)
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
##{e'})
|]
Wasm.defineFunction [wat|
(func (export "main")
(call $scm-entry (i32.const 0))
(call $gh-write (ref.as_non_null (global.get $result))))
|]
lowerProgram :: Program -> Eff es Text
lowerProgram (MkProgram e) = lower e
+117 -110
View File
@@ -1,7 +1,6 @@
{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedLists #-}
module Gyehoek.CPS.Stackify module Gyehoek.CPS.Stackify
( stackifyExp ( stackifyProgram
, stackifyProgram
, module Gyehoek.CPS.Syntax , module Gyehoek.CPS.Syntax
) where ) where
@@ -13,20 +12,14 @@ import Gyehoek.GenSym
import Effectful.Writer.Static.Shared import Effectful.Writer.Static.Shared
import Data.Foldable import Data.Foldable
import qualified Data.HashMap.Strict as H import qualified Data.HashMap.Strict as H
import Data.List (elemIndex) import Data.List (elemIndex, nub, intersect)
import Data.Text qualified as T
import Gyehoek.Prelude 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
live g e = free' e & filter \x ->
x `H.member` g.bound
&& not (x `elem` g.contStack)
data BlockBuilder data BlockBuilder
= Code (List Stk.Instr) BlockBuilder = Code (List Stk.Instr) BlockBuilder
| Tail Stk.Tail | Tail Stk.Tail
@@ -37,123 +30,137 @@ buildBlock = go [] where
go acc (Code xs bb) = go (acc ++ xs) bb go acc (Code xs bb) = go (acc ++ xs) bb
go acc (Tail t) = Stk.MkBlock acc t go acc (Tail t) = Stk.MkBlock acc t
emitRoutine :: Stackify :> es => Stk.Routine -> Eff es () -- affine
emitRoutine rt = tell [rt] _ValName :: Traversal' Val Name
_ValName = failing #_ValVar (#_ValImm . #_ImmLabel . #_MkLabel)
stackify stackify
:: (GenSym :> es, Stackify :> es) :: forall es. (GenSym :> es)
=> Env -> Exp -> Eff es BlockBuilder => Env -> Exp -> Eff es BlockBuilder
stackify g (ExpLetRec [(f, kap@(AbsKappa' xs m))] e) = do stackify _ (ExpContinue (ValVar k) xs) =
let vs = (f, Stk.ValLabel f) : (bindReg <$> xs) Code [ ] _
let ls = live g kap
m' <- stackify (g & #bound .~ H.fromList (vs ++ (bindReg <$> ls))) m
emitRoutine $
Stk.MkRoutine f xs . buildBlock $
Code [Stk.Pop x | x <- ls] m'
let g' = g & #bound . at f ?~ Stk.ValLabel f
& #liveness . at f ?~ ls
stackify g' e
stackify g (ExpLetRec [(f, AbsLambda' xs k m)] e) = do stackify _ (ExpPrim p k) = _
let vs = (k:xs) <&> \x -> (x, Stk.ValReg x)
m' <- stackify (g & #bound .~ H.fromList vs
& #contStack %~ (k:)) m
emitRoutine $ Stk.MkRoutine f xs (buildBlock m')
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) = pure $
Code [ Stk.PushCont k ] $
Code [ Stk.Push (Stk.ValReg l) | l <- ls ] $
Tail (Stk.TailCall (stackifyVal g f) (stackifyVal g <$> xs))
where
k = var g ktail
ls = fold $ (k ^? #ValImm . #ImmLabel)
>>= \klbl -> g ^. #liveness . at klbl
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 . Tail $ Stk.TailCall (Stk.ValLabel k) xs'
Just j -> do
ktail <- gensym' @Name $ k ^. _Wrapped'
pure $
Code (replicate j $ Stk.PopCont "_") $
Code [Stk.PopCont ktail] $
Tail (Stk.TailCall (Stk.ValReg ktail) xs')
where xs' = stackifyVal g <$> xs
stackify g (ExpPrim p (MkKappa [x] e)) = do
e' <- stackify (g & #bound . at x ?~ Stk.ValReg x) e
pure $
Code [ Stk.Prim x (stackifyVal g <$> p) ] $
e'
stackify _ e = error [i|unimplemented exp: #{e}|] stackify _ e = error [i|unimplemented exp: #{e}|]
stackifyVal :: Env -> Val -> Stk.Val stackifyAbs :: (GenSym :> es) => Env -> Label -> Abs -> Eff es Stk.Routine
stackifyVal g = \case
ValImm imm -> Stk.ValImm imm
ValVar v -> var g v
v -> error [i|unimplemented val: #{v}|]
var :: Env -> Name -> Stk.Val stackifyAbs g lbl (MkAbs xs mtail e) =
var g v = case g ^. #bound . at v of Stk.MkRoutine lbl . buildBlock . preamble <$> stackify g e
Just x -> x where
Nothing -> Stk.ValLabel v preamble = Code (popArgs $ (mtail ^.. _Just) ++ xs)
bindReg :: Name -> (Name, Stk.Val) popArgs :: List Name -> List Stk.Instr
bindReg x = (x, Stk.ValReg x) popArgs = fmap (Stk.Pop . MkReg) . reverse
pushArgs :: List Name -> List Stk.Instr
pushArgs = _
data Env = MkEnv data Env = MkEnv
{ bound :: HashMap Name Stk.Val {
-- | 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 Name (List Name)
, contStack :: List Name
} }
deriving (Show, Generic) deriving (Show, Generic)
emptyEnv :: Env emptyEnv :: Env
emptyEnv = MkEnv mempty mempty ["halt"] emptyEnv = MkEnv
{
}
stackifyExp :: GenSym :> es => Name -> Exp -> Eff es Stk.Program stackifyProgram
stackifyExp lbl e = do :: forall es. GenSym :> es
(code,p) <- runStackify $ stackify emptyEnv e => HoistedProgram -> Eff es Stk.Program
pure $ p <> [ Stk.MkRoutine lbl [] (buildBlock code) ] 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
stackifyProgram :: GenSym :> es => Program -> Eff es Stk.Program p :: HoistedProgram
stackifyProgram (MkProgram e) = stackifyExp "main" e p = [cps|
(letrec (($r12-code32
(κ (x13)
(prim
fac :: Program (get-env)
fac = [cps| (κ (r12 start-ktail0)
(letrec ((fac (λ (n ktail) (continue start-ktail0 x13)))))
(prim (zero? n) ($prim-k7-code22
(κ (x0) (κ (r6)
(if x0 (prim
(continue ktail 1) (get-env)
(prim (- n 1) (κ (prim-k7 lambda-tail1 n fac)
(κ (x1) (prim
(letrec ((fac-k0 (make-shared-closure ($r8-code19) (lambda-tail1 n))
(κ (x2) (κ (r8)
(prim (* n x2) (fac r6 r8)))))))
(κ (x3) ($prim-k11-code16
(continue ktail x3)))))) (κ (r10)
(fac x1 fac-k0)))))))))) (prim
(fac 6 halt)) (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)))))))
|] |]
+207 -80
View File
@@ -10,14 +10,18 @@ module Gyehoek.CPS.Syntax
, Kappa(..) , Kappa(..)
, Lambda(..) , Lambda(..)
, Exp(..) , Exp(..)
, Kexp(..)
, ExpF(..) , ExpF(..)
, Name(..) , Name(..)
, Prim(..) , Prim(..)
, Program(..) , Program(..)
, HoistedProgram(..)
, Lit(..) , Lit(..)
, Imm(..) , Imm(..)
, Obj(..) , Obj(..)
, Hob(..) , Hob(..)
, Label(..)
, Reg(..)
, pattern Halt , pattern Halt
, pattern Halt1 , pattern Halt1
, _MkKappa , _MkKappa
@@ -36,7 +40,13 @@ module Gyehoek.CPS.Syntax
, Abs(..) , Abs(..)
, Free(..) , Free(..)
, pattern ValLabel , pattern ValLabel
, labelName -- don't like that this is part of the api , pattern ObjLabel
, absBody
, pattern MkAbs
, _MkAbs
, unhoist
, pattern ExpJump
, _ExpJump
) )
where where
@@ -52,6 +62,12 @@ import Gyehoek.Prelude hiding (op)
import Gyehoek.Sexp (Datum) import Gyehoek.Sexp (Datum)
import Gyehoek.Sexp (G, (:-)(..)) import Gyehoek.Sexp (G, (:-)(..))
import qualified Data.InvertibleGrammar.Base as IG 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 -- Data types
@@ -60,13 +76,24 @@ data Val
| ValVar Name | ValVar Name
deriving (Show, Generic, Data, Eq) deriving (Show, Generic, Data, Eq)
pattern ValLabel :: Name -> Val pattern ValLabel :: Label -> Val
pattern ValLabel x = ValImm (ImmLabel x) pattern ValLabel x = ValImm (ImmLabel x)
newtype Label = MkLabel { inner :: Name }
deriving stock (Generic, Data)
deriving newtype (Show, Eq, Gen, IsString, Hashable)
deriving anyclass (NFData, Wrapped)
newtype Reg = MkReg { inner :: Name }
deriving stock (Generic, Data)
deriving newtype (Show, Eq, Gen, IsString, Hashable)
deriving anyclass (NFData, Wrapped)
data Imm data Imm
= ImmInt Int = ImmInt Int
| ImmBool Bool | ImmBool Bool
| ImmLabel Name | ImmLabel Label
| ImmUndefined
deriving stock (Show, Generic, Data, Eq) deriving stock (Show, Generic, Data, Eq)
deriving anyclass (NFData) deriving anyclass (NFData)
@@ -76,9 +103,14 @@ data Obj
deriving stock (Show, Generic, Data, Eq) deriving stock (Show, Generic, Data, Eq)
deriving anyclass (NFData) deriving anyclass (NFData)
pattern ObjLabel l = ObjImm (ImmLabel l)
-- | a heap object. -- | a heap object.
data Hob data Hob
= HobClosure { label :: Name, env :: List Obj } = 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 stock (Show, Generic, Data, Eq)
deriving anyclass (NFData) deriving anyclass (NFData)
@@ -93,38 +125,90 @@ data Abs
| AbsLambda Lambda | AbsLambda Lambda
deriving (Show, Generic, Data, Eq) 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 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) 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 data Exp
= ExpPrim (Prim Val) Kappa = ExpPrim (Prim Val) Kexp
| ExpLetRec { binders :: List (Name, Abs), body :: Exp } | ExpLetRec { binders :: List (Name, Abs), body :: Exp }
| ExpContinue Name (List Val) | ExpContinue Val (List Val)
| ExpIf Val Exp Exp | ExpIf Val Name Name
| ExpApply | ExpApply
{ op :: Val { op :: Val
, args :: List Val , args :: List Val
, cont :: Name , cont :: Kexp
} }
deriving (Show, Generic, Data, Eq) deriving (Show, Generic, Data, Eq)
data Kexp
= KexpVar Name
| KexpKappa Kappa
deriving (Show, Generic, Data, Eq)
pattern Halt :: List Val -> Exp pattern Halt :: List Val -> Exp
pattern Halt xs = ExpContinue "halt" xs pattern Halt xs = ExpContinue (ValLabel "halt") xs
pattern Halt1 :: Val -> Exp pattern Halt1 :: Val -> Exp
pattern Halt1 x = ExpContinue "halt" [x] pattern Halt1 x = ExpContinue (ValLabel "halt") [x]
data Def = DefConstant Name Exp data Def = DefConstant Name Exp
deriving (Show, Generic, Data) deriving (Show, Generic, Data)
data Program = MkProgram data Program = MkProgram
{ body :: Exp { body :: Lambda
} }
deriving (Show, Generic, Data) 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 ''Kappa
makePrisms ''Exp makePrisms ''Exp
makeFieldsId ''Exp makeFieldsId ''Exp
@@ -148,6 +232,21 @@ _AbsLambda' = prism'
instance Plated Exp where plate = uniplate 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 -- DatumIso instances
@@ -167,51 +266,64 @@ instance S.DatumIso Imm where
datumIso = S.match datumIso = S.match
$ S.With (. S.int) $ S.With (. S.int)
$ S.With (. S.datumIso) $ S.With (. S.datumIso)
$ S.With (. labelName) $ S.With (. S.datumIso)
$ S.With (. S.unreadable (const "#<undefined>"))
$ S.End $ S.End
labelName :: S.DatumGrammar Name instance S.DatumIso Label where
labelName = S.coproduct datumIso = S.with \g -> S.coproduct
[ S.decorate S.SynConstant >>> S.datumIso @Name >>> S.prismIso [ S.decorate S.SynConstant >>> S.datumIso @Name >>> S.prismIso
(S.expected "label") (S.expected "label")
(prefixed @Name "$") (prefixed @Name "$")
, S.list $ S.el (S.sym "$") >>> S.el (S.datumIso @Name) , S.list $ S.el (S.sym "$") >>> S.el (S.datumIso @Name)
] ]
>>> g
instance S.DatumIso Reg where
datumIso = S.with \g ->
S.decorate S.SynVariable >>> S.datumIso @Name >>> S.prismIso
(S.expected "register")
(prefixed @Name "%")
>>> g
instance S.DatumIso Hob where instance S.DatumIso Hob where
datumIso = S.match datumIso = S.match
$ S.With (. closure) $ S.With (. closure)
$ S.With (. cont)
$ S.With (. conspair)
$ S.End $ S.End
where where
conspair = S.dottedList (S.el S.datumIso) S.datumIso
-- closures can be printed, but not parsed. -- closures can be printed, but not parsed.
closure :: G (Datum :- t) (List Obj :- Name :- t) closure :: G (Datum :- t) (List Obj :- Label :- t)
closure = IG.Flip $ IG.PartialIso closure = IG.Flip $ IG.PartialIso
(\(env:-code:-t) -> [S.sx|(<closure> #{code} ##{env})|] :- 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) (const . Left $ mempty)
instance S.DatumIso Lambda where instance S.DatumIso Lambda where
datumIso = S.match datumIso = S.with (lam >>>)
$ S.With (. lambda)
$ S.End
where where
lambda = S.list $ lam :: forall t. G (Datum :- t) (Exp :- Name :- List Name :- t)
S.el S.lambdaKeyword lam = S.lambdaLike
>>> S.el binders S.lambdaKeyword
>>> S.el S.datumIso binders
(S.el $ S.datumIso @Exp)
binders :: forall t. G (Datum :- t) (Name :- List Name :- t) binders :: forall t. G (Datum :- t) (Name :- List Name :- t)
binders = S.list $ binders =
S.rest (S.datumIso @Name) S.list (S.rest $ S.datumIso @Name)
>>> S.onTail (S.flipped $ IG.PartialIso >>> S.flipped S.snoced
(\(ktail:-args:-t) -> (args ++ [ktail]) :- t) >>> S.swap
(\(args:-t) -> case args ^? _Snoc of
Just (args',ktail) -> Right $ ktail :- args' :- t
Nothing -> Left $ S.expected "cont param")
)
instance S.DatumIso Kappa where instance S.DatumIso Kappa where
datumIso = S.with \g -> datumIso = S.with \g ->
S.lambdaLike S.kappaKeyword S.lambdaLike S.kappaKeyword
(S.list $ S.rest (S.datumIso @Name)) (S.datumIso @(List Name))
(S.el $ S.datumIso @Exp) (S.el $ S.datumIso @Exp)
>>> g >>> g
@@ -238,35 +350,56 @@ instance S.DatumIso Exp where
if_ = S.ifLike "if" if_ = S.ifLike "if"
S.datumIso S.datumIso S.datumIso S.datumIso S.datumIso S.datumIso
app :: forall t. app :: forall t.
G (Datum :- t) (Name :- ([Val] :- (Val :- t))) G (Datum :- t) (Kexp :- List Val :- Val :- t)
app = S.list $ S.el (S.datumIso @Val) app = S.list $
-- >>> S.flipped Gyehoek.Datum.nonEmptyGrammar 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.rest (S.datumIso @Val)
-- >>> _ >>> S.onTail S.swap
>>> 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)
prim = S.list $ prim = S.list $
S.el (S.decorate S.SynBuiltin >>> S.sym "prim") S.el (S.decorate S.SynBuiltin >>> S.sym "prim")
>>> S.el (primDatumIso id (S.datumIso @Val)) >>> S.el (primDatumIso id (S.datumIso @Val))
>>> S.el S.datumIso >>> 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 instance S.DatumIso Program where
datumIso = S.with \prog -> S.datumIso @Exp >>> prog 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 -- quasiquoters
class Data a => CPS a where class Data a => CPS a where
toCPS :: Datum -> a toCPS :: HasCallStack => Datum -> a
instance CPS Exp where toCPS = S.fromDatumUnsafe S.datumIso instance CPS Exp where toCPS = S.fromDatumUnsafe S.datumIso
instance CPS Val where toCPS = S.fromDatumUnsafe S.datumIso instance CPS Val where toCPS = S.fromDatumUnsafe S.datumIso
@@ -274,6 +407,7 @@ instance CPS Kappa where toCPS = S.fromDatumUnsafe S.datumIso
instance CPS Lambda 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 Abs where toCPS = S.fromDatumUnsafe S.datumIso
instance CPS Program 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.QuasiQuoter
cps = S.makeSx' [| toCPS |] cps = S.makeSx' [| toCPS |]
@@ -296,7 +430,8 @@ class Free a where
freeWithBound :: HashSet Name -> a -> HashSet Name freeWithBound :: HashSet Name -> a -> HashSet Name
freeWithBound bound = HS.fromList . freeWithBound' bound 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' :: a -> List Name
free' = freeWithBound' mempty free' = freeWithBound' mempty
@@ -306,22 +441,32 @@ instance Free Abs where
freeWithBound' bound (AbsKappa kap) = freeWithBound' bound kap freeWithBound' bound (AbsKappa kap) = freeWithBound' bound kap
freeWithBound' bound (AbsLambda lam) = freeWithBound' bound lam 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 instance Free Exp where
freeWithBound' bound = \case freeWithBound' bound = \case
ExpPrim p k -> ExpPrim p k ->
p & toListOf (folded . #ValVar . filtered (`notElem` bound)) (p ^.. folded . #ValVar . filtered (`notElem` bound))
& (<> freeWithBound' bound k) ++ freeWithBound' bound k
ExpLetRec bs m -> ExpLetRec bs m ->
foldMapOf (each . _2) (freeWithBound' bound') bs foldMapOf (each . _2) (freeWithBound' bound') bs
<> freeWithBound' bound' m <> freeWithBound' bound' m
where bound' = bound & insertFrom (bs ^.. each . _1) where bound' = bound & insertFrom (bs ^.. each . _1)
ExpContinue k xs -> filter (`notElem` bound) (k : xs ^.. each . #ValVar) ExpContinue k xs -> filter (`notElem` bound) ((k:xs) ^.. each . #ValVar)
ExpIf c t f -> ExpIf c t f ->
(c ^.. #ValVar . filtered (`notElem` bound)) (c ^.. #ValVar . filtered (`notElem` bound))
<> freeWithBound' bound t <> freeWithBound' bound f <> mif (`notElem` bound) t <> mif (`notElem` bound) f
ExpApply f xs k -> ExpApply f xs k ->
(f:xs) ^.. (each . #ValVar . filtered (`notElem` bound)) (f:xs) ^.. (each . #ValVar . filtered (`notElem` bound))
<> (k ^.. filtered (`notElem` bound)) <> freeWithBound' bound k
instance Free Kappa where instance Free Kappa where
freeWithBound' bound (MkKappa xs m) = freeWithBound' bound (MkKappa xs m) =
@@ -330,21 +475,3 @@ instance Free Kappa where
instance Free Lambda where instance Free Lambda where
freeWithBound' bound (MkLambda xs k m) = freeWithBound' bound (MkLambda xs k m) =
freeWithBound' (bound & insertFrom (k:xs)) m freeWithBound' (bound & insertFrom (k:xs)) m
class Vars a where
-- | Traverse the immediate variables of an expression.
vars :: Traversal' a Name
instance Vars Val where
vars k (ValVar x) = ValVar <$> k x
vars _ x = pure x
instance Vars a => Vars (Prim a) where
vars k p = traverseOf (each . vars) k p
instance Vars Exp where
vars k (ExpPrim p kap) = ExpPrim <$> vars k p <*> pure kap
vars k (ExpContinue kname xs) = ExpContinue <$> k kname <*> pure xs
vars _ e = pure e
+46 -20
View File
@@ -1,7 +1,7 @@
module Gyehoek.Driver 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 where
import Gyehoek.Options import Gyehoek.Options
import Prelude hiding (readFile) import Prelude hiding (readFile)
import Options.Applicative import Options.Applicative
@@ -17,7 +17,7 @@ import qualified Data.Text.Encoding as T
import System.IO (Handle) import System.IO (Handle)
import System.IO qualified as IO import System.IO qualified as IO
import Gyehoek.CPS.Convert import Gyehoek.CPS.Convert
import Gyehoek.CPS.Lower import Gyehoek.Stack.Lower
import Gyehoek.CPS.Eval qualified as CPS import Gyehoek.CPS.Eval qualified as CPS
import Control.Monad import Control.Monad
import Text.Pretty.Simple (pShowNoColor) import Text.Pretty.Simple (pShowNoColor)
@@ -26,7 +26,7 @@ import System.Environment.Blank (getEnvDefault)
import qualified Data.Text.IO as TIO import qualified Data.Text.IO as TIO
import qualified Data.ByteString.Lazy as BS import qualified Data.ByteString.Lazy as BS
import Gyehoek.CPS.Stackify (stackifyProgram) import Gyehoek.CPS.Stackify (stackifyProgram)
import Gyehoek.Stack.VM (eval, writeObj, Obj) import Gyehoek.Stack.VM (eval, writeObj, Obj, traceEval)
import qualified Data.Text as T import qualified Data.Text as T
import Gyehoek.Stack.Syntax qualified as Stk import Gyehoek.Stack.Syntax qualified as Stk
import Gyehoek.CPS.Close (closeProgram) import Gyehoek.CPS.Close (closeProgram)
@@ -35,14 +35,16 @@ import Control.Arrow ((>>>))
import Gyehoek.Prelude import Gyehoek.Prelude
import Gyehoek.Jalmot import Gyehoek.Jalmot
import qualified Gyehoek.Sexp as S import qualified Gyehoek.Sexp as S
import Gyehoek.CPS.Hoist (hoistProgram)
import Gyehoek.CPS.Contify (contifyProgram)
main :: IO () main :: IO ()
main = do main = do
opts <- execParser $ info (helper <*> parser) fullDesc opts <- execParser $ info (helper <*> parser) fullDesc
runJalmotIO . runFileSystem . runGenSym . driver $ opts runJalmotIO . runFileSystem . runGenSym . driver $ opts
-- hPutStr :: FileSystem :> es => Handle -> Text -> Eff es () -- hPutStr :: FileSystem :> es => Handle -> Text -> Eff es ()
-- hPutStr h = FB.hPutStr h . T.encodeUtf8 -- hPutStr h = FB.hPutStr h . T.encodeUtf8
@@ -118,27 +120,35 @@ driver opts = do
hPutStrLn FS.stdout . view strict . pShowNoColor $ scm hPutStrLn FS.stdout . view strict . pShowNoColor $ scm
cps <- convertProgram scm cps <- convertProgram scm
when opts.dumpCPS do when opts.dumpCPS do
hPutStrLn FS.stdout =<< S.encodeWith S.datumIso cps S.writeDatum cps
closedCps <- closeProgram cps closedCps <- closeProgram cps
when opts.dumpClosed do 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 let rt_is p = is (_Just . p) opts.runtime
dumpOrRun opts.dumpStackified (rt_is #Stackify) dumpOrRun opts.dumpStackified (rt_is #Stackify)
(stackifyProgram closedCps) (stackifyProgram hoistedCps)
(hPutStrLn FS.stdout <=< S.encodeDataWith S.dataIso) (hPutStrLn FS.stdout <=< S.encodeDataWith S.dataIso)
(eval >>> fmap writeObj (eval >=> fmap writeObj
>>> T.unwords >>> T.unwords
>>> hPutStrLn FS.stdout) >>> hPutStrLn FS.stdout)
when (rt_is #HigherOrderCPS) do
CPS.evalProgram cps
>>= S.writeData
when (rt_is #CPS) do when (rt_is #CPS) do
closedCps CPS.evalProgram closedCps
& CPS.evalProgram >>= S.writeData
& fmap writeObj -- dumpOrRun opts.inspectWasm (rt_is #Wasm)
& T.unwords -- (lowerProgram cps)
& hPutStrLn FS.stdout -- inspectWasm
dumpOrRun opts.inspectWasm (rt_is #Wasm) -- (\wat -> withFile opts.output FS.WriteMode \h -> hPutStrLn h wat)
(lowerProgram cps) when opts.traceStackified do
inspectWasm stackifyProgram hoistedCps >>= traceEval
(\wat -> withFile opts.output FS.WriteMode \h -> hPutStrLn h wat)
parse_e2e :: FilePath -> IO Scm.Program parse_e2e :: FilePath -> IO Scm.Program
parse_e2e = runJalmotIO . runFileSystem . readScm parse_e2e = runJalmotIO . runFileSystem . readScm
@@ -155,4 +165,20 @@ lower_e2e =
eval_e2e :: FilePath -> IO (List Obj) eval_e2e :: FilePath -> IO (List Obj)
eval_e2e fp = runJalmotIO . runFileSystem . runGenSym $ do eval_e2e fp = runJalmotIO . runFileSystem . runGenSym $ do
stk <- stackifyProgram <=< closeProgram <=< convertProgram <=< readScm $ fp stk <- stackifyProgram <=< closeProgram <=< convertProgram <=< readScm $ fp
pure . eval $ stk 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
+8
View File
@@ -8,6 +8,7 @@ module Gyehoek.Jalmot
, runJalmotIO , runJalmotIO
, runJalmotIOE , runJalmotIOE
, runJalmotUnsafe , runJalmotUnsafe
, runJalmotCS
) )
where where
@@ -29,6 +30,8 @@ deriving instance Data p => Data (Grammar.ErrorMessage p)
data AJalmot data AJalmot
= ReaderError (ParseErrorBundle Text Void) = ReaderError (ParseErrorBundle Text Void)
| GrammarError (Grammar.ErrorMessage Ann) | GrammarError (Grammar.ErrorMessage Ann)
| VMError Text
| EvalError Text
deriving (Show, Generic, Data) deriving (Show, Generic, Data)
data AJalmotCS = MkAJalmotCS !CallStack !AJalmot data AJalmotCS = MkAJalmotCS !CallStack !AJalmot
@@ -39,6 +42,9 @@ type Jalmot = Error AJalmot
runJalmot :: Eff (Jalmot : es) a -> Eff es (Either (CallStack, AJalmot) a) runJalmot :: Eff (Jalmot : es) a -> Eff es (Either (CallStack, AJalmot) a)
runJalmot = runError runJalmot = runError
runJalmotCS :: Eff (Jalmot : es) a -> Eff es (Either AJalmotCS a)
runJalmotCS = (mapped . _Left %~ uncurry MkAJalmotCS) . runError
runJalmotIOE :: IOE :> es => Eff (Jalmot : es) a -> Eff es a runJalmotIOE :: IOE :> es => Eff (Jalmot : es) a -> Eff es a
runJalmotIOE eff = runJalmotIOE eff =
runJalmot eff >>= \case runJalmot eff >>= \case
@@ -60,6 +66,8 @@ instance Exception AJalmot where
pretty err pretty err
& layoutPretty defaultLayoutOptions & layoutPretty defaultLayoutOptions
& renderString & renderString
VMError err -> [i|#{err}|]
EvalError err -> [i|#{err}|]
instance Exception AJalmotCS where instance Exception AJalmotCS where
backtraceDesired = const False backtraceDesired = const False
+4
View File
@@ -0,0 +1,4 @@
module Gyehoek.Language
(
) where
+16 -4
View File
@@ -13,7 +13,7 @@ import Data.Foldable
import Gyehoek.Prelude hiding (argument) import Gyehoek.Prelude hiding (argument)
data Runtime = Stackify | Wasm | CPS data Runtime = Stackify | Wasm | CPS | HigherOrderCPS
deriving (Show, Generic, Eq) deriving (Show, Generic, Eq)
data Language data Language
@@ -29,6 +29,10 @@ data Options = MkOptions
, dumpCPS :: Bool , dumpCPS :: Bool
, dumpParsed :: Bool , dumpParsed :: Bool
, dumpStackified :: Bool , dumpStackified :: Bool
, dumpHoisted :: Bool
, dumpContified :: Bool
, traceStackified :: Bool
, noColour :: Bool
, runtime :: Maybe Runtime , runtime :: Maybe Runtime
, inspectWasm :: Bool , inspectWasm :: Bool
, output :: FilePath , output :: FilePath
@@ -50,7 +54,8 @@ runtimeValues = ["stackify","wasm","cps","none"]
runtimeReader = maybeReader \case runtimeReader = maybeReader \case
"stackify" -> Just (Just Stackify) "stackify" -> Just (Just Stackify)
"wasm" -> Just (Just Wasm) "wasm" -> Just (Just Wasm)
"cps" -> Just (Just CPS) "cps1" -> Just (Just CPS)
("cps";"higher-order-cps") -> Just (Just HigherOrderCPS)
"none" -> Just Nothing "none" -> Just Nothing
_ -> Nothing _ -> Nothing
@@ -60,13 +65,20 @@ parser = do
dumpCPS <- switch (long "dump-cps") dumpCPS <- switch (long "dump-cps")
dumpStackified <- switch (long "dump-stackified") dumpStackified <- switch (long "dump-stackified")
dumpParsed <- switch (long "dump-parsed") 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' inspectWasm <- switch $ long "inspect-wasm" <> short 'p'
runtime <- option runtimeReader . fold $ runtime <- option runtimeReader . fold $
[ long "runtime" [ long "runtime"
, short 'R' , short 'R'
, value (Just Stackify) , value (Just HigherOrderCPS)
, completeWith runtimeValues , completeWith runtimeValues
, showDefaultWith $ const "stackify" , showDefaultWith $ const "higher-order-cps"
, metavar "RUNTIME" , metavar "RUNTIME"
] ]
sourceLanguage <- option languageReader . fold $ sourceLanguage <- option languageReader . fold $
+2
View File
@@ -19,6 +19,7 @@ module Gyehoek.Prelude
, (>>>) , (>>>)
, (>=>) , (>=>)
, (<=<) , (<=<)
, wrappedIso
) where ) where
import Control.Lens hiding (List, (:<)) import Control.Lens hiding (List, (:<))
@@ -40,4 +41,5 @@ import Data.List.NonEmpty (NonEmpty((:|)))
import Numeric.Natural (Natural) import Numeric.Natural (Natural)
import Control.Category ((>>>)) import Control.Category ((>>>))
import Control.Monad import Control.Monad
import Data.Generics.Wrapped (Wrapped(..))
+31 -11
View File
@@ -51,7 +51,7 @@ import qualified Effectful.FileSystem.IO.ByteString as FB
import qualified Data.Set.Ordered as O import qualified Data.Set.Ordered as O
import Gyehoek.Sexp.Grammar qualified as Sexp import Gyehoek.Sexp.Grammar qualified as Sexp
import Gyehoek.Sexp.Grammar qualified as S import Gyehoek.Sexp.Grammar qualified as S
import Gyehoek.Sexp.Grammar (DatumIso, DataIso) import Gyehoek.Sexp.Grammar (DatumIso, G, DataIso, (:-)((:-)))
import Gyehoek.Prelude import Gyehoek.Prelude
@@ -81,9 +81,15 @@ data Prim e
| PrimZeroP e | PrimZeroP e
| PrimNewline | PrimNewline
| PrimMakeClosure { code :: e, env :: List e } | PrimMakeClosure { code :: e, env :: List e }
| PrimEnvRef e Int | PrimMakeSharedClosure { codes :: List e, env :: List e }
| PrimEnvCode e | PrimGetEnv
| PrimEnv
| PrimEnvRef Int
| PrimCallCC e | PrimCallCC e
| PrimCaptureCC
| PrimInvokeCC e (List e)
| PrimValues (List e)
| PrimCallWithValues e e
deriving stock (Show, Generic, Functor, Foldable, Traversable, Data, Eq) deriving stock (Show, Generic, Functor, Foldable, Traversable, Data, Eq)
deriving anyclass (NFData) deriving anyclass (NFData)
@@ -106,7 +112,7 @@ data Exp
= ExpLet (List (Name, Exp)) Exp = ExpLet (List (Name, Exp)) Exp
| ExpLetRec (List (Name, Exp)) Exp | ExpLetRec (List (Name, Exp)) Exp
| ExpPrim (Prim Exp) | ExpPrim (Prim Exp)
| ExpBegin (List Exp) | ExpBegin (NonEmpty Exp)
| ExpIf Exp Exp Exp | ExpIf Exp Exp Exp
| ExpLit Lit | ExpLit Lit
| ExpLambda (List Name) Exp | ExpLambda (List Name) Exp
@@ -162,21 +168,29 @@ primDatumIso namefn a = S.match
$ S.With (. ht1 "integer?") $ S.With (. ht1 "integer?")
$ S.With (. ht1 "write") $ S.With (. ht1 "write")
$ S.With (. ht1 "zero?") $ S.With (. ht1 "zero?")
$ S.With (. nullop "newline") $ S.With (. ht0 "newline")
$ S.With (. ht1' "make-closure") $ S.With (. ht1' "make-closure")
$ S.With (. S.headTagged2 (namefn "env-ref") a S.int) $ S.With (. S.headTagged2 (namefn "make-shared-closure")
$ S.With (. ht1 "env-code") (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 "call/cc") $ 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 $ S.End
where where
idn = S.el . S.sym . namefn idn = S.el . S.sym . namefn
nullop s = S.list $ idn s ht0 s = S.list $ idn s
ht1 s = S.headTagged1 (namefn s) a ht1 s = S.headTagged1 (namefn s) a
ht2 s = S.headTagged2 (namefn s) a a ht2 s = S.headTagged2 (namefn s) a a
ht1' s = S.headTagged1' (namefn s) a a ht1' s = S.headTagged1' (namefn s) a a
ht0' s = S.headTagged0' (namefn s) a
instance DatumIso a => DatumIso (Prim a) where instance DatumIso a => DatumIso (Prim a) where
-- datumIso = primDatumIso ("prim:"<>) datumIso
datumIso = primDatumIso id S.datumIso datumIso = primDatumIso id S.datumIso
instance DatumIso Lit where instance DatumIso Lit where
@@ -203,7 +217,7 @@ instance DatumIso Exp where
$ S.With (. S.letLike "let" S.datumIso S.datumIso S.datumIso) $ S.With (. S.letLike "let" S.datumIso S.datumIso S.datumIso)
$ S.With (. S.letLike "letrec" S.datumIso S.datumIso S.datumIso) $ S.With (. S.letLike "letrec" S.datumIso S.datumIso S.datumIso)
$ S.With (. S.datumIso) $ S.With (. S.datumIso)
$ S.With (. S.beginLike "begin" S.datumIso) $ S.With (. begin)
$ S.With (. S.ifLike "if" S.datumIso S.datumIso S.datumIso) $ S.With (. S.ifLike "if" S.datumIso S.datumIso S.datumIso)
$ S.With (. S.datumIso) $ S.With (. S.datumIso)
$ S.With (. lam) $ S.With (. lam)
@@ -212,12 +226,18 @@ instance DatumIso Exp where
$ S.End $ S.End
where where
lam = S.lambdaLike S.lambdaKeyword S.datumIso (S.el S.datumIso) lam = S.lambdaLike S.lambdaKeyword S.datumIso (S.el S.datumIso)
begin :: forall t. G (S.Datum :- t) (NonEmpty Exp :- t)
begin = S.beginLike "begin" $
S.el (S.datumIso @Exp) >>> S.rest (S.datumIso @Exp)
>>> S.onTail (S.Iso
(\(xs:-x:-t) -> (x:|xs):-t)
(\((x:|xs):-t) -> xs:-x:-t))
instance DatumIso CommandOrDef where instance DatumIso CommandOrDef where
datumIso = S.match datumIso = S.match
$ S.With (\_Command -> _Command . S.datumIso) $ S.With (\_Command -> _Command . S.datumIso)
$ S.With (\_Definition -> _Definition . S.datumIso) $ S.With (\_Definition -> _Definition . S.datumIso)
$ S.With (\_Begin -> _Begin . S.beginLike "begin" S.datumIso) $ S.With (\_Begin -> _Begin . S.beginLike "begin" (S.rest S.datumIso))
$ S.End $ S.End
instance DataIso Program where instance DataIso Program where
+43 -15
View File
@@ -15,6 +15,7 @@ module Gyehoek.Sexp.Grammar
, encodeDataTest , encodeDataTest
, encodeDataTestColour , encodeDataTestColour
, encodeOrShow' , encodeOrShow'
, encodeOrShowData'
, decodeDataWith , decodeDataWith
, encodeDataWith' , encodeDataWith'
, decodeTest , decodeTest
@@ -28,6 +29,8 @@ module Gyehoek.Sexp.Grammar
, fromDatumUnsafe , fromDatumUnsafe
, Control.Category.id , Control.Category.id
, fromDataUnsafe , fromDataUnsafe
, writeDatum
, writeData
) )
where where
@@ -45,6 +48,7 @@ import qualified Control.Category
import qualified Data.Vector as V import qualified Data.Vector as V
import Data.String (IsString (fromString)) import Data.String (IsString (fromString))
import qualified Data.Text as T import qualified Data.Text as T
import System.Environment (lookupEnv)
toDatum :: Jalmot :> es => DatumGrammar a -> a -> Eff es Datum toDatum :: Jalmot :> es => DatumGrammar a -> a -> Eff es Datum
@@ -59,19 +63,21 @@ toData g =
>>> runGrammar noAnn >>> runGrammar noAnn
>>> either (throwError . GrammarError) pure >>> either (throwError . GrammarError) pure
fromDatum :: Jalmot :> es => DatumGrammar a -> Datum -> Eff es a fromDatum :: (HasCallStack, Jalmot :> es) => DatumGrammar a -> Datum -> Eff es a
fromDatum g = fromDatum g =
forward (sealed g) forward (sealed g)
>>> runGrammar noAnn >>> runGrammar noAnn
>>> either (throwError . GrammarError) pure >>> either (throwError . GrammarError) pure
fromDatumUnsafe :: DatumGrammar a -> Datum -> a fromDatumUnsafe :: HasCallStack => DatumGrammar a -> Datum -> a
fromDatumUnsafe g = runJalmotUnsafe . fromDatum g fromDatumUnsafe g = runJalmotUnsafe . fromDatum g
fromDataUnsafe :: DataGrammar a -> List Datum -> a fromDataUnsafe :: HasCallStack => DataGrammar a -> List Datum -> a
fromDataUnsafe g = runJalmotUnsafe . fromData g fromDataUnsafe g = runJalmotUnsafe . fromData g
fromData :: Jalmot :> es => DataGrammar a -> List Datum -> Eff es a fromData
:: (HasCallStack, Jalmot :> es)
=> DataGrammar a -> List Datum -> Eff es a
fromData g = fromData g =
forward (sealed g) forward (sealed g)
>>> runGrammar noAnn >>> runGrammar noAnn
@@ -124,6 +130,39 @@ encodeOrShow' g x = fromString $
Left _ -> show x Left _ -> show x
Right t -> T.unpack t 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 class DatumIso a where
datumIso :: DatumGrammar a datumIso :: DatumGrammar a
@@ -150,14 +189,3 @@ instance DatumIso a => DataIso (V.Vector a) where
instance (DatumIso a, DatumIso b) => DatumIso (a, b) where instance (DatumIso a, DatumIso b) => DatumIso (a, b) where
datumIso = with \tup2 -> list (el datumIso >>> el datumIso) >>> tup2 datumIso = with \tup2 -> list (el datumIso >>> el datumIso) >>> tup2
data Example = MkExample (List Int) Text
deriving (Generic, Show)
instance DataIso Example where
dataIso = with \g ->
flipped snoced
>>> onHead (traversed $ sealed int)
>>> onTail (onHead $ sealed symbol)
>>> swap
>>> g
+58 -8
View File
@@ -9,7 +9,7 @@ module Gyehoek.Sexp.Grammar.Base
, DatumGrammar , DatumGrammar
, DataGrammar , DataGrammar
, Grammar , Grammar
, ListContext , ListContext(..)
, (:-)((:-)) , (:-)((:-))
-- * lists -- * lists
, list , list
@@ -31,6 +31,7 @@ module Gyehoek.Sexp.Grammar.Base
, number , number
, integer , integer
, int , int
, unreadable
-- * TODO: sort lol -- * TODO: sort lol
, prismIso , prismIso
, isoIso, decorate , isoIso, decorate
@@ -40,7 +41,7 @@ module Gyehoek.Sexp.Grammar.Base
, lambdaLike , lambdaLike
, lambdaKeyword , lambdaKeyword
, kappaKeyword , kappaKeyword
, beginLike , beginLike, headTagged2', dottedList
) where ) where
import Data.InvertibleGrammar import Data.InvertibleGrammar
@@ -55,6 +56,7 @@ import Data.Scientific (Scientific)
import qualified Data.Scientific as Sci import qualified Data.Scientific as Sci
import qualified Data.Text as T import qualified Data.Text as T
import Control.Monad.RWS (modify) import Control.Monad.RWS (modify)
import qualified Data.List.NonEmpty as NE
-- $setup -- $setup
@@ -104,6 +106,39 @@ list
-> G (Datum :- t) t' -> G (Datum :- t) t'
list = listWithIndentation Ordinary list = listWithIndentation Ordinary
-- |
-- >>> let grammar = with \g -> dottedList (el int) int >>> g
-- >>> decodeTest @(Int,Int) grammar "(1 . 2)"
-- ( 1
-- , 2
-- )
-- >>> let grammar = with \g -> dottedList (el int >>> el int) int >>> g
-- >>> decodeTest @(Int,Int,Int) grammar "(1 2 . 3)"
-- ( 1
-- , 2
-- , 3
-- )
dottedList
:: forall t t' t''. G (ListContext :- t) (ListContext :- t')
-> G (Datum :- t') t''
-> G (Datum :- t) t''
dottedList g final = begin >>> Dive (onTail (g >>> end) >>> final)
where
begin = locate >>> Flip (PartialIso
(\(x:-MkListContext xs:-t) -> case NE.nonEmpty xs of
Just xs' -> DotList xs' x :- t
Nothing -> error "fuck")
(\case
DotList xs x :- t -> Right $ x :- MkListContext (NE.toList xs) :- t
_ -> Left $ expected "dotted list"))
end :: Grammar Ann (ListContext :- t') t'
end = Flip $ PartialIso
(\t -> MkListContext [] :- t)
(\(MkListContext lst :- t) ->
case lst of
[] -> Right t
d:_ -> Left $ unexpectedDatum d)
listWithIndentation listWithIndentation
:: Indentation :: Indentation
-> G (ListContext :- t) (ListContext :- t') -> G (ListContext :- t) (ListContext :- t')
@@ -325,6 +360,13 @@ headTagged2
-> G (Datum :- t) (b :- a :- t) -> G (Datum :- t) (b :- a :- t)
headTagged2 s g1 g2 = list $ el (symProcedure s) >>> el g1 >>> el g2 headTagged2 s g1 g2 = list $ el (symProcedure s) >>> el g1 >>> el g2
headTagged2'
:: Text
-> DatumGrammar a -> DatumGrammar b -> DatumGrammar c
-> G (Datum :- t) (List c :- b :- a :- t)
headTagged2' s g1 g2 gt =
list $ el (symProcedure s) >>> el g1 >>> el g2 >>> rest gt
ifLike ifLike
-- | keyword -- | keyword
:: Text :: Text
@@ -358,9 +400,9 @@ letLike kw name rhs e = listWithIndentation (NSpecial 1) $
lambdaLike lambdaLike
:: (forall t. G (Datum :- t) t) :: (forall t. G (Datum :- t) t)
-> DatumGrammar a -> G (Datum :- t1) (a :- t2)
-> G (ListContext :- a :- t) (ListContext :- t') -> G (ListContext :- a :- t2) (ListContext :- t3)
-> G (Datum :- t) t' -> G (Datum :- t1) t3
lambdaLike kw formals body = listWithIndentation (NSpecial 1) $ lambdaLike kw formals body = listWithIndentation (NSpecial 1) $
el (decorate SynBuiltin >>> kw) el (decorate SynBuiltin >>> kw)
>>> el formals >>> el formals
@@ -374,11 +416,19 @@ kappaKeyword = coproduct [ sym "κ", sym "kappa" ]
beginLike beginLike
:: Text :: Text
-> DatumGrammar a -> G (ListContext :- t) (ListContext :- t')
-> G (Datum :- t) (List a :- t) -> G (Datum :- t) t'
beginLike kw g = beginLike kw g =
listWithIndentation (NSpecial 0) $ listWithIndentation (NSpecial 0) $
el (symBuiltin kw) >>> rest g el (symBuiltin kw) >>> g
-- | define a printed syntax for an object which cannot be read.
unreadable
:: (t -> Text)
-> G (Datum :- t) t
unreadable f = Flip $ PartialIso
(\t -> Unreadable (f t) :- t)
(const $ Left mempty)
isoIso :: Iso' s a -> Grammar p (s :- t) (a :- t) isoIso :: Iso' s a -> Grammar p (s :- t) (a :- t)
isoIso l = iso (view l) (review l) isoIso l = iso (view l) (review l)
+57 -6
View File
@@ -4,21 +4,26 @@ module Gyehoek.Sexp.Print
, printDatum' , printDatum'
, printData , printData
, printData' , printData'
, htmlDatum
, htmlData
) where ) where
import Gyehoek.Sexp.Syntax import Gyehoek.Sexp.Syntax
import Data.Text.Prettyprint.Doc import Prettyprinter
import Data.Functor.Foldable import Data.Functor.Foldable
import qualified Control.Comonad.Trans.Cofree as F import qualified Control.Comonad.Trans.Cofree as F
import Prettyprinter.Util import Prettyprinter.Util
import Gyehoek.Prelude hiding (Simple, (:<)) import Gyehoek.Prelude hiding (Simple, (:<))
import Data.Foldable (traverse_) import Data.Foldable (traverse_, toList)
import qualified Prettyprinter.Render.Terminal as ANSI import qualified Prettyprinter.Render.Terminal as ANSI
import System.IO (stdout) import System.IO (stdout)
import Prettyprinter.Render.Terminal (Color(..), color, italicized, AnsiStyle, bold, colorDull) import Prettyprinter.Render.Terminal (Color(..), color, italicized, AnsiStyle, bold, colorDull)
import Prettyprinter.Render.Text (renderStrict) import Prettyprinter.Render.Text (renderStrict)
import qualified Data.Scientific as Sci import qualified Data.Scientific as Sci
import Data.List (intersperse) import Data.List (intersperse)
import Lucid
import Prettyprinter.Render.Util.SimpleDocTree (treeForm)
import Prettyprinter.Lucid (renderHtml)
printDatum' :: Datum -> Text printDatum' :: Datum -> Text
@@ -31,6 +36,31 @@ printDatum' =
{ layoutPageWidth = AvailablePerLine 80 1.0 { layoutPageWidth = AvailablePerLine 80 1.0
} }
htmlDatum :: Datum -> Html ()
htmlDatum =
prettyDatum 0
>>> layoutPretty opts
>>> treeForm
>>> fmap highlightHtml
>>> renderHtml
where
opts = LayoutOptions
{ layoutPageWidth = AvailablePerLine 80 1.0
}
htmlData :: Foldable f => f Datum -> Html ()
htmlData =
foldr f mempty
>>> layoutPretty opts
>>> treeForm
>>> fmap highlightHtml
>>> renderHtml
where
f x y = prettyDatum 0 x <> hardline <> hardline <> y
opts = LayoutOptions
{ layoutPageWidth = AvailablePerLine 80 1.0
}
printDatum :: Datum -> Text printDatum :: Datum -> Text
printDatum = printDatumW 80 printDatum = printDatumW 80
@@ -44,7 +74,7 @@ printDatumW :: Int -> Datum -> Text
printDatumW w = printDatumW w =
prettyDatum 0 prettyDatum 0
>>> layoutSmart opts >>> layoutSmart opts
>>> reAnnotateS highlight >>> reAnnotateS highlightAnsi
>>> ANSI.renderStrict >>> ANSI.renderStrict
where where
opts = LayoutOptions opts = LayoutOptions
@@ -54,6 +84,13 @@ printDatumW w =
prettyDatum :: Int -> Datum -> Doc Syn prettyDatum :: Int -> Datum -> Doc Syn
prettyDatum depth datum = case datum of prettyDatum depth datum = case datum of
Simple simp -> annotate (datum ^. syntax) $ prettySimple depth simp Simple simp -> annotate (datum ^. syntax) $ prettySimple depth simp
DotList xs x ->
pparen depth . group . align $
vsep [ vsep (prettyDatum (depth+1) <$> toList xs)
, "."
, prettyDatum (depth+1) x
]
List' indent xs -> List' indent xs ->
case indent of case indent of
NSpecial n | keyword:args <- xs -> NSpecial n | keyword:args <- xs ->
@@ -87,13 +124,14 @@ prettySimple depth = \case
& annotate SynConstant & annotate SynConstant
SimpleString s -> annotate SynString $ viaShow s SimpleString s -> annotate SynString $ viaShow s
SimpleSymbol s -> pretty s SimpleSymbol s -> pretty s
SimpleUnreadable s -> pretty s
putDoc :: Doc Syn -> IO () putDoc :: Doc Syn -> IO ()
putDoc = ANSI.renderIO stdout putDoc = ANSI.renderIO stdout
. reAnnotateS highlight . layoutSmart defaultLayoutOptions . (<>"\n") . reAnnotateS highlightAnsi . layoutSmart defaultLayoutOptions . (<>"\n")
highlight :: Syn -> AnsiStyle highlightAnsi :: Syn -> AnsiStyle
highlight = \case highlightAnsi = \case
(SynBuiltin; SynMacro) -> color Magenta <> italicized <> bold (SynBuiltin; SynMacro) -> color Magenta <> italicized <> bold
SynProcedure -> color Blue SynProcedure -> color Blue
SynConstant -> color Yellow SynConstant -> color Yellow
@@ -101,3 +139,16 @@ highlight = \case
_ -> mempty _ -> mempty
where where
rainbow = cycle [Red,Yellow,Green,Blue,Magenta,Cyan] rainbow = cycle [Red,Yellow,Green,Blue,Magenta,Cyan]
highlightHtml :: Syn -> Html () -> Html ()
highlightHtml syn = span_ [class_ synClass]
where
synClass = case syn of
SynBuiltin -> "syn-builtin"
SynMacro -> "syn-macro"
SynConstant -> "syn-constant"
SynString -> "syn-string"
SynProcedure -> "syn-procedure"
SynVariable -> "syn-variable"
SynNone -> "syn-none"
SynParen n -> [i|syn-paren-#{mod n 5}|]
+3
View File
@@ -29,6 +29,7 @@ module Gyehoek.Sexp.Syntax
, indentation , indentation
, adorn , adorn
, indentWith , indentWith
, pattern Unreadable
, pattern Bytevector , pattern Bytevector
, pattern Symbol , pattern Symbol
, pattern String , pattern String
@@ -79,6 +80,7 @@ data Simple
| SimpleString Text | SimpleString Text
| SimpleSymbol Text | SimpleSymbol Text
| SimpleBytevector ByteString | SimpleBytevector ByteString
| SimpleUnreadable Text
deriving stock (Show, Eq, Data, Generic, Lift) deriving stock (Show, Eq, Data, Generic, Lift)
deriving anyclass (NFData) deriving anyclass (NFData)
@@ -230,6 +232,7 @@ pattern Character a = Simple (SimpleCharacter a)
pattern String a = Simple (SimpleString a) pattern String a = Simple (SimpleString a)
pattern Symbol a = Simple (SimpleSymbol a) pattern Symbol a = Simple (SimpleSymbol a)
pattern Bytevector a = Simple (SimpleBytevector a) pattern Bytevector a = Simple (SimpleBytevector a)
pattern Unreadable a = Simple (SimpleUnreadable a)
--- Lift1 instances --- Lift1 instances
+29
View File
@@ -0,0 +1,29 @@
module Gyehoek.Stack.Lower
( lowerProgram
) where
import Gyehoek.Stack.Syntax
import Gyehoek.Wasm qualified as Wasm
import Gyehoek.Prelude
import Gyehoek.Wasm (wat, watM)
lowerRoutine :: Routine -> Wasm.Function
lowerRoutine rt = _
lowerBlock :: Block -> Wasm.Expr
lowerBlock = _
lowerInstr :: Instr -> Wasm.Expr
lowerInstr = \case
-- PopCont ktail -> [wat|
-- |]
lowerProgram :: Program -> Eff es Wasm.Module
lowerProgram p = pure [watM|
(module
##{rs})
|]
where
rs = p ^.. #routines . each . to lowerRoutine
+29 -24
View File
@@ -14,8 +14,11 @@ module Gyehoek.Stack.Syntax
, Imm(..) , Imm(..)
, Hob(..) , Hob(..)
, Prim(..) , Prim(..)
, Name , Name(..)
, Reg(..)
, Label(..)
, pattern ValLabel , pattern ValLabel
, pattern ObjLabel
, stkP , stkP
) where ) where
@@ -24,13 +27,13 @@ import qualified Gyehoek.Sexp as S
import Gyehoek.Scheme.Syntax (Name(..), Lit(..), Prim(..)) import Gyehoek.Scheme.Syntax (Name(..), Lit(..), Prim(..))
import GHC.Exts (IsList(..)) import GHC.Exts (IsList(..))
import Data.List (intersperse) import Data.List (intersperse)
import Gyehoek.CPS.Syntax (Imm(..), Obj(..), Hob(..), labelName) import Gyehoek.CPS.Syntax (Imm(..), Obj(..), Hob(..), pattern ObjLabel, Reg, Label)
import Gyehoek.Prelude import Gyehoek.Prelude
import Gyehoek.Sexp ((:-)((:-))) import Gyehoek.Sexp ((:-)((:-)))
newtype Program = MkProgram newtype Program = MkProgram
{ routines :: HashMap Name Routine { routines :: HashMap Label Routine
} }
deriving stock (Show, Generic, Data) deriving stock (Show, Generic, Data)
deriving newtype (Semigroup, Monoid) deriving newtype (Semigroup, Monoid)
@@ -44,8 +47,7 @@ instance IsList Program where
toList = toListOf $ #routines . each toList = toListOf $ #routines . each
data Routine = MkRoutine data Routine = MkRoutine
{ label :: Name { label :: Label
, params :: List Name
, start :: Block , start :: Block
} }
deriving stock (Show, Generic, Data) deriving stock (Show, Generic, Data)
@@ -59,27 +61,32 @@ data Block = MkBlock
deriving anyclass (NFData) deriving anyclass (NFData)
data Tail data Tail
= TailCall Val (List Val) -- | call the procedure at stack index `n` supplied with `n`
-- arguments on top of the stack, then return by calling the
-- continuation at stack index `n+1`.
= TailCall Int
| Call Int
| If Val Block Block | If Val Block Block
| Return Int
| CallCC
deriving stock (Show, Generic, Data) deriving stock (Show, Generic, Data)
deriving anyclass (NFData) deriving anyclass (NFData)
data Instr data Instr
= Pop Name = Pop Reg
| Push Val | Push Val
| PopCont Name | Load Reg Int
| PushCont Val | Prim Reg (Prim Val)
| Prim Name (Prim Val)
deriving stock (Show, Generic, Data) deriving stock (Show, Generic, Data)
deriving anyclass (NFData) deriving anyclass (NFData)
data Val data Val
= ValReg Name = ValReg Reg
| ValImm Imm | ValImm Imm
deriving stock (Show, Generic, Data, Eq) deriving stock (Show, Generic, Data, Eq)
deriving anyclass (NFData) deriving anyclass (NFData)
pattern ValLabel :: Name -> Val pattern ValLabel :: Label -> Val
pattern ValLabel x = ValImm (ImmLabel x) pattern ValLabel x = ValImm (ImmLabel x)
@@ -89,11 +96,10 @@ pure []
instance S.DatumIso Instr where instance S.DatumIso Instr where
datumIso = S.match datumIso = S.match
$ S.With (S.headTagged1 "pop!" regName >>>) $ S.With (S.headTagged1 "pop!" S.datumIso >>>)
$ S.With (S.headTagged1 "push!" S.datumIso >>>) $ S.With (S.headTagged1 "push!" S.datumIso >>>)
$ S.With (S.headTagged1 "pop-cont!" regName >>>) $ S.With (S.headTagged2 "load" S.datumIso S.datumIso >>>)
$ S.With (S.headTagged1 "push-cont!" S.datumIso >>>) $ S.With (S.headTagged2 "prim" S.datumIso S.datumIso >>>)
$ S.With (S.headTagged2 "prim" regName S.datumIso >>>)
$ S.End $ S.End
where where
@@ -107,10 +113,14 @@ instance S.DataIso Block where
instance S.DatumIso Tail where instance S.DatumIso Tail where
datumIso = S.match datumIso = S.match
$ S.With (S.headTagged1' "tail-call" S.datumIso S.datumIso >>>) $ S.With (S.headTagged1 "tail-call" S.datumIso >>>)
$ S.With (S.headTagged1 "call" S.datumIso >>>)
$ S.With (if_ >>>) $ S.With (if_ >>>)
$ S.With (S.headTagged1 "return" S.datumIso >>>)
$ S.With (S.headTagged0 "call/cc" >>>)
$ S.End $ S.End
where where
-- if_ = S.ifLike "if" (S.datumIso @Val) S.datumIso S.datumIso
if_ = S.ifLike "if" (S.datumIso @Val) (branch "then") (branch "else") if_ = S.ifLike "if" (S.datumIso @Val) (branch "then") (branch "else")
branch :: Text -> S.DatumGrammar Block branch :: Text -> S.DatumGrammar Block
branch s = branch s =
@@ -120,7 +130,7 @@ instance S.DatumIso Tail where
instance S.DatumIso Val where instance S.DatumIso Val where
datumIso = S.match datumIso = S.match
$ S.With (regName >>>) $ S.With (S.datumIso >>>)
$ S.With (S.datumIso >>>) $ S.With (S.datumIso >>>)
$ S.End $ S.End
@@ -128,16 +138,11 @@ instance S.DatumIso Routine where
datumIso = S.with \rout -> datumIso = S.with \rout ->
S.listWithIndentation (S.NSpecial 1) S.listWithIndentation (S.NSpecial 1)
( S.el (S.decorate S.SynBuiltin >>> S.sym "define") ( S.el (S.decorate S.SynBuiltin >>> S.sym "define")
>>> S.el (S.list $ S.el labelName >>> S.rest regName) >>> S.el (S.datumIso @Label)
>>> S.restData (S.dataIso @Block) >>> S.restData (S.dataIso @Block)
) )
>>> rout >>> rout
regName :: S.DatumGrammar Name
regName = S.decorate S.SynVariable >>> S.datumIso @Name >>> S.prismIso
(S.expected "register")
(prefixed @Name "%")
instance S.DataIso Program where instance S.DataIso Program where
dataIso = S.dataIso @(List Routine) >>> S.iso fromList toList dataIso = S.dataIso @(List Routine) >>> S.iso fromList toList
+431 -80
View File
@@ -1,4 +1,6 @@
{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE ViewPatterns, MultilineStrings #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DeriveAnyClass #-}
module Gyehoek.Stack.VM module Gyehoek.Stack.VM
( VM(..) ( VM(..)
, Env(..) , Env(..)
@@ -6,122 +8,313 @@ module Gyehoek.Stack.VM
, trace , trace
, module Gyehoek.Stack.Syntax , module Gyehoek.Stack.Syntax
, writeObj , writeObj
, traceEval
) where ) where
import Gyehoek.Stack.Syntax import Gyehoek.Stack.Syntax
import Control.Lens import Control.Lens
import qualified Data.HashMap.Strict as H import qualified Data.HashMap.Strict as H
import Data.List (unfoldr) import Data.List (unfoldr, intersperse, compareLength)
import Gyehoek.Prelude import Gyehoek.Prelude
import qualified Data.List.NonEmpty as NE
import Lucid
import Data.Foldable (traverse_)
import qualified Gyehoek.Sexp as S
import Gyehoek.Jalmot
import Text.Pretty.Simple (pStringNoColor, pShowNoColor)
import Effectful.State.Static.Local (runState, evalState, get)
import Data.Traversable
import Control.Applicative (Alternative(..))
import Gyehoek.Sexp.Print (htmlData)
import Control.DeepSeq (deepseq, ($!!))
import Gyehoek.Sexp.Print (htmlData, htmlDatum)
import Control.DeepSeq (deepseq, ($!!))
import Data.String (fromString)
import Data.Monoid (First)
import GHC.Stack (popCallStack)
import Data.Maybe (fromMaybe)
-- | non-essential information maintained only to aide in debugging.
data DebugVM = MkDebugVM
{ activeRoutine :: Label
}
deriving (Show, Generic)
newtype Frame = MkFrame { locals :: List Obj }
deriving stock (Show, Generic)
-- affine
returnAddress :: Traversal' Frame Obj
returnAddress = #locals . _last
-- affine
activeProcedure :: Traversal' Frame Obj
activeProcedure = #locals . _init . _last
newtype Stack = MkStack { frames :: NonEmpty Frame }
deriving stock (Show, Generic)
data VM = MkVM data VM = MkVM
{ stack :: List Obj { stack :: Stack
, kstack :: List Name
, code :: List Instr , code :: List Instr
, tail :: Tail , tail :: Tail
, registers :: HashMap Name Obj , registers :: HashMap Reg Obj
, stdout :: Text , stdout :: Text
, result :: Maybe (List Obj) , result :: Maybe (List Obj)
, debug :: DebugVM
} }
deriving (Show, Generic) deriving (Show, Generic)
type instance Index Frame = Int
type instance IxValue Frame = Obj
instance Ixed Frame where
ix j = wrappedIso . ix j
instance Cons Frame Frame Obj Obj where
_Cons = prism'
(\(x,MkFrame xs) -> MkFrame (x:xs))
\case
MkFrame (x:xs) -> Just (x, MkFrame xs)
MkFrame [] -> Nothing
instance Each Frame Frame Obj Obj where each = wrappedIso . each
instance Each Stack Stack Frame Frame where each = wrappedIso . each
pushes :: Foldable f => f Obj -> Frame -> Frame
pushes = flip $ foldr cons
_NonEmpty :: Iso (NonEmpty a) (NonEmpty b) (a, List a) (b, List b)
_NonEmpty = iso
(\(x:|xs) -> (x,xs))
(\(x,xs) -> x:|xs)
pushFrame :: Frame -> Stack -> Stack
pushFrame f (MkStack xs) = MkStack $ NE.cons f xs
activeFrame :: Lens' VM Frame
activeFrame = #stack . #frames . _NonEmpty . _1
data Env = MkEnv data Env = MkEnv
{ labels :: HashMap Name Routine { labels :: HashMap Label Routine
} }
deriving (Show, Generic) deriving (Show, Generic)
step :: Env -> VM -> VM step :: Jalmot :> es => Env -> VM -> Eff es VM
step g vm = case vm ^. #code of step g vm = case vm ^. #code of
c:cs -> stepI g (vm & #code .~ cs) c c:cs -> stepI g (vm & #code .~ cs) c
[] -> stepT g vm vm.tail [] -> stepT g vm vm.tail
stepI :: Env -> VM -> Instr -> VM vmerror :: (HasCallStack, Jalmot :> es) => Text -> Eff es a
vmerror = throwError . VMError
stepI e vm (Push v) = vm & #stack %~ (evalVal e vm v :) stepI :: Jalmot :> es => Env -> VM -> Instr -> Eff es VM
stepI e vm (PushCont k) = vm & #kstack %~ (evalToLabel e vm k :) stepI e vm ins = vmerror [i|unimplemented instruction: #{ins}|]
stepI e vm (Prim r p) = case evalVal e vm <$> p of 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
rt <- getRoutine g f
let newFrame = MkFrame $ args ++ [f,ret]
pure $ vm
& jumpToRoutine rt
& activeFrame .~ frm
-- 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)
& expectOf [i|bad return: #{show tc}|] _Just
expectOf [i|no return addr|] (activeFrame . returnAddress) vm >>= \case
ObjLabel "halt" -> pure $ vm & #result ?~ xs
ra -> do
rt <- getRoutine g ra
vm & traverseOf #stack (fmap snd . popFrame)
& mapped . activeFrame %~ pushes xs
& mapped %~ jumpToRoutine rt
-- it is not essential we clear the registers, but it'll make
-- bugs more obvious.
& mapped . #registers .~ mempty
stepT g vm tc@(TailCall nargs) = do
(args,f,ra) <- parseTailCall nargs (vm ^. activeFrame)
& expectOf [i|bad call: #{show tc}|] _Just
case f of
ObjLabel "halt" -> pure $ vm & #result ?~ args
_ -> do
rt <- getRoutine g f
let newFrame = MkFrame $ args ++ [f, ra]
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
stepT g vm (If c t f) = do
branch <- evalVal g vm c <&> \case
ObjImm (ImmBool False) -> f
_ -> 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 PrimZeroP x -> case x of
ObjImm (ImmInt n) -> ret . ObjImm . ImmBool $ n == 0 ObjImm (ImmInt n) -> ret1 . ObjImm . ImmBool $ n == 0
_ -> error [i|bad arg to zero?: #{x}|] _ -> vmerror [i|bad arg to zero?: #{x}|]
PrimAdd x y -> arith_binop (+) x y PrimAdd x y -> arith_binop (+) x y
PrimMul x y -> arith_binop (*) x y PrimMul x y -> arith_binop (*) x y
PrimSub x y -> arith_binop (-) x y PrimSub x y -> arith_binop (-) x y
PrimDiv x y -> arith_binop div x y PrimDiv x y -> arith_binop div x y
PrimMakeClosure f env -> PrimMakeClosure f env ->
case f of case f of
ObjImm (ImmLabel l) -> ret . ObjHob $ HobClosure l env ObjImm (ImmLabel l) -> ret1 . ObjHob $ HobClosure l env
_ -> error [i|expected label, got #{f}|] _ -> vmerror [i|expected label, got #{f}|]
PrimEnvCode env -> PrimEnv -> do
case env of x <- vm & expectOf "expected closure" (activeFrame . activeProcedure)
ObjHob (HobClosure l _) -> ret . ObjImm . ImmLabel $ l ret1 x
_ -> error [i|expected closure, got #{env}|] PrimEnvRef n -> do
PrimEnvRef env n -> (label,env) <- vm & expectOf "expected closure"
case env of (activeFrame . activeProcedure . #_ObjHob . #_HobClosure)
ObjHob (HobClosure _ xs) -> ret $ xs ^?! ix n x <- env & expectOf "expected upval" (ix n)
_ -> error [i|expected closure, got #{env}|] ret1 x
x -> error [i|unimplemented prim: #{p}|] PrimCons x y -> ret1 $ ObjHob $ HobPair x y
PrimCar x -> case x of
ObjHob (HobPair car _) -> ret1 car
_ -> vmerror [i|expected pair, got ${x}|]
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 where
ret v = vm & #registers . at r ?~ v ret vs = pure $ vm & activeFrame . #locals <>:~ vs
ret1 v = ret [v]
arith_binop op (ObjImm (ImmInt x)) (ObjImm (ImmInt y)) = arith_binop op (ObjImm (ImmInt x)) (ObjImm (ImmInt y)) =
ret $ ObjImm (ImmInt (op x y)) ret1 $ ObjImm (ImmInt (op x y))
arith_binop _ x y = error [i|bad arith: #{x}, #{y}|] arith_binop _ x y = vmerror [i|bad arith: #{x}, #{y}|]
stepI e vm (Pop r) = case vm ^. #stack of
[] -> error "empty stack"
(x:xs) -> vm & #registers . at r ?~ x
& #stack .~ xs
stepI e vm ins@(PopCont r) = case vm ^. #kstack of popFrame :: (HasCallStack, Jalmot :> es) => Stack -> Eff es (Frame, Stack)
[] -> error [i|empty cont stack: #{ins}|] popFrame stk = case stk ^. #frames . to NE.uncons of
(x:xs) -> vm & #registers . at r ?~ ObjImm (ImmLabel x) (_, Nothing) -> vmerror "no frame to pop"
& #kstack .~ xs (f, Just fs) -> pure (f, stk & #frames .~ fs)
stepI e vm ins = error [i|unimplemented instruction: #{ins}|] jumpToBlock :: Block -> VM -> VM
jumpToBlock b vm = vm
& #code .~ b.code
& #tail .~ b.tail
stepT :: Env -> VM -> Tail -> VM jumpToRoutine :: Routine -> VM -> VM
jumpToRoutine rt vm = vm
& jumpToBlock rt.start
& #debug . #activeRoutine .~ rt.label
stepT g vm (TailCall f xs) = getLabel :: Obj -> Maybe Label
case evalToLabel g vm f of getLabel = \case
"halt" -> vm & #result ?~ fmap (evalVal g vm) xs ObjHob (HobClosure {label}) -> Just label
l -> vm & #code .~ rt.start.code ObjHob (HobContinuation {cont}) -> getLabel cont
& #tail .~ rt.start.tail ObjImm (ImmLabel label) -> Just label
& #registers .~ x -> Nothing
fmap (evalVal g vm) (H.fromList $ rt.params `zip` xs)
where
rt = case g ^. #labels . at l of
Nothing -> error [i|undefined label: #{l}|]
Just x -> x
stepT g vm (If c t f) = vm & #code .~ branch.code & #tail .~ branch.tail getRoutine :: (HasCallStack, Jalmot :> es) => Env -> Obj -> Eff es Routine
where getRoutine g f = do
branch = case evalVal g vm c of l <- getLabel f & expectOf [i|no label for #{f}|] _Just
ObjImm (ImmBool False) -> f case g ^. #labels . at l of
_ -> t Just rt -> pure rt
Nothing -> vmerror [i|undefined label #{l}|]
expectOf
:: (HasCallStack, Jalmot :> es)
=> Text -> Getting (First a) s a -> s -> Eff es a
expectOf msg l = maybe (vmerror msg) pure . preview l
evalToLabel :: Jalmot :> es => Env -> VM -> Val -> Eff es Label
evalToLabel e vm v = evalToLabel e vm v =
case evalVal e vm v of evalVal e vm v >>= \case
ObjImm (ImmLabel x) -> x ObjImm (ImmLabel x) -> pure x
x -> error [i|not a label: #{x}|] x -> vmerror [i|not a label: #{x}|]
evalVal :: Env -> VM -> Val -> Obj evalVal :: Jalmot :> es => Env -> VM -> Val -> Eff es Obj
evalVal e vm = \case evalVal e vm = \case
ValImm imm -> ObjImm imm ValImm imm -> pure $ ObjImm imm
ValReg r -> case vm ^. #registers . at r of ValReg r -> case vm ^. #registers . at r of
Just x -> x Just x -> pure x
Nothing -> error [i|undefined register: #{r}|] Nothing -> vmerror [i|undefined register: #{r}|]
splitAtExact :: Int -> List a -> Maybe (List a, List a)
splitAtExact n xs = case compareLength xs n of
(EQ;GT) -> Just $ splitAt n xs
LT -> Nothing
takeExact :: Int -> List a -> Maybe (List a)
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)
let (xs',[f,ret]) = splitAt nargs xs
pure (xs',f,ret,MkFrame ys)
parseTailCall :: Int -> Frame -> Maybe (List Obj, Obj, Obj)
parseTailCall nargs frm = do
(xs,_) <- splitAtExact (nargs+1) (frm ^. #locals)
let (xs',f) = xs ^?! _Snoc
pure (xs',f,frm ^?! returnAddress)
initialVM :: VM initialVM :: VM
initialVM = MkVM initialVM = MkVM
{ stack = [] { stack = MkStack . NE.singleton . MkFrame $
, kstack = ["halt"] [ ObjLabel "start"
, ObjLabel "<nowhere at all>"
, ObjLabel "halt"
]
, tail = TailCall 0
, code = [] , code = []
, tail = TailCall (ValLabel "main") []
, registers = mempty , registers = mempty
, stdout = "" , stdout = ""
, result = Nothing , result = Nothing
, debug = MkDebugVM
{ activeRoutine = "<nowhere>"
}
} }
initialEnv :: Program -> Env initialEnv :: Program -> Env
@@ -134,23 +327,181 @@ loop f a = case f a of
Right a' -> loop f a' Right a' -> loop f a'
Left b -> b Left b -> b
eval :: Program -> List Obj loopM :: Monad m => (a -> m (Either b a)) -> a -> m b
eval p = initialVM & loop \vm -> case vm ^. #result of loopM f a = f a >>= \case
Nothing -> Right $ step (initialEnv p) vm Right a' -> loopM f a'
Just rs -> Left rs Left b -> pure b
trace :: Program -> List VM eval :: Jalmot :> es => Program -> Eff es (List Obj)
trace p = initialVM & unfoldr \vm -> eval p = initialVM & loopM \vm -> case vm ^. #result of
case vm.result of Nothing -> Right <$> step (initialEnv p) vm
Just _ -> Nothing Just rs -> pure . Left $ rs
Nothing -> Just (vm, step e vm)
where e = initialEnv p data Trace
= Step { vm :: VM, next :: Trace }
| StepToSuccess { vm :: VM, result :: List Obj }
| StepToFailure { vm :: VM, err :: AJalmotCS }
deriving (Show)
trace :: Program -> Trace
trace p = go (initialEnv p) initialVM
where
go g vm =
case vm.result of
Just rs -> StepToSuccess vm rs
Nothing ->
case runPureEff . runJalmotCS $ step g vm of
Left err -> StepToFailure vm err
Right vm' -> Step vm (go g vm')
writeObj :: Obj -> Text writeObj :: Obj -> Text
writeObj (ObjImm im) = case im of writeObj = runJalmotUnsafe . S.encodeWith' S.datumIso
ImmInt n -> [i|#{n}|]
ImmBool True -> "#t" traceEval :: IOE :> es => Program -> Eff es ()
ImmBool False -> "#f" traceEval p = do
ImmLabel l -> "#<procedure>" let t = trace p
writeObj (ObjHob h) = case h of liftIO . renderToFile "trace.html" . ppDoc p $ t
HobClosure code env -> "#<procedure>"
ppDoc :: Program -> Trace -> Html ()
ppDoc p t =
html_ do
head_ do
title_ "stackify trace"
style_ """
pre {
max-width: 95vw;
overflow: scroll;
}
table {
max-width: 95vw;
}
tbody > tr:nth-of-type(even) {
background-color: rgb(237 238 242);
}
.loc {
font-size: 0.8rem;
}
.syn-builtin, .syn-macro {
color: purple;
font-style: italic;
font-weight: bold;
}
.syn-constant {
color: olive;
}
.syn-procedure {
color: teal;
}
td pre {
display: inline
}
.syn-paren-0 { color: maroon; }
.syn-paren-1 { color: olive; }
.syn-paren-2 { color: green; }
.syn-paren-3 { color: navy; }
.syn-paren-4 { color: purple; }
.stack-frame
{ display: inline-flex
; flex-direction: row
; column-gap: 0.5em
}
"""
body_ do
details_ do
summary_ "stack code"
pre_ $ code_ do
htmlData . runJalmotUnsafe . S.toData S.dataIso $ p
ppTrace t
ppTrace :: Trace -> Html ()
ppTrace trace =
table_ do
thead_ $ tr_ do
traverse_ (th_ [scope_ "col"])
["routine","next instruction","stack frame"]
tbody_ do
go trace
where
go :: Trace -> Html ()
go = \case
Step vm next -> ppVM vm >> go next
StepToSuccess vm rs -> do
tr_ [class_ "trace-result"] do
td_ do
details_ do
summary_ "result"
pre_ do
code_ . toHtml . pShowNoColor $ vm
td_ [colspan_ "2"] do
sequence_ . intersperse " | " $ code_ . ppDatum <$> rs
StepToFailure vm err -> do
ppVM vm
tr_ [class_ "trace-failure"] do
td_ [colspan_ "3"] do
details_ do
summary_ "error"
pre_ do
samp_ do
fromString $ displayException err
ppVM :: VM -> Html ()
ppVM vm = do
tr_ do
td_ do
details_ do
summary_ do
var_ [class_ "loc"] do
vm ^. #debug . #activeRoutine . to ppDatum
pre_ do
code_ . toHtml . pShowNoColor $ vm
td_ do
code_ curi
td_ do
ppStack vm.stack
where
curi = vm ^?! failing (#code . _head . to ppDatum) (#tail . to ppDatum)
ppStack :: Stack -> Html ()
ppStack stk = do
span_ [class_ "stack"] do
stk ^.. each
& fmap ppFrame
& intersperse " | "
& sequence_
ppFrame :: Frame -> Html ()
ppFrame frm = do
span_ [class_ "stack-frame"] do
sequence_ $ frm ^.. #locals . each . to ppDatum
ppData :: S.DataIso a => a -> Html ()
ppData = htmlData . runJalmotUnsafe . S.toData S.dataIso
ppDatum :: S.DatumIso a => a -> Html ()
ppDatum = htmlDatum . runJalmotUnsafe . S.toDatum S.datumIso
fac (n :: Int) = [stkP|
(define $start
(push! $fac)
(push! #{n})
(tail-call 1))
(define $fac
(load %n 0)
(prim %x0 (zero? %n))
(if %x0
(then (push! 1)
(return 1))
(else (prim %x1 (- %n 1))
(push! $fac-c0)
(push! $fac)
(push! %x1)
(call 1))))
(define $fac-c0
(pop! %x2)
(pop! %n)
(prim %x3 (* %n %x2))
(push! %x3)
(return 1))
|]
+18 -159
View File
@@ -6,182 +6,41 @@ module Gyehoek.Wasm
( (
-- * syntax -- * syntax
Module Module
, Idx , Program
, Function
, Expr , Expr
-- ** quasiquoters -- ** quasiquoters
, expr , watM
, S.sx
, S.sxs
-- * GenMod effect
, GenMod
, runGenMod
, execGenMod
, defineFunction
, defineType
, defineGlobal
, emit
, renderModule
, wat , wat
, wats
, defineFunctions
, defineTypes
, defineGlobals
) )
where where
import Data.List (List) import Data.List (List)
import GHC.Generics (Generic) import GHC.Generics (Generic)
import Data.Text (Text)
import Effectful
import Numeric.Natural (Natural)
import Effectful.Dispatch.Dynamic
import Effectful.State.Dynamic
import Control.Lens
import Data.Vector.Strict (Vector)
import qualified Data.Vector.Strict as V
import GHC.IsList (IsList(..))
import Language.Haskell.TH.Quote (QuasiQuoter) import Language.Haskell.TH.Quote (QuasiQuoter)
import Data.Data (Data)
import Gyehoek.Sexp qualified as S import Gyehoek.Sexp qualified as S
import Gyehoek.Sexp (Datum, sx, (>>>)) import Gyehoek.Sexp (Datum, (>>>))
import Data.Foldable (traverse_) import Data.Data (Data)
import Data.Coerce (coerce)
newtype Module = MkModule { inner :: Vector Datum } type Program = Module
deriving (Show, Generic) type Function = Datum
type Expr = List Datum
newtype Module = MkModule { inner :: List Datum }
deriving (Show, Generic, Data)
deriving newtype (Semigroup, Monoid) deriving newtype (Semigroup, Monoid)
newtype Expr = MkExpr { inner :: Vector Instr } instance S.DatumIso Module where
deriving (Show, Generic, Data, Eq) datumIso = S.with \g ->
deriving newtype (Semigroup, Monoid) S.list (S.el (S.sym "module") >>> S.rest S.datumIso)
>>> g
instance IsList Expr where
type Item Expr = Instr
fromList = MkExpr . V.fromList
toList = V.toList . view #inner
newtype Instr = MkInstr { inner :: Datum }
deriving (Show, Generic, Data, Eq)
newtype Idx = MkIdx { inner :: Natural }
deriving (Generic, Data)
deriving newtype (Show)
-- GenMod
-- | 'GenModState' is a 'Module' paired with the numbers of functions,
-- types, globals, etc. defined in the module.
data GenModState = MkGenModState
{ mod :: Module
, funcs :: Natural
, types :: Natural
, globals :: Natural
}
deriving (Show, Generic)
instance Semigroup GenModState where
m1 <> m2 = MkGenModState
{ mod = m1.mod <> m2.mod
, funcs = m1.funcs + m2.funcs
, types = m1.types + m2.types
, globals = m1.globals + m2.globals
}
instance Monoid GenModState where
mempty = MkGenModState
{ mod = mempty
, funcs = 0
, types = 0
, globals = 0
}
data GenMod :: Effect where
DefineFunction :: Datum -> GenMod m Idx
DefineType :: Datum -> GenMod m Idx
DefineGlobal :: Datum -> GenMod m Idx
Emit :: Datum -> GenMod m ()
type instance DispatchOf GenMod = Dynamic
defineFunction :: GenMod :> es => Datum -> Eff es Idx
defineFunction = send . DefineFunction
defineFunctions :: GenMod :> es => List Datum -> Eff es (List Idx)
defineFunctions = traverse (send . DefineFunction)
defineType :: GenMod :> es => Datum -> Eff es Idx
defineType = send . DefineType
defineTypes :: GenMod :> es => List Datum -> Eff es (List Idx)
defineTypes = traverse (send . DefineType)
defineGlobal :: GenMod :> es => Datum -> Eff es Idx
defineGlobal = send . DefineGlobal
defineGlobals :: GenMod :> es => List Datum -> Eff es (List Idx)
defineGlobals = traverse (send . DefineGlobal)
emit :: GenMod :> es => List Datum -> Eff es ()
emit = traverse_ (send . Emit)
appendAndIncrement
:: State GenModState :> es
=> LensLike' ((,) Natural) GenModState Natural
-> Datum
-> Eff es Idx
appendAndIncrement l s =
state \st -> st
& #mod . #inner <>~ V.singleton s
& l <<%~ succ
& _1 %~ MkIdx
runGenMod :: Eff (GenMod : es) a -> Eff es (a, Module)
runGenMod =
let run = (mapped . _2 %~ view #mod) . runStateLocal (mempty @GenModState)
in reinterpret run \cases
_ (DefineFunction s) -> appendAndIncrement #funcs s
_ (DefineType s) -> appendAndIncrement #types s
_ (DefineGlobal s) -> appendAndIncrement #globals s
_ (Emit s) -> #mod . #inner <>= V.singleton s
execGenMod :: Eff (GenMod : es) a -> Eff es Module
execGenMod = fmap snd . runGenMod
renderModule :: Module -> Text
renderModule (MkModule ss) = S.encodeWith' S.datumIso [sx|
(module ##{ss})
|]
-- DatumIso instances
instance S.DatumIso Idx where
datumIso = S.with \idx ->
S.integer >>> S.partialOsi f g
>>> idx
where
f n | n < 0 = Left $ S.unexpected "negative"
<> S.expected "natural"
| otherwise = Right $ fromIntegral n
g = fromIntegral
instance S.DatumIso Instr where
datumIso = S.with S.id
instance S.DataIso Expr where
dataIso = S.dataIso @(Vector Instr) >>> S.iso coerce coerce
-- quasiquoters -- quasiquoters
expr :: QuasiQuoter
expr = S.makeSxs
[|| MkExpr . V.fromList . fmap (S.fromDatumUnsafe $ S.datumIso @Instr) ||]
wat :: QuasiQuoter wat :: QuasiQuoter
wat = S.makeSx [|| id ||] wat = S.makeSxs [|| S.fromDataUnsafe (S.dataIso @(List Datum)) ||]
wats :: QuasiQuoter watM :: QuasiQuoter
wats = S.makeSxs [|| id ||] watM = S.makeSx [|| S.fromDatumUnsafe (S.datumIso @Module) ||]
BIN
View File
Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
((λ ()
(* 2 (call/cc
(λ (k)
(begin (k 6)
3))))))
+68 -43
View File
@@ -5,50 +5,75 @@ import Test.Tasty.HUnit
import Gyehoek.CPS.Syntax (cps, Obj(..), Hob(..), Imm(..)) import Gyehoek.CPS.Syntax (cps, Obj(..), Hob(..), Imm(..))
import Gyehoek.CPS.Eval qualified as Sut import Gyehoek.CPS.Eval qualified as Sut
import Data.List (List) import Data.List (List)
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 = testGroup "cps interpreter" $ brokenEvalTests :: List String
[ primitives brokenEvalTests =
, testCase "halt with constant" do []
evalsTo [ObjImm (ImmInt 123)] [cps| -- [ "adder"
(continue halt 123) -- , "apply2"
|] -- , "apply-twice"
, testCase "identity cont" do -- , "arith"
evalsTo [ObjImm (ImmInt 154)] [cps| -- , "begin-1"
(letrec ((id (κ (x) -- , "callcc-constant"
(continue halt x)))) -- , "callcc-discard"
(continue id 154)) -- , "callcc-early-exit-1"
|] -- , "callcc-early-exit-2"
, testCase "identity function" do -- , "callcc-early-exit-3"
evalsTo [ObjImm (ImmInt 456)] [cps| -- , "callcc-early-exit-4"
(letrec ((id (λ (x ktail) -- , "callcc-early-exit-5"
(continue ktail x)))) -- , "callcc-early-exit-6"
(id 456 halt)) -- , "callcc-nested-1"
|] -- , "callcc-nested-2"
, testCase "square" do -- , "complicated-1"
evalsTo [ObjImm (ImmInt 81)] [cps| -- , "cons-1"
(letrec ((square (λ (x ktail) -- , "factorial"
(prim (* x x) -- , "false"
(κ (r) (continue ktail r)))))) -- , "fn-of-fn"
(square 9 halt)) -- , "if-false"
|] -- , "if-number"
] -- , "if-true"
-- , "lambda"
-- , "letrec-fn"
-- , "let-fn"
-- , "lit-int"
-- , "square"
-- , "true"
-- ]
evalsTo :: HasCallStack => List Obj -> Sut.Program -> Assertion test_eval :: IO TestTree
evalsTo rs p = Sut.evalProgram p @?= rs test_eval = do
cs <- listDirectory "golden/exec"
primitives = testGroup "primitives" <&> fmap ("golden/exec" </>)
[ testGroup "arith" pure $ testGroup "cps interpreter"
[ testCase "basic 1" do [ testGroup "higher-order" $ cpsCase Driver.eval_cps2_e2e <$> cs
evalsTo [ObjImm (ImmInt 20)] [cps| , testGroup "first-order" $ cpsCase Driver.eval_cps_e2e <$> cs
(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)))))
|]
] ]
]
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)
+79 -69
View File
@@ -4,83 +4,93 @@ import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit import Test.Tasty.HUnit
import qualified Gyehoek.CPS.Stackify as Sut import qualified Gyehoek.CPS.Stackify as Sut
import Gyehoek.Stack.VM as Stk import Gyehoek.Stack.VM as Stk
import Data.List (List)
import Gyehoek.CPS.Syntax (cps) import Gyehoek.CPS.Syntax (cps)
import Gyehoek.CPS.Syntax qualified as CPS
import Gyehoek.GenSym (runGenSym) import Gyehoek.GenSym (runGenSym)
import Effectful import Effectful
import Gyehoek.Prelude
import Gyehoek.Jalmot
import Test.Tasty.ExpectedFailure (expectFail, ignoreTestBecause)
test_stackify = -- test_stackify =
[ trivialReturn -- [ trivialReturn
, tailCall -- , tailCall
, prim -- , prim
, condition -- , condition
, procedure -- , procedure
] -- ]
evalsTo :: List Obj -> Sut.Exp -> Assertion -- evalsTo :: HasCallStack => List Obj -> Sut.Program -> Assertion
evalsTo rs e = -- evalsTo rs e = runJalmotUnsafe (Stk.eval e') @?= rs
Stk.eval e' @?= rs -- where
where e' = runPureEff . runGenSym $ Sut.stackifyExp "main" e -- e' = e & Sut.stackifyProgram & runGenSym & runPureEff
trivialReturn = testGroup "trivial return" -- trivialReturn = testGroup "trivial return"
[ testCase "return int" do -- [ testCase "return int" do
evalsTo [ObjImm (ImmInt 4)] -- evalsTo [ObjImm (ImmInt 4)]
[cps|(continue halt 4)|] -- [cps|(λ (ktail) (continue ktail 4))|]
, testCase "return bool" do -- , testCase "return bool" do
evalsTo [ObjImm (ImmBool True)] -- evalsTo [ObjImm (ImmBool True)]
[cps|(continue halt #t)|] -- [cps|(λ (ktail) (continue ktail #t))|]
evalsTo [ObjImm (ImmBool False)] -- evalsTo [ObjImm (ImmBool False)]
[cps|(continue halt #f)|] -- [cps|(λ (ktail) (continue ktail #f))|]
] -- ]
tailCall = testGroup "tail call" -- tailCall = testGroup "tail call"
[ testCase "square" do -- [ testCase "square" do
evalsTo [ObjImm (ImmInt 16)] -- evalsTo [ObjImm (ImmInt 16)] [cps|
[cps|(letrec ((square (λ (x ktail) -- (λ (ktail0)
(prim (* x x) -- (letrec ((square (λ (x ktail)
(κ (x0) (continue ktail x0)))))) -- (prim (* x x)
(square 4 halt))|] -- (κ (x0) (continue ktail x0))))))
] -- (square 4 halt)))
-- |]
-- ]
prim = testGroup "prim" -- prim = testGroup "prim"
[ testCase "multiply" do -- [ testCase "multiply" do
evalsTo [ObjImm (ImmInt 20)] -- evalsTo [ObjImm (ImmInt 20)]
[cps|(prim (* 4 5) -- [cps|(λ (ktail0)
(κ (x) (continue halt x)))|] -- (prim (* 4 5)
, testCase "add" do -- (κ (x) (continue ktail0 x))))|]
evalsTo [ObjImm (ImmInt 9)] -- , testCase "add" do
[cps|(prim (+ 4 5) -- evalsTo [ObjImm (ImmInt 9)]
(κ (x) (continue halt x)))|] -- [cps|(λ (ktail0)
-- , testGroup "call/cc" -- (prim (+ 4 5)
-- [ testCase "trivial" do -- (κ (x) (continue ktail0 x))))|]
-- evalsTo [ObjImm (ImmInt 123)] -- -- , testGroup "call/cc"
-- [cps|(letrec ((f (λ (cc ktail) (continue cc 123)))) -- -- [ testCase "trivial" do
-- (prim (call/cc f)))|] -- -- evalsTo [ObjImm (ImmInt 123)]
-- ] -- -- [cps|(letrec ((f (λ (cc ktail) (continue cc 123))))
] -- -- (prim (call/cc f)))|]
-- -- ]
-- ]
condition = testCase "if" do -- condition = testCase "if" do
evalsTo [ObjImm (ImmInt 123)] -- evalsTo [ObjImm (ImmInt 123)]
[cps|(if #t (continue halt 123) (continue halt 456))|] -- [cps|(λ (ktail0)
evalsTo [ObjImm (ImmInt 456)] -- (if #t (continue ktail0 123) (continue ktail0 456)))|]
[cps|(if #f (continue halt 123) (continue halt 456))|] -- evalsTo [ObjImm (ImmInt 456)]
-- [cps|(λ (ktail0)
-- (if #f (continue ktail0 123) (continue ktail0 456)))|]
procedure = testGroup "procedure" -- procedure = testGroup "procedure"
[ testCase "factorial" do -- [ testCase "factorial" do
evalsTo [ObjImm (ImmInt 720)] -- evalsTo [ObjImm (ImmInt 720)]
[cps|(letrec ((fac (λ (n ktail) -- [cps|(λ (ktail0)
(prim (zero? n) -- (letrec ((fac (λ (n ktail)
(κ (x0) -- (prim (zero? n)
(if x0 -- (κ (x0)
(continue ktail 1) -- (if x0
(prim (- n 1) -- (continue ktail 1)
(κ (x1) -- (prim (- n 1)
(letrec ((fac-k0 -- (κ (x1)
(κ (x2) -- (letrec ((fac-k0
(prim (* n x2) -- (κ (x2)
(κ (x3) -- (prim (* n x2)
(continue ktail x3)))))) -- (κ (x3)
(fac x1 fac-k0)))))))))) -- (continue ktail x3))))))
(fac 6 halt))|] -- (fac x1 fac-k0))))))))))
] -- (fac 6 halt)))|]
-- ]
+13 -8
View File
@@ -28,22 +28,27 @@ free = testGroup "free"
qq :: TestTree qq :: TestTree
qq = testGroup "parser" qq = testGroup "parser"
[ testCase "lambda" do [ testCase "lambda" do
assertEqual "" (Sut.MkLambda ["x","y"] "ktail" assertEqual ""
(Sut.ExpContinue "ktail" [Sut.ValVar "x"])) (Sut.MkLambda ["x","y"] "ktail"
(Sut.ExpContinue (Sut.ValVar "ktail") [Sut.ValVar "x"]))
[cps|(λ (x y ktail) (continue ktail x))|] [cps|(λ (x y ktail) (continue ktail x))|]
assertEqual "" (Sut.MkLambda [] "ktail" assertEqual ""
(Sut.ExpContinue "ktail" [Sut.ValVar "x"])) (Sut.MkLambda [] "ktail"
(Sut.ExpContinue (Sut.ValVar "ktail") [Sut.ValVar "x"]))
[cps|(λ (ktail) (continue ktail x))|] [cps|(λ (ktail) (continue ktail x))|]
, testCase "kappa" do , testCase "kappa" do
assertEqual "" (Sut.MkKappa ["x","y"] assertEqual ""
(Sut.ExpContinue "k123" [Sut.ValVar "x", Sut.ValVar "y"])) (Sut.MkKappa ["x","y"]
(Sut.ExpContinue
(Sut.ValVar "k123")
[Sut.ValVar "x", Sut.ValVar "y"]))
[cps|(κ (x y) (continue k123 x y))|] [cps|(κ (x y) (continue k123 x y))|]
, testCase "application" do , testCase "application" do
assertEqual "" (Sut.ExpApply (Sut.ValVar "f") assertEqual "" (Sut.ExpApply (Sut.ValVar "f")
[Sut.ValVar "x",Sut.ValVar "y"] [Sut.ValVar "x",Sut.ValVar "y"]
"k") (Sut.KexpVar "k"))
[cps|(f x y k)|] [cps|(f x y k)|]
assertEqual "" (Sut.ExpApply (Sut.ValVar "f") assertEqual "" (Sut.ExpApply (Sut.ValVar "f")
[] "k") [] (Sut.KexpVar "k"))
[cps|(f k)|] [cps|(f k)|]
] ]
+4 -6
View File
@@ -29,11 +29,8 @@ brokenWasmTests =
brokenStackifyTests :: List String brokenStackifyTests :: List String
brokenStackifyTests = brokenStackifyTests =
[] [
-- [ "adder" ]
-- , "let-fn"
-- , "callcc-nested1" -- requires closure-conversion
-- ]
test_root :: IO TestTree test_root :: IO TestTree
test_root = do test_root = do
@@ -43,7 +40,8 @@ test_root = do
testGroup "execution" <$> sequenceA testGroup "execution" <$> sequenceA
[ ignoreTestBecause "wasm codegen is on the backburner" [ ignoreTestBecause "wasm codegen is on the backburner"
<$> wasmTests tests <$> wasmTests tests
, stackifyTests tests , ignoreTestBecause "i'm killing myself"
<$> stackifyTests tests
] ]
maybeBroken name broken = applyWhen (name `elem` broken) expectFail maybeBroken name broken = applyWhen (name `elem` broken) expectFail
+89 -52
View File
@@ -6,70 +6,107 @@ import Test.Tasty.HUnit
import Gyehoek.Stack.Syntax import Gyehoek.Stack.Syntax
import Gyehoek.Stack.VM qualified as Sut import Gyehoek.Stack.VM qualified as Sut
import Data.List (List) import Data.List (List)
import Gyehoek.Jalmot
import Gyehoek.Prelude (i)
import Test.Tasty.ExpectedFailure (expectFail, ignoreTestBecause)
evalsTo :: List Obj -> Program -> Assertion evalsTo :: List Obj -> Program -> Assertion
evalsTo rs p = Sut.eval p @?= rs 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 "lit int" do [ testCase "immediate halt" do
evalsTo [] [stkP|
(define $start
(return 0))
|]
, testCase "lit int" do
evalsTo [ObjImm (ImmInt 3)] [stkP| evalsTo [ObjImm (ImmInt 3)] [stkP|
(define ($main) (define $start
(pop-cont! %ktail) (push! 3)
(tail-call %ktail 3)) (return 1))
|]
, testCase "non-tail identity function" do
evalsTo [ObjImm (ImmInt 123)] [stkP|
(define $id
(return 1))
(define $c
(return 1))
(define $start
(push! $c)
(push! $id)
(push! 123)
(call 1))
|]
, testCase "tail identity function" do
evalsTo [ObjImm (ImmInt 123)] [stkP|
(define $id
(return 1))
(define $start
(push! $id)
(push! 123)
(tail-call 1))
|] |]
, testCase "return constant" do , testCase "return constant" do
evalsTo [ObjImm (ImmInt 123)] [stkP| evalsTo [ObjImm (ImmInt 123)] [stkP|
(define ($main) (define $start
(tail-call $silly)) (push! $silly)
(define ($silly) (tail-call 1))
(pop-cont! %ktail) (define $silly
(tail-call %ktail 123)) (push! 123)
(return 1))
|] |]
, testCase "identity function" do , testCase "return multiple" do
evalsTo [ObjImm (ImmInt 45)] [stkP| evalsTo [ObjImm (ImmInt n) | n <- [1,2,3]] [stkP|
(define ($main) (define $start
(tail-call $id 45)) (push! 3)
(define ($id %x) (push! 2)
(pop-cont! %ktail) (push! 1)
(tail-call %ktail %x)) (return 3))
|]
, testCase "return none" do
evalsTo [] [stkP|
(define $start
(return 0))
|] |]
-- , testCase "square" do
-- evalsTo [ObjImm (ImmInt 16)] [stkP|
-- (define ($main))
-- |]
, testCase "square" do , testCase "square" do
evalsTo [ObjImm (ImmInt 16)] [stkP| evalsTo [ObjImm (ImmInt 16)] [stkP|
(define ($main) (define $start
(tail-call $square 4)) (push! $square)
(define ($square %x) (push! 4)
(prim %x2 (* %x %x)) (tail-call 1))
(pop-cont! %ktail) (define $square
(tail-call %ktail %x2)) (pop! %x)
(prim (* %x %x))
(return 1))
|] |]
, testCase "factorial" do , testGroup "factorial"
let hsfac (n :: Int) = foldr (*) (1) [1..n] let
let fac (n :: Int) = [stkP| hsfac (n :: Int) = foldr @List (*) 1 [1..n]
(define ($fac %n) fac (n :: Int) = [stkP|
(prim %x0 (zero? %n)) (define $start
(if %x0 (push! $fac)
(then (pop-cont! %ktail) (push! #{n})
(tail-call %ktail 1)) (tail-call 1))
(else (push! %n) (define $fac
(prim %x1 (- %n 1)) (load %n 0)
(push-cont! $fac-k0) (prim (zero? %n))
(tail-call $fac %x1)))) (pop! %x0)
(define ($fac-k0 %x2) (if %x0
(pop! %n) (then (push! 1)
(prim %x3 (* %x2 %n)) (return 1))
(pop-cont! %ktail) (else (push! $fac-c0)
(tail-call %ktail %x3)) (push! $fac)
(define ($main) (prim (- %n 1))
(tail-call $fac #{n})) (call 1))))
|] (define $fac-c0
evalsTo [ObjImm (ImmInt 1)] $ fac 0 (pop! %x2)
evalsTo [ObjImm (ImmInt 1)] $ fac 1 (pop! %n)
evalsTo [ObjImm (ImmInt 720)] $ fac 6 (prim (* %n %x2))
(return 1))
|]
mkcase n = testCase [i|#{n}|] do
evalsTo [ObjImm . ImmInt $ hsfac n] $ fac n
-- 20 is the greatest `n` for which n! ≤ maxBount @Int -- 20 is the greatest `n` for which n! ≤ maxBount @Int
evalsTo [ObjImm (ImmInt 2432902008176640000)] $ fac 20 in [ mkcase n | n <- [0,1,6,20] ]
] ]