Arc Forumnew | comments | leaders | submitlogin
Arc usable on Unix (include ability to run scripts!)
14 points by palsecam 5582 days ago | 9 comments
Fellow Arcists,

I worked lately to make Arc be more friendly with Unix, the 40 Years Old OS. I have now a solution that does everything that I want (including running scripts), so I thought you may like it too.

Let's see the results in practice, step by step:

1. Interaction with an human works as previously, except than Arc now exits on ^D (EOF):

   $ arc
   arc> (+ 40 2)
   42
   arc> ^D
   (exit)
   $
^D is Control-D and means "End-Of-File". It's the right behaviour to exit when this special character is read (try in bash, python, ruby, bc, ...). Once you're used to it (takes 5 secs), ^D is handy ('cause universal) and sooooo much quicker than typing "(quit)".

As you can see, Arc displays "(quit)" before to exit when it catches EOF. This is not mandatory, even a violation of the "Silence is gold" rule, but bash does that too, and I like that.

2. The EOF stuff is very not useless. Once we get it, we automatically get the ability to do (and this is very important):

  $ echo '(prn "Hello, pipes & co world!")' | arc
  Hello, pipes & co world!
  $ echo '(prn "Hello, scripts!")' > tmp.arc
  $ arc < tmp.arc
  Hello, scripts!
  $
Because you know, stdin is really just like any file. (Currently, since Arc doesn't handle EOF very well, trying the previous examples will result in an infinite loop where Arc displays "#<eof>: Bad object in expression" again and again).

3. Smart readers will have remarked that the previous example should have given:

  $ echo '(prn "Hello, pipes & co world!")' | arc
  arc> (prn "Hello, pipes & co world")
  Hello, pipes & co world!
  "Hello, pipes & co world!"
  arc> (quit)
  $
They are right. But we actually can make a distinction between human input ("interactive") and non-human input. For instance, bash does that. See:

  $ cat ~/.bashrc
  ...
  # If not running interactively, don't do anything
  [ -z "$PS1" ] && return
  ...
So we do that too. We check if stdin is from a terminal, and if so, we display the prompt, and each evaluation result. If not, no prompt, and only explicit printing (via 'prn/'write/'etc.) will be displayed. (If you prefer to have the eval results always printed, change the value of not-interactive-write-evalres? in the patched ac.scm.)

4. In 2. there is a "Hello, scripts!" example. Actually, this is a lie. At this point, we can't execute a script that will interact with the user (e.g: call 'read). But remember, often when you do:

   $ python some-fake-script-doing-no-interaction.py
   $ wc some-file.txt
what you mean is actually (in the True Pedantic Unix Way):

   $ python < some-fake-script-doing-no-interaction.py
   $ wc < some-file.txt
But you don't care about the pedantic note above and just want to run scripts, ones that interact with the user and ones that don't, and without having to type '<' each time. Well, here you have (text between << and >> indicates input by the user):

   $ cat example-script.arc
   #!/usr/bin/env arc
   (pr "Enter your name: ")
   (prn "Hello, " (readline) "!")
   $ arc example-script.arc
   Enter your name: <<palsecam>>
   Hello, palsecam!
Or even:

   $ chmod +x example-script.arc
   $ ./example-script.arc
   Enter your name: <<palsecam>>
   Hello, palsecam!
5. Last little thing, the errors are now displayed on stderr. This changes nothing since by default stderr == stdout, but if you understand the power of redirections, you may appreciate.

6. Oh, and to run scripts, I was obliged to make Arc works even when not called in its root directory (current/loading directory stuff, in as.scm).

7. That's all. At this point, Arc is usable on Unix IMO, but if you think that something is missing, please tell me so!

----------

The patch will be posted in a comment.

It modifies as.scm and ac.scm, and is 88 lines long (generated with diff -Nurp). 88 lines may seem a lot, but it's not that big when you remove the context lines and the comments. Plus, there is no complex or tricky modifications.

The `arc' command is this little shell program (you can use something different, an alias for instance, but "./example-script.arc" would not work then):

   #!/bin/sh
   ## arc: launcher for Arc. Put this file in your $PATH and chmod +x it.

   ARC_DIR="/home/paul/arc/3.1/"   # change this, obviously
   rlwrap mzscheme -qr ${ARC_DIR}as.scm $@  # $@ holds the list of cmdline args
   # notice the mzscheme switch is "-qr" and not "-f" here

Happy Arc hacking on Unix, or, why not, Unix hacking with Arc!


3 points by palsecam 5582 days ago | link

The patch:

   diff -Nurp 3.1orig/ac.scm 3.1/ac.scm
   --- 3.1orig/ac.scm	2009-08-10 00:42:57.000000000 +0200
   +++ 3.1/ac.scm	2009-08-10 18:59:37.000000000 +0200
   @@ -1127,29 +1127,40 @@
    (define last-condition* #f)
    
    (define (tl)
   -  (display "Use (quit) to quit, (tl) to return here after an interrupt.\n")
   -  (tl2))
   +  (let ((interactive? (terminal-port? (current-input-port))))
   +;    (when interactive? (display "Use (quit) or ^D to quit, blah blah...\n"))
   +    (tl2 interactive?)))
    
   -(define (tl2)
   -  (display "arc> ")
   +(define not-interactive-write-evalres? #f)
   +
   +(define (tl2 interactive?)
   +  (when interactive? (display "arc> "))
      (on-err (lambda (c) 
                (set! last-condition* c)
   -            (display "Error: ")
   -            (write (exn-message c))
   -            (newline)
   -            (tl2))
   +	    (parameterize ((current-output-port (current-error-port)))
   +	      (display "Error: ")
   +	      (write (exn-message c))
   +	      (newline))
   +            (tl2 interactive?))
        (lambda ()
          (let ((expr (read)))
   -        (if (eqv? expr ':a)
   -            'done
   -            (let ((val (arc-eval expr)))
   -              (write (ac-denil val))
   -              (namespace-set-variable-value! '_that val)
   -              (namespace-set-variable-value! '_thatexpr expr)
   -              (newline)
   -              (tl2)))))))
   +	(if (eof-object? expr)
   +	    (when interactive? (display "(quit)") (newline))
   +	    (let ((val (arc-eval expr)))
   +	      (when (or interactive? not-interactive-write-evalres?)
   +		    (write (ac-denil val)) 
   +		    (newline))
   +	      (namespace-set-variable-value! '_that val)
   +	      (namespace-set-variable-value! '_thatexpr expr)
   +	      (tl2 interactive?)))))))
   +
   +; 'aload{1} could be rewritten to just call 'tl (that's part of why
   +; we test for EOF in 'tl2 and not in 'ac), but need to modify 'tl 
   +; to take a file as parameter, and not always read stdin.
   +; Can't trick 'aload{1} using 'with-input-from-file, because there 
   +; would be a problem if the loaded file calls 'read.
    
   -(define (aload1 p)
   +(define (aload1 p) 
      (let ((x (read p)))
        (if (eof-object? x)
            #t
   diff -Nurp 3.1orig/as.scm 3.1/as.scm
   --- 3.1orig/as.scm	2009-08-10 00:42:57.000000000 +0200
   +++ 3.1/as.scm	2009-08-10 18:57:13.000000000 +0200
   @@ -1,16 +1,14 @@
   -; mzscheme -m -f as.scm
   -; (tl)
   -; (asv)
   -; http://localhost:8080
   -
    (require mzscheme) ; promise we won't redefine mzscheme bindings
    
    (require "ac.scm") 
    (require "brackets.scm")
    (use-bracket-readtable)
    
   -(aload "arc.arc")
   -(aload "libs.arc") 
   -
   -(tl)
   +(parameterize ((current-directory (current-load-relative-directory)))
   +  (aload "arc.arc")
   +  (aload "libs.arc"))
    
   +(let ((args (vector->list (current-command-line-arguments))))
   +  (if (not (empty? args))  ; cmdline args are script filenames...
   +      (for-each (lambda (f) (aload f)) args)  ; ...execute them!
   +      (tl)))
Also available (for direct d/l) at http://pastebin.com/f56fbe59e

-----

2 points by patchfrus 5576 days ago | link

Hm. Snif. How do I apply this patch? I said patch -p1 < patch, and only garbage was found. Then I said patch -u -p1 < patch and only garbage was found. Is there anything to be found in this patch that is not garbage? LOL.

%patch -p1 < unix-patch-2.txt patch: Only garbage was found in the patch input.

-----

1 point by palsecam 5576 days ago | link

Ooops yes, apparently the file on pastebin is "corrupted", I don't know why. "@@" are missing. Maybe a bad copy/paste, but this is suprising. Anyway:

(Good) patch re-uploaded at http://dabuttonfactory.com/res/arc-unix.patch

> How do I apply this patch?

   mkdir arc && cd arc && \
   curl ycombinator.com/arc/arc3.1.tar | tar -x && \
   mv arc3.1 3.1orig && cp -r 3.1orig 3.1 && \
   curl dabuttonfactory.com/res/arc-unix.patch | patch -p0
But all this is assuming you want to copy my arborescence:

   arc/
     3.1/       # "my" version w/ patchs
     3.1orig/   # "official", ORIGinal version
     3.0/
     ...
Maybe you'd better look at the diff and manually apply the patch. A good way to start looking at Arc guts ;-) This was how I expected interested people to use the diff. But using `patch' directly is a good solution too :-)

-----

5 points by rntz 5582 days ago | link

Don't get me wrong, I like the idea, and I'll probably incorporate the auto-detection of interactive terminals and relative load-path fix to anarki (we already have eof-detection, thank you very much :), but the implementation has a rather significant not-exactly-bug-but-nonetheless-undesired-effect: it assumes that when (tl2) exits, the arc process itself will die, so it doesn't call (exit) explicitly.

This assumption is emphatically not the case when mzscheme is run with the options I prefer (-mf for mzscheme 372, -qif for mzscheme 4); for when the arc (tl) quits, we drop down into mzscheme. This is extremely useful, and is the purpose of the (if (eqv? expr ':a) 'done ...) check in 'tl2 that you eliminate (you don't even comment it out!) with this patch. So, with your patch applied, a) there's no proper way to drop back to the scheme interpreter, and b) if you run arc with the scheme REPL underneath (the -i/--repl flag), it will not in fact exit on eof, but will drop back to scheme.

-----

2 points by rntz 5582 days ago | link

I have ported (and pushed) the changes your patch makes to anarki, with some modifications (avoiding the complaints I make in the parent post); while the actual changes are spread across several commits, a summary diff can be found at http://sprunge.us/SWVF?diff; note that this diff is not relative to arc3.1.tar, but to anarki before I ported the changes, so it will look a little different. I've also omitted changes to arc.sh, because they will be wholly unfamiliar to anyone not using anarki. Instead, you can take a look at the entirety of arc.sh, which is anarki's script for running arc, at http://sprunge.us/XPWN?bash.

-----

2 points by tc-rucho 5582 days ago | link

I really never got my hands on making Arc work with scripts because of the load time. Anyway, good hack, I know it will come in handy

-----

2 points by palsecam 5582 days ago | link

This is a major problem. I agree with you, and I don't see myself writing a lot of Arc scripts in the current situation.

Maybe a client/server would be a way to solve the problem. I may try something like that one of these days. I mean, launch Arc once as a daemon, then talk to it with a lightweight client, via FIFOs (named pipes) or something.

For the moment, you may want to comment the imported arc libraries you don't use (in libs.arc or as.scm), for instance all the HTTP/HTML stuff. Loading only arc.arc, my load time is reduced by 50%.

-----

1 point by Thr4wn 5581 days ago | link

Also remember, that you can add with-readline (http://www.greenend.org.uk/rjk/2005/withreadline.html) for convenience.

-----

1 point by palsecam 5581 days ago | link

Thanks Thr4wn, but isn't ``with-readline'' +/- the same thing than ``rlwrap''? Does ``with-readline'' have something extra?

(rlwrap is used here in the shell launcher)

-----