;; Copyright 2015-2024
;; Kaz Kylheku <kaz@kylheku.com>
;; Vancouver, Canada
;; All rights reserved.
;;
;; Redistribution and use in source and binary forms, with or without
;; modification, are permitted provided that the following conditions are met:
;;
;; 1. Redistributions of source code must retain the above copyright notice,
;;    this list of conditions and the following disclaimer.
;;
;; 2. Redistributions in binary form must reproduce the above copyright notice,
;;    this list of conditions and the following disclaimer in the documentation
;;    and/or other materials provided with the distribution.
;;
;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
;; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
;; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;; ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
;; LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
;; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
;; SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
;; CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
;; ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
;; POSSIBILITY OF SUCH DAMAGE.

(defvar *place-clobber-expander* (hash))
(defvar *place-update-expander* (hash))
(defvar *place-delete-expander* (hash))
(defvar *place-macro* (hash))
(defvar sys:*pl-env* nil)
(defvar sys:*pl-form* nil)

(defun sys:eval-err (. params)
  (throwf 'eval-error . params))

(defun sys:sym-update-expander (getter-name setter-name
                                place-expr op-body)
  ^(macrolet ((,getter-name () ',place-expr)
              (,setter-name (val-expr) ^(sys:setq ,',place-expr
                                                  ,val-expr)))
     ,op-body))

(defun sys:sym-clobber-expander (simple-setter-name
                                 place-expr op-body)
  ^(macrolet ((,simple-setter-name (val-expr)
                 ^(sys:setq ,',place-expr ,val-expr)))
     ,op-body))

(defun sys:sym-delete-expander (deleter-name
                                place-expr . op-body)
  ^(macrolet ((,deleter-name (:env env)
                (when (lexical-var-p env ',place-expr)
                  (sys:eval-err "~s is a lexical variable, thus not deletable"
                                ',place-expr))
                ^(prog1
                   (symbol-value ',',place-expr)
                   (makunbound ',',place-expr))))
     ,*op-body))

(defun sys:get-place-macro (sym)
  (or [*place-macro* sym]
      (progn (sys:autoload-try-fun sym) [*place-macro* sym])))

(defun macroexpand-place (unex-place : env)
  (while t
    (let ((place unex-place)
          pm-expander)
      (while (and (consp place)
                  (sys:setq pm-expander (sys:get-place-macro (car place)))
                  (sys:setq place (sys:set-macro-ancestor [pm-expander place] place))
                  (neq place unex-place))
        (sys:setq unex-place place))
      (sys:setq place (macroexpand-1 place env))
      (when (or (eq place unex-place)
                (null place)
                (and (atom place) (not (symbolp place))))
        (return-from macroexpand-place place))
      (sys:setq unex-place place))))

(defun macroexpand-1-place (unex-place : env)
  (ignore env)
  (let ((pm-expander (if (consp unex-place)
                       (sys:get-place-macro (car unex-place)))))
    (if pm-expander
      [pm-expander unex-place]
      unex-place)))

(defun place-form-p (unex-place env)
  (let ((place (macroexpand-place unex-place env)))
    (or (bindable place)
        (and (consp place) [*place-update-expander* (car place)] t))))

(defun get-update-expander (place)
  (cond
    ((symbolp place) (fun sys:sym-update-expander))
    ((consp place) (or [*place-update-expander* (car place)]
                       (sys:eval-err "~s is not an assignable place" place)))
    (t (sys:eval-err "form ~s is not syntax denoting an assignable place" place))))

(defun get-clobber-expander (place)
  (cond
    ((symbolp place) (fun sys:sym-clobber-expander))
    ((consp place) (or [*place-clobber-expander* (car place)]
                       (iflet ((fun [*place-update-expander* (car place)]))
                          (op apply fun (gensym) @1 @2 @rest))
                       (sys:eval-err "~s is not an assignable place" place)))
    (t (sys:eval-err "form ~s is not syntax denoting an assignable place" place))))

(defun get-delete-expander (place)
  (cond
    ((symbolp place) (fun sys:sym-delete-expander))
    ((consp place) (or [*place-delete-expander* (car place)]
                       (sys:eval-err "~s is not a deletable place" place)))
    (t (sys:eval-err "form ~s is not syntax denoting a deletable place" place))))

(defun sys:r-s-let-expander (bindings body e letsym pred)
  (let ((exp-bindings (mapcar (aret ^(,@1 ,(macroexpand @2 e))) bindings)))
    (let ((renames [keep-if pred exp-bindings second])
          (regular [remove-if pred exp-bindings second]))
    (cond ((and renames regular)
            ^(symacrolet ,renames
               (,letsym ,regular ,*body)))
          (renames ^(symacrolet ,renames ,*body))
          (regular ^(,letsym ,regular ,*body))
          (t ^(progn ,*body))))))

(defmacro rlet (bindings :env e . body)
  [sys:r-s-let-expander bindings body e 'let constantp])

(defmacro slet (bindings :env e . body)
  (sys:r-s-let-expander bindings body e 'let [orf constantp
                                                  (op lexical-var-p e)]))

(defmacro alet (bindings :env e . body)
  (let ((exp-bindings (mapcar (aret ^(,@1 ,(macroexpand @2 e))) bindings)))
    (if [some exp-bindings constantp second]
      [sys:r-s-let-expander exp-bindings body e 'alet constantp]
      ^(,(if [all exp-bindings (op lexical-var-p e) second]
           'symacrolet 'let)
         ,exp-bindings ,*body))))

(defmacro with-gensyms (syms . body)
  ^(let ,(zip syms (repeat '((gensym)))) ,*body))

(defun sys:propagate-ancestor (to-tree from-form . syms)
  (unless (macro-ancestor to-tree)
    (tree-case to-tree
      ((a . d)
       (when (memq a syms)
         (sys:set-macro-ancestor to-tree from-form))
       (sys:propagate-ancestor a from-form . syms)
       (sys:propagate-ancestor d from-form . syms))))
  to-tree)

(defun call-update-expander (getter setter unex-place env body)
  (sys:propagate-ancestor body unex-place getter setter)
  (let* ((place (macroexpand-place unex-place env))
         (expander (get-update-expander place))
         (sys:*pl-env* env)
         (sys:*pl-form* unex-place)
         (expansion [expander getter setter place body])
         (expansion-ex (expand expansion env)))
    (sys:propagate-ancestor expansion-ex place getter setter)))

(defun call-clobber-expander (ssetter unex-place env body)
  (sys:propagate-ancestor body unex-place ssetter)
  (let* ((place (macroexpand-place unex-place env))
         (expander (get-clobber-expander place))
         (sys:*pl-env* env)
         (sys:*pl-form* unex-place)
         (expansion [expander ssetter place body])
         (expansion-ex (expand expansion env)))
    (sys:propagate-ancestor expansion-ex place ssetter)))

(defun call-delete-expander (deleter unex-place env body)
  (sys:propagate-ancestor body unex-place deleter)
  (let* ((place (macroexpand-place unex-place env))
         (expander (get-delete-expander place))
         (sys:*pl-env* env)
         (sys:*pl-form* unex-place)
         (expansion [expander deleter place body])
         (expansion-ex (expand expansion env)))
    (sys:propagate-ancestor expansion-ex place deleter)))

(defmacro with-update-expander ((getter setter) unex-place env body)
  ^(with-gensyms (,getter ,setter)
     (call-update-expander ,getter ,setter ,unex-place ,env ,body)))

(defmacro with-clobber-expander ((ssetter) unex-place env body)
  ^(with-gensyms (,ssetter)
     (call-clobber-expander ,ssetter ,unex-place ,env ,body)))

(defmacro with-delete-expander ((deleter) unex-place env body)
  ^(with-gensyms (,deleter)
     (call-delete-expander ,deleter ,unex-place ,env ,body)))

(defmacro set (:env env . place-value-pairs)
  (let ((assign-forms (mapcar (tb ((place : (value nil value-present-p)))
                                (unless value-present-p
                                  (sys:eval-err "set: arguments must be pairs"))
                                (with-clobber-expander (ssetter) place env
                                   ^(,ssetter ,value)))
                              (tuples 2 place-value-pairs))))
    (if (cdr assign-forms)
      ^(progn ,*assign-forms)
      (car assign-forms))))

(defmacro pset (:env env . place-value-pairs)
  (let ((len (length place-value-pairs)))
    (cond
      ((oddp len) (sys:eval-err "pset: arguments must be pairs"))
      ((<= len 2) ^(set ,*place-value-pairs))
      (t (let* ((pvtgs (mapcar (tb ((a b))
                                 (list a b (gensym) (gensym) (gensym)))
                               (tuples 2 place-value-pairs)))
                (ls (reduce-left (tb ((lets stores) (place value temp getter setter))
                                   (ignore place getter)
                                   (list ^((,temp ,value) ,*lets)
                                         ^((,setter ,temp) ,*stores)))
                                 pvtgs '(nil nil)))
                (lets (first ls))
                (stores (second ls))
                (body-form ^(rlet (,*lets) ,*stores)))
           (reduce-left (tb (accum-form (place value temp getter setter))
                          (ignore place value temp)
                          (call-update-expander getter setter
                                                place env accum-form))
                        pvtgs body-form))))))

(defmacro zap (place : (new-val nil) :env env)
  (with-update-expander (getter setter) place env
    ^(prog1 (,getter) (,setter ,new-val))))

(defmacro flip (place :env env)
  (with-update-expander (getter setter) place env
    ^(,setter (not (,getter)))))

(defmacro inc (place : (delta 1) :env env)
  (with-update-expander (getter setter) place env
    (caseql delta
      (0 place)
      (1 ^(,setter (succ (,getter))))
      (2 ^(,setter (ssucc (,getter))))
      (3 ^(,setter (sssucc (,getter))))
      (t ^(,setter (+ (,getter) ,delta))))))

(defmacro dec (place : (delta 1) :env env)
  (with-update-expander (getter setter) place env
    (caseql delta
      (0 place)
      (1 ^(,setter (pred (,getter))))
      (2 ^(,setter (ppred (,getter))))
      (3 ^(,setter (pppred (,getter))))
      (t ^(,setter (- (,getter) ,delta))))))

(defmacro pinc (place : (delta 1) :env env)
  (with-gensyms (oldval)
    (with-update-expander (getter setter) place env
      (caseql delta
        (0 place)
        (1 ^(let ((,oldval (,getter))) (,setter (succ ,oldval)) ,oldval))
        (2 ^(let ((,oldval (,getter))) (,setter (ssucc ,oldval)) ,oldval))
        (3 ^(let ((,oldval (,getter))) (,setter (sssucc ,oldval)) ,oldval))
        (t ^(let ((,oldval (,getter))) (,setter (+ ,oldval, delta)) ,oldval))))))

(defmacro pdec (place : (delta 1) :env env)
  (with-gensyms (oldval)
    (with-update-expander (getter setter) place env
      (caseql delta
        (0 place)
        (1 ^(let ((,oldval (,getter))) (,setter (pred ,oldval)) ,oldval))
        (2 ^(let ((,oldval (,getter))) (,setter (ppred ,oldval)) ,oldval))
        (3 ^(let ((,oldval (,getter))) (,setter (pppred ,oldval)) ,oldval))
        (t ^(let ((,oldval (,getter))) (,setter (- ,oldval, delta)) ,oldval))))))

(defmacro swap (place-0 place-1 :env env)
  (with-gensyms (tmp)
    (with-update-expander (getter-0 setter-0) place-0 env
      (with-update-expander (getter-1 setter-1) place-1 env
        ^(let ((,tmp (,getter-0)))
           (,setter-0 (,getter-1))
           (,setter-1 ,tmp))))))

(defmacro push (new-item place :env env)
  (with-gensyms (new-sym)
    ^(alet ((,new-sym ,new-item))
       ,(with-update-expander (getter setter) place env
          ^(,setter (cons ,new-sym (,getter)))))))

(defmacro pop (place :env env)
  (with-gensyms (tmp)
    (with-update-expander (getter setter) place env
      ^(alet ((,tmp (,getter)))
         (prog1 (car ,tmp) (,setter (cdr ,tmp)))))))

(defmacro pushnew (new-item place :env env :
                            (testfun :)
                            (keyfun :))
  (with-update-expander (getter setter) place env
    (with-gensyms (new-item-sym old-list-sym)
      ^(rlet ((,new-item-sym ,new-item))
         ,(with-update-expander (getter setter) place env
            ^(let ((,old-list-sym (,getter)))
               (if (member ,new-item-sym ,old-list-sym ,testfun ,keyfun)
                 ,old-list-sym
                 (,setter (cons ,new-item-sym ,old-list-sym)))))))))

(defmacro shift (:form f :env env . places)
  (tree-case places
    (() (compile-error f "need at least two arguments"))
    ((t) (compile-error f "need at least two arguments"))
    ((place newvalue)
     (with-update-expander (getter setter) place env
        ^(prog1 (,getter) (,setter ,newvalue))))
    ((place . others)
      (with-update-expander (getter setter) place env
        ^(prog1 (,getter) (,setter (shift ,*others)))))))

(defmacro rotate (:env env . places)
  (tree-case places
    (() ())
    ((fplace) fplace)
    ((fplace . rplaces)
     (with-gensyms (tmp)
       (with-update-expander (getter-f setter-f) fplace env
         ^(let ((,tmp (,getter-f)))
            (,setter-f (shift ,*rplaces ,tmp))
            ,tmp))))))

(defmacro test-set (:env env place)
  (with-update-expander (getter setter) place env
    ^(unless (,getter)
       (,setter t))))

(defmacro test-clear (:env env place)
  (with-update-expander (getter setter) place env
    ^(when (,getter)
       (,setter nil)
       t)))

(defmacro compare-swap (:env env comp-fun place comp-val store-val)
  (with-update-expander (getter setter) place env
    ^(when (,comp-fun (,getter) ,comp-val)
       (,setter ,store-val)
       t)))

(defmacro test-inc (place : (delta 1) (upfrom-val 0))
  ^(eql (pinc ,place ,delta) ,upfrom-val))

(defmacro test-dec (place : (delta 1) (downto-val 0))
  ^(eql (dec ,place ,delta) ,downto-val))

(defmacro del (place :env env)
  (with-delete-expander (deleter) place env
    ^(,deleter)))

(defmacro lset (:form f . places-source)
  (let ((places (butlast places-source))
        (source (last places-source))
        (orig (gensym))
        (iter (gensym)))
    (unless places
      (compile-error f "require one or more places followed by expression"))
    ^(let* ((,orig ,(car source))
            (,iter ,orig))
       ,*(butlast (mappend (ret ^((set ,@1 (car ,iter)) (set ,iter (cdr ,iter))))
                           places))
       ,orig)))

(defmacro upd (place . opip-args)
  (with-gensyms (pl)
    ^(placelet ((,pl ,place))
       (set ,pl (call (opip ,*opip-args) ,pl)))))

(defmacro ensure (:env env place init-expr)
  (with-gensyms (existing)
    (with-update-expander (getter setter) place env
      ^(iflet ((,existing (,getter)))
         ,existing
         (,setter ,init-expr)))))

(defmacro defplace (place-destructuring-args body-sym
                    (getter-sym setter-sym update-body) :
                    ((ssetter-sym clobber-body))
                    ((deleter-sym delete-body)))
  (let ((name (car place-destructuring-args))
        (args (cdr place-destructuring-args)))
    (unless (and name
                 (symbolp name)
                 (not (keywordp name))
                 (not (eq t name)))
      (compile-error sys:*pl-form* "~s cannot be used as a place name" name))
    (with-gensyms (place)
      ^(progn
         (sethash *place-update-expander* ',name
           (lambda (,getter-sym ,setter-sym ,place ,body-sym)
             (tree-bind ,args (cdr ,place)
                ,update-body)))
         ,*(if ssetter-sym
            ^((sethash *place-clobber-expander* ',name
                (lambda (,ssetter-sym ,place ,body-sym)
                  (tree-bind ,args (cdr ,place)
                     ,clobber-body)))))
         ,*(if deleter-sym
             ^((sethash *place-delete-expander* ',name
                 (lambda (,deleter-sym ,place ,body-sym)
                   (tree-bind ,args (cdr ,place)
                      ,delete-body)))))
         ',name))))

(defmacro define-place-macro (name place-destructuring-args . body)
  (with-gensyms (name-dummy args)
    ^(progn
       (sethash *place-macro* ',name
                (lambda (,args)
                  (mac-param-bind ,args
                                  (,name-dummy ,*place-destructuring-args)
                                  ,args ,*body)))
       ',name)))

(defplace (sys:var arg) body
  (getter setter
    ^(macrolet ((,getter () ^(sys:var ,',arg))
                (,setter (val) ^(sys:setq ,'(sys:var ,arg) ,val)))
       ,body)))

(defplace (sys:l1-val arg) body
  (getter setter
    ^(macrolet ((,getter () ^(sys:l1-val ,',arg))
                (,setter (val) ^(sys:l1-setq ,',arg ,val)))
         ,body))
  (ssetter
    ^(macrolet ((,ssetter (val) ^(sys:l1-setq ,',arg ,val)))
       ,body)))

(defplace (sys:lisp1-value arg) body
  (getter setter
    ^(macrolet ((,getter () ^(sys:lisp1-value ,',arg))
                (,setter (val) ^(sys:lisp1-setq ,',arg ,val)))
         ,body))
  (ssetter
    ^(macrolet ((,ssetter (val) ^(sys:lisp1-setq ,',arg ,val)))
       ,body)))

(defplace (car cell) body
  (getter setter
    (with-gensyms (cell-sym)
      ^(slet ((,cell-sym ,cell))
         (macrolet ((,getter () ^(car ,',cell-sym))
                    (,setter (val) ^(sys:rplaca ,',cell-sym ,val)))
           ,body))))
  (ssetter
    ^(macrolet ((,ssetter (val) ^(sys:rplaca ,',cell ,val)))
      ,body))
  (deleter
    ^(macrolet ((,deleter () ^(pop ,',cell)))
       ,body)))

(defplace (cdr cell) body
  (getter setter
    (with-gensyms (cell-sym)
      ^(slet ((,cell-sym ,cell))
         (macrolet ((,getter () ^(cdr ,',cell-sym))
                    (,setter (val) ^(sys:rplacd ,',cell-sym ,val)))
           ,body))))
  (ssetter
    ^(macrolet ((,ssetter (val) ^(sys:rplacd ,',cell ,val)))
       ,body))
  (deleter
    ^(macrolet ((,deleter ()
                  (with-gensyms (tmp)
                    (with-update-expander (cgetter csetter) ',cell nil
                      ^(let ((,tmp (,cgetter)))
                         (prog1 (cdr ,tmp) (,csetter (car ,tmp))))))))
       ,body)))

(defplace (nthcdr index list) body
  (getter setter
    (with-gensyms (index-sym list-sym sentinel-head-sym parent-cell-sym)
      (if (place-form-p list sys:*pl-env*)
        (with-update-expander (lgetter lsetter) list sys:*pl-env*
          ^(alet ((,index-sym ,index)
                  (,list-sym (,lgetter)))
             (let* ((,sentinel-head-sym (cons nil ,list-sym))
                    (,parent-cell-sym (nthcdr ,index-sym ,sentinel-head-sym)))
               (macrolet ((,getter () ^(cdr ,',parent-cell-sym))
                          (,setter (val)
                            ^(prog1 (sys:rplacd ,',parent-cell-sym ,val)
                                    (,',lsetter (cdr ,',sentinel-head-sym)))))
                 ,body))))
        ^(alet ((,index-sym ,index)
                (,list-sym ,list))
           (let ((,parent-cell-sym (nthcdr (pred ,index-sym) ,list-sym)))
             (macrolet ((,getter () ^(cdr ,',parent-cell-sym))
                        (,setter (val)
                          ^(sys:rplacd ,',parent-cell-sym ,val)))
               ,body)))))))

(defplace (nthlast index list) body
  (getter setter
    (with-gensyms (index-sym list-sym sentinel-head-sym parent-cell-sym)
      (if (place-form-p list sys:*pl-env*)
        (with-update-expander (lgetter lsetter) list sys:*pl-env*
          ^(alet ((,index-sym ,index)
                  (,list-sym (,lgetter)))
             (let* ((,sentinel-head-sym (cons nil ,list-sym))
                    (,parent-cell-sym (nthlast (succ ,index-sym)
                                               ,sentinel-head-sym)))
               (macrolet ((,getter () ^(cdr ,',parent-cell-sym))
                          (,setter (val)
                            ^(prog1 (sys:rplacd ,',parent-cell-sym ,val)
                                    (,',lsetter (cdr ,',sentinel-head-sym)))))
                 ,body))))
        ^(alet ((,index-sym index)
                (,list-sym ,list))
           (let ((,parent-cell-sym (nthlast (succ ,index-sym) ,list-sym)))
             (macrolet ((,getter () ^(cdr ,',parent-cell-sym))
                        (,setter (val)
                          ^(sys:rplacd ,',parent-cell-sym ,val)))
               ,body)))))))

(defplace (butlastn num list) body
  (getter setter
    (with-gensyms (num-sym list-sym head-sym tail-sym val-sym)
      (with-update-expander (lgetter lsetter) list sys:*pl-env*
        ^(alet ((,num-sym ,num)
                (,list-sym (,lgetter)))
           (let* ((,tail-sym (nthlast ,num-sym ,list-sym))
                  (,head-sym (ldiff ,list-sym ,tail-sym)))
             (macrolet ((,getter () ,head-sym)
                        (,setter (val)
                           ^(alet ((,',val-sym ,val))
                              (progn (,',lsetter (append ,',val-sym
                                                         ,',tail-sym))
                                     ,',val-sym))))
               ,body)))))))

(defplace (vecref vector index :whole args) body
  (getter setter
    (with-gensyms (vec-sym ind-sym)
      (ignore args)
      ^(alet ((,vec-sym ,vector)
              (,ind-sym ,index))
         (macrolet ((,getter () ^(vecref ,',vec-sym ,',ind-sym))
                    (,setter (val) ^(refset ,',vec-sym ,',ind-sym ,val)))
            ,body))))
  (ssetter
    ^(macrolet ((,ssetter (val) ^(refset ,*',args ,val)))
       ,(ignore vector index)
       ,body))
  (deleter
    (with-gensyms (vec-sym ind-sym)
      (ignore args)
      ^(alet ((,vec-sym ,vector)
              (,ind-sym ,index))
         (macrolet ((,deleter ()
                       ^(prog1 (vecref ,',vec-sym ,',ind-sym)
                               (replace-vec ,',vec-sym nil
                                            ,',ind-sym (succ ,',ind-sym)))))
           ,body)))))

(defplace (chr-str string index :whole args) body
  (getter setter
    (with-gensyms (str-sym ind-sym)
      (ignore args)
      ^(alet ((,str-sym ,string)
              (,ind-sym ,index))
         (macrolet ((,getter () ^(chr-str ,',str-sym ,',ind-sym))
                    (,setter (val) ^(chr-str-set ,',str-sym ,',ind-sym ,val)))
            ,body))))
  (ssetter
    ^(macrolet ((,ssetter (val) ^(chr-str-set ,*',args ,val)))
       ,(ignore string index)
       ,body))
  (deleter
    (with-gensyms (str-sym ind-sym)
      (ignore args)
      ^(alet ((,str-sym ,string)
              (,ind-sym ,index))
         (macrolet ((,deleter ()
                       ^(prog1 (chr-str ,',str-sym ,',ind-sym)
                               (replace-str ,',str-sym nil
                                            ,',ind-sym (succ ,',ind-sym)))))
           ,body)))))

(defplace (ref seq index :whole args) body
  (getter setter
    (with-gensyms (seq-sym ind-sym)
      (ignore args)
      ^(alet ((,seq-sym ,seq)
              (,ind-sym ,index))
         (macrolet ((,getter () ^(ref ,',seq-sym ,',ind-sym))
                    (,setter (val) ^(refset ,',seq-sym ,',ind-sym ,val)))
            ,body))))
  (ssetter
    ^(macrolet ((,ssetter (val) ^(refset ,*',args ,val)))
       ,(ignore seq index)
       ,body))
  (deleter
    (with-gensyms (seq-sym ind-sym)
      (ignore args)
      (with-clobber-expander (seq-ssetter) seq sys:*pl-env*
        ^(alet ((,seq-sym ,seq)
                (,ind-sym ,index))
           (macrolet ((,deleter ()
                         ^(prog1 (ref ,',seq-sym ,',ind-sym)
                                 (,',seq-ssetter (replace ,',seq-sym nil
                                                          ,',ind-sym
                                                          (succ ,',ind-sym))))))
             ,body))))))

(defplace (sub seq : (from 0) (to t)) body
  (getter setter
    (with-gensyms (seq-sym from-sym to-sym v-sym)
      (with-update-expander (seq-getter seq-setter) seq sys:*pl-env*
        ^(alet ((,seq-sym (,seq-getter))
                (,from-sym ,from)
                (,to-sym ,to))
           (macrolet ((,getter () ^(sub ,',seq-sym ,',from-sym ,',to-sym))
                      (,setter (val)
                        ^(alet ((,',v-sym ,val))
                           (,',seq-setter (replace ,',seq-sym ,',v-sym
                                                   ,',from-sym ,',to-sym))
                           ,',v-sym)))
              ,body)))))
  (ssetter
    (with-gensyms (seq-sym from-sym to-sym v-sym)
      (with-update-expander (seq-getter seq-setter) seq sys:*pl-env*
        ^(macrolet ((,ssetter (val)
                     ^(alet ((,',seq-sym (,',seq-getter))
                             (,',from-sym ,',from)
                             (,',to-sym ,',to)
                             (,',v-sym ,val))
                        (,',seq-setter (replace ,',seq-sym ,',v-sym
                                                ,',from-sym ,',to-sym))
                        ,',v-sym)))
        ,body))))
  (deleter
    (with-gensyms (seq-sym from-sym to-sym)
      (with-update-expander (seq-getter seq-setter) seq sys:*pl-env*
        ^(alet ((,seq-sym (,seq-getter))
                (,from-sym ,from)
                (,to-sym ,to))
           (macrolet ((,deleter ()
                         ^(prog1
                            (sub ,',seq-sym ,',from-sym ,',to-sym)
                            (,',seq-setter (replace ,',seq-sym nil
                                                    ,',from-sym ,',to-sym)))))
             ,body))))))

(defplace (gethash hash key : (default nil have-default-p)) body
  (getter setter
    (with-gensyms (entry-sym)
      (ignore have-default-p)
      ^(let ((,entry-sym (inhash ,hash ,key ,default)))
         (macrolet ((,getter () ^(cdr ,',entry-sym))
                    (,setter (val) ^(sys:rplacd ,',entry-sym ,val)))
            ,body))))
  nil
  (deleter
    ^(macrolet ((,deleter ()
                  (if ,have-default-p
                    (with-gensyms (entry-sym
                                   dfl-sym)
                      ^(alet ((,entry-sym (inhash ,',hash ,',key))
                              (,dfl-sym ,',default))
                         (if ,entry-sym
                           (remhash ,',hash ,',key)
                           ,dfl-sym)))
                    ^(remhash ,',hash ,',key))))
       ,body)))

(defplace (hash-userdata hash) body
  (getter setter
    (with-gensyms (hash-sym)
      ^(slet ((,hash-sym ,hash))
         (macrolet ((,getter () ^(hash-userdata ,',hash-sym))
                    (,setter (val) ^(set-hash-userdata ,',hash-sym ,val)))
            ,body))))
  (ssetter
    ^(macrolet ((,ssetter (val)
                  ^(set-hash-userdata ,',hash ,val)))
      ,body)))

(defplace (dwim obj-place . args) body
  (getter setter
    (with-gensyms (obj-sym newval-sym)
      (let ((arg-syms (mapcar (ret (gensym)) args)))
        (if (place-form-p obj-place sys:*pl-env*)
          (with-update-expander (ogetter-sym osetter-sym)
                                ^(sys:l1-val ,obj-place) sys:*pl-env*
            ^(rlet ((,obj-sym (,ogetter-sym))
                    ,*(mapcar (ret ^(,@1 (sys:l1-val ,@2))) arg-syms args))
               (macrolet ((,getter ()
                            '[,obj-sym ,*arg-syms])
                          (,setter (val)
                            ^(rlet ((,',newval-sym ,val))
                               (,',osetter-sym
                                 (sys:dwim-set t ,',obj-sym
                                               ,*',arg-syms ,',newval-sym))
                               ,',newval-sym)))
                 ,body)))
          ^(rlet ((,obj-sym ,obj-place)
                  ,*(mapcar (ret ^(,@1 (sys:l1-val ,@2))) arg-syms args))
             (macrolet ((,getter ()
                          '[,obj-sym ,*arg-syms])
                        (,setter (val)
                          ^(rlet ((,',newval-sym ,val))
                             (sys:dwim-set nil ,',obj-sym
                                           ,*',arg-syms ,',newval-sym)
                             ,',newval-sym)))
               ,body))))))
  (ssetter
    (with-gensyms (obj-sym newval-sym)
      (let ((arg-syms (mapcar (ret (gensym)) args)))
        (if (place-form-p obj-place sys:*pl-env*)
          (with-update-expander (ogetter-sym osetter-sym)
                                ^(sys:l1-val ,obj-place) sys:*pl-env*
            ^(macrolet ((,ssetter (val)
                         ^(rlet ((,',obj-sym (,',ogetter-sym))
                                 ,*(mapcar (ret ^(,@1 (sys:l1-val ,@2)))
                                           ',arg-syms ',args)
                                 (,',newval-sym ,val))
                            (,',osetter-sym
                              (sys:dwim-set t ,',obj-sym
                                            ,*',arg-syms
                                            ,',newval-sym))
                            ,',newval-sym)))
                 ,body))
          ^(macrolet ((,ssetter (val)
                       ^(rlet ((,',obj-sym ,',obj-place)
                               ,*(mapcar (ret ^(,@1 (sys:l1-val ,@2)))
                                         ',arg-syms ',args)
                               (,',newval-sym ,val))
                          (sys:dwim-set nil ,',obj-sym
                                        ,*',arg-syms
                                        ,',newval-sym)
                          ,',newval-sym)))
               ,body)))))

  (deleter
    (with-gensyms (obj-sym oldval-sym)
      (let ((arg-syms (mapcar (ret (gensym)) args)))
        (if (place-form-p obj-place sys:*pl-env*)
          (with-update-expander (ogetter-sym osetter-sym)
                                ^(sys:l1-val ,obj-place) sys:*pl-env*
            ^(macrolet ((,deleter ()
                         ^(rlet ((,',obj-sym (,',ogetter-sym))
                                 ,*(mapcar (ret ^(,@1 (sys:l1-val ,@2)))
                                           ',arg-syms ',args))
                            (let ((,',oldval-sym [,',obj-sym ,*',arg-syms]))
                              (progn
                                (,',osetter-sym
                                  (sys:dwim-del t ,',obj-sym ,*',arg-syms))
                                ,',oldval-sym)))))
                 ,body))
          ^(macrolet ((,deleter ()
                       ^(rlet ((,',obj-sym ,',obj-place)
                               ,*(mapcar (ret ^(,@1 (sys:l1-val ,@2)))
                                         ',arg-syms ',args))
                          (let ((,',oldval-sym [,',obj-sym ,*',arg-syms]))
                            (progn
                              (sys:dwim-del nil ,',obj-sym ,*',arg-syms)
                              ,',oldval-sym)))))
               ,body))))))

(defplace (mref1 seq index) body
  (getter setter
    (with-gensyms (obj-sym ind-sym val-sym)
      (if (place-form-p seq sys:*pl-env*)
        (with-update-expander (seq-getter seq-setter) seq sys:*pl-env*
          ^(alet ((,obj-sym (,seq-getter))
                  (,ind-sym ,index))
             (macrolet ((,getter () ^(mref ,',obj-sym ,',ind-sym))
                        (,setter (val)
                          ^(alet ((,',val-sym ,val))
                             (,',seq-setter (sys:dwim-set t
                                                          ,',obj-sym
                                                          ,',ind-sym
                                                          ,',val-sym))
                             ,',val-sym)))
               ,body)))
        ^(rlet ((,obj-sym ,seq)
                (,ind-sym ,index))
           (macrolet ((,getter () '(mref ,obj-sym ,ind-sym))
                      (,setter (val)
                         ^(alet ((,',val-sym ,val))
                            (sys:dwim-set nil
                                          ,',obj-sym
                                          ,',ind-sym
                                          ,',val-sym)
                            ,',val-sym)))
             ,body)))))
  (ssetter
    (with-gensyms (val-sym)
      (if (place-form-p seq sys:*pl-env*)
        (with-update-expander (seq-getter seq-setter) seq sys:*pl-env*
          ^(macrolet ((,ssetter (val)
                        ^(alet ((,',val-sym ,val))
                           (,',seq-setter
                             (sys:dwim-set t
                                           (,',seq-getter)
                                           ,',index
                                           ,',val-sym))
                           ,',val-sym)))
             ,body))
        ^(macrolet ((,ssetter (val)
                      ^(alet ((,',val-sym ,val))
                         (sys:dwim-set nil
                                       ,',seq
                                       ,',index
                                       ,',val-sym)
                         ,',val-sym)))
             ,body))))
  (deleter
    (with-gensyms (obj-sym ind-sym)
      (with-update-expander (seq-getter seq-setter) seq sys:*pl-env*
        ^(alet ((,obj-sym (,seq-getter))
                (,ind-sym ,index))
           (macrolet ((,deleter ()
                        ^(prog1 (mref ,',obj-sym ,',ind-sym)
                           (,',seq-setter
                             (sys:dwim-del ,',(place-form-p seq sys:*pl-env*)
                                           ,',obj-sym
                                           ,',index)))))
             ,body))))))

(defplace (force promise) body
  (getter setter
    (with-gensyms (promise-sym)
      ^(rlet ((,promise-sym ,promise))
          (macrolet ((,getter ()
                        ^(force ,',promise-sym))
                     (,setter (val)
                        ^(set (car (cdr ,',promise-sym)) ,val)))
            ,body))))
  (ssetter
    (with-gensyms (promise-sym)
      ^(rlet ((,promise-sym ,promise))
         (macrolet ((,ssetter (val)
                      ^(prog1
                         (set (car (cdr ,',promise-sym)) ,val)
                         (set (car ,',promise-sym) 'sys:promise-forced))))
           ,body)))))

(defplace (errno) body
  (getter setter
    ^(macrolet ((,getter () '(errno))
                (,setter (val-expr)
                   (with-gensyms (val-sym)
                     ^(slet ((,val-sym ,val-expr))
                         (progn (errno ,val-sym) ,val-sym)))))
       ,body)))

(defplace (fun sym) body
  (getter setter
    ^(macrolet ((,getter () ^(fun ,',sym))
                (,setter (val) ^(sys:setqf ,',sym ,val)))
       ,body))
  nil
  (deleter
    ^(macrolet ((,deleter (:env env)
                   (when (lexical-fun-p env ',sym)
                     (compile-error ',sys:*pl-form*
                                    "~s is a lexical function, \
                                    \ thus not deletable"))
                    ^(fmakunbound ',',sym)))
       ,body)))

(defun sys:get-fun-getter-setter (sym : f)
  (tree-case sym
    ((type struct slot)
      (if (eq type 'meth)
        (caseql slot
          (:init (cons (op struct-get-initfun struct)
                       (op struct-set-initfun struct)))
          (:postinit (cons (op struct-get-postinitfun struct)
                           (op struct-set-postinitfun struct)))
          (t (cons (op static-slot struct slot)
                   (op static-slot-ensure struct slot))))
        :))
    ((type sym)
      (if (eq type 'macro)
        (let ((cell (or (inhash sys:top-mb sym nil))))
          (cons (op cdr)
                (op sys:rplacd cell)))
        :))
    ((op . t)
     (if (eq op 'lambda)
       (compile-error f "cannot assign to lambda")
       (compile-error f "invalid function syntax ~s" sym)))
    (else
      (if (bindable else)
        (let ((cell (inhash sys:top-fb sym nil)))
          (cons (op cdr)
                (op sys:rplacd cell)))
        (compile-error f "~s isn't a bindable symbol" else)))))

(defplace (symbol-function sym-expr) body
  (getter setter
    (with-gensyms (gs-sym)
      ^(let ((,gs-sym (sys:get-fun-getter-setter ,sym-expr ',sys:*pl-form*)))
          (macrolet ((,getter () ^(call (car ,',gs-sym)))
                     (,setter (val) ^(call (cdr ,',gs-sym) ,val)))
            ,body))))
  nil
  (deleter
    ^(macrolet ((,deleter () ^(fmakunbound ,',sym-expr)))
       ,body)))

(defun sys:get-mb (f sym)
  (or (inhash sys:top-mb sym)
      (compile-error f "unbound macro ~s" sym)))

(defplace (symbol-macro sym-expr) body
  (getter setter
    (with-gensyms (binding-sym)
      ^(let ((,binding-sym (sys:get-mb ',sys:*pl-form* ,sym-expr)))
          (macrolet ((,getter () ^(cdr ,',binding-sym))
                     (,setter (val) ^(sys:rplacd ,',binding-sym ,val)))
            ,body))))
  nil
  (deleter
    ^(macrolet ((,deleter () ^(mmakunbound ,',sym-expr)))
       ,body)))

(defun sys:get-vb (sym)
  (inhash sys:top-vb sym nil))

(defplace (symbol-value sym-expr) body
  (getter setter
    (with-gensyms (sym)
      ^(let ((,sym ,sym-expr))
          (macrolet ((,getter () ^(symbol-value ,',sym))
                     (,setter (val) ^(sys:set-symbol-value ,',sym ,val)))
            ,body))))
  nil
  (deleter
    ^(macrolet ((,deleter () ^(makunbound ,',sym-expr)))
       ,body)))

(defplace (slot struct sym) body
  (getter setter
    (with-gensyms (struct-sym slot-sym)
      ^(alet ((,struct-sym ,struct)
              (,slot-sym ,sym))
         (macrolet ((,getter () ^(slot ,',struct-sym ,',slot-sym))
                    (,setter (val) ^(slotset ,',struct-sym ,',slot-sym ,val)))
           ,body))))
  (ssetter
    ^(macrolet ((,ssetter (val) ^(slotset ,',struct ,',sym ,val)))
      ,body)))

(defun read-once (value) value)

(defplace (read-once place) body
  (getter setter
    (with-gensyms (cache-var)
      (with-update-expander (pgetter psetter) place sys:*pl-env*
        ^(slet ((,cache-var (,pgetter)))
           (macrolet ((,getter () ',cache-var)
                      (,setter (val) ^(,',psetter (set ,',cache-var ,val))))
             ,body))))))

(defmacro define-modify-macro (name lambda-list function)
  (let ((cleaned-lambda-list (mapcar [iffi consp car]
                                     (remql : lambda-list))))
    (with-gensyms (place-sym)
      ^(defmacro ,name (:env env ,place-sym ,*lambda-list)
         (with-update-expander (getter setter) ,place-sym env
           ^(,setter (,',function (,getter) ,,*cleaned-lambda-list)))))))

(defmacro sys:placelet-1 (((sym place)) :env env . body)
  (with-gensyms (tmp-place pl-getter pl-setter)
    (unwind-protect
      (progn
        ;; This temporary proxy place installed into the
        ;; *place-update-expander* hash, and the forced expansion
        ;; of the symacrolet form are necessary for correctness.
        ;; If we don't perform that expand, then the temporary proxy
        ;; place is not used, and sym ends up being an alias
        ;; for the getter form (,',pl-getter) of the original place.
        ;; Then, placelet will only work for places whose getter forms
        ;; themselves places. This is not required in general. A (foo ...)
        ;; place can, for instance, use (get-foo ...) and (set-foo ...)
        ;; getters and setters, where (get-foo ...) is not a place.
        ;; If sym turns into a symbol macro for a (get-foo ...) form,
        ;; uses of sym as a place will fail due to get-foo not being a place.
       (sethash *place-update-expander* tmp-place
                 (lambda (tmp-getter tmp-setter tmp-place tmp-body)
                   (ignore tmp-place)
                   ^(macrolet ((,tmp-getter () ^(,',pl-getter))
                               (,tmp-setter (val) ^(,',pl-setter ,val)))
                      ,tmp-body)))
        (call-update-expander pl-getter pl-setter place env
          ^(macrolet ((,tmp-place () ^(,',pl-getter)))
             ,(expand ^(symacrolet ((,sym (,tmp-place)))
                         ,*body) env))))
      (remhash *place-update-expander* tmp-place))))

(defmacro placelet* (:form f sym-place-pairs . body)
  (tree-case sym-place-pairs
    (() ^(progn ,*body))
    (((sym place)) ^(sys:placelet-1 ((,sym ,place)) ,*body))
    (((sym place) . rest-pairs) ^(sys:placelet-1 ((,sym ,place))
                                   (placelet* (,*rest-pairs) ,*body)))
    (obj (compile-error f "bad syntax: ~s" obj))))

(defmacro placelet (:form f sym-place-pairs . body)
  (unless (all sym-place-pairs
               [andf consp (opip length (= 2)) (oand first bindable)])
    (compile-error f "bad syntax: ~s" sym-place-pairs))
  (tree-bind (: syms places) (transpose sym-place-pairs)
     (let ((temps (mapcar (ret (gensym)) syms)))
       ^(placelet* (,*(zip temps places))
          (symacrolet (,*(zip syms temps))
            ,*body)))))

(defun sys:register-simple-accessor (get-fun set-fun)
  (sethash *place-update-expander* get-fun
           (lambda (getter setter place body)
             (let* ((args (cdr place))
                    (temps (mapcar (ret (gensym)) args)))
               ^(let ,(zip temps args)
                  (macrolet ((,getter () ^(,',get-fun ,*',temps))
                             (,setter (val)
                               ^(,',set-fun ,*',temps ,val)))
                    ,body)))))
  (sethash *place-clobber-expander* get-fun
           (lambda (ssetter place body)
             ^(macrolet ((,ssetter (val)
                               ^(,',set-fun ,*(cdr ',place) ,val)))
                ,body)))
  get-fun)

(defmacro define-accessor (get-fun set-fun)
  ^(sys:register-simple-accessor ',get-fun ',set-fun))

(define-place-macro first (obj) ^(car ,obj))
(define-place-macro rest (obj) ^(cdr ,obj))
(define-place-macro second (obj) ^(ref ,obj 1))
(define-place-macro third (obj) ^(ref ,obj 2))
(define-place-macro fourth (obj) ^(ref ,obj 3))
(define-place-macro fifth (obj) ^(ref ,obj 4))
(define-place-macro sixth (obj) ^(ref ,obj 5))
(define-place-macro seventh (obj) ^(ref ,obj 6))
(define-place-macro eighth (obj) ^(ref ,obj 7))
(define-place-macro ninth (obj) ^(ref ,obj 8))
(define-place-macro tenth (obj) ^(ref ,obj 9))

(define-place-macro last (:env e obj : (n nil have-n))
  (cond
    ((and have-n (constantp n e) (not (plusp n)))
     ^(sub ,obj t t))
    ((and have-n (constantp n e))
     ^(sub ,obj ,(- n) t))
    (have-n
     ^(sub ,obj (- (max ,n 0)) t))
    (t ^(sub ,obj -1 t))))

(define-place-macro butlast (:env e obj : (n nil have-n))
  (cond
    ((and have-n (constantp n e) (not (plusp n)))
     obj)
    ((and have-n (constantp n e))
     ^(sub ,obj 0 ,(- n)))
    (have-n
     ^(sub ,obj 0 (- (max ,n 0))))
    (t ^(sub ,obj 0 -1))))

(define-place-macro nth (index obj)
  ^(car (nthcdr ,index ,obj)))

(define-place-macro mref (obj . indices)
  (tree-case indices
    (() obj)
    ((x) ^(mref1 ,obj ,x))
    ((x y) ^(mref1 (ref ,obj ,x) ,y))
    (t (let* ((l2 (nthlast 2 indices))
              (bl (ldiff indices l2))
              (x (car l2))
              (y (cadr l2)))
         ^(mref1 (ref (mref ,obj ,*bl) ,x) ,y)))))