s-expressionists / Eclector

A portable Common Lisp reader that is highly customizable, can recover from errors and can return concrete syntax trees
https://s-expressionists.github.io/Eclector/
BSD 2-Clause "Simplified" License
108 stars 9 forks source link

Trouble compiling Iterate #57

Closed kpoeck closed 5 years ago

kpoeck commented 5 years ago

I have trouble compiling iterate using eclector as the reader. Reduced test-case with sbcl

(ql:quickload :eclector)

compile-load the following file

(in-package :cl-user)

(defun make-bang-var (n)
  (intern (format nil "!~d" n)))

(defun bang-vars (form)
  (delete-duplicates (bang-vars-1 form '()) :test #'eq))

(defun bang-vars-1 (form vars)
  (cond
    ((consp form)
     (bang-vars-1 (cdr form)
                  (bang-vars-1 (car form) vars)))
    ((and (symbolp form) (bang-var? form)) (cons form vars))
    (t vars)))

(defun bang-var? (sym)
  (char= (char (symbol-name sym) 0) #\!))

(defun bang-var-num (sym)
  (let ((num (read-from-string (subseq (symbol-name sym) 1))))
    (if (not (and (integerp num) (> num 0)))
        (error "#L: ~a is not a valid variable specifier" sym)
        num)))

(defun list-of-forms? (x)
  (and (consp x) (consp (car x))
       (not (eq (caar x) 'lambda))))

(defun sharpL-reader (stream subchar n-args)
  (declare (ignore subchar))
  ;; Depending how an implementation chooses to expand `(,!1 (get-free-temp))
  ;; at read-time, it might be a macro that must be expanded before groveling
  ;; the resultant sexpr. Here it gets expanded in the null environment for
  ;; lack of anything better. If the macro is sensitive to its lexical
  ;; environment, it suggests perhaps an inappropriate use of #L.
  ;; However, to support unforseen cases, we will use the original form as
  ;; read for the resulting lambda's body. Moreover, rather than stuff new
  ;; atoms into the body which is impossible if the representation is opaque,
  ;; redirect "!" vars onto gensyms using SYMBOL-MACROLET.
  (let* ((form (eclector.reader:read stream t nil t))
         (refd-!vars (sort (bang-vars (macroexpand form))
                           #'< :key #'bang-var-num))
         (bang-var-nums (mapcar #'bang-var-num refd-!vars))
         (max-bv-num (if refd-!vars (car (last bang-var-nums)) 0)))
    (cond ((null n-args)
           (setq n-args max-bv-num))
          ((< n-args max-bv-num)
           (error "#L: digit-string ~d specifies too few arguments" n-args)))
    (let* ((all-!vars (loop for i from 1 to n-args collect (make-bang-var i)))
           (formals (mapcar (lambda (x) (declare (ignore x)) (gensym))
                            all-!vars)))
      `#'(lambda ,formals
           ,@(let ((ignore (mapcan (lambda (!var tempvar)
                                     (unless (member !var refd-!vars)
                                       (list tempvar)))
                                   all-!vars formals)))
               (if ignore `((declare (ignore ,@ignore)))))
           (symbol-macrolet ,(mapcan (lambda (!var tempvar)
                                       (when (member !var refd-!vars)
                                         (list (list !var tempvar))))
                                     all-!vars formals)
             ,@(if (list-of-forms? form) form (list form)))))))

(defun enable-sharpL-reader ()
  (eclector.readtable:set-dispatch-macro-character
   eclector.readtable:*readtable*
   #\# #\L #'sharpL-reader)
  )

(defun test ()
  (eclector.reader:read-from-string "#L(list !2 !3 !5)")
  (eclector.reader:read-from-string "#L`(,!1 (get-free-temp))"))

testit

 (eclector.reader:read-from-string "#L(list !2 !3 !5)")
#'(LAMBDA (#:G558 #:G559 #:G560 #:G561 #:G562)
    (DECLARE (IGNORE #:G558 #:G561))
    (SYMBOL-MACROLET ((!2 #:G559) (!3 #:G560) (!5 #:G562))
      (LIST !2 !3 !5)))
(eclector.reader:read-from-string "#L`(,!1 (get-free-temp))")
->
Backquote has been used in a context that does not permit it (like #C(1 `,2)).
   [Condition of type ECLECTOR.READER:INVALID-CONTEXT-FOR-BACKQUOTE]
scymtym commented 5 years ago

The error is due to the fact that Eclector disallows backquote by default and explicitly allows it for certain contexts. As a result, backquote is currently not allowed in user-defined reader macros.

I'm honestly not sure what the correct behavior is based on what spec says about backquote. That said, I don't see any harm in allowing what iterate does.

kpoeck commented 5 years ago

Works fine in branch 'backquote-fixes'