phorth---(その1.7)main関数---簡単な対話型シェル

デバグ

オペレータsubの定義が間違ってました.

-- procExp i (Invoke "sub") = procStack i (\(x1:x2:xs) -> (x1-x2):xs)
procExp i (Invoke "sub") = procStack i (\(x1:x2:xs) -> (x2-x1):xs)

スタックの順番を間違えてました.

簡単な対話型シェル(REPL: Read-Eval-Print Loop)

入出力のインタフェースとして,簡単な対話型シェルを作っておきます.Gaucheの対話型シェルがgoshという名前なのを真似して,phorthのシェルをphoshと名づけました.

initInterp = Interp{ stack=[], dict=Map.empty}

main :: IO ()
main = repl initInterp
repl :: Interp -> IO ()
repl i = do putStr ("phosh<"++(show $ length $ stack i)++"> ") 
            hFlush stdout
            src <- getLine `catch` (\e -> return ":q")
            case words src of
                (":q": _) -> return ()
                otherwise -> repl' i src 

repl'::Interp -> String -> IO ()
repl' i src = case (parse phorthProg "" src) of
                  Left err  -> do putStr "parse Error ar"
                                  print err
                  Right ast -> do i' <- interp i ast
                                  repl i'

main関数として,空っぽのインタプリータが引数として与えられた対話型シェルreplを定義します.repl本体はみての通り,プロンプトを表示して,入力を一行ずつ読み込んで処理して,また自分自身を読み込んで処理するループです.ただし,行頭に``:q''がきたときには処理そのものを終わらせるため,phorth本体の前にシェルそのものへの命令がないかチェックしています.

このシェルには仕様上の大きな問題があります.それは「一行単位」だということそのものです.例えば,オペレータを新規に定義するために長いコードが必要な場合でも改行ができないのです.改行してしまうとその段階で処理されてしまうので,パーサエラーとなります.

これを回避するには,harrorthと同様に,``{'',``}''そのものを「インタープリータの状態を変える」オペレータとして,インタープリータそのものに状態(入力の途中か否か)を持たせる必要があると思いますが,厄介そうなので例によって保留です.

また,repl関数もやはり引数としてInterp型のインタープリータを持たざると得ません.同じ物を延々と引数としてもつのは状態を持って歩いてるのと同じで,これは隠蔽すべき対象です.

その2では(シェルの問題は保留して)この隠蔽を考えます.

それにしても・・・案外簡単に,いい加減とはいえ,シェルつきRPN電卓ができました.ここまででコードは181行ので一個のファイルだけです.

そろそろファイル分割を考えたほうがいいのかもしれません.

長いけどここまでのソースコード全文晒し

import System.IO
import Text.ParserCombinators.Parsec
import qualified Data.Map as Map

-- AST
type AST = [Exp]

data Exp =   Push Literal
           | Invoke Word 
           | Name Word
           | Block AST
           deriving (Show, Eq)
type Literal = Integer
type Word = String


-- Parser
phorthProg::Parser AST
phorthProg = do ast <- phorth
                eof
                return ast

phorth::Parser AST
phorth = do skipMany space
            sepEndBy1 phorthExp (skipMany space)

phorthExp::Parser Exp
phorthExp = literal <|> word <|> name <|> block 

literal::Parser Exp
literal = do sign <- option ' ' (char '-')
             num  <- (many1 digit)
             return (Push (read $ sign:num))

opname::Parser String
opname = do first  <- letter 
            others <- many (letter <|> digit)
            return $ first:others
         <|>
            string "=="

word::Parser Exp
word = opname >>= \x ->  return $ Invoke x


name::Parser Exp
name = char '/' >> opname >>= \x ->  return $ Name x

block::Parser Exp
block = do char '{'
           ast <- phorth
           char '}'
           return $ Block ast

-- Evaluator

type Stack = [Stackable]
data Stackable =  Pushed Literal
                | Opname Word
                | Code AST
                deriving Eq

instance Num Stackable where
    (+) (Pushed a) (Pushed b) = Pushed (a+b)
    (*) (Pushed a) (Pushed b) = Pushed (a*b)
    abs (Pushed a)            = Pushed $ abs a
    signum (Pushed a)         = Pushed $ signum a
    fromInteger a             = Pushed  a
    negate (Pushed a)         = Pushed $ negate a

instance Integral Stackable where
   quotRem   (Pushed a) (Pushed b) = (Pushed $ quot a b, Pushed $ rem a b)
   toInteger (Pushed a)            = a

instance Enum Stackable where
   succ (Pushed a)     = Pushed $ succ a
   pred (Pushed a)     = Pushed $ pred a
   toEnum a            = Pushed $ toInteger a
   fromEnum (Pushed a) = fromInteger a
   enumFrom (Pushed a) = map Pushed $ enumFrom a
   enumFromThen (Pushed a) (Pushed b) = map Pushed $ enumFromThen a b
   enumFromTo   (Pushed a) (Pushed b) = map Pushed $ enumFromTo a b
   enumFromThenTo (Pushed a) (Pushed b) (Pushed c) = map Pushed $ enumFromThenTo a b c

instance Real Stackable where
   toRational (Pushed a) = toRational a

instance Ord Stackable where
   compare (Pushed a) (Pushed b) = compare a b

instance Show Stackable where
    show (Pushed a) = show a
    show (Opname a) = "Opname " ++ show a
    show (Code a)   = "Code " ++ show a

type Dict  = Map.Map Word AST

data Interp = Interp{  stack::Stack
                      ,dict ::Dict
                    } deriving (Show) -- interpreter

interp:: Interp -> AST -> IO Interp
interp i (x:xs) = do i' <- procExp i x
                     interp i' xs
interp i [] = return i

procStack::Interp->(Stack->Stack)->IO Interp
procStack i f = return i{stack = f $ stack $ i}

procExp:: Interp -> Exp -> IO Interp
procExp i (Push lit)  = procStack i $(:) (Pushed lit)
procExp i (Name name) = procStack i $(:) (Opname name)
procExp i (Block ast) = procStack i $(:) (Code ast)
procExp i (Invoke "pop") = procStack i $ drop 1
procExp i@Interp {stack=(Code code):(Opname name):xs, dict = dict} (Invoke "def") 
    = return  i{stack=xs,dict = Map.insert name code dict }

procExp i (Invoke "add") = procStack i (\ (x1:x2:xs) -> (x1+x2):xs) 
procExp i (Invoke "sub") = procStack i (\(x1:x2:xs) -> (x2-x1):xs) 
procExp i (Invoke "mul") = procStack i (\(x1:x2:xs) -> (x1*x2):xs) 
procExp i (Invoke "mod") = procStack i (\(x1:x2:xs) -> ((abs x2) `mod` (abs x1)):xs) 
procExp i (Invoke "idiv")
    = procStack i (\(x1:x2:xs) ->  (signum x1)*(signum x2) *((abs x2) `div` (abs x1)):xs)
procExp i (Invoke "abs") = procStack i (\(x:xs) -> (abs x):xs) 
procExp i (Invoke "neg") = procStack i (\(x:xs) -> (negate x):xs) 

-- procExp i@Interp {stack=x:xs} (Invoke "dup") = return i{stack=x:x:xs} 
procExp i (Invoke "dup")   = procStack i (\ (x:xs)->(x:x:xs)) 
procExp i (Invoke "exch")  = procStack i (\ (x1:x2:xs)-> (x2:x1:xs)) 
procExp i (Invoke "clear") = procStack i (\ _ -> []) 
procExp i (Invoke "copy")  
    = procStack i (\ ((Pushed n):xs) -> (take (fromInteger n) xs)++xs )
procExp i (Invoke "count") = procStack i (\xs ->(Pushed $ toInteger $ length xs):xs )


procExp i@Interp {stack=xs} (Invoke "==")
    = mapM_ print xs >> return i{stack=[]} 
procExp i@Interp {stack=xs} (Invoke "pstack")
    = mapM_ print xs >> return i
procExp i (Invoke "roll")
    = procStack i (\((Pushed k):(Pushed n):xs)->roll (fromInteger n) (fromInteger k) xs)

procExp i@Interp{dict=dict} (Invoke name) = interp i (dict Map.! name)



roll::Int->Int->[a]->[a]
roll n k xs  = case k of
          k | k < 0           -> roll' n (abs k) xs
            | n < k           -> roll n (k `mod` n) xs
            | 1 < k && k <= n -> roll n (k-1) $ roll n 1 xs
            | k == 1          -> (drop 1 $ take n xs) ++ [head xs] ++ (drop n xs)
            | k == 0          -> xs
    where roll' n k xs = case k of
              k | n < k           -> roll' n (k `mod` n) xs
                | 1 < k && k <= n -> roll' n (k-1) $ roll' n 1 xs
                | k == 1          -> [last $ take n xs] ++ (take (n-1) xs) ++ (drop n xs)


{--
main
--}

initInterp = Interp{ stack=[], dict=Map.empty}

main :: IO ()
main = repl initInterp
repl :: Interp -> IO ()
repl i = do putStr ("phosh<"++(show $ length $ stack i)++"> ") 
            hFlush stdout
            src <- getLine `catch` (\e -> return ":q")
            case words src of
                (":q": _) -> return ()
                otherwise -> repl' i src 

repl'::Interp -> String -> IO ()
repl' i src = case (parse phorthProg "" src) of
                  Left err  -> do putStr "parse Error ar"
                                  print err
                  Right ast -> do i' <- interp i ast
                                  repl i'