An extensible POSIX-compatible implementation of the ed(1) text editor
git clone https://git.8pit.net/edward.git
1;;>| Address Constructors 2;;> 3;;> Procedures which create single address and range values. 4 5;;> Create a single address with an optional offset. 6 7(define make-addr 8 (case-lambda 9 ((addr) (list addr '())) 10 ((addr off) (list addr off)))) 11 12;;> Create an address range consisting of two addresses. 13 14(define make-range 15 (case-lambda 16 (() (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)))) 20 21;;> Predicate which returns true if the parsed address is a range. 22 23(define (range? obj) 24 (and (list? obj) 25 (eqv? (length obj) 3))) 26 27;;> Convert the given address to a range. 28 29(define (addr->range addr) 30 (if (range? addr) 31 addr 32 (make-range addr))) 33 34;;> Convert the given range to an address. 35 36(define (range->addr addr) 37 (if (range? addr) 38 (last addr) 39 addr)) 40 41;;> Predicate which returns true if the given `obj` constitutes an address separator. 42 43(define (address-separator? obj) 44 (or 45 (eq? obj #\,) 46 (eq? obj #\;))) 47 48;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 49 50;;>| Parser Combinators 51;;> 52;;> Edward [parser combinators][edward parse] for parsing ed addresses. 53;;> 54;;> [edward parse]: edward.parse.html 55 56;; From POSIX.1-2008: 57;; 58;; The <period> character ('.') shall address the current line. 59 60(define parse-current 61 (parse-map 62 (parse-char #\.) 63 (lambda (ch) 64 (cons 'current-line '())))) 65 66;; From POSIX.1-2008: 67;; 68;; The <dollar-sign> character ('$') shall address the last line of 69;; the edit buffer. 70 71(define parse-last 72 (parse-map 73 (parse-char #\$) 74 (lambda (ch) 75 (cons 'last-line '())))) 76 77;; From POSIX.1-2008: 78;; 79;; The positive decimal number n shall address the nth line of the 80;; edit buffer. 81 82(define parse-nth 83 (parse-map 84 parse-digits 85 (lambda (num) 86 (cons 'nth-line num)))) 87 88;; From POSIX.1-2008: 89;; 90;; The <apostrophe>-x character pair ("'x") shall address the line 91;; marked with the mark name character x, which shall be a lowercase 92;; letter from the portable character set. 93 94(define parse-mark 95 (parse-map 96 (parse-seq 97 (parse-commit 98 (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))