-
Notifications
You must be signed in to change notification settings - Fork 2
/
re-subst.scm
163 lines (139 loc) · 5.13 KB
/
re-subst.scm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
;;; Substitution ops with regexps
;;; Copyright (c) 1998 by Olin Shivers.
;;; These function have to be in a separate package because they use
;;; the scsh I/O function WRITE-STRING. The rest of the regexp system
;;; has no dependencies on scsh system code, and is defined independently
;;; of scsh -- which scsh, in turn, relies upon: pieces of scsh-level-0
;;; use the regexp basics. So we have to split this code out to avoid
;;; a circular dependency in the modules: scsh-level-0 needs the regexp
;;; package which needs WRITE-STRING, which comes from the regexp package.
(define (bogus-substring-spec? s start end)
(or (< start 0)
(< (string-length s) end)
(< end start)))
(define (write-string s . args)
(let-optionals args ((port (current-output-port))
(start 0)
(end (string-length s)))
(if (bogus-substring-spec? s start end)
(error "Bad substring indices" s start end))
(write-block (os-string->byte-vector (x->os-string s))
start (- end start) port)))
(define (regexp-substitute port match . items)
(let* ((str (regexp-match:string match))
(submatches (regexp-match:submatches match))
(range (lambda (item) ; Return start & end of
(cond ((integer? item) ; ITEM's range in STR.
(let ((submatch (vector-ref submatches item)))
(values (match-start submatch)
(match-end submatch))))
((eq? 'pre item) (values 0
(match-start
(vector-ref submatches 0))))
((eq? 'post item) (values (match-end
(vector-ref submatches 0))
(string-length str)))
(else (error "Illegal substitution item."
item
regexp-substitute))))))
(if port
;; Output port case.
(for-each (lambda (item)
(if (string? item) (write-string item port)
(receive (si ei) (range item)
(write-string str port si ei))))
items)
;; Here's the string case. Make two passes -- one to
;; compute the length of the target string, one to fill it in.
(let* ((len (fold (lambda (item i)
(+ i (if (string? item) (string-length item)
(receive (si ei) (range item) (- ei si)))))
0 items))
(ans (make-string len)))
(fold (lambda (item index)
(cond ((string? item)
(string-copy! ans index item)
(+ index (string-length item)))
(else (receive (si ei) (range item)
(string-copy! ans index str si ei)
(+ index (- ei si))))))
0 items)
ans))))
(define (regexp-substitute/global port re str . items)
(let ((str-len (string-length str))
(range (lambda (start submatches item) ; Return start & end of
(cond ((integer? item) ; ITEM's range in STR.
(let ((submatch (vector-ref submatches item)))
(values (match-start submatch)
(match-end submatch))))
((eq? 'pre item)
(values start
(match-start
(vector-ref submatches 0))))
(else (error "Illegal substitution item."
item
regexp-substitute/global)))))
(num-posts (fold (lambda (item count)
(+ count (if (eq? item 'post) 1 0)))
0 items)))
(if (and port (< num-posts 2))
;; Output port case, with zero or one POST items.
(let recur ((start 0))
(if (<= start str-len)
(let ((match (regexp-search re str start)))
(if match
(let* ((submatches (regexp-match:submatches match))
(s (match-start (vector-ref submatches 0)))
(e (match-end (vector-ref submatches 0)))
(empty? (= s e)))
(for-each (lambda (item)
(cond ((string? item) (write-string item port))
((procedure? item) (write-string (item match) port))
((eq? 'post0 item)
(if (and empty? (< s str-len))
(write-char (string-ref str s) port)))
((eq? 'post item)
(recur (if empty? (+ 1 e) e)))
(else (receive (si ei)
(range start submatches item)
(write-string str port si ei)))))
items))
(write-string str port start))))) ; No match.
;; Either we're making a string, or >1 POST.
(let* ((pieces (let recur ((start 0))
(if (> start str-len)
'()
(let ((match (regexp-search re str start))
(cached-post #f))
(if match
(let* ((submatches
(regexp-match:submatches match))
(s (match-start (vector-ref submatches 0)))
(e (match-end (vector-ref submatches 0)))
(empty? (= s e)))
(fold (lambda (item pieces)
(cond ((string? item)
(cons item pieces))
((procedure? item)
(cons (item match) pieces))
((eq? 'post0 item)
(if (and empty? (< s str-len))
(cons (string (string-ref str s))
pieces)
pieces))
((eq? 'post item)
(if (not cached-post)
(set! cached-post
(recur (if empty? (+ e 1) e))))
(append cached-post pieces))
(else (receive (si ei)
(range start submatches item)
(cons (substring str si ei)
pieces)))))
'() items))
;; No match. Return str[start,end].
(list (if (zero? start) str
(substring str start (string-length str)))))))))
(pieces (reverse pieces)))
(if port (for-each (lambda (p) (write-string p port)) pieces)
(apply string-append pieces))))))