A Scheme Foreign Function Interface

to JavaScript Based on an Infix Extension

Marc-André Bélanger

Marc Feeley

Presented at ELS 2021


Built with Gambit Scheme and RevealJS.

Available at https://udem-dlteam.github.io/els2021-presentation/


  1. Motivation
  2. Goals
  3. Quick demo
  4. Syntax
  5. Implementation
  6. Examples
  7. Related work

Gambit Scheme


Foreign Function Interface (FFI)

Foreign Function Interface

Calling C from Gambit

(c-declare "#include <math.h>")

(define ldexp (c-lambda (double int) double "ldexp"))

The FFI is implementation specific, idiosyncratic.

Calling C from Gambit

(c-declare #<<c-declare-end

typedef char EBCDIC; /* EBCDIC encoded characters */

void put_char (EBCDIC c) { ... } /* EBCDIC I/O functions */
EBCDIC get_char (void) { ... }

char EBCDIC_to_ISO_8859_1[256] = { ... }; /* conversion tables */
char ISO_8859_1_to_EBCDIC[256] = { ... };

  int x = ___INT(src); /* convert from Scheme character to int */
  if (x > 255) return ___FIX(___UNKNOWN_ERR);
  *dst = ISO_8859_1_to_EBCDIC[x];
  return ___FIX(___NO_ERR);

#define ___BEGIN_CFUN_SCMOBJ_to_EBCDIC(src,dst,i) \
if ((___err = SCMOBJ_to_EBCDIC (src, &dst)) == ___FIX(___NO_ERR)) {
#define ___END_CFUN_SCMOBJ_to_EBCDIC(src,dst,i) }

#define ___BEGIN_CFUN_EBCDIC_to_SCMOBJ(src,dst) \
{ dst = ___CHR(EBCDIC_to_ISO_8859_1[src]);
#define ___END_CFUN_EBCDIC_to_SCMOBJ(src,dst) }

#define ___BEGIN_SFUN_EBCDIC_to_SCMOBJ(src,dst,i) \
{ dst = ___CHR(EBCDIC_to_ISO_8859_1[src]);
#define ___END_SFUN_EBCDIC_to_SCMOBJ(src,dst,i) }

#define ___BEGIN_SFUN_SCMOBJ_to_EBCDIC(src,dst) \
{ ___err = SCMOBJ_to_EBCDIC (src, &dst);
#define ___END_SFUN_SCMOBJ_to_EBCDIC(src,dst) }


(c-define-type EBCDIC "EBCDIC" "EBCDIC_to_SCMOBJ" "SCMOBJ_to_EBCDIC" #f)

(define put-char (c-lambda (EBCDIC) void "put_char"))
(define get-char (c-lambda () EBCDIC "get_char"))

(c-define (write-EBCDIC c) (EBCDIC) void "write_EBCDIC" ""
  (write-char c))

(c-define (read-EBCDIC) () EBCDIC "read_EBCDIC" ""

The FFI is implementation specific, idiosyncratic.


  • Natural syntax: stay close to JavaScript
  • Expressions as interface: avoid cumbersome DSL, let eval do the work
  • Avoid type annotations: automatic conversions
  • Use from REPL: avoid compilation

Quick demo

(define (hello name)
  \alert("Hello, "+(`name)+"!"))

(define (set-bg-color color)


Bridging the syntactic gap

  • JavaScript has an infix syntax
    function foo(x) {
        return x;
  • Scheme has a prefix syntax
    (define (foo x) x)
  • Scheme Infix eXtension
  • Gambit has a reader extension to parse an infix language

Scheme Infix eXtension (SIX)

Scheme Infix eXtension syntax

Scheme Infix eXtension (SIX)

Interfacing to pure JavaScript

(* (+ 1 2) \3+4 (+ 5 6))

(string-append \"a" "b" \"c")

\alert("ELS 2021")


(define add1 \function(x) { return x+1; })

Scheme Infix eXtension (SIX)

Mixing JavaScript and Scheme expressions

\1+`(* 2 3)


(string-append "now: " \ new Date().toString())

(+ 1 2 \3*4*(`5) 6 7)


(let ((x 10)) \Math.sqrt(1/`x))

Scheme Infix eXtension (SIX)

Resolving identifier ambiguities

(define number-of-items 100)





  • Universal Backend (GVM)
  • Reader (SIX)

GVM representation of types

Figure 1: GVM’s representation of the Scheme types in JavaScript

Bridging the semantic gap

The \ reader macro constructs an AST from a SIX expression


    (six.literal 2)
      (quasiquote v)
        (six.identifier i)
        (six.literal 1)))))

Bridging the semantic gap

The six.infix macro

  • Creates a string of the JavaScript code wrapped in a function so that it can be passed to the JavaScript eval
  • One Scheme parameter per ` expression, eval'd, converted and passed
  • Performs type conversions

FFI mapping of types

Figure 2: FFI mapping of types between Scheme and JavaScript

Pass-through types

(scheme val) ---> _Scheme object
foreign(val) ---> _Foreign object

Converting procedures and functions

  • Transparent conversion
    • Scheme procedures mapped to JS async functions
    • Results are Promises
  • Scheme to JS call: add Scheme callback for Promise resolution, Scheme thread waits until callback is called
  • JS to Scheme call: create a promise, put promise, procedure and parameters in a “callback queue” managed by a Scheme thread, when Scheme call is done the promise is resolved with the result

Function memoization through self-modifying code

(define (add1 x) \(`x)+1)

Accessing Scheme Object Representation

(define num 1+2i)
(println \(`num).scmobj.imag)
(println num)


Green threads

(define-syntax future
  (lambda (stx)
    (syntax-case stx ()
      ((future expr)
       #'(thread (lambda () expr))))))

(define touch thread-join!)

(define (pmap f lst)
  ;; "parallel" map
  (map touch (map (lambda (x) (future (f x))) lst)))

(define memo

(define (page n)
   "/commons/thumb/1/1e/" memo
   "/page" (number->string n) "-593px-" memo ".jpg"))

(define (fetch-blob url)
  \fetch(`url).then(function (r) { return r.blob(); }))

(define (->URL blob)

(define (show url)
    "<img src='"+(`url)+"' width=200px>"))

(define (images)
  (pmap (lambda (n) (->URL (fetch-blob (page n))))
        (iota 43 1)))

;(for-each show (images))

Plotting the ISS position

(define (fetch-json url)
  \fetch(`url).then(function (r) { return r.json(); }))

(define url "https://api.wheretheiss.at/v1/satellites/25544")

(define (add-marker p)
  (let loop ()
    (let* ((json (fetch-json url))
           (lat  \(`json).latitude)
           (lon  \(`json).longitude)
           (marker (list->table `(("latLng" ,lat ,lon)))))
      \map.addMarker(iss_markers.length, `marker)
      (thread-sleep! p)

(define (update-iss-position p)
  (thread (lambda () (add-marker p))))

(define (start)
  (thread-sleep! 2)
  (update-iss-position 10))

Thank you!

This presentation is available at

Try out Gambit Scheme's JS REPL at