on master help sign in
make REPL behavior more predictable for multi-line entries
f3e09a8Evan Czaplicki
terminal/src/Repl.hs+105−51
@@ -161,18 +161,21 @@ data Input
161
161
162read :: Repl.InputT M Input
162read :: Repl.InputT M Input
163read =
163read =
164− do line <- Repl.getInputLine "> "
164+ do maybeLine <- Repl.getInputLine "> "
165− case line of
165+ case maybeLine of
166 Nothing ->
166 Nothing ->
167 return Exit
167 return Exit
168
168
169− Just chars ->
169+ Just line ->
170+ let
171+ lines = Lines line []
172+ in
170− case categorize chars of
173+ case categorize lines of
171 Done input -> return input
174 Done input -> return input
172− Continue -> readMore chars
175+ Continue -> readMore lines
173
176
174
177
175−readMore :: [Char] -> Repl.InputT M Input
178+readMore :: Lines -> Repl.InputT M Input
176readMore previousLines =
179readMore previousLines =
177 do input <- Repl.getInputLineWithInitial "| " (" ","")
180 do input <- Repl.getInputLineWithInitial "| " (" ","")
178 case input of
181 case input of
@@ -181,11 +184,55 @@ readMore previousLines =
181
184
182 Just chars ->
185 Just chars ->
183 let
186 let
184− allLines = previousLines ++ '\n' : chars
187+ lines = addLine chars previousLines
185 in
188 in
186− case categorize allLines of
189+ case categorize lines of
187 Done input -> return input
190 Done input -> return input
188− Continue -> readMore allLines
191+ Continue -> readMore lines
192+
193+
194+
195+-- LINES
196+
197+
198+data Lines =
199+ Lines
200+ { _prevLine :: String
201+ , _revLines :: [String]
202+ }
203+
204+
205+addLine :: [Char] -> Lines -> Lines
206+addLine line (Lines x xs) =
207+ Lines line (x:xs)
208+
209+
210+isBlank :: Lines -> Bool
211+isBlank (Lines prev rev) =
212+ null rev && all (==' ') prev
213+
214+
215+isSingleLine :: Lines -> Bool
216+isSingleLine (Lines _ rev) =
217+ null rev
218+
219+
220+endsWithBlankLine :: Lines -> Bool
221+endsWithBlankLine (Lines prev _) =
222+ all (==' ') prev
223+
224+
225+linesToByteString :: Lines -> BS_UTF8.ByteString
226+linesToByteString (Lines prev rev) =
227+ BS_UTF8.fromString (unlines (reverse (prev:rev)))
228+
229+
230+getFirstLine :: Lines -> String
231+getFirstLine (Lines x xs) =
232+ case xs of
233+ [] -> x
234+ y:ys -> getFirstLine (Lines y ys)
235+
189
236
190
237
191
238
@@ -197,18 +244,18 @@ data CategorizedInput
197 | Continue
244 | Continue
198
245
199
246
200−categorize :: [Char] -> CategorizedInput
247+categorize :: Lines -> CategorizedInput
201−categorize chars
248+categorize lines
202− | all (==' ') chars = Done Skip
249+ | isBlank lines = Done Skip
203− | startsWithColon chars = Done (toCommand chars)
250+ | startsWithColon lines = Done (toCommand lines)
204− | startsWithKeyword "import" chars = attemptImport chars
251+ | startsWithKeyword "import" lines = attemptImport lines
205− | otherwise = attemptDeclOrExpr chars
252+ | otherwise = attemptDeclOrExpr lines
206
253
207
254
208−attemptImport :: [Char] -> CategorizedInput
255+attemptImport :: Lines -> CategorizedInput
209−attemptImport chars =
256+attemptImport lines =
210 let
257 let
211− src = BS_UTF8.fromString (chars ++ "\n")
258+ src = linesToByteString lines
212 parser = P.specialize (\_ _ _ -> ()) PM.chompImport
259 parser = P.specialize (\_ _ _ -> ()) PM.chompImport
213 in
260 in
214 case P.fromByteString parser (\_ _ -> ()) src of
261 case P.fromByteString parser (\_ _ -> ()) src of
@@ -216,63 +263,67 @@ attemptImport chars =
216 Done (Import name src)
263 Done (Import name src)
217
264
218 Left () ->
265 Left () ->
219− if endsWithBlankLine chars
220− then Done (Import "ERR" src)
266+ ifFail lines (Import "ERR" src)
221− else Continue
267+
222
268
269+ifFail :: Lines -> Input -> CategorizedInput
270+ifFail lines input =
271+ if endsWithBlankLine lines
272+ then Done input
273+ else Continue
223
274
275+
276+ifDone :: Lines -> Input -> CategorizedInput
277+ifDone lines input =
278+ if isSingleLine lines || endsWithBlankLine lines
279+ then Done input
280+ else Continue
281+
282+
224−attemptDeclOrExpr :: [Char] -> CategorizedInput
283+attemptDeclOrExpr :: Lines -> CategorizedInput
225−attemptDeclOrExpr chars =
284+attemptDeclOrExpr lines =
226 let
285 let
227− src = BS_UTF8.fromString (chars ++ "\n")
286+ src = linesToByteString lines
228 exprParser = P.specialize (toExprPosition src) PE.expression
287 exprParser = P.specialize (toExprPosition src) PE.expression
229 declParser = P.specialize (toDeclPosition src) PD.declaration
288 declParser = P.specialize (toDeclPosition src) PD.declaration
230 in
289 in
231 case P.fromByteString declParser (,) src of
290 case P.fromByteString declParser (,) src of
232 Right (decl, _) ->
291 Right (decl, _) ->
233 case decl of
292 case decl of
234− PD.Value _ (A.At _ (Src.Value (A.At _ name) _ _ _)) -> Done (Decl name src)
293+ PD.Value _ (A.At _ (Src.Value (A.At _ name) _ _ _)) -> ifDone lines (Decl name src)
235− PD.Union _ (A.At _ (Src.Union (A.At _ name) _ _ )) -> Done (Type name src)
294+ PD.Union _ (A.At _ (Src.Union (A.At _ name) _ _ )) -> ifDone lines (Type name src)
236− PD.Alias _ (A.At _ (Src.Alias (A.At _ name) _ _ )) -> Done (Type name src)
295+ PD.Alias _ (A.At _ (Src.Alias (A.At _ name) _ _ )) -> ifDone lines (Type name src)
237 PD.Port _ _ -> Done Port
296 PD.Port _ _ -> Done Port
238
297
239 Left declPosition
298 Left declPosition
240− | startsWithKeyword "type" chars ->
299+ | startsWithKeyword "type" lines ->
241− if endsWithBlankLine chars
242− then Done (Type "ERR" src)
300+ ifFail lines (Type "ERR" src)
243− else Continue
244
301
245− | startsWithKeyword "port" chars ->
302+ | startsWithKeyword "port" lines ->
246 Done Port
303 Done Port
247
304
248 | otherwise ->
305 | otherwise ->
249 case P.fromByteString exprParser (,) src of
306 case P.fromByteString exprParser (,) src of
250 Right _ ->
307 Right _ ->
251− Done (Expr src)
308+ ifDone lines (Expr src)
252
309
253 Left exprPosition ->
310 Left exprPosition ->
254− if endsWithBlankLine chars
311+ if exprPosition >= declPosition then
255− then Done (if declPosition <= exprPosition then Expr src else Decl "ERR" src)
312+ ifFail lines (Expr src)
256− else Continue
313+ else
257−
314+ ifFail lines (Decl "ERR" src)
258
315
259−endsWithBlankLine :: [Char] -> Bool
260−endsWithBlankLine chars =
261− case reverse (List.lines chars) of
262− [] -> False
263− cs:_ -> all (==' ') cs
264
316
265−
266−startsWithColon :: [Char] -> Bool
317+startsWithColon :: Lines -> Bool
267−startsWithColon chars =
318+startsWithColon lines =
268− case dropWhile (==' ') chars of
319+ case dropWhile (==' ') (getFirstLine lines) of
269 [] -> False
320 [] -> False
270 c:_ -> c == ':'
321 c:_ -> c == ':'
271
322
272
323
273−toCommand :: [Char] -> Input
324+toCommand :: Lines -> Input
274−toCommand chars =
325+toCommand lines =
275− case drop 1 $ dropWhile (==' ') chars of
326+ case drop 1 $ dropWhile (==' ') (getFirstLine lines) of
276 "reset" -> Reset
327 "reset" -> Reset
277 "exit" -> Exit
328 "exit" -> Exit
278 "quit" -> Exit
329 "quit" -> Exit
@@ -280,8 +331,11 @@ toCommand chars =
280 rest -> Help (Just (takeWhile (/=' ') rest))
331 rest -> Help (Just (takeWhile (/=' ') rest))
281
332
282
333
283−startsWithKeyword :: [Char] -> [Char] -> Bool
334+startsWithKeyword :: [Char] -> Lines -> Bool
284−startsWithKeyword keyword line =
335+startsWithKeyword keyword lines =
336+ let
337+ line = getFirstLine lines
338+ in
285 List.isPrefixOf keyword line &&
339 List.isPrefixOf keyword line &&
286 case drop (length keyword) line of
340 case drop (length keyword) line of
287 [] -> True
341 [] -> True