0

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!

4
  • The code above has unbalanced parenthesis, and is not properly indented. Could you fix these things first? Commented Feb 24, 2021 at 13:03
  • I apologize for that. Should be properly indented now. The parentheses are extremely confusing, still learning. Commented Feb 24, 2021 at 13:38
  • 1
    Parentheses can (and should) be tracked with the help of the editor, see for example github.com/susam/emacs4cl for detailed explanations; at out-of-the-box Emacs highlights matching parentheses which is already nicer than writing in a text editor (notepad, etc.). Commented Feb 24, 2021 at 14:51
  • Also I would encourage you to write small expressions or functions first, test them in the REPL, and only when you are comfortable with what is happening in the program, build larger functions. Otherwise you may have multiple bugs to catch (like here). Commented Feb 24, 2021 at 14:54

3 Answers 3

2

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.

Sign up to request clarification or add additional context in comments.

Comments

2

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!"

Comments

2

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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.