Skip to content

Latest commit

 

History

History
158 lines (125 loc) · 5.13 KB

HACKING.org

File metadata and controls

158 lines (125 loc) · 5.13 KB

Hacking JSCL

Newbie guide

  • Load slime from the root directory.
  • C-c C-l jscl.lisp to load the whole project
  • (jscl:bootstrap) will generate jscl.js
  • Add tests
  • Open tests.html in your browser to see your failed tests

Code organization, style, etc.

Every definition should include documentation and unit tests.

Definitions are organized in different files following CLHS chapters.

Inside every file definition ordering should follow each CLHS dictionary index. This should make easier to find what has already been defined and what has not been defined yet.

Definitions should follow CLHS naming (e. g., cons definition should be (defun cons (object-1 object-2) …), not (defun cons (x y)) or (defun cons (obj1 obj2)).

Tests should follow the same organization as definitions.

CLHS examples can be used as tests.

Documentation strings should not be taken from CLHS (due to license issues). It is recommended to take them from SBCL instead of reinventing them.

Hacking the compiler

Interactive development

  • Load slime and bootstrap JSCL as explained in the newbie guide.
  • Work in package JSCL with (in-package #:jscl).
  • Use the compile-toplevel function to prepare the JavaSript code associated to a S-expression SEXP, compiled as a toplevel form.
  • Use the process-toplevel function to prepare the JavaScript AST associated to a S-expression SEXP, compiled as a toplevel form.
JSCL> (process-toplevel '(+ 1 2))
(PROGN (SELFCALL (PROGN) (RETURN (+ 1 2))))

JSCL> (compile-toplevel '(+ 1 2))
"(function(){return 1+2;
})();
"

Compiler anatomy

This paragraph outlines a few crude elements of the compiler. A compilation environment keeps track of macros, identifiers numbers, etc.

Code generation. The codegen.lisp file implements details of JavaScript code generation. The file entrypoint is the js function which takes a as input a S-expression representing a JavaScript program as an AST in common Lisp notation and emits the corresponding JavaScript code to the *js-output* output channel, which can be set to t to target stdout. For instance

JSCL> (js '(+ 1 2))
1+2;

The representation accepted by js uses call to denote function calls and named-function to distinguish function expressions featuring a function name.

JSCL> (js '(call (property |console| "log") "A message."))
console['log']('A message.');

JSCL> (js '(call (get |console| |log|) "A message."))
console.log('A message.');

JSCL> (js '(call (get |console| "log") "A message."))
console.log('A message.');


JSCL> (js '(function (a b) (return (+ a b))))
(function(A,B){return A+B;
});

JSCL> (js '(named-function "add2" (a b) (return (+ a b))))
(function add2(A,B){return A+B;
});

JavaScript driver The file prelude.js is a JavaScript driver defining various low-level functions, such as trampolines to call Common-Lisp functions from JavaScript or handling of Cons cells and Symbol cells.

It defines a global object JSCL populated with a hand of substructures such as packages owning Lisp packages and internal where internal functions called by the emitted JavaScript are kept.

It is worth to mention that these internal functions are not used, nor are they referred to, in the code generation.

Compiler Macros, Compilations and Builtins Compilations and Builtins are used to enrich the Common Lisp vocabulary understood by the process-toplevel function. Compilations and Builtins differ in how they handle their arguments, so that Compilations can defined special forms and Builtins can define primitive functions. Compilations and Builtins can use Compiler Macros to factorise or in-line parts of the JavaScript AST they need to generate. Compiler macros should not be mistaken for host Common Lisp macros.

JSCL> (define-builtin mod (x y)
  `(selfcall
    (if (== ,y 0)
        (throw "Division by zero in mod"))
    (return (% ,x ,y))))

JSCL> (with-compilation-environment (process-toplevel '(mod 7 2)))
(PROGN
 (SELFCALL
  (IF (== 2 0)
      (THROW "Division by zero in mod"))
  (RETURN (% 7 2))))

Multiple Values

Every Common Lisp function is represented as a Javascript function with an extra first argument called values. Functions can return multiple values by returning the result of this function, e.g:

(lambda () (values 1 2 3))

could be compiled to something like

function (values) {
  return values(1,2,3)
}

There are two possible values for this argument, pv (primary value), which returns the first arg. And mv, which return all of them as an tagged array.

When compiler will automatically pass pv or mv, depending on the context where the function is used. For example in (+ 1 (f x)), f is called with pv. However, if all multiple values are relevant, like in:

(lambda ()
  (f 0))

then f is called passing the values from the parent function. So this would compile to

function (values) {
  return f(values, 0);
}