This package recognizes an alternative, Scheme-like front-end syntax for JavaScript, dubbed Parenthetical JavaScript (or PJS for short). If you pronounce that "pee-jays" you will make me happy.
The PJS library can be required via:
The syntax of PJS is as follows:
The set of recognized operators is:
prefix-op : ++, --, +, -, ~, !
infix-op : *, /, %, +, -, <, <<, >, >>, >>>, <=, >=, ==, !=, ===, !==, &, ^, \|, &&, \|\|
postfix-op : ++, --
assign-op : =, *=, /=, %=, +=, -=, <<=, >>=, >>>=, &=, ^=, \|=
For aesthetics and consistency with Scheme, some of the JavaScript operators with awkward or obscure operator tokens are given convenient synonyms in PJS:
or = \|\|
and = &&
bitwise-and = &
bitwise-ior = \|
bitwise-not = ~
bitwise-xor = ^
Identifiers in PJS (i.e., the id non-terminal in the grammar) are restricted to valid JavaScript identifiers. This ensures that, when generating actual JavaScript syntax, the exact name of a variable or object property is preserved, with no name mangling. (Since variable names are sometimes observable in JavaScript, this prevents subtle bugs where a program may change its behavior based on a particular mangling.)
The grammar as presented contains several ambiguities.
The following primitive operators have names that are valid JavaScript identifiers:
and
array
begin
block
field
label
object
or
prefix
postfix
regexp
The parsing and code generation functions maintain an environment for lexically bound variables. JavaScript variable bindings can therefore shadow these operators. For example, in the expression
(function(array)(return(array123)))
the inner occurrence of array is parsed as a variable reference rather than an array constructor.
Operators may be disambiguated with the use of the special form #%keyword. For example, (#%keyword. array) unconditionally refers to the initial binding of array, regardless of the current environment. The above example can then be rewritten as
(function(array)(return((#%keyword. array)123)))
Note that if JavaScript code would dynamically bind one of these names (e.g., via with or eval), the operator is not shadowed, since the static environment only tracks lexical bindings.
Expression statements introduce a few minor ambiguities into the PJS syntax. These include function declarations vs. named function expressions and if statements vs. if expressions. These are always resolved in favor of the declaration or statement form rather than the expression form, since in both cases the expression form is less likely to occur.
The expression form operator #%expression forces its argument to be parsed as an expression. For example, the expression (#%expression (if xyz)) is parsed as a conditional expression rather than a conditional statement.
The PJS syntax provides several additional syntactic conveniences.
Similar to the Java Dot Notation originally introduced by JScheme, PJS permits the use of dotted identifiers as a short-hand for the obvious corresponding chains of object field references. Unlike in JScheme, it is impossible to bind a variable with a dot in its name, so dotted identifiers are always unambiguously decoded as field references.
For example, the identifier document.body.innerHTML is equivalent to the expression (field(fielddocumentbody)innerHTML).
Binary JavaScript operators are generalized in PJS to n-ary operators and expanded left-associatively into nested applications of the binary operator.
For example, the expression (- 4321) is equivalent to (- (- (- 43)2)1).
The PJS library provides procedures that operate on either syntax objects or S-expressions. The syntax object procedures maintain source location information.
stx:syntax?
stx:syntax?
stx:syntax?
procedure
( expression->syntaxexpr)→syntax?
expr:Expression?
procedure
( statement->syntaxstmt)→syntax?
stmt:Statement?
procedure
( source-element->syntaxelt)→syntax?
elt:SourceElement?