mirror of
https://github.com/samsonjs/elschemo.git
synced 2026-03-25 09:15:55 +00:00
Initial commit, previous history in darcs repo\!
This commit is contained in:
commit
7b39ccc468
5 changed files with 1306 additions and 0 deletions
4
README
Normal file
4
README
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
This was never intended for public consumption. Build with `rake build` and then run ./elschemo, that's it!
|
||||
|
||||
sjs
|
||||
sami.samhuri@gmail.com
|
||||
40
Rakefile
Normal file
40
Rakefile
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
bin = "elschemo"
|
||||
names = %w[lisp]
|
||||
|
||||
task :build do
|
||||
sh "ghc --make -package parsec -fglasgow-exts -o #{bin} #{extensionize 'hs', names}"
|
||||
end
|
||||
|
||||
task :clean do
|
||||
sh "rm -f #{bin} #{obj_files(names)}"
|
||||
end
|
||||
|
||||
def obj_files names
|
||||
"#{extensionize 'hi', names} #{extensionize 'o', names}"
|
||||
end
|
||||
|
||||
def extensionize ext, names
|
||||
names.join(".#{ext} ") + ".#{ext}"
|
||||
end
|
||||
|
||||
|
||||
|
||||
# bin = "elschemo"
|
||||
# names = %w[main elschemo parser eval numeric primitives io]
|
||||
#
|
||||
# task :build do
|
||||
# sh "ghc --make -package parsec -fglasgow-exts -o #{bin} #{extensionize 'hs', names}"
|
||||
# end
|
||||
#
|
||||
# task :clean do
|
||||
# sh "rm -f #{bin} #{obj_files(names)}"
|
||||
# end
|
||||
#
|
||||
# def obj_files names
|
||||
# "#{extensionize 'hi', names} #{extensionize 'o', names}"
|
||||
# end
|
||||
#
|
||||
# def extensionize ext, names
|
||||
# names.join(".#{ext} ") + ".#{ext}"
|
||||
# end
|
||||
#
|
||||
46
TODO
Normal file
46
TODO
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
my own wishes for this little scheme
|
||||
------------------------------------
|
||||
|
||||
* implement char=?, char<?, char>?, char<=?, and char>=?
|
||||
(char<? #\a #\b) => #t
|
||||
|
||||
* readline support (blah, use emacs it's good enough)
|
||||
|
||||
* eval code in any given binding (and, hence, expose the binding somehow)
|
||||
|
||||
|
||||
tutorial exercises
|
||||
------------------
|
||||
|
||||
* Add data types and parsers to support the full numeric tower of Scheme numeric types. Haskell has built-in
|
||||
types to represent many of these; check the Prelude. For the others, you can define compound types that
|
||||
represent eg. a Rational as a numerator and denominator, or a Complex as a real and imaginary part (each
|
||||
itself a Real number).
|
||||
|
||||
http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-9.html#%_sec_6.2.1
|
||||
|
||||
|
||||
* Add support for the backquote syntactic sugar: the Scheme standard details what it should expand into
|
||||
(quasiquote/unquote).
|
||||
|
||||
http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_sec_4.2.6
|
||||
|
||||
* Add support for vectors. The Haskell representation is up to you: GHC does have an Array data type, but it
|
||||
can be difficult to use. Strictly speaking, a vector should have constant-time indexing and updating, but
|
||||
destructive update in a purely functional language is difficult. You may have a better idea how to do this
|
||||
after the section on set!, later in this tutorial.
|
||||
|
||||
http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-9.html#%_sec_6.3.6
|
||||
http://www.haskell.org/ghc/docs/latest/html/libraries/base/Data-Array.html
|
||||
|
||||
* Instead of using the try combinator, left-factor the grammar so that the common subsequence is its own
|
||||
parser. You should end up with a parser that matches a string of expressions, and one that matches either
|
||||
nothing or a dot and a single expressions. Combining the return values of these into either a List or a
|
||||
DottedList is left as a (somewhat tricky) exercise for the reader: you may want to break it out into another
|
||||
helper function.
|
||||
|
||||
|
||||
* Implement <strike>cond</strike> and case expressions.
|
||||
|
||||
* Add the rest of the string functions. You don't yet know enough to do string-set!; this is difficult to
|
||||
implement in Haskell, but you'll have enough information after the next 2 sections
|
||||
990
lisp.hs
Normal file
990
lisp.hs
Normal file
|
|
@ -0,0 +1,990 @@
|
|||
-- author: sjs
|
||||
-- last updated: july-10-2007
|
||||
--
|
||||
-- A small Scheme based on the tutorial by Jonathan Tang.
|
||||
-- http://halogen.note.amherst.edu/~jdtang/scheme_in_48/tutorial/overview.html
|
||||
--
|
||||
-- Where possible and convenient I try to support R5RS.
|
||||
-- http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-2.html
|
||||
|
||||
module Main where
|
||||
import Char
|
||||
import Control.Monad.Error
|
||||
import Data.IORef
|
||||
import Data.List (isPrefixOf)
|
||||
import IO hiding (try)
|
||||
import Monad
|
||||
import Numeric
|
||||
import System.Environment
|
||||
import System.Random
|
||||
import Text.ParserCombinators.Parsec hiding (spaces)
|
||||
|
||||
elSchemoVersion = "0.7"
|
||||
|
||||
-- If no file is named on the command line run the REPL, otherwise run the
|
||||
-- file then start the REPL
|
||||
main :: IO ()
|
||||
main = do args <- getArgs
|
||||
putStrLn ("ElSchemo v" ++ elSchemoVersion ++ " by sjs")
|
||||
if null args
|
||||
then runRepl
|
||||
else runOneThenRepl args
|
||||
|
||||
-- Some very basic syntax error messages
|
||||
readOrThrow :: Parser a -> String -> ThrowsError a
|
||||
readOrThrow parser input = case parse parser "lisp" input of
|
||||
Left err -> throwError $ Parser err
|
||||
Right val -> return val
|
||||
|
||||
readExpr = readOrThrow parseExpr
|
||||
readExprList = readOrThrow (endBy parseExpr maybeSpaces)
|
||||
|
||||
type Name = String
|
||||
type LispInt = Integer
|
||||
type LispFloat = Float
|
||||
|
||||
-- numeric lisp data types
|
||||
data LispNum = Integer LispInt
|
||||
| Float LispFloat
|
||||
deriving (Eq, Ord, Show)
|
||||
|
||||
-- lisp data types
|
||||
data LispVal = Atom Name
|
||||
| List [LispVal]
|
||||
| DottedList [LispVal] LispVal
|
||||
| Number LispNum
|
||||
| Char Char
|
||||
| String String
|
||||
| Bool Bool
|
||||
| PrimitiveFunc Name ([LispVal] -> ThrowsError LispVal)
|
||||
| Func {params :: [Name], vararg :: (Maybe String),
|
||||
body :: [LispVal], closure :: Env}
|
||||
| IOFunc Name ([LispVal] -> IOThrowsError LispVal)
|
||||
| Port Handle
|
||||
| Null Bool
|
||||
|
||||
|
||||
-- make the lisp data types instances of relevant classes
|
||||
|
||||
lispNumPlus :: LispNum -> LispNum -> LispNum
|
||||
lispNumPlus (Integer x) (Integer y) = Integer $ x + y
|
||||
lispNumPlus (Integer x) (Float y) = Float $ (fromInteger x) + y
|
||||
lispNumPlus (Float x) (Float y) = Float $ x + y
|
||||
lispNumPlus (Float x) (Integer y) = Float $ x + (fromInteger y)
|
||||
|
||||
lispNumMinus :: LispNum -> LispNum -> LispNum
|
||||
lispNumMinus (Integer x) (Integer y) = Integer $ x - y
|
||||
lispNumMinus (Integer x) (Float y) = Float $ (fromInteger x) - y
|
||||
lispNumMinus (Float x) (Float y) = Float $ x - y
|
||||
lispNumMinus (Float x) (Integer y) = Float $ x - (fromInteger y)
|
||||
|
||||
lispNumMult :: LispNum -> LispNum -> LispNum
|
||||
lispNumMult (Integer x) (Integer y) = Integer $ x * y
|
||||
lispNumMult (Integer x) (Float y) = Float $ (fromInteger x) * y
|
||||
lispNumMult (Float x) (Float y) = Float $ x * y
|
||||
lispNumMult (Float x) (Integer y) = Float $ x * (fromInteger y)
|
||||
|
||||
lispNumDiv :: LispNum -> LispNum -> LispNum
|
||||
lispNumDiv (Integer x) (Integer y) = Integer $ x `div` y
|
||||
lispNumDiv (Integer x) (Float y) = Float $ (fromInteger x) / y
|
||||
lispNumDiv (Float x) (Float y) = Float $ x / y
|
||||
lispNumDiv (Float x) (Integer y) = Float $ x / (fromInteger y)
|
||||
|
||||
lispNumAbs :: LispNum -> LispNum
|
||||
lispNumAbs (Integer x) = Integer (abs x)
|
||||
lispNumAbs (Float x) = Float (abs x)
|
||||
|
||||
lispNumSignum :: LispNum -> LispNum
|
||||
lispNumSignum (Integer x) = Integer (signum x)
|
||||
lispNumSignum (Float x) = Float (signum x)
|
||||
|
||||
instance Num LispNum where
|
||||
(+) = lispNumPlus
|
||||
(-) = lispNumMinus
|
||||
(*) = lispNumMult
|
||||
abs = lispNumAbs
|
||||
signum = lispNumSignum
|
||||
fromInteger x = Integer x
|
||||
|
||||
|
||||
lispNumToRational :: LispNum -> Rational
|
||||
lispNumToRational (Integer x) = toRational x
|
||||
lispNumToRational (Float x) = toRational x
|
||||
|
||||
instance Real LispNum where
|
||||
toRational = lispNumToRational
|
||||
|
||||
|
||||
lispValEq :: LispVal -> LispVal -> Bool
|
||||
lispValEq (Bool arg1) (Bool arg2) = arg1 == arg2
|
||||
lispValEq (Number arg1) (Number arg2) = arg1 == arg2
|
||||
lispValEq (String arg1) (String arg2) = arg1 == arg2
|
||||
lispValEq (Atom arg1) (Atom arg2) = arg1 == arg2
|
||||
lispValEq (DottedList xs x) (DottedList ys y) = lispValEq (List $ xs ++ [x]) (List $ ys ++ [y])
|
||||
lispValEq (List arg1) (List arg2) = (length arg1 == length arg2) &&
|
||||
(and $ map equalPair $ zip arg1 arg2)
|
||||
where equalPair (x1, x2) = lispValEq x1 x2
|
||||
lispValEq _ _ = False
|
||||
|
||||
instance Eq LispVal where (==) = lispValEq
|
||||
|
||||
|
||||
-- Empty values for each LispVal, these are used for letrec
|
||||
|
||||
emptyValue :: LispVal -> LispVal
|
||||
emptyValue (Number (Integer _)) = Number (Integer 0)
|
||||
emptyValue (Number (Float _)) = Number (Float 0.0)
|
||||
emptyValue (Atom _) = Atom "_"
|
||||
emptyValue (List _) = List []
|
||||
emptyValue (DottedList l r) = DottedList [] (emptyValue r)
|
||||
emptyValue (Char _) = Char ' '
|
||||
emptyValue (String _) = String ""
|
||||
emptyValue (Bool _) = Bool False
|
||||
emptyValue (Func params vararg _ env) = Func params vararg [PrimitiveFunc "null" nullFunc] env
|
||||
|
||||
nullFunc :: [LispVal] -> ThrowsError LispVal
|
||||
nullFunc _ = return $ Null False
|
||||
|
||||
|
||||
-- Parser
|
||||
|
||||
whitespace :: Parser ()
|
||||
whitespace = skipMany1 space
|
||||
|
||||
maybeSpaces :: Parser ()
|
||||
maybeSpaces = skipMany space
|
||||
|
||||
symbol :: Parser Char
|
||||
symbol = oneOf "!$%&|*+-/:<=>?@^_~#" <?> "symbol"
|
||||
|
||||
|
||||
-- parse a binary digit, analagous to decDigit, octDigit, hexDigit
|
||||
binDigit :: Parser Char
|
||||
binDigit = oneOf "01" <?> "binary digit (0 or 1)"
|
||||
|
||||
-- analogous to isDigit, isOctdigit, isHexDigit
|
||||
isBinDigit :: Char -> Bool
|
||||
isBinDigit c = (c == '0' || c == '1')
|
||||
|
||||
-- analogous to readDec, readOct, readHex
|
||||
readBin :: (Integral a) => ReadS a
|
||||
readBin = readInt 2 isBinDigit digitToInt
|
||||
|
||||
|
||||
-- Integers, floats, characters and atoms can all start with a # so wrap those with try.
|
||||
-- (Maybe left factor the grammar in the future)
|
||||
parseExpr :: Parser LispVal
|
||||
parseExpr = (try parseFloat <?> "float")
|
||||
<|> (try parseInteger <?> "int")
|
||||
<|> (try parseChar <?> "char")
|
||||
<|> (parseAtom <?> "atom")
|
||||
<|> (parseString <?> "string")
|
||||
<|> (parseQuoted <?> "quoted expression")
|
||||
<|> do (char '(' <?> "expression") -- should use between?
|
||||
x <- (try parseList) <|> parseDottedList
|
||||
char ')'
|
||||
return x
|
||||
<|> (parseComment <?> "comment")
|
||||
|
||||
|
||||
parseComment :: Parser LispVal
|
||||
parseComment = do skipMany space
|
||||
char ';'
|
||||
skipMany $ noneOf "\n\r"
|
||||
return $ Null False
|
||||
|
||||
|
||||
parseAtom :: Parser LispVal
|
||||
parseAtom = do first <- letter <|> symbol
|
||||
rest <- many (letter <|> digit <|> symbol)
|
||||
let atom = [first] ++ rest
|
||||
return $ case atom of
|
||||
"#t" -> Bool True
|
||||
"#f" -> Bool False
|
||||
otherwise -> Atom atom
|
||||
|
||||
|
||||
parseSign :: Parser Char
|
||||
parseSign = do try (char '-')
|
||||
<|> do optional (char '+')
|
||||
return '+'
|
||||
|
||||
applySign :: Char -> LispNum -> LispNum
|
||||
applySign sign n = if sign == '-' then negate n else n
|
||||
|
||||
-- Parse binary, octal, decimal, and hexadecimal integers according to R5RS (#b, #d, #o, #x),
|
||||
-- or parse a regular integer in decimal format.
|
||||
parseInteger :: Parser LispVal
|
||||
parseInteger = do base <- do { char '#'; oneOf "bdox" } <|> return 'd'
|
||||
sign <- parseSign
|
||||
int <- parseDigits base
|
||||
return . Number $ applySign sign $ Integer . fst . head . (reader base) $ int
|
||||
where reader base = case base of
|
||||
'b' -> readBin
|
||||
'd' -> readDec
|
||||
'o' -> readOct
|
||||
'x' -> readHex
|
||||
|
||||
|
||||
-- Parse a string of digits in the given base.
|
||||
parseDigits :: Char -> Parser String
|
||||
parseDigits base = many1 d >>= return
|
||||
where d = case base of
|
||||
'b' -> binDigit
|
||||
'd' -> digit
|
||||
'o' -> octDigit
|
||||
'x' -> hexDigit
|
||||
|
||||
|
||||
-- Parse floating point numbers, but only in decimal.
|
||||
parseFloat :: Parser LispVal
|
||||
parseFloat = do optional (string "#d")
|
||||
sign <- parseSign
|
||||
whole <- many1 digit
|
||||
char '.'
|
||||
fract <- many1 digit
|
||||
return . Number $ applySign sign (makeFloat whole fract)
|
||||
where makeFloat whole fract = Float . fst . head . readFloat $ whole ++ "." ++ fract
|
||||
|
||||
|
||||
-- Parse a single character according to R5RS (#\a, #\A, #\(, ...).
|
||||
parseChar :: Parser LispVal
|
||||
parseChar = do char '#' >> char '\\'
|
||||
c <- letter <|> digit <|> symbol <|> oneOf "()[]{} "
|
||||
return $ Char c
|
||||
|
||||
|
||||
-- Parse an R5RS compliant string.
|
||||
parseString :: Parser LispVal
|
||||
parseString = do
|
||||
char '"'
|
||||
x <- many singleChar -- <?> "character"
|
||||
char '"'
|
||||
return $ String x
|
||||
|
||||
|
||||
escapedChars :: String
|
||||
escapedChars = "n\"\\"
|
||||
|
||||
singleChar :: Parser Char
|
||||
singleChar = noneOf "\\\""
|
||||
<|> try (do c <- char '\\' >> oneOf escapedChars
|
||||
if c == 'n' then return '\n' else return c)
|
||||
<|> char '\\'
|
||||
|
||||
|
||||
|
||||
-- Parse lists
|
||||
parseList :: Parser LispVal
|
||||
parseList = liftM List $ sepBy parseExpr whitespace
|
||||
|
||||
|
||||
-- Parse lists of the form (head . tail)
|
||||
parseDottedList :: Parser LispVal
|
||||
parseDottedList = do
|
||||
head <- endBy parseExpr whitespace
|
||||
tail <- char '.' >> whitespace >> parseExpr
|
||||
return $ DottedList head tail
|
||||
|
||||
|
||||
-- Parse a quoted expression, ie. '(+ 3 5)
|
||||
parseQuoted :: Parser LispVal
|
||||
parseQuoted = do
|
||||
char '\''
|
||||
x <- parseExpr
|
||||
return $ List [Atom "quote", x]
|
||||
|
||||
|
||||
-- Convert LispVals to strings suitable for display
|
||||
showVal :: LispVal -> String
|
||||
showVal (Null False) = ""
|
||||
showVal (String contents) = "\"" ++ escape contents ++ "\""
|
||||
showVal (Char c) = "#\\" ++ [c]
|
||||
showVal (Atom name) = name
|
||||
showVal (List []) = "()"
|
||||
showVal (Number (Integer n)) = show n
|
||||
showVal (Number (Float n)) = show n
|
||||
showVal (Bool True) = "#t"
|
||||
showVal (Bool False) = "#f"
|
||||
showVal (List contents) = "(" ++ unwordsList contents ++ ")"
|
||||
showVal (DottedList head tail) = "(" ++ unwordsList head ++ " . " ++ showVal tail ++ ")"
|
||||
showVal (PrimitiveFunc name _) = "#<primitive:" ++ name ++ ">"
|
||||
showVal (Func {params = args, vararg = varargs, body = body, closure = env}) =
|
||||
"(lambda (" ++ unwords(map show args) ++
|
||||
(case varargs of
|
||||
Nothing -> ""
|
||||
Just arg -> " . " ++ arg) ++ ") ...)"
|
||||
showVal (Port _) = "#<IO port>"
|
||||
showVal (IOFunc name _) = "#<IO primitive:" ++ name ++ ">"
|
||||
|
||||
unwordsList :: [LispVal] -> String
|
||||
unwordsList = unwords . map showVal
|
||||
|
||||
instance Show LispVal where show = showVal
|
||||
|
||||
|
||||
-- xs =~ s/from/to/g
|
||||
subst :: (Eq a) => [a] -> [a] -> [a] -> [a]
|
||||
subst _ _ [ ] = []
|
||||
subst from to xs@(a:as) =
|
||||
if isPrefixOf from xs
|
||||
then to ++ drop (length from) xs
|
||||
else a : subst from to as
|
||||
|
||||
-- escape a string for display
|
||||
escape :: String -> String
|
||||
escape s = subst "\n" "\\n" (subst "\"" "\\\"" (subst "\\" "\\\\" s))
|
||||
|
||||
-- Evaluation
|
||||
eval :: Env -> LispVal -> IOThrowsError LispVal
|
||||
eval env (List [Atom "load", String filename]) = do
|
||||
load filename >>= evalExprs env
|
||||
return $ Atom ("Loaded " ++ filename ++ ".")
|
||||
eval env (Null _) = return $ Null False
|
||||
eval env val@(Char _) = return val
|
||||
eval env val@(String _) = return val
|
||||
eval env val@(Number _) = return val
|
||||
eval env val@(Bool _) = return val
|
||||
eval env (Atom id) = getVar env id
|
||||
eval env (List []) = return $ List []
|
||||
eval env (List [Atom "quote", val]) = return val
|
||||
eval env (List (Atom "and" : params)) = lispAnd env params
|
||||
eval env (List (Atom "or" : params)) = lispOr env params
|
||||
eval env (List (Atom "if" : params)) = lispIf env params
|
||||
eval env (List (Atom "cond" : params)) = lispCond env params
|
||||
eval env (List [Atom "set!", Atom var, form]) =
|
||||
eval env form >>= setVar env var
|
||||
eval env (List [Atom "define", Atom var, form]) =
|
||||
eval env form >>= defineVar env var
|
||||
eval env (List (Atom "define" : List (Atom var : params) : body)) =
|
||||
makeNormalFunc env params body >>= defineVar env var
|
||||
eval env (List (Atom "define" : DottedList (Atom var : params) varargs : body)) =
|
||||
makeVarargs varargs env params body >>= defineVar env var
|
||||
eval env (List (Atom "lambda" : List params : body)) =
|
||||
makeNormalFunc env params body
|
||||
eval env (List (Atom "lambda" : DottedList params varargs : body)) =
|
||||
makeVarargs varargs env params body
|
||||
eval env (List (Atom "lambda" : varargs@(Atom _) : body)) =
|
||||
makeVarargs varargs env [] body
|
||||
|
||||
eval env (List (Atom "let" : List params : body)) = do
|
||||
values <- evalArgs env params
|
||||
(liftIO $ bindVars env $ zip names values) >>= evalBody
|
||||
where evalBody env = liftM last $ evalExprs env body
|
||||
names = argNames params
|
||||
|
||||
eval env (List (Atom "let*" : List [List [Atom var, form]] : body)) = do
|
||||
eval env form >>= defineVar env var
|
||||
liftM last $ evalExprs env body
|
||||
eval env (List (Atom "let*" : List (List [Atom var, form] : rest) : body)) = do
|
||||
eval env form >>= defineVar env var
|
||||
eval env (List (Atom "let*" : List rest : body))
|
||||
|
||||
eval env (List (Atom "letrec" : List params : body)) = do
|
||||
-- bind the names of vars so they are visible to everything within the letrec
|
||||
env <- liftIO $ bindVars env $ zip names emptyValues
|
||||
-- now evaluate the args and set the proper values
|
||||
values <- evalArgs env params
|
||||
setVars env $ zip names values
|
||||
-- ready to evaluate the body
|
||||
liftM last $ evalExprs env body
|
||||
where names = argNames params
|
||||
emptyValues = [emptyValue x | x <- params]
|
||||
|
||||
-- R6RS adds letrec*, with fairly obvious semantics
|
||||
|
||||
eval env (List (function : args)) = do
|
||||
func <- eval env function
|
||||
argVals <- mapM (eval env) args
|
||||
apply func argVals
|
||||
|
||||
-- taken from Arc, (f . (1 2 3)) is the same as (apply f '(1 2 3))
|
||||
eval env (DottedList [function] (List args)) = do
|
||||
func <- eval env function
|
||||
argVals <- mapM (eval env) args
|
||||
apply func argVals
|
||||
|
||||
eval env badForm = throwError $ BadSpecialForm "Unrecongized special form" badForm
|
||||
|
||||
|
||||
evalExprs :: Env -> [LispVal] -> IOThrowsError [LispVal]
|
||||
evalExprs env exprs = mapM (eval env) exprs
|
||||
|
||||
argNames :: [LispVal] -> [Name]
|
||||
argNames params = map (\(List [Atom x, y]) -> x) params
|
||||
|
||||
args :: [LispVal] -> [LispVal]
|
||||
args params = map (\(List [x, y]) -> y) params
|
||||
|
||||
evalArgs :: Env -> [LispVal] -> IOThrowsError [LispVal]
|
||||
evalArgs env params = evalExprs env (args params)
|
||||
|
||||
|
||||
-- Call primitive functions directly.
|
||||
-- Call user-defined functions after ensuring the correct number of arguments are present,
|
||||
-- and then evaluating those args in the proper context.
|
||||
apply :: LispVal -> [LispVal] -> IOThrowsError LispVal
|
||||
apply (PrimitiveFunc _ func) args = liftThrows $ func args
|
||||
apply (IOFunc _ func) args = func args
|
||||
apply (Func params varargs body closure) args =
|
||||
if num params /= num args && varargs == Nothing
|
||||
then throwError $ NumArgs (num params) args
|
||||
else (liftIO $ bindVars closure $ zip params args) >>= bindVarArgs varargs >>= evalBody
|
||||
where remainingArgs = drop (length params) args
|
||||
num = toInteger . length
|
||||
evalBody env = liftM last $ evalExprs env body
|
||||
bindVarArgs arg env = case arg of
|
||||
Just argName -> liftIO $ bindVars env [(argName, List remainingArgs)]
|
||||
Nothing -> return env
|
||||
apply unFunc args = throwError $ NotFunction "Not a function" (show unFunc)
|
||||
|
||||
|
||||
-- special forms
|
||||
|
||||
lispAnd :: Env -> [LispVal] -> IOThrowsError LispVal
|
||||
lispAnd env [pred] = eval env pred
|
||||
lispAnd env (pred:rest) = do
|
||||
result <- eval env pred
|
||||
case result of
|
||||
Bool False -> return $ Bool False
|
||||
_ -> lispAnd env rest
|
||||
|
||||
lispOr :: Env -> [LispVal] -> IOThrowsError LispVal
|
||||
lispOr env [pred] = eval env pred
|
||||
lispOr env (pred:rest) = do
|
||||
result <- eval env pred
|
||||
case result of
|
||||
Bool False -> lispOr env rest
|
||||
val -> return val
|
||||
|
||||
lispIf :: Env -> [LispVal] -> IOThrowsError LispVal
|
||||
lispIf env (pred:conseq:alt) = do
|
||||
result <- eval env pred
|
||||
case result of
|
||||
Bool False -> eval env $ head alt
|
||||
Bool True -> eval env conseq
|
||||
badCond -> throwError $ TypeMismatch "boolean" badCond
|
||||
|
||||
lispCond :: Env -> [LispVal] -> IOThrowsError LispVal
|
||||
lispCond env (List (Atom "else" : exprs) : []) = liftM last $ evalExprs env exprs
|
||||
lispCond env (List (pred:conseq) : rest) = do
|
||||
result <- eval env pred
|
||||
case result of
|
||||
Bool False -> case rest of
|
||||
[] -> return $ Null False
|
||||
_ -> lispCond env rest
|
||||
_ -> liftM last $ evalExprs env conseq
|
||||
|
||||
|
||||
primitives :: [(Name, [LispVal] -> ThrowsError LispVal)]
|
||||
primitives = [("+", numericBinop (+)),
|
||||
("-", subtractOp),
|
||||
("*", numericBinop (*)),
|
||||
("/", floatBinop (/)),
|
||||
("mod", integralBinop mod),
|
||||
("quotient", integralBinop quot),
|
||||
("remainder", integralBinop rem),
|
||||
("=", numBoolBinop (==)),
|
||||
("<", numBoolBinop (<)),
|
||||
(">", numBoolBinop (>)),
|
||||
("/=", numBoolBinop (/=)),
|
||||
(">=", numBoolBinop (>=)),
|
||||
("<=", numBoolBinop (<=)),
|
||||
("&&", boolBoolBinop (&&)),
|
||||
("||", boolBoolBinop (||)),
|
||||
("car", car),
|
||||
("cdr", cdr),
|
||||
("cons", cons),
|
||||
("null?", nullBoolUnop),
|
||||
("eq?", eqv),
|
||||
("eqv?", eqv),
|
||||
("equal?", equal),
|
||||
("string=?", strBoolBinop (==)),
|
||||
("string<", strBoolBinop (<)),
|
||||
("string>?", strBoolBinop (>)),
|
||||
("string<=?", strBoolBinop (<=)),
|
||||
("string>=?", strBoolBinop (>=)),
|
||||
("symbol?", symbolBoolUnop),
|
||||
("list?", listBoolUnop),
|
||||
("dotted-list?", dottedListBoolUnop),
|
||||
("number?", numBoolUnop),
|
||||
("integer?", intBoolUnop),
|
||||
("float?", floatBoolUnop),
|
||||
("char?", charBoolUnop),
|
||||
("string?", strBoolUnop),
|
||||
("bool?", boolBoolUnop),
|
||||
("symbol->string", convertSymbolToString),
|
||||
("string->symbol", convertStringToSymbol),
|
||||
("string->list", convertStringToList),
|
||||
("list->string", convertListToString),
|
||||
("char->integer", convertCharToInt),
|
||||
("integer->char", convertIntToChar),
|
||||
("char-upcase", convertCharUpcase),
|
||||
("char-downcase", convertCharDowncase),
|
||||
("char-at", charAt),
|
||||
("ceiling", floatingUnop ceiling),
|
||||
("floor", floatingUnop floor),
|
||||
("string-concatenate", stringConcatenate),
|
||||
("string-slice", stringSlice),
|
||||
("string-reverse", stringReverse)]
|
||||
|
||||
primitiveBindings :: IO Env
|
||||
primitiveBindings = nullEnv >>= (flip bindVars $ map (makeFunc IOFunc) ioPrimitives
|
||||
++ map (makeFunc PrimitiveFunc) primitives)
|
||||
where makeFunc constructor (var, func) = (var, constructor var func)
|
||||
|
||||
numericBinop :: (LispNum -> LispNum -> LispNum) -> [LispVal] -> ThrowsError LispVal
|
||||
numericBinop op singleVal@[_] = throwError $ NumArgs 2 singleVal
|
||||
numericBinop op params = mapM unpackNum params >>= return . Number . foldl1 op
|
||||
|
||||
integralBinop :: (LispInt -> LispInt -> LispInt) -> [LispVal] -> ThrowsError LispVal
|
||||
integralBinop op singleVal@[_] = throwError $ NumArgs 2 singleVal
|
||||
integralBinop op params = mapM unpackInt params >>= return . Number . Integer . foldl1 op
|
||||
|
||||
floatBinop :: (LispFloat -> LispFloat -> LispFloat) -> [LispVal] -> ThrowsError LispVal
|
||||
floatBinop op singleVal@[_] = throwError $ NumArgs 2 singleVal
|
||||
floatBinop op params = mapM unpackFloat params >>= return . Number . Float . foldl1 op
|
||||
|
||||
subtractOp :: [LispVal] -> ThrowsError LispVal
|
||||
subtractOp num@[_] = unpackNum (head num) >>= return . Number . negate
|
||||
subtractOp params = numericBinop (-) params
|
||||
|
||||
boolBinop :: (LispVal -> ThrowsError a) -> (a -> a -> Bool) -> [LispVal] -> ThrowsError LispVal
|
||||
boolBinop unpacker op singleVal@[_] = throwError $ NumArgs 2 singleVal
|
||||
boolBinop unpacker op args = do left <- unpacker $ head args
|
||||
right <- unpacker $ args !! 1
|
||||
return $ Bool $ left `op` right
|
||||
|
||||
numBoolBinop :: (LispNum -> LispNum -> Bool) -> [LispVal] -> ThrowsError LispVal
|
||||
numBoolBinop op params = boolBinop unpackNum op params
|
||||
|
||||
strBoolBinop = boolBinop unpackStr
|
||||
boolBoolBinop = boolBinop unpackBool
|
||||
|
||||
unpackNum :: LispVal -> ThrowsError LispNum
|
||||
unpackNum (Number (Integer n)) = return $ Integer n
|
||||
unpackNum (Number (Float n)) = return $ Float n
|
||||
unpackNum notNum = throwError $ TypeMismatch "number" notNum
|
||||
|
||||
unpackInt :: LispVal -> ThrowsError Integer
|
||||
unpackInt (Number (Integer n)) = return n
|
||||
unpackInt (String n) = let parsed = reads n in
|
||||
if null parsed
|
||||
then throwError $ TypeMismatch "integer" $ String n
|
||||
else return . fst . head $ parsed
|
||||
unpackInt (List [n]) = unpackInt n
|
||||
unpackInt notInt = throwError $ TypeMismatch "integer" notInt
|
||||
|
||||
unpackFloat :: LispVal -> ThrowsError Float
|
||||
unpackFloat (Number (Float f)) = return f
|
||||
unpackFloat (Number (Integer f)) = return $ fromInteger f
|
||||
unpackFloat (String f) = let parsed = reads f in
|
||||
if null parsed
|
||||
then throwError $ TypeMismatch "float" $ String f
|
||||
else return . fst . head $ parsed
|
||||
unpackFloat (List [f]) = unpackFloat f
|
||||
unpackFloat notFloat = throwError $ TypeMismatch "float" notFloat
|
||||
|
||||
unpackStr :: LispVal -> ThrowsError String
|
||||
unpackStr (String s) = return s
|
||||
unpackStr (Number (Integer s)) = return $ show s
|
||||
unpackStr (Number (Float s)) = return $ show s
|
||||
unpackStr (Bool s) = return $ show s
|
||||
unpackStr notString = throwError $ TypeMismatch "string" notString
|
||||
|
||||
unpackBool :: LispVal -> ThrowsError Bool
|
||||
unpackBool (Bool b) = return b
|
||||
unpackBool notBool = throwError $ TypeMismatch "boolean" notBool
|
||||
|
||||
|
||||
-- list processing (and strings now too)
|
||||
car :: [LispVal] -> ThrowsError LispVal
|
||||
car [List (x : xs)] = return x
|
||||
car [DottedList (x : xs) _] = return x
|
||||
car [String (c:xs)] = return . Char $ c
|
||||
car [badArg] = throwError $ TypeMismatch "pair" badArg
|
||||
car badArgs = throwError $ NumArgs 1 badArgs
|
||||
|
||||
cdr :: [LispVal] -> ThrowsError LispVal
|
||||
cdr [List (x : xs)] = return $ List xs
|
||||
cdr [DottedList [xs] x] = return x
|
||||
cdr [DottedList (_ : xs) x] = return $ DottedList xs x
|
||||
cdr [String (_:rest)] = return $ String rest
|
||||
cdr [badArg] = throwError $ TypeMismatch "pair" badArg
|
||||
cdr badArgs = throwError $ NumArgs 1 badArgs
|
||||
|
||||
cons :: [LispVal] -> ThrowsError LispVal
|
||||
cons [x1, List []] = return $ List [x1]
|
||||
cons [x, List xs] = return $ List ([x] ++ xs)
|
||||
cons [x, DottedList xs xlast] = return $ DottedList ([x] ++ xs) xlast
|
||||
cons [Char c, String s] = return $ String (c:s)
|
||||
cons [String a, String b] = return $ String (a ++ b)
|
||||
cons [x1, x2] = return $ DottedList [x1] x2
|
||||
cons badArgs = throwError $ NumArgs 2 badArgs
|
||||
|
||||
|
||||
-- equivalence testing
|
||||
eqv :: [LispVal] -> ThrowsError LispVal
|
||||
eqv [arg1, arg2] = return . Bool $ lispValEq arg1 arg2
|
||||
eqv badArgs = throwError $ NumArgs 2 badArgs
|
||||
|
||||
-- For any type that is an instance of Eq, you can define an Unpacker that takes a function
|
||||
-- from LispVal to that type, and may throw an error.
|
||||
data Unpacker = forall a. Eq a => AnyUnpacker (LispVal -> ThrowsError a)
|
||||
|
||||
unpackEquals :: LispVal -> LispVal -> Unpacker -> ThrowsError Bool
|
||||
unpackEquals arg1 arg2 (AnyUnpacker unpacker) =
|
||||
do unpacked1 <- unpacker arg1
|
||||
unpacked2 <- unpacker arg2
|
||||
return $ unpacked1 == unpacked2
|
||||
`catchError` (const $ return False)
|
||||
|
||||
|
||||
equal :: [LispVal] -> ThrowsError LispVal
|
||||
equal [List arg1, List arg2] = return . Bool $ (length arg1 == length arg2) &&
|
||||
(and $ map equalPair $ zip arg1 arg2)
|
||||
where equalPair (x1, x2) =let (Bool x) = extractValue $ equal [x1, x2] in x
|
||||
|
||||
equal [arg1, arg2] = do
|
||||
primitiveEquals <- liftM or $ mapM (unpackEquals arg1 arg2)
|
||||
[AnyUnpacker unpackNum, AnyUnpacker unpackFloat, AnyUnpacker unpackInt, AnyUnpacker unpackStr, AnyUnpacker unpackBool]
|
||||
return $ Bool $ (primitiveEquals || lispValEq arg1 arg2)
|
||||
equal badArgs = throwError $ NumArgs 2 badArgs
|
||||
|
||||
|
||||
boolUnop :: (LispVal -> LispVal) -> [LispVal] -> ThrowsError LispVal
|
||||
boolUnop op [val] = return . op $ val
|
||||
boolUnop op badArgs = throwError $ NumArgs 1 badArgs
|
||||
|
||||
symbolBoolUnop = boolUnop isLispSymbol
|
||||
listBoolUnop = boolUnop isLispList
|
||||
dottedListBoolUnop = boolUnop isLispDottedList
|
||||
numBoolUnop = boolUnop isLispNumber
|
||||
intBoolUnop = boolUnop isLispInteger
|
||||
floatBoolUnop = boolUnop isLispFloat
|
||||
charBoolUnop = boolUnop isLispChar
|
||||
strBoolUnop = boolUnop isLispString
|
||||
boolBoolUnop = boolUnop isLispBool
|
||||
nullBoolUnop = boolUnop isNull
|
||||
|
||||
floatingUnop :: (LispFloat -> LispInt) -> [LispVal] -> ThrowsError LispVal
|
||||
floatingUnop op [Number (Float val)] = return . Number . Integer . op $ val
|
||||
floatingUnop op [badArg] = throwError $ TypeMismatch "float" badArg
|
||||
floatingUnop op badArgs = throwError $ NumArgs 1 badArgs
|
||||
|
||||
-- type identification primitives
|
||||
isLispSymbol :: LispVal -> LispVal
|
||||
isLispSymbol (Atom _) = Bool True
|
||||
isLispSymbol _ = Bool False
|
||||
|
||||
isLispList :: LispVal -> LispVal
|
||||
isLispList (List _) = Bool True
|
||||
isLispList _ = Bool False
|
||||
|
||||
isLispDottedList :: LispVal -> LispVal
|
||||
isLispDottedList (DottedList _ _) = Bool True
|
||||
isLispDottedList _ = Bool False
|
||||
|
||||
isLispNumber :: LispVal -> LispVal
|
||||
isLispNumber (Number _) = Bool True
|
||||
isLispNumber _ = Bool False
|
||||
|
||||
isLispInteger :: LispVal -> LispVal
|
||||
isLispInteger (Number (Integer _)) = Bool True
|
||||
isLispInteger _ = Bool False
|
||||
|
||||
isLispFloat :: LispVal -> LispVal
|
||||
isLispFloat (Number (Float _)) = Bool True
|
||||
isLispFloat _ = Bool False
|
||||
|
||||
isLispChar :: LispVal -> LispVal
|
||||
isLispChar (Char _) = Bool True
|
||||
isLispChar _ = Bool False
|
||||
|
||||
isLispString :: LispVal -> LispVal
|
||||
isLispString (String _) = Bool True
|
||||
isLispString _ = Bool False
|
||||
|
||||
isLispBool :: LispVal -> LispVal
|
||||
isLispBool (Bool _) = Bool True
|
||||
isLispBool _ = Bool False
|
||||
|
||||
isNull :: LispVal -> LispVal
|
||||
isNull (List []) = Bool True
|
||||
isNull _ = Bool False
|
||||
|
||||
-- conversions
|
||||
|
||||
conversion :: (LispVal -> ThrowsError LispVal) -> [LispVal] -> ThrowsError LispVal
|
||||
conversion func [arg] = func arg
|
||||
conversion _ badArgs = throwError $ NumArgs 1 badArgs
|
||||
|
||||
convertSymbolToString = conversion symbolToString
|
||||
convertStringToSymbol = conversion stringToSymbol
|
||||
convertStringToList = conversion stringToList
|
||||
convertListToString = conversion listToString
|
||||
convertCharUpcase = conversion charUpcase
|
||||
convertCharDowncase = conversion charDowncase
|
||||
convertCharToInt = conversion charToInt
|
||||
convertIntToChar = conversion intToChar
|
||||
|
||||
symbolToString :: LispVal -> ThrowsError LispVal
|
||||
symbolToString (Atom a) = return $ String a
|
||||
symbolToString badArg = throwError $ TypeMismatch "atom" badArg
|
||||
|
||||
stringToSymbol :: LispVal -> ThrowsError LispVal
|
||||
stringToSymbol (String s) = return $ Atom s
|
||||
stringToSymbol badArg = throwError $ TypeMismatch "string" badArg
|
||||
|
||||
stringToList :: LispVal -> ThrowsError LispVal
|
||||
stringToList (String s) = return $ str2lst (String s)
|
||||
stringToList badArg = throwError $ TypeMismatch "atom" badArg
|
||||
|
||||
listToString :: LispVal -> ThrowsError LispVal
|
||||
listToString (List l) = return $ lst2str (List l)
|
||||
listToString badArg = throwError $ TypeMismatch "list" badArg
|
||||
|
||||
charUpcase :: LispVal -> ThrowsError LispVal
|
||||
charUpcase (Char c) = return $ Char $ toUpper c
|
||||
charUpcase badArg = throwError $ TypeMismatch "char" badArg
|
||||
|
||||
charDowncase :: LispVal -> ThrowsError LispVal
|
||||
charDowncase (Char c) = return $ Char $ toLower c
|
||||
charDowncase badArg = throwError $ TypeMismatch "char" badArg
|
||||
|
||||
charToInt :: LispVal -> ThrowsError LispVal
|
||||
charToInt (Char c) = return $ Number . Integer . toInteger $ ord c
|
||||
charToInt badArg = throwError $ TypeMismatch "char" badArg
|
||||
|
||||
intToChar :: LispVal -> ThrowsError LispVal
|
||||
intToChar (Number (Integer n)) = return $ Char . chr $ fromInteger n
|
||||
intToChar badArg = throwError $ TypeMismatch "integer" badArg
|
||||
|
||||
|
||||
-- string functions
|
||||
|
||||
lst2str :: LispVal -> LispVal
|
||||
lst2str (List l) = String [c | Char c <- l]
|
||||
|
||||
str2lst :: LispVal -> LispVal
|
||||
str2lst (String s) = List [Char c | c <- s]
|
||||
|
||||
charAt :: [LispVal] -> ThrowsError LispVal
|
||||
charAt [Number (Integer idx), String s] = return . Char $ s !! (fromInteger idx)
|
||||
charAt [badArg, String _] = throwError $ TypeMismatch "integer" badArg
|
||||
charAt [Number (Integer _), badArg] = throwError $ TypeMismatch "string" badArg
|
||||
charAt badArgs = throwError $ NumArgs 2 badArgs
|
||||
|
||||
stringConcatenate :: [LispVal] -> ThrowsError LispVal
|
||||
stringConcatenate [List xs] = return . String $ foldl (++) "" strings
|
||||
where strings = [s | String s <- xs]
|
||||
stringConcatenate [badArg] = throwError $ TypeMismatch "list" badArg
|
||||
stringConcatenate badArgs = throwError $ NumArgs 1 badArgs
|
||||
|
||||
stringSlice :: [LispVal] -> ThrowsError LispVal
|
||||
stringSlice [String s, Number (Integer start), Number (Integer len)] =
|
||||
return . String $ take (fromInteger len) $ drop (fromInteger start) s
|
||||
stringSlice badArgs@[_,_,_] = throwError $ TypeMismatch "string,integer,integer" (List badArgs)
|
||||
stringSlice badArgs = throwError $ NumArgs 3 badArgs
|
||||
|
||||
stringReverse :: [LispVal] -> ThrowsError LispVal
|
||||
stringReverse [String s] = return . String $ reverse s
|
||||
stringReverse [badArg] = throwError $ TypeMismatch "string" badArg
|
||||
stringReverse badArgs = throwError $ NumArgs 1 badArgs
|
||||
|
||||
|
||||
-- error checking
|
||||
data LispError = NumArgs Integer [LispVal]
|
||||
| TypeMismatch String LispVal
|
||||
| Parser ParseError
|
||||
| BadSpecialForm String LispVal
|
||||
| NotFunction String String
|
||||
| UnboundVar String String
|
||||
| Default String
|
||||
| FileNotFound String
|
||||
|
||||
showError :: LispError -> String
|
||||
showError (UnboundVar message varname) = message ++ ": " ++ varname
|
||||
showError (BadSpecialForm message form) = message ++ ": " ++ show form
|
||||
showError (NotFunction message func) = message ++ ": " ++ func
|
||||
showError (NumArgs expected found) = "Expected " ++ show expected
|
||||
++ " args, found values (" ++ unwordsList found ++ ")"
|
||||
showError (TypeMismatch expected found) = "Invalid type: expected " ++ expected
|
||||
++ ", found " ++ show found
|
||||
showError (Parser parseErr) = "Parse error at " ++ show parseErr
|
||||
showError (FileNotFound filename) = "File not found: " ++ show filename
|
||||
|
||||
instance Show LispError where show = showError
|
||||
|
||||
instance Error LispError where
|
||||
noMsg = Default "An error has occured"
|
||||
strMsg = Default
|
||||
|
||||
type ThrowsError = Either LispError
|
||||
|
||||
--XXX what type does trapError have?
|
||||
trapError action = catchError action (return . show)
|
||||
|
||||
extractValue :: ThrowsError a -> a
|
||||
extractValue (Right val) = val
|
||||
|
||||
-- REPL (read-eval-print loop)
|
||||
|
||||
flushStr :: String -> IO ()
|
||||
flushStr str = putStr str >> hFlush stdout
|
||||
|
||||
readPrompt :: String -> IO String
|
||||
readPrompt prompt = flushStr prompt >> getLine
|
||||
|
||||
evalString :: Env -> String -> IO String
|
||||
evalString env expr = runIOThrows $ liftM show $ (liftThrows $ readExpr expr) >>= eval env
|
||||
|
||||
evalAndPrint :: Env -> String -> IO ()
|
||||
evalAndPrint env expr = do
|
||||
result <- evalString env expr
|
||||
if result == ""
|
||||
then return ()
|
||||
else putStrLn result
|
||||
|
||||
until_ :: Monad m => (a -> Bool) -> m a -> (a -> m ()) -> m ()
|
||||
until_ pred prompt action = do
|
||||
result <- prompt
|
||||
if pred result
|
||||
then return ()
|
||||
else action result >> until_ pred prompt action
|
||||
|
||||
runOneThenRepl :: [String] -> IO ()
|
||||
runOneThenRepl args = do
|
||||
env <- primitiveBindings >>= flip bindVars [("args", List $ map String $ drop 1 args)]
|
||||
(runIOThrows $ liftM show $ eval env (List [Atom "load", String (args !! 0)]))
|
||||
>>= hPutStrLn stderr
|
||||
runReplWithEnv $ return env
|
||||
|
||||
runReplWithEnv :: IO Env -> IO ()
|
||||
runReplWithEnv env = do env >>= until_ (== "quit") (readPrompt "> ") . evalAndPrint
|
||||
|
||||
runRepl :: IO ()
|
||||
runRepl = runReplWithEnv primitiveBindings
|
||||
--
|
||||
--runOneThenRepl :: [String] -> IO ()
|
||||
--runOneThenRepl args = do runOneWithEnv primitiveBindings args >>= runReplWithEnv
|
||||
|
||||
|
||||
-- saving state, the environment (a list of strings paired to mutable values)
|
||||
type Env = IORef [(String, IORef LispVal)]
|
||||
|
||||
nullEnv :: IO Env
|
||||
nullEnv = newIORef []
|
||||
|
||||
|
||||
type IOThrowsError = ErrorT LispError IO
|
||||
|
||||
liftThrows :: ThrowsError a -> IOThrowsError a
|
||||
liftThrows (Left err) = throwError err
|
||||
liftThrows (Right val) = return val
|
||||
|
||||
runIOThrows :: IOThrowsError String -> IO String
|
||||
runIOThrows action = runErrorT (trapError action) >>= return . extractValue
|
||||
|
||||
|
||||
isBound :: Env -> String -> IO Bool
|
||||
isBound envRef var = readIORef envRef >>= return . maybe False (const True) . lookup var
|
||||
|
||||
getVar :: Env -> String -> IOThrowsError LispVal
|
||||
getVar envRef var = do env <- liftIO $ readIORef envRef
|
||||
maybe (throwError $ UnboundVar "Getting an unbound variable" var)
|
||||
(liftIO . readIORef)
|
||||
(lookup var env)
|
||||
|
||||
setVar :: Env -> String -> LispVal -> IOThrowsError LispVal
|
||||
setVar envRef var value = do env <- liftIO $ readIORef envRef
|
||||
maybe (throwError $ UnboundVar "Setting an unbound variable" var)
|
||||
(liftIO . (flip writeIORef value))
|
||||
(lookup var env)
|
||||
return value
|
||||
|
||||
setVars :: Env -> [(String, LispVal)] -> IOThrowsError LispVal
|
||||
setVars envRef values = liftM last $ mapM set values
|
||||
where set (var, value) = setVar envRef var value
|
||||
|
||||
defineVar :: Env -> String -> LispVal -> IOThrowsError LispVal
|
||||
defineVar envRef var value = do
|
||||
alreadyDefined <- liftIO $ isBound envRef var
|
||||
if alreadyDefined
|
||||
then setVar envRef var value >> return value
|
||||
else liftIO $ do
|
||||
valueRef <- newIORef value
|
||||
env <- readIORef envRef
|
||||
writeIORef envRef ((var, valueRef) : env)
|
||||
return value
|
||||
|
||||
bindVars :: Env -> [(String, LispVal)] -> IO Env
|
||||
bindVars envRef bindings = readIORef envRef >>= extendEnv bindings >>= newIORef
|
||||
where extendEnv bindings env = liftM (++ env) (mapM addBinding bindings)
|
||||
addBinding (var, value) = do ref <- newIORef value
|
||||
return (var, ref)
|
||||
|
||||
--- XXX types? same as makeFunc, no?
|
||||
makeFunc varargs env params body = return $ Func (map showVal params) varargs body env
|
||||
makeNormalFunc = makeFunc Nothing
|
||||
makeVarargs = makeFunc . Just . showVal
|
||||
|
||||
|
||||
ioPrimitives :: [(String, [LispVal] -> IOThrowsError LispVal)]
|
||||
ioPrimitives = [("apply", applyProc),
|
||||
("open-input-file", makePort ReadMode),
|
||||
("open-output-file", makePort WriteMode),
|
||||
("close-input-file", closePort),
|
||||
("close-output-file", closePort),
|
||||
("read", readProc),
|
||||
("write", writeProc),
|
||||
("read-contents", readContents),
|
||||
("read-all", readAll),
|
||||
("display", display),
|
||||
("random", randomInt)]
|
||||
|
||||
applyProc :: [LispVal] -> IOThrowsError LispVal
|
||||
applyProc [func, List args] = apply func args
|
||||
applyProc (func : args) = apply func args
|
||||
|
||||
makePort :: IOMode -> [LispVal] -> IOThrowsError LispVal
|
||||
makePort mode [String filename] = liftM Port $ liftIO $ openFile filename mode
|
||||
|
||||
closePort :: [LispVal] -> IOThrowsError LispVal
|
||||
closePort [Port port] = liftIO $ hClose port >> (return $ Bool True)
|
||||
closePort _ = return $ Bool False
|
||||
|
||||
readProc :: [LispVal] -> IOThrowsError LispVal
|
||||
readProc [] = readProc [Port stdin]
|
||||
readProc [Port port] = (liftIO $ hGetLine port) >>= liftThrows . readExpr
|
||||
|
||||
writeProc :: [LispVal] -> IOThrowsError LispVal
|
||||
writeProc [obj] = writeProc [obj, Port stdout]
|
||||
writeProc [obj, Port port] = liftIO $ hPrint port obj >> (return $ Bool True)
|
||||
|
||||
readContents :: [LispVal] -> IOThrowsError LispVal
|
||||
readContents [String filename] = liftM String $ liftIO $ readFile filename
|
||||
|
||||
load :: String -> IOThrowsError [LispVal]
|
||||
load filename = (liftIO $ readFile filename) >>= liftThrows . readExprList
|
||||
|
||||
readAll :: [LispVal] -> IOThrowsError LispVal
|
||||
readAll [String filename] = liftM List $ load filename
|
||||
|
||||
|
||||
-- display values, fallback on showVal since only strings and chars need to be displayed differently
|
||||
displayVal :: LispVal -> String
|
||||
displayVal (String contents) = contents
|
||||
displayVal (Char c) = [c]
|
||||
displayVal x = showVal x
|
||||
|
||||
|
||||
display :: [LispVal] -> IOThrowsError LispVal
|
||||
display [] = return $ Null False
|
||||
display (x : xs) = do liftIO . putStr $ displayVal x; display xs
|
||||
|
||||
randomInt :: [LispVal] -> IOThrowsError LispVal
|
||||
randomInt [Number (Integer high)] = liftM (Number . Integer) $ liftIO num
|
||||
where num = getStdRandom (randomR (0,high-1))
|
||||
randomInt [badArg] = throwError $ TypeMismatch "integer" badArg
|
||||
randomInt args = throwError $ NumArgs 1 args
|
||||
226
stdlib.scm
Normal file
226
stdlib.scm
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
;; the basics
|
||||
(define nil ())
|
||||
(define null nil)
|
||||
(define true #t)
|
||||
(define false #f)
|
||||
(define (pair? x) (and (list? x) (not (null? x))))
|
||||
(define (not x) (if (eq? #f x) #t #f))
|
||||
(define (newline) (display "\n"))
|
||||
|
||||
(define (list . objs) objs)
|
||||
(define (id obj) obj)
|
||||
(define (flip func)
|
||||
(lambda (arg1 arg2) (func arg2 arg1)))
|
||||
(define (curry func arg1)
|
||||
(lambda (arg) (func arg1 arg)))
|
||||
(define (compose f g)
|
||||
(lambda (arg) (f (apply g arg))))
|
||||
|
||||
;; math
|
||||
(define zero? (curry = 0))
|
||||
(define negative? (curry (flip <) 0))
|
||||
(define positive? (curry (flip >) 0))
|
||||
(define inc (curry + 1))
|
||||
(define dec (curry (flip -) 1))
|
||||
|
||||
(define (divides? a b)
|
||||
(zero? (remainder b a)))
|
||||
|
||||
(define (prime? x)
|
||||
(= x (smallest-divisor x)))
|
||||
|
||||
;; this sort of coercion shouldn't be necessary
|
||||
(define (odd? num)
|
||||
(cond ((and (float? num) (equal? num (floor num))) (= (mod (floor num) 2) 1))
|
||||
((float? num) (display "odd? expects an integer, given:" num) #f)
|
||||
(else (= (mod num 2) 1))))
|
||||
(define (even? num)
|
||||
(cond ((and (float? num) (equal? num (floor num))) (zero? (mod (floor num) 2)))
|
||||
((float? num) (display "even? expects an integer, given:" num) #f)
|
||||
(else (zero? (mod num 2)))))
|
||||
|
||||
(define (square x) (* x x))
|
||||
(define (abs x)
|
||||
(cond ((< x 0) (- x))
|
||||
(else x)))
|
||||
|
||||
;; exponentation using recursion (pretty fast)
|
||||
(define (expt-rec b n)
|
||||
(if (zero? n)
|
||||
1
|
||||
(* b (expt-rec b (dec n)))))
|
||||
|
||||
;; exponentation using foldl (slowest)
|
||||
(define (expt-fold b n)
|
||||
(fold * 1 (fill (range 1 n) b)))
|
||||
|
||||
;; exponentation using iteration
|
||||
;; (seems a bit slower than the recursive version, but still fast)
|
||||
(define (expt-iter b counter . product)
|
||||
(let product (lambda (if (null? product) 1 (car product)))
|
||||
(if (zero? counter)
|
||||
product
|
||||
(expt-iter b (dec counter) (* b product)))))
|
||||
|
||||
;; fast exponentation
|
||||
(define (fast-expt b n)
|
||||
(cond ((= n 0) 1)
|
||||
((even? n) (square (fast-expt b (/ n 2))))
|
||||
(else (* b (fast-expt b (- n 1))))))
|
||||
(define expt fast-expt)
|
||||
|
||||
;; calculate square roots
|
||||
(define (sqrt-good-enough? guess x)
|
||||
(let ((delta 0.0001))
|
||||
(< (abs (- (square guess) x))
|
||||
delta)))
|
||||
|
||||
(define (sqrt-improve guess x)
|
||||
(average guess (/ x guess)))
|
||||
|
||||
(define (sqrt-iter guess x)
|
||||
(if (sqrt-good-enough? guess x)
|
||||
guess
|
||||
(sqrt-iter (sqrt-improve guess x) x)))
|
||||
|
||||
(define (sqrt x)
|
||||
(sqrt-iter 1.0 x))
|
||||
|
||||
(define (factorial n)
|
||||
(if (<= n 1)
|
||||
1
|
||||
(* n (factorial (- n 1)))))
|
||||
(define ! factorial)
|
||||
|
||||
;; greatest common denominator
|
||||
(define (gcd a b)
|
||||
(if (zero? b)
|
||||
a
|
||||
(gcd b (remainder a b))))
|
||||
|
||||
(define (find-divisor n test-divisor)
|
||||
(let ((next (lambda (n)
|
||||
(if (eq? n 2)
|
||||
3
|
||||
(+ n 2)))))
|
||||
(cond ((> (square test-divisor) n) n)
|
||||
((divides? test-divisor n) test-divisor)
|
||||
(else (find-divisor n (next test-divisor))))))
|
||||
|
||||
(define (smallest-divisor n)
|
||||
(find-divisor n 2))
|
||||
|
||||
(define (expmod base exp m)
|
||||
(cond ((zero? exp) 1)
|
||||
((even? exp)
|
||||
(remainder (square (expmod base (/ exp 2) m))
|
||||
m))
|
||||
(else
|
||||
(remainder (* base (expmod base (dec exp) m))
|
||||
m))))
|
||||
|
||||
;; test for prime numbers using Fermat's method
|
||||
(define (fermat-test n)
|
||||
(let ((try-it (lambda (a)
|
||||
(= (expmod a n n) a))))
|
||||
(try-it (inc (random (dec n))))))
|
||||
|
||||
;; this runs Fermat's test a given number of times
|
||||
(define (fast-prime? n times)
|
||||
(cond ((zero? times) true)
|
||||
((fermat-test n) (fast-prime? n (dec times)))
|
||||
(else false)))
|
||||
|
||||
;; folds
|
||||
|
||||
;; SICP calls this accumulate
|
||||
(define (foldr func end lst)
|
||||
(if (null? lst)
|
||||
end
|
||||
(func (car lst) (foldr func end (cdr lst)))))
|
||||
|
||||
(define (foldl func accum lst)
|
||||
(if (null? lst)
|
||||
accum
|
||||
(foldl func (func accum (car lst)) (cdr lst))))
|
||||
|
||||
(define fold foldl)
|
||||
(define reduce fold)
|
||||
|
||||
(define (unfold func init pred)
|
||||
(if (pred init)
|
||||
(cons init '())
|
||||
(cons init (unfold func (func init) pred))))
|
||||
|
||||
(define (sum-list lst) (fold + 0 lst))
|
||||
(define (sum . lst) (sum-list lst))
|
||||
(define (product . lst) (fold * 0 lst))
|
||||
(define (average . xs) (/ (sum-list xs) (length xs)))
|
||||
(define avg average)
|
||||
|
||||
(define (max first . num-list) (fold (lambda (old new) (if (> old new) old new)) first num-list))
|
||||
(define (min first . num-list) (fold (lambda (old new) (if (< old new) old new)) first num-list))
|
||||
(define (length lst) (fold (lambda (x y) (+ x 1)) 0 lst))
|
||||
(define (reverse lst) (fold (flip cons) '() lst))
|
||||
(define (nth n lst) (if (= n 1) (car lst) (nth (- n 1) (cdr lst))))
|
||||
|
||||
|
||||
(define (mem-helper pred op) (lambda (acc next) (if (and (not acc) (pred (op next))) next acc)))
|
||||
(define (memq obj lst) (fold (mem-helper (curry eq? obj) id) #f lst))
|
||||
(define (memv obj lst) (fold (mem-helper (curry eqv? obj) id) #f lst))
|
||||
(define (member obj lst) (fold (mem-helper (curry equal? obj) id) #f lst))
|
||||
(define (assq obj alist) (fold (mem-helper (curry eq? obj) car) #f alist))
|
||||
(define (assv obj alist) (fold (mem-helper (curry eqv? obj) car) #f alist))
|
||||
(define (assoc obj alist) (fold (mem-helper (curry equal? obj) car) #f alist))
|
||||
|
||||
;; TODO define fold-k (fold w/ continuations) and use it to short-circuit these
|
||||
(define (any? pred lst) (fold (lambda (any-found x) (or any-found (pred x))) #f lst))
|
||||
(define (all? pred lst) (fold (lambda (all-matched x) (and (pred x) all-matched)) #t lst))
|
||||
|
||||
;; transformations
|
||||
;; (define (map func lst) (foldr (lambda (x y) (cons (func x) y)) '() lst))
|
||||
;; (define (mapr func lst) (fold (lambda (x y) (cons (func y) x)) '() lst))
|
||||
(define (filter pred lst) (foldr (lambda (x y) (if (pred x) (cons x y) y)) '() lst))
|
||||
(define (fill lst n) (map (lambda (x) n) lst))
|
||||
|
||||
;; the more general version of map, similar to mapcar in Lisp
|
||||
(define (map func . seqs)
|
||||
(cond ((= 1 (length seqs))
|
||||
(foldr (lambda (x y) (cons (func x) y)) '() (car seqs)))
|
||||
((null? (car seqs)) nil)
|
||||
(else
|
||||
(cons (apply func (map car seqs))
|
||||
(apply map (cons func (map cdr seqs)))))))
|
||||
|
||||
|
||||
(define (caar lst) (car (car lst)))
|
||||
(define (cadr lst) (car (cdr lst)))
|
||||
(define (caddr lst) (car (cdr (cdr lst))))
|
||||
(define (cadddr lst) (car (cdr (cdr (cdr lst)))))
|
||||
|
||||
|
||||
;; like python's range
|
||||
;; SICP calls this enumerate-interval ... i prefer range
|
||||
(define (range min max)
|
||||
(if (> min max)
|
||||
nil
|
||||
(cons min (range (inc min) max))))
|
||||
|
||||
;; recursive method of concatenating 2 lists
|
||||
(define (append list1 list2)
|
||||
(if (null? list1)
|
||||
list2
|
||||
(cons (car list1) (append (cdr list1) list2))))
|
||||
|
||||
;; string manipulation
|
||||
(define (string-map func s)
|
||||
(list->string (map func (string->list s))))
|
||||
|
||||
(define (string-upcase s)
|
||||
(string-map char-upcase s))
|
||||
|
||||
(define (string-downcase s)
|
||||
(string-map char-downcase s))
|
||||
|
||||
(define (string-append . lst)
|
||||
(string-concatenate lst))
|
||||
Loading…
Reference in a new issue