> module Pixley where

> import Text.ParserCombinators.Parsec
> import qualified Data.Map as Map

Definitions
===========

An environment maps names (represented as strings) to expressions.

> type Env = Map.Map String Expr

> data Expr = Symbol String
>           | Cons Expr Expr
>           | Null
>           | Boolean Bool
>           | Lambda Env Expr Expr
>           | Macro Env Expr
>     deriving (Ord, Eq)

> instance Show Expr where
>     show (Symbol s)      = s
>     show e@(Cons _ _)    = "(" ++ (showl e)
>     show Null            = "()"
>     show (Boolean True)  = "#t"
>     show (Boolean False) = "#f"
>     show (Lambda env args body) = "(lambda " ++ (show args) ++ " " ++ (show body) ++ ")"
>     show (Macro env body) = "(macro " ++ (show body) ++ ")"

> showl Null = ")"
> showl (Cons a Null) = (show a) ++ ")"
> showl (Cons a b) = (show a) ++ " " ++ (showl b)
> showl other = ". " ++ (show other) ++ ")"

Parser
======

The overall grammar of the language is:

    Expr ::= symbol | "(" {Expr} ")"

A symbol is denoted by a string which may contain only alphanumeric
characters, hyphens, underscores, and question marks.

> symbol = do
>     c <- letter
>     cs <- many (alphaNum <|> char '-' <|> char '?' <|> char '_' <|> char '*')
>     return (Symbol (c:cs))

> list = do
>     string "("
>     e <- many expr
>     spaces
>     string ")"
>     return (consFromList e)

The top-level parsing function implements the overall grammar given above.
Note that we need to give the type of this parser here -- otherwise the
type inferencer freaks out for some reason.

> expr :: Parser Expr
> expr = do
>     spaces
>     r <- (symbol <|> list)
>     return r

A convenience function for parsing Pixley programs.

> pa program = parse expr "" program

A helper function to make Cons cells from Haskell lists.

> consFromList [] =
>     Null
> consFromList (x:xs) =
>     Cons x (consFromList xs)

Evaluator
=========

> car (Cons a b) = a
> cdr (Cons a b) = b

We need to check for properly-formed lists, because that's what
Scheme and Pixley do.

> listp Null = Boolean True
> listp (Cons a b) = listp b
> listp _ = Boolean False

> eval env (Symbol s) =
>     (Map.!) env s
> eval env (Cons (Symbol "quote") (Cons sexpr Null)) =
>     sexpr
> eval env (Cons (Symbol "car") (Cons sexpr Null)) =
>     car (eval env sexpr)
> eval env (Cons (Symbol "cdr") (Cons sexpr Null)) =
>     cdr (eval env sexpr)
> eval env (Cons (Symbol "cons") (Cons sexpr1 (Cons sexpr2 Null))) =
>     Cons (eval env sexpr1) (eval env sexpr2)
> eval env (Cons (Symbol "list?") (Cons sexpr Null)) =
>     listp (eval env sexpr)
> eval env (Cons (Symbol "equal?") (Cons sexpr1 (Cons sexpr2 Null))) =
>     Boolean ((eval env sexpr1) == (eval env sexpr2))
> eval env (Cons (Symbol "let*") (Cons bindings (Cons body Null))) =
>     eval (bindAll bindings env) body
> eval env (Cons (Symbol "cond") rest) =
>     checkAll env rest
> eval env (Cons (Symbol "lambda") (Cons args (Cons body Null))) =
>     Lambda env args body

> eval env (Cons fun actuals) =
>     case eval env fun of
>         Lambda closedEnv formals body ->
>             eval (bindArgs env closedEnv formals actuals) body
>         Macro closedEnv body ->
>             let
>                 env' = Map.insert "arg" actuals closedEnv
>             in
>                 eval env' body
> eval env weirdThing =
>     error ("You can't evaluate a " ++ show weirdThing)

> checkAll env (Cons (Cons (Symbol "else") (Cons branch Null)) Null) =
>     eval env branch
> checkAll env (Cons (Cons test (Cons branch Null)) rest) =
>     case eval env test of
>         Boolean True ->
>             eval env branch
>         Boolean False ->
>             checkAll env rest

> bindAll Null env =
>     env
> bindAll (Cons binding rest) env =
>     bindAll rest (bind binding env)

> bind (Cons (Symbol sym) (Cons sexpr Null)) env =
>     Map.insert sym (eval env sexpr) env

> bindArgs env closedEnv Null Null =
>     closedEnv
> bindArgs env closedEnv (Cons (Symbol sym) formals) (Cons actual actuals) =
>     Map.insert sym (eval env actual) (bindArgs env closedEnv formals actuals)

> consFromEnvList [] =
>     Null
> consFromEnvList ((k,v):rest) =
>     Cons (Cons (Symbol k) (Cons v Null)) (consFromEnvList rest)

> envFromCons Null =
>     Map.empty
> envFromCons (Cons (Cons (Symbol k) (Cons v Null)) rest) =
>     Map.insert k v (envFromCons rest)

Top-Level Driver
================

> runPixley program =
>     let
>         Right ast = parse expr "" program
>     in
>         eval Map.empty ast
