Discussion:
[gambit-list] Javascript Backend
Bob Coleman
2014-03-05 22:19:52 UTC
Permalink
Greetings,

I have been playing around with the javascript backend and have been impressed with it's capabilities.  Interacting with other javascript libraries is not a problem, but I haven't found a way for other javascript code to call scheme defined functions.  Is there a way to define functions to be called by javascript similar to c-lambda or c-define?

Also, is it possible to access the value of the -target flag in scheme?  I would like to define some functions differently based on whether the program is being built for the C or javascript backend.

Thanks for your help.
Bob
Marc Feeley
2014-03-06 07:22:53 UTC
Permalink
Post by Bob Coleman
Greetings,
I have been playing around with the javascript backend and have been impressed with it's capabilities.
I’m glad you like it. Over the past week I have made several enhancements with the short term goal of implementing a remote debugger (so that Scheme code running in the browser can be debugged live using a REPL running on the server). Just a few minutes ago I committed the support for first-class continuations which are needed for the exception handling of the REPL.
Post by Bob Coleman
Interacting with other javascript libraries is not a problem, but I haven't found a way for other javascript code to call scheme defined functions. Is there a way to define functions to be called by javascript similar to c-lambda or c-define?
It is quite easy to interface to JavaScript functions from Scheme. In fact, using JavaScript’s eval gives a much simpler approach than using Gambit’s C interface for the C back-end. For example, you can define

(define js
(##inline-host-expression "Gambit_js2scm(eval)"))

This creates a Scheme callable function which performs a JavaScript eval. Then, in your code you can say:

(define alert (js "alert"))
(alert "hello!")

This will “convert” the JavaScript “alert” function to a Scheme function.

For the other way around (exporting a Scheme function callable from JavaScript), the js function can be used like this:

(define setglo (js "function (name,val) { window[name] = val; }"))

then

(setglo "foo" (lambda (n) (* n n)))

And then in JavaScript land your can call (foo 10).
Post by Bob Coleman
Also, is it possible to access the value of the -target flag in scheme? I would like to define some functions differently based on whether the program is being built for the C or javascript backend.
Define this macro:

(##define-macro (case-target . clauses)
(let ((target (if (and (pair? ##compilation-options)
(pair? (car ##compilation-options)))
(let ((t (assq 'target ##compilation-options)))
(if t (cadr t) 'c))
'c)))
(let loop ((clauses clauses))
(if (pair? clauses)
(let* ((clause (car clauses))
(cases (car clause)))
(if (or (eq? cases 'else)
(memq target cases))
`(begin ,@(cdr clause))
(loop (cdr clauses))))
`(begin)))))

and use it like this:

(case-target
((c)
(define foo (c-lambda () int "foo")))
((js)
(define foo (lambda () (##inline-host-expression "42")))))

Marc
Bob Coleman
2014-03-06 15:56:31 UTC
Permalink
Marc,

Thank you for your reply.  This was exactly what I was looking for.  Is the "Gambit_js2scm" function available in v4.7.2, or will I need to upgrade to the latest code?

I'm glad to hear you are continuing to improve the universal backend and am excited that first-class continuations are now implemented.  With first-class continuations available are there plans to implement green threads as well?  I've thought that green threads may be a interesting method to implement sprites for a HTML5 game.

Bob
Post by Bob Coleman
Greetings,
I have been playing around with the javascript backend and have been impressed with it's capabilities.
I’m glad you like it.  Over the past week I have made several enhancements with the short term goal of implementing a remote debugger (so that Scheme code running in the browser can be debugged live using a REPL running on the server).  Just a few minutes ago I committed the support for first-class continuations which are needed for the exception handling of the REPL.
Post by Bob Coleman
  Interacting with other javascript libraries is not a problem, but I haven't found a way for other javascript code to call scheme defined functions.  Is there a way to define functions to be called by javascript similar to c-lambda or c-define?
It is quite easy to interface to JavaScript functions from Scheme.  In fact, using JavaScript’s eval gives a much simpler approach than using Gambit’s C interface for the C back-end.  For example, you can define

(define js
  (##inline-host-expression "Gambit_js2scm(eval)"))

This creates a Scheme callable function which performs a JavaScript eval.  Then, in your code you can say:

(define alert (js "alert"))
(alert "hello!")

This will “convert” the JavaScript “alert” function to a Scheme function.

For the other way around (exporting a Scheme function callable from JavaScript), the js function can be used like this:

(define setglo (js "function (name,val) { window[name] = val; }"))

then

(setglo "foo" (lambda (n) (* n n)))

And then in JavaScript land your can call (foo 10).
Post by Bob Coleman
Also, is it possible to access the value of the -target flag in scheme?  I would like to define some functions differently based on whether the program is being built for the C or javascript backend.
Define this macro:

(##define-macro (case-target . clauses)
  (let ((target (if (and (pair? ##compilation-options)
                          (pair? (car ##compilation-options)))
                    (let ((t (assq 'target ##compilation-options)))
                      (if t (cadr t) 'c))
                    'c)))
    (let loop ((clauses clauses))
      (if (pair? clauses)
          (let* ((clause (car clauses))
                  (cases (car clause)))
            (if (or (eq? cases 'else)
                    (memq target cases))
                `(begin ,@(cdr clause))
                (loop (cdr clauses))))
          `(begin)))))

and use it like this:

(case-target

  ((c)
    (define foo (c-lambda () int "foo")))
  ((js)
    (define foo (lambda () (##inline-host-expression "42")))))

Marc
Marc Feeley
2014-03-06 17:13:18 UTC
Permalink
Marc,
Thank you for your reply. This was exactly what I was looking for. Is the "Gambit_js2scm" function available in v4.7.2, or will I need to upgrade to the latest code?
You need the latest master branch from github.
I'm glad to hear you are continuing to improve the universal backend and am excited that first-class continuations are now implemented. With first-class continuations available are there plans to implement green threads as well? I've thought that green threads may be a interesting method to implement sprites for a HTML5 game.
Yes that’s the plan.

Marc
Marc Feeley
2014-03-06 22:48:32 UTC
Permalink
Post by Marc Feeley
(define setglo (js "function (name,val) { window[name] = val; }"))
then
(setglo "foo" (lambda (n) (* n n)))
And then in JavaScript land your can call (foo 10).
Ha! I meant foo(10) of course!

Marc
Mikael
2014-05-16 13:29:31 UTC
Permalink
Dear Marc,

Did ##inline-host-expression (and ##inline-host-statement) change name
recently?

On 4.7.2 it compiles to an ordinary procedure call.

Test case: (define js (##inline-host-expression "myprocedure(myargument)"))
compiles to: .... return Gambit_glo['##inline-host-expression']();

This is even while grep -r inline-host-expression gambit/* gives:

./gambit/gsc/_t-univ.scm:(univ-prim-proc-add! '("##inline-host-expression"
(1) #t 0 0 (#f) extended))
./gambit/gsc/_t-univ.scm:(univ-define-prim "##inline-host-expression" #f
./gambit/gsc/_t-univ.scm: (compiler-internal-error
"##inline-host-expression requires a constant string argument"))))

so it seems to be there somehow.

Thanks and best regards,
Mikael

2014-03-06 8:22 GMT+01:00 Marc Feeley <***@iro.umontreal.ca>:
..
Post by Marc Feeley
(define foo (lambda () (##inline-host-expression "42")))))
Marc Feeley
2014-05-16 13:36:40 UTC
Permalink
Post by Mikael
Dear Marc,
Did ##inline-host-expression (and ##inline-host-statement) change name recently?
On 4.7.2 it compiles to an ordinary procedure call.
Test case: (define js (##inline-host-expression "myprocedure(myargument)"))
compiles to: .... return Gambit_glo['##inline-host-expression']();
./gambit/gsc/_t-univ.scm:(univ-prim-proc-add! '("##inline-host-expression" (1) #t 0 0 (#f) extended))
./gambit/gsc/_t-univ.scm:(univ-define-prim "##inline-host-expression" #f
./gambit/gsc/_t-univ.scm: (compiler-internal-error "##inline-host-expression requires a constant string argument"))))
so it seems to be there somehow.
Thanks and best regards,
Mikael
You need to

(declare (extended-bindings) (not safe))

for those procedures to be inlined.

I may change the “safety” of those procedures though because others have had similar problems.

Also I will add tests to avoid JS-level errors when calling an undefined global variable.

Marc
Marc Feeley
2014-05-16 15:15:36 UTC
Permalink
Post by Marc Feeley
You need to
(declare (extended-bindings) (not safe))
for those procedures to be inlined.
I may change the “safety” of those procedures though because others have had similar problems.
Also I will add tests to avoid JS-level errors when calling an undefined global variable.
Marc
I have now changed the safety to avoid the need for (not safe).

Also, the procedures ##inline-host-expression and ##inline-host-statement have been generalized to allow parameters. The Nth parameter after the code string will be inserted where there is a @N@ reference in the code string.

Finally the ##inline-host-declaration procedure has been added to allow inserting toplevel host code in the generated code.

Below is an example. Note the use of the Gambit_scm2js and Gambit_js2scm functions to convert values between their JavaScript and Scheme representations.

Marc


(declare (extended-bindings)) ;; to inline the ##inline-host-... procedures

(##inline-host-declaration #<<host-code-end

function out(x) { console.log(x); }

host-code-end
)

(define (out x)
(##inline-host-statement "out(Gambit_scm2js(@1@));" x))

(define (add x y)
(##inline-host-expression
"Gambit_js2scm(Gambit_scm2js(@1@) + Gambit_scm2js(@2@))" x y))

(out "hello!")

(out #t)

(out (add 11 22))

;; Output:
;;
;; % gsc -c -target js test.scm
;; % node test.js
;; hello!
;; true
;; 33

Loading...