packages feed

ampersand-3.0.3: src/Database/Design/Ampersand/Input/Parsing.hs

{-# OPTIONS_GHC  -XScopedTypeVariables #-}
-- This module provides an interface to be able to parse a script and to
-- return an FSpec, as tuned by the command line options.
-- This might include that RAP is included in the returned FSpec.
module Database.Design.Ampersand.Input.Parsing 
  ( parseADL, parseADL1pExpr, parseRule, parseCtx
  )
where

import Database.Design.Ampersand.ADL1
import Prelude hiding (putStrLn, writeFile) -- make sure everything is UTF8
import Database.Design.Ampersand.Basics
import Database.Design.Ampersand.Misc
import Database.Design.Ampersand.Input.ADL1.UU_Scanner (scan,initPos, Token)
import UU.Parsing (parse, evalSteps, getMsgs, Pair(..), Message, Parser)
import Database.Design.Ampersand.Input.ADL1.Parser
import Database.Design.Ampersand.Input.ADL1.CtxError
import Data.List
import System.Directory
import System.FilePath
import Data.Traversable (sequenceA)

fatal :: Int -> String -> a
fatal = fatalMsg "Parsing"

-- Parse an Ampersand file and all transitive includes
parseADL  :: Options -> FilePath -> IO (Guarded P_Context)
parseADL opts filePath =
  whenCheckedIO (parseSingleADL opts filePath) $ \(ctxt, filePaths) ->
    whenCheckedIO (parseADLs opts [filePath] filePaths) $ \ctxts ->
      return $ Checked $ foldl mergeContexts ctxt ctxts

parseADLs :: Options -> [FilePath] -> [FilePath] -> IO (Guarded [P_Context])
parseADLs _    _               []        = return $ Checked []
parseADLs opts parsedFilePaths filePaths =
 do { let filePathsToParse = nub filePaths \\ parsedFilePaths
    ; whenCheckedIO (fmap sequenceA $ mapM (parseSingleADL opts) filePathsToParse) $ \ctxtNewFilePathss ->
       do { let (ctxts, newFilessToParse) = unzip ctxtNewFilePathss
          ; whenCheckedIO (parseADLs opts (parsedFilePaths ++ filePaths) $ concat newFilessToParse) $ \ctxts' ->
              return $ Checked $ ctxts ++ ctxts'
          }
    }

-- Parse an Ampersand file, but not its includes (which are simply returned as a list)
parseSingleADL :: Options -> FilePath -> IO (Guarded (P_Context, [FilePath]))
parseSingleADL opts filePath =
 do { verboseLn opts $ "Reading file " ++ filePath
    ; mFileContents <- readUTF8File filePath
    ; case mFileContents of
        Left err -> error $ "ERROR reading file " ++ filePath ++ ":\n" ++ err 
                    -- TODO: would like to return an Errors value here, but this datatype currently only accommodates UUParsing Messages 
        Right fileContents ->
         do { whenCheckedIO (return $ runParser pContext filePath fileContents) $ \(ctxts,relativePaths) -> 
               do { filePaths <- mapM normalizePath relativePaths
                  ; return $ Checked (ctxts, filePaths)
                  }
             }
    }
 where normalizePath relativePath = canonicalizePath $ takeDirectory filePath </> relativePath 

runParser :: AmpParser res -> String -> String -> Guarded res
runParser parser filename input =
  let scanner = scan keywordstxt keywordsops specialchars opchars filename initPos
      steps = parse parser (scanner input)
  in  case  getMsgs steps of
         []    -> let Pair res _ = evalSteps steps
                  in  Checked res
         msg:_ -> Errors [PE msg]

runParser' :: forall res . Parser Token res -> String -> String -> Either ParseError res
runParser' parser filename input =
  let scanner = scan keywordstxt keywordsops specialchars opchars filename initPos
      steps = parse parser (scanner input)
  in  case  getMsgs steps of
         []    -> let Pair res _ = evalSteps steps
                  in  Right res
         msg:_ -> Left msg




type ParseError = Message Token (Maybe Token)

-- In order to read derivation rules, we use the Ampersand parser.
-- Since it is applied on static code only, error messagea may be produced as fatals.
parseRule :: String -> Term TermPrim
parseRule str
   = case  runParser' pRule "inside Haskell code" str of
       Right result -> result
       Left  msg    -> fatal 274 ("Parse errors in "++str++":\n   "++show msg)
 
-- | Parse isolated ADL1 expression strings
parseADL1pExpr :: String -> String -> Either String (Term TermPrim)
parseADL1pExpr pexprstr fn = parseExpr pexprstr fn

-- | Parse isolated ADL1 expression strings
parseExpr :: String            -- ^ The string to be parsed
          -> String            -- ^ The name of the file (used for error messages)
          -> Either String (Term TermPrim)  -- ^ The result: Either an error message,  or a good result
parseExpr str fn =
  case runParser' pTerm fn str of
      Right result -> Right result
      Left  msg    -> Left $ "Parse errors:\n"++show msg
     
parseCtx :: String -> String -> Guarded (P_Context, [String])
parseCtx = runParser pContext