-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathRead.hs
347 lines (294 loc) · 9.92 KB
/
Read.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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
module Read where
import SExp
import Data.Char
import qualified Data.Map as Map
import Control.Monad
import Control.Monad.Error
import Control.Monad.State
import Data.Maybe
import Data.List
import Debug.Trace
data ReaderMacro = RM
{ rmPattern :: String
, rmReader :: Reader -> Reader
}
data ParenType
= Paren
| Block
| Input
| Item
deriving (Show, Eq)
data ReadResult
= ReadDone SExp String
| ReadError String
| ReadUnexpected ParenType String
deriving (Show, Eq)
type ReadProc a = StateT ReaderState IO a
type Reader = String -> ReadProc ReadResult
data MacroMap = MM (Map.Map Char (Either ReaderMacro MacroMap))
emptyM :: MacroMap
emptyM = MM Map.empty
insM :: ReaderMacro -> MacroMap -> Maybe MacroMap
insM rm mm = ins (rmPattern rm) mm
where
ins :: String -> MacroMap -> Maybe MacroMap
ins "" _ = Nothing
ins (c:cs) (MM mm) = case Map.lookup c mm of
Nothing -> Just $ MM $ Map.insert c (create cs) mm
Just (Left _) -> Nothing
Just (Right mm') -> do
mm'' <- ins cs mm'
return $ MM $ Map.insert c (Right mm'') mm
create name = foldr build (Left rm) name
where
build c ct = Right $ MM $ Map.insert c ct $ Map.empty
data ReaderState = RS
{ rsMacroMap :: !MacroMap
, rsUserState :: !(Map.Map Symbol SExp)
}
doTrace = False
xtrace :: (Show a) => String -> ReadProc a -> ReadProc a
xtrace name action
| doTrace = do
rsus <- gets rsUserState
liftIO $ putStrLn $ "+" ++ name ++ "; " ++ show rsus
v <- action
rsus1 <- gets rsUserState
liftIO $ putStrLn $ "-" ++ name ++ "; " ++ show rsus1
liftIO $ putStrLn $ " => " ++ show v
return v
| otherwise = action
initRS :: ReaderState
initRS = RS { rsMacroMap = defaultMM, rsUserState = Map.empty }
constituent :: Char -> Bool
constituent c = isAlpha c || isDigit c || elem c "+-*/%=<>_?!\\|&'"
monitor :: (Show a) => a -> a
monitor x = trace (show x) x
isReadingBlock :: ReadProc Bool
isReadingBlock = do
ctxs <- getlist "context_stack"
return $ fromMaybe False $ msum $ map f ctxs
where
f x
| x == sym "block" = Just True
| x == sym "rparen" = Just False
| otherwise = Nothing
defaultReader :: Reader
defaultReader [] = xtrace "defaultR[]" $ do
exitBlock
return $ ReadUnexpected Input []
defaultReader input = xtrace "defaultR" $ do
care <- isReadingBlock
beg <- getv "line_beginning?"
if beg /= NilS && care
then readFromLine defaultReader input
else do
putv "line_beginning?" NilS
readExp input
exitBlock :: ReadProc ()
exitBlock = do
stk <- getv "context_stack"
when (car stk == sym "block") $ do
modifyv "context_stack" cdr
modifyv "indent_stack" cdr
readExp :: Reader
readExp input@(c:_) = xtrace ("readExp " ++ show input) $
if isDigit c then numReader next input else
if constituent c then symReader next input else do
rs <- get
loop (rsMacroMap rs) input []
where
loop _ [] cur = return $ ReadError $ "eoi reached within macro sequence: " ++ reverse cur
loop (MM partial) (c:cs) cur = case Map.lookup c partial of
Nothing -> return $ ReadError $ "unhandled macro sequence: " ++ show (reverse (c:cur))
Just (Left rm) -> rmReader rm next cs
Just (Right mm) -> loop mm cs (c:cur)
next = defaultReader
readFromLine :: Reader -> Reader
readFromLine sub input = xtrace ("readFromLine " ++ show input) $ do
stk <- getv "context_stack"
if car stk == sym "block"
then do
idts <- getlist "indent_stack"
let cur = asStr $ head idts
indent <- getstr "current_indent"
case leIndent cur indent of
True -> readItem sub input
False -> do
modifyv "context_stack" cdr
modifyv "indent_stack" cdr
sub input
else return $ ReadUnexpected Item input
symReader :: Reader -> Reader
symReader = genReader constituent SymS
numReader :: Reader -> Reader
numReader = genReader isDigit (LitS . IntL . read)
genReader :: (Char -> Bool) -> (String -> SExp) -> Reader -> Reader
genReader pred conv _ input = case span pred input of
(cs, rest) -> return $ ReadDone (conv cs) rest
readSeq :: Reader -> ([SExp] -> ParenType -> String -> ReadProc ReadResult) -> Reader
readSeq sub cont input = xtrace "read-seq" $ do
r <- loop input []
case r of
Left c -> return $ ReadError c
Right (es, c, rest) -> cont es c rest
where
loop input acc = do
r <- sub input
case r of
ReadDone v rest -> loop rest (v:acc)
ReadError c -> return $ Left c
ReadUnexpected c rest -> return $ Right (reverse acc, c, rest)
listReader :: Reader -> ParenType -> Reader
listReader sub close input = xtrace ("listReader " ++ show close) $ withv "newline_sensitive?" NilS $ readSeq sub cont input
where
cont es c rest
| c == close = return $ ReadDone (ListS es) $ tail rest
| otherwise = do
s <- get
trace (show $ rsUserState s) $ return $ ReadError $ "unexpected closing " ++ show c ++ ", expecting " ++ show close ++ "\n current input=" ++ rest
readItem :: Reader -> Reader
readItem sub input = xtrace ("readItem " ++ show input) $ do
putv "line_beginning?" NilS
r <- withv "newline_sensitive?" (sym "t") $
pushToContextStackAnd (sym "item") $
readSeq sub cont input
return r
where
cont es _ rest = case es of
[elm] -> return $ ReadDone elm rest
_ -> return $ ReadDone (ListS es) rest
defaultMM :: MacroMap
defaultMM = case foldM (flip insM) emptyM tbl of
Nothing -> error "overlapping macro mapping"
Just mm -> mm
where
tbl =
[ entry " " nop
, entry "\t" nop
, entry "\n" handleNewline
, entry "(" popen
, entry ")" pclose
, entry ":\n" handleColonNewline
, entry "." handleDot
, entry "\"" handleStringLit
, entry ";" lineComment
]
nop = id
popen next input = pushToContextStackAnd (sym "rparen") $
listReader next Paren input
pclose _ input = exitBlock >> return (ReadUnexpected Paren (')':input))
entry prefix op = RM{ rmPattern = prefix, rmReader = op }
pushToContextStackAnd :: SExp -> ReadProc a -> ReadProc a
pushToContextStackAnd s action = do
stk <- getlist "context_stack"
withv "context_stack" (ListS $ s : stk) action
handleNewline :: Reader -> Reader
handleNewline next input = do
ctxs <- getv "newline_sensitive?"
if ctxs == NilS
then next input
else do
indent_stack <- getv "indent_stack"
let cur = asStr $ car indent_stack
case (leIndent indent cur, leIndent cur indent) of
(True, True) -> end Item
(True, False) -> do
end Item
(False, True) -> next rest
(False, False) -> return $ ReadError "incompatible indent"
where
(indent, rest) = splitIndent input
end typ = do
putv "line_beginning?" $ sym "t"
putv "current_indent" $ str indent
return $ ReadUnexpected typ rest
lineComment :: Reader -> Reader
lineComment next input = next $ snd $ break (=='n') input
handleDot :: Reader -> Reader
handleDot sub input = xtrace "dot" $ pushToContextStackAnd (sym "dot") $
readSeq sub cont input
where
cont es _ rest = return $ ReadDone (ListS es) rest
splitIndent :: String -> (String, String)
splitIndent str = case span isIndentChar str of
(_, '\n':next) -> splitIndent next
a -> a
where
isIndentChar c = c == ' ' || c == '\t'
leIndent :: String -> String -> Bool
leIndent x y = isPrefixOf x y
handleColonNewline :: Reader -> Reader
handleColonNewline def rest = case splitIndent rest of
(indent, code) -> do
indent_stack <- getv "indent_stack"
let cur = asStr $ car indent_stack
case leIndent cur indent of
True -> do
putv "indent_stack" $ cons (str indent) indent_stack
putv "current_indent" (str indent)
modifyv "context_stack" (cons $ sym "block")
putv "line_beginning?" $ sym "t"
readItem def code
False -> endIndent def indent code
handleStringLit :: Reader -> Reader
handleStringLit def rest = case break (=='"') rest of
(_, []) -> return $ ReadUnexpected Input rest
(body, _:z) -> return $ ReadDone (LitS $ StringL body) z
endIndent :: Reader -> String -> Reader
endIndent _ indent input = do
putv "line_beginning?" $ sym "t"
putv "current_indent" $ str indent
return $ ReadUnexpected Item input
getstr :: String -> ReadProc String
getstr key = do
v <- getv key
case v of
LitS (StringL str) -> return str
_ -> return ""
getlist :: String -> ReadProc [SExp]
getlist key = do
v <- getv key
case v of
ListS v -> return v
NilS -> return []
_ -> error $ "getlist: not a list: " ++ show v
getv :: String -> ReadProc SExp
getv key = gets $ fromMaybe NilS . Map.lookup key . rsUserState
putv :: String -> SExp -> ReadProc ()
putv key val = do
--when doTrace $ liftIO $ putStrLn $ "putv " ++ key ++ " <- " ++ show val
modify $ \rs -> rs{ rsUserState = Map.insert key val $ rsUserState rs }
modifyv :: String -> (SExp -> SExp) -> ReadProc ()
modifyv key f = getv key >>= putv key . f
withv :: String -> SExp -> ReadProc a -> ReadProc a
withv key val action = do
old <- getv key
putv key val
r <- action
new <- getv key
when (new /= val) $ fail $ "withv: temporary binding " ++ key ++ "=" ++ show val ++ " not preserved, resulting in " ++ show new
putv key old
return r
{-
readSexp :: String -> SExp
readSexp input = case evalState (defaultReader input) initRS of
ReadError s -> error s
ReadDone sexp _ -> sexp
ReadUnexpected c _ -> error $ "unexpected ending " ++ show c
-}
readSexpIO :: String -> IO SExp
readSexpIO input = do
r <- evalStateT (defaultReader $ removeComment input) initRS
case r of
ReadError s -> fail s
ReadDone sexp _ -> return sexp
ReadUnexpected c _ -> fail $ "unexpected ending " ++ show c
removeComment :: String -> String
removeComment = unlines . filter ((/="#") . take 1) . lines
readTest :: IO ()
readTest = do
input <- readFile "test_input.txt"
let source = "(defmodule nil:\n" ++ removeComment input ++ ")"
putStrLn . showSexp =<< readSexpIO source
-- vim: sw=2 ts=2 sts=2