An extensible POSIX-compatible implementation of the ed(1) text editor
git clone https://git.8pit.net/edward.git
1;;>| Utility Parsing Combinators 2;;> 3;;> Additional high-level combinators implemented using the aforementioned 4;;> [basic combinators][basic section]. The provided high-level combinators, 5;;> return more useful parser error messages then the basic ones. 6;;> 7;;> [basic section]: #section-basic-parsing-combinators 8 9;;> Parser which always fails with the given error message. 10 11(define (parse-fail msg) 12 (lambda (source index sk fk) 13 (fk source index msg))) 14 15;;> Bind a constant `value` to a given `parser`. 16;;> That is, always return this value if the parser succeeds. 17 18(define (parse-bind value parser) 19 (parse-map 20 parser 21 (lambda (x) value))) 22 23;;> Run a parser `f`, which *must* return a list, and convert 24;;> its return value to a string using the `list->string` procedure. 25 26(define (parse-as-string parser) 27 (parse-map 28 parser 29 list->string)) 30 31;;> Parse one or more digits (i.e. `0-9`), interpret them as a 32;;> decimal number, and return this number. 33 34(define parse-digits 35 (parse-with-failure-reason 36 (parse-map 37 (parse-token char-set:digit) 38 string->number) 39 "expected digits")) 40 41;;> Parse an ASCII lowercase character (i.e. `a-z`). 42 43(define parse-lowercase 44 (parse-with-failure-reason 45 (parse-char char-set:lower-case) 46 "expected lowercase character")) 47 48;;> Attempt parsing using the given parser `f`, if it fails return a default value `def`. 49 50(define (parse-default f def) 51 (parse-map 52 (parse-optional f) 53 (lambda (x) 54 (if x x def)))) 55 56;;> Parse a newline character. 57 58(define parse-newline 59 (parse-with-failure-reason 60 (parse-char #\newline) 61 "expected newline")) 62 63;;> Parse a single blank character (i.e. horizontal whitespace). 64 65(define parse-blank 66 (parse-with-failure-reason 67 (parse-char char-set:blank) 68 "expected whitespace")) 69 70;;> Parse one or more blank characters. 71 72(define parse-blanks+ 73 (parse-with-failure-reason 74 (parse-token char-set:blank) 75 "expected whitespaces")) 76 77;;> Parse zero or more blank characters. 78 79(define parse-blanks 80 (parse-optional parse-blanks+)) 81 82;;> Invokes parser `f` between the parsers `lhs` and `rhs`. 83 84(define (parse-between lhs f rhs) 85 (parse-map 86 (parse-seq lhs f rhs) 87 cadr)) 88 89;;> Returns the result of parser `f` but allows preceding its input 90;;> with a backslash character to escape it in the parsed input format. 91 92(define (parse-esc f) 93 (parse-map 94 (parse-seq 95 (parse-char #\\) 96 f) 97 cadr)) 98 99;;> Parse an `alist` mapping chars to values which should be returned for each char.100101(define (parse-alist alist)102 (apply103 parse-or104 (map105 (lambda (x)106 (parse-map107 (parse-char (car x))108 (lambda (_)109 (cdr x))))110 alist)))111112;; Utility procedure for parsing ed(1) BRE addresses.113114(define (%parse-regex-lit ch end)115 (parse-with-failure-reason116 (parse-atomic117 (parse-as-string118 (parse-between119 (parse-char ch)120 (parse-repeat (parse-or121 (parse-esc (parse-char ch))122 (parse-char (char-set-complement (char-set ch #\newline)))))123 end)))124 "expected regex"))125126;;> Parse a regex literal which is enclosed by the character `ch`.127128(define (parse-regex-lit ch)129 (%parse-regex-lit130 ch131 (parse-char ch)))132133;;> Parse a regex literal which starts with character `ch` and is134;;> terminated by the same character or the end of line.135136(define (parse-regex-lit* ch)137 (%parse-regex-lit138 ch139 (parse-or140 (parse-char ch)141 parse-end-of-line)))142143;;> Invoke given parser and strip trailing blanks (if any).144145(define (parse-strip-blanks parser)146 (parse-map147 (parse-seq148 parser149 parse-blanks)150 car))151152;;> Like [parse-seq](#parse-seq) but skip blanks *before* each parser.153154(define (parse-blanks-seq . o)155 (define (%parse-blanks-seq lst)156 (parse-seq-list157 (apply append158 (zip (make-list159 (length lst)160 (parse-ignore parse-blanks))161 lst))))162163 (%parse-blanks-seq o))164165;;> Parse a single line (excluding the terminating newline character).166167(define parse-line168 (parse-atomic169 (parse-or170 (parse-bind "" parse-newline) ;; empty line171 (parse-map172 (parse-seq173 (parse-token (lambda (x) (not (char=? x #\newline))))174 ;; XXX: parse-end-of-line does _not_ consume the end-of-file.175 ;; This is crucial for parse-input-mode to work correctly.176 (parse-or parse-newline parse-end-of-line))177 car))))178179;;> Feed the result of the parser `ctx` to a single argument procedure `f`.180;;> The procedure must then return a new parser which is executed181;;> afterwards on the same index as `ctx`.182183(define (parse-with-context ctx f)184 (lambda (source index sk fk)185 (let* ((yield (lambda (r s i fk) r))186 (value (call-with-parse ctx source index yield)))187 (if value ;; default fk returns #f on error188 ((f value) source index sk fk)189 (fk source index "context parser failed")))))