エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

計算機、のパーサ、その3

どうにかここまで整理。

module Main (main) where

import Text.ParserCombinators.Parsec

-- 数式の要素(四則演算の演算子と整数値)
data Elem = Plus | Minus | Times | Divide | Value Integer

-- 後置法(逆ポーランド)で格納された要素を計算する関数
calc :: [Elem] -> Integer
calc es = calc' es []
  where
    calc' :: [Elem] -> [Integer] -> Integer
    calc' []             [n]      = n
    calc' ((Value n):es) ns       = calc' es (n:ns)
    calc' (Plus:es)      (n:m:ns) = calc' es ((m + n):ns)
    calc' (Minus:es)     (n:m:ns) = calc' es ((m - n):ns)
    calc' (Times:es)     (n:m:ns) = calc' es ((m * n):ns)
    calc' (Divide:es)    (n:m:ns) = calc' es ((m `div` n):ns)

-- 文字列(中置表記の数式)を解析して、後置法で要素をリストに格納するパーサ
-- 至って素朴な実装
expression :: Parser [Elem]
expression = do { many space; t <- term; ts <- many $ try terms; many space; return $ t ++ (concat ts) }
  where
    terms   = do { many space; op <- plus <|> minus; many space; t <- term; return (t ++ op) }
    term    = do { f <- factor; fs <- many $ try factors; return $ f ++ (concat fs) }
    factors = do { many space; op <- times <|> divide; many space; f <- factor; return (f ++ op) }
    factor  = do { char '('; e <- expression;  char ')';  return e } <|> value
    value   = do { v <- many digit; return [Value $ read v] }
    plus    = do { char '+'; return [Plus]   }
    minus   = do { char '-'; return [Minus]  }
    times   = do { char '*'; return [Times]  }
    divide  = do { char '/'; return [Divide] }

-- 一行ずつ文字列(数式)を読み込み、計算する
main = do
  exps <- getContents
  mapM_ eval $ lines exps
  where
    eval exp = case (parse expression "" exp) of
                 Right exp -> print $ calc exp
                 Left  err -> print err