From 85fa16c362abd0e5e128beb9e5140c1d5d647676 Mon Sep 17 00:00:00 2001 From: Chris Hanson Date: Wed, 12 Oct 1994 07:54:09 +0000 Subject: [PATCH] Initial revision --- v7/src/edwin/eystep.scm | 298 +++++++++++++++++++++++++ v7/src/runtime/ystep.scm | 469 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 767 insertions(+) create mode 100644 v7/src/edwin/eystep.scm create mode 100644 v7/src/runtime/ystep.scm diff --git a/v7/src/edwin/eystep.scm b/v7/src/edwin/eystep.scm new file mode 100644 index 000000000..89bae4ce3 --- /dev/null +++ b/v7/src/edwin/eystep.scm @@ -0,0 +1,298 @@ +;;; -*-Scheme-*- +;;; +;;; $Id: eystep.scm,v 1.1 1994/10/12 07:54:09 cph Exp $ +;;; +;;; Copyright (c) 1994 Massachusetts Institute of Technology +;;; +;;; This material was developed by the Scheme project at the +;;; Massachusetts Institute of Technology, Department of +;;; Electrical Engineering and Computer Science. Permission to +;;; copy this software, to redistribute it, and to use it for any +;;; purpose is granted, subject to the following restrictions and +;;; understandings. +;;; +;;; 1. Any copy made of this software must include this copyright +;;; notice in full. +;;; +;;; 2. Users of this software agree to make their best efforts (a) +;;; to return to the MIT Scheme project any improvements or +;;; extensions that they make, so that these may be included in +;;; future releases; and (b) to inform MIT of noteworthy uses of +;;; this software. +;;; +;;; 3. All materials developed as a consequence of the use of this +;;; software shall duly acknowledge such use, in accordance with +;;; the usual standards of acknowledging credit in academic +;;; research. +;;; +;;; 4. MIT has made no warrantee or representation that the +;;; operation of this software will be error-free, and MIT is +;;; under no obligation to provide any services, by way of +;;; maintenance, update, or otherwise. +;;; +;;; 5. In conjunction with products arising from the use of this +;;; material, there shall be no use of the name of the +;;; Massachusetts Institute of Technology nor of any adaptation +;;; thereof in any advertising, promotional, or sales literature +;;; without prior written consent from MIT in each case. +;;; +;;; NOTE: Parts of this program (Edwin) were created by translation +;;; from corresponding parts of GNU Emacs. Users should be aware that +;;; the GNU GENERAL PUBLIC LICENSE may apply to these parts. A copy +;;; of that license should have been included along with this file. +;;; + +;;;; Edwin Interface to YStep + +(declare (usual-integrations)) + +(define-command step-expression + "Single-step an expression." + "xExpression to step" + (lambda (expression) + (with-stepper-evaluation-context + (lambda () + (step-form expression (evaluation-environment #f)))))) + +(define-command step-last-sexp + "Single-step the expression preceding point." + () + (lambda () + (step-region (let ((point (current-point))) + (make-region (backward-sexp point 1 'ERROR) point)) + (evaluation-environment #f)))) + +(define-command step-defun + "Single-step the definition that the point is in or before." + () + (lambda () + (step-region (let ((start (current-definition-start))) + (make-region start (forward-sexp start 1 'ERROR))) + (evaluation-environment #f)))) + +(define (step-region region environment) + (with-stepper-evaluation-context + (lambda () + (step-form (with-input-from-region region read) environment)))) + +(define (with-stepper-evaluation-context thunk) + (bind-condition-handler (list condition-type:error) + evaluation-error-handler + (lambda () + (with-input-from-port dummy-i/o-port + (lambda () + (with-output-to-transcript-buffer thunk)))))) + +;;;; Stepper Mode + +(define-major-mode stepper read-only-noarg "Stepper" + "Major mode for the stepper. +space advances the computation by one step +o steps over the current expression +u step over, but show the intevening events +U step over, show and animate intervening events +r run current expression to completion without stepping +e expand the step under the cursor +c contract the step under the cursor") + +(define-key 'stepper #\space 'stepper-step) +(define-key 'stepper #\o 'stepper-step-over) +(define-key 'stepper #\u 'stepper-step-until) +(define-key 'stepper #\U 'stepper-step-until-visible) +(define-key 'stepper #\r 'stepper-run) +(define-key 'stepper #\e 'stepper-expand) +(define-key 'stepper #\c 'stepper-contract) +(define-key 'stepper #\? 'stepper-summary) + +(define-command stepper-summary + "Summarize the stepper commands in the typein window." + () + (lambda () + (message "Space: single step, o: step over, e: expand, c: contract"))) + +(define-command stepper-step + "Single step. With argument, step multiple times." + "p" + (lambda (argument) (step-n (current-stepper-state) argument))) + +(define-command stepper-run + "Run current eval to completion without stepping." + () + (lambda () (step-run (current-stepper-state)))) + +(define-command stepper-step-until + "Step until current eval completes." + () + (lambda () (step-until (current-stepper-state)))) + +(define-command stepper-step-until-visibly + "Step until current eval completes, showing each step as it happens." + () + (lambda () (step-until-visibly (current-stepper-state)))) + +(define-command stepper-step-over + "Step over the current eval." + () + (lambda () (step-over (current-stepper-state)))) + +(define-command stepper-expand + "Expand the current step." + () + (lambda () + (let ((state (current-stepper-state)) + (node (current-node))) + (ynode-expand! node) + (edwin-step-output state #f `(EXPAND ,node))))) + +(define-command stepper-contract + "Contract the current step." + () + (lambda () + (let ((state (current-stepper-state)) + (node (current-node))) + (ynode-contract! node) + (edwin-step-output state #f `(CONTRACT ,node))))) + +;;;; Stepper Output Interface + +(define (initialize-package!) + ;; Load the stepper and grab its output hooks. + (load-option 'STEPPER) + (set! step-output-initialize edwin-step-output-initialize) + (set! step-output edwin-step-output) + (set! step-output-final-result edwin-step-output-final-result) + unspecific) + +(define (edwin-step-output-initialize state) + (select-buffer-other-window (get-stepper-buffer state))) + +(define (get-stepper-buffer state) + (let ((buffer (new-buffer "*Stepper*"))) + (add-kill-buffer-hook buffer kill-stepper-buffer) + (buffer-put! buffer 'STEPPER-STATE state) + (hash-table/put! stepper-buffers state buffer) + (set-buffer-read-only! buffer) + (set-buffer-major-mode! buffer (ref-mode-object stepper)) + buffer)) + +(define (kill-stepper-buffer buffer) + (let ((state (buffer-get buffer 'STEPPER-STATE))) + (if state + (hash-table/remove! stepper-buffers state))) + (buffer-remove! buffer 'STEPPER-STATE)) + +(define (buffer->stepper-state buffer) + (or (buffer-get buffer 'STEPPER-STATE) + (error:bad-range-argument buffer 'BUFFER->STEPPER-STATE))) + +(define (stepper-state->buffer state) + (or (hash-table/get stepper-buffers state #f) + (get-stepper-buffer state))) + +(define stepper-buffers + (make-eq-hash-table)) + +(define (current-stepper-state) + (buffer->stepper-state (current-buffer))) + +(define (edwin-step-output-final-result state result) + state + (editor-error + (string-append "Stepping terminated with result " + (write-to-string result)))) + +(define (current-node) + (let ((point (current-point))) + (or (get-text-property (mark-group point) + (mark-index point) + 'STEPPER-NODE + #f) + (editor-error "Point not pointing to stepper node.")))) + +(define (get-buffer-ynode-regions buffer) + (or (buffer-get buffer 'YNODE-REGIONS) + (let ((table (make-eq-hash-table))) + (buffer-put! buffer 'YNODE-REGIONS table) + table))) + +(define (clear-ynode-regions! regions) + (for-each mark-temporary! (hash-table/datum-list regions)) + (hash-table/clear! regions)) + +(define (ynode-start-mark regions node) + (hash-table/get regions node #f)) + +(define (save-ynode-region! regions node start end) + (hash-table/put! regions node (mark-temporary-copy start)) + (add-text-property (mark-group start) (mark-index start) (mark-index end) + 'STEPPER-NODE node)) + +(define (edwin-step-output state redisplay? #!optional last-event) + (let ((buffer (stepper-state->buffer state)) + (last-event + (if (default-object? last-event) + (stepper-last-event state) + last-event))) + (let ((regions (get-buffer-ynode-regions buffer))) + (clear-ynode-regions! regions) + (with-read-only-defeated (buffer-start buffer) + (lambda () + (delete-string (buffer-start buffer) (buffer-end buffer)) + (let ((node (stepper-root-node state)) + (start (mark-right-inserting-copy (buffer-start buffer))) + (point (mark-left-inserting-copy (buffer-start buffer)))) + (let loop ((node node) (level 0)) + (if (not (eq? (ynode-type node) 'STEPPED-OVER)) + (begin + (move-mark-to! start point) + (output-and-mung-region point + (lambda () + (debugger-pp (ynode-exp node) + (* 2 level) + (current-output-port))) + (and last-event + (eq? (car last-event) 'CALL) + (eq? (cadr last-event) node) + (lambda (region) + (highlight-region-excluding-indentation region + #t)))) + (insert-string (if (ynode-hidden-children? node) + " ===> " + " => ") + point) + (let ((value-node (ynode-value-node node))) + (output-and-mung-region point + (lambda () + (write + (ynode-result + (if (eq? (ynode-type node) 'STEP-OVER) + value-node + node)))) + (and last-event + (eq? (car last-event) 'RETURN) + (eq? (cadr last-event) value-node) + (lambda (region) + (highlight-region region #t))))) + (insert-newline point) + (save-ynode-region! regions node start point) + (if (not (eq? 'STEP-OVER (ynode-type node))) + (for-each (lambda (n) (loop n (+ level 1))) + (reverse (ynode-children node))))))) + (mark-temporary! point) + (mark-temporary! start)))) + (buffer-not-modified! buffer) + (if last-event + (let ((start (ynode-start-mark regions (cadr last-event)))) + (if start + (set-buffer-point! buffer start)))))) + (if redisplay? (update-screens! #f))) + +(define (output-and-mung-region point thunk region-munger) + ;; Display something in the stepper buffer and then run something on + ;; it. REGION-MUNGER takes one argument, a region. + (let ((start (mark-right-inserting-copy point))) + (with-output-to-mark point thunk) + (if region-munger (region-munger (make-region start point))) + (mark-temporary! start))) + +(initialize-package!) \ No newline at end of file diff --git a/v7/src/runtime/ystep.scm b/v7/src/runtime/ystep.scm new file mode 100644 index 000000000..3f80067fd --- /dev/null +++ b/v7/src/runtime/ystep.scm @@ -0,0 +1,469 @@ +#| -*-Scheme-*- + +$Id: ystep.scm,v 1.1 1994/10/12 07:54:00 cph Exp $ + +Copyright (c) 1994 Massachusetts Institute of Technology + +This material was developed by the Scheme project at the Massachusetts +Institute of Technology, Department of Electrical Engineering and +Computer Science. Permission to copy this software, to redistribute +it, and to use it for any purpose is granted, subject to the following +restrictions and understandings. + +1. Any copy made of this software must include this copyright notice +in full. + +2. Users of this software agree to make their best efforts (a) to +return to the MIT Scheme project any improvements or extensions that +they make, so that these may be included in future releases; and (b) +to inform MIT of noteworthy uses of this software. + +3. All materials developed as a consequence of the use of this +software shall duly acknowledge such use, in accordance with the usual +standards of acknowledging credit in academic research. + +4. MIT has made no warrantee or representation that the operation of +this software will be error-free, and MIT is under no obligation to +provide any services, by way of maintenance, update, or otherwise. + +5. In conjunction with products arising from the use of this material, +there shall be no use of the name of the Massachusetts Institute of +Technology nor of any adaptation thereof in any advertising, +promotional, or sales literature without prior written consent from +MIT in each case. |# + +;;;; YStep - a step away from ZStep +;;; package: (runtime stepper) + +(declare (usual-integrations)) + +(define-structure (stepper (constructor make-stepper (stack))) + (stack '()) + (run? #f) ;#t => run; #f => step + (step-over #f) ;#f or top node of step-over + (step-until? #f) ;the step-over is really a step-until + next ;continuation of stepped program + continuation + last-event ;last thing that happened + (finished #f) ;when completed, stack is + ;empty and this points to top node + hooks ;low-level stepper hooks + (trace '()) ;low-level trace recording + ) + +(define (stack-push! state node) + (set-stepper-stack! state (cons node (stepper-stack state)))) + +(define (stack-pop! state) + (set-stepper-stack! state (cdr (stepper-stack state)))) + +(define (stack-top state) + (car (stepper-stack state))) + +(define (stack-bottom state) + (car (last-pair (stepper-stack state)))) + +(define (stack-empty? state) + (null? (stepper-stack state))) + +(define (stepper-root-node state) + (if (stack-empty? state) + (stepper-finished state) + (stack-bottom state))) + +;;; The magic numbers here represent the number of eval and return +;;; events that occur during the startup process. They will very +;;; likely have to change when the system changes. + +(define (step-form expression environment) + ;; start a new evaluation + (step-start (make-ynode #f 'TOP ynode-exp:top-level) + (lambda () (eval expression environment)) + (if (stepper-compiled?) 0 6) + (if (stepper-compiled?) 1 5))) + +(define (step-proceed) + ;; proceed from breakpoint + (step-start (make-ynode #f 'PROCEED ynode-exp:proceed) + (lambda () (continue)) + (if (stepper-compiled?) 0 4) + (if (stepper-compiled?) 5 7))) + +(define (stepper-compiled?) + (compiled-procedure? (lambda () unspecific))) + +(define (step-start top-node thunk skip-evals skip-returns) + (if (not (step-hooks-present?)) + (error "Sorry, this copy of Scheme does not support stepping.")) + (let ((state (make-stepper (list top-node)))) + (set-stepper-hooks! state (make-stepper-hooks state)) + (set-stepper-next! state + (lambda () + (dummy-eval-step + (make-starting-hooks state skip-evals skip-returns)) + (thunk))) + (step-output-initialize state) + (step state))) + +(define (step state) + (set-stepper-run?! state #f) + (raw-step state)) + +(define (step-run state) + (set-stepper-run?! state #t) + (raw-step state)) + +(define (step-quit state) + ;; [entry] not working yet + (dummy-eval-step no-step-hooks) + ((stepper-next state))) + +(define (step-n state n) + (do ((n n (- n 1)) + (value unspecific (step state))) + ((<= n 0) value))) + +(define (step-over state) + (set-stepper-step-until?! state #f) + (step-over-1 state)) + +(define (step-until state) + (set-stepper-step-until?! state #t) + (step-over-1 state)) + +(define (step-until-visibly state) + (set-stepper-step-until?! state 'ANIMATE) + (step-over-1 state)) + +(define (step-over-1 state) + (if (not (eq? (car (stepper-last-event state)) 'CALL)) + (error "Last event was not a call:" (stepper-last-event state))) + (set-stepper-step-over! state (stack-top state)) + (new-ynode-type! (stack-top state) + (if (stepper-step-until? state) 'EVAL 'STEP-OVER)) + (raw-step state)) + +(define (raw-step state) + ;; the workhorse + (if (stepper-finished state) + (step-output-final-result state (ynode-result (stepper-finished state))) + (begin + (set-stepper-next! state + (call-with-current-continuation + (lambda (kk) + (set-stepper-continuation! state kk) + ((stepper-next state))))) + (if (stepper-run? state) + (raw-step state) + (step-output state #f))))) + +;;; Output Stubs: + +(define (step-output-initialize state) + state + unspecific) + +(define (step-output state redisplay?) + state redisplay? + unspecific) + +(define (step-output-final-result state value) + state + value) + +;;;; Low-level Hooks + +(define (make-stepper-hooks state) + (letrec + ((hooks + (hunk3-cons + (lambda (expr env) + (hook-record state + (list 'EVAL (map-reference-trap (lambda () expr)) env)) + (process-eval state (map-reference-trap (lambda () expr))) + (primitive-eval-step expr env hooks)) + (lambda (proc . args) + (hook-record state + (list 'APPLY + proc + (map (lambda (arg) + (map-reference-trap (lambda () arg))) + args))) + (process-apply state proc) + (primitive-apply-step proc args hooks)) + (lambda (value) + (hook-record state + (list 'RETURN (map-reference-trap (lambda () value)))) + (process-return state (map-reference-trap (lambda () value))) + (primitive-return-step value hooks))))) + hooks)) + +(define (make-starting-hooks state skip-evals skip-returns) + (letrec + ((hooks + (hunk3-cons + (lambda (expr env) + (if (and (<= skip-evals 0) (<= skip-returns 0)) + ((system-hunk3-cxr0 (stepper-hooks state)) expr env) + (begin + (set! skip-evals (- skip-evals 1)) + (hook-record state (list 'EVAL expr env)) + (primitive-eval-step expr env hooks)))) + #f + (lambda (result) + (if (and (<= skip-evals 0) (<= skip-returns 0)) + ((system-hunk3-cxr2 (stepper-hooks state)) result) + (begin + (set! skip-returns (- skip-returns 1)) + (hook-record state (list 'RESULT result)) + (primitive-return-step result hooks))))))) + hooks)) + +(define no-step-hooks + (hunk3-cons #f #f #f)) + +(define-integrable primitive-eval-step + (ucode-primitive primitive-eval-step)) + +(define-integrable primitive-apply-step + (ucode-primitive primitive-apply-step)) + +(define-integrable primitive-return-step + (ucode-primitive primitive-return-step)) + +;;;; Worker Bees + +(define (process-eval state exp) + (if (reduction? exp (ynode-exp (stack-top state))) + (process-reduction state)) + (let ((node + (make-ynode (and (not (stack-empty? state)) + (stack-top state)) + (if (and (stepper-step-over state) + (not (stepper-step-until? state))) + 'STEPPED-OVER + 'EVAL) + exp))) + (stack-push! state node) + (set-stepper-last-event! state `(CALL ,node)) + (maybe-redisplay state))) + +(define (process-apply state proc) + (if (compound-procedure? proc) + (process-reduction state))) + +(define (process-return state result) + (if (stepper-step-over state) + (maybe-end-step-over state)) + (let ((node + (let ((node (stack-top state))) + (if (eq? (ynode-type node) 'PROCEED) + (ynode-splice-under node) + (begin + (stack-pop! state) + node))))) + (new-ynode-result! node result) + (if (stack-empty? state) + (set-stepper-finished! state node)) + (set-stepper-last-event! state `(RETURN ,node)) + (maybe-redisplay state))) + +(define (maybe-redisplay state) + (if (stepper-step-over state) + (if (eq? (stepper-step-until? state) 'ANIMATE) + (step-output state #t)) + (call-with-current-continuation + (lambda (k) + ((stepper-continuation state) (lambda () (k unspecific))))))) + +(define (maybe-end-step-over state) + (if (ynode-reduces-to? (stack-top state) (stepper-step-over state)) + (begin + (set-stepper-step-over! state #f) + (set-stepper-step-until?! state #f)))) + +(define (process-reduction state) + (new-ynode-result! (stack-top state) ynode-result:reduced) + (stack-pop! state)) + +(define (reduction? f1 f2) + ;; Args are SCode expressions. True if F2 is a reduction of F1. + (cond ((conditional? f2) + (or (eq? f1 (conditional-consequent f2)) + (eq? f1 (conditional-alternative f2)))) + ((sequence? f2) + (eq? f1 (car (last-pair (sequence-actions f2))))) + (else #f))) + +;;;; Stepper nodes + +(define-structure (ynode (constructor make-ynode-1 (parent type exp))) + ;; Could easily store environment as well. + parent + type + (exp #f read-only #t) + (children '()) + (result #f) + (redisplay-flags (cons #t (if parent (ynode-redisplay-flags parent) '())) + read-only #t)) + +(define ynode-exp:top-level (list 'STEPPER-TOP-LEVEL)) +(define ynode-exp:proceed (list 'STEPPER-PROCEED)) + +(define ynode-result:waiting (list ')) +(define ynode-result:reduced (list ')) +(define ynode-result:unknown (list ')) + +(define (ynode-reduced? node) + (eq? (ynode-result node) ynode-result:reduced)) + +(define (make-ynode parent type exp) + (let ((node (make-ynode-1 parent type exp))) + (set-ynode-result! node ynode-result:waiting) + (if parent + (set-ynode-children! parent (cons node (ynode-children parent)))) + (ynode-needs-redisplay! node) + node)) + +(define (ynode-previous node) + (let loop ((sibs (ynode-children (ynode-parent node)))) + (cond ((null? sibs) + #f) + ((eq? (car sibs) node) + (and (not (null? (cdr sibs))) + (cadr sibs))) + (else + (loop (cdr sibs)))))) + +(define (ynode-next node) + (let loop ((sibs (ynode-children (ynode-parent node)))) + (cond ((or (null? sibs) (null? (cdr sibs))) + #f) + ((eq? (cadr sibs) node) + (car sibs)) + (else + (loop (cdr sibs)))))) + +(define (ynode-value-node node) + (if (ynode-reduced? node) + (let ((next (ynode-next node))) + (and next + (ynode-value-node next))) + node)) + +(define (ynode-reduces-to? node reduces-to) + (and node + (or (eq? node reduces-to) + (let ((previous (ynode-previous node))) + (and previous + (ynode-reduced? previous) + (ynode-reduces-to? previous reduces-to)))))) + +(define (ynode-splice-under node) + (let ((children (ynode-children node))) + (set-ynode-children! node '()) + (let ((new-node (make-ynode node 'EVAL ynode-result:unknown))) + (set-ynode-children! new-node children) + (for-each (lambda (c) (set-ynode-parent! c new-node)) children) + (let loop ((node new-node)) + (ynode-needs-redisplay! ynode) + (for-each loop (ynode-children node))) + new-node))) + +(define (ynode-reductions node) + (if (ynode-reduced? node) + (let ((next (ynode-next node))) + (cons next (ynode-reductions next))) + '())) + +(define (ynode-dependents node) + ;; A dependent (misnomer) roughly means nodes that are directly + ;; called by another node (which is not the same as children, + ;; because reductions muck things up). + (if (ynode-reduced? node) + (cons (ynode-next node) + (ynode-direct-children node)) + (ynode-direct-children node))) + +(define (ynode-direct-children node) + ;; A "direct" child is one that is not a reduction of another child... + (let loop ((children (ynode-children node)) (dependents '())) + (if (null? children) + dependents + (loop (cdr children) + (if (and (not (null? (cdr children))) + (ynode-reduced? (cadr children))) + dependents + (cons (car children) dependents)))))) + +(define (ynode-hidden-children? node) + ;; used to control drawing of arrow + (and (eq? (ynode-type node) 'STEP-OVER) + (not (null? (ynode-children node))))) + +(define (ynode-needs-redisplay! ynode) + (if (not (car (ynode-redisplay-flags ynode))) + (begin + (set-car! (ynode-redisplay-flags ynode) #t) + (if (ynode-parent ynode) + (ynode-needs-redisplay! (ynode-parent ynode)))))) + +(define (ynode-needs-redisplay? ynode) + (car (ynode-redisplay-flags ynode))) + +(define (ynode-doesnt-need-redisplay! ynode) + (set-car! (ynode-redisplay-flags ynode) #f)) + +(define (new-ynode-type! ynode type) + (set-ynode-type! ynode type) + (ynode-needs-redisplay! ynode)) + +(define (new-ynode-result! ynode result) + (set-ynode-result! ynode result) + (ynode-needs-redisplay! ynode)) + +(define (ynode-expand! node) + (new-ynode-type! node 'EVAL) + (for-each (lambda (dependent) + (if (eq? (ynode-type dependent) 'STEPPED-OVER) + (new-ynode-type! dependent 'STEP-OVER))) + (ynode-dependents node))) + +(define (ynode-contract! node) + (new-ynode-type! node 'STEP-OVER) + (for-each (lambda (dependent) + (new-ynode-type! dependent 'STEPPED-OVER)) + (ynode-reductions node))) + +;;;; Miscellaneous + +(define (dummy-eval-step hooks) + (primitive-eval-step #f system-global-environment hooks)) + +(define (step-hooks-present?) + (let ((flag #f)) + (dummy-eval-step + (hunk3-cons #f + #f + (lambda (value) + (set! flag #t) + (primitive-return-step value no-step-hooks)))) + flag)) + +;;; Debugging trace: + +;;; disabled, see next definition +;;; (define (hook-record state item) +;;; (set-stepper-trace! state (cons item (stepper-trace state)))) + +(define-integrable (hook-record state item) + ;; DEFINE-INTEGRABLE guarantees that argument in ITEM position is + ;; not evaluated. + state item + unspecific) + +(define (print-hook-trace state) + (pp (let loop ((thing (stepper-trace state))) + (cond ((list? thing) (map loop thing)) + ((symbol? thing) thing) + (else (unsyntax thing)))))) \ No newline at end of file -- 2.25.1