-exe
… and if you want that wrapped in a trivial HTML you can -exe -o foo.html
-:r7rs
continuation-capture
to switch threads)
fetch
?
fetch
and it will all be concurrent (it is one of the examples in the paper I shared with you, which will be presented at ELS21 by the way)
try this on https://gambitscheme.org/try
(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.body.insertAdjacentHTML(
"beforeend",
"<img src='"+(`url)+"' width=200px>"))
(define images
(pmap (lambda (n) (->URL (fetch-blob (page n))))
(iota 43 1)))
(for-each show images)
you should see all the pages appear quickly… if you replace pmap
by map
it will be 10x slower
@amirouche it depends what kind of documentation you are looking for… there are academic papers about how the universal backend works (see for example https://www.researchgate.net/publication/304551221_Compiling_for_multi-language_task_migration). There is no “software developper” documentation however. Here’s a quick overview. The files implementing the universal backend are gsc/_t-univ-*.scm
. In essence they implement a translator from the GVM (Gambit Virtual Machine) code and the target language (JavaScript, Python, etc). There are 2 main entry point to the universal backend: the procedure univ-dump-code
(which implements GVM -> target) and the procedure univ-link
(which implements the static module linker). Both of these procedures, and other methods of the backend are contained in a target
object that is initialized by the univ-setup
procedure:
(univ-setup 'js '((".js" . JavaScript)) '() '())
(univ-setup 'python '((".py" . Python)) '((pre3)) '())
(univ-setup 'ruby '((".rb" . Ruby)) '() '())
(univ-setup 'php '((".php" . PHP)) '((pre53)) '())
These calls define the targets that can be used on the gsc command line, i.e. -target js
, -target python
, etc The fields of the target
object are documented in gsc/_back.scm
. For example:
;; nb-regs Integer denoting the maximum number of GVM registers
;; that should be used when generating GVM code for this
;; target machine.
;;
;; nb-arg-regs Integer denoting the maximum number of procedure call
;; arguments that are passed in GVM registers.
;;
;; compactness Integer denoting the level of compactness of the
;; generated code. Levels from 0 to 5 cause the
;; generation of increasingly compact code with little or
;; no impact on execution speed. Lower values tend to
;; make the generated code more humanly readable. Above
;; a level of 5 the compiler will trade execution speed
;; for saving code space. The detailed meaning of this
;; option depends on the target and some targets may
;; ignore it.
The universal backend implements the code generation for multiple target languages by using code generation macros. These have names starting with a ^
, for example (^if test true [false])
generates a 1 or 2 branch “if” construct in the target language and (^ X Y Z)
concatenates the target code X, Y and Z. The ^if
macro is implemented through the univ-emit-if
procedure which dispatches on the target language:
(define (univ-emit-if ctx test true #!optional (false #f))
(case (target-name (ctx-target ctx))
((js php java)
(^ "if (" test ") {\n"
(^indent true)
(if false
(^ "} else {\n"
(^indent false))
(^))
"}\n"))
((python)
(^ "if " test ":\n"
(^indent true)
(if false
(^ "else:\n"
(^indent false))
(^))))
((ruby)
(^ "if " test "\n"
(^indent true)
(if false
(^ "else\n"
(^indent false))
(^))
"end\n"))
((go)
(^ "if " test " {\n"
(^indent true)
(if false
(^ "} else {\n"
(^indent false))
(^))
"}\n"))
(else
(compiler-internal-error
"univ-emit-if, unknown target"))))
The implementation of Scheme primitives is done through the univ-define-prim
macro. For example here is the implementation of ##fxmin
that returns the minimum of two fixnums:
(univ-define-prim "##fxmin" #t
(make-translated-operand-generator
(lambda (ctx return arg1 arg2)
(return (^if-expr 'scmobj
(^< (^fixnum-unbox arg1) (^fixnum-unbox arg2))
arg1
arg2)))))
^if-expr
macro generates conditional expressions. Note that the first parameter is a type (the type of the value returned by the conditional expression) because some targets need to have this information, namely the go
target implements conditional expressions using a function call:(define (univ-emit-if-expr ctx type expr1 expr2 expr3)
(case (target-name (ctx-target ctx))
((js ruby java)
(^ expr1 " ? " expr2 " : " expr3))
((php)
(^parens (^ expr1 " ? " expr2 " : " expr3)))
((python)
(^ expr2 " if " expr1 " else " expr3))
((go)
(^apply (univ-emit-fn-decl
ctx
#f
type
'()
(^if expr1
(^return expr2)
(^return expr3)))
'()))
(else
(compiler-internal-error
"univ-emit-if-expr, unknown target"))))
##
and the empty namespace relate to (import (gambit))
in a R7RS library ?
@amirouche If an R7RS app has 2 modules mod1.sld
and mod2.sld
then here is how to compile and link them:
% cat mod1.sld
;; File: mod1.sld
(define-library (mod1)
(export hi)
(import (scheme base) (scheme write))
(begin
(define (hi) (display "hello\n"))))
% cat mod2.sld
;; File: mod2.sld
(define-library (mod2)
(import (scheme base) (mod1))
(begin
(define (main) (hi) (hi))
(main)))
% gsc -target js -link . -nopreload mod1.sld mod2.sld
mod1.sld:
mod2.sld:
% cat mod2_.js mod1.js mod2.js `gsc -i -e '(display (path-expand "~~lib/_gambit.js"))'` > app.js
% node app.js
hello
hello
% cat mod2_.js `gsc -i -e '(display (path-expand "~~lib/_gambit.js"))'` mod2.js mod1.js > app.js
% node app.js
hello
hello
% cat mod1.js mod2.js `gsc -i -e '(display (path-expand "~~lib/_gambit.js"))'` mod2_.js > app.js
% node app.js
/Users/feeley/app.js:4
_cst0__mod1 = new _ScmString([104,101,108,108,111,10]);
^
ReferenceError: _ScmString is not defined
at Object.<anonymous> (/Users/feeley/app.js:4:1)
at Module._compile (internal/modules/cjs/loader.js:1138:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10)
at Module.load (internal/modules/cjs/loader.js:986:32)
at Function.Module._load (internal/modules/cjs/loader.js:879:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
at internal/main/run_main_module.js:17:47
Note that the order in which the generated .js
files are concatenated has no importance, except that the “link file” (here mod2_.js
) must come first because it contains the runtime system functions needed to create Scheme objects and “register” each module in the other files. When all modules are “registered” the Scheme code will start executing. In principle (untested) after the link file is loaded, the other files could even be loaded asynchronously. If you prefer to manually start the app (say after other JS modules and a UI component are initialized) then you should do:
@all_modules_registered@ = function () { };
and at the point where you want to start the execution of the Scheme code:
@program_start@();
Note that the @
s are special notation to abstract away from the actual name used by the Gambit compiler, which could be a short name if you specify a compactness
option above 0. To use the @
feature you need to write:
(##inline-host-declaration “
@all_modules_registered@ = function () { };
function my_program_start() { @program_start@(); }
”)
and call my_program_start()
in your web app.
Here is my test program:
❯ cat webui.scm
;; required to use javascript notation inside Scheme.
(import (_six js))
(import (scheme base))
(define (console-log obj)
\console.log(`obj))
(define (document-query-selector selector)
\document.querySelector(`selector))
(console-log 42)
(console-log (document-query-selector "body"))
Here is the cli dance:
gsc -target js -exe -nopreload $(pwd)/../../../gambit/lib/scheme/base/base.sld $(pwd)/../../../gambit/lib/_six/js.sld webui.scm && mv webui app.js && python3 -m http.server
index.html
that will load app.js
.