1;;>| Address Constructors2;;>3;;> Procedures which create single address and range values.45;;> Create a single address with an optional offset.67(define make-addr8 (case-lambda9 ((addr) (list addr '()))10 ((addr off) (list addr off))))1112;;> Create an address range consisting of two addresses.1314(define make-range15 (case-lambda16 (() (make-range (make-addr '(current-line))))17 ((addr) (list addr #\, addr))18 ((start end) (list start #\, end))19 ((start sep end) (list start sep end))))2021;;> Predicate which returns true if the parsed address is a range.2223(define (range? obj)24 (and (list? obj)25 (eqv? (length obj) 3)))2627;;> Convert the given address to a range.2829(define (addr->range addr)30 (if (range? addr)31 addr32 (make-range addr)))3334;;> Convert the given range to an address.3536(define (range->addr addr)37 (if (range? addr)38 (last addr)39 addr))4041;;> Predicate which returns true if the given `obj` constitutes an address separator.4243(define (address-separator? obj)44 (or45 (eq? obj #\,)46 (eq? obj #\;)))4748;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4950;;>| Parser Combinators51;;>52;;> Edward [parser combinators][edward parse] for parsing ed addresses.53;;>54;;> [edward parse]: edward.parse.html5556;; From POSIX.1-2008:57;;58;; The <period> character ('.') shall address the current line.5960(define parse-current61 (parse-map62 (parse-char #\.)63 (lambda (ch)64 (cons 'current-line '()))))6566;; From POSIX.1-2008:67;;68;; The <dollar-sign> character ('$') shall address the last line of69;; the edit buffer.7071(define parse-last72 (parse-map73 (parse-char #\$)74 (lambda (ch)75 (cons 'last-line '()))))7677;; From POSIX.1-2008:78;;79;; The positive decimal number n shall address the nth line of the80;; edit buffer.8182(define parse-nth83 (parse-map84 parse-digits85 (lambda (num)86 (cons 'nth-line num))))8788;; From POSIX.1-2008:89;;90;; The <apostrophe>-x character pair ("'x") shall address the line91;; marked with the mark name character x, which shall be a lowercase92;; letter from the portable character set.9394(define parse-mark95 (parse-map96 (parse-seq97 (parse-commit98 (parse-char #\')99 "invalid mark")100 parse-lowercase)101 (lambda (lst)102 (cons 'marked-line (cadr lst)))))103104;; From POSIX.1-2008:105;;106;; A BRE enclosed by <slash> characters ('/') shall address the first107;; line found by searching forwards from the line following the108;; current line toward the end of the edit buffer. The second <slash>109;; can be omitted at the end of a command line. Within the BRE, a110;; <backslash>-<slash> pair ("\/") shall represent a literal <slash>111;; instead of the BRE delimiter.112113(define parse-forward-bre114 (parse-map115 (parse-regex-lit* #\/)116 (lambda (str)117 (cons 'regex-forward str))))118119;; From POSIX-1.2008:120;;121;; A BRE enclosed by <question-mark> characters ('?') shall address122;; the first line found by searching backwards from the line preceding123;; the current line toward the beginning of the edit buffer. The124;; second <question-mark> can be omitted at the end of a command line.125;; Within the BRE, a <backslash>-<question-mark> pair ("\?") shall126;; represent a literal <question-mark> instead of the BRE delimiter.127128(define parse-backward-bre129 (parse-map130 (parse-regex-lit* #\?)131 (lambda (str)132 (cons 'regex-backward str))))133134;; From POSIX.1-2008:135;;136;; A <plus-sign> ('+') or <hyphen-minus> character ('-') followed by a137;; decimal number shall address the current line plus or minus the138;; number. A <plus-sign> or <hyphen-minus> character not followed by a139;; decimal number shall address the current line plus or minus 1.140141(define parse-offset142 (parse-map143 (parse-seq144 (parse-or145 (parse-char #\+)146 (parse-char #\-))147 (parse-optional parse-digits))148 (lambda (lst)149 (let* ((fst (car lst))150 (snd (cadr lst))151 (num (if snd snd 1)))152 (if (eqv? fst #\-)153 (- 0 num) num)))))154155(define parse-relative156 (parse-map157 parse-offset158 (lambda (num)159 (cons 'relative num))))160161;;> Utility procedure for parsing addresses without offset.162163(define parse-addr164 (parse-or165 parse-current166 parse-last167 parse-nth168 parse-mark169 parse-forward-bre170 parse-backward-bre171 parse-relative))172173(define %parse-addr-with-off174 (parse-repeat175 (parse-map176 (parse-seq177 (parse-ignore parse-blanks)178 (parse-or179 parse-offset180 parse-digits))181 car)))182183;;> Addresses can be followed by zero or more address offsets, optionally184;;> separated by blanks. Offsets are a decimal number optionally prefixed by185;;> `+` or `-` character. A `+` or `-` character not followed by a186;;> decimal number shall be interpreted as `+1`/`-1`. This procedure is187;;> responsible for parsing an address with an optional offset.188189(define parse-addr-with-off190 (parse-or191 (parse-atomic192 (parse-seq193 parse-addr194 %parse-addr-with-off))195 (parse-fail "unknown address format")))196197;; From POSIX-1.2008:198;;199;; Addresses shall be separated from each other by a <comma> (',') or200;; <semicolon> character (';').201;;202;; POSIX also mandates a table with rules in case addresses are omitted203;; on either side of the separation character. Consult the standard for204;; more information.205206(define parse-separator207 (parse-char address-separator?))208209;;> This procedure expands a given parsed address according to the210;;> [omission rules][ed addresses] mandated by the POSIX standard. The211;;> procedure receives an address parsed by [parse-addrs][parse-addrs]212;;> as an input value and returns an [address-range][make-addr].213;;>214;;> [parse-addrs]: #parse-addrs215;;> [make-addr]: #make-addr216;;> [ed addresses]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ed.html#tag_20_38_13_02217218(define expand-addr219 (match-lambda220 ((#\,)221 (make-range (make-addr '(nth-line . 1))222 #\,223 (make-addr '(last-line))))224 ((#\, addr)225 (make-range (make-addr '(nth-line . 1)) #\, addr))226 ((addr #\,)227 (make-range addr #\, addr))228 ((#\;)229 (make-range (make-addr '(current-line)) #\; (make-addr '(last-line))))230 ((#\; addr)231 (make-range (make-addr '(current-line)) #\; addr))232 ((addr #\;)233 (make-range addr #\; addr))234 ((addr1 sep addr2)235 (make-range addr1 sep addr2))))236237(define %parse-addrs238 (parse-or239 (parse-repeat+240 (parse-memoize "address parser"241 (parse-seq242 (parse-or243 parse-addr-with-off244 (parse-ignore parse-beginning-of-line))245 (parse-ignore parse-blanks)246 parse-separator247 (parse-ignore parse-blanks)248 (parse-or249 parse-addr-with-off250 (parse-ignore parse-epsilon)))))251 (parse-map252 parse-addr-with-off253 (lambda (addr)254 (list (make-range addr))))))255256;;> Parse an address chain consisting of multiple addresses separated by257;;> `,` or `;`. Returns an address list which can be converted to a line258;;> pair using the [addrlst->lpair][addrlst->lpair] procedure.259;;>260;;> [addrlst->lpair]: edward.ed.editor.html#addrlst->lpair261262(define parse-addrs263 (parse-map264 %parse-addrs265 concatenate))