Author: rklochkov Date: Fri Jan 25 06:09:35 2013 New Revision: 14
Log: Version 0.2
Modified: README.md advanced-readtable.asd package.lisp src.lisp
Modified: README.md ============================================================================== --- README.md Mon Dec 31 14:35:23 2012 (r13) +++ README.md Fri Jan 25 06:09:35 2013 (r14) @@ -9,6 +9,41 @@ - local intern package like in SBCL: package::(symbol1 symbol2) will intern package::symbol1 and package::symbol2
+To start +-------- + +Either use named-readtables and write + + (in-readtable :advanced) + +or simply add to advanced-readtable to current readtable + + (advanced-readtable:!) + +Hierarchy packages +------------------ + +Advanced-readtable has fully functional built-in support of hierarchy-packages. + + CL-USER> (defpackage .test (:use cl))) + #<PACKAGE "COMMON-LISP-USER.TEST"> + CL-USER> (in-package .test) + TEST> (in-package ..) + CL-USER> (defpackage .test.a (:use cl)) + #<PACKAGE "COMMON-LISP-USER.TEST.A"> + CL-USER> (in-package .test.a) + A> '...::car + CAR + A> (eq '...::car 'cl:car) + T + A> (in-package ...test) + TEST> (in-package ..) + CL-USER> + + +API +=== + _push-import-prefix_ -- enables import prefix on package name --------------------------------------------
@@ -51,6 +86,10 @@ (push-local-nickname :lib1 :lib :a) (push-local-nickname :lib2 :lib :b)
+This command also adds local subpackage alias. In the previous example a.lib +and b.lib will be aliases to lib1 and lib2. If there is a real package with +such name, alias will be shadowed, so don't worry too much about it. + _push-local-package_ -- sets local-package for a symbol ----------------------------------------------
@@ -69,10 +108,14 @@
, because first for is in ITERATE package, but second -- is not.
+Be careful: this change is not local to your package. + _set-macro-symbol_ - syntax is like set-macro-character, ------------------
-But FUNC is binded to SYMBOL, not character. +But FUNC is binded to SYMBOL, not character. This symbol will be processed +in all cases, where it is not bounded by ||. + Now you may make something like
html:[body [table (as-html sql:[select * from t1])]] @@ -80,6 +123,19 @@ html:[ and sql:[ will have different handlers and you may mix them in one expression.
+Also it allows to make simple symbol-aliases. For example: + + (set-macro-symbol '|ALIAS| (lambda (stream symbol) + (declare (ignore stream symbol)) + 'advanced-readtables:push-local-package)) +Now you may do + + (alias 'iter:iter :iterate) + +Moreover, you may alias variables from other packages and set them through +alias. But be careful: this change is not local to your package. + + _get-macro-symbol_ - syntax is like get-macro-character, ------------------
Modified: advanced-readtable.asd ============================================================================== --- advanced-readtable.asd Mon Dec 31 14:35:23 2012 (r13) +++ advanced-readtable.asd Fri Jan 25 06:09:35 2013 (r14) @@ -1,9 +1,9 @@ (asdf:defsystem #:advanced-readtable :description "Advanced customizable readtable" :author "Roman Klochkov kalimehtar@mail.ru" - :version "0.1.0" + :version "0.2.0" :license "BSD" :serial t - :components - ((:file "package") - (:file "src"))) + :components ((:file "package") + (:file "src"))) +
Modified: package.lisp ============================================================================== --- package.lisp Mon Dec 31 14:35:23 2012 (r13) +++ package.lisp Fri Jan 25 06:09:35 2013 (r14) @@ -1,8 +1,10 @@ -(defpackage #:advanced-readtable +(cl:|DEFPACKAGE| #:advanced-readtable (:use #:cl) (:shadow #:find-package - #:find-symbol) + #:find-symbol + #:in-package + #:defpackage) (:export #:set-macro-symbol #:get-macro-symbol @@ -18,4 +20,6 @@ #:push-import-prefix #:push-local-nickname #:push-local-package - #:set-handler)) + #:set-handler + #:enable-global-nicknames + #:enable-hierarchy-packages))
Modified: src.lisp ============================================================================== --- src.lisp Mon Dec 31 14:35:23 2012 (r13) +++ src.lisp Fri Jan 25 06:09:35 2013 (r14) @@ -10,24 +10,29 @@ ;;;; package::symbol1 and package::symbol2
(defvar *per-package-finders* (make-hash-table :test 'eq) - "Hash package -> list of handlers. Each handler is a cons (key . function)") + "Hash package -> list of handlers. Each handler is a cons (key . function) +function = (lambda (name package) ...) -> package") + (defvar *package-finders* nil "List of handlers. Each handler is a cons (key . function) function = (lambda (name package) ...) -> package")
+(defvar *global-nicknames* nil + "Placeholder for global nicknames, when not null, it is an alias hash") + ;;; ;;; Prepare readtables ;;;
-(defvar *advanced-readtable* (copy-readtable nil)) (defvar *colon-readtable* (copy-readtable nil) "Support readtable with colon as whitespace") +(set-syntax-from-char #: #\Space *colon-readtable* *colon-readtable*)
;;; ;;; Readtable handlers ;;;
-(defpackage #:advanced-readtable.junk) +(|CL|:defpackage #:advanced-readtable.junk)
(defun try-funcall (handlers-list name package) (declare (type list handlers-list) @@ -42,27 +47,30 @@ 1. By full name with CL:FIND-PACKAGE. 2. By per-package handlers. Here we wil try local-nicknames and so on. 3. By global handlers. Here we may use, for example, hierarchical packages." - (declare (type (or null package) current-package)) + (declare (type package current-package)) (if (typep name 'package) name (let ((sname (string name))) (or (cl:find-package name) - (when current-package - (try-funcall (package-finders current-package) - sname current-package)) + (try-funcall (package-finders current-package) + sname current-package) (try-funcall *package-finders* sname current-package)))))
(defvar *package-symbol-finders* (make-hash-table :test 'eq) - "Hash package -> list of handlers. Each handler is a cons (key . function)") + "Hash package -> list of handlers. Each handler is a cons (key . function) +function = (lambda (name package) ...) -> symbol") + (defvar *symbol-finders* nil "List of handlers. Each handler is a cons (key . function) function = (lambda (name package) ...) -> symbol") + (defvar *extra-finders* (make-hash-table :test 'eq) "Hash symbol -> list of handlers. Each handler is a cons (key . function) function = (lambda (name package) ...) -> symbol These will be used before CL:FIND-SYMBOL")
(defvar *symbol-readmacros* (make-hash-table :test 'eq)) + (defvar *disable-symbol-readmacro* nil "Disables processing of symbol-readmacro.")
@@ -109,7 +117,6 @@ (values symbol status) (try-funcall (cdr handlers-list) name package)))))
- (defun find-symbol (name &optional dpackage) "We try to find symbol 1. In package set with car of list, for example, PUSH-LOCAL-PACKAGE @@ -119,14 +126,15 @@ 5. By global finders 6. By CL-FIND-SYMBOL" (declare (type string name)) +; (when (string= name "NIL") +; (return-from find-symbol (cl:find-symbol name (or dpackage *package*)))) (let ((package (if dpackage (find-package dpackage) *package*))) (macrolet ((mv-or (&rest clauses) (if clauses `(multiple-value-bind (symbol status) ,(car clauses) - (if symbol (values symbol status) + (if status (values symbol status) (mv-or . ,(cdr clauses)))) - `(values nil nil)))) - + `(values nil nil)))) (mv-or (try-mv-funcall *extra-symbol-finders* name package) (when dpackage (cl:find-symbol name package)) @@ -135,27 +143,38 @@ (try-mv-funcall *symbol-finders* name package) (unless dpackage (cl:find-symbol name package))))))
+(defun collect-dots (stream) + (do ((n 0 (1+ n)) + (c (read-char stream nil) (read-char stream nil))) + ((or (null c) (char/= c #.)) + (when c + (unread-char c stream)) + (if (and (plusp n) (member c '(nil #\Space #) #( #\Tab #\Newline #:))) + (intern (make-string n :initial-element #.)) + (dotimes (foo n) (unread-char #. stream)))))) + (defun read-token (stream) " DO: Reads from STREAM a symbol or number up to whitespace or colon RETURN: symbols name or numbers value" (let ((*readtable* *colon-readtable*) (*package* (cl:find-package '#:advanced-readtable.junk))) - (read-preserving-whitespace stream nil))) + (or (collect-dots stream) + (read-preserving-whitespace stream nil))))
(defun count-colons (stream) " DO: Reads colons from STREAM RETURN: number of the colons" - (let ((c (read-char stream nil))) - (if (eql c #:) - (+ 1 (count-colons stream)) - (progn (unread-char c stream) 0)))) + (do ((n 0 (1+ n)) + (c (read-char stream nil) (read-char stream nil))) + ((or (null c) (char/= c #:)) + (when c (unread-char c stream)) n)))
(defun read-after-colon (stream maybe-package colons) "Read symbol package:sym or list package:(...)" (declare (type stream stream) - (type (integer 0 2) colons)) + (type integer colons)) (check-type colons (integer 0 2)) (when (= colons 0) ; no colon: this is a symbol or an atom (return-from read-after-colon @@ -186,16 +205,16 @@ (unless status (if (= colons 1) (error "No external symbol ~S in ~S" (symbol-name token) package) - (cerror "Intern ~S in ~S" "No such symbol ~S in package ~S" - (symbol-name token) package))) + (progn + (cerror "Intern ~S in ~S" "No such symbol ~S in package ~S" + (symbol-name token) package) + (setf symbol (intern (symbol-name token) package))))) (unintern token) (when (and (= colons 1) (not (eq status :external))) (cerror "Use anyway" "Symbol ~A not external" symbol)) symbol))))
- - (defun read-token-with-colons (stream char) "Reads token, then analize package part if needed" (unread-char char stream) @@ -227,8 +246,6 @@ (defun open-paren-reader (stream char) (let ((*car-list* t) (*extra-symbol-finders* *extra-symbol-finders*)) (funcall default-open-paren-reader stream char)))) - -
(defun (setf package-finders) (value &optional (package *package*)) (setf (gethash (find-package package) *per-package-finders*) value)) @@ -310,10 +327,23 @@ version 2 to LIB2 and make (push-local-nickname :lib1 :lib :a) (push-local-nickname :lib2 :lib :b) + +If enabled global-nicknames via enable-global-nicknames, +then also created alias in current package. + +For example, + (push-local-nickname :lib1 :lib :a), states, that package A.LIB is eq to LIB1. " - (let ((dpackage (find-package long-package))) - (%set-handler (package-finders current-package) `(:nick ,long-package ,nick) name - (when (string= name (string nick)) dpackage)))) + (let ((dpackage (find-package long-package)) + (s-nick (string nick))) + (%set-handler (package-finders current-package) + `(:nick ,(string long-package) ,s-nick) name + (when (string= name s-nick) dpackage)) + (when *global-nicknames* + (setf (gethash (concatenate 'string + (package-name current-package) + "." s-nick) *global-nicknames*) + dpackage))))
(defun push-local-package (symbol local-package) "Sets local-package for a symbol. Many macroses use there own clauses. @@ -333,86 +363,158 @@ (multiple-value-bind (symbol status) (cl:find-symbol name dpackage) (when (eq status :external) symbol)))))
+;;; TODO: process nicknames in hierarchy +;;; ex: cl-user.test == common-lisp-user.test +;;; cl-user.test.a == common-lisp-user.test.a + +(defun normalize-package (name) + "Returns nil if already normalized. +Replace first section of hierarchy with proper name" + (let ((pos (position #. name))) + (when pos + (if (= pos 0) ; .subpackage + (concatenate 'string (package-name *package*) name) + (let* ((base (subseq name 0 pos)) + (p (find-package base))) + (when (and p (string/= (package-name p) base)) + (concatenate 'string (package-name p) "." + (subseq name (1+ pos))))))))) + +(flet ((parent (name) + (let ((pos (position #. name :from-end t))) + (if pos (subseq name 0 pos) ""))) + (relative-to (parent name) + (cond + ((string= parent "") name) + ((string= name "") parent) + (t (concatenate 'string parent "." name))))) + (defun hierarchy-find-package (name package) + (if (char= (char name 0) #.) + (do ((i 1 (1+ i)) + (p (package-name package) (parent p))) + ((or (= i (length name)) (char/= (char name i) #.)) + (find-package (relative-to p (subseq name i))))) + (let ((normalized (normalize-package name))) + (when normalized + (find-package normalized package)))))) + +(defun correct-package (designator) + (let ((p (find-package designator))) + (if p (package-name p) designator))) + +(defmacro in-package (designator) + `(|CL|:in-package ,(correct-package (string designator)))) + +(defmacro defpackage (package &rest options) + (let ((normalized (normalize-package (string package))) + (options + (mapcar (lambda (option) + (cons (car option) + (case (car option) + (:use (mapcar #'correct-package (cdr option))) + ((:import-from :shadowing-import-from) + (cons (correct-package (second option)) + (cddr option))) + (t (cdr option))))) + options))) + `(|CL|:defpackage ,(or normalized package) . ,options))) + +(defun substitute-symbol (stream symbol) + (declare (ignore stream)) + (find-symbol (symbol-name symbol) #.*package*)) + +(defun enable-hierarchy-packages () + (set-handler *package-finders* :hierarchy #'hierarchy-find-package) + (set-macro-symbol '|CL|:in-package #'substitute-symbol) + (set-macro-symbol '|CL|:defpackage #'substitute-symbol)) + +(defun enable-global-nicknames () + (setf *global-nicknames* (make-hash-table :test 'equal)) + (%set-handler *package-finders* :global-nicknames name + (gethash name *global-nicknames*))) + +(enable-hierarchy-packages) +(enable-global-nicknames) + ;;; ;;; Readtable analysis and change ;;; - -(defmacro with-case (case &body body) - (let ((save (gensym))) - `(let ((,save (readtable-case *readtable*))) - (setf (readtable-case *readtable*) ,case) - (unwind-protect - (progn ,@body) - (setf (readtable-case *readtable*) ,save))))) - -(defun does-not-terminate-token-p (c) - (ignore-errors - (let ((str (format nil "a~Ab" c))) - (string= str (symbol-name - (with-case :preserve - (read-from-string (format nil "#:~A" str)))))))) - - -(defun whitespace-p (c) - (ignore-errors - (= 2 (length (read-from-string (format nil "(#:a~A#:b)" c)))))) - -(defun multiple-escape-p (c) - (ignore-errors - (string= "qQ" (symbol-name - (with-case :upcase - (read-from-string (format nil "#:~AqQ~A" c c))))))) - -(defun single-escape-p (c) - (ignore-errors - (string= (symbol-name '#:\ ) (symbol-name - (read-from-string (format nil "#:~A'" c)))))) - - - -(defun macro-char-p (c) - "If C is macro-char, return GET-MACRO-CHARACTER" - #+allegro (unless - (eql (get-macro-character c) #'excl::read-token) - (get-macro-character c)) - #-allegro (get-macro-character c)) - -(defun fill-char-table () - "Returns simple-vector with character syntax classes" - (let ((*readtable* (copy-readtable nil)) - (char-table (make-array 127))) - (dotimes (i (length char-table)) - (let ((c (code-char i))) - (setf - (svref char-table i) - (cond - ((eql c #:) :colon) - ((macro-char-p c) :macro) - ((does-not-terminate-token-p c) :does-not-terminate-token) - ((whitespace-p c) :whitespace) - ((multiple-escape-p c) :multiple-escape) - ((single-escape-p c) :single-escape))))) - char-table)) - -(let (initialized) - (defun activate (&optional force) - "Inits *advanced-readtable* and *colon-readtable*." - (when (or force (not initialized)) - (setq initialized t) - (let ((char-table (fill-char-table))) - (dotimes (i (length char-table)) - (let ((b (svref char-table i)) - (c (code-char i))) - (unless (char= ## c) - (when (member b '(:does-not-terminate-token - :multiple-escape :single-escape)) - ;; will make it non-terminating macro character - ;; = potentially beginning of the package-name - (set-macro-character c #'read-token-with-colons - t *advanced-readtable*)))))) +(eval-when (:compile-toplevel :load-toplevel :execute) + (defmacro with-case (case &body body) + (let ((save (gensym))) + `(let ((,save (readtable-case *readtable*))) + (setf (readtable-case *readtable*) ,case) + (unwind-protect + (progn ,@body) + (setf (readtable-case *readtable*) ,save))))) + + (defun does-not-terminate-token-p (c) + (ignore-errors + (let ((str (format nil "a~Ab" c))) + (string= str (symbol-name + (with-case :preserve + (read-from-string (format nil "#:~A" str)))))))) + + (defun whitespace-p (c) + (ignore-errors + (= 2 (length (read-from-string (format nil "(#:a~A#:b)" c)))))) + + (defun multiple-escape-p (c) + (ignore-errors + (string= "qQ" (symbol-name + (with-case :upcase + (read-from-string (format nil "#:~AqQ~A" c c))))))) + + (defun single-escape-p (c) + (ignore-errors + (string= (symbol-name '#:\ ) (symbol-name + (read-from-string + (format nil "#:~A'" c)))))) + + (defun macro-char-p (c) + "If C is macro-char, return GET-MACRO-CHARACTER" + #+allegro (unless + (eql (get-macro-character c) #'excl::read-token) + (get-macro-character c)) + #-allegro (get-macro-character c)) + + (defun to-process (c) + (cond + ((eql c #:) nil) + ((macro-char-p c) nil) + ((does-not-terminate-token-p c) t) + ((whitespace-p c) nil) + ((multiple-escape-p c) t) + ((single-escape-p c) t) + (t nil)))
- (set-syntax-from-char #: #\Space *colon-readtable* *colon-readtable*) - (set-macro-character #( #'open-paren-reader nil *advanced-readtable*)) - (setf *readtable* *advanced-readtable*))) + (defparameter +additional-chars+ "" + "Fill this, if you need extra characters for packages to begin with") + + (defun chars-to-process () + (let ((*readtable* (copy-readtable nil))) + (nconc + (loop :for i :from 1 :to 127 + :for c = (code-char i) + :when (to-process c) :collect c) + (loop :for c :across +additional-chars+ + :when (to-process c) :collect c)))) + + (defun make-named-rt () + `(,(cl:find-symbol "DEFREADTABLE" "NAMED-READTABLES") :advanced + (:merge :standard) + ,@(loop :for c :in (chars-to-process) + :collect `(:macro-char ,c #'read-token-with-colons t)) + (:macro-char #( #'open-paren-reader nil)))) + +(macrolet ((def-advanced-readtable () + (make-named-rt))) + (when (cl:find-package "NAMED-READTABLES") + (def-advanced-readtable))) + +(defun activate () + (dolist (c (chars-to-process)) + (set-macro-character c #'read-token-with-colons t)) + (set-macro-character #( #'open-paren-reader t))
(defun ! () (activate))