]> gitweb.factorcode.org Git - factor.git/blob - basis/xmode/marker/marker.factor
xmode.marker: more correct faster update-match-group
[factor.git] / basis / xmode / marker / marker.factor
1 ! Copyright (C) 2008 Slava Pestov.
2 ! See http://factorcode.org/license.txt for BSD license.
3
4 USING: accessors ascii assocs combinators
5 combinators.short-circuit formatting kernel make math namespaces
6 regexp regexp.parser sequences splitting strings
7 xmode.marker.state xmode.rules xmode.tokens xmode.utilities ;
8
9 IN: xmode.marker
10
11 ! Next two words copied from parser-combinators
12 ! Just like head?, but they optionally ignore case
13
14 : string= ( str1 str2 ignore-case -- ? )
15     [ [ >upper ] bi@ ] when sequence= ;
16
17 : string-head? ( str1 str2 ignore-case -- ? )
18     2over shorter?
19     [ 3drop f ] [
20         [
21             [ nip ]
22             [ length head-slice ] 2bi
23         ] dip string=
24     ] if ;
25
26 ! Based on org.gjt.sp.jedit.syntax.TokenMarker
27
28 : current-keyword ( -- string )
29     last-offset get position get line get subseq ;
30
31 : keyword-number? ( keyword -- ? )
32     {
33         [ current-rule-set highlight-digits?>> ]
34         [ dup [ digit? ] any? ]
35         [
36             dup [ digit? ] all? [
37                 current-rule-set digit-re>>
38                 [ dupd matches? ] [ f ] if*
39             ] unless*
40         ]
41     } 0&& nip ;
42
43 : mark-number ( keyword -- id )
44     keyword-number? DIGIT and ;
45
46 : mark-keyword ( keyword -- id )
47     current-rule-set keywords>> at ;
48
49 : add-remaining-token ( -- )
50     current-rule-set default>> prev-token, ;
51
52 : mark-token ( -- )
53     current-keyword
54     dup mark-number [ ] [ mark-keyword ] ?if
55     [ prev-token, ] when* ;
56
57 : current-char ( -- char )
58     position get line get nth ;
59
60 GENERIC: match-position ( rule -- n )
61
62 M: mark-previous-rule match-position drop last-offset get ;
63
64 M: rule match-position drop position get ;
65
66 : can-match-here? ( matcher rule -- ? )
67     match-position {
68         [ over ]
69         [ over at-line-start?>>     over zero?                implies ]
70         [ over at-whitespace-end?>> over whitespace-end get = implies ]
71         [ over at-word-start?>>     over last-offset get =    implies ]
72     } 0&& 2nip ;
73
74 : rest-of-line ( -- str )
75     line get position get tail-slice ;
76
77 : match-start ( string regexp -- slice/f )
78     first-match dup [ dup from>> 0 = [ drop f ] unless ] when ;
79
80 GENERIC: text-matches? ( string text -- match-count/f )
81
82 M: f text-matches?
83     2drop f ;
84
85 M: string-matcher text-matches?
86     [ string>> ] [ ignore-case?>> ] bi
87     [ string-head? ] keepd length and ;
88
89 M: regexp text-matches?
90     [ >string ] dip match-start dup [ to>> ] when ;
91
92 <PRIVATE
93
94 ! XXX: Terrible inefficient regexp match group support
95
96 : #match-groups ( regexp -- n/f )
97     raw>> [ CHAR: ( = ] count [ f ] when-zero ;
98
99 : nth-index ( n obj seq -- i )
100     [ = dup [ drop 1 - dup 0 < ] when ] with find drop nip ;
101
102 : match-group-regexp ( regexp n -- skip-regexp match-regexp )
103     [ [ options>> options>string ] [ raw>> ] bi ] dip
104     CHAR: ( pick nth-index cut CHAR: ) over index 1 + head
105     rot '[ _ H{ } [ <optioned-regexp> ] 2cache ] bi@ ;
106
107 : skip-first-match ( match regexp -- tailseq )
108     first-match [ seq>> ] [ to>> ] bi tail ;
109
110 : nth-match ( match regexp n -- slice/f )
111     match-group-regexp [ skip-first-match ] [ first-match ] bi* ;
112
113 :: update-match-group ( str match regexp n -- str' )
114     n H{ } [ CHAR: 1 + CHAR: $ swap "" 2sequence ] cache :> x
115     x str subseq-range :> ( from to )
116     from [
117         to str snip-slice match regexp n nth-match glue
118     ] [ str ] if* ;
119
120 : update-match-groups ( str match regexp -- str' )
121     [ >string ] dip
122     dup #match-groups [ update-match-group ] 2with each-integer ;
123
124 GENERIC: fixup-end ( match regexp end -- end' )
125
126 M: string-matcher fixup-end
127     [ string>> -rot update-match-groups ]
128     [ ignore-case?>> ] bi <string-matcher> ;
129
130 MEMO: <fixup-regexp> ( raw matched options -- regexp )
131     <optioned-regexp> {
132         [ parse-tree>> ] [ options>> ] [ dfa>> ] [ next-match>> ]
133     } cleave regexp boa ;
134
135 M: regexp fixup-end
136     [ raw>> [ -rot update-match-groups ] keep swap ]
137     [ options>> options>string ] bi <fixup-regexp> ;
138
139 : fixup-end? ( text -- ? )
140     { [ regexp? ] [ #match-groups ] } 1&& ;
141
142 : fixup-end/text-matches? ( string regexp rule -- match-count/f )
143     [ >string ] 2dip [ [ match-start dup ] keep ] dip pick [
144         end>> [ [ fixup-end ] change-text drop ] [ 2drop ] if*
145     ] [
146         3drop
147     ] if dup [ to>> ] when ;
148
149 PRIVATE>
150
151 :: rule-start-matches? ( rule -- match-count/f )
152     rule start>> dup rule can-match-here? [
153         rest-of-line swap text>>
154         dup fixup-end? [
155             rule fixup-end/text-matches?
156         ] [
157             text-matches?
158         ] if
159     ] [
160         drop f
161     ] if ;
162
163 : rule-end-matches? ( rule -- match-count/f )
164     dup mark-following-rule? [
165         [ start>> ] keep can-match-here? 0 and
166     ] [
167         [ end>> dup ] keep can-match-here? [
168             rest-of-line
169             swap text>> context get end>> or
170             text-matches?
171         ] [
172             drop f
173         ] if
174     ] if ;
175
176 DEFER: get-rules
177
178 : get-always-rules ( ruleset -- vector/f )
179     f swap rules>> at ;
180
181 : get-char-rules ( char ruleset -- vector/f )
182     [ ch>upper ] dip rules>> at ;
183
184 : get-rules ( char ruleset -- seq )
185     [ get-char-rules ] [ get-always-rules ] bi [ append ] when* ;
186
187 GENERIC: handle-rule-start ( match-count rule -- )
188
189 GENERIC: handle-rule-end ( match-count rule -- )
190
191 : find-escape-rule ( -- rule )
192     context get dup
193     in-rule-set>> escape-rule>> [ ] [
194         parent>> in-rule-set>>
195         dup [ escape-rule>> ] when
196     ] ?if ;
197
198 : check-escape-rule ( rule -- ? )
199     escape-rule>> [ find-escape-rule ] unless*
200     dup [
201         dup rule-start-matches? [
202             swap handle-rule-start
203             delegate-end-escaped? toggle
204             t
205         ] [
206             drop f
207         ] if*
208     ] when ;
209
210 : check-every-rule ( -- ? )
211     current-char current-rule-set get-rules
212     [ rule-start-matches? ] map-find
213     [ handle-rule-start t ] [ drop f ] if* ;
214
215 : ?end-rule ( -- )
216     current-rule [
217         dup rule-end-matches?
218         [ swap handle-rule-end ] [ drop ] if*
219     ] when* ;
220
221 : rule-match-token* ( rule -- id )
222     dup match-token>> {
223         { f [ dup body-token>> ] }
224         { t [ current-rule-set default>> ] }
225         [ ]
226     } case nip ;
227
228 M: escape-rule handle-rule-start
229     drop
230     ?end-rule
231     process-escape? get [
232         escaped? toggle
233         position [ + ] change
234     ] [ drop ] if ;
235
236 M: seq-rule handle-rule-start
237     ?end-rule
238     mark-token
239     add-remaining-token
240     [ body-token>> next-token, ] keep
241     delegate>> [ push-context ] when* ;
242
243 UNION: abstract-span-rule span-rule eol-span-rule ;
244
245 M: abstract-span-rule handle-rule-start
246     ?end-rule
247     mark-token
248     add-remaining-token
249     [ rule-match-token* next-token, ] keep
250     ! ... end subst ...
251     dup context get in-rule<<
252     delegate>> push-context ;
253
254 M: span-rule handle-rule-end
255     2drop ;
256
257 M: mark-following-rule handle-rule-start
258     ?end-rule
259     mark-token add-remaining-token
260     [ rule-match-token* next-token, ] keep
261     f context get end<<
262     context get in-rule<< ;
263
264 M: mark-following-rule handle-rule-end
265     nip rule-match-token* prev-token,
266     f context get in-rule<< ;
267
268 M: mark-previous-rule handle-rule-start
269     ?end-rule
270     mark-token
271     dup body-token>> prev-token,
272     rule-match-token* next-token, ;
273
274 : do-escaped ( -- )
275     escaped? get [
276         escaped? off
277         ! ...
278     ] when ;
279
280 : check-end-delegate ( -- ? )
281     context get parent>> [
282         in-rule>> [
283             dup rule-end-matches? [
284                 [
285                     swap handle-rule-end
286                     ?end-rule
287                     mark-token
288                     add-remaining-token
289                 ] keep context get parent>> in-rule>>
290                 rule-match-token* next-token,
291                 pop-context
292                 seen-whitespace-end? on t
293             ] [ check-escape-rule ] if*
294         ] [ f ] if*
295     ] [ f ] if* ;
296
297 : handle-no-word-break ( -- )
298     context get parent>> [
299         in-rule>> [
300             dup no-word-break?>> [
301                 rule-match-token* prev-token,
302                 pop-context
303             ] [ drop ] if
304         ] when*
305     ] when* ;
306
307 : check-rule ( -- )
308     ?end-rule
309     handle-no-word-break
310     mark-token
311     add-remaining-token ;
312
313 : (check-word-break) ( -- )
314     check-rule
315
316     1 current-rule-set default>> next-token, ;
317
318 : rule-set-empty? ( ruleset -- ? )
319     [ rules>> ] [ keywords>> ] bi
320     [ assoc-empty? ] both? ;
321
322 : check-word-break ( -- ? )
323     current-char dup blank? [
324         drop
325
326         seen-whitespace-end? get [
327             position get 1 + whitespace-end set
328         ] unless
329
330         (check-word-break)
331
332     ] [
333         ! Micro-optimization with incorrect semantics; we keep
334         ! it here because jEdit mode files depend on it now...
335         current-rule-set rule-set-empty? [
336             drop
337         ] [
338             dup alpha? [
339                 drop
340             ] [
341                 current-rule-set rule-set-no-word-sep* member? [
342                     (check-word-break)
343                 ] unless
344             ] if
345         ] if
346
347         seen-whitespace-end? on
348     ] if
349     escaped? off
350     delegate-end-escaped? off t ;
351
352
353 : mark-token-loop ( -- )
354     position get line get length < [
355         {
356             [ check-end-delegate ]
357             [ check-every-rule ]
358             [ check-word-break ]
359         } 0|| drop
360
361         position inc
362         mark-token-loop
363     ] when ;
364
365 : mark-remaining ( -- )
366     line get length position set
367     check-rule ;
368
369 : unwind-no-line-break ( -- )
370     context get parent>> [
371         in-rule>> [
372             no-line-break?>> [
373                 pop-context
374                 unwind-no-line-break
375             ] when
376         ] when*
377     ] when* ;
378
379 : tokenize-line ( line-context line rules -- line-context' seq )
380     [
381         "MAIN" of -rot
382         init-token-marker
383         mark-token-loop
384         mark-remaining
385         unwind-no-line-break
386         context get
387     ] { } make ;