on master help sign in
make REPL behavior more predictable for multi-line entries

Introduces the Lines type to keep track of all the lines that have been input so far in an explicit way. (Before, newlines were getting added kind of haphazardly.) From there, introduce ifDone and ifFail to enforce the following rules: - If a single-line entry parses, evaluate it. - If a single-line entry does not parse, let people give a second line. - If a multi-line entry ends with a blank line, evaluate it. - If a multi-line entry does not end with a blank line, give another line. This should help with some of the cases that @rlefevre shared: > case True of | True -> "hi" XXX Now it will be: > case True of | True -> "hi" | False -> "bye" | "hi" : String

commitf3e09a8ba808b93d92a0c914a4b7864338348f49 authorEvan Czaplicki <evancz@users.noreply.github.com> dateTue 16 Jul 2019, 14:29 (7 years ago) parenta88aeb0
1 file changed+105−51
terminal/src/Repl.hs+105−51
terminal/src/Repl.hs+105−51
@@ -161,18 +161,21 @@ data Input
161161
162162read :: Repl.InputT M Input
163163read =
164− do line <- Repl.getInputLine "> "
165− case line of
164+ do maybeLine <- Repl.getInputLine "> "
165+ case maybeLine of
166166 Nothing ->
167167 return Exit
168168
169− Just chars ->
170− case categorize chars of
169+ Just line ->
170+ let
171+ lines = Lines line []
172+ in
173+ case categorize lines of
171174 Done input -> return input
172− Continue -> readMore chars
175+ Continue -> readMore lines
173176
174177
175−readMore :: [Char] -> Repl.InputT M Input
178+readMore :: Lines -> Repl.InputT M Input
176179readMore previousLines =
177180 do input <- Repl.getInputLineWithInitial "| " (" ","")
178181 case input of
@@ -181,11 +184,55 @@ readMore previousLines =
181184
182185 Just chars ->
183186 let
184− allLines = previousLines ++ '\n' : chars
187+ lines = addLine chars previousLines
185188 in
186− case categorize allLines of
189+ case categorize lines of
187190 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+
189236
190237
191238
@@ -197,18 +244,18 @@ data CategorizedInput
197244 | Continue
198245
199246
200−categorize :: [Char] -> CategorizedInput
201−categorize chars
202− | all (==' ') chars = Done Skip
203− | startsWithColon chars = Done (toCommand chars)
204− | startsWithKeyword "import" chars = attemptImport chars
205− | otherwise = attemptDeclOrExpr chars
247+categorize :: Lines -> CategorizedInput
248+categorize lines
249+ | isBlank lines = Done Skip
250+ | startsWithColon lines = Done (toCommand lines)
251+ | startsWithKeyword "import" lines = attemptImport lines
252+ | otherwise = attemptDeclOrExpr lines
206253
207254
208−attemptImport :: [Char] -> CategorizedInput
209−attemptImport chars =
255+attemptImport :: Lines -> CategorizedInput
256+attemptImport lines =
210257 let
211− src = BS_UTF8.fromString (chars ++ "\n")
258+ src = linesToByteString lines
212259 parser = P.specialize (\_ _ _ -> ()) PM.chompImport
213260 in
214261 case P.fromByteString parser (\_ _ -> ()) src of
@@ -216,63 +263,67 @@ attemptImport chars =
216263 Done (Import name src)
217264
218265 Left () ->
219− if endsWithBlankLine chars
220− then Done (Import "ERR" src)
221− else Continue
266+ ifFail lines (Import "ERR" src)
267+
222268
269+ifFail :: Lines -> Input -> CategorizedInput
270+ifFail lines input =
271+ if endsWithBlankLine lines
272+ then Done input
273+ else Continue
223274
224−attemptDeclOrExpr :: [Char] -> CategorizedInput
225−attemptDeclOrExpr chars =
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+
283+attemptDeclOrExpr :: Lines -> CategorizedInput
284+attemptDeclOrExpr lines =
226285 let
227− src = BS_UTF8.fromString (chars ++ "\n")
286+ src = linesToByteString lines
228287 exprParser = P.specialize (toExprPosition src) PE.expression
229288 declParser = P.specialize (toDeclPosition src) PD.declaration
230289 in
231290 case P.fromByteString declParser (,) src of
232291 Right (decl, _) ->
233292 case decl of
234− PD.Value _ (A.At _ (Src.Value (A.At _ name) _ _ _)) -> Done (Decl name src)
235− PD.Union _ (A.At _ (Src.Union (A.At _ name) _ _ )) -> Done (Type name src)
236− PD.Alias _ (A.At _ (Src.Alias (A.At _ name) _ _ )) -> Done (Type name src)
293+ PD.Value _ (A.At _ (Src.Value (A.At _ name) _ _ _)) -> ifDone lines (Decl name src)
294+ PD.Union _ (A.At _ (Src.Union (A.At _ name) _ _ )) -> ifDone lines (Type name src)
295+ PD.Alias _ (A.At _ (Src.Alias (A.At _ name) _ _ )) -> ifDone lines (Type name src)
237296 PD.Port _ _ -> Done Port
238297
239298 Left declPosition
240− | startsWithKeyword "type" chars ->
241− if endsWithBlankLine chars
242− then Done (Type "ERR" src)
243− else Continue
299+ | startsWithKeyword "type" lines ->
300+ ifFail lines (Type "ERR" src)
244301
245− | startsWithKeyword "port" chars ->
302+ | startsWithKeyword "port" lines ->
246303 Done Port
247304
248305 | otherwise ->
249306 case P.fromByteString exprParser (,) src of
250307 Right _ ->
251− Done (Expr src)
308+ ifDone lines (Expr src)
252309
253310 Left exprPosition ->
254− if endsWithBlankLine chars
255− then Done (if declPosition <= exprPosition then Expr src else Decl "ERR" src)
256− else Continue
257−
311+ if exprPosition >= declPosition then
312+ ifFail lines (Expr src)
313+ else
314+ ifFail lines (Decl "ERR" src)
258315
259−endsWithBlankLine :: [Char] -> Bool
260−endsWithBlankLine chars =
261− case reverse (List.lines chars) of
262− [] -> False
263− cs:_ -> all (==' ') cs
264316
265−
266−startsWithColon :: [Char] -> Bool
267−startsWithColon chars =
268− case dropWhile (==' ') chars of
317+startsWithColon :: Lines -> Bool
318+startsWithColon lines =
319+ case dropWhile (==' ') (getFirstLine lines) of
269320 [] -> False
270321 c:_ -> c == ':'
271322
272323
273−toCommand :: [Char] -> Input
274−toCommand chars =
275− case drop 1 $ dropWhile (==' ') chars of
324+toCommand :: Lines -> Input
325+toCommand lines =
326+ case drop 1 $ dropWhile (==' ') (getFirstLine lines) of
276327 "reset" -> Reset
277328 "exit" -> Exit
278329 "quit" -> Exit
@@ -280,8 +331,11 @@ toCommand chars =
280331 rest -> Help (Just (takeWhile (/=' ') rest))
281332
282333
283−startsWithKeyword :: [Char] -> [Char] -> Bool
284−startsWithKeyword keyword line =
334+startsWithKeyword :: [Char] -> Lines -> Bool
335+startsWithKeyword keyword lines =
336+ let
337+ line = getFirstLine lines
338+ in
285339 List.isPrefixOf keyword line &&
286340 case drop (length keyword) line of
287341 [] -> True