summaryrefslogtreecommitdiff
path: root/Biz/Language/Bs/Primitives.hs
blob: c074c59562f8dda477e81ad1f486eb61bf0caeaf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoImplicitPrelude #-}
-- | bs primitives
--
-- I would like to reduce the number of primitives in the language to some
-- minimal number, like SKI combinator or Nock instructions. I'm not sure what
-- the minimal number is. The idea is to move primitives from here into core.scm
-- over time.
module Language.Bs.Primitives where

import Control.Exception
import Control.Monad.Except
import Data.Text as T
import Data.Text.IO as TIO
import Language.Bs.Expr
import Network.HTTP
import Protolude
import System.Directory
import System.IO

type Prim   = [(T.Text, Expr)]
type Unary  = Expr -> Eval Expr
type Binary = Expr -> Expr -> Eval Expr

mkF :: ([Expr] -> Eval Expr) -> Expr
mkF = IFun . IFunc

primEnv :: Prim
primEnv = [
    ("+"            , mkF $ binopFold (numOp    (+))  (Numb 0) )
  , ("*"            , mkF $ binopFold (numOp    (*))  (Numb 1) )
  , ("string-append", mkF $ binopFold (strOp    (<>)) (Tape "") )
  , ("-"            , mkF $ binop $    numOp    (-))
  , ("<"            , mkF $ binop $    numCmp   (<))
  , ("<="           , mkF $ binop $    numCmp   (<=))
  , (">"            , mkF $ binop $    numCmp   (>))
  , (">="           , mkF $ binop $    numCmp   (>=))
  , ("=="           , mkF $ binop $    numCmp   (==))
  , ("even?"        , mkF $ unop $     numBool   even)
  , ("odd?"         , mkF $ unop $     numBool   odd)
  , ("neg?"         , mkF $ unop $     numBool (< 0))
  , ("pos?"         , mkF $ unop $     numBool (> 0))
  , ("eq?"          , mkF $ binop eqCmd )
  , ("null?"        , mkF $ unop (eqCmd Nil) )
  , ("bl-eq?"       , mkF $ binop $ eqOp (==))
  , ("and"          , mkF $ binopFold (eqOp (&&)) (Bool True))
  , ("or"           , mkF $ binopFold (eqOp (||)) (Bool False))
  , ("not"          , mkF $ unop $ notOp)
  , ("cons"         , mkF $ Language.Bs.Primitives.cons)
  , ("cdr"          , mkF $ Language.Bs.Primitives.cdr)
  , ("car"          , mkF $ Language.Bs.Primitives.car)
  , ("quote"        , mkF $ quote)
  , ("file?"        , mkF $ unop fileExists)
  , ("slurp"        , mkF $ unop slurp)
  , ("wslurp"       , mkF $ unop wSlurp)
  , ("put"          , mkF $ binop put_)
  ]

unop :: Unary -> [Expr] -> Eval Expr
unop op [x]    = op x
unop _ args    = throw $ NumArgs 1 args

binop :: Binary -> [Expr] -> Eval Expr
binop op [x,y]  = op x y
binop _  args   = throw $ NumArgs 2 args

fileExists :: Expr  -> Eval Expr
fileExists (Tape txt) = Bool <$> liftIO (doesFileExist $ T.unpack txt)
fileExists val          = throw $ TypeMismatch "read expects string, instead got: " val

slurp :: Expr  -> Eval Expr
slurp (Tape txt) = liftIO $ wFileSlurp txt
slurp val          =  throw $ TypeMismatch "read expects string, instead got: " val

wFileSlurp :: T.Text -> IO Expr
wFileSlurp fileName = withFile (T.unpack fileName) ReadMode go
  where go = readTextFile fileName

openURL :: T.Text -> IO Expr
openURL x = do
  req  <- simpleHTTP (getRequest $ T.unpack x)
  body <- getResponseBody req
  return $ Tape $ T.pack body

wSlurp :: Expr -> Eval Expr
wSlurp (Tape txt) =  liftIO  $  openURL txt
wSlurp val = throw $ TypeMismatch "wSlurp expects a string, instead got: " val

readTextFile :: T.Text -> Handle -> IO Expr
readTextFile fileName h = do
  exists <- doesFileExist $ T.unpack fileName
  if exists
  then (TIO.hGetContents h) >>= (return . Tape)
  else throw $ ReadFileError $ T.concat [" file does not exist: ", fileName]

put_ :: Expr -> Expr -> Eval Expr
put_ (Tape file) (Tape msg) =  liftIO $ wFilePut file msg
put_ (Tape _)  val = throw $ TypeMismatch "put expects string in the second argument (try using show), instead got: " val
put_ val  _ = throw $ TypeMismatch "put expects string, instead got: " val

wFilePut :: T.Text -> T.Text -> IO Expr
wFilePut fileName msg = withFile (T.unpack fileName) WriteMode go
  where go = putTextFile fileName msg

putTextFile :: T.Text -> T.Text -> Handle -> IO Expr
putTextFile fileName msg h = do
  canWrite <- hIsWritable h
  if canWrite
  then (TIO.hPutStr h msg) >> (return $ Tape msg)
  else throw $ ReadFileError $ T.concat [" file does not exist: ", fileName]

binopFold :: Binary -> Expr -> [Expr] -> Eval Expr
binopFold op farg args = case args of
    []-> throw $ NumArgs 2 args
    [a,b]  -> op a b
    _ -> foldM op farg args

numBool :: (Integer -> Bool) -> Expr -> Eval Expr
numBool op (Numb x) = return $ Bool $ op x
numBool _   x       = throw $ TypeMismatch "numeric op " x

numOp :: (Integer -> Integer -> Integer) -> Expr -> Expr -> Eval Expr
numOp op (Numb x) (Numb y) = return $ Numb $ op x  y
numOp _  Nil      (Numb y) = return $ Numb y
numOp _  (Numb x) Nil      = return $ Numb x
numOp _  x        (Numb _) = throw $ TypeMismatch "numeric op" x
numOp _  (Numb _)  y       = throw $ TypeMismatch "numeric op" y
numOp _  x         _       = throw $ TypeMismatch "numeric op" x

strOp :: (T.Text -> T.Text -> T.Text) -> Expr -> Expr -> Eval Expr
strOp op (Tape x) (Tape y) = return $ Tape $ op x y
strOp _  Nil      (Tape y) = return $ Tape y
strOp _  (Tape x) Nil      = return $ Tape x
strOp _  x        (Tape _) = throw $ TypeMismatch "string op" x
strOp _  (Tape _) y        = throw $ TypeMismatch "string op" y
strOp _  x        _        = throw $ TypeMismatch "string op" x

eqOp :: (Bool -> Bool -> Bool) -> Expr -> Expr -> Eval Expr
eqOp op (Bool x) (Bool y) = return $ Bool $ op x y
eqOp _  x        (Bool _) = throw $ TypeMismatch "bool op" x
eqOp _  (Bool _)  y       = throw $ TypeMismatch "bool op" y
eqOp _  x         _       = throw $ TypeMismatch "bool op" x

numCmp :: (Integer -> Integer -> Bool) -> Expr -> Expr -> Eval Expr
numCmp op (Numb x) (Numb y) = return . Bool $ op x  y
numCmp _  x        (Numb _) = throw $ TypeMismatch "numeric op" x
numCmp _  (Numb _) y        = throw $ TypeMismatch "numeric op" y
numCmp _  x        _        = throw $ TypeMismatch "numeric op" x

notOp :: Expr -> Eval Expr
notOp (Bool True) = return $ Bool False
notOp (Bool False) = return $ Bool True
notOp x = throw $ TypeMismatch " not expects Bool" x

eqCmd :: Expr -> Expr -> Eval Expr
eqCmd (Atom x) (Atom y) = return . Bool $ x == y
eqCmd (Numb x) (Numb y) = return . Bool $ x == y
eqCmd (Tape x) (Tape y) = return . Bool $ x == y
eqCmd (Bool x) (Bool y) = return . Bool $ x == y
eqCmd  Nil      Nil     = return $ Bool True
eqCmd  _        _       = return $ Bool False

cons :: [Expr] -> Eval Expr
cons [x,(List ys)] = return $ List $ x:ys
cons [x,y]         = return $ List [x,y]
cons _  = throw $ ExpectedList "cons, in second argument"

car :: [Expr] -> Eval Expr
car [List []    ] = return Nil
car [List (x:_)]  = return x
car []            = return Nil
car _             = throw $ ExpectedList "car"

cdr :: [Expr] -> Eval Expr
cdr [List (_:xs)] = return $ List xs
cdr [List []]     = return Nil
cdr []            = return Nil
cdr _             = throw $ ExpectedList "cdr"

quote :: [Expr] -> Eval Expr
quote [List xs]   = return $ List $ Atom "quote" : xs
quote [expr]      = return $ List $ Atom "quote" : [expr]
quote args        = throw $ NumArgs 1 args