summaryrefslogtreecommitdiff
path: root/Parser.hs
blob: 54728938299a0be61d15f6b1a8cdfe78d8926367 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
{-# LANGUAGE LambdaCase #-}
module Parser(parseProgram, parseExpression) where

import qualified Control.Exception as E
import Control.Monad
import Control.Monad.Trans
import Data.Char
import Data.Maybe
import System.FilePath
import System.IO.Error (isDoesNotExistError)
import Text.Parsec
import Text.Parsec.Pos

import AST


newtype ParserState = ParserState (Maybe FilePath)
type Parser = ParsecT String ParserState IO


parseProgram :: Maybe FilePath -> String -> IO (Either ParseError Program)
parseProgram mpath = runParserT pProgram (ParserState mpath) (fromMaybe "" mpath)

pProgram :: Parser Program
pProgram = between pWhiteComment eof (liftM Program (many pValue))


parseExpression :: String -> IO (Either ParseError Value)
parseExpression = runParserT pExpression (ParserState Nothing) ""

pExpression :: Parser Value
pExpression = between pWhiteComment eof pValue


pValue :: Parser Value
pValue = pPPC <|> pVEllipsis <|> pVList <|> pVNum <|> pVString <|> pVName <|> pVQuoted  <?> "value"

pVList :: Parser Value
pVList = flip label "list" $ do
    symbol "("
    exs <- many pValue
    symbol ")"
    return $ VList exs

pVNum :: Parser Value
pVNum = liftM (VNum . read) (try ((:) <$> char '-' <*> many1 digit) <|> many1 digit) <* pWhiteComment  <?> "number"

pVString :: Parser Value
pVString = fmap VString pString

pVName :: Parser Value
pVName = flip label "name" $ do
    first <- satisfy isFirstNameChar
    rest <- many (satisfy isNameChar)
    pWhiteComment
    return $ VName $ first : rest
  where
    isNameChar c = ((c >= '*' && c <= 'z') || c `elem` "!$%&|~") && c /= ';'
    isFirstNameChar c = isNameChar c && not (isDigit c)

pVQuoted :: Parser Value
pVQuoted = char '\'' >> liftM VQuoted pValue  <?> "quoted value"

pVEllipsis :: Parser Value
pVEllipsis = symbol "..." >> return VEllipsis  <?> "ellipsis"

pPPC :: Parser Value
pPPC = flip label "preprocessor command" $ do
    symbol "#include"
    incfname <- pString
    ParserState basefname <- getState
    (fname, src) <- liftIO (readPPCinclude basefname incfname)
    stateBackup <- getParserState
    void $ setParserState (State ("(do " ++ src ++ ")") (initialPos fname) (ParserState (Just fname)))
    result <- pValue <* eof
    void $ setParserState stateBackup
    return result


symbol :: String -> Parser ()
symbol s = try (string s) >> pWhiteComment

pString :: Parser String
pString = flip label "string" $ do
    void $ char '"'
    s <- manyTill pStringChar (symbol "\"")
    return s

pStringChar :: Parser Char
pStringChar =
    (char '\\' >>
        choice [ char 'n' >> return '\n'
               , char 'r' >> return '\r'
               , char 't' >> return '\t'
               , char '0' >> return '\0'
               , char 'x' >> pHexDigit >>= \a -> pHexDigit >>= \b -> return (chr (16 * a + b))
               , char '"' >> return '"'
               , char '\\' >> return '\\' ]) <|>
    anyChar
  where
    pHexDigit :: Parser Int
    pHexDigit = (subtract 48 . fromEnum <$> digit)
                    <|> ((+ (10 - 97)) . ord <$> oneOf "abcdef")
                    <|> ((+ (10 - 65)) . ord <$> oneOf "ABCDEF")

pWhiteComment :: Parser ()
pWhiteComment = do
    pWhitespace
    void $ many $ pComment >> pWhitespace
  where
    pWhitespace :: Parser ()
    pWhitespace = void (many space)  <?> "whitespace"

    pComment :: Parser ()
    pComment = flip label "comment" $ do
        void $ char ';'
        void (manyTill anyChar (void endOfLine <|> eof))

readPPCinclude :: Maybe FilePath -> String -> IO (FilePath, String)
readPPCinclude mbase fname =
    let searchDirs = [maybe "." takeDirectory mbase]
        cands = map (</> fname) searchDirs
        tryCand cand = E.tryJust (guard . isDoesNotExistError) (readFile cand) >>= \case
                            Left _ -> return Nothing
                            Right src -> return (Just (cand, src))
    in (catMaybes <$> sequence (map tryCand cands)) >>= \case
            [] -> error $ "Cannot find #include'd file '" ++ fname ++ "'"
            pair : _ -> return pair