X-Git-Url: http://git.hcoop.net/clinton/parenscript.git/blobdiff_plain/58c4ef4f70ba26bcdbced5fa32c80fbae5909541..4a56eb79eed989dcfa42988b53d271b0627a3908:/docs/reference.lisp diff --git a/docs/reference.lisp b/docs/reference.lisp index 57a1ea0..77677de 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,21 +19,21 @@ ;;; 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: -(+ i (if 1 2 3)) => i + (1 ? 2 : 3) +(+ 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} @@ -40,56 +45,44 @@ ;;; "bang", "what", "hash", "at", "percent", "slash", ;;; "start" and "plus" respectively. The `$' character is untouched. -!?#@% => bangwhathashatpercent +!?#@% => 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 +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 +*array => Array; ;;; 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 +*global-array* => GLOBALARRAY; ;;;## 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 +/= %= += -= <<= >>= >>>= &= ^= |= 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* 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 +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,16 +94,16 @@ VAR VOID VOLATILE 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 +1 => 1; -123.123 => 123.123 +123.123 => 123.123; ;;; Note that the base is not conserved between Lisp and JavaScript. -#x10 => 16 +#x10 => 16; ;;;## String literals ;;;t \index{string} @@ -120,13 +113,14 @@ VAR VOID VOLATILE WHEN WHILE WITH WITH-SLOTS ;;; Lisp strings are converted into JavaScript literals. -"foobar" => 'foobar' +"foobar" => 'foobar'; + +"bratzel bub" => 'bratzel bub'; -"bratzel bub" => 'bratzel bub' +;;; Special characters such as newline and backspace are converted +;;; into their corresponding JavaScript escape sequences. -;;; 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. +" " => '\\t'; ;;;## Array literals ;;;t \index{array} @@ -139,34 +133,34 @@ VAR VOID VOLATILE 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. -(array) => [ ] +(array) => [ ]; -(array 1 2 3) => [ 1, 2, 3 ] +(array 1 2 3) => [ 1, 2, 3 ]; (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 ;;; on the JavaScript side. -(make-array) => new Array() +(make-array) => new Array(); -(make-array 1 2 3) => new Array(1, 2, 3) +(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')) +=> 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 @@ VAR VOID VOLATILE 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,36 +180,36 @@ VAR VOID VOLATILE 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 ;;; 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 "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 } } +(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 +(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} @@ -240,16 +235,17 @@ an-object.foo => anObject.foo ;;; to use modifiers such as slash-i (case-insensitive) or ;;; slash-g (match-globally (all)). -(regex "foobar") => /foobar/ +(regex "foobar") => /foobar/; -(regex "/foobar/i") => /foobar/i +(regex "/foobar/i") => /foobar/i; ;;; Here CL-INTERPOL proves really useful. -(regex #?r"/([^\s]+)foobar/i") => /([^\s]+)foobar/i +(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} @@ -258,29 +254,31 @@ 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' (or `F') are converted to their +;;; JavaScript boolean equivalents `true' and `false'. -;;; The Lisp symbols `T' and `FALSE' are converted to their JavaScript -;;; boolean equivalents `true' and `false'. +T => true; -T => true +FALSE => false; -FALSE => false +F => false; ;;; The Lisp symbol `NIL' is converted to the JavaScript keyword ;;; `null'. -NIL => null +NIL => null; ;;; The Lisp symbol `UNDEFINED' is converted to the JavaScript keyword ;;; `undefined'. -UNDEFINED => undefined +UNDEFINED => undefined; ;;; The Lisp symbol `THIS' is converted to the JavaScript keyword ;;; `this'. -THIS => this +THIS => this; ;;;# Variables ;;;t \index{variable} @@ -291,16 +289,14 @@ 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 +variable => variable; -a-variable => aVariable +a-variable => aVariable; -*math => Math - -*math.floor => Math.floor +*math => Math; ;;;# Function calls and method calls ;;;t \index{function} @@ -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 @@ -322,26 +317,16 @@ a-variable => aVariable ;;; the normal JavaScript function call representation, with the ;;; arguments given in paren after the function name. -(blorg 1 2) => blorg(1, 2) +(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 ]); -((aref foo i) 1 2) => foo[i](1, 2) +((slot-value this 'blorg) 1 2) => this.blorg(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). +((aref foo i) 1 2) => foo[i](1, 2); -(.blorg this 1 2) => this.blorg(1, 2) - -(this.blorg 1 2) => this.blorg(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,54 +343,47 @@ 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 -;;; operators. - -(* 1 2) => 1 * 2 +;;; Parenscript operator is not the assignment operator. -(= 1 2) => 1 == 2 +(* 1 2) => 1 * 2; -(eql 1 2) => 1 == 2 +(= 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/ +;;; 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 ;;; pre-decrementing operators. These operators can ;;; take only one argument. -(incf i) => ++i +(incf i) => ++i; -(decf 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; -(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. +;;; If `not' is used on another boolean-returning operator, the +;;; operator is reversed. -(not (< i 2)) => i >= 2 - -(not (eql i 2)) => i != 2 +(not (< i 2)) => i >= 2; ;;;# Body forms ;;;t \index{body form} @@ -415,8 +393,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 +405,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 +430,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,51 +438,84 @@ 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 + +; (SETQ {lhs rhs}*) +; (PSETQ {lhs rhs}*) ; -; lhs ::= a ParenScript left hand side expression -; rhs ::= a ParenScript expression +; lhs ::= a Parenscript symbol +; rhs ::= a Parenscript expression -;;; Assignment is done using the `SETF' form, which is transformed -;;; into a series of assignments using the JavaScript `=' operator. +;;; 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 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. @@ -515,15 +526,14 @@ a-variable => aVariable (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. @@ -532,17 +542,18 @@ a-variable => aVariable ;;; 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; +(macrolet ((left (el) + `(slot-value ,el 'offset-left))) + (left some-div)) +=> someDiv.offsetLeft; ;;;# Single argument statements ;;;t \index{single-argument statement} @@ -554,16 +565,16 @@ 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 ;;; `TRY' form. `RETURN' is used to return a value from a function ;;; call. -(return 1) => return 1 +(return 1) => return 1; -(throw "foobar") => throw 'foobar' +(throw "foobar") => throw 'foobar'; ;;;# Single argument expression ;;;t \index{single-argument expression} @@ -582,23 +593,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) +(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} @@ -611,11 +622,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 @@ -623,35 +634,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} @@ -660,78 +671,62 @@ a-variable => aVariable ;;;t \index{scoping} ;;;t \index{DEFVAR} ;;;t \index{VAR} +;;;t \index{LET} ;;;t \index{LET*} -;;;t \index{LEXICAL-LET*} ; (DEFVAR var {value}?) ; (VAR var {value}?) -; (LET* ({var | (var value)}) body) -; (LEXICAL-LET* ({var | (var value)}) body) +; (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 ;;; 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 ] +(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 special forms for manipulating local -;;; variables: `LET*' and `LEXICAL-LET*'. Both bind their variable -;;; lists sequentially, as indicated by the '*' at the end of their -;;; names, however `LET*' does so using a simple JavaScript -;;; assignment, while `LEXICAL-LET*' actually introduces a new lexical -;;; environment for the variable bindings by creating and populating a -;;; new object and using it as the lexical context for the JavaScript -;;; 'with' 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); - } - -(if (= i 1) - (lexical-let* ((blorg "hallo")) - (alert blorg)) - (lexical-let* ((blorg "blitzel")) - (alert blorg))) - => if (i == 1) { - (function () { - var newlexicalcontext1 = new Object; - newlexicalcontext1['blorg'] = 'hallo'; - with (newlexicalcontext1) { - alert(blorg); - }; - })(); - } else { - (function () { - var newlexicalcontext3 = new Object; - newlexicalcontext3['blorg'] = 'blitzel'; - with (newlexicalcontext3) { - alert(blorg); - }; - })(); - } +;;; 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 x = 1; + var A_TMPSTACK1; + try { + A_TMPSTACK1 = A; + A = 2; + var y = x + 1; + var x2 = x + y; + A + x2 + y; + } finally { + A = A_TMPSTACK1; + }; + ;;;# Iteration constructs ;;;t \index{iteration} ;;;t \index{iteration construct} @@ -742,80 +737,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)) (== 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 i = 0; + var s = 0; + for (; i <= 10; ) { + document.write('i: ' + i + ' s: ' + s + '
'); + var _js1 = i + 1; + var _js2 = s + i + (i + 1); + i = _js1; + s = _js2; + }; + +;;; 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 '(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); - } +(while ((@ film is-not-finished)) + ((@ this eat) (new *popcorn))) +=> while (film.isNotFinished()) { + this.eat(new Popcorn); + }; ;;;# The `CASE' statement ;;;t \index{CASE} @@ -825,29 +881,30 @@ 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*) ; clause ::= (value body) | (default body) @@ -860,12 +917,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} @@ -876,19 +932,18 @@ 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 ;;; executing the body. -(with (create :foo "foo" :i "i") +(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} @@ -899,7 +954,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 @@ -913,99 +968,88 @@ 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{PS-HTML} ;;;t \index{HTML generation} -;;;t \index{CSS} -;;;t \index{CSS generation} - ; (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 +((@ 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. -(let* ((disabled nil) +(let ((disabled nil) (authorized t)) - (setf element.inner-h-t-m-l + (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 (ps-html ((:div :style (css-inline :color color-name)) - color-name " looks like this.")))) - => function makeColorDiv(colorName) { - return '
' + colorName - + ' looks like this.
'; - } +=> 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. @@ -1015,57 +1059,60 @@ a-variable => aVariable (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 (i-array &rest body) - (let ((var (first i-array)) - (array (second i-array)) - (arrvar (ps-gensym "arr")) - (idx (ps-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 code itself (as opposed to -;;; from Lisp) by using the ParenScript `MACROLET' and `DEFMACRO' -;;; forms. - -;;; ParenScript also supports the use of macros defined in the +;;; 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 +;;; 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} @@ -1073,24 +1120,24 @@ a-variable => aVariable ; (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 ;;; a particular package receive a prefix when translated to ;;; JavaScript with the `PS-PACKAGE-PREFIX' place. -(defpackage "MY-LIBRARY" - (:use #:parenscript)) -(setf (ps-package-prefix :my-library) "my_library_") +(defpackage "PS-REF.MY-LIBRARY" + (:use "PARENSCRIPT")) +(setf (ps-package-prefix "PS-REF.MY-LIBRARY") "my_library_") -(defun my-library::library-function (x y) +(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} @@ -1098,56 +1145,76 @@ a-variable => aVariable ;;;t \index{OBFUSCATE-PACKAGE} ;;;t \index{UNOBFUSCATE-PACKAGE} -; (OBFUSCATE-PACKAGE package-designator) +; (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 certain Lisp -;;; packages. - -(defpackage "OBFUSCATE-ME") -(obfuscate-package :obfuscate-me) - -(defun obfuscate-me::library-function2 (a b obfuscate-me::foo) - (+ a (my-library::library-function b obfuscate-me::foo))) +;;; 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 +;;;# 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.