I'm beginning to work through Practical Common LISP and the first exercise is to write a simple database. I'm using GNU CLISP 2.48 (2009-07-28) on cygwin.
This code, which I've compared against the book several times, doesn't produce output the way the book says it should
(defun make-cd (title artist rating ripped)
(list :title title :artist artist :rating rating :ripped))
(defvar *db* nil)
(defun add-record (cd) (push cd *db*))
(add-record (make-cd "Roses" "Kathy Mattea" 7 t))
(add-record (make-cd "Fly" "Dixie Chicks" 8 t))
(add-record (make-cd "Home" "Dixie Chicks" 9 t))
(defun dump-db ()
(dolist (cd *db*)
(format t "~{~a:~10t~a~%~}~%" cd)))
(dump-db)
I get
TITLE: Home
ARTIST: Dixie Chicks
RATING: 9
RIPPED:
*** - There are not enough arguments left for this format directive.
Current point in control string:
"~{~a:~10t~a~%~}~%"
|
I don't understand format or LISP well enough to being to troubleshoot. The book says I should be getting a list of all the records in the database. What has gone wrong?
First, let's look at the return from (make-cd):
[12]> (make-cd "Home" "Dixie Chicks" 9 t)
(:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED)
You aren't including a value for :ripped! Change (make-cd) to:
(defun make-cd (title artist rating ripped)
(list :title title :artist artist :rating rating :ripped ripped))
Note the ripped after :ripped.
If you use the compiler in CLISP, it tells you what is wrong:
[1]> (defun make-cd (title artist rating ripped)
(list :title title :artist artist :rating rating :ripped))
MAKE-CD
[2]> (compile 'make-cd)
WARNING: in MAKE-CD : variable RIPPED is not used.
Misspelled or missing IGNORE declaration?
MAKE-CD ;
1 ;
NIL
The variable RIPPED is not used.
The format directive ~{...~} is an iterative construct, and its corresponding argument is expected to be a list. Furthermore in this case, because of the two occurrences of ~a, each iteration will consume two items, so the total number of items in the list is expected to be even. Yet you provided it with an odd number of items.
Related
I need some help understanding completion-at-point.
I have this minimal example, where I want to:
activate when I type "#"
search/complete on candidates car ...
... but return cdr, so result at point is, for example "#doe" (though I may need to extend this later to drop the "#" in some cases, like with LaTeX).
The actual use case is to insert a citation key in a document, but search on author, title, etc. The intention is for this to be used with solutions like corfu and company-capf.
In that code, which is a front-end to bibtex-completion like helm-bibtex and ivy-bibtex, I have a core bibtex-actions-read function based on completing-read-multiple for minibuffer completion.
With this capf, I want to use the same cached data to complete against for at-point completion.
With this test example, I get 1 and 2, which is what I want on the UI end.
(defun test-capf ()
"My capf."
(when (looking-back "#[a-zA-Z]*")
(list
(save-excursion
(backward-word)
(point))
(point)
(lambda (str pred action)
(let ((candidates '(("a title doe" . "doe")
("different title jones" . "jones")
("nothing smith" . "smith"))))
(complete-with-action action candidates str pred))))))
But how do I adapt it to this to add 3? That is, if I type "#not", corfu or company should display "nothing smith", and if I select that item, it should return "#smith" at-point.
Note: my package pretty much depends on completion-styles like orderless, so order is of course not significant.
Do I need to use an :exit-function here?
For completeness, here's the current actual function, which now says "no matches" when I try to use it.
(defun bibtex-actions-complete-key-at-point ()
"Complete citation key at point.
When inserting '#' in a buffer the capf UI will present user with
a list of entries, from which they can narrow against a string
which includes title, author, etc., and then select one. This
function will then return the key 'key', resulting in '#key' at
point."
;; FIX current function only returns "no match"
;; TODO this regex needs to adapt for mode/citation syntax
(when (looking-back "#[a-zA-Z]+" 5)
(let* ((candidates (bibtex-actions--get-candidates))
(begin (save-excursion (backward-word) (point)))
(end (point)))
(list begin end candidates :exclusive 'no
;; I believe I need an exit-function so I can insert the key instead
;; of the candidate string.
:exit-function
(lambda (chosen status)
(when (eq status 'finished)
(cdr (assoc chosen candidates))))))))
Any other tips or suggestions?
This Q&A is related, but I can't figure out how to adapt it.
Why not just keep the completion candidates in your completion table, not conses?
There are some useful wrappers in minibuffer.el around completion tables. In this case you could use completion-table-dynamic, as a wrapper to use a function as the COLLECTION argument to complete-with-action.
I think the more efficient way would just collect the cdrs of your current candidates and allow the C implementations of all-completions to find matches
(complete-with-action action (mapcar #'cdr candidates) str pred)
Or, calling a function to return current candidates
(completion-table-dynamic
(lambda (_str)
(mapcar #'cdr (my-current-candidates))))
Or, filtering in elisp
(let ((candidates '((...)))
(beg '...)
(end '...))
;; ...
(list beg end
(completion-table-dynamic
(lambda (str)
(cl-loop for (a . b) in candidates
if (string-prefix-p str a)
collect b)))))
The solution was an exit-function, with body like this:
(delete-char (- (length str)))
(insert (cdr (assoc str candidates)))))
I am a beginner in Lisp. I have two functions, a defparameter, and a defstruct. Every time a book is passed to inputBook I would like the title of the book (the string) to become the name of the defparameter. Is this possible?
I have tried to hard code a string in like below where it says "MobyDick" but I get an error. So is this even possible?
I tried to simply use the parameter passed title but if you try to pass another book into the function, they are both assigned to title but the last one passed will print, not the first and not both. So how can I do it so that I have as many "books" without a list or a hash table?
If the latter is not possible, how could I alter the code so that a defparameter would be created (unique) to any number of books and accessible through the getAuthor function? Does that make sense? (Please see the functions below.)
(defstruct book()
(title) ;;title of the book, type string
(author) ;;author of the book, type string
(date)) ;; date of the book, type string or int
(defun inputBook(title author month year)
(defparameter "MobyDick" ;;I want this to use the parameter title
(make-book :title title
:author author
:date '(month year))))
(defun getAuthor (book)
(write (book-author book)))
Many many thanks in advance! Also, I am a beginner beginner. I have been learning through googling and I am stumped here.
You probably want something which looks like this, rather than some madness of top-level variables.
(defvar *books* (make-hash-table))
(defun bookp (title)
(nth-value 1 (gethash title *books*)))
(defun remove-book (title)
(remhash title *books*))
(defun book (title)
(nth-value 0 (gethash title *books*)))
(defun (setf book) (new title)
(setf (gethash title *books*) new))
Then, for instance:
> (setf (book 'moby) (make-book ...))
> (book 'moby)
> (bookp 'moby)
> (remove-book 'moby)
Using symbols with arbitrary names has the typical drawback: you can overwrite the values of existing symbols. Thus it would be useful to have a separate package for it, which does not import symbols from other packages.
Better would be to have a hash table, which would map from a string to the book object.
Sketch of code with symbols:
(defstruct book
(title) ;; title of the book, type string
(author) ;; author of the book, type string
(date)) ;; date of the book, type list
(defun input-book (title author month year)
(setf (symbol-value (intern title))
(make-book :title title
:author author
:date (list month year))))
Example:
CL-USER 52 > (input-book "Lisp Style & Design"
"Molly M. Miller, Eric Benson"
1 1990)
#S(BOOK :TITLE "Lisp Style & Design"
:AUTHOR "Molly M. Miller, Eric Benson"
:DATE (1 1990))
CL-USER 53 > (book-author (symbol-value '|Lisp Style & Design|))
"Molly M. Miller, Eric Benson"
CL-USER 54 > (book-author |Lisp Style & Design|)
"Molly M. Miller, Eric Benson"
I'm using a function that uses a mapcar to apply a (simple) function to all members of a list, like this :
(mapcar 'my-concat-function '(
"/path/one.php"
"/path/two.php"))
But I want to use directory-files to generate the file list and filter it, something like this :
(mapcar 'my-concat-function '(
(directory-files "/path/" nil "\\.php$")))
But I always get a
find-file-noselect: Wrong type argument: stringp, (directory-files "/path/" nil "\\.php$")
When I evaluate
(directory-files "/path/" nil "\\.php$")
It returns
("one.php" "two.php" "three.php" ...)
(I did not add the "..." ; Emacs did. No matter the size of the list, it seems to always end with "...")
Question :
How can I format the output of directory-files so that it produces exactly what mapcar wants, a single list of atoms, I don't really know how to call this form :
"one.php" "two.php" "three.php"
Without the parenthesis, and without those weird "..."?
EDIT
When I try the forms suggested (thank you guys) the quoted function as 1st arg of mapcar does not work (the regexp don't find anything, all files end up open in empty (?) buffers) anymore :(
Here is the full code, thank you very much for helping, it's weird, this function took very little time to write, and now i'm blocked since hours on this simple list issue, arg.
(defun px-bpm-parse (fname)
"Extract elements. Basic Project Management."
(setq in-buf (set-buffer (find-file fname)))
(setq u1 '())
(setq u2 '())
(setq u3 '())
(setq project-dir "/var/www/html/microlabel.git/")
(beginning-of-buffer)
(while
(re-search-forward "^.*<link.*href=\"\\([^\"]+\\)\".*rel=\"stylesheet\"" nil t)
(when (match-string 0)
(setq url (match-string 1) )
(setq u3 (cons (concat "[[file:" project-dir url "][" url "]]\n") u3))))
(beginning-of-buffer)
(while
(re-search-forward "^.*<a.*href=\"\\([^\"]+\\)\"[^>]+>\\([^<]+\\)</a>" nil t)
(when (match-string 0)
(setq url (match-string 1) )
(setq title (match-string 2) )
(setq u1 (cons (concat "[[file:" project-dir url "][" title "]]\n") u1))))
(beginning-of-buffer)
(while
(re-search-forward "^.*<script.*src=\"\\([^\"]+\\)\"" nil t)
(when (match-string 0)
(setq url (match-string 1) )
(setq u2 (cons (concat "[[file:" project-dir url "][" url "]]\n") u2))))
(beginning-of-buffer)
(progn
(with-current-buffer "BPM.org"
(insert "** File: ")
;; (org-insert-link &optional COMPLETE-FILE LINK-LOCATION DEFAULT-DESCRIPTION)
(insert fname)
(insert "\n*** HREF Links (by name)\n")
(mapcar 'insert u1)
(insert "\n*** SCRIPT Links\n")
(mapcar 'insert u2)
(insert "\n*** CSS Links\n")
(mapcar 'insert u3)
(insert "\n\n"))
(switch-to-buffer "BPM.org")
(org-mode)))
(defun px-bpm ()
;; (defun px-bpm (prj-root)
"List all links"
(interactive)
;; (interactive "sEnter project root directory ")
(progn
(with-current-buffer (get-buffer-create "BPM.org")
(insert "* File dependencies\n\n"))
;; (mapcar 'px-bpm-parse '(
;; "/var/www/html/microlabel.git/add.php"
;; ))
(mapcar 'px-bpm-parse (directory-files "/var/www/html/microlabel.git/" nil "\\.php$"))
))
When you evaluate a form and see a result of the form (x y z ...), it's just printed in that way because the output is long. The result is actually the list that you'd expect. For instance,
(list 1 2 3 4 5 6 7 8 9 10 11 12 13)
;=> (1 2 3 4 5 6 7 8 9 10 11 12 ...)
Yet, the last element of the list is what it should be:
(last (list 1 2 3 4 5 6 7 8 9 10 11 12 13))
;=> (13)
Since (directory-files "/path/" nil "\\.php$") returns a list and the second argument to mapcar should be a list, you can make it the second argument:
(mapcar 'my-concat-function (directory-files "/path/" nil "\\.php$"))
I write this in a source buffer:
(defun make-cd (artist album rating like)
(list :artist artist :album album :score rating :like like))
(defvar *dab* nil)
(defun addcd (cd) (push cd *dab*))
(defun readab ()
(dolist (cd *dab*)
(format t "~{~10t~a--~5t~a~%~}~%" cd)))
I compile into the REPL by pressing C-c C-k.
The first several functions work fine from REPL:
CL-USER> (make-cd "dixie" "fun time" 6 "y")
(:ARTIST "dixie" :ALBUM "fun time" :SCORE 6 :LIKE "y")
CL-USER> (addcd (make-cd "dixie" "whooola" 6 "y"))
((:ARTIST "dixie" :ALBUM "whooola" :SCORE 6 :LIKE "y"))
But the last is reported as undefined?
CL-USER> (readab)
; Evaluation aborted on #<CCL::UNDEFINED-FUNCTION-CALL #x302000B3895D>.
Am I missing something very obvious?
The issue is due to the C-c C-k command not automatically saving the source before it is processed, and thus the processing is happening on the old save of the source. Save first and the problem goes away.
I'm trying to learn Lisp now, as a supplement to my CS1 course because the class was moving too slow for me. I picked up "Practical Common Lisp," which so far has turned out to be a great book, but I'm having some trouble getting some examples to work. For instance, if I load the following file into the REPL:
;;;; Created on 2010-09-01 19:44:03
(defun makeCD (title artist rating ripped)
(list :title title :artist artist :rating rating :ripped ripped))
(defvar *db* nil)
(defun addRecord (cd)
(push cd *db*))
(defun dumpDB ()
(dolist (cd *db*)
(format t "~{~a:~10t~a~%~}~%" cd)))
(defun promptRead (prompt)
(format *query-io* "~a: " prompt)
(force-output *query-io*)
(read-line *query-io*))
(defun promptForCD ()
(makeCD
(promptRead "Title")
(promptRead "Artist")
(or (parse-integer (promptRead "Rating") :junk-allowed t) 0)
(y-or-n-p "Ripped [y/n]: ")))
(defun addCDs ()
(loop (addRecord (promptForCD))
(if (not (y-or-n-p "Another? [y/n]: ")) (return))))
(defun saveDB (fileName)
(with-open-file (out fileName
:direction :output
:if-exists :supersede)
(with-standard-io-syntax
(print *db* out))))
(defun loadDB (fileName)
(with-open-file (in fileName)
(with-standard-io-syntax
(setf *db* (read in)))))
(defun select (selectorFn)
(remove-if-not selectorFn *db*))
(defun artistSelector (artist)
#'(lambda (cd) (equal (getf cd :artist) artist)))
And query the 'database' using (select (artistSelector "The Beatles")), even if I do indeed have an entry in the database where :artist is equal to "The Beatles", the function returns NIL.
What am I doing incorrectly here?
Nothing, AFAICT:
$ sbcl
This is SBCL 1.0.34.0...
[[pasted in code above verbatim, then:]]
* (addRecord (makeCD "White Album" "The Beatles" 5 t))
((:TITLE "White Album" :ARTIST "The Beatles" :RATING 5 :RIPPED T))
* (select (artistSelector "The Beatles"))
((:TITLE "White Album" :ARTIST "The Beatles" :RATING 5 :RIPPED T))
CL-USER 18 > (addcds)
Title: Black Album
Artist: Prince
Rating: 10
Title: White Album
Artist: The Beatles
Rating: 10
NIL
CL-USER 19 > (select (artistSelector "The Beatles"))
((:TITLE "White Album" :ARTIST "The Beatles" :RATING 10 :RIPPED T))