Moby: the Moby Scheme Compiler
1 What is Moby?
Moby is a project from the PLT Scheme team. The Moby compiler consumes Advanced Student Language (ASL) programs that use World primitives, and produces applications for mobile platforms. The current prototype supports desktop browsers and smartphones. Our long-term goal is to make Scheme the premiere reactive scripting language for mobile phones.
Shriram Krishnamurthi presented the ideas behind Moby at ILC 2009 in his talk The Moby Scheme Compiler for Smartphones.
2 Running Moby from DrScheme
To use Moby from DrScheme, create a file in the Module language, and at the top of your program, include the following language line:
followed by the program. For example, running the program:
#lang planet dyoo/moby:1 |
(define initial-world 0) |
(js-big-bang initial-world (on-tick 1 add1)) |
will invoke a web browser, which should show the running program on a web page. The page should also provide links to download packages of the compiled program.
3 API
3.1 World
The Moby language supports reactive World-style programming; the reactive libraries allow one to write interactive web sites that work with the DOM as well as CSS stylesheets.
(js-big-bang 0 |
(on-tick 1 add1)) |
In the absence of an on-draw or on-redraw handler, js-big-bang will show the current state of the world; on transitions, the world is re-drawn to screen.
3.2 World Examples
Example 1:
The following will render the world as a paragraph of text, styled with a font-size of 30. Whenever the world is changed due to a stimulus, on-draw is called to re-draw the world.
(js-big-bang 0 ; initial world |
; the dom tree renderer |
(on-draw |
(lambda (w) |
(list (js-p '(("id" "myPara"))) |
(list (js-text "hello world")))) |
; the css renderer |
(lambda (w) |
'(("myPara" ("font-size" "30")))))) |
Example 2:
The following will show a logo and an input text field. Whenever the number in the text is changed, the world will reflect that change.
(define (form-value w) |
(format "~a" w)) |
(define (update-form-value w v) |
(string->number v)) |
(define elt |
(js-bidirectional-input "text" form-value update-form-value)) |
(define (draw w) |
(list (js-div) |
(list (js-img "http://plt-scheme.org/logo.png")) |
(list elt) |
(list (js-p '(("id" "aPara"))) |
(list (js-text (format "~a" w)))))) |
(define (draw-css) |
'(("aPara" ("font-size" "50px")))) |
(js-big-bang 0 |
(on-draw draw draw-css)) |
Example:
The following program shows a ball falling down a scene.
; Simple falling ball example. A red ball falls down the screen |
; until hitting the bottom. |
; The dimensions of the screen: |
(define WIDTH 320) |
(define HEIGHT 480) |
; The radius of the red circle. |
(define RADIUS 15) |
; The world is the distance from the top of the screen. |
(define INITIAL-WORLD 0) |
; tick: world -> world |
; Moves the ball down. |
(define (tick w) |
(+ w 5)) |
; hits-floor?: world -> boolean |
; Returns true when the distance reaches the screen height. |
(define (hits-floor? w) |
(>= w HEIGHT)) |
; We have some simple test cases. |
(check-expect (hits-floor? 0) false) |
(check-expect (hits-floor? HEIGHT) true) |
; render: world -> scene |
; Produces a scene with the circle at a height described by the world. |
(define (render w) |
(place-image (circle RADIUS "solid" "red") (/ WIDTH 2) w |
(empty-scene WIDTH HEIGHT))) |
; Start up a big bang, 15 frames a second. |
(js-big-bang INITIAL-WORLD |
(on-tick 1/15 tick) |
(on-redraw render) |
(stop-when hits-floor?)) |
3.2.1 Types
A dom-sexp describes the structure of a web page:
dom-sexp | = | (list dom-element dom-sexp ...) |
a css-sexp describes the structure of a page’s styling:
css-sexp | = |
|
attrib | = | (list string string) |
Each of the dom-elements can take in an optional attribute list to assign to the new dom element; the common useful attribute is a key-value binding for an "id", which can be used to identify an element in the css-drawing function.
(define a-dom-sexp (list (js-div '(("id" "main-div"))) |
(list (js-text "Hello world")))) |
(define a-css-sexp (list (list "main-div" |
(list "background" "white") |
(list "font-size" "40px")))) |
Here are the dom-element constructors.
(js-button world-update-f [attribs]) → dom-element |
world-update-f : (world -> world) |
attribs : (listof attrib) = '() |
(js-button* world-update-f effect-f [attribs]) → dom-element |
world-update-f : (world -> world) |
effect-f : (world -> effect) |
attribs : (listof attrib) = '() |
| ||||||||||||||||||||||||||||
type : string | ||||||||||||||||||||||||||||
val-f : (world -> string) | ||||||||||||||||||||||||||||
world-update-f : (world string -> world) | ||||||||||||||||||||||||||||
attribs : (listof attrib) = '() |
(define input-node |
(js-input "text" '(("id" "myname")))) |
(define (refresh w) |
(get-input-value "myname")) |
(define (draw w) |
(list (js-div) |
(list (js-div) (list (js-text (format "I see: ~s~n" w)))) |
(list (js-div) (list input-node)) |
(list (js-div) (list (js-button refresh) (list (js-text "Update!")))))) |
(define (draw-css w) |
'()) |
(js-big-bang "" |
(on-draw draw draw-css)) |
A single text input form element allows the user to enter some value. That value is read from the interface by the refresh function that’s associated to the button.
3.2.2 Stimulus Handlers
Stimulus handlers are provided as additional arguments to a js-big-bang.
Each stimulus has an unstarred and a starred version; the starred version allows you to provide an effect-generating function. When the given stimulus emits, the old world is used to compute both the new world and the optional effect. Afterwards, each effect in the effect group is applied.
effect | = | atomic-effect | ||
| | (listof effect) |
| |||
|
| |||||||||||||||||
|
| |||
|
| |||
|
3.2.3 Effects
Effects allow world programs to apply side effects to the outside world. These are used in conjunction with the starred version of the stimulus handlers described above.
sound | = | string | ||
| | playlist |
3.3 API Extensions
The following helper functions and forms are provided by Moby.
(location-distance lat-1 long-1 lat-2 long-2) → number? |
lat-1 : number? |
long-1 : number? |
lat-2 : number? |
long-2 : number? |
make-foo: X Y -> foo
foo-a: foo -> X
foo-b: foo -> Y
foo?: any -> boolean
set-foo-a!: foo X -> void
set-foo-b!: foo Y -> void
4 Developer details
The compiler takes a ASL program and translates it to Javascript code. Moby reimplements the ASL primitives in a Javascript runtime library that’s included with the compiled application. (See doc/moby-developer-api.txt for more details.)
To support smartphones, Moby uses a bridge library called Phonegap, which provides access to the native facilities of several cell phones. In this way, Moby should be able to support multiple platforms with a lot of code reuse. Moby handles the other libraries (tilt, location, sms, music), though with support only for the Android platforms for now.
4.1 Dependencies
Moby is mostly written in PLT Scheme, and the project sources are hosted by github.com. To develop with Moby, you will need the following:
PLT Scheme >=4.2.2 (http://plt-scheme.org/)
git (http://git-scm.com)
Java >=1.6 (http://java.sun.com/)
Apache Ant >=1.7.1 (http://ant.apache.org/)
Google Android SDK >= 1.5r3 (http://developer.android.com/)
4.2 Installing from Developer Sources
Download the Moby source, currently hosted on github and place them in your PLT Scheme collects directory.
For example,$ cd ~/.plt-scheme/4.2.1/collects
$ git clone git://github.com/dyoo/moby-scheme.git moby
downloads the developer sources into a PLT Scheme user collects directory.Also, do a setup-plt -l moby so that PLT Scheme compiles the Moby source code.
2. If you’re going to do Android development, make sure that ant and the android binary are in your path; Moby will use your PATH variable to find Apache Ant and the Android SDK.
You can verify that android and ant can be found with the following:$ which android
/usr/local/android/tools/android
$ which ant
/usr/bin/ant
The path values you get back may differ according to your system’s configuration.
4.3 Running Moby from the command line
js: compiles to a web page application, which can be deployed on any web server.
js+android-phonegap: compiles to an Android .apk application package; can also use features of the mobile platform.
By default, the command line utility will use the js backend.
$ cd moby/examples |
$ mred ../src/moby.ss falling-ball.ss |
$ cd FallingBall/ |
$ ls |
index.html main.js runtime test |
$ cd moby/examples |
$ mred ../src/moby.ss -t js+android-phonegap falling-ball.ss |
$ cd FallingBall |
$ ls |
AndroidManifest.xml build.properties gen res |
assets build.xml libs src |
bin default.properties local.properties tests |
|
$ ls bin |
classes classes.dex DroidGap.ap_ DroidGap-debug.apk |
$ ant install |
Buildfile: build.xml |
|
[some output cut] |
|
install: |
[echo] Installing bin/DroidGap-debug.apk onto default emulator... |
[exec] 1594 KB/s (120997 bytes in 0.074s) |
03:38 I/ddms: Created: [Debugger 8610-->1641 inactive] |
03:38 I/ddms: Good handshake from client, sending HELO to 1641 |
[exec] pkg: /data/local/tmp/DroidGap-debug.apk |
[exec] Success |
03:39 I/ddms: Closing [Client pid: 1641] |
|
BUILD SUCCESSFUL |
Total time: 6 seconds |
After this, you can look at the Android emulator, which should now have the "FallingBall" application installed.
4.4 Compiler
"src/compiler/beginner-to-javascript.ss": translates Scheme programs to javascript programs.
"src/compiler/env.ss": maintains the environment structures that map identifiers to bindings.
"src/compiler/permission.ss": defines a list of capabilities that a function can be tagged with.
"src/compiler/toplevel.ss": maps the primitive toplevel names available in the base language.
"src/compiler/modules.ss": adds a few extensions to the toplevel language, including the reactive world primitives.
"src/compiler/pinfo.ss": maintains program information used to analyze a program and figure out what functions are used and what capabilities are needed.
"src/compiler/desugar.ss": applies syntactic transformations on programs to a core form.
"src/compiler/helpers.ss": auxillary helper functions.
The compiler is intentionally written in a small superset of the language ("src/compiler/lang.ss"). As a consequence, it is self hosting, and we take advantage of this to produce a running compiler on the browser. ("support/js/test/test-repl.html")
"src/compiler/bootstrap-js-compiler.ss": compiles "beginner-to-javascript.ss" against itself to produce "support/js/compiler.js".
4.4.1 An example
Let’s see what beginner-to-javascript.ss gives us:
> (define p '((define (f x) |
(* x x)) |
|
(f 3))) |
|
> (define cp (program->compiled-program p)) |
program->compiled-program consumes a program – a list of s-expressions – and produces a compiled-program structure.
> cp |
#<compiled-program> |
The compiled program consists of a list of toplevel definitions and expressions.
> (compiled-program-defns cp) |
"\nfunction f(x) { return plt.Kernel._star_([x,x]); }" |
|
> (compiled-program-toplevel-exprs cp) |
> cp |
#<compiled-program> |
The compiled program consists of a list of toplevel definitions and expressions.
> (compiled-program-defns cp) |
"\nfunction f(x) { return plt.Kernel._star_([x,x]); }" |
|
> (compiled-program-toplevel-exprs cp) |
"(function (toplevel_dash_expression_dash_show0) { \n\ntoplevel_dash_expression_dash_show0((f((plt.types.Rational.makeInstance(3, 1))))); })" |
If we want to embed the evaluation of this program in a web page, we can use the two strings above to do so. For convenience, we provide a helper function compiled-program-main that ties both the definitions and expression evaluation together.
4.5 Runtime
The Javascript program that’s emitted depends on a runtime kernel that’s currently implemented in Javascript. See the files in "support/js/runtime".
4.6 Ugly Hacks
The afterAttach() hack: one problem with excanvases in IE is that they can’t render until they’re attached to a parent node. Unfortunately, this means that we can’t render a canvas at a call to a value’s pretty-printing routine, because that value isn’t yet attached to the DOM.
if (node.afterAttach) { node.afterAttach(); } |
"support/js/runtime/jsworld.js"
"support/js/runtime/jsworld/jsworld.js"
"support/js/runtime/types.js"
"support/js/runtime/kernel.js"
5 Appendix
5.1 Bindings from ASL
The following toplevel bindings are available from Moby, and have the same meaning as in Advanced Student Language.
*
+
-
/
<
<=
=
=~
>
>=
abs
acos
add1
andmap
angle
append
asin
atan
boolean=?
boolean?
build-list
caaar
caadr
caar
cadar
cadddr
caddr
cadr
car
cdaar
cdadr
cdar
cddar
cdddr
cddr
cdr
ceiling
char->integer
char-alphabetic?
char-ci<=?
char-ci<?
char-ci=?
char-ci>=?
char-ci>?
char-downcase
char-lower-case?
char-numeric?
char-upcase
char-upper-case?
char-whitespace?
char<=?
char<?
char=?
char>=?
char>?
char?
check-expect
check-within
check-error
complex?
conjugate
cons
cons?
cos
cosh
current-seconds
denominator
e
eighth
empty
empty?
eof
eof-object?
eq?
equal?
equal~?
eqv?
error
even?
exact->inexact
exp
expt
false
false?
fifth
first
floor
foldl
format
fourth
gcd
identity
imag-part
inexact->exact
inexact?
integer->char
integer?
lcm
length
list
list*
list->string
list-ref
log
magnitude
make-posn
make-string
map
max
member
memq
memv
min
modulo
negative?
not
null
null?
number->string
number?
numerator
odd?
pair?
pi
positive?
posn-x
posn-y
posn?
quotient
random
rational?
real-part
real?
remainder
rest
reverse
round
second
seventh
sgn
sin
sinh
sixth
sqr
sqrt
string
string->list
string->number
string->symbol
string-append
string-ci<=?
string-ci<?
string-ci=?
string-ci>=?
string-ci>?
string-copy
string-length
string-ref
string<=?
string<?
string=?
string>=?
string>?
string?
struct?
sub1
substring
symbol->string
symbol=?
symbol?
tan
third
true
zero?
begin
set!
let
let*
letrec
case
build-vector
make-vector
vector
vector-length
vector-ref
vector-set!
vector?
5.2 Unimplemented forms
begin0
delay
shared
recur
when
unless