Copyright © 1998, 2001 by Ian Horswill, see comment within this file.

Using the Scheme48 environment

Using the Scheme48 environment

The GRL compiler is written for the Scheme48 system.

The prompt

When you type at scheme, you're typing at the command processor.  It prints a prompt, reads a command, runs it, and prints the response.  As with C++, Java, or Common Lisp, Scheme48 allows you to structure your code into modules called packages.  The Scheme prompt tells you what package you're in, and whether you're debugging an error:

Prompt Meaning
> Everything's normal.  You're entering code into the default package and there are no errors.
1> You're in the normal package and you've generated an error.  To get out of the error, type ,pop or ,reset.
2> You generated an error, and then while debugging that error, you generated another error.  Higher numbers mean higher levels of nesting of errors.  To return to the previous error, type ,pop, to stop debugging entirely, type ,reset.
packagename> Everything's normal, but you're entering code into the package named packagename.  To go back to the default package, type ,user.
1 packagename> Some code residing in the package packagename generated an error.  Again, higher numbers mean nested errors.  Type ,pop to return to the previous error or ,reset to quit debugging altogether.
inspect:  You're in the object browser (the "inspector"):
  • To look at one of the slots of the object being inspected, type the number listed next to it
  • To go back to whatever object you were previously browsing, type u (for "up").
  • If you're looking at a stack frame, you can also type d (for "down") to look at the the stack frame of whoever called it.
  • To get out of the object inspector and return to the command processor, type ,pop or ,reset.

The command processor  reads lines from the keyboard and runs them.  Usually, the things you type are definitions to be compiled, or expressions to be evaluated.  However, there are also a few special commands that get handled specially by the command processor.  These start with a comma:

Command Meaning
,help Prints a list of all the commands
,inspect expression Evaluates expression and allows you to browse the slots of whatever data structure it returns.  Return to normal execution using ,pop or ,reset.
,debug Allows you to browse the stack.  Return to normal execution using ,pop or ,reset.  If you are just running GRL code, you shouldn't have to concern yourself with this unless you're doing something arcane.
,preview Displays the scheme equivalent of the call stack.  Stack frames in scheme are called "continuations" (for good reasons with which you need not concern yourself).  The implementors of Scheme48 thought of this command as showing you what things were going to be run if the program kept running, rather than showing what things had been run in the past to get to this point.  Hence the name "preview".  If you are just running GRL code, you shouldn't have to concern yourself with this unless you're doing something arcane.
,load filename Evaluates all the code in filename as if it had been typed into the scheme prompt by hand
,pop Aborts debugging of the current error and returns to the previous error, if any
,reset Aborts all debugging and returns to the top-level command interpreter
,in package Evaluates all subsequent commands within the environment of package
,in package command Evaluates command within the environment of package
,exit Kills scheme

Interrupting your program

To interrupt your scheme or GRL program, or to kill the GRL compiler if it somehow gets into an infinite loop, type Control-C Control-C (from inside emacs) or type whatever you type on your host operating system to interrupt a process (usually Control-C).  To get rid of scheme, type ,exit.

Loading GRL code using Emacs

You can type GRL definitions directly at the scheme prompt.  However, you'll almost always want to enter your code into a .scm file and load it from there into scheme.  To begin entering a GRL program, go into emacs and type Control-x f (find file) to load a file into an Emacs window.  Type the name of the file you want to edit and hit return.  Make sure the that file name ends with .scm so that Emacs understands that it's code destined for the Scheme interpreter.  If the file doesn't exist, Emacs will create a blank buffer with than name.  Now enter a GRL definition like (define-signal test #f), which says that test should be a constant-valued signal with the value false.  Make sure the cursor is either inside of this definition or after the closing parenthesis, then type Control-Meta-x (i.e. hold down control and alt, then press x).  Emacs will transmit this definition to scheme.  If you continue entering definitions, you can continue to load them until you are ready to try compiling some code (see next section).  You can also change an existing definition and reload it by typing C-M-x again.  

If you're starting work on a program you've previously entered, you won't want to go through the file and type C-M-x on each definition. You can load all the code in the file into scheme by typing ",load filename" (the comma is not a misprint) at the scheme prompt.  Alternatively, you can type Control-c l inside Emacs.  For simple programs, this is the simplest way to start up.  For more complicated programs, you'll want to use the Scheme48 package system to create a set of packages and load them as a unit.

Compiling GRL code

To compile your GRL program, type (compile signal-name ...) at the scheme prompt.  This will compile the signals you specify, along with all their inputs, their inputs' inputs, etc.  The most common structure for a GRL program is something like:

(define-signal sensor1 ... read the sensor ...)
(define-signal sensor2 ... read the sensor ...)
...
(define-signal sensorn ... read the sensor ...)

(define-signal behavior1 (behavior activation-level-code  motor-vector-code))
(define-signal behavior2 (behavior activation-level-code  motor-vector-code))
...
(define-signal behaviorn (behavior activation-level-code  motor-vector-code))

(define-signal base-controller (drive-base (behavior-or behavior1 behavior2 ... behaviorn)))

It has a set of interfaces to the sensors at the top, some behaviors that compute commands and activation levels from the sensor signals, and then a call to some interface transducer (drive-base in this example) to transmit the final motor command to the base.  To compile this program, you would only need to type (compile base-controller), since the behaviors are inputs to base-controller and the sensor signals are inputs to the behaviors.  If you change the program so that it doesn't use one of the sensor signals, the compiler will automatically omit its code from the compiled image.

Caveat: If you try to type (compile (drive-base (behavior-or behavior1 behavior2))) rather than (compile base-controller) you will get an error.  The reason is that drive-base and behavior-or are not scheme procedures and so the scheme interpreter doesn't know how to process calls to them.  They are data structures representing a transducer and a signal procedure, respectively.  The reason you can "call" them in the definitions above is that define-signal is really a macro that transforms the "calls" into scheme code that creates data structures to represent the new signals you're defining.  To a first approximation, you can pretend that you can only call transducers and signal procedures inside of define-signal and you can only pass raw names like base-controller to compile.

The compile procedure compiles signals to scheme code and prints it on the screen.  This isn't very useful for most users, so you'll generally want to use some procedure other than compile.  For example, compile-to-c in package c-generator (type ,open c-generator to access it) will compile the signals to C code and display it on the screen, while compile-to-c-file will compile it to C code and write it to a file.  You can use try-signals in package scheme48-run-time (type ,open scheme48-run-time to access it) to compile signals to scheme code and run them immediately within Scheme48 itself.  This can be useful to hand-test transducers from the keyboard.  If your robot has enough of a computer to run Scheme48 itself, then you can also have Scheme48 drive the robot directly and and never have to cross-compile anything.

Packages

Packages (also called structures) are the equivalent of the namespaces of C++ or Java or the packages of Common Lisp.  They're lexical environments that can import and export bindings.  A given package is defined by the set of packages it imports from, the set of bindings it exports, and the set of files that contain its definitions.  The GRL distribution is divided into the following packages.  To access a package, type ",open package-names ...". 

Making your own packages

At any given time, the Scheme48 command processor is operating "inside" some specific package.  That is, it will attempt to resolve any variable names you reference by checking for bindings in that package and in the export lists of those packages it imports from.  You can change the package you're in by typing:

,in package-name

at which point, the command processor's prompt will change to "package-name>".  You can also type ",in package-name  command" to just run command in package-name and then return to the package you came from.

You can add a package to the import list of your package by saying ",open package-names ...".

Packages are bound to names, so of course there has to be a package that contains those bindings.  It's called the configuration package or just "config" for short.  Config doesn't have a binding for itself, so you can't get at it with the ,in command.  To evaluate something in the config package, you say ",config command" instead.

To create a new package, you use the define-structure macro, which is available inside the config package:

(define-structure package-name export-list
  (open package-names ...)
  (files source-file-names ...))

For example:

(define-structure my-package (export my-proc my-signal)
  (open scheme grl-language grl-library)
  (files my-code))

This says that my-package is a new package that opens scheme, grl-language, and grl-library, exports the procedure my-proc and the signal my-signal, and whose code is found in my-code.scm in the current directory.  Notice that:

Important: if you reevaluate the define-structure macro, the command processor will actually create a new package and bind it to the package name you provided, blowing away your only pointer to the old version of the package.  So you almost never want to evaluate define-structure more than once per session.  If you want to load more code into the package, you can just say ",load filenames ...".  If you want to import something new, you can just use the ,open command.  Adding exports is more tricky, however.  What you need to do is to not use the export procedure directly.  Instead, you change the above definition to:

(define-interface my-package-interface (export my-proc my-signal)

(define-structure my-package my-package-interface
  (open scheme grl-language grl-library)
  (files my-code))

the define-interface macro is written so that when you reevaluate it, it mutates the old interface object rather than creating a new one, thus updating your package without having to create a new package.

Package files

In practice, no one ever types code into the config package directly.  Instead, you typically create a separate file to hold your package and interface declarations, typically called packages.scm and placed in the same directory as the code for those packages.  You can then just say:

,config ,load packages.scm

which means: "go to the config package, then compile and run all the commands in packages.scm, then return to the package I'm already in."

Getting back to the package you started in

The package you start up in is called the "user package".  Like the config package, it doesn't have a binding in the config package.  To get back to it after you leave it, you have to use the ,user command.

 

Using the object inspector

Scheme48 includes a simple object inspector that you can use to look at the data structures used by GRL to represent your signals, or to inspect Scheme's call stack (see below).  You can enter the inspector by typing ,debug (to inspect the stack) or ",inspect expression" to inspect the value of an arbitrary scheme expression.  The inspector then prints the fields of the object along with numbers:

> ,inspect default-polly-pipeline
'#{Compound-signal (139) default-polly-pipeline}

[0: source-code] '((polly-pipeline # 15 # 30 ---))
[1: name] 'default-polly-pipeline
[2: inputs] '(#{Constant-signal # side-width-percent} #{Constant-signal # 15} #{Constant-signal # center-width-percent} #{Constant-signal # 30} #{Constant-signal # edge-threshold} ---)
[3: operator] '#{Signal-procedure polly-pipeline}
[4: other-required-signals] '()
[5: type] #f
[6: declared-type] #f
[7: initial-value] '#{Unspecific}
[8: location] '#{Compound-signal (827) default-polly-pipeline}
[9: parent] #f
[10: part-of-expansion-of] #f
[11: plist] '()
[12: force-expression?] #f
[13: force-inline?] #f
[14: gatherer] #f
[m] more...
inspect: 

(Note that the prompt changes to "inspect:").  This shows that default-polly-pipeline evaluated to the object #{Compound-signal (139) default-polly-pipeline}.  It's one of the objects used by the compiler to represent signals.  It has a bunch of fields: source code, name, inputs, etc.  You can get a better look at the values of those fields by typing the number of the field.  For example, to see the inputs of the signal, type 2:

inspect: 2
'(#{Constant-signal # side-width-percent} #{Constant-signal # 15} #{Constant-signal # center-width-percent} #{Constant-signal # 30} #{Constant-signal # edge-threshold} ---)

 [0] '#{Constant-signal (133) side-width-percent}
 [1] '#{Constant-signal (134) 15}
 [2] '#{Constant-signal (135) center-width-percent}
 [3] '#{Constant-signal (136) 30}
 [4] '#{Constant-signal (137) edge-threshold}
 [5] '#{Constant-signal (138) 25}
inspect: 

This says that the value of the inputs field is a list of six elements.  To see the first input to the signal, side-width-percent, type 0:

inspect: 0
'#{Constant-signal (133) side-width-percent}

 [0: source-code] '('side-width-percent (polly-pipeline # 15 # 30 ---))
 [1: name] "constant"
 [2: inputs] '(side-width-percent)
 [3: operator] '#{Primitive identity}
 [4: other-required-signals] '()
 [5: type] #f
 [6: declared-type] #f
 [7: initial-value] '#{Unspecific}
 [8: location] '#{Unspecific}
 [9: parent] #f
 [10: part-of-expansion-of] #f
 [11: plist] '()
 [12: force-expression?] #f
 [13: force-inline?] #f
 [14: gatherer] #f
 [m] more...
inspect: 

To go back to the list, type "u" for "up":

inspect: u
'(#{Constant-signal # side-width-percent} #{Constant-signal # 15} #{Constant-signal # center-width-percent} #{Constant-signal # 30} #{Constant-signal # edge-threshold} ---)

 [0] '#{Constant-signal (133) side-width-percent}
 [1] '#{Constant-signal (134) 15}
 [2] '#{Constant-signal (135) center-width-percent}
 [3] '#{Constant-signal (136) 30}
 [4] '#{Constant-signal (137) edge-threshold}
 [5] '#{Constant-signal (138) 25}
inspect: 

Typing "u" again, brings you back to default-polly-pipeline.

inspect: u
'#{Compound-signal (139) default-polly-pipeline}

[0: source-code] '((polly-pipeline # 15 # 30 ---))
[1: name] 'default-polly-pipeline
[2: inputs] '(#{Constant-signal # side-width-percent} #{Constant-signal # 15} #{Constant-signal # center-width-percent} #{Constant-signal # 30} #{Constant-signal # edge-threshold} ---)
[3: operator] '#{Signal-procedure polly-pipeline}
[4: other-required-signals] '()
[5: type] #f
[6: declared-type] #f
[7: initial-value] '#{Unspecific}
[8: location] '#{Compound-signal (827) default-polly-pipeline}
[9: parent] #f
[10: part-of-expansion-of] #f
[11: plist] '()
[12: force-expression?] #f
[13: force-inline?] #f
[14: gatherer] #f
[m] more...
inspect: 

Notice the the operator (the procedure being applied to the inputs) is a signal procedure, polly-pipeline.  That means that at compile-time the compiler calls that signal procedure with the input signals as arguments, that signal expanded it into some other network of signals and returned it to the compiler.  The compiler then tried to compile that, until it expanded it into a network of signals that didn't have any signal procedures.  When it expands a signal, the compiler stored the expanded version into the location field of the object, which here is field number 8:

inspect: 8
'#{Compound-signal (827) default-polly-pipeline}

 [0: source-code] '((make-polly-internal input smoothed-image dangerous-pixels depth-map ---) (let* # #) (#) (#))
 [1: name] 'default-polly-pipeline
 [2: inputs] '(#{Transducer-signal # "default-polly-pipeline-input"} #{Compound-signal # smoothed-image} #{Transducer-signal # dangerous-pixels} #{Transducer-signal # depth-map} #{Constant-signal # 112} ---)
 [3: operator] '#{Signal-procedure make-polly-internal}
 [4: other-required-signals] '()
 [5: type] #f
 [6: declared-type] #f
 [7: initial-value] '#{Unspecific}
 [8: location] '#{Signal-group (1249) default-polly-pipeline}
 [9: parent] '#{Compound-signal (139) default-polly-pipeline}
 [10: part-of-expansion-of] '#{Compound-signal (139) default-polly-pipeline}
 [11: plist] '()
 [12: force-expression?] #f
 [13: force-inline?] #f
 [14: gatherer] #f
 [m] more...
inspect: 8
'#{Signal-group (1249) default-polly-pipeline}

 [0: source-code] #f
 [1: name] 'default-polly-pipeline
 [2: inputs] '(#{Transducer-signal # "default-polly-pipeline-input"} #{Transducer-signal # "default-polly-pipeline-smoothed-image"} #{Transducer-signal # dangerous-pixels} #{Transducer-signal # depth-map} #{Constant-signal # 112} ---)
 [3: operator] '(group input smoothed-image dangerous-pixels depth-map ---)
 [4: other-required-signals] '()
 [5: type] '(group input smoothed-image dangerous-pixels depth-map ---)
 [6: declared-type] #f
 [7: initial-value] '#{Unspecific}
 [8: location] #f
 [9: parent] '#{Compound-signal (827) default-polly-pipeline}
 [10: part-of-expansion-of] '#{Compound-signal (827) default-polly-pipeline}
 [11: plist] '()
 [12: force-expression?] #f
 [13: force-inline?] #f
 [14: gatherer] #f
 [m] more...
inspect: 

So this shows us that default-polly-pipeline, a signal whose operator was polly-pipeline, was expanded into a signal whose operator was make-polly-internal, and that expanded into a signal group with fields named input, smoothed-image, dangerous-pixels, etc.  To see the components themselves, we could then inspect the inputs of this signal, if we wanted to.

Using the scheme debugger

When you trigger an error, Scheme goes into the debugger.  The debugger is just the normal command processor, except that it puts a number at the beginning of the prompt, for example: "1 >".   If you trigger another error while debugging the first one, the number increments.  To give up on debugging an error, type ",pop".  That will bring you to the error before it.  To give up debugging entirely, type ",reset".  At NWU, we have bound ,pop and ,reset to the F3 and F2 keys, respectively.

When you're in the debugger, you can print a stack trace by typing ,preview (bound to F11 at NWU).  You can inspect individual stack frames by typing ,debug (F12).  This places you in the scheme48 object inspector:

Here's an example debugging session.  Just to get started, we'll define a function to debug, foo:

> (define (foo a b)
    (+ a (/ 1 (- b 7))))
>

Now let's make it break:

> (foo 3 7)

Error: rational division by zero
       1
1>

This tells us that we divided by zero and that the thing we tried to divide by zero was 1.  So let's find out what called the division procedure:

1> ,preview
  integer/ in ratnums
  / in scheme-level-0
  foo
  evaluate-and-select in command-processor
  loop
  thread-start in thread-top-level in threads
1>

This says the the procedure that died was integer/ in the package ratnums (don't worry about packages if you don't know what they are).  It was called by the procedure /, which was called by foo, which was called by a procedure evaluate-and-select which was part of scheme48's command processor.  Now let's look at the arguments.

1> ,debug
'#{Exception-continuation (pc 69) (integer/ in ratnums)}

 [0] 1
 [1] 0
inspect: 

Now we're inspecting scheme48's stack. 

inspect: d
'#{Exception-continuation (pc 15) (/ in scheme-level-0)}

inspect: d
'#{Continuation (pc 38) foo}

Waiting for (/ 1 (- b 7))
  in (+ a ^^^)
 [0] 3
 [1: a] 3
 [2: b] 7
inspect: ,reset

Top level
>