Wednesday, December 20, 2017

Helping bash and tinyscheme get along | Friendlier command line execution of scheme code

I was looking for a cleaner way to manage SDR Touch radio frequencies and I thought I'd give Scheme S-expressions a go.

I quickly arrived at this 'format':


(("Arl,VA"
  (166.9 "DC Police?")
  (166.7 "Dc Police?"))

 ("OnABoat"
  (462 "FRS Low")
  (467 "FRS High")
  (457.5 "Marine1")
  (467.5 "Marine2")
  (467.7 "Marine3"))

 ("NCL"
  (457.5250 "Bridge Ops")
  (457.5500 "Engineering")
  (457.5750 "???"))

Because I programmed the solution in Scheme, turning this basic set of S-expressions into the required mess of XML was straightforward. You can find the solution here. I have to say, it was a real delight using S-Expressions to solve this one. XML, JSON and raw text all jumped out at me as options, but in this case, S-Expressions were the clear way to go.

I coded this solution on my Android device using Termux, emacs and Tinyscheme.

While the solution worked great under emacs' interactive scheme mode, I decided I wanted the ability to run the conversion command from a bash prompt. While tinyscheme has some basic command line handling, it was lacking a few details, such as the ability to get the script's working directory. This may seem like an obscure requirement, but with this value I'm able to reliably (load ...) files without worrying about running the script from a specific directory.

To make tinyscheme more command line friendly, I created tsexec, a small script below that kicks off tinyscheme with a number of helpful functions predefined. These include:

(cmd-arg0) - returns the name of scheme script that is currently executing.

(cmd-args) - returns the list of command line arguments passed to the currently executing script.

(cmd-dir . path) - when invoked with no arguments, returns the directory the currently executing script lives in. When an argument is provided, the value is prefixed with the source script's directory.

Using these functions I'm was able to write a command line friendly version of my scheme code to generate XML.

(load (cmd-dir "utils.scm"))  ;; utils.scm is now imported in a location independent way

(define (make-presets frequency-file xml-file)
  (with-output-to-file xml-file
    (lambda ()
      (with-data-from-file frequency-file
       (lambda (data)
  (show "<?xml version='1.0' encoding='UTF-8'?>")
  (show "<sdr_presets version='1'>")
  (for-each (lambda (category)
       (show "<category id='" (gen-id) "' name='" (car category) "'>")
       (for-each (lambda (preset)
     (show "<preset id='" (gen-id) "' ")
     (show "        name='" (cadr preset) "' ")
     (show "        freq='" (freqval (car preset)) "' ")
     (show "        centfreq='" (freqval (car preset)) "' ")
     (show "        offset='0'")
     (show "        order='" (gen-id) "' ")
     (show "        filter='10508' ")
     (show "        dem='1' ")
     (show "/>"))
          (cdr category))
       (show "</category>"))
     data)
  (show "</sdr_presets>"))))))
  

;; Simple argument handling below
(cond
 ((= 1 (length (cmd-args)))
  (make-presets (car (cmd-args)) "SDRTouchPresets.xml"))
 ((= 2 (length (cmd-args)))
  (make-presets (car (cmd-args)) (cadr (cmd-args))))
 (else
  (show "Need to provide frequency and output file to continue.")))

Here's a few screenshots of me running this code:

Termux has a handy add-on that allows you to invoke shell scripts from your phone's home screen. I'm thinking tsexec may be an ideal way to connect up this short-cut ability to tinyscheme.

Finally, here's the source code for tsexec. Enjoy!

#!/data/data/com.termux/files/usr/bin/bash

##
## Execute a tinyscheme script. Setup various functions that can
## be used to access command line args and such
##

if [ ! -f "$1" ] ; then
    echo "Refusing to run non-existant script [$1]"
    exit 1
fi

CMD_DIR=$(pwd)/$(dirname $1)
CMD_ARG0=$(basename $1)
CMD_MAIN="$CMD_DIR/$CMD_ARG0"
shift

CMD_ARGS=""
for arg in "$@" ; do
  CMD_ARGS="$CMD_ARGS \"$arg\""
done


if [ ! -f "$CMD_MAIN" ] ; then
    echo "Failed to derive CMD_DIR, guessed: $CMD_DIR"
    exit 2
fi

wrapper() {
  cat <<EOF
;; Autogenerated scheme wrapper script
(define (cmd-arg0) 
  "$CMD_ARG0")

(define (cmd-dir . path)
  (if (null? path)
      "$CMD_DIR"
      (string-append "$CMD_DIR/" (car path))))

(define (cmd-args)
  (list $CMD_ARGS))

(load "$CMD_MAIN")
EOF
}

wrapper > $HOME/.ts.$$
tinyscheme $HOME/.ts.$$
rm $HOME/.ts.$$

4 comments:

  1. you might want to try chibi scheme instead. it's still quite tiny but has full r5rs/r7rs scheme with a good ffi. you can compile out unneeded features too.

    ReplyDelete
  2. Thanks for the tip John.

    tinyscheme was an easy option because Termux allows for trivial installation. Just do:

    pkg install tinyscheme

    But I bet I could get chibi to compile on my device directly. I'll give that a try. Thanks!

    ReplyDelete
  3. I've found myself wanting to do similar sexpr-based data munging and conversion in the past...

    Now you've built all that yourself you have no need of this (and I've no idea how easy it is to get Chicken on an Android device these days), so this is presented purely for interest, but I've used Chicken to build a set of s-expression processing commands meant to be used in shell pipelines - that includes stuff like sexpr<->xml interop :-)

    Check out the examples: https://www.kitten-technologies.co.uk/project/magic-pipes/

    ReplyDelete
  4. Arlaric -

    I'd never considered mashing up s-expressions and shell pipes, but looking at your examples, it makes a lot of sense. Very cool.

    This is awesome - thanks for sharing.


    ReplyDelete