From 40bfaced3478e2d8212c9e11cafc32411abd3cf5 Mon Sep 17 00:00:00 2001 From: Ryan Culpepper Date: Fri, 15 Dec 2017 23:39:40 +0100 Subject: [PATCH] syntax/parse: add unwindable state: syntax-parse-state-{ref,set!,...} --- parse/pre.rkt | 40 ++++++++++++++++++++++++++++++++++++-- parse/private/parse.rkt | 15 +++++++++++--- parse/private/residual.rkt | 31 ++++++++++++++++++++++++++--- 3 files changed, 78 insertions(+), 8 deletions(-) diff --git a/parse/pre.rkt b/parse/pre.rkt index b9f801e..1ee5047 100644 --- a/parse/pre.rkt +++ b/parse/pre.rkt @@ -1,10 +1,46 @@ #lang racket/base (require "private/sc.rkt" "private/litconv.rkt" - "private/lib.rkt") + "private/lib.rkt" + "private/residual.rkt") (provide (except-out (all-from-out "private/sc.rkt") define-integrable-syntax-class syntax-parser/template parser/rhs) (all-from-out "private/litconv.rkt") - (all-from-out "private/lib.rkt")) + (all-from-out "private/lib.rkt") + syntax-parse-state-ref + syntax-parse-state-set! + syntax-parse-state-update! + syntax-parse-state-cons!) + +(define not-given (gensym)) + +(define (state-ref who key default) + (define state (current-state)) + (if (eq? default not-given) + (if (hash-has-key? state key) + (hash-ref state key) + (error who "no value found for key\n key: ~e" key)) + (hash-ref state key default))) + +(define (syntax-parse-state-ref key [default not-given]) + (state-ref 'syntax-parse-state-ref key default)) + +(define (check-update who) + (unless (current-state-writable?) + (error who "cannot update syntax-parse state outside of ~~do/#:do block"))) + +(define (syntax-parse-state-set! key value) + (check-update 'syntax-parse-state-set!) + (current-state (hash-set (current-state) key value))) + +(define (syntax-parse-state-update! key update [default not-given]) + (check-update 'syntax-parse-state-update!) + (define old (state-ref 'syntax-parse-state-update! key default)) + (current-state (hash-set (current-state) key (update old)))) + +(define (syntax-parse-state-cons! key value [default null]) + (check-update 'syntax-parse-state-cons!) + (define old (hash-ref (current-state) key default)) + (current-state (hash-set (current-state) key (cons value old)))) diff --git a/parse/private/parse.rkt b/parse/private/parse.rkt index e1ea667..1b7160c 100644 --- a/parse/private/parse.rkt +++ b/parse/private/parse.rkt @@ -449,7 +449,9 @@ Conventions: [cx x] [fh0 (syntax-patterns-fail ctx0)]) def ... - (parameterize ((current-syntax-context (cadr ctx0))) + (parameterize ((current-syntax-context (cadr ctx0)) + (current-state '#hasheq()) + (current-state-writable? #f)) (with ([fail-handler fh0] [cut-prompt fh0] [undo-stack null]) @@ -597,7 +599,9 @@ Conventions: [#s(pat:literal literal input-phase lit-phase) #`(if (and (identifier? x) (free-identifier=? x (quote-syntax literal) input-phase lit-phase)) - k + (with ([undo-stack (cons (current-state) undo-stack)]) + (state-cons! 'literals x) + k) (fail (failure* pr (es-add-literal (quote-syntax literal) es))))] [#s(pat:action action subpattern) #'(parse:A x cx action pr es (parse:S x cx subpattern pr es k))] @@ -790,7 +794,12 @@ Conventions: [pr* (ps-add-stx pr y)]) (parse:S y cy pattern pr* es k))] [#s(action:do (stmt ...)) - #'(let () (no-shadow stmt) ... (#%expression k))] + #'(parameterize ((current-state-writable? #t)) + (let ([init-state (current-state)]) + (no-shadow stmt) ... + (parameterize ((current-state-writable? #f)) + (with ([undo-stack (maybe-add-state-undo init-state (current-state) undo-stack)]) + (#%expression k)))))] [#s(action:undo (stmt ...)) #'(with ([undo-stack (cons (lambda () stmt ... (void)) undo-stack)] [cut-prompt illegal-cut-error]) diff --git a/parse/private/residual.rkt b/parse/private/residual.rkt index 1f87360..aebc796 100644 --- a/parse/private/residual.rkt +++ b/parse/private/residual.rkt @@ -315,10 +315,35 @@ (define (illegal-cut-error . _) (error 'syntax-parse "illegal use of cut")) -(provide unwind-to) +;; ---- + +(provide unwind-to + maybe-add-state-undo + current-state + current-state-writable? + state-cons!) (define (unwind-to undos base) - ;; PRE: undos = (list* proc ... base) + ;; PRE: undos = (list* proc/hash ... base) (unless (eq? undos base) - ((car undos)) + (let ([top-undo (car undos)]) + (cond [(procedure? top-undo) (top-undo)] + [(hash? top-undo) (current-state top-undo)])) (unwind-to (cdr undos) base))) + +(define (maybe-add-state-undo init-state new-state undos) + (if (eq? init-state new-state) + undos + (cons init-state undos))) + +;; To make adding undos to rewind current-state simpler, only allow updates +;; in a few contexts: +;; - literals (handled automatically) +;; - in ~do/#:do blocks (sets current-state-writable? = #t) + +(define current-state (make-parameter (hasheq))) +(define current-state-writable? (make-parameter #f)) + +(define (state-cons! key value) + (define state (current-state)) + (current-state (hash-set state key (cons value (hash-ref state key null)))))