A Scheme Foreign Function Interface

to JavaScript Based on an Infix Extension

Marc-André Bélanger

Marc Feeley

Presented at ELS 2021

https://zenodo.org/record/4711424

Built with Gambit Scheme and RevealJS.

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

Overview

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

Gambit Scheme

https://gambitscheme.org/try

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] = { ... };

___SCMOBJ SCMOBJ_to_EBCDIC (___SCMOBJ src, EBCDIC *dst)
{
  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-declare-end
)

(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" ""
  (read-char))
                    

The FFI is implementation specific, idiosyncratic.

Goals

  • 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)
  \document.body.style.background=`color)
                    

Syntax

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")

\document.body.style.background="blue"

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

Scheme Infix eXtension (SIX)

Mixing JavaScript and Scheme expressions


\1+`(* 2 3)

\1+`var

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

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

\Math.sqrt(`9)-3

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

Scheme Infix eXtension (SIX)

Resolving identifier ambiguities


(define number-of-items 100)

\10*`number-of-items

\`number-of-items*10

\(`number-of-items)*10
                    

Implementation

  • 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


'\2*`v[i+1]

(six.infix
  (six.x*y
    (six.literal 2)
    (six.index
      (quasiquote v)
      (six.x+y
        (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)
\(`num).scmobj.imag=9
(println num)
                    

Examples

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
  (string-append
   "Scheme_-_An_interpreter_for_extended_"
   "lambda_calculus.djvu"))

(define (page n)
  (string-append
   "https://upload.wikimedia.org/wikipedia"
   "/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)
  \URL.createObjectURL(`blob))

(define (show url)
  \document.getElementById("threads").insertAdjacentHTML(
    "beforeend",
    "<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)))))
      \iss_markers.push(`marker)
      \map.addMarker(iss_markers.length, `marker)
      (thread-sleep! p)
      (loop))))

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

(define (start)
  \document.getElementById("ui-10").style.display="none"
  \showMap()
  (thread-sleep! 2)
  (update-iss-position 10))
                    

Thank you!

This presentation is available at
https://udem-dlteam.github.io/els2021-presentation/

Try out Gambit Scheme's JS REPL at
https://gambitscheme.org/try