【问题标题】:what is the interactive REPL IO function?什么是交互式 REPL IO 功能?
【发布时间】:2016-09-28 01:30:30
【问题描述】:

我学习 Common Lisp 已经有一段时间了,我遇到了一个问题 我如何实现这样一个功能,允许用户输入一些单词,直到用户输入退出。 (其实我想知道什么样的命令行交互函数API适合这样的需求)

例如 在 REPL 中提示“please input a word:”,然后将用户输入存储到全局 my-words 中,当用户输入“exit”时退出。

【问题讨论】:

  • 我不清楚。你想读取单行输入(当用户按下返回时终止),为什么你需要一个全局 my-words 变量?请添加有关您需要此功能的原因以及您所做的尝试的详细信息。谢谢
  • 是的,当用户按下返回时终止,
  • 其实我只是想写一个用来收集单词的样例,仅供测试。

标签: io common-lisp read-eval-print-loop


【解决方案1】:

您的规范有点不完整(例如,什么构成了您的问题中的单词?如果用户添加多个单词怎么办?如果输入为空怎么办?)。下面我使用CL-PPCRE 将输入拆分为不同的单词并一次添加它们,因为它通常看起来很有用。在您的情况下,您可能需要添加更多错误检查。

如果您想与用户交互,您应该读写*QUERY-IO* 流。在这里,我将根据您的要求提供一个带有全局变量的版本,以及另一个没有副作用的版本(除了输入/输出)。

带全局变量

定义全局变量并用一个空的可调数组对其进行初始化。 我使用的是数组,以便在末尾添加单词很容易,但您也可以使用队列。

(defvar *my-words* (make-array 10 :fill-pointer 0 :adjustable t))

以下函数改变全局变量:

(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 使用简单的语法,其中只有表达式,没有特定于 loop 的关键字。我先把提示写到*QUERY-IO*~& FORMAT 指令执行与FRESH-LINE 相同的操作。正如 Rainer 在 cmets 中指出的那样,我们必须调用 FINISH-OUTPUT 以确保在期望用户回复之前有效地打印消息。

  • 然后,我从同一个双向流中读取一整行,并将其拆分为一个单词列表,其中一个单词是一串非空白字符。

  • 使用DOLIST,我遍历列表并使用VECTOR-PUSH-EXTEND 将单词添加到全局数组中。但是一旦我遇到"exit",我就会终止循环;因为我依赖STRING-EQUAL,所以测试不区分大小写。

无副作用的方法

不鼓励像上面那样使用全局变量。如果您只需要提示 返回一个单词列表,那么以下内容就足够了。在这里,我使用PUSH/NREVERSE 成语来构建生成的单词列表。

(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))))))

单词注意事项

正如 jkiiski 评论的那样,在:word-boundary 处拆分单词可能会更好。我尝试了不同的组合,以下结果似乎对奇怪的示例字符串很满意:

(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")

我首先删除所有空格并将字符串拆分为字符串列表,其中可以包含标点符号。然后,每个字符串本身在:word-boundary 处拆分,并与MAPCAN 连接以形成单独单词的列表。但是,我无法真正猜测您的实际需求是什么,因此您可能应该定义自己的 SPLIT-INTO-WORDS 函数来验证和拆分输入字符串。

【讨论】:

  • 您可以使用 (ppcre:do-matches-as-strings (w "\\w+" (read-line *query-io*)) ...) 循环遍历单词,而不是在空格处拆分,这样标点符号就不会被视为单词的一部分。
  • 不要忘记在等待答案之前显示输出 - 以防输出流被缓冲......
  • @RainerJoswig 谢谢,我倾向于忘记这一点。
  • 抱歉,我没有清楚地描述我的目的,但你确实给出了很好的答案和 cmets 这也帮助了我,真的很感激!!!
【解决方案2】:
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)

【讨论】:

  • 这正是我所期望的,谢谢你的好意。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-02
  • 1970-01-01
  • 2019-07-25
  • 1970-01-01
  • 2016-05-09
  • 1970-01-01
相关资源
最近更新 更多