user12282981
user12282981

Reputation: 9

Lisp basic print function getting user input

I am supposed to write a program that gets simple user input as a string and the code supposed to writes back a response (name, are you a person etc.) The program suppose to terminate when word 'bye' is typed. The code is below:

(defun x()
    (setq words "empty")
    (loop while (string/= words "BYE")
        (setq words (read-delimited-list #\~)   
        (write-line words)
        (format t "ROBBIE%: Hello, who are you?")
        (case (string-include "I'm" words)
            (format t "ROBBIE%: Nice to see you, how are you?")
            ((string-include "Hi" words)
            (format t "ROBBIE%: How are you?")
            (or (string-include "fine" words) (string-include "person" words))
            (format t "ROBBIE%: No I'm a computer")))
            (format t "BYE"))

(x)

However, when I compile this on program 2 errors pop up:

Line2:3 warning: undefined variable: COMMON-LISP-USER:: WORDS

Line3:3 error: during macroexpansion of (LOOP WHILE (STRING/= WORDS "BYE") ...). Use BREAK-ON-SIGNALS to intercept.

I've done programming in python but this is extremely complicated lang for me and I need some help understanding why this isn't working? Any advice is greatly appreciated!

Upvotes: 0

Views: 1264

Answers (3)

Martin Půda
Martin Půda

Reputation: 7568

Replace read-delimited-list with read-line, case with cond and balance some parentheses. Here is working solution, including some function for string-inclusion:

(defun string-include (string1 string2)
  (let* ((string1 (string string1)) (length1 (length string1)))
    (if (zerop length1)
        nil 
        (labels ((sub (s)
                   (cond
                    ((> length1 (length s)) nil)
                    ((string= string1 s :end2 (length string1)) string1)
                    (t (sub (subseq s 1))))))
          (sub (string string2))))))

(defun x ()
    (let ((words "empty"))
      (format t "ROBBIE%: Hello, who are you?~%")
      (loop while (string/= words "BYE") do
            (progn
              (finish-output)
              (setq words (read-line))   
              (cond ((string-include "I'm" words)
                     (format t "ROBBIE%: Nice to see you, how are you?~%"))
                    ((string-include "Hi" words)
                     (format t "ROBBIE%: How are you?~%"))
                    ((or (string-include "fine" words) 
                         (string-include "person" words))
                     (format t "ROBBIE%: No I'm a computer~%")))))
            (format t "BYE")))

Then you just call it:

(x)

Upvotes: 2

coredump
coredump

Reputation: 38809

Let me focus on aspects of your code not already covered by other solutions.

Loop

Here is your loop structure:

(let ((words "empty"))
  (loop
    while (string/= words "BYE")
    do
    (progn
      (setq words (read-line)))))

First of all, after do you don't need (progn ...). You could write equally:

(let ((words "empty"))
  (loop
    while (string/= words "BYE")
    do (setq words (read-line))))

Having to initialize words to some arbitrary value (called sometime a sentinel value) is a code smell (not always a bad thing, but there might be better alternatives). Here you can simplify the loop by using a for clause.

(loop
  for words = (read-line)
  while (string/= words "BYE")
  do ...)

Also, you may want to use until with a string= test, this might be more readable:

(loop
  for words = (read-line)
  until (string= words "BYE")
  do ...)

Search

You can test for string inclusion with SEARCH. Here is a little commented snippet of code to showcase how string manipulation function could work:

(defun test-I-am (input)
  (let ((start (search "I'am" input)))
    (when start
      ;; we found an occurrence at position start
      ;; let's find the next space character
      (let ((space (position #\space input :start start)))
        (when space
          ;; we found a space character, the name starts just after
          (format nil "Hello ~a!" (subseq input (1+ space))))))))

With this simple algorithm, here is a test (e.g. in your REPL):

> (test-i-am "I'am tired")
"Hello tired!"

Upvotes: 2

Svante
Svante

Reputation: 51501

When you do this:

(defun x ()
  (setf words "foo"))

then words is not defined. It references some global variable, and if that doesn't exist, it will create it, but possibly with some strange behaviour regarding scope and extent. This is not portable code.

In order to introduce a local variable, use let:

(defun x ()
  (let ((words "foo"))
    ;; words is is scope here
    )
  ;; but not here
  )

Loop (in the more usual »extended« form) uses loop keywords for all its clauses. There is no implicit body. In order to do something, you might use do, which allows multiple forms to follow:

(defun x ()
  (let ((words "foo"))
    (loop while (string/= words "bye")
          do (setf words (read-line …))
             (format …))))

Case uses compile-time values to compare using eql:

(case something
  (:foo (do-a-foo))
  ((:bar :baz) (do-a-bell))
  (t (moooh)))

This doesn't work with strings, because strings are not eql unless they are the same object (i. e. they are eq). In your case, you want a cond:

(cond ((string-include-p words "Whatever")
       …)
      ((string-include-p words "yo man")
       …))

For interaction with the user, you'd maybe want to use the bidirectional *query-io* stream:

(format *query-io* "Who am I?")

and

(read-line *query-io*)

Read-line gives you strings, and seems much better suited to your task than read-delimited-list, which has other use cases.

Upvotes: 2

Related Questions