Author: do you see issues with that use case?
244 karma · joined March 12, 2014
Author: do you see issues with that use case?
The Gambit Scheme REPL that comes with a tutorial, supports threads in the browser and has a JS FFI: https://try.gambitscheme.org
Gambit in emacs in the browser: https://feeley.github.io/gambit-in-emacs-in-the-browser/
https://app.codeboot.org/5.3.1/?init=.fbWF0aF9wcmFjdGljZS5we...
For more complex UIs, CodeBoot provides an FFI for accessing the DOM directly from Python code. For example here is a dice throwing app with a button to roll the dice again. The text in the button has translations to multiple languages and will adjust to the browser's default.
https://app.codeboot.org/5.3.1/?init=.fZGljZS5weQ==~XQAAgADq...
https://try.gambitscheme.org is a playground for the Scheme language (specifically Gambit Scheme). It has a REPL and a code editor that saves files in the browser local storage. It also has a tutorial for learning the basics of Scheme.
https://codeboot.org is a playground for Python (there's an old version that supports JavaScript and we may resurrect it at some point). It was specifically designed as a simple IDE to teach programming to beginners and is in use at the Université de Montréal. Aside from the simple and intuitive UI, it has two important features for teaching:
1) Fine granularity single-stepping of Python code to help teach students how the computer goes about executing the code. It displays a bubble containing the variables in scope and their values.
2) It can bundle programs and the current state of execution in a URL that can be embedded in documents (HTML, PDF, PowerPoint, Keynote, etc) so that coding examples can be executed by the students in a single click. It also allows students to create simple web apps that require no installation and can easily be shared with others (because the URL contains all the relevant code of the app). You will find examples of this feature on the following pages:
https://codeboot-org.github.io/presentations/
https://codeboot-org.github.io/cegep/ (in french, but just click on the images)
- REPL history with up/down arrows (explained on the landing page's README)
- You can use the "help" procedure to browse the full documentation
- TAB key for autocompletion
- In addition to the REPL there is a code editor to edit programs stored in the browser
- The system supports multiple threads, one REPL per thread
- There is a builtin tutorial to learn the basics of Scheme with examples executable in the REPL with a single click on a "run" button
- The REPL supports single stepping the execution of code
- Easy interface to JS with the SIX syntax extension, e.g. (let ((msg "hello")) \console.log(`msg, 1+2/3))
- The error messages are clear and precise giving the file and line number and highlighting in yellow the piece of code (in the REPL or file) that raised the exception. Just try this in both systems to see the difference:
> (define (f x) (/ 1 (* x x)))
> (f 5)
> (f "hello") ;; the * procedure will raise an exception and highlight "(* x x)"What I'd love to do someday is to write a compiler for a fairly complete C subset in POSIX shell. The goal would be to use only a POSIX shell and this compiler to compile TCC, and then use TCC to bootstrap gcc, all from source. This would be a great tool for reproducible builds from source. If someone here finds this interesting and would like to help out, please reach out to me.
$ cat hello.scm
(display "hello!\n")
$ gsc hello.scm
$ gsi hello.o1
hello!
$ ls -l hello.o1 # this is generated by gcc
-rwxrwxr-x 1 feeley feeley 18152 Mar 13 17:16 hello.o1
$ rm hello.o1
$ gsc -cc "tcc -shared" hello.scm
$ gsi hello.o1
hello!
$ ls -l hello.o1 # this is generated by tcc
-rwxrwxr-x 1 feeley feeley 4432 Mar 13 17:17 hello.o1It has tail calls, continuations and even green threads (SRFI 18 compliant), and a decentralized module system that can load libraries directly from github and a JS FFI! See this paper for advanced examples that you can copy-paste to the REPL: http://www.iro.umontreal.ca/~feeley/papers/BelangerFeeleyELS...
> (module-whitelist-add! '(github.com/feeley))
> (import (github.com/feeley/roman demo))
This paper might be helpful if you want to know why it was designed that way:https://www.iro.umontreal.ca/~feeley/papers/HamelFeeleyELS20...
https://www-labs.iro.umontreal.ca/~feeley/papers/BelangerFee...
So the core of the RVM has no dependencies with external tools (i.e. it is in "pure" shell script). The only dependencies are for I/O, namely for the "putchar" RVM primitive and the "getchar" RVM primitive (both of which do byte-at-a-time binary I/O on stdin and stdout). For putchar the shell "printf" (which is usually builtin) command is used, specifically
printf \\$((byte/64))$((byte/8%8))$((byte%8))
For getchar is is not possible to use the shell "read" command because it does not handle null bytes on stdin (and we want Ribbit to support binary I/O). This is admittedly somewhat of an artificial constraint given that Ribbit will probably be mostly used for text processing, like to write compilers that map source code text to target code text. So getchar is implemented with set `sed q | od -v -A n -t u1`
and then the next byte on stdin is available using $1 and "shift" is used to move to the next byte. The call to sed is to read each line separately (useful to implement a REPL where you enter a command which is executed before having to enter the next command). Both "sed" and "od" are standard unix tools. Moreover we checked that those set of options have existed for a long time (at least the early 1990's). It would be interesting to test many POSIX shells to verify Ribbit's portability (we used bash, dash, zsh and ksh for our testing).The low performance of the POSIX shell RVM is due to two things. First, the shell's interpreter is not fast. The simple loop
n=10000000; while [ $n -gt 0 ]; do n=$((n-1)); done
runs about 5000 times slower with bash than the equivalent JavaScript code run with nodejs, which is implemented with a JIT. Secondly, the POSIX shell does not have arrays so it is necessary to use the shell "eval" command to do indexing. For example a[i]=0 becomes eval a$i=0
As you can imagine the RVM implementation uses arrays all over the place for implementing access to the RVM's stack and Scheme objects (both of which are implemented using a garbage collected heap, which is conceptually an array of 3 field records and implemented in the shell as 3 "arrays").The POSIX shell version of Ribbit was designed to be as portable as possible, only relying on a POSIX shell to run Scheme code. This can be useful in a context where you only have a POSIX shell at hand and you want to bootstrap a more efficient set of development tools (C compiler, editor, etc) and you don't mind waiting a day or two for the build to complete. It is also useful when you want a reproducible build pipeline and the only tool you trust is the shell.
The Ribbit design is optimized for a setting where the program is communicated say over the Web before it is executed, so it is important to minimize the footprint = transfer time.
Ribbit is also bootstrapped (it can compile itself). This means that you only need another Scheme system for the initial step of the bootstrap to compile the Ribbit compiler to one of the supported target languages. After this step the Ribbit compiler only needs an implementation of the target language to run. For example, you can use the Gambit interpreter to compile the Ribbit compiler to a POSIX shell script (about 64K bytes). After this is done then you only need a POSIX shell to compile any Scheme program to C, JavaScript, Python, or POSIX shell script.
In its current state the POSIX shell target is mostly a proof of concept to demonstrate that the Ribbit VM is extremely portable. One area of practical use is for bootstrapping compilers for other languages using minimal tools (see the Mes project https://bootstrappable.org/projects/mes.html for bootstrapping gcc to have a reproducible build process for gcc). With Ribbit's POSIX shell target it would be possible to build gcc on any system that has a POSIX shell (without needing other build tools like a C compiler, linker, etc).
% /usr/bin/time gsi rsc.scm -t c -l min repl-min.scm
0.06 real 0.05 user 0.00 sys
% gcc -O3 repl-min.scm.c
% echo "(define f (lambda (n) (if (< n 2) n (+ (f (- n 1)) (f (- n 2))))))(f 25)" | /usr/bin/time ./a.out
> 0
> 75025
>
0.10 real 0.10 user 0.00 sys
My guess is that you were using an old version of Gambit (before I added the support for older versions of Gambit).The work on Ribbit is not driven by a specific application. It started as a challenge to write the smallest footprint implementation of Scheme after being inspired by the "sectorlisp" system (which has now reached an amazing milestone of just 0.5 KB!). However sectorlisp doesn't have a GC, continuations, tail-calls, etc so the comparison is a bit apples-to-oranges.
The main advantages of the RVM are its small size and its portability. This is particularly useful for code mobility. The exact same Scheme code can run on the desktop, in the browser, on the web server, on a microcontroller, an Excel spreadsheet, a shell script, etc etc And because continuations are a good way to represent the state of a running process I foresee applications where a running program migrates easily from one environment to the other by serializing the continuation (a game running in your desktop browser could be migrated to the browser on your phone to continue playing on the subway ride back home).
Concerning "functional-style programming (avoiding mutation) inherently uses more ram than a more mutation-heavy style" it greatly depends on how smart your compiler is. A smart one can in some cases generate the same code. An example that comes to mind is a tail-recursive function that adds numbers from a list. The functional program has no mutations but a compiler doing TCO can generate the same code as an imperative program that uses mutations.
The Ribbit compiler is written in Scheme and can be executed with Gambit, Guile or Chicken. It has been tested with Gambit v4.7.5 and above. For the best experience install Gambit from https://github.com/gambit/gambit .
Currently Ribbit supports the target languages C, JavaScript, Python and Scheme which are selectable with the compiler's `-t` option with `c`, `js`, `py`, and `scm` respectively. The compacted RVM code can be obtained with the target `none` which is the default.
The `-m` option causes a minification of the generated program. This requires a recent version of Gambit.
The `-l` option allows selecting the Scheme runtime library (located in the `lib` subdirectory). The `min` library has the fewest procedures and a REPL that supports the core Scheme forms only. The `max` library has most of the R4RS predefined procedures, except for file I/O. The `max-tc` library is like `max` but with run time type checking. The default is the `max-tc` library.
Here are a few examples:
Use Gambit to compile the minimal REPL to JavaScript
and execute with nodejs:
% cd src
% gsi rsc.scm -t js -l min repl-min.scm
% echo "(define f (lambda (n) (if (< n 2) n (+ (f (- n 1)) (f (- n 2))))))(f 25)" | node repl-min.scm.js
> 0
> 75025
> % cd src
% gsi rsc.scm -m -t py -l max repl-max.scm
% python3 repl-max.scm.py
> (+ 1 (* 2 3))
7
or for the C version: % gsi rsc.scm -m -t c -l max repl-max.scm
% gcc -DCPROG -o repl-max.scm.exe repl-max.scm.c
% ./repl-max.scm.exe
> (+ 1 (* 2 3))
7
To install Gambit follow the instructions here: https://github.com/gambit/gambitIf you are interested in building a a self-contained HTML document that embeds a full featured Scheme implementation, you should look into Gambit. Check out https://try.gambitscheme.org
The use case which has motivated our work is code mobility where an executable program can be embedded in a document, email, or website. In that use case the size of the program must be small to minimize the transmission or loading time or to satisfy space constraints, such as the size of a disk boot sector, the URL length limit and the UDP packet size. On the other hand, the space used while the program is executing is of secondary importance.
So the main target application is not microcontrollers, even if it can obviously be used for some microcontrollers (for example a 1-2$ ESP32-C3 has 400 KB of RAM).
One of the possible applications of Ribbit is to implement the RVM (Ribbit VM) in the Excel spreadsheet formula language (which will soon be turing-complete with the addition of the LAMBDA form) to be able to program spreadsheets in Scheme. The execution environment has lots and lots of RAM, but you want the .xls file itself (which contains the RVM) to be as small as possible so it can easily be sent in emails, etc.
The last part of the demo shows how to use threads in the browser and also the JavaScript FFI. The FFI based on an infix syntax is explained in greater detail in this ELS'21 paper: http://www.iro.umontreal.ca/~feeley/papers/BelangerFeeleyELS... . The paper contains several examples. Here's a simple one you can type at the REPL:
(define message "<h1 id=\"title\">hello!</h1>")
\document.body.insertAdjacentHTML("afterbegin", `message)
\document.getElementById("title").innerText=`(object->string (expt 2 100))
Basically a backslash switches to JavaScript (with infix syntax) and a backquote switches back to Scheme (with prefix syntax).
The whole Gambit system fits in a 640KB gzipped JavaScript file, so it is reasonably fast to load.
If you are interested in a really tiny Scheme implementation in JavaScript that supports tail-calls and call/cc and an incremental Scheme compiler and a REPL and most of the R4RS procedures, you might want to try out the Ribbit Scheme implementation which is just under 6 KB of (non-gzipped) JavaScript: https://udem-dlteam.github.io/ribbit/repl-max.html . That implementation is described in a paper at the VMIL'21 workshop (presentation on October 19).
(jseval "alert('hello')")