summaryrefslogtreecommitdiff
path: root/lore/Language/Bs/Eval.hs
blob: 8495b88aac147789e98656f4afebb1136116783a (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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Language.Bs.Eval (
  evalText
, evalFile
, runParseTest
, safeExec
, runASTinEnv
, basicEnv
, fileToEvalForm
, textToEvalForm
, getFileContents
) where

import Control.Exception
import Control.Monad.Reader
import qualified Data.Map as Map
import Data.String
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import Language.Bs.Expr
import Language.Bs.Parser
import Language.Bs.Primitives
import Protolude
import System.Directory
import Text.Parsec

funcEnv :: Map.Map T.Text Expr
funcEnv = Map.fromList $ primEnv
  <> [ ("read" , IFun $ IFunc $ unop readFn)
     , ("parse", IFun $ IFunc $ unop parseFn)
     , ("eval", IFun $ IFunc $ unop eval)
     , ("show", IFun $ IFunc $ unop (return . Tape . ppexpr))
     ]

basicEnv :: Env
basicEnv = Env Map.empty funcEnv

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

parseFn :: Expr -> Eval Expr
parseFn (Tape txt) = either (throw . ParseError . show) return $ readExpr txt
parseFn val        = throw $ TypeMismatch "parse expects string, instead got:" val

safeExec :: IO a -> IO (Either String a)
safeExec m = do
  result <- Control.Exception.try m
  case result of
    Left (eTop :: SomeException) ->
      case fromException eTop of
        Just (enclosed :: LispError) ->
          return $ Left (show enclosed)
        Nothing ->
          return $ Left (show eTop)
    Right val ->
      return $ Right val

runASTinEnv :: Env -> Eval b -> IO b
runASTinEnv code action = runReaderT (unEval action) code

lineToEvalForm :: T.Text -> Eval Expr
lineToEvalForm input = either (throw . ParseError . show  )  eval $ readExpr input

evalFile :: FilePath -> T.Text -> IO () -- program file
evalFile filePath fileExpr = (runASTinEnv basicEnv $ fileToEvalForm filePath fileExpr) >>= print

fileToEvalForm :: FilePath -> T.Text -> Eval Expr
fileToEvalForm filePath input = either (throw . ParseError . show )  evalBody $ readExprFile filePath input

runParseTest :: T.Text -> T.Text -- for view AST
runParseTest input = either (T.pack . show) (T.pack . show) $ readExpr input

sTDLIB :: FilePath
sTDLIB = "lore/core.scm"

endOfList :: Expr -> Expr -> Expr
endOfList (List x) expr = List $ x ++ [expr]
endOfList n _  = throw $ TypeMismatch  "failure to get variable: " n

parseWithLib :: T.Text -> T.Text -> Either ParseError Expr
parseWithLib std inp = do
  stdlib <- readExprFile sTDLIB std
  expr   <- readExpr inp
  return $ endOfList stdlib expr


getFileContents :: FilePath -> IO T.Text
getFileContents fname = do
  exists <- doesFileExist fname
  if exists then TIO.readFile  fname else return "File does not exist."

textToEvalForm :: T.Text -> T.Text -> Eval Expr
textToEvalForm std input = either (throw . ParseError . show )  evalBody $ parseWithLib std input

evalText :: T.Text -> IO () --REPL
evalText textExpr = do
  stdlib <- getFileContents sTDLIB
  res <- runASTinEnv basicEnv $ textToEvalForm stdlib textExpr
  print res

getVar :: Expr ->  Eval Expr
getVar (Atom atom) = do
  Env{..} <- ask
  case Map.lookup atom (Map.union fenv env) of -- lookup, but prefer functions
      Just x  -> return x
      Nothing -> throw $ UnboundVar atom
getVar n = throw $ TypeMismatch  "failure to get variable: " n

ensureAtom :: Expr -> Eval Expr
ensureAtom n@(Atom _) = return n
ensureAtom n@(List _) = throw $ TypeMismatch "got list" n
ensureAtom n = throw $ TypeMismatch "expected an atomic value" n

extractVar :: Expr -> T.Text
extractVar (Atom atom) = atom
extractVar n = throw $ TypeMismatch "expected an atomic value" n

getEven :: [t] -> [t]
getEven [] = []
getEven (x:xs) = x : getOdd xs

getOdd :: [t] -> [t]
getOdd [] = []
getOdd (_:xs) = getEven xs

applyFunc :: Expr -> [Expr] -> [Expr] -> Eval Expr
applyFunc expr params args = bindArgsEval params args expr

bindArgsEval :: [Expr] -> [Expr] -> Expr -> Eval Expr
bindArgsEval params args expr = do
  Env{..} <- ask

  let newVars = zipWith (\a b -> (extractVar a,b)) params args
  let (newEnv, newFenv) =  Map.partition (not . isFunc) $ Map.fromList newVars
  local (const $ Env (newEnv <> env) (newFenv <> fenv)) $ eval expr

isFunc :: Expr -> Bool
isFunc (List ((Atom "lambda"):_)) = True
isFunc _  = False

eval :: Expr -> Eval Expr
eval (List [Atom "dumpEnv", x]) = do
  Env{..} <- ask
  liftIO $ print $ toList env
  liftIO $ print $ toList fenv
  eval x
eval (Numb i)   = return $ Numb i
eval (Tape s)   = return $ Tape s
eval (Bool b)   = return $ Bool b
eval (List [])  = return Nil
eval Nil        = return Nil
eval n@(Atom _) = getVar n

eval (List [Atom "showSF", rest])      = return . Tape . T.pack $ show rest
eval (List ((:) (Atom "showSF") rest)) = return . Tape . T.pack . show $ List rest

eval (List [Atom "quote", val]) = return val

eval (List [Atom "if", pred_, then_, else_]) = do
  ifRes <- eval pred_
  case ifRes of
    (Bool True)  -> eval then_
    (Bool False) -> eval else_
    _ ->
      throw $ BadSpecialForm "if's first arg must eval into a boolean"
eval (List ( (:) (Atom "if") _)) =
  throw $ BadSpecialForm "(if <bool> <s-expr> <s-expr>)"

eval (List [Atom "begin", rest]) = evalBody rest
eval (List ((:) (Atom "begin") rest )) = evalBody $ List rest

-- top-level define
-- TODO: how to make this eval correctly?
eval (List [Atom "define", List (name:args), body]) = do
  Env{..} <- ask
  _ <- eval body
  bindArgsEval (name:args) [body] name

eval (List [Atom "define", name, body]) = do
  Env{..} <- ask
  _ <- eval body
  bindArgsEval [name] [body] name

eval (List [Atom "let", List pairs, expr]) = do
  Env{..} <- ask
  atoms <- mapM ensureAtom $ getEven pairs
  vals  <- mapM eval       $ getOdd  pairs
  bindArgsEval atoms vals expr

eval (List (Atom "let":_) ) =
  throw $ BadSpecialForm "let function expects list of parameters and S-Expression body\n(let <pairs> <s-expr>)"


eval (List [Atom "lambda", List params, expr]) = do
  ctx <- ask
  return  $ Func (IFunc $ applyFunc expr params) ctx
eval (List (Atom "lambda":_) ) = throw $ BadSpecialForm "lambda function expects list of parameters and S-Expression body\n(lambda <params> <s-expr>)"


-- needed to get cadr, etc to work
eval (List [Atom "cdr", List [Atom "quote", List (_:xs)]]) =
  return $ List xs
eval (List [Atom "cdr", arg@(List (x:xs))]) =
  case x of
      -- proxy for if the list can be evaluated
      Atom _ -> do
        val <- eval arg
        eval $ List [Atom "cdr", val]
      _ -> return $ List xs


eval (List [Atom "car", List [Atom "quote", List (x:_)]]) =
  return $  x
eval (List [Atom "car", arg@(List (x:_))]) =
  case x of
      Atom _ -> do
        val <- eval arg
        eval $ List [Atom "car", val]
      _ -> return $ x


eval (List ((:) x xs)) = do
  Env{..} <- ask
  funVar <- eval x
  xVal <- mapM eval xs
  case funVar of
      (IFun (IFunc internalFn)) ->
          internalFn xVal

      (Func (IFunc definedFn) (Env benv _)) ->
          local (const $ Env benv fenv) $ definedFn xVal

      _ ->
          throw $ NotFunction funVar

updateEnv :: T.Text -> Expr -> Env -> Env
updateEnv var e@(IFun _)   Env{..} =  Env env $ Map.insert var e fenv
updateEnv var e@(Func _ _) Env{..} = Env env $ Map.insert var e fenv
updateEnv var e            Env{..} = Env (Map.insert var e env) fenv

evalBody :: Expr -> Eval Expr
evalBody (List [List ((:) (Atom "define") [Atom var, defExpr]), rest]) = do
  evalVal <- eval defExpr
  ctx <- ask
  local (const $ updateEnv var evalVal ctx) $ eval rest

evalBody (List ((:) (List ((:) (Atom "define") [Atom var, defExpr])) rest)) = do
  evalVal <- eval defExpr
  ctx <- ask
  local (const $ updateEnv var evalVal ctx) $ evalBody $ List rest

evalBody x = eval x