Регистрация | Войти
Lisp — программируемый язык программирования

Справочное руководство Parenscript

Компилятор Parenscript

  (PS &body body)
(PS-TO-STREAM stream &body body)
(PS* &rest body)
(PS-DOC &body body)
(PS-DOC* &body body)
(PS-INLINE form &optional *JS-STRING-DELIMITER*)
(PS-INLINE* form &optional *JS-STRING-DELIMITER*)
(PS-COMPILE-STREAM stream)
(PS-COMPILE-FILE file)
(LISP lisp-forms)
(SYMBOL-TO-JS-STRING symbol)
*JS-TARGET-VERSION*
*PARENSCRIPT-STREAM*
*JS-STRING-DELIMITER*
*JS-INLINE-STRING-DELIMITER*
*PS-READ-FUNCTION*
*VERSION*
*DEFINED-OPERATORS*
body
неявный PROGN

Различие между обычным и * версиями формами компилятора Parenscript примерно похоже на различие между COMPILE и EVAL. Формы с * - это функции, которые производят всю компиляцию во время выполнения, тогда как обычные формы - это макросы, которые производят большую часть компиляции (кроме использования специальных форм, см. далее) во время разворачивания макросов.

PS и PS* предоставляют основной интерфейс к компилятору Parenscript. PS-DOC и PS-DOC* компилируют код, при установленной в 0 переменной *PS-GENSYM-COUNTER*, и удобны для автоматизированных тестов.

По-умолчанию Parenscript выводит результат в строку. Вы можете перенаправить вывод прямо в поток с помощью одного из методов: использовать PS-TO-STREAM вместо PS, или связать переменную *PARENSCRIPT-STREAM* перед вызовом PS*.

PS-INLINE и PS-INLINE* принимают одну форму Parenscript и выводят строку начинающуюся с javascript:, которая может использоваться в html тегах. У них есть аргумент, значение которого будет связано с символом *JS-STRING-DELIMITER* для управления символом экранирования JavaScript строк для того, чтобы обеспечить совместимость с используемым механизмом генерации HTML (например, если в html используется #\', то использование #\" позволит избежать конфликтов). По-умолчанию значение берется из *JS-INLINE-STRING-DELIMITER*.

Parenscript также может вызывать Common Lisp код во время вывода JavaScript, (т.е. каждый раз когда будет вызыватся компиляция или разворот макросов, FIXME), с помощью формы LISP. Форма LISP будет выполнена, и ее вывод будет интерпретирован как код для Parenscript. Для PS и PS-INLINE, генерация вывода Parenscript кода, будет производится во время разворачивания макросов, и LISP форма будет вставлена в код и будет иметь доступ к лексическому окружению. PS* и PS1* выполняют формы LISP используя EVAL, предоставляя им доступ только к текущему динамическому окружению (конечно изпользование LISP при вызов этих функций необязательно, FIXME).

*JS-TARGET-VERSION* (по-умолчанию 1.3) указывает, какую версию JavaScript будет использовать Parenscript. Для новых версий JS, некоторые Parenscript формы могут быть скомпилированы в более сжатые и/или эффективные выражения, которые не присутствовали в предыдущих версиях JS.

SYMBOL-TO-JS-STRING - это Parenscript функция предназначенная для транслирования символов Common Lisp в идентификаторы JavaScript (см. раздел с таблицей конверсий символов для правил трансляции). Это необходимо для написания библиотек или других частей кода, которые взаимодействуют со сгенерированным Parenscript-ом.

Новые версии Parenscript могут реализовывать специальные формы Common Lisp-а, функции и макросы, которые не были реализованы в предыдущих версиях. Это может вызвать проблемы, если ваш код уже предоставляет реализацию. Для помощи в разрешении этой проблемы, *DEFINED-OPERATORS* предоставляет список специальных форм, макросов и символов уже реализованных в Parenscript. *VERSION* связан со значением номера текущего релиза Parenscript.

Преобразование символов

Parenscript подерживает вывод обоих видов символов: регистрозависимых и регистронезависимых. По-умолчанию Lisp reader возводит все символы в верхний регистр. После установки readtable-case в :invert (вы можете использовать библиотеку named-readtables для упрощения задачи) регистр символов сохраняется, и Parenscript будет выводить символы (как encodeURIComponent) без изменения.

Символы Lisp (не keyword-ы), которые содержат специальные символы, преобразуются в идентификаторы JavaScript используя простые правила. Символы !, ?, #, @, %, /, * и + замещаются их словесными эквивалентами "bang", "what", "hash", "at", "percent", "slash", "start" и "plus" соответственно. Символ $ не меняется.

!?#@%
bangwhathashatpercent;

Дефис указывает, что следующий символ будет возведен в верхний регистр.

bla-foo-bar
blaFooBar;

Идентификаторы JavaScript, которые начинаются с большой буквы могут быть транслированы с предшествующем - или *.

  *array
Array;

Символы начинающиеся и заканчивающиеся на + или * будет транслированы в идентификатор в верхнем регистре, как константы или глобальные переменные.

  *global-array*
GLOBALARRAY;

Keywords не транслируются в идентификаторы JavaScript, но выводятся в нижнем регистре без каких-либо преобразований, как строки. Так сделано, потому что строки являются наиболее похожим эквивалентом для Common Lisp keyword-ов.

:+
'+';
:foo-bar
'foo-bar';

Parenscript система пространств имен

  (in-package package-designator)
(use-package package-designator)
(setf (PS-PACKAGE-PREFIX package-designator) string)

Так как JavaScript не предоставляет постранств имен или системы пакетов, Parenscript реализует механизм пространств имен для сгенерированного JavaScript-а с помощью интеграции с Common Lisp системой пакетов. Parenscript код разбирается Lisp reader-ом, все символы (кроме невнутренних, например, с использованием #:) принадлежат Lisp пакету. По-умолчанию, префикс для пакетов не используется. Вы можете указать, чтобы символы в определенном пакете содержали префикс при транслировании в JavaScript код, с помощью PS-PACKAGE-PREFIX.

(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;
};

Parenscript предоставляет формы IN-PACKAGE и USE-PACKAGE, наиболее полезные c PS-COMPILE-FUNCTION и PS-COMPILE-STREAM.

Обфускация идентификаторов

  (OBFUSCATE-PACKAGE package-designator &optional symbol-map)
(UNOBFUSCATE-PACKAGE package-designator)

Сходный с механизмом пространств имен, Parenscript предоставляет возможнсть генерации обфусцированных идентификаторов в заданном CL пакете. Функция OBFUSCATE-PACKAGE опционально может использоваться в замыкании, где заданы соотвествия символов к их обфусцированным аналогам. По-умолчанию, соответствие задается с помощью PS-GENSYM.

(defpackage "PS-REF.OBFUSCATE-ME")
(obfuscate-package "PS-REF.OBFUSCATE-ME"
(let ((code-pt-counter #x8CF6)
(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, 賸) {
return a + libraryFunction(b, 賸);
};

Обфускация и пространства имен могут быть использованы одновременно.

Так как Parenscript ничего не знает о DOM и других JavaScript библиотеках, функции и свойства объектов могут быть обфусцированы. Чтобы избежать данной проблемы, Parenscript содержит ps-dom1-symbols, ps-dom2-symbols, ps-window-wd-symbols, ps-dom-nonstandard-symbols and ps-dhtml-symbols пакеты, которые определяют различные свойства DOM и идентификаторы функций, как экспортированные символы (в двух вариантах: регистрозависимом и регистронезависимом), которые вы можете импортировать в свой пакет для защиты символа от обфусцирования. Пакет ps-dhtml-symbols содержит самый широкий набор символов.

Если вы используете обфускацию и внешние JavaScript библиотеки, вы можете использовать этот же метод для защиты ваших символов от обфусцирования.

Минимизация

  *PS-PRINT-PRETTY*
*INDENT-NUM-SPACES*

*PS-PRETPRINT-PRETTY* и *INDENT-NUM-SPACES* указывают, будет ли результирующий код "красиво" распечатан, и если так, то сколько пробелов используется для отступов. По умолчанию код печатается "красиво" с 4-мя пробелами.

Установка *PS-PRINT-PRETTY* в nil и включенная обфускация уменьшит JavaScript код.

Зарезервированные символы

Следующие символы являются зарезервированными в Parenscript, и не могут использоваться в качестве имен переменных.

! ~ ++ -- * / % + - << >> >>> < > <= >= == != === !== & ^ | && || *= /= %= += -= <<= >>= >>>= &= ^= |= 1- 1+ @ ABSTRACT AND AREF ARRAY BOOLEAN BREAK BYTE CASE CATCH 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 GETPROP STATIC SUPER SWITCH SYMBOL-MACROLET SYNCHRONIZED T THIS THROW THROWS TRANSIENT TRY TYPEOF UNDEFINED UNLESS VAR VOID VOLATILE WHEN WHILE WITH WITH-SLOTS

Операторы, выражения и возврат

В отличие от лиспа, где все является выражением, JavaScript делает различие между выражениями возращающими значения, которые могут использоваться в других выражениях, и операторы не имеющие возвращаемого значения, которые не могут встречатся в других выражениях.

Некоторые специальные формы Parenscript компилируются в выражения, тогда как другие только в операторы. Несомненно такие формы Parenscript, как IF или PROGN, генерируют разный JavaScript в зависимости от того, где используются. В таких случаях ParenScript пытается генерировать "операторный" код для повышения читабельности, оставляя код с выражениями только по необходимости.

(+ i (if x (foo) (bar)))
i + (x ? foo() : bar());
(if x (foo) (bar))
    if (x) {
foo();
} else {
bar();
};

Одна важная возможность существующая в лиспе и отсутствующая в JavaScript - неявный возврат значения из функции. Parenscript поддерживает неявный возврат.

(defun foo (x)
(1+ x))
    function foo(x) {
return x + 1;
};
(lambda (x)
(case x
(1 (loop repeat 3 do (alert "foo")))
(:bar (alert "bar"))
(otherwise 4)))
    function (x) {
switch (x) {
case 1:
for (var _js1 = 0; _js1 < 3; _js1 += 1) {
alert('foo');
};
return null;
case 'bar':
return alert('bar');
default:
return 4;
};
};

Следует отметить, что Parenscript может сгенерировать некорректный JavaScript.

(+ 1 (dotimes (x 3) (+ x x)))
    1 + for (var x = 0; x < 3; x += 1) {
x + x;
};

Типы и предикаты

  (TYPEOF object)
(INSTANCEOF object type)
(NULL object)
(UNDEFINED object)
(DEFINED object)
(STRINGP object)
(NUMBERP object)
(FUNCTIONP object)
(OBJECTP object)
object
выражение возвращающее объект
type
обозначение типа

Литералы

Числа

Parenscript пребразует integer в integer, float, ratio во floats в десятеричной системе.

1
1;
123.123
123.123;
3/4
0.75;
#x10
16;

Строки и символы

Строки лиспа преобразуются в JavaScript строки.

"foobar"
'foobar';

Непечатаемые символы выводятся с помощью escape последовательностей.

#\Tab
'\t';
"\\n"
'\\n';

Регулярные выражения

  (REGEX regex)
regex
строка

Регулярные выражения могут быть созданы с помощью формы REGEX. Если аргумент не начинается с /, он будет окружен /, иначе останется как есть.

(regex "foobar")
/foobar/;
(regex "/foobar/i")
/foobar/i;

CL-INTERPOL служит помощником для написания регулярных выражений:

(regex #?r"/([^\s]+)foobar/i")
/([^\s]+)foobar/i;

Логический тип и undefined

  T
F
FALSE
NIL
UNDEFINED

T и FALSE (или F) транслируются в их JavaScript эквиваленты: true и false.

NIL преобразуется в null.

UNDEFINED в глобальную переменную undefined.

Объекты

  (NEW constructor)
(CREATE {name value}*)
(GETPROP object {slot-specifier}*)
(@ {slot-specifier}*)
(CHAIN {slot-specifier | function-call}*)
(WITH-SLOTS ({slot-name}*) object body)
(DELETE object)
constructor
вызов функции как конструктора
name
символ. строка или keyword
value
выражение
object
выражение возвращающее объект
slot-specifier
закавыченный символ, строка, число или выражение возвращающее строку или число
body
неявный progn

Объект может быть создан с помощью CREATE. CREATE принимает список свойств и значений.

(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 } };

Свойства объекты могут быть получены с помощью GETPROP, что принимает объект и список свойств.

(getprop obj 'foo)
obj.foo;
(getprop obj foo)
obj[foo];
(getprop element i 'child-node 0 'node-value)
element[i].childNode[0].nodeValue;

Удобный макрос @ кавычит все принимаемые символы.

(@ an-object foo bar)
anObject.foo.bar;
(@ foo bar child-node 0 node-value)
foo.bar.childNode[0].nodeValue;

CHAIN может использоватся для цепочек вызовов функций и свойств:

(chain foo (bar x y) 0 baz)
foo.bar(x, y)[0].baz;

WITH-SLOTS может быть использована для связи передаваемых имен слотов с переменными, которые затем будут развернуты в соответствующие конструкции.

(with-slots (a b c) this
(+ a b c))
    this.a + this.b + this.c;

Массивы

  (ARRAY {values}*)
(LIST {values}*)
([] {values}*)
(MAKE-ARRAY {values}*)
(LENGTH array)
(AREF array index)
(ELT array index)
(DESTRUCTURING-BIND bindings array body)
(CONCATENATE 'STRING {values}*)
(APPEND {values}*)
values
выражение
array
выражение
index
выражение

Массивы могут быть созданы используя формы ARRAY и LIST.

(array)
[];
(array 1 2 3)
[1, 2, 3];
(list (foo) (bar) 3)
[foo(), bar(), 3];
(array (array 2 3)
(array "foo" "bar"))
    [[ 2, 3 ], ['foo', 'bar']];

Массивы также могут быть созданы с помощью вызова функции Array с помощью MAKE-ARRAY.

(make-array)
new Array();
(make-array 1 2 3)
new Array(1, 2, 3);
(make-array
(make-array 2 3)
(make-array "foobar" "bratzel bub"))
    new Array(new Array(2, 3), new Array('foobar', 'bratzel bub'));

Доступ к элементам массива осуществляется с помощью AREF или ELT.

Арифметические и логические операторы

  (<operator> {argument}*)
(<single-operator> argument)
<operator>
один из *, /, %, +, -, <<, >>, >>>, < >, EQL, ==, !=, =, ===, !==, &, ^, |, &&, AND, ||, OR
<single-operator>
один из INCF, DECF, ++, --, NOT, !
argument
выражение

Формы операторов похожи на формы с вызовом функции, однако содержат имя оператора вместо имени функции.

Пожалуйста, запомните, что = траслируется в == . = не является оператором присваивания.

(* 1 2)
1 * 2;
(= 1 2)
1 === 2;

Математические функции и константы

  (MAX {number}*)
(MIN {number}*)
(FLOOR number &optional divisor)
(CEILING number &optional divisor)
(ROUND number &optional divisor)
(SIN number)
(COS number)
(TAN number)
(ASIN number)
(ACOS number)
(ATAN number1 &optional number2)
(SINH number)
(COSH number)
(TANH number)
(ASINH number)
(ACOSH number)
(ATANH number)
(1+ number)
(1- number)
(ABS number)
(EVENP number)
(ODDP number)
(EXP number)
(EXPT base power)
(LOG number &optional base)
(SQRT number)
(RANDOM &optional limit)
PI

Математический функции перечисленные выше работают также, как в аналоги Common Lisp, кроме того, что не поддерживаются комплексные числа. Однако, многие из них реализованы как макросы и не могут использоваться в качестве параметров.

Блоки

  (PROGN {statement}*) in statement context
(PROGN {expression}*) in expression context
(PROG1 {expression | statement}*)
(PROG2 {expression | statement}*)
statement
форма транслирующаяся в оператор
expression
форма транслирующаяся в выражение

Транслрование PROGN зависит от контекста использования.

(progn (blorg i) (blafoo i))
    blorg(i);
blafoo(i);

(+ i (progn (blorg i) (blafoo i)))
i + (blorg(i), blafoo(i));

Функции и несколько значений

  (DEFUN name lambda-list body)
(LAMBDA lambda-list body)
(FLET ({(name lambda-list body)}*) body)
(LABELS ({(name lambda-list body)}*) body)
(VALUES {expression}*)
(MULTIPLE-VALUE-BIND (var*) expression body)
(APPLY function expression*)
(FUNCALL function expression*)
THIS
expression
форма транслирующаяся в выражение
name
символ
lambda-list
список лямбд
body
неявный PROGN
var
символ для имени переменной
function
выражение, возвращающее функцию

Определения новых функций может быть введены используя все доступные Lisp формы - DEFUN, LAMBDA, FLET и LABELS. Аргументы могут быть: &optional, &rest и &key.

Parenscript механизм возврата нескольких значений использует первое значение для обычных вызовов JavaScript, это значит, что функции возвращающие несколько значение могут вызываться в JavaScript, и MULTIPLE-VALUE-BIND работает с обычными JavaScript функциями. FIXME.

APPLY макрос, который разворачивается в вызов JavaScript apply метода.

Передача управления и исключения

  (RETURN {value}?)
(THROW {exp}?)
(TRY form {(:CATCH (var) body)}? {(:FINALLY body)}?)
(IGNORE-ERRORS body)
value
оператор или выражение
exp
выражение
var
переменная, к которой привязан сгенерированный объект исключения
body
неявный PROGN

Parenscript RETURN и THROW работают НЕ также, как одноименные Common Lisp формы.

RETURN может быть использовано для возврата значения только из функции - Parenscript не имеет аналогов Common Lisp blocks. RETURN работает с переданным оператором и выражением (в этом случае, происходит семантический анализ для определения значения для возврата).

(lambda (x)
(return (case x
(1 :a)
(2 :b))))
    function (x) {
switch (x) {
case 1:
return 'a';
case 2:
return 'b';
};
};

Также THROW транслируется напрямую в JavaScript throw, для использования с try, который в свою очередь также напрямую переводится в JavaScript try.

(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');
};

Условные переходы

  (IF condition then {else})
(WHEN condition then)
(UNLESS condition then)
(COND {clauses}*)
(CASE case-value clause*)
(SWITCH case-value clause*)
BREAK
condition
выражение
then
оператор в контексте оператора, выражение в контексте выражения
else
оператор в контексте оператора, выражение в контексте выражения
clause
(<value> body) | (default body)

IF, WHEN, UNLESS и COND работают как их аналоги в лиспе.

(cond ((= x 1) (+ x (if (foo y) 2 3))))
    if (x == 1) {
x + (foo(y) ? 2 : 3);
};

CASE работает как и его Common Lisp эквивалент. Дополнительная форма, SWITCH, имеет тот же синтаксис, CASE, но отдельные ветки должны быть завершены символом BREAK. Это позволяет "fall-throughs" в стиле С.

Следует отметить, что ветка default в SWITCH операторе должна быть названа как DEFAULT.

Декларация переменных и связывание

  (LET ({var | (var value)}*) body)
(LET* ({var | (var value)}*) body)
(DEFVAR var {value}?)
(VAR var {value}?)
var
символ
value
выражение
body
неявный PROGN
object
выражение вычислемое в объект

Parenscript предоставляет специальные формы LET и LET* для создания новых переменных. Обе специальные формы реализуют лексическое окружение с помощью переименования предоставляемых переменных с помощью GENSYM, и реализуют динамическое связывание с помощью TRY-FINALLY.

Let и Let* верхнего уровня будут создавать новые глобальные переменные, или перезаписывать значения уже одноименных существующих.

Помните, что лексические окружения в JavaScript отличаются от Lisp, и даже могут быть реализованы неправильно в некоторых реализациях JavaScript браузера. Особенно это относится к лексическим замыканиям.

Специальные переменные могут быть задекларированы с помощью DEFVAR. Следует отметить, что результат неопределен, если DEFVAR не является top-level формой.

Одна возможность Parenscript, не представленная в Common Lisp - глобальные переменные в лексическом окружении, которые создаются формой VAR. Результат неопределен, если VAR не является top-level формой.

Пример создания и связывания переменных:

(defvar *a* 4)
(var *b* 3)
(lambda ()
(let ((x 1)
(*a* 2)
(*b* 6))
(let* ((y (+ x 1))
(x (+ x y)))
(+ *a* *b* x y))))
    var A = 4;
var B = 3;
function () {
var x = 1;
var B = 6;
var A_TMPSTACK1;
try {
A_TMPSTACK1 = A;
A = 2;
var y = x + 1;
var x2 = x + y;
return A + B + x2 + y;
} finally {
A = A_TMPSTACK1;
};
};

Присваивание

Parenscript присваивание осуществляется с помощью стандартных SETF, SETQ, PSETF и PSETQ. Parenscript поддерживает протокол Common Lisp о SETFable местах.

Новые места могут быть задекларированы одним из путей: используя DEFSETF или используя DEFUN вместе с setf для имени функции. Оба метода аналогичны Common Lisp. DEFSETF поддерживает и длинную, и короткую формы, тогда как DEFUN с setf место генерирует JavaScript функцию с именем с префиксом setf_: (defun (setf color) (new-color el) (setf (@ el style color) new-color)) function setf_color(newColor, el) {

        return el.style.color = newColor;
};
(setf (color some-div) (+ 23 "em"))
    var _js2 = someDiv;
var _js1 = 23 + 'em';
__setf_color(_js1, _js2);

Следующий пример иллюстрирует, как setf места могут быть использованы для предоставления унифицированного протокола для позиционирования элементов в HTML страницах.

Циклы

  (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)
(LOOP {loop clauses}*)
var
символ
numeric-form
выражение возвращающее число
list-form
выражение возвращающее массив
object-form
выражение возвращающее объект
init
выражение
step
выражение
end-test
выражение
result
выражение
body
неявный PROGN

Parenscript содержит широкий набор Common Lisp конструкций циклов, которые транслируются в эффективный JavaScript код, включая подмножество реализации loop.

Макросы

Декларация макросов

  (DEFMACRO name lambda-list macro-body)
(DEFPSMACRO name lambda-list macro-body)
(DEFMACRO+PS name lambda-list macro-body)
(IMPORT-MACROS-FROM-LISP symbol*)
(MACROLET ({name lambda-list macro-body}*) body)
name
символ
lambda-list
список аргументов
macro-body
Lisp код преобразующийся в Parenscript код
body
неявный PROGN
symbol
символ связанный с Lisp определением макроса

Макросы Parenscript похожи на макросы Lisp, у них есть доступ ко всем языку lisp, но они должны возвращать Parenscript код. Так как Parenscript реализует большое подмножество Common Lisp, многие Lisp макросы уже возвращают валидный Parenscript код. Parenscript предоставляет несколько других путей для определения новых макросов, и использования уже существующих Lisp макросов.

DEFMACRO и MACROLET могут быть использованы для определения новых макросов в Parenscript коде. Следует отметить, что макросы определяются в нулевом окружении (пример не сработает (let ( (x 1) ) (defmacro baz (y) `(+ ,y ,x))) ), так как окружающий код будет уже транслирован в JavaScript.

DEFPSMACRO форма Lisp (не Parenscript), которая служит для создания Parenscript макросов без вызова компилятора.

DEFMACRO+PS определяет два макроса с одним и тем же именем и результатом, один в Parenscript, другой в Common Lisp. DEFMACRO+PS используется, конда полный разворот Lisp макроса не может быть выполнен в Parenscript.

Parenscript также поддерживает использование макросов определенных в Common Lisp. Существующие макросы могут быть импортированы в Parenscript с помощью IMPORT-MACROS-FROM-LISP. Данная функциональность позволяет использовать один код в двух разных окружениях: CL и Parenscript, и облегчает отладку, так как могут использоваться все средства доступные для CL. Важно отметить, что окружения для разворота макросов отличаются и некоторые Lisp макросы не заработают в Parenscript.

Символы макросов

  (DEFINE-PS-SYMBOL-MACRO symbol expansion)
(SYMBOL-MACROLET ({name macro-body}*) body)

Символы макросов могут быть назначены с помощью SYMBOL-MACROLET и для Lisp DEFINE-PS-SYMBOL-MACRO. Например, Parenscript WITH-SLOTS реализован так:

(defpsmacro with-slots (slots object &rest body)
`(symbol-macrolet ,(mapcar #'(lambda (slot)
`(,slot '(getprop ,object ',slot)))
slots)
,@body))

GENSYM

  (PS-GENSYM {string})
(WITH-PS-GENSYMS symbols &body body)
(PS-ONCE-ONLY (&rest vars) &body body)
*PS-GENSYM-COUNTER*

Равенство JavaScript базируется на равенстве их строковых представлений, в отличие от Common Lisp, где два невнутренних одноименных символы являются различными объектами. Parenscript GENSYM зависит от значений *PS-GENSYM-COUNTER* только для генерации уникальных имен. *PS-GENSYM-COUNTER* не является потокобезопасным, будьте внимательны при использовании генератора из разных потоков.

Утилиты

DOM

  (INNER-HTML el)
(URI-ENCODE el)
(ATTRIBUTE el)
(OFFSET compass el)
(SCROLL compass el)
(INNER wh el)
(CLIENT wh el)
el
выражение возвращающее элемент DOM
compass
один из :TOP, :LEFT, :HEIGHT, :WIDTH, :BOTTOM, :RIGHT
wh
один из :WIDTH, :HEIGHT

Генерация HTML

  (PS-HTML html-expression)
(WHO-PS-HTML html-expression)
*PS-HTML-EMPTY-TAG-AWARE-P*
*PS-HTML-MODE*

Parenscript содержит два HTML генератора: PS-HTML и WHO-PS-HTML. Первый принимает LHTML-style синтаксис, последний - CL-WHO-style.

*PS-HTML-EMPTY-TAG-AWARE-P* и *PS-HTML-MODE* указывают, как будет закрыт тег без контента. Если *PS-HTML-EMpty_TAG-AWARE-P* nil, все теги закрываются полностью (например, :BR -> <BR></BR>), иначе если *PS-HTML-MODE* равен :SGML, такие теги как BR выводятся не закрытыми, если *PS-HTML-MODE* равен :XML, используется XML стиль для закрытия (например, :BR -> <BR />).

  (ps-html ((:a :href "foobar") "blorg"))
'<A HREF=\"foobar\">blorg</A>';
(who-ps-html (:a :href (generate-a-link) "blorg"))
'<A HREF=\"' + generateALink() + '\">blorg</A>';

Parentscript компилятор может быть рекурсивно вызван в HTML выражении:

  ((@ document write)
(ps-html ((:a :href "#"
:onclick (ps-inline (transport))) "link")))
      document.write('<A HREF=\"#\" ONCLICK=\"' + ('javascript:' + 'transport()') + '\">link</A>');

Формы могут быть использованы в списках атрибутов для условной генерации следующих атрибутов. В данном примере textarea иногда становится недоступной.

  (let ((disabled nil)
(authorized t))
(setf (@ element inner-h-t-m-l)
(ps-html ((:textarea (or disabled (not authorized)) :disabled "disabled")
"Edit me"))))
      var disabled = null;
var authorized = true;
element.innerHTML =
'<TEXTAREA'
+ (disabled || !authorized ? ' DISABLED=\"' + 'disabled' + '\"' : '')
+ '>Edit me</TEXTAREA>';

Библиотека

  (MEMBER object array)
(MAP function array)
(MAPCAR function {array}*)
(REDUCE function array object)
(MAP-INTO function array)
(SET-DIFFERENCE array1 array2)
*PS-LISP-LIBRARY*

Все конструкции Parenscript перечисленные выше не имеют зависимостей времени выполнения. Parenscript также содержит библиотеку полезных функций, которые могут быть добавлены в ваш проект. Эти функции содержатся, как код Parenscript, в специальной переменной *PS-LISP-LIBRARY*.

MAP отличается от Common Lisp эквивалента тем, что, как и MAPCAR, принимает только одну последовательность. MAP-UNTIL похож на MAP, но заменяет контент полученного массива.

Интеграция со SLIME

Директория extras дистрибутива Parenscript содержит js-expander.el, который при загрузке в Emacs c SLIME добавляет возможность быстрого просмотра результата компиляции любой Lisp формы в JavaScript, и работает так же как и 'C-c M-m' в SLIME.

'C-c j' (PS) или 'C-c d' (PS-DOC) на выражении Parenscript в буфере со slime-mode, воздает буфер с результирующим JavaScript кодом. Следует отметить, что расширение не работает в slime-repl-mode.

@2009-2013 lisper.ru