diff --git a/LICENSE.md b/LICENSE.md index bd3a33d..d65bb1a 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,8 +1,4 @@ -Copyright (c) 2010 - 2022 - -Robert Strandh (robert.strandh@gmail.com) - -All rights reserved. +Copyright 2010-2023 Robert Strandh (robert.strandh@gmail.com) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -13,10 +9,11 @@ met: 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. + 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 +“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, diff --git a/code/basic-output.lisp b/code/basic-output.lisp index 8a8cda4..564a995 100644 --- a/code/basic-output.lisp +++ b/code/basic-output.lisp @@ -8,15 +8,24 @@ ;;; ;;; 22.3.1.1 ~c Character -(define-directive #\c c-directive nil (named-parameters-directive) ()) - -(define-format-directive-interpreter c-directive - (let ((char (consume-next-argument 'character))) - (cond ((and (not colonp) (not at-signp)) +(defclass c-directive (directive) + ()) + +(defmethod specialize-directive + (client (char (eql #\C)) directive end-directive) + (declare (ignore client end-directive)) + (change-class directive 'c-directive)) + +(defmethod interpret-item (client (directive c-directive) &optional parameters) + (declare (ignore parameters)) + (let ((char (consume-next-argument 'character)) + (colon-p (colon-p directive)) + (at-sign-p (at-sign-p directive))) + (cond ((and (not colon-p) (not at-sign-p)) ;; Neither colon nor at-sign. ;; The HyperSpec says to do what WRITE-CHAR does. (write-char char *destination*)) - ((not at-signp) + ((not at-sign-p) ;; We have only a colon modifier. ;; The HyperSpec says to do what WRITE-CHAR does for ;; printing characters, and what char-name does otherwise. @@ -25,7 +34,7 @@ (if (and (graphic-char-p char) (not (eql char #\Space))) (write-char char *destination*) (write-string (char-name char) *destination*))) - ((not colonp) + ((not colon-p) ;; We have only an at-sign modifier. ;; The HyperSpec says to print it the way the Lisp ;; reader can understand, which I take to mean "use PRIN1". @@ -43,114 +52,156 @@ (write-char char *destination*) (write-string (char-name char) *destination*)))))) -(define-format-directive-compiler c-directive - `((let ((char (consume-next-argument 'character))) - ,(cond ((and (not colonp) (not at-signp)) - `(write-char char *destination*)) - ((not at-signp) - `(if (and (graphic-char-p char) (not (eql char #\Space))) - (write-char char *destination*) - (write-string (char-name char) *destination*))) - ((not colonp) - `(let ((*print-escape* t)) - (incless:write-object ,(incless:client-form client) char *destination*))) - (t - `(if (and (graphic-char-p char) (not (eql char #\Space))) - (write-char char *destination*) - (write-string (char-name char) *destination*))))))) +(defmethod compile-item (client (directive c-directive) &optional parameters) + (declare (ignore parameters)) + (let ((colon-p (colon-p directive)) + (at-sign-p (at-sign-p directive))) + `((let ((char (consume-next-argument 'character))) + ,(cond ((and (not colon-p) (not at-sign-p)) + `(write-char char *destination*)) + ((not at-sign-p) + `(if (and (graphic-char-p char) (not (eql char #\Space))) + (write-char char *destination*) + (write-string (char-name char) *destination*))) + ((not colon-p) + `(let ((*print-escape* t)) + (incless:write-object ,(incless:client-form client) char *destination*))) + (t + `(if (and (graphic-char-p char) (not (eql char #\Space))) + (write-char char *destination*) + (write-string (char-name char) *destination*)))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.1.2 ~% Newline. -(define-directive #\% percent-directive nil (named-parameters-directive no-modifiers-mixin) - ((how-many :type (integer 0) :default-value 1))) +(defclass percent-directive (directive no-modifiers-mixin) + ()) + +(defmethod specialize-directive + (client (char (eql #\%)) directive end-directive) + (declare (ignore client end-directive)) + (change-class directive 'percent-directive)) + +(defmethod parameter-specifications (client (directive percent-directive)) + (declare (ignore client)) + '((:type (integer 0) :default 1))) -(define-format-directive-interpreter percent-directive - (loop repeat how-many +(defmethod interpret-item (client (directive percent-directive) &optional parameters) + (loop repeat (car parameters) do (terpri *destination*))) -(define-format-directive-compiler percent-directive - (let ((how-many (compile-time-value directive 'how-many))) - (case how-many - (0 '()) - (1 '((terpri *destination*))) - (2 '((terpri *destination*) - (terpri *destination*))) - (otherwise - `((loop repeat how-many - do (terpri *destination*))))))) +(defmethod compile-item (client (directive percent-directive) &optional parameters) + (case (car parameters) + (0 '()) + (1 '((terpri *destination*))) + (2 '((terpri *destination*) + (terpri *destination*))) + (otherwise + `((loop repeat ,(car parameters) + do (terpri *destination*)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.1.3 ~& Fresh line and newlines. -(define-directive #\& ampersand-directive nil (named-parameters-directive no-modifiers-mixin) - ((how-many :type (integer 0) :default-value 1))) +(defclass ampersand-directive (directive no-modifiers-mixin) + ()) -(define-format-directive-interpreter ampersand-directive - (unless (zerop how-many) - (fresh-line *destination*) - (loop repeat (1- how-many) - do (terpri *destination*)))) +(defmethod specialize-directive + (client (char (eql #\&)) directive end-directive) + (declare (ignore client end-directive)) + (change-class directive 'ampersand-directive)) -(define-format-directive-compiler ampersand-directive - (let ((how-many (compile-time-value directive 'how-many))) +(defmethod parameter-specifications + (client (directive ampersand-directive)) + (declare (ignore client)) + '((:type (integer 0) :default 1))) + +(defmethod interpret-item (client (item ampersand-directive) &optional parameters) + (let ((how-many (car parameters))) + (unless (zerop how-many) + (fresh-line *destination*) + (loop repeat (1- how-many) + do (terpri *destination*))))) + +(defmethod compile-item (client (item ampersand-directive) &optional parameters) + (let ((how-many (car parameters))) (case how-many - ((:argument-reference :remaining-argument-count) - `((unless (zerop how-many) - (fresh-line *destination*) - (loop repeat (1- how-many) - do (terpri *destination*))))) (0 nil) (1 `((fresh-line *destination*))) (2 `((fresh-line *destination*) (terpri *destination*))) (otherwise - `((fresh-line *destination*) - (loop repeat ,(1- how-many) - do (terpri *destination*))))))) + (if (numberp how-many) + `((fresh-line *destination*) + (loop repeat ,(1- how-many) + do (terpri *destination*))) + `((let ((how-many ,how-many)) + (unless (zerop how-many) + (fresh-line *destination*) + (loop repeat (1- how-many) + do (terpri *destination*)))))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.1.4 ~| Page separators. -(define-directive #\| vertical-bar-directive nil (named-parameters-directive no-modifiers-mixin) - ((how-many :type (integer 0) :default-value 1))) +(defclass vertical-bar-directive (directive no-modifiers-mixin) + ()) + +(defmethod specialize-directive + (client (char (eql #\|)) directive end-directive) + (declare (ignore client end-directive)) + (change-class directive 'vertical-bar-directive)) -(define-format-directive-interpreter vertical-bar-directive - (loop repeat how-many +(defmethod parameter-specifications + (client (directive vertical-bar-directive)) + (declare (ignore client)) + '((:type (integer 0) :default 1))) + +(defmethod interpret-item (client (directive vertical-bar-directive) &optional parameters) + (loop repeat (car parameters) do (write-char #\Page *destination*))) -(define-format-directive-compiler vertical-bar-directive - (let ((how-many (compile-time-value directive 'how-many))) +(defmethod compile-item (client (directive vertical-bar-directive) &optional parameters) + (let ((how-many (car parameters))) (case how-many (0 nil) (1 `((write-char #\Page *destination*))) (2 `((write-char #\Page *destination*) (write-char #\Page *destination*))) (otherwise - `((loop repeat how-many + `((loop repeat ,how-many do (write-char #\Page *destination*))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.1.5 ~~ Tildes. -(define-directive #\~ tilde-directive nil (named-parameters-directive no-modifiers-mixin) - ((how-many :type (integer 0) :default-value 1))) +(defclass tilde-directive (directive no-modifiers-mixin) + ()) + +(defmethod specialize-directive + (client (char (eql #\~)) directive end-directive) + (declare (ignore client end-directive)) + (change-class directive 'tilde-directive)) + +(defmethod parameter-specifications (client (directive tilde-directive)) + (declare (ignore client)) + '((:type (integer 0) :default 1))) -(define-format-directive-interpreter tilde-directive - (loop repeat how-many +(defmethod interpret-item (client (directive tilde-directive) &optional parameters) + (loop repeat (car parameters) do (write-char #\~ *destination*))) -(define-format-directive-compiler tilde-directive - (let ((how-many (compile-time-value directive 'how-many))) +(defmethod compile-item (client (directive tilde-directive) &optional parameters) + (let ((how-many (car parameters))) (case how-many (0 nil) (1 `((write-char #\~ *destination*))) (2 `((write-char #\~ *destination*) (write-char #\~ *destination*))) (otherwise - `((loop repeat how-many + `((loop repeat ,how-many do (write-char #\~ *destination*))))))) diff --git a/code/control-flow-operations.lisp b/code/control-flow-operations.lisp index 7092a96..46407fb 100644 --- a/code/control-flow-operations.lisp +++ b/code/control-flow-operations.lisp @@ -8,85 +8,113 @@ ;;; ;;; 22.3.7.1 ~* Go to -(define-directive #\* go-to-directive nil (named-parameters-directive at-most-one-modifier-mixin) - ((param :type (integer 0)))) - -(define-format-directive-interpreter go-to-directive - (cond (colonp - ;; Back up in the list of arguments. - ;; The default value for the parameter is 1. - (go-to-argument (- (or param 1)))) - (at-signp - ;; Go to an absolute argument number. - ;; The default value for the parameter is 0. - (go-to-argument (or param 0) t)) - (t - ;; Skip the next arguments. - ;; The default value for the parameter is 1. - (go-to-argument (or param 1))))) - -(define-format-directive-compiler go-to-directive - (cond (colonp - ;; Back up in the list of arguments. - ;; The default value for the parameter is 1. - `((go-to-argument (- (or param 1))))) - (at-signp - ;; Go to an absolute argument number. - ;; The default value for the parameter is 0. - `((go-to-argument (or param 0) t))) - (t - ;; Skip the next arguments. - ;; The default value for the parameter is 1. - `((go-to-argument (or param 1)))))) +(defclass go-to-directive + (directive at-most-one-modifier-mixin) nil) + +(defmethod specialize-directive + ((client t) (char (eql #\*)) directive (end-directive t)) + (change-class directive 'go-to-directive)) + +(defmethod parameter-specifications ((client t) (directive go-to-directive)) + '((:type (or null (integer 0)) :default nil))) + +(defmethod interpret-item (client (directive go-to-directive) &optional parameters) + (declare (ignore client)) + (let ((param (car parameters))) + (cond ((colon-p directive) + ;; Back up in the list of arguments. + ;; The default value for the parameter is 1. + (go-to-argument (- (or param 1)))) + ((at-sign-p directive) + ;; Go to an absolute argument number. + ;; The default value for the parameter is 0. + (go-to-argument (or param 0) t)) + (t + ;; Skip the next arguments. + ;; The default value for the parameter is 1. + (go-to-argument (or param 1)))))) + +(defmethod compile-item (client (directive go-to-directive) &optional parameters) + (declare (ignore client)) + (let ((param (car parameters))) + (cond ((colon-p directive) + ;; Back up in the list of arguments. + ;; The default value for the parameter is 1. + `((go-to-argument (- (or ,param 1))))) + ((at-sign-p directive) + ;; Go to an absolute argument number. + ;; The default value for the parameter is 0. + `((go-to-argument (or ,param 0) t))) + (t + ;; Skip the next arguments. + ;; The default value for the parameter is 1. + `((go-to-argument (or ,param 1))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.7.3 ~] End of conditional expression -(define-directive #\] end-conditional-directive nil (named-parameters-directive no-modifiers-mixin end-structured-directive-mixin) ()) +(defclass end-conditional-directive + (directive no-modifiers-mixin + end-structured-directive-mixin) + nil) -(define-format-directive-interpreter end-conditional-directive - ;; do nothing - nil) - -(define-format-directive-compiler end-conditional-directive - ;; do nothing - nil) +(defmethod specialize-directive + ((client t) (char (eql #\])) directive (end-directive t)) + (change-class directive 'end-conditional-directive)) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.7.2 ~[ Conditional expression -(define-directive #\[ conditional-directive end-conditional-directive - (named-parameters-directive structured-directive-mixin at-most-one-modifier-mixin) - ((param :type integer)) - (%last-clause-is-default-p :initform nil :accessor last-clause-is-default-p)) +(defclass conditional-directive + (directive structured-directive-mixin + at-most-one-modifier-mixin) + ((%last-clause-is-default-p :initform nil + :accessor last-clause-is-default-p))) + +(defmethod specialize-directive + ((client t) (char (eql #\[)) directive + (end-directive end-conditional-directive)) + (change-class directive 'conditional-directive)) + +(defmethod specialize-directive + ((client t) (char (eql #\[)) directive (end-directive t)) + (error 'unmatched-directive + :directive directive + :control-string (control-string directive) + :tilde-position (start directive))) + +(defmethod parameter-specifications + ((client t) (directive conditional-directive)) + '((:type (or null integer) :default nil))) -(defmethod check-directive-syntax progn ((directive conditional-directive)) +(defmethod check-directive-syntax progn (client (directive conditional-directive)) + (declare (ignore client)) ;; Check that, if a parameter is given, then there are ;; no modifiers. - (when (and (not (null (given-parameters directive))) - (or (colonp directive) (at-signp directive))) + #+(or)(when (and (not (null (parameters directive))) + (or (colon-p directive) (at-sign-p directive))) (error 'modifier-and-parameter :directive directive)) ;; Check that, if a colon modifier was given, then ;; there should be a single clause separator (two clauses). - (when (and (colonp directive) + (when (and (colon-p directive) (/= (length (clauses directive)) 2)) (error 'colon-modifier-requires-two-clauses)) ;; Check that, if an at-sign modifier was given, then ;; there should be a no clause separators (a single clause). - (when (and (at-signp directive) + (when (and (at-sign-p directive) (/= (length (clauses directive)) 1)) (error 'at-sign-modifier-requires-one-clause)) (let ((pos (position-if (lambda (items) (let ((last (aref items (1- (length items))))) (and (structured-separator-p last) - (colonp last)))) + (colon-p last)))) (clauses directive)))) ;; Check that, if a modifier is given, then there should ;; be no clause separator with colon modifier. - (when (and (or (colonp directive) (at-signp directive)) + (when (and (or (colon-p directive) (at-sign-p directive)) pos) (error 'clause-separator-with-colon-modifier-not-allowed :directive directive)) @@ -96,94 +124,112 @@ :directive directive)) (setf (last-clause-is-default-p directive) (and pos t)))) -(define-format-directive-interpreter conditional-directive - (cond (at-signp - (when (consume-next-argument t) - (go-to-argument -1) - (interpret-items client (aref (clauses directive) 0)))) - (colonp - (interpret-items client - (aref (clauses directive) - (if (consume-next-argument t) 1 0)))) - (t - ;; If a parameter was given, use it, - ;; else use the next argument. - (let ((val (or param (consume-next-argument 'integer)))) - (if (or (minusp val) - (>= val (length (clauses directive)))) - ;; Then the argument is out of range - (when (last-clause-is-default-p directive) - ;; Then execute the default-clause - (interpret-items client - (aref (clauses directive) - (1- (length (clauses directive)))))) - ;; Else, execute the corresponding clause - (interpret-items client - (aref (clauses directive) val))))))) - -(define-format-directive-compiler conditional-directive - (cond (at-signp - `((when (consume-next-argument t) - (go-to-argument -1) - ,@(compile-items client (aref (clauses directive) 0))))) - (colonp - `((cond ((consume-next-argument t) - ,@(compile-items client (aref (clauses directive) 1))) - (t - ,@(compile-items client (aref (clauses directive) 0)))))) - (t - ;; If a parameter was given, use it, - ;; else use the next argument. - `((let ((val (or param (consume-next-argument 'integer)))) +(defmethod interpret-item (client (directive conditional-directive) &optional parameters) + (let ((param (car parameters))) + (cond ((at-sign-p directive) + (when (consume-next-argument t) + (go-to-argument -1) + (interpret-items client (aref (clauses directive) 0)))) + ((colon-p directive) + (interpret-items client + (aref (clauses directive) + (if (consume-next-argument t) 1 0)))) + (t + ;; If a parameter was given, use it, + ;; else use the next argument. + (let ((val (or param (consume-next-argument 'integer)))) (if (or (minusp val) - (>= val ,(length (clauses directive)))) + (>= val (length (clauses directive)))) ;; Then the argument is out of range - ,(when (last-clause-is-default-p directive) - ;; Then execute the default-clause - `(progn ,@(compile-items client - (aref (clauses directive) - (1- (length (clauses directive))))))) + (when (last-clause-is-default-p directive) + ;; Then execute the default-clause + (interpret-items client + (aref (clauses directive) + (1- (length (clauses directive)))))) ;; Else, execute the corresponding clause - (case val - ,@(loop for i from 0 - for clause across (clauses directive) - collect `(,i ,@(compile-items client clause)))))))))) + (interpret-items client + (aref (clauses directive) val)))))))) + +(defmethod compile-item (client (directive conditional-directive) &optional parameters) + (let ((param (car parameters))) + (cond ((at-sign-p directive) + `((when (consume-next-argument t) + (go-to-argument -1) + ,@(compile-items client (aref (clauses directive) 0))))) + ((colon-p directive) + `((cond ((consume-next-argument t) + ,@(compile-items client (aref (clauses directive) 1))) + (t + ,@(compile-items client (aref (clauses directive) 0)))))) + (t + ;; If a parameter was given, use it, + ;; else use the next argument. + `((let ((val (or ,param (consume-next-argument 'integer)))) + (if (or (minusp val) + (>= val ,(length (clauses directive)))) + ;; Then the argument is out of range + ,(when (last-clause-is-default-p directive) + ;; Then execute the default-clause + `(progn ,@(compile-items client + (aref (clauses directive) + (1- (length (clauses directive))))))) + ;; Else, execute the corresponding clause + (case val + ,@(loop for i from 0 + for clause across (clauses directive) + collect `(,i ,@(compile-items client clause))))))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.7.5 ~} End of iteration -(define-directive #\} end-iteration-directive nil (named-parameters-directive only-colon-mixin end-structured-directive-mixin) ()) - -(define-format-directive-interpreter end-iteration-directive - ;; do nothing - nil) +(defclass end-iteration-directive + (directive only-colon-mixin + end-structured-directive-mixin) + ()) -(define-format-directive-compiler end-iteration-directive - ;; do nothing - nil) +(defmethod specialize-directive + ((client t) (char (eql #\})) directive (end-directive t)) + (change-class directive 'end-iteration-directive)) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.7.4 ~{ Iteration -(define-directive #\{ iteration-directive end-iteration-directive - (named-parameters-directive structured-directive-mixin) - ((iteration-limit :type (integer 0)))) +(defclass iteration-directive + (directive structured-directive-mixin) + ()) + +(defmethod specialize-directive + ((client t) (char (eql #\{)) directive + (end-directive end-iteration-directive)) + (change-class directive 'iteration-directive)) + +(defmethod specialize-directive + ((client t) (char (eql #\{)) directive (end-directive t)) + (error 'unmatched-directive :directive directive :control-string + (control-string directive) :tilde-position (start directive))) + +(defmethod parameter-specifications + ((client t) (directive iteration-directive)) + '((:type (or null (integer 0)) :default nil))) -(define-format-directive-interpreter iteration-directive +(defmethod interpret-item (client (directive iteration-directive) &optional parameters) ;; eliminate the end-of-iteration directive from the ;; list of items - (let* ((items (aref (clauses directive) 0)) - (oncep (colonp (aref items (1- (length items)))))) + (let* ((colon-p (colon-p directive)) + (at-sign-p (at-sign-p directive)) + (iteration-limit (car parameters)) + (items (aref (clauses directive) 0)) + (oncep (colon-p (aref items (1- (length items)))))) (if (= (length items) 1) (let ((control (consume-next-argument '(or string function)))) - (cond ((and colonp at-signp) + (cond ((and colon-p at-sign-p) ;; The remaining arguments should be lists. Each argument ;; is used in a different iteration. (if (functionp control) (catch *inner-tag* - (loop with *outer-catch-tag* = *inner-tag* + (loop with *outer-tag* = *inner-tag* with *outer-exit-if-exhausted* = *inner-exit-if-exhausted* for index from 0 while (or (null iteration-limit) @@ -192,7 +238,7 @@ do (funcall *inner-exit-if-exhausted*) do (apply control *destination* (consume-next-argument 'list)))) (catch *inner-tag* - (loop with *outer-catch-tag* = *inner-tag* + (loop with *outer-tag* = *inner-tag* with *outer-exit-if-exhausted* = *inner-exit-if-exhausted* with catch-tag = (list nil) for index from 0 @@ -204,7 +250,7 @@ (let ((*inner-tag* catch-tag)) (catch *inner-tag* (format-with-runtime-arguments client control)))))))) - (colonp + (colon-p ;; We use one argument, and that should be a list of sublists. ;; Each sublist is used as arguments for one iteration. (if (functionp control) @@ -230,7 +276,7 @@ (loop for args in arg ; a bit unusual naming perhaps repeat iteration-limit do (one-iteration args))))))) - (at-signp + (at-sign-p (if (functionp control) (loop for args = (consume-remaining-arguments) then (apply control *destination* args) @@ -265,7 +311,7 @@ when (or (not oncep) (plusp index)) do (funcall *inner-exit-if-exhausted*) do (format-with-runtime-arguments client control)))))))) - (cond ((and colonp at-signp) + (cond ((and colon-p at-sign-p) ;; The remaining arguments should be lists. Each argument ;; is used in a different iteration. (catch *inner-tag* @@ -276,7 +322,7 @@ do (funcall *inner-exit-if-exhausted*) do (with-arguments (consume-next-argument 'list) (interpret-items client items))))) - (colonp + (colon-p ;; We use one argument, and that should be a list of sublists. ;; Each sublist is used as arguments for one iteration. (with-arguments (consume-next-argument 'list) @@ -287,7 +333,7 @@ do (funcall *inner-exit-if-exhausted*) do (with-arguments (consume-next-argument 'list) (interpret-items client items))))) - (at-signp + (at-sign-p (catch *inner-tag* (loop for index from 0 while (or (null iteration-limit) @@ -307,16 +353,20 @@ do (funcall *inner-exit-if-exhausted*) do (interpret-items client items)))))))) -(define-format-directive-compiler iteration-directive +(defmethod compile-item (client (directive iteration-directive) &optional parameters) ;; eliminate the end-of-iteration directive from the ;; list of items - (let* ((items (aref (clauses directive) 0)) - (oncep (colonp (aref items (1- (length items)))))) + (let* ((colon-p (colon-p directive)) + (at-sign-p (at-sign-p directive)) + (iteration-limit (car parameters)) + (items (aref (clauses directive) 0)) + (oncep (colon-p (aref items (1- (length items)))))) (if (= (length items) 1) - (cond ((and colonp at-signp) + (cond ((and colon-p at-sign-p) ;; The remaining arguments should be lists. Each argument ;; is used in a different iteration. - `((let ((control (consume-next-argument '(or function string)))) + `((let ((iteration-limit ,iteration-limit) + (control (consume-next-argument '(or function string)))) (catch *inner-tag* (loop for index from 0 while (or (null iteration-limit) @@ -330,10 +380,11 @@ do (with-arguments (consume-next-argument 'list) (format-with-runtime-arguments ,(incless:client-form client) control))))))) - (colonp + (colon-p ;; We use one argument, and that should be a list of sublists. ;; Each sublist is used as arguments for one iteration. - `((let ((control (consume-next-argument '(or function string)))) + `((let ((iteration-limit ,iteration-limit) + (control (consume-next-argument '(or function string)))) (with-arguments (consume-next-argument 'list) (loop for index from 0 while (or (null iteration-limit) @@ -347,8 +398,9 @@ do (with-arguments (consume-next-argument 'list) (format-with-runtime-arguments ,(incless:client-form client) control))))))) - (at-signp - `((let ((control (consume-next-argument '(or function string)))) + (at-sign-p + `((let ((iteration-limit ,iteration-limit) + (control (consume-next-argument '(or function string)))) (if (functionp control) (loop for args = (consume-remaining-arguments) then (apply control *destination* args) @@ -372,7 +424,8 @@ ;; no modifiers ;; We use one argument, and that should be a list. ;; The elements of that list are used by the iteration. - `((let ((control (consume-next-argument '(or function string)))) + `((let ((iteration-limit ,iteration-limit) + (control (consume-next-argument '(or function string)))) (if (functionp control) (loop for args = (consume-next-argument 'list) then (apply control *destination* args) @@ -392,62 +445,73 @@ do (format-with-runtime-arguments ,(incless:client-form client) control)))))))) (let ((compiled-items (compile-items client items))) - (cond ((and colonp at-signp) + (cond ((and colon-p at-sign-p) ;; The remaining arguments should be lists. Each argument ;; is used in a different iteration. - `((catch *inner-tag* - (loop for index from 0 - while (or (null iteration-limit) - (< index iteration-limit)) - ,@(if oncep - '(when (plusp index) do (funcall *inner-exit-if-exhausted*)) - '(do (funcall *inner-exit-if-exhausted*))) - do (with-arguments (consume-next-argument 'list) - ,@compiled-items))))) - (colonp + `((let ((iteration-limit ,iteration-limit)) + (catch *inner-tag* + (loop for index from 0 + while (or (null iteration-limit) + (< index iteration-limit)) + ,@(if oncep + '(when (plusp index) do (funcall *inner-exit-if-exhausted*)) + '(do (funcall *inner-exit-if-exhausted*))) + do (with-arguments (consume-next-argument 'list) + ,@compiled-items)))))) + (colon-p ;; We use one argument, and that should be a list of sublists. ;; Each sublist is used as arguments for one iteration. - `((with-arguments (consume-next-argument 'list) - (loop for index from 0 - while (or (null iteration-limit) - (< index iteration-limit)) - ,@(if oncep - '(when (plusp index) do (funcall *inner-exit-if-exhausted*)) - '(do (funcall *inner-exit-if-exhausted*))) - do (with-arguments (consume-next-argument 'list) - ,@compiled-items))))) - (at-signp - `((catch *inner-tag* - (loop for index from 0 - while (or (null iteration-limit) - (< index iteration-limit)) - ,@(if oncep - '(when (plusp index) do (funcall *inner-exit-if-exhausted*)) - '(do (funcall *inner-exit-if-exhausted*))) - ,@(when compiled-items - (list* 'do compiled-items)))))) + `((let ((iteration-limit ,iteration-limit)) + (with-arguments (consume-next-argument 'list) + (loop for index from 0 + while (or (null iteration-limit) + (< index iteration-limit)) + ,@(if oncep + '(when (plusp index) do (funcall *inner-exit-if-exhausted*)) + '(do (funcall *inner-exit-if-exhausted*))) + do (with-arguments (consume-next-argument 'list) + ,@compiled-items)))))) + (at-sign-p + `((let ((iteration-limit ,iteration-limit)) + (catch *inner-tag* + (loop for index from 0 + while (or (null iteration-limit) + (< index iteration-limit)) + ,@(if oncep + '(when (plusp index) do (funcall *inner-exit-if-exhausted*)) + '(do (funcall *inner-exit-if-exhausted*))) + ,@(when compiled-items + (list* 'do compiled-items))))))) (t ;; no modifiers ;; We use one argument, and that should be a list. ;; The elements of that list are used by the iteration. - `((with-arguments (consume-next-argument 'list) - (loop for index from 0 - while (or (null iteration-limit) - (< index iteration-limit)) - ,@(if oncep - '(when (plusp index) do (funcall *inner-exit-if-exhausted*)) - '(do (funcall *inner-exit-if-exhausted*))) - ,@(when compiled-items - (list* 'do compiled-items))))))))))) + `((let ((iteration-limit ,iteration-limit)) + (with-arguments (consume-next-argument 'list) + (loop for index from 0 + while (or (null iteration-limit) + (< index iteration-limit)) + ,@(if oncep + '(when (plusp index) do (funcall *inner-exit-if-exhausted*)) + '(do (funcall *inner-exit-if-exhausted*))) + ,@(when compiled-items + (list* 'do compiled-items)))))))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.7.6 ~? Recursive processing -(define-directive #\? recursive-processing-directive nil (named-parameters-directive only-at-sign-mixin) ()) +(defclass recursive-processing-directive + (directive only-at-sign-mixin) + ()) + +(defmethod specialize-directive + ((client t) (char (eql #\?)) directive (end-directive t)) + (change-class directive 'recursive-processing-directive)) -(define-format-directive-interpreter recursive-processing-directive - (if at-signp +(defmethod interpret-item (client (directive recursive-processing-directive) &optional parameters) + (declare (ignore parameters)) + (if (at-sign-p directive) ;; reuse the arguments from the parent control-string (format-with-runtime-arguments client (consume-next-argument 'string)) @@ -458,8 +522,9 @@ (consume-next-argument 'string) (consume-next-argument 'list)))) -(define-format-directive-compiler recursive-processing-directive - (if at-signp +(defmethod compile-item (client (directive recursive-processing-directive) &optional parameters) + (declare (ignore parameters)) + (if (at-sign-p directive) ;; reuse the arguments from the parent control-string `((format-with-runtime-arguments ,(incless:client-form client) (consume-next-argument 'string))) diff --git a/code/control-string-compiler.lisp b/code/control-string-compiler.lisp index feead70..7ac1d47 100644 --- a/code/control-string-compiler.lisp +++ b/code/control-string-compiler.lisp @@ -1,59 +1,10 @@ (cl:in-package #:invistra) -(defun compile-parameter-value (directive parameter-spec) - (let* ((parameter-name (car parameter-spec)) - (compile-time-value (funcall parameter-name directive))) - (cond ((null compile-time-value) - ;; The parameter was not given at all, in the format control - ;; string, neither as a constant value, nor as a value to - ;; be acquired at runtime (# or V). We must use a default - ;; value of it has any. - (getf (cdr parameter-spec) :default-value)) - ((eq compile-time-value :argument-reference) - ;; The parameter was given the explicit value V in the - ;; format control string, meaning we use the next argument - ;; to acquire the value of the parameter. We must generate - ;; code to test that there are more arguments, to consume - ;; the next one, and to check that the type of the argument - ;; acquired is correct. - `(or (consume-next-argument '(or null ,(getf (cdr parameter-spec) :type))) - ,(getf (cdr parameter-spec) :default-value))) - ((eq compile-time-value :remaining-argument-count) - ;; The parameter was given the explicit value # in the - ;; format control string, meaning we use the number of - ;; remaining arguments as the value of the parameter. - `(if (typep *remaining-argument-count* - ',(getf (cdr parameter-spec) :type)) - *remaining-argument-count* - (error 'argument-type-error - :expected-type ',(getf (cdr parameter-spec) :type) - :datum *remaining-argument-count*))) - (t - ;; The parameter was given an explicit value (number or - ;; character) in the format control string, and this is the - ;; value we want. - compile-time-value)))) - -(defun compile-directive (client directive) - (let ((parameter-specs (parameter-specs (class-name (class-of directive))))) - (if parameter-specs - `((let ,(loop for parameter-spec in parameter-specs - collect `(,(car parameter-spec) - ,(compile-parameter-value directive parameter-spec))) - (declare (ignorable ,@(mapcar #'car parameter-specs))) - ,@(compile-format-directive client directive))) - (compile-format-directive client directive)))) - -(defun compile-item (client item) - (if (stringp item) - `((write-string ,item *destination*)) - (compile-directive client item))) - (defun compile-items (client items) (loop for item across items - append (compile-directive client item))) + append (compile-item client item))) (defun compile-control-string (client control-string) - (let ((items (structure-items (split-control-string control-string)))) + (let ((items (structure-items client (split-control-string control-string)))) `(progn ,@(loop for item across items collect (compile-item client item))))) diff --git a/code/directive.lisp b/code/directive.lisp index 970e54b..d659cc3 100644 --- a/code/directive.lisp +++ b/code/directive.lisp @@ -24,11 +24,11 @@ (defgeneric directive-character (directive)) -(defgeneric given-parameters (directive)) +(defgeneric parameters (directive)) -(defgeneric colonp (directive)) +(defgeneric colon-p (directive)) -(defgeneric at-signp (directive)) +(defgeneric at-sign-p (directive)) (defgeneric structured-end-p (directive) (:method (directive) @@ -45,6 +45,29 @@ (declare (ignore directive)) nil)) +(defgeneric interpret-parameter (parameter)) + +(defgeneric compile-parameter (parameter)) + +(defclass parameter () + ((%type :accessor parameter-type + :initarg :type + :initform '(or null character integer)) + (%default :accessor parameter-default + :initarg :default + :initform nil))) + +(defclass argument-reference-parameter (parameter) + ()) + +(defclass remaining-argument-count-parameter (parameter) + ()) + +(defclass literal-parameter (parameter) + ((%value :accessor parameter-value + :initarg :value + :initform nil))) + ;;; How we represent a directive. It may seem wasteful to allocate ;;; a class instance for each directive, but most format directives ;;; are handled at compile time anyway. @@ -59,16 +82,11 @@ ;; The directive character used. (%directive-character :initarg :directive-character :reader directive-character) ;; a list of parameters, each one is either an integer or a character - (%given-parameters :initarg :given-parameters :reader given-parameters) + (%parameters :initarg :parameters :accessor parameters) ;; true if and only if the `:' modifier was given - (%colonp :initarg :colonp :reader colonp) + (%colon-p :initarg :colon-p :reader colon-p) ;; true if and only if the `@' modifier was given - (%at-signp :initarg :at-signp :reader at-signp))) - -;;; The base class of all directives that take a maximum number of -;;; named parameters. Those are all the directives except the -;;; call-function directive. -(defclass named-parameters-directive (directive) ()) + (%at-sign-p :initarg :at-sign-p :reader at-sign-p))) ;;; Mixin class for directives that take no modifiers (defclass no-modifiers-mixin () ()) @@ -107,138 +125,72 @@ requirements nil))))) -;;; Specialize a directive according to a particular directive -;;; character. -(defun specialize-directive (directive end-directive) - (change-class - directive - (directive-subclass-name (directive-character directive) directive end-directive))) - -;;; A macro that helps us define directives. It takes a directive -;;; character, a directive name (to be used for the class) and a body -;;; in the form of a list of parameter specifications. Each parameter -;;; specification is a list where the first element is the name of the -;;; parameter, and the remaining elemnts are keyword/value pairs. -;;; Currently, the only keywords allowed are :type and -;;; :default-value. -(defmacro define-directive (character name end-name superclasses parameters &body slots) - `(progn - (defmethod directive-subclass-name - ((char (eql ,(char-upcase character))) directive - ,(if end-name `(end-directive ,end-name) 'end-directive)) - (declare (ignore directive)) - ',name) - - ,(when end-name - `(defmethod directive-subclass-name - ((char (eql ,(char-upcase character))) directive end-directive) - (declare (ignore end-directive)) - (error 'unmatched-directive - :directive directive - :control-string (control-string directive) - :tilde-position (start directive)))) - - (eval-when (:compile-toplevel :load-toplevel :execute) - (defmethod parameter-specs ((directive-name (eql ',name))) - ',(loop for parameter in parameters - collect (if (getf (cdr parameter) :default-value) - parameter - (cons (car parameter) - (list* :default-value nil (cdr parameter))))))) - - (defclass ,name ,superclasses - (,@(loop for parameter in parameters - collect `(,(car parameter) - :initform nil - :reader - ,(car parameter))) - ,@slots)))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Checking syntax, interpreting, and compiling directives. -(defmethod check-directive-syntax progn (directive) - (with-accessors ((given-parameters given-parameters)) - directive - (let ((parameter-specs (parameter-specs (class-name (class-of directive))))) - ;; When a parameter was explicitly given, check that - ;; what was given does not have an incompatible type - ;; with respect to the default value of the corresponding - ;; slot, and assign the explicitly given value to - ;; the slot. - (let ((parameter-number 1)) - (mapc (lambda (parameter-spec parameter-value) - (unless (or (eq parameter-value :remaining-argument-count) - (eq parameter-value :argument-reference)) - (unless - (or - ;; Either a parameter was not supplied, but it has a - ;; default value - (and (null parameter-value) - (not (null (getf (cdr parameter-spec) :default-value)))) - ;; Or else it was supplied, and it is of the right type. - (typep parameter-value (getf (cdr parameter-spec) :type))) - (error 'parameter-type-error - :expected-type - (getf (cdr parameter-spec) :type) - :datum parameter-value))) - (setf (slot-value directive (car parameter-spec)) - parameter-value) - (incf parameter-number)) - parameter-specs - given-parameters))))) - -(defmethod check-directive-syntax progn ((directive named-parameters-directive)) - (with-accessors ((given-parameters given-parameters)) - directive - (let ((parameter-specs (parameter-specs (class-name (class-of directive))))) - ;; Check that the number of parameters given is no more than - ;; what this type of directive allows. - (when (> (length given-parameters) (length parameter-specs)) - (error 'too-many-parameters - :directive directive - :at-most-how-many (length parameter-specs) - :how-many-found (length given-parameters)))))) +(defmethod check-directive-syntax progn (client directive) + (loop for remaining-parameters = (parameters directive) then (cdr remaining-parameters) + for parameter = (car remaining-parameters) + for remaining-specs = (parameter-specifications client directive) + then (if (getf (car remaining-specs) :rest) + remaining-specs + (cdr remaining-specs)) + for spec = (car remaining-specs) + for count from 0 + finally (setf (parameters directive) parameters) + if (and parameter spec) + do (apply #'reinitialize-instance parameter + :allow-other-keys t spec) + else if (and (null parameter) + (or (null spec) + (getf spec :rest))) + do (loop-finish) + else if (null spec) + do (error 'too-many-parameters + :directive directive + :at-most-how-many (1+ count) + :how-many-found (+ count (length remaining-parameters))) + else + do (setf parameter (apply #'make-instance 'literal-parameter + :allow-other-keys t spec)) + collect parameter into parameters + when (typep parameter 'literal-parameter) + do (with-accessors ((parameter-value parameter-value) + (parameter-type parameter-type) + (parameter-default parameter-default)) + parameter + (unless parameter-value + (setf parameter-value parameter-default)) + (unless (typep parameter-value parameter-type) + (error 'parameter-type-error + :expected-type parameter-type + :datum parameter-value))))) ;;; Signal an error if a modifier has been given for such a directive. -(defmethod check-directive-syntax progn ((directive no-modifiers-mixin)) - (with-accessors ((colonp colonp) - (at-signp at-signp) - (control-string control-string) - (end end)) - directive - (when (or colonp at-signp) - (error 'directive-takes-no-modifiers - :directive directive)))) +(defmethod check-directive-syntax progn (client (directive no-modifiers-mixin)) + (declare (ignore client)) + (when (or (colon-p directive) (at-sign-p directive)) + (error 'directive-takes-no-modifiers + :directive directive))) ;;; Signal an error if an at-sign has been given for such a directive. -(defmethod check-directive-syntax progn ((directive only-colon-mixin)) - (with-accessors ((at-signp at-signp) - (control-string control-string) - (end end)) - directive - (when at-signp - (error 'directive-takes-only-colon - :directive directive)))) +(defmethod check-directive-syntax progn (client (directive only-colon-mixin)) + (declare (ignore client)) + (when (at-sign-p directive) + (error 'directive-takes-only-colon + :directive directive))) ;;; Signal an error if a colon has been given for such a directive. -(defmethod check-directive-syntax progn ((directive only-at-sign-mixin)) - (with-accessors ((colonp colonp) - (control-string control-string) - (end end)) - directive - (when colonp - (error 'directive-takes-only-at-sign - :directive directive)))) +(defmethod check-directive-syntax progn (client (directive only-at-sign-mixin)) + (declare (ignore client)) + (when (colon-p directive) + (error 'directive-takes-only-at-sign + :directive directive))) ;;; Signal an error if both modifiers have been given for such a directive. -(defmethod check-directive-syntax progn ((directive at-most-one-modifier-mixin)) - (with-accessors ((colonp colonp) - (at-signp at-signp) - (control-string control-string) - (end end)) - directive - (when (and colonp at-signp) - (error 'directive-takes-at-most-one-modifier - :directive directive)))) +(defmethod check-directive-syntax progn (client (directive at-most-one-modifier-mixin)) + (declare (ignore client)) + (when (and (colon-p directive) (at-sign-p directive)) + (error 'directive-takes-at-most-one-modifier + :directive directive))) diff --git a/code/floating-point-printers.lisp b/code/floating-point-printers.lisp index 8d5f47c..f905653 100644 --- a/code/floating-point-printers.lisp +++ b/code/floating-point-printers.lisp @@ -53,29 +53,28 @@ (write-char #\. *destination*)) when (= pos d-pos) do (write-char #\. *destination*) - do (write-char (aref *digits* digit) *destination*))) + do (write-char (char incless:*digit-chars* digit) *destination*))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.3.1 ~f Fixed-format floating point. -(define-directive #\f - f-directive - nil - (named-parameters-directive) - ((w :type (or null integer) - :default-value nil) - (d :type (or null integer) - :defaule-value nil) - (k :type (or null integer) - :default-value 0) - (overflowchar :type (or null character) - :default-value nil) - (padchar :type character - :default-value #\Space))) + +(defclass f-directive (directive) nil) + +(defmethod specialize-directive + ((client t) (char (eql #\F)) directive (end-directive t)) + (change-class directive 'f-directive)) + +(defmethod parameter-specifications ((client t) (directive f-directive)) + '((:type (or null integer) :default nil) + (:type (or null integer) :default nil) + (:type (or null integer) :default 0) + (:type (or null character) :default nil) + (:type character :default #\Space))) (defun print-fixed-arg (client value digits exponent - colonp at-signp w d k overflowchar padchar) - (declare (ignore client colonp)) + colon-p at-sign-p w d k overflowchar padchar) + (declare (ignore client colon-p)) (let ((decimal (make-instance 'decimal :digits digits)) sign len) @@ -84,7 +83,7 @@ decimal (setf sign (cond ((minusp (float-sign value)) #\-) - ((and at-signp (plusp value)) #\+))) + ((and at-sign-p (plusp value)) #\+))) (incf exponent k) (cond ((zerop (aref decimal-digits 0)) (setf decimal-position 1)) @@ -163,43 +162,44 @@ do (write-char overflowchar *destination*)) t))))) -(define-format-directive-interpreter f-directive +(defmethod interpret-item (client (directive f-directive) &optional parameters) (print-float-arg client (lambda (client value digits exponent) - (print-fixed-arg client value digits exponent - colonp at-signp w d k overflowchar padchar)))) + (apply #'print-fixed-arg + client value digits exponent + (colon-p directive) (at-sign-p directive) + parameters)))) -(define-format-directive-compiler f-directive - `((print-float-arg ,(incless:client-form client) - (lambda (client value digits exponent) - (print-fixed-arg client value digits exponent - ,colonp ,at-signp w d k overflowchar padchar))))) +(defmethod compile-item (client (directive f-directive) &optional parameters) + `((let ((parameters (list ,@parameters))) + (print-float-arg ,(incless:client-form client) + (lambda (client value digits exponent) + (apply #'print-fixed-arg + client value digits exponent + ,(colon-p directive) ,(at-sign-p directive) + parameters)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.3.2 ~e Exponential floating point. -(define-directive #\e - e-directive - nil - (named-parameters-directive) - ((w :type (or null integer) - :default-value nil) - (d :type (or null integer) - :defaule-value nil) - (e :type (or null integer) - :defaule-value nil) - (k :type (or null integer) - :default-value 1) - (overflowchar :type (or null character) - :default-value nil) - (padchar :type character - :default-value #\Space) - (exponentchar :type (or null character) - :default-value nil))) - -(defun print-exponent-arg (client value digits exponent colonp at-signp w d e k overflowchar padchar exponentchar) - (declare (ignore colonp)) +(defclass e-directive (directive) ()) + +(defmethod specialize-directive + ((client t) (char (eql #\E)) directive (end-directive t)) + (change-class directive 'e-directive)) + +(defmethod parameter-specifications ((client t) (directive e-directive)) + '((:type (or null integer) :default nil) + (:type (or null integer) :default nil) + (:type (or null integer) :default nil) + (:type (or null integer) :default 1) + (:type (or null character) :default nil) + (:type character :default #\Space) + (:type (or null character) :default nil))) + +(defun print-exponent-arg (client value digits exponent colon-p at-sign-p w d e k overflowchar padchar exponentchar) + (declare (ignore colon-p)) (let ((decimal (make-instance 'decimal :digits digits)) sign len exp) @@ -208,7 +208,7 @@ decimal (setf sign (cond ((minusp (float-sign value)) #\-) - ((and at-signp (plusp value)) #\+))) + ((and at-sign-p (plusp value)) #\+))) (setf exponent (if (or (zerop (length decimal-digits)) (zerop (aref decimal-digits 0))) 0 @@ -302,45 +302,43 @@ (loop repeat w do (write-char overflowchar *destination*))))))) -(define-format-directive-interpreter e-directive +(defmethod interpret-item (client (directive e-directive) &optional parameters) (print-float-arg client (lambda (client value digits exponent) - (print-exponent-arg client value digits exponent - colonp at-signp w d e k - overflowchar padchar exponentchar)))) + (apply #'print-exponent-arg + client value digits exponent + (colon-p directive) (at-sign-p directive) + parameters)))) -(define-format-directive-compiler e-directive - `((print-float-arg ,(incless:client-form client) - (lambda (client value digits exponent) - (print-exponent-arg client value digits exponent - ,colonp ,at-signp w d e k - overflowchar padchar exponentchar))))) +(defmethod compile-item (client (directive e-directive) &optional parameters) + `((let ((parameters (list ,@parameters))) + (print-float-arg ,(incless:client-form client) + (lambda (client value digits exponent) + (apply #'print-exponent-arg client value digits exponent + ,(colon-p directive) ,(at-sign-p directive) + parameters)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.3.3 ~g General floating point. -(define-directive #\g - g-directive - nil - (named-parameters-directive) - ((w :type (or null integer) - :default-value nil) - (d :type (or null integer) - :defaule-value nil) - (e :type (or null integer) - :defaule-value nil) - (k :type (or null integer) - :default-value 1) - (overflowchar :type (or null character) - :default-value nil) - (padchar :type character - :default-value #\Space) - (exponentchar :type (or null character) - :default-value nil))) +(defclass g-directive (directive) ()) + +(defmethod specialize-directive + ((client t) (char (eql #\G)) directive (end-directive t)) + (change-class directive 'g-directive)) + +(defmethod parameter-specifications ((client t) (directive g-directive)) + '((:type (or null integer) :default nil) + (:type (or null integer) :default nil) + (:type (or null integer) :default nil) + (:type (or null integer) :default 1) + (:type (or null character) :default nil) + (:type character :default #\Space) + (:type (or null character) :default nil))) (defun print-general-arg (client value digits exponent - colonp at-signp w d e k + colon-p at-sign-p w d e k overflowchar padchar exponentchar) (unless d (let ((q (if (minusp exponent) @@ -352,49 +350,52 @@ (dd (- d exponent))) (cond ((<= 0 dd d) (let ((char (if (print-fixed-arg client value digits exponent - colonp at-signp ww dd 0 + colon-p at-sign-p ww dd 0 overflowchar padchar) overflowchar #\space))) (dotimes (i ee) (write-char char *destination*)))) (t (print-exponent-arg client value digits exponent - colonp at-signp w d e k + colon-p at-sign-p w d e k overflowchar padchar exponentchar))))) -(define-format-directive-interpreter g-directive +(defmethod interpret-item (client (directive g-directive) &optional parameters) (print-float-arg client (lambda (client value digits exponent) - (print-general-arg client value digits exponent - colonp at-signp w d e k - overflowchar padchar exponentchar)))) + (apply #'print-general-arg + client value digits exponent + (colon-p directive) (at-sign-p directive) + parameters)))) -(define-format-directive-compiler g-directive - `((print-float-arg ,(incless:client-form client) - (lambda (client value digits exponent) - (print-general-arg client value digits exponent - ,colonp ,at-signp w d e k - overflowchar padchar exponentchar))))) +(defmethod compile-item (client (directive g-directive) &optional parameters) + `((let ((parameters (list ,@parameters))) + (print-float-arg ,(incless:client-form client) + (lambda (client value digits exponent) + (apply #'print-general-arg + client value digits exponent + ,(colon-p directive) ,(at-sign-p directive) + parameters)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.3.4 ~$ Monetary floating point. -(define-directive #\$ - monetary-directive - nil - (named-parameters-directive) - ((d :type integer - :default-value 2) - (n :type integer - :default-value 1) - (w :type (or null integer) - :default-value nil) - (padchar :type character - :default-value #\Space))) +(defclass monetary-directive (directive) nil) + +(defmethod specialize-directive + ((client t) (char (eql #\$)) directive (end-directive t)) + (change-class directive 'monetary-directive)) + +(defmethod parameter-specifications + ((client t) (directive monetary-directive)) + '((:type integer :default 2) + (:type integer :default 1) + (:type (or null integer) :default nil) + (:type character :default #\Space))) (defun print-monetary-arg (client value digits exponent - colonp at-signp d n w padchar) + colon-p at-sign-p d n w padchar) (let ((decimal (make-instance 'decimal :digits digits)) sign len) @@ -403,7 +404,7 @@ decimal (setf sign (cond ((minusp (float-sign value)) #\-) - ((and at-signp (plusp value)) #\+))) + ((and at-sign-p (plusp value)) #\+))) (cond ((zerop (aref decimal-digits 0)) (setf decimal-position 1)) ((not (plusp exponent)) @@ -439,26 +440,31 @@ (length decimal-digits))) (cond ((> len (if w (max w 100) 100)) (print-exponent-arg client value digits exponent - colonp at-signp w (+ d n -1) nil 1 + colon-p at-sign-p w (+ d n -1) nil 1 #\Space padchar nil)) (t - (when (and colonp sign) + (when (and colon-p sign) (write-char sign *destination*)) (when w (loop repeat (max 0 (- w len)) do (write-char padchar *destination*))) - (when (and (not colonp) sign) + (when (and (not colon-p) sign) (write-char sign *destination*)) (print-decimal decimal)))))) -(define-format-directive-interpreter monetary-directive +(defmethod interpret-item (client (directive monetary-directive) &optional parameters) (print-float-arg client (lambda (client value digits exponent) - (print-monetary-arg client value digits exponent - colonp at-signp d n w padchar)))) - -(define-format-directive-compiler monetary-directive - `((print-float-arg ,(incless:client-form client) - (lambda (client value digits exponent) - (print-monetary-arg ,client value digits exponent - ,colonp ,at-signp d n w padchar))))) + (apply #'print-monetary-arg + client value digits exponent + (colon-p directive) (at-sign-p directive) + parameters)))) + +(defmethod compile-item (client (directive monetary-directive) &optional parameters) + `((let ((parameters (list ,@parameters))) + (print-float-arg ,(incless:client-form client) + (lambda (client value digits exponent) + (apply #'print-monetary-arg + client value digits exponent + ,(colon-p directive) ,(at-sign-p directive) + parameters)))))) diff --git a/code/format.lisp b/code/format.lisp index 2ced1ba..84c6aff 100644 --- a/code/format.lisp +++ b/code/format.lisp @@ -37,7 +37,7 @@ (defun interpret-items (client items) (loop for item across items - do (interpret-format-directive client item))) + do (interpret-item client item))) ;;; Runtime environment @@ -96,64 +96,8 @@ (*inner-tag* ',block-name)) ,@body)))) -(defun compute-parameter-value (directive parameter-spec) - (let* ((parameter-name (car parameter-spec)) - (compile-time-value (funcall parameter-name directive))) - (cond ((null compile-time-value) - ;; The parameter was not given at all in the format control - ;; string, neither as a constant value, nor as a value to - ;; be acquired at runtime (# or V). We must use a default - ;; value if it has any. - (getf (cdr parameter-spec) :default-value)) - ((eq compile-time-value :argument-reference) - ;; The parameter was given the explicit value V in the - ;; format control string, meaning we use the next argument - ;; to acquire the value of the parameter. We must test - ;; that there are more arguments, consume the next one, and - ;; check that the type of the argument acquired is correct. - (or (consume-next-argument `(or null - ,(getf (cdr parameter-spec) :type))) - (getf (cdr parameter-spec) :default-value))) - ((eq compile-time-value :remaining-argument-count) - ;; The parameter was given the explicit value # in the - ;; format control string, meaning we use the number of - ;; remaining arguments as the value of the parameter. - (unless (typep *remaining-argument-count* - (getf (cdr parameter-spec) :type)) - (error 'argument-type-error - :expected-type (getf (cdr parameter-spec) :type) - :datum *remaining-argument-count*)) - *remaining-argument-count*) - (t - ;; The parameter was given an explicit value (number or - ;; character) in the format control string, and this is the - ;; value we want. - compile-time-value)))) - ;;; The directive interpreter. -(defmethod interpret-format-directive (client directive) - (declare (ignore client)) - (error 'unknown-format-directive - :control-string (control-string directive) - :tilde-position (start directive) - :index (1- (end directive)))) - -(defmacro define-format-directive-interpreter (class-name &body body) - `(defmethod interpret-format-directive (client (directive ,class-name)) - (declare (ignorable client)) - (with-accessors ((control-string control-string) - (start start) - (suffix-start suffix-start) - (end end) - (colonp colonp) - (at-signp at-signp)) - directive - (let ,(loop for parameter-spec in (parameter-specs class-name) - collect `(,(car parameter-spec) - (compute-parameter-value directive ',parameter-spec))) - ,@body)))) - (defun consume-next-argument (type) (unless (< *previous-argument-index* (length *previous-arguments*)) (let (exited) @@ -211,27 +155,35 @@ (setf *previous-argument-index* new-arg-index) (aref *previous-arguments* *previous-argument-index*))))) -(defmacro define-format-directive-compiler (class-name &body body) - `(defmethod compile-format-directive (client (directive ,class-name)) - (declare (ignorable client)) - (with-accessors ((control-string control-string) - (start start) - (suffix-start suffix-start) - (end end) - (colonp colonp) - (at-signp at-signp) - (given-parameters given-parameters) - ,@(loop for parameter-spec in (parameter-specs class-name) - collect `(,(car parameter-spec) ,(car parameter-spec)))) - directive - ,@body))) - -(defun compile-time-value (directive slot-name) - (or (slot-value directive slot-name) - (getf (cdr (find slot-name - (parameter-specs (class-name (class-of directive))) - :key #'car)) - :default-value))) +(defmethod interpret-parameter ((parameter argument-reference-parameter)) + (or (consume-next-argument `(or null ,(parameter-type parameter))) + (parameter-default parameter))) + +(defmethod compile-parameter ((parameter argument-reference-parameter)) + `(or (consume-next-argument '(or null ,(parameter-type parameter))) + ,(parameter-default parameter))) + +(defmethod interpret-parameter ((parameter remaining-argument-count-parameter)) + (if (typep *remaining-argument-count* + (parameter-type parameter)) + *remaining-argument-count* + (error 'argument-type-error + :expected-type (parameter-type parameter) + :datum *remaining-argument-count*))) + +(defmethod compile-parameter ((parameter remaining-argument-count-parameter)) + `(if (typep *remaining-argument-count* + ',(parameter-type parameter)) + *remaining-argument-count* + (error 'argument-type-error + :expected-type ',(parameter-type parameter) + :datum *remaining-argument-count*))) + +(defmethod interpret-parameter ((parameter literal-parameter)) + (parameter-value parameter)) + +(defmethod compile-parameter ((parameter literal-parameter)) + (parameter-value parameter)) ;;; The reason we define this function is that the ~? directive ;;; (recursive processing), when a @ modifier is used, reuses @@ -241,7 +193,7 @@ (defun format-with-runtime-arguments (client control-string) (catch *inner-tag* (interpret-items client - (structure-items (split-control-string control-string))))) + (structure-items client (split-control-string control-string))))) (defun format (client destination control &rest args) (let ((*destination* (cond ((or (streamp destination) @@ -264,7 +216,8 @@ (get-output-stream-string *destination*) nil))) -(defmethod interpret-format-directive (client (item string)) +(defmethod interpret-item (client (item string) &optional parameters) + (declare (ignore parameters)) (if *newline-kind* (loop with start = 0 with in-blank-p = nil @@ -281,7 +234,8 @@ do (setf in-blank-p blankp)) (write-string item *destination*))) -(defmethod compile-format-directive (client (item string)) +(defmethod compile-item (client (item string) &optional parameters) + (declare (ignore parameters)) (if *newline-kind* #+sicl nil #-sicl (loop with start = 0 @@ -300,3 +254,13 @@ do (setf start index) do (setf in-blank-p blankp)) `((write-string ,item *destination*)))) + +(defmethod interpret-item :around (client (item directive) &optional parameters) + (declare (ignore parameters)) + (call-next-method client item + (mapcar #'interpret-parameter (parameters item)))) + +(defmethod compile-item :around (client (item directive) &optional parameters) + (declare (ignore parameters)) + (call-next-method client item + (mapcar #'compile-parameter (parameters item)))) diff --git a/code/formatter.lisp b/code/formatter.lisp index dfbd570..6652b1c 100644 --- a/code/formatter.lisp +++ b/code/formatter.lisp @@ -2,7 +2,7 @@ (defun formatter (client control-string) (check-type control-string string) - (let ((items (structure-items (split-control-string control-string)))) + (let ((items (structure-items client (split-control-string control-string)))) `(lambda (*destination* &rest args) (with-arguments args ,@(compile-items client items) diff --git a/code/generic-functions.lisp b/code/generic-functions.lisp index ce5c06b..286b6e2 100644 --- a/code/generic-functions.lisp +++ b/code/generic-functions.lisp @@ -3,31 +3,30 @@ ;;; Return the name of a subclass to be used for a particular ;;; directive. Each particular directive subclass must be accompanied ;;; by an eql-specialized method on this generic function. -(defgeneric directive-subclass-name (directive-character directive end-directive)) +(defgeneric specialize-directive (client directive-character directive end-directive)) ;;; For the default case, signal an error. -(defmethod directive-subclass-name (directive-character directive end-directive) +(defmethod specialize-directive (client directive-character directive end-directive) + (declare (ignore client directive-character end-directive)) (error 'unknown-directive-character :directive directive)) -;;; Given a name of a type of a directive, return a list of parameter -;;; specifiers for that type of directive. Each type of directive -;;; should supply an eql specialized method for this generic function. -(defgeneric parameter-specs (directive-name) - (:method (directive-name) - (declare (ignore directive-name)) +(defgeneric parameter-specifications (client directive) + (:method (client directive) + (declare (ignore client directive)) nil)) ;;; Check the syntax of a directive. -(defgeneric check-directive-syntax (directive) +(defgeneric check-directive-syntax (client directive) (:method-combination progn :most-specific-last)) -;;; DIRECTIVE is an instance of a subclass of the DIRECTIVE class -;;; describing the directive. -(defgeneric interpret-format-directive (client directive)) +(defgeneric interpret-item (client item &optional parameters) + (:method (client item &optional parameters) + (declare (ignore client item parameters)))) -;;; The directive compiler. -(defgeneric compile-format-directive (client directive)) +(defgeneric compile-item (client item &optional parameters) + (:method (client item &optional parameters) + (declare (ignore client item parameters)))) (defgeneric parse-directive-suffix (directive-character control-string start end) (:method (directive-character control-string start end) @@ -38,3 +37,4 @@ (:method (item) (declare (ignore item)) nil)) + diff --git a/code/layout-control.lisp b/code/layout-control.lisp index 688a445..a87cd00 100644 --- a/code/layout-control.lisp +++ b/code/layout-control.lisp @@ -8,12 +8,19 @@ ;;; ;;; 22.3.6.1 ~TAB Tabulate -(define-directive #\t tabulate-directive nil (named-parameters-directive) - ((colnum :type (integer 0) :default-value 1) - (colinc :type (integer 0) :default-value 1))) +(defclass tabulate-directive (directive) ()) + +(defmethod specialize-directive + ((client t) (char (eql #\T)) directive (end-directive t)) + (change-class directive 'tabulate-directive)) + +(defmethod parameter-specifications + ((client t) (directive tabulate-directive)) + '((:type (integer 0) :default 1) + (:type (integer 0) :default 1))) (defmethod layout-requirements ((item tabulate-directive)) - (when (colonp item) + (when (colon-p item) (list :logical-block))) (defun format-relative-tab (client colnum colinc) @@ -37,67 +44,74 @@ (trivial-stream-column:advance-to-column (+ cur (- colinc (rem (- cur colnum) colinc))) *destination*)))))) -(define-format-directive-interpreter tabulate-directive - (cond (colonp - #-sicl - (inravina:pprint-tab client *destination* - (if at-signp :section-relative :section) - colnum colinc)) - (at-signp - (format-relative-tab client colnum colinc)) - (t - (format-absolute-tab client colnum colinc)))) - -(define-format-directive-compiler tabulate-directive - (cond (colonp - #-sicl - `((inravina:pprint-tab ,(incless:client-form client) *destination* - ,(if at-signp :section-relative :section) - colnum colinc))) - (at-signp - `((format-relative-tab ,(incless:client-form client) colnum colinc))) - (t - `((format-absolute-tab ,(incless:client-form client) colnum colinc))))) +(defmethod interpret-item (client (directive tabulate-directive) &optional parameters) + (let ((colon-p (colon-p directive)) + (at-sign-p (at-sign-p directive))) + (cond (colon-p + #-sicl + (apply #'inravina:pprint-tab + client *destination* + (if at-sign-p :section-relative :section) + parameters)) + (at-sign-p + (apply #'format-relative-tab client parameters)) + (t + (apply #'format-absolute-tab client parameters))))) + +(defmethod compile-item (client (directive tabulate-directive) &optional parameters) + (let ((colon-p (colon-p directive)) + (at-sign-p (at-sign-p directive))) + (cond (colon-p + #-sicl + `((inravina:pprint-tab ,(incless:client-form client) *destination* + ,(if at-sign-p :section-relative :section) + ,@parameters))) + (at-sign-p + `((format-relative-tab ,(incless:client-form client) ,@parameters))) + (t + `((format-absolute-tab ,(incless:client-form client) ,@parameters)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.6.3 ~> End of justification or of logical block -(define-directive #\> - end-justification-directive - nil - (named-parameters-directive end-structured-directive-mixin) - ()) - -(defmethod check-directive-syntax progn ((directive end-justification-directive)) - (cond ((colonp directive) - (change-class directive 'end-logical-block-directive)) - ((at-signp directive) - (error "wibble")))) - -(define-format-directive-interpreter end-justification-directive - ;; do nothing - nil) +(defclass end-justification-directive + (directive end-structured-directive-mixin no-modifiers-mixin) nil) -(define-format-directive-compiler end-justification-directive - ;; do nothing - nil) +(defmethod specialize-directive + ((client t) (char (eql #\>)) directive (end-directive t)) + (if (colon-p directive) + (change-class directive 'end-logical-block-directive) + (change-class directive 'end-justification-directive))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.6.2 ~< Justification -(define-directive #\< - justification-directive - end-justification-directive - (named-parameters-directive structured-directive-mixin) - ((mincol :type integer :default-value 0) - (colinc :type (integer 0) :default-value 1) - (minpad :type integer :default-value 0) - (padchar :type character :default-value #\Space))) +(defclass justification-directive + (directive structured-directive-mixin) nil) + +(defmethod specialize-directive + ((client t) (char (eql #\<)) directive + (end-directive end-justification-directive)) + (change-class directive 'justification-directive)) + +(defmethod specialize-directive + ((client t) (char (eql #\<)) directive (end-directive t)) + (error 'unmatched-directive + :directive directive + :control-string (control-string directive) + :tilde-position (start directive))) + +(defmethod parameter-specifications + ((client t) (directive justification-directive)) + '((:type integer :default 0) + (:type (integer 0) :default 1) + (:type integer :default 0) + (:type character :default #\Space))) (defmethod layout-requirements :around ((item justification-directive)) - (merge-layout-requirements (list (if (colonp (aref (aref (clauses item) 0) (1- (length (aref (clauses item) 0))))) + (merge-layout-requirements (list (if (colon-p (aref (aref (clauses item) 0) (1- (length (aref (clauses item) 0))))) :justify-dynamic :justify)) (call-next-method) @@ -109,8 +123,8 @@ 100)) (defun print-justification (client pad-left pad-right extra-space line-len - mincol colinc minpad padchar - newline-segment segments) + newline-segment segments + mincol colinc minpad padchar) (declare (ignore client)) (when (and (not pad-left) (not pad-right) (null (cdr segments))) (setf pad-left t)) @@ -152,7 +166,7 @@ (when pad-right (write-padding nil))))) -(define-format-directive-interpreter justification-directive +(defmethod interpret-item (client (directive justification-directive) &optional parameters) (loop with newline-segment = nil with *extra-space* = nil with *line-length* = nil @@ -161,20 +175,22 @@ (with-output-to-string (*destination*) (interpret-items client clause))) for index from 0 - finally (print-justification client - colonp at-signp *extra-space* *line-length* - mincol colinc minpad padchar - newline-segment segments) + finally (apply #'print-justification client + (colon-p directive) (at-sign-p directive) + *extra-space* *line-length* + newline-segment segments + parameters) while segment if (and (zerop index) - (colonp (aref clause (1- (length clause))))) + (colon-p (aref clause (1- (length clause))))) do (setf newline-segment segment) else collect segment into segments)) -(define-format-directive-compiler justification-directive +(defmethod compile-item (client (directive justification-directive) &optional parameters) `((prog (newline-segment segments - *extra-space* *line-length*) + *extra-space* *line-length* + (parameters (list ,@parameters))) ,@(loop for clause across (clauses directive) for segment = `(catch *inner-tag* (with-output-to-string (*destination*) @@ -182,7 +198,7 @@ for index from 0 while segment if (and (zerop index) - (colonp (aref clause (1- (length clause))))) + (colon-p (aref clause (1- (length clause))))) collect `(let ((segment ,segment)) (if segment (setf newline-segment segment) @@ -193,7 +209,8 @@ (push segment segments) (go end)))) end - (print-justification ,(incless:client-form client) - ,colonp ,at-signp *extra-space* *line-length* - mincol colinc minpad padchar - newline-segment (nreverse segments))))) + (apply #'print-justification ,(incless:client-form client) + ,(colon-p directive) ,(at-sign-p directive) + *extra-space* *line-length* + newline-segment (nreverse segments) + parameters)))) diff --git a/code/miscellaneous-operations.lisp b/code/miscellaneous-operations.lisp index 823a61e..661775f 100644 --- a/code/miscellaneous-operations.lisp +++ b/code/miscellaneous-operations.lisp @@ -8,63 +8,77 @@ ;;; ;;; 22.3.8.2 ~) End of case conversion -(define-directive #\) - end-case-conversion-directive - nil - (named-parameters-directive - no-modifiers-mixin end-structured-directive-mixin) - ()) +(defclass end-case-conversion-directive + (directive no-modifiers-mixin + end-structured-directive-mixin) + nil) -(define-format-directive-interpreter end-case-conversion-directive - ;; do nothing - nil) - -(define-format-directive-compiler end-case-conversion-directive - ;; do nothing - nil) +(defmethod specialize-directive + ((client t) (char (eql #\))) directive (end-directive t)) + (change-class directive 'end-case-conversion-directive)) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.8.1 ~( Case conversion -(define-directive #\( - case-conversion-directive - end-case-conversion-directive - (named-parameters-directive structured-directive-mixin) - ()) +(defclass case-conversion-directive + (directive structured-directive-mixin) nil) + +(defmethod specialize-directive + ((client t) (char (eql #\()) directive + (end-directive end-case-conversion-directive)) + (change-class directive 'case-conversion-directive)) -(define-format-directive-interpreter case-conversion-directive - (let ((*destination* (cond ((and colonp at-signp) - (make-instance 'upcase-stream :target *destination*)) - (colonp - (make-instance 'capitalize-stream :target *destination*)) - (at-signp - (make-instance 'first-capitalize-stream :target *destination*)) - (t - (make-instance 'downcase-stream :target *destination*))))) - (interpret-items client (aref (clauses directive) 0)))) +(defmethod specialize-directive + ((client t) (char (eql #\()) directive (end-directive t)) + (error 'unmatched-directive + :directive directive + :control-string (control-string directive) + :tilde-position (start directive))) -(define-format-directive-compiler case-conversion-directive - `((let ((*destination* ,(cond ((and colonp at-signp) - '(make-instance 'upcase-stream :target *destination*)) - (colonp - '(make-instance 'capitalize-stream :target *destination*)) - (at-signp - '(make-instance 'first-capitalize-stream :target *destination*)) - (t - '(make-instance 'downcase-stream :target *destination*))))) - ,@(compile-items client (aref (clauses directive) 0))))) +(defmethod interpret-item (client (item case-conversion-directive) &optional parameters) + (declare (ignore parameters)) + (let* ((colon-p (colon-p item)) + (at-sign-p (at-sign-p item)) + (*destination* (cond ((and colon-p at-sign-p) + (make-instance 'upcase-stream :target *destination*)) + (colon-p + (make-instance 'capitalize-stream :target *destination*)) + (at-sign-p + (make-instance 'first-capitalize-stream :target *destination*)) + (t + (make-instance 'downcase-stream :target *destination*))))) + (interpret-items client (aref (clauses item) 0)))) + +(defmethod compile-item (client (item case-conversion-directive) &optional parameters) + (declare (ignore parameters)) + (let ((colon-p (colon-p item)) + (at-sign-p (at-sign-p item))) + `((let ((*destination* ,(cond ((and colon-p at-sign-p) + '(make-instance 'upcase-stream :target *destination*)) + (colon-p + '(make-instance 'capitalize-stream :target *destination*)) + (at-sign-p + '(make-instance 'first-capitalize-stream :target *destination*)) + (t + '(make-instance 'downcase-stream :target *destination*))))) + ,@(compile-items client (aref (clauses item) 0)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.8.3 ~p Plural -(define-directive #\p plural-directive nil (named-parameters-directive) ()) +(defclass plural-directive (directive) nil) + +(defmethod specialize-directive + ((client t) (char (eql #\P)) directive (end-directive t)) + (change-class directive 'plural-directive)) -(define-format-directive-interpreter plural-directive - (when colonp +(defmethod interpret-item (client (item plural-directive) &optional parameters) + (declare (ignore parameters)) + (when (colon-p item) (go-to-argument -1)) - (if at-signp + (if (at-sign-p item) (write-string (if (eql (consume-next-argument t) 1) "y" "ies") @@ -72,10 +86,11 @@ (unless (eql (consume-next-argument t) 1) (write-char #\s *destination*)))) -(define-format-directive-compiler plural-directive - `(,@(when colonp +(defmethod compile-item (client (item plural-directive) &optional parameters) + (declare (ignore parameters)) + `(,@(when (colon-p item) `((go-to-argument -1))) - ,(if at-signp + ,(if (at-sign-p item) `(write-string (if (eql (consume-next-argument t) 1) "y" "ies") diff --git a/code/miscellaneous-pseudo-operations.lisp b/code/miscellaneous-pseudo-operations.lisp index a186041..58d8919 100644 --- a/code/miscellaneous-pseudo-operations.lisp +++ b/code/miscellaneous-pseudo-operations.lisp @@ -12,40 +12,62 @@ ;;; ;;; 22.3.9.1 ~; Clause separator -(define-directive #\; - semicolon-directive - nil - (named-parameters-directive) - ((extra-space :type (or null integer) :default-value nil) - (line-length :type (or null integer) :default-value nil))) +(defclass semicolon-directive (directive) nil) + +(defmethod specialize-directive + ((client t) (char (eql #\;)) directive (end-directive t)) + (change-class directive 'semicolon-directive)) + +(defmethod parameter-specifications + ((client t) (directive semicolon-directive)) + '((:type (or null integer) :default nil) + (:type (or null integer) :default nil))) (defmethod structured-separator-p ((directive semicolon-directive)) t) -(define-format-directive-interpreter semicolon-directive - (when extra-space - (setf *extra-space* extra-space)) - (when line-length - (setf *line-length* line-length))) - -(define-format-directive-compiler semicolon-directive - `((when extra-space +(defmethod interpret-item (client (directive semicolon-directive) &optional parameters) + (let ((extra-space (car parameters)) + (line-length (cadr parameters))) + (when extra-space (setf *extra-space* extra-space)) (when line-length (setf *line-length* line-length)))) +(defmethod compile-item (client (directive semicolon-directive) &optional parameters) + (let ((extra-space (car parameters)) + (line-length (cadr parameters))) + `(,@(cond ((numberp extra-space) + `((setf *extra-space* ,extra-space))) + (extra-space + `((setf *extra-space* (or ,extra-space + *extra-space*))))) + ,@(cond ((numberp line-length) + `((setf *line-length* ,line-length))) + (line-length + `((setf *line-length* (or ,line-length + *line-length*)))))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.9.2 ~^ Escape upward -(define-directive #\^ circumflex-directive nil (named-parameters-directive) - ((p1 :type (or character integer)) - (p2 :type (or character integer)) - (p3 :type (or character integer)))) +(defclass circumflex-directive (directive) nil) + +(defmethod specialize-directive + ((client t) (char (eql #\^)) directive (end-directive t)) + (change-class directive 'circumflex-directive)) + +(defmethod parameter-specifications + ((client t) (directive circumflex-directive)) + '((:type (or null character integer)) + (:type (or null character integer)) + (:type (or null character integer)))) (defmethod check-directive-syntax progn - ((directive circumflex-directive)) - (let ((parameters (given-parameters directive))) + (client (directive circumflex-directive)) + (declare (ignore client)) + (let ((parameters (parameters directive))) (when (and (second parameters) (not (first parameters))) (error 'parameter-omitted :parameter1 1 @@ -55,50 +77,69 @@ :parameter2 2 :parameter3 3)))) -(define-format-directive-interpreter circumflex-directive - (cond ((and (null p1) (null p2) (null p3)) - (funcall (if colonp *outer-exit-if-exhausted* *inner-exit-if-exhausted*))) - ((or (and (eql p1 0) (null p2) (null p3)) - (and (null p1) (eql p2 0) (null p3)) - (and (null p1) (null p2) (eql p3 0)) - (and (null p1) p2 p3 (eql p2 p3)) - (and (null p2) p1 p3 (eql p1 p3)) - (and (null p3) p1 p2 (eql p1 p2)) - (and p1 p2 p3 (<= p1 p2 p3))) - (funcall (if colonp *outer-exit* *inner-exit*) nil)))) - -(define-format-directive-compiler circumflex-directive - (cond ((null p1) - `((funcall ,(if colonp '*outer-exit-if-exhausted* '*inner-exit-if-exhausted*)))) - ((null p2) - `((cond ((null p1) - (funcall ,(if colonp '*outer-exit-if-exhausted* '*inner-exit-if-exhausted*))) - ((eql 0 p1) - (funcall ,(if colonp '*outer-exit* '*inner-exit*) nil))))) - ((null p3) - `((cond ((and (null p1) (null p2)) - (funcall ,(if colonp '*outer-exit-if-exhausted* '*inner-exit-if-exhausted*))) - ((or (and (null p1) (eql 0 p2)) - (and (eql 0 p1) (null p2)) - (and p1 p2 (eql p1 p2))) - (funcall ,(if colonp '*outer-exit* '*inner-exit*) nil))))) - (t - `((cond ((and (null p1) (null p2) (null p3)) - (funcall ,(if colonp '*outer-exit-if-exhausted* '*inner-exit-if-exhausted*))) - ((or (and (null p1) (null p2) (eql 0 p3)) - (and (null p1) (eql 0 p2) (null p3)) - (and (eql 0 p1) (null p2) (null p3)) - (and (null p1) p2 p3 (eql p2 p3)) - (and (null p2) p1 p3 (eql p1 p3)) - (and (null p3) p1 p2 (eql p1 p2)) - (and p1 p2 p3 (<= p1 p2 p3))) - (funcall ,(if colonp '*outer-exit* '*inner-exit*) nil))))))) +(defmethod interpret-item (client (directive circumflex-directive) &optional parameters) + (let ((p1 (car parameters)) + (p2 (cadr parameters)) + (p3 (caddr parameters)) + (colon-p (colon-p directive))) + (cond ((and (null p1) (null p2) (null p3)) + (funcall (if colon-p *outer-exit-if-exhausted* *inner-exit-if-exhausted*))) + ((or (and (eql p1 0) (null p2) (null p3)) + (and (null p1) (eql p2 0) (null p3)) + (and (null p1) (null p2) (eql p3 0)) + (and (null p1) p2 p3 (eql p2 p3)) + (and (null p2) p1 p3 (eql p1 p3)) + (and (null p3) p1 p2 (eql p1 p2)) + (and p1 p2 p3 (<= p1 p2 p3))) + (funcall (if colon-p *outer-exit* *inner-exit*) nil))))) + +(defmethod compile-item (client (directive circumflex-directive) &optional parameters) + (let ((p1 (car parameters)) + (p2 (cadr parameters)) + (p3 (caddr parameters)) + (colon-p (colon-p directive))) + (cond ((null p1) + `((funcall ,(if colon-p '*outer-exit-if-exhausted* '*inner-exit-if-exhausted*)))) + ((null p2) + `((let ((p1 ,p1)) + (cond ((null p1) + (funcall ,(if colon-p '*outer-exit-if-exhausted* '*inner-exit-if-exhausted*))) + ((eql 0 p1) + (funcall ,(if colon-p '*outer-exit* '*inner-exit*) nil)))))) + ((null p3) + `((let ((p1 ,p1) + (p2 ,p2)) + (cond ((and (null p1) (null p2)) + (funcall ,(if colon-p '*outer-exit-if-exhausted* '*inner-exit-if-exhausted*))) + ((or (and (null p1) (eql 0 p2)) + (and (eql 0 p1) (null p2)) + (and p1 p2 (eql p1 p2))) + (funcall ,(if colon-p '*outer-exit* '*inner-exit*) nil)))))) + (t + `((let ((p1 ,p1) + (p2 ,p2) + (p3 ,p3)) + (cond ((and (null p1) (null p2) (null p3)) + (funcall ,(if colon-p '*outer-exit-if-exhausted* '*inner-exit-if-exhausted*))) + ((or (and (null p1) (null p2) (eql 0 p3)) + (and (null p1) (eql 0 p2) (null p3)) + (and (eql 0 p1) (null p2) (null p3)) + (and (null p1) p2 p3 (eql p2 p3)) + (and (null p2) p1 p3 (eql p1 p3)) + (and (null p3) p1 p2 (eql p1 p2)) + (and p1 p2 p3 (<= p1 p2 p3))) + (funcall ,(if colon-p '*outer-exit* '*inner-exit*) nil))))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.9.3 ~Newline Igored newline -(define-directive #\Newline newline-directive nil (named-parameters-directive at-most-one-modifier-mixin) ()) +(defclass newline-directive + (directive at-most-one-modifier-mixin) nil) + +(defmethod specialize-directive + ((client t) (char (eql #\Newline)) directive (end-directive t)) + (change-class directive 'newline-directive)) (defmethod parse-directive-suffix ((directive-character (eql #\Newline)) control-string start end) (or (position-if (lambda (char) @@ -106,22 +147,24 @@ control-string :start start :end end) end)) -(define-format-directive-interpreter newline-directive - (cond (colonp +(defmethod interpret-item (client (directive newline-directive) &optional parameters) + (declare (ignore parameters)) + (cond ((colon-p directive) ;; Remove the newline but print the following whitespace. - (write-string (subseq control-string suffix-start end) *destination*)) - (at-signp + (write-string (subseq (control-string directive) (suffix-start directive) (end directive)) *destination*)) + ((at-sign-p directive) ;; Print the newline, but remove the following whitespace. (write-char #\Newline *destination*)) (t ;; Ignore both the newline and the following whitespace. nil))) -(define-format-directive-compiler newline-directive - (cond (colonp +(defmethod compile-item (client (directive newline-directive) &optional parameters) + (declare (ignore parameters)) + (cond ((colon-p directive) ;; Remove the newline but print the following whitespace. - `((write-string ,(subseq control-string suffix-start end) *destination*))) - (at-signp + `((write-string ,(subseq (control-string directive) (suffix-start directive) (end directive)) *destination*))) + ((at-sign-p directive) ;; Print the newline, but remove the following whitespace. `((write-char #\Newline *destination*))) (t diff --git a/code/packages.lisp b/code/packages.lisp index 7407f6d..7c2cc2d 100644 --- a/code/packages.lisp +++ b/code/packages.lisp @@ -4,6 +4,16 @@ #:formatter) #+sicl (:local-nicknames (:trivial-gray-streams :cyclosis)) - (:export #:format + (:export #:*roman-digits* + #:at-sign-p + #:check-directive-syntax + #:colon-p + #:compile-item + #:directive + #:format + #:format-compiler-macro #:formatter - #:format-compiler-macro)) + #:interpret-item + #:parameter-specifications + #:parse-directive-suffix + #:specialize-directive)) diff --git a/code/parse-control-string.lisp b/code/parse-control-string.lisp index b2a1271..8457a99 100644 --- a/code/parse-control-string.lisp +++ b/code/parse-control-string.lisp @@ -13,29 +13,30 @@ ;;; values, the parameter that was parsed and the position immediately ;;; beyond the parameter that was parsed. (defun parse-parameter (string start end tilde-position) - (cond ((= start end) + (when (= start end) (error 'end-of-control-string-error :control-string string :tilde-position tilde-position :why "expected a parameter")) - ((eql (char string start) #\,) - ;; Indicates absence of parameter. - (values nil start)) - ((or (eql (char string start) #\v) (eql (char string start) #\V)) + (case (char string start) + ((#\v #\V) ;; Indicates that the value is to be taken from the arguments. - (values :argument-reference (1+ start))) - ((eql (char string start) #\#) + (values (make-instance 'argument-reference-parameter) + (1+ start))) + (#\# ;; Indicates that the value is the remaining number of arguments - (values :remaining-argument-count (1+ start))) - ((eql (char string start) #\') + (values (make-instance 'remaining-argument-count-parameter) + (1+ start))) + (#\' (incf start) (when (= start end) (error 'end-of-control-string-error :control-string string :tilde-position tilde-position :why "character expected")) - (values (char string start) (1+ start))) - ((find (char string start) "+-0123456789") + (values (make-instance 'literal-parameter :value (char string start)) + (1+ start))) + ((#\+ #\- #\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9) (multiple-value-bind (value position) (parse-integer string :start start :junk-allowed t) (when (null value) @@ -43,13 +44,10 @@ :control-string string :tilde-position tilde-position :index start)) - (values value position))) - (t - (values nil start) - #+(or)(error 'expected-parameter-start - :control-string string - :tilde-position tilde-position - :index start)))) + (values (make-instance 'literal-parameter :value value) + position))) + (otherwise + (values (make-instance 'literal-parameter) start)))) ;;; Parse the parameters of a format directive. STRING is the entire ;;; control string START is the position of the tilde character that @@ -118,7 +116,7 @@ (let ((end (length string))) (multiple-value-bind (parameters position1) (parse-parameters string start end) - (multiple-value-bind (colonp at-signp position2) + (multiple-value-bind (colon-p at-sign-p position2) (parse-modifiers string position1 end start) (when (= position2 end) (error 'end-of-control-string-error @@ -131,4 +129,4 @@ (let ((directive-character (char string position2)) (suffix-start (incf position2))) (setf position2 (parse-directive-suffix directive-character string suffix-start end)) - (values directive-character parameters colonp at-signp suffix-start position2)))))) + (values directive-character parameters colon-p at-sign-p suffix-start position2)))))) diff --git a/code/pretty-printer-operations.lisp b/code/pretty-printer-operations.lisp index 4f4ae32..3442f1e 100644 --- a/code/pretty-printer-operations.lisp +++ b/code/pretty-printer-operations.lisp @@ -8,57 +8,68 @@ ;;; ;;; 22.3.5.1 ~_ Conditional newline -(define-directive #\_ underscore-directive nil (named-parameters-directive) ()) +(defclass underscore-directive (directive) nil) + +(defmethod specialize-directive + ((client t) (char (eql #\_)) directive (end-directive t)) + (change-class directive 'underscore-directive)) (defmethod layout-requirements ((item underscore-directive)) (list :logical-block)) -(define-format-directive-interpreter underscore-directive +(defmethod interpret-item (client (directive underscore-directive) &optional parameters) + (declare (ignore parameters) + (ignorable client)) #-sicl - (inravina:pprint-newline client *destination* - (cond ((and colonp at-signp) :mandatory) - (colonp :fill) - (at-signp :miser) - (t :linear)))) + (let ((colon-p (colon-p directive)) + (at-sign-p (at-sign-p directive))) + (inravina:pprint-newline client *destination* + (cond ((and colon-p at-sign-p) :mandatory) + (colon-p :fill) + (at-sign-p :miser) + (t :linear))))) -(define-format-directive-compiler underscore-directive +(defmethod compile-item (client (directive underscore-directive) &optional parameters) + (declare (ignore parameters) + (ignorable client)) #-sicl - `((inravina:pprint-newline ,(incless:client-form client) *destination* - ,(cond ((and colonp at-signp) :mandatory) - (colonp :fill) - (at-signp :miser) - (t :linear))))) - -(define-directive #\> - end-logical-block-directive - nil - (named-parameters-directive end-structured-directive-mixin) - ()) - -(define-format-directive-interpreter end-logical-block-directive - ;; do nothing - nil) - -(define-format-directive-compiler end-logical-block-directive - ;; do nothing - nil) + (let ((colon-p (colon-p directive)) + (at-sign-p (at-sign-p directive))) + `((inravina:pprint-newline ,(incless:client-form client) *destination* + ,(cond ((and colon-p at-sign-p) :mandatory) + (colon-p :fill) + (at-sign-p :miser) + (t :linear)))))) + +(defclass end-logical-block-directive + (directive end-structured-directive-mixin) nil) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.5.2 ~< Logical block -(define-directive #\< - logical-block-directive - end-logical-block-directive - (named-parameters-directive structured-directive-mixin) - ()) +(defclass logical-block-directive + (directive structured-directive-mixin) nil) + +(defmethod specialize-directive + ((client t) (char (eql #\<)) directive + (end-directive end-logical-block-directive)) + (change-class directive 'logical-block-directive)) + +(defmethod specialize-directive + ((client t) (char (eql #\<)) directive (end-directive t)) + (error 'unmatched-directive + :directive directive + :control-string (control-string directive) + :tilde-position (start directive))) (defmethod layout-requirements :around ((item logical-block-directive)) (merge-layout-requirements (list :logical-block) (call-next-method) t)) -(defmethod check-directive-syntax progn ((directive logical-block-directive)) +(defmethod check-directive-syntax progn (client (directive logical-block-directive)) + (declare (ignore client)) (flet ((check-fix (items) (when (notevery (lambda (item) (or (stringp item) @@ -73,17 +84,21 @@ (when (= (length (clauses directive)) 3) (check-fix (aref (clauses directive) 2))))) -(define-format-directive-interpreter logical-block-directive +(defmethod interpret-item (client (directive logical-block-directive) &optional parameters) + (declare (ignore parameters) + (ignorable client)) #-sicl (let* ((last-clause (aref (clauses directive) (1- (length (clauses directive))))) - (*newline-kind* (if (at-signp (aref last-clause (1- (length last-clause)))) + (colon-p (colon-p directive)) + (at-sign-p (at-sign-p directive)) + (*newline-kind* (if (at-sign-p (aref last-clause (1- (length last-clause)))) :fill nil)) (prefix (cond ((> (length (clauses directive)) 1) (if (> (length (aref (clauses directive) 0)) 1) (aref (aref (clauses directive) 0) 0) "")) - (colonp + (colon-p "(") (t ""))) @@ -91,16 +106,16 @@ (if (> (length (aref (clauses directive) 2)) 1) (aref (aref (clauses directive) 2) 0) "")) - (colonp + (colon-p ")") (t ""))) (per-line-prefix-p (and (> (length (clauses directive)) 1) - (at-signp (aref (aref (clauses directive) 0) + (at-sign-p (aref (aref (clauses directive) 0) (1- (length (aref (clauses directive) 0))))))) - (object (unless at-signp (consume-next-argument t)))) + (object (unless at-sign-p (consume-next-argument t)))) (flet ((interpret-body (*destination* escape-hook pop-argument-hook) - (if at-signp + (if at-sign-p (interpret-items client (aref (clauses directive) (if (= (length (clauses directive)) 1) 0 @@ -121,17 +136,21 @@ :per-line-prefix-p per-line-prefix-p :suffix suffix)))) -(define-format-directive-compiler logical-block-directive +(defmethod compile-item (client (directive logical-block-directive) &optional parameters) + (declare (ignore parameters) + (ignorable client)) #-sicl (let* ((last-clause (aref (clauses directive) (1- (length (clauses directive))))) - (*newline-kind* (if (at-signp (aref last-clause (1- (length last-clause)))) + (colon-p (colon-p directive)) + (at-sign-p (at-sign-p directive)) + (*newline-kind* (if (at-sign-p (aref last-clause (1- (length last-clause)))) :fill nil)) (prefix (cond ((> (length (clauses directive)) 1) (if (> (length (aref (clauses directive) 0)) 1) (aref (aref (clauses directive) 0) 0) "")) - (colonp + (colon-p "(") (t ""))) @@ -139,14 +158,14 @@ (if (> (length (aref (clauses directive) 2)) 1) (aref (aref (clauses directive) 2) 0) "")) - (colonp + (colon-p ")") (t ""))) (per-line-prefix-p (and (> (length (clauses directive)) 1) - (at-signp (aref (aref (clauses directive) 0) + (at-sign-p (aref (aref (clauses directive) 0) (1- (length (aref (clauses directive) 0)))))))) - (if at-signp + (if at-sign-p `((inravina:execute-logical-block ,(incless:client-form client) *destination* nil (lambda (*destination* escape-hook pop-argument-hook) @@ -178,23 +197,31 @@ ;;; ;;; 22.3.5.3 ~i Indent -(define-directive #\i i-directive nil (named-parameters-directive) - ((how-many :type integer :default-value 0))) +(defclass i-directive (directive) nil) + +(defmethod specialize-directive + ((client t) (char (eql #\I)) directive (end-directive t)) + (change-class directive 'i-directive)) + +(defmethod parameter-specifications ((client t) (directive i-directive)) + '((:type integer :default 0))) (defmethod layout-requirements ((item i-directive)) (list :logical-block)) -(define-format-directive-interpreter i-directive +(defmethod interpret-item (client (directive i-directive) &optional parameters) + (declare (ignorable client parameters)) #-sicl (inravina:pprint-indent client *destination* - (if colonp :current :block) - how-many)) + (if (colon-p directive) :current :block) + (car parameters))) -(define-format-directive-compiler i-directive +(defmethod compile-item (client (directive i-directive) &optional parameters) + (declare (ignorable client parameters)) #-sicl `((inravina:pprint-indent ,(incless:client-form client) *destination* - ,(if colonp :current :block) - how-many))) + ,(if (colon-p directive) :current :block) + ,(car parameters)))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; @@ -205,13 +232,17 @@ ;;; belonging to the directive beyond the directive character itself, ;;; which means the standard mechanism of parsing it cannot be used. ;;; Second, this directive takes an arbitrary number of parameters. -;;; -;;; So, define-format-directive-interpreter cannot be used, since its -;;; main purpose is to give lexical access to each parameter by name. -(define-directive #\/ call-function-directive nil (directive) - () - (%function-name :accessor function-name)) +(defclass call-function-directive (directive) + ((%function-name :accessor function-name))) + +(defmethod specialize-directive + ((client t) (char (eql #\/)) directive (end-directive t)) + (change-class directive 'call-function-directive)) + +(defmethod parameter-specifications (client (directive call-function-directive)) + (declare (ignore client)) + '((:type (or null character integer) :default nil :rest t))) (defmethod parse-directive-suffix ((directive-character (eql #\/)) control-string start end) (let ((position-of-trailing-slash @@ -223,14 +254,15 @@ :why "expected a trailing slash")) (1+ position-of-trailing-slash))) -(defmethod check-directive-syntax progn ((directive call-function-directive)) +(defmethod check-directive-syntax progn (client (directive call-function-directive)) + (declare (ignore client)) ;; Check that there is at most one package marker in the function name. ;; Also, compute a symbol from the name. (with-accessors ((control-string control-string) (start start) (suffix-start suffix-start) (end end) - (colonp colonp)) + (colon-p colon-p)) directive ;; The HyperSpec says that all the characters of the function ;; name are treated as if they were upper-case. @@ -259,53 +291,21 @@ :directive directive)) (setf (function-name directive) (intern symbol-name package))))) -(defmethod interpret-format-directive (client (directive call-function-directive)) - (with-accessors ((control-string control-string) - (start start) - (end end) - (colonp colonp) - (at-signp at-signp) - (given-parameters given-parameters) - (function-name function-name)) - directive - (let ((param-args - (loop for parameter in given-parameters - collect (cond ((eq parameter :remaining-argument-count) - *remaining-argument-count*) - ((eq parameter :argument-reference) - (consume-next-argument t)) - (t parameter))))) - (apply function-name +(defmethod interpret-item (client (directive call-function-directive) &optional parameters) + (declare (ignore client)) + (apply (function-name directive) + *destination* + (consume-next-argument t) + (colon-p directive) + (at-sign-p directive) + parameters)) + +(defmethod compile-item (client (directive call-function-directive) &optional parameters) + (declare (ignore client)) + `((let ((parameters (list ,@parameters))) + (apply ',(function-name directive) *destination* (consume-next-argument t) - colonp - at-signp - param-args)))) - -;;; This is not quite right. We should probably look up the -;;; function name at runtime as opposed to compile time. -(defmethod compile-format-directive (client (directive call-function-directive)) - (declare (ignorable client)) - (with-accessors ((control-string control-string) - (start start) - (end end) - (colonp colonp) - (at-signp at-signp) - (given-parameters given-parameters) - (function-name function-name)) - directive - `((let ((param-args (list ,@(mapcar (lambda (parameter) - (case parameter - (:remaining-argument-count - '*remaining-argument-count*) - (:argument-reference - '(consume-next-argument t)) - (otherwise - parameter))) - given-parameters)))) - (apply ',function-name - *destination* - (consume-next-argument t) - ,colonp - ,at-signp - param-args))))) + ,(colon-p directive) + ,(at-sign-p directive) + parameters)))) diff --git a/code/printer-operations.lisp b/code/printer-operations.lisp index f9789bd..cd19b8c 100644 --- a/code/printer-operations.lisp +++ b/code/printer-operations.lisp @@ -4,9 +4,9 @@ (in-package #:invistra) -(defun print-a-or-s (raw-output at-signp mincol colinc minpad padchar) +(defun print-a-or-s (raw-output at-sign-p mincol colinc minpad padchar) (let ((pad-length (max minpad (* colinc (ceiling (- mincol (length raw-output)) colinc))))) - (if at-signp + (if at-sign-p (progn (loop repeat pad-length do (write-char padchar *destination*)) (write-string raw-output *destination*)) (progn (write-string raw-output *destination*) @@ -16,119 +16,132 @@ ;;; ;;; 22.3.4.1 ~a Aesthetic. -(define-directive #\a a-directive nil (named-parameters-directive) - ((mincol :type integer :default-value 0) - (colinc :type (integer 0) :default-value 1) - (minpad :type integer :default-value 0) - (padchar :type character :default-value #\Space))) +(defclass a-directive (directive) nil) -(define-format-directive-interpreter a-directive +(defmethod specialize-directive + ((client t) (char (eql #\A)) directive (end-directive t)) + (change-class directive 'a-directive)) + +(defmethod parameter-specifications ((client t) (directive a-directive)) + '((:type integer :default 0) + (:type (integer 0) :default 1) + (:type integer :default 0) + (:type character :default #\Space))) + +(defmethod interpret-item (client (directive a-directive) &optional parameters) (let ((*print-escape* nil) (*print-readably* nil) (arg (consume-next-argument t))) - (print-a-or-s (if (and colonp (null arg)) - "()" - (with-output-to-string (stream) - (incless:write-object client arg stream))) - at-signp mincol colinc minpad padchar))) + (apply #'print-a-or-s + (if (and (colon-p directive) (null arg)) + "()" + (with-output-to-string (stream) + (incless:write-object client arg stream))) + (at-sign-p directive) parameters))) -(define-format-directive-compiler a-directive +(defmethod compile-item (client (directive a-directive) &optional parameters) `((let* ((*print-escape* nil) (*print-readably* nil) - (arg (consume-next-argument t)) - (raw-output - ,(if colonp + (parameters (list ,@parameters)) + (arg (consume-next-argument t))) + (apply #'print-a-or-s + ,(if (colon-p directive) `(if (null arg) "()" (with-output-to-string (stream) (incless:write-object ,(incless:client-form client) arg stream))) `(with-output-to-string (stream) - (incless:write-object ,(incless:client-form client) arg stream)))) - (pad-length (max minpad (* colinc (ceiling (- mincol (length raw-output)) colinc))))) - ,@(if at-signp - `((loop repeat pad-length - do (write-char padchar *destination*)) - (write-string raw-output *destination*)) - `((write-string raw-output *destination*) - (loop repeat pad-length - do (write-char padchar *destination*))))))) + (incless:write-object ,(incless:client-form client) arg stream))) + ,(at-sign-p directive) parameters)))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.4.2 ~s Standard. -(define-directive #\s s-directive nil (named-parameters-directive) - ((mincol :type integer :default-value 0) - (colinc :type (integer 0) :default-value 1) - (minpad :type integer :default-value 0) - (padchar :type character :default-value #\Space))) +(defclass s-directive (directive) nil) + +(defmethod specialize-directive + ((client t) (char (eql #\S)) directive (end-directive t)) + (change-class directive 's-directive)) -(define-format-directive-interpreter s-directive +(defmethod parameter-specifications ((client t) (directive s-directive)) + '((:type integer :default 0) + (:type (integer 0) :default 1) + (:type integer :default 0) + (:type character :default #\Space))) + +(defmethod interpret-item (client (directive s-directive) &optional parameters) (let ((*print-escape* t) (arg (consume-next-argument t))) - (print-a-or-s (if (and colonp (null arg)) - "()" - (with-output-to-string (stream) - (incless:write-object client arg stream))) - at-signp mincol colinc minpad padchar))) + (apply #'print-a-or-s + (if (and (colon-p directive) (null arg)) + "()" + (with-output-to-string (stream) + (incless:write-object client arg stream))) + (at-sign-p directive) parameters))) -(define-format-directive-compiler s-directive +(defmethod compile-item (client (directive s-directive) &optional parameters) `((let* ((*print-escape* t) - (arg (consume-next-argument t)) - (raw-output - ,(if colonp + (parameters (list ,@parameters)) + (arg (consume-next-argument t))) + (apply #'print-a-or-s + ,(if (colon-p directive) `(if (null arg) "()" (with-output-to-string (stream) (incless:write-object ,(incless:client-form client) arg stream))) `(with-output-to-string (stream) - (incless:write-object ,(incless:client-form client) arg stream)))) - (pad-length (max minpad (* colinc (ceiling (- mincol (length raw-output)) colinc))))) - ,@(if at-signp - `((loop repeat pad-length - do (write-char padchar *destination*)) - (write-string raw-output *destination*)) - `((write-string raw-output *destination*) - (loop repeat pad-length - do (write-char padchar *destination*))))))) + (incless:write-object ,(incless:client-form client) arg stream))) + ,(at-sign-p directive) parameters)))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.4.3 ~w Write. -(define-directive #\w w-directive nil (named-parameters-directive) ()) +(defclass w-directive (directive) nil) + +(defmethod specialize-directive + ((client t) (char (eql #\W)) directive (end-directive t)) + (change-class directive 'w-directive)) (defmethod layout-requirements ((item w-directive)) (list :logical-block)) -(define-format-directive-interpreter w-directive - (cond ((and colonp at-signp) - (let ((*print-pretty* t) - (*print-level* nil) - (*print-length* nil)) - (incless:write-object client (consume-next-argument t) *destination*))) - (colonp - (let ((*print-pretty* t)) - (incless:write-object client (consume-next-argument t) *destination*))) - (at-signp - (let ((*print-level* nil) - (*print-length* nil)) - (incless:write-object client (consume-next-argument t) *destination*))) - (t - (incless:write-object client (consume-next-argument t) *destination*)))) - -(define-format-directive-compiler w-directive - (cond ((and colonp at-signp ) - `((let ((*print-pretty* t) +(defmethod interpret-item (client (directive w-directive) &optional parameters) + (declare (ignore parameters)) + (let ((arg (consume-next-argument t)) + (colon-p (colon-p directive)) + (at-sign-p (at-sign-p directive))) + (cond ((and colon-p at-sign-p) + (let ((*print-pretty* t) (*print-level* nil) (*print-length* nil)) - (incless:write-object ,(incless:client-form client) (consume-next-argument t) *destination*)))) - (colonp - `((let ((*print-pretty* t)) - (incless:write-object ,(incless:client-form client) (consume-next-argument t) *destination*)))) - (at-signp - `((let ((*print-level* nil) + (incless:write-object client arg *destination*))) + (colon-p + (let ((*print-pretty* t)) + (incless:write-object client arg *destination*))) + (at-sign-p + (let ((*print-level* nil) (*print-length* nil)) - (incless:write-object ,(incless:client-form client) (consume-next-argument t) *destination*)))) - (t - `((incless:write-object ,(incless:client-form client) (consume-next-argument t) *destination*))))) + (incless:write-object client arg *destination*))) + (t + (incless:write-object client arg *destination*))))) + +(defmethod compile-item (client (directive w-directive) &optional parameters) + (declare (ignore parameters)) + (let ((colon-p (colon-p directive)) + (at-sign-p (at-sign-p directive))) + (cond ((and colon-p at-sign-p ) + `((let ((*print-pretty* t) + (*print-level* nil) + (*print-length* nil)) + (incless:write-object ,(incless:client-form client) (consume-next-argument t) *destination*)))) + (colon-p + `((let ((*print-pretty* t)) + (incless:write-object ,(incless:client-form client) (consume-next-argument t) *destination*)))) + (at-sign-p + `((let ((*print-level* nil) + (*print-length* nil)) + (incless:write-object ,(incless:client-form client) (consume-next-argument t) *destination*)))) + (t + `((incless:write-object ,(incless:client-form client) (consume-next-argument t) *destination*)))))) diff --git a/code/radix-control.lisp b/code/radix-control.lisp index 498e361..08cbb6e 100644 --- a/code/radix-control.lisp +++ b/code/radix-control.lisp @@ -4,7 +4,17 @@ ;;; ;;; 22.3.2 Radix control -(defun print-radix-arg (client radix colonp at-signp mincol padchar commachar comma-interval) +(defclass base-radix-directive (directive) + ()) + +(defmethod parameter-specifications (client (directive base-radix-directive)) + (declare (ignore client)) + '((:type integer :default 0) + (:type character :default #\Space) + (:type character :default #\,) + (:type (integer 1) :default 3))) + +(defun print-radix-arg (client colon-p at-sign-p radix mincol padchar commachar comma-interval) (let ((argument (consume-next-argument t))) (if (not (integerp argument)) (let ((*print-base* radix) @@ -17,10 +27,10 @@ (*print-readably* nil)) (with-output-to-string (stream) (incless:write-object client (abs argument) stream)))) - (comma-length (if colonp + (comma-length (if colon-p (max 0 (floor (1- (length string)) comma-interval)) 0)) - (sign-length (if (or at-signp (minusp argument)) 1 0)) + (sign-length (if (or at-sign-p (minusp argument)) 1 0)) (total-length (+ (length string) comma-length sign-length)) (pad-length (max 0 (- mincol total-length)))) ;; Print the padding. @@ -29,14 +39,14 @@ ;; Possibliy print a sign. (cond ((minusp argument) (write-char #\- *destination*)) - (at-signp + (at-sign-p (write-char #\+ *destination*)) (t nil)) ;; Print the string in reverse order (loop for index downfrom (1- (length string)) to 0 for c across string do (write-char c *destination*) - do (when (and colonp + do (when (and colon-p (plusp index) (zerop (mod index comma-interval))) (write-char commachar *destination*))))))) @@ -45,58 +55,109 @@ ;;; ;;; 22.3.2.1 ~r Radix. -(define-directive #\r r-directive nil (named-parameters-directive) - ((radix :type (integer 2 36) :default-value nil) - (mincol :type integer :default-value 0) - (padchar :type character :default-value #\Space) - (commachar :type character :default-value #\,) - (comma-interval :type (integer 1) :default-value 3))) - -;;; Print an integer as roman numerals to the stream. -;;; The integer must be strictly greater than zero, -;;; and strictly less than 4000. -(defun print-as-roman (integer stream) - (declare (type (integer 1) integer)) - (multiple-value-bind (thousands rest) (floor integer 1000) - (loop repeat thousands - do (write-char #\M stream)) - (multiple-value-bind (hundreds rest) (floor rest 100) - (write-string (case hundreds - (0 "") (1 "C") (2 "CC") (3 "CCC") (4 "CD") - (5 "D" ) (6 "DC") (7 "DCC") (8 "DCCC") (9 "CM")) - stream) - (multiple-value-bind (tenths rest) (floor rest 10) - (write-string (case tenths - (0 "") (1 "X") (2 "XX") (3 "XXX") (4 "XL") - (5 "L" ) (6 "LX") (7 "LXX") (8 "LXXX") (9 "XC")) - stream) - (write-string (case rest - (0 "") (1 "I") (2 "II") (3 "III") (4 "IV") - (5 "V" ) (6 "VI") (7 "VII") (8 "VIII") (9 "IX")) - stream))))) - -;;; Print an integer as old roman numerals to the stream. -;;; The integer must be strictly greater than zero, -;;; and strictly less than 4000. -(defun print-as-old-roman (integer stream) - (declare (type (integer 1) integer)) - (multiple-value-bind (thousands rest) (floor integer 1000) - (loop repeat thousands - do (write-char #\M stream)) - (multiple-value-bind (hundreds rest) (floor rest 100) - (write-string (case hundreds - (0 "") (1 "C") (2 "CC") (3 "CCC") (4 "CCCC") - (5 "D" ) (6 "DC") (7 "DCC") (8 "DCCC") (9 "DCCCC")) - stream) - (multiple-value-bind (tenths rest) (floor rest 10) - (write-string (case tenths - (0 "") (1 "X") (2 "XX") (3 "XXX") (4 "XXXX") - (5 "L" ) (6 "LX") (7 "LXX") (8 "LXXX") (9 "LXXXX")) - stream) - (write-string (case rest - (0 "") (1 "I") (2 "II") (3 "III") (4 "IIII") - (5 "V" ) (6 "VI") (7 "VII") (8 "VIII") (9 "VIIII")) - stream))))) +(defclass radix-directive (base-radix-directive) nil) + +(defmethod specialize-directive + ((client t) (char (eql #\R)) directive (end-directive t)) + (change-class directive 'radix-directive)) + +(defmethod parameter-specifications ((client t) (directive radix-directive)) + (list* '(:type (or null (integer 2 36)) :default nil) + (call-next-method))) + +(defparameter *roman-digits* + '("I" "V" "X" "L" "C" "D" "M")) + +(defun print-roman-arg () + (labels ((write-digit (value digits) + (multiple-value-bind (q r) + (floor value 10) + (unless (zerop q) + (write-digit q (cddr digits))) + (case r + (9 + (write-string (car digits) *destination*) + (write-string (caddr digits) *destination*)) + (4 + (write-string (car digits) *destination*) + (write-string (cadr digits) *destination*)) + (otherwise + (multiple-value-bind (q1 r1) + (floor r 5) + (unless (zerop q1) + (write-string (cadr digits) *destination*)) + (loop repeat r1 + do (write-string (car digits) *destination*)))))))) + (let ((digit-count (list-length *roman-digits*))) + (write-digit (consume-next-argument + (if digit-count + (multiple-value-bind (q r) + (floor digit-count 2) + `(integer 1 + ,(1- (if (zerop r) + (* (expt 10 (1- q)) 9) + (* (expt 10 q) 4))))) + '(integer 1))) + *roman-digits*)))) + +(defun print-old-roman-arg () + (labels ((write-digit (value digits) + (multiple-value-bind (q r) + (floor value 10) + (unless (zerop q) + (write-digit q (cddr digits))) + (multiple-value-bind (q1 r1) + (floor r 5) + (unless (zerop q1) + (write-string (cadr digits) *destination*)) + (loop repeat r1 + do (write-string (car digits) *destination*)))))) + (let ((digit-count (list-length *roman-digits*))) + (write-digit (consume-next-argument + (if digit-count + (multiple-value-bind (q r) + (floor digit-count 2) + `(integer 1 + ,(1- (* (expt 10 q) + (if (zerop r) 1 5))))) + '(integer 1))) + *roman-digits*)))) + +#+(or)(defun print-roman-arg (old-roman-p) + (labels ((write-digit (value digits) + (multiple-value-bind (q r) + (floor value 10) + (unless (zerop q) + (write-digit q (cddr digits))) + (cond ((and (not old-roman-p) (= r 9)) + (write-string (car digits) *destination*) + (write-string (caddr digits) *destination*)) + ((and (not old-roman-p) (= r 4)) + (write-string (car digits) *destination*) + (write-string (cadr digits) *destination*)) + (t + (multiple-value-bind (q1 r1) + (floor r 5) + (unless (zerop q1) + (write-string (cadr digits) *destination*)) + (loop repeat r1 + do (write-string (car digits) *destination*)))))))) + (let ((digit-count (list-length *roman-digits*))) + (write-digit (consume-next-argument + (if digit-count + (multiple-value-bind (q r) + (floor digit-count 2) + `(integer 1 + ,(1- (cond ((and (zerop r) old-roman-p) + (expt 10 q)) + ((zerop r) + (* (expt 10 (1- q)) 9)) + (old-roman-p + (* (expt 10 q) 5)) + (t + (* (expt 10 q) 4)))))) + '(integer 1))) + *roman-digits*)))) (defparameter *cardinal-ones* #(nil "one" "two" "three" "four" "five" "six" "seven" "eight" "nine")) @@ -117,52 +178,53 @@ "septendecillion" "octodecillion" "novemdecillion" "vigintillion")) ;;; Print a cardinal number between 1 and 99. -(defun print-cardinal-tenths (n stream) +(defun print-cardinal-tenths (n) (cond ((< n 10) - (write-string (aref *cardinal-ones* n) stream)) + (write-string (aref *cardinal-ones* n) *destination*)) ((< n 20) - (write-string (aref *cardinal-teens* (- n 10)) stream)) + (write-string (aref *cardinal-teens* (- n 10)) *destination*)) (t (multiple-value-bind (tens ones) (floor n 10) - (write-string (aref *cardinal-tens* tens) stream) + (write-string (aref *cardinal-tens* tens) *destination*) (unless (zerop ones) - (write-char #\- stream) - (write-string (aref *cardinal-ones* ones) stream)))))) + (write-char #\- *destination*) + (write-string (aref *cardinal-ones* ones) *destination*)))))) ;;; Print a cardinal number between 1 and 999. -(defun print-cardinal-hundreds (n stream) +(defun print-cardinal-hundreds (n) (cond ((< n 100) - (print-cardinal-tenths n stream)) + (print-cardinal-tenths n)) (t (multiple-value-bind (hundreds rest) (floor n 100) - (write-string (aref *cardinal-ones* hundreds) stream) - (write-string " hundred" stream) + (write-string (aref *cardinal-ones* hundreds) *destination*) + (write-string " hundred" *destination*) (unless (zerop rest) - (write-char #\Space stream) - (print-cardinal-tenths rest stream)))))) + (write-char #\Space *destination*) + (print-cardinal-tenths rest)))))) ;;; Print a cardinal number n such that 0 < n < 10^65. -(defun print-cardinal-non-zero (n stream magnitude) +(defun print-cardinal-non-zero (n magnitude) (multiple-value-bind (thousands rest) (floor n 1000) (unless (zerop thousands) - (print-cardinal-non-zero thousands stream (1+ magnitude))) + (print-cardinal-non-zero thousands (1+ magnitude))) (unless (or (zerop thousands) (zerop rest)) - (write-char #\Space stream)) + (write-char #\Space *destination*)) (unless (zerop rest) - (print-cardinal-hundreds rest stream) + (print-cardinal-hundreds rest) (unless (zerop magnitude) - (write-char #\Space stream) - (write-string (aref *groups-of-three* magnitude) stream))))) + (write-char #\Space *destination*) + (write-string (aref *groups-of-three* magnitude) *destination*))))) ;;; Print a cardinal number n such that - 10^65 < n < 10^65. -(defun print-cardinal-number (n stream) - (cond ((minusp n) - (write-string "negative " stream) - (print-cardinal-non-zero (- n) stream 0)) - ((zerop n) - (write-string "zero" stream)) - (t - (print-cardinal-non-zero n stream 0)))) +(defun print-cardinal-arg () + (let ((n (consume-next-argument `(integer ,(1+ (- (expt 10 65))) ,(1- (expt 10 65)))))) + (cond ((minusp n) + (write-string "negative " *destination*) + (print-cardinal-non-zero (- n) 0)) + ((zerop n) + (write-string "zero" *destination*)) + (t + (print-cardinal-non-zero n 0))))) (defparameter *ordinal-ones* #(nil "first" "second" "third" "fourth" "fifth" "sixth" "seventh" "eighth" "ninth")) @@ -176,166 +238,170 @@ "fiftieth" "sixtieth" "seventieth" "eightieth" "ninetieth")) ;;; Print an ordinal number between 1 and 99. -(defun print-ordinal-tenths (n stream) +(defun print-ordinal-tenths (n) (cond ((< n 10) - (write-string (aref *ordinal-ones* n) stream)) + (write-string (aref *ordinal-ones* n) *destination*)) ((< n 20) - (write-string (aref *ordinal-teens* (- n 10)) stream)) + (write-string (aref *ordinal-teens* (- n 10)) *destination*)) (t (multiple-value-bind (tens ones) (floor n 10) (cond ((zerop ones) - (write-string (aref *ordinal-tens* tens) stream)) + (write-string (aref *ordinal-tens* tens) *destination*)) (t - (write-string (aref *cardinal-tens* tens) stream) - (write-char #\- stream) - (write-string (aref *ordinal-ones* ones) stream))))))) + (write-string (aref *cardinal-tens* tens) *destination*) + (write-char #\- *destination*) + (write-string (aref *ordinal-ones* ones) *destination*))))))) ;;; Print an ordinal number n such that 0 < n < 1000. -(defun print-ordinal-hundreds (n stream) +(defun print-ordinal-hundreds (n) (cond ((< n 100) - (print-ordinal-tenths n stream)) + (print-ordinal-tenths n)) (t (multiple-value-bind (hundreds rest) (floor n 100) - (write-string (aref *cardinal-ones* hundreds) stream) - (write-string " hundred" stream) + (write-string (aref *cardinal-ones* hundreds) *destination*) + (write-string " hundred" *destination*) (cond ((zerop rest) - (write-string "th" stream)) + (write-string "th" *destination*)) (t - (write-char #\Space stream) - (print-ordinal-tenths rest stream))))))) + (write-char #\Space *destination*) + (print-ordinal-tenths rest))))))) ;;; Print an ordinal number n such that 0 < n < 10^65. -(defun print-ordinal-non-zero (n stream) +(defun print-ordinal-non-zero (n) (multiple-value-bind (hundreds rest) (floor n 100) (cond ((zerop rest) ;; Hudreds is nonzero. - (print-cardinal-non-zero n stream 0) - (write-string "th" stream)) + (print-cardinal-non-zero n 0) + (write-string "th" *destination*)) ((zerop hundreds) - (print-ordinal-hundreds rest stream)) + (print-ordinal-hundreds rest)) (t ;; They are both nonzero. - (print-cardinal-non-zero (* 100 hundreds) stream 0) - (write-char #\Space stream) - (print-ordinal-tenths rest stream))))) + (print-cardinal-non-zero (* 100 hundreds) 0) + (write-char #\Space *destination*) + (print-ordinal-tenths rest))))) ;;; Print an ordinal number n such that - 10^65 < n < 10^65. -(defun print-ordinal-number (n stream) - (cond ((minusp n) - (write-string "negative " stream) - (print-ordinal-non-zero (- n) stream)) - ((zerop n) - (write-string "zeroth" stream)) - (t - (print-ordinal-non-zero n stream)))) - -(define-format-directive-interpreter r-directive - (cond ((not (null radix)) - (print-radix-arg client radix colonp at-signp mincol padchar commachar comma-interval)) - ((and colonp at-signp) - (print-as-old-roman (consume-next-argument '(integer 1)) - *destination*)) - (at-signp - (print-as-roman (consume-next-argument '(integer 1)) - *destination*)) - (colonp - (print-ordinal-number (consume-next-argument - `(integer ,(1+ (- (expt 10 65))) ,(1- (expt 10 65)))) - *destination*)) - (t - (print-cardinal-number (consume-next-argument - `(integer ,(1+ (- (expt 10 65))) ,(1- (expt 10 65)))) - *destination*)))) - -(define-format-directive-compiler r-directive - (let ((print-number-radix `(print-radix-arg ,(incless:client-form client) - radix ,colonp ,at-signp mincol - padchar commachar comma-interval)) - (print-null-radix (cond ((and colonp at-signp) - `(print-as-old-roman (consume-next-argument '(integer 1)) - *destination*)) - (at-signp - `(print-as-roman (consume-next-argument '(integer 1)) - *destination*)) - (colonp - `(print-ordinal-number (consume-next-argument - `(integer ,(1+ (- (expt 10 65))) - ,(1- (expt 10 65)))) - *destination*)) - (t - `(print-cardinal-number (consume-next-argument - `(integer ,(1+ (- (expt 10 65))) - ,(1- (expt 10 65)))) - *destination*))))) - (cond ((numberp radix) - (list print-number-radix)) - ((null radix) - (list print-null-radix)) - (t - `((if (null radix) - ,print-null-radix - ,print-number-radix)))))) +(defun print-ordinal-arg () + (let ((n (consume-next-argument `(integer ,(1+ (- (expt 10 65))) ,(1- (expt 10 65)))))) + (cond ((minusp n) + (write-string "negative " *destination*) + (print-ordinal-non-zero (- n))) + ((zerop n) + (write-string "zeroth" *destination*)) + (t + (print-ordinal-non-zero n))))) + +(defmethod interpret-item (client (directive radix-directive) &optional parameters) + (let ((radix (car parameters)) + (colon-p (colon-p directive)) + (at-sign-p (at-sign-p directive))) + (cond (radix + (apply #'print-radix-arg client colon-p at-sign-p parameters)) + ((and at-sign-p colon-p) + (print-old-roman-arg)) + (at-sign-p + (print-roman-arg)) + (colon-p + (print-ordinal-arg)) + (t + (print-cardinal-arg))))) + +(defmethod compile-item (client (directive radix-directive) &optional parameters) + (let ((colon-p (colon-p directive)) + (at-sign-p (at-sign-p directive))) + (cond ((numberp (car parameters)) + `((print-radix-arg ,(incless:client-form client) + ,colon-p ,at-sign-p ,@parameters))) + ((null (car parameters)) + (cond ((and at-sign-p colon-p) + `((print-old-roman-arg))) + (at-sign-p + `((print-roman-arg))) + (colon-p + `((print-ordinal-arg))) + (t + `((print-cardinal-arg))))) + (t + `((let ((parameters (list ,@parameters))) + (if (car parameters) + (apply #'print-radix-arg ,(incless:client-form client) + ,colon-p ,at-sign-p parameters) + ,(cond ((and at-sign-p colon-p) + `(print-old-roman-arg)) + (at-sign-p + `(print-roman-arg)) + (colon-p + `(print-ordinal-arg)) + (t + `(print-cardinal-arg)))))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.2.2 ~d Decimal. -(define-directive #\d d-directive nil (named-parameters-directive) - ((mincol :type integer :default-value 0) - (padchar :type character :default-value #\Space) - (commachar :type character :default-value #\,) - (comma-interval :type (integer 1) :default-value 3))) +(defclass decimal-radix-directive (base-radix-directive) + ()) + +(defmethod specialize-directive + ((client t) (char (eql #\D)) directive (end-directive t)) + (change-class directive 'decimal-radix-directive)) -(define-format-directive-interpreter d-directive - (print-radix-arg client 10 colonp at-signp mincol padchar commachar comma-interval)) +(defmethod interpret-item (client (directive decimal-radix-directive) &optional parameters) + (apply #'print-radix-arg client (colon-p directive) (at-sign-p directive) 10 parameters)) -(define-format-directive-compiler d-directive - `((print-radix-arg ,(incless:client-form client) 10 ,colonp ,at-signp mincol padchar commachar comma-interval))) +(defmethod compile-item (client (directive decimal-radix-directive) &optional parameters) + `((print-radix-arg ,(incless:client-form client) ,(colon-p directive) ,(at-sign-p directive) 10 ,@parameters))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.2.3 ~b Binary. -(define-directive #\b b-directive nil (named-parameters-directive) - ((mincol :type integer :default-value 0) - (padchar :type character :default-value #\Space) - (commachar :type character :default-value #\,) - (comma-interval :type (integer 1) :default-value 3))) +(defclass binary-radix-directive (base-radix-directive) + ()) -(define-format-directive-interpreter b-directive - (print-radix-arg client 2 colonp at-signp mincol padchar commachar comma-interval)) +(defmethod specialize-directive + ((client t) (char (eql #\B)) directive (end-directive t)) + (change-class directive 'binary-radix-directive)) -(define-format-directive-compiler b-directive - `((print-radix-arg ,(incless:client-form client) 2 ,colonp ,at-signp mincol padchar commachar comma-interval))) +(defmethod interpret-item (client (directive binary-radix-directive) &optional parameters) + (apply #'print-radix-arg client (colon-p directive) (at-sign-p directive) 2 parameters)) + +(defmethod compile-item (client (directive binary-radix-directive) &optional parameters) + `((print-radix-arg ,(incless:client-form client) ,(colon-p directive) ,(at-sign-p directive) 2 ,@parameters))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.2.4 ~o Octal. -(define-directive #\o o-directive nil (named-parameters-directive) - ((mincol :type integer :default-value 0) - (padchar :type character :default-value #\Space) - (commachar :type character :default-value #\,) - (comma-interval :type (integer 1) :default-value 3))) +(defclass octal-radix-directive (base-radix-directive) + ()) + +(defmethod specialize-directive + ((client t) (char (eql #\O)) directive (end-directive t)) + (change-class directive 'octal-radix-directive)) -(define-format-directive-interpreter o-directive - (print-radix-arg client 8 colonp at-signp mincol padchar commachar comma-interval)) +(defmethod interpret-item (client (directive octal-radix-directive) &optional parameters) + (apply #'print-radix-arg client (colon-p directive) (at-sign-p directive) 8 parameters)) -(define-format-directive-compiler o-directive - `((print-radix-arg ,(incless:client-form client) 8 ,colonp ,at-signp mincol padchar commachar comma-interval))) +(defmethod compile-item (client (directive octal-radix-directive) &optional parameters) + `((print-radix-arg ,(incless:client-form client) ,(colon-p directive) ,(at-sign-p directive) 8 ,@parameters))) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; 22.3.2.5 ~x Hexadecimal. -(define-directive #\x x-directive nil (named-parameters-directive) - ((mincol :type integer :default-value 0) - (padchar :type character :default-value #\Space) - (commachar :type character :default-value #\,) - (comma-interval :type (integer 1) :default-value 3))) +(defclass hexadecimal-radix-directive (base-radix-directive) + ()) + +(defmethod specialize-directive + ((client t) (char (eql #\X)) directive (end-directive t)) + (change-class directive 'hexadecimal-radix-directive)) -(define-format-directive-interpreter x-directive - (print-radix-arg client 16 colonp at-signp mincol padchar commachar comma-interval)) +(defmethod interpret-item + (client (directive hexadecimal-radix-directive) &optional parameters) + (apply #'print-radix-arg client (colon-p directive) (at-sign-p directive) 16 parameters)) -(define-format-directive-compiler x-directive - `((print-radix-arg ,(incless:client-form client) 16 ,colonp ,at-signp mincol padchar commachar comma-interval))) +(defmethod compile-item + (client (directive hexadecimal-radix-directive) &optional parameters) + `((print-radix-arg ,(incless:client-form client) ,(colon-p directive) ,(at-sign-p directive) 16 ,@parameters))) diff --git a/code/shadow-export.lisp b/code/shadow-export.lisp deleted file mode 100644 index eb462db..0000000 --- a/code/shadow-export.lisp +++ /dev/null @@ -1,10 +0,0 @@ -(cl:in-package #:invistra) - -(defparameter *symbols* '(#:format)) - -(cl:loop - with package = (find-package '#:invistra) - for symbol in *symbols* - do (shadow (symbol-name symbol)) - (export (find-symbol (symbol-name symbol) package))) - diff --git a/code/split-control-string.lisp b/code/split-control-string.lisp index 5c00817..1f9e3c7 100644 --- a/code/split-control-string.lisp +++ b/code/split-control-string.lisp @@ -27,8 +27,8 @@ ;; a directive. (multiple-value-bind (directive-character parameters - colonp - at-signp + colon-p + at-sign-p suffix-start end-of-directive-position) (parse-format-directive control-string tilde-position) @@ -38,7 +38,7 @@ :suffix-start suffix-start :end end-of-directive-position :directive-character (char-upcase directive-character) - :given-parameters parameters - :colonp colonp - :at-signp at-signp) + :parameters parameters + :colon-p colon-p + :at-sign-p at-sign-p) (setf start end-of-directive-position)))))))) diff --git a/code/status.text b/code/status.text deleted file mode 100644 index 8db6241..0000000 --- a/code/status.text +++ /dev/null @@ -1,33 +0,0 @@ -What is done: - - We have a near-complete implementation of the format function. What - is missing is the floating-point printers. It compiles the string - and generates primitive output operations, as simple calls to other - Common Lisp functions if they are available. - - We have an implementation of the Burger-Dybvig algorithm that we - plan to use for the floating-point printer operations. We have - tested it against a slower but simpler implementation for all - single-floats, and the two versions give the same result. - - We also have an implementation of a very fast algorithm that - generates floating-point digits using floating-point arithmetic. - If a check is added to that algorithm to verify that the result - would read back in accurately, then this algorithm can be used - to suggest a fast and accurate way of printing floats. They - won't be the shortest possible as is always the case with - Burger & Dybvig, but some people might not care about that. - -How to test it: - -What remains to do: - - Add more tests. There is a subdirectory Format-tests - the contents of which should probably me moved here, the files - renamed (.lsp -> .lisp), and they should be adapted to the - lisp-unit testing framework. The system definition should - include these new files. - - We need to extend the Burger & Dybvig algorith to print - fixed-field floats, and we need to integrate the result into - the implementation of format. diff --git a/code/structure-items.lisp b/code/structure-items.lisp index 448ae28..78d3425 100644 --- a/code/structure-items.lisp +++ b/code/structure-items.lisp @@ -4,7 +4,7 @@ end (clauses (list nil))) -(defun structure-items (items) +(defun structure-items (client items) (loop with result = (list (make-group)) for item in (reverse items) finally (reduce (lambda (req it) @@ -15,7 +15,8 @@ :initial-value nil) (return (coerce (car (group-clauses (car result))) 'vector)) unless (stringp item) - do (specialize-directive item (group-end (car result))) + do (specialize-directive client (directive-character item) + item (group-end (car result))) (cond ((structured-start-p item) (setf (clauses item) (map 'vector (lambda (items) @@ -26,5 +27,5 @@ (push (make-group :end item) result)) ((structured-separator-p item) (push nil (group-clauses (car result))))) - (check-directive-syntax item) + (check-directive-syntax client item) do (push item (car (group-clauses (car result)))))) diff --git a/code/utilities.lisp b/code/utilities.lisp index 87d79ab..ca741a5 100644 --- a/code/utilities.lisp +++ b/code/utilities.lisp @@ -97,5 +97,3 @@ (write-char (char-downcase char) (target stream))) (t (write-char char (target stream))))))) - -(defparameter *digits* "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") diff --git a/invistra-extrinsic.asd b/invistra-extrinsic.asd index 3c517dd..49d705b 100644 --- a/invistra-extrinsic.asd +++ b/invistra-extrinsic.asd @@ -3,8 +3,9 @@ (defsystem "invistra-extrinsic" :description "System for loading Invistra extrinsically into an implementation." :license "BSD" - :author "Robert Strandh" - :maintainer "Robert Strandh" + :author ("Robert Strandh" + "Tarn W. Burton") + :maintainer "Tarn W. Burton" :version (:read-file-form "version.sexp") :homepage "https://github.com/s-expressionists/Invistra" :bug-tracker "https://github.com/s-expressionists/Invistra/issues" diff --git a/invistra-intrinsic.asd b/invistra-intrinsic.asd index b1c1046..082ee45 100644 --- a/invistra-intrinsic.asd +++ b/invistra-intrinsic.asd @@ -3,8 +3,9 @@ (defsystem :invistra-intrinsic :description "System for loading Invistra intrinsically into an implementation." :license "BSD" - :author "Robert Strandh" - :maintainer "Robert Strandh" + :author ("Robert Strandh" + "Tarn W. Burton") + :maintainer "Tarn W. Burton" :version (:read-file-form "version.sexp") :homepage "https://github.com/s-expressionists/Invistra" :bug-tracker "https://github.com/s-expressionists/Invistra/issues" diff --git a/invistra-shim.asd b/invistra-shim.asd index 154ae6f..ab215a4 100644 --- a/invistra-shim.asd +++ b/invistra-shim.asd @@ -3,8 +3,9 @@ (defsystem "invistra-shim" :description "System for loading Invistra as a shim into an implementation." :license "BSD" - :author "Robert Strandh" - :maintainer "Robert Strandh" + :author ("Robert Strandh" + "Tarn W. Burton") + :maintainer "Tarn W. Burton" :version (:read-file-form "version.sexp") :homepage "https://github.com/s-expressionists/Invistra" :bug-tracker "https://github.com/s-expressionists/Invistra/issues" @@ -22,7 +23,8 @@ (defsystem "invistra-shim/test" :description "ANSI Test system for Invistra" :license "MIT" - :author "Tarn W. Burton" + :author ("Robert Strandh" + "Tarn W. Burton") :maintainer "Tarn W. Burton" :depends-on ("alexandria" "invistra-shim") :perform (test-op (op c) diff --git a/invistra.asd b/invistra.asd index caa4df2..89bce43 100644 --- a/invistra.asd +++ b/invistra.asd @@ -3,8 +3,9 @@ (defsystem :invistra :description "A portable and extensible Common Lisp FORMAT implementation" :license "BSD" - :author "Robert Strandh" - :maintainer "Robert Strandh" + :author ("Robert Strandh" + "Tarn W. Burton") + :maintainer "Tarn W. Burton" :version (:read-file-form "version.sexp") :homepage "https://github.com/s-expressionists/Invistra" :bug-tracker "https://github.com/s-expressionists/Invistra/issues"