Archive for February, 2008

Ouija: A Scheme Interpreter in Flash

Today I’m releasing to you, the world, my Scheme interpreter in Flash. This is in no way a polished thing, so don’t expect a ton, but it is pretty cool in my opinion. In the past few months I’ve created a few flash prototypes with it because it lets me develop significantly faster than with just plain ActionScript.

Please note while reading this that the company that I was once employed at is no longer, and I am currently looking for employment. So, if you are reading this and saying to yourself, “Wow, I wish I could hire someone that could make me a Scheme interpreter in Flash”, you are in luck!

Anyway, here’s a REPL for you to play with, the top portion is interactive while the bottom is persistent, allowing you to put code there which remains when you refresh or come back later. The REPL does have it’s problems, but it’s good enough to let you play with it. The SWF file that you’re hitting is loading REPL.oui when it starts, but only because the SWF name is REPL.swf. If you download REPL.swf and rename it Test.swf, it will attempt to load Test.oui at runtime.

The differences between this and the Lisp Interpreter I made a while back are as follows:

  • Scheme syntax instead of Common Lisp
  • Closures work
  • Continuations
  • FULL ACCESS TO THE WHOLE FLASH8 RUNTIME, this allows you to do graphics, sound, animation and anything else you can do with Flash normally
  • In AS2 as opposed to AS3; I did this due to the flexibility for AS2 over AS3, and my hope is to port it very shortly.

So if you know Scheme already you can start messing around with things like

(define a '(1 2 3 4))
(apply + a)

The next step is doing things with the Flash side.

(define pic (create-clip))
(set! (_x pic) 200)
(load-image pic "ouija.jpg")
(set! (_scale pic) 50)

So now you have a movieClip on the stage with an image loaded into it and sized to 50%. Normally in Flash we’d have to call a load command and set up an event to fire when it was loaded so that we could start doing things with it, but because we’re using Scheme, we actually can halt execution of code until the image is loaded (this is the default behavior, but we can still utilize the event-driven method by using threads). We can do the same thing with tweening.


(transition 24
(pic
('_x :to 300)
('_rotation :to 360)))

When you’re done messing with the image you can remove it like this


((=> pic 'remove-movie-clip))

This is the basic method of interacting with the Flash API. In Flash a movieclip has a method on it called removeMovieClip. By calling (=> pic ‘remove-movie-clip) we are getting that method, and by wrapping it in an extra set of parenthesis we are calling it (note that the dashes from remove-movie-clip are removed and the letter immediately following the dash is capitalized when the method is looked up. This is because Ouija is case-insensitive where as ActionScript is case-sensitive).

Now, audio is another case where I don’t have any “nice” ouija methods written. So in Flash you’d have to write.


var song=new Sound();
song.loadSound("song.mp3")
//yes there is an mp3 called song.mp3 on my server
//and I'm pretty sure the writer isn't going to sue me
//for putting it up there.
song.onLoad=function() { song.start(); }

So translated directly into Ouija it would look like this

(define song (make-instance *sound))
;the * at the beginning is to make the first letter caps
((=> song 'load-sound) "song.mp3")
(set! (=> song 'on-load) (lambda () ((=> song 'start))))

And then to stop it


((=> song 'stop))

In all, there are quite a lot of things you can do and ideas to play with. I’m sure there are a ton of broken things, but I figure if I don’t release this thing now, I never will.

Also if you’re interested you can check out this site I created in Ouija (here is the source code, also note that the version of the SWF contains some extras not in the REPL). After I finished this version I hand-compiled it so that it would run more quickly on the pre-Intel Mac versions of the Flash Player and then we updated some things. That version is here. After that, the company shutdown.

Alright, so here’s a list of functions and constants in Ouija.

Constants

  • t or #t : the true boolean
  • nil or #f : unlike Scheme, Ouija uses the Common Lisp nil as both false and the end of a list, #f is also defined, but it is a reference to the NIL object
  • _root : this is a reference to the root MovieClip of the running flash
  • pi, 2pi, pi/2, pi/3, pi/4, pi/6, pi/12 : pi and common fractions of pi

List Functions

  • (cons a b) : creates a cons cell
  • (car a) (cdr a) (caar a) (cadr a) (cdar a) (cddr a) : these can also be used with set!, (set! (cadr a) 5)
  • (list 1 2 3 4 5) : creates a list
  • (list* 1 2 3 4 5) : creates a list with the last cell being a dotted pair, in this case (1 2 3 4 . 5)
  • (append list1 list2 …) : returns new list with list1 appended to the beginning of list2
  • (append! list1 list2 …) : destructive version of above
  • (reverse lst) : reverses list
  • (reverse! lst) : destructive version of above
  • (map fn lst …) : return new list by applying fn to each element of lst, can take multiple lists
  • (for-each fn lst) : like map, but only for side-effects, doesn’t return a list
  • (map-append fn lst) : like map only it appends instead of consing
  • (filter fn lst) : returns new list filled with elements that didn’t return NIL when passed to fn
  • (foldl fn init lst) : left fold
  • (foldr fn init lst) : right fold
  • (compose fn1 fn2 …) : returns a function composed of the arguments
  • (nth index lst) : returns the nth element in list, also works with set!
  • (nthcdr index lst) : like about, but returns whole cons cell
  • first, second ….. tenth : also works for set!
  • (copy-list lst) : return copy of list
  • (last lst) : returns the last cons cell in the list
  • (butlast lst) : returns a copy of the list without it’s last element
  • (length lst) : returns the length of the list, also works for arrays
  • (getarg prop-list key) : returns the value of the associated value in a prop-list
  • (filter-out-keys prop-list remove-list) : returns new prop-list with specified keys removed
  • (member value list) : finds value in list, returns the rest of the list

Math Stuff

  • = + - * / < > <= >=: all take multiple parameters
  • max min
  • sqrt cos sin tan acos asin atan atan2 floor ceiling round expt modulo truncate quotient remainder

String Stuff

  • (->string arg) : converts argument to string
  • (mkstr arg1 arg2 …) : converts each arg to a string and concatenates them all
  • (string-append arg1 arg2 …) : like mkstr, but it assumes all args are strings already
  • (format ctrl-string arg1 arg2 …) : only supports ~a ~n ~x and ~~.

Object Stuff

  • (make-instance class arg1 arg2 …) : creates a new instance of a class
  • (=> obj ’slot) : returns property of an object, works with set!
  • (create . prop-list) : creates object ex: (create :name “Chuck Loads” :age 30 :sex ‘male)
  • (array arg1 arg2 …) : creates array
  • (with obj . body) : allows you to execute code in the scope of the given object

Normal Scheme Stuff

  • if lambda eq? equal? quote and or when unless push! pop! inc! dec! not let (normal and named) fluid-let call/cc let/cc dynamic-wind display newline
  • cond (use t instead of else)
  • define : can be used to define curried functions as well (define ((add a) b) (+ a b))

Predicates

  • boolean? number? atom? pair? list? symbol? keyword? procedure? null? zero? negative? positive? odd? even?

Flash Stuff

  • (create-clip) : create movieclip, optionally takes parent clip
  • (create-textfield) : creates textfield, optionally takes parent clip
  • ((=> clip ‘line-style) 1 0 100) : need to do this or something like it to draw on the clip
  • (draw-rect clip x y width height)
  • (draw-line clip x y width height)
  • (draw-rounded-rect clip x y width height radius)
  • (draw-circle clip x y r)
  • (transition frames . data) : see the example
  • _visible _alpha _x _y _xscale _yscale _scale _rotation _width _height : function accessors for movieclip properties
  • (return-click) : waits for user to click on something and then returns a reference to what was clicked.
  • (get-timer) : return ticks since swf started
  • (update-stage) : updates stage during execution of a function
  • (display-layout parent . children) : creates a movieclip and textfield hierarchy, example below


(display-layout image-content
(movie-clip (:id mc :_visible #f)
(text-field (:id title
:font "ITC Franklin Gothic Demi" :size 26
:auto-size "left" :text-color 0×5f2c1f
:embed-fonts #t :selectable #f
:_x 26 :_y 20
:text (=> content-data 'title)))
(text-field (:id body
:font "ITC Franklin Gothic Book" :size 14
:auto-size "left" :text-color 0×333333
:multiline #t :word-wrap #t :embed-fonts #t
:_x 280 :_y 60 :_width 260
:html-text (=> content-data 'text)))
(movie-clip (:_x 30 :_y 65
:src "test.jpg")))))

Other stuff

  • (eval val) : evaluates symbol, value or s-expression
  • (load file) : loads and executes Ouija code from a file
  • (read) : only works in repl, gets value from user
  • (read-from-string str) : parses string into symbol, number or s-expression
  • (gensym) : create symbol
  • (defmacro (name . args) . body) : Common Lisp style macro; Ouija does not do Scheme macros
  • (thunk . body) : same as (lambda () . body)
  • (amb arg1 arg2 …) : you should know what this does; if not, look it up
  • (amb-assert test)
  • (amb-collect . body)
  • (random (&optional n)) : returns random number
  • (random-char) : return random character
  • (random-string (&optional length)) : returns random string
  • (thread fn) : calls fn in a separate thread
  • (sleep seconds) : waits for a certain amount of time before execution is resumed
  • (dotimes (i times) . body) : standard CL dotimes
  • (dolist (i lst) . body) : standard CL dolist
  • (in-parallel fn1 fn2 …) : executes each function in a “thread”, then returns a list of the results
  • (map-in-parallel fn lst) : like map, only it does each in a “thread” instead of sequentially