1 ! Copyright (C) 2004 Chris Double.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: lists lists.lazy promises kernel sequences strings math
4 arrays splitting quotations combinators namespaces
5 unicode.case unicode.categories sequences.deep accessors ;
8 ! Parser combinator protocol
9 GENERIC: parse ( input parser -- list )
11 M: promise parse ( input parser -- list )
14 TUPLE: parse-result parsed unparsed ;
16 ERROR: cannot-parse input ;
18 : parse-1 ( input parser -- result )
25 C: <parse-result> parse-result
27 : <parse-results> ( parsed unparsed -- list )
28 <parse-result> 1list ;
30 : parse-result-parsed-slice ( parse-result -- slice )
32 unparsed>> 0 0 rot <slice>
35 dup from>> [ rot parsed>> length - ] keep
39 : string= ( str1 str2 ignore-case -- ? )
40 [ [ >upper ] bi@ ] when sequence= ;
42 : string-head? ( str head ignore-case -- ? )
46 [ [ length head-slice ] keep ] dip string=
49 : ?string-head ( str head ignore-case -- newstr ? )
50 [ 2dup ] dip string-head?
51 [ length tail-slice t ] [ drop f ] if ;
53 TUPLE: token-parser string ignore-case? ;
55 C: <token-parser> token-parser
57 : token ( string -- parser ) f <token-parser> ;
59 : case-insensitive-token ( string -- parser ) t <token-parser> ;
61 M: token-parser parse ( input parser -- list )
62 [ string>> ] [ ignore-case?>> ] bi
63 [ tuck ] dip ?string-head
64 [ <parse-results> ] [ 2drop nil ] if ;
66 : 1token ( n -- parser ) 1string token ;
68 TUPLE: satisfy-parser quot ;
70 C: satisfy satisfy-parser ( quot -- parser )
72 M: satisfy-parser parse ( input parser -- list )
73 #! A parser that succeeds if the predicate,
74 #! when passed the first character in the input, returns
79 quot>> [ unclip-slice dup ] dip call
80 [ swap <parse-results> ] [ 2drop nil ] if
83 LAZY: any-char-parser ( -- parser )
86 TUPLE: epsilon-parser ;
88 C: epsilon epsilon-parser ( -- parser )
90 M: epsilon-parser parse ( input parser -- list )
91 #! A parser that parses the empty string. It
92 #! does not consume any input and always returns
93 #! an empty list as the parse tree with the
95 drop "" swap <parse-results> ;
97 TUPLE: succeed-parser result ;
99 C: succeed succeed-parser ( result -- parser )
101 M: succeed-parser parse ( input parser -- list )
102 #! A parser that always returns 'result' as a
103 #! successful parse with no input consumed.
104 result>> swap <parse-results> ;
108 C: fail fail-parser ( -- parser )
110 M: fail-parser parse ( input parser -- list )
111 #! A parser that always fails and returns
112 #! an empty list of successes.
115 TUPLE: ensure-parser test ;
117 : ensure ( parser -- ensure )
120 M: ensure-parser parse ( input parser -- list )
121 2dup test>> parse nil?
122 [ 2drop nil ] [ drop t swap <parse-results> ] if ;
124 TUPLE: ensure-not-parser test ;
126 : ensure-not ( parser -- ensure )
127 ensure-not-parser boa ;
129 M: ensure-not-parser parse ( input parser -- list )
130 2dup test>> parse nil?
131 [ drop t swap <parse-results> ] [ 2drop nil ] if ;
133 TUPLE: and-parser parsers ;
135 : <&> ( parser1 parser2 -- parser )
137 [ parsers>> ] dip suffix
140 ] if and-parser boa ;
142 : <and-parser> ( parsers -- parser )
143 dup length 1 = [ first ] [ and-parser boa ] if ;
145 : and-parser-parse ( list p1 -- list )
147 dup unparsed>> rot parse
150 [ parsed>> 2array ] keep
151 unparsed>> <parse-result>
153 ] lazy-map-with lconcat ;
155 M: and-parser parse ( input parser -- list )
156 #! Parse 'input' by sequentially combining the
157 #! two parsers. First parser1 is applied to the
158 #! input then parser2 is applied to the rest of
159 #! the input strings from the first parser.
160 parsers>> unclip swapd parse
161 [ [ and-parser-parse ] reduce ] 2curry promise ;
163 TUPLE: or-parser parsers ;
165 : <or-parser> ( parsers -- parser )
166 dup length 1 = [ first ] [ or-parser boa ] if ;
168 : <|> ( parser1 parser2 -- parser )
171 M: or-parser parse ( input parser1 -- list )
172 #! Return the combined list resulting from the parses
173 #! of parser1 and parser2 being applied to the same
174 #! input. This implements the choice parsing operator.
175 parsers>> 0 swap seq>list
176 [ parse ] lazy-map-with lconcat ;
178 : trim-head-slice ( string -- string )
179 #! Return a new string without any leading whitespace
180 #! from the original string.
182 dup first blank? [ rest-slice trim-head-slice ] when
185 TUPLE: sp-parser p1 ;
187 #! Return a parser that first skips all whitespace before
188 #! calling the original parser.
189 C: sp sp-parser ( p1 -- parser )
191 M: sp-parser parse ( input parser -- list )
192 #! Skip all leading whitespace from the input then call
193 #! the parser on the remaining input.
194 [ trim-head-slice ] dip p1>> parse ;
196 TUPLE: just-parser p1 ;
198 C: just just-parser ( p1 -- parser )
200 M: just-parser parse ( input parser -- result )
201 #! Calls the given parser on the input removes
202 #! from the results anything where the remaining
203 #! input to be parsed is not empty. So ensures a
204 #! fully parsed input string.
205 p1>> parse [ unparsed>> empty? ] lfilter ;
207 TUPLE: apply-parser p1 quot ;
209 C: <@ apply-parser ( parser quot -- parser )
211 M: apply-parser parse ( input parser -- result )
212 #! Calls the parser on the input. For each successful
213 #! parse the quot is call with the parse result on the stack.
214 #! The result of that quotation then becomes the new parse result.
215 #! This allows modification of parse tree results (like
216 #! converting strings to integers, etc).
217 [ p1>> ] [ quot>> ] bi
219 [ parsed>> swap call ] keep
220 unparsed>> <parse-result>
223 TUPLE: some-parser p1 ;
225 C: some some-parser ( p1 -- parser )
227 M: some-parser parse ( input parser -- result )
228 #! Calls the parser on the input, guarantees
229 #! the parse is complete (the remaining input is empty),
230 #! picks the first solution and only returns the parse
231 #! tree since the remaining input is empty.
234 : <& ( parser1 parser2 -- parser )
235 #! Same as <&> except discard the results of the second parser.
238 : &> ( parser1 parser2 -- parser )
239 #! Same as <&> except discard the results of the first parser.
242 : <:&> ( parser1 parser2 -- result )
243 #! Same as <&> except flatten the result.
244 <&> [ first2 suffix ] <@ ;
246 : <&:> ( parser1 parser2 -- result )
247 #! Same as <&> except flatten the result.
248 <&> [ first2 swap prefix ] <@ ;
250 : <:&:> ( parser1 parser2 -- result )
251 #! Same as <&> except flatten the result.
252 <&> [ first2 append ] <@ ;
254 LAZY: <*> ( parser -- parser )
255 dup <*> <&:> { } succeed <|> ;
257 : <+> ( parser -- parser )
258 #! Return a parser that accepts one or more occurences of the original
262 LAZY: <?> ( parser -- parser )
263 #! Return a parser that optionally uses the parser
264 #! if that parser would be successful.
265 [ 1array ] <@ f succeed <|> ;
267 TUPLE: only-first-parser p1 ;
269 LAZY: only-first ( parser -- parser )
270 only-first-parser boa ;
272 M: only-first-parser parse ( input parser -- list )
273 #! Transform a parser into a parser that only yields
274 #! the first possibility.
275 p1>> parse 1 swap ltake ;
277 LAZY: <!*> ( parser -- parser )
278 #! Like <*> but only return one possible result
279 #! containing all matching parses. Does not return
280 #! partial matches. Useful for efficiency since that's
281 #! usually the effect you want and cuts down on backtracking
285 LAZY: <!+> ( parser -- parser )
286 #! Like <+> but only return one possible result
287 #! containing all matching parses. Does not return
288 #! partial matches. Useful for efficiency since that's
289 #! usually the effect you want and cuts down on backtracking
293 LAZY: <!?> ( parser -- parser )
294 #! Like <?> but only return one possible result
295 #! containing all matching parses. Does not return
296 #! partial matches. Useful for efficiency since that's
297 #! usually the effect you want and cuts down on backtracking
301 LAZY: <(?)> ( parser -- parser )
302 #! Like <?> but take shortest match first.
303 f succeed swap [ 1array ] <@ <|> ;
305 LAZY: <(*)> ( parser -- parser )
306 #! Like <*> but take shortest match first.
307 #! Implementation by Matthew Willis.
308 { } succeed swap dup <(*)> <&:> <|> ;
310 LAZY: <(+)> ( parser -- parser )
311 #! Like <+> but take shortest match first.
312 #! Implementation by Matthew Willis.
315 : pack ( close body open -- parser )
316 #! Parse a construct enclosed by two symbols,
317 #! given a parser for the opening symbol, the
318 #! closing symbol, and the body.
321 : nonempty-list-of ( items separator -- parser )
322 [ over &> <*> <&:> ] keep <?> tuck pack ;
324 : list-of ( items separator -- parser )
325 #! Given a parser for the separator and for the
326 #! items themselves, return a parser that parses
327 #! lists of those items. The parse tree is an
328 #! array of the parsed items.
329 nonempty-list-of { } succeed <|> ;
331 LAZY: surrounded-by ( parser start end -- parser' )
332 [ token ] bi@ swapd pack ;
334 : exactly-n ( parser n -- parser' )
335 swap <repetition> <and-parser> [ flatten ] <@ ;
337 : at-most-n ( parser n -- parser' )
342 -rot 1- at-most-n <|>
345 : at-least-n ( parser n -- parser' )
346 dupd exactly-n swap <*> <&> ;
348 : from-m-to-n ( parser m n -- parser' )
349 [ [ exactly-n ] 2keep ] dip swap - at-most-n <:&:> ;