Arc Forumnew | comments | leaders | submitlogin
ASK: How to read user input?
3 points by mpr 604 days ago | 14 comments
Hello there,

I see that there is good documentation on i/o utilities in arc, but it seems to me they are all suited for reading S-expressions. How might one read arbitrary text from stdin? I have the following:

    (def prompt (msg)
      (pr msg)
      (readline))
This function just returns nil without waiting for input if entered at the REPL, because readline sees the newline entered in order to execute the function. So I fixed it up as follows:

    (def prompt (msg)
      (pr msg)
      (readc)   ; grabs the newline
      (readline))
It looks like this one works until I do something like this:

(defvar x (prompt "> "))

When I enter 'x' at the REPL immediately after, I get an error,

    application: not a procedure;
     expected a procedure that can be applied to arguments
      given: ""
      arguments...: [none]
      context...:
       /Users/matt/code/arc/anarki/ac.scm:1252:4
Could someone point out to me how to read arbitrary text into a variable from a stream (like stdin)?

Thanks, mpr



3 points by akkartik 603 days ago | link

I think you're misusing defvar. You can read about defvar at https://awwx.ws/defvar2. In short, to invoke your prompt function everytime you refer to x, say this:

  arc> (defvar x (fn() (prompt "> ")))
For example:

  arc> (len x)
  > abc
  3
BTW, I tend to avoid using the interactive repl when I'm performing raw I/O because of the need for the (readc) hack you mentioned. I'd rather just write my code in a script and run it in batch mode.

-----

2 points by mpr 603 days ago | link

I was using defvar where I should have been using =. But the idea in my post was just to store the result of (prompt) in the variable x, which is meant to be a string.

And yes, now that I am moving past the experimental phase of my script, I will be running things in batch mode.

Thanks for the advice.

-----

3 points by jsgrahamus 602 days ago | link

Synchronicity at work. I was just wondering the same thing and have been working with tryarc.org

When I try the above I get:

  arc> (def prompt (msg)
        (pr msg)
        (readc)   ; grabs the newline
        (readline))
  #<procedure: prompt>
  arc> (prompt "Ask anything: ")
  Ask anything: nil
  arc>  
Not going to be changing tryarc.org. Any suggestions?

Steve

-----

2 points by akkartik 602 days ago | link

This works for me on Anarki. You don't need the readc anymore, that's fixed thanks to mpr below.

-----

1 point by jsgrahamus 602 days ago | link

Thanks.

Tried arclite and it, too, had problems with readline. However, its problem is that readline (and parse-format) depend upon readc, which is not defined.

-----

1 point by akkartik 602 days ago | link

Wow, I had to go lookup what arclite was :)

-----

3 points by jsgrahamus 602 days ago | link

Found it on the arc wiki.

-----

1 point by kinnard 604 days ago | link

I believe what you're looking to do is assign what's "read" in to a variable e.g.:

````

(def getUserPrompt ()

    (= msg readline)

    (pr msg)
)

````

-----

1 point by mpr 603 days ago | link

Yes, but I'm using the msg arg as the prompt text. Example:

    arc> (= x (prompt "> "))
    > this is the user text
    arc> x
    "this is the user text"
I ended up hacking the ac.scm file to throw away the first newline after an expr is (read). It works for now.

-----

2 points by kinnard 603 days ago | link

I think I understand. You want a function that prints arbitrary user prompts and then takes in user inputs?

You should share your hack!

-----

3 points by mpr 603 days ago | link

Yep, thats the idea. Anyway, here is my hack, in all its hackish glory:

    (define (trash-line c)
      (if (equal? c #\newline)
        '()
        (trash-line (read-char))))

    (define (tl2 interactive?)
      (when interactive? (display "arc> "))
      (on-err (lambda (c)
                (set! last-condition* c)
                (parameterize ((current-output-port (current-error-port)))
                  ((error-display-handler) (exn-message c) c)
                  (newline))
                (tl2 interactive?))
        (lambda ()
          (let ((expr (read)))

            ;; HACK located here
            (trash-line (read-char)) ; throw away until we hit the newline


            (if (eof-object? expr)
                 (begin (when interactive? (newline))
                        (exit)))
            (if (eqv? expr ':a)
                'done
                (let ((val (arc-eval expr)))
                  (when interactive?
                    (write (ac-denil val))
                    (newline))
                  (parameterize ((current-namespace (main-namespace)))
                    (namespace-set-variable-value! '_that val)
                    (namespace-set-variable-value! '_thatexpr expr))
                  (tl2 interactive?)))))))
So I call the trash-line function after the expr is read, but before it is eval'd by arc, so that there is not leading #\newline in the input buffer.

This does seem to work for the readline'ing I was doing yesterday. Probably doesn't handle all cases.

As akkartik mentioned above, this hack is obviated by running scripts in batch mode.

-mpr

Edit: this change is in my ac.scm file around line 1250

-----

2 points by akkartik 603 days ago | link

I like it! I don't think it'll break anything; can you send a pull request? Then we'll be able to run such code reliably at the repl! That would be sweet.

Edit 38 minutes later: hmm, there's one issue. Right now you can type multiple expressions in on a single line, but this change would drop everything after the end of the first expression. A better approach would be to drop only whitespace and stop at the very first non-whitespace character.

-----

2 points by mpr 603 days ago | link

Thanks! Yeah, I am aware of that bug, and was kind of ignoring it ;) I'll implement your suggested fix then send a pull request.

-----

3 points by mpr 603 days ago | link

Pull request submitted

-----