packages feed

lambdacube-2008.12.24: Cube.hs

-- Simple interface to lambda cube type checker.
import System.Environment(getArgs)
import Data.Char(isSpace)
import Data.List(sortBy)
import Data.Function(on)
import REPL
import CubeExpr

main :: IO ()
main = do
    args <- getArgs
    case args of
	["-?"] -> usage
	["-help"] -> usage
	["--help"] -> usage
        [] -> interactive
	_  -> batch args

usage :: IO ()
usage = putStr "\
\Usage: cube           -- start in interactive mode\n\
\       cube files     -- concatenate files, type check, evaluate\n\
\       cube - files   -- insert let&in, concatenate files, type check, evaluate\n\
\"

batch :: [FilePath] -> IO ()
batch ("-":names) = mapM readFile names >>= batch' . addIn . ("let " :)
  where addIn ss = init ss ++ [" in "] ++ [last ss]
batch names = mapM readFile names >>= batch'

batch' :: [String] -> IO ()
batch' files = do
    let expr = unRight "Parse error: " $ unreads $ concat files
        typ = unRight "Type error: " $ typeCheck expr
    putStrLn $ "Type:\n" ++ show typ
    putStrLn $ "Value:\n" ++ show (nf expr)

unRight :: String -> Either String a -> a
unRight msg (Left msg') = error $ msg ++ msg'
unRight _ (Right a) = a

unreads :: (Show a, Read a) => String -> Either String a
unreads s =
    let xss = reads s
    in  case filter (null . snd) xss of
        (x, _) : _ -> Right x  -- pick first when ambiguous.
        _ -> 
	    -- Find the shortest suffix and report error there.
	    let rest = head $ sortBy (compare `on` length) $ map snd xss ++ [s]
	        ls = length (filter (=='\n') s)
		lrest = length (filter (=='\n') rest)
	    in  Left $ "line " ++ show (ls - lrest) ++ ": " ++ rest

data State = State { defs :: String, skipLam :: Bool } deriving (Show)

interactive :: IO ()
interactive = repl $ REPL { repl_init = cinit, repl_eval = ceval, repl_exit = cexit }
  where cinit = do
          putStrLn "Welcome to the Cube."
	  putStrLn "Use :help to get help."
	  return ("Cube> ", State "" False)
	cexit _ = putStrLn "Bye."
	ceval s line = do
	    let rest = dropWhile isSpace $ dropWhile (not . isSpace) line
	        load = readFile rest >>= addFile s
		quit = return (True, s)
		help = do putStrLn helpMsg; return (False, s)
	    case words line of
	      [] -> return (False, s)
	      ":h" : _ -> help
	      ":help" : _ -> help
	      ":q" : _ -> quit
	      ":quit" : _ -> quit
	      ":let" : _ -> addFile s (rest ++ ";\n")
	      ":l" : _ -> load
	      ":load" : _ -> load
	      ":defs" : _ -> do putStrLn (defs s); return (False, s)
	      ":skip" : _ -> return (False, s { skipLam = not (skipLam s) })
	      _ -> do evalPrint s line 
	              return (False, s)

        evalPrint s se = do
	    mt <- readAndCheck s se
	    case mt of
	         Nothing -> return ()
		 Just (e, t) -> do
		     let v = nf e
		         (v', t') = if skipLam s then skipLambda v t else (v, t)
		     putStrLn $ show v' ++ "\n  ::\n" ++ show t'
	    return (False, s)

        addFile s n = do
	     let s' = s { defs = defs s ++ n }
	     mt <- readAndCheck s' "\\ (a::*) -> a"
	     case mt of
	         Nothing -> return (False, s)
		 Just _ ->  return (False, s')

	readAndCheck s e =
	     case unreads $ "let " ++ defs s ++ " in " ++ e of
	     Left msg -> do putStrLn $ "Syntax error: " ++ msg; return Nothing
	     Right expr ->
	         case typeCheck expr of
		 Left msg -> do putStrLn $ "Type error: " ++ msg; return Nothing
		 Right typ -> return $ Just (expr, typ)

helpMsg :: String
helpMsg = "\
\Commands:\n\
\  :defs        show loaded definitions\n\
\  :let def     add definition\n\
\  :load name   load file with definitions\n\
\  :help        show this message\n\
\  :quit        quit\n\
\  :skip        toggle skipping initial lambdas\n\
\  expr         evaluate expression\n\
\"