#lang scheme/base (provide (all-defined-out)) (require "scat-base.ss" "stack.ss" "ns.ss" "rep.ss" "base-utils.ss" "../tools.ss" "scat-syntax.ss" scheme/match scheme/control (for-syntax syntax/stx scheme/base) ) ;; Control operations for the SCAT base language: anything that needs ;; to grab the whole state. In contrast with the functions in ;; scat-base, which operate only on the stack, these need to know ;; about the state object. ;; The passing of threaded state is abstracted in the functions below. (define-syntax define-ctrl (syntax-rules () ((_ name . args) (define-ns (scat) name (ctrl-lambda . args))))) ;; Abstract implementation of stack and hidden data. ;; (define-syntax (ctrl-lambda stx) ;; (syntax-case stx () ;; ((_ (proto ...) . expr) ;; #`(lambda (state) ;; (let ((#,(datum->syntax (stx-car #'expr) 'stack->state) ;; (lambda (stack) ;; (update-state state stack)))) ;; #,(syntax-case #'(proto ...) () ;; ((s t u ...) ;; #`(match state ;; ((struct stack (ctor (list-rest . (s t u ...)))) ;; . expr))) ;; ((stack) ;; #`(match state ;; ((struct stack (ctor stack)) ;; . expr))))))))) (define-syntax (ctrl-lambda stx) (syntax-case stx () ((_ (s t u ...) . expr) #`(state-lambda #,(datum->syntax #'s 'stack) ;; context for 'update' ((list-rest . (s t u ...))) ;; => . expr)) ((_ (stack) . expr) #`(state-lambda #,(datum->syntax #'s 'stack) ;; context for 'update' (stack) ;; => . expr)))) ;; Attempt to write a macro that prevents functions to ever access the ;; state data. It looks like it's easier to just have an explicit ;; parameter and state accessors + knowledge of implementation of ;; functions. ;; (define-syntax (broem-lambda stx) ;; (syntax-case stx () ;; ((_ stack . expr) ;; #`(control-lambda ;; stack data ;; (let ((#,(syntax->datum stx 'STATE) ;; introduce name ;; (lambda (s) (make-state s data)))) ;; . expr))))) ;; CONTROL WORDS ;; These use the following functions to access/create state: ;; * state->stack (extracts stack from state) ;; * stack->state (combines stack with enclosing state) ;; * state-cons (push element to stack in state) (define state->stack stack-list) (define state-cons stack-cons) (define state-top stack-top) ;; Function application. (define-ctrl run (fn stack+) (fn (update stack+))) ;; Run, but preserve top element. (define-ctrl dip (fn datum stack+) (state-cons datum (fn (update stack+)))) ;; Run a function in a modified environment. See scat-base.ss for ;; dynamic environment constructors. (define-ctrl dynamic (consume-thunk fn stack+) (consume-thunk (lambda () (fn (update stack+))))) ;; call/cc (fn -- ) ;; Push the current continuation on the stack, wrapped in state ;; function, and execute fn. The continuation wrapper when invoked, ;; passes the whole state. (define-ctrl call/cc (fn stack+) (call/cc (lambda (k) (fn (update (cons (lambda (s) (k s)) stack+)))))) ;; amb-choose (now later save -- now/later) ;; Non-deterministic choice: Create a continuation which backtracks ;; and invokes the 'later' branch. Pass this continuation to and ;; passes it to 'save', then returning the the 'now' branch. ;; amb :: (amb-choose run) (define-ctrl amb-choose (save later now stack+) (call/cc (lambda (k) (state-cons now (save (update (cons (lambda _ (k (update (cons later stack+)))) stack+))))))) ;; Similar, but using prompts. Go through a series of alternatives. ;; does it drop or not? -> no: it's usually a check inserted after ;; something that returns a value or false. (define attempt-tag (make-continuation-prompt-tag 'attempt)) (define-word check s (if (car s) s (abort-current-continuation attempt-tag (lambda () #f)))) (define-ctrl attempts (alternatives stack+) (let next ((a alternatives)) (if (null? a) (error 'attempts-exhausted) (or (prompt-at attempt-tag ((car a) (update stack+))) (next (cdr a)))))) ;; Map will leave the rest of the stack and state data untouched: ;; mapped functions get it passed, but the modification is ignored: ;; only the top stack element is collected. (define-ctrl map (fn lst stack+) (update (cons (map (lambda (x) (state-top (fn (update (cons x stack+))))) lst) stack+))) ;; A function that behaves like 'map', but operates on a list of ;; stacks. (define-ctrl stack-map (fn stacks stack+) (update (cons (map (lambda (stack) (state->stack (fn (update stack)))) stacks) stack+))) ;; For-each is list interpretation. For this reason, the last function ;; is evaluated in tail position. (see 'interpret-list'). (define-ctrl for-each (fn l stack+) (interpret-list (lambda (i s) (fn (state-cons i s))) car cdr null? l (update stack+))) (define-ctrl for (fn n stack+) (when (negative? n) (error 'negative-index "~a" n)) (interpret-list (lambda (_ s) (fn s)) void sub1 zero? n (update stack+))) ;; Left and right fold. The prototype is analogous to that of ;; for-each, which is related to left fold. (define (make-fold fold) (ctrl-lambda (fn l i stack+) (update (cons (fold (lambda (kar kdr) (state-top (fn (update (list* kdr kar stack+))))) i l) stack+)))) (define-ns (scat) foldl (make-fold foldl)) (define-ns (scat) foldr (make-fold foldr)) ;; Composition ;; These are here because they know about the code representation ;; (unary scheme functions). (define-word compose (g f . stack+) (cons (make-word (lambda (state) (g (f state)))) stack+)) ;; Partial application (conspose). The closest thing to closures. I ;; feel no need for 'quote' as in Joy. Just use "() cns". Here it is ;; possible to relativly unambiguously create a source rep. (define-word cns (f a . stack+) (cons (make-word (lambda (state) (f (state-cons a state)))) stack+)) ;; Exception handling using 'catch'. If the 'body' code generates an ;; exception that is tagged with 'tag', the 'handler' code is ;; invoked with the exception value pushed to the stack. ;; (define-word catch (tag handler body . s) ;; (with-handlers ;; (((lambda (ex) ;; (or (eq? tag #t) ;; catch all ;; (and (list? ex) ;; (eq? tag (car ex))))) ;; (lambda (ex) ;; (apply-base (code handler) ;; (cons ex ;; (pretty-exn ex) ;; s))))) ;; (apply-base (code body) s))) ;; Fixme: error capture disabled here: handle somewhere else. ;(define-word run/error (fn . s) ; (apply-base (code fn) s)) ;; Apply a function to an isolated stack, ignoring state WRITE effects. (define-ctrl apply (i-stack fn stack+) (let ((o-stack (state->stack (fn (update i-stack))))) (update (cons o-stack stack+)))) ;; these compositions are 'tainted' with control flow. (compositions (scat) scat: (2dip (cons) dip dip uncons) ;; (p-error "\nERROR:" d d cr) (pl (p cr) for-each) (list '() apply reverse) ;; flow control (ifte choose run) (if () ifte) (unless () swap ifte) ;; (try #t catch) ;; catch all exceptions (forever dup dip forever) ;; loop forever (until exception) ;; (need over (drop) (throw) ifte) ;; (1-throw ('() cons) dip swons throw) (2run (run) dip run) ;; file ;; (try-delete-file (delete-file) (drop drop) try) )