diff --git a/src/BC/Eval.hs b/src/BC/Eval.hs index b84c414..738db66 100644 --- a/src/BC/Eval.hs +++ b/src/BC/Eval.hs @@ -20,7 +20,7 @@ evalAll state (x:xs) = eval :: State -> [Value] -> (Value, State) eval state [x@(BNum _)] = (x, state) -eval state [(BDef (BSym sym) expr)] = +eval state [BDef (BSym sym) expr] = let (val, newstate) = eval state expr in (val, M.insert sym val newstate) eval state [x@(BFun sym _ _)] = (BBool True, M.insert sym x state) @@ -35,7 +35,7 @@ eval state [x@(BWhile cond body)] = (val, retstate) = eval newstate [x] in if truthy val then (val, retstate) else (bodyval, retstate) else (BBool False, whilestate) -eval state [(BIf cond body alt)] = +eval state [BIf cond body alt] = let (evald, ifstate) = eval state cond in if truthy evald @@ -43,13 +43,13 @@ eval state [(BIf cond body alt)] = else case alt of Just vals -> evalAll ifstate vals Nothing -> (BBool False, ifstate) -eval state [(BSym x)] = +eval state [BSym x] = case M.lookup x state of Just val -> (val, state) Nothing -> (BErr (x ++ " is undefined"), state) -eval state [(BCall (BSym name) args)] = +eval state [BCall (BSym name) args] = case M.lookup name state of - Just val@(BFun _ _ _) -> funCall state val args + Just val@BFun{} -> funCall state val args Nothing -> (BErr ("function " ++ name ++ " is undefined"), state) _ -> (BErr (name ++ " is not a function"), state) eval state [] = (BSym "", state) @@ -59,13 +59,10 @@ eval state l = (treeEval state l [] [], state) treeEval :: State -> [Value] -> [Value] -> [Value] -> Value treeEval _ [] [] (num:_) = num treeEval state [] ops nums = handleOp state [] ops nums -treeEval state (x@(BIf _ _ _):xy) ops nums = - let (val, newstate) = eval state [x] - in treeEval newstate (val:xy) ops nums treeEval state (x@(BNum _):xy) ops nums = treeEval state xy ops (x:nums) -treeEval state ((BBool x):xy) ops nums = +treeEval state (BBool x:xy) ops nums = treeEval state xy ops ((BNum $ BInt $ if x then 1 else 0):nums) -treeEval state expr@(x@(BSym sym):xy) ops@((BSym op):_) nums = +treeEval state expr@(x@(BSym sym):xy) ops@(BSym op:_) nums = case M.lookup sym state of Just val -> treeEval state xy ops (val:nums) Nothing -> @@ -81,21 +78,21 @@ treeEval state (x@(BSym sym):xy) [] nums = else BErr (sym ++ " is undefined") treeEval state (x:xy) ops vals = let (val, nstate) = eval state [x] - in treeEval nstate xy ops (val:vals) + in treeEval nstate (val:xy) ops vals handleOp :: State -> [Value] -> [Value] -> [Value] -> Value -handleOp state expr ((BSym op):ops) ((BNum op2):((BNum op1):nums)) = - treeEval state expr ops (((findOp op) op1 op2):nums) -handleOp _ expr ((BSym op):ops) x = BErr ("Not enough arguments to operation " ++ op) +handleOp state expr (BSym op:ops) (BNum op2:(BNum op1:nums)) = + treeEval state expr ops (findOp op op1 op2:nums) +handleOp _ expr (BSym op:ops) x = BErr ("Not enough arguments to operation " ++ op) findOp x = case binOp x of - Just op -> \a -> \b -> (BNum $ op a b) + Just op -> \a b -> (BNum $ op a b) Nothing -> case logicalOp x of - Just lop -> \a -> \b -> (BBool $ lop a b) - Nothing -> \a -> \b -> (BNum $ BInt 0) + Just lop -> \a b -> BBool $ lop a b + Nothing -> \a b -> BNum $ BInt 0 logicalOp :: String -> Maybe (Number -> Number -> Bool) diff --git a/src/BC/Parse.hs b/src/BC/Parse.hs index c9f5fdf..726c84b 100644 --- a/src/BC/Parse.hs +++ b/src/BC/Parse.hs @@ -22,7 +22,7 @@ commasep parser = P.sepBy parser sep where sep = do _ <- optspace str <- P.string "," _ <- optspace - return $ str + return str float :: P.Parser Value @@ -70,26 +70,30 @@ block = do body <- P.sepBy parser newline _ <- optspace _ <- P.string "}" - return $ body + return body where newline = do _ <- P.many (P.string " " P.<|> P.string "\t") _ <- P.string "\n" P.many (P.string " " P.<|> P.string "\t") --- I obviously can't parsec -parseIf :: P.Parser Value -parseIf = do - _ <- P.string "if" +headAndBlock parser = do _ <- optspace _ <- P.string "(" _ <- optspace - cond <- parser + args <- parser _ <- optspace _ <- P.string ")" _ <- optspace body <- block _ <- optspace + return (args, body) + + +parseIf :: P.Parser Value +parseIf = do + _ <- P.string "if" + (cond, body) <- headAndBlock parser alt <- P.optionMaybe (P.string "else") case alt of Just _ -> do @@ -102,15 +106,7 @@ parseIf = do while :: P.Parser Value while = do _ <- P.string "while" - _ <- optspace - _ <- P.string "(" - _ <- optspace - cond <- parser - _ <- optspace - _ <- P.string ")" - _ <- optspace - body <- block - _ <- optspace + (cond, body) <- headAndBlock parser return $ BWhile cond body @@ -129,15 +125,7 @@ fun = do _ <- P.string "define" _ <- P.spaces name <- P.many1 $ P.letter P.<|> symchar - _ <- optspace - _ <- P.string "(" - _ <- optspace - args <- commasep (P.many1 $ P.letter P.<|> symchar) - _ <- optspace - _ <- P.string ")" - _ <- optspace - body <- block - _ <- optspace + (args, body) <- headAndBlock (commasep (P.many1 $ P.letter P.<|> symchar)) return $ BFun name args body @@ -165,11 +153,11 @@ expr = P.try bool parser :: P.Parser [Value] -parser = (P.sepBy expr (P.string " " P.<|> P.string "\t")) +parser = P.sepBy expr (P.string " " P.<|> P.string "\t") outerparser :: P.Parser [Value] -outerparser = (P.sepBy expr P.spaces) +outerparser = P.sepBy expr P.spaces parse :: String -> [Value] diff --git a/src/BC/Prompt.hs b/src/BC/Prompt.hs index 2adfb7b..6826934 100644 --- a/src/BC/Prompt.hs +++ b/src/BC/Prompt.hs @@ -1,5 +1,6 @@ module BC.Prompt (startPrompt) where +import Control.Monad (unless) import Data.Char import System.IO import System.Posix.Signals @@ -10,6 +11,10 @@ import BC.Parse import BC.State import BC.Types + +data Prompt = PState Int [String] + + printHeader :: IO () printHeader = do putStrLn $ "bc (better calculator) version " ++ versionStr @@ -20,10 +25,10 @@ printHeader = do output :: State -> String -> IO State output state out = let res = parse out - in if length res == 1 && isErr (res !! 0) + in if length res == 1 && isErr (head res) then do - putStrLn $ show (res !! 0) - return $ state + print (head res) + return state else let (ret, newstate) = eval state res in do putStrLn $ returnStr ++ show ret @@ -33,21 +38,17 @@ output state out = printStatus :: State -> String -> IO () printStatus state str = let res = parse str - in if length res == 1 && isErr (res !! 0) - then return () - else + in unless (length res == 1 && isErr (head res)) $ let (evald, _) = eval state res out = show evald - in if isErr evald || length out == 0 - then return () - else let str = " \x1b[33m=> " ++ trunc out ++ "\x1b[0m" - in putStr (str ++ repeat '\b' (length str - 9)) - where repeat str 0 = "" - repeat str n = (str:repeat str (n-1)) - trunc s = + in unless (isErr evald || null out) $ + let str = " \x1b[33m=> " ++ trunc out ++ "\x1b[0m" + in do putStr str + moveCursor (length str - 9) + where trunc s = let tr = truncLen s in if contains tr '\n' - then takeWhile (\x -> x /= '\n') tr ++ "..." + then takeWhile (/= '\n') tr ++ "..." else tr truncLen s = if length s > 20 then take 20 s ++ "..." else s @@ -56,53 +57,84 @@ cleanPrompt :: IO () cleanPrompt = putStr "\x1b[2K\r" +moveCursor :: Int -> IO () +moveCursor n = putStr (repeatB n) + where repeatB 0 = "" + repeatB n = '\b':repeatB (n-1) + + -- TODO: Stub -readSpecialKey :: IO () -readSpecialKey = do +readSpecialKey :: Int -> String -> Prompt -> IO (Int, String, Prompt) +readSpecialKey pos acc pstate@(PState hpos history) = do c <- getChar c2 <- getChar - return () + if c == '[' + then + case c2 of + 'A' -> + if hpos < (length history - 1) + then + let nacc = history !! (hpos+1) + in return (length nacc, nacc, PState (hpos+1) history) + else return (pos, acc, pstate) + 'B' -> + if hpos > -1 + then if hpos == 0 + then return (0, "", PState (-1) history) + else + let nacc = history !! (hpos-1) + in return (length nacc, nacc, PState (hpos-1) history) + else return (pos, acc, pstate) + 'C' -> return (pos+1, acc, pstate) + 'D' -> return (pos-1, acc, pstate) + _ -> return (pos, acc, pstate) + else return (pos, acc, pstate) -readline :: State -> IO (Maybe String) -readline state = read' "" - where read' acc = do +readline :: State -> Prompt -> IO (Maybe String) +readline state = read' "" 0 + where read' acc pos pstate = do cleanPrompt putStr promptStr putStr acc printStatus state acc + moveCursor (length acc - pos) c <- getChar case c of '\EOT' -> return Nothing - '\n' -> return (Just acc) + '\n' -> return (Just acc) '\DEL' -> - if length acc > 0 - then do - putStr "\b \b" - read' (init acc) + if null acc || pos == 0 + then read' acc pos pstate else do - read' acc + putStr "\b \b" + read' (take (pos-1) acc ++ drop pos acc) (pos-1) pstate '\x1b' -> do - readSpecialKey - read' acc - c -> + (pos, nacc, newpstate) <- readSpecialKey pos acc pstate + read' nacc (clamp pos 0 (length acc)) newpstate + c -> if isPrint c then do putStr [c] - read' (acc ++ [c]) - else read' acc + read' (acc ++ [c]) (pos+1) pstate + else read' acc pos pstate + clamp n min max + | n < min = min + | n > max = max + | otherwise = n -prompt :: State -> IO () -prompt state = do - input <- readline state +prompt :: State -> Prompt -> IO () +prompt state pstate@(PState _ history) = do + input <- readline state pstate case input of Nothing -> putStrLn "\nBye!" Just "quit" -> putStrLn "\nBye!" Just str -> do putStrLn "" newstate <- output state str - prompt newstate + let newpstate = PState (-1) (str:history) + prompt newstate newpstate installHandlers :: IO () @@ -117,4 +149,4 @@ startPrompt :: IO () startPrompt = do printHeader installHandlers - prompt newState + prompt newState (PState (-1) []) diff --git a/src/BC/Types.hs b/src/BC/Types.hs index 1276b8a..f318001 100644 --- a/src/BC/Types.hs +++ b/src/BC/Types.hs @@ -54,7 +54,7 @@ instance Floating Number where (BInt x) ** (BFloat y) = BFloat $ fromIntegral x ** y (BInt x) ** (BInt y) = BInt $ x ^ y logBase x y = log y / log x - sqrt x = x ** (BFloat 0.5) + sqrt x = x ** BFloat 0.5 tan x = sin x / cos x tanh x = sinh x / cosh x @@ -71,7 +71,7 @@ instance Num Number where abs (BFloat x) = BFloat $ abs x signum (BInt x) = BInt $ signum x signum (BFloat x) = BFloat $ signum x - fromInteger x = BInt x + fromInteger = BInt negate (BInt x) = BInt $ negate x negate (BFloat x) = BFloat $ negate x @@ -100,7 +100,7 @@ precedence "!=" = 2 contains :: Eq a => [a] -> a -> Bool contains [] _ = False -contains (x:xy) y = if x == y then True else contains xy y +contains (x:xy) y = (x == y) || contains xy y operators = ["<=", ">=", "==", "!=", "<", ">", "||", "&&", "^", "*", "/", "-", "+", "%"] diff --git a/src/Main.hs b/src/Main.hs index f71dfd1..18e2e2e 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -12,7 +12,5 @@ import BC.State main :: IO () main = do tty <- queryTerminal stdInput - if tty - then startPrompt - else getContents >>= putStrLn . show . evalOne . parse + if tty then startPrompt else getContents >>= print . evalOne . parse where evalOne inp = let (val, _) = eval newState inp in val