1;; parse.scm -- Parser Combinators2;; Copyright (c) 2013 Alex Shinn. All rights reserved.3;; BSD-style license: http://synthcode.com/license.txt45;;>| Parse Streams6;;>7;;> Parse streams are an abstraction to treat ports as proper streams8;;> so that we can backtrack from previous states. A single9;;> Parse-Stream record represents a single buffered chunk of text.1011(define-record-type Parse-Stream12 (%make-parse-stream13 filename port buffer cache offset prev-char line column tail fk)14 parse-stream?15 ;; The file the data came from, for debugging and error reporting.16 (filename parse-stream-filename)17 ;; The underlying port.18 (port parse-stream-port)19 ;; A vector of characters read from the port. We use a vector20 ;; rather than a string for guaranteed O(1) access.21 (buffer parse-stream-buffer)22 ;; A vector of caches corresponding to parser successes or failures23 ;; starting from the corresponding char. Currently each cache is24 ;; just an alist, optimized under the assumption that the number of25 ;; possible memoized parsers is relatively small. Note that26 ;; memoization is only enabled explicitly.27 (cache parse-stream-cache)28 ;; The current offset of filled characters in the buffer.29 ;; If offset is non-zero, (vector-ref buffer (- offset 1)) is30 ;; valid.31 (offset parse-stream-offset parse-stream-offset-set!)32 ;; The previous char before the beginning of this Parse-Stream.33 ;; Used for line/word-boundary checks.34 (prev-char parse-stream-prev-char)35 ;; The debug info for the start line and column of this chunk.36 (line parse-stream-line)37 (column parse-stream-column)38 ;; The successor Parse-Stream chunk, created on demand and filled39 ;; from the same port.40 (tail %parse-stream-tail %parse-stream-tail-set!)41 ;; Initial fk as passed to call-with-parse. Retained as part of42 ;; the Parse-Stream for the parse-commit procedure.43 (fk parse-stream-fk parse-stream-fk-set!))4445;; We want to balance avoiding reallocating buffers with avoiding46;; holding many memoized values in memory.47(define default-buffer-size 256)4849;;> Create a parse stream open on the given `filename`, with a50;;> possibly already opened `port`.5152(define (make-parse-stream filename . o)53 (let ((port (if (pair? o) (car o) (open-input-file filename)))54 (len (if (and (pair? o) (pair? (cdr o))) (cadr o) default-buffer-size)))55 (%make-parse-stream56 filename port (make-vector len #f) (make-vector len '()) 0 #f 0 0 #f #f)))5758;;> Open `filename` and create a parse stream on it.5960(define (file->parse-stream filename)61 (make-parse-stream filename (open-input-file filename)))6263;;> Create a parse stream on a string `str`.6465(define (string->parse-stream str)66 (make-parse-stream #f (open-input-string str)))6768;;> Access the next buffered chunk of a parse stream.6970(define (parse-stream-tail source)71 (or (%parse-stream-tail source)72 (let* ((len (vector-length (parse-stream-buffer source)))73 (line-info (parse-stream-count-lines source))74 (line (+ (parse-stream-line source) (car line-info)))75 (col (if (zero? (car line-info))76 (+ (parse-stream-column source) (cadr line-info))77 (cadr line-info)))78 (tail (%make-parse-stream (parse-stream-filename source)79 (parse-stream-port source)80 (make-vector len #f)81 (make-vector len '())82 083 (parse-stream-last-char source)84 line85 col86 #f87 (parse-stream-fk source))))88 (%parse-stream-tail-set! source tail)89 tail)))9091(define (parse-stream-fill! source i)92 (let ((off (parse-stream-offset source))93 (buf (parse-stream-buffer source))94 (src (parse-stream-port source)))95 (if (<= off i)96 (do ((off off (+ off 1)))97 ((> off i) (parse-stream-offset-set! source off))98 (let ((ch (if (port? src) (read-char src) (file-read-char src))))99 (if ch100 (vector-set! buf off ch)101 ;; When EOF was encountered, add one eof-object to102 ;; the buffer, then read past the EOF through recursion.103 (begin104 (vector-set! buf off (eof-object))105 (parse-stream-offset-set! source (inc off))106 (parse-stream-fill! source i)))))107 #f)))108109;;> Returns true iff `i` is the first character position in the110;;> parse stream `source`.111112(define (parse-stream-start? source i)113 (and (zero? i) (not (parse-stream-prev-char source))))114115;;> Returns true iff `i` is the last character position in the116;;> parse stream `source`.117118(define (parse-stream-end? source i)119 (eof-object? (parse-stream-ref source i)))120121;;> Returns the character in parse stream `source` indexed by122;;> `i`.123124(define (parse-stream-ref source i)125 (parse-stream-fill! source i)126 (vector-ref (parse-stream-buffer source) i))127128(define (parse-stream-last-char source)129 (let ((buf (parse-stream-buffer source)))130 (let lp ((i (min (- (vector-length buf) 1) (parse-stream-offset source))))131 (if (negative? i)132 (parse-stream-prev-char source)133 (let ((ch (vector-ref buf i)))134 (if (eof-object? ch)135 (lp (- i 1))136 ch))))))137138(define (parse-stream-char-before source i)139 (if (> i (parse-stream-offset source))140 (parse-stream-ref source (- i 1))141 (parse-stream-prev-char source)))142143(define (parse-stream-max-char source)144 (let ((buf (parse-stream-buffer source)))145 (let lp ((i (min (- (vector-length buf) 1)146 (parse-stream-offset source))))147 (if (or (negative? i)148 (char? (vector-ref buf i)))149 i150 (lp (- i 1))))))151152(define (parse-stream-count-lines source . o)153 (let* ((buf (parse-stream-buffer source))154 (end (if (pair? o) (car o) (vector-length buf))))155 (let lp ((i 0) (from 0) (lines 0))156 (if (>= i end)157 (list lines (- i from) from)158 (let ((ch (vector-ref buf i)))159 (cond160 ((not (char? ch))161 (list lines (- i from) from))162 ((eqv? ch #\newline)163 (lp (+ i 1) i (+ lines 1)))164 (else165 (lp (+ i 1) from lines))))))))166167(define (parse-stream-end-of-line source i)168 (let* ((buf (parse-stream-buffer source))169 (end (vector-length buf)))170 (let lp ((i i))171 (if (>= i end)172 i173 (let ((ch (vector-ref buf i)))174 (if (or (not (char? ch)) (eqv? ch #\newline))175 i176 (lp (+ i 1))))))))177178(define (parse-stream-debug-info s i)179 ;; i is the failed parse index, but we want the furthest reached180 ;; location181 (if (%parse-stream-tail s)182 (parse-stream-debug-info (%parse-stream-tail s) i)183 (let ((max-char (parse-stream-max-char s)))184 (if (< max-char 0)185 (list 0 0 "")186 (let* ((line-info187 (parse-stream-count-lines s max-char))188 (line (+ (parse-stream-line s) (car line-info)))189 (col (if (zero? (car line-info))190 (+ (parse-stream-column s) (cadr line-info))191 (cadr line-info)))192 (from (car (cddr line-info)))193 (to (parse-stream-end-of-line s (+ from 1)))194 (str (parse-stream-substring s from s to)))195 (list line col str))))))196197(define (parse-stream-next-source source i)198 (if (>= (+ i 1) (vector-length (parse-stream-buffer source)))199 (parse-stream-tail source)200 source))201202(define (parse-stream-next-index source i)203 (if (>= (+ i 1) (vector-length (parse-stream-buffer source)))204 0205 (+ i 1)))206207(define (parse-stream-close source)208 (let ((src (parse-stream-port source)))209 (if (port? src)210 (close-input-port (parse-stream-port source))211 (file-close src))))212213(define (vector-substring vec start . o)214 (let* ((end (if (pair? o) (car o) (vector-length vec)))215 (res (make-string (- end start))))216 (do ((i start (+ i 1)))217 ((= i end) res)218 (string-set! res (- i start) (vector-ref vec i)))))219220(define (parse-stream-in-tail? s0 s1)221 (let ((s0^ (%parse-stream-tail s0)))222 (or (eq? s0^ s1)223 (and s0^ (parse-stream-in-tail? s0^ s1)))))224225(define (parse-stream< s0 i0 s1 i1)226 (if (eq? s0 s1)227 (< i0 i1)228 (parse-stream-in-tail? s0 s1)))229230;;> Returns a string composed of the characters starting at parse231;;> stream `s0` index `i0` (inclusive), and ending at `s1`232;;> index `i1` (exclusive).233234(define (parse-stream-substring s0 i0 s1 i1)235 (cond236 ((eq? s0 s1)237 (parse-stream-fill! s0 i1)238 (vector-substring (parse-stream-buffer s0) i0 i1))239 (else240 (let lp ((s (parse-stream-tail s0))241 (res (list (vector-substring (parse-stream-buffer s0) i0))))242 (let ((buf (parse-stream-buffer s)))243 (cond244 ((eq? s s1)245 (apply string-append246 (reverse (cons (vector-substring buf 0 i1) res))))247 (else248 (lp (parse-stream-tail s)249 (cons (vector-substring buf 0) res)))))))))250251(define (parse-stream-cache-cell s i f)252 (assv f (vector-ref (parse-stream-cache s) i)))253254(define (parse-stream-cache-set! s i f x)255 (let ((cache (vector-ref (parse-stream-cache s) i)))256 (cond257 ((assv f cache)258 => (lambda (cell)259 ;; prefer longer matches260 (if (and (pair? (cdr cell))261 (parse-stream< (car (cddr cell)) (cadr (cddr cell)) s i))262 (set-cdr! cell x))))263 (else264 (vector-set! (parse-stream-cache s) i (cons (cons f x) cache))))))265266;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;267268;;>| Parser Interface269;;>270;;> Procedures for operating on a created parse stream.271272;;> Combinator to indicate failure.273274(define (parse-failure s i reason)275 (let ((line+col (parse-stream-debug-info s i)))276 (error "incomplete parse at" (append line+col (list reason)))))277278;;> Call the parser combinator `f` on the parse stream279;;> `source`, starting at index `index`, passing the result to280;;> the given success continuation `sk`, which should be a281;;> procedure of the form `(result source index fail)`. The282;;> optional failure continuation should be a procedure of the form283;;> `(source index reason)`, and defaults to just returning284;;> `#f`.285286(define (call-with-parse f source index sk . o)287 (let ((s (if (string? source) (string->parse-stream source) source))288 (fk (if (pair? o) (car o) (lambda (s i reason) #f))))289 (parse-stream-fk-set! s fk)290 (f s index sk fk)))291292;;> Call the parser combinator `f` on the parse stream293;;> `source`, at index `index`, and return the result, or294;;> `#f` if parsing fails.295296(define (parse f source . o)297 (let ((index (if (pair? o) (car o) 0)))298 (call-with-parse f source index (lambda (r s i fk) r))))299300;;> Call the parser combinator `f` on the parse stream301;;> `source`, at index `index`. If the entire source is not302;;> parsed, raises an error, otherwise returns the result.303304(define (parse-fully f source . o)305 (let ((s (if (string? source) (string->parse-stream source) source))306 (index (if (pair? o) (car o) 0)))307 (call-with-parse308 f s index309 (lambda (r s i fk)310 (if (parse-stream-end? s i) r (fk s i "incomplete parse")))311 parse-failure)))312313;;> The fundamental parse iterator. Repeatedly applies the parser314;;> combinator `f` to `source`, starting at `index`, as315;;> long as a valid parse is found. On each successful parse applies316;;> the procedure `kons` to the parse result and the previous317;;> `kons` result, beginning with `knil`. If no parses318;;> succeed returns `knil`.319320(define (parse-fold f kons knil source . o)321 (let lp ((p (if (string? source) (string->parse-stream source) source))322 (index (if (pair? o) (car o) 0))323 (acc knil))324 (f p index (lambda (r s i fk) (lp s i (kons r acc))) (lambda (s i r) acc))))325326;;> Parse as many of the parser combinator `f` from the parse327;;> stream `source`, starting at `index`, as possible, and328;;> return the result as a list.329330(define (parse->list f source . o)331 (let ((index (if (pair? o) (car o) 0)))332 (reverse (parse-fold f cons '() source index))))333334;;> As `parse->list` but requires the entire source be parsed335;;> with no left over characters, signalling an error otherwise.336337(define (parse-fully->list f source . o)338 (let lp ((s (if (string? source) (string->parse-stream source) source))339 (index (if (pair? o) (car o) 0))340 (acc '()))341 (f s index342 (lambda (r s i fk)343 (if (eof-object? r) (reverse acc) (lp s i (cons r acc))))344 (lambda (s i reason) (error "incomplete parse")))))345346;;> Return a new parser combinator with the same behavior as `f`,347;;> but on failure replaces the reason with `reason`. This can be348;;> useful to provide more descriptive parse failure reasons when349;;> chaining combinators. For example, `parse-string` just350;;> expects to parse a single fixed string. If it were defined in351;;> terms of `parse-char`, failure would indicate some char352;;> failed to match, but it's more useful to describe the whole string353;;> we were expecting to see.354355(define (parse-with-failure-reason f reason)356 (lambda (r s i fk)357 (f r s i (lambda (s i r) (fk s i reason)))))358359;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;360361;;>| Basic Parsing Combinators362;;>363;;> Combinators to construct new parsers.364365;;> Parse nothing successfully.366367(define parse-epsilon368 (lambda (source index sk fk)369 (sk #t source index fk)))370371;;> Parse any single character successfully. Fails at end of input.372373(define parse-anything374 (lambda (source index sk fk)375 (if (parse-stream-end? source index)376 (fk source index "end of input")377 (sk (parse-stream-ref source index)378 (parse-stream-next-source source index)379 (parse-stream-next-index source index)380 fk))))381382;;> Always fail to parse.383384(define parse-nothing385 (lambda (source index sk fk)386 (fk source index "nothing")))387388;;> The disjunction combinator. Returns the first combinator that389;;> succeeds parsing from the same source and index.390391(define (parse-or f . o)392 (if (null? o)393 f394 (let ((g (apply parse-or o)))395 (lambda (source index sk fk)396 (let ((fk2 (lambda (s i r)397 (g source index sk fk398 ;; (lambda (s2 i2 r2)399 ;; (fk s2 i2 `(or ,r ,r2)))400 ))))401 (f source index sk fk2))))))402403;;> The conjunction combinator. If both `f` and `g` parse404;;> successfully starting at the same source and index, returns the405;;> result of `g`. Otherwise fails.406407(define (parse-and f g)408 (lambda (source index sk fk)409 (f source index (lambda (r s i fk) (g source index sk fk)) fk)))410411;;> The negation combinator. If `f` succeeds, fails, otherwise412;;> succeeds with `#t`.413414(define (parse-not f)415 (lambda (source index sk fk)416 (f source index (lambda (r s i fk) (fk s i "not"))417 (lambda (s i r) (sk #t source index fk)))))418419(define (parse-seq-list o)420 (cond421 ((null? o)422 parse-epsilon)423 ((null? (cdr o))424 (let ((f (car o)))425 (lambda (s i sk fk)426 (f s i (lambda (r s i fk)427 (sk (if (eq? r ignored-value) '() (list r)) s i fk))428 fk))))429 (else430 (let* ((f (car o))431 (o (cdr o))432 (g (car o))433 (o (cdr o))434 (g (if (pair? o)435 (apply parse-seq g o)436 (lambda (s i sk fk)437 (g s i (lambda (r s i fk)438 (sk (if (eq? r ignored-value) '() (list r))439 s i fk))440 fk)))))441 (lambda (source index sk fk)442 (f source443 index444 (lambda (r s i fk)445 (g s i (lambda (r2 s i fk)446 (let ((r2 (if (eq? r ignored-value) r2 (cons r r2))))447 (sk r2 s i fk)))448 fk))449 fk))))))450451;;> The sequence combinator. Each combinator is applied in turn just452;;> past the position of the previous. If all succeed, returns a list453;;> of the results in order, skipping any ignored values.454455(define (parse-seq . o)456 (parse-seq-list o))457458;;> Convert the list of parser combinators `ls` to a459;;> `parse-seq` sequence.460461(define (list->parse-seq ls)462 (if (null? (cdr ls)) (car ls) (parse-seq-list ls)))463464;;> The optional combinator. Parse the combinator `f` (in465;;> sequence with any additional combinator args `o`), and return466;;> the result, or parse nothing successully on failure.467468(define (parse-optional f . o)469 (if (pair? o)470 (parse-optional (apply parse-seq f o))471 (lambda (source index sk fk)472 (f source index sk (lambda (s i r) (sk #f source index fk))))))473474(define ignored-value (list 'ignore))475476;;> The repetition combinator. Parse `f` repeatedly and return a477;;> list of the results. `lo` is the minimum number of parses478;;> (deafult 0) to be considered a successful parse, and `hi` is479;;> the maximum number (default infinite) before stopping.480481(define (parse-repeat f . o)482 (let ((lo (if (pair? o) (car o) 0))483 (hi (and (pair? o) (pair? (cdr o)) (cadr o))))484 (lambda (source0 index0 sk fk)485 (let repeat ((source source0) (index index0) (fk fk) (j 0) (res '()))486 (let ((fk (if (>= j lo)487 (lambda (s i r) (sk (reverse res) source index fk))488 fk)))489 (if (and hi (= j hi))490 (sk (reverse res) source index fk)491 (f source492 index493 (lambda (r s i fk) (repeat s i fk (+ j 1) (cons r res)))494 fk)))))))495496;;> Parse `f` one or more times.497498(define (parse-repeat+ f)499 (parse-repeat f 1))500501;;> Parse `f` and apply the procedure `proc` to the result on success.502503(define (parse-map f proc)504 (lambda (source index sk fk)505 (f source index (lambda (res s i fk) (sk (proc res) s i fk)) fk)))506507;;> Parse `f` and apply the procedure `proc` to the substring508;;> of the parsed data. `proc` defaults to the identity.509510(define (parse-map-substring f . o)511 (let ((proc (if (pair? o) (car o) (lambda (res) res))))512 (lambda (source index sk fk)513 (f source514 index515 (lambda (res s i fk)516 (sk (proc (parse-stream-substring source index s i)) s i fk))517 fk))))518519;;> Parses the same streams as `f` but ignores the result on520;;> success. Inside a `parse-seq` the result will not be521;;> included in the list of results. Useful for discarding522;;> boiler-plate without the need for post-processing results.523524(define (parse-ignore f)525 (parse-map f (lambda (res) ignored-value)))526527;;> Parse with `f` and further require `check?` to return true528;;> when applied to the result.529530(define (parse-assert f check?)531 (lambda (source index sk fk)532 (f source533 index534 (lambda (res s i fk)535 (if (check? res) (sk res s i fk) (fk s i "assertion failed")))536 fk)))537538;;> Parse with `f` once and keep the first result, not allowing539;;> further backtracking within `f`.540541(define (parse-atomic f)542 (lambda (source index sk fk)543 (f source index (lambda (res s i fk2) (sk res s i fk)) fk)))544545;;> Parse with `f` once, keep the first result, and commit to the546;;> current parse path, discarding any prior backtracking options.547;;> Can optionally be passed a failure reason with which all resulting548;;> failure messages will be prefixed.549550(define (parse-commit f . o)551 (let ((prefix (if (pair? o) (string-append (car o) ": ") "")))552 (lambda (source index sk fk)553 (let ((commit-fk (parse-stream-fk source)))554 (f555 source556 index557 (lambda (res s i fk)558 (sk res s i (lambda (s i r)559 (commit-fk s i (string-append prefix r)))))560 fk)))))561562;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;563564;;>| Boundary Checks565;;>566;;> Procedures for performing boundary checks within a parser combinator.567568;;> Returns true iff `index` is the first index of the first parse569;;> stream `source`.570571(define parse-beginning572 (lambda (source index sk fk)573 (if (parse-stream-start? source index)574 (sk #t source index fk)575 (fk source index "expected beginning"))))576577;;> Returns true iff `index` is the last index of the last parse578;;> stream `source`.579580(define parse-end581 (lambda (source index sk fk)582 (if (parse-stream-end? source index)583 (begin584 (sk #t585 (parse-stream-next-source source index)586 (parse-stream-next-index source index)587 fk))588 (fk source index "expected end"))))589590;;> Returns true iff `source`, `index` indicate the beginning591;;> of a line (or the entire stream).592593(define parse-beginning-of-line594 (lambda (source index sk fk)595 (let ((before (parse-stream-char-before source index)))596 (if (or (not before) (eqv? #\newline before))597 (sk #t source index fk)598 (fk source index "expected beginning of line")))))599600;;> Returns true iff `source`, `index` indicate the end of a601;;> line (or the entire stream).602603(define parse-end-of-line604 (lambda (source index sk fk)605 (if (or (parse-stream-end? source index)606 (eqv? #\newline (parse-stream-ref source index)))607 (sk #t source index fk)608 (fk source index "expected end of line"))))609610;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;611612;;>| Constant Parsers613;;>614;;> Underlying combinators which parse a constant input and, contrary to615;;> the parsers documented above, cannot be passed parser combinators as616;;> procedure arguments.617618(define (parse-char-pred pred)619 (lambda (source index sk fk)620 (let ((ch (parse-stream-ref source index)))621 (if (and (char? ch) (pred ch))622 (sk ch623 (parse-stream-next-source source index)624 (parse-stream-next-index source index)625 fk)626 (fk source index "failed char pred")))))627628(define (x->char-predicate x)629 (cond630 ((char? x)631 (lambda (ch) (eqv? ch x)))632 ((char-set? x)633 (lambda (ch) (and (char? ch) (char-set-contains? x ch))))634 ((procedure? x)635 (lambda (ch) (and (char? ch) (x ch))))636 (else637 (error "don't know how to handle char predicate" x))))638639;;> Parse a single char which matches `x`, which can be a640;;> character, character set, or arbitrary procedure.641642(define (parse-char x)643 (parse-char-pred (x->char-predicate x)))644645;;> Parse a single char which does not match `x`, which can be a646;;> character, character set, or arbitrary procedure.647648(define (parse-not-char x)649 (let ((pred (x->char-predicate x)))650 (parse-char-pred (lambda (ch) (not (pred ch))))))651652;;> Parse the exact string `str`.653654(define (parse-string str)655 (parse-map (parse-with-failure-reason656 (parse-seq-list (map parse-char (string->list str)))657 (string-append "expected '" str "'"))658 list->string))659660;;> Parse a sequence of characters matching `x` as with661;;> `parse-char`, and return the resulting substring.662663(define (parse-token x)664 ;; (parse-map (parse-repeat+ (parse-char x)) list->string)665 ;; Tokens are atomic - we don't want to split them at any point in666 ;; the middle - so the implementation is slightly more complex than667 ;; the above. With a sane grammar the result would be the same668 ;; either way, but this provides a useful optimization.669 (let ((f (parse-char x)))670 (lambda (source0 index0 sk fk)671 (let lp ((source source0) (index index0))672 (f source673 index674 (lambda (r s i fk) (lp s i))675 (lambda (s i r)676 (if (and (eq? source source0) (eqv? index index0))677 (fk s i r)678 (sk (parse-stream-substring source0 index0 source index)679 source index fk))))))))680681;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;682683;;>| Laziness and Memoization684;;>685;;> [Lazy evaluation][wikipedia lazy] of parser combinators and [memoization][wikipedia memoization].686;;>687;;> [wikipedia lazy]: https://en.wikipedia.org/wiki/Lazy_evaluation688;;> [wikipedia memoization]: https://en.wikipedia.org/wiki/Memoization689690;;> A delayed combinator. This is equivalent to the parser combinator691;;> `f`, but is delayed so it can be more efficient if never used692;;> and `f` is expensive to compute. Moreover, it can allow693;;> self-referentiality as in:694;;>695;;> (letrec* ((f (parse-lazy (parse-or (parse-seq g f) h))))696;;> ...)697698(define-syntax parse-lazy699 (syntax-rules ()700 ((parse-lazy f)701 (let ((g (delay f)))702 (lambda (source index sk fk)703 ((force g) source index sk fk))))))704705;; Utility definitions for memoization.706707;; debugging708(define *procedures* '())709(define (procedure-name f)710 (cond ((assq f *procedures*) => cdr) (else #f)))711(define (procedure-name-set! f name)712 (set! *procedures* (cons (cons f name) *procedures*)))713714(define memoized-failure (list 'failure))715716;;> Parse the same strings as `f`, but memoize the result at each717;;> source and index to avoid exponential backtracking. `name` is718;;> provided for debugging only.719720(define (parse-memoize name f)721 ;;(if (not (procedure-name f)) (procedure-name-set! f name))722 (lambda (source index sk fk)723 (cond724 ((parse-stream-cache-cell source index f)725 => (lambda (cell)726 (if (and (pair? (cdr cell)) (eq? memoized-failure (cadr cell)))727 (fk source index (cddr cell))728 (apply sk (append (cdr cell) (list fk))))))729 (else730 (f source731 index732 (lambda (res s i fk)733 (parse-stream-cache-set! source index f (list res s i))734 (sk res s i fk))735 (lambda (s i r)736 (if (not (pair? (parse-stream-cache-cell source index f)))737 (parse-stream-cache-set!738 source index f (cons memoized-failure r)))739 (fk s i r)))))))