]> gitweb.factorcode.org Git - factor.git/blob - basis/regexp/regexp.factor
regexp: don't use execute so the generated code is easier to read
[factor.git] / basis / regexp / regexp.factor
1 ! Copyright (C) 2008, 2009 Doug Coleman, Daniel Ehrenberg.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors arrays classes compiler.units kernel
4 kernel.private lexer make math ranges namespaces quotations
5 regexp.ast regexp.compiler regexp.negation regexp.parser
6 sequences sequences.private splitting strings vocabs.loader
7 words ;
8 IN: regexp
9
10 TUPLE: regexp
11     { raw read-only }
12     { parse-tree read-only }
13     { options read-only }
14     dfa next-match ;
15
16 TUPLE: reverse-regexp < regexp ;
17
18 <PRIVATE
19
20 M: lookahead question>quot
21     ! Returns ( index string -- ? )
22     term>> ast>dfa dfa>shortest-word 1quotation [ f ] prepose ;
23
24 : <reversed-option> ( ast -- reversed )
25     "r" string>options <with-options> ;
26
27 M: lookbehind question>quot
28     ! Returns ( index string -- ? )
29     term>> <reversed-option>
30     ast>dfa dfa>reverse-shortest-word
31     1quotation [ [ 1 - ] dip f ] prepose ;
32
33 : match-index-from ( i string regexp -- index/f )
34     ! This word is unsafe. It assumes that i is a fixnum
35     ! and that string is a string.
36     dup dfa>> execute( index string regexp -- i/f ) ; inline
37
38 GENERIC: end/start ( string regexp -- end start )
39 M: regexp end/start drop length 0 ;
40 M: reverse-regexp end/start drop length 1 - -1 swap ;
41
42 PRIVATE>
43
44 : matches? ( string regexp -- ? )
45     [ string check-instance ] dip
46     [ end/start ] 2keep
47     match-index-from
48     [ = ] [ drop f ] if* ;
49
50 <PRIVATE
51
52 : search-range ( i string reverse? -- seq )
53     [ drop -1 ] [ length ] if [a..b] ; inline
54
55 :: (next-match) ( i string regexp quot: ( i string regexp -- j ) reverse? -- start end ? )
56     i string regexp quot call dup
57     [| j | reverse? [ j i ] [ i j ] if string ] [ drop f f f ] if ; inline
58
59 :: next-match ( i string regexp quot: ( i string regexp -- j ) reverse? -- start end ? )
60     f f f
61     i string reverse? search-range
62     [ 3nip string regexp quot reverse? (next-match) dup ] find 2drop ; inline
63
64 : do-next-match ( i string regexp -- start end ? )
65     dup next-match>>
66     execute( i string regexp -- start end ? ) ; inline
67
68 :: (each-match-forward) ( ... i string regexp quot: ( ... start end string -- ... ) -- ... )
69     i string length <= [
70         i string regexp do-next-match [| start end |
71             start end string quot call
72             start end eq? [ end 1 +  ] [ end ] if
73             string regexp quot (each-match-forward)
74         ] [ 2drop ] if
75     ] when ; inline recursive
76
77 :: (each-match-backward) ( ... i string regexp quot: ( ... start end string -- ... ) -- ... )
78     i -1 >= [
79         i string regexp do-next-match [| start end |
80             start 1 + end 1 + string quot call
81             start end eq? [ start 1 - ] [ start ] if
82             string regexp quot (each-match-backward)
83         ] [ 2drop ] if
84     ] when ; inline recursive
85
86 : (each-match) ( ... i string regexp quot: ( ... start end string -- ... ) -- ... )
87     over reverse-regexp? [ (each-match-backward) ] [ (each-match-forward) ] if ; inline
88
89 GENERIC: match-iterator-start ( string regexp -- start )
90 M: regexp match-iterator-start 2drop 0 ;
91 M: reverse-regexp match-iterator-start drop length ;
92
93 : prepare-match-iterator ( string regexp -- i string regexp )
94     [ string check-instance ] dip [ match-iterator-start ] 2keep ; inline
95
96 PRIVATE>
97
98 : each-match ( ... string regexp quot: ( ... start end string -- ... ) -- ... )
99     [ prepare-match-iterator ] dip (each-match) ; inline
100
101 : map-matches ( ... string regexp quot: ( ... start end string -- ... obj ) -- ... seq )
102     collector [ each-match ] dip >array ; inline
103
104 : all-matching-slices ( string regexp -- seq )
105     [ <slice-unsafe> ] map-matches ;
106
107 : all-matching-subseqs ( string regexp -- seq )
108     [ subseq ] map-matches ;
109
110 : count-matches ( string regexp -- n )
111     [ 0 ] 2dip [ 3drop 1 + ] each-match ;
112
113 <PRIVATE
114
115 :: (re-split) ( string regexp quot: ( from to seq -- slice ) -- new-slices )
116     0 string regexp [| end start end' string |
117         end' ! leave it on the stack for the next iteration
118         end start string quot call
119     ] map-matches
120     ! Final chunk
121     swap string length string quot call suffix ; inline
122
123 PRIVATE>
124
125 :: first-match ( string regexp -- slice/f )
126     string regexp prepare-match-iterator do-next-match [
127         regexp reverse-regexp? [ [ 1 + ] bi@ ] when
128         string <slice-unsafe>
129     ] [ 2drop f ] if ;
130
131 : re-contains? ( string regexp -- ? )
132     prepare-match-iterator do-next-match 2nip >boolean ;
133
134 : re-split ( string regexp -- seq )
135     [ <slice-unsafe> ] (re-split) ;
136
137 : re-replace ( string regexp replacement -- result )
138     [ [ subseq ] (re-split) ] dip join ;
139
140 :: re-replace-with ( string regexp quot: ( slice -- replacement ) -- result )
141     [
142         0 string regexp [
143             drop [ [ string <slice-unsafe> , ] keep ] dip
144             [ string <slice-unsafe> quot call( x -- x ) , ] keep
145         ] each-match string [ length ] [ <slice-unsafe> ] bi ,
146     ] { } make concat ;
147
148 <PRIVATE
149
150 : get-ast ( regexp -- ast )
151     [ parse-tree>> ] [ options>> ] bi <with-options> ;
152
153 GENERIC: compile-regexp ( regex -- regexp )
154
155 : regexp-initial-word ( i string regexp -- i/f )
156     [ compile-regexp ] with-compilation-unit match-index-from ;
157
158 M: regexp compile-regexp
159     dup '[
160         dup \ regexp-initial-word =
161         [ drop _ get-ast ast>dfa dfa>word ] when
162     ] change-dfa ;
163
164 M: reverse-regexp compile-regexp
165     t backwards? [ call-next-method ] with-variable ;
166
167 DEFER: compile-next-match
168
169 : next-initial-word ( i string regexp -- start end string )
170     [ compile-next-match ] with-compilation-unit do-next-match ;
171
172 : compile-next-match ( regexp -- regexp )
173     dup '[
174         dup \ next-initial-word = [
175             drop _ [ compile-regexp dfa>> def>> ] [ reverse-regexp? ] bi
176             '[ { array-capacity string regexp } declare _ _ next-match ]
177             ( i string regexp -- start end string ) define-temp
178         ] when
179     ] change-next-match ;
180
181 PRIVATE>
182
183 : new-regexp ( string ast options class -- regexp )
184     [ \ regexp-initial-word \ next-initial-word ] dip boa ; inline
185
186 : make-regexp ( string ast -- regexp )
187     f f <options> regexp new-regexp ;
188
189 : <optioned-regexp> ( string options -- regexp )
190     [ dup parse-regexp ] [ string>options ] bi*
191     dup on>> reversed-regexp swap member?
192     [ reverse-regexp new-regexp ]
193     [ regexp new-regexp ] if ;
194
195 : <regexp> ( string -- regexp ) "" <optioned-regexp> ;
196
197 <PRIVATE
198
199 : take-until ( lexer -- string )
200     dup skip-blank [
201         dupd [
202             [ [ "\\/" member? ] find-from ] keep swap [
203                 CHAR: \ = [ [ 2 + ] dip t ] [ f ] if
204             ] [
205                 "Unterminated regexp" throw
206             ] if*
207         ] loop over [ subseq ] dip 1 +
208     ] change-lexer-column ;
209
210 : parse-noblank-token ( lexer -- str/f )
211     dup still-parsing-line? [ (parse-raw) ] [ drop f ] if ;
212
213 : parse-regexp ( accum -- accum )
214     lexer get [ take-until "\\/" "/" replace ] [ parse-noblank-token ] bi
215     <optioned-regexp> compile-next-match suffix! ;
216
217 PRIVATE>
218
219 SYNTAX: R/ parse-regexp ;
220
221
222 { "prettyprint" "regexp" } "regexp.prettyprint" require-when