Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions docs/compiler.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,68 @@ state so the caller can inspect or save it.

This is used when the compiler needs to **observe** the secondary
values rather than just propagate them.

## Targets

`*target*` tells a compilation handler how the result should be
delivered. Handlers see three targets:

- `:return` — emit a return statement
- `(:assign v)` — assign to the JS variable `v`
- `:discard` — execute for side effects only

Callers of `convert` may also request `:expression` (the default),
but `convert` translates this to `(:assign tmp)` before calling
`convert-1`, so handlers never see it directly.

### The `convert` functions

- **`(convert sexp &key multiple-value-p target)`** — compile a
subform whose value is needed in the given target.
- **`(convert-tail sexp &key target)`** — compile a subform in tail
position, preserving the current `*multiple-value-p*` and
defaulting to the current `*target*`.
- **`(convert-for-value sexp &optional multiple-value-p)`** — compile
a subform and return `(values preceding-stmts js-expression)`,
introducing a temporary variable if needed.

### Expression vs statement detection

`convert` determines whether a handler's result is a JS expression or
a JS statement by calling `js-expression-p` (in codegen.lisp) on the
returned AST. Statement-only operators (`return`, `var`, `group`,
`if`, `while`, `try`, `throw`, etc.) are classified as statements;
everything else is an expression.

To keep this unambiguous:
- Use `?` for ternary expressions, `if` for if-statements.
- Use `progn` for expression sequences (comma operator), `group` for
statement blocks.

### Writing compilation handlers

Handlers fall into two categories:

**Leaf handlers** compute their result directly. Sub-forms (if any)
are compiled via `convert` or `convert-for-value`. The handler
returns a JS expression AST and `convert` adapts it to the target
automatically.

```lisp
(define-builtin car (x)
`(get ,x "$$jscl_car"))
```

**Propagator handlers** delegate to a sub-form in result (tail)
position by passing `*target*` through via `convert-tail`. They
produce JS statements and `convert` uses them as-is.

For example, `if` propagates the target to both branches via
`convert-tail` (which defaults to `*target*`):

```lisp
(define-compilation if (condition true &optional false)
`(if (!== ,(convert condition) ,(convert nil))
,(convert-tail true)
,(convert-tail false)))
```
17 changes: 15 additions & 2 deletions src/compiler/codegen.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,19 @@



;;; Returns T if FORM is a JS expression AST node, NIL if it is a
;;; statement. `if' is always a statement (use `?' for ternary).
;;; `progn' is always an expression (comma operator); use `group' for
;;; statement sequences.
(defun js-expression-p (form)
(cond
((atom form) t)
((vectorp form) t)
(t (not (member (car form)
'(return var let group if while switch for for-in
try catch finally throw label break))))))


;;; Statements generators
;;;
;;; `js-stmt' generates code for Javascript statements. A form is
Expand Down Expand Up @@ -494,7 +507,7 @@
(js-format "return ")
(js-expr value)
(js-end-stmt)))
(var
((var let)
(flet ((js-var (spec)
(destructuring-bind (variable &optional initial)
(ensure-list spec)
Expand All @@ -503,7 +516,7 @@
(js-format "=")
(js-expr initial no-comma)))))
(destructuring-bind (var &rest vars) (cdr form)
(js-format "var ")
(js-format (if (eq (car form) 'let) "let " "var "))
(js-var var)
(dolist (var vars)
(js-format ",")
Expand Down
Loading