From: Henrik Motakef
Subject: FORMAT with named arguments?
Date: 
Message-ID: <87k7jcb8nl.fsf@pokey.henrik-motakef.de>
Hi,

I wonder if there is something like FORMAT (or some format directive
I've overlooked) that allows to process the data arguments in another
order than how they are passed, or even better, by looking them up in
a hash-table or alist.

The background is that I would like to add some kind of configurable
logging to a (toy) app I'm writing. It would be nice if one could
change what gets written to the log stream by a format string, for
example like this:

  (defvar *stuff* '((appname . "Foo") 
                    (message . "Something went wrong")
                    (level . :debug) 
                    (time . (get-universal-time))))
  
  (format* nil "~A:time [~A:level] ~A:appname - ~A:message" *stuff*)
  => "3246529490 [debug] Foo - Something went wrong"
  
  (format* nil "~A:message (~A:appname)" *stuff*)
  => "Something went wrong (Foo)"

Is there something like this, either in the standard or in some
library? Is it a stupid idea? Should I start reinventing FORMAT?

tia
Henrik

From: Tim Daly, Jr.
Subject: Re: FORMAT with named arguments?
Date: 
Message-ID: <wkel9kyuy5.fsf@tenkan.org>
Henrik Motakef <··············@web.de> writes:

> Hi,
> 
> I wonder if there is something like FORMAT (or some format directive
> I've overlooked) that allows to process the data arguments in another
> order than how they are passed, or even better, by looking them up in
> a hash-table or alist.
...
>   (defvar *stuff* '((appname . "Foo") 
>                     (message . "Something went wrong")
>                     (level . :debug) 
>                     (time . (get-universal-time))))
>   
>   (format* nil "~A:time [~A:level] ~A:appname - ~A:message" *stuff*)
>   => "3246529490 [debug] Foo - Something went wrong"

Well, in clgtk I have a little macro I use that lets me do something
along those lines, although probably not exactly what you want.  So,
for example:


        (deftemplate stuff (appname message level time)
          "$TIME$ [$LEVEL$] $APPNAME$ - $MESSAGE$")

expands into:

        (DEFUN STUFF (APPNAME MESSAGE LEVEL TIME)
          "$TIME$ [$LEVEL$] $APPNAME$ - $MESSAGE$"
          (FORMAT NIL "~A [~A] ~A - ~A" TIME LEVEL APPNAME MESSAGE))

so you can:

        * (stuff "foo" "bar" "baz" "bof")

        "bof [baz] foo - bar"


Which is pretty pointless.  I did it so that I can make templates that
are more readable (the things to be replaced have names, instead of
always ~A), and so that I can repeat substitutions without repeating
arguments:

        (deftemplate foo (bar baz) 
          "$BAR$ is an extra large $BAR$. $BAZ$ is no $BAR$.")

        * (foo "splut" "cram")

        "splut is an extra large splut. cram is no splut."


and I built it atop FORMAT, because I didn't want to lose FORMAT's
hair:

        (deftemplate callfun (name args)
          "    $NAME$ (~{$ARGS$~#[~:;, ~]~});
        ")

        * (callfun "chew" '("food" "houses" "cats"))

        "    chew (food, houses, cats);
        "

I only developed it as far as was necessary for clgtk.  Maybe you can
modify it to do what you want.  Here it is:

(defmacro deftemplate (name vars template)
  (let (replacements)
    (dolist (el vars)
      (when (consp el)
        (push (cons el (apply #'symb el)) replacements)))
    (multiple-value-bind (format-template format-vars)
        (render-template template)
      (setf vars (sublis replacements vars))
      (setf format-vars (subsubseqlis replacements format-vars))
      `(defun ,name ,vars
         ,template
         (format nil ,format-template ,@format-vars)))))

(defun render-template (template &optional (state 'str) (vars nil) (acc ""))
  (if template
    (multiple-value-bind (token template)
        (split #\$ template)
      (case state
        (str (render-template template 'var vars
                              (concatenate 'string acc token)))
        (var (render-template template 'str
                              (cons (intern (string-upcase token)) vars)
                              (concatenate 'string acc "~A")))))
    (values acc (reverse vars))))

(defun split (split-on string)
  (let ((pos (position split-on string)))
    (if pos
        (values (subseq string 0 pos)
                (subseq string (1+ pos)))
      (values string nil))))


(defun subsubseqlis (alist in)
  (dolist (a alist)
    (setf in (subsubseq (car a) (cdr a) in)))
  in)

(defun subsubseq (from to in)
  "Replace all subsequences in IN that match FROM with TO."
  (when (consp in)
    (if (equal from (subseq in 0 (length from)))
        (cons to (subsubseq from to (subseq in (length from))))
      (cons (car in) (subsubseq from to (cdr in))))))

(defun mkstr (&rest args)
  (with-output-to-string (s)
    (dolist (a args) (princ a s))))

(defun symb (&rest args)
  (values (intern (apply #'mkstr args))))

        
I welcome any flames about the code!! 

Have fun,
Tim
From: Wade Humeniuk
Subject: Re: FORMAT with named arguments?
Date: 
Message-ID: <7NQB9.8968$bh1.887480@news1.telusplanet.net>
"Henrik Motakef" <··············@web.de> wrote in message
···················@pokey.henrik-motakef.de...
> Hi,
>
> I wonder if there is something like FORMAT (or some format directive
> I've overlooked) that allows to process the data arguments in another
> order than how they are passed, or even better, by looking them up in
> a hash-table or alist.
> Is there something like this, either in the standard or in some
> library? Is it a stupid idea? Should I start reinventing FORMAT?

Instead of rewriting format, how about something like I did with
SQL embedded into LW?  You can create a whole new language
which could be the format language.

#|

External SQL syntax, allows embedded SQL in Lisp and embedded Lisp
in SQL.  The SQL statements are presented as lists but are not
treated that way by the Lisp Reader.

Example Select

#!sql(select merchant_name,merchant_id
             from merchant
             where merchant_id = !~A/merchant-id)

translates to

(sql:query (format nil "select merchant_name,merchant_id from merchant where merchant_id = ~A"
                   (sql-obj merchant-id)))
|#

Create a #!format macro syntax.

The code is very simple.

Wade
From: Kalle Olavi Niemitalo
Subject: Re: FORMAT with named arguments?
Date: 
Message-ID: <87r8dk2mnz.fsf@Astalo.y2000.kon.iki.fi>
Henrik Motakef <··············@web.de> writes:

> I wonder if there is something like FORMAT (or some format directive
> I've overlooked) that allows to process the data arguments in another
> order than how they are passed, or even better, by looking them up in
> a hash-table or alist.

There is Tilde Asterisk.

  (format nil ···@*~S ··@*~S" 'foo 'bar)
  ;; => "BAR FOO"

If you want to access data by name, you could use Tilde Slash:

  (defun print-event-time (stream event &optional colon-p at-sign-p)
    (declare (ignore colon-p at-sign-p))
    (princ (cdr (assoc 'time event)) stream))

  (defun print-event-message (stream event &optional colon-p at-sign-p)
    (declare (ignore colon-p at-sign-p))
    (princ (cdr (assoc 'message event)) stream))

  (format nil "~/print-event-time/~:* ~/print-event-message/" *stuff*)
  ;; => "(GET-UNIVERSAL-TIME) Something went wrong"

If your functions aren't in the CL-USER package, you have to name
your package explicitly.  FORMAT is a general-purpose function;
it doesn't know which format strings are for logging, so you can't
abbreviate.

If you let people directly enter format strings like this,
I think you should also let them write new functions that
format the time more readably, for example.