@@ -1,129 +1,103 @@
1module Text.Lexer
1module Text.Lexer
5+import public Text.Lexer.Core
6+import public Text.Quantity
3%default total
8%default total
11+||| Recognise any character
13+any = pred (const True)
15+||| Recognise a lexer or recognise no input. This is not guaranteed
17+opt : Lexer -> Recognise False
20+||| Recognise any character if the sub-lexer `l` fails.
21+non : (l : Lexer) -> Lexer
22+non l = reject l <+> any
5−||| A language of token recognisers.
24+||| Produce recognisers by applying a function to elements of a container, and
6−||| @ consumes If `True`, this recogniser is guaranteed to consume at
25+||| recognise the first match. Consumes input if the function produces consuming
7−||| least one character of input when it succeeds.
26+||| recognisers. Fails if the container is empty.
8−export
27+choiceMap : {c : Bool} ->
9−data Recognise : (consumes : Bool) -> Type where
10− Empty : Recognise False
11− Fail : Recognise c
28+ Foldable t => (a -> Recognise c) -> t a -> Recognise c
12− Lookahead : (positive : Bool) -> Recognise c -> Recognise False
29+choiceMap {c} f xs = foldr (\x, acc => rewrite sym (andSameNeutral c) in
13− Pred : (Char -> Bool) -> Recognise True
30+ f x <|> acc)
14− SeqEat : Recognise True -> Inf (Recognise e) -> Recognise True
31+ fail xs
15− SeqEmpty : Recognise e1 -> Recognise e2 -> Recognise (e1 || e2)
32+
16− Alt : Recognise e1 -> Recognise e2 -> Recognise (e1 && e2)
33+||| Recognise the first matching recogniser in a container. Consumes input if
17−
34+||| recognisers in the list consume. Fails if the container is empty.
18−||| A token recogniser. Guaranteed to consume at least one character.
35+choice : Foldable t => t (Recognise c) -> Recognise c
19−public export
36+choice = choiceMap id
21−Lexer = Recognise True
38+||| Sequence a list of recognisers. Guaranteed to consume input if the list is
22−
39+||| non-empty and the recognisers consume.
24−inf : Bool -> Type -> Type
28−||| Sequence two recognisers. If either consumes a character, the sequence
29−||| is guaranteed to consume a character.
31−(<+>) : {c1 : Bool} ->
32− Recognise c1 -> inf c1 (Recognise c2) -> Recognise (c1 || c2)
33−(<+>) {c1 = False} = SeqEmpty
34−(<+>) {c1 = True} = SeqEat
36−||| Alternative recognisers. If both consume, the combination is guaranteed
37−||| to consumer a character.
39−(<|>) : Recognise c1 -> Recognise c2 -> Recognise (c1 && c2)
42−||| A recogniser that always fails.
47−||| Positive lookahead. Never consumes input.
49−expect : Recognise c -> Recognise False
40+concat : (xs : List (Recognise c)) -> Recognise (c && isCons xs)
50−expect = Lookahead True
41+concat = concatMap id
52−||| Negative lookahead. Never consumes input.
54−reject : Recognise c -> Recognise False
55−reject = Lookahead False
57−||| Recognise a specific character
43+||| Recognise a specific character.
59is : Char -> Lexer
44is : Char -> Lexer
60−is x = Pred (==x)
45+is x = pred (==x)
62−||| Recognise anything but the given character
47+||| Recognise anything but the given character.
64isNot : Char -> Lexer
48isNot : Char -> Lexer
65−isNot x = Pred (/=x)
49+isNot x = pred (/=x)
67−||| Recognise a character case-insensitively
51+||| Recognise a specific character (case-insensitive).
69like : Char -> Lexer
52like : Char -> Lexer
70−like x = Pred (\y => toUpper x == toUpper y)
53+like x = pred (\y => toUpper x == toUpper y)
72−||| Recognise anything but the given character case-insensitively
55+||| Recognise anything but the given character (case-insensitive).
74notLike : Char -> Lexer
56notLike : Char -> Lexer
75−notLike x = Pred (\y => toUpper x /= toUpper y)
57+notLike x = pred (\y => toUpper x /= toUpper y)
77−||| Recognise a specific string
59+||| Recognise a specific string.
78−export
60+||| Fails if the string is empty.
79exact : String -> Lexer
61exact : String -> Lexer
80−exact str with (unpack str)
62+exact str = case unpack str of
81− exact str | [] = Fail -- Not allowed, Lexer has to consume
63+ [] => fail
82− exact str | (x :: xs)
64+ (x :: xs) => concatMap is (x :: xs)
83− = foldl SeqEmpty (is x) (map is xs)
85−||| Recognise a specific string case-insensitively
66+||| Recognise a specific string (case-insensitive).
86−export
67+||| Fails if the string is empty.
87approx : String -> Lexer
68approx : String -> Lexer
88−approx str with (unpack str)
69+approx str = case unpack str of
89− approx str | [] = Fail -- Not allowed, Lexer has to consume
70+ [] => fail
90− approx str | (x :: xs)
71+ (x :: xs) => concatMap like (x :: xs)
91− = foldl SeqEmpty (like x) (map like xs)
93−||| Recognise a lexer or recognise no input. This is not guaranteed
73+||| Recognise any of the characters in the given string
94−||| to consume input
74+oneOf : String -> Lexer
95−export
75+oneOf cs = pred (\x => x `elem` unpack cs)
96−opt : Lexer -> Recognise False
99−||| Recognise a sequence of at least one sub-lexers
77+||| Recognise a character range [`a`-`b`]. Also works in reverse!
100−export
78+range : (start : Char) -> (end : Char) -> Lexer
101−some : Lexer -> Lexer
79+range start end = pred (\x => (x >= min start end)
102−some l = l <+> opt (some l)
80+ && (x <= max start end))
104−||| Recognise a sequence of at zero or more sub-lexers. This is not
83+ ||| Recognise a sequence of at least one sub-lexers
105−||| guaranteed to consume input
84+ some : Lexer -> Lexer
106−export
85+ some l = l <+> many l
107−many : Lexer -> Recognise False
108−many l = opt (some l)
110−||| Recognise the first matching lexer from a Foldable. Always consumes input
87+ ||| Recognise a sequence of at zero or more sub-lexers. This is not
111−||| if one of the options succeeds. Fails if the foldable is empty.
88+ ||| guaranteed to consume input
112−export
89+ many : Lexer -> Recognise False
113−choice : Foldable t => t Lexer -> Lexer
90+ many l = opt (some l)
114−choice xs = foldr Alt Fail xs
116||| Repeat the sub-lexer `l` zero or more times until the lexer
92||| Repeat the sub-lexer `l` zero or more times until the lexer
117||| `stopBefore` is encountered. `stopBefore` will not be consumed.
93||| `stopBefore` is encountered. `stopBefore` will not be consumed.
118||| Not guaranteed to consume input.
94||| Not guaranteed to consume input.
120manyUntil : (stopBefore : Recognise c) -> (l : Lexer) -> Recognise False
95manyUntil : (stopBefore : Recognise c) -> (l : Lexer) -> Recognise False
121manyUntil stopBefore l = many (reject stopBefore <+> l)
96manyUntil stopBefore l = many (reject stopBefore <+> l)
123||| Repeat the sub-lexer `l` zero or more times until the lexer
98||| Repeat the sub-lexer `l` zero or more times until the lexer
124||| `stopAfter` is encountered, and consume it. Guaranteed to
99||| `stopAfter` is encountered, and consume it. Guaranteed to
125||| consume if `stopAfter` consumes.
100||| consume if `stopAfter` consumes.
127manyThen : (stopAfter : Recognise c) -> (l : Lexer) -> Recognise c
101manyThen : (stopAfter : Recognise c) -> (l : Lexer) -> Recognise c
128manyThen stopAfter l = manyUntil stopAfter l <+> stopAfter
102manyThen stopAfter l = manyUntil stopAfter l <+> stopAfter
@@ -131,255 +105,130 @@ manyThen stopAfter l = manyUntil stopAfter l <+> stopAfter
131||| encountered.
105||| encountered.
133||| Useful for defining comments.
107||| Useful for defining comments.
135manyTill : (l : Lexer) -> (end : Lexer) -> Recognise False
108manyTill : (l : Lexer) -> (end : Lexer) -> Recognise False
136manyTill l end = end <|> opt (l <+> manyTill l end)
109manyTill l end = end <|> opt (l <+> manyTill l end)
137%deprecate manyTill
110%deprecate manyTill
138 "Prefer `lineComment`, or `manyUntil`/`manyThen` (argument order is flipped)."
111 "Prefer `lineComment`, or `manyUntil`/`manyThen` (argument order is flipped)."
140−||| Recognise a sub-lexer at least `min` times. Consumes input unless
113+||| Recognise a sub-lexer repeated as specified by `q`. Fails if `q` has
141−||| min is zero.
114+||| `min` and `max` in the wrong order. Consumes input unless `min q` is zero.
142−export
115+count : (q : Quantity) -> (l : Lexer) -> Recognise (isSucc (min q))
143−atLeast : (min : Nat) -> (l : Lexer) -> Recognise (min > 0)
116+count (Qty Z Nothing) l = many l
144−atLeast Z l = many l
117+count (Qty Z (Just Z)) _ = empty
145−atLeast (S min) l = l <+> atLeast min l
118+count (Qty Z (Just (S max))) l = opt $ l <+> count (atMost max) l
146−
119+count (Qty (S min) Nothing) l = l <+> count (atLeast min) l
147−||| Recognise a sub-lexer at most `max` times. Not guaranteed to
120+count (Qty (S min) (Just Z)) _ = fail
148−||| consume input.
121+count (Qty (S min) (Just (S max))) l = l <+> count (between min max) l
150−atMost : (max : Nat) -> (l : Lexer) -> Recognise False
152−atMost (S k) l = atMost k l <+> opt l
154−||| Recognise a sub-lexer repeated between `min` and `max` times. Fails
155−||| if the inputs are out of order. Consumes input unless min is zero.
157−between : (min : Nat) -> (max : Nat) -> (l : Lexer) -> Recognise (min > 0)
158−between Z max l = atMost max l
159−between (S min) Z _ = Fail
160−between (S min) (S max) l = l <+> between min max l
162−||| Recognise exactly `count` repeated occurrences of a sub-lexer.
163−||| Consumes input unless count is zero.
165−exactly : (count : Nat) -> (l : Lexer) -> Recognise (count > 0)
166−exactly n l = between n n l
168−||| Recognise any character
171−any = Pred (const True)
173−||| Recognise any character if the sub-lexer `l` fails.
175−non : (l : Lexer) -> Lexer
176−non l = reject l <+> any
178−||| Recognise no input (doesn't consume any input)
180−empty : Recognise False
183−||| Recognise a character that matches a predicate
185−pred : (Char -> Bool) -> Lexer
188−||| Recognise any of the characters in the given string
190−oneOf : String -> Lexer
191−oneOf cs = pred (\x => x `elem` unpack cs)
193−data StrLen : Type where
194− MkStrLen : String -> Nat -> StrLen
196−getString : StrLen -> String
197−getString (MkStrLen str n) = str
199−strIndex : StrLen -> Nat -> Maybe Char
200−strIndex (MkStrLen str len) i
201− = if i >= len then Nothing
202− else Just (assert_total (prim__strIndex str (cast i)))
204−mkStr : String -> StrLen
205−mkStr str = MkStrLen str (length str)
207−strTail : Nat -> StrLen -> StrLen
208−strTail start (MkStrLen str len)
209− = MkStrLen (substr start len str) (minus len start)
211−-- If the string is recognised, returns the index at which the token
213−scan : Recognise c -> Nat -> StrLen -> Maybe Nat
214−scan Empty idx str = pure idx
215−scan Fail idx str = Nothing
216−scan (Lookahead positive r) idx str
217− = if isJust (scan r idx str) == positive
220−scan (Pred f) idx str
221− = do c <- strIndex str idx
225−scan (SeqEat r1 r2) idx str
226− = do idx' <- scan r1 idx str
227− -- TODO: Can we prove totality instead by showing idx has increased?
228− assert_total (scan r2 idx' str)
229−scan (SeqEmpty r1 r2) idx str
230− = do idx' <- scan r1 idx str
232−scan (Alt r1 r2) idx str
233− = case scan r1 idx str of
234− Nothing => scan r2 idx str
235− Just idx => Just idx
237−takeToken : Lexer -> StrLen -> Maybe (String, StrLen)
239− = do i <- scan lex 0 str -- i must be > 0 if successful
240− pure (substr 0 i (getString str), strTail i str)
242−||| Recognise a character range [`a`-`b`]. Also works in reverse!
244−range : (start : Char) -> (end : Char) -> Lexer
245−range start end = pred (\x => (x >= min start end)
246− && (x <= max start end))
248||| Recognise a single digit 0-9
123||| Recognise a single digit 0-9
250digit : Lexer
124digit : Lexer
251digit = pred isDigit
125digit = pred isDigit
253||| Recognise one or more digits
127||| Recognise one or more digits
255digits : Lexer
128digits : Lexer
256digits = some digit
129digits = some digit
258||| Recognise a single hexidecimal digit
131||| Recognise a single hexidecimal digit
260hexDigit : Lexer
132hexDigit : Lexer
261hexDigit = pred isHexDigit
133hexDigit = pred isHexDigit
263||| Recognise one or more hexidecimal digits
135||| Recognise one or more hexidecimal digits
265hexDigits : Lexer
136hexDigits : Lexer
266hexDigits = some hexDigit
137hexDigits = some hexDigit
268||| Recognise a single alpha character
139||| Recognise a single alpha character
270alpha : Lexer
140alpha : Lexer
271alpha = pred isAlpha
141alpha = pred isAlpha
273||| Recognise one or more alpha characters
143||| Recognise one or more alpha characters
275alphas : Lexer
144alphas : Lexer
276alphas = some alpha
145alphas = some alpha
278||| Recognise a lowercase alpha character
147||| Recognise a lowercase alpha character
280lower : Lexer
148lower : Lexer
281lower = pred isLower
149lower = pred isLower
283||| Recognise one or more lowercase alpha characters
151||| Recognise one or more lowercase alpha characters
285lowers : Lexer
152lowers : Lexer
286lowers = some lower
153lowers = some lower
288||| Recognise an uppercase alpha character
155||| Recognise an uppercase alpha character
290upper : Lexer
156upper : Lexer
291upper = pred isUpper
157upper = pred isUpper
293||| Recognise one or more uppercase alpha characters
159||| Recognise one or more uppercase alpha characters
295uppers : Lexer
160uppers : Lexer
296uppers = some upper
161uppers = some upper
298||| Recognise an alphanumeric character
163||| Recognise an alphanumeric character
300alphaNum : Lexer
164alphaNum : Lexer
301alphaNum = pred isAlphaNum
165alphaNum = pred isAlphaNum
303||| Recognise one or more alphanumeric characters
167||| Recognise one or more alphanumeric characters
305alphaNums : Lexer
168alphaNums : Lexer
306alphaNums = some alphaNum
169alphaNums = some alphaNum
308||| Recognise a single whitespace character
171||| Recognise a single whitespace character
310space : Lexer
172space : Lexer
311space = pred isSpace
173space = pred isSpace
313||| Recognise one or more whitespace characters
175||| Recognise one or more whitespace characters
315spaces : Lexer
176spaces : Lexer
316spaces = some space
177spaces = some space
318||| Recognise a single newline sequence. Understands CRLF, CR, and LF
179||| Recognise a single newline sequence. Understands CRLF, CR, and LF
320newline : Lexer
180newline : Lexer
321newline = let crlf = "\r\n" in
181newline = let crlf = "\r\n" in
322 exact crlf <|> oneOf crlf
182 exact crlf <|> oneOf crlf
324||| Recognise one or more newline sequences. Understands CRLF, CR, and LF
184||| Recognise one or more newline sequences. Understands CRLF, CR, and LF
326newlines : Lexer
185newlines : Lexer
327newlines = some newline
186newlines = some newline
329||| Recognise a single non-whitespace, non-alphanumeric character
188||| Recognise a single non-whitespace, non-alphanumeric character
331symbol : Lexer
189symbol : Lexer
332symbol = pred (\x => not (isSpace x || isAlphaNum x))
190symbol = pred (\x => not (isSpace x || isAlphaNum x))
334||| Recognise one or more non-whitespace, non-alphanumeric characters
192||| Recognise one or more non-whitespace, non-alphanumeric characters
336symbols : Lexer
193symbols : Lexer
337symbols = some symbol
194symbols = some symbol
339||| Recognise zero or more occurrences of a sub-lexer between
196||| Recognise zero or more occurrences of a sub-lexer between
340||| delimiting lexers
197||| delimiting lexers
342surround : (start : Lexer) -> (end : Lexer) -> (l : Lexer) -> Lexer
198surround : (start : Lexer) -> (end : Lexer) -> (l : Lexer) -> Lexer
343surround start end l = start <+> manyThen end l
199surround start end l = start <+> manyThen end l
345||| Recognise zero or more occurrences of a sub-lexer surrounded
201||| Recognise zero or more occurrences of a sub-lexer surrounded
346||| by the same quote lexer on both sides (useful for strings)
202||| by the same quote lexer on both sides (useful for strings)
348quote : (q : Lexer) -> (l : Lexer) -> Lexer
203quote : (q : Lexer) -> (l : Lexer) -> Lexer
349quote q l = surround q q l
204quote q l = surround q q l
351||| Recognise an escape character (often '\\') followed by a sub-lexer
206||| Recognise an escape character (often '\\') followed by a sub-lexer
353escape : (esc : Char) -> Lexer -> Lexer
207escape : (esc : Char) -> Lexer -> Lexer
354escape esc l = is esc <+> l
208escape esc l = is esc <+> l
356||| Recognise a string literal, including escaped characters.
210||| Recognise a string literal, including escaped characters.
357||| (Note: doesn't yet handle escape sequences such as \123)
211||| (Note: doesn't yet handle escape sequences such as \123)
359stringLit : Lexer
212stringLit : Lexer
360stringLit = quote (is '"') (escape '\\' any <|> any)
213stringLit = quote (is '"') (escape '\\' any <|> any)
362||| Recognise a character literal, including escaped characters.
215||| Recognise a character literal, including escaped characters.
363||| (Note: doesn't yet handle escape sequences such as \123)
216||| (Note: doesn't yet handle escape sequences such as \123)
365charLit : Lexer
217charLit : Lexer
366charLit = let q = '\'' in
218charLit = let q = '\'' in
367 is q <+> (escape '\\' any <|> isNot q) <+> is q
219 is q <+> (escape '\\' any <|> isNot q) <+> is q
369||| Recognise an integer literal (possibly with a '-' prefix)
221||| Recognise an integer literal (possibly with a '-' prefix)
371intLit : Lexer
222intLit : Lexer
372intLit = opt (is '-') <+> digits
223intLit = opt (is '-') <+> digits
374||| Recognise a hexidecimal literal, prefixed by "0x" or "0X"
225||| Recognise a hexidecimal literal, prefixed by "0x" or "0X"
376hexLit : Lexer
226hexLit : Lexer
377hexLit = approx "0x" <+> hexDigits
227hexLit = approx "0x" <+> hexDigits
379||| Recognise `start`, then recognise all input until a newline is encountered,
229||| Recognise `start`, then recognise all input until a newline is encountered,
380||| and consume the newline. Will succeed if end-of-input is encountered before
230||| and consume the newline. Will succeed if end-of-input is encountered before
381||| a newline.
231||| a newline.
383lineComment : (start : Lexer) -> Lexer
232lineComment : (start : Lexer) -> Lexer
384lineComment start = start <+> manyUntil newline any <+> opt newline
233lineComment start = start <+> manyUntil newline any <+> opt newline
@@ -388,78 +237,8 @@ lineComment start = start <+> manyUntil newline any <+> opt newline
389||| For block comments that don't support nesting (such as C-style comments),
238||| For block comments that don't support nesting (such as C-style comments),
390||| use `surround`.
239||| use `surround`.
392blockComment : (start : Lexer) -> (end : Lexer) -> Lexer
240blockComment : (start : Lexer) -> (end : Lexer) -> Lexer
393blockComment start end = start <+> middle <+> end
241blockComment start end = start <+> middle <+> end
395 middle : Recognise False
243 middle : Recognise False
396 middle = manyUntil end (blockComment start end <|> any)
244 middle = manyUntil end (blockComment start end <|> any)
398−||| A mapping from lexers to the tokens they produce.
399−||| This is a list of pairs `(Lexer, String -> tokenType)`
400−||| For each Lexer in the list, if a substring in the input matches, run
401−||| the associated function to produce a token of type `tokenType`
403−TokenMap : (tokenType : Type) -> Type
404−TokenMap tokenType = List (Lexer, String -> tokenType)
406−||| A token, and the line and column where it was in the input
408−record TokenData a where
414−fspanEnd : Nat -> (Char -> Bool) -> String -> (Nat, String)
415−fspanEnd k p "" = (k, "")
418− let x = prim__strHead xxs
419− xs = prim__strTail xxs in
420− if p x then fspanEnd (S k) p xs
423−-- Faster version of 'span' from the prelude (avoids unpacking)
425−fspan : (Char -> Bool) -> String -> (String, String)
427− = let (end, rest) = fspanEnd 0 p xs in
428− (substr 0 end xs, rest)
430−tokenise : (line : Int) -> (col : Int) ->
431− List (TokenData a) -> TokenMap a ->
432− StrLen -> (List (TokenData a), (Int, Int, StrLen))
433−tokenise line col acc tmap str
434− = case getFirstToken tmap str of
435− Just (tok, line', col', rest) =>
436− -- assert total because getFirstToken must consume something
437− assert_total (tokenise line' col' (tok :: acc) tmap rest)
438− Nothing => (reverse acc, (line, col, str))
440− countNLs : List Char -> Nat
441− countNLs str = List.length (filter (== '\n') str)
443− getCols : String -> Int -> Int
445− = case fspan (/= '\n') (reverse x) of
446− (incol, "") => c + cast (length incol)
447− (incol, _) => cast (length incol)
449− getFirstToken : TokenMap a -> StrLen -> Maybe (TokenData a, Int, Int, StrLen)
450− getFirstToken [] str = Nothing
451− getFirstToken ((lex, fn) :: ts) str
452− = case takeToken lex str of
453− Just (tok, rest) => Just (MkToken line col (fn tok),
454− line + cast (countNLs (unpack tok)),
455− getCols tok col, rest)
456− Nothing => getFirstToken ts str
458−||| Given a mapping from lexers to token generating functions (the
459−||| TokenMap a) and an input string, return a list of recognised tokens,
460−||| and the line, column, and remainder of the input at the first point in the
461−||| string where there are no recognised tokens.
463−lex : TokenMap a -> String -> (List (TokenData a), (Int, Int, String))
464−lex tmap str = let (ts, (l, c, str')) = tokenise 0 0 [] tmap (mkStr str) in
465− (ts, (l, c, getString str'))