-- Copyright (c) 2023-2024 Chris Pressey, Cat's Eye Technologies
-- This file is distributed under a BSD license. See LICENSES directory:
-- SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-Fountain
module Main (main) where
import System.Environment
import System.Exit
import System.IO
import qualified Language.Fountain.Grammar as Grammar
import qualified Language.Fountain.Loader as Loader
import qualified Language.Fountain.Parser as Parser
import qualified Language.Fountain.Generator as Generator
import qualified Language.Fountain.Preprocessor as Preprocessor
data Flags = Flags {
dumpState :: Bool,
startSymbol :: Maybe String,
suppressNewline :: Bool,
seed :: Int
} deriving (Show, Ord, Eq)
defaultFlags = Flags{
dumpState = False,
startSymbol = Nothing,
suppressNewline = False,
seed = 0
}
parseFlags flags ("--dump-state":rest) =
parseFlags flags{ dumpState = True } rest
parseFlags flags ("--start-symbol":s:rest) =
parseFlags flags{ startSymbol = Just s } rest
parseFlags flags ("--suppress-newline":rest) =
parseFlags flags{ suppressNewline = True } rest
parseFlags flags ("--seed":seedStr:rest) =
parseFlags flags{ seed = (read seedStr) :: Int } rest
parseFlags flags other = (flags, other)
getStartSymbol g flags = case startSymbol flags of
Just x -> x
Nothing -> (Grammar.startSymbol g)
main = do
args <- getArgs
let (flags, args') = parseFlags defaultFlags args
let output = if (suppressNewline flags) then putStr else putStrLn
case args' of
["load", grammarFileName] -> do
grammar <- loadSource grammarFileName
-- TODO: add flag to show internal format
output $ Grammar.depictGrammar grammar
["preprocess", grammarFileName] -> do
grammar <- loadSource grammarFileName
let grammar' = Preprocessor.preprocessGrammarForGeneration grammar
-- TODO: add flag to show internal format
output $ Grammar.depictGrammar grammar'
("parse":grammarFileName:textFileName:initialParams) -> do
grammar <- loadSource grammarFileName
let grammar' = Preprocessor.preprocessGrammarForParsing grammar
text <- loadText textFileName
let start = getStartSymbol grammar' flags
let initialState = Parser.constructState text initialParams
let finalState = Parser.parseFrom grammar' start initialState
output $ if (dumpState flags) then show finalState else formatParseResult $ Parser.obtainResult finalState
exitWith $ either (\_msg -> ExitFailure 1) (\_remaining -> ExitSuccess) $ Parser.obtainResult finalState
("generate":grammarFileName:initialParams) -> do
grammar <- loadSource grammarFileName
let grammar' = Preprocessor.preprocessGrammarForGeneration grammar
let start = getStartSymbol grammar' flags
let initialState = Generator.constructState (seed flags) initialParams
let finalState = Generator.generateFrom grammar' start initialState
output $ if (dumpState flags) then show finalState else formatGenerateResult $ Generator.obtainResult finalState
exitWith $ either (\_msg -> ExitFailure 1) (\_remaining -> ExitSuccess) $ Generator.obtainResult finalState
_ -> usage
usage = abortWith
(
"Usage:\n" ++
" fountain {flags} load <fountain-filename>\n" ++
" fountain {flags} preprocess <fountain-filename>\n" ++
" fountain {flags} parse <fountain-filename> <text-filename> {params}\n" ++
" fountain {flags} generate <fountain-filename> {params}\n" ++
" where {flags} is any of:\n" ++
" --dump-state: dump the internal parse/generate state as part of output\n" ++
" --start-symbol <NT>: name of the nonterminal to start parsing/generating at\n" ++
" (default is the nonterminal that appears first in the grammar)\n" ++
" --suppress-newline: don't output a final newline after output\n" ++
" --seed <int>: seed for pseudo-random choices during backtracking (default 0)\n" ++
" and {params} is a list of arguments of the form `var=value` with which\n" ++
" variables will be initialized in the initial parse/generate state."
)
loadSource fileName = do
handle <- openFile fileName ReadMode
-- hSetEncoding handle utf8
text <- hGetContents handle
case Loader.parseFountain text of
Right g -> do
return g
Left err ->
abortWith $ show err
loadText fileName = do
handle <- if fileName == "--" then return stdin else openFile fileName ReadMode
-- hSetEncoding handle utf8
text <- hGetContents handle
return text
abortWith msg = do
hPutStrLn stderr msg
exitWith $ ExitFailure 1
formatParseResult (Right "") = "Success"
formatParseResult (Right s) = "Remaining: " ++ (show s)
formatParseResult (Left _) = "Failure"
formatGenerateResult (Right s) = s
formatGenerateResult (Left _) = "Failure"