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

Package Local Readtables #81

Closed digikar99 closed 5 months ago

digikar99 commented 5 months ago

How easy or non-trivial might it to be use eclector to implement package local readtables? By package local, I mean that whether or not a character has a reader macro or whether it is a constituent character would depend on the value of cl:*readtable*.

I'm looking for a lisp-only solution without going into the specifics of any IDE like SLIME/SLY.

I found it trivial to implement one for characters that already have a macro in the standard readtable. But doing the same for characters that are constituents in the standard readtable seemed to require digging into the implementation of read/read-preserving-whitespace.

scymtym commented 5 months ago

I'm not sure I fully understand the desired functionality, but maybe something like this:

(cl:defpackage #:eclector.examples.package-local-readtable
  (:use
   #:cl))

(cl:in-package #:eclector.examples.package-local-readtable)

(defvar *custom-readtable-1*
  (let ((table (eclector.readtable:copy-readtable eclector.reader:*readtable*)))
    (eclector.readtable:set-macro-character
     table #\- (lambda (stream char)
                 (declare (ignore char))
                 :from-readtable-1))
    table))

(defvar *custom-readtable-2*
  (let ((table (eclector.readtable:copy-readtable eclector.reader:*readtable*)))
    (eclector.readtable:set-macro-character
     table #\- (lambda (stream char)
                 (declare (ignore char))
                 :from-readtable-2))
    table))

(defclass client () ())

(defgeneric readtable-for-package (client package)
  (:method ((client client) (package t))
    (let ((package-name (package-name package)))
     (cond ((string= package-name "PACKAGE1")
            *custom-readtable-1*)
           ((string= package-name "PACKAGE2")
            *custom-readtable-2*)
           (t
            eclector.reader:*readtable*)))))

(defmethod eclector.reader:state-value ((client client)
                                        (aspect (eql '*readtable*)))
  (let ((package (eclector.reader:state-value client '*package*)))
    (readtable-for-package client package)))

(defpackage #:package1)
(defpackage #:package2)
(defpackage #:package3)

(list (let ((eclector.reader:*client* (make-instance 'client))
            (*package* (find-package "PACKAGE1") ))
        (eclector.reader:read-from-string "(foo-bar)"))
      (let ((eclector.reader:*client* (make-instance 'client))
            (*package* (find-package "PACKAGE2") ))
        (eclector.reader:read-from-string "(foo-bar)"))
      (let ((eclector.reader:*client* (make-instance 'client))
            (*package* (find-package "PACKAGE3") ))
        (eclector.reader:read-from-string "(foo-bar)")))
;;; => ((PACKAGE1::FOO :FROM-READTABLE-1 PACKAGE1::BAR)
;;;     (PACKAGE2::FOO :FROM-READTABLE-2 PACKAGE2::BAR)
;;;     (PACKAGE3::FOO-BAR))
digikar99 commented 5 months ago

Yes, this is what I wanted. And this also illustrates a nice use of clients that I have been trying to wrap my head around.

Thank you much!