@@ -119,6 +119,20 @@ choice : (xs : List Lexer) -> {auto ok : NonEmpty xs} -> Lexer
119119choice (x :: []) = x
120120choice (x :: xs@(_ :: _)) = x <|> choice xs
121121
122+||| Repeat the sub-lexer `l` zero or more times until the lexer
123+||| `stopBefore` is encountered. `stopBefore` will not be consumed.
124+||| Not guaranteed to consume input.
125+export
126+manyUntil : (stopBefore : Recognise c) -> (l : Lexer) -> Recognise False
127+manyUntil stopBefore l = many (reject stopBefore <+> l)
128+
129+||| Repeat the sub-lexer `l` zero or more times until the lexer
130+||| `stopAfter` is encountered, and consume it. Guaranteed to
131+||| consume if `stopAfter` consumes.
132+export
133+manyThen : (stopAfter : Recognise c) -> (l : Lexer) -> Recognise c
134+manyThen stopAfter l = manyUntil stopAfter l <+> stopAfter
135+
122136||| Recognise many instances of `l` until an instance of `end` is
123137||| encountered.
124138|||
@@ -126,6 +140,8 @@ choice (x :: xs@(_ :: _)) = x <|> choice xs
126140export
127141manyTill : (l : Lexer) -> (end : Lexer) -> Recognise False
128142manyTill l end = end <|> opt (l <+> manyTill l end)
143+%deprecate manyTill
144+ "Prefer `lineComment`, or `manyUntil`/`manyThen` (argument order is flipped)."
129145
130146||| Recognise any character
131147export
@@ -220,7 +236,7 @@ digits = some digit
220236||| Recognise a single hexidecimal digit
221237export
222238hexDigit : Lexer
223−hexDigit = digit <|> oneOf "abcdefABCDEF"
239+hexDigit = pred isHexDigit
224240
225241||| Recognise one or more hexidecimal digits
226242export
@@ -277,6 +293,17 @@ export
277293spaces : Lexer
278294spaces = some space
279295
296+||| Recognise a single newline sequence. Understands CRLF, CR, and LF
297+export
298+newline : Lexer
299+newline = let crlf = "\r\n" in
300+ exact crlf <|> oneOf crlf
301+
302+||| Recognise one or more newline sequences. Understands CRLF, CR, and LF
303+export
304+newlines : Lexer
305+newlines = some newline
306+
280307||| Recognise a single non-whitespace, non-alphanumeric character
281308export
282309symbol : Lexer
@@ -291,7 +318,7 @@ symbols = some symbol
291318||| delimiting lexers
292319export
293320surround : (start : Lexer) -> (end : Lexer) -> (l : Lexer) -> Lexer
294−surround start end l = start <+> manyTill l end
321+surround start end l = start <+> manyThen end l
295322
296323||| Recognise zero or more occurrences of a sub-lexer surrounded
297324||| by the same quote lexer on both sides (useful for strings)
@@ -325,7 +352,26 @@ intLit = opt (is '-') <+> digits
325352||| Recognise a hexidecimal literal, prefixed by "0x" or "0X"
326353export
327354hexLit : Lexer
328−hexLit = is '0' <+> oneOf "xX" <+> hexDigits
355+hexLit = approx "0x" <+> hexDigits
356+
357+||| Recognise `start`, then recognise all input until a newline is encountered,
358+||| and consume the newline. Will succeed if end-of-input is encountered before
359+||| a newline.
360+export
361+lineComment : (start : Lexer) -> Lexer
362+lineComment start = start <+> manyUntil newline any <+> opt newline
363+
364+||| Recognise all input between `start` and `end` lexers.
365+||| Supports balanced nesting.
366+|||
367+||| For block comments that don't support nesting (such as C-style comments),
368+||| use `surround`.
369+export
370+blockComment : (start : Lexer) -> (end : Lexer) -> Lexer
371+blockComment start end = start <+> middle <+> end
372+ where
373+ middle : Recognise False
374+ middle = manyUntil end (blockComment start end <|> any)
329375
330376||| A mapping from lexers to the tokens they produce.
331377||| This is a list of pairs `(Lexer, String -> tokenType)`