Extending Scheme interpreter

jcubic edited this page Dec 23, 2010 · 11 revisions

Creating new Scheme Function in JavaScript

to create new scheme function you must use BiwaScheme.define_libfunc. this function have 4 arguments:

  • name of the function
  • minimum number of arguments
  • maximum number of arguments
  • function with one arguments which is array of arguments passed to newly created function in scheme.

if minimum is set to 0 and maximum is set to null, function will have any number of arguments.

BiwaScheme.define_libfunc("add", 2, 2, function(args) {
    assert_number(args[0]);
    assert_number(args[1]);
    return args[0] + args[1];
});

This define new function that you can call from scheme:

(display (add 2 3))

In function you can check for specific types with one of assert series of functions. if function don't return anything it must return BiwaScheme.undef otherwise it throw exception which is catch by error function passed as argument to class BiwaScheme.Interpreter if you want to return a list you can convert Array to list using Array::to_list() method.

Creating Higher order procedures

To create new scheme function that call another function passed (in scheme) as argument you must call invoke_closure from BiwaScheme.Interpreter class

BiwaScheme.define_libfunc("each", 2, 2, function(args) {
    assert_closure(args[1]);
    assert_list(args[0]);
    var array = args[0].to_array();
    var interpreter = new BiwaScheme.Interpreter();
    for (var i=array.length; i--;) {
        interpreter.invoke_closure(args[1], array[i]);
    }
    return BiwaScheme.undef;
 });

this function create new scheme higher order procedure that iterate over list. You can use it in scheme.

(each display '(1 2 3 4 5 6 7))

To create new procedure that return another procedure

 BiwaScheme.define_libfunc("add2", 1, 1, function(ar){
     assert_closure(ar[0]);
     return function(args){
        return new BiwaScheme.Call(ar[0], [args[0]+2]);
     };
 });

instead of invoke_closure you can also use BiwaScheme.Call class.

(map (add2 (lambda (x) (+ x 1))) '(1 2 3 4 5))

functions defined with BiwaScheme.define_libfunc can return JavaScript functions, they are evaluated as normal scheme procedures by interpreter.

Scheme Function with scheme code

You can also create scheme function from JavaScript using scheme code. To do this you must call BiwaScheme.define_scmfunc function This function have the same argument as BiwaScheme.define_libfunc but instead of passing function as fourth argument you pass string containing scheme lambda expression.

    BiwaScheme.define_scmfunc('!', 1, 1, 
            "(lambda (n) \
                (let iter ((n n) (result 1)) \
                    (if (= n 0) \
                        result \
                        (iter (- n 1) (* result n)))))");

This function define new scheme procedure ! which calculate factorial with tail recursion.

Call scheme function from javascript

If function is defined using BiwaScheme.define_libfunc it's placed in BiwaScheme.CoreEnv object It's JavaScript function passed as fourth argument to BiwaScheme.define_libfunc

BiwaScheme.define_libfunc('foo', 0, 0, function(args) {
   BiwaScheme.CoreEnv['display'](['call foo']);
   return BiwaScheme.undef;
});

If you want to call function that is defined in scheme using define you must invoke the closure which is placed in BiwaScheme.TopEnv.

BiwaScheme.define_libfunc('call-bar', 0, 0, function(args) {
   return new BiwaScheme.Interpreter().invoke_closure(BiwaScheme.TopEnv['bar'], ['hello']);
});

And in scheme you can define function bar and call call-bar

(define (bar str)
   (display (string-concat "you say: " str)))
(call-bar)

Interacting with JavaScript from scheme

if you want to mess around with javascript from schme you can use one of these functions (defined in webscheme_lib.js)

  • js-load - load javascript libary
  • js-eval - evaluate javascript code
  • js-ref - return value of object property
  • js-call - invoke javascript function
  • js-invoke - call javascript method on object
  • js-new - create new javascript object
  • js-obj - create object from even arguments
  • js-closure - create javascript function from scheme function (wrapper)
  • js-null? - test for javascript null
  • js-undefined? - test for javascript undefined
  • json->sexp - convert JSON to S-Expression

create new javascript anonymous function and call it

(js-call (js-eval "( function() { alert('hello'); } )"))

create new scheme anonymous function wrap it with javascript function and call that function (This is rather pointless)

(js-call (js-closure (lambda () (display "hello"))))

in this example you can remove js-call because interpreter when encounter Javascript function in procedure application it call that function so you can:

((js-closure (lambda () (display "hello"))))

create new immediate javascript object and convert it to S-Expression (this will produce AList)

(json->sexp (js-eval "( {foo: 'bar', bar: 'quux'} )"))

Access to javascript property

(js-ref (js-eval "( {foo: 'bar', bar: 'quux'} )") "foo")

Access to javascript property through AList

(cdr (assoc "foo" (json->sexp (js-eval "( {foo: 'bar', bar: 'quux'} )"))))

Call Scheme function as Javascript Function from Scheme (there is no need for this, but you can do this)

(define (foo str) 
   (js-eval (string-concat (list "BiwaScheme.CoreEnv['display']([' " str "'])"))))

(foo "hello")

You can also invoke a closure from BiwaScheme.TopEnv.

Create Lisp Macros

You can define lisp macros from Javascript using BiwaScheme.define_syntax. this function have 2 arguments

  • name of the macro
  • function with one argument which is scheme expression passed to the macro

Expression is data structure created from BiwaScheme.Pair objects every pair has car and cdr which you can use to traverse expression. Function passed as second parameter must return a tree of symbols (i simple case it can be a list) which will be evaluated when user call macro from scheme.

HINT: you can convert list to array with to_array method, do your modifications and then call to_list method on that array.

Array.prototype.to_tree = function() {
    for(var i in this) {
        if (this[i] instanceof Array) {
            return this[i].to_tree();
        }
    }
    return this.to_list();
};

BiwaScheme.define_syntax('foo', function(expr) {
    return [BiwaScheme.Sym("display"),
            [BiwaScheme.Sym("quote"), expr.cdr.to_array().to_tree()]
           ].to_tree();
});

this is simple macro that display expression passed as argument.

You must be aware that actual expression passed in scheme to the macro is in cdr property of argument that define macro in javascript.

This is the source code that you can check. BiwaScheme wrapper around JQuery library. http://terminal.jcubic.pl/js/jqbiwa.js