Gathering macro-time information

Context: Now I am implementing in Racket a few of the control structures
of Ink, a notation for writing text adventure games.
(If you are interested, see Writing web-based interactive fiction with ink )

I have the following macro:

define-syntax-parse-rule (choi* name action ...)
  ; a choice that can be taken only once during entire execution
  ; Do I need to worry about thread0safety?
  #:with globalcounter (syntax-local-lift-expression #'1)                                     
     (when ( > globalcounter 0)
       (propose (Choice name (thunk action ...
                                      (set! globalcounter ( - globalcounter 1))
                                      ))
                )
       )
  )

this defines an alternative that can be taken only once. And it works.
The globalcounter counts the number of activations, and refuses
to propose it if it has already been executed.
The globalcounter is generated by the call to syntax-local-lift-exoression.
There is a separate globalcounter for each use of the choi* macro.

Now one of the features of Ink is a RESET command.
RESET resets the state of the system to what it was at the start of
a game, meaning in my case that all those globalcounters have to be
reset to 1.

I do not know how to implement this, because I have found no means
to make or access a list of all these globalcounters at macro time or run time.

Surely there must be some way of collecting this information at
macro time so that I can use it in a macrogenerated run-time reset
function?

While it is possible to collect the list of #'globalcounter IDs at expansion time by just define a expansion-time variable, I think this does not to work because you'll have to ensure that the reset function is expanded last so it retrieves the complete list of counters. Even worse, the code that reset the counters must reside in the same module (which is caused by the use of mutable variables):

#lang racket/base

(require (for-syntax racket/base racket/syntax syntax/parse syntax/id-set))

(begin-for-syntax
  (define id-set (mutable-free-id-set)))

(define-syntax (only-twice stx)
  (syntax-parse stx
    [(_ body:expr)
     #:with globalcounter (syntax-local-lift-expression #''2)
     (free-id-set-add! id-set (syntax-local-introduce #'globalcounter))
     (syntax/loc stx
       (cond
         [(> globalcounter 0)
          (set! globalcounter (sub1 globalcounter))
          body]
         [else
          (displayln "ran out of fuel")]))]))

(define-syntax (gen-reset-commands _stx)
  (define/with-syntax (globalcounter ...)
    (map syntax-local-introduce (free-id-set->list id-set)))
  (syntax/loc _stx
    (begin
      (set! globalcounter '2)
      ...
      (void))))

(define (test1 x)
  (only-twice (printf "test1: x = ~s\n" x)))

(define (test2 y)
  (only-twice (printf "test2: y = ~s\n" y)))

(define (reset!) ;; <- oops, testoops won't be reset
  (displayln "reset!")
  (gen-reset-commands))

(define (testoops z)
  (only-twice (printf "testoops: z = ~s\n" z)))

(test1 'A)
(test1 'B)
(test1 'C)
(test1 'D)

(test2 'P)
(test2 'Q)
(test2 'R)
(test2 'S)

(testoops 'I)
(testoops 'J)
(testoops 'K)
(testoops 'L)

(reset!)

(test1 'A)
(test2 'P)
(testoops 'I) ;; <- doesn't work!

To fix the issue, an alternative is to simply collect the list of counters at run time and let each counter add itself to the list, just like the call to add-to-reset-list! that is lifted via syntax-local-lift-expression:



(define-syntax (only-twice stx)
  (syntax-parse stx
    [(_ body:expr)
     #:with globalcounter (syntax-local-lift-expression #''2)
     (syntax-local-lift-expression
      #'(add-to-reset-list! (λ () (set! globalcounter '2))))
     (syntax/loc stx
       (cond
         [(> globalcounter 0)
          (set! globalcounter (sub1 globalcounter))
          body]
         [else
          (displayln "ran out of fuel")]))]))

(define reset-list '())
(define (add-to-reset-list! proc)
  (set! reset-list (cons proc reset-list)))

;; ...

(define (reset!)
  (displayln "reset!")
  (for ([proc (in-list reset-list)])
    (proc)))

But..why not go further and just do everything at run time? This has the advantage that the macro generates fewer code, and the guard is shared:

(define-syntax (only-twice stx)
  (syntax-parse stx
    [(_ body:expr)
     #:with globalcounter (syntax-local-lift-expression #'(create-resettable-counter))
     (syntax/loc stx
       (guard/counter globalcounter (λ () body)))]))

(define reset-list '())

(define (create-resettable-counter)
  (define fresh-counter (box 2))
  (set! reset-list (cons fresh-counter reset-list))
  fresh-counter)

(define (guard/counter counter proc)
  ;; not thread safe but anyway
  (cond
    [(> (unbox counter) 0)
     (set-box! counter (sub1 (unbox counter)))
     (proc)]
    [else
     (displayln "ran out of fuel")]))

;; ....


(define (reset!)
  (displayln "reset!")
  (for ([counter (in-list reset-list)])
    (set-box! counter 2)))

I like this latest version, that makes minimal use of the macro processor.
Thank you indeed!