split format string of (format t ...) - common-lisp

Sometimes I like to output some text with (format t ..).
To prevent long unreadable format-strings in source code, and get the output easily aligned, I use (format t (concatenate 'string ....).
Example:
(format t (concatenate 'string
"some output~%"
" error-msg: ~a~%"
" uiop-cwd: ~a~%"
" uiop-file-exists: ~a~%")
"error foo"
(uiop:getcwd)
(uiop:file-exists-p "hello_world.bmp"))
Is there a more idiomatic and at-compile-time way to do the same in Common Lisp?

Here is an equivalent format string that makes use of the Tilde newline format directive, which ignores the following newline and spaces (until the next visible character). In order to indent with spaces as you did, I wrote the forced newline ~% before the spaces:
(format t
"some output~
~% error-msg: ~a~
~% uiop-cwd: ~a~
~% uiop-file-exists: ~a~%"
"error foo"
(uiop:getcwd)
(uiop:file-exists-p "hello_world.bmp"))
(NB. This is a single string so there is no concatenation to be done at compile-time.)

You can do quite well with something like:
(defun fmt (to control/s &rest args-to-format)
(declare (dynamic-extent args-to-format)) ;?OK
(apply #'format to (if (listp control/s)
(apply #'concatenate 'string control/s)
control/s)
args-to-format))
(define-compiler-macro fmt (&whole form to control/s &rest args-to-format)
(cond
((stringp control/s)
`(format ,to ,control/s ,#args-to-format))
((and (listp control/s)
(eql (first control/s) 'quote))
;; literal
(destructuring-bind (_ thing) control/s
(declare (ignore _))
(print "here")
(if (and (listp thing) (every #'stringp thing))
`(format ,to ,(apply #'concatenate 'string thing) ,#args-to-format)
form)))
(t
form)))
The compiler macro should ensure that the common case of
(fmt t '("~&foo: ~S~%"
"bar~%") ...)
will have no run-time cost at all.

Related

In common lisp how can I format a floating point and specify grouping, group char and decimal separator char

Let's say I have the floating point number 1234.9
I want to format it as 1.234,90
Is there a format directive combination for that? ~D ,which can handle the grouping and the group char, handles only integers. ~F doesn't handle grouping at all. And none as far as I know can change the decimal point from . to ,
The only solution I see is to use ~D for the integer part digit grouping and concatenate it with , and the decimal part. Any better ideas?
You can define a function to be called with tilde-slash, which most of the other answers have already done, but in order to get output similar to ~F, but with comma chars injected, and with the decimal point replaced, I think it's best to call get the output produced by ~F, and then modify it and write it to the string. Here's a way to do that, using a utility inject-comma that adds a comma character at specified intervals to a string. Here's the directive function:
(defun print-float (stream arg colonp atp
&optional
(point-char #\.)
(comma-char #\,)
(comma-interval 3))
"A function for printing floating point numbers, with an interface
suitable for use with the tilde-slash FORMAT directive. The full form
is
~point-char,comma-char,comma-interval/print-float/
The point-char is used in place of the decimal point, and defaults to
#\\. If : is specified, then the whole part of the number will be
grouped in the same manner as ~D, using COMMA-CHAR and COMMA-INTERVAL.
If # is specified, then the sign is always printed."
(let* ((sign (if (minusp arg) "-" (if (and atp (plusp arg)) "+" "")))
(output (format nil "~F" arg))
(point (position #\. output :test 'char=))
(whole (subseq output (if (minusp arg) 1 0) point))
(fractional (subseq output (1+ point))))
(when colonp
(setf whole (inject-comma whole comma-char comma-interval)))
(format stream "~A~A~C~A"
sign whole point-char fractional)))
Here are some examples:
(progn
;; with # (for sign) and : (for grouping)
(format t "~','.2#:/print-float/ ~%" 12345.6789) ;=> +1.23.45,679
;; with no # (no sign) and : (for grouping)
(format t "~'.'_3:/print-float/ ~%" 12345.678) ;=> 12_345.678
;; no # (but sign, since negative) and : (for grouping)
(format t "~'.'_3:/print-float/ ~%" -12345.678) ;=> -12_345.678
;; no # (no sign) and no : (no grouping)
(format t "~'.'_3#/print-float/ ~%" 12345.678)) ;=> +12345.678 (no :)
Here are the examples from coredump-'s answer, which actually helped me catch a bug with negative numbers:
CL-USER> (loop for i in '(1034.34 -223.12 -10.0 10.0 14 324 1020231)
do (format t "~','.:/print-float/~%" i))
1.034,34
-223,12
-10,0
10,0
14,0
324,0
1.020.231,0
NIL
Here's inject-comma, with some examples:
(defun inject-comma (string comma-char comma-interval)
(let* ((len (length string))
(offset (mod len comma-interval)))
(with-output-to-string (out)
(write-string string out :start 0 :end offset)
(do ((i offset (+ i comma-interval)))
((>= i len))
(unless (zerop i)
(write-char comma-char out))
(write-string string out :start i :end (+ i comma-interval))))))
(inject-comma "1234567" #\, 3)
;;=> "1,234,567"
(inject-comma "1234567" #\. 2)
;;=> "1.23.45.67"
As the comment of jkiiski suggests, you could use the ~/func/ directive.
This is just an example, you can elaborate more with the function:
CL-USER> (defun q(stream arg &rest args)
(declare (ignore args))
(format stream
"~,,'.,:D,~a"
(truncate arg)
(let ((float-string (format nil "~f" arg)))
(subseq float-string (1+ (position #\. float-string))))))
Q
CL-USER> (format t "~/q/~%" 1024.36)
1.024,36
NIL
CL-USER> (format t "~/q/~%" -1024.36)
-1.024,36
NIL
Edited
The first version had round, which is wrong, truncate is the right operator to use.
If you don't mind splitting integer and fractional part, you can do the following:
(multiple-value-bind (int rest) (floor 1234.56)
(let ((rest (round (* rest 1000))))
(format t "~,,'.,:D,~D~%" int rest)))
1.234,560
The multiplication before rounding tells how many digits after comma you would like to print. Not sure if this approach lands itself nicely into automatic control of precision printing, i.e. 1.5 printed as "1,5" and not as "1,500".
Other answers currently use round, which is probably not the intended behavior when rounding up (positive numbers) or down (negative numbers). Here is another approach for a ~/custom/ directive, derived mostly from Renzo's answer.
(defun custom (stream number &rest args)
(declare (ignore args))
(multiple-value-bind (integer decimal) (truncate number)
(format stream "~,,'.,:D~#[,~a~]"
integer
(unless (zerop decimal)
(let ((decimal-string (princ-to-string (abs decimal))))
(subseq decimal-string (1+ (position #\. decimal-string))))))))
TESTS
(loop for i in '(1034.34 -223.12 -10.0 10.0 14 324 1020231)
collect (custom nil i))
=> ("1.034,33996582" "-223,11999512" "-10" "10" "14" "324" "1.020.231")
I've come to this little solution for positive numbers.
(defun comma-point (stream arg &rest args)
(declare (ignore args))
(multiple-value-bind (i r) (truncate arg)
(format stream "~,,',,:D.~2,'0D" i (truncate (* 100 r)))))
;; ^ ^
;; | `Decimal point
;; `Thousands separator
(defun point-comma (stream arg &rest args)
(declare (ignore args))
(multiple-value-bind (i r) (truncate arg)
(format stream "~,,'.,:D,~2,'0D" i (truncate (* 100 r)))))
(defun space-comma (stream arg &rest args)
(declare (ignore args))
(multiple-value-bind (i r) (truncate arg)
(format stream "~,,' ,:D,~2,'0D" i (truncate (* 100 r)))))
The testing numbers:
(dolist (value '(1034.34 -223.12 -10.0 10.0 14 324 1020231.099))
(format t "~16#A" (format nil "~/comma-point/" value))
(format t "~16#A" (format nil "~/point-comma/" value))
(format t "~16#A~%" (format nil "~/space-comma/" value)))
;; 1,034.33 1.034,33 1 034,33
;; -223.-11 -223,-11 -223,-11
;; -10.00 -10,00 -10,00
;; 10.00 10,00 10,00
;; 14.00 14,00 14,00
;; 324.00 324,00 324,00
;; 1,020,231.12 1.020.231,12 1 020 231,12
The second test number shows that does not work for negative numbers (-223.11 => -223,-11). Also, using truncate (or other similar functions) implies that a loss of accuracy appears, as can be seen in the last test number (1020231.099 => 1.020.231,12).

Declare global variable using an "artificial" symbol

By "artificial", I mean one created from a string using intern or make-symbol.
I have a section of my code that declares up to 49 global variables:
(defparameter *CHAR-COUNT-1-1* (make-hash-table))
...
(defparameter *CHAR-COUNT-1-7* (make-hash-table))
...
(defparameter *CHAR-COUNT-7-7* (make-hash-table))
I thought, instead, I could create a function to do all that:
(loop for n from 1 to 7 do
(loop for i from 1 to 7 do
(defparameter (symbol-value (intern (concatenate 'string "*CHAR-COUNT-" (write-to-string n) "-" (write-to-string i) "*")))
(make-hash-table :test 'equalp))))
But get the error(sbcl):
unhandled SIMPLE-ERROR in thread #<SB-THREAD:THREAD "main thread" RUNNING
{1002978EE3}>:
Can't declare a non-symbol as SPECIAL: (SYMBOL-VALUE
(INTERN
(CONCATENATE 'STRING "*CHAR-COUNT-"
(WRITE-TO-STRING N) "-"
(WRITE-TO-STRING I)
"*")))
What is the correct way to do this?
Defparameter is a macro, not a function. That means that it defines a special syntax. The defparameter form needs to have a symbol as its second argument, but you're providing the list:
(symbol-value (intern (concatenate 'string "*CHAR-COUNT-" (write-to-string n) "-" (write-to-string i) "*")))
What you want is a form like
(progn
(defparameter *foo-1-1* (make-hash-table ...))
...
(defparameter *foo-n-n* (make-hash-table ...)))
You seem familiar enough with loop and creating the symbols to create that list; just change
(loop … do (loop … do (defparameter …)))
to
`(progn
,#(loop … nconcing
(loop … collecting
`(defparameter ,(intern …) …))))
and you can get the form you need. Then it's just a matter of putting it all into a macro
(defmacro … (…)
`(progn
,#(loop … nconcing
(loop … collecting
`(defparameter ,(intern …) …)))))
and calling the macro.
One of "use a macro that returns a PROGN with DEFPARAMETER stanzas" or "use PROCLAIM, it is a function, not a macro".
The correct way is to use a proper data structure instead of encoding dimensions in symbol names. Do you really want to calculate and encode symbol names any time you want to access the correct table?
(defparameter *char-counts* (make-array '(7 7)))
(dotimes (i 49) ; or (reduce #'* (array-dimensions *char-counts*))
(setf (row-major-aref *char-counts* i) (make-hash-table)))
Now you can access the array of tables just with the indices (x and y in this example):
(gethash (aref *char-counts* x y) :foo)

Joining a series of paths components in common lisp

How do I join a series of path components in common lisp?
In python, I can do,
`os.path.join("/home/", username, "dira", "dirb", "dirc");`
What would be the equivalent in common lisp?
Of course I can write my own function, but I suspect I should be able to use something built-in.
If you insist on using strings to represent pathnames, then there seems to be no built-in solution except rolling your own.
(defun join-strings (list &key (separator "/") (force-leading nil))
(let* ((length (length list))
(separator-size (length separator))
(text-size (reduce #'+ (mapcar #'length list) :initial-value 0))
(size (+ text-size (* separator-size (if force-leading length (1- length)))))
(buffer (make-string size)))
(flet ((copy-to (position string)
(loop
:with wp := position
:for char :across string
:do (setf (char buffer (prog1 wp (incf wp))) char)
:finally (return wp))))
(loop
:with wp := 0
:for string :in list
:do (when (or force-leading (plusp wp)) (setf wp (copy-to wp separator)))
(setf wp (copy-to wp string)))
buffer)))
(join-strings '("home" "kurt" "source" "file.txt") :force-leading t)
==> "/home/kurt/source/file.txt"
However, if you can use pathnames, then you could, for example, do:
(merge-pathnames #P"subdir1/subdir2/file.type" #P"/usr/share/my-app")
==> #P"/usr/share/my-app/subdir1/subdir2/file.type"
The pathname API also provides functions to manipulate pathnames symbolically, extract the components of a pathname, etc.:
(pathname-directory #P"subdir1/subdir2/file.type")
==> '(:relative "subdir1" "subdir2")
(pathname-name #P"subdir1/subdir2/file.type")
==> "file"
(pathname-type #P"subdir1/subdir2/file.type")
==> "type"
(make-pathname :name "file" :type "type" :directory '(:relative "subdir1" "subdir2"))
==> #P"subdir1/subdir2/file.type"
In particular, the directory component of a pathname is represented as a list, and thus, you can use the full set of list handling functions to derive directory values from others:
(make-pathname :directory (append '(:absolute "usr" "share") '("more" "stuff"))
:name "packages" :type "lisp")
A simpler join-strings
(defun join-strings (lst sep)
(if
(atom lst)
lst
(reduce
(lambda (a b)
(concatenate 'string a sep b))
(cdr lst)
:initial-value (car lst))))

lisp function to concatenate a list of strings

I need to write a function that will concatenate a list into a string. example:
(concatString (quote ("hello" " world"))) ==> "hello world"
here is what i have so far:
(defun concatString (list)
"A non-recursive function that concatenates a list of strings."
(cond
((not (listp list))
(princ "Error: argument to concatNR must be a list")(terpri) ())) ; check if parameter is a list
(if (not (null list)) ;check if list is not null
(let ((result (car list)))
(dolist (item (cdr list))
(if (stringp item)
(setq result (concatenate result item)))
)
)
)
)
I'm getting a "Error: "hello" is and illegal type specifier" message when i try to run it. I've tried a bunch of ways to modify this function and i havent been able to figure it out. does anyone have any ideas?
concatenate requires a sequence type specifier as its second argument. To concatenate two strings, you should call concatenate as:
(concatenate 'string "hello" "world")
Another bug in your code: you do not make sure that the car of the list is a string before assigning it to result. By fixing your code, I came up with the following implementation:
(defun concatString (list)
"A non-recursive function that concatenates a list of strings."
(if (listp list)
(let ((result ""))
(dolist (item list)
(if (stringp item)
(setq result (concatenate 'string result item))))
result)))
;; tests
> (concatString (list "hello" " world"))
"hello world"
> (concatString (list "hello" 1 2 3 " world"))
"hello world"
> (concatString (list "hello" 1 2 "3" " world"))
"hello3 world"
> (concatString (list 1 2 3 "hello" " world"))
"hello world"
The following redefinition of concatString is more efficient as it does not create many intermediary string objects:
(defun concatString (list)
"A non-recursive function that concatenates a list of strings."
(if (listp list)
(with-output-to-string (s)
(dolist (item list)
(if (stringp item)
(format s "~a" item))))))
Just use the format function on a list, this will convert everything to strings and concatenate them with the correct format string.
(defun my-concat( list )
(format nil "~{~a~}" list))
If you want to concatenate them with a space use this form with the "~^" directive:
(defun my-concat( list )
(format nil "~{~a~^ ~}" list))
If you'd like to filter out the results, you can just transform the list before formatting it.
(defun my-concat(list)
(format nil "~{~a~^ ~}" (remove-if-not #'stringp list)))
To concatenate sequences to a string, use concatenate 'string.
(defun concat-strings (list)
(apply #'concatenate 'string list))
To remove anything from the list that is not a string, use remove-if-not.
(defun concat-strings (list)
(apply #'concatenate 'string
(remove-if-not #'stringp list)))
If the argument is not a list, an error will be signaled by remove-if-not. You can add the assertion before, of course, to give a more specific error message, but it does not really add value here.
(defun concat-strings (list)
(assert (listp list)
"This is not a list: ~s." list)
(apply #'concatenate 'string
(remove-if-not #'stringp list)))
EDIT:
As Rainer notes, apply only works on lists of limited length. If you do not have reason to believe that your list cannot be longer than call-arguments-limit minus one, a reduce form is better:
(defun concat-strings (list)
(reduce (lambda (a b)
(concatenate 'string a b))
(remove-if-not #'stringp list)))
Common Lisp the Language, 2nd Edition
concatenate result-type &rest
sequences
This one should work
(concatenate 'string result item)
According to the Common Lisp Cookbook :
(concatenate 'string "Karl" " " "Marx")
"Karl Marx"
Why limit yourself to lists?
(defun concatenate-strings (sequence)
(reduce #'(lambda (current next)
(if (stringp next)
(concatenate 'string current next)
current))
sequence
:initial-value ""))
Here are my two cents:
(defmacro concatString (&rest strings) `(concatenate 'string
,#strings) )

How to make this code simpler, clearer and "more lispy"?

I want to parse the text line from the Wavefront OBJ file. Currently I am interested in "V" and "F" types only.
My algorithm is as follows:
check if line is not nil (otherwise step 2 would fail)
drop comment after "#" and trim spaces
drop prefix "v " or "f "
split string to the list of elements where each element
is split to the list if it is symbol like |34/76/23|
is converted from the list: I take one element only, the first by default
or coerced to the given type if it is atomic number already.
Here is the code:
(defun parse-line (line prefix &key (type 'single-float))
(declare (optimize (debug 3)))
(labels ((rfs (what)
(read-from-string (concatenate 'string "(" what ")")))
(unpack (str &key (char #\/) (n 0))
(let ((*readtable* (copy-readtable)))
(when char ;; we make the given char a delimiter (space)
(set-syntax-from-char char #\Space))
(typecase str
;; string -> list of possibly symbols.
;; all elements are preserved by (map). nil's are dropped
(string (delete-if #'null
(map 'list
#'unpack
(rfs str))))
;; symbol -> list of values
(symbol (unpack (rfs (symbol-name str))))
;; list -> value (only the requested one)
(list (unpack (nth n str)))
;; value -> just coerce to type
(number (coerce str type))))))
(and line
(setf line (string-trim '(#\Space #\Tab)
(subseq line 0 (position #\# line))))
(< (length prefix) (length line))
(string= line prefix :end1 (length prefix) :end2 (length prefix))
(setf line (subseq line (length prefix)))
(let ((value (unpack line :char nil)))
(case (length value)
(3 value)
(4 (values (subseq value 0 3) ;; split quad 0-1-2-3 on tri 0-1-2 + tri 0-2-3
(list (nth 0 value)
(nth 2 value)
(nth 3 value)))))))))
Step four (label "unpack") is kind of recursive. It is one function and can call itself three times.
Anyway, this solution seems to be clunky.
My question is: how should one solve this task with shorter and clearer code?
I would approach this in a more structured manner.
You want to parse an obj file into some sort of data structure:
(defun parse-obj-file (filespec)
;; todo
)
You need to think about how the data structure returned should look. For now, let us return a list of two lists, one of the vertices, one of the faces. The parser will go through each line, determine whether it is either a vertex or a face, and then collect it into the appropriate list:
(defun parse-obj-file (filespec)
(with-open-file (in-stream filespec
:direction :input)
(loop for line = (read-line in-stream nil)
while line
when (cl-ppcre:scan "^v " line)
collect (parse-vertex line) into vertices
when (cl-ppcre:scan "^f " line)
collect (parse-face line) into faces
finally (return (list vertices faces)))))
I used the cl-ppcre library here, but you could also use mismatch or search. You will then need to define parse-vertex and parse-face, for which cl-ppcre:split should come in quite handy.
It would perhaps also be useful to define classes for vertices and faces.
Update: This is how I would approach vertices:
(defclass vertex ()
((x :accessor x :initarg :x)
(y :accessor y :initarg :y)
(z :accessor z :initarg :z)
(w :accessor w :initarg :w)))
(defun parse-vertex (line)
(destructuring-bind (label x y z &optional w)
(cl-ppcre:split "\\s+" (remove-comment line))
(declare (ignorable label))
(make-instance 'vertex
:x (parse-number x)
:y (parse-number y)
:z (parse-number z)
:w (parse-number w))))
Parse-number is from the parse-number library. It is better than using read.
Update 2: (Sorry for making this a run-on story; I have to interlace some work.) A face consists of a list of face-points.
(defclass face-point ()
((vertex-index :accessor vertex-index :initarg :vertex-index)
(texture-coordinate :accessor texture-coordinate
:initarg :texture-coordinate)
(normal :accessor normal :initarg :normal)))
(defun parse-face (line)
(destructuring-bind (label &rest face-points)
(cl-ppcre:split "\\s+" (remove-comment line))
(declare (ignorable label))
(mapcar #'parse-face-point face-points)))
(defun parse-face-point (string)
(destructuring-bind (vertex-index &optional texture-coordinate normal)
(cl-ppcre:split "/" string)
(make-instance 'face-point
:vertex-index vertex-index
:texture-coordinate texture-coordinate
:normal normal)))
Remove-comment simply throws away everything after the first #:
(defun remove-comment (line)
(subseq line 0 (position #\# line)))

Resources