swearjure-1.0.0: src/Swearjure/AST.hs
{-# OPTIONS_GHC -Wall -Werror #-}
{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}
{-# LANGUAGE FlexibleContexts #-}
module Swearjure.AST where
import Control.Monad.Except
import Control.Monad.Reader
import Control.Monad.State
import Data.Generics.Fixplate
import qualified Data.Map as M
import Data.Ratio
import qualified Data.Set as S
import Swearjure.Errors
import System.Random (newStdGen)
import System.Random.Shuffle (shuffle')
import Text.PrettyPrint
shuffle :: [a] -> EvalState [a]
shuffle [] = return []
shuffle xs = do rand <- liftIO newStdGen
return $ shuffle' xs (length xs) rand
type EvalState = ReaderT Env (StateT Int (ExceptT SwjError IO))
type NS = String
data EnvF e = Toplevel (M.Map String (NS, (Bool, e)))
| Nested (EnvF e) (M.Map String (NS, (Bool, e)))
deriving (Eq, Ord, Show, Functor, Foldable, Traversable)
type Env = EnvF Val
eqNs :: Maybe String -> String -> Bool
eqNs (Just x) y = x == y
eqNs Nothing _ = True
-- lookup and lookupMacro would've been namespaced if this was proper clojure,
-- but you can't refer to same-named values in different namespaces in
-- Swearjure, so it doesn't matter.
lookupMacro :: (MonadReader Env m) => (Maybe String) -> String -> m (Maybe Val)
lookupMacro ns s = ask >>= lookupRec
where lookupRec (Toplevel m) = m `findOr` return Nothing
lookupRec (Nested up m) = m `findOr` lookupRec up
m `findOr` other
= do let v = M.lookup s m
case v of
Just (_, (False, _)) -> return Nothing
Just (ns', (True, macro)) | eqNs ns ns' -> return (Just macro)
Just (_, (True, _)) -> other
Nothing -> other
lookup :: (MonadReader Env m, MonadError SwjError m) =>
(Maybe String) -> String -> m Val
lookup ns s = ask >>= lookupRec
where lookupRec (Toplevel m) = m `findOr` throwError (NotFound s)
lookupRec (Nested up m) = m `findOr` lookupRec up
m `findOr` other
= do let v = M.lookup s m
case v of
Just (ns', (False, val)) | eqNs ns ns' -> return val
Just (ns', (True, _))
| eqNs ns ns' -> throwError $ IllegalArgument $
"Can't take value of macro: #'" ++ ns'
++ "/" ++ s
_ -> other
extendEnv :: (MonadReader Env m) => Env -> [(String, Val)] -> m a -> m a
extendEnv oldEnv mappingList = local extend
where extend _ = Nested oldEnv mapping
mapping = M.fromList $ map (\(x, y) -> (x, ("user", (False, y))))
mappingList
specials :: S.Set String
specials = S.fromList
["fn*", "quote", ".", "var", "&", "if", "nil"]
getMapping :: (MonadReader Env m) => String -> m (Maybe Val)
getMapping s = if S.member s specials
then return . Just . Fix . ESym Nothing $ s
else ask >>= lookupRec
where lookupRec (Toplevel m) = m `findOr` return Nothing
lookupRec (Nested up m) = m `findOr` lookupRec up
m `findOr` other = do let v = M.lookup s m
maybe other (namespaced . fst) v
namespaced ns = return . Just . Fix . ESym (Just ns) $ s
data FnF e = Fn { fnEnv :: EnvF e
, fnNs :: String
, fnName :: String
, fnRecName :: Maybe String
, fnFns :: [([String], Maybe String, [e])]
}
| PrimFn (PFn Val)
deriving (Eq, Ord, Show, Functor, Foldable, Traversable)
data PFn e = Prim (String, String) ([e] -> EvalState e)
instance Eq (PFn e) where
(Prim a _) == (Prim b _) = a == b
instance Ord (PFn e) where
(Prim a _) `compare` (Prim b _) = a `compare` b
instance Show (PFn e) where
show (Prim (ns, name) _) = "#<" ++ show ns ++ "$" ++ show name ++ ">"
type Fn = FnF Val
data SwjValF e = ESym (Maybe String) String
| EStr String
| EInt Integer
| ERatio Rational
| EFloat Double
| EChar Char
| EList [e]
| EVec [e]
| EHM [(e, e)]
| ESet [e]
| EKw (Maybe String) String
| EBool Bool
| EFn (FnF e)
| EVar (FnF e) Bool
| Nil
deriving (Eq, Ord, Show, Functor, Foldable, Traversable)
typeName' :: SwjValF e -> String
typeName' (ESym _ _) = "Symbol"
typeName' (EStr _ ) = "String"
typeName' (EInt _) = "Integer"
typeName' (ERatio _) = "Ratio"
typeName' (EFloat _) = "Float"
typeName' (EChar _) = "Char"
typeName' (EList _) = "PersistentList"
typeName' (EVec _) = "PersistentVector"
typeName' (EHM _) = "PersistentHashMap"
typeName' (ESet _) = "PersistentSet"
typeName' (EKw _ _) = "Keyword"
typeName' (EBool _) = "Boolean"
typeName' (EFn _) = "Fn"
typeName' (EVar _ _) = "Var"
typeName' Nil = "nil"
typeName :: Val -> String
typeName = typeName' . unFix
type Val = Mu SwjValF
iList :: [Val] -> Val
iList = Fix . EList
iVec :: [Val] -> Val
iVec = Fix . EVec
iHM :: [(Val, Val)] -> Val
iHM = Fix . EHM
iSet :: [Val] -> Val
iSet = Fix . ESet
instance EqF SwjValF where equalF = (==)
instance OrdF SwjValF where compareF = compare
instance ShowF SwjValF where showsPrecF = showsPrec
prStr :: Val -> String
prStr = show . pp' sfn
where sfn str = char '"' <> text (unquote str) <> char '"'
unquote = concatMap uq
uq '\\' = "\\\\"
uq '"' = "\\\""
uq x = [x]
pp' :: (String -> Doc) -> Val -> Doc
pp' sfn = cata go
where go (ESym ns s) = nsPP ns <> text s
go (EStr s) = sfn s
go (EInt n) = integer n
go (ERatio r) = integer (numerator r) <> char '/' <> integer (denominator r)
go (EFloat f) = double f
go (EChar c) = char '\\' <> char c -- prettyprint-dependent
go (EList xs) = parens $ hsep xs
go (EVec xs) = brackets $ hsep xs
go (EHM pairs) = braces $ hsep $ concatMap (\(x,y) -> [x, y]) pairs
go (ESet s) = char '#' <> braces (hsep s)
go (EKw ns s) = char ':' <> nsPP ns <> text s
go (EBool True) = text "true"
go (EBool False) = text "false"
go (EFn Fn { fnNs = ns, fnName = fname})
= text "#<" <> text ns <> char '$' <> text fname <> char '>'
go (EFn (PrimFn (Prim (ns, fname) _)))
= text "#<" <> text ns <> char '$' <> text fname <> char '>'
go (EVar (Fn { fnNs = ns, fnName = fname}) _)
= text "#'" <> text ns <> char '/' <> text fname
go (EVar (PrimFn (Prim (ns, fname) _)) _)
= text "#'" <> text ns <> char '/' <> text fname
go Nil = text "nil"
nsPP (Just ns) = text ns <> char '/'
nsPP Nothing = empty
_quote :: Val
_quote = Fix $ ESym Nothing "quote"
_unquote :: Val
_unquote = Fix $ ESym (Just "clojure.core") "unquote"
_unquoteSplicing :: Val
_unquoteSplicing = Fix $ ESym (Just "clojure.core") "unquote-splicing"
_seq :: Val
_seq = Fix $ ESym (Just "clojure.core") "seq"
_concat :: Val
_concat = Fix $ ESym (Just "clojure.core") "concat"
_apply :: Val
_apply = Fix $ ESym (Just "clojure.core") "apply"
_list :: Val
_list = Fix $ ESym (Just "clojure.core") "list"
_vector :: Val
_vector = Fix $ ESym (Just "clojure.core") "vector"
_hashset :: Val
_hashset = Fix $ ESym (Just "clojure.core") "hash-set"
_hashmap :: Val
_hashmap = Fix $ ESym (Just "clojure.core") "hash-map"
_nil :: Val
_nil = Fix Nil
_fnStar :: Val
_fnStar = Fix $ ESym Nothing "fn*"
_var :: Val
_var = Fix $ ESym Nothing "var"