diff --git a/parse/debug.rkt b/parse/debug.rkt index efb87b9..c11b606 100644 --- a/parse/debug.rkt +++ b/parse/debug.rkt @@ -43,8 +43,8 @@ [(name ...) (map attr-name attrs)] [(depth ...) (map attr-depth attrs)]) #'(let ([fh (lambda (fs) fs)]) - (app-argu parser x x (ps-empty x x) #f fh fh #f - (lambda (fh . attr-values) + (app-argu parser x x (ps-empty x x) #f null fh fh #f + (lambda (fh undos . attr-values) (map vector '(name ...) '(depth ...) attr-values)) argu))))))])) diff --git a/parse/experimental/provide.rkt b/parse/experimental/provide.rkt index 280a73d..173d81e 100644 --- a/parse/experimental/provide.rkt +++ b/parse/experimental/provide.rkt @@ -84,7 +84,7 @@ [opc-id opc] ... [okwc-id okwc] ...) (rename-contract - (->* (any/c any/c any/c any/c any/c any/c any/c any/c + (->* (any/c any/c any/c any/c any/c any/c any/c any/c any/c mpc-id ... mkw-c-part ... ...) (okw-c-part ... ...) any) diff --git a/parse/experimental/reflect.rkt b/parse/experimental/reflect.rkt index 460d964..77d504f 100644 --- a/parse/experimental/reflect.rkt +++ b/parse/experimental/reflect.rkt @@ -73,9 +73,9 @@ (arity minpos* maxpos* minkws* maxkws*))])] [curried-parser (make-keyword-procedure - (lambda (kws2 kwargs2 x cx pr es fh cp rl success . rest2) + (lambda (kws2 kwargs2 x cx pr es undos fh cp rl success . rest2) (let-values ([(kws kwargs) (merge2 kws1 kws2 kwargs1 kwargs2)]) - (keyword-apply parser kws kwargs x cx pr es fh cp rl success + (keyword-apply parser kws kwargs x cx pr es undos fh cp rl success (append rest1 rest2)))))] [ctor (cond [(reified-syntax-class? r) diff --git a/parse/experimental/specialize.rkt b/parse/experimental/specialize.rkt index 72f1e6c..ad569c1 100644 --- a/parse/experimental/specialize.rkt +++ b/parse/experimental/specialize.rkt @@ -36,5 +36,5 @@ 'splicing? 'opts #f)) (define-values (parser) - (lambda (x cx pr es fh0 cp0 rl success . formals) - (app-argu target-parser x cx pr es fh0 cp0 rl success argu))))))))]))) + (lambda (x cx pr es undos fh0 cp0 rl success . formals) + (app-argu target-parser x cx pr es undos fh0 cp0 rl success argu))))))))]))) diff --git a/parse/experimental/splicing.rkt b/parse/experimental/splicing.rkt index e0694aa..56abbd5 100644 --- a/parse/experimental/splicing.rkt +++ b/parse/experimental/splicing.rkt @@ -35,7 +35,7 @@ description) (define parser (let ([permute (mk-permute '(a.name ...))]) - (lambda (x cx pr es fh _cp rl success param ...) + (lambda (x cx pr es undos fh _cp rl success param ...) (let ([stx (datum->syntax cx x cx)]) (let ([result (let/ec escape @@ -46,13 +46,13 @@ (case (car result) ((ok) (apply success - ((mk-check-result pr 'name (length '(a.name ...)) permute x cx fh) + ((mk-check-result pr 'name (length '(a.name ...)) permute x cx undos fh) (cdr result)))) ((error) (let ([es (es-add-message (cadr result) (es-add-thing pr (get-description param ...) #f rl es))]) - (fh (failure pr es)))))))))) + (fh undos (failure pr es)))))))))) (define-syntax name (stxclass 'name (arity (length '(param ...)) (length '(param ...)) '() '()) (sort-sattrs '(#s(attr a.name a.depth #f) ...)) @@ -76,7 +76,7 @@ (for/list ([index (in-vector indexes)]) (list-ref result index))))))) -(define (mk-check-result pr name attr-count permute x cx fh) +(define (mk-check-result pr name attr-count permute x cx undos fh) (lambda (result) (unless (list? result) (error name "parser returned non-list")) @@ -91,5 +91,5 @@ (error name "expected exact nonnegative integer for first element of result list, got ~e" skip)) (let-values ([(rest-x rest-cx) (stx-list-drop/cx x cx skip)]) - (list* fh rest-x rest-cx (ps-add-cdr pr skip) + (list* fh undos rest-x rest-cx (ps-add-cdr pr skip) (permute (cdr result)))))))) diff --git a/parse/private/parse.rkt b/parse/private/parse.rkt index 17c996c..e829b66 100644 --- a/parse/private/parse.rkt +++ b/parse/private/parse.rkt @@ -89,11 +89,11 @@ #f (scopts 0 #t #t 'description) (quote-syntax predicate))) - (define (parser x cx pr es fh0 cp0 rl success) + (define (parser x cx pr es undos fh0 cp0 rl success) (if (predicate x) - (success fh0) + (success fh0 undos) (let ([es (es-add-thing pr 'description #t rl es)]) - (fh0 (failure* pr es)))))))])) + (fh0 undos (failure* pr es)))))))])) (define-syntax (parser/rhs stx) (syntax-case stx () @@ -155,7 +155,7 @@ [transparent? transparent?] [delimit-cut? delimit-cut?] [body body]) - #`(lambda (x cx pr es fh0 cp0 rl success . formals*) + #`(lambda (x cx pr es undos fh0 cp0 rl success . formals*) (with ([this-syntax x] [this-role rl]) def ... @@ -168,7 +168,8 @@ #,(if no-fail? #'#f #'es))] [pr (if 'transparent? pr (ps-add-opaque pr))]) (with ([fail-handler fh0] - [cut-prompt cp0]) + [cut-prompt cp0] + [undo-stack undos]) ;; Update the prompt, if required ;; FIXME: can be optimized away if no cut exposed within variants (with-maybe-delimit-cut delimit-cut? @@ -230,7 +231,8 @@ def ... (#%expression (with ([fail-handler fh0] - [cut-prompt fh0]) + [cut-prompt fh0] + [undo-stack null]) (parse:S x cx pattern pr es (list (attribute name) ...)))))))))))])) @@ -251,18 +253,24 @@ Parsing protocols: pr, es are progress and expectstack, respectively rest-x, rest-cx, rest-pr are variable names to bind in context of success-expr -(stxclass-parser x cx pr es fail-handler cut-prompt role success-proc arg ...) : Ans +(stxclass-parser x cx pr es undos fail-handler cut-prompt role success-proc arg ...) : Ans success-proc: - for stxclass, is (fail-handler attr-value ... -> Ans) - for splicing-stxclass, is (fail-handler rest-x rest-cx rest-pr attr-value -> Ans) - fail-handler, cut-prompt : failure -> Ans + for stxclass, is (fail-handler undos attr-value ... -> Ans) + for splicing-stxclass, is (undos fail-handler rest-x rest-cx rest-pr attr-value -> Ans) + fail-handler, cut-prompt : undos failure -> Ans Fail-handler is normally represented with stxparam 'fail-handler', but must be threaded through stxclass calls (in through stxclass-parser, out through success-proc) to support backtracking. Cut-prompt is never changed within stxclass or within alternative, so no threading needed. +The undo stack is normally represented with stxparam 'undo-stack', but must be +threaded through stxclass calls (like fail-handler). A failure handler closes +over a base undo stack and receives an extended current undo stack; the failure +handler unwinds effects by performing every action in the difference between +them and then restores the saved undo stack. + Usually sub-patterns processed in tail position, but *can* do non-tail calls for: - ~commit - var of stxclass with ~commit @@ -368,7 +376,7 @@ Conventions: (reorder-iattrs (wash-sattrs #'relsattrs) (wash-iattrs #'iattrs))]) (with-syntax ([(#s(attr name _ _) ...) reliattrs]) - #'(success fail-handler also ... (attribute name) ...)))])) + #'(success fail-handler undo-stack also ... (attribute name) ...)))])) ;; ---- @@ -443,7 +451,8 @@ Conventions: def ... (parameterize ((current-syntax-context (cadr ctx0))) (with ([fail-handler fh0] - [cut-prompt fh0]) + [cut-prompt fh0] + [undo-stack null]) #,(cond [(pair? patterns) (with-syntax ([matrix (optimize-matrix @@ -537,26 +546,31 @@ Conventions: #'())]) (if (not (syntax-e #'commit?)) ;; The normal protocol - #'(app-argu parser x cx pr es fail-handler cut-prompt role - (lambda (fh av ...) + #'(app-argu parser x cx pr es undo-stack fail-handler cut-prompt role + (lambda (fh undos av ...) (let-attributes (name-attr ...) (let-attributes* ((nested-a ...) (av ...)) - (with ([fail-handler fh]) + (with ([fail-handler fh] [undo-stack undos]) k)))) argu) ;; The commit protocol ;; (Avoids putting k in procedure) - #'(let-values ([(fs av ...) - (with ([fail-handler (lambda (fs) (values fs (let ([av #f]) av) ...))]) + #'(let-values ([(fs undos av ...) + (with ([fail-handler + (lambda (undos fs) + (unwind-to undos undo-stack) + (values fs undo-stack (let ([av #f]) av) ...))]) (with ([cut-prompt fail-handler]) - (app-argu parser x cx pr es fail-handler cut-prompt role - (lambda (fh av ...) (values #f av ...)) + (app-argu parser x cx pr es undo-stack + fail-handler cut-prompt role + (lambda (fh undos av ...) (values #f undos av ...)) argu)))]) (if fs (fail fs) (let-attributes (name-attr ...) (let-attributes* ((nested-a ...) (av ...)) - k))))))] + (with ([undo-stack undos]) + k)))))))] [#s(pat:reflect obj argu attr-decls name (nested-a ...)) (with-syntax ([(name-attr ...) (if (identifier? #'name) @@ -564,11 +578,11 @@ Conventions: #'())]) (with-syntax ([arity (arguments->arity (syntax->datum #'argu))]) #'(let ([parser (reflect-parser obj 'arity 'attr-decls #f)]) - (app-argu parser x cx pr es fail-handler cut-prompt #f - (lambda (fh . result) + (app-argu parser x cx pr es undo-stack fail-handler cut-prompt #f + (lambda (fh undos . result) (let-attributes (name-attr ...) (let/unpack ((nested-a ...) result) - (with ([fail-handler fh]) + (with ([fail-handler fh] [undo-stack undos]) k)))) argu))))] [#s(pat:datum datum) @@ -598,9 +612,9 @@ Conventions: [#s(pat:or (a ...) (subpattern ...) (subattrs ...)) (with-syntax ([(#s(attr id _ _) ...) #'(a ...)]) #`(let ([success - (lambda (fh id ...) + (lambda (fh undos id ...) (let-attributes ([a id] ...) - (with ([fail-handler fh]) + (with ([fail-handler fh] [undo-stack undos]) k)))]) (try (parse:S x cx subpattern pr es (disjunct subattrs success () (id ...))) @@ -610,14 +624,14 @@ Conventions: [pr0 pr] [es0 es] [fail-to-succeed - (lambda (fs) k)]) + (lambda (undos fs) (unwind-to undos undo-stack) k)]) ;; ~not implicitly prompts to be safe, ;; but ~! not allowed within ~not (unless within ~delimit-cut, etc) ;; (statically checked!) (with ([fail-handler fail-to-succeed] [cut-prompt fail-to-succeed]) ;; to be safe (parse:S x cx subpattern pr es - (fh0 (failure* pr0 es0)))))] + (fh0 undo-stack (failure* pr0 es0)))))] [#s(pat:pair head tail) #`(let ([datum (if (syntax? x) (syntax-e x) x)] [cx (if (syntax? x) x cx)]) ;; FIXME: shadowing cx?! @@ -748,7 +762,7 @@ Conventions: #`(let ([alt-sub-id (attribute sub-id)] ...) (let ([id #f] ...) (let ([sub-id alt-sub-id] ...) - (success fail-handler pre ... id ...)))))])) + (success fail-handler undo-stack pre ... id ...)))))])) ;; (parse:A x cx A-pattern pr es k) : expr[Ans] ;; In k: attrs(A-pattern) are bound. @@ -778,9 +792,9 @@ Conventions: [#s(action:do (stmt ...)) #'(let () (no-shadow stmt) ... (#%expression k))] [#s(action:undo (stmt ...)) - #'(try (with ([cut-prompt illegal-cut-error]) - (#%expression k)) - (begin (#%expression stmt) ... (fail (failure* pr es))))] + #'(with ([undo-stack (cons (lambda () stmt ... (void)) undo-stack)] + [cut-prompt illegal-cut-error]) + k)] [#s(action:ord pattern group index) #'(let ([pr* (ps-add pr '#s(ord group index))]) (parse:A x cx pattern pr* es k))] @@ -831,27 +845,32 @@ Conventions: #'())]) (if (not (syntax-e #'commit?)) ;; The normal protocol - #`(app-argu parser x cx pr es fail-handler cut-prompt role - (lambda (fh rest-x rest-cx rest-pr av ...) + #`(app-argu parser x cx pr es undo-stack fail-handler cut-prompt role + (lambda (fh undos rest-x rest-cx rest-pr av ...) (let-attributes (name-attr ...) (let-attributes* ((nested-a ...) (av ...)) - (with ([fail-handler fh]) + (with ([fail-handler fh] [undo-stack undos]) k)))) argu) ;; The commit protocol ;; (Avoids putting k in procedure) - #'(let-values ([(fs rest-x rest-cx rest-pr av ...) - (with ([fail-handler (lambda (fs) (values fs #f #f #f (let ([av #f]) av) ...))]) + #'(let-values ([(fs undos rest-x rest-cx rest-pr av ...) + (with ([fail-handler + (lambda (undos fs) + (unwind-to undos undo-stack) + (values fs undo-stack #f #f #f (let ([av #f]) av) ...))]) (with ([cut-prompt fail-handler]) - (app-argu parser x cx pr es fail-handler cut-prompt role - (lambda (fh rest-x rest-cx rest-pr av ...) - (values #f rest-x rest-cx rest-pr av ...)) + (app-argu parser x cx pr es undo-stack + fail-handler cut-prompt role + (lambda (fh undos rest-x rest-cx rest-pr av ...) + (values #f undos rest-x rest-cx rest-pr av ...)) argu)))]) (if fs (fail fs) (let-attributes (name-attr ...) (let-attributes* ((nested-a ...) (av ...)) - k))))))] + (with ([undo-stack undos]) + k)))))))] [#s(hpat:reflect obj argu attr-decls name (nested-a ...)) (with-syntax ([(name-attr ...) (if (identifier? #'name) @@ -860,11 +879,11 @@ Conventions: #'())]) (with-syntax ([arity (arguments->arity (syntax->datum #'argu))]) #'(let ([parser (reflect-parser obj 'arity 'attr-decls #t)]) - (app-argu parser x cx pr es fail-handler cut-prompt #f - (lambda (fh rest-x rest-cx rest-pr . result) + (app-argu parser x cx pr es undo-stack fail-handler cut-prompt #f + (lambda (fh undos rest-x rest-cx rest-pr . result) (let-attributes (name-attr ...) (let/unpack ((nested-a ...) result) - (with ([fail-handler fh]) + (with ([fail-handler fh] [undo-stack undos]) k)))) argu))))] [#s(hpat:and head single) @@ -875,9 +894,9 @@ Conventions: [#s(hpat:or (a ...) (subpattern ...) (subattrs ...)) (with-syntax ([(#s(attr id _ _) ...) #'(a ...)]) #`(let ([success - (lambda (fh rest-x rest-cx rest-pr id ...) + (lambda (fh undos rest-x rest-cx rest-pr id ...) (let-attributes ([a id] ...) - (with ([fail-handler fh]) + (with ([fail-handler fh] [undo-stack undos]) k)))]) (try (parse:H x cx rest-x rest-cx rest-pr subpattern pr es (disjunct subattrs success (rest-x rest-cx rest-pr) (id ...))) @@ -921,7 +940,8 @@ Conventions: [pr0 pr] [es0 es] [fail-to-succeed - (lambda (fs) + (lambda (undos fs) + (unwind-to undos undo-stack) (let ([rest-x x] [rest-cx cx] [rest-pr pr]) @@ -932,7 +952,7 @@ Conventions: (with ([fail-handler fail-to-succeed] [cut-prompt fail-to-succeed]) ;; to be safe (parse:H x cx rest-x rest-cx rest-pr subpattern pr es - (fh0 (failure* pr0 es0)))))] + (fh0 undo-stack (failure* pr0 es0)))))] [_ #'(parse:S x cx ;; FIXME: consider proper-list-pattern? (yes is consistent with ~seq) @@ -999,11 +1019,11 @@ Conventions: tail-pattern-is-null?]) (define/with-syntax alt-map #'((id . alt-id) ...)) (define/with-syntax loop-k - #'(dots-loop dx* dcx* loop-pr* fail-handler rel-rep ... alt-id ...)) + #'(dots-loop dx* dcx* loop-pr* undo-stack fail-handler rel-rep ... alt-id ...)) #`(let () ;; dots-loop : stx progress rel-rep ... alt-id ... -> Ans - (define (dots-loop dx dcx loop-pr fh rel-rep ... alt-id ...) - (with ([fail-handler fh]) + (define (dots-loop dx dcx loop-pr undos fh rel-rep ... alt-id ...) + (with ([fail-handler fh] [undo-stack undos]) (try-or-pair/null-check do-pair/null? dx dcx loop-pr es (try (parse:EH dx dcx loop-pr head-attrs check-null? head-repc dx* dcx* loop-pr* alt-map head-rep head es loop-k) @@ -1017,7 +1037,7 @@ Conventions: (parse:S dx dcx tail loop-pr es k))])))) (let ([rel-rep 0] ... [alt-id (rep:initial-value attr-repc)] ...) - (dots-loop x cx pr fail-handler rel-rep ... alt-id ...)))))])) + (dots-loop x cx pr undo-stack fail-handler rel-rep ... alt-id ...)))))])) ;; (try-or-pair/null-check bool x cx es pr pair-alt maybe-null-alt) (define-syntax try-or-pair/null-check diff --git a/parse/private/residual.rkt b/parse/private/residual.rkt index d3d6e2c..0799a62 100644 --- a/parse/private/residual.rkt +++ b/parse/private/residual.rkt @@ -279,8 +279,9 @@ ["runtime-report.rkt" (call-current-failure-handler ctx fs)]) -;; syntax-patterns-fail : (list Symbol/#f Syntax) -> FailureSet -> (escapes) -(define ((syntax-patterns-fail ctx) fs) +;; syntax-patterns-fail : (list Symbol/#f Syntax) -> (Listof (-> Any)) FailureSet -> escapes +(define ((syntax-patterns-fail ctx) undos fs) + (unwind-to undos null) (call-current-failure-handler ctx fs)) ;; == specialized ellipsis parser @@ -314,3 +315,11 @@ (define (illegal-cut-error . _) (error 'syntax-parse "illegal use of cut")) + +(provide unwind-to) + +(define (unwind-to undos base) + ;; PRE: undos = (list* proc ... base) + (unless (eq? undos base) + ((car undos)) + (unwind-to (cdr undos) base))) diff --git a/parse/private/runtime-reflect.rkt b/parse/private/runtime-reflect.rkt index d561684..d5089d8 100644 --- a/parse/private/runtime-reflect.rkt +++ b/parse/private/runtime-reflect.rkt @@ -82,13 +82,13 @@ A Reified is [else (loop (cdr result) indexes (add1 i))]))) (make-keyword-procedure - (lambda (kws kwargs x cx pr es fh cp rl success . rest) - (keyword-apply parser kws kwargs x cx pr es fh cp rl + (lambda (kws kwargs x cx pr es undos fh cp rl success . rest) + (keyword-apply parser kws kwargs x cx pr es undos fh cp rl (if splicing? - (lambda (fh x cx pr . result) - (apply success fh x cx pr (take-indexes result indexes))) - (lambda (fh . result) - (apply success fh (take-indexes result indexes)))) + (lambda (fh undos x cx pr . result) + (apply success fh undos x cx pr (take-indexes result indexes))) + (lambda (fh undos . result) + (apply success fh undos (take-indexes result indexes)))) rest)))))) (define (wrong-depth who a b) diff --git a/parse/private/runtime.rkt b/parse/private/runtime.rkt index 7b6cb19..15ccdf6 100644 --- a/parse/private/runtime.rkt +++ b/parse/private/runtime.rkt @@ -14,6 +14,7 @@ (provide with fail-handler cut-prompt + undo-stack wrap-user-code fail @@ -59,29 +60,38 @@ residual.rkt. (define-syntax-parameter cut-prompt (lambda (stx) (wrong-syntax stx "internal error: cut-prompt used out of context"))) +(define-syntax-parameter undo-stack + (lambda (stx) + (wrong-syntax stx "internal error: undo-stack used out of context"))) (define-syntax-rule (wrap-user-code e) (with ([fail-handler #f] - [cut-prompt #t]) + [cut-prompt #t] + [undo-stack null]) e)) (define-syntax-rule (fail fs) - (fail-handler fs)) + (fail-handler undo-stack fs)) (define-syntax (try stx) (syntax-case stx () [(try e0 e ...) (with-syntax ([(re ...) (reverse (syntax->list #'(e ...)))]) (with-syntax ([(fh ...) (generate-temporaries #'(re ...))]) - (with-syntax ([(next-fh ...) (drop-right (syntax->list #'(fail-handler fh ...)) 1)] - [(last-fh) (take-right (syntax->list #'(fail-handler fh ...)) 1)]) - #'(let* ([fh (lambda (fs1) + (with-syntax ([(next-fh ... last-fh) #'(fail-handler fh ...)]) + #'(let* ([fh (lambda (undos1 fs1) (with ([fail-handler - (lambda (fs2) - (next-fh (cons fs1 fs2)))]) + (lambda (undos2 fs2) + (unwind-to undos2 undos1) + (next-fh undos1 (cons fs1 fs2)))] + [undo-stack undos1]) re))] ...) - (with ([fail-handler last-fh]) + (with ([fail-handler + (lambda (undos2 fs2) + (unwind-to undos2 undo-stack) + (last-fh undo-stack fs2))] + [undo-stack undo-stack]) e0)))))])) ;; == Attributes @@ -208,8 +218,8 @@ residual.rkt. (length (syntax->list #'(parg ...))) (syntax->datum #'(kw ...)))]) (with-syntax ([parser (stxclass-parser sc)]) - #'(lambda (x cx pr es fh cp rl success) - (app-argu parser x cx pr es fh cp rl success argu)))))])) + #'(lambda (x cx pr es undos fh cp rl success) + (app-argu parser x cx pr es undos fh cp rl success argu)))))])) (define-syntax (app-argu stx) (syntax-case stx ()