X-Git-Url: https://git.hcoop.net/clinton/parenscript.git/blobdiff_plain/d777a40578e87bcdb547fa7da36c94fd7603b43a..ed954200c5072db239b261e1e1090df16bcbf238:/docs/reference.lisp diff --git a/docs/reference.lisp b/docs/reference.lisp index 51de2ce..530baaf 100644 --- a/docs/reference.lisp +++ b/docs/reference.lisp @@ -1,15 +1,15 @@ -;;;# ParenScript Language Reference +;;;# 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 +;;; 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. +;;; 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} @@ -19,9 +19,9 @@ ;;; 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 +;;; Parenscript forms are expression, but certain special forms are ;;; not (the forms which are transformed to a JavaScript -;;; statement). All ParenScript expressions are statements +;;; 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: @@ -29,11 +29,11 @@ (+ i (if 1 2 3)) => i + (1 ? 2 : 3) (if 1 2 3) - => if (1) { - 2; - } else { - 3; - } +=> if (1) { + 2; + } else { + 3; + } ;;;# Symbol conversion ;;;t \index{symbol} @@ -60,7 +60,7 @@ bla-foo-bar => blaFooBar *array => Array ;;; The `.' character is left as is in symbols. This allows the -;;; ParenScript programmer to use a practical shortcut when accessing +;;; Parenscript programmer to use a practical shortcut when accessing ;;; slots or methods of JavaScript objects. Instead of writing (slot-value foobar 'slot) @@ -81,7 +81,7 @@ foobar.slot ;;;t \index{keyword} ;;;t \index{reserved keywords} -;;; The following keywords and symbols are reserved in ParenScript, +;;; The following keywords and symbols are reserved in Parenscript, ;;; and should not be used as variable names. ! ~ ++ -- * / % + - << >> >>> < > <= >= == != ==== !== & ^ | && || *= @@ -107,7 +107,7 @@ WHEN WHILE WITH WITH-SLOTS ; number ::= a Lisp number ;;; -;;; ParenScript supports the standard JavaScript literal +;;; Parenscript supports the standard JavaScript literal ;;; values. Numbers are compiled into JavaScript numbers. 1 => 1 @@ -130,9 +130,10 @@ WHEN WHILE WITH WITH-SLOTS "bratzel bub" => 'bratzel bub' -;;; Escapes in Lisp are not converted to JavaScript escapes. However, -;;; to avoid having to use double backslashes when constructing a -;;; string, you can use the CL-INTERPOL library by Edi Weitz. +;;; Special characters such as newline and backspace are converted +;;; into their corresponding JavaScript escape sequences. + +" " => '\\t\\b' ;;;## Array literals ;;;t \index{array} @@ -145,9 +146,9 @@ WHEN WHILE WITH WITH-SLOTS ; (MAKE-ARRAY {values}*) ; (AREF array index) ; -; values ::= a ParenScript expression -; array ::= a ParenScript expression -; index ::= a ParenScript expression +; values ::= a Parenscript expression +; array ::= a Parenscript expression +; index ::= a Parenscript expression ;;; Array literals can be created using the `ARRAY' form. @@ -157,7 +158,7 @@ WHEN WHILE WITH WITH-SLOTS (array (array 2 3) (array "foobar" "bratzel bub")) - => [ [ 2, 3 ], [ '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 @@ -170,9 +171,9 @@ WHEN WHILE WITH WITH-SLOTS (make-array (make-array 2 3) (make-array "foobar" "bratzel bub")) - => new Array(new Array(2, 3), new 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 +;;; 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 @@ -191,11 +192,11 @@ WHEN WHILE WITH WITH-SLOTS ; (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 +; 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 +; body ::= a list of Parenscript statements ;;; ;;; Object literals can be create using the `CREATE' form. Arguments @@ -203,15 +204,14 @@ WHEN WHILE WITH WITH-SLOTS ;;; more "lispy", the property names can be keywords. (create :foo "bar" :blorg 1) - => { 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 } } +=> { 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. @@ -228,7 +228,7 @@ an-object.foo => anObject.foo (with-slots (a b c) this (+ a b c)) - => this.a + this.b + this.c; +=> this.a + this.b + this.c; ;;;## Regular Expression literals ;;;t \index{REGEX} @@ -300,7 +300,7 @@ THIS => this ;;; 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 +;;; allows the Parenscript programmer to be flexible, as flexible as ;;; JavaScript itself. variable => variable @@ -320,10 +320,10 @@ a-variable => aVariable ; (function {argument}*) ; (method object {argument}*) ; -; function ::= a ParenScript expression or a Lisp symbol +; function ::= a Parenscript expression or a Lisp symbol ; method ::= a Lisp symbol beginning with . -; object ::= a ParenScript expression -; argument ::= a ParenScript expression +; 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 @@ -334,24 +334,23 @@ a-variable => aVariable (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 ]) +=> 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) -;;; A method call is a function call where the function name is a -;;; symbol and begins with a "." . In a method call, the name of the -;;; function is append to its first argument, thus reflecting the -;;; method call syntax of JavaScript. Please note that most method -;;; calls can be abbreviated using the "." trick in symbol names (see -;;; "Symbol Conversion" above). +((slot-value (aref foobar 1) 'blorg) NIL T) => foobar[1].blorg(null, true) -(.blorg this 1 2) => this.blorg(1, 2) +;;; 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) -(.blorg (aref foobar 1) NIL T) - => foobar[1].blorg(null, true) - ;;;# Operator Expressions ;;;t \index{operator} ;;;t \index{operator expression} @@ -367,14 +366,14 @@ a-variable => aVariable ; operator ::= one of *, /, %, +, -, <<, >>, >>>, < >, EQL, ; ==, !=, =, ===, !==, &, ^, |, &&, AND, ||, OR. ; single-operator ::= one of INCF, DECF, ++, --, NOT, ! -; argument ::= a ParenScript expression +; 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 +;;; Parenscript operator is not the assignment operator. Unlike +;;; JavaScript, Parenscript supports multiple arguments to the ;;; operators. (* 1 2) => 1 * 2 @@ -387,10 +386,10 @@ a-variable => aVariable ;;; according to the JavaScript operator precedence that can be found ;;; in table form at: - http://www.codehouse.com/javascript/precedence/ +;;; http://www.codehouse.com/javascript/precedence/ (* 1 (+ 2 3 4) 4 (/ 6 7)) - => 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 @@ -424,8 +423,8 @@ a-variable => aVariable ; (PROGN {statement}*) in statement context ; (PROGN {expression}*) in expression context ; -; statement ::= a ParenScript statement -; expression ::= a ParenScript expression +; 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 @@ -436,13 +435,13 @@ a-variable => aVariable ;;; For example, in a statement context: (progn (blorg i) (blafoo i)) - => blorg(i); - blafoo(i); +=> blorg(i); + blafoo(i); ;;; In an expression context: (+ i (progn (blorg i) (blafoo i))) - => i + (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. @@ -461,7 +460,7 @@ a-variable => aVariable ; ; name ::= a Lisp Symbol ; argument ::= a Lisp symbol -; body ::= a list of ParenScript statements +; 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 @@ -469,18 +468,18 @@ a-variable => aVariable (defun a-function (a b) (return (+ a b))) - => function aFunction(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) { +=> function (a, b) { return a + b; - } + } ;;;# Assignment ;;;t \index{assignment} @@ -494,14 +493,14 @@ a-variable => aVariable ; (SETF {lhs rhs}*) ; (PSETF {lhs rhs}*) ; -; lhs ::= a ParenScript left hand side expression -; rhs ::= a ParenScript expression +; 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 +; 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 @@ -510,10 +509,10 @@ a-variable => aVariable (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; +=> 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" @@ -529,12 +528,12 @@ a-variable => aVariable (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; +=> 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 @@ -545,10 +544,7 @@ a-variable => aVariable ;; but... (setq (aref a 0) 1) - -;; results in: - -ERROR: The value (AREF A 0) is not of type SYMBOL. +;; => 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 @@ -560,14 +556,14 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. (defun (setf color) (new-color el) (setf (slot-value (slot-value el 'style) 'color) new-color)) - => function __setf_color(newColor, el) { +=> 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); +=> 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. @@ -576,17 +572,18 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. ;;; 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 (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; +=> 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; +=> someDiv.offsetLeft; ;;;# Single argument statements ;;;t \index{single-argument statement} @@ -598,7 +595,7 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. ; (RETURN {value}?) ; (THROW {value}?) ; -; value ::= a ParenScript expression +; 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 @@ -626,23 +623,23 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. ; (INSTANCEOF {value}) ; (NEW {value}) ; -; value ::= a ParenScript expression +; 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. +;;; 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) { +=> if (typeof blorg == String) { alert('blorg is a string: ' + blorg); - } else { + } else { alert('blorg is not a string'); - } + } ;;;# Conditional Statements ;;;t \index{conditional statements} @@ -655,11 +652,11 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. ; (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 +; 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 @@ -670,15 +667,15 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. (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!'); - } +=> if (blorg.isCorrect()) { + carryOn(); + return i; + } else { + alert('blorg is not correct!'); + } (+ i (if (blorg.add-one) 1 2)) - => i + (blorg.addOne() ? 1 : 2) +=> i + (blorg.addOne() ? 1 : 2) ;;; The `WHEN' and `UNLESS' forms can be used as shortcuts for the ;;; `IF' form. @@ -686,16 +683,16 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. (when (blorg.is-correct) (carry-on) (return i)) - => if (blorg.isCorrect()) { - carryOn(); - 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!'); - } +=> if (!blorg.isCorrect()) { + alert('blorg is not correct!'); + } ;;;# Variable declaration ;;;t \index{variable} @@ -717,8 +714,8 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. ; (LEXICAL-LET* ({var | (var value)}*) body) ; ; var ::= a Lisp symbol -; value ::= a ParenScript expression -; body ::= a list of ParenScript statements +; 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 @@ -728,7 +725,7 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. (defvar *a* (array 1 2 3)) => var A = [ 1, 2, 3 ] ;;; One feature present in Parenscript that is not part of Common Lisp -;;; is lexically-scoped function variables, which are declared using +;;; are lexically-scoped global variables, which are declared using ;;; the `VAR' special form. ;;; Parenscript provides two versions of the `LET' and `LET*' special @@ -753,46 +750,46 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. (simple-let* ((a 0) (b 1)) (alert (+ a b))) - => var a = 0; - var 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); +=> 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); +=> 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); +=> 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 @@ -819,14 +816,14 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. ; (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 +; 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. @@ -842,11 +839,11 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. ((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 + '
'); - }; +=> 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): @@ -854,19 +851,19 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. (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; - }; +=> 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*': @@ -874,19 +871,19 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. (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 + '
'); - }; +=> 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] + '
'); - }; +=> 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: @@ -894,13 +891,13 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. (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; - })()); +=> 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. @@ -908,26 +905,26 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. (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 + '
'); - }; +=> 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; - })()); +=> 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; + })()); ;;; `DOEACH' iterates across the enumerable properties of JS objects, ;;; binding either simply the key of each slot, or alternatively, both @@ -936,29 +933,29 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. (let* ((obj (create :a 1 :b 2 :c 3))) (doeach (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] + '
'); - }; +=> var obj = { a : 1, b : 2, c : 3 }; + for (var i in obj) { + document.write(i + ': ' + obj[i] + '
'); + }; (let* ((obj (create :a 1 :b 2 :c 3))) (doeach ((k v) obj) (document.write (+ k ": " v "
")))) - => var obj = { a : 1, b : 2, c : 3 }; - var v; - for (var k in obj) { - v = obj[k]; - document.write(k + ': ' + v + '
'); - }; +=> var obj = { a : 1, b : 2, c : 3 }; + var v; + for (var k in obj) { + v = obj[k]; + document.write(k + ': ' + v + '
'); + }; ;;; 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); - } +=> while (film.isNotFinished()) { + this.eat(new Popcorn); + } ;;;# The `CASE' statement ;;;t \index{CASE} @@ -968,28 +965,29 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. ; (CASE case-value clause*) ; ; clause ::= (value body) | ((value*) body) | t-clause -; case-value ::= a ParenScript expression -; value ::= a ParenScript expression +; case-value ::= a Parenscript expression +; value ::= a Parenscript expression ; t-clause ::= {t | otherwise | default} body -; body ::= a list of ParenScript statements +; 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. +;;; 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 (blorg[i]) { + case 1: + case 'one': + alert('one'); + break; + case 2: + alert('two'); + break; + default: + alert('default clause'); } ; (SWITCH case-value clause*) @@ -1003,12 +1001,11 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. (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'); - } - +=> 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} @@ -1019,8 +1016,8 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. ; (WITH object body) ; -; object ::= a ParenScript expression evaluating to an object -; body ::= a list of ParenScript statements +; 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 @@ -1028,10 +1025,9 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. (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); - } +=> with ({ foo : 'foo', i : 'i' }) { + alert('i is now intermediary scoped: ' + i); + } ;;;# The `TRY' statement ;;;t \index{TRY} @@ -1042,7 +1038,7 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. ; (TRY body {(:CATCH (var) body)}? {(:FINALLY body)}?) ; -; body ::= a list of ParenScript statements +; body ::= a list of Parenscript statements ; var ::= a Lisp symbol ;;; The `TRY' form is converted to a JavaScript `try' statement, and @@ -1056,13 +1052,13 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. (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'); - } +=> try { + throw 'i'; + } catch (error) { + alert('an error happened: ' + error); + } finally { + alert('Leaving the try form'); + } ;;;# The HTML Generator ;;;t \index{PS-HTML} @@ -1070,25 +1066,25 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. ; (PS-HTML html-expression) -;;; The HTML generator of ParenScript is very similar to the htmlgen +;;; 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 +;;; 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' +=> 'blorg' (ps-html ((:a :href (generate-a-link)) "blorg")) - => 'blorg' +=> 'blorg' -;;; We can recursively call the ParenScript compiler in an HTML +;;; We can recursively call the Parenscript compiler in an HTML ;;; expression. (document.write (ps-html ((:a :href "#" - :onclick (lisp (ps-inline (transport)))) "link"))) - => document.write('link') + :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. @@ -1098,38 +1094,46 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. (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 = - 'Edit me'; +=> var disabled = null; + var authorized = true; + element.innerHTML = + 'Edit me'; ;;;# 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 +; 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 +;;; 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 +;;; `DEFPSMACRO', the Parenscript language can be ;;; extended. `DEFPSMACRO' adds the new macro to the toplevel macro -;;; environment, which is always accessible during ParenScript +;;; environment, which is always accessible during Parenscript ;;; compilation. For example, the `1+' and `1-' operators are ;;; implemented using macros. @@ -1139,9 +1143,9 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. (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): +;;; 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")) @@ -1154,42 +1158,45 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. (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. +;;; 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 +;;; 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 +;;; 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 +;;; 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 +;;; 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 +;;; 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' +;;; 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 +;;; 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 +;;; 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. +;;; used by Parenscript. -;;; ParenScript also supports symbol macros, which can be introduced -;;; using the ParenScript form `SYMBOL-MACROLET'.For example, the -;;; ParenScript `WITH-SLOTS' is implemented using symbol macros. +;;; 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. -(defjsmacro with-slots (slots object &rest body) +(defpsmacro with-slots (slots object &rest body) `(symbol-macrolet ,(mapcar #'(lambda (slot) `(,slot '(slot-value ,object ',slot))) slots) ,@body)) - -;;;# The ParenScript namespace system +;;;# The Parenscript namespace system ;;;t \index{package} ;;;t \index{namespace} ;;;t \index{PS-PACKAGE-PREFIX} @@ -1197,9 +1204,9 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. ; (setf (PS-PACKAGE-PREFIX package-designator) string) ;;; Although JavaScript does not offer namespacing or a package -;;; system, ParenScript does provide a namespace mechanism for +;;; 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 +;;; 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 @@ -1225,7 +1232,7 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. ; (OBFUSCATE-PACKAGE package-designator) ; (UNOBFUSCATE-PACKAGE package-designator) -;;; Similar to the namespace mechanism, ParenScript provides a +;;; Similar to the namespace mechanism, Parenscript provides a ;;; facility to generate obfuscated identifiers in certain Lisp ;;; packages. @@ -1238,40 +1245,48 @@ ERROR: The value (AREF A 0) is not of type SYMBOL. ;;; The obfuscation and namespace facilities can be used on packages ;;; at the same time. -;;;# The ParenScript Compiler +;;;# The Parenscript Compiler ;;;t \index{compiler} -;;;t \index{ParenScript compiler} -;;;t \index{COMPILE-SCRIPT} +;;;t \index{Parenscript compiler} ;;;t \index{PS} ;;;t \index{PS*} +;;;t \index{PS1*} ;;;t \index{PS-INLINE} +;;;t \index{PS-INLINE*} ;;;t \index{LISP} -;;;t \index{nested compilation} -; (COMPILE-SCRIPT script-form &key (output-stream nil)) ; (PS &body body) ; (PS* &body body) -; (PS-INLINE &body body) -; (LISP &body lisp-forms) +; (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 macros `PS' and `PS-INLINE', -;;; avoid the need to quote the ParenScript expression. `PS*' and -;;; `COMPILE-SCRIPT' evaluate their arguments. All these forms except -;;; for `COMPILE-SCRIPT' treat the given forms as an implicit -;;; `PROGN'. `PS' and `PS*' return a string of the compiled body, -;;; while `COMPILE-SCRIPT' takes an optional output-stream parameter -;;; that can be used to specify a stream to which the generated -;;; JavaScript will be written. `PS-INLINE' generates a string that -;;; can be used in HTML node attributes. - -;;; ParenScript can also call out to arbitrary Lisp code at -;;; compile-time using the special form `LISP'. This is typically used -;;; to insert the values of Lisp special variables into ParenScript -;;; code at compile-time, and can also be used to make nested calls to -;;; the ParenScript compiler, which comes in useful when you want to -;;; use the result of `PS-INLINE' in `PS-HTML' forms, for -;;; example. Alternatively the same thing can be accomplished by -;;; constructing ParenScript programs as lists and passing them to -;;; `PS*' or `COMPILE-SCRIPT'. +; 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.