-
Notifications
You must be signed in to change notification settings - Fork 21
/
Copy pathSSParser.hs
114 lines (97 loc) · 3.81 KB
/
SSParser.hs
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
module SSParser(readExpr) where
import LispVal
import Text.ParserCombinators.Parsec hiding ( spaces )
import Control.Monad
-----------------------------------------------------------
-- PARSER --
-----------------------------------------------------------
symbol :: Parser Char
symbol = oneOf "!$%&|*+-/:<=?>@^_~#"
-- We have to lift the List constructor because it takes a [LispVal]
-- and yields a LispVal. However, the result that endBy yield is in the
-- Parser monad.
-- The tutorial originally recommends the use of sepBy,
-- instead of endBy. Howecer, I coulnd't make the following example
-- work using setBy:
-- ~/src/scheme $ ./simple_parser "(6 )"
-- The parser gets confused by the empty space between the '6' and
-- the ')'. It assumes that another list element will appear but none
-- does. On the other hand, using endBy rejects examples such as "(6)".
-- sepEndBy, which combines sepBy and endBy seems to solve the problem.
parseList :: Parser LispVal
parseList = liftM List $ sepEndBy parseExpr spaces
parseDottedList :: Parser LispVal
parseDottedList =
do {
exprs <- many (parseExpr >>= \p -> spaces >> return p);
lastExpr <- (char '.' >> spaces >> parseExpr);
return $ DottedList exprs lastExpr;
}
-- Parses a String. It Looks for a double quote followed by a sequence of
-- characters among which the backslash is not and then another double quote.
parseString :: Parser LispVal
parseString =
do { char '"';
x <- many (noneOf "\"\\" <|> escaped);
char '"';
return $ String x
}
escaped = char '\\' >> choice (zipWith escapeChar codes replacements)
escapeChar code replacement = char code >> return replacement
codes = ['b', 'n', 'f', 'r', 't', '\\', '\"', '/']
replacements = ['\b', '\n', '\f', '\r', '\t', '\\', '\"', '/']
parseAtom :: Parser LispVal
parseAtom =
do { first <- letter <|> symbol;
rest <- many ( letter <|> digit <|> symbol);
let atom = [first] ++ rest in
return $ case atom of
"#t" -> Bool True
"true" -> Bool True
"#f" -> Bool False
"false" -> Bool False
otherwise -> Atom atom;
}
parseQuoted :: Parser LispVal
parseQuoted =
do { char '\'';
x <- parseExpr;
return $ List [Atom "quote", x];
}
parseNumber :: Parser LispVal
parseNumber =
do { digits <- (char '-' >> (many1 digit) >>= \num -> return ('-':num)) <|> (many1 digit);
return $ (Number . read) digits;
}
-- Alternate implementation for parseNumber. I used the
-- one above because I can understand it better. The
-- liftM function gets an (a -> b) function and makes it
-- into an (m a -> m b) function, where m is of the Monad
-- class. The monad to which the function is lifted depends
-- on the monad on which liftM was executed (in this case,
-- Parser LispVal). To use liftM, we had to import
-- Control.Monad.
-- parseNumber : : Parser LispVal
-- parseNumber = liftM (Number . read) $ many1 digit
parseExpr :: Parser LispVal
parseExpr = try parseNumber <|>
try parseAtom <|>
try parseString <|>
parseQuoted <|>
do {
char '(' >> spaces1;
-- The try function below implements backtracking.
-- It is necessary because lists start with the same
-- syntax all the way until a "." is found.
l <- (try parseDottedList) <|> parseList;
spaces1 >> char ')';
return l;
}
spaces :: Parser ()
spaces = skipMany1 space
spaces1 :: Parser ()
spaces1 = skipMany space
readExpr :: String -> LispVal
readExpr input = case parse parseExpr "Scheme" input of
Left err -> Error (show err)
Right val -> val