How do you make an interactive input loop in Racket? - racket

I'm trying to make a loop which asks for a command, executes the command, and then loops again. It only exits if the command entered is "exit". I have tried a couple different things, but when I run them they work once before (read-line) reads an # and then does this infinitely without waiting for input. My initial attempt looked like this:
(define (inputLoop)
(define command "init")
(do()((equal? command "exit") (display "exited successfully..."))
(display "What would you like to do?(start,stop,exit)")
(set! command (read-line))
(cond [(equal? command "start") (start)]
[(equal? command "stop") (stop)]
[else (void)])))
My next attempt was similar, but instead of using a do-loop, it just recursively called the function called inputLoop if the command was not "exit".
Here is a sample of the output:
What would you like to do?(start,stop,exit)start
What would you like to do?(start,stop,exit)
What would you like to do?(start,stop,exit)
...<thousands-of-lines-here>
What would you like to do?(start,stop,exit)
What would you like to do?(start,stop,exit)
What would you like to do?(start,stop,exit)exit
exited successfully...

This is a simple version of what you describe:
(define (input-loop)
(display "What would you like to do? (start,stop,exit)")
(define command (read-line))
(cond [(string=? command "start") (start) (input-loop)]
[(string=? command "stop") (stop) (input-loop)]
[(string=? command "exit") (displayln "exited successfully...")]
[else (displayln "unknown command") (input-loop)]))
You could also have an escape-continuation and break out of the loop; this comes closest to a classical while True: [...] break approach:
(define (input-loop)
(let/ec break
(let loop ()
(display "What would you like to do? (start,stop,exit)")
(define command (read-line))
(cond [(string=? command "start") (start)]
[(string=? command "stop") (stop) ]
[(string=? command "exit") (break)]
[else (displayln "unknown command")])
(loop)))
(displayln "exited successfully..."))
Note that in the second case, you can conveniently put the closing logic (display "exited successfully") at the end of the procedure, whereas in the first case you need to put it into the loop, otherwise the code may be executed several times.
Example execution on command-line (OS X):
pu#pumbair: ~/Projects/L-Racket racket input-loop.rkt
What would you like to do? (start,stop,exit)a
unknown command
What would you like to do? (start,stop,exit)starr
unknown command
What would you like to do? (start,stop,exit)start
start!
What would you like to do? (start,stop,exit)stop
stop!
What would you like to do? (start,stop,exit)exit
exited successfully...
using this (mock-up) version:
#lang racket
(define (start) (displayln "start!"))
(define (stop) (displayln "stop!"))
(define (input-loop)
(let/ec break
(let loop ()
(display "What would you like to do? (start,stop,exit)")
(define command (read-line))
(cond [(string=? command "start") (start)]
[(string=? command "stop") (stop) ]
[(string=? command "exit") (break)]
[else (displayln "unknown command")])
(loop)))
(displayln "exited successfully..."))
(input-loop)

After following #uselpa 's advice about checking my start and stop methods, I've come to the conclusion that my problem was in my start method. It created several places to run concurrently using the (place id body ...+) form documented in the racket documentation. The documentation states,
The bodys close only over id plus the top-level bindings of the enclosing module
Since my code had the call to the loop in the top-level, each created place executed the loop upon creation. My solution was instead to use the (dynamic-place module-path start-name) form documented here. I just had to move my code to an external file. There is no access to top-level bindings from the calling code for the place code using this method.
After making these changes, I was able to successfully run both of the loop techniques provided by #uselpa in their answer, without any infinite loops or errors.

Related

How do I make my Racket-designed language run scripts from external files?

I've got a custom language I'm designing in Racket, let's call it waffle.
Let's say I have
(define (printstr . input)
(if (string? (car input))
(write (string-join input ""))
(write input)))
; ... a whole bunch of other definitions
(command-line
#:multi
[("-v" "--verbose") "more verbose" (set! loglevel (add1 loglevel))]
[("-q" "--quiet") "be quiet" (set! loglevel 0)]
#:once-any
[("-i" "--in-place") "edit in-place" (set! mode 'in-place)]
[("-c" "--create-new") "create a new file" (set! mode 'new)]
[("-n" "--dry-run") "do nothing" (set! mode #f)]
#:once-each
[("-d" "--directory") dir "work in a given directory" (set! root dir)]
#:help-labels "operations to perform:"
#:multi
[("+l" "++line") "add a line" (set! ops `(,#ops "add"))]
[("-l" "--line") "delete a line" (set! ops `(,#ops "delete"))]
[("-e" "--edit") "edit a line" (set! ops `(,#ops "edit"))]
#:args (file)
(define in (open-input-file file))
; This is probably where I'm going wrong with how my language REPL evaluates files passed to it.
(eval (file->list file) ns))
Then I create an executable from DrRacket using 'Racket [menu] -> Create Executable ... -> [Type] Launcher'. Name is e.g. waffle-test.
I've got a file written in my waffle language, hello.waffle:
(printstr "Hello!")
I expect this to print 'Hello!' on the command line and then exit without errors. But I get a strange error I don't understand, and I get my prompt back without a newline.
$ ./waffle-test hello.waffle
application: not a procedure;
expected a procedure that can be applied to arguments
given: #<void>
arguments...: [none]
context...:
eval-one-top12
"/home/connie/Desktop/racket-ffgardfghf/waffle": [running body]
temp37_0
for-loop
run-module-instance!125
perform-require!78
"Hello!" $
I know you're not supposed to use eval but I can't find out how to make my language executable read and run files I pass to it. What is the best approach to doing this?
Just with this simple test I figured out a couple of things:
#!racket
(file->list "test.waffle")
With test.waffle as:
(waffle me)
(waffle it)
The repl printed:
((waffle me)
(waffle it))
However that is not valid code, even if waffle is a valid procedure. You need it to look like this:
(begin
(waffle me)
(waffle it))
Now you can do this by require your language to have it, but you can also just cons a begin to the resulting structure and eval will evaluate each top level form one by one in order.
You will run into problems with using eval. You'll know that soon enough. The correct way to make an interpreter is by creating your own eval that implements your languages syntax and takes en environment with primitives. It has an interface to use the host, but not regardless. With eval waffle programs has access to all internals and you're not really creating an interpreter since you just expose Racket.
I remember someone did the same with Ruby and had a web page and someone just tried to type in a command that deleted all the files on the system and the web service went away.
By fixing a few bugs in my code I managed to solve my problem!
I changed
(eval (file->list file) ns))
to
(define program (cons 'begin (file->list file)))
(eval program ns))
I also changed the 'printstr' function from
(define (printstr . input)
(if (string? (car input))
(write (string-join input ""))
(write input)))
to
(define (printstr input)
(displayln input))

How to call a racket procedure from terminal by giving command line argumands

I have a racket file called foo.rkt Inside that file I have a procedure called textify and it takes 2 parameters. Now how can I call this procedure from terminal ? What I want to do is simply:
> racket foo.rkt myfirstarg mysecondarg
and then I want this call to activate (textify myfirstarg mysecondarg) procedure. Is this possible ?
Here is the content of foo.rkt:
#lang racket
(require wxme)
(provide
(contract-out
[textify (-> path-string? path-string? void?)]))
(define (textify in out)
(call-with-input-file in
(λ (in-port)
(call-with-output-file out
(λ (out-port)
(copy-port (wxme-port->text-port in-port) out-port))
#:exists 'truncate))))
You can simply do this as the last expression in your file:
(apply textify (vector->list (current-command-line-arguments)))
If you are making a more advanced program that has switches you can use command-line that does this for you.

Calling CCL + Quicklisp script as executable with command line arguments and achieving the desired output

After discovering a very simple way to watch YouTube videos from the command line using my new Raspberry Pi 2 (running Raspbian) using only easily obtainable packages, namely:
omxplayer -o local $(youtube-dl -g {videoURL})
I immediately wanted a way to watch entire YouTube playlists that way. So I saw this as a perfect excuse to hack together a solution in Common Lisp :)
My solution (imaginatively dubbed RpiTube) is a script that, when given the URL of a YouTube playlist, searches the page's HTML source and extracts the URLs for the videos contained within it. I can then
pass these URLs to a Bash script that ultimately calls the above command for each video individually, one after the other. The Common Lisp script itself is complete and works, however I'm having difficulty invoking it with the URL as a command-line argument. This is mainly because I'm still quite new to Quicklisp, Lisp packages and creating executables from Common Lisp code.
I'm running Clozure Common Lisp (CCL) with Quicklisp (installed as per Rainer Joswig's instructions). I've included the complete code below. It may be a little inefficient, but to my amazement it runs reasonably quickly even on the Raspberry Pi. (Suggested improvements are appreciated.)
;rpitube.lisp
;Given the URL of a YouTube playlist's overview page, return a list of the URLs of videos in said playlist.
(load "/home/pi/quicklisp/setup.lisp")
(ql:quickload :drakma)
(ql:quickload "cl-html-parse")
(ql:quickload "split-sequence")
(defun flatten (x)
"Paul Graham's utility function from On Lisp."
(labels ((rec (x acc)
(cond ((null x) acc)
((atom x) (cons x acc))
(t (rec (car x) (rec (cdr x) acc))))))
(rec x nil)))
(defun parse-page-source (url)
"Generate lisp list of a page's html source."
(cl-html-parse:parse-html (drakma:http-request url)))
(defun occurences (e l)
"Returns the number of occurences of an element in a list. Note: not fully tail recursive."
(cond
((null l) 0)
((equal e (car l)) (1+ (occurences e (cdr l))))
(t (occurences e (cdr l)))))
(defun extract-url-stubs (flatlist unique-atom url-retrieval-fn)
"In a playlist's overview page the title of each video is represented in HTML as a link,
whose href entry is part of the video's actual URL (referred to here as a stub).
Within the link's tag there is also an entry that doesn't occur anywhere else in the
page source. This is the unique-atom (a string) that we will use to locate the link's tag
within the flattened list of the page source, from which we can then extract the video's URL
stub using a simple url-retrieval-fn (see comments below this function). This function is iterative, not
recursive, because the latter approach was too confusing."
(let* ((tail (member unique-atom flatlist :test #'equal))
(n (occurences unique-atom tail))
(urls nil))
(loop for x in tail with i = 0
while (< (length urls) n) do
(if (string= x unique-atom)
(setf urls (cons (funcall url-retrieval-fn tail i) urls)))
(incf i))
(reverse urls)))
;Example HTML tag:
;<a class="pl-video-title-link yt-uix-tile-link yt-uix-sessionlink spf-link " data-sessionlink="verylongirrelevantinfo" href="/watch?v=uniquevideocode&index=numberofvideoinplaylist&list=uniqueplaylistcode" dir="ltr"></a>
;Example tag when parsed and flattened:
;(:A :CLASS "pl-video-title-link yt-uix-tile-link yt-uix-sessionlink spf-link " :DATA-SESSIONLINK "verylongirrelevantinfo" :HREF "/watch?v=uniquevideocode&index=numberofvideoinplaylist&list=uniqueplaylistcode" :DIR "ltr")
;The URL stub is the fourth list element after unique-atom ("pl-video-title..."), so the url-retreival-fn is:
;(lambda (l i) (elt l (+ i 4))), where i is the index of unique-atom.
(defun get-vid-urls (url)
"Extracts the URL stubs, turns them into full URLs, and returns them in a list."
(mapcar (lambda (s)
(concatenate 'string
"https://www.youtube.com"
(car (split-sequence:split-sequence #\& s))))
(extract-url-stubs (flatten (parse-page-source url))
"pl-video-title-link yt-uix-tile-link yt-uix-sessionlink spf-link "
(lambda (l i) (elt l (+ i 4))))))
(let ((args #+clozure *unprocessed-command-line-arguments*))
(if (and (= (length args) 1)
(stringp (car args)))
(loop for url in (get-vid-urls (car args)) do
(format t "~a " url))
(error "Usage: rpitube <URL of youtube playlist>
where URL is of the form:
'https://www.youtube.com/playlist?list=uniqueplaylistcode'")))
First I tried adding the following line to the script
#!/home/pi/ccl/armcl
and then running
$ chmod +x rpitube.lisp
$ ./rpitube.lisp {playlistURL}
which gives:
Unrecognized non-option arguments: (./rpitube.lisp {playlistURL})
when I would at least have expected that ./rpitube.lisp be absent from this list of unrecognized arguments. I know that in Clozure CL, in order to pass a command line argument to an REPL session untouched, I have to separate them from the other arguments with a double hyphen, like this:
~/ccl/armcl -l rpitube.lisp -- {playlistURL}
But invoking the script like this clearly lands me in a REPL after the script has run, which I don't want. Additionally the Quicklisp loading information and progress bars are printed to the terminal, which I also don't want. (Incidentally, as Rainer suggested, I haven't added Quicklisp to my CCL init file, since I generally don't want the additional overhead i.e. few second's loading time on the Raspberry Pi. I'm not sure if that's relevant).
I then decided to try creating a standalone executable by running (once the above code is loaded):
(ccl:save-application "rpitube" :prepend-kernel t)
And calling it from a shell like this:
$ ./rpitube {playlistURL}
which gives:
Unrecognized non-option arguments: ({playlistURL})
which seems to be an improvement, but I'm still doing something wrong. Do I need to replace the Quicklisp-related code by creating my own asdf package that requires drakma, cl-html-extract and split-sequence, and loading that with in-package, etc.? I have created my own package before in another project - specifically because I wanted to split up my code into multiple files - and it seems to work, but I still loaded my package via ql:quickload as opposed to in-package, since the latter never seemed to work (perhaps I should ask about that as a separate question). Here, the rpitube.lisp code is so short that it seems unecessary to create a whole quickproject and package for it, especially since I want it to be a standalone executable anyway.
So: how do I change the script (or its invocation) so that it can accept the URL as a command-line argument, can be run non-interactively (i.e. doesn't open a REPL), and ONLY prints the desired output to the terminal - a space-delimited list of URLs - without any Quicklisp loading information?
Ok, I've managed to adapt a solution from the suggestion linked by user #m-n above. RpiTube now seems to work for most playlists that I have tried except some music playlists, which are unreliable since I live in Germany and many music videos are blocked in this country for legal reasons. Huge playlists, very high quality (or very long) videos might be unreliable.
The BASH script:
#! /bin/bash
#Calls rpitube.lisp to retrieve the URLs of the videos in the provided
#playlist, and then plays them in order using omxplayer, optionally
#starting from the nth video instead of the first.
CCL_PATH='/home/pi/ccl/armcl'
RPITUBE_PATH='/home/pi/lisp/rpitube.lisp'
N=0
USAGE='
Usage: ./rpitube [-h help] [-n start at nth video] <playlist URL>
where URL is of the form: https://www.youtube.com/playlist?list=uniqueplaylistcode
******** Be sure to surround the URL with single quotes! *********'
play()
{
if `omxplayer -o local $(youtube-dl -g "$1") > /dev/null`; then
return 0
else
echo "An error occured while playing $1."
exit 1
fi
}
while getopts ":n:h" opt; do
case $opt in
n ) N=$((OPTARG - 1)) ;;
h ) echo "$USAGE"
exit 1 ;;
\? ) echo "Invalid option."
echo "$USAGE"
exit 1 ;;
esac
done
shift $(($OPTIND - 1))
if [[ "$#" -ne 1 ]]; then
echo "Invalid number of arguments."
echo "$USAGE"
exit 1
elif [[ "$1" != *'https://www.youtube.com/playlist?list='* ]]; then
echo "URL is of the wrong form."
echo "$USAGE"
exit 1
else
echo 'Welcome to RpiTube!'
echo 'Fetching video URLs... (may take a moment, especially for large playlists)'
urls="$(exec $CCL_PATH -b -e '(progn (load "'$RPITUBE_PATH'") (main "'$1'") (ccl::quit))')"
echo 'Starting video... press Q to skip to next video, left/right arrow keys to rewind/fast-forward, Ctrl-C to quit.'
count=0
for u in $urls; do #do NOT quote $urls here
[[ $count -lt $N ]] && count=$((count + 1)) && continue
play "$u"
echo 'Loading next video...'
done
echo 'Reached end of playlist. Hope you enjoyed it! :)'
fi
I made the following changes to the CL script: added the :silent option to the ql:quickload calls; replace my own ocurrences function with the built-in count (:test #'equal); and most importantly several things to the code at the end of the script that actually calls the URL-fetching functions. First I wrapped it in a main function that takes one argument, namely the playlist URL, and removed the references to *command-line-argument-list* etc. The important part: instead of invoking the entire rpitube.lisp script with the URL as a command line argument to CCL, I invoke it without arguments, and instead pass the URL to the main function directly (in the call to exec). See below:
(defun main (url)
(if (stringp url)
(loop for u in (get-vid-urls url) do
(format t "~a " u))
(error "Usage: rpitube <URL of youtube playlist>
where URL is of the form:
'https://www.youtube.com/playlist?list=uniqueplaylistcode'")))
This method could be applied widely and it works fine, but I'd be amazed if there isn't a better way to do it. If I can make any progress with the "toplevel" function + executable idea, I'll edit this answer.
An example working invocation, run on a small playlist of short videos, with playback beginning at the 3rd video:
$ ./rpitube -n 3 'https://www.youtube.com/playlist?list=PLVPJ1jbg0CaE9eZCTWS4KxOWi3NWv_oXL'
Many thanks.
I looked at this some and would like to share what I found. There are also several Lisp libraries which aim to facilitate scripting, executable building, or command-line argument handling.
For your executable building approach, save-application lets you specify a :toplevel-function, a function of zero arguments. In this case you will need to get the command line arguments through ccl:*command-line-argument-list*, and skip the first element (the name of the program). This is probably the minimal change to get your program running (I haven't run this; so it may have typos):
(defun toplevel ()
(let ((args #+clozure *command-line-argument-list*))
(if (and (= (length args) 2)
(stringp (second args)))
(loop for url in (get-vid-urls (second args)) do
(format t "~a " url))
(error "Usage: rpitube <URL of youtube playlist>
where URL is of the form:
'https://www.youtube.com/playlist?list=uniqueplaylistcode'"))))
(save-application "rpitube" :prepend-kernal t :toplevel-function #'toplevel)
Alternatively, some Lisp implementations have a --scpript command-line parameter which allows something similar to your #!/home/pi/ccl/armcl script to work. CCL doesn't seem to have an equivalent option, but a previous answer -- https://stackoverflow.com/a/3445196/2626993 -- suggests writing a short Bash script which would essentially behave like you hoped CCL would with this attempt.
quickload calls can be silenced with an argument:
(ql:quickload :drakma :silent t)

variable defined inside a procedure is kept?

I wrote a procedure (do-test).
Since the test might have some effects on the variable env,
I defined env inside do-test hoping that env would not be carried
with the procedure, so everytime I run it, I will get a fresh environment to work with.
To my surprise, my test procedures are actually carrying the previous env.
Please find the follwing code:
(define (do-test)
(define env '(1))
;(define env (list 1))
(display env)
(if (not (equal? (car env) 1))
(error "assertion failed.")
'ok)
(set-car! env 2)
'ok)
(do-test)
(do-test)
I've tried to run this code using mit-scheme / guile / codepad.org, and all of which
told me runing (do-test) twice yielded different results.
But if I change the line (define env '(1)) to (define env (list 1)), I will get the expected result.
(you can find my code before and after the change in codepad.org)
To my knowledge, '(1) and (list 1) should be the same except that the second will the call procedure list.
I'm wondering why this happens and how can I prevent the program from reusing previous values?
Please take a look at R5RS, which says that
it is an error to alter a constant (i.e. the value of a literal
expression) using a mutation procedure like set-car! or
string-set!.
So '(1) and (list 1) are not exactly the same thing: when you are intended to build some data that would be mutated in the future, don't use quotations.
If you try to do this:
(define (f) (list 1))
(define (g) '(1))
(display (eq? (f) (f)))(newline)
(display (eq? (g) (g)))(newline)
You will get:
#f
#t
This is suggesting that f will make a list whenever called. But g will treat its list '(1) as a constant and that list is allocated only once, no matter how many times it is called.

AUCTeX: Run Compile Command n-times

I'd like to have a function that asks for a number n and executes the default compile command n-times afterwards. That is to say unlike C-c C-c (i.e. TeX-command-master) I don't want to be asked which command to run, it should select the default compile command based on the AUCTeX settings. Naturally if any error occurs the execution should stop.
I know about TeX-texify, however, this doesn't statisfy my needs because sometimes I just want emacs to run pdflatex five times indepent of what the AUCTeX parser thinks is adequate.
Any help is much appreciated!
Edit: I have looked into this a little further and using code from the above reference I have started writing a function that does this. However, it has one major flaw. Let me first give you the code:
(defcustom TeX-MultiTeX-Command "LaTeX" "Default MultiTeX command" :type 'string :group 'TeX-command)
(defun TeX-MultiTeX (n)
"Run TeX-command n-times"
(interactive "nRun TeX/LaTeX how many times: ")
(while (> n 0)
(TeX-command TeX-MultiTeX-Command 'TeX-master-file)
(setq n (- n 1))))
As you can see, I have implemented a config variable for selecting the correct compilation command. Now let me present the problem:
The compilation of the LaTeX document takes some time, however, my function instantly calls the second (and following) executions of the compile command. Maybe someone can provide help in finding a solution that checks whether compilation has finished successfully prior to executing (TeX-command TeX-MultiTeX-Command 'TeX-master-file), then executes said function or prints some error message if compilation finished with an error.
With the help of the code of the TeX-texify function I have developed a function that does what I want, the code is given below.
I'd like to thank user4815162342; although this solution is not based on his suggestion, I think his solution might be of use for a different problem. Also I'd like to thank TN, the author of TeX-texify, I shamelessly took and adapted his code for my problem. ;)
(defcustom TeX-MultiTeX-Command "LaTeX"
"Default MultiTeX command"
:type 'string :group 'TeX-command)
(defun TeX-MultiTeX-sentinel (&optional proc sentinel)
"Non-interactive! Call the standard-sentinel of the current LaTeX-process.
If there is still something left do do start the next latex-command."
(set-buffer (process-buffer proc))
(funcall TeX-MultiTeX-sentinel proc sentinel)
(let ((case-fold-search nil))
(when (string-match "\\(finished\\|exited\\)" sentinel)
(set-buffer TeX-command-buffer)
(unless (plist-get TeX-error-report-switches (intern (TeX-master-file)))
(TeX-MultiTeX TeX-MultiTeX-num-left)))))
(defun TeX-MultiTeX (n)
"Run TeX-command n-times"
(interactive "nRun TeX/LaTeX how many times: ")
(when (or (called-interactively-p 'any)
(null (boundp 'TeX-MultiTeX-num-left)))
(setq TeX-MultiTeX-num-left n))
(if (>= TeX-MultiTeX-num-left 1)
(progn
(TeX-command TeX-MultiTeX-Command 'TeX-master-file)
(setq TeX-MultiTeX-num-left (- TeX-MultiTeX-num-left 1))
(setq proc (get-buffer-process (current-buffer)))
(setq TeX-MultiTeX-sentinel (process-sentinel proc))
(set-process-sentinel proc 'TeX-MultiTeX-sentinel))))
It seems that you need a synchronous way to run TeX-command. I haven't word with TeX-command, but if it uses the compilation API, it can be made to wait for the compilation to finish, although it's not exactly obvious how to do that. Here is an example that uses compilation-finish-functions to achieve the desired effect:
(require 'cl) ; for lexical-let
(defun compile-and-wait (compilefun)
(interactive)
(lexical-let ((done nil) finish-callback)
(setq finish-callback
;; when the compilation is done, remove the callback from
;; compilation-finish-functions and interrupt the wait
(lambda (buf msg)
(setq compilation-finish-functions
(delq finish-callback compilation-finish-functions))
(setq done t)))
(push finish-callback compilation-finish-functions)
(funcall compilefun)
(while (not done)
(sleep-for .1))))
EDIT
AUC TeX is not using compilation mode to spawn TeX, so the above cannot work. Since it's still useful for other compilation buffers, I'm leaving it in the answer. Another way to implement TeX-MultiTeX is by binding TeX-process-asynchronous to nil, which should ensure that AUC TeX waits for the command to finish.