;;;# Parenscript Language Reference
;;; Create a useful package for the code here...
(in-package #:cl-user)
(defpackage #:ps-ref (:use #:ps))
(in-package #:ps-ref)
;;; This chapters describes the core constructs of Parenscript, as
;;; well as its compilation model. This chapter is aimed to be a
;;; comprehensive reference for Parenscript developers. Programmers
;;; looking for how to tweak the Parenscript compiler itself should
;;; turn to the Parenscript Internals chapter.
;;;# Statements and Expressions
;;;t \index{statement}
;;;t \index{expression}
;;; In contrast to Lisp, where everything is an expression, JavaScript
;;; makes the difference between an expression, which evaluates to a
;;; value, and a statement, which has no value. Examples for
;;; JavaScript statements are `for', `with' and `while'. Most
;;; Parenscript forms are expression, but certain special forms are
;;; not (the forms which are transformed to a JavaScript
;;; statement). All Parenscript expressions are statements
;;; though. Certain forms, like `IF' and `PROGN', generate different
;;; JavaScript constructs whether they are used in an expression
;;; context or a statement context. For example:
(+ i (if 1 2 3)) => i + (1 ? 2 : 3)
(if 1 2 3)
=> if (1) {
2;
} else {
3;
}
;;;# Symbol conversion
;;;t \index{symbol}
;;;t \index{symbol conversion}
;;; Lisp symbols are converted to JavaScript symbols by following a
;;; few simple rules. Special characters `!', `?', `#', `@', `%',
;;; '/', `*' and `+' get replaced by their written-out equivalents
;;; "bang", "what", "hash", "at", "percent", "slash",
;;; "start" and "plus" respectively. The `$' character is untouched.
!?#@% => bangwhathashatpercent
;;; The `-' is an indication that the following character should be
;;; converted to uppercase. Thus, `-' separated symbols are converted
;;; to camelcase. The `_' character however is left untouched.
bla-foo-bar => blaFooBar
;;; If you want a JavaScript symbol beginning with an uppercase, you
;;; can either use a leading `-', which can be misleading in a
;;; mathematical context, or a leading `*'.
*array => Array
;;; The `.' character is left as is in symbols. This allows the
;;; Parenscript programmer to use a practical shortcut when accessing
;;; slots or methods of JavaScript objects. Instead of writing
(slot-value foobar 'slot)
;;; we can write
foobar.slot
;;; A symbol beggining and ending with `+' or `*' is converted to all
;;; uppercase, to signify that this is a constant or a global
;;; variable.
*global-array* => GLOBALARRAY
*global-array*.length => GLOBALARRAY.length
;;;## Reserved Keywords
;;;t \index{keyword}
;;;t \index{reserved keywords}
;;; The following keywords and symbols are reserved in Parenscript,
;;; and should not be used as variable names.
! ~ ++ -- * / % + - << >> >>> < > <= >= == != ==== !== & ^ | && || *=
/= %= += -= <<= >>= >>>= &= ^= |= 1- 1+ ABSTRACT AND AREF ARRAY
BOOLEAN BREAK BYTE CASE CATCH CC-IF CHAR CLASS COMMA CONST CONTINUE
CREATE DEBUGGER DECF DEFAULT DEFUN DEFVAR DELETE DO DO* DOEACH DOLIST
DOTIMES DOUBLE ELSE ENUM EQL EXPORT EXTENDS F FALSE FINAL FINALLY
FLOAT FLOOR FOR FOR-IN FUNCTION GOTO IF IMPLEMENTS IMPORT IN INCF
INSTANCEOF INT INTERFACE JS LABELED-FOR LAMBDA LET LET* LEXICAL-LET
LEXICAL-LET* LISP LIST LONG MAKE-ARRAY NATIVE NEW NIL NOT OR PACKAGE
PRIVATE PROGN PROTECTED PUBLIC RANDOM REGEX RETURN SETF SHORT
SLOT-VALUE STATIC SUPER SWITCH SYMBOL-MACROLET SYNCHRONIZED T THIS
THROW THROWS TRANSIENT TRY TYPEOF UNDEFINED UNLESS VAR VOID VOLATILE
WHEN WHILE WITH WITH-SLOTS
;;;# Literal values
;;;t \index{literal value}
;;;## Number literals
;;;t \index{number}
;;;t \index{number literal}
; number ::= a Lisp number
;;;
;;; Parenscript supports the standard JavaScript literal
;;; values. Numbers are compiled into JavaScript numbers.
1 => 1
123.123 => 123.123
;;; Note that the base is not conserved between Lisp and JavaScript.
#x10 => 16
;;;## String literals
;;;t \index{string}
;;;t \index{string literal}
; string ::= a Lisp string
;;; Lisp strings are converted into JavaScript literals.
"foobar" => 'foobar'
"bratzel bub" => 'bratzel bub'
;;; Special characters such as newline and backspace are converted
;;; into their corresponding JavaScript escape sequences.
" " => '\\t'
;;;## Array literals
;;;t \index{array}
;;;t \index{ARRAY}
;;;t \index{MAKE-ARRAY}
;;;t \index{AREF}
;;;t \index{array literal}
; (ARRAY {values}*)
; (MAKE-ARRAY {values}*)
; (AREF array index)
;
; values ::= a Parenscript expression
; array ::= a Parenscript expression
; index ::= a Parenscript expression
;;; Array literals can be created using the `ARRAY' form.
(array) => [ ]
(array 1 2 3) => [ 1, 2, 3 ]
(array (array 2 3)
(array "foobar" "bratzel bub"))
=> [ [ 2, 3 ], [ 'foobar', 'bratzel bub' ] ]
;;; Arrays can also be created with a call to the `Array' function
;;; using the `MAKE-ARRAY'. The two forms have the exact same semantic
;;; on the JavaScript side.
(make-array) => new Array()
(make-array 1 2 3) => new Array(1, 2, 3)
(make-array
(make-array 2 3)
(make-array "foobar" "bratzel bub"))
=> new Array(new Array(2, 3), new Array('foobar', 'bratzel bub'))
;;; Indexing arrays in Parenscript is done using the form `AREF'. Note
;;; that JavaScript knows of no such thing as an array. Subscripting
;;; an array is in fact reading a property from an object. So in a
;;; semantic sense, there is no real difference between `AREF' and
;;; `SLOT-VALUE'.
;;;## Object literals
;;;t \index{CREATE}
;;;t \index{SLOT-VALUE}
;;;t \index{WITH-SLOTS}
;;;t \index{object literal}
;;;t \index{object}
;;;t \index{object property}
;;;t \index{property}
; (CREATE {name value}*)
; (SLOT-VALUE object slot-name)
; (WITH-SLOTS ({slot-name}*) object body)
;
; name ::= a Parenscript symbol or a Lisp keyword
; value ::= a Parenscript expression
; object ::= a Parenscript object expression
; slot-name ::= a quoted Lisp symbol
; body ::= a list of Parenscript statements
;;;
;;; Object literals can be create using the `CREATE' form. Arguments
;;; to the `CREATE' form is a list of property names and values. To be
;;; more "lispy", the property names can be keywords.
(create :foo "bar" :blorg 1)
=> { foo : 'bar', blorg : 1 }
(create :foo "hihi"
:blorg (array 1 2 3)
:another-object (create :schtrunz 1))
=> { foo : 'hihi',
blorg : [ 1, 2, 3 ],
anotherObject : { schtrunz : 1 } }
;;; Object properties can be accessed using the `SLOT-VALUE' form,
;;; which takes an object and a slot-name.
(slot-value an-object 'foo) => anObject.foo
;;; A programmer can also use the "." symbol notation explained above.
an-object.foo => anObject.foo
;;; The form `WITH-SLOTS' can be used to bind the given slot-name
;;; symbols to a macro that will expand into a `SLOT-VALUE' form at
;;; expansion time.
(with-slots (a b c) this
(+ a b c))
=> this.a + this.b + this.c;
;;;## Regular Expression literals
;;;t \index{REGEX}
;;;t \index{regular expression}
;;;t \index{CL-INTERPOL}
; (REGEX regex)
;
; regex ::= a Lisp string
;;; Regular expressions can be created by using the `REGEX' form. If
;;; the argument does not start with a slash, it is surrounded by
;;; slashes to make it a proper JavaScript regex. If the argument
;;; starts with a slash it is left as it is. This makes it possible
;;; to use modifiers such as slash-i (case-insensitive) or
;;; slash-g (match-globally (all)).
(regex "foobar") => /foobar/
(regex "/foobar/i") => /foobar/i
;;; Here CL-INTERPOL proves really useful.
(regex #?r"/([^\s]+)foobar/i") => /([^\s]+)foobar/i
;;;## Literal symbols
;;;t \index{T}
;;;t \index{F}
;;;t \index{FALSE}
;;;t \index{NIL}
;;;t \index{UNDEFINED}
;;;t \index{THIS}
;;;t \index{literal symbols}
;;;t \index{null}
;;;t \index{true}
; T, F, FALSE, NIL, UNDEFINED, THIS
;;; The Lisp symbols `T' and `FALSE' (or `F') are converted to their
;;; JavaScript boolean equivalents `true' and `false'.
T => true
FALSE => false
F => false
;;; The Lisp symbol `NIL' is converted to the JavaScript keyword
;;; `null'.
NIL => null
;;; The Lisp symbol `UNDEFINED' is converted to the JavaScript keyword
;;; `undefined'.
UNDEFINED => undefined
;;; The Lisp symbol `THIS' is converted to the JavaScript keyword
;;; `this'.
THIS => this
;;;# Variables
;;;t \index{variable}
;;;t \index{symbol}
; variable ::= a Lisp symbol
;;; All the other literal Lisp values that are not recognized as
;;; special forms or symbol macros are converted to JavaScript
;;; variables. This extreme freedom is actually quite useful, as it
;;; allows the Parenscript programmer to be flexible, as flexible as
;;; JavaScript itself.
variable => variable
a-variable => aVariable
*math => Math
*math.floor => Math.floor
;;;# Function calls and method calls
;;;t \index{function}
;;;t \index{function call}
;;;t \index{method}
;;;t \index{method call}
; (function {argument}*)
; (method object {argument}*)
;
; function ::= a Parenscript expression or a Lisp symbol
; method ::= a Lisp symbol beginning with .
; object ::= a Parenscript expression
; argument ::= a Parenscript expression
;;; Any list passed to the JavaScript that is not recognized as a
;;; macro or a special form (see "Macro Expansion" below) is
;;; interpreted as a function call. The function call is converted to
;;; the normal JavaScript function call representation, with the
;;; arguments given in paren after the function name.
(blorg 1 2) => blorg(1, 2)
(foobar (blorg 1 2) (blabla 3 4) (array 2 3 4))
=> foobar(blorg(1, 2), blabla(3, 4), [ 2, 3, 4 ])
((slot-value this 'blorg) 1 2) => this.blorg(1, 2)
((aref foo i) 1 2) => foo[i](1, 2)
((slot-value (aref foobar 1) 'blorg) NIL T) => foobar[1].blorg(null, true)
;;; Note that while most method calls can be abbreviated using the "."
;;; trick in symbol names (see "Symbol Conversion" above), this is not
;;; advised due to the fact that "object.function" is treated as a
;;; symbol distinct from both "object" and "function," which will
;;; cause problems if Parenscript package prefixes or package
;;; obfuscation is used.
(this.blorg 1 2) => this.blorg(1, 2)
;;;# Operator Expressions
;;;t \index{operator}
;;;t \index{operator expression}
;;;t \index{assignment operator}
;;;t \index{EQL}
;;;t \index{NOT}
;;;t \index{AND}
;;;t \index{OR}
; (operator {argument}*)
; (single-operator argument)
;
; operator ::= one of *, /, %, +, -, <<, >>, >>>, < >, EQL,
; ==, !=, =, ===, !==, &, ^, |, &&, AND, ||, OR.
; single-operator ::= one of INCF, DECF, ++, --, NOT, !
; argument ::= a Parenscript expression
;;; Operator forms are similar to function call forms, but have an
;;; operator as function name.
;;;
;;; Please note that `=' is converted to `==' in JavaScript. The `='
;;; Parenscript operator is not the assignment operator. Unlike
;;; JavaScript, Parenscript supports multiple arguments to the
;;; operators.
(* 1 2) => 1 * 2
(= 1 2) => 1 == 2
(eql 1 2) => 1 == 2
;;; Note that the resulting expression is correctly parenthesized,
;;; according to the JavaScript operator precedence that can be found
;;; in table form at:
;;; http://www.codehouse.com/javascript/precedence/
(* 1 (+ 2 3 4) 4 (/ 6 7))
=> 1 * (2 + 3 + 4) * 4 * (6 / 7)
;;; The pre increment and decrement operators are also
;;; available. `INCF' and `DECF' are the pre-incrementing and
;;; pre-decrementing operators. These operators can
;;; take only one argument.
(incf i) => ++i
(decf i) => --i
;;; The `1+' and `1-' operators are shortforms for adding and
;;; substracting 1.
(1- i) => i - 1
(1+ i) => i + 1
;;; The `not' operator actually optimizes the code a bit. If `not' is
;;; used on another boolean-returning operator, the operator is
;;; reversed.
(not (< i 2)) => i >= 2
(not (eql i 2)) => i != 2
;;;# Body forms
;;;t \index{body form}
;;;t \index{PROGN}
;;;t \index{body statement}
; (PROGN {statement}*) in statement context
; (PROGN {expression}*) in expression context
;
; statement ::= a Parenscript statement
; expression ::= a Parenscript expression
;;; The `PROGN' special form defines a sequence of statements when
;;; used in a statement context, or sequence of expression when used
;;; in an expression context. The `PROGN' special form is added
;;; implicitly around the branches of conditional executions forms,
;;; function declarations and iteration constructs.
;;; For example, in a statement context:
(progn (blorg i) (blafoo i))
=> blorg(i);
blafoo(i);
;;; In an expression context:
(+ i (progn (blorg i) (blafoo i)))
=> i + (blorg(i), blafoo(i))
;;; A `PROGN' form doesn't lead to additional indentation or
;;; additional braces around it's body.
;;;# Function Definition
;;;t \index{function}
;;;t \index{method}
;;;t \index{function definition}
;;;t \index{DEFUN}
;;;t \index{LAMBDA}
;;;t \index{closure}
;;;t \index{anonymous function}
; (DEFUN name ({argument}*) body)
; (LAMBDA ({argument}*) body)
;
; name ::= a Lisp Symbol
; argument ::= a Lisp symbol
; body ::= a list of Parenscript statements
;;; As in Lisp, functions are defined using the `DEFUN' form, which
;;; takes a name, a list of arguments, and a function body. An
;;; implicit `PROGN' is added around the body statements.
(defun a-function (a b)
(return (+ a b)))
=> function aFunction(a, b) {
return a + b;
}
;;; Anonymous functions can be created using the `LAMBDA' form, which
;;; is the same as `DEFUN', but without function name. In fact,
;;; `LAMBDA' creates a `DEFUN' with an empty function name.
(lambda (a b) (return (+ a b)))
=> function (a, b) {
return a + b;
}
;;;# Assignment
;;;t \index{assignment}
;;;t \index{SETF}
;;;t \index{PSETF}
;;;t \index{SETQ}
;;;t \index{PSETQ}
;;;t \index{DEFSETF}
;;;t \index{assignment operator}
; (SETF {lhs rhs}*)
; (PSETF {lhs rhs}*)
;
; lhs ::= a Parenscript left hand side expression
; rhs ::= a Parenscript expression
; (SETQ {lhs rhs}*)
; (PSETQ {lhs rhs}*)
;
; lhs ::= a Parenscript symbol
; rhs ::= a Parenscript expression
;;; Assignment is done using the `SETF', `PSETF', `SETQ', and `PSETQ'
;;; forms, which are transformed into a series of assignments using
;;; the JavaScript `=' operator.
(setf a 1) => a = 1;
(setf a 2 b 3 c 4 x (+ a b c))
=> a = 2;
b = 3;
c = 4;
x = a + b + c;
;;; The `SETF' form can transform assignments of a variable with an
;;; operator expression using this variable into a more "efficient"
;;; assignment operator form. For example:
(setf a (+ a 2 3 4 a)) => a += 2 + 3 + 4 + a;
(setf a (- 1 a)) => a = 1 - a;
;;; The `PSETF' and `PSETQ' forms perform parallel assignment of
;;; places or variables using a number of temporary variables created
;;; by `PS-GENSYM'. For example:
(let* ((a 1) (b 2))
(psetf a b b a))
=> var a = 1;
var b = 2;
var _js1 = b;
var _js2 = a;
a = _js1;
b = _js2;
;;; The `SETQ' and `PSETQ' forms operate identically to `SETF' and
;;; `PSETF', but throw a compile-time error if the left-hand side form
;;; is not a symbol. For example:
(setq a 1) => a = 1;
;; but...
(setq (aref a 0) 1)
;; => ERROR: The value (AREF A 0) is not of type SYMBOL.
;;; New types of setf places can be defined in one of two ways: using
;;; `DEFSETF' or using `DEFUN' with a setf function name; both are
;;; analogous to their Common Lisp counterparts.
;;; `DEFSETF' supports both long and short forms, while `DEFUN' of a
;;; setf place generates a JavaScript function name with the __setf_
;;; prefix:
(defun (setf color) (new-color el)
(setf (slot-value (slot-value el 'style) 'color) new-color))
=> function __setf_color(newColor, el) {
el.style.color = newColor;
};
(setf (color some-div) (+ 23 "em"))
=> var _js2 = someDiv;
var _js1 = 23 + 'em';
__setf_color(_js1, _js2);
;;; Note that temporary variables are generated to preserve evaluation
;;; order of the arguments as they would be in Lisp.
;;; The following example illustrates how setf places can be used to
;;; provide a uniform protocol for positioning elements in HTML pages:
(defsetf left (el) (offset)
`(setf (slot-value (slot-value ,el 'style) 'left) ,offset))
=> null
(setf (left some-div) (+ 123 "px"))
=> var _js2 = someDiv;
var _js1 = 123 + 'px';
_js2.style.left = _js1;
(progn (defmacro left (el)
`(slot-value ,el 'offset-left))
(left some-div))
=> someDiv.offsetLeft;
;;;# Single argument statements
;;;t \index{single-argument statement}
;;;t \index{RETURN}
;;;t \index{THROW}
;;;t \index{THROW}
;;;t \index{function}
; (RETURN {value}?)
; (THROW {value}?)
;
; value ::= a Parenscript expression
;;; The single argument statements `return' and `throw' are generated
;;; by the form `RETURN' and `THROW'. `THROW' has to be used inside a
;;; `TRY' form. `RETURN' is used to return a value from a function
;;; call.
(return 1) => return 1
(throw "foobar") => throw 'foobar'
;;;# Single argument expression
;;;t \index{single-argument expression}
;;;t \index{object creation}
;;;t \index{object deletion}
;;;t \index{DELETE}
;;;t \index{VOID}
;;;t \index{TYPEOF}
;;;t \index{INSTANCEOF}
;;;t \index{NEW}
;;;t \index{new}
; (DELETE {value})
; (VOID {value})
; (TYPEOF {value})
; (INSTANCEOF {value})
; (NEW {value})
;
; value ::= a Parenscript expression
;;; The single argument expressions `delete', `void', `typeof',
;;; `instanceof' and `new' are generated by the forms `DELETE',
;;; `VOID', `TYPEOF', `INSTANCEOF' and `NEW'. They all take a
;;; Parenscript expression.
(delete (new (*foobar 2 3 4))) => delete new Foobar(2, 3, 4)
(if (= (typeof blorg) *string)
(alert (+ "blorg is a string: " blorg))
(alert "blorg is not a string"))
=> if (typeof blorg == String) {
alert('blorg is a string: ' + blorg);
} else {
alert('blorg is not a string');
}
;;;# Conditional Statements
;;;t \index{conditional statements}
;;;t \index{IF}
;;;t \index{WHEN}
;;;t \index{UNLESS}
;;;t \index{conditionals}
; (IF conditional then {else})
; (WHEN condition then)
; (UNLESS condition then)
;
; condition ::= a Parenscript expression
; then ::= a Parenscript statement in statement context, a
; Parenscript expression in expression context
; else ::= a Parenscript statement in statement context, a
; Parenscript expression in expression context
;;; The `IF' form compiles to the `if' javascript construct. An
;;; explicit `PROGN' around the then branch and the else branch is
;;; needed if they consist of more than one statement. When the `IF'
;;; form is used in an expression context, a JavaScript `?', `:'
;;; operator form is generated.
(if (blorg.is-correct)
(progn (carry-on) (return i))
(alert "blorg is not correct!"))
=> if (blorg.isCorrect()) {
carryOn();
return i;
} else {
alert('blorg is not correct!');
}
(+ i (if (blorg.add-one) 1 2))
=> i + (blorg.addOne() ? 1 : 2)
;;; The `WHEN' and `UNLESS' forms can be used as shortcuts for the
;;; `IF' form.
(when (blorg.is-correct)
(carry-on)
(return i))
=> if (blorg.isCorrect()) {
carryOn();
return i;
}
(unless (blorg.is-correct)
(alert "blorg is not correct!"))
=> if (!blorg.isCorrect()) {
alert('blorg is not correct!');
}
;;;# Variable declaration
;;;t \index{variable}
;;;t \index{variable declaration}
;;;t \index{binding}
;;;t \index{scoping}
;;;t \index{DEFVAR}
;;;t \index{VAR}
;;;t \index{LET}
;;;t \index{LET*}
;;;t \index{LEXICAL-LET}
;;;t \index{LEXICAL-LET*}
; (DEFVAR var {value}?)
; (VAR var {value}?)
; (LET ({var | (var value)}*) body)
; (LET* ({var | (var value)}*) body)
; (LEXICAL-LET ({var | (var value)}*) body)
; (LEXICAL-LET* ({var | (var value)}*) body)
;
; var ::= a Lisp symbol
; value ::= a Parenscript expression
; body ::= a list of Parenscript statements
;;; Parenscript special variables can be declared using the `DEFVAR'
;;; special form, which is similar to its equivalent form in
;;; Lisp. Note that the result is undefined if `DEFVAR' is not used as
;;; a top-level form.
(defvar *a* (array 1 2 3)) => var A = [ 1, 2, 3 ]
;;; One feature present in Parenscript that is not part of Common Lisp
;;; are lexically-scoped global variables, which are declared using
;;; the `VAR' special form.
;;; Parenscript provides two versions of the `LET' and `LET*' special
;;; forms for manipulating local variables: `SIMPLE-LET' /
;;; `SIMPLE-LET*' and `LEXICAL-LET' / `LEXICAL-LET*'. By default,
;;; `LET' and `LET*' are aliased to `SIMPLE-LET' and `SIMPLE-LET*',
;;; respectively.
;;; `SIMPLE-LET' and `SIMPLE-LET*' bind their variable lists using
;;; simple JavaScript assignment. This means that you cannot rely on
;;; the bindings going out of scope at the end of the form.
;;; `LEXICAL-LET' and `LEXICAL-LET*' actually introduce new lexical
;;; environments for the variable bindings by creating anonymous
;;; functions.
;;; As you would expect, `SIMPLE-LET' and `LEXICAL-LET' do parallel
;;; binding of their variable lists, while `SIMPLE-LET*' and
;;; `LEXICAL-LET*' bind their variable lists sequentially.
;;; examples:
(simple-let* ((a 0) (b 1))
(alert (+ a b)))
=> var a = 0;
var b = 1;
alert(a + b);
(simple-let* ((a "World") (b "Hello"))
(simple-let ((a b) (b a))
(alert (+ a b))))
=> var a = 'World';
var b = 'Hello';
var _js_a1 = b;
var _js_b2 = a;
var a = _js_a1;
var b = _js_b2;
delete _js_a1;
delete _js_b2;
alert(a + b);
(simple-let* ((a 0) (b 1))
(lexical-let* ((a 9) (b 8))
(alert (+ a b)))
(alert (+ a b)))
=> var a = 0;
var b = 1;
(function () {
var a = 9;
var b = 8;
alert(a + b);
})();
alert(a + b);
(simple-let* ((a "World") (b "Hello"))
(lexical-let ((a b) (b a))
(alert (+ a b)))
(alert (+ a b)))
=> var a = 'World';
var b = 'Hello';
(function (a, b) {
alert(a + b);
})(b, a);
alert(a + b);
;;; Moreover, beware that scoping rules in Lisp and JavaScript are
;;; quite different. For example, don't rely on closures capturing
;;; local variables in the way that you would normally expect.
;;;# Iteration constructs
;;;t \index{iteration}
;;;t \index{iteration construct}
;;;t \index{loop}
;;;t \index{array traversal}
;;;t \index{property}
;;;t \index{object property}
;;;t \index{DO}
;;;t \index{DOTIMES}
;;;t \index{DOLIST}
;;;t \index{FOR-IN}
;;;t \index{WHILE}
; (DO ({var | (var {init}? {step}?)}*) (end-test {result}?) body)
; (DO* ({var | (var {init}? {step}?)}*) (end-test {result}?) body)
; (DOTIMES (var numeric-form {result}?) body)
; (DOLIST (var list-form {result}?) body)
; (FOR-IN (var object) body)
; (WHILE end-test body)
;
; var ::= a Lisp symbol
; numeric-form ::= a Parenscript expression resulting in a number
; list-form ::= a Parenscript expression resulting in an array
; object-form ::= a Parenscript expression resulting in an object
; init ::= a Parenscript expression
; step ::= a Parenscript expression
; end-test ::= a Parenscript expression
; result ::= a Parenscript expression
; body ::= a list of Parenscript statements
;;; All interation special forms are transformed into JavaScript `for'
;;; statements and, if needed, lambda expressions.
;;; `DO', `DO*', and `DOTIMES' carry the same semantics as their
;;; Common Lisp equivalents.
;;; `DO*' (note the variety of possible init-forms:
(do* ((a) b (c (array "a" "b" "c" "d" "e"))
(d 0 (1+ d))
(e (aref c d) (aref c d)))
((or (= d c.length) (eql e "x")))
(setf a d b e)
(document.write (+ "a: " a " b: " b "
")))
=> for (var a = null, b = null, c = ['a', 'b', 'c', 'd', 'e'], d = 0, e = c[d]; !(d == c.length || e == 'x'); d += 1, e = c[d]) {
a = d;
b = e;
document.write('a: ' + a + ' b: ' + b + '
');
};
;;; `DO' (note the parallel assignment):
(do ((i 0 (1+ i))
(s 0 (+ s i (1+ i))))
((> i 10))
(document.write (+ "i: " i " s: " s "
")))
=> var _js_i1 = 0;
var _js_s2 = 0;
var i = _js_i1;
var s = _js_s2;
delete _js_i1;
delete _js_s2;
for (; i <= 10; ) {
document.write('i: ' + i + ' s: ' + s + '
');
var _js3 = i + 1;
var _js4 = s + i + (i + 1);
i = _js3;
s = _js4;
};
;;; compare to `DO*':
(do* ((i 0 (1+ i))
(s 0 (+ s i (1- i))))
((> i 10))
(document.write (+ "i: " i " s: " s "
")))
=> for (var i = 0, s = 0; i <= 10; i += 1, s += i + (i - 1)) {
document.write('i: ' + i + ' s: ' + s + '
');
};
;;; `DOTIMES':
(let* ((arr (array "a" "b" "c" "d" "e")))
(dotimes (i arr.length)
(document.write (+ "i: " i " arr[i]: " (aref arr i) "
"))))
=> var arr = ['a', 'b', 'c', 'd', 'e'];
for (var i = 0; i < arr.length; i += 1) {
document.write('i: ' + i + ' arr[i]: ' + arr[i] + '
');
};
;;; `DOTIMES' with return value:
(let* ((res 0))
(alert (+ "Summation to 10 is "
(dotimes (i 10 res)
(incf res (1+ i))))))
=> var res = 0;
alert('Summation to 10 is ' + (function () {
for (var i = 0; i < 10; i += 1) {
res += i + 1;
};
return res;
})());
;;; `DOLIST' is like CL:DOLIST, but that it operates on numbered JS
;;; arrays/vectors.
(let* ((l (list 1 2 4 8 16 32)))
(dolist (c l)
(document.write (+ "c: " c "
"))))
=> var l = [1, 2, 4, 8, 16, 32];
for (var c = null, _js_arrvar2 = l, _js_idx1 = 0; _js_idx1 < _js_arrvar2.length; _js_idx1 += 1) {
c = _js_arrvar2[_js_idx1];
document.write('c: ' + c + '
');
};
(let* ((l (list 1 2 4 8 16 32))
(s 0))
(alert (+ "Sum of " l " is: "
(dolist (c l s)
(incf s c)))))
=> var l = [1, 2, 4, 8, 16, 32];
var s = 0;
alert('Sum of ' + l + ' is: ' + (function () {
for (var c = null, _js_arrvar2 = l, _js_idx1 = 0; _js_idx1 < _js_arrvar2.length; _js_idx1 += 1) {
c = _js_arrvar2[_js_idx1];
s += c;
};
return s;
})());
;;; `FOR-IN' is translated to the JS `for...in' statement.
(let* ((obj (create :a 1 :b 2 :c 3)))
(for-in (i obj)
(document.write (+ i ": " (aref obj i) "
"))))
=> var obj = { a : 1, b : 2, c : 3 };
for (var i in obj) {
document.write(i + ': ' + obj[i] + '
');
};
;;; The `WHILE' form is transformed to the JavaScript form `while',
;;; and loops until a termination test evaluates to false.
(while (film.is-not-finished)
(this.eat (new *popcorn)))
=> while (film.isNotFinished()) {
this.eat(new Popcorn);
}
;;;# The `CASE' statement
;;;t \index{CASE}
;;;t \index{SWITCH}
;;;t \index{switch}
; (CASE case-value clause*)
;
; clause ::= (value body) | ((value*) body) | t-clause
; case-value ::= a Parenscript expression
; value ::= a Parenscript expression
; t-clause ::= {t | otherwise | default} body
; body ::= a list of Parenscript statements
;;; The Lisp `CASE' form is transformed to a `switch' statement in
;;; JavaScript. Note that `CASE' is not an expression in
;;; Parenscript.
(case (aref blorg i)
((1 "one") (alert "one"))
(2 (alert "two"))
(t (alert "default clause")))
=> switch (blorg[i]) {
case 1:
case 'one':
alert('one');
break;
case 2:
alert('two');
break;
default:
alert('default clause');
}
; (SWITCH case-value clause*)
; clause ::= (value body) | (default body)
;;; The `SWITCH' form is the equivalent to a javascript switch statement.
;;; No break statements are inserted, and the default case is named `DEFAULT'.
;;; The `CASE' form should be prefered in most cases.
(switch (aref blorg i)
(1 (alert "If I get here"))
(2 (alert "I also get here"))
(default (alert "I always get here")))
=> switch (blorg[i]) {
case 1: alert('If I get here');
case 2: alert('I also get here');
default: alert('I always get here');
}
;;;# The `WITH' statement
;;;t \index{WITH}
;;;t \index{dynamic scope}
;;;t \index{binding}
;;;t \index{scoping}
;;;t \index{closure}
; (WITH object body)
;
; object ::= a Parenscript expression evaluating to an object
; body ::= a list of Parenscript statements
;;; The `WITH' form is compiled to a JavaScript `with' statements, and
;;; adds the object `object' as an intermediary scope objects when
;;; executing the body.
(with (create :foo "foo" :i "i")
(alert (+ "i is now intermediary scoped: " i)))
=> with ({ foo : 'foo', i : 'i' }) {
alert('i is now intermediary scoped: ' + i);
}
;;;# The `TRY' statement
;;;t \index{TRY}
;;;t \index{CATCH}
;;;t \index{FINALLY}
;;;t \index{exception}
;;;t \index{error handling}
; (TRY body {(:CATCH (var) body)}? {(:FINALLY body)}?)
;
; body ::= a list of Parenscript statements
; var ::= a Lisp symbol
;;; The `TRY' form is converted to a JavaScript `try' statement, and
;;; can be used to catch expressions thrown by the `THROW'
;;; form. The body of the catch clause is invoked when an exception
;;; is catched, and the body of the finally is always invoked when
;;; leaving the body of the `TRY' form.
(try (throw "i")
(:catch (error)
(alert (+ "an error happened: " error)))
(:finally
(alert "Leaving the try form")))
=> try {
throw 'i';
} catch (error) {
alert('an error happened: ' + error);
} finally {
alert('Leaving the try form');
}
;;;# The HTML Generator
;;;t \index{PS-HTML}
;;;t \index{HTML generation}
; (PS-HTML html-expression)
;;; The HTML generator of Parenscript is very similar to the htmlgen
;;; HTML generator library included with AllegroServe. It accepts the
;;; same input forms as the AllegroServer HTML generator. However,
;;; non-HTML construct are compiled to JavaScript by the Parenscript
;;; compiler. The resulting expression is a JavaScript expression.
(ps-html ((:a :href "foobar") "blorg"))
=> 'blorg'
(ps-html ((:a :href (generate-a-link)) "blorg"))
=> 'blorg'
;;; We can recursively call the Parenscript compiler in an HTML
;;; expression.
(document.write
(ps-html ((:a :href "#"
:onclick (ps-inline (transport))) "link")))
=> document.write('link')
;;; Forms may be used in attribute lists to conditionally generate
;;; the next attribute. In this example the textarea is sometimes disabled.
(let* ((disabled nil)
(authorized t))
(setf element.inner-h-t-m-l
(ps-html ((:textarea (or disabled (not authorized)) :disabled "disabled")
"Edit me"))))
=> var disabled = null;
var authorized = true;
element.innerHTML =
'';
;;;# Macrology
;;;t \index{macro}
;;;t \index{macrology}
;;;t \index{DEFPSMACRO}
;;;t \index{DEFMACRO/PS}
;;;t \index{DEFMACRO+PS}
;;;t \index{DEFINE-PS-SYMBOL-MACRO}
;;;t \index{IMPORT-MACROS-FROM-LISP}
;;;t \index{MACROLET}
;;;t \index{SYMBOL-MACROLET}
;;;t \index{PS-GENSYM}
;;;t \index{compiler}
; (DEFPSMACRO name lambda-list macro-body)
; (DEFPSMACRO/PS name lambda-list macro-body)
; (DEFPSMACRO+PS name lambda-list macro-body)
; (DEFINE-PS-SYMBOL-MACRO symbol expansion)
; (IMPORT-MACROS-FROM-LISP symbol*)
; (MACROLET ({name lambda-list macro-body}*) body)
; (SYMBOL-MACROLET ({name macro-body}*) body)
; (PS-GENSYM {string})
;
; name ::= a Lisp symbol
; lambda-list ::= a lambda list
; macro-body ::= a Lisp body evaluating to Parenscript code
; body ::= a list of Parenscript statements
; string ::= a string
;;; Parenscript can be extended using macros, just like Lisp can be
;;; extended using Lisp macros. Using the special Lisp form
;;; `DEFPSMACRO', the Parenscript language can be
;;; extended. `DEFPSMACRO' adds the new macro to the toplevel macro
;;; environment, which is always accessible during Parenscript
;;; compilation. For example, the `1+' and `1-' operators are
;;; implemented using macros.
(defpsmacro 1- (form)
`(- ,form 1))
(defpsmacro 1+ (form)
`(+ ,form 1))
;;; A more complicated Parenscript macro example is the implementation
;;; of the `DOLIST' form (note how `PS-GENSYM', the Parenscript of
;;; `GENSYM', is used to generate new Parenscript variable names):
(defpsmacro dolist ((var array &optional (result nil result?)) &body body)
(let ((idx (ps-gensym "_js_idx"))
(arrvar (ps-gensym "_js_arrvar")))
`(do* (,var
(,arrvar ,array)
(,idx 0 (1+ ,idx)))
((>= ,idx (slot-value ,arrvar 'length))
,@(when result? (list result)))
(setq ,var (aref ,arrvar ,idx))
,@body)))
;;; Macros can be defined in Parenscript code itself (as opposed to
;;; from Lisp) by using the Parenscript `MACROLET' and `DEFMACRO'
;;; forms. Note that macros defined this way are defined in a null
;;; lexical environment (ex - (let ((x 1)) (defmacro baz (y) `(+ ,y
;;; ,x))) will not work), since the surrounding Parenscript code is
;;; just translated to JavaScript and not actually evaluated.
;;; Parenscript also supports the use of macros defined in the
;;; underlying Lisp environment. Existing Lisp macros can be imported
;;; into the Parenscript macro environment by
;;; `IMPORT-MACROS-FROM-LISP'. This functionality enables code sharing
;;; between Parenscript and Lisp, and is useful in debugging since the
;;; full power of Lisp macroexpanders, editors and other supporting
;;; facilities can be used. However, it is important to note that the
;;; macroexpansion of Lisp macros and Parenscript macros takes place
;;; in their own respective environments, and many Lisp macros
;;; (especially those provided by the Lisp implementation) expand into
;;; code that is not usable by Parenscript. To make it easy for users
;;; to take advantage of these features, two additional macro
;;; definition facilities are provided by Parenscript: `DEFMACRO/PS'
;;; and `DEFMACRO+PS'. `DEFMACRO/PS' defines a Lisp macro and then
;;; imports it into the Parenscript macro environment, while
;;; `DEFMACRO+PS' defines two macros with the same name and expansion,
;;; one in Parenscript and one in Lisp. `DEFMACRO+PS' is used when the
;;; full 'macroexpand' of the Lisp macro yields code that cannot be
;;; used by Parenscript.
;;; Parenscript also supports symbol macros, which can be introduced
;;; using the Parenscript form `SYMBOL-MACROLET' or defined in Lisp
;;; with `DEFINE-PS-SYMBOL-MACRO'. For example, the Parenscript
;;; `WITH-SLOTS' is implemented using symbol macros.
(defpsmacro with-slots (slots object &rest body)
`(symbol-macrolet ,(mapcar #'(lambda (slot)
`(,slot '(slot-value ,object ',slot)))
slots)
,@body))
;;;# The Parenscript namespace system
;;;t \index{package}
;;;t \index{namespace}
;;;t \index{PS-PACKAGE-PREFIX}
; (setf (PS-PACKAGE-PREFIX package-designator) string)
;;; Although JavaScript does not offer namespacing or a package
;;; system, Parenscript does provide a namespace mechanism for
;;; generated JavaScript by integrating with the Common Lisp package
;;; system. Since Parenscript code is normally read in by the Lisp
;;; reader, all symbols (except for uninterned ones, ie - those
;;; specified with the #: reader macro) have a Lisp package. By
;;; default, no packages are prefixed. You can specify that symbols in
;;; a particular package receive a prefix when translated to
;;; JavaScript with the `PS-PACKAGE-PREFIX' place.
(defpackage "PS-REF.MY-LIBRARY"
(:use "PARENSCRIPT"))
(setf (ps-package-prefix "PS-REF.MY-LIBRARY") "my_library_")
(defun ps-ref.my-library::library-function (x y)
(return (+ x y)))
-> function my_library_libraryFunction(x, y) {
return x + y;
}
;;;# Identifier obfuscation
;;;t \index{obfuscation}
;;;t \index{identifiers}
;;;t \index{OBFUSCATE-PACKAGE}
;;;t \index{UNOBFUSCATE-PACKAGE}
; (OBFUSCATE-PACKAGE package-designator &optional symbol-map)
; (UNOBFUSCATE-PACKAGE package-designator)
;;; Similar to the namespace mechanism, Parenscript provides a
;;; facility to generate obfuscated identifiers in specified CL
;;; packages. The function `OBFUSCATE-PACKAGE' may optionally be
;;; passed a hash-table or a closure that maps symbols to their
;;; obfuscated counterparts. By default, the mapping is done using
;;; `PS-GENSYM'.
(defpackage "PS-REF.OBFUSCATE-ME")
(obfuscate-package "PS-REF.OBFUSCATE-ME"
(let ((code-pt-counter #x8CF0)
(symbol-map (make-hash-table)))
(lambda (symbol)
(or (gethash symbol symbol-map)
(setf (gethash symbol symbol-map)
(make-symbol (string (code-char (incf code-pt-counter)))))))))
(defun ps-ref.obfuscate-me::a-function (a b ps-ref.obfuscate-me::foo)
(+ a (ps-ref.my-library::library-function b ps-ref.obfuscate-me::foo)))
-> function 賱(a, b, 賲) {
a + my_library_libraryFunction(b, 賲);
}
;;; The obfuscation and namespace facilities can be used on packages
;;; at the same time.
;;;# The Parenscript Compiler
;;;t \index{compiler}
;;;t \index{Parenscript compiler}
;;;t \index{PS}
;;;t \index{PS*}
;;;t \index{PS1*}
;;;t \index{PS-INLINE}
;;;t \index{PS-INLINE*}
;;;t \index{LISP}
; (PS &body body)
; (PS* &body body)
; (PS1* parenscript-form)
; (PS-INLINE form &optional *js-string-delimiter*)
; (PS-INLINE* form &optional *js-string-delimiter*)
; (LISP lisp-forms)
;
; body ::= Parenscript statements comprising an implicit `PROGN'
;;; For static Parenscript code, the macro `PS' compiles the provided
;;; forms at Common Lisp macro-expansion time. `PS*' and `PS1*'
;;; evaluate their arguments and then compile them. All these forms
;;; except for `PS1*' treat the given forms as an implicit
;;; `PROGN'.
;;; `PS-INLINE' and `PS-INLINE*' take a single Parenscript form and
;;; output a string starting with "javascript:" that can be used in
;;; HTML node attributes. As well, they provide an argument to bind
;;; the value of *js-string-delimiter* to control the value of the
;;; JavaScript string escape character to be compatible with whatever
;;; the HTML generation mechanism is used (for example, if HTML
;;; strings are delimited using #\', using #\" will avoid conflicts
;;; without requiring the output JavaScript code to be escaped). By
;;; default the value is taken from *js-inline-string-delimiter*.
;;; Parenscript can also call out to arbitrary Common Lisp code at
;;; code output time using the special form `LISP'. The form provided
;;; to `LISP' is evaluated, and its result is compiled as though it
;;; were Parenscript code. For `PS' and `PS-INLINE', the Parenscript
;;; output code is generated at macro-expansion time, and the `LISP'
;;; statements are inserted inline and have access to the enclosing
;;; Common Lisp lexical environment. `PS*' and `PS1*' evaluate the
;;; `LISP' forms with eval, providing them access to the current
;;; dynamic environment only.