hiccup-0.40: TclLib/ControlProcs.hs
{-# LANGUAGE BangPatterns #-}
module TclLib.ControlProcs (controlProcs) where
import Common
import Core
import Control.Monad.Error
import qualified TclObj as T
import TclObj ((.==))
import Util
controlProcs = makeCmdMap $
[("while", procWhile), ("if", procIf), ("for", procFor),
("foreach", procForEach), ("switch", procSwitch)]
procIf (cond:yes:rest) = do
condVal <- doCond cond
if condVal then evalTcl yes
else case rest of
[] -> ret
[s,blk] -> if (T.asBStr s) == (pack "else") then evalTcl blk else tclErr "Invalid If"
(s:r) -> if s .== "elseif" then procIf r else tclErr "Invalid If"
procIf _ = argErr "if"
procWhile [cond,body] = loop `catchError` herr
where herr EBreak = ret
herr EContinue = loop `catchError` herr
herr x = throwError x
loop = do condVal <- doCond cond
if condVal then evalTcl body >> loop else ret
procWhile _ = argErr "while"
eatErr f v = (f >> ret) `catchError` \e -> if v == e then ret else throwError e
{-# INLINE eatErr #-}
procFor args = case args of
[start,test,next,body] -> do evalTcl start
loop test next body `eatErr` EBreak
ret
_ -> argErr "for"
where loop test next body = do
c <- doCond test
if c then (evalTcl body `eatErr` EContinue) >> evalTcl next >> loop test next body
else ret
procForEach args =
case args of
[vl_,l_,block] -> do
vl <- T.asList vl_
l <- T.asList l_
let vllen = length vl
if vllen == 1
then allowBreak (mapM_ (doSingl (head vl) block) l)
else do
let chunks = l `chunkBy` vllen
allowBreak (mapM_ (doChunk vl block) chunks)
ret
_ -> argErr "foreach"
where allowBreak f = f `eatErr` EBreak
doChunk vl block items = do zipWithM_ (\a b -> varSet (T.asBStr a) b) vl (items ++ repeat T.empty)
evalTcl block `eatErr` EContinue
doSingl v block i = do varSet (T.asBStr v) i
evalTcl block `eatErr` EContinue
chunkBy lst n = let (a,r) = splitAt n lst
in a : (if null r then [] else r `chunkBy` n)
procSwitch args = case args of
[str,pairlst] -> T.asList pairlst >>= doSwitch (exacter str)
[opt,str,pairlst] -> if opt .== "--" || opt .== "-exact"
then T.asList pairlst >>= doSwitch (exacter str)
else if opt .== "-glob"
then T.asList pairlst >>= doSwitch (globber str)
else tclErr $ "switch: bad option " ++ show opt
_ -> argErr "switch"
where globber s = let bs = T.asBStr s in \o -> globMatch (T.asBStr o) bs
exacter s = let bs = T.asBStr s in \o -> exactMatch (T.asBStr o) bs
doSwitch matchP lst = do
pl <- toPairs lst
switcher pl False
where switcher [(k,v)] useNext = do
if matchP k || useNext || k .== "default"
then if v .== "-" then tclErr $ "no body specified for pattern " ++ show k
else evalTcl v
else ret
switcher ((k,v):xs) useNext = do
if matchP k || useNext
then if v .== "-" then switcher xs True else evalTcl v
else switcher xs False
switcher [] _ = tclErr "impossible condition in \"switch\""
toPairs [a,b] = return [(a,b)]
toPairs (a:b:r) = liftM ((a,b):) (toPairs r)
toPairs _ = tclErr "list must have even number of elements"