It's been a little while since I wrote about Haskell and the Scheme interpreter I've been using to learn and play with both Haskell and Scheme. I finished the tutorial and got myself a working Scheme interpreter and indeed it has been fun to use it for trying out little things now and then. (Normally I would use Emacs or Dr. Scheme for that sort of thing.) There certainly are interesting things to try floating around da intranet. And also things to read and learn from, such as misp (via Moonbase).

I'm going to describe two new features of my Scheme in this post. The second one is more interesting and was more fun to implement (cond).

Pasing Scheme integers

Last time I left off at parsing R5RS compliant numbers, which is exercise 3.3.4 if you're following along the tutorial. Only integers in binary, octal, decimal, and hexadecimal are parsed right now. The syntaxes for those are #b101010 , #o52 , 42 (or #d42 ), and #x2a , respectively. To parse these we use the readOct , readDec , readHex , and readInt functions provided by the Numeric module, and import them thusly:

import Numeric (readOct, readDec, readHex, readInt)

In order to parse binary digits we need to write a few short functions to help us out. For some reason I couldn't find binDigit , isBinDigit and readBin in their respective modules but luckily they're trivial to implement. The first two are self-explanatory, as is the third if you look at the implementation of its relatives for larger bases. In a nutshell readBin says to: "read an integer in base 2, validating digits with isBinDigit ."

-- parse a binary digit, analagous to decDigit, octDigit, hexDigit binDigit :: Parser Char binDigit = oneOf "01" -- 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

The next step is to augment parseNumber so that it can handle R5RS numbers in addition to regular decimal numbers. To refresh, the tutorial's parseNumber function looks like this:

parseNumber :: Parser LispVal parseNumber = liftM (Number . read) $ many1 digit

Three more lines in this function will give us a decent starting point:

parseNumber = do char '#' base parseDigits with the given base to do the dirty work. If that fails then fall back to parsing a boring old string of decimal digits. That brings us to actually parsing the numbers. parseDigits is simple, but there might be a more Haskell-y way of doing this.

-- Parse a string of digits in the given base. parseDigits :: Char - Parser LispVal parseDigits base = many1 d >>= return where d = case base of 'b' -> binDigit 'd' -> digit 'o' -> octDigit 'x' -> hexDigit

readFoo

head

fst

1 2 3 4 5 6 7 8 9

eval env (List (Atom "cond" : List (Atom "else" : exprs) : [])) = liftM last $ mapM (eval env) exprs eval env (List (Atom "cond" : List (pred : conseq) : rest)) = do result <- eval env $ pred case result of Bool False -> case rest of [] -> return $ List [] _ -> eval env $ List (Atom "cond" : rest) _ -> liftM last $ mapM (eval env) conseq

else

cond

rest

pred

#f

#f

conseq

else

conseq

cond