git @ Cat's Eye Technologies Deturgenchry / master src / Deturgenchry.hs
master

Tree @master (Download .tar.gz)

Deturgenchry.hs @masterraw · history · blame

module Deturgenchry where

import Text.ParserCombinators.Parsec

-- ========== MAPS ========== --

data Map k v = Binding k v (Map k v)
             | EmptyMap
             deriving (Eq, Ord)

get _ EmptyMap = Nothing
get key (Binding key' val map)
    | key == key' = Just val
    | otherwise   = get key map

set key val map = Binding key val (strip key map)

strip key EmptyMap = EmptyMap
strip key (Binding key' val map)
    | key == key' = strip key map
    | otherwise   = Binding key' val (strip key map)

merge map EmptyMap = map
merge map (Binding key val rest) =
    merge (set key val map) rest

instance (Show k, Show v) => Show (Map k v) where
    show EmptyMap = "[]"
    show (Binding k v map) = (show k) ++ "=" ++ (show v) ++ "\n" ++ show map

-- ========== Grammar ========== --

-- Program      ::= {ClassDefn}.
-- ClassDefn    ::= "class" name "{" {MethodDefn} "}".
-- MethodDefn   ::= "method" name "(" [name {"," name}] ")" Statement.
-- Statement    ::= Block | Conditional | Transfer | Assignment.
-- Block        ::= "{" {Statement} "}".
-- Conditional  ::= "if" Expr Statement "else" Statement.
-- Transfer     ::= "pass" Expr Expr.
-- Assignment   ::= name "=" Expr.
-- Expr         ::= RefExpr | "new" name | IntegerLiteral.
-- RefExpr      ::= name {"." name} [SetExpr | CallExpr].
-- SetExpr      ::= "[" name "=" Expr {"," name "=" Expr} "]".
-- CallExpr     ::= "(" Expr {"," Expr} ")".

-- ========== AST ========== --

type Name = String

data Program    = Program [ClassDefn]
    deriving (Show, Ord, Eq)

data ClassDefn  = ClassDefn Name [MethodDefn]
    deriving (Show, Ord, Eq)

data MethodDefn = MethodDefn Name [Name] Statement
    deriving (Show, Ord, Eq)

data Statement  = Block [Statement]
                | Conditional Expr Statement Statement
                | Transfer Expr Expr
                | Assign Name Expr
    deriving (Show, Ord, Eq)

data Expr       = Get [Name]
                | Call [Name] [Expr]
                | Mod [Name] [(Name, Expr)]
                | IntLit String
                | New Name
    deriving (Show, Ord, Eq)

-- ========== PARSER ========== --

name :: Parser Name
name = do
    c <- letter
    cs <- many alphaNum
    spaces
    return (c:cs)

classDefn :: Parser ClassDefn
classDefn = do
    string "class"
    spaces
    n <- name
    string "{"
    spaces
    ms <- many (methodDefn)
    string "}"
    spaces
    return (ClassDefn n ms)

methodDefn :: Parser MethodDefn
methodDefn = do
    string "method"
    spaces
    n <- name
    string "("
    spaces
    ps <- sepBy (name) (string "," >> spaces)
    string ")"
    spaces
    s <- statement
    return (MethodDefn n ps s)

statement :: Parser Statement
statement = (blockStatement <|> condStatement <|> xferStatement <|> assignment)

blockStatement :: Parser Statement
blockStatement = do
    string "{"
    spaces
    ss <- many (statement)
    string "}"
    spaces
    return (Block ss)

condStatement :: Parser Statement
condStatement = do
    string "if"
    spaces
    e <- expr
    s1 <- statement
    string "else"
    spaces
    s2 <- statement
    return (Conditional e s1 s2)

xferStatement :: Parser Statement
xferStatement = do
    string "pass"
    spaces
    dest <- expr
    stuff <- expr
    return (Transfer dest stuff)

assignment :: Parser Statement
assignment = do
    n <- name
    string "="
    spaces
    e <- expr
    return (Assign n e)

expr :: Parser Expr
expr = (try intLit) <|> (try newExpr) <|> refExpr

intLit :: Parser Expr
intLit = do
    d <- digit
    ds <- many digit
    spaces
    return (IntLit (d:ds))

newExpr :: Parser Expr
newExpr = do
    string "new"
    spaces
    n <- name
    return (New n)

refExpr :: Parser Expr
refExpr = do
    names <- sepBy1 (name) (string ".")
    spaces
    e <- (modExpr names <|> callExpr names <|> getExpr names)
    return e

modExpr names = do
    string "["
    spaces
    pairs <- sepBy1 (modification) (string "," >> spaces)
    string "]"
    spaces
    return (Mod names pairs)
  where
    modification = do
        n <- name
        string "="
        spaces
        e <- expr
        return (n, e)

callExpr names = do
    string "("
    spaces
    es <- sepBy (expr) (string "," >> spaces)
    string ")"
    spaces
    return (Call names es)

getExpr names = do
    return (Get names)

program :: Parser Program
program = do
    cs <- many (classDefn)
    return (Program cs)

-- ========== RUNTIME ========== --

--
-- A ContObj is what continuations pass along to each other.
-- It can be a context (environment), a single value, or a list of values.
--
data ContObj = Ctx (Map Name Value)
             | Obj Value
             | Objs [Value]

--
-- A continuation represents the remaining (sub-)computation(s) in a
-- computation.
--
data Continuation = Continuation (ContObj -> ContObj)

instance Show Continuation where
    show (Continuation _) = ""
instance Eq Continuation where
    Continuation _ == Continuation _ = False

continue (Continuation f) contObj =
    f contObj  -- straightforward enuff

--
-- A value may be:
--   an unbounded integer
--   an object (which has a class name and a set of named attributes)
--   a continuation value (which is an environment @ a continuation)
--   null
--
data Value = IntVal Integer
            | ObjVal String (Map Name Value)
            | ContVal (Map Name Value) Continuation
            | Null
    deriving (Show, Eq)

only name Nothing = error ("No such attribute " ++ name ++ " on value")
only _ (Just x) = x

getAttribute name (ObjVal c m) = only name (get name m)
getAttribute name (ContVal m k) = only name (get name m)
getAttribute name value = error ("Can't get attributes from value " ++ (show value))

-- ========== INTERPRETER ========== --

interpret prog = do
    print (evalProg prog)

---------------------------------------------------
evalProg :: Program -> Value

evalProg p =
    case (getClass "Main" p) of
        Nothing -> error "No Main class with main() method found"
        Just mainClass ->
            case (getMethod "main" mainClass) of
                Nothing -> error "No Main class with main() method found"
                Just mainMethod ->
                    let
                        final = Continuation id
                        r = callMethod p (ContVal EmptyMap final) mainMethod []
                    in
                        case r of
                            Ctx c -> Null
                            Obj o -> o

getClass name (Program []) = Nothing
getClass name (Program (c@(ClassDefn candidate _methods):rest))
    | candidate == name = Just c
    | otherwise         = getClass name (Program rest)

getMethod name (ClassDefn _ []) = Nothing
getMethod name (ClassDefn className (m@(MethodDefn candidate _args _stmt):rest))
    | candidate == name = Just m
    | otherwise         = getMethod name (ClassDefn className rest)

callMethod p other (MethodDefn name formals stmt) actuals =
    case (length actuals) - (length formals) of
        0 ->
            let
                ctx = buildContext formals actuals
                ctx' = set "other" other ctx
            in
                evalStatement p ctx' stmt (Continuation id)
        n | n > 0 ->
            error "Too many parameters passed to method"
        n | n < 0 ->
            error "Too few parameters passed to method"

buildContext [] [] = EmptyMap
buildContext (formal:formals) (actual:actuals) =
    set formal actual (buildContext formals actuals)

---------------------------------------------------
evalStatement :: Program -> (Map Name Value) -> Statement -> Continuation -> ContObj

evalStatement p ctx (Block []) cc =
    Ctx ctx
evalStatement p ctx (Block (stmt:rest)) cc =
    evalStatement p ctx stmt (Continuation $ \(Ctx ctx') ->
        evalStatement p ctx' (Block rest) cc)

evalStatement p ctx (Conditional e s1 s2) cc =
    evalExpr p ctx e (Continuation $ \(Obj value) ->
        case value of
            Null -> evalStatement p ctx s2 cc
            _    -> evalStatement p ctx s1 cc)

evalStatement p ctx (Transfer dest e) _ =
    evalExpr p ctx e (Continuation $ \(Obj value) ->
        evalExpr p ctx dest (Continuation $ \(Obj (ContVal m (Continuation k))) ->
            k $ Obj value))

evalStatement p ctx (Assign name e) cc =
    evalExpr p ctx e (Continuation $ \(Obj value) ->
        case get name ctx of
            Nothing -> continue cc $ Ctx $ set name value ctx
            Just _  -> error ("Attempted re-assignment of bound name " ++ name))

---------------------------------------------------
evalExpr :: Program -> (Map Name Value) -> Expr -> Continuation -> ContObj

evalExpr p ctx (Get ["self"]) cc =
    continue cc $ Obj $ ContVal EmptyMap cc
evalExpr p ctx (Get [name]) cc =
    case get name ctx of
        Nothing -> error ("Name " ++ name ++ " not in scope")
        Just val -> continue cc $ Obj val
evalExpr p ctx (Get (name:names)) cc =
    evalExpr p ctx (Get names) (Continuation $ \(Obj value) ->
        continue cc $ Obj $ getAttribute name value)

evalExpr p ctx (Call [localName, methodName] exprs) cc =
    evalExprs p ctx exprs [] (Continuation $ \(Objs actuals) ->
        evalExpr p ctx (Get [localName]) (Continuation $ \(Obj (ObjVal className attrs)) ->
            let
                Just klass = getClass className p
                Just method = getMethod methodName klass
                newOther = ContVal ctx $ cc
            in
                callMethod p newOther method actuals))

evalExpr p ctx (Mod names pairs) cc =
    continue cc $ Obj Null

evalExpr p ctx (IntLit i) cc =
    continue cc $ Obj $ IntVal (evalIntLit i)

evalExpr p ctx (New className) cc =
    continue cc $ Obj $ ObjVal className EmptyMap

---------------------------------------------------

evalExprs p ctx [] vals cc =
    continue cc $ Objs vals

evalExprs p ctx (expr:exprs) vals cc =
    evalExpr p ctx expr (Continuation $ \(Obj val) ->
        evalExprs p ctx exprs (val:vals) cc)

---------------------------------------------------

evalIntLit [] = 0
evalIntLit (d:ds) =
    (evalIntLit ds) * 10 + (digitVal d)

digitVal '0' = 0
digitVal '1' = 1
digitVal '2' = 2
digitVal '3' = 3
digitVal '4' = 4
digitVal '5' = 5
digitVal '6' = 6
digitVal '7' = 7
digitVal '8' = 8
digitVal '9' = 9

-- ========== DRIVER ========== --

pa x = do
  parseTest program x

runDeturgenchry programText =
    case parse (program) "" programText of
        Left err -> error (show err)
        Right prog -> show (evalProg prog)