packages feed

swearjure-1.0.0: src/Swearjure/Primitives.hs

{-# OPTIONS_GHC -Wall -Werror #-}
{-# LANGUAGE RankNTypes #-}

module Swearjure.Primitives where

import           Control.Applicative ((<$>))
import           Control.Monad.Except
import           Data.Char (chr, ord)
import           Data.Generics.Fixplate
import qualified Data.Map as M
import           Data.Ratio
import qualified Data.Set as S
import           Prelude hiding (seq)
import           Swearjure.AST hiding (lookup)
import           Swearjure.Errors
import           System.IO

getFn :: [Val] -> EvalState Val
getFn [m, v] = getFn [m, v, Fix Nil]
getFn [m, k, default'] = find (unFix m) k default'
  where find (ESet vals) v d
          | v `elem` vals = return v
          | otherwise = return d
        find (EVec vals) (Fix (EInt n)) d
          | n < 0 = return d
          | length vals > fromIntegral n = return $ vals !! fromIntegral n
          | otherwise = return d
        find (EVec _) _ d = return d
        find (EHM pairs) v d = case lookup v pairs of
                                Just res -> return res
                                Nothing -> return d
        find _ _ d = return d
getFn x = throwError $ ArityException (length x) "core/get"

get1Fn :: [Val] -> EvalState Val
get1Fn [m, k] = go (unFix m)
  where go (EVec vals)
          = case unFix k of
             EInt n | n < 0 -> throwError $ IllegalArgument "Index can't be negative"
             EInt n | length vals > fromIntegral n -> return $ vals !! fromIntegral n
             EInt _ -> throwError $ IllegalArgument "Index out of bounds for vector"
             _ -> throwError $ IllegalArgument "Key must be integer"
        go (ESet vals)
          = if k `elem` vals
            then return k
            else return $ Fix Nil
        go _ = throwError $ IllegalState "Internal Swearjure error -- get1Fn got unexpected value"
get1Fn xs = throwError $ ArityException (length xs) (typeName $ head xs)

-- this one must be wrapped properly to use in envs. (liftM (Fix . EList) . seq)
seq :: [Val] -> EvalState [Val]
seq [x] = go (unFix x)
  where go (ESet vals) = return vals
        go (EList vals) = return vals
        go (EVec vals) = return vals
        go (EHM pairs) = return $ vecPairs pairs
        go (EStr s) = return $ map (Fix . EChar) s
        go Nil = return []
        go x' = throwError $ CastException (typeName' x') "ISeq"
        vecPairs = map (\(a, b) -> (Fix (EVec [a, b])))
seq x = throwError $ ArityException (length x) "core/seq"

-- same as above
concat :: [Val] -> EvalState [Val]
concat xs = foldM prepend [] (reverse xs)
  where prepend acc v = do s <- seq [v]
                           return $ s ++ acc

multiCmp :: (SwjValF Val -> SwjValF Val -> EvalState Bool) -> String
            -> [SwjValF Val] -> EvalState Val
multiCmp _ fname [] = throwError $ ArityException 0 fname
multiCmp _ _ [_] = return $ Fix $ EBool True
multiCmp f _ [x, y] = liftM (Fix . EBool) $ f x y
multiCmp f fname (a : b : r) = do res <- f a b
                                  if res
                                    then multiCmp f fname (b : r)
                                    else return $ Fix $ EBool False

numOp :: (forall a. Ord a => a -> a -> Bool) -> SwjValF Val -> SwjValF Val
         -> EvalState Bool
numOp op = cmp
  where cmp (EInt x) (EInt y) = return $ x `op` y
        cmp (EInt x) (EFloat y) = return $ fromIntegral x `op` y
        cmp (EInt x) (ERatio y) = return $ (x % 1) `op` y
        cmp (EFloat x) (EInt y) = return $ x `op` fromIntegral y
        cmp (EFloat x) (EFloat y) = return $ x `op` y
        cmp (EFloat x) (ERatio y) = return $ x `op` asFloat y
        cmp (ERatio x) (ERatio y) = return $ x `op` y
        cmp (ERatio x) (EInt y) = return $ x `op` (y % 1)
        cmp (ERatio x) (EFloat y) = return $ asFloat x `op` y
        cmp x y
          | isNum x = throwError $ CastException (typeName' y) "Number"
          | otherwise = throwError $ CastException (typeName' x) "Number"

lt :: [Val] -> EvalState Val
lt = multiCmp (numOp (<)) "core/<" . map unFix

lte :: [Val] -> EvalState Val
lte = multiCmp (numOp (<=)) "core/<=" . map unFix

gt :: [Val] -> EvalState Val
gt = multiCmp (numOp (>)) "core/>" . map unFix

gte :: [Val] -> EvalState Val
gte = multiCmp (numOp (>=)) "core/>=" . map unFix

numEq :: [Val] -> EvalState Val
numEq = multiCmp (numOp (==)) "core/==" . map unFix

eq :: [Val] -> EvalState Val
eq [] = throwError $ ArityException 0 "core/="
eq [_] = return $ Fix $ EBool True
eq (x : y : r) = if x == y
                 then eq (y : r)
                 else return $ Fix $ EBool False

-- hash-map and hash-set

hashMap :: [Val] -> EvalState Val
hashMap xs = Fix . EHM  <$> (go M.empty xs >>= shuffle . M.toList)
  where go m [] = return m
        go _ [k] = throwError $ IllegalArgument $ "No value supplied for key: " ++ prStr k
        go m (k : v : kvs) = go (M.insert k v m) kvs

hashSet :: [Val] -> EvalState Val
hashSet xs = Fix . ESet <$> (go S.empty xs >>= shuffle . S.toList)
  where go s [] = return s
        go s (v : vs) = go (S.insert v s) vs

-- ->> and ->

threadLast :: [Val] -> EvalState Val
threadLast [] = throwError $ ArityException 0 "core/->>"
threadLast [x] = return x
threadLast (x : Fix (EList ys) : r) = threadLast $ iList (ys ++ [x]) : r
threadLast (x : y : r) = threadLast $ iList [y, x] : r

threadSnd :: [Val] -> EvalState Val
threadSnd [] = throwError $ ArityException 0 "core/->"
threadSnd [x] = return x
threadSnd (x : Fix (EList ys) : r)
  = let (yfst, ysnd) = splitAt 1 ys in
     threadSnd $ iList (yfst ++ [x] ++ ysnd) : r
threadSnd (x : y : r) = threadSnd $ iList [y, x] : r

-- I see that these operations can be generalized, but it won't make them easier
-- to maintain or anything, really.

plus :: [Val] -> EvalState Val
plus xs = Fix <$> foldM (|+|) (EInt 0) (map unFix xs)
  where (EInt x) |+| (EInt y) = return $ EInt (x + y)
        (EInt x) |+| (EFloat y) = return $ EFloat (fromIntegral x + y)
        (EInt x) |+| (ERatio rat) = unRatio ((x % 1) + rat)
        (EFloat x) |+| (EInt y) = return $ EFloat (x + fromIntegral y)
        (EFloat x) |+| (EFloat y) = return $ EFloat (x + y)
        (EFloat x) |+| (ERatio rat) = return $ EFloat (x + asFloat rat)
        (ERatio rat) |+| (EInt y) = unRatio (rat + (y % 1))
        (ERatio rat) |+| (EFloat y) = return $ EFloat (asFloat rat + y)
        (ERatio x) |+| (ERatio y) = unRatio (x + y)
        x |+| y
          | isNum x = throwError $ CastException (typeName' y) "Number"
          | otherwise = throwError $ CastException (typeName' x) "Number"

minus :: [Val] -> EvalState Val
minus [] = throwError $ ArityException 0 "core/-"
minus [x] = minus [Fix (EInt 0), x]
minus (x' : xs) = Fix <$> foldM (|-|) (unFix x') (map unFix xs)
  where (EInt x) |-| (EInt y) = return $ EInt (x - y)
        (EInt x) |-| (EFloat y) = return $ EFloat (fromIntegral x - y)
        (EInt x) |-| (ERatio rat) = unRatio ((x % 1) - rat)
        (EFloat x) |-| (EInt y) = return $ EFloat (x - fromIntegral y)
        (EFloat x) |-| (EFloat y) = return $ EFloat (x - y)
        (EFloat x) |-| (ERatio rat) = return $ EFloat (x - asFloat rat)
        (ERatio rat) |-| (EInt y) = unRatio (rat - (y % 1))
        (ERatio rat) |-| (EFloat y) = return $ EFloat (asFloat rat - y)
        (ERatio x) |-| (ERatio y) = unRatio (x - y)
        x |-| y
          | isNum x = throwError $ CastException (typeName' y) "Number"
          | otherwise = throwError $ CastException (typeName' x) "Number"

mul :: [Val] -> EvalState Val
mul xs = Fix <$> foldM (|*|) (EInt 1) (map unFix xs)
  where (EInt x) |*| (EInt y) = return $ EInt (x * y)
        (EInt x) |*| (EFloat y) = return $ EFloat (fromIntegral x * y)
        (EInt y) |*| (ERatio rat) = unRatio ((y % 1) * rat)
        (EFloat x) |*| (EInt y) = return $ EFloat (x * fromIntegral y)
        (EFloat x) |*| (EFloat y) = return $ EFloat (x * y)
        (EFloat x) |*| (ERatio rat) = return $ EFloat (x * asFloat rat)
        (ERatio rat) |*| (EInt y) = unRatio (rat * (y % 1))
        (ERatio rat) |*| (EFloat y) = return $ EFloat (asFloat rat * y)
        (ERatio x) |*| (ERatio y) = unRatio (x * y)
        x |*| y
          | isNum x = throwError $ CastException (typeName' y) "Number"
          | otherwise = throwError $ CastException (typeName' x) "Number"

divFn :: [Val] -> EvalState Val
divFn [] = throwError $ ArityException 0 "core//"
divFn [x] = divFn [Fix (EInt 1), x]
divFn (x' : xs) = Fix <$> foldM divide (unFix x') (map unFix xs)
  where (EInt n) `divide` (EInt val) = safeRat n val
        (EInt x) `divide` (ERatio y) = unRatio $ (x % 1) / y
        (EInt x) `divide` (EFloat y) = return $ EFloat (fromIntegral x / y)
        (EFloat x) `divide` (EInt y) = return $ EFloat (x / fromIntegral y)
        (EFloat x) `divide` (EFloat y) = return $ EFloat (x / y)
        (EFloat x) `divide` (ERatio rat) = EFloat x `divide` EFloat (asFloat rat)
        (ERatio _) `divide` (EInt 0) = throwError $ IllegalArgument "Cannot divide by 0"
        (ERatio rat) `divide` (EInt y) = unRatio (rat / (y % 1))
        (ERatio rat) `divide` (EFloat y) = EFloat (asFloat rat) `divide` EFloat y
        (ERatio x) `divide `(ERatio y) = unRatio (x / y)
        x `divide` y
          | isNum x = throwError $ CastException (typeName' y) "Number"
          | otherwise = throwError $ CastException (typeName' x) "Number"

prChars :: [Val] -> EvalState Val
prChars [] = return $ Fix Nil
prChars (Fix (EInt n) : xs) = do liftIO $ putChar (chr $ fromIntegral n)
                                 liftIO $ hFlush stdout
                                 prChars xs
prChars (x : _) = throwError $ CastException (typeName x) "Integer"

readChar :: [a] -> EvalState Val
readChar [] = do n <- ord <$> liftIO getChar
                 return $ Fix $ EInt (toInteger n)
readChar xs = throwError $ ArityException (length xs) "swearjure.core/<<'"

prn :: [Val] -> EvalState Val
prn xs = do let printString = unwords $ map prStr xs
            liftIO $ putStrLn printString
            return $ Fix Nil

safeRat :: Integer -> Integer -> EvalState (SwjValF e)
safeRat num den = case den of
                   0 -> throwError $ IllegalArgument "Cannot divide by 0"
                   _ -> unRatio (num % den)

unRatio :: Rational -> EvalState (SwjValF e)
unRatio rat = return (case denominator rat of
                       1 -> EInt $ numerator rat
                       _ -> ERatio rat)

asFloat :: Rational -> Double
asFloat rat = fromIntegral (numerator rat) / fromIntegral (denominator rat)

isNum :: SwjValF e -> Bool
isNum (EInt _) = True
isNum (EFloat _) = True
isNum (ERatio _) = True
isNum _ = False