(G)erbil E(x)tended (J)avaScript (S)cheme Loader
npm install gxjs-loaderGxJS Loader: Load Scheme files into JavaScript
GxJS Loader is used to take either source code written in Gerbil Gambit or Gambit Scheme
scheme and transpile them to JavaScript.
Easy! I choose yarn, but npm also works.
yarn add gxjs-loader --dev
To start with here’s a Gerbil file (.ss) that mixes JavaScript code with
Gerbil code.
(import :js)
(js#declaration "console.log('started GxJS loader!')")
(def property-name "This is valid")
(def GxJS-jso
{
thisIsTheAnswer: 42
'this "is how we make the moonshine"
property-name 'symbols-have-no-js-value!
})
(def (this-is-gxjs! (val 42))
(js#statement "console.log('This is GxJS!', (@1@), (@2@), (@3@))"
val
(js#foreign->js GxJS-jso)
(js#scm->js (string-append "string " "append"))))
(js#statement "console.log('finished GxJS loader');
module.exports = RTS.scm2host(@1@);" this-is-gxjs!)
Now a JavaScript file that requires what we export, calls what we export, and
exports what we export.
const GxJS = require('gxjs-loader!./gxjs-loader-usage.ss')
GxJS('yay!');
module.exports = GxJS;
When run at the shell after compilation with node -e that outputs:
started GxJS loader!
finished GxJS loader
This is GxJS! { codes: [ 121, 97, 121, 33 ] } {
thisIsTheAnswer: 42,
this: 'is how we hake the moonshine',
'This is valid': {
name: 'symbols-have-no-js-value!',
hash: 386716666,
interned: true
}
} string append
Note that the RTS took our 'yay!' JavaScript string and turned it into a
scheme string when calling the function.
Gerbil itself transpiles its Meta-Scheme dialect to Gambit scheme. If we
want to compile a Gambit scheme file without calling gxc, the gerbil
compiler, we can do so.
(declare (extended-bindings))
(##inline-host-declaration "console.log('Started Gambit-only GxJS loader!')")
(define property-name "This is valid")
(define GxJS-vector
(##vector
property-name 42
'this "is how we hake the moonshine"))
(define (this-is-gambit-gxjs! #!optional (val 42))
(##inline-host-statement "console.log('This is GxJS!', (@1@), (@2@))"
val GxJS-vector))
(##inline-host-statement "console.log('finished Gambit-only GxJS loader');
module.exports = RTS.scm2host(@1@);" this-is-gambit-gxjs!)
Now a JavaScript file that requires what we export, calls what we export, and
exports what we export.
const gambitGxJS = require('gxjs-loader!./gxjs-loader-usage.scm')
gambitGxJS('gambitYay!');
module.exports = gambitGxJS;
cd ../gxjs-loader/ && yarn run webpack && du -h dist/* && cd - ; echo "TESTING"; echo; cd ../gxjs-tests/; yarn run webpack ; node -e "require ('./dist/main.js')"
By default the loader just loads the scheme file, using Gerbil then Gambit to
compile it first, into the gxjs runtime system.
It does so by adding const RTS = require('gxjs') to the top, then wrapping the
output from the compiler in an ArrowFunctionExpression that it calls.
If the extenstion is .scm we do not call Gerbil at all, by default.
But that can be changed. Mostly for developing the runtimes things need changing
around. Here are the options.
- -link: If this is passed and not false make a link file that has the
RTS definition and do not require any RTS’s.
- return: When this is passed a symbol name then the very end of the
code has a return statement for that symbol. i.e.
gxjs-loader?-linkreturn=RTS adds that.
- call: All the generated code is wrapped in an
ArrowFunctionExpression, mostly to isolate it and deal with scope. If
we want to call that function, which be default we do, call contains
the arguments we wish to call it with. Default: [].
If false we do not call the generated function.
- exports: Do we want to place a module.exports = before the function
or function call expression? If this is passed and not false, yes, yes
we do. i.e.
'gxjs-loader?-link&return=RTS&exports!./gxjs-link-loader-runtime.ss'
- args: This is an array of parameters to declare the ArrowFunction
with. By default [] of course.
- RTS: If is is a string that becomes the argument that we require
for const RTS to become the runtime system. If it’s false there is no
RTS required.
i.e.
'gxjs-loader?args=["RTS"]&RTS=false&call=false&exports!./gxjs-link-loader-runtime.ss'
or 'gxjs-loader?RTS=./gxjs-link-loader.js!./gxjs-link-use-runtime.ss'
Most of the time there is a const RTS = require('gxjs') inserted in the file.
But sometimes, like, so far, the one time I needed to create the runtime
contained in 'gxjs', we actually want the compiler to create one for us.
Let’s create the runtime we want, which is the smallest. Essentially all it does
is change the upstream (Gambit v4.9.3) module initialization to one that
always runs.
(import :js)
(js#statement #<
const r = this;
r.sp = -1;
r.stack[++this.sp] = void 0;
r.r0 = this.underflow;
r.nargs = 0;
r.trampoline(module_descr[4]);
};
EOF
)
And now the js to use it.
const RTS = require('gxjs-loader?-link&return=RTS&exports!./gxjs-link-loader-runtime.ss');
const extraRunTime = require('gxjs-loader?args=["RTS"]&RTS=false&call=false&exports!./gxjs-link-loader-runtime.ss');
extraRunTime(RTS);
console.log('New RTS:', RTS.glo, extraRunTime)
module.exports = RTS;
Now we can use that RTS elsewhere
(import :js)
(js#declaration "console.log('Using another RTS,', RTS.glo)")
(js#statement "module.exports = 42")
const answer = require('gxjs-loader?RTS=./gxjs-link-loader.js!./gxjs-link-use-runtime.ss')
module.exports = answer;
Go to