compile/shift

Symbol: compile/shift

Syntax: (shift K BODY ...)

Use-case: Capture the current delimited continuation up to the nearest compile/reset prompt and bind it to K, then run BODY. K is a DelimitedContinuation that cannot be called from Python (only from within a Koskari eval loop via INVOKE_K or INVOKE_K_VAR).

Behaviour: Emits PUSH_SHIFT 0, declares K as a local variable, emits VAR_STORE K (binds the captured delimited continuation), compiles body with helper_begin, emits POP_SHIFT 0. K is a binding in the enclosing scope, as a loop variable or a with name is: it stays bound after the form, it rebinds an existing K, and a later shift in the slice that reuses the name rebinds it again. A handler that invokes its continuation more than once therefore takes a name no later shift in the slice uses, since each shot runs the slice against the same scope. Calling K packs its arguments the way return does: (k) resumes with None, (k v) with v, (k a b) with the tuple (a, b), and a call with keywords, (k a x: 1), with a values instance over all of them. The resumed slice is the body of K: a return in it is the value of the (k ...) call, a raise in it propagates from the (k ...) call site, and a break or continue in it reaches only a loop opened inside the slice.

Errors: Capture can fail in two structurally different ways, each with its own exception under the KoskariContinuationError category (see Error codes):

  • KoskariPromptNotFound – no matching prompt anywhere on the dynamic chain: shift has no enclosing reset. Establish a delimiter with reset.

  • KoskariPromptUnreachable – a matching prompt exists, but a node between the shift and the prompt denied the capture: shift cannot capture across a generator boundary. Adding a reset will not help; restructure so the capture does not cross the boundary. The denying boundary is recorded on the exception’s boundary attribute.

Both carry prompt_id (the delimiter id the capture was seeking) and operation (the surface construct, shift). A library that establishes its own delimiter can use prompt_id to recognize failures against it and translate them into its own vocabulary, as koskari.solve does with NoActiveSearch.

Examples:

;; Capture k; invoke immediately
(reset
  (shift k
    (k "value")))

;; Two shifts in one slice, each shot of k reaching j: distinct names,
;; since both bind in the same scope
(reset
  (#tuple (shift k (#tuple (k 1) (k 2)))
          (shift j (#tuple (j 10) (j 20)))))
;; => (((1, 10), (1, 20)), ((2, 10), (2, 20)))