X-Git-Url: http://git.hcoop.net/clinton/parenscript.git/blobdiff_plain/b44afd8fa79e05dca28eec73f9ac6d3775e8d5d2..5ffb1ebaaff5e8f899fd4b1bd91b015f2c52d656:/docs/reference.lisp diff --git a/docs/reference.lisp b/docs/reference.lisp index 23d44cd..fca8684 100644 --- a/docs/reference.lisp +++ b/docs/reference.lisp @@ -1,10 +1,15 @@ -;;;# ParenScript Language Reference +;;;# Parenscript Language Reference -;;; This chapters describes the core constructs of ParenScript, as +;;; 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. +;;; 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} @@ -14,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: @@ -24,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} @@ -54,42 +59,30 @@ 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 -;;; 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, +;;; 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 DOEACH DOLIST DOTIMES DOUBLE ELSE ENUM EQL EXPORT EXTENDS FALSE -FINAL FINALLY FLOAT FLOOR FOR FUNCTION GOTO IF IMPLEMENTS IMPORT IN INCF -INSTANCEOF INT INTERFACE JS LAMBDA 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 +! ~ ++ -- * / % + - << >> >>> < > <= >= == != ==== !== & ^ | && || *= +/= %= += -= <<= >>= >>>= &= ^= |= 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* 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} @@ -101,7 +94,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 @@ -124,9 +117,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' ;;;## Array literals ;;;t \index{array} @@ -139,9 +133,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. @@ -151,7 +145,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 @@ -164,9 +158,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 @@ -175,6 +169,7 @@ WHEN WHILE WITH WITH-SLOTS ;;;## Object literals ;;;t \index{CREATE} ;;;t \index{SLOT-VALUE} +;;;t \index{@} ;;;t \index{WITH-SLOTS} ;;;t \index{object literal} ;;;t \index{object} @@ -185,11 +180,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 @@ -197,24 +192,24 @@ 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. (slot-value an-object 'foo) => anObject.foo -;;; A programmer can also use the "." symbol notation explained above. +;;; The convenience macro `@' is provided to make multiple levels of +;;; indirection easy to express -an-object.foo => anObject.foo +(@ an-object foo bar) => anObject.foo.bar ;;; 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 @@ -222,7 +217,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} @@ -250,6 +245,7 @@ an-object.foo => anObject.foo ;;;## Literal symbols ;;;t \index{T} +;;;t \index{F} ;;;t \index{FALSE} ;;;t \index{NIL} ;;;t \index{UNDEFINED} @@ -258,15 +254,17 @@ an-object.foo => anObject.foo ;;;t \index{null} ;;;t \index{true} -; T, FALSE, NIL, UNDEFINED, THIS +; T, F, FALSE, NIL, UNDEFINED, THIS -;;; The Lisp symbols `T' and `FALSE' are converted to their JavaScript -;;; boolean equivalents `true' and `false'. +;;; 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'. @@ -291,7 +289,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 @@ -300,8 +298,6 @@ a-variable => aVariable *math => Math -*math.floor => Math.floor - ;;;# Function calls and method calls ;;;t \index{function} ;;;t \index{function call} @@ -309,12 +305,11 @@ a-variable => aVariable ;;;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 +; function ::= a Parenscript expression or a Lisp symbol +; 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 @@ -325,23 +320,13 @@ 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 ]) - -((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). +=> foobar(blorg(1, 2), blabla(3, 4), [ 2, 3, 4 ]) -(.blorg this 1 2) => this.blorg(1, 2) +((slot-value this 'blorg) 1 2) => this.blorg(1, 2) -(this.blorg 1 2) => this.blorg(1, 2) +((aref foo i) 1 2) => foo[i](1, 2) -(.blorg (aref foobar 1) NIL T) - => foobar[1].blorg(null, true) +((slot-value (aref foobar 1) 'blorg) NIL T) => foobar[1].blorg(null, true) ;;;# Operator Expressions ;;;t \index{operator} @@ -358,14 +343,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 @@ -374,14 +359,14 @@ a-variable => aVariable (eql 1 2) => 1 == 2 -;;; Note that the resulting expression is correctly parenthized, +;;; 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/ +;;; 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 @@ -415,8 +400,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 @@ -427,13 +412,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. @@ -452,7 +437,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 @@ -460,50 +445,123 @@ 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} ;;;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 +; lhs ::= a Parenscript left hand side expression +; rhs ::= a Parenscript expression -;;; Assignment is done using the `SETF' form, which is transformed -;;; into a series of assignments using the JavaScript `=' operator. +; (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; +=> 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 (1+ a)) => a++; - (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 a1 = 1; + var b2 = 2; + var _js3_5 = b2; + var _js4_6 = a1; + a1 = _js3_5; + b2 = _js4_6; + +;;; 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_3 = someDiv; + var _js1_4 = 23 + 'em'; + __setf_color(_js1_4, _js2_3); + +;;; 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_3 = someDiv; + var _js1_4 = 123 + 'px'; + _js2_3.style.left = _js1_4; + +(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} @@ -514,7 +572,7 @@ a-variable => aVariable ; (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 @@ -542,23 +600,23 @@ a-variable => aVariable ; (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} @@ -571,11 +629,11 @@ a-variable => aVariable ; (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 @@ -583,35 +641,35 @@ a-variable => aVariable ;;; form is used in an expression context, a JavaScript `?', `:' ;;; operator form is generated. -(if (blorg.is-correct) +(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 (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) +(when ((@ blorg is-correct)) (carry-on) (return i)) - => if (blorg.isCorrect()) { - carryOn(); - return i; - } +=> if (blorg.isCorrect()) { + carryOn(); + return i; + } -(unless (blorg.is-correct) +(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} @@ -619,54 +677,62 @@ a-variable => aVariable ;;;t \index{binding} ;;;t \index{scoping} ;;;t \index{DEFVAR} +;;;t \index{VAR} ;;;t \index{LET} +;;;t \index{LET*} ; (DEFVAR var {value}?) -; (LET ({var | (var value)) body) +; (VAR var {value}?) +; (LET ({var | (var value)}*) body) +; (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 -;;; Variables (either local or global) can be declared using the -;;; `DEFVAR' form, which is similar to its equivalent form in -;;; Lisp. The `DEFVAR' is converted to "var ... = ..." form in -;;; JavaScript. +;;; 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 ] -(if (= i 1) - (progn (defvar blorg "hallo") - (alert blorg)) - (progn (defvar blorg "blitzel") - (alert blorg))) - => if (i == 1) { - var blorg = 'hallo'; - alert(blorg); - } else { - var blorg = 'blitzel'; - alert(blorg); - } - -;;; A more lispy way to declare local variable is to use the `LET' -;;; form, which is similar to its Lisp form. - -(if (= i 1) - (let ((blorg "hallo")) - (alert blorg)) - (let ((blorg "blitzel")) - (alert blorg))) - => if (i == 1) { - var blorg = 'hallo'; - alert(blorg); - } else { - var blorg = 'blitzel'; - alert(blorg); - } - -;;; However, beware that scoping in Lisp and JavaScript are quite -;;; different. For example, don't rely on closures capturing local -;;; variables in the way you'd think they would. +;;; 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 the `LET' and `LET*' special forms for +;;; creating new variable bindings. Both special forms implement +;;; lexical scope by renaming the provided variables via `GENSYM', and +;;; implement dynamic binding using `TRY'-`FINALY'. Note that +;;; top-level `LET' and `LET*' forms will create new global variables. + +;;; 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. + + +;;; examples: + +(progn + (defvar *a* 4) + (let ((x 1) + (*a* 2)) + (let* ((y (+ x 1)) + (x (+ x y))) + (+ *a* x y)))) +=> var A = 4; + var x1 = 1; + var A2; + try { + A2 = A; + A = 2; + var y3 = x1 + 1; + var x4 = x1 + y3; + A + x4 + y3; + } finally { + A = A2; + }; ;;;# Iteration constructs ;;;t \index{iteration} @@ -678,80 +744,141 @@ a-variable => aVariable ;;;t \index{DO} ;;;t \index{DOTIMES} ;;;t \index{DOLIST} -;;;t \index{DOEACH} +;;;t \index{FOR-IN} ;;;t \index{WHILE} -; (DO ({var | (var {init}? {step}?)}*) (end-test) body) -; (DOTIMES (var numeric-form) body) -; (DOLIST (var list-form) body) -; (DOEACH (var object) body) +; (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 ::= a ParenScript expression resulting in an object -; init ::= a ParenScript expression -; step ::= a ParenScript expression -; end-test ::= a ParenScript expression -; body ::= a list of ParenScript statements - -;;; The `DO' form, which is similar to its Lisp form, is transformed -;;; into a JavaScript `for' statement. Note that the ParenScript `DO' -;;; form does not have a return value, that is because `for' is a -;;; statement and not an expression in JavaScript. +; 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' (do ((i 0 (1+ i)) - (l (aref blorg i) (aref blorg i))) - ((or (= i blorg.length) - (eql l "Fumitastic"))) - (document.write (+ "L is " l))) - => for (var i = 0, l = blorg[i]; - !(i == blorg.length || l == 'Fumitastic'); - i = i + 1, l = blorg[i]) { - document.write('L is ' + l); - } - -;;; The `DOTIMES' form, which lets a variable iterate from 0 upto an -;;; end value, is a shortcut for `DO'. - -(dotimes (i blorg.length) - (document.write (+ "L is " (aref blorg i)))) - => for (var i = 0; i < blorg.length; i = i + 1) { - document.write('L is ' + blorg[i]); - } - -;;; The `DOLIST' form is a shortcut for iterating over an array. Note -;;; that this form creates temporary variables using a function called -;;; `PS-GENSYM', which is similar to its Lisp counterpart `GENSYM'. - -(dolist (l blorg) - (document.write (+ "L is " l))) - => var tmpArr1 = blorg; - for (var tmpI2 = 0; tmpI2 < tmpArr1.length; - tmpI2 = tmpI2 + 1) { - var l = tmpArr1[tmpI2]; - document.write('L is ' + l); - }; - -;;; The `DOEACH' form is converted to a `for (var .. in ..)' form in -;;; JavaScript. It is used to iterate over the enumerable properties -;;; of an object. - -(doeach (i object) - (document.write (+ i " is " (aref object i)))) - => for (var i in object) { - document.write(i + ' is ' + object[i]); - } + (s 0 (+ s i (1+ i)))) + ((> i 10)) + ((@ document write) (+ "i: " i " s: " s "
"))) +=> var i1 = 0; + var s2 = 0; + for (; i1 <= 10; ) { + document.write('i: ' + i1 + ' s: ' + s2 + '
'); + var _js3_5 = i1 + 1; + var _js4_6 = s2 + i1 + (i1 + 1); + i1 = _js3_5; + s2 = _js4_6; + }; + +;;; 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 arr1 = ['a', 'b', 'c', 'd', 'e']; + for (var i = 0; i < arr1.length; i += 1) { + document.write('i: ' + i + ' arr[i]: ' + arr1[i] + '
'); + }; + +;;; `DOTIMES' with return value: + +(let ((res 0)) + (alert (+ "Summation to 10 is " + (dotimes (i 10 res) + (incf res (1+ i)))))) +=> var res1 = 0; + alert('Summation to 10 is ' + (function () { + for (var i = 0; i < 10; i += 1) { + res1 += i + 1; + }; + return res1; + })()); + +;;; `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 l1 = [1, 2, 4, 8, 16, 32]; + for (var c = null, _js_arrvar3 = l1, _js_idx2 = 0; _js_idx2 < _js_arrvar3.length; _js_idx2 += 1) { + c = _js_arrvar3[_js_idx2]; + document.write('c: ' + c + '
'); + }; + +(let ((l '(1 2 4 8 16 32)) + (s 0)) + (alert (+ "Sum of " l " is: " + (dolist (c l s) + (incf s c))))) +=> var l1 = [1, 2, 4, 8, 16, 32]; + var s2 = 0; + alert('Sum of ' + l1 + ' is: ' + (function () { + for (var c = null, _js_arrvar4 = l1, _js_idx3 = 0; _js_idx3 < _js_arrvar4.length; _js_idx3 += 1) { + c = _js_arrvar4[_js_idx3]; + s2 += c; + }; + return s2; + })()); + +;;; `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 obj1 = { a : 1, b : 2, c : 3 }; + for (var i in obj1) { + document.write(i + ': ' + obj1[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); - } +(while ((@ film is-not-finished)) + ((@ this eat) (new *popcorn))) +=> while (film.isNotFinished()) { + this.eat(new Popcorn); + } ;;;# The `CASE' statement ;;;t \index{CASE} @@ -761,28 +888,29 @@ a-variable => aVariable ; (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*) @@ -796,12 +924,11 @@ a-variable => aVariable (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} @@ -812,8 +939,8 @@ a-variable => aVariable ; (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 @@ -821,10 +948,9 @@ a-variable => aVariable (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} @@ -835,7 +961,7 @@ a-variable => aVariable ; (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 @@ -849,232 +975,253 @@ a-variable => aVariable (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{HTML} +;;;t \index{PS-HTML} ;;;t \index{HTML generation} -;;;t \index{CSS} -;;;t \index{CSS generation} +; (PS-HTML html-expression) -; (HTML html-expression) - -;;; The HTML generator of ParenScript is very similar to the HTML -;;; generator included in AllegroServe. It accepts the same input -;;; forms as the AllegroServer HTML generator. However, non-HTML -;;; construct are compiled to JavaScript by the ParenScript +;;; 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. -(html ((:a :href "foobar") "blorg")) - => 'blorg' +(ps-html ((:a :href "foobar") "blorg")) +=> 'blorg' -(html ((:a :href (generate-a-link)) "blorg")) - => 'blorg' +(ps-html ((:a :href (generate-a-link)) "blorg")) +=> 'blorg' -;;; We can recursively call the JS compiler in a HTML expression. +;;; We can recursively call the Parenscript compiler in an HTML +;;; expression. -(document.write - (html ((:a :href "#" - :onclick (ps-inline (transport))) "link"))) - => document.write('link') +((@ 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 - (html ((:textarea (or disabled (not authorized)) :disabled "disabled") + (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'; - -; (CSS-INLINE css-expression) - -;;; Stylesheets can also be created in ParenScript. - -(css-inline :color "red" - :font-size "x-small") - => 'color:red;font-size:x-small' - -(defun make-color-div(color-name) - (return (html ((:div :style (css-inline :color color-name)) - color-name " looks like this.")))) - => function makeColorDiv(colorName) { - return '
' + colorName - + ' looks like this.
'; - } +=> var disabled1 = null; + var authorized2 = true; + element.innerHTML = + 'Edit me'; ;;;# Macrology ;;;t \index{macro} ;;;t \index{macrology} -;;;t \index{DEFJSMACRO} +;;;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{JS-GENSYM} +;;;t \index{PS-GENSYM} ;;;t \index{compiler} -; (DEFJSMACRO name lambda-list macro-body) +; (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) -; (JS-GENSYM {string}?) +; (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 -;;; `DEFJSMACRO', the ParenScript language can be -;;; extended. `DEFJSMACRO' adds the new macro to the toplevel macro -;;; environment, which is always accessible during ParenScript +;;; `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. -(defjsmacro 1- (form) +(defpsmacro 1- (form) `(- ,form 1)) -(defjsmacro 1+ (form) +(defpsmacro 1+ (form) `(+ ,form 1)) -;;; A more complicated ParenScript macro example is the implementation -;;; of the `DOLIST' form (note how `JS-GENSYM', the ParenScript of -;;; `GENSYM', is used to generate new ParenScript variable names): - -(defpsmacro dolist (i-array &rest body) - (let ((var (first i-array)) - (array (second i-array)) - (arrvar (js-gensym "arr")) - (idx (js-gensym "i"))) - `(let ((,arrvar ,array)) - (do ((,idx 0 (incf ,idx))) - ((>= ,idx (slot-value ,arrvar 'length))) - (let ((,var (aref ,arrvar ,idx))) - ,@body))))) - -;;; Macros can be defined in ParenScript itself (as opposed to Lisp) -;;; by using the ParenScript `MACROLET' and 'DEFMACRO' forms. - -;;; ParenScript also supports the use of macros defined in the -;;; underlying Lisp. 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/JS' and -;;; 'DEFMACRO+JS'. 'DEFMACRO/JS' defines a Lisp macro and then imports -;;; it into the ParenScript macro environment, while 'DEFMACRO+JS' -;;; defines two macros with the same name and expansion, one in -;;; ParenScript and one in Lisp. 'DEFMACRO+JS' 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'. A new macro -;;; environment is created and added to the current macro environment -;;; list while compiling the body of the `SYMBOL-MACROLET' form. For -;;; example, the ParenScript `WITH-SLOTS' is implemented using symbol -;;; macros. - -(defjsmacro with-slots (slots object &rest body) +;;; 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 Compiler +;;;# 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{JS-COMPILE} -;;;t \index{JS-TO-STRINGS} -;;;t \index{JS-TO-STATEMENT-STRINGS} -;;;t \index{JS-TO-STRING} -;;;t \index{JS-TO-LINE} -;;;t \index{JS} -;;;t \index{JS-INLINE} -;;;t \index{JS-FILE} -;;;t \index{JS-SCRIPT} -;;;t \index{nested compilation} - -; (JS-COMPILE expr) -; (JS-TO-STRINGS compiled-expr position) -; (JS-TO-STATEMENT-STRINGS compiled-expr position) -; -; compiled-expr ::= a compiled ParenScript expression -; position ::= a column number -; -; (JS-TO-STRING expression) -; (JS-TO-LINE expression) +;;;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) ; -; expression ::= a Lisp list of ParenScript code -; -; (JS body) -; (JS-INLINE body) -; (JS-FILE body) -; (JS-SCRIPT body) -; -; body ::= a list of ParenScript statements - -;;; The ParenScript compiler can be invoked from withing Lisp and from -;;; within ParenScript itself. The primary API function is -;;; `JS-COMPILE', which takes a list of ParenScript, and returns an -;;; internal object representing the compiled ParenScript. - -(js-compile '(foobar 1 2)) - => # - -;;; This internal object can be transformed to a string using the -;;; methods `JS-TO-STRINGS' and `JS-TO-STATEMENT-STRINGS', which -;;; interpret the ParenScript in expression and in statement context -;;; respectively. They take an additional parameter indicating the -;;; start-position on a line (please note that the indentation code is -;;; not perfect, and this string interface will likely be -;;; changed). They return a list of strings, where each string -;;; represents a new line of JavaScript code. They can be joined -;;; together to form a single string. - -(js-to-strings (js-compile '(foobar 1 2)) 0) - => ("foobar(1, 2)") - -;;; As a shortcut, ParenScript provides the functions `JS-TO-STRING' -;;; and `JS-TO-LINE', which return the JavaScript string of the -;;; compiled expression passed as an argument. - -(js-to-string '(foobar 1 2)) - => "foobar(1, 2)" - -;;; For static ParenScript code, the macros `JS', `JS-INLINE', -;;; `JS-FILE' and `JS-SCRIPT' avoid the need to quote the ParenScript -;;; expression. All these forms add an implicit `PROGN' form around -;;; the body. `JS' returns a string of the compiled body, where the -;;; other expression return an expression that can be embedded in a -;;; HTML generation construct using the AllegroServe HTML -;;; generator. `JS-SCRIPT' generates a "SCRIPT" node, `JS-INLINE' -;;; generates a string to be used in node attributs, and `JS-FILE' -;;; prints the compiled ParenScript code to the HTML stream. - -;;; These macros are also available inside ParenScript itself, and -;;; generate strings that can be used inside ParenScript code. Note -;;; that `JS-INLINE' in ParenScript is not the same `JS-INLINE' form -;;; as in Lisp, for example. The same goes for the other compilation -;;; macros. - +; 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.