Add failing unit test for regexp
[factor/jcg.git] / basis / regexp / traversal / traversal.factor
blobd8c25eda18ffcea56cc2a0a759c7d48f20fb3747
1 ! Copyright (C) 2008 Doug Coleman.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors assocs combinators kernel math
4 quotations sequences regexp.parser regexp.classes fry arrays
5 combinators.short-circuit regexp.utils prettyprint regexp.nfa
6 shuffle ;
7 IN: regexp.traversal
9 TUPLE: dfa-traverser
10     dfa-table
11     traversal-flags
12     traverse-forward
13     lookahead-counters
14     lookbehind-counters
15     capture-counters
16     captured-groups
17     capture-group-index
18     last-state current-state
19     text
20     match-failed?
21     start-index current-index
22     matches ;
24 : <dfa-traverser> ( text regexp -- match )
25     [ dfa-table>> ] [ dfa-traversal-flags>> ] bi
26     dfa-traverser new
27         swap >>traversal-flags
28         swap [ start-state>> >>current-state ] [ >>dfa-table ] bi
29         swap >>text
30         t >>traverse-forward
31         0 >>start-index
32         0 >>current-index
33         0 >>capture-group-index
34         V{ } clone >>matches
35         V{ } clone >>capture-counters
36         V{ } clone >>lookbehind-counters
37         V{ } clone >>lookahead-counters
38         H{ } clone >>captured-groups ;
40 : final-state? ( dfa-traverser -- ? )
41     [ current-state>> ]
42     [ dfa-table>> final-states>> ] bi key? ;
44 : beginning-of-text? ( dfa-traverser -- ? )
45     current-index>> 0 <= ; inline
47 : end-of-text? ( dfa-traverser -- ? )
48     [ current-index>> ] [ text>> length ] bi >= ; inline
50 : text-finished? ( dfa-traverser -- ? )
51     {
52         [ current-state>> empty? ]
53         [ end-of-text? ]
54         [ match-failed?>> ]
55     } 1|| ;
57 : save-final-state ( dfa-straverser -- )
58     [ current-index>> ] [ matches>> ] bi push ;
60 : match-done? ( dfa-traverser -- ? )
61     dup final-state? [
62         dup save-final-state
63     ] when text-finished? ;
65 : previous-text-character ( dfa-traverser -- ch )
66     [ text>> ] [ current-index>> 1- ] bi nth ;
68 : current-text-character ( dfa-traverser -- ch )
69     [ text>> ] [ current-index>> ] bi nth ;
71 : next-text-character ( dfa-traverser -- ch )
72     [ text>> ] [ current-index>> 1+ ] bi nth ;
74 GENERIC: flag-action ( dfa-traverser flag -- )
77 M: beginning-of-input flag-action ( dfa-traverser flag -- )
78     drop
79     dup beginning-of-text? [ t >>match-failed? ] unless drop ;
81 M: end-of-input flag-action ( dfa-traverser flag -- )
82     drop
83     dup end-of-text? [ t >>match-failed? ] unless drop ;
86 M: beginning-of-line flag-action ( dfa-traverser flag -- )
87     drop
88     dup {
89         [ beginning-of-text? ]
90         [ previous-text-character terminator-class class-member? ]
91     } 1|| [ t >>match-failed? ] unless drop ;
93 M: end-of-line flag-action ( dfa-traverser flag -- )
94     drop
95     dup {
96         [ end-of-text? ]
97         [ next-text-character terminator-class class-member? ]
98     } 1|| [ t >>match-failed? ] unless drop ;
101 M: word-boundary flag-action ( dfa-traverser flag -- )
102     drop
103     dup {
104         [ end-of-text? ]
105         [ current-text-character terminator-class class-member? ]
106     } 1|| [ t >>match-failed? ] unless drop ;
109 M: lookahead-on flag-action ( dfa-traverser flag -- )
110     drop
111     lookahead-counters>> 0 swap push ;
113 M: lookahead-off flag-action ( dfa-traverser flag -- )
114     drop
115     dup lookahead-counters>>
116     [ drop ] [ pop '[ _ - ] change-current-index drop ] if-empty ;
118 M: lookbehind-on flag-action ( dfa-traverser flag -- )
119     drop
120     f >>traverse-forward
121     [ 2 - ] change-current-index
122     lookbehind-counters>> 0 swap push ;
124 M: lookbehind-off flag-action ( dfa-traverser flag -- )
125     drop
126     t >>traverse-forward
127     dup lookbehind-counters>>
128     [ drop ] [ pop '[ _ + 2 + ] change-current-index drop ] if-empty ;
130 M: capture-group-on flag-action ( dfa-traverser flag -- )
131     drop
132     [ current-index>> 0 2array ]
133     [ capture-counters>> ] bi push ;
135 M: capture-group-off flag-action ( dfa-traverser flag -- )
136     drop
137     dup capture-counters>> empty? [
138         drop
139     ] [
140         {
141             [ capture-counters>> pop first2 dupd + ]
142             [ text>> <slice> ]
143             [ [ 1+ ] change-capture-group-index capture-group-index>> ]
144             [ captured-groups>> set-at ]
145         } cleave
146     ] if ;
148 : process-flags ( dfa-traverser -- )
149     [ [ 1+ ] map ] change-lookahead-counters
150     [ [ 1+ ] map ] change-lookbehind-counters
151     [ [ first2 1+ 2array ] map ] change-capture-counters
152     ! dup current-state>> .
153     dup [ current-state>> ] [ traversal-flags>> ] bi
154     at [ flag-action ] with each ;
156 : increment-state ( dfa-traverser state -- dfa-traverser )
157     [
158         dup traverse-forward>>
159         [ [ 1+ ] change-current-index ]
160         [ [ 1- ] change-current-index ] if
161         dup current-state>> >>last-state
162     ] [ first ] bi* >>current-state ;
164 : match-literal ( transition from-state table -- to-state/f )
165     transitions>> at at ;
167 : match-class ( transition from-state table -- to-state/f )
168     transitions>> at* [
169         [ drop class-member? ] assoc-with assoc-find [ nip ] [ drop ] if
170     ] [ drop ] if ;
172 : match-default ( transition from-state table -- to-state/f )
173     nipd transitions>> at t swap at ;
175 : match-transition ( obj from-state dfa -- to-state/f )
176     { [ match-literal ] [ match-class ] [ match-default ] } 3|| ;
178 : setup-match ( match -- obj state dfa-table )
179     [ [ current-index>> ] [ text>> ] bi nth ]
180     [ current-state>> ]
181     [ dfa-table>> ] tri ;
183 : do-match ( dfa-traverser -- dfa-traverser )
184     dup process-flags
185     dup match-done? [
186         dup setup-match match-transition
187         [ increment-state do-match ] when*
188     ] unless ;
190 : return-match ( dfa-traverser -- slice/f )
191     dup matches>>
192     [ drop f ]
193     [
194         [ [ text>> ] [ start-index>> ] bi ]
195         [ peek ] bi* rot <slice>
196     ] if-empty ;