{- | Fifth assignment for IB016, semester spring 2017, 20 points. == Simplified Prolog program parser Your task is to implement the parser and pretty-printer for Prolog programs. The full format is quite complicated -- for the purpose of this homework, you'll only parse a subset of Prolog programs (i.e. everything you can parse is a valid Prolog program but not every valid Prolog program is parseable according to the specification here). You should be able to parse your own Programs from IB015 (with some exceptions such as arithmetic and semicolon-alternatives). === Parser (13 points) The required Prolog format is given below in extended Bacus-Naur form. This is often used to describe the syntax in computer science. More details about the formalism can be found at and . If unsure about the meaning, feel free to drop us an email or ask at the discussion forum. @ prolog = ( \ | \ )* clause = \ "." | \ ":-" \ ( "," \ )* "." | ":-" \ ( "," \ )* "." predicate = \ | \ "(" \ ( "," \ )* ")" clause-body = \ | \ \ \ | "!" term = \ | \ | \ list = "[" \ ( "," \ )* "]" | "[" \ ( "," \ )* "|" \ "]" operator = "=" | "\\=" | "==" | "\\==" atom = \ | "'" \ "'" variable = ? mixture of lowercase, uppercase, digits and underscore starting with uppercase or underscore ? atom-string = ? mixture of lowercase, uppercase, digits and underscore starting with lowercase ? quote-string = ? string with any characters except for "'" ? comment = "%" \ \ | "/*" \ "*/" single-line-comment = ? string with any characters except for end-of-line (\\r\\n or \\n) ? multi-line-comment = ? string with any characters not containing substring "*/" ? @ Apart from the rules in the grammar, any form of whitespace is allowed nearly everywhere. The only notable exception are multi-character keywords (such as operators and ":-"). See the example in the pretty-printer section. For easier comprehension of the format, there is a set of railroad diagrams visualizing the parsed grammar. The optional whitespace characters are not displayed in the diagrams. <> <> <> <> <> <> <> === Pretty-printer (7 points) After parsing the Prolog program, write an instance for 'Pretty Prolog' enabling string output in form a Prolog source code. The output should be a valid Prolog program and should be directly parseable by the 'prolog' parser without any information loss. Especially note that: * You have to retain the order of clauses and predicates since these matter. * Comment location matters, since they often relate to adjacent lines. * Whitespace (incl. line endings) do not have semantic value in Prolog (except for ending the single-line comments). Handle them as you see fit (e.g. you can have the whole predicate in a single line or split it to one predicate per line). The exact output formatting is up to you. Aspire to legible and comprehensible code, similar to what you would write yourself. See the example below. @ >>> readFile "in.pl" >>= putStrLn % Example Prolog file /* First predicate *\/ first(A, _b) :- man(A), !, fact. /* Second predicate */ second ( X, a ) :- man ( X ) , fact . >>> parseAndPrintFile "in.pl" % Example Prolog file % First predicate first( A, _b ) :- man( A ), !, fact. /* Second predicate */ second( X, a ) :- man( X ), fact. @ === Further notes * For simplicity, you may assume we are using common 'String' as input, @()@ as the user state and 'Identity' as the underlying monad -- that is, you can use the 'Parser' type provided by module @Text.Parsec.String@. * Adhere strictly to the specification provided in this task. The structure of true Prolog programs is more complicated. Furthermore, different compilers accept different formats. * You can specify the 'Prolog' data type as you see fit. It is recommended to lay out the data structures before writing the actual parsers. However, the data type should retain all important information about the source code. === Module and package constraints You can use any modules from the following packages: , and . === Tips and tricks * Try to design the parser flexibly, so that if you ever decided to enhance it you would not have to rewrite all the data types and parser units. * Try to think of the complexity, try to minimize necessary look-ahead (i.e. do not overuse 'try'). * Try test-driven development -- first write a bunch of valid strings/files for a certain subset of the grammar and then try to implement the corresponding parser. * Restrictive data types are usually better (safer) than too general ones. That means it is better to have a type allowing only valid Prolog programs as opposed to using a general type such as String. * Note that, for the simplicity, we forbid operators deeper in terms (they are only allowed in predicate level). -} -- Name: Name Surname -- UID: 123456 module PrologParser ( main, Prolog (..), prolog, Pretty (..), parseAndPrintFile ) where import Text.Parsec import Text.Parsec.String ( Parser ) import System.Environment ( getArgs ) import System.IO ( hPrint, stderr ) -- #### Data type declarations #### -- | Data type representing Prolog programs. Adjust as necessary. data Prolog = Undefined deriving Show -- #### Parsers #### -- | If the provided input holds a valid Prolog program (terminated with end-of-file), -- the parser should return it as a 'Prolog' structure in your own user-defined -- data type. If the input is invalid, the parse error should be returned. prolog :: Parser Prolog prolog = undefined -- #### Pretty printers #### -- | Types that can be displayed in a nice human-readable form. class Pretty a where -- | Pretty-print the object into human- and machine-readable string, possibly hiding the internal data types. prettyPrint :: a -> String instance Pretty Prolog where prettyPrint = undefined -- #### Functions using parsers #### -- | Parse Prolog from a given file. -- Print out the formatted Prolog or the parse error. parseAndPrintFile :: String -> IO () parseAndPrintFile filename = do input <- readFile filename either (hPrint stderr) (putStr . prettyPrint) $ parse prolog filename input -- | Parse the given file as Prolog printing the result. -- In case of successful parse, print the parsed Prolog program using 'prettyPrint'. -- In case of parse failure, print the error. -- -- The filename is given as the only command line argument. -- If there are less/more arguments, display short usage info and exit. -- You do not have to check if the file exists. main :: IO () main = do args <- getArgs if length args /= 1 then putStrLn "Provide 1 file to parse as argument." else parseAndPrintFile $ head args