私はしばらくの間共通のLispを学んでいます、私が満たした質問がありました ユーザー入力出口までユーザーがいくつかの単語を入力できるような機能を実装できる方法。 （実際には、どのような種類のコマンドライン対話型機能APIがそのような要件に合わせても知りたい）
e.g。 REPLで「Wordを入力してください：」を求めてから、ユーザー入力をGlobal My-Words に保存し、ユーザー入力が "EXIT"のときに終了します。
I have been learning Common Lisp for a while, there was a question I have met that how I can implement such a function which allows user to input some words until user input exit. (actually I want to know what kind of command line interactive function APIs fit such requirement)
e.g. prompt "please input a word: " in the REPL, then store user inputs into a global my-words , exit when user input "exit".</div
You specification is a little bit incomplete (e.g. what constitutes a word in your problem? What if the user add multiple words? What if the input is empty?). Here below I am using CL-PPCRE to split the input into different words and add them all at once, because it seems useful in general. In your case you might want to add more error checking.
If you want to interact with the user, you should read and write from and to the
*QUERY-IO* stream. Here I'll present a version with a global variables, as you requested, as well as another one without side-effects (apart from input/output).
Define the global variable and initialize it with an empty adjustable array. I am using an array so that it is easy to add words at the end, but you could also use a queue.
(defvar *my-words* (make-array 10 :fill-pointer 0 :adjustable t))
The following function mutates the global variable:
(defun side-effect-word-repl () (loop (format *query-io* "~&Please input a word: ") (finish-output *query-io*) (let ((words (ppcre:split '(:greedy-repetition 1 nil :whitespace-char-class) (read-line *query-io*)))) (dolist (w words) (when (string-equal w "exit") ; ignore case (return-from side-effect-word-repl)) (vector-push-extend w *my-words*)))))
LOOP uses the simple syntax where there are only expressions and no loop-specific keywords. I first write the prompt to
FORMAT directive performs the same operation as
FRESH-LINE. As Rainer pointed out in comments, we have to call
FINISH-OUTPUT to ensure the message is effectively printed before the user is expected to reply.
Then, I read a whole line from the same bidirectional stream, and split it into a list of words, where a word is a string of non-whitespace characters.
DOLIST, I iterate over the list and add words into the global array with
VECTOR-PUSH-EXTEND. But as soon as I encouter
"exit", I terminate the loop; since I rely on
STRING-EQUAL, the test is done case-insensitively.
Having a global variable as done above is discouraged. If you only need to have a prompt which returns a list of words, then the following will be enough. Here, I use the
NREVERSE idiom to built the resulting list of words.
(defun pure-word-repl () (let ((result '())) (loop (format *query-io* "~&Please input a word: ") (finish-output *query-io*) (let ((words (ppcre:split '(:greedy-repetition 1 nil :whitespace-char-class) (read-line *query-io*)))) (dolist (w words) (when (string-equal w "exit") (return-from pure-word-repl (nreverse result))) (push w result))))))
As jkiiski commented, it might be better to split words at
:word-boundary. I tried different combinations and the following result seems satisfying with weird example strings:
(mapcan (lambda (string) (ppcre:split :word-boundary string)) (ppcre:split '(:greedy-repetition 1 nil :whitespace-char-class) "amzldk 'amlzkd d;:azdl azdlk")) => ("amzldk" "'" "amlzkd" "d" ";:" "azdl" "azdlk")
I first remove all whitespaces and split the string into a list of strings, which can contain punctuation marks. Then, each string is itself splitted at
:word-boundary, and concatenated with
MAPCAN to form a list of separate words. However, I can't really guess what your actual needs are, so you should probably define your own
SPLIT-INTO-WORDS function to validate and split an input string.
CL-USER 23 > (progn (format t "~%enter a list of words:~%") (finish-output) (setf my-words (read)) (terpri)) enter a list of words: (foo bar baz)
CL-USER 28 > (loop with word = nil do (format t "~%enter a word or exit:~%") (finish-output) (setf word (read)) (terpri) until (eql word 'exit) collect word) enter a word or exit: foo enter a word or exit: bar enter a word or exit: baz enter a word or exit: exit (FOO BAR BAZ)