[Haskell-cafe] Minim interpreter

Chaddaï Fouché chaddai.fouche at gmail.com
Mon Jul 23 19:47:43 EDT 2007


I wrote such an interpreter though the code is quite ugly due to my
lack of experience in the field as well as with Haskell... It took me
the better part of two hour but mainly because I didn't use Parsec
before this. I would of course be happy of any suggestion to amend it
but a plain rewriting might be best... (even by me ;-) )
There are probably some bugs (in part due to the fuzzy definition of
the language semantics and real syntax).
Here is the beast :
module Minim (the real work is done here)
##############################################
module Minim (Statement (..), Test (..), Program (..), Expr (..), eval) where
import qualified Data.Map as M
import Data.Char

data Statement =
    Assign String Expr
    | Inc String
    | Dec String
    | Cond Test Statement Statement
    | Goto String
    | Print Expr
    | Nl
    | Input String
    deriving (Show)

data Test =
    Le Expr Expr
    | Ge Expr Expr
    | Eq Expr Expr
    | And Test Test
    | Or Test Test
    | Not Test
    deriving (Show)

data Expr =
    Str String
    | Number Int
    | EVar String
    deriving (Eq, Ord)

instance Show Expr where
    show (Str s) = s
    show (Number i) = show i
    show (EVar s) = "Variable : " ++ s

newtype Program = Program ([Statement],[(String,[Statement])])
    deriving (Show)
eval :: Program -> IO ()
eval (Program (xs, tags)) =
    evalS xs tags M.empty

evalS :: [Statement] -> [(String, [Statement])] -> M.Map String Expr -> IO ()
evalS (s0:ss) tags context =
    s0 `seq`
    case s0 of
      Assign str expr -> evalS ss tags
                         $ M.insert str (evalE expr context) context
      Inc str -> evalS ss tags
                 $ M.adjust inc_expr str context
                     where
                       inc_expr (Number i) = Number $ i + 1
                       inc_expr _ = error $ "You can't increment "
                                    ++ str ++ ", it isn't numeric.\n"
      Dec str -> evalS ss tags
                 $ M.adjust dec_expr str context
                     where
                       dec_expr (Number i) = Number $ i - 1
                       dec_expr _ = error $ "You can't increment "
                                    ++ str ++ ", it isn't numeric.\n"
      Cond test s1 s2 -> if evalT test context
                         then evalS (s1:ss) tags context
                         else evalS (s2:ss) tags context
      Goto str -> maybe
                  (error $ "No such tag : " ++ str)
                  (\nss -> evalS nss tags context)
                  $ lookup str tags
      Print expr -> do putStr (show $ evalE expr context)
                       evalS ss tags context
      Nl -> do putStrLn ""
               evalS ss tags context
      Input str -> do input <- getLine
                      let expr = if (not $ null input) && all isDigit input
                                 then Number $ read input
                                 else Str input
                      evalS ss tags $ M.insert str expr context
evalS [] _ _ = return ()

evalE :: Expr -> M.Map String Expr -> Expr
evalE (EVar str) context =
    maybe
    (error $ "There's no such variable : " ++ str)
    id
    $ M.lookup str context
evalE e _ = e

evalT :: Test -> M.Map String Expr -> Bool
evalT t context =
    case t of
      Eq e1 e2 -> evalE e1 context == evalE e2 context
      Le e1 e2 -> evalE e1 context < evalE e2 context
      Ge e1 e2 -> evalE e1 context > evalE e2 context
      And t1 t2 -> evalT t1 context && evalT t2 context
      Or t1 t2 -> evalT t1 context || evalT t2 context
      Not t1 -> not $ evalT t1 context
##############################################
module MinimParser
##############################################
module MinimParser (parseFile) where
import Minim
import Text.ParserCombinators.Parsec hiding (spaces, parseTest)
import Text.ParserCombinators.Parsec.Expr
import Text.ParserCombinators.Parsec.Token hiding (symbol)
import Control.Monad

spaces :: Parser ()
spaces = skipMany1 $ char ' '

symbol :: Parser String
symbol = many1 letter

litVar :: Parser Expr
litVar = liftM EVar symbol

litString :: Parser Expr
litString = do char '"'
               s <- many (noneOf "\"")
               char '"'
               return $ Str s

litNumber :: Parser Expr
litNumber = return . Number . read =<< many digit

parseExpr :: Parser Expr
parseExpr = litVar <|> litString <|> litNumber

opTable = [ [Infix (string "and" >> return And) AssocNone,
             Infix (string "or" >> return Or) AssocNone],
            [Prefix (string "not" >> return Not)]
          ]

parseTest :: Parser Test
parseTest = buildExpressionParser opTable simpleTest

simpleTest :: Parser Test
simpleTest =
    (do char '('
        spaces
        test <- parseTest
        spaces
        char ')'
        return test
    ) <|>
    do e1 <- parseExpr
       spaces
       op <- oneOf "=<>"
       spaces
       e2 <- parseExpr
       return $ case op of
                  '=' -> Eq e1 e2
                  '<' -> Le e1 e2
                  '>' -> Ge e1 e2

printS :: Parser Statement
printS =
    do
      string "print"
      spaces
      expr <- parseExpr
      return $ Print expr

inputS :: Parser Statement
inputS =
    do
      string "input"
      spaces
      var <- symbol
      return $ Input var

assignS :: Parser Statement
assignS =
    do
      var <- symbol
      spaces
      string "is"
      spaces
      expr <- parseExpr
      return $ Assign var expr

gotoS :: Parser Statement
gotoS = liftM Goto $ string "goto" >> spaces >> symbol

incS :: Parser Statement
incS = liftM Inc $ string "++" >> spaces >> symbol

decS :: Parser Statement
decS = liftM Dec $ string "--" >> spaces >> symbol

condS :: Parser Statement
condS =
    do
      string "if"
      spaces
      test <- parseTest
      spaces
      string "then"
      spaces
      s1 <- parseStatement
      spaces
      string "else"
      spaces
      s2 <- parseStatement
      return $ Cond test s1 s2

parseStatement :: Parser Statement
parseStatement =
    incS <|>
    decS <|>
    printS <|>
    try condS <|>
    inputS <|>
    gotoS <|>
    (string "nl" >> return Nl) <|>
    assignS

parseProgram :: Parser Program
parseProgram =
    try (do
          stat <- parseStatement
          newline
          program <- parseProgram
          case program of
            Program (stats, tags) -> return $ Program (stat:stats, tags)
        ) <|>
    (do tag <- symbol
        newline
        program <- parseProgram
        case program of
          Program (stats, tags) -> return $ Program (stats, (tag,stats):tags)
    ) <|>
    (eof >> ( return $ Program ([], []) ))

parseFile          :: String -> IO Program
parseFile fileName =
    do
      input <- readFile fileName
      case (parse parseProgram fileName input) of
        Left err -> error $ show err
        Right p -> return p
##############################################
main module (nothing there of course)
##############################################
module Main where
import MinimParser
import Minim (eval)
import System
import System.IO

main :: IO ()
main = do
  hSetBuffering stdout NoBuffering
  arg <- getArgs
  program <- parseFile $ arg!!0
  eval program
##############################################

-- 
Jedaï


More information about the Haskell-Cafe mailing list