cpsa-2.3.5: src/CPSA/JSON/Main.hs
-- Translate JSON encoded CPSA into CPSA S-Expressions
-- Copyright (c) 2014 The MITRE Corporation
--
-- This program is free software: you can redistribute it and/or
-- modify it under the terms of the BSD License as published by the
-- University of California.
module Main (main) where
import Numeric
import Data.Char (isSpace, isDigit, isPrint)
import System.IO
import System.Environment
import System.Console.GetOpt
import CPSA.Lib.CPSA
import CPSA.Lib.Entry
-- Runtime parameters
data Params = Params
{ file :: Maybe FilePath, -- Nothing specifies standard output
prefix :: Bool, -- Use prefix notation?
margin :: Int } -- Output line length
deriving Show
indent :: Int
indent = optIndent defaultOptions
main :: IO ()
main =
do
(p, params) <- jStart options interp
h <- outputHandle $ file params
let printer = if prefix params then pp else printItem
go (writeCpsaLn (printer (margin params) indent) h) p
hClose h
writeCpsaLn :: (SExpr a -> String) -> Handle -> SExpr a -> IO ()
writeCpsaLn printer h sexpr =
do
hPutStrLn h $ printer sexpr
hPutStrLn h ""
go :: (SExpr () -> IO ()) -> Handle -> IO ()
go f p =
loop
where
loop =
do
x <- jRead p
case x of
Nothing ->
return ()
Just sexpr ->
do
f sexpr
loop
-- Command line option flags
data Flag
= Help -- Help
| Info -- Version information
| Margin String -- Output line length
| Infix -- Select output notation
| Output String -- Output file name
deriving Show
defaultMargin :: Int
defaultMargin = optMargin defaultOptions
options :: [OptDescr Flag]
options =
[ Option ['o'] ["output"] (ReqArg Output "FILE") "output FILE",
Option ['m'] ["margin"] (ReqArg Margin "INT")
("set output margin (default " ++ show defaultMargin ++ ")"),
Option ['i'] ["infix"] (NoArg Infix) "output uses infix notation",
Option ['h'] ["help"] (NoArg Help) "show help message",
Option ['v'] ["version"] (NoArg Info) "show version number" ]
-- Interpret option flags
interp :: [Flag] -> IO Params
interp flags =
loop flags (Params { file = Nothing, -- By default, no output file
prefix = True,
margin = defaultMargin })
where
loop [] params = return params
loop (Output name : flags) params
| file params == Nothing =
loop flags $ params { file = Just name }
loop (Infix : flags) params =
loop flags $ params { prefix = False }
loop (Margin value : flags) params =
case readDec value of
[(margin, "")] ->
loop flags $ params { margin = margin }
_ ->
do
msg <- usage options ["Bad value for margin\n"]
abort msg
loop (Info : _) _ =
success cpsaVersion
loop (Help : _) _ =
do -- Show help then exit with success
msg <- usage options []
success msg
loop _ _ =
do -- Show help then exit with failure
msg <- usage options ["Bad option combination\n"]
abort msg
-- Returns the input S-expression and an interpretation of the command
-- line options.
jStart :: [OptDescr a] -> ([a] -> IO b) -> IO (Handle, b)
jStart options interp =
do
argv <- getArgs
(flags, files) <- opts options argv
opts <- interp flags
p <- openInput options files
return (p, opts)
opts :: [OptDescr a] -> [String] -> IO ([a], [String])
opts options argv =
case getOpt RequireOrder options argv of
(o, n, []) -> return (o, n)
(_, _, errs) ->
do
msg <- usage options errs
abort msg
openInput :: [OptDescr a] -> [String] -> IO Handle
openInput _ [file] =
openFile file ReadMode -- Input from named file
openInput _ [] =
return stdin -- Input from the standard input
openInput options _ =
do
msg <- usage options ["too many input files\n"]
abort msg
-- Read a JSON expression, and fail on error
jRead :: Handle -> IO (Maybe (SExpr ()))
jRead p =
do
x <- tryIO (jLoad p)
case x of
Right x ->
return x
Left err ->
abort err
-- The result of scanning is a token.
data Token
= Atom (SExpr ())
| Lparen
| Rparen
| Eof
-- Top level parser
jLoad :: Handle -> IO (Maybe (SExpr ()))
jLoad h =
do
t <- scan h
case t of
Atom x ->
return $ Just x
Lparen ->
do
x <- list h []
return $ Just x
Rparen ->
abort "Close of unopened list"
Eof ->
do
hClose h
return Nothing
-- A recursive decent parser
list :: Handle -> [SExpr ()] -> IO (SExpr ())
list h xs =
do
t <- scan h
case t of
Rparen ->
return (L () (seqrev xs))
Atom x ->
list h (x : xs)
Lparen ->
do
x <- list h []
list h (x : xs)
Eof ->
abort "Unexpected end of input in list"
-- Read the next character returning Nothing on EOF
get :: Handle -> IO (Maybe Char)
get h =
do
eof <- hIsEOF h
case eof of
True ->
return Nothing
False ->
do
ch <- hGetChar h
return $ Just ch
-- Peek at the next character returning Nothing on EOF
peek :: Handle -> IO (Maybe Char)
peek h =
do
eof <- hIsEOF h
case eof of
True ->
return Nothing
False ->
do
ch <- hLookAhead h
return $ Just ch
-- Return the next token
scan :: Handle -> IO (Token)
scan h =
do
ch <- get h
case ch of
Nothing ->
return Eof
Just ch ->
skip h ch
-- Skip whitespace and then handle first character of a token
skip :: Handle -> Char -> IO (Token)
skip h ',' = scan h -- Treat comma as white space
skip h ch | isSpace ch = scan h
skip _ '[' =
return Lparen
skip _ ']' =
return Rparen
skip h ch =
atom h ch
-- Scan a string, number, or a symbol
atom :: Handle -> Char -> IO (Token)
atom h '"' = string h []
atom h '+' = sign h True
atom h '-' = sign h False
atom h ch | isDigit ch = number h [ch]
atom _ _ = abort "Bad char in atom"
-- Scan a quoted string of characters
string :: Handle -> String -> IO (Token)
string h s =
do
ch <- get h
case ch of
Nothing ->
abort "End of input in string"
Just '"' ->
symOrStr s
Just '\\' ->
quote h s
Just ch | isPrint ch ->
string h (ch : s)
Just _ ->
abort "Bad char for string"
-- Handle backslash in string
quote :: Handle -> String -> IO (Token)
quote h s =
do
ch <- get h
case ch of
Nothing ->
abort "End of input in string"
Just ch | isPrint ch ->
string h (ch : s)
Just _ ->
abort "Bad char for string"
-- Is string a symbol or a quoted string?
symOrStr :: String -> IO (Token)
symOrStr ('"' : s) =
case seqrev s of
'"' : str ->
return $ Atom (Q () str)
_ ->
abort "Quote in symbol"
symOrStr s =
return $ Atom (S () $ seqrev s)
-- A reverse that evaluates the list elements.
seqrev :: [a] -> [a]
seqrev l =
foldl (\xs x -> x `seq` xs `seq` (x:xs)) [] l
-- Scan a sequence of digits
number :: Handle -> String -> IO (Token)
number h s =
do
ch <- peek h
case ch of
Nothing ->
return (Atom (N () (read (seqrev s))))
Just ch | isDigit ch ->
do
_ <- hGetChar h
number h (ch : s)
Just _ ->
return (Atom (N () (read (seqrev s))))
-- Scan a number that starts with a sign
sign :: Handle -> Bool -> IO (Token)
sign h plus =
do
ch <- get h
case ch of
Nothing ->
abort "Sign followed by EOF"
Just ch | isDigit ch ->
let s = if plus then [ch] else [ch, '-'] in
number h s
Just _ ->
abort "Sign not followed by a digit"