ECMAScript 4 Netscape Proposal
|
Monday, June 30, 2003
A multi-page version of this document is also available.
This document is Netscape’s proposal to the ECMA TC39TG1 working group for the ECMAScript Edition 4 language. This proposal is being updated continuously to match issues resolved in the committee discussions. The TC39TG1 working group’s current schedule calls for a release of a ECMAScript Edition 4 standard sometime in 2002.
See also a draft of the specification under construction in PDF format.
JavaScript 2.0 is a slight superset of ECMAScript Edition 4. JavaScript 2.0 contains features that were considered for ECMAScript Edition 4 but for which more experience is desirable before standardization occurs.
Contents
Changes
The following are recent major changes in this document:
Date | Revisions |
---|---|
Jun 30, 2003 |
|
Jun 4, 2003 |
|
May 22, 2003 |
|
May 2, 2003 |
|
Apr 2, 2003 |
|
Mar 24, 2003 |
|
Feb 17, 2003 |
|
Jan 29, 2003 |
|
Jan 24, 2003 |
|
Jan 13, 2003 |
|
Nov 19, 2002 |
|
Oct 29, 2002 |
|
Sep 25, 2002 |
|
Sep 20, 2002 |
|
Jun 18, 2002 |
|
May 17, 2002 |
|
Apr 22, 2002 |
|
Mar 4, 2002 |
|
Jan 4, 2002 |
|
Dec 20, 2001 |
|
Dec 19, 2001 |
|
Dec 17, 2001 |
|
Dec 6, 2001 |
|
Nov 26, 2001 |
|
Oct 26, 2001 |
|
Oct 18, 2001 |
|
Oct 16, 2001 |
|
Oct 3, 2001 |
|
Sep 26, 2001 |
|
Sep 24, 2001 |
|
Aug 22, 2001 |
|
Aug 17, 2001 |
|
Aug 15, 2001 |
|
Aug 10, 2001 |
|
Jul 24, 2001 |
|
Jun 29, 2001 |
|
Jun 15, 2001 |
|
Apr 11, 2001 |
|
Mar 9, 2001 |
|
Mar 2, 2001 |
|
Feb 28, 2001 |
|
Feb 22, 2001 |
|
Feb 8, 2001 |
|
Feb 6, 2001 |
|
Jan 31, 2001 |
|
Jan 25, 2001 |
|
Jan 11, 2001 |
|
Jan 9, 2001 |
|
Dec 21, 2000 |
|
Dec 20, 2000 |
|
Dec 18, 2000 |
|
Dec 2, 2000 |
|
Nov 29, 2000 |
|
Nov 20, 2000 |
|
Nov 4, 2000 |
|
Oct 27, 2000 |
|
Oct 10, 2000 |
|
Oct 9, 2000 |
|
Sep 23, 2000 |
|
Sep 22, 2000 |
|
Sep 21, 2000 |
|
Sep 18, 2000 | Integrated many changes based on recent discussions with Herman and others:
|
Aug 21, 2000 |
|
Aug 17, 2000 | First version; split off from the JavaScript 2.0 proposal. |
ECMAScript 4 Netscape Proposal
Introduction
|
Wednesday, September 4, 2002
ECMAScript 4 is the next major step in the evolution of the ECMAScript language. ECMAScript 4 incorporates the following features in addition to those already found in ECMAScript 3:
- Class definition syntax, both static and dynamic
- Packages, including a namespace and versioning mechanism
- Types for program and interface documentation
- Invariant declarations such as
const
andfinal
private
,internal
,public
, and user-defined access controls- Machine types such as
int
for more faithful communication with other programming languages
These facilities reinforce each other while remaining fairly small and simple. Unlike in Java, the philosophy behind them is to provide the minimal necessary facilities that other parties can use to write packages that specialize the language for particular domains rather than define these packages as part of the language core.
The versioning and access control mechanisms make the language is suitable for programming-in-the-large.
ECMAScript 4 Netscape Proposal
Introduction
Motivation
|
Wednesday, September 4, 2002
Goals
The main goals of ECMAScript 4 are:
- Making the language suitable for writing modular and object-oriented applications
- Making it possible and easy to write robust and secure applications
- Improving upon ECMAScript’s facilities for interfacing with a variety of other languages and environments
- Improving ECMAScript’s suitability for writing applications for which performance matters
- Simplifying the language where possible
- Keeping the language implementation compact and flexible
The following are specifically not goals of ECMAScript 4:
- Making ECMAScript 4 suitable for all programming tasks
- Making ECMAScript 4 similar to any existing programming language
ECMAScript is not currently an all-purpose programming language. Its strengths are its quick execution from source (thus enabling it to be distributed in web pages in source form), its dynamism, and its interfaces to Java and other environments. ECMAScript 4 is intended to improve upon these strengths, while adding others such as the abilities to reliably compose ECMAScript programs out of components and libraries and to write object-oriented programs. On the other hand, it is not our intent to have ECMAScript 4 supplant languages such as C++ and Java, which will still be more suitable for writing many kinds of applications, including very large, performance-critical, and low-level ones.
Rationale
The proposed features are derived from the goals above. Consider, for example, the goals of writing modular and robust applications.
To achieve modularity we would like some kind of a library mechanism. The proposed package mechanism serves this purpose, but by itself it would not be enough. Unlike existing ECMAScript programs which tend to be monolithic, packages and their clients are often written by different people at different times. Once we introduce packages, we encounter the problems of the author of a package not having access to all of its clients, or the author of a client not having access to all versions of the library it needs. If we add packages to the language without solving these problems, we will never be able to achieve robustness, so we must address these problems by creating facilities for defining abstractions between packages and clients.
To create these abstractions we make the language more disciplined by adding optional types and type-checking. We also introduce a coherent and disciplined syntax for defining classes and hierarchies and versioning of classes. Unlike ECMAScript 3, the author of a class can guarantee invariants concerning its instances and can control access to its instances, making the package author’s job tractable. The class syntax is also much more self-documenting than in ECMAScript 3, making it easier to understand and use ECMAScript 4 code. Defining subclasses is easy in ECMAScript 4, while doing it robustly in ECMAScript 3 is quite difficult.
To make packages work we need to make the language more robust in other areas as well. It would not be good if one package
redefined Object.toString
or added methods to the Array
prototype and thereby corrupted another
package. We can simplify the language by eliminating many idioms like these (except when running legacy programs, which would
not use packages) and provide better alternatives instead. This has the added advantage of speeding up the language’s implementation
by eliminating thread synchronization points. Making the standard packages robust can also significantly reduce the memory
requirements and improve speed on servers by allowing packages to be shared among many different requests rather than having
to start with a clean set of packages for each request because some other request might have modified some property.
ECMAScript 4 should interface with other languages even better than ECMAScript 3 does. If the goal of integration is achieved, the user of an abstraction should not have to care much about whether the abstraction is written in ECMAScript, Java, or another language. It should also be possible to make ECMAScript abstractions that appear native to Java or other language users.
In order to achieve seamless interfacing with other languages, ECMAScript should provide equivalents for the fundamental
data types of those languages. Details such as syntax do not have to be the same, but the concepts should be there. ECMAScript
3 lacks support for integers, making it hard to interface with a Java method that expects a long
.
ECMAScript is appearing in a number of different application domains, many of which are evolving. Rather than support all of these domains in the core ECMAScript, ECMAScript 4 should provide flexible facilities that allow these application domains to define their own, evolving standards that are convenient to use without requiring continuous changes to the core of ECMAScript. ECMAScript 4 partially addresses this goal by letting user programs define facilities such as getters and setters — facilities that could only be done by the core of the language in ECMAScript 3.
ECMAScript 4 Netscape Proposal
Introduction
Notation
|
Wednesday, June 11, 2003
Character Notation
This proposal uses the following conventions to denote literal characters:
Printable ASCII literal characters (values 20 through 7E hexadecimal) are in a blue monospaced font
. Unicode
characters in the Basic Multilingual Plane (code points from 0000 to FFFF hexadecimal) are denoted by enclosing their four-digit
hexadecimal Unicode code points between «u
and »
.
Supplementary Unicode characters (code points from 10000 to 10FFFF hexadecimal) are denoted by enclosing their eight-digit
hexadecimal Unicode code points between «U
and »
.
For example, the non-breakable space character would be denoted in this document as «u00A0»
,
and the character with the code point 1234F hexadecimal would be denoted as «U0001234F»
.
A few of the common control characters are represented by name:
Abbreviation | Unicode Value |
---|---|
«NUL» |
«u0000» |
«BS» |
«u0008» |
«TAB» |
«u0009» |
«LF» |
«u000A» |
«VT» |
«u000B» |
«FF» |
«u000C» |
«CR» |
«u000D» |
«SP» |
«u0020» |
A space character is denoted in this document either by a blank space where it’s obvious from the context or by «SP»
where the space might be confused with some other notation.
Grammar Notation
Each LR(1) syntactic grammar and lexical grammar rule consists of a nonterminal, a , and one or more expansions of the nonterminal separated by vertical bars (|). The expansions are usually listed on separate lines but may be listed on the same line if they are short. An empty expansion is denoted as «empty».
Consider the sample rule:
...
Identifier,
...
IdentifierThis rule states that the nonterminal SampleList can represent one of four kinds of sequences of input tokens:
- It can represent nothing (indicated by the «empty» alternative);
- It can represent the token
...
followed by some expansion of the nonterminal Identifier; - It can represent an expansion of the nonterminal SampleListPrefix;
- It can represent an expansion of the nonterminal SampleListPrefix followed by the tokens
,
and...
and an expansion of the nonterminal Identifier.
Input tokens are characters (and the special End placeholder) in the lexical
grammar and lexer tokens in the syntactic grammar. Input tokens to be typed literally
are in a bold blue monospaced font
. Spaces separate input tokens and nonterminals from
each other. An input token that consists of a space character is denoted as «SP»
.
Other non-ASCII or non-printable characters are denoted by also using «
and »
,
as described in the character notation section.
Lookahead Constraints
If the phrase “[lookahead set]” appears in the expansion of a production, it indicates that the production may not be used if the immediately following input terminal is a member of the given set. That set can be written as a list of terminals enclosed in curly braces. For convenience, set can also be written as a nonterminal, in which case it represents the set of all terminals to which that nonterminal could expand.
For example, given the rules
0
| 1
| 2
| 3
| 4
| 5
| 6
| 7
| 8
| 9
the rule
n
[lookahead {1
, 3
, 5
, 7
, 9
}] DecimalDigitsmatches either the letter n
followed by one or more decimal digits the first of which is even, or a decimal
digit not followed by another decimal digit.
These lookahead constraints do not make the grammars more theoretically powerful than LR(1), but they do allow these grammars to be written more simply. The semantic engine compiles grammars with lookahead constraints into parse tables that have the same format as those produced from ordinary LR(1) or LALR(1) grammars.
Line Break Constraints
If the phrase “[no line break]” appears in the expansion of a production, it indicates that this production cannot be used if there is a line break following the last terminal matched by the grammar. Line break constraints are only present in the syntactic grammar.
Parametrized Rules
Many rules in the grammars occur in groups of analogous rules. Rather than list them individually, these groups have been summarized using the shorthand illustrated by the example below:
Metadefinitions such as
introduce grammar arguments and . If these arguments later parametrize the nonterminal on the left side of a rule, that rule is implicitly replicated into a set of rules in each of which a grammar argument is consistently substituted by one of its variants. For example, the sample rule
=
AssignmentExpressionnormal,expands into the following four rules:
=
AssignmentExpressionnormal,allowIn=
AssignmentExpressionnormal,noIn=
AssignmentExpressionnormal,allowIn=
AssignmentExpressionnormal,noInAssignmentExpressionnormal,allowIn is now an unparametrized nonterminal and processed normally by the grammar.
Some of the expanded rules (such as the fourth one in the example above) may be unreachable from the grammar’s starting nonterminal; these are ignored.
Special Lexical Rules
A few lexical rules have too many expansions to be practically listed. These are specified by descriptive text instead of a list of expansions after the .
Some lexical rules contain the metaword except. These rules match any expansion that is listed before the except
but that does not match any expansion after the except. All of these rules ultimately expand into single characters.
For example, the rule below matches any single UnicodeCharacter except the *
and
/
characters:
ECMAScript 4 Netscape Proposal
Core Language
|
Wednesday, August 14, 2002
- Concepts
- Lexer
- Expressions
- Statements
- Definitions
- Variables
- Functions
- Classes
- Namespaces
- Packages
- Pragmas
This chapter presents an informal description of the core language. The exact syntax and semantics are specified in the formal description. Libraries are also specified in a separate library chapter.
ECMAScript 4 Netscape Proposal
Core Language
Concepts
|
Wednesday, September 4, 2002
Values
A value is an entity that can be stored in a variable, passed to a function, or returned from a function. Sample values include:
undefined
null
5
(a number)true
(a boolean)"Kilopi"
(a string)[1, 5, false]
(a three-element array){a:3, b:7}
(an object with two properties)function (x) {return x*x}
(a function)String
(a class, a function, and a type)
Types
A type t represents three things:
- A possibly infinite set of values S
- A partial mapping I from the set of all values to the set S
- A partial mapping E from the set of all values to the set S
The set S indicates which values are considered to be members of type t. We write v t to indicate that value v is a member of type t. The mapping I indicates how values may be implicitly coerced to type t. For each value v already in S, the mapping I must map v to itself. The mapping E indicates how values may be explicitly coerced to type t. For each value v in the domain of I, E must map v to the same value as I maps v. In other words, any implicit coercion is also an explicit coercion but not vice versa.
A value can be a member of multiple sets, and, in general, a value belongs to more than one type. Thus, it is generally not useful to ask about the type of a value; one may ask instead whether a value belongs to some given type. There can also exist two different types with the same set of values but different coercion mappings.
On the other hand, a variable does have a particular type. If we declare a variable x of type t, then whatever value is held in x is guaranteed to be a member of type t, and we can assign any value of type t to x. We may also be able to assign a value v t to x if type t’s mapping specifies an implicit coercion for value v; in this case the coerced value is stored in x.
Every type represents some set of values but not every set of values is represented by some type (this is required for logical consistency — there are uncountably infinitely many sets of values but only countably infinitely many types).
Every type is also itself a value and can be stored in a variable, passed to a function, or returned from a function.
Type Hierarchy
If type a’s set of values is a subset of type b’s set of values, then we say that that type a is a subtype of type b. We denote this as a b.
Subtyping is transitive, so if a b and b c, then a c. Subtyping is also reflexive: a a. Also, if v t and t s, then v s.
The set of all values is represented by the type Object
, which
is the supertype of all types. A variable with type Object
can hold any value.
The set of no values is represented by the type Never
, which is
the subtype of all types. A function with the return type Never
cannot return.
Classes
A class is a template for creating similar values, often called objects or instances. These instances generally share characteristics such as common methods and properties.
Every class is also a type and a value. When used as a type, a class represents the set of all possible instances of that class.
A class C can be derived from a superclass S. Class C can then inherit characteristics of class S. Every instance of C is also an instance of S, but not vice versa, which, by the definition of subtyping above, implies that C S when we consider C and S as types.
The subclass relation imposes a hierarchy relation on the set of all classes. ECMAScript 4 currently does not support multiple inheritance, although this is a possible future direction. If multiple inheritance were allowed, the subclass relation would impose a partial order on the set of all classes.
Members
A class typically contains a set of members, which can be variables, functions, etc. Members are classified as either instance or global members. Instance members become properties of instances of the class. Global members become properties (sometimes called class properties) of the class object itself; a class has only one global member with a given name.
Members can have attributes which modify their behavior.
Instances
An instance (sometimes called an object) contains a set of properties. An instance belongs to a particular class and must have properties for the instance members defined in that class and its ancestors; these bindings are made when the instance is created. An instance may also have additional dynamic properties, which can be added and deleted any time after the instance has been created.
Unless specified otherwise, each separately created instance has a distinct identity. The ===
operator returns
false
when applied to two different instances or true
when applied to the same instance (with the
exception of NaN
, which always compares unequal to itself).
Instances provide the appearance of lasting forever, although an implementation is expected to garbage-collect them when they are no longer reachable.
Properties
A property is a runtime binding of a property name to a value. The values of some properties can change. Properties can be fixed or dynamic. Fixed properties are declared as members of a class definition and are created at the time the object is constructed. Dynamic properties can be added to an object at any time after the object was created. All ECMAScript 3 properties are dynamic.
Properties can have attributes. Fixed properties inherit their attributes from the corresponding members.
Property Names
A property name identifies a property of an instance and consists
of a namespace N, an identifier id, and a class C.
There is no language syntax for fully specifying a property name; in this specification property names are denoted using the
notation N::
idC. If the property is fixed, then
C is the class in which the corresponding member is defined. If the property is dynamic,
then C is the instance’s most derived class.
An instance can contain at most one property for each property name. An instance can contain multiple
properties with the same namespace N and name id but different classes C; in this case the
property with the most derived class C is said to override the others. The overridden properties are still
present in the instance and can be accessed using the super
operator.
Scopes
A scope represents one of the following delimited portions of ECMAScript source code. Some scopes are further distinguished as being regional scopes, as indicated in the table below.
Nonterminal | Regional | Description |
---|---|---|
Program | yes | The top level of a program |
PackageDefinition | yes | A package definition |
ClassDefinition | yes | A class definition |
FunctionDefinition | yes | A function definition |
FunctionExpression | yes | A function expression |
Block | no* | A block (but not a group of directives prefixed with attributes) |
ForStatement | no* | A for statement |
CatchClause | no* | A catch clause of a try statement |
*These three scopes become regional scopes if the next outer scope is a class scope.
A scope is a static entity that does not change while an ECMAScript program is running (except that if the program calls
eval
then new ECMAScript source code will be created which may share existing scopes or create its own scopes).
A scope other than the top level of a program (the global scope) is always contained inside another scope. If two scopes overlap,
one must be contained entirely within the other, so scopes form a hierarchy.
Scope information is used at run time to help with variable and property lookups and visibility checks.
A scope should not be confused with an activation frame. A scope should also not be confused with a namespace.
Activation Frames
An activation frame contains a set of runtime bindings of all qualified names defined in a scope to values. A new activation frame comes into existence each time the scope is entered. A function closure captures a reference to the activation frame in which it was created. Activation frames provide the appearance of lasting forever, although an implementation is expected to discard them after the scope is exited and any closures that captured the activation frame have been garbage-collected.
The values of some bindings in an activation frame can change. The bindings’ values begin in an uninitialized state. It is an error to read a binding in an uninitialized state.
Activation frame bindings can have attributes which modify their behavior.
Qualified Names
A qualified name consists of a namespace N and an identifier id.
In this specification qualified names are denoted using the notation N::
id. An activation
frame can contain at most one binding for each qualified name.
Namespaces
A namespace parametrizes names. A namespace attribute N may be attached to the declaration of any name
or property p. That namespace then acts like a lock on accesses to p: another piece of code may access
p only by qualifying it with that namespace using N::
p or by executing a use namespace(
N)
directive in a scope surrounding the access of p. Unlike in C++, a namespace is not a scope and does not contain
any names or properties itself; a namespace only modifies the accessibility and visibility of names or properties attached
to activation frames, classes, or objects.
A namespace is a value that can be passed as the first operand of the ::
operator.
public
is the default namespace for declarations; a use namespace(public)
directive is implicit
around the global scope. Each package has a predefined, anonymous internal
namespace and each class has a predefined,
anonymous private
namespace; these provide access control. User-defined namespaces may also be used for more
flexible access control.
ECMAScript 4 Netscape Proposal
Core Language
Lexer
|
Wednesday, June 4, 2003
This section presents an informal overview of the ECMAScript 4 lexer. See the stages and lexical semantics sections in the formal description chapter for the details.
Changes since ECMAScript 3
The ECMAScript 4 lexer behaves in the same way as the ECMAScript 3 lexer except for the following:
- There are additional punctuators and reserved words.
- The lexer recognizes several nonreserved words that have special meanings in some contexts but can be used as identifiers.
- Only semicolon insertion on line breaks is handled by the lexer; the ECMAScript 4 parser allows semicolons to be omitted
before a closing
}
. In addition, the ECMAScript 4 parser allows semicolons to be omitted before theelse
of anif
-else
statement and before thewhile
of ado
-while
statement. - Semicolon insertion on line breaks are both disabled in strict mode.
- [no line break] restrictions in grammar productions are ignored in strict mode.
Source Code
ECMAScript 4 source text consists of a sequence of UTF-16 Unicode version 2.1 or later characters normalized to Unicode Normalized Form C (canonical composition), as described in the Unicode Technical Report #15.
Comments and White Space
Comments and white space behave just like in ECMAScript 3.
Punctuators
The following ECMAScript 3 punctuation tokens are recognized in ECMAScript 4:
!
!=
!==
%
%=
&
&&
&=
(
)
*
*=
+
++
+=
,
-
--
-=
.
/
/=
:
::
;
<
<<
<<=
<=
=
==
===
>
>=
>>
>>=
>>>
>>>=
?
[
]
^
^=
{
|
|=
||
}
~
The following punctuation tokens are new in ECMAScript 4:
&&=
...
^^
^^=
||=
Keywords
The following reserved words are used in ECMAScript 4:
as
break
case
catch
class
const
continue
default
delete
do
else
export
extends
false
finally
for
function
if
import
in
instanceof
is
namespace
new
null
package
private
public
return
super
switch
this
throw
true
try
typeof
use
var
void
while
with
The following reserved words are reserved for future expansion:
abstract
debugger
enum
goto
implements
interface
native
protected
synchronized
throws
transient
volatile
The following words have special meaning in some contexts in ECMAScript 4 but are not reserved and may be used as identifiers:
get
set
Any of the above keywords may be used as an identifier by including a \_
escape anywhere within the identifier,
which strips it of any keyword meanings. The two, four, and eight-digit hexadecimal escapes \xdd
, \udddd
,
and \Udddddddd
may also be used in identifiers; these strip the identifier of any keyword meanings as
well.
Changes from ECMAScript 3
The following words were reserved in ECMAScript 3 but are not reserved in ECMAScript 4:
boolean
byte
char
double
final
float
int
long
short
static
The following words were not reserved in ECMAScript 3 but are reserved in ECMAScript 4:
as
is
namespace
use
Semicolon Insertion
The ECMAScript 4 syntactic grammar explicitly makes semicolons optional in the following situations:
- Before any
}
- Before the
else
of anif
-else
statement - Before the
while
of ado
-while
statement (but not before thewhile
of awhile
statement) - Before the end of the program
Semicolons are optional in these situations even if they would construct empty statements. Strict mode has no effect on semicolon insertion in the above cases.
In addition, sometimes line breaks in the input stream are turned into VirtualSemicolon tokens. Specifically, if the first through the nth tokens of an ECMAScript program form are grammatically valid but the first through the n+1st tokens are not and there is a line break (or a comment including a line break) between the nth tokens and the n+1st tokens, then the parser tries to parse the program again after inserting a VirtualSemicolon token between the nth and the n+1st tokens. This kind of VirtualSemicolon insertion does not occur in strict mode.
See also the semicolon insertion syntax rationale.
Numeric Literals
The syntax for numeric literals is the same as in ECMAScript 3, with the addition of long
, ulong
,
and float
numeric literals. The rules for numeric literals are as follows:
- A numeric literal without a suffix is converted to an IEEE double-precision floating-point number.
- A numeric literal with the suffix
l
orL
is interpreted as along
value and must be a decimal or hexadecimal constant without an exponent or decimal point and be in the range of 0 through 263; furthermore, if the value is exactly 263 then the literal can only be used as the operand of the-
unary negation operator. - A numeric literal with the suffix
ul
,uL
,Ul
, orUL
is interpreted as aulong
value and must be a decimal or hexadecimal constant without an exponent or decimal point and be in the range of 0 through 264–1. - A numeric literal with the suffix
f
orF
is interpreted as afloat
value and must be a decimal constant. Hexadecimalfloat
constants are not permitted because the suffix would be interpreted as a hexadecimal digit.
The suffix must be adjacent to the number with no intervening white space. A number may not be followed by an identifier without intervening white space.
Regular Expression Literals
Regular expression literals begin with a slash (/
) character not immediately followed by another slash (two
slashes start a line comment). Like in ECMAScript 3, regular expression literals are ambiguous with the division (/
)
or division-assignment (/=
) tokens. The lexer treats a /
or /=
as a division or division-assignment
token if either of these tokens would be allowed by the syntactic grammar as the next token; otherwise, the lexer treats a
/
or /=
as starting a regular expression.
This unfortunate dependence of lexical parsing on grammatical parsing is inherited from ECMAScript 3. See the regular expression syntax rationale for a discussion of the issues.
ECMAScript 4 Netscape Proposal
Core Language
Expressions
|
Monday, June 30, 2003
Most of the behavior of expressions is the same as in ECMAScript 3. Differences are highlighted below.
Identifiers
The above keywords are not reserved and may be used in identifiers.
Qualified Identifiers
Just like in ECMAScript Edition 3, an identifier evaluates to an internal data structure called a reference. However, ECMAScript
4 references can be qualified by a qualifier, which, in the general syntax, is a ParenExpression
that evaluates to a namespace. For convenience, if the ParenExpression
consists of a single identifier, the parentheses may be omitted: (a)::m
may be written as a::m
.
The reserved words public
and private
may also be used as qualifiers. public
evaluates
to the public
namespace. internal
(which is not a reserved
word) evaluates to the containing package’s anonymous namespace. private
can only be used inside a class and evaluates to the containing class’s anonymous namespace.
See the name lookup section for more information on the ::
operator.
Primary Expressions
null
true
false
this
public
evaluates to the public
namespace. private
can be used only inside a class and evaluates to that class’s private namespace.
this
may only be used in methods, constructors,
or functions with the prototype
attribute set.
Function Expressions
A FunctionExpression creates and returns an anonymous function.
Object Literals
Array Literals
Super Expressions
super
, which may only be used inside a class C, can be applied to a subexpression that evaluates
to an instance v of C. That subexpression can be either a ParenExpression
or omitted, in which case it defaults to this
.
As specified in the grammar below, the SuperExpression must be embedded
as the left operand of a .
(property lookup) or []
(indexing) operator. super
changes
the behavior of the operator in which it is embedded by limiting its property search to definitions inherited from class C’s
superclass. See property lookup.
Postfix Expressions
++
--
A SimpleQualifiedIdentifier or ExpressionQualifiedIdentifier expression id resolves to the binding of id in the innermost enclosing scope that has a visible binding of id. If a qualifier q is present before the id, then the QualifiedIdentifier expression resolves to the binding of id in the innermost enclosing scope that has a visible binding of id in the namespace q.
Property Operators
The .
operator accepts a QualifiedIdentifier as the
second operand and performs a property lookup.
The grammar allows the []
operator to take multiple arguments. However, all
built-in objects take at most one argument. Implementation-defined host objects may take more arguments.
For most objects other than arrays and some host objects, the expression o[
m]
explicitly coerces m to a qualified name q and
returns the result of o.
q. See property lookup.
An argument list may contain a final argument preceded by ...
. That argument must be an Array
and cannot be null
. The elements of that array become additional arguments to the function, following the arguments
preceding the ...
, if any; the array itself is not passed as an argument. The array must not contain holes.
Unary Operators
delete
PostfixExpressionvoid
UnaryExpressiontypeof
UnaryExpression++
PostfixExpression--
PostfixExpression+
UnaryExpression-
UnaryExpression-
NegatedMinLong~
UnaryExpression!
UnaryExpressionThe typeof
operator returns a string as in ECMAScript 3. There is no way to query the
most specific class of an object — all one can ask is whether an object is a member of a specific class.
Multiplicative Operators
Additive Operators
Bitwise Shift Operators
Relational Operators
The expression a is
b takes an expression that must evaluate
to a type as its second operand b. When a is not null
, the expression a is
b
returns true
if a is a member of type b and false
otherwise; this is equivalent to testing whether a can be stored in a variable of type b without coercion.
When a is null
, a is
b behaves analogously to method
dispatch and returns true
if b is either Object
or Null
and false
otherwise. As a special case, when a is –0.0 and b is sbyte
, byte
, short
,
ushort
, int
, or uint
, a is
b returns true
.
The expression a as
b returns a if a
is a member of type b. Otherwise, if a can be implicitly
coerced to type b, then the result is the result of that implicit coercion. Otherwise, a as
b
returns null
if null
is a member of type b or throws an exception otherwise. In any case
b must evaluate to a type.
The instanceof
operator behaves in the same way as in ECMAScript 3 — a instanceof
b
follows a’s prototype chain.
Equality Operators
Binary Bitwise Operators
Binary Logical Operators
The ^^
operator is a logical exclusive-or operator. It evaluates both operands. If they both convert to true
or both convert to false, then ^^
returns false; otherwise ^^
returns the unconverted value of whichever
argument converted to true.
Conditional Operator
Assignment Operators
Comma Expressions
Type Expressions
Compile-Time Constant Expressions
A compile-time constant expression is an expression that either produces an error or evaluates to a value that can be determined at compile time.
The reason that a compile-time constant expression is not guaranteed to always evaluate successfully at run
time is that global name lookup cannot be guaranteed to succeed. It is possible for a program to import a package P
that defines a global constant P::
A that can be accessed as A and then dynamically
define another top-level variable Q::
A that collides with A. It does not appear
to be practical to restrict compile-time constant expressions to only qualified names to eliminate the possibility of such
collisions.
A compile-time expression can consist of the following:
null
, numeric, boolean, and string constants.- Uses of the operators
+
(unary and binary),-
(unary and binary),~
,!
,*
,/
,%
,<<
,>>
,>>>
,<
,>
,<=
,>=
,is
,as
,in
,instanceof
,==
,!=
,===
,!==
,&
,^
,|
,&&
,^^
,||
,?:
, and,
as long as they are used only on numbers, booleans, strings,null
, orundefined
. - References to compile-time constants, subject to the restrictions below. The references may be qualified, but the qualifiers themselves have to be compile-time constant expressions.
- Lookup of properties of compile-time expressions.
- Calls to pure functions using compile-time expressions as arguments.
A pure function cannot have any read or write side effects or create any objects. A pure function’s result depends only on its arguments. An ECMAScript host embedding may define some pure functions. Currently there is no way for a script to define any such functions, but a future language extension may permit that.
A reference R to a definition D of a compile-time constant is allowed inside a compile-time constant
expression as long as the conditions below are met. If D was imported from another package, then the location of
D is considered to be the location of the import
directive. If D is visible to R
by virtue of an intervening use
directive U, then the conditions below have to be satisfied both with
respect to D and R and with respect to U and R.
- D is visible to R.
- There does not exist any scope between D’s scope and R’s scope that contains any declarations that can shadow D.
- D was not hidden or made inaccessible by a conflict arising from another
use
directive between D and R.
Some compile-time constant expressions only allow references to definitions that are textually prior to the point of the reference. Other compile-time constant expressions allow forward references to later compile-time constant definitions.
Restrictions
ECMAScript 4 imposes the following restrictions:
- TypeExpressions must be compile-time constant expressions that evaluate to types. Except for the TypeExpression specifying the superclass of a class, these expressions can contain forward references.
- Attributes must be compile-time constant expressions that evaluate to attributes. These expressions cannot contain forward references when used as attributes of statements or directives.
const
definitions defining constants that are used in compile-time constant expressions must have initializers that are themselves compile-time constant expressions. These initializers cannot contain forward references.- Default parameter values must be compile-time constant expressions. These expressions can contain forward references.
- Initializers for instance members must be compile-time constant expressions. These expressions can contain forward references.
- Each live
import
,class
, andnamespace
directive must dominate the end of the program or package. This restriction limits these statements to the top level of the program, a top-level block, or a top-level conditional whose condition is known at compile time. - Each live declaration of a class member must dominate the end of the
class
definition.
A statement A dominates statement B if any of the following conditions are met:
- A and B are the same statement.
- A and B are in the same block, with A before B and no
case
ordefault
labels between them. - Statement B is enclosed inside statement C and A dominates C.
- Statement A is enclosed inside a block C, C is not prefixed by an attribute that evaluates
to
false
, and C dominates B.
Note that the above definition is conservative. If statement A dominates statement B, then it is guaranteed that, if B is executed then A must have been executed earlier; however, there may be some other statements A' that also are guaranteed to have been executed before B but which do not dominate B by the above definition.
A statement A is dead if any of the following conditions are met:
- A is prefixed by an attribute that evaluates to
false
. - There exists a
break
,continue
,return
, orthrow
statement B such that statements A and B are in the same block with B before A and nocase
ordefault
labels between them. - A is enclosed inside statement B and B is dead.
Note that the above definition is conservative. If a statement is dead, then it is guaranteed that it cannot be executed; however, there may be statements that cannot be executed that are not dead by the above definition.
A statement is live if it is not dead.
ECMAScript 4 Netscape Proposal
Core Language
Statements
|
Wednesday, June 4, 2003
Statements
Most of the behavior of statements is the same as in ECMAScript 3. Differences are highlighted below.
;
;
A Substatement is a statement directly contained by one of the compound
statements label:
, if
, switch
, while
, do while
,
for
, or with
(but not a block). A substatement cannot be a directive except
that, in non-strict mode only, it can be a var
definition without attributes or types.
A substatement can also consist of one or more attributes applied to a group of substatements
enclosed in braces. The attributes must evaluate to either true
or false
. The braces do not form
a scope in this case.
The Semicolon productions allow both grammatical and line-break semicolon insertion.
Empty Statement
Expression Statement
Super Statement
The super
statement calls the superclass’s constructor. It can
only be used inside a class’s constructor.
Block Statement
A block groups statements and forms a scope.
Labeled Statements
If Statement
The semicolon is optional before the else
.
Switch Statement
Do-While Statement
The semicolon is optional before the closing while
.
While Statement
For Statements
A for
statement forms a scope. Any definitions in it (including the ForInitializer
and ForInBinding) are visible inside the for
statement and its
substatement, but not outside the for
statement. However, a var
definition inside a for
statement may be hoisted to the nearest enclosing regional scope.
With Statement
Continue and Break Statements
Return Statement
A return
statement can only be used inside a function or constructor.
The return
statement cannot have an expression if used inside a constructor
or a setter.
Throw Statement
Try Statement
Each CatchClause forms a scope. The Parameter, if any, is defined as a local variable visible only within the CatchClause.
The Blocks following try
and finally
are also scopes
like other Block statements.
Directives
Attributes can be applied to a group of directives
by following them by a {
, the directives, and a }
. The attributes apply to all of the enclosed directives.
The attribute true
is ignored. The attribute false
causes all of the enclosed directives to be omitted.
When used this way, the braces do not form a block or a scope.
Annotated groups are useful to define several items without having to repeat attributes for each one. For example,
class foo { var z:Integer; public var a; private var b; private function f() {} private function g(x:Integer):Boolean {} }
is equivalent to:
class foo { var z:Integer; public var a; private { var b; function f() {} function g(x:Integer):Boolean {} } }
Programs
ECMAScript 4 Netscape Proposal
Core Language
Definitions
|
Thursday, May 22, 2003
Introduction
Definitions are directives that introduce new constants, variables, functions, classes, namespaces, and packages. All definitions except those of packages can be preceded by zero or more attributes. In non-strict mode there must not be any line breaks between the attributes or after the last attribute.
Attributes
An attribute is an expression (usually just an identifier) that modifies a definition’s meaning. Attributes can specify
a definition’s scope, namespace, semantics, and other hints. An ECMAScript program may also define and subsequently use its
own attributes. Attributes can be qualified identifiers (as long as
they don’t start with a (
) and dotted and function call expressions, but they must be compile-time
constants.
The table below summarizes the predefined attributes.
Category | Attributes | Behavior |
---|---|---|
Namespace | private internal public |
Makes the definition visible only in the enclosing class’s private namespace (private ),
the enclosing package’s private namespace (internal ), or anywhere (public ). |
Visibility Modifier | enumerable |
This definition can be seen using a for -in statement. |
explicit |
This top-level definition is not shared via an import directive. |
|
Class Modifier | final |
This class cannot be subclassed. Can be used only on classes. |
dynamic |
Direct instances of this class can contain dynamic properties. Can be used only on classes. | |
Member Modifier | static virtual final |
The definition creates a global member (static ) or instance member (virtual or
final ) of the enclosing class. If defining an instance member, the definition can (virtual )
or cannot (final ) be overridden in subclasses. Can be used only on class members. |
override override(true) override(false) override(undefined) |
Assertion that the definition overrides (override or override(true) ), may override
(override(undefined) ), or does not override (override(false) ) a member of a superclass. Can
be used only on class members. Controls errors only. |
|
Conditional | true false |
The definition or directive is (true ) or is not (false ) processed. |
Miscellaneous | prototype |
Allows a function to access this and be used as a prototype-based constructor. |
unused |
Assertion that the definition is not used. |
Multiple conflicting attributes cannot be used in the same definition, so virtual final private
is an error. The attributes true
and false
do not conflict. Specifying an attribute more than once
has the same effect as specifying it once.
Namespace Attributes
Namespace attributes control the definition’s visibility. User-defined attributes provide a finer grain of visibility control.
Every package P has a predefined, anonymous namespace PackageInternalP. That
namespace is attached to all definitions with the internal
attribute in that package. Package P’s
scope includes an implicit use namespace(
PackageInternalP)
definition around the package that grants access to these definitions from within the package only.
Every class C has a predefined, anonymous namespace ClassInternalC. That namespace is
attached to all definitions with the private
attribute in that class. Class C’s scope includes
an implicit use namespace(
ClassInternalC)
definition around the class
that grants access to these definitions from within that class only. private
can only be used inside a class.
Namespace attributes, including user-defined namespaces, are additive; if several are given for a definition, then that
definition is put into each of the designated namespaces. Thus, a single definition may define a name in two or more namespaces
namespace1 and namespace2 by listing the namespaces
as attributes: namespace1 namespace2 var x
.
Such multiple definitions are aliases of each other; there is only one storage location x
.
A definition of a name id is always put into the namespaces explicitly specified in the definition’s attributes. In addition, the definition may be placed in additional namespaces according to the rules below:
- If the definition explicitly specifies one or more namespaces N:
- If the definition defines an instance member in a class C and one of C’s ancestors contains an instance member M with name id and at least one of the namespaces in N, then the definition will override M and be placed in all of M’s namespaces. It is an error if N contains another namespace that is not in M’s namespaces.
- Otherwise, the definition is put into each of the namespaces in N; however, to avoid confusion the
override(false)
oroverride(undefined)
attribute is required on the definition if an inherited instance member with the name id is visible at the point of the definition. This restriction prevents one from accidentally defining aprivate
instance member with the same name as apublic
instance member in a superclass (by the rules of member lookup, such aprivate
member would be shadowed by the existingpublic
member for all unqualified accesses).
- If the definition does not explicitly specify any namespaces:
- If the definition is in a class C and one of C’s ancestors contains a visible member M with name id, then the definition will override M and be placed in all of M’s namespaces. It is an error to attempt to override two different members with one definition.
- Otherwise, the definition is put into the
public
namespace.
Visibility Modifier Attributes
Visibility modifier attributes control the definition’s visibility in several special cases.
enumerable
An enumerable
definition can be seen by the for
-in
iteration statement. A non-enumerable
definition cannot be seen by such a statement. enumerable
only applies to public
definitions.
The default for dynamic properties and class
properties is enumerable
. The default for instance properties
is non-enumerable
. There is no way to make a user-defined dynamic or class property non-enumerable
.
explicit
explicit
is used to add definitions to a package P without having them conflict with definitions
in other packages that import package P. explicit
prevents the definition from being accessed as a
top-level variable when a package is imported. The definition can still be accessed as a property
of an object. For example,
package My.P1 {
const c1 = 5;
explicit const c2 = 7;
}
package My.P2 {
import P = My.P1; //
Imports My.P1
without qualification
c1; //
OK; evaluates to 5
c2; //
Error: c2
not defined because explicit
variables are not shared
P.c2; //
OK: explicit
properties are visible
}
Class Modifier Attributes
Class modifier attributes apply to the definition of a class C itself. They may only be used on definitions of classes.
final
If a class C is defined using the final
attribute, then any attempt to define a subclass of C
signals an error.
Note that final
is also a member modifier — when used on a class member,
final
makes that member nonoverridable. If final
is used on a class member that is itself a class,
then it acts like a class modifier instead of a member modifier — it prevents the inner class from being subclassed.
dynamic
Direct instances of a dynamic
class C can contain dynamic
properties. Other instances cannot contain dynamic properties. A class is dynamic
if it has the dynamic
attribute or it has a dynamic
ancestor other than Object
.
Member Modifier Attributes
Member modifier attributes modify a class member definition’s semantics with respect to a class hierarchy. They may only be used on a definition of a member M of a class C. They cannot be used on definitions that, for example, create local variables inside a function.
static
, virtual
, and final
The static
attribute makes M be a global member of C.
The virtual
and final
attributes make M be an instance member of C.
The final
attribute prevents subclasses from defining their own members with the name M (unless
they can’t see this M, in which case they can define an independent M). virtual
allows
subclasses to override M.
The default setting for the definition of a member M of a class named C is:
Default Attribute | Kind of Member M |
---|---|
none — the function is treated specially as a class constructor | function C |
virtual |
function F where the name F differs from C |
final |
var and const definitions |
none — static attribute must be specified explicitly |
class and namespace definitions |
These attributes may not be used on an export
definition, since export
reuses the original member’s
setting.
Note that final
is also a class modifier — when used on a class member
M, final
prevents M from being subclassed rather than making M be a nonoverridable
instance member of C.
override
The override
attribute reports errors; it has no other effect on the behavior of the program. The override
attribute can only be used on definitions in a class and describes the programmer’s intent to either override or not
override a member from a superclass. If the actual behavior, as defined by the namespace
defaulting and overriding rules, differs, then an error is signaled.
The table below describes the behavior of when a definition of a member M with name id is placed in a class C:
Override attribute given | ||||
---|---|---|---|---|
None | override oroverride(true) |
override(undefined) |
override(false) |
|
M overrides a member in some superclass according to the namespace defaulting and overriding rules | Error | OK | OK | Error |
M does not override anything but there exists an ancestor of C with a member with name id visible at the point of definition of M | Error | Error | OK | OK |
M does not override anything and no ancestor of C has a member with name id visible at the point of definition of M | OK | Error | OK | OK |
The middle case arises for example when an ancestor of a class C defines a public
member named
X
and class C attempts to define a private
member named X
.
Conditional Attributes
An attribute whose value is true
causes the definition or directive to be evaluated normally. An attribute
whose value is false
causes the definition or directive to be skipped; the remaining attributes and the body
of the definition or directive are not evaluated. These are useful for turning definitions on and off based on configuration
settings, such as:
const debug = true; const nondebug = !debug; debug var nCalls = 0; debug function checkConsistency() {...}
Miscellaneous Attributes
prototype
The prototype
attribute can only be used on a function. A function with this attribute treats
this
in the same manner as ECMAScript 3 and defines its own prototype-based class as in ECMAScript 3. By
default, the prototype
attribute is set on any unchecked function. It
can be set explicitly on other functions as long as they are not getters, setters, or constructors.
unused
The unused
attribute is a hint that the definition is not referenced anywhere. Referencing the definition
will generate an error.
User-Defined Attributes
A user-defined attribute may be defined using a const
definition or other definitions that define constants.
All attributes must be compile-time constants. For example:
const ipriv = internal static; explicit namespace Version1; explicit namespace Version2; internal const Version1and2 = Version1 Version2; class C { ipriv var x; // Same as internal static var x; Version1and2 var simple; // Same as Version1 Version2 var simple; Version2 var complicated; ipriv const a:Array = new Array(10); private var i; for (i = 0; i != 10; i++) a[i] = i; }
Definition Scope
A definition applies to the innermost enclosing scope except when it is hoisted. If that scope is a class, the definition appears as a member of that class. If that scope is a package, the definition appears as a member of that package.
Scope Hoisting
For compatibility with ECMAScript 3, in some cases a definition’s scope is hoisted to the innermost regional scope R instead of the innermost scope S. This happens only when all of the conditions below are met:
- The definition is a
var
definition. - The definition does not specify a type.
- The definition has no attributes other than
true
. - The regional scope R is not a class.
- Strict mode is not in effect.
When a definition of n is hosted, the effect is as though n were declared (but not initialized) at the top of the regional scope R.
Definitions not meeting the above criteria are not hoisted. However, an inner non-hoisted definition of name n in scope S within regional scope R prevents n from being referenced or defined in any scope within R but outside S; see definition conflicts.
Extent
A definition extends an activation frame with one or more bindings of qualified
names to values. The bindings are generally visible from the activation frame’s scope.
However, a definition may be invisible or partially invisible inside its scope either because it is shadowed by a more local
definition or it uses a namespace that is not use
d. The name lookup rules
specify the detailed behavior of accessing activation frame bindings.
Each definition or declaration D of a name n applies to some scope S using the rules above. Any of S’s activation frames will contain a binding for n as soon as S is entered. That binding starts in the following state:
- In non-strict mode, a
var
n definition D without a type or attributes is initialized to the valueundefined
upon entry into S. - In non-strict mode, a
function
n definition D without types or attributes is initialized to its closure upon entry into S instead of at the time D is executed. - A
class
definition D of a name n in scope S binds n to an opaque value V of typeType
upon entry into S. V may be used as a type to declare variables, but all other operations are prohibited. V becomes the actual class object at the time the definition is executed, after which point instances of V may be created and subclasses may be derived from V. - All other definitions produce bindings in the uninitialized state upon entry into the scope S. The bindings are initialized at the time the definition is executed.
Accessing an activation frame binding in the uninitialized state is an error. If this happens, implementations are encouraged to throw an exception, but may return a value V if they can prove that the definition would assign the value V to the binding.
Definition Conflicts
In general, it is not legal to rebind the same name in the same namespace within an activation frame A. There are a couple exceptions:
- Multiple
var
definitions (which may be hoisted) are allowed in a regional scope as long as all such definitions have no type and no attributes and strict mode is not in effect. - A getter and a setter with the same name may be defined independently.
In addition, if a name n is defined in a scope S inside regional scope R, then it is not permitted to access a definition of n made outside of R from anywhere inside R. Also, two nested scopes S1 and S2 located inside the same regional scope R cannot both define n (S1, S2, and R may be the same scope). In either of these situations, n may be hoisted; if hoisting is not allowed, an error occurs. For example,
const b:Integer = 1; function f(c:Boolean):Integer { const a = b; // Error: b is defined inside the local scope below, which prevents accesses to global b // from anywhere inside the regional scope if (c) { const b:Integer = a + 10; // OK to hide the global b from here. return b; } return a; } function g(c:Boolean):Integer { const b = 3; // OK to hide the global b from here. if (c) { const b:Integer = 10; // Error: can’t redefine b inside the same regional scope. return b; } return b; } function h(c:Boolean):Integer { if (c) { const b:Integer = 10; // OK to hide the global b from here. return b; } else { const b:Integer = 42; // OK: Two independent local definitions of b. return b; } }
To help catch accidental redefinitions, binding a qualified name q::
n
in activation frame A when there is already a binding r::
n in A causes
an error if both namespaces q and r are use
d at the point of the definition of q::
n
and the bindings are not aliases of each other. This prevents the same name from being used for both public
and
private
variables in the same class. Two bindings sharing the same name but with different namespaces may still
be introduced into an activation frame, but only by code that does not use
one or both of the namespaces.
Examples
In the example below the comments indicate the scope and namespace of each definition:
var a0; //
Public global variable
internal const a1 = true;//
Package-visible global variable
private var a2; //
Error: private
can only be used inside a class
public var a3 = b1; //
Public global variable
if (a1) {
var b0; //
Local to this block
var b1; //
Hoisted to the global level because of the reference to b1
in the definition of a3
}
if (a1) {
var b0; //
Local to this block
}
public function F() { //
Public global function
var c0; //
Local to this function
internal var c1; //
Local to this function
(may generate a style warning)
public var c2; //
Local to this function
(may generate a style warning)
}
class C { //
Public global class
var e0; //
Public class instance variable
private var e1; //
Class-visible class instance variable
internal var e2; //
Package-visible class instance variable
public var e3; //
Public class instance variable
static var e4; //
Public class-global variable
private static var e5; //
Class-visible class-global variable
internal static var e6;//
Package-visible class-global variable
public static var e7; //
Public class-global variable
if (a1) {
var f0; //
Local to this block
private var f1; //
Local to this block
(may generate a style warning)
}
public function I() {} //
Public class method
}
Discussion
Should we have a protected
Attribute? It has been omitted for now to keep
the language simple, but there does not appear to be any fundamental reason why it could not be supported. If we do support
it, it might be better to choose the C++ protected
concept (visible only in class and subclasses); the Java
protected
concept (visible in class, subclasses, and the original class’s package) could be represented as
internal
protected
.
ECMAScript 4 Netscape Proposal
Core Language
Variables
|
Wednesday, June 4, 2003
Variable Definitions
A SimpleVariableDefinition represents the subset of VariableDefinition expansions that may be used when the variable definition is used as a Substatement instead of a Directive in non-strict mode. In strict mode variable definitions may not be used as substatements.
A variable defined with var
can be modified, while one defined with const
is read-only after
its value is set. Identifier is the name of the variable and
TypeExpression is its type. Identifier
can be any non-reserved identifier. TypeExpression must
be a compile-time expression that evaluates to a type t other
than Never
. TypeExpression
may contain forward references to compile-time constants defined later in the program.
If provided, AssignmentExpression gives the variable’s
initial value v. If AssignmentExpression
is not provided in a var
definition, then undefined
is assumed. If the variable being defined is
not an instance member of a class, then the AssignmentExpression
is evaluated at the time the variable definition is evaluated. The resulting value is then implicitly
coerced to the variable’s type t and stored in the variable. If the variable is defined using var
,
any values subsequently assigned to the variable are also implicitly coerced
to type t at the time of each such assignment. If the variable is an instance member of a class, then the AssignmentExpression
is evaluated each time an instance of the class is constructed.
Reading or writing a variable before its definition is evaluated signals an error except when the variable definition has
no attributes and no type and strict mode is not in effect; in that case, the variable
may be read or written prior to its definition being evaluated and its initial value is undefined
.
Multiple variables separated by commas can be defined in the same VariableDefinition. The values of earlier variables are available in the AssignmentExpressions of later variables.
If omitted, TypeExpression defaults to type Object
.
Thus, the definition
var a, b=3, c:Integer=7, d, e:Integer, f:Number=c;
is equivalent to:
var a:Object = undefined;
var b:Object = 3;
var c:Integer = 7;
var d:Object = undefined;
var e:Integer = undefined; //
Implicitly coerced to NaN
var f:Number = c; //
7
The compiler might issue a warning for a VariableDefinition that
contains an untyped variable prior to a typed variable to remind programmers that the type of d
is Object
rather than Integer
in var d, e:Integer
.
Constant Definitions
const
means that assignments to Identifier are
not allowed after the constant has been set. Once defined, a constant cannot be redefined in the same scope, even if the redefinition
would be to the same value. If the VariableBinding in a const
declaration does not contain an initializer, then the constant may be written once after it is defined. Any attempt to read
the constant prior to writing its value will result in an error. For example:
function f(x) {return x+c}
f(3); //
Error: c
’s value is not defined
const c = 5;
f(3); //
Returns 8
const c = 5; //
Error: redefining c
Just like any other definition, a constant may be rebound after leaving its scope. For example, the following is legal;
j
is local to the block, so a new j
binding is created each time through the loop:
var k = 0; for (var i = 0; i < 10; i++) { const j = i; k += j; }
A const
definition defines a compile-time constant if
it has an initializer and that initializer is a compile-time constant expression
which may contain forward references to other compile-time constants.
In order for the compiler to be able to distinguish const
definitions that define run-time constants from
ones that define compile-time constants, it must be able to resolve each variable referenced in a const
initializer
to a scope. Because of this, a const
initializer may only refer to variables
declared at compile time; referring to a dynamically created variable not declared at compile time results in a compile-time
error. It is also an error to attempt to create a dynamic variable that changes the resolution of a variable in a const
initializer. For example:
const a = 5; //
OK: a
is
a compile-time constant with the value 5
OK:
const b = a + c; // b
is a compile-time constant with the
value 7
OK:
const c = 2; // c
is a compile-time constant
with the value 2
OK:
const d = e; // d
is a run-time constant that
starts as undefined
Error:
var e = 2.718281828459045;
f = "Run time";
const g = f; // f
is not declared at compile
time
OK:
const h = uint; // h
is a compile time constant that holds the
type uint
OK: creates the global property
this.ulong = 15; // ulong
that shadows the system
ulong
type
Error: can’t create the global property
this.uint = 15; // uint
because it would
change
the resolution of
// uint
in the definition of h
Inside a class, const
preceded by final
defines an instance
member. Preceding it with virtual
would also define an instance member, but is only useful if one wants subclasses
to be able to override the constant. Precede it with static
to define a global
member. The default is final
.
If const
is declaring an instance member m of a class, then the initializer is evaluated each time
an instance of the class is constructed. If absent, then the member’s property may be written exactly once,
cannot be re-written after it has been written, and must be written before it can be read. For example:
class C {
static const red = 0xFF0000; //
Defines static constant C.red
static const green = 0x00FF00; //
Defines static constant C.green
static const blue = 0x0000FF; //
Defines static constant C.blue
static const infrared; //
Defines uninitialized
static constant C.infrared
const myColor; //
Defines instance constant C::myColor
with value set by the constructor
final const yourColor; //
Defines instance constant C::yourColor
with value set by the constructor
const ourColor = 0; //
Defines instance constant C::ourColor
that is always zero (not very useful)
virtual const theirColor = 0; //
Defines instance constant C::theirColor
that can be overridden by subclasses
function C(x:int) {
myColor = x; //
Sets this instance’s myColor
ourColor = x; //
Error: ourColor
is already set to 0
myColor = x; //
Error: myColor
can be set only once
var a = [x, this];
a[1].yourColor = x; //
Sets this instance’s yourColor
}
}
Getters and Setters
A definition var
x:
t =
v internally
creates a hidden variable and defines a getter and a setter to access that variable:
- Evaluate t, which should evaluate to a type.
- Create an anonymous variable .
- Implicitly coerce
undefined
to type t (such a coercion must exist for every type) and assign the result to . - Define a getter
function get
x():
t{return
}
. - Define a setter
function set
x(a:
t):Void {
= a}
. - Evaluate v, implicitly coerce it to type t, and assign the result to .
A definition const
x:
t =
v internally
creates a hidden variable and defines a getter to access that variable:
- Evaluate t, which should evaluate to a type.
- Create an anonymous variable .
- Define a getter
function get
x():
t{return
}
. - Define a setter
function set
x(a:
t):Never {throw
ConstWriteError}
. - Evaluate v, implicitly coerce it to type t, and assign the result to .
This relationship between a variable and its getter and setter is normally transparent but can be exploited occasionally.
For instance, a variable can be declared that is private
for writing but public
for reading:
private var name:String; public export get name;
A subclass may override a variable’s getter or setter. To do this, the original variable has to be declared non-final
because variables are final
by default:
class C { virtual var x:Integer; var y:Integer; } class D extends C { override function set x(a:Integer):Integer {y = a*2} } var c = new C; c.x = 5; c.x; // Returns 5 c.y; // Returns NaN (the default value for an Integer variable) var d = new D; d.x = 5; d.x; // Returns NaN d.y; // Returns 10
ECMAScript 4 Netscape Proposal
Core Language
Functions
|
Monday, April 28, 2003
Syntax
Like other definitions, a function definition may be preceded by one or more attributes,
which affect the function’s scope, namespace, and semantics. Every function (except a getter or a setter)
is also a value and has type Function
.
Unless a function f is defined with the prototype
attribute
(either explicitly or by default because f is unchecked), that function does not
define a class, f’s name cannot be used in a new
expression, and f cannot refer to
this
unless f is an instance method or constructor of a class.
A FunctionDefinition can specify a function, getter
(if its name is preceded by get
), or setter (if its name is preceded by set
).
Parameters give the names and the types of the function’s parameters. Result gives the type of the function’s result. The Block contains the function body and is evaluated only when the function is called.
Parameter Declarations
A function may take zero or more parameters and an optional rest parameter. Optional parameters may follow but not precede required parameters (this condition is not in the grammar but is checked by the formal semantics).
Individual parameters have the forms:
The TypeExpression gives the parameter’s type and defaults
to type Object
. The TypeExpression must evaluate
to a type other than Never
.
If a Parameter is followed by a =
, then that parameter is optional.
If a function call does not provide an argument for an optional parameter, then that parameter is set to the value of its
AssignmentExpression, implicitly
coerced to the parameter’s type if necessary. The AssignmentExpression
must be a compile-time constant.
If a Parameter is prefixed with const
, then the parameter is
declared using const
instead of var
. The effect is that the parameter’s value cannot be changed
from within the function. Without the const
, the function can change the parameter’s value, which, however, has
no effect on the argument.
If a function call does not provide an argument for a required Parameter,
then an error occurs unless the function is unchecked, in which case the parameter gets the value
undefined
, implicitly coerced to the parameter’s
type if necessary.
The parameters’ Identifiers are local variables with types given by the corresponding TypeExpressions inside the function’s Block. Code in the Block may read and write these variables. Arguments are passed by value, so writes to these variables do not affect the passed arguments’ values in the caller.
Attempting to define a function with two different parameters with the same name is an error.
Rest Parameter
If the ...
is present, the function accepts arguments not matched by any of the other listed parameters. If
a parameter is given after the ...
, then that parameter’s identifier is bound to an array of all remaining
arguments. That identifier is declared as a local var
or const
using the type Array
.
The remaining arguments are stored as elements of the rest array with numeric indices starting from 0.
Each unchecked function also has a predefined const
arguments
local variable which holds an array (of type Array
) of all arguments passed to this function.
Result Type
The function’s result type is TypeExpression, which defaults
to type Object
if not given. The TypeExpression
must evaluate to a type.
If the function does not return a useful value, it’s good practice to set TypeExpression
to Void
to document this fact. If the function cannot return at all (it either always falls into an infinite
loop or throws an exception), then it’s good practice to set TypeExpression
to Never
to document this fact; this also lets the compiler know that code after a call to this function is unreachable,
which can help cut down on spurious warnings.
Evaluation Order
A function’s parameter and result TypeExpressions are evaluated at the time the function definition or declaration is executed. These types are then saved for use in argument and result coercions at the time the function is called.
The static and dynamic extent of a parameter includes all subsequent parameters’ and the result type’s TypeExpressions and AssignmentExpressions. However, the case where a subsequent parameter’s or the result type’s TypeExpression or AssignmentExpression references a prior parameter is reserved for a future language extension. For now, an implementation should raise an error in this case:
const t = Integer; function choice(a:Boolean, t:Type, c:t, d:t):t { return a ? c : d; }
This definition of choice
should (for now) be an error and not:
function choice(a:Boolean, t:Type, c:Integer, d:Integer):Integer { return a ? c : d; }
The intent is that a future language extension might make the first definition of choice
legal and permit
calls to it like choice(true,String,"Be","Not Be")
, which would return "Be"
.
When a function is called, the following list indicates the order of evaluation of the various expressions in a FunctionDefinition. These steps are taken only after all of the argument names and values have been evaluated.
- If the function is unchecked, bind the
arguments
local variable to an array of all arguments and their names. - Get the saved type t that was the result of evaluating the first parameter’s TypeExpression at the time the function was defined.
- If the first parameter is required and no argument has been supplied for it, then raise an error unless the function
is unchecked, in which case let
undefined
be the first parameter’s value. - If the first parameter is optional and there is an argument remaining, use the value of the argument. If there are no remaining arguments, then evaluate the first parameter’s AssignmentExpression and let it be the first parameter’s value.
- Implicitly coerce the argument (or default) value to type t and bind the parameter’s Identifier to the result.
- Repeat steps 2-5 for each additional parameter.
- If there is a RestParameter with an Identifier, bind that Identifier to an array of the remaining arguments using indices starting from 0.
- If there is no RestParameter and any arguments remain, raise an error unless the function is unchecked.
- Evaluate the body.
- Get the saved type r that was the result of evaluating the result TypeExpression at the time the function was defined.
- Implicitly coerce the result to type r and return it.
Getters and Setters
If a FunctionName contains the keyword get
or set
,
then the defined function is a getter or a setter.
A getter must not take any parameters. Unlike an ordinary function, a getter is invoked by merely
mentioning its name without an Arguments list in any expression
except as the destination of an assignment. For example, the following code returns the string “<2,3,1>
”:
var x:Integer = 0; function get serialNumber():Integer {return ++x} var y = serialNumber; return "<" + serialNumber + "," + serialNumber + "," + y + ">";
A getter must either evaluate a return
statement or throw an exception; it cannot fall off the end without
returning a value.
A setter must take exactly one required parameter. Unlike an ordinary function, a setter is invoked
by merely mentioning its name (without an Arguments list)
on the left side of an assignment or as the target of a mutator such as ++
or --
. The setter should
not return a value and should be declared as returning type Void
or Never
. The result of an assignment
expression is the argument passed to the setter. For example, the following code returns the string “<1,2,42,43>
”:
var x:Integer = 0; function get serialNumber():Integer {return ++x} function set serialNumber(n:Integer):Void {x=n} var s = "<" + serialNumber + "," + serialNumber; s += "," + (serialNumber = 42); return s + "," + serialNumber + ">";
A setter cannot return a value; it may invoke a return
statement as long as that statement does not supply
an expression.
A setter can have the same name as a getter in the same lexical scope. A getter or setter cannot be extracted from its variable, so the notion of the type of a getter or setter is vacuous; a getter or setter can only be called.
Contrast the following:
var x:Integer = 0; function f():Integer {return ++x} function g():Function {return f} function get h():Function {return f} f; // Evaluates to function f g; // Evaluates to function g h; // Evaluates to function f (not h) f(); // Evaluates to 1 g(); // Evaluates to function f h(); // Evaluates to 2 g()(); // Evaluates to 3
See also the discussion of getter and setter syntax.
Unchecked Functions
An unchecked function relaxes argument checking. Unchecked function definitions are provided for compatibility with ECMAScript 3.
A function
definition is unchecked if all of the following are true:
- strict mode is disabled at the point of the function definition;
- the function is not a class member;
- the function has no optional or rest parameters;
- none of the function’s parameters has a declared type;
- the function does not have a declared return type;
- the function is not a getter or setter.
An unchecked function also has the prototype
attribute set by default.
ECMAScript 4 Netscape Proposal
Core Language
Classes
|
Monday, April 28, 2003
Class Definitions
Classes are defined using the class
keyword.
Like other definitions, a class definition may be preceded by one or more attributes,
which affect the class’s scope, namespace, and semantics. Every class is also a value and has type Type
.
A class
definition may only be located at a scope that allows class definitions,
defined as follows:
- The global scope allows class definitions
- A package scope allows class definitions
- A class scope allows class definitions
- If a scope X allows class definitions and a block B is directly inside scope X, then B’s scope also allows class definitions
According to these rules, a class may not be defined inside a function or a compound statement other than a block. If a
class B is defined as a member of another class A, then B must be declared static
.
Superclasses
A class may have a superclass specified by its extends
clause. If omitted, the superclass defaults to Object
.
The superclass TypeExpression must be a compile-time
constant expression without forward references.
A class is a subtype of its superclass.
Body
When a ClassDefinition is evaluated, the following steps take place:
- Create a new type t and bind the class’s QualifiedIdentifier to the constant t.
- The TypeExpression, if any, in the
extends
clause is evaluated, and t is made a subtype of its superclass. Anystatic
members of t’s superclass are also defined as properties of the object t. - A new, anonymous namespace for holding the class’s
private
members is constructed anduse
d for the lexical extent of the Block. - Block is evaluated using a new activation frame initialized
with alias bindings for all most derived global members of the superclass. Any
static
and constructor members defined for Block’s activation frame are added as properties of the object t as they are being defined; these may hidestatic
members inherited from superclasses. - If Block is evaluated successfully (without throwing out an exception), all instance members defined for Block’s top-level scope (along with those inherited from superclasses) are collected to make a template for creating instances of type t.
A ClassDefinition’s Block is evaluated just like any other Block, so it can contain expressions, statements, loops, etc. Such statements that do not contain declarations do not contribute members to the class being declared, but they are evaluated when the class is declared.
Instance Members
A class C’s instance member id becomes a separate property of each instance of C.
If c is an instance of C, then such a property can be accessed using the expression c.
id.
Instance members are inherited from the superclass.
If present, an initializer for a var
or const
instance member must be a compile-time
constant expression.
Methods
A function
instance member is called a method. A method may use this
to refer to the object on
which it was called. The value of this
will always be an instance of the class or one of its subclasses. A method
may not change the value of this
.
A method is not in itself a value and has no type. There is no way to extract an undispatched method from a class. The
.
operator produces a function (more specifically, a closure) that is already dispatched and has this
bound to the left operand of the .
operator.
A method is called by combining the .
operator with a function call. For example:
class C {
returns
var x:Integer = 3;
function m() {return x}
function n(x) {return x+4}
}
var c = new C;
c.m(); //3
returns
c.n(7); //11
var f:Function = c.m; //f
is a zero-argument function with this
bound to c
returns
f(); //3
returns
c.x = 8;
f(); //8
Overriding
A class C may override a method m defined in its superclass s. To do this, C
should define a method m' with the same name as m and use the override
attribute in the definition of m'. Overriding a method without using the override
attribute or
using the override
attribute when not overriding a method results in an error intended to catch misspelled method
names.
The overriding method m' must have the same set of parameters that the overridden method m has.
Let p be any parameter. If m' does not specify a type for p, it inherits the type of p from m. If m' does specify a type for p, it must be the same type as that for p in m. If p is optional in m, then it must also be optional in m' with the same parameter name; however, the default value may differ.
If omitted, the return type of m' defaults to the return type of m. If supplied, the return type of m' must be the same as the return type of m.
A final
method cannot be overridden (or further overridden) in the subclasses in which it is visible.
The overridden method m' is put in the same namespaces as method m.
Method m' may call method m using the super operator: either
super.
m(
args)
or super this.
m(
args)
.
A method may only override another method. An instance variable may only override another instance variable. A getter may override a getter or an instance variable. A setter may override a setter or an instance variable.
Static Members
A class C’s static
member id becomes a property of the class object C.
This member can be accessed using the expression C.
id. static
members are
inherited from the superclass.
Inherited static
variables have only one global value, not one value per subclass. For example, if class C
has a static variable v and class D inherits from C, then v can be read or written
either as C.
v or as D.
v; it’s the same variable rather
than two separate variables.
Each instance member o named n of class C (other than members that are setters without a corresponding getter) also causes a global member g named n to be defined in C. That global member is currently inaccessible and reserved for a future language extension.
Constructors
A constructor is a function that creates a new instance of a class C. A constructor is defined as a method with
the name C without any of the attributes static
, virtual
, or final
. A constructor is
invoked using the expression new
C or new
C(
args)
.
A constructor can refer to its class’s instance variables via this
. If a class C inherits from
class B, then when B’s constructor is called while creating an instance of C, B’s
constructor will be able to call virtual methods of class C on the partially constructed instance. Likewise, B’s
constructor could store this
into a global variable v and some other function could call a method
of C on the partially constructed object v. Class C’s methods can be assured that they are
only called on fully initialized instances of C only if neither C nor any of its ancestors contains
a constructor that exhibits either of the behaviors above.
A constructor may invoke a return
statement as long as that statement does not supply a value; a constructor
cannot return a value. The newly created object is returned automatically. A constructor’s return type must be omitted.
A constructor always returns a new instance.
A class named C must not define a static
member with the name C in any namespace; such
usage is reserved for a future extension.
If a class C does not define a constructor or a static function with the name C, a default constructor is automatically defined; that constructor takes the arguments that C’s superclass’s constructor takes, calls that superconstructor with those arguments, and initializes C’s new instance members to their default values.
Calling a Superconstructor
Let C be a class and B its superclass. C’s constructor must call B’s
constructor before it accesses this
or super
or before it returns. The call can be either explicit
or implicit; if C’s constructor does not contain any calls to B’s constructor, then a call
to B’s constructor with no arguments is automatically inserted as the first statement of C’s
constructor. C’s constructor does not have to call B’s constructor when it exits by throwing
an exception. C’s constructor may not call B’s constructor again after it already called
it.
C’s constructor calls B’s constructor using the statement super(
args)
.
This must be a complete statement; it means something different if it is a subexpression of a larger expression. It is not
possible to skip class hierarchy levels while constructing an object — if C’s superclass is B
and B’s superclass is A, then C’s constructor cannot directly call A’s
constructor.
ECMAScript 4 Netscape Proposal
Core Language
Namespaces
|
Wednesday, September 4, 2002
Namespace Definition
A namespace
definition defines a new namespace named Identifier.
A namespace
definition may only be located at a scope that allows
class definitions. If a namespace is defined as a member of a class, then the namespace must be declared static
.
Use Directive
A use namespace
directive makes the contents of each namespace in the comma-separated list ParenListExpression
accessible without a qualifier.
use namespace
directives are lexically scoped and their effect does not extend past the end of the enclosing
block, directive group, or substatement group. A use namespace
directive may be preceded by attributes; however, all such attributes must evaluate
to either true
or false
.
Name Lookup
The following paragraphs describe what happens when a name is looked up. See also the description of how namespace attributes affect name definitions.
Properties
Conceptually, an instance x is a collection of properties.
All properties have property names q::
nC,
where q is a namespace, n is an identifier, and C is a class. There may be several aliases
that refer to the same property (due to either multiple namespace attributes
or aliases introduced with the export
definition), but a property name q::
nC
can refer to at most one property of an instance.
An instance x can have several properties q::
nC with
the same namespace q and name n but different classes C. In the following descriptions, q::
n
denotes the most derived of these properties, which is the one with the most derived class C.
A property reference can be either unqualified or qualified and is looked up according to the table below. There are two
entries in the table for each kind of lookup, depending on whether the left operand of the .
operator is a SuperExpression
or not. x is an expression that evaluates to an instance,
is the set of all scopes enclosing the property reference, and Q is the set of
all namespaces q that are use
d by the scopes in .
Qualified reference x. q:: n where q
is a namespace |
Select x’s most derived property q:: n. Signal an error if there
is no such property. |
Qualified reference super x. q:: n
where q is a namespace |
This form may only be used for references in the scope of a class C other than Object .
Let S be C’s superclass. Among all of x’s properties q:: nA
select the one whose class A is most derived but still either S or an ancestor of S.
Signal an error if there is no such property. |
Unqualified reference x. n |
Let A be the least derived (closest to Object ) class such that x contains
at least one property named q:: nA where q is any
element of Q; signal an error if x has no such properties. Let Q' be the set of all namespaces
q such that q is in Q and x contains the property named q:: nA.
Let P be the set of all most derived properties q:: n of x such
that q is in Q'. If P has only one element p or if all of P’s elements
are aliases of one property p, select p; otherwise signal an error. |
Unqualified reference super x. n |
This form may only be used for references in the scope of a class C other than Object .
Let S be C’s superclass. Let A be the least derived (closest to Object )
class such that x contains at least one property named q:: nA
where q is any element of Q; signal an error if x has no such properties or if A
is not S or an ancestor of S. Let Q' be the set of all namespaces q such that
q is in Q and x contains the property named q:: nA.
For each q in Q' let Bq be the most derived class such that Bq
is S or an ancestor of S and x contains the property q:: nBq;
for each q in Q' let pq be the property q:: nBq.
Let P be the set of all such properties pq. If P has only one
element p or if all of P’s elements are aliases of one property p, select p;
otherwise signal an error. |
Dynamic reference x[ s] |
Let s evaluate to a string n. Get the property x.public:: n.
Signal an error if there is no such property. |
Dynamic reference super x[ s] |
Let s evaluate to a string n. Get the property super x.public:: n.
Signal an error if there is no such property. |
Note that the only way to access an overridden method is to use super
. This is by design
to prevent security attacks.
Variables
Conceptually, all variables (which for the purpose of this section also include constants, functions, classes, and such)
have qualified names q::
n, where q
is a namespace and n an identifier. There may be several aliases that refer to the same variable (due to either
multiple namespace attributes or aliases introduced with the export
definition), but there cannot be two different variables defined using the same qualified name in the same scope.
A variable reference can be either unqualified or qualified and is looked up as follows:
Qualified reference q:: n where q is a namespace |
Let be the set of all scopes
enclosing the qualified reference q
|
Unqualified reference n |
Let be the set of all scopes enclosing the unqualified reference n. Search the scopes in , starting from the innermost one and continuing outwards until a value is found or all scopes have been examined. If no binding has been found after all scopes have been examined, signal an error. For each scope S in , do the following:
|
ECMAScript 4 Netscape Proposal
Core Language
Packages
|
Wednesday, June 4, 2003
Packages were originally part of the ECMAScript Edition 4 proposal but have been removed due to time constraints. If implemented, packages and import directives might be defined as described below.
Defining Packages
Packages are an abstraction mechanism for grouping and distributing related code. Packages are designed to be linked at run time to allow a program to take advantage of packages written elsewhere or provided by the embedding environment. ECMAScript 4 offers a number of facilities to make packages robust for dynamic linking:
- Selected package contents can be protected from outside reference.
- Classes can maintain invariants that cannot be violated by code outside the class and/or package.
- Function arguments and data structure references can be type-checked to limit the kinds of unexpected inputs the package’s code can experience.
- Packages can export multiple namespaces, allowing graceful upgrades to packages without changing the code that uses them.
A package is defined using the following syntax:
When a package is defined, it may, but is not required to, be given a PackageName, which is either a string or a series of dot-separated identifiers. It is implementation-defined what the restrictions, if any, are on naming packages to avoid clashes with other packages that may be present.
The Block contains the body of a package P. The Block
is evaluated at the time package P is loaded. Any public
top-level definitions are available to other
packages that import
package P. Any public
class member definitions are available to
all other packages, regardless of whether they import
package P. Top-level and class definitions defined
by P in another namespace N are available to other packages only if they use
namespace
N or qualify the access with namespace N.
A package is loaded (its body is evaluated) when the package is first imported or invoked directly (if, for example, the
package is on an HTML web page). Some standard packages are loaded when the ECMAScript engine first starts up. When a package
is loaded, its statements are evaluated in order, which may cause other packages to be loaded along the way when import
directives are encountered. Circularities are not allowed in the graph of package imports.
Two attempts to load the same package in the same environment result in sharing of that package. What constitutes an environment is necessarily application-dependent. However, if package P1 loads packages P2 and P3, both of which load package P4, then P4 is loaded only once and thereafter its code and data is shared by P2 and P3.
Javascript does not support package definition circularities (two packages A and B that each import the other), although an implementation may provide such a facility as an extension.
Importing Packages
A package P can reference another package Q via an import
directive:
An import
directive may be preceded by attributes; however, all such
attributes must evaluate to either true
or false
.
There are two ways an import
directive can name a package to be imported:
- The PackageName may be PackageIdentifiers. In this case, the system looks for a package with that exact PackageIdentifiers on its implementation-defined search path.
- The PackageName may be a literal string. In this case, the system interprets the contents of the string in an implementation-defined manner in order to locate the package. Specific ECMAScript 4 embeddings should define the manner in which the contents of the string are interpreted. For example, a browser embedding may be defined to interpret the string as a URI and look for a package at the location given by that URI.
An import
directive does the following:
- Locate the target package specified by PackageName. If the package has not yet been loaded, then load it and wait until the target package’s Block is done evaluating. If loading the target package causes an import of the current package then throw a package circularity exception.
- Let P be the target package object.
- If Identifier is given,
const
-bind it to P in the current scope. - For each non-
explicit
top-level definition N::
n (n in namespace N) in P, bind an alias N::
n to P’s N::
n in the global scope unless N::
n is already defined in the global scope.
If package P has a top-level definition n and package Q imports P using import PkgP =
P,
then package Q can refer to n as either n or PkgP.
n. The shorter
form n is not available if it conflicts with some other n. If package P has an explicit
top-level definition n and package Q imports P, then package Q can refer to that
n only as PkgP.
n.
ECMAScript 4 Netscape Proposal
Core Language
Pragmas
|
Tuesday, January 28, 2003
Pragmas allow a script writer to select program modes and options. Pragmas are lexically scoped and their effect does not extend past the end of the enclosing block, directive group, or substatement group.
The keyword use
is followed by one or more PragmaItems, each
of which consists of an identifier, an optional argument, and an optional ?
.
The pragma identifiers below are currently defined. Implementations may define additional identifiers that have meaning as pragmas.
Identifier | Meaning |
---|---|
ecmascript( n) |
Error if version n of ECMAScript is not supported; otherwise recommends but does not require that the implementation only support ECMAScript version n features. |
strict strict(true) |
Strict mode |
strict(false) |
Non-strict mode (default) |
The pragma takes effect starting with the directive after the pragma and continues either until the end of the enclosing
block or statement group or until overridden by another pragma in the same block or statement group, whichever comes first.
If a pragma references the same identifier several times, the last reference takes precedence. The semicolon insertion rule
changes implied by the strict
pragma apply to the semicolon, if any, ending the use
directive that
contains that pragma.
If an implementation does not recognize a pragma identifier, then if the PragmaItem
ends with a ?
then that PragmaItem is ignored; if the PragmaItem
does not end with a ?
then an error occurs.
Strict Mode
Many parts of ECMAScript 4 are relaxed or unduly convoluted due to compatibility requirements with ECMAScript 3. Strict mode sacrifices some of this compatibility for simplicity and additional error checking. Strict mode is intended to be used in newly written ECMAScript 4 programs, although existing ECMAScript 3 programs may be retrofitted.
The opposite of strict mode is nonstrict mode, which is the default. A program can readily mix strict and nonstrict portions.
Strict mode has the following effects:
- Line-break semicolon insertion is turned off. (Grammatical semicolon insertion remains turned on.)
- [no line break] restrictions in grammar productions are ignored. Line breaks can be placed anywhere between input tokens.
- Variables must be declared.
- Definition scopes are not hoisted.
var
andfunction
declarations without attributes or types are initialized at the beginning of a scope.- FunctionDefinitions define constants rather than variables.
- Calls to functions defined under strict mode are checked for the correct number of arguments except in functions that explicitly allow a variable number of arguments. (The mode of the call site does not matter.)
- Implementations may choose to disable other compatibility extensions such as support for octal literals. These are not officially part of ECMAScript 4 but most implementations support these in nonstrict mode for compatibility with older programs.
An implementation does not have to implement strict mode; however, implementations are encouraged to do so.
See also the rationale.
ECMAScript 4 Netscape Proposal
Libraries
|
Monday, December 11, 2000
This chapter presents the libraries that accompany the core language.
For the time being, only the libraries new to ECMAScript 4 are described. The basic libraries such as String
,
Array
, etc. carry over from ECMAScript 3.
ECMAScript 4 Netscape Proposal
Libraries
Types
|
Wednesday, June 4, 2003
Predefined Types
The following types are predefined in ECMAScript 4:
Unlike in ECMAScript 3, there is no distinction between objects and primitive values. All values can have methods. Values
of some classes are sealed, which disallows addition of dynamic properties. User-defined classes can be made to behave like
primitives by using the class modifier final
.
The above type names are not reserved words. They can be used as names of local variables or class members. However, they are defined as constants in the global scope, so a package cannot use them to name global variables.
Object
is the supertype of all types. Never
is the subtype of all types. Never
is
useful to describe the return type of a function that cannot return normally because it either falls into an infinite loop
or always throws an exception. Never
cannot be used as the type of a variable or parameter. Void
is useful to describe the return type of a function that can return but that does not produce a useful value.
See rationale.
A literal number is a member of the type Number
; if that literal has an integral value, then it is also a
member of type Integer
. A literal string is a member of the type String
. There are no literals of
type char
; a char
value can be constructed by an explicit or implicit conversion.
An object created with the expression new
f where f is a function has the type
Object
.
User-Defined Types
Any class defined using the class
declaration is also a type that denotes the set of all of its and its descendants’
instances. These include the predefined classes, so Object
, Date
, etc. are all types. null
is an instance of a user-defined class. undefined
is never an instance of a user-defined class.
Meaning of Types
Types are generally used to restrict the set of objects that can be held in a variable or passed as a function argument. For example, the declaration
var x:Integer;
restricts the values that can be held in variable x
to be integers.
A type declaration does not affect the semantics of reading the variable or accessing one of its properties. Thus, as long
as expression new MyType()
returns a value of type MyType
, the following two code snippets are equivalent:
var x:MyType = new MyType(); x.foo();
var x = new MyType(); x.foo();
This equivalence always holds, even if these snippets are inside the declaration of class MyType
and foo
is a private field of that class. As a corollary, adding true type annotations does not change the meaning of a program.
Type Expressions
The language cannot syntactically distinguish type expressions from value expressions, so a type expression can be any compile-time constant expression that evaluates to a type.
A type is also a value (whose type is Type
) and can be used in expressions, assigned to variables, passed
to functions, etc. For example, the code
const R:Type = Number; function abs_val(x:R):R { return x<0 ? -x : x; }
is equivalent to:
function abs_val(x:Number):Number { return x<0 ? -x : x; }
Implicit Coercions
Implicit coercions can take place in the following situations:
- Assigning a value v to a variable of type t
- Declaring an uninitialized variable of type t, in which case
undefined
is implicitly coerced to type t - Passing an argument v to a function whose corresponding parameter has type t
- Returning a result v from a function declared to return a value of type t
In any of these cases, if v t, then v is passed unchanged. If v t, then if t defines an implicit mapping for value v then that mapped v is used; otherwise an error occurs.
Explicit Coercions
An explicit coercion performs more aggressive transformations than an implicit coercion. To invoke an explicit coercion, use the type as a function, passing it the value as an argument:
type(
value)
For example, Integer(258.1)
returns the integer 258
, and String(2+2==4)
returns
the string "true"
.
If value is already a member of type, the explicit coercion returns value unchanged. If value can be implicitly coerced to type, the explicit coercion returns the result of the implicit coercion. Otherwise, the explicit coercion uses type’s explicit mapping.
ECMAScript 4 Netscape Proposal
Libraries
Machine Types
|
Tuesday, March 4, 2003
Purpose
Machine types are low-level numeric types for use in ECMAScript 4 programs. These types provide Java-style integer operations
that are useful for communicating between ECMAScript 4 and other programming languages. These types are not intended to
replace Number
and Integer
for general-purpose scripting.
Contents
The following low-level numeric types are available:
Type | Suffix | Values |
---|---|---|
sbyte |
Integer values between –128 and 127 inclusive, excluding –0.0 |
|
byte |
Integer values between 0 and 255 inclusive, excluding –0.0 |
|
short |
Integer values between –32768 and 32767 inclusive, excluding –0.0 |
|
ushort |
Integer values between 0 and 65535 inclusive, excluding –0.0 |
|
int |
Integer values between –2147483648 and 2147483647 inclusive, excluding –0.0 |
|
uint |
Integer values between 0 and 4294967295 inclusive, excluding –0.0 |
|
long |
L |
Long integer values between –9223372036854775808 and 9223372036854775807 inclusive |
ulong |
UL |
Long integer values between 0 and 18446744073709551615 inclusive |
float |
F |
Single-precision IEEE floating-point numbers, including positive and negative zeroes, infinities, and NaN |
The above type names are not reserved words.
8, 16, and 32-bit Integers
The first six types sbyte
, byte
, short
, ushort
, int
, and
uint
are all proper subtypes of Integer
, which is itself a subtype of Number
. A particular
number is a member of multiple types. For example, 3.0 is a member of sbyte
, byte
, short
,
ushort
, int
, uint
, Integer
, Number
, and Object
,
while –2000.0 is a member of short
, int
, Integer
, Number
, and Object
.
ECMAScript does not distinguish between the literals 3 and 3.0 in any way.
All arithmetic operations and comparisons on sbyte
, byte
, short
, ushort
,
int
, and uint
values treat them just like they would any other Number
values —
the operations are performed using full IEEE double-precision arithmetic.
Implicit Coercions
There are no predefined implicit coercions from values of type sbyte
,
byte
, short
, ushort
, int
, or uint
other than the coercions
predefined on the type Number
. The following predefined implicit coercions
are applicable when the destination type is sbyte
, byte
, short
, ushort
,
int
, or uint
:
undefined
+0.0- –0.0 +0.0
long
andulong
values within range of the destination type T are converted to equivalent values of type T- finite integral
float
values within range of the destination type T are converted to equivalent values of type T
Note that there are no implicit coercions from +,
–, or NaN to sbyte
, byte
, short
,
ushort
, int
, or uint
.
Explicit Coercions
There are no predefined explicit coercions from values of type sbyte
,
byte
, short
, ushort
, int
, or uint
other than the coercions
predefined on the type Number
. The predefined explicit coercions below
are applicable when the destination type T is sbyte
, byte
, short
, ushort
,
int
, or uint
. The notation |T| represents the range of the type T, where |sbyte
|
= |byte
| = 256, |short
| = |ushort
| = 65536, and |int
| = |uint
|
= 232.
undefined
+0.0- A
long
orulong
value x is converted to the one value y of type T that satisfies x = y (mod |T|) float
values are first converted to equivalentNumber
values and then converted as below- A
Number
value is first converted to anInteger
value x by truncating towards zero if necessary. Then, if x is –0.0, +, –, or NaN, it is converted to +0.0; otherwise, x is converted to the one value y of type T that satisfies x = y (mod |T|)
64-bit Integers
The types long
and ulong
represent signed and unsigned 64-bit integers. long
and
ulong
literals are written with the suffix L
or UL
and no exponent or decimal point.
Literal values of type long
are written as –9223372036854775808L
through 9223372036854775807L
.
Literal values of type ulong
are written as 0UL
through 18446744073709551615UL
.
The types long
and ulong
are disjoint from Number
, so 5L
and 5
are different objects, although they compare ==
and ===
to each other. 5L
and 5UL
are also different objects, although they compare ==
and ===
to each other.
Negation, addition, subtraction, and multiplication, and modulo (%
) on long
and ulong
values is exact, and long
and ulong
values may be mixed in an expression. There are five possible
cases depending on the mathematical result x:
- If –9223372036854775808 x –1,
then the result has type
long
. - If 0 x 9223372036854775807,
then the result has type
ulong
if at least one operand has typeulong
; otherwise, the result has typelong
. - If 9223372036854775808 x 18446744073709551615,
then the result has type
ulong
. - Otherwise, the result is the closest representable
Number
using the IEEE round-to-nearest mode.
Division involving two long
or ulong
operands returns the most precise quotient available from
among the possible long
, ulong
, and Number
values. In some cases the quotient will
be a long
or ulong
; in other cases the quotient will be a Number
. See the semantics
for the details.
Division and modulo on long
and ulong
values can produce the Number
values positive
or negative infinity or NaN when the divisor is zero.
Addition, subtraction, multiplication, division, and modulo mixing a long
or ulong
operand with
a Number
(or any subtype of Number
) or float
operand first checks whether the Number
or float
operand is an exact integer (including either +0.0 or –0.0 but not infinities or NaN). If it is,
then the computation uses the integral semantics above. If not, then the long
or ulong
operand is
coerced to a Number
and the operation is done using Number
arithmetic.
The bitwise operations &
, |
, and ^
are 64 bits wide if at least one operand
is a long
or ulong
, in which case the other operand is truncated to an integer and treated modulo
264 if necessary. The result is a ulong
if at least one operand is a ulong
; otherwise,
the result is a long
.
The bitwise shifts <<
, >>
, and >>>
are 64 bits wide if the
first operand is a long
or ulong
. The result is a ulong
if the first operand is a ulong
;
otherwise, the result is a long
. >>
copies the most significant bit and >>>
shifts in zero bits regardless of whether the first operand is a long
or ulong
.
Comparisons mixing a long
or ulong
operand with a Number
(or any subtype of Number
)
or float
operand compare exact mathematical values without any coercions.
Implicit Coercions
The following predefined implicit coercions are applicable when the destination
type is long
:
undefined
0Lulong
values between 0UL and 9223372036854775807UL are converted to equivalentlong
values- Finite
Integer
values between –9223372036854775808 and 9223372036854775807 are converted to equivalentlong
values - Finite integral
float
values between –9223372036854775808F and 9223372036854775807F are converted to equivalentlong
values
The following predefined implicit coercions are applicable when the destination
type is ulong
:
undefined
0ULlong
values between 0L and 9223372036854775807L are converted to equivalentulong
values- Finite
Integer
values between –0.0 and 18446744073709551615 are converted to equivalentulong
values - Finite integral
float
values between –0.0F and 18446744073709551615F are converted to equivalentulong
values
Note that there are no implicit coercions from NaN or positive or negative infinity
to long
or ulong
.
A long
or ulong
value can be implicitly coerced to
type Number
, Integer
, or float
. The result is the closest representable Number
or float
value using the same rounding as when a string is converted to a number. If the source is 0L or 0UL
then the result is +0.0 or +0.0F.
Explicit Coercions
The predefined explicit coercions below are applicable when the destination
type T is long
or ulong
.
undefined
0L or 0UL- A
long
orulong
value x is converted to the one value y of type T that satisfies x = y (mod 264) float
values are first converted to equivalentNumber
values and then converted as below- A
Number
value is first converted to anInteger
value x by truncating towards zero if necessary. Then, if x is –0.0, +, –, or NaN, it is converted to 0L or 0UL; otherwise, x is converted to the one value y of type T that satisfies x = y (mod 264).
A long
or ulong
value x can be explicitly coerced
to type Number
, Integer
, float
or String
. Explicit coercions to Number
,
Integer
, float
are the same as the implicit coercions. Explicit coercions to type String
produce the x as a string of decimal digits. Negative values have a minus sign prepended. Zero produces the string
"0"
; all other values produce strings starting with a non-zero digit.
Single-Precision Floats
The type float
represents single-precision IEEE floating-point numbers. float
literals are written
with the suffix F
. float
infinities and NaN are separate from Number
infinities and
NaN.
The type float
is disjoint from Number
, so 5F
and 5
are different objects,
although they compare ==
to each other.
Negating a float
value returns a float
value. All other arithmetic first converts the float
value to the corresponding Number
value. The bitwise operations &
, |
, ^
,
<<
, >>
, and >>>
coerce any float
operands to type
Number
before proceeding.
Implicit Coercions
The following predefined implicit coercions are applicable when the destination
type is float
:
undefined
float(NaN)
Number
values (including NaN and the infinities) are converted to the closest representablefloat
values using the IEEE round-to-nearest modelong
andulong
values are converted to the closest representablefloat
values (excluding –0.0F)
A float
value can be implicitly coerced to type Number
.
The result is the equivalent Number
value.
ECMAScript 4 Netscape Proposal
Formal Description
|
Wednesday, August 14, 2002
- Semantic Notation
- Stages
- Lexical Grammar Summary (also available as Word RTF)
- Lexical Grammar and Semantics (Word RTF)
- Regular Expression Grammar Summary (Word RTF)
- Regular Expression Grammar and Semantics (Word RTF)
- Syntactic Grammar Summary (Word RTF)
- Syntactic Grammar and Semantics (Word RTF)
This chapter presents the formal syntax and semantics of ECMAScript 4. The syntax notation and semantic notation sections explain the notation used for this description. A simple metalanguage based on a typed lambda calculus is used to specify the semantics.
The syntax and semantic sections are available in both HTML 4.0 and Microsoft Word RTF formats. In the HTML versions each use of a grammar nonterminal or metalanguage value, type, or field is hyperlinked to its definition, making the HTML version preferred for browsing. On the other hand, the RTF version looks much better when printed. The fonts, colors, and other formatting of the various grammar and semantic elements are all encoded as CSS (in HTML) or Word (in RTF) styles and can be altered if desired.
The syntax and semantics sections are machine-generated from code supplied to a small engine that can type-check and execute the semantics directly. This engine is in the CVS tree at mozilla/js2/semantics; the input files are at mozilla/js2/semantics/JS20.
ECMAScript 4 Netscape Proposal
Formal Description
Semantic Notation
|
Friday, June 13, 2003
The semantics of ECMAScript 4 are written in Algol-like pseudocode. The following sections define the notation used to write the semantics’ concepts, expressions, procedures, and actions.
Operators
The table below summarizes operators used in expressions in this document. The operators are listed in groups in order from the highest precedence (tightest-binding) to the lowest precedence (loosest-binding). Other than the relational operators, operators in the same group have the same precedence and associate left-to-right, so, for example, 7–3+2–1 means ((7–3)+2)–1 instead of 7–(3+(2–1)) or (7–(3+2))–1. As is traditional in mathematics, the relational operators cascade, so
a = b c < d
means
a = b and b c and c < d
Parentheses are used to override precedences or clarify expressions.
Expressions used in describing algorithms may perform side effects. Except for and, or, and ?:, the operators compute all of their operands left-to-right, and if computation of any operand throws an exception e, then the operator immediately propagates e without computing any following operands.
Group | Operator | Description |
---|---|---|
Nonassociative | (x) | Return x. Parentheses are used to override operator precedence. |
{x1, x2, ... , xn} | Set or semantic domain with the elements x1, x2, ... , xn | |
{f(x) | x A} {f(x) | x A such that predicate(x)} |
Set comprehension | |
[x0, x1, ... , xn–1] | List with the elements x0, x1, ... , xn–1 | |
[f(x) | x u] [f(x) | x u such that predicate(x)] |
List comprehension | |
Namelabel1: x1, ... , labeln: xn Name |
Tuple constructor | |
|x| | Absolute value of a number x, cardinality of a set x, or length of a list x | |
x | Floor of x | |
x | Ceiling of x | |
Action[nonterminali] | This notation is used inside an action for a grammar production that has nonterminal nonterminal on the production’s left or right side. Return the value of action Action invoked on the ith instance of nonterminal nonterminal on the left or right side of . The subscript i can be omitted if it is 1 and there is only one instance of nonterminal nonterminal on ’s right side. | |
nonterminali | This notation is used inside an action for a grammar production
that has nonterminal nonterminal on the production’s left or right side. Furthermore,
every complete expansion of grammar nonterminal nonterminal expands into a single character. Return the character to which the ith instance of nonterminal nonterminal on the right side of expands. The subscript i can be omitted if there is only one instance of nonterminal nonterminal in . If the subscript is omitted and nonterminal nonterminal appears on the left side of , then this expression returns the single character to which this whole production expands. |
|
Suffix | ilong | Convert integer i to a Long |
iulong | Convert integer i to a ULong | |
xf32 | Convert real number x to the “closest” Float32 value by calling realToFloat32(x) | |
xf64 | Convert real number x to the “closest” Float64 value by calling realToFloat64(x) | |
xy | x raised to the yth power | |
u[i] | ith element of list u | |
u[i ... j] u[i ...] |
Slice of list u | |
u[i \ x] | List element substitution | |
a.label | Field named label of tuple or record a | |
T{} | Semantic domain of all sets whose elements are members of semantic domain T | |
T[] | Semantic domain of all lists whose elements are members of semantic domain T | |
f(x1, ..., xn) | Procedure call | |
Prefix | new Namelabel1: x1, ... , labeln: xn | Record constructor |
–x | Real number negation | |
min A | Smallest element of a set | |
max A | Largest element of a set | |
Factor | x y | Real number product |
x / y | Real number quotient (y must not be zero) | |
x mod y | Real number remainder (y must not be zero) | |
A B | Set intersection | |
T1 T2 ... Tn T () T T1 T2 ... Tn () () () |
Semantic domain of procedures | |
Term | x + y | Real number addition |
x – y | Real number subtraction or set difference | |
u v | List concatenation | |
A B | Set union | |
Relational | x = y x y |
Equality and inequality predicates on tags, real numbers,
sets, booleans, characters,
lists, strings, tuples,
and records. Values of differing kinds (such as the boolean true
and the character ‘A ’) are always considered unequal. |
x < y x y x > y x y |
Order predicates on real numbers, characters, and strings. | |
x A x A |
Set membership predicates | |
A B | Subset predicate | |
A B | Proper subset predicate | |
Negation | not a | Logical negation |
Conjunction | a and b | Short-circuiting logical conjunction |
Disjunction | a or b | Short-circuiting logical disjunction |
a xor b | Logical exclusive or | |
Conditional | a ? x : y | Conditional |
some x A satisfies predicate(x) every x A satisfies predicate(x) |
Set or list quantifiers |
Semantic Domains
Semantic domains describe the possible values that a variable might take on in an algorithm. The algorithms are constructed in a way that ensures that these constraints are always met, regardless of any valid or invalid programmer or user input or actions.
A semantic domain can be intuitively thought of as a set of possible values, and, in fact, any set of values explicitly described in this document is also a semantic domain. Nevertheless, semantic domains have a more precise mathematical definition in domain theory (see for example [Schmidt86]) that allows one to define semantic domains recursively without encountering paradoxes such as trying to define a set A whose members include all functions mapping values from A to Integer. The problem with an ordinary definition of such a set A is that the cardinality of the set of all functions mapping A to Integer is always strictly greater than the cardinality of A, leading to a contradiction. Domain theory uses a least fixed point construction to allow A to be defined as a semantic domain without encountering problems.
Semantic domains have names in Capitalized Red Small Caps. Such a name is to be considered distinct from a tag or regular variable with the same name, so Undefined, undefined, and undefined are three different and independent entities.
A variable v is constrained using the notation
v: T
where T is a semantic domain. This constraint indicates that the value of v will always be a member of the semantic domain T. These declarations are informative (they may be dropped without affecting the semantics’ correctness) but useful in understanding the semantics. For example, when the semantics state that x: Integer then one does not have to worry about what happens when x has the value true or +f64.
The constraints can be proven statically. The ECMAScript semantics have been machine-checked to ensure that every constraint holds.
Tags
Tags are computational tokens with no internal structure. Tags are written using a dark red font. Two tags are equal if and only if they have the same name.
Each tag that does not also name a tuple or a record is defined using the notation:
In the HTML version of the semantics, each use of a tag’s name is linked back to its definition.
Booleans
The tags true and false represent booleans. Boolean is the two-element semantic domain {true, false}.
Let a and b be booleans and x and y any values. In addition to = and , the following operations can be done on them:
Note that the and, or, and ?: operators short-circuit. These are the only operators that do not always compute all of their operands.
Sets
A set is an unordered, possibly infinite collection of elements. Each element may occur at most once in a set. There must be an equivalence relation = defined on all pairs of the set’s elements. Elements of a set may themselves be sets.
A set is denoted by enclosing a comma-separated list of values inside braces:
{element1, element2, ... , elementn}
The empty set is written as {}. Any duplicate elements are included only once in the set.
For example, the set {3, 0, 10, 11, 12, 13, –5} contains seven integers.
Sets of either integers or characters can be abbreviated using the ... range operator, which generates inclusive ranges of integers or character code points. For example, the above set can also be written as {0, –5, 3 ... 3, 10 ... 13}.
If the beginning of the range is equal to the end of the range, then the range consists of only one element: {7 ... 7} is the same as {7}. If the end of the range is one less than the beginning, then the range contains no elements: {7 ... 6} is the same as {}. The end of the range is never more than one less than the beginning.
A set can also be written using the set comprehension notation
{f(x) | x A}
which denotes the set of the results of computing expression f on all elements x of set A. A predicate can be added:
{f(x) | x A such that predicate(x)}
denotes the set of the results of computing expression f on all elements x of set A that satisfy the predicate expression. There can also be more than one free variable x and set A, in which case all combinations of free variables’ values are considered. For example,
{x | x Integer such that x2 < 10} = {–3, –2, –1, 0, 1, 2, 3};
{x2 | x {–5, –1, 1, 2, 4}} = {1, 4, 16, 25};
{x10 + y | x {1, 2, 4}, y {3, 5}} = {13, 15, 23, 25, 43, 45}.
The same notation is used for operations on sets and on semantic domains. Let A and B be sets (or semantic domains) and x and y be values. The following operations can be done on them:
If T is a semantic domain, then T{} is the semantic domain of all sets whose elements are members of T. For example, if T = {1,2,3}, then T{} = {{}, {1}, {2}, {3}, {1,2}, {1,3}, {2,3}, {1,2,3}}. The empty set {} is a member of T{} for any semantic domain T.
In addition to the above, the some and every quantifiers can be used on sets (see also lists). The quantifier
some x A satisfies predicate(x)
returns true if there exists at least one element x in set A such that predicate(x) computes to true. If there is no such element x, then the some quantifier’s result is false. If the some quantifier returns true, then variable x is left bound to any element of A for which predicate(x) computes to true; if there is more than one such element x, then one of them is chosen arbitrarily. For example,
some x {3, 16, 19, 26} satisfies x mod 10 = 6
evaluates to true and leaves x set to either 16 or 26. Other examples include:
(some x {3, 16, 19, 26} satisfies x mod 10 = 7) = false;
(some x {} satisfies x mod 10 = 7) = false;
(some x {“Hello
”} satisfies true) = true
and leaves x set to the string “Hello
”;
(some x {} satisfies true) = false.
The quantifier
every x A satisfies predicate(x)
returns true if there exists no element x in set A such that predicate(x) computes to false. If there is at least one such element x, then the every quantifier’s result is false. As a degenerate case, the every quantifier is always true if the set A is empty. For example,
(every x {3, 16, 19, 26} satisfies x mod 10 = 6) = false;
(every x {6, 26, 96, 106} satisfies x mod 10 = 6) = true;
(every x {} satisfies x mod 10 = 6) = true.
Real Numbers
Numbers written in plain font are exact mathematical real numbers. Numbers can be written with or without a decimal point. Integers preceded with “0x” are hexadecimal (base 16). 4294967296, 4294967296.000, 0x100000000, and 232 are all the same number. 0.1 is the exact value 1/10.
Integer is the semantic domain of all integers {... –3, –2, –1, 0, 1, 2, 3 ...}. 3.0, 3, 0xFF, and –10100 are all integers.
Rational is the semantic domain of all rational numbers. Every integer is also a rational number: Integer Rational. 3, 1/3, 7.5, –12/7, and 2–5 are examples of rational numbers.
Real is the semantic domain of all real numbers. Every rational number is also a real number: Rational Real. is an example of a real number slightly larger than 3.14.
Let x and y be real numbers. The following operations can be done on them and always produce exact results:
Real numbers can be compared using =, , <, , >, and . The result is either true or false.
Bitwise Integer Operators
The four procedures below perform bitwise operations on integers. The integers are treated as though they were written in infinite-precision two’s complement binary notation, with each 1 bit representing true and 0 bit representing false.
More precisely, any integer x can be represented as an infinite sequence of bits ai where the index i ranges over the nonnegative integers and every ai {0, 1}. The sequence is traditionally written in reverse order:
..., a4, a3, a2, a1, a0
The unique sequence corresponding to an integer x is generated by the formula
ai = x / 2i mod 2
If x is zero or positive, then its sequence will have infinitely many consecutive leading 0’s, while a negative integer x will generate a sequence with infinitely many consecutive leading 1’s. For example, 6 generates the sequence ...0...0000110, while –6 generates ...1...1111010.
The logical and, or, and xor operations below operate on corresponding elements of the sequences ai and bi generated by the two parameters x and y. The result is another infinite sequence of bits ci. The result of the operation is the unique integer z that generates the sequence ci. For example, anding corresponding elements of the sequences generated by 6 and –6 yields the sequence ...0...0000010, which is the sequence generated by the integer 2. Thus, bitwiseAnd(6, –6) = 2.
Procedure | Description |
---|---|
bitwiseAnd(x: Integer, y: Integer): Integer | The bitwise and of x and y |
bitwiseOr(x: Integer, y: Integer): Integer | The bitwise or of x and y |
bitwiseXor(x: Integer, y: Integer): Integer | The bitwise xor of x and y |
bitwiseShift(x: Integer, count: Integer): Integer | Shift x to the left by count bits. If count is negative, shift x to the right by –count bits. Bits shifted out of the right end are lost; bit shifted in at the right end are zero. bitwiseShift(x, count) is exactly equivalent to x 2count. |
Characters
Characters enclosed in single quotes ‘ and ’ represent Unicode characters with code points ranging from
0000 to 10FFFF hexadecimal. Even though Unicode does not define characters for some of these code points, in this specification
any of these 1114112 code points is considered to be a valid character. Examples of characters include ‘A
’,
‘b
’, ‘«LF»
’, ‘«uFFFF»
’,
‘«U00010000»
’ and , ‘«U0010FFFF»
’
(see also the notation for non-ASCII characters).
Unicode has the notion of code points, which are numerical indices of characters in the Unicode character table, as well as code units, which are numerical values for storing characters in a particular representation. ECMAScript is designed to make it appear that strings are represented in the UTF-16 representation, which means that a code unit is a 16-bit value (an implementation may store strings in other formats such as UTF-8, but it must make it appear for indexing and character extraction purposes as if strings were sequences of 16-bit code units). For convenience this specification does not distinguish between code units and code points in the range from 0000 to FFFF hexadecimal.
Char16 is the semantic domain of the
65536 Unicode characters in the set {‘«u0000»
’ ... ‘«uFFFF»
’}.
These characters form Unicode’s Basic Multilingual Plane. These characters have code points between 0000 and FFFF hexadecimal.
Code units are also represented by values in the Char16 semantic domain.
SupplementaryChar
is the semantic domain of the 1048576 Unicode characters in the set {‘«U00010000»
’ ... ‘«U0010FFFF»
’}.
These are Unicode’s supplementary characters with code points between 10000 and 10FFFF hexadecimal. Since these characters
are not members of the Char16 domain, they cannot be stored directly in strings of Char16
code units. Instead, whereever necessary the semantic algorithms convert supplementary characters into pairs of surrogate
code units before storing them into strings. The first surrogate code unit h is in the set {‘«uD800»
’ ... ‘«uDBFF»
’}
and the second surrogate code unit l is in the set {‘«uDC00»
’ ... ‘«uDFFF»
’};
together they encode the supplementary character with the code point value 0x10000 + (char16ToInteger(h) – 0xD800)0x400 + char16ToInteger(l) – 0xDC00.
Char21 is the semantic domain of all
1114112 Unicode characters {‘«u0000»
’ ... ‘«U0010FFFF»
’}.
Characters can be compared using =, ,
<, , >, and .
These operators compare code point values, so ‘A
’ = ‘A
’, ‘A
’ < ‘B
’,
‘A
’ < ‘a
’, and ‘«uFFFF»
’ < ‘«U00010000»
’
are all true.
Character Conversions
The following procedures convert between characters and integers:
Procedure | Description |
---|---|
char16ToInteger(c: Char16): {0 ... 0xFFFF} | The number of character c’s Unicode code point or code unit |
char21ToInteger(c: Char21): {0 ... 0x10FFFF} | The number of character c’s Unicode code point |
integerToChar16(i: {0 ... 0xFFFF}): Char16 | The character whose Unicode code point or code unit number is i |
integerToSupplementaryChar(i: {0x10000 ... 0x10FFFF}): SupplementaryChar | The character whose Unicode code point number is i |
integerToChar21(i: {0 ... 0x10FFFF}): Char21 | The character whose Unicode code point number is i |
The procedure digitValue is defined as follows:
0
’ ... ‘9
’, ‘A
’ ... ‘Z
’, ‘a
’ ... ‘z
’}): {0 ... 35}Lists
A finite ordered list of zero or more elements is written by listing the elements inside bold brackets:
[element0, element1, ... , elementn–1]
For example, the following list contains four strings:
[“parsley
”, “sage
”, “rosemary
”, “thyme
”]
The empty list is written as [].
Unlike a set, the elements of a list are indexed by integers starting from 0. A list can contain duplicate elements.
A list can also be written using the list comprehension notation
[f(x) | x u]
which denotes the list [f(u[0]), f(u[1]), ... , f(u[|u|–1])] whose elements consist of the results of applying expression f to each corresponding element of list u. x is the name of the parameter in expression f. A predicate can be added:
[f(x) | x u such that predicate(x)]
denotes the list of the results of computing expression f on all elements x of list u that satisfy the predicate expression. The results are listed in the same order as the elements x of list u. For example,
[x2 | x [–1, 1, 2, 3, 4, 2, 5]] = [1, 1, 4, 9, 16, 4, 25]
[x+1 | x [–1, 1, 2, 3, 4, 5, 3, 10] such that x mod 2 = 1] = [0, 2, 4, 6, 4]
Let u = [e0, e1, ... , en–1] and v = [f0, f1, ... , fm–1] be lists, e be an element, i and j be integers, and x be a value. The operations below can be done on lists. The operations are meaningful only when their preconditions are met; the semantics never use the operations below without meeting their preconditions.
Lists are functional — there is no notation for modifying a list in place.
If T is a semantic domain, then T[] is the semantic domain of all lists whose elements are members of T. The empty list [] is a member of T[] for any semantic domain T.
In addition to the above, the some and every quantifiers can be used on lists just as on sets:
some x u satisfies predicate(x)
every x u satisfies predicate(x)
These quantifiers’ behavior on lists is analogous to that on sets, except that, if the some quantifier returns true then it leaves variable x set to the first element of list u that satisfies condition predicate(x). For example,
some x [3, 36, 19, 26] satisfies x mod 10 = 6
evaluates to true and leaves x set to 36.
Strings
A list of Char16 code units is called a string. In addition to the normal list notation, for notational convenience a string can also be written as zero or more characters enclosed in double quotes (see also the notation for non-ASCII characters). Thus,
“Wonder«LF»
”
is equivalent to:
[‘W
’, ‘o
’, ‘n
’, ‘d
’, ‘e
’, ‘r
’, ‘«LF»
’]
The empty string is written as “”.
A string holds code units, not code points. Supplementary Unicode characters are represented as pairs of surrogate code units when stored in strings.
In addition to all of the other list operations, <, , >, and are defined on strings. A string x is less than string y when y is not the empty string and either x is the empty string, the first code unit of x is less than the first code unit of y, or the first code unit of x is equal to the first code unit of y and the rest of string x is less than the rest of string y.
Note that these relations compare code units, not code points, which
can produce unexpected effects if a string contains supplementary characters expanded
into a pairs of surrogates. For example, even though ‘«uFFFF»
’ < ‘«U00010000»
’,
the supplementary character ‘«U00010000»
’ is represented in
a string as “«uD800»«uDC00»
”, and, by the above rules,
“«uFFFF»
” > “«uD800»«uDC00»
”.
String is the semantic domain of all strings. String = Char16[].
Tuples
A tuple is an immutable aggregate of values comprised of a name and zero or more labeled fields.
The pseudocode defines each tuple and describes its fields. A tuple definition has the form
and defines tuples with name Name to have n fields with semantic domains T1 through Tn respectively. In the HTML version of the semantics, each use of a tuple’s Name is linked back to its definition.
After Name is defined, the notation
Namelabel1: v1, ... , labeln: vn
represents a tuple with name Name and values v1 through vn for fields labeled label1 through labeln respectively. Each value vi is a member of the corresponding semantic domain Ti. When most of the fields are copied from an existing tuple a, this notation can be abbreviated as
Namelabeli1: vi1, ... , labelik: vik, other fields from a
which represents a tuple with name Name and values vi1 through vik for fields labeled labeli1 through labelik respectively and the values of correspondingly labeled fields from a for all other fields.
If a is the tuple Namelabel1: v1, ... , labeln: vn, then
a.labeli
returns the ith field’s value vi. Tuples are functional — there is no notation for modifying a tuple in place. In the HTML version of the semantics, each use of labeli is linked back to a’s type.
The equality operators = and may be used to compare tuples. Tuples are equal when they have the same name and their corresponding fields’ values are equal.
The notation
Name
represents the semantic domain of all tuples with name Name.
Records
A record is a mutable aggregate of values similar to a tuple but with different equality behavior.
A record is comprised of a name and an address. The address points to a mutable data structure comprised of zero or more labeled fields. The address acts as the record’s serial number — every record allocated by new (see below) gets a different address, including records created by identical expressions or even the same expression used twice.
The pseudocode defines each record and describes its fields. A record definition has the form
and defines records with name Name to have n fields with semantic domains T1 through Tn respectively. In the HTML version of the semantics, each use of a record’s Name is linked back to its definition.
After Name is defined, the expression
new Namelabel1: v1, ... , labeln: vn
creates a record with name Name and a new address . The fields labeled label1 through labeln at address are initialized with values v1 through vn respectively. Each value vi is a member of the corresponding semantic domain Ti. A labelk: vk pair may be omitted from a new expression, which indicates that the initial value of field labelk does not matter because the semantics will always explicitly write a value into that field before reading it.
When most of the fields are copied from an existing record a, the new expression can be abbreviated as
new Namelabeli1: vi1, ... , labelik: vik, other fields from a
which represents a record b with name Name and a new address . The fields labeled labeli1 through labelik at address are initialized with values vi1 through vik respectively; the other fields at address are initialized with the values of correspondingly labeled fields from a’s address.
If a is a record with name Name and address , then
a.labeli
returns the current value v of the ith field at address . That field may be set to a new value w, which must be a member of the semantic domain Ti, using the assignment
a.labeli w
after which a.labeli will evaluate to w. Any record with a different address is unaffected by the assignment. In the HTML version of the semantics, each use of labeli is linked back to a’s type.
The equality operators = and may be used to compare records. Records are equal if and only if they have the same address.
The notation
Name
represents the infinite semantic domain of all records that have name Name and all addresses.
ECMAScript Numeric Types
ECMAScript does not support exact real numbers as one of the programmer-visible data types. Instead, ECMAScript numbers have finite range and precision. The semantic domain of all programmer-visible numbers representable in ECMAScript is GeneralNumber, defined as the union of four basic numeric semantic domains Long, ULong, Float32, and Float64:
The four basic numeric semantic domains are all disjoint from each other and from the semantic domains Integer, Rational, and Real.
The semantic domain FiniteGeneralNumber is the subtype of all finite values in GeneralNumber:
Signed Long Integers
Programmer-visible signed 64-bit long integers are represented by the semantic domain Long. These are wrapped in a tuple to keep them disjoint from members of the semantic domains ULong, Float32, and Float64.
Shorthand Notation
In this specification, when i is an integer between –263 and 263 – 1, the notation ilong indicates the result of Longvalue: i, which is the integer i wrapped in a Long tuple.
Unsigned Long Integers
Programmer-visible unsigned 64-bit long integers are represented by the semantic domain ULong. These are wrapped in a tuple to keep them disjoint from members of the semantic domains Long, Float32, and Float64.
Shorthand Notation
In this specification, when i is an integer between 0 and 264 – 1, the notation iulong indicates the result of ULongvalue: i, which is the integer i wrapped in a ULong tuple.
Single-Precision Floating-Point Numbers
Float32 is the semantic domain of all representable single-precision floating-point IEEE 754 values, with all not-a-number values considered indistinguishable from each other. Float32 is the union of the following semantic domains:
The non-zero finite values are wrapped in a tuple to keep them disjoint from members of the semantic domains Long, ULong, and Float64. The remaining values are the tags +zerof32 (positive zero), –zerof32 (negative zero), +f32 (positive infinity), –f32 (negative infinity), and NaNf32 (not a number).
There are 4261412864 (that is, 232–225) normalized values:
m is called the significand.
There are also 16777214 (that is, 224–2) denormalized non-zero values:
m is called the significand.
Members of the semantic domain NonzeroFiniteFloat32 with value greater than zero are called positive finite. The remaining members of NonzeroFiniteFloat32 are called negative finite.
Since floating-point numbers are either tags or tuples wrapping rational
numbers, the notation = and may be used to compare them.
Note that = is false for different tags, so +zerof32 –zerof32
but NaNf32 = NaNf32. The ECMAScript
x ==
y and x ===
y operators have
different behavior for Float32 values, defined by isEqual
and isStrictlyEqual.
Shorthand Notation
In this specification, when x is a real number, the notation xf32 indicates the result of realToFloat32(x), which is the “closest” Float32 value as defined below. Thus, 3.4 is a Real number, while 3.4f32 is a Float32 value (whose exact value is actually 3.400000095367431640625). The positive finite Float32 values range from 10–45f32 to (3.4028235 1038)f32.
Conversion
The procedure realToFloat32 converts a real number x into the applicable element of Float32 as follows:
This procedure corresponds exactly to the behavior of the IEEE 754 “round to nearest” mode.
The procedure truncateFiniteFloat32 truncates a FiniteFloat32 value to an integer, rounding towards zero:
Arithmetic
The following table defines negation of Float32 values using IEEE 754 rules. Note that exprf32 indicates the result of realToFloat32(expr).
x | Result |
–f32 | +f32 |
negative finite | (–x.value)f32 |
–zerof32 | +zerof32 |
+zerof32 | –zerof32 |
positive finite | (–x.value)f32 |
+f32 | –f32 |
NaNf32 | NaNf32 |
Double-Precision Floating-Point Numbers
Float64 is the semantic domain of all representable double-precision floating-point IEEE 754 values, with all not-a-number values considered indistinguishable from each other. Float64 is the union of the following semantic domains:
The non-zero finite values are wrapped in a tuple to keep them disjoint from members of the semantic domains Long, ULong, and Float32. The remaining values are the tags +zerof64 (positive zero), –zerof64 (negative zero), +f64 (positive infinity), –f64 (negative infinity), and NaNf64 (not a number).
There are 18428729675200069632 (that is, 264–254) normalized values:
m is called the significand.
There are also 9007199254740990 (that is, 253–2) denormalized non-zero values:
m is called the significand.
Members of the semantic domain NonzeroFiniteFloat64 with value greater than zero are called positive finite. The remaining members of NonzeroFiniteFloat64 are called negative finite.
Since floating-point numbers are either tags or tuples wrapping rational
numbers, the notation = and may be used to compare them.
Note that = is false for different tags, so +zerof64 –zerof64
but NaNf64 = NaNf64. The ECMAScript
x ==
y and x ===
y operators have
different behavior for Float64 values, defined by isEqual
and isStrictlyEqual.
Shorthand Notation
In this specification, when x is a real number, the notation xf64 indicates the result of realToFloat64(x), which is the “closest” Float64 value as defined below. Thus, 3.4 is a Real number, while 3.4f64 is a Float64 value (whose exact value is actually 3.399999999999999911182158029987476766109466552734375). The positive finite Float64 values range from (5 10–324)f64 to (1.7976931348623157 10308)f64.
Conversion
The procedure realToFloat64 converts a real number x into the applicable element of Float64 as follows:
This procedure corresponds exactly to the behavior of the IEEE 754 “round to nearest” mode.
The procedure float32ToFloat64 converts a Float32 number x into the corresponding Float64 number as defined by the following table:
x | Result |
–f32 | –f64 |
–zerof32 | –zerof64 |
+zerof32 | +zerof64 |
+f32 | +f64 |
NaNf32 | NaNf64 |
Any NonzeroFiniteFloat32 value | NonzeroFiniteFloat64value: x.value |
The procedure truncateFiniteFloat64 truncates a FiniteFloat64 value to an integer, rounding towards zero:
Arithmetic
The following tables define procedures that perform common arithmetic on Float64 values using IEEE 754 rules. Note that exprf64 indicates the result of realToFloat64(expr).
x | Result |
–f64 | +f64 |
negative finite | (–x.value)f64 |
–zerof64 | +zerof64 |
+zerof64 | +zerof64 |
positive finite | x |
+f64 | +f64 |
NaNf64 | NaNf64 |
x | Result |
–f64 | +f64 |
negative finite | (–x.value)f64 |
–zerof64 | +zerof64 |
+zerof64 | –zerof64 |
positive finite | (–x.value)f64 |
+f64 | –f64 |
NaNf64 | NaNf64 |
x | y | ||||||
–f64 | negative finite | –zerof64 | +zerof64 | positive finite | +f64 | NaNf64 | |
–f64 | –f64 | –f64 | –f64 | –f64 | –f64 | NaNf64 | NaNf64 |
negative finite | –f64 | (x.value + y.value)f64 | x | x | (x.value + y.value)f64 | +f64 | NaNf64 |
–zerof64 | –f64 | y | –zerof64 | +zerof64 | y | +f64 | NaNf64 |
+zerof64 | –f64 | y | +zerof64 | +zerof64 | y | +f64 | NaNf64 |
positive finite | –f64 | (x.value + y.value)f64 | x | x | (x.value + y.value)f64 | +f64 | NaNf64 |
+f64 | NaNf64 | +f64 | +f64 | +f64 | +f64 | +f64 | NaNf64 |
NaNf64 | NaNf64 | NaNf64 | NaNf64 | NaNf64 | NaNf64 | NaNf64 | NaNf64 |
x | y | ||||||
–f64 | negative finite | –zerof64 | +zerof64 | positive finite | +f64 | NaNf64 | |
–f64 | NaNf64 | –f64 | –f64 | –f64 | –f64 | –f64 | NaNf64 |
negative finite | +f64 | (x.value – y.value)f64 | x | x | (x.value – y.value)f64 | –f64 | NaNf64 |
–zerof64 | +f64 | (–y.value)f64 | +zerof64 | –zerof64 | (–y.value)f64 | –f64 | NaNf64 |
+zerof64 | +f64 | (–y.value)f64 | +zerof64 | +zerof64 | (–y.value)f64 | –f64 | NaNf64 |
positive finite | +f64 | (x.value – y.value)f64 | x | x | (x.value – y.value)f64 | –f64 | NaNf64 |
+f64 | +f64 | +f64 | +f64 | +f64 | +f64 | NaNf64 | NaNf64 |
NaNf64 | NaNf64 | NaNf64 | NaNf64 | NaNf64 | NaNf64 | NaNf64 | NaNf64 |
x | y | ||||||
–f64 | negative finite | –zerof64 | +zerof64 | positive finite | +f64 | NaNf64 | |
–f64 | +f64 | +f64 | NaNf64 | NaNf64 | –f64 | –f64 | NaNf64 |
negative finite | +f64 | (x.value y.value)f64 | +zerof64 | –zerof64 | (x.value y.value)f64 | –f64 | NaNf64 |
–zerof64 | NaNf64 | +zerof64 | +zerof64 | –zerof64 | –zerof64 | NaNf64 | NaNf64 |
+zerof64 | NaNf64 | –zerof64 | –zerof64 | +zerof64 | +zerof64 | NaNf64 | NaNf64 |
positive finite | –f64 | (x.value y.value)f64 | –zerof64 | +zerof64 | (x.value y.value)f64 | +f64 | NaNf64 |
+f64 | –f64 | –f64 | NaNf64 | NaNf64 | +f64 | +f64 | NaNf64 |
NaNf64 | NaNf64 | NaNf64 | NaNf64 | NaNf64 | NaNf64 | NaNf64 | NaNf64 |
x | y | ||||||
–f64 | negative finite | –zerof64 | +zerof64 | positive finite | +f64 | NaNf64 | |
–f64 | NaNf64 | +f64 | +f64 | –f64 | –f64 | NaNf64 | NaNf64 |
negative finite | +zerof64 | (x.value / y.value)f64 | +f64 | –f64 | (x.value / y.value)f64 | –zerof64 | NaNf64 |
–zerof64 | +zerof64 | +zerof64 | NaNf64 | NaNf64 | –zerof64 | –zerof64 | NaNf64 |
+zerof64 | –zerof64 | –zerof64 | NaNf64 | NaNf64 | +zerof64 | +zerof64 | NaNf64 |
positive finite | –zerof64 | (x.value / y.value)f64 | –f64 | +f64 | (x.value / y.value)f64 | +zerof64 | NaNf64 |
+f64 | NaNf64 | –f64 | –f64 | +f64 | +f64 | NaNf64 | NaNf64 |
NaNf64 | NaNf64 | NaNf64 | NaNf64 | NaNf64 | NaNf64 | NaNf64 | NaNf64 |
x | y | |||
–f64, +f64 | positive or negative finite | –zerof64, +zerof64 | NaNf64 | |
–f64 | NaNf64 | NaNf64 | NaNf64 | NaNf64 |
negative finite | x | float64Negate(float64Remainder(float64Negate(x), y)) | NaNf64 | NaNf64 |
–zerof64 | –zerof64 | –zerof64 | NaNf64 | NaNf64 |
+zerof64 | +zerof64 | +zerof64 | NaNf64 | NaNf64 |
positive finite | x | (x.value – |y.value|x.value/|y.value|)f64 | NaNf64 | NaNf64 |
+f64 | NaNf64 | NaNf64 | NaNf64 | NaNf64 |
NaNf64 | NaNf64 | NaNf64 | NaNf64 | NaNf64 |
Note that float64Remainder(float64Negate(x), y) always produces the same result as float64Negate(float64Remainder(x, y)). Also, float64Remainder(x, float64Negate(y)) always produces the same result as float64Remainder(x, y).
Procedures
A procedure is a function that receives zero or more arguments, performs computations, and optionally returns a result.
Procedures may perform side effects. In this document the word procedure is used to refer to internal algorithms; the
word function is used to refer to the programmer-visible function
ECMAScript construct.
A procedure is denoted as:
If the procedure does not return a value, the : T on the first line is omitted.
f is the procedure’s name, param1 through paramn are the procedure’s parameters, T1 through Tn are the parameters’ respective semantic domains, T is the semantic domain of the procedure’s result, and step1 through stepm describe the procedure’s computation steps, which may produce side effects and/or return a result. If T is omitted, the procedure does not return a result. When the procedure is called with argument values v1 through vn, the procedure’s steps are performed and the result, if any, returned to the caller.
A procedure’s steps can refer to the parameters param1 through paramn; each reference to a parameter parami evaluates to the corresponding argument value vi. Procedure parameters are statically scoped. Arguments are passed by value.
Operations
The only operation done on a procedure f is calling it using the f(arg1, ..., argn) syntax. f is computed first, followed by the argument expressions arg1 through argn, in left-to-right order. If the result of computing f or any of the argument expressions throws an exception e, then the call immediately propagates e without computing any following argument expressions. Otherwise, f is invoked using the provided arguments and the resulting value, if any, returned to the caller.
Procedures are never compared using =, , or any of the other comparison operators.
Semantic Domains of Procedures
The semantic domain of procedures that take n parameters in semantic domains T1 through Tn respectively and produce a result in semantic domain T is written as T1 T2 ... Tn T. If n = 0, this semantic domain is written as () T. If the procedure does not produce a result, the semantic domain of procedures is written either as T1 T2 ... Tn () or as () ().
Computation Steps
Computation steps in procedures are described using a mixture of English and formal notation. The various kinds of formal steps are described in this section. Multiple steps are separated by semicolons and performed in order unless an earlier step exits via a return or propagates an exception.
Informal steps state invariants and provide comments.
A nothing step performs no operation.
A note step performs no operation. It provides an informative comment about the algorithm. If Comment is an expression, then the note step is an informative comment that asserts that the expression, if evaluated at this point, would be guaranteed to evaluate to true.
A computation step may consist of an expression. The expression is computed and its value, if any, ignored.
An assignment step is indicated using the assignment operator . This step computes the value of expression and assigns the result to the temporary variable or mutable global v. If this is the first time the temporary variable is referenced in a procedure, the variable’s semantic domain T is listed; any value stored in v is guaranteed to be a member of the semantic domain T.
This step declares v to be a temporary variable with semantic domain T without assigning anything to the variable. v will not be read unless some other step first assigns a value to it.
Inside an action, the assignment operator can also be used to define the result of another action Action[nonterminali] applied to the current expansion of nonterminali, which must appear on the current production’s left or right side. Such an assignment is done only when the value of Action[nonterminali] is not defined explicitly via an action. The value of Action[nonterminali] is set at most once and never modified afterwards. If the same nonterminal is expanded several times while parsing a source program, all such expansions are treated independently.
Temporary variables are local to the procedures that define them (including any nested procedures). Each time a procedure is called it gets a new set of temporary variables.
This form of assignment sets the value of field label of record a to the value of expression.
An if step computes expression1, which will evaluate to either true or false. If it is true, the first list of steps is performed. Otherwise, expression2 is computed and tested, and so on. If no expression evaluates to true, the list of steps following the else is performed. The else clause may be omitted, in which case no action is taken when no expression evaluates to true.
A case step computes expression, which will evaluate to a value v. If v T1, then the first list of steps is performed. Otherwise, if v T2, then the second list of steps is performed, and so on. If v is not a member of any Ti, the list of steps following the else is performed. The else clause may be omitted, in which case v will always be a member of some Ti.
A while step computes expression, which will evaluate to either true or false. If it is false, no action is taken. If it is true, the list of steps is performed and then expression is computed and tested again. This repeats until expression returns true (or until the procedure exits via a return or an exception is propagated out).
A for each step computes expression, which will evaluate to either a set or a list A. The list of steps is performed repeatedly with variable x bound to each element of A. If A is a list, x is bound to each of its elements in order; if A is a set, the order in which x is bound to its elements is arbitrary. The repetition ends after x has been bound to all elements of A (or when either the procedure exits via a return or an exception is propagated out).
A return step computes expression to obtain a value v and returns from the enclosing procedure with the result v. No further steps in the enclosing procedure are performed. The expression may be omitted, in which case the enclosing procedure returns with no result.
Exceptions
A throw step computes expression to obtain a value v and begins propagating exception v outwards, exiting partially performed steps and procedure calls until the exception is caught by a catch step. Unless the enclosing procedure catches this exception, no further steps in the enclosing procedure are performed.
A try step performs the first list of steps. If they complete normally (or if they return), then the try step is done. If any of the steps propagates out an exception e, then if e T, then exception e stops propagating, variable v is bound to the value e, and the second list of steps is performed. If e T, then exception e keeps propagating out.
A try step does not intercept exceptions that may be propagated out of its second list of steps.
Nested Procedures
An inner proc may be nested as a step inside an outer proc. In this case the inner procedure is a closure and can access the parameters and temporaries of the outer procedure.
Semantic Actions
Semantic actions tie the grammar and the semantics together. A semantic action ascribes semantic meaning to a grammar production.
To illustrate the use of semantic actions, we shall look at an example, followed by a description of the notation for specifying semantic actions.
Example
Consider the following grammar, with the start nonterminal Numeral:
This grammar defines the syntax of an acceptable input: “37
”, “33#4
”
and “30#2
” are acceptable syntactically, while “1a
” is not. However, the
grammar does not indicate what these various inputs mean. That is the job of the semantics, which are defined in terms of
actions on the parse tree of grammar rule expansions. Consider the following sample set of actions defined on this grammar,
with a starting Numeral action called (in this example) Value:
Action names are written in violet cursive type. The definition
states that the action Value can be applied to any expansion of the nonterminal Numeral,
and the result is an Integer. This action either maps an input to an integer or
throws an exception. The code above throws the exception syntaxError when presented
with the input “30#2
”.
There are two definitions of the Value action on Numeral, one for each grammar production that expands Numeral:
Each definition of an action is allowed to perform actions on the terminals and nonterminals on the right side of the expansion.
For example, Value applied to the first Numeral
production (the one that expands Numeral into Digits)
simply applies the DecimalValue action to the expansion of the nonterminal Digits
and returns the result. On the other hand, Value applied to the second Numeral
production (the one that expands Numeral into Digits #
Digits)
performs a computation using the results of the DecimalValue and BaseValue
applied to the two expansions of the Digits nonterminals. In this case there are
two identical nonterminals Digits on the right side of the expansion, so subscripts
are used to indicate on which the actions DecimalValue and BaseValue
are performed.
The definition
states that the action BaseValue can be applied to any expansion of the nonterminal Digits, and the result is a procedure that takes one Integer argument base and returns an Integer. The procedure’s body is comprised of independent cases for each production that expands Digits. When the procedure is called, the case corresponding to the expansion of the nonterminal Digits is evaluated.
The Value action on Digit illustrates the direct use of a nonterminal in a semantic expression: digitValue(Digit). Using the nonterminal Digit in this way refers to the character into which the Digit grammar rule expands.
We can fully evaluate the semantics on our sample inputs to get the following results:
Input | Semantic Result |
---|---|
37 |
37 |
33#4 |
15 |
30#2 |
throw syntaxError |
Abbreviated Actions
In some cases the all actions named A for a nonterminal N’s rule are repetitive, merely calling A on the nonterminals on the right side of the expansions of N in the grammar. In these cases the semantics of action A are abbreviated, as illustrated by the example below.
Given the grammar rule
*
Subexpression+
Subexpressionthis
the notation
is an abbreviation for the following:
*
Subexpression] do+
Subexpression2] dothis
] do nothingNote that:
- The expanded calls to Validate get the same arguments cxt and env passed in to the call to Validate on Expression.
- When an expansion of Expression has more than one nonterminal on its right side, Validate is called on all of the nonterminals in left-to-right order.
- When an expansion of Expression has no nonterminals on its right side, Validate does nothing.
The propagation notation is also used in when the actions return a value. In this case each expansion must have exactly one nonterminal. For example, given the grammar rule
the notation
is an abbreviation for the following:
Semantic Definition Summary
The following notation is used to define top-level semantic entities:
This notation defines Name to be a shorthand for the semantic domain expression. In the HTML version of the semantics, each use of Name is linked back to this definition.
This notation defines name to be a constant value given by the result of computing expression. The value is guaranteed to be a member of the semantic domain T. In the HTML version of the semantics, each use of name is linked back to this definition.
This notation defines name to be a mutable global value. Its initial value is the result of computing expression, but it may be subsequently altered using an assignment. The value is guaranteed to be a member of the semantic domain T. In the HTML version of the semantics, each use of name is linked back to this definition.
This notation defines f to be a procedure.
This notation defines a tag.
This notation defines a tuple.
This notation defines a record.
This notation states that action Action can be performed on nonterminal nonterminal and returns a value that is a member of the semantic domain T. The action’s value is either defined using the notation Action[nonterminal expansion] = expression below or set as a side effect of computing another action via an action assignment.
This notation specifies the value that action Action on nonterminal nonterminal computes in the case where nonterminal nonterminal expands to the given expansion. expansion can contain zero or more terminals and nonterminals (as well as other notations allowed on the right side of a grammar production). Furthermore, the terminals and nonterminals of expansion can be subscripted to allow them to be unambiguously referenced by action references or nonterminal references inside expression.
This notation combines the above two — it specifies the semantic domain of the action as well as its value.
This notation is used when the computation of the action is too complex for an expression. Here the steps to compute the action are listed as step1 through stepm. A return step produces the value of the action.
This notation is used only when Action returns a procedure when applied to nonterminal nonterminal with a single expansion expansion. Here the steps of the procedure are listed as step1 through stepm.
This notation is used only when Action returns a procedure when applied to nonterminal nonterminal with several expansions expansion1 through expansionn. The procedure is comprised of a series of cases, one for each expansion. Only the steps corresponding to the expansion found by the grammar parser used are evaluated.
This notation is an abbreviation stating that calling Action on nonterminal causes Action to be called with the same arguments on every nonterminal on the right side of the appropriate expansion of nonterminal.
ECMAScript 4 Netscape Proposal
Formal Description
Stages
|
Tuesday, October 15, 2002
The source code is processed in the following stages:
- If necessary, convert the source code into the Unicode UTF-16 format, normalized form C.
- Remove any Unicode format control characters (category Cf) from the source code.
- Simultaneously split the source code into input elements using the lexical grammar and semantics and parse it using the syntactic grammar to obtain a parse tree P.
- Evaluate P using the syntactic semantics by computing the action Eval on it.
Lexing and Parsing
Processing stage 3 is done as follows:
- Let inputElements be an empty array of input elements (syntactic grammar terminals and line breaks).
- Let input be the input sequence of Unicode characters. Append a special placeholder End to the end of input.
- Let state be a variable that holds one of the constants re, div, or num. Initialize it to re.
- Apply the lexical grammar to parse the longest possible prefix of input. Use the start symbol NextInputElementre, NextInputElementdiv, or NextInputElementnum depending on whether state is re, div, or num, respectively. The result of the parse should be a lexical grammar parse tree T. If the parse failed, return a syntax error.
- Compute the action InputElement on T to obtain an InputElement e.
- If e is the endOfInput input element, go to step 15.
- Remove the characters matched by T from input, leaving only the yet-unlexed suffix of input.
- Interpret e as a syntactic grammar terminal or line break
as follows:
- A lineBreak is interpreted as a line break, which is not a terminal itself but indicates one or more line breaks between two terminals. It prevents the syntactic grammar from matching any productions that have a [no line break] annotation in the place where the lineBreak occurred.
- An Identifier s is interpreted as the terminal Identifier. Applying the semantic action Name to the Identifier returns the String value s.name.
- A Keyword s is interpreted as the reserved word, future reserved word, or non-reserved word terminal corresponding to the Keyword’s String s.
- A Punctuator s is interpreted as the punctuation token or future punctuation token terminal corresponding to the Punctuator’s String s.
- A NumberToken x is interpreted as the terminal Number. Applying the semantic action Value to the Number returns the GeneralNumber value x.value.
- A negatedMinLong, which results from a numeric
long
token with the value 263, is interpreted as the terminal NegatedMinLong. - A StringToken s is interpreted as the terminal String. Applying the semantic action Value to the String returns the String value s.value.
- A RegularExpression z is interpreted as the terminal RegularExpression.
- Append the resulting terminal or line break to the end of the inputElements array.
- If the inputElements array forms a valid prefix of the context-free language defined by the syntactic grammar, go to step 13.
- If is not a lineBreak but the previous element of the inputElements array is a lineBreak, then insert a VirtualSemicolon terminal between that lineBreak and in the inputElements array.
- If the inputElements array still does not form a valid prefix of the context-free language defined by the syntactic grammar, signal a syntax error and stop.
- If is a Number
or NegatedMinLong, then set state to num. Otherwise,
if the inputElements array followed by the terminal
/
forms a valid prefix of the context-free language defined by the syntactic grammar, then set state to div; otherwise, set state to re. - Go to step 4.
- If the inputElements array does not form a valid sentence of the context-free language defined by the syntactic grammar, signal a syntax error and stop.
- Return the parse tree obtained by the syntactic grammar’s derivation of the sentence formed by the inputElements array.
ECMAScript 4 Netscape Proposal
Formal Description
Lexical Grammar
|
Monday, June 30, 2003
This LALR(1) grammar describes the lexical syntax of the ECMAScript 4 proposal. See also the description of the grammar notation.
This document is also available as a Word RTF file.
The lexer’s start symbols are:
NextInputElementnum if the
previous input element was a number;
NextInputElementre if the
previous input element was not a number and a /
should be interpreted as a regular expression; and
NextInputElementdiv if the
previous input element was not a number and a /
should be interpreted as a division or division-assignment
operator.
In addition to the above, the start symbol
StringNumericLiteral is used by the syntactic semantics for
string-to-number conversions and the start symbol
StringDecimalLiteral is used by the syntactic semantics for
implementing the parseFloat
function.
Unicode Character Classes
«TAB»
| «VT»
| «FF»
| «SP»
| «u00A0»
«u2000»
| «u2001»
| «u2002»
| «u2003»
| «u2004»
| «u2005»
| «u2006»
| «u2007»
«u2008»
| «u2009»
| «u200A»
| «u200B»
«u3000»
Comments
White Space
Line Breaks
Input Elements
Keywords and Identifiers
Punctuators
!
!
=
!
=
=
%
%
=
&
&
&
&
&
=
&
=
(
)
*
*
=
+
+
+
+
=
,
-
-
-
-
=
.
.
.
.
:
:
:
;
<
<
<
<
<
=
<
=
=
=
=
=
=
=
>
>
=
>
>
>
>
=
>
>
>
>
>
>
=
?
[
]
^
^
=
^
^
^
^
=
{
|
|
=
|
|
|
|
=
}
~
Numeric Literals
.
FractionString Literals
Regular Expression Literals
String-to-Number Conversion
N
a
N
parseFloat Conversion
ECMAScript 4 Netscape Proposal
Formal Description
Lexical Semantics
|
Monday, June 30, 2003
The lexical semantics describe the actions the lexer takes in order to transform an input stream of Unicode characters into a stream of tokens. For convenience, the lexical grammar is repeated here. See also the description of the semantic notation.
This document is also available as a Word RTF file.
The lexer’s start symbols are:
NextInputElementnum if the
previous input element was a number;
NextInputElementre if the
previous input element was not a number and a /
should be interpreted as a regular expression; and
NextInputElementdiv if the
previous input element was not a number and a /
should be interpreted as a division or division-assignment
operator.
In addition to the above, the start symbol
StringNumericLiteral is used by the syntactic semantics for
string-to-number conversions and the start symbol
StringDecimalLiteral is used by the syntactic semantics for
implementing the parseFloat
function.
Semantics
Unicode Character Classes
Syntax
«TAB»
| «VT»
| «FF»
| «SP»
| «u00A0»
«u2000»
| «u2001»
| «u2002»
| «u2003»
| «u2004»
| «u2005»
| «u2006»
| «u2007»
«u2008»
| «u2009»
| «u200A»
| «u200B»
«u3000»
Semantics
Comments
Syntax
White Space
Syntax
Line Breaks
Syntax
Input Elements
Syntax
Semantics
\
}] WhiteSpace InputElementdiv]
= Lex[InputElementdiv];Syntax
Semantics
Keywords and Identifiers
Syntax
Semantics
abstract
”, “as
”, “break
”,
“case
”, “catch
”, “class
”,
“const
”, “continue
”, “debugger
”,
“default
”, “delete
”, “do
”,
“else
”, “enum
”, “export
”,
“extends
”, “false
”, “finally
”,
“for
”, “function
”, “get
”,
“goto
”, “if
”, “implements
”,
“import
”, “in
”, “instanceof
”,
“interface
”, “is
”, “namespace
”,
“native
”, “new
”, “null
”,
“package
”, “private
”, “protected
”,
“public
”, “return
”, “set
”,
“super
”, “switch
”, “synchronized
”,
“this
”, “throw
”, “throws
”,
“transient
”, “true
”, “try
”,
“typeof
”, “use
”, “var
”,
“volatile
”, “while
”,
“with
”} and
not ContainsEscapes[IdentifierName] thenSyntax
Semantics
Punctuators
Syntax
!
!
=
!
=
=
%
%
=
&
&
&
&
&
=
&
=
(
)
*
*
=
+
+
+
+
=
,
-
-
-
-
=
.
.
.
.
:
:
:
;
<
<
<
<
<
=
<
=
=
=
=
=
=
=
>
>
=
>
>
>
>
=
>
>
>
>
>
>
=
?
[
]
^
^
=
^
^
^
^
=
{
|
|
=
|
|
|
|
=
}
~
Semantics
Numeric Literals
Syntax
Semantics
Syntax
.
FractionSemantics
Syntax
Semantics
Syntax
Semantics
Syntax
Semantics
String Literals
Syntax
Semantics
Syntax
Semantics
Syntax
Semantics
Syntax
Semantics
Syntax
Semantics
Syntax
Semantics
Regular Expression Literals
Syntax
Semantics
String-to-Number Conversion
Syntax
N
a
N
Semantics
parseFloat Conversion
Syntax
Semantics
ECMAScript 4 Netscape Proposal
Formal Description
Regular Expression Grammar
|
Monday, June 9, 2003
This LR(1) grammar describes the regular expression syntax of the ECMAScript 4 proposal. See also the description of the grammar notation.
This document is also available as a Word RTF file.
Unicode Character Classes
Regular Expression Definitions
Regular Expression Patterns
Disjunctions
Alternatives
Terms
*
+
?
Assertions
Atoms
.
\
AtomEscapeEscapes
A
| B
| C
| D
| E
| F
| G
| H
| I
| J
| K
| L
| M
| N
| O
| P
| Q
| R
| S
| T
| U
| V
| W
| X
| Y
| Z
a
| b
| c
| d
| e
| f
| g
| h
| i
| j
| k
| l
| m
| n
| o
| p
| q
| r
| s
| t
| u
| v
| w
| x
| y
| z
Decimal Escapes
Hexadecimal Escapes
Character Class Escapes
User-Specified Character Classes
Character Class Range Atoms
ECMAScript 4 Netscape Proposal
Formal Description
Regular Expression Semantics
|
Monday, June 9, 2003
The regular expression semantics describe the actions the regular expression engine takes in order to transform a regular expression pattern into a function for matching against input strings. For convenience, the regular expression grammar is repeated here. See also the description of the semantic notation.
This document is also available as a Word RTF file.
Case-insensitive matches are not implemented in the semantics below.
Semantics
Unicode Character Classes
Syntax
Semantics
Regular Expression Definitions
Semantics
Field str is the input string. ignoreCase, multiline, and span are the corresponding regular expression flags.
A REMatch holds an intermediate state during the pattern-matching process. endIndex is the index of the next input character to be matched by the next component in a regular expression pattern. If we are at the end of the pattern, endIndex is one plus the index of the last matched input character. captures is a zero-based array of the strings captured so far by capturing parentheses.
A Continuation is a function that attempts to match the remaining portion of the pattern against the input string, starting at the intermediate state given by its REMatch argument. If a match is possible, it returns a REMatch result that contains the final state; if no match is possible, it returns a failure result.
A Matcher is a function that attempts to match a middle portion of the pattern against the input string, starting at the intermediate state given by its REMatch argument. Since the remainder of the pattern heavily influences whether (and how) a middle portion will match, we must pass in a Continuation function that checks whether the rest of the pattern matched. If the continuation returns failure, the matcher function may call it repeatedly, trying various alternatives at pattern choice points.
The REInput parameter contains the input string and is merely passed down to subroutines.
A Integer Matcher is a function executed at the time the regular expression is compiled that returns a Matcher for a part of the pattern. The Integer parameter contains the number of capturing left parentheses seen so far in the pattern and is used to assign static, consecutive numbers to capturing parentheses.
characterSetMatcher returns a Matcher that matches a single input string character. If invert is false, the match succeeds if the character is a member of the acceptanceSet set of characters (possibly ignoring case). If invert is true, the match succeeds if the character is not a member of the acceptanceSet set of characters (possibly ignoring case).
characterMatcher returns a Matcher that matches a single input string character. The match succeeds if the character is the same as ch (possibly ignoring case).
Regular Expression Patterns
Syntax
Semantics
Disjunctions
Syntax
Semantics
|
Disjunction1]
= CountParens[Alternative] + CountParens[Disjunction1];Alternatives
Syntax
Semantics
Terms
Syntax
Semantics
Syntax
*
+
?
Semantics
Assertions
Syntax
Semantics
Atoms
Syntax
.
\
AtomEscapeSemantics
Escapes
Syntax
Semantics
Syntax
A
| B
| C
| D
| E
| F
| G
| H
| I
| J
| K
| L
| M
| N
| O
| P
| Q
| R
| S
| T
| U
| V
| W
| X
| Y
| Z
a
| b
| c
| d
| e
| f
| g
| h
| i
| j
| k
| l
| m
| n
| o
| p
| q
| r
| s
| t
| u
| v
| w
| x
| y
| z
Semantics
c
ControlLetter]
= integerToChar16(bitwiseAnd(char16ToInteger(ControlLetter), 31));Decimal Escapes
Syntax
Semantics
Hexadecimal Escapes
Syntax
Semantics
Character Class Escapes
Syntax
Semantics
User-Specified Character Classes
Syntax
Semantics
-
ClassAtomdash2 ClassRanges]
= characterRange(AcceptanceSet[ClassAtom1], AcceptanceSet[ClassAtomdash2])
AcceptanceSet[ClassRanges];Character Class Range Atoms
Syntax
Semantics
ECMAScript 4 Netscape Proposal
Formal Description
Syntactic Grammar
|
Monday, June 30, 2003
This LALR(1) grammar describes the syntax of the ECMAScript 4 proposal. The starting nonterminal is Program. See also the description of the grammar notation.
This document is also available as a Word RTF file.
Terminals
General tokens: Identifier NegatedMinLong Number RegularExpression String VirtualSemicolon
Punctuation tokens: !
!=
!==
%
%=
&
&&
&&=
&=
(
)
*
*=
+
++
+=
,
-
--
-=
.
...
/
/=
:
::
;
<
<<
<<=
<=
=
==
===
>
>=
>>
>>=
>>>
>>>=
?
[
]
^
^=
^^
^^=
{
|
|=
||
||=
}
~
Reserved words: as
break
case
catch
class
const
continue
default
delete
do
else
extends
false
finally
for
function
if
import
in
instanceof
is
namespace
new
null
package
private
public
return
super
switch
this
throw
true
try
typeof
use
var
void
while
with
Future reserved words: abstract
debugger
enum
export
goto
implements
interface
native
protected
synchronized
throws
transient
volatile
Non-reserved words: get
set
Expressions
Identifiers
Qualified Identifiers
Primary Expressions
null
true
false
this
Function Expressions
Object Literals
Array Literals
Super Expressions
Postfix Expressions
++
--
Property Operators
Unary Operators
delete
PostfixExpressionvoid
UnaryExpressiontypeof
UnaryExpression++
PostfixExpression--
PostfixExpression+
UnaryExpression-
UnaryExpression-
NegatedMinLong~
UnaryExpression!
UnaryExpressionMultiplicative Operators
Additive Operators
Bitwise Shift Operators
Relational Operators
Equality Operators
Binary Bitwise Operators
Binary Logical Operators
Conditional Operator
Assignment Operators
Comma Expressions
Type Expressions
Statements
;
;
Empty Statement
Expression Statement
Super Statement
Block Statement
Labeled Statements
If Statement
Switch Statement
Do-While Statement
While Statement
For Statements
With Statement
Continue and Break Statements
Return Statement
Throw Statement
Try Statement
Directives
Attributes
Use Directive
Import Directive
Pragma
Definitions
Variable Definition
Simple Variable Definition
A SimpleVariableDefinition represents the subset of VariableDefinition expansions that may be used when the variable definition is used as a Substatement instead of a Directive in non-strict mode. In strict mode variable definitions may not be used as substatements.
Function Definition
Class Definition
Namespace Definition
Programs
Package Definition
ECMAScript 4 Netscape Proposal
Formal Description
Syntactic Semantics
|
Monday, June 30, 2003
The syntactic semantics describe the actions the parser takes to evaluate an ECMAScript 4 program. For convenience, the syntactic grammar is repeated here. The starting nonterminal is Program. See also the description of the semantic notation.
The semantics are under construction. Many execution paths resulting in ???? represent semantics yet to be implemented.
This document is also available as a Word RTF file.
Terminals
General tokens: Identifier NegatedMinLong Number RegularExpression String VirtualSemicolon
Punctuation tokens: !
!=
!==
%
%=
&
&&
&&=
&=
(
)
*
*=
+
++
+=
,
-
--
-=
.
...
/
/=
:
::
;
<
<<
<<=
<=
=
==
===
>
>=
>>
>>=
>>>
>>>=
?
[
]
^
^=
^^
^^=
{
|
|=
||
||=
}
~
Reserved words: as
break
case
catch
class
const
continue
default
delete
do
else
extends
false
finally
for
function
if
import
in
instanceof
is
namespace
new
null
package
private
public
return
super
switch
this
throw
true
try
typeof
use
var
void
while
with
Future reserved words: abstract
debugger
enum
export
goto
implements
interface
native
protected
synchronized
throws
transient
volatile
Non-reserved words: get
set
Data Model
Semantic Exceptions
Extended integers and rationals
Objects
Undefined
Null
Strings
Namespaces
Qualified Names
Attributes
Classes
Simple Instances
Slots
Uninstantiated Functions
Method Closures
Dates
Regular Expressions
Packages
Objects with Limits
References
Modes of expression evaluation
Contexts
Labels
Function Support
Environments
Properties
Miscellaneous
Data Operations
Numeric Utilities
Character Utilities
«uD800»
’ ... ‘«uDBFF»
’} and
i + 1 |s| and
s[i + 1] {‘«uDC00»
’ ... ‘«uDFFF»
’} thenObject Utilities
Object Class Inquiries
Object to Boolean Conversion
Object to Primitive Conversion
Object to Number Conversions
Object to String Conversions
false
”;true
”;.
” digits[1 ...]NaN
”;0
”;Infinity
”;-Infinity
”;.
” digits[e + 1 ...]NaN
”;0
”;Infinity
”;-Infinity
”;.
” digits[e + 1 ...]Object to Qualified Name Conversion
Object to Class Conversion
Object to Attribute Conversion
Implicit Coercions
Attributes
Access Utilities
Environmental Utilities
Property Lookup
Object
)
and proceed through more specific classes that are ancestors of c.this
to be used to access instance properties within a class’s scope without using the
.
operator. An implicit this
is well-defined only inside instance methods and constructors;
readImplicitThis throws an error if there is no well-defined
implicit this
value or if an attempt is made to read it before it has been initialised.Reading
length
property of o, ensuring that it is an integer between 0 and
arrayLimit inclusive.[
i]
or none
if no such property was found; unlike dotRead,
indexRead does not return a default value for missing properties.
i should always be a valid array index.[
args]
when o is a native object.
Host objects may either also use ordinaryBracketRead or
choose a different procedure P to evaluate o[
args]
by
writing P into
objectType(o).bracketRead.super(
o)[
args]
, in which case limit is the
superclass of the class inside which the super
expression appears. Otherwise, limit is set to
objectType(o).with
statement’s frame before that statement’s
expression has been evaluatedconst
variables from a constant expression;const
instance variable before it is initialisedconst
variable’s initialiser if there is one.Writing
length
property of o. Note that if o is an
Array
, the act of writing its length
property will invoke the
Array_setLength setter.with
statement’s frame before that statement’s expression
has been evaluatedDeleting
with
statement’s frame before that statement’s expression
has been evaluatedEnumerating
Calling Instances
Creating Instances
Adding Local Definitions
explicit
attribute can only be used at the top level of a packagestatic
property of a class cannot have the same name as the class, regardless of the
namespacevar
or a function
statement. If it is defined using var
, then
initialValue is always undefined (if the var
statement has an initialiser, then the variable’s value will be written later when the var
statement is executed). If it is defined using function
, then initialValue must be a function
instance or open instance. A var
hoisted variable may be hoisted into the
ParameterFrame if there is already a parameter with the same name; a
function
hoisted variable is never hoisted into the
ParameterFrame and will shadow a parameter with the same name for
compatibility with ECMAScript Edition 3. If there are multiple function
definitions, the initial value is
the last function
definition.var
already exists, so there is no need to create another one. Overwrite its initial value if the
new definition is a function
definition.Adding Instance Definitions
explicit
attribute can only be used at the top level of a packagepublic
namespace is always open.override
attributeoverride(false)
attributeoverride(false)
but it overrides a superclass’s
propertyoverride
or override(true)
but it
doesn’t override a superclass’s propertyInstantiation
===
to each other and share one set of properties (including the prototype
property,
if applicable) rather than each having its own. ECMAScript programs should not rely on this distinction.Sealing
Standard Class Utilities
Expressions
Syntax
Terminal Actions
Identifiers
Syntax
Semantics
Qualified Identifiers
Syntax
Validation
::
Identifier]
(cxt: Context, env: Environment)Setup
Evaluation
::
Identifier]
(env: Environment, phase: Phase): MultinamePrimary Expressions
Syntax
null
true
false
this
Validation
Setup
Evaluation
this
from within a constructor before the superconstructor has been
called#
” Flags[RegularExpression];Function Expressions
Syntax
Validation
Setup
Evaluation
function
expression is not a constant expression because it can evaluate to different
valuesfunction
expression is not a constant expression because it can evaluate to different
valuesObject Literals
Syntax
Validation
Setup
Evaluation
:
AssignmentExpressionallowIn]
(env: Environment, o: Object, phase: {run})Array Literals
Syntax
Validation
Setup
Evaluation
Super Expressions
Syntax
Validation
super
expression without an argument is meaningful only inside an instance method or a
constructorsuper
expression is meaningful only if the enclosing class has a superclasssuper
expression is meaningful only if the enclosing class has a superclassSetup
Evaluation
super
from within a constructor before the superconstructor has been
calledPostfix Expressions
Syntax
++
--
Validation
Setup
Evaluation
new
FullNewSubexpression Arguments]
(env: Environment, phase: Phase): ObjOrRefProperty Operators
Syntax
Validation
Setup
Evaluation
...
AssignmentExpressionallowIn]
(env: Environment, phase: Phase): Object[]Unary Operators
Syntax
delete
PostfixExpressionvoid
UnaryExpressiontypeof
UnaryExpression++
PostfixExpression--
PostfixExpression+
UnaryExpression-
UnaryExpression-
NegatedMinLong~
UnaryExpression!
UnaryExpressionValidation
Setup
Evaluation
+
a. If phase is
compile, only constant operations are permitted.-
a. If phase is
compile, only constant operations are permitted.!
a. If phase is
compile, only constant operations are permitted.Multiplicative Operators
Syntax
Validation
Setup
Evaluation
Additive Operators
Syntax
Validation
Setup
Evaluation
Bitwise Shift Operators
Syntax
Validation
Setup
Evaluation
Relational Operators
Syntax
Validation
Setup
Evaluation
Equality Operators
Syntax
Validation
Setup
Evaluation
Binary Bitwise Operators
Syntax
Validation
Setup
Evaluation
Binary Logical Operators
Syntax
Validation
Setup
Evaluation
Conditional Operator
Syntax
Validation
Setup
Evaluation
?
NonAssignmentExpression1 :
NonAssignmentExpression2] doAssignment Operators
Syntax
Semantics
Validation
Setup
Evaluation
Comma Expressions
Syntax
Validation
Setup
Evaluation
Type Expressions
Syntax
Validation
Setup and Evaluation
Statements
Syntax
;
;
Validation
true
and false
may be used in a statement but not a
substatementSetup
Evaluation
Empty Statement
Syntax
Expression Statement
Syntax
Validation
Setup
Evaluation
function
, {
}] ListExpressionallowIn]
(env: Environment): ObjectSuper Statement
Syntax
Validation
Setup
Evaluation
Block Statement
Syntax
Validation
{
Directives }
]
(cxt: Context, env: Environment, jt: JumpTargets, preinst: Boolean, frame: Frame){
Directives }
]
(cxt: Context, env: Environment, jt: JumpTargets, preinst: Boolean)Setup
Evaluation
Labeled Statements
Syntax
Validation
:
Substatement]
(cxt: Context, env: Environment, sl: Label{}, jt: JumpTargets)Setup
Evaluation
If Statement
Syntax
Validation
Setup
Evaluation
Switch Statement
Semantics
Syntax
Validation
switch
ParenListExpression {
CaseElements }
]
(cxt: Context, env: Environment, jt: JumpTargets)Setup
Evaluation
switch
ParenListExpression {
CaseElements }
]
(env: Environment, d: Object): ObjectDo-While Statement
Syntax
Validation
do
Substatementabbrev while
ParenListExpression]
(cxt: Context, env: Environment, sl: Label{}, jt: JumpTargets)Setup
Evaluation
do
Substatementabbrev while
ParenListExpression]
(env: Environment, d: Object): ObjectWhile Statement
Syntax
Validation
while
ParenListExpression Substatement]
(cxt: Context, env: Environment, sl: Label{}, jt: JumpTargets)Setup
Evaluation
while
ParenListExpression Substatement]
(env: Environment, d: Object): ObjectFor Statements
Syntax
Validation
false
attribute canot be applied to a for
-in
variable
definitionSetup
Evaluation
With Statement
Syntax
Validation
with
ParenListExpression Substatement]
(cxt: Context, env: Environment, jt: JumpTargets)Setup
Evaluation
with
ParenListExpression Substatement]
(env: Environment, d: Object): ObjectContinue and Break Statements
Syntax
Validation
Setup
Evaluation
Return Statement
Syntax
Validation
return
statement inside a setter or constructor cannot return a valueSetup
Evaluation
Throw Statement
Syntax
Validation
Setup
Evaluation
Try Statement
Syntax
Validation
catch
(
Parameter )
Block]
(cxt: Context, env: Environment, jt: JumpTargets)Setup
Evaluation
catch
clause threw
another exception or ControlTransfer x, so
replace the original exception with x.finally
clause is executed
even if the original block exited due to a ControlTransfer
(break
, continue
, or return
).finally
clause is not inside
a try-catch semantic statement, so if it
throws another exception or ControlTransfer, then the
original exception or ControlTransfer exception is
dropped.catch
(
Parameter )
Block]
(env: Environment, exception: Object): Object {reject}Directives
Syntax
Validation
true
and false
Setup
Evaluation
Attributes
Syntax
Validation
Setup
Evaluation
Use Directive
Syntax
Validation
Import Directive
Syntax
Validation
import
directive only permits the attributes true
and
false
Pragma
Syntax
Validation
strict
” thenecmascript
” thenDefinitions
Variable Definition
Syntax
Validation
static
, virtual
, or final
attributefor
-in
statement’s variable definition must not have an
initialiserSetup
Evaluation
Simple Variable Definition
Syntax
A SimpleVariableDefinition represents the subset of VariableDefinition expansions that may be used when the variable definition is used as a Substatement instead of a Directive in non-strict mode. In strict mode variable definitions may not be used as substatements.
Validation
var
UntypedVariableBindingList]
(cxt: Context, env: Environment)Setup
Evaluation
var
UntypedVariableBindingList]
(env: Environment, d: Object): ObjectFunction Definition
Syntax
Validation
function
FunctionName FunctionCommon]
(cxt: Context,
env: Environment,
preinst: Boolean,
a: CompoundAttribute,
unchecked: Boolean,
hoisted: Boolean)function
FunctionName FunctionCommon]
(cxt: Context, env: Environment, c: Class, a: CompoundAttribute, final: Boolean)function
FunctionName FunctionCommon]
(cxt: Context, env: Environment, c: Class, a: CompoundAttribute)function
FunctionName FunctionCommon]
(cxt: Context, env: Environment, preinst: Boolean, attr: AttributeOptNotFalse)static
, virtual
, or final
attribute(
Parameters )
Result Block]
(cxt: Context, env: Environment, kind: FunctionKind, handling: Handling)(
Parameters )
Result Block]
(cxt: Context, env: Environment, kind: StaticFunctionKind): UninstantiatedFunctionSetup
(
Parameters )
Result Block]
(overriddenSignature: ParameterFrame)Evaluation
(
Parameters )
Result Block]
(this: Object, f: SimpleInstance, args: Object[], phase: Phase): Object(
Parameters )
Result Block]
(runtimeEnv: Environment, phase: Phase): Object(
Parameters )
Result Block]
(newValue: Object, runtimeEnv: Environment, phase: Phase)(
Parameters )
Result Block]
(this: Object, args: Object[], phase: Phase): Object(
Parameters )
Result Block]
(this: Object, phase: Phase): Object(
Parameters )
Result Block]
(this: Object, newValue: Object, phase: Phase)(
Parameters )
Result Block]
(this: SimpleInstance, args: Object[], phase: {run})(
Parameters )
Result Block]
(f: SimpleInstance, args: Object[], phase: Phase): ObjectNumber
and is passed five arguments the first of which is a String
, then
the implementation may throw an exception either about the argument count mismatch or about the type coercion
error in the first argument.callee
”, false, false, f)
and ignore its result;arguments
object.arguments
object....
parameter and is not unchecked.Syntax
Validation
,
NonemptyParameters1]
= Plain[ParameterInit] and Plain[NonemptyParameters1];,
NonemptyParameters1]
= 1 + ParameterCount[NonemptyParameters1];Setup
Class Definition
Syntax
Validation
class
Identifier Inheritance Block]
(cxt: Context, env: Environment, preinst: Boolean, attr: AttributeOptNotFalse)object
”,
privateNamespace: privateNamespace,
dynamic: dynamic,
final: final,
defaultValue: null,
defaultHint: hintNumber,
hasProperty: super.hasProperty,
bracketRead: super.bracketRead,
bracketWrite: super.bracketWrite,
bracketDelete: super.bracketDelete,
read: super.read,
write: super.write,
delete: super.delete,
enumerate: super.enumerate,
call: ordinaryCall,
construct: ordinaryConstruct,
init: none,
is: ordinaryIs,
coerce: ordinaryCoerce;Setup
Evaluation
Namespace Definition
Syntax
Validation
namespace
Identifier]
(cxt: Context, env: Environment, preinst: Boolean, attr: AttributeOptNotFalse)virtual
or final
attributePrograms
Syntax
Processing
Package Definition
Syntax
Processing
internal
”, Namespace, internal)},
archetype: ObjectPrototype,
name: name,
initialize: busy,
sealed: true,
internalNamespace: pkgInternal;.
Identifier]
= Names[PackageIdentifiers1] [Name[Identifier]];Predefined Identifiers
stdExplicitConstBinding(internal::“
internal
”, Namespace, internal),stdConstBinding(public::“
explicit
”, Attribute, global_explicit),stdConstBinding(public::“
enumerable
”, Attribute, global_enumerable),stdConstBinding(public::“
dynamic
”, Attribute, global_dynamic),stdConstBinding(public::“
static
”, Attribute, global_static),stdConstBinding(public::“
virtual
”, Attribute, global_virtual),stdConstBinding(public::“
final
”, Attribute, global_final),stdConstBinding(public::“
prototype
”, Attribute, global_prototype),stdConstBinding(public::“
unused
”, Attribute, global_unused),stdFunction(public::“
override
”, global_override, 1),stdConstBinding(public::“
NaN
”, Number, NaNf64),stdConstBinding(public::“
Infinity
”, Number, +f64),stdConstBinding(public::“
fNaN
”, float, NaNf32),stdConstBinding(public::“
fInfinity
”, float, +f32),stdConstBinding(public::“
undefined
”, Void, undefined),stdFunction(public::“
eval
”, global_eval, 1),stdFunction(public::“
parseInt
”, global_parseint, 2),stdFunction(public::“
parseLong
”, global_parselong, 2),stdFunction(public::“
parseFloat
”, global_parsefloat, 1),stdFunction(public::“
isNaN
”, global_isnan, 1),stdFunction(public::“
isFinite
”, global_isfinite, 1),stdFunction(public::“
decodeURI
”, global_decodeuri, 1),stdFunction(public::“
decodeURIComponent
”, global_decodeuricomponent, 1),stdFunction(public::“
encodeURI
”, global_encodeuri, 1),stdFunction(public::“
encodeURIComponent
”, global_encodeuricomponent, 1),stdConstBinding(public::“
Object
”, Class, Object),stdConstBinding(public::“
Never
”, Class, Never),stdConstBinding(public::“
Void
”, Class, Void),stdConstBinding(public::“
Null
”, Class, Null),stdConstBinding(public::“
Boolean
”, Class, Boolean),stdConstBinding(public::“
GeneralNumber
”, Class, GeneralNumber),stdConstBinding(public::“
long
”, Class, long),stdConstBinding(public::“
ulong
”, Class, ulong),stdConstBinding(public::“
float
”, Class, float),stdConstBinding(public::“
Number
”, Class, Number),stdConstBinding(public::“
sbyte
”, Class, sbyte),stdConstBinding(public::“
byte
”, Class, byte),stdConstBinding(public::“
short
”, Class, short),stdConstBinding(public::“
ushort
”, Class, ushort),stdConstBinding(public::“
int
”, Class, int),stdConstBinding(public::“
uint
”, Class, uint),stdConstBinding(public::“
char
”, Class, char),stdConstBinding(public::“
String
”, Class, String),stdConstBinding(public::“
Array
”, Class, Array),stdConstBinding(public::“
Namespace
”, Class, Namespace),stdConstBinding(public::“
Attribute
”, Class, Attribute),stdConstBinding(public::“
Date
”, Class, Date),stdConstBinding(public::“
RegExp
”, Class, RegExp),stdConstBinding(public::“
Class
”, Class, Class),stdConstBinding(public::“
Function
”, Class, Function),stdConstBinding(public::“
PrototypeFunction
”, Class, PrototypeFunction),stdConstBinding(public::“
Package
”, Class, Package),stdConstBinding(public::“
Error
”, Class, Error),stdConstBinding(public::“
ArgumentError
”, Class, ArgumentError),stdConstBinding(public::“
AttributeError
”, Class, AttributeError),stdConstBinding(public::“
ConstantError
”, Class, ConstantError),stdConstBinding(public::“
DefinitionError
”, Class, DefinitionError),stdConstBinding(public::“
EvalError
”, Class, EvalError),stdConstBinding(public::“
RangeError
”, Class, RangeError),stdConstBinding(public::“
ReferenceError
”, Class, ReferenceError),stdConstBinding(public::“
SyntaxError
”, Class, SyntaxError),stdConstBinding(public::“
TypeError
”, Class, TypeError),stdConstBinding(public::“
UninitializedError
”, Class, UninitializedError),stdConstBinding(public::“
URIError
”, Class, URIError)},archetype: ObjectPrototype, name: “”, initialize: none, sealed: false, internalNamespace: internal
Built-in Namespaces
Built-in Attributes
Built-in Functions
+
’ then i i + 1 elsif s[i] = ‘-
’ then sign –1; i i + 1 end if0x
”, “0X
”} thenBuilt-in Classes
Object
Object
”,
typeofString: “object
”,
dynamic: true,
final: false,
defaultValue: undefined,
defaultHint: hintNumber,
hasProperty: ordinaryHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: ordinaryRead,
write: ordinaryWrite,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: callObject,
construct: constructObject,
init: none,
is: ordinaryIs,
coerce: coerceObject;stdConstBinding(public::“
constructor
”, Class, Object),stdFunction(public::“
toString
”, Object_toString, 0),stdFunction(public::“
toLocaleString
”, Object_toLocaleString, 0),stdFunction(public::“
valueOf
”, Object_valueOf, 0),stdFunction(public::“
hasOwnProperty
”, Object_hasOwnProperty, 1),stdFunction(public::“
isPrototypeOf
”, Object_isPrototypeOf, 1),stdFunction(public::“
propertyIsEnumerable
”, Object_propertyIsEnumerable, 1),stdFunction(public::“
sealProperty
”, Object_sealProperty, 1)},archetype: none, sealed: prototypesSealed, type: Object, slots: {}, call: none, construct: none, env: none;
Never
Never
”,
typeofString: “”,
dynamic: false,
final: true,
defaultValue: none,
hasProperty: ordinaryHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: ordinaryRead,
write: ordinaryWrite,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: sameAsConstruct,
construct: constructNever,
init: none,
is: ordinaryIs,
coerce: coerceNever;Void
Void
”,
typeofString: “undefined
”,
dynamic: false,
final: true,
defaultValue: undefined,
hasProperty: ordinaryHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: ordinaryRead,
write: ordinaryWrite,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: callVoid,
construct: constructVoid,
init: none,
is: ordinaryIs,
coerce: coerceVoid;Null
Null
”,
typeofString: “object
”,
dynamic: false,
final: true,
defaultValue: null,
hasProperty: ordinaryHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: ordinaryRead,
write: ordinaryWrite,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: callNull,
construct: constructNull,
init: none,
is: ordinaryIs,
coerce: coerceNull;Boolean
Boolean
”,
typeofString: “boolean
”,
dynamic: false,
final: true,
defaultValue: false,
hasProperty: ordinaryHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: ordinaryRead,
write: ordinaryWrite,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: sameAsConstruct,
construct: constructBoolean,
init: none,
is: ordinaryIs,
coerce: coerceBoolean;stdConstBinding(public::“
constructor
”, Class, Boolean),stdFunction(public::“
toString
”, Boolean_toString, 0),stdReserve(public::“
valueOf
”, ObjectPrototype)},archetype: ObjectPrototype, sealed: prototypesSealed, type: Object, slots: {}, call: none, construct: none, env: none;
GeneralNumber
GeneralNumber
”,
typeofString: “object
”,
dynamic: false,
final: true,
defaultValue: NaNf64,
defaultHint: hintNumber,
hasProperty: ordinaryHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: ordinaryRead,
write: ordinaryWrite,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: sameAsConstruct,
construct: constructGeneralNumber,
init: none,
is: ordinaryIs,
coerce: coerceGeneralNumber;stdConstBinding(public::“
constructor
”, Class, GeneralNumber),stdFunction(public::“
toString
”, GeneralNumber_toString, 1),stdReserve(public::“
valueOf
”, ObjectPrototype),stdFunction(public::“
toFixed
”, GeneralNumber_toFixed, 1),stdFunction(public::“
toExponential
”, GeneralNumber_toExponential, 1),stdFunction(public::“
toPrecision
”, GeneralNumber_toPrecision, 1)},archetype: ObjectPrototype, sealed: prototypesSealed, type: Object, slots: {}, call: none, construct: none, env: none;
-
”; r –r end if;0
’, fractionDigits + 1 – |digits|) digits.
” digits[k ...]-
”; r –r end if;0
”; e 00
’ do digits digits[0 ... |digits| – 2] end while-
”; r –r end if;.
” digits[e + 1 ...]long
stdConstBinding(public::“
MAX_VALUE
”, ulong, (263 – 1)long),stdConstBinding(public::“
MIN_VALUE
”, ulong, (–263)long)},instanceProperties: {}, super: GeneralNumber, prototype: longPrototype, complete: true, name: “
long
”,
typeofString: “long
”,
dynamic: false,
final: true,
defaultValue: 0long,
hasProperty: ordinaryHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: ordinaryRead,
write: ordinaryWrite,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: sameAsConstruct,
construct: constructLong,
init: none,
is: ordinaryIs,
coerce: coerceLong;stdConstBinding(public::“
constructor
”, Class, long),stdReserve(public::“
toString
”, GeneralNumberPrototype),stdReserve(public::“
valueOf
”, GeneralNumberPrototype)},archetype: GeneralNumberPrototype, sealed: prototypesSealed, type: Object, slots: {}, call: none, construct: none, env: none;
ulong
stdConstBinding(public::“
MAX_VALUE
”, ulong, (264 – 1)ulong),stdConstBinding(public::“
MIN_VALUE
”, ulong, 0ulong)},instanceProperties: {}, super: GeneralNumber, prototype: ulongPrototype, complete: true, name: “
ulong
”,
typeofString: “ulong
”,
dynamic: false,
final: true,
defaultValue: 0ulong,
hasProperty: ordinaryHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: ordinaryRead,
write: ordinaryWrite,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: sameAsConstruct,
construct: constructULong,
init: none,
is: ordinaryIs,
coerce: coerceULong;stdConstBinding(public::“
constructor
”, Class, ulong),stdReserve(public::“
toString
”, GeneralNumberPrototype),stdReserve(public::“
valueOf
”, GeneralNumberPrototype)},archetype: GeneralNumberPrototype, sealed: prototypesSealed, type: Object, slots: {}, call: none, construct: none, env: none;
float
stdConstBinding(public::“
MAX_VALUE
”, float, (3.40282351038)f32),stdConstBinding(public::“
MIN_VALUE
”, float, (10–45)f32),stdConstBinding(public::“
NaN
”, float, NaNf32),stdConstBinding(public::“
NEGATIVE_INFINITY
”, float, –f32),stdConstBinding(public::“
POSITIVE_INFINITY
”, float, +f32)},instanceProperties: {}, super: GeneralNumber, prototype: floatPrototype, complete: true, name: “
float
”,
typeofString: “float
”,
dynamic: false,
final: true,
defaultValue: NaNf32,
hasProperty: ordinaryHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: ordinaryRead,
write: ordinaryWrite,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: sameAsConstruct,
construct: constructFloat,
init: none,
is: ordinaryIs,
coerce: coerceFloat;stdConstBinding(public::“
constructor
”, Class, float),stdReserve(public::“
toString
”, GeneralNumberPrototype),stdReserve(public::“
valueOf
”, GeneralNumberPrototype)},archetype: GeneralNumberPrototype, sealed: prototypesSealed, type: Object, slots: {}, call: none, construct: none, env: none;
Number
stdConstBinding(public::“
MAX_VALUE
”, Number, (1.797693134862315710308)f64),stdConstBinding(public::“
MIN_VALUE
”, Number, (510–324)f64),stdConstBinding(public::“
NaN
”, Number, NaNf64),stdConstBinding(public::“
NEGATIVE_INFINITY
”, Number, –f64),stdConstBinding(public::“
POSITIVE_INFINITY
”, Number, +f64)},instanceProperties: {}, super: GeneralNumber, prototype: NumberPrototype, complete: true, name: “
Number
”,
typeofString: “number
”,
dynamic: false,
final: true,
defaultValue: NaNf64,
hasProperty: ordinaryHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: ordinaryRead,
write: ordinaryWrite,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: sameAsConstruct,
construct: constructNumber,
init: none,
is: ordinaryIs,
coerce: coerceNumber;stdConstBinding(public::“
constructor
”, Class, Number),stdReserve(public::“
toString
”, GeneralNumberPrototype),stdReserve(public::“
valueOf
”, GeneralNumberPrototype)},archetype: GeneralNumberPrototype, sealed: prototypesSealed, type: Object, slots: {}, call: none, construct: none, env: none;
stdConstBinding(public::“
MAX_VALUE
”, Number, highf64),stdConstBinding(public::“
MIN_VALUE
”, Number, lowf64)},instanceProperties: {}, super: Number, prototype: Number.prototype, complete: true, name: name, typeofString: “
number
”,
dynamic: false,
final: true,
defaultValue: +zerof64,
hasProperty: Number.hasProperty,
bracketRead: Number.bracketRead,
bracketWrite: Number.bracketWrite,
bracketDelete: Number.bracketDelete,
read: Number.read,
write: Number.write,
delete: Number.delete,
enumerate: Number.enumerate,
call: sameAsConstruct,
construct: construct,
init: none,
is: is,
coerce: coercechar
fromCharCode
”, char_fromCharCode, 1)},
instanceProperties: {},
super: Object,
prototype: charPrototype,
complete: true,
name: “char
”,
typeofString: “char
”,
dynamic: false,
final: true,
defaultValue: ‘«NUL»
’,
hasProperty: ordinaryHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: ordinaryRead,
write: ordinaryWrite,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: sameAsConstruct,
construct: constructChar,
init: none,
is: ordinaryIs,
coerce: coerceChar;stdConstBinding(public::“
constructor
”, Class, char),stdReserve(public::“
toString
”, StringPrototype),stdReserve(public::“
valueOf
”, StringPrototype)},archetype: StringPrototype, sealed: prototypesSealed, type: Object, slots: {}, call: none, construct: none, env: none;
String
fromCharCode
”, String_fromCharCode, 1)},
instanceProperties: {new InstanceGettermultiname: {public::“
length
”},
final: true,
enumerable: false,
call: String_length},super: Object, prototype: StringPrototype, complete: true, name: “
String
”,
typeofString: “string
”,
dynamic: false,
final: true,
defaultValue: null,
hasProperty: stringHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: readString,
write: ordinaryWrite,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: sameAsConstruct,
construct: constructString,
init: none,
is: ordinaryIs,
coerce: coerceString;stdConstBinding(public::“
constructor
”, Class, String),stdFunction(public::“
toString
”, String_toString, 0),stdReserve(public::“
valueOf
”, ObjectPrototype),stdFunction(public::“
charAt
”, String_charAt, 1),stdFunction(public::“
charCodeAt
”, String_charCodeAt, 1),stdFunction(public::“
concat
”, String_concat, 1),stdFunction(public::“
indexOf
”, String_indexOf, 1),stdFunction(public::“
lastIndexOf
”, String_lastIndexOf, 1),stdFunction(public::“
localeCompare
”, String_localeCompare, 1),stdFunction(public::“
match
”, String_match, 1),stdFunction(public::“
replace
”, String_replace, 1),stdFunction(public::“
search
”, String_search, 1),stdFunction(public::“
slice
”, String_slice, 2),stdFunction(public::“
split
”, String_split, 2),stdFunction(public::“
substring
”, String_substring, 2),stdFunction(public::“
toLowerCase
”, String_toLowerCase, 0),stdFunction(public::“
toLocaleLowerCase
”, String_toLocaleLowerCase, 0),stdFunction(public::“
toUpperCase
”, String_toUpperCase, 0),stdFunction(public::“
toLocaleUpperCase
”, String_toLocaleUpperCase, 0)},archetype: ObjectPrototype, sealed: prototypesSealed, type: Object, slots: {}, call: none, construct: none, env: none;
Array
new InstanceVariablemultiname: {arrayPrivate::“
length
”},
final: true,
enumerable: false,
type: Number,
defaultValue: +zerof64,
immutable: false,new InstanceGettermultiname: {public::“
length
”},
final: true,
enumerable: false,
call: Array_getLength,new InstanceSettermultiname: {public::“
length
”},
final: true,
enumerable: false,
call: Array_setLength},super: Object, prototype: ArrayPrototype, complete: true, name: “
Array
”,
typeofString: “object
”,
privateNamespace: arrayPrivate,
dynamic: true,
final: true,
defaultValue: null,
defaultHint: hintNumber,
hasProperty: ordinaryHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: ordinaryRead,
write: writeArray,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: sameAsConstruct,
construct: ordinaryConstruct,
init: initArray,
is: ordinaryIs,
coerce: ordinaryCoerce;Array
’s private length. See also
readLength, which can work on non-Array
objects.Array
’s private length to length after ensuring that length is
between 0 and arrayLimit inclusive. See also
writeLength, which can work on non-Array
objects.0
” then return 00
’ and (every ch name satisfies ch {‘0
’ ... ‘9
’}) thenstdConstBinding(public::“
constructor
”, Class, Array),stdFunction(public::“
toString
”, Array_toString, 0),stdFunction(public::“
toLocaleString
”, Array_toLocaleString, 0),stdFunction(public::“
concat
”, Array_concat, 1),stdFunction(public::“
join
”, Array_join, 1),stdFunction(public::“
pop
”, Array_pop, 0),stdFunction(public::“
push
”, Array_push, 1),stdFunction(public::“
reverse
”, Array_reverse, 0),stdFunction(public::“
shift
”, Array_shift, 0),stdFunction(public::“
slice
”, Array_slice, 2),stdFunction(public::“
sort
”, Array_sort, 1),stdFunction(public::“
splice
”, Array_splice, 2),stdFunction(public::“
unshift
”, Array_unshift, 1)},archetype: ObjectPrototype, sealed: prototypesSealed, type: Object, slots: {}, call: none, construct: none, env: none;
Array
.toLocaleString
cannot be called on an Array
from a constant expressionArray
.toLocaleString
applied to the elements of the array.Array
.Array
.,
”;Array
.Array
.Array
.Array
.Array
.Array
.Array
.Array
.Namespace
Namespace
”,
typeofString: “namespace
”,
dynamic: false,
final: true,
defaultValue: null,
defaultHint: hintString,
hasProperty: ordinaryHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: ordinaryRead,
write: ordinaryWrite,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: ordinaryCall,
construct: constructNamespace,
init: none,
is: ordinaryIs,
coerce: ordinaryCoerce;stdFunction(public::“
toString
”, Namespace_toString, 0),stdReserve(public::“
valueOf
”, ObjectPrototype)},archetype: ObjectPrototype, sealed: prototypesSealed, type: Object, slots: {}, call: none, construct: none, env: none;
Attribute
Attribute
”,
typeofString: “object
”,
dynamic: false,
final: true,
defaultValue: null,
defaultHint: hintString,
hasProperty: ordinaryHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: ordinaryRead,
write: ordinaryWrite,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: dummyCall,
construct: dummyConstruct,
init: none,
is: ordinaryIs,
coerce: ordinaryCoerce;Date
Date
”,
typeofString: “object
”,
dynamic: true,
final: true,
defaultValue: null,
defaultHint: hintString,
hasProperty: ordinaryHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: ordinaryRead,
write: ordinaryWrite,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: dummyCall,
construct: dummyConstruct,
init: none,
is: ordinaryIs,
coerce: ordinaryCoerce;RegExp
RegExp
”,
typeofString: “object
”,
dynamic: true,
final: true,
defaultValue: null,
defaultHint: hintNumber,
hasProperty: ordinaryHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: ordinaryRead,
write: ordinaryWrite,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: dummyCall,
construct: dummyConstruct,
init: none,
is: ordinaryIs,
coerce: ordinaryCoerce;Class
Class
”,
typeofString: “function
”,
dynamic: false,
final: true,
defaultValue: null,
defaultHint: hintString,
hasProperty: ordinaryHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: ordinaryRead,
write: ordinaryWrite,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: dummyCall,
construct: dummyConstruct,
init: none,
is: ordinaryIs,
coerce: ordinaryCoerce;prototype
”},
final: true,
enumerable: false,
call: Class_prototype;stdConstBinding(public::“
constructor
”, Class, Class),stdFunction(public::“
toString
”, Class_toString, 0),stdReserve(public::“
valueOf
”, ObjectPrototype),stdConstBinding(public::“
length
”, Number, 1f64)},archetype: ObjectPrototype, sealed: prototypesSealed, type: Object, slots: {}, call: none, construct: none, env: none;
Function
Function
”,
typeofString: “function
”,
dynamic: false,
final: true,
defaultValue: null,
defaultHint: hintString,
hasProperty: ordinaryHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: ordinaryRead,
write: ordinaryWrite,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: dummyCall,
construct: dummyConstruct,
init: none,
is: ordinaryIs,
coerce: ordinaryCoerce;length
”},
final: true,
enumerable: false,
type: Number,
defaultValue: none,
immutable: true;PrototypeFunction
prototype
”},
final: true,
enumerable: false,
type: Object,
defaultValue: undefined,
immutable: false},
super: Function,
prototype: FunctionPrototype,
complete: true,
name: “Function
”,
typeofString: “function
”,
dynamic: true,
final: true,
defaultValue: null,
defaultHint: hintString,
hasProperty: ordinaryHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: ordinaryRead,
write: ordinaryWrite,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: dummyCall,
construct: dummyConstruct,
init: none,
is: ordinaryIs,
coerce: ordinaryCoerce;Package
Package
”,
typeofString: “object
”,
dynamic: true,
final: true,
defaultValue: null,
defaultHint: hintString,
hasProperty: ordinaryHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: ordinaryRead,
write: ordinaryWrite,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: dummyCall,
construct: dummyConstruct,
init: none,
is: ordinaryIs,
coerce: ordinaryCoerce;Error
new InstanceVariablemultiname: {public::“
name
”},
final: false,
enumerable: true,
type: String,
defaultValue: null,
immutable: false,new InstanceVariablemultiname: {public::“
message
”},
final: false,
enumerable: true,
type: String,
defaultValue: null,
immutable: false},super: Object, prototype: ErrorPrototype, complete: true, name: “
Error
”,
typeofString: “object
”,
dynamic: true,
final: false,
defaultValue: null,
defaultHint: hintNumber,
hasProperty: ordinaryHasProperty,
bracketRead: ordinaryBracketRead,
bracketWrite: ordinaryBracketWrite,
bracketDelete: ordinaryBracketDelete,
read: ordinaryRead,
write: ordinaryWrite,
delete: ordinaryDelete,
enumerate: ordinaryEnumerate,
call: callError,
construct: ordinaryConstruct,
init: initError,
is: ordinaryIs,
coerce: ordinaryCoerce;stdConstBinding(public::“
constructor
”, Class, Error),stdFunction(public::“
toString
”, Error_toString, 1),stdVarBinding(public::“
name
”, String, “Error
”),stdVarBinding(public::“
message
”, String, an
implementation-defined string)},archetype: ObjectPrototype, sealed: prototypesSealed, type: Object, slots: {}, call: none, construct: none, env: none;
Error Subclasses
object
”,
dynamic: true,
final: false,
defaultValue: null,
defaultHint: hintNumber,
hasProperty: Error.hasProperty,
bracketRead: Error.bracketRead,
bracketWrite: Error.bracketWrite,
bracketDelete: Error.bracketDelete,
read: Error.read,
write: Error.write,
delete: Error.delete,
enumerate: Error.enumerate,
call: call,
construct: ordinaryConstruct,
init: none,
is: ordinaryIs,
coerce: ordinaryCoerce;stdConstBinding(public::“
constructor
”, Class, c),stdVarBinding(public::“
name
”, String, name),stdVarBinding(public::“
message
”, String, an
implementation-defined string)},archetype: ErrorPrototype, sealed: prototypesSealed, type: Object, slots: {}, call: none, construct: none, env: none;
ECMAScript 4 Netscape Proposal
Rationale
|
Tuesday, January 28, 2003
- Execution Model
- Member Lookup
- Versioning
- Named Function Parameters
- Operators
- Units
- Unit Patterns (Word RTF)
- Syntax
- Miscellaneous
This chapter discusses the decisions made in desigining ECMAScript 4. Rationales are presented together with descriptions of other alternatives that were/are considered. Currently outstanding issues are in red.
ECMAScript 4 Netscape Proposal
Rationale
Execution Model
|
Tuesday, September 10, 2002
This page is somewhat out of date.
Introduction
When does a declaration (of a value, function, type, class, method, pragma, etc.) take effect? When are expressions evaluated? The answers to these questions distinguish among major kinds of programming languages. Let’s consider the following function definition in a language with C++ or Java-like syntax:
gadget f(widget x) { if ((gizmo)(x) != null) return (gizmo)(x); return x.owner; }
In a static language such as Java or C++, all type expressions are evaluated at compile time. Thus, in this example widget
and gadget
would be evaluated at compile time. If gizmo
were a type, then it too would be evaluated
at compile time ((gizmo)(x)
would become a type cast). Note that we must be able to statically distinguish identifiers
used for variables from identifiers used for types so we can decide whether (gizmo)(x)
is a one-argument function
call (in which case gizmo
would be evaluated at run time) or a type cast (in which case gizmo
would
be evaluated at compile time). In most cases, in a static language a declaration is visible throughout its enclosing scope,
although there are exceptions that have been deemed too complicated for a compiler to handle such as the following C++:
typedef int *x; class foo { typedef x *y; typedef char *x; }
Many dynamic languages can construct, evaluate, and manipulate type expressions at run time. Some dynamic languages (such
as Common Lisp) distinguish between compile time and run time and provide constructs (eval-when
) to evaluate
expressions early. The simplest dynamic languages (such as Scheme) process input in a single pass and do not distinguish between
compile time and run time. If we evaluated the above function in such a simple language, widget
and gadget
would be evaluated at the time the function is called.
Challenges
ECMAScript is a scripting language. Many programmers wish to write ECMAScript scripts embedded in web pages that work in a variety of environments. Some of these environments may provide libraries that a script would like to use, while on other environments the script may have to emulate those libraries. Let’s take a look at an example of something one would expect to be able to easily do in a scripting language:
Bob is writing a script for a web page that wants to take advantage of an optional package MacPack
that is
present on some environments (Macintoshes) but not on others. MacPack
provides a class HyperWindoid
from which Bob wants to subclass his own class BobWindoid
. On other platforms Bob has to define an emulation
class BobWindoid
' that is implemented differently from BobWindoid
— it has a different set of private
methods and fields. There also is a class WindoidGuide
in Bob’s package; the code and method signatures of classes
BobWindoid
and BobWindoid
' refer to objects of type WindoidGuide
, and class WindoidGuide
’s
code refers to objects of type BobWindoid
(or BobWindoid
' as appropriate).
Were ECMAScript to use a dynamic execution model (described below), declarations take effect only when executed, and Bob can implement his package as shown below.
class WindoidGuide; // forward declaration if (onMac()) { import "MacPack"; global class BobWindoid extends HyperWindoid { private var x; var g:WindoidGuide; private function speck() {...}; public function zoom(a:WindoidGuide, uncle:HyperWindoid = null):WindoidGuide {...}; } } else { // emulation class BobWindoid' global class BobWindoid { private var i:Integer, j:Integer; var g:WindoidGuide; private function advertise(h:WindoidGuide):WindoidGuide {...}; private function subscribe(h:WindoidGuide):WindoidGuide {...}; public function zoom(a:WindoidGuide):WindoidGuide {...}; } } class WindoidGuide { var currentWindoid:BobWindoid; function introduce(arg:BobWindoid):BobWindoid {...}; }
On the other hand, if the language were static (meaning that types are compile-time expressions), Bob would run into problems.
How could he declare the two alternatives for the class BobWindoid
?
Bob’s first thought was to split his package into three HTML SCRIPT tags (containing BobWindoid
,
BobWindoid
', and WindoidGuide
) and turn one of the first two off depending on the platform. Unfortunately
this doesn’t work because he gets type errors if he separates the definition of class BobWindoid
(or BobWindoid
')
from the definition of WindoidGuide
because these classes mutually refer to each other. Furthermore, Bob would
like to share the script among many pages, so he’d like to have the entire script in a single BobUtilities.js file.
Note that this problem would be newly introduced by ECMAScript 4 if it were to evaluate type expressions at compile time. ECMAScript 3 does not suffer from this problem because it does not have a concept of evaluating an expression at compile time, and it is relatively easy to conditionally define a class (which is merely a function) by declaring a single global variable g and conditionally assigning either one or another anonymous function to it.
There exist other alternatives in between the dynamic execution model and the static model that also solve Bob’s problem. One of them is described at the end of this chapter.
Dynamic Execution Model
In a pure dynamic execution model the entire program is processed in one pass. Declarations take effect only when they are executed. A declaration that is never executed is ignored. Scheme follows this model, as did early versions of Visual Basic.
The dynamic execution model considerably simplifies the language and allows an interpreter to treat programs read from a file identically to programs typed in via an interactive console. Also, a dynamic execution model interpreter or just-in-time compiler may start to execute a script even before it has finished downloading all of it.
One of the most significant advantages of the dynamic execution model is that it allows ECMAScript 4 scripts to turn parts of themselves on and off based on dynamically obtained information. For example, a script or library could define additional functions and classes if it runs on an environment that provides a CSS unit arithmetic library while still working on environments that do not.
The dynamic execution model requires identifiers naming functions and variables to be defined before they are used. A
use occurs when an identifier is read, written, or called, at which point that identifier is resolved to a variable or a function
according to the scoping rules. A reference from within a control statement such as if
and while
located outside a function is resolved only when execution reaches the reference. References from within the body of a function
are resolved only after the function is called; for efficiency, an implementation is allowed to resolve all references within
a function or method that does not contain eval
at the first time the function is called.
According to these rules, the following program is correct and would print 7
:
function f(a:Integer):Integer { return a+b; } var b:Integer = 4; print(f(3));
Assuming that variable b
is predefined by the host if featurePresent
is true, this program would
also work:
function f(a:Integer):Integer { return a+b; } if (!featurePresent) { var b:Integer = 4; } print(f(3));
On the other hand, the following program would produce an error because f
is referenced before it is defined:
print(f(3)); function f(a:Integer):Integer { return a*2; }
Defining mutually recursive functions is not a problem as long as one defines all of them before calling them.
Hybrid Execution Model
ECMAScript 3 does not follow the pure dynamic execution model, and, for reasons of compatibility, ECMAScript 4 strays from that model as well, adopting a hybrid execution model instead. Specifically, ECMAScript 4 inherits the following static execution model aspects from ECMAScript 3:
- Variable declarations of variables at the global scope cause the variables to be created at the time the program is entered rather than at the time the declaractions are evaluated.
- Variable declarations of local variables inside a function cause the variables to be created at the time the function is entered rather than at the time the declaractions are evaluated.
- Unless specified otherwise, declarations of functions at the global scope cause the functions to be created at the time the program is entered rather than at the time the declaractions are evaluated.
- Unless specified otherwise, declarations of functions nested inside a function cause the functions to be created at the time the function is entered rather than at the time the declaractions are evaluated.
In addition to the above, the evaluation of class declarations has special provisions for delayed evaluation to allow mutually-referencing classes.
The second condition above allows the following program to work in ECMAScript 4:
const b:String = "Bee"; function square(a:Integer):Integer { b = a; // Refers to local b defined below, not global b return b*a; var b:Integer; }
While allowed, using variables ahead of declaring them, such as in the above example, is considered bad style and may generate a warning.
The third condition above makes the last example from the pure execution model section work:
print(f(3)); function f(a:Integer):Integer { return a*2; }
Again, actually calling a function at the top level before declaring it is considered bad style and may generate a warning. It also will not work with classes.
Compiling The Dynamic Execution Model
Perhaps the easiest way to compile a script under the dynamic execution model is to accumulate function definitions unprocessed and compile them only when they are first called. Many JITs do this anyway because this lets them avoid the overhead of compiling functions that are never called. This process does not impose any more of an overhead than the static model would because under the static model the compiler would need to either scan the source code twice or save all of it unprocessed during the first pass for processing in the second pass.
Compiling a dynamic execution model script off-line also does not present special difficulties as long as eval
is
restricted to not introduce additional declarations that shadow existing ones (if eval
is allowed to do this,
it would present problems for any execution model, including the static one). Under the dynamic execution model, once
the compiler has reached the end of a scope it can assume that that scope is complete; at that point all identifiers inside
that scope can be resolved to the same extent that they would be in the static model.
Conditional Compilation Alternative
Bob’s problem could also be solved by using conditional compilation similar in spirit to C’s preprocessor. If we do this, we have to ask about how expressive the conditional compilation meta-language should be. C’s preprocessor is too weak. In ECMAScript applications we’d often find that we need the full power of ECMAScript so that we can inspect the DOM, the environment, etc. when deciding how to control compilation. Besides, using ECMAScript as the meta-language would reduce the number of languages that a programmer would have to learn.
Here’s one sketch of how this could be done:
- Execution of a script occurs in a series of passes. The last one is called run time. It is preceded by a compile time pass, which is preceded (if necessary) by a compile compile time pass, which is preceded (if necessary) by a compile compile compile time pass, etc. Having anything beyond a compile compile time pass is very rare, but rapid-development tools could generate such code.
- All definitions made in a previous pass (such as compile time) are also visible in a later pass (such as run time).
If we define a type to also be a function that casts its argument to it type, this allows an implementation to not worry
about whether
(x)(y)
is a function call of functionx
or a cast ofy
to typex
. - TypeExpressions are evaluated at compile time. These appear as types of variables, constants, and fields; argument and result types of functions and methods; and superclasses.
- Declarations are accumulated at compile time. Variables are declared, but variable initializers are not evaluated. Functions and classes are declared and defined at compile time.
- One can lift a Block from run time to compile time by preceding the block with a
#
symbol. For example,#{var x:int = 3}
defines a compile-time constant x and initializes it to 3. One can also lift avar
,const
, orfunction
declaration directly by preceding it with a#
symbol, so#var x:int = 3;
would accomplish the same thing. - Any TypeExpressions in blocks or statements lifted to compile time are evaluated at compile
compile time.
int
in the preceding example is such a TypeExpression. - Any block or statement that is lifted twice (as in
#{#var x:int = 3}
) is evaluated at compile compile time, and so forth. - The compile time pass can selectively exclude source code statements from the run time pass using the construct below.
If the first Expression is true, the first Statements list
is included in the run time pass. Otherwise, if the second Expression is true, the second
Statements list is included in the run time pass, and so on.
#
if
(
Expression)
Statements [#
else
if
(
Expression)
Statements] ... [#
else
Statements]#
end
if
- Unlike in C, the Statements above must have balanced parentheses, braces, brackets, etc.
Also, unlike in C, preprocessing is not line-oriented;
#
’s can appear anywhere on a line. - The compile compile time pass can use
#if
to conditionally exclude compile time code, etc.
Note that because variable initializers are not evaluated at compile time, one has to use #var a = int
rather
than var a = int
to define an alias a
for a type name int
.
This sketch does not address many issues that would have to be resolved, such as how typed variables are handled after they are declared but before they are initialized (this problem doesn’t arise in the dynamic execution model), how the lexical scopes of the run time pass would interact with scoping of the compile time pass, etc.
Comparing the Dynamic Execution Model with Conditional Compilation
Both approaches solve Bob’s problem, but they differ in other areas. In the sequel "conditional compilation" refers to the conditional compilation alternative described above.
- The dynamic execution model is simpler to describe and analyze.
- The dynamic execution model is easier to learn for non-programmers.
- Conditional compilation is more familar to C/C++ programmers.
- Conditional compilation requires either a control meta-language or facilities for lifting execution from run to compile time. These often have unintended consequences (compile-time evaluation does not follow the same scopes as run-time evaluation) and have been a significant source of problems in Common Lisp.
- Conditional compilation can more faithfully emulate ECMAScript 3 semantics because it allows a function to be called at the top level before it is defined.
- Conditional compilation cannot guarantee that variables’ initializers have been run before the variables are referenced. The dynamic execution model guarantees that.
- Conditional compilation does not allow one to use general prototype-based types because prototypes are often calculated at run time. The dynamic execution model allows these.
- Conditional compilation does not support functors and templates without significant additional syntax. The dynamic execution model naturally extends to these.
Compiler Blocks
Another alternative execution model briefly considered but rejected is the idea of allowing compiler blocks. A compiler block has the syntax:
compile
{
Statement ... Statement }
The compile attribute is a hint that the block may be (but does not have to be) evaluated early. The statements inside this block should depend only on each other, on the results of earlier compiler blocks, and on properties of the environment that are designated as being available early. Other than perhaps being evaluated early, compiler blocks respect all of the scope rules and semantics of the enclosing program. Any definitions introduced by a compiler block are saved and reintroduced at normal evaluation time. On the other hand, side effects may or may not be reintroduced at normal evaluation time, so compiler blocks should not rely on side effects.
compile
is an attribute, so it may also be applied to individual definitions without
enclosing them in a block.
As an example, after defining
compile var x = 2; function f1() { compile { var y = 5; var x = 1; while (y) x *= y--; } return ++x; } function f2() { compile { var y = x; } return x+y; }
the value of global x
will still be 2
, calling f1()
will always return 121
,
and calling f2()
will return 4
. If the statement x=5
is then evaluated at the global
level, f1()
will still return 121
because it uses its own local x
. On the other hand,
calling f2()
may return either 7
or 10
at the implementation’s discretion — 7
if the implementation evaluated the compile
block early and saved the value of y
or 10
if it didn’t. As this example illustrates, it is poor technique to define variables inside compiler blocks;
constants are usually better.
A fully dynamic implementation of ECMAScript 4 may choose to ignore the compile
attribute
and evaluate all compiler blocks at normal evaluation time. A fully static implementation may require that all user-defined
types and attributes be defined inside compiler blocks.
Should const
definitions with simple constant expressions such as const four = 2+2
be treated as though they were implicitly compiler definitions (compile const four = 2+2
)?
ECMAScript 4 Netscape Proposal
Rationale
Member Lookup
|
Friday, September 20, 2002
This page is somewhat out of date.
Introduction
There have been much discussion in the TC39 subgroup about the meaning of a member lookup operation. Numerous considerations intersect here.
We will express a general unqualified member lookup operation as a.
b, where a
is an expression and b is an identifier. We will also consider qualified member lookup operations and write them
as a.
n::
b, where n is an expression that evaluates to
some namespace. In almost all cases we will be interested in the dynamic type Td of a. In one scheme
we will also consider the static type Ts of the expression a. If the language is sound, we will always
have Td Ts.
In the simplest approach, we treat an object as merely an association table of member names and member values. In this
interpretation we simply look inside object a and check if there is a member named b. If there is, we return the
member’s value; if not, we return undefined
or signal an error.
There are a number of difficulties with this simple approach, and most object-oriented languages have not adopted it:
- Members cannot be made
private
orinternal
. - Accidental clashes can occur when programming in the large.
Once we allow private
or internal
members, we must allow for the possibility that object a
will have more than one member named b — abstraction considerations require that users of a class C
not be aware of expose C’s private members, so, in particular, a user should be able to create a subclass D
of C and add members to D without knowing the names of C’s private members. Both C++ and
Java allow this. We must also allow for the possibility that object a will have a member named b but
we are not allowed to access it. We will assume that access control is specified by lexical scoping, as is traditional in
modern languages.
Desirable Criteria
Some of the criteria we would like the member lookup model to satisfy are:
- Safety. The lookup does not permit access to a
private
member outside the class where the member is defined, nor does it allow access to aninternal
member outside the package where the member is defined. Furthermore, if a class C accesses its private member m, a hostile subclass D of C cannot silently substitute a member m' that would masquerade as m inside C’s code. - Abstraction.
private
andinternal
members are invisible outside their respective classes or packages. For programming in the large, a class can provide severalpublic
versions to its importers, andpublic
members of more recent versions are invisible to importers of older versions. This is needed to provide robust libraries. - Robustness. We can make any of the following program changes without having to restructure
the program:
- Add valid type annotations to variables and functions.
- Change a member’s visibility to
private
,internal
, orpublic
, assuming, of course, that that member is not used outside its new visibility. - Split a complicated expression statement e into several statements that compute subexpressions of e, store them in local variables, and then combine them to compute e. We should be able to do this without intimate knowledge of what e does or calls.
- Rename a member to a different name, assuming, of course, that the new name does not cause conflicts and that we fix up all references to that member.
- Namespace independence. If one class C has a member named m, this should not place restrictions on an unrelated class D having an unrelated member with the same name m.
- Compatibility. An ECMAScript 4 class should be usable from ECMAScript 3 code and ECMAScript 3 code minimally upgraded to ECMAScript 4 without having to restructure the latter code. Achieving compatibility should not require the ECMAScript 4 class itself to be restructured or give up any of the other desirable criteria. Code without type annotations works as expected.
Lookup Models
There are three main competing models for performing a general unqualified member lookup operation as a.
b.
Let S be the set of members named b of the object obtained by evaluating expression a (hereafter
shortened to just "object a") that are accessible via the namespace rules
applied in the lexical scope where a.
b is evaluated. All three models pick some member
s S. Clearly, if the set S
is empty, then the member lookup fails. In addition, the Spice and pure Static models may sometimes deliberately fail even
when set S is not empty. Except for such deliberate failures, if the set S contains only one member
s, all three models return that element s. If the set S contains multiple members, the three
models will likely choose different members.
Another interesting (and useful) tidbit is that the Static and Dynamic models always agree on the interpretation of member
lookup operations of the form this.
b. All three models agree on on the interpretation of member lookup
operations of the form this.
b in the case where b is a member defined in the current class.
A note about overriding: When a subclass D overrides a member m of its superclass C, then the definition of the member m is conceptually replaced in all instances of D. However, the three models are only concerned with the topmost class in which member m is declared. All three models handle overriding the way one would expect of an object-oriented language. They differ in the cases where class C has a member named m, subclass D of C has a member with the same name m, but D’s m does not override C’s m because C’s m is not visible inside D (it’s not well known, but such non-overriding does and must happen in C++ and Java as well).
Static Model
In the Static model we look at the static type Ts of expression a. Let S1 be the subset of S whose class is either Ts or one of Ts’s ancestors. We pick the member in S1 with the most derived class.
The pure static model above is implemented by Java and C++. It would not work well in that form in ECMAScript because many,
if not most, expressions have type Any
. Because type Any
has no members, users would have to cast
expression a to a given type T before they could access members of type T. Because of this
we must extend the static model to handle the case where the subset S1 is empty, or, in other words, the static
lookup fails. (Rather than doing this, we could extend the static model in the case where the static type Ts is
some special type, but then we would have to decide which types are special and which ones are not. Any
is clearly
special. What about Object
? What about Array
? It’s hard to draw the line consistently.)
In whichever cases way we extend the static model, we also have a choice of which member we choose. We could back off to the dynamic model, we could choose the most derived member in S, or perhaps we could choose some other approach.
Constraints:
Safety | Good within the pure static model. Problems in the extended static model (a subclass could silently shadow a member) that could perhaps be addressed by warnings. |
Abstraction | Good. |
Robustness | Very bad. Updating a function’s or global variable return type silently changes the meaning of all code that uses that function or global variable; in a large project such a change would be quite difficult. Difficult to correctly split expressions into subexpressions. |
Namespace independence | Good. |
Compatibility | Bad within the pure static model (type casts needed everywhere). May be good in the extended static model, depending on the choice of how we extend it. |
Other |
This model may be difficult to compile well because the compiler may have difficulty in determining the intermediate types in compound expressions. Languages based on the static model have traditionally been compiled off-line, and such compilers tend to be difficult to write for on-line compilation without requiring the programmer to predeclare all of his data structures (if there are any forward-referenced ones, then the compiler doesn’t know whether they should have a type or not). A more dynamic execution model may actually help because it defers compilation until more information is known. |
Spice Model
In the Spice model we think of each member m defined in a class C as though it were a function definition for a (possibly overloaded) function whose first argument has type C. Definitions in an inner lexical scope shadow definitions in outer scopes. The Spice model does not consider the static type Ts of expression a.
Let L be the innermost lexical scope enclosing the member lookup expression a.
b
such that some member named b is defined in L. Let Lb be the set of all members named b
defined in lexical scope L, and let S1 = S Lb
(the intersection of S and Lb). If S1 is empty, we fail. If S1 contains exactly
one member s, we use s. If S1 contains several members, we fail (this would only happen for
import conflicts).
Constraints:
Safety | Good. |
Abstraction | Good. |
Robustness | Poor. Renaming an internal member may break code outside the class that defines that member
even if that code does not access that member. Converting a member from private to one of the other two
visibilities also can introduce conflicts in other, unrelated classes in the same package that just happen to have an
unrelated member with the same name. Fortunately these conflicts usually (but not always) result in errors rather than
silent changes to the meaning of the program, so one can often find them by exhaustively testing the program after making
a change. |
Namespace independence | Bad. Members with the same name in unrelated classes often conflict. |
Compatibility | Poor? Many existing programs rely on namespace independence and would have to be restructured. |
Other |
Most object-oriented programmers would be confused by a violation of namespace independence. Programming without this assumption requires a different point of view than most programmers are used to. (I am not talking about Lisp and Self programmers, who are familiar with that way of thinking.) |
[There are numerous other variants of the Spice model as well.]
Dynamic Model
In the Dynamic model we pick the member s in S defined in the innermost lexical scope L
enclosing the member lookup expression a.
b. We fail if the innermost such lexical
scope L contains more than one member in S (this would only happen for import conflicts).
Constraints:
Safety | Good at the language level, but see "other" below. |
Abstraction | Good. |
Robustness | Good. All of these changes are easy to do. |
Namespace independence | Good. |
Compatibility | Good. |
Other |
Packages using the dynamic model may be vulnerable to hijacking (coerced into doing something other than what the author intended) by a determined intruder. It is possible for a compiler to detect such vulnerabilities and warn about them. |
Namespaces
The various models make it possible to get into situations where either there is no way to access a visible member of an
object or it is not safe to do so (see member hijacking). In these cases we’d like to be able to
explicitly choose one of several potential members with the same name. The ::
namespace syntax allows this. The
left operand of ::
is an expression that evaluates to a package or class; we may also allow special keywords
such as public
, internal
, or private
instead of an expression here, or omit the expression
altogether. The right operand of ::
is a name. The result is the name qualified by the namespace.
As we have seen, the name b in a member access expression a.
b does not necessarily
refer to a unique accessible member of object a. In a qualified member access expression a.
n::
b,
the namespace n narrows the set of members considered, although it’s possible that the set may still contain more
than one member, in which case the lookup model again disambiguates. Let S be the set of members named b
of object a that are accessible. The following table shows how a.
n::
b
subsets set S depending on n:
n | Subset |
---|---|
None | Only the dynamic member named b, if any exists |
A class C | The fixed member of C named b, if it exists; if not, try C’s superclass instead, and so on up the chain |
A package P | The subset of S containing all accessible members of P |
private |
The fixed member named b of the current class |
internal |
The subset of S containing all accessible members that have package (internal )
visibility |
public |
The subset of S containing all accessible members that have public visibility |
The ::
operator serves a different role from the .
operator. The ::
operator produces
a qualified name, while the .
operator produces a value. A qualified name can be used as
the right operand of .
; a value cannot. If a qualified name is used in a place where a value is expected, the
qualified name is looked up using the lexical scoping rules to obtain the value (most likely a global variable).
Dynamic Members
All of the models above address only access to fixed properties of a class.
ECMAScript also allows one to dynamically add properties to individual instances of a class. For simplicity we do not provide
access control or versioning on these dynamic properties — all of them are
public and open to everyone. Because of the safety criterion, a member lookup of a private
or internal
member must choose the private
or internal
member even if there is a dynamic
member of the same name. To satisfy the robustness criterion, we should treat public
members as similarly as possible to private
or internal
members, so we always give preference to
a fixed property when there is a dynamic property of the same name.
To access a dynamic property that is shadowed by a fixed property, we can either prefix the member’s name with ::
or use an indirect property access.
Indirect Member Access
How should we define the behavior of the expression a[
b]
(assuming a’s
class is not a typed array or other class that overrides the default meaning of the []
operator)? There are a
couple of possibilities:
- We could evaluate the expression b to some string
"
s"
and treat a[
b]
as though it were a.
s. This is essentially what ECMAScript 3 does. Unfortunately it’s hard to keep this behavior consistent with ECMAScript 3 programs’ expectations (they expect no more than one member with the same name, etc.), and this kind of indirection is also vulnerable to hijacking. It may be possible to solve the hijacking problem by devising restricted variants of the[]
operator such as a.
n::[
b]
that follow the rules given in the namespaces section above. - We could evaluate the expression b to some string
"
s"
and treat a[
b]
as though it were a.::
s, thus limiting our selection to dynamic members. Dynamic members are well-behaved, but this kind of behavior would violate the compatibility criterion when ECMAScript 3 scripts try to reflect an ECMAScript 4 object using the[]
operator.
In general it seems like it would be a bad idea to extend the syntax of the string "
s"
to allow ::
operators inside the string. Such strings are too easily forged to play the role of pointers to members.
Member Hijacking
[explain security attacks]
ECMAScript 4 Netscape Proposal
Rationale
Versioning
|
Tuesday, October 9, 2001
Motivation
As a package evolves over time it often becomes necessary to change its exported interface. Most of these changes involve adding definitions (top-level or class members), although occasionally a definition may be deleted or renamed. In a monolithic environment where all ECMAScript source code comes preassembled from the same source, this is not a problem. On the other hand, if packages are dynamically linked from several sources then versioning problems are likely to arise.
One of the most common avoidable problems is collision of definitions. Unless we solve this problem, an author of a library will not be able to add even one definition in a future version of his library because that definition’s name could already be in use by some client or some other library that a client also links with. This problem occurs both in the global scope and in the scopes of classes from which clients are allowed to inherit.
Example
Here’s an example of how such a collision can arise. Suppose that a library provider creates a library called BitTracker
that exports a class Data
. This library becomes so successful that it is bundled with all web browsers produced
by the BrowsersRUs company:
package BitTracker; public class Data { public field author; public field contents; function save() {...} }; function store(d) { ... storeOnFastDisk(d); }
Now someone else writes a web page W that takes advantage of BitTracker
. The class Picture
derives from Data
and adds, among other things, a method called size
that returns the dimensions
of the picture:
import BitTracker; class Picture extends Data { public method size() {...} field palette; }; function orientation(d) { if (d.size().h >= d.size().v) return "Landscape"; else return "Portrait"; }
The author of the BitTracker
library, who hasn’t seen W, decides in response to customer requests
to add a method called size
that returns the number of bytes of data in a Data
object. He then releases
the new and improved BitTracker
library. BrowsersRUs includes this library with its latest NavigatorForInternetComputing
17.0 browser:
package BitTracker; public class Data { public field author; public field contents; public method size() {...} function save() {...} }; function store(d) { ... if (d.size() > limit) storeOnSlowDisk(d); else storeOnFastDisk(d); }
An unsuspecting user U upgrades his old BrowsersRUs browser to the latest NavigatorForInternetComputing 17.0
browser and a week later is dismayed to find that page W doesn’t work anymore. U’s granddaughter Alyssa
P. Hacker tries to explain to U that he’s experiencing a name conflict on the size
methods, but U
has no idea what she is talking about. U attempts to contact the author of W, but she has moved on to
other pursuits and is on a self-discovery mission to sub-Saharan Africa. Now U is steaming at BrowsersRUs, which
in turn is pointing its finger at the author of BitTracker
.
Solutions
How could the author of BitTracker
have avoided this problem? Simply choosing a name other than size
wouldn’t work, because there could be some other page W2 that conflicts with the new name. There are several possible
approaches:
- Naming conventions. We could require each defined name to be prefixed by the full name of the party from which
this definition originates. Unfortunately, this would get tedious and unnecessarily impact casual uses of the language. Furthermore,
this approach is impractical for the names of methods because it is often desirable to share the same method name across
several classes to attain polymorphism; this would not be possible if Netscape’s objects all used the
com_netscape_length
method while MIT’s objects used theedu_mit_length
method. - Explicit imports. We could require each client package to import every external name it references. This works reasonably well for global names but becomes tedious for the names of class members, which would have to be imported separately for each class. Alternatives exist for bulk importing members of a class, but they are somewhat complicated and do not work for interfaces or multiple inheritance.
- Versions. We could require package authors to mark the names they export with explicit versions. A package’s developer could introduce a new version of the package with additional names as long as those names were made invisible to prior versions.
The last approach appears to be the most desirable because it places the smallest burden on casual users of the language, who merely have to import the packages they use and supply the current version numbers in the import statements. A package author has to be careful not to disturb the set of visible prior-version definitions when releasing an updated package, but authors of dynamically linkable packages are assumed to be more sophisticated users of the language and could be supplied with tools to automatically check updated packages’ consistency.
Versioning in ECMAScript 4
ECMAScript 4 employs namespaces to provide safe versioning. A package can export several namespaces, each of which provides a different view of the package’s contents. Each namespace corresponds to a version of the package’s API.
Terminology
A version describes the API of a package. A release refers to the entirety of a package, including its code. One release can export many versions of its API. A package developer should make sure that multiple releases of a package that export version V export exactly the same set of definitions in version V.
Example
As an example, suppose that a developer wrote a sorting package Sorter
with functions sort
and merge
that called bubble sort in the initial version. In the next release the developer adds a function called stablesort
and includes it in version V2
. In a subsequent release V3
, the developer changes the sort
algorithm to a quicksort that calls stablesort
as a subroutine and adds the permute
function. That
last release of the package might look like:
package Sorter { explicit namespace V2; explicit namespace V3; internal const V2and3 = V2 V3; public var serialNumber; public function sort(compare: Function, array: Array):Array {...} public function merge(compare: Function, array1: Array, array2: Array):Array {...} V2and3 function stablesort(compare: Function, array: Array):Array {...} V3 function permute(array: Array):Array {...} }
Suppose, further, that client packages C1 and C2
both import Sorter
. There is only one instance of Sorter
running — the latest release.
By default, both C1 and C2 see only Sorter
’s
original API. However, suppose that C2 is aware that Sorter
has been
extended and would like to also use some of its newer functionality. To do this, C2
evaluates the magic incantation
use namespace(Sorter.V2);
after it imports Sorter
. This enables C2 to also see the stablesort
function. Note that, in this example, both clients see the same sort
and merge
functions and the
same serialNumber
variable (in particular, if C1 wrote to serialNumber
, then C2
would see the updated value), but only C2 can see the stablesort
function. Both clients get the quicksort
release of sort
. If client package C1 defined its own stablesort
function, then that
function would not conflict with Sorter
’s stablesort
; furthermore, Sorter
’s sort
would still refer to Sorter
’s stablesort
in its internal subroutine call.
Had only the first release of Sorter
been available, client C2 would
obtain an error because Sorter.V2
would be undefined. Client C1 could
run normally, although the sort
function it calls would use bubble sort instead of the quicksort.
The example above illustrates versioning as it applies to a package’s globals. The same techniques can be used to add members to existing classes, and there versioning is much more useful.
ECMAScript 4 Netscape Proposal
Rationale
Named Function Parameters
|
Wednesday, January 29, 2003
Named function parameters had originally been part of the ECMAScript 4 proposal but were deferred to a future version of the language in order to keep ECMAScript 4 small. If implemented, named function parameters might behave as described in this section.
Overview
The named function parameter extension allows some function parameters to be passed by name instead of by position. Parameter names are simple strings. A function with three positional (one of which is optional) and four named parameters might be declared as:
function f(a: Number, b: Boolean, c: Number = 5, named x: Integer = 7, named y = null, named z = 34, named t = undefined)
Such a function can then be invoked as follows:
f(2, true, y: 5); f(2, true, 8, z: 32, x: 9);
Named parameters are always optional and must include default values. A function call may specify positional arguments followed by named arguments. Positional parameters can only match positional arguments. Named parameters can only match named arguments. The same parameter may not be both positional and named.
The following sections explain the changes to add named function parameters in more detail.
Lexer
A new non-reserved keyword named named
is added. This keyword is a valid identifier:
Function Parameter Lists
A function may take zero or more positional parameters followed by either an optional rest parameter followed by zero or more named parameters or a named rest parameter:
Individual parameters have the forms:
named
NamedParameterCoreIf a Parameter is followed by a =
, then that parameter is optional.
If a function call does not provide an argument for an optional parameter, then that parameter is set to the value of its
AssignmentExpression, implicitly
coerced to the parameter’s type if necessary.
If a parameter is prefixed with named
, then the parameter is matched by name rather than by position. Named
parameters are always optional and must have initializers.
Attempting to define a function with two different parameters with the same name is an error.
A function with named parameters is never unchecked.
Rest Parameter
If the ...
is present, the function accepts arguments not matched by any of the other listed parameters. If
a parameter is given after the ...
, then that parameter’s identifier is bound to an array of all remaining
arguments. That identifier is declared as a local const
using the parameter’s type, which defaults to Array
.
If the rest parameter is named
, then the parameter’s type is always Array
and cannot be specified
explicitly.
The remaining positional arguments are stored as elements of the rest array with numeric indices starting from 0. If the
rest parameter is not named
, then there must be no remaining named arguments. Otherwise, extra named arguments
are allowed and are stored as named properties of the rest array.
Call Processing
When a function is called, the following list indicates the order of evaluation of the various expressions in a FunctionDefinition. These steps are taken only after all of the argument names and values have been evaluated.
- If the function is unchecked, bind the
arguments
local variable to an array of all arguments and their names. - Get the saved type t that was the result of evaluating the first parameter’s TypeExpression at the time the function was defined.
- If the first parameter is required and no positional argument has been supplied for it, then raise an error unless the
function is unchecked, in which case let
undefined
be the first parameter’s value. - If the first parameter is optional and there is a positional argument remaining, use the value of the positional argument. If there are no remaining positional arguments, then evaluate the first parameter’s AssignmentExpression and let it be the first parameter’s value.
- If the first parameter is named and there is a named argument with a matching argument name, then use the value of that named argument. Otherwise, evaluate the first parameter’s AssignmentExpression and let it be the first parameter’s value.
- Implicitly coerce the argument (or default) value to type t and bind the parameter’s Identifier to the result.
- Repeat steps 2-6 for each additional required, optional, and named parameter.
- If there is a RestParameter and one or more of the remaining arguments is named, raise an error.
- If there is a RestParameter with an Identifier, bind that Identifier to an array of the remaining positional arguments using indices starting from 0.
- If there is a NamedRestParameter, bind its Identifier to an array of the remaining positional arguments using indices starting from 0 as well as the remaining named arguments stored using named properties.
- If there is no RestParameter or NamedRestParameter and any arguments remain, raise an error unless the function is unchecked.
- Evaluate the body.
- Get the saved type r that was the result of evaluating the result TypeExpression at the time the function was defined.
- Implicitly coerce the result to type r and return it.
Function Calls
The a[
args]
and f(
args)
indexing and function call operators are extended to allow named arguments:
A list of arguments can contain both positional and named arguments. The positional arguments are the subexpressions separated by commas within the ListExpressionallowIn. Named arguments use the same syntax as object literals. All strings except those consisting entirely of digits are legal for argument names. No two arguments may have the same name. Named arguments must follow positional arguments, but otherwise the order of named arguments is immaterial.
ECMAScript 4 Netscape Proposal
Rationale
Operators
|
Thursday, May 22, 2003
User-overridable operators had originally been part of the ECMAScript 4 proposal but were deferred to a future version of the language in order to keep ECMAScript 4 small. If implemented, operator overriding might behave as described in this section.
Overview
ECMAScript 4 provides a number of operators such as such as +
, *
, a[
b]
,
and -=
. These operators are dispatched based on the types of their arguments. Some operators are dispatched only
on one operand, in which case the dispatch behaves like property lookup. Other operators are dispatched on two operands simultaneously.
Finally, some operators are syntactic sugars for combinations of existing operators.
There are predefined definitions for all ECMAScript operators. In addition, a class may provide additional definitions by overriding the existing behavior. Such overrides apply only when at least one of the dispatched operands is an instance of that class or its descendants. Operator overriding is useful, for example, to implement units without having to add units to the core of the ECMAScript 4 language.
This section presents the expansions of operators in ECMAScript source code, the built-in definitions of those operators, and the means of overriding them.
Invoking Operators
When an operator is invoked in an expression, the corresponding operator is invoked according to the table below. The operator invocations use pseudo-syntax to denote which operator is called; only the expression syntax on the left side of the table can be used to invoke operators.
Expression | Operator Invocation |
---|---|
+ x |
operator "+"( x) |
- x |
operator "-"( x) |
~ x |
operator "~"( x) |
++ x |
( x = operator "++"( x)) |
x++ |
( = x, x = operator "++"( x), ) ,
where is a temporary variable |
-- x |
( x = operator "--"( x)) |
x-- |
( = x, x = operator "--"( x), ) ,
where is a temporary variable |
x( a1, ..., an) |
operator "()"( x, a1, ..., an) |
new x |
operator "new"( x) |
new x( a1, ..., an) |
operator "new"( x, a1, ..., an) |
x[ a1, ..., an] |
operator "[]"( x, a1, ..., an) |
x[ a1, ..., an] = y |
operator "[]="( x, y, a1, ..., an) |
delete x[ a1, ..., an] |
operator "delete[]"( x, a1, ..., an) |
x + y |
operator "+"( x, y) |
x - y |
operator "-"( x, y) |
x * y |
operator "*"( x, y) |
x / y |
operator "/"( x, y) |
x % y |
operator "%"( x, y) |
x << y |
operator "<<"( x, y) |
x >> y |
operator ">>"( x, y) |
x >>> y |
operator ">>>"( x, y) |
x < y |
operator "<"( x, y) |
x > y |
operator "<"( y, x) |
x <= y |
operator "<="( x, y) |
x >= y |
operator "<="( y, x) |
x in y |
operator "in"( x, y) |
x == y |
operator "=="( x, y) |
x != y |
!( operator "=="( x, y)) |
x === y |
operator "==="( x, y) |
x !== y |
!( operator "==="( x, y)) |
x & y |
operator "&"( x, y) |
x ^ y |
operator "^"( x, y) |
x | y |
operator "|"( x, y) |
x += y |
( x = operator "+"( x, y)) |
x -= y |
( x = operator "-"( x, y)) |
x *= y |
( x = operator "*"( x, y)) |
x /= y |
( x = operator "/"( x, y)) |
x %= y |
( x = operator "%"( x, y)) |
x <<= y |
( x = operator "<<"( x, y)) |
x >>= y |
( x = operator ">>"( x, y)) |
x >>>= y |
( x = operator ">>>"( x, y)) |
x &= y |
( x = operator "&"( x, y)) |
x ^= y |
( x = operator "^"( x, y)) |
x |= y |
( x = operator "|"( x, y)) |
Operator Dispatch
Unary operators are single-dispatched just like regular methods. in
is like a unary operator in that it can
only be overridden by its right operand’s class, and it is single-dispatched by its right operand. Like in regular method
dispatch, null
is considered to be a member of only the types Null
and Object
.
Binary operator methods are double-dispatched based on the types of both operands. When one of the binary operators
is invoked in an expression a b,
all applicable operator "
"(
x,
y)
methods are examined to find the most specific one, which is then called. A definition of operator "
"(
x:
X,
y:
Y)
is applicable to a b
if the value of a is a member of X and the value of b is
a member of Y, except that, like in regular method dispatch, null
is considered to be a member of only the types Null
and Object
. A definition of operator "
"(
x:
X,
y:
Y)
is most specific if it is applicable and if every applicable definition of operator "
"(
x:
X',
y:
Y')
satisfies X X' and Y
Y'. If there is no most specific applicable definition of
or if there is more than one most specific applicable definition of ,
then an error occurs when the expression a b
is evaluated.
One of the operands of an operator expression inside a class C can be a super
expression, in which case the set of applicable definitions of the operator is restricted to those defined to take a parameter
of type T in that position, where T is any proper ancestor of C. This way a method overriding
an operator can invoke its superclass’s definition of that operator.
Expression Grammar Changes
To accommodate super
expressions, the grammar rules for expressions would be changed as described below.
Super Expressions
The SuperExpression grammar rule is split into two to make FullSuperExpression into a separate rule:
super
, which may only be used inside a class C, can be applied to a subexpression that evaluates
to an instance v of C. That subexpression can be either a ParenExpression
or omitted, in which case it defaults to this
.
As specified in the grammar below, the SuperExpression must be embedded as an operand of one of the following operators:
- The left operand of a
.
(property lookup),[]
(indexing),()
(call),new
, ornew()
operator (for the()
andnew()
operators the SuperExpression must be a FullSuperExpression) - The right operand of an
in
operator - The operand of a unary
+
,-
,~
,++
, or--
operator - Either operand of a binary
+
,-
,*
,/
,%
,<<
,>>
,>>>
,|
,^
,&
,<
,>
,<=
,>=
,==
,!=
,===
, or!==
operator - Either operand of a
+=
,-=
,*=
,/=
,%=
,<<=
,>>=
,>>>=
,|=
,^=
, or&=
operator
super
changes the behavior of the operator in which it is embedded by limiting its property search to definitions
inherited from class C’s superclass. See property lookup and
operator dispatch.
Postfix Expressions
These grammar rules are modified to permit super
expressions as operands of ()
(call), new
,
new()
, and postfix ++
and --
. Unmodified rules are not shown below.
++
--
Unary Operators
These grammar rules are modified to permit super
expressions as operands of unary +
, -
,
and ~
as well as prefix ++
and --
operators.
delete
PostfixExpressionvoid
UnaryExpressiontypeof
UnaryExpression-
NegatedMinLong!
UnaryExpressionMultiplicative Operators
These grammar rules are modified to permit super
expressions as operands of binary *
, /
,
and %
operators.
Additive Operators
These grammar rules are modified to permit super
expressions as operands of binary +
and -
operators.
Bitwise Shift Operators
These grammar rules are modified to permit super
expressions as operands of the <<
, >>
,
and >>>
operators.
Relational Operators
These grammar rules are modified to permit super
expressions as operands of the <
, >
,
<=
, and >=
operators and the right operand of an in
operator.
Equality Operators
These grammar rules are modified to permit super
expressions as operands of the ==
, !=
,
===
, and !==
operators.
Binary Bitwise Operators
These grammar rules are modified to permit super
expressions as operands of the &
, ^
,
and |
operators.
Assignment Operators
These grammar rules are modified to permit super
expressions as operands of the +=
, -=
,
*=
, /=
, %=
, <<=
, >>=
, >>>=
,
|=
, ^=
, and &=
operators.
Built-In Operator Definitions
All operators have built-in definitions. These are summarized in the table below.
Name | Signature | Behavior |
---|---|---|
"()" |
( x:Object, ... args) |
Call x with the arguments args and return the result. |
"new" |
( x:Object, ... args) |
Call x’s constructor slot with the arguments args and return the result. |
"[]" |
( x:Object, n:Object) |
Return the value of x’s most derived public property named n.toString() .
See property lookup. |
"[]=" |
( x:Object, y:Object, n:Object) |
Set the value of x’s most derived public property named n.toString()
to y. |
( x:Array, y:Object, n:Object) |
Set the value of x’s most derived public property named n.toString()
to y. Update the length property as in ECMAScript 3. |
|
"delete[]" |
( x:Object, n:Object) |
Try to delete x’s most derived public property named n.toString() . |
"~" |
( x:Object) |
Return the bitwise complement of ToInt32(operator "+"( x) ). |
"++" |
( x:Object) |
Return operator "+"( operator "+"( x), 1) . |
"--" |
( x:Object) |
Return operator "-"( operator "+"( x), 1) . |
"+" |
( x:Object) |
Return ToNumber(x). |
( x:Object, y:Object) |
Let x' = ToPrimitive(x) and y' = ToPrimitive(y).
If at least one of x' and y' is a string, then return operator "+"( x', y') .
Otherwise return ToNumber(x') + ToNumber(y'). |
|
( x:String, y:Object) |
Return x concatenated with y.toString() . |
|
( x:Object, y:String) |
Return x.toString() concatenated with y. |
|
( x:String, y:String) |
Return x concatenated with y. | |
( x:Number, y:Number) |
Return x + y. | |
"-" |
( x:Object) |
Return -ToNumber(x). |
( x:Object, y:Object) |
Return ToNumber(x) – ToNumber(y). | |
"*" |
( x:Object, y:Object) |
Return ToNumber(x) ToNumber(y). |
"/" |
( x:Object, y:Object) |
Return ToNumber(x) / ToNumber(y). |
"%" |
( x:Object, y:Object) |
Return ToNumber(x) % ToNumber(y). |
"<<" |
( x:Object, y:Object) |
Return ToNumber(x) << ToNumber(y). |
">>" |
( x:Object, y:Object) |
Return ToNumber(x) >> ToNumber(y). |
">>>" |
( x:Object, y:Object) |
Return ToNumber(x) >>> ToNumber(y). |
"<" |
( x:Object, y:Object) |
Same as in ECMAScript 3. |
">=" |
( x:Object, y:Object) |
Same as in ECMAScript 3. |
"in" |
( x:Object, y:Object) |
Same as in ECMAScript 3. |
"==" |
( x:Object, y:Object) |
Same as in ECMAScript 3. |
"===" |
( x:Object, y:Object) |
Same as in ECMAScript 3. |
"&" |
( x:Object, y:Object) |
Return ToNumber(x) & ToNumber(y). |
"^" |
( x:Object, y:Object) |
Return ToNumber(x) ^ ToNumber(y). |
"|" |
( x:Object, y:Object) |
Return ToNumber(x) | ToNumber(y). |
Operator Overriding
An operator can be overridden only inside a class C. That class should define a function with the operator
attribute (operator
is an additional member modifier attribute).
The function’s name must be one of the strings in the table below. The name must be a string rather than an identifier;
the FunctionName grammar rule is extended to allow strings:
A FunctionName can be a String only for operator overrides.
The table also defines the acceptable signatures that the operator can have. At least one parameter must be specified as
taking a value of type C (a type that is a subclass of C is not acceptable). If the operator is ===
,
then both parameters must take values of type C. A class can define several different signatures of the same binary
(but not unary) operator, differing in the choice of parameter types X or Y, which may be omitted if
they are Object
. The parameters denoted as taking types C, X, or Y in the table
below cannot be optional or named parameters. The return type of an operator override can be any type.
Name | Signatures |
---|---|
"~" |
( x: C) |
"+" |
( x: C) ( x: C, y: Y) ( x: X, y: C) ( x: C, y: C)
|
"*" |
( x: C, y: Y) ( x: X, y: C) ( x: C, y: C)
|
"===" |
( x: C, y: C) |
"in" |
( x: X, y: C) |
"()" |
( x: C, a1, ..., an) |
"[]=" |
( x: C, y: Y, a1, ..., an) |
The operator methods should return the result of the operator. For the ++
and --
operators,
the result is the incremented or decremented value. The ()
, new
, []
, delete[]
,
and []=
operators take additional argument lists. If desired, these argument lists can include optional, named,
or rest arguments. The []=
operator must take one more initial required parameter, which is the value stored
in the element.
The >
, >=
, !=
, and !==
operators cannot be overridden directly
because they are syntactic sugars for invocations of the <
, <=
, ==
, and ===
operators; override the latter operators instead. The !
, ||
, ^^
, &&
,
and ?:
operators cannot be overridden directly, but they are affected by any redefinition of toBoolean
.
The reason for not overriding ||
, &&
, and ?:
is that they do not always evaluate
all of their arguments, and programmers often take advantage of this short-circuiting behavior. !
is not overridable
in order to preserve useful identities such as:
if (!
x)
Aelse
Bif (
x)
Belse
A!(
x&&
y)
!
x|| !
y- x
!=
y!(
x==
y)
Definitions of operators cannot specify a namespace. No access restrictions can be imposed on operators.
Example
The following class illustrates overriding a few operators.
class Complex { var x:Number, y:Number; operator function "+"(a:Complex):Complex { return a; } operator function "-"(a:Complex):Complex { return new Complex(x: -a.x, y: -a.y); } operator function "+"(a:Complex, b:Complex):Complex { return new Complex(x: a.x+b.x, y: a.y+b.y); } operator function "+"(a:Complex, b:Number):Complex { return new Complex(x: a.x+b, y: a.y); } operator function "+"(a:Number, b:Complex):Complex { return new Complex(x: a+b.x, y: b.y); } function toBoolean():Boolean { return this.x != 0 || this.y != 0; } }
toBoolean
Method
Just like toString
, every object has a toBoolean
method that can be overridden. This method is
called by statements such as if
, while
, do while
, and for
and operators
such as !
, ||
, ^^
, &&
, and ? :
. This method
should return either true
or false
.
For-In Operator
An object x’s class can override the meaning of for-in loops such as for (
v in
x)
by overriding the following three methods which are located in the system-defined namespace Iterator
:
Method | Description |
---|---|
x.Iterator::forIn() |
If the iteration is empty, return null . Otherwise, return an object o with public
properties named value and state (o may also have other properties). The value
of o.value is the first element returned by the iteration. The value of o.state
can have any type and will be passed exactly once to either Iterator::next or Iterator::done .
The value of o is not guaranteed to be valid after o.state is passed to Iterator::next
or Iterator::done . |
x.Iterator::next( i) |
i was returned by a previous call to Iterator::forIn or Iterator::next .
If the iteration is done, return null . Otherwise, return an object o with public
properties named value and state (o may also have other properties). The value
of o.value is the next element returned by the iteration. The value of o.state
can have any type and will be passed exactly once to either Iterator::next or Iterator::done .
The value of o is not guaranteed to be valid after o.state is passed to Iterator::next
or Iterator::done . |
x.Iterator::done( i) |
i was returned by a previous call to Iterator::forIn or Iterator::next .
This call alerts x that the iteration is exiting prematurely and it can do whatever cleanup it needs on i.
Every i returned by Iterator::forIn or Iterator::next is guaranteed to be subsequently
passed exactly once either to Iterator::next or Iterator::done . Iterator::done
should return undefined . |
Rationale
A few other approaches had been considered for overriding the for-in operator. The three-method approach above was chosen for the following reasons:
- Why use an iterator object instead of a function and a callback (i.e. have
Iterator::forIn
take a parameter f and have it call f for each element in the collection)? The reason is that the iterator model permits future extensions such as iterating through two collections concurrently. This cannot be done using callbacks. - Why return an object from
Iterator::forIn
andIterator::next
instead of havingIterator::forIn
andIterator::next
return just an iterator and having a separateIterator::get
method that, given an iterator, returns an element? The rationale here is that the separateIterator::get
method would introduce unavoidable concurrency hazards. Sincenull
(or any other value) can be a member of a collection, we can’t use the return value fromIterator::get
to detect the end of iteration; instead, we’d have to use anull
return value fromIterator::forIn
orIterator::next
to indicate the end of iteration. In such a case, however, it would be possible for another thread to delete the last element between the call toIterator::forIn
orIterator::next
and the call toIterator::get
, andIterator::get
would then be stuck. We could use an exception to indicate the end of iteration, but this would put exception handling on the fast path of program execution. - Why use an object with two named slots rather than a two-element read-only array for the return value? Named slots can have heterogeneous types, while the types of array elements must be the same.
- Why define
Iterator::done
? Some objects may erect temporary scaffolding to support iteration. This method informs them that they can take down such scaffolding. Since ECMAScript currently lacks finalization, there is no other way for the iterated object to know that the iteration aborted early — the start of another iteration cannot be used as an indication that the previous iteration completed because several iterations can be happening simultaneously on the same object.
Class Reflection Operator
Earlier drafts of this proposal had a .class
operator that returned an instance’s class. This operator
was defined using the grammar production:
The operator was removed because obtaining an instance’s class breaks abstraction. The client of a factory method m could determine whether m was returning instances of some stated class C or subclasses of C, and m could not evolve to alter its implementation decision of what precise instances to return without potentially breaking clients.
ECMAScript 4 Netscape Proposal
Rationale
Units
|
Friday, September 20, 2002
Units derived from the Spice proposals had originally been part of the ECMAScript 4 proposal but were deferred to a future version of the language in order to keep ECMAScript 4 small. If implemented, units might behave as described in this section.
Lexing Units
A unit expression usually consists of a number followed by a unit name with no intervening white space, as in 7cm
.
The unit name can be quoted (for example, 7 "cm"
), in which case white space is permitted between
the number and the unit and the unit name can instead be a unit pattern (for example, 7 "Kg*cm^2/s"
).
When a numeric literal is immediately followed by an identifier, the lexer converts the identifier to a string literal. The parser then treats the number and string as a unit expression. The identifier cannot start with an underscore, but there are no reserved word restrictions on the identifier; any identifier that begins with a letter will work, even if it is a reserved word.
For example, 3in
is converted to 3 "in"
. 5xena
is converted to 5 "xena"
.
On the other hand, 0xena
is converted to 0xe "na"
. It is unwise to define unit names that begin
with the letters e
or E
either alone or followed by a decimal digit, or x
or X
followed by a hexadecimal digit because of potential ambiguities with exponential or hexadecimal notation.
Unit Expressions
null
true
false
public
this
A Number literal, ParenListExpression, or UnitExpression followed by a String literal is a unit expression, which is evaluated as follows:
Evaluate the String literal to obtain a string S. Parse that string according to
the unit pattern grammar and semantics to obtain a list of identifiers and exponents [id1e1,
id2e2, ..., idnen].
If the parse fails, signal a syntax error. If the list is empty, let U be the function nullUnit
, which
accepts one required and one optional argument and returns its first argument, ignoring the optional argument.
If the list is not empty, for each i between 1 and n, let Vi be the value of
looking up idi in the system unit
namespace. If ei is 1, let Fi
= Vi; otherwise, let Fi = Vi.public::pow(
ei)
.
Then let U = F1*
F2*
...*
Fn.
For example, if S is "Kg*m^2/s^2*q"
, then U is the value of unit::Kg*
unit::m.public::pow(2)*
unit::s.public::pow(–2)*
unit::q.public::pow(–1)
,
where unit is the system unit
namespace (the distinction between unit and the name unit
is only relevant for perverse code that has a local definition named unit
; the presence of such a local definition
doesn’t affect unit lookup).
The result U should be callable as a function that accepts one required and one optional argument. The unit
expression calls U, providing the numeric value of the Number literal, ParenListExpression,
or UnitExpression as the first argument. The second argument is present
only for the UnitExpression Number [no line break] String
production, in which case it is the original Number literal expressed as a string. Continuing
the example above, the unit expression 32.50 "Kg*m^2/s^2*q"
, evaluates to the result of (
unit::Kg*
unit::m.public::pow(2)*
unit::s.public::pow(–2)*
unit::q.public::pow(–1))(32.5, "32.50")
.
U’s second argument allows user-defined unit classes to define extended syntaxes for numbers. For instance,
a long-integer package might define a unit called "L"
that treats the Number literal
as a full 64-bit number without rounding it to a float64 first. Such a unit can be combined with other units by listing the
units one after another; note that the lexer allows the first unit to be unquoted if it directly
follows the number: 3L "cm"
is the same as 3 "L" "cm"
and evaluates to the result
of unit::cm(
unit::L(3, "3"))
.
Defining Units
Units are defined by placing them in the unit
namespace, which is predefined by the system. Unit
expressions are implicitly qualified using the unit
namespace. The unit namespace is not use
d
by default, so a program needs to explicitly qualify identifiers with unit::
to access existing unit definitions..
The easiest way to define new units is to scale, multiply, or divide existing ones. For example:
unit const µm = unit::m/1e6; unit const Å = unit::m/1e10; unit const dm = unit::m/10; unit const \_in = unit::m*0.0254; unit const liter = unit::dm.pow(3);
\_
must be used to define the unit in
because in
is a reserved word. However, the
unit in
may be used without quoting it, as in the expression 3in + 5cm
.
Unit Class Extension
If class extensions were also added to the language, the class extension
mechanism could be used instead of a unit
namespace to define units. This way units could be designated as internal
,
private
, etc.
The unit
attribute would be defined as though
const unit = extend(Unit);
were evaluated at the top level. Unit
would be the class that holds the definitions of unit names.
ECMAScript 4 Netscape Proposal
Rationale
Unit Patterns
|
Monday, June 30, 2003
This LALR(1) grammar describes the syntax of quoted unit patterns. The semantics describe the actions the lexer takes in order to interpret a quoted unit pattern. The input to this grammar and semantics is the unit string literal’s contents after the string literal has been processed. See also the description of the semantic notation.
This document is also available as a Word RTF file.
The start nonterminal is UnitPattern.
White Space
Syntax
«TAB»
| «VT»
| «FF»
| «SP»
| «u00A0»
«u2000»
| «u2001»
| «u2002»
| «u2003»
| «u2004»
| «u2005»
| «u2006»
| «u2007»
«u2008»
| «u2009»
| «u200A»
| «u200B»
«u3000»
Unit Patterns
Syntax
Semantics
/
WhiteSpacewsopt UnitProductwsopt2]
= Value[UnitProductwsopt1] unitReciprocal(Value[UnitProductwsopt2]);*
WhiteSpacewsopt UnitFactor]
= Value[UnitProductwsopt1] Value[UnitFactor];^
WhiteSpacewsopt SignedInteger WhiteSpace]
= [UnitFactoridentifier: Name[Identifier], exponent: IntegerValue[SignedInteger]];Signed Integers
Syntax
Semantics
Identifiers
Syntax
Semantics
ECMAScript 4 Netscape Proposal
Rationale
Syntax
|
Tuesday, November 19, 2002
This section presents a number of syntactic alternatives that were considered while developing this proposal.
Semicolon Insertion
Definitions
The term semicolon insertion informally refers to the ability to write programs while omitting semicolons between statements. In both ECMAScript 3 and ECMAScript 4 there are two kinds of semicolon insertion:
- Grammatical Semicolon Insertion
- Semicolons before a closing
}
and the end of the program are optional in both ECMAScript 3 and 2.0. In addition, the ECMAScript 4 parser allows semicolons to be omitted before theelse
of anif
-else
statement and before thewhile
of ado
-while
statement. - Line-Break Semicolon Insertion
- If the first through the nth tokens of an ECMAScript program form are grammatically valid but the first through the n+1st tokens are not and there is a line break between the nth tokens and the n+1st tokens, then the parser tries to parse the program again after inserting a VirtualSemicolon token between the nth and the n+1st tokens.
Grammatical semicolon insertion is implemented directly by the syntactic grammar’s productions, which simply do not require a semicolon in the aforementioned cases. Line breaks in the source code are not relevant to grammatical semicolon insertion.
Line-break semicolon insertion cannot be easily implemented in the syntactic grammar. This kind of semicolon insertion turns a syntactically incorrect program into a correct program and relies on line breaks in the source code.
Discussion
Grammatical semicolon insertion is harmless. On the other hand, line-break semicolon insertion suffers from the following problems:
- Line breaks are relevant in the program’s source code
- The consequences of this kind of semicolon insertion appear inconsistent to programmers
- Existing program behavior can change unexpectedly when new syntax is introduced
The first problem presents difficulty for some preprocessors such as the one for XML attributes which turn line breaks into spaces. The second and third ones are more serious. Programmers are confused when they discover that the program
a = b + c (d + e).print()
doesn’t do what they expect:
a = b + c; (d + e).print();
Instead, that program is parsed as:
a = b + c(d + e).print();
The third problem is the most serious. New features are added to the language turn illegal syntax into legal syntax. If
an existing program relies on the illegal syntax to trigger line-break semicolon insertion, then the program will silently
change behavior once the feature is added. For example, the juxtaposition of a numeric literal followed by a string literal
(such as 4 "in"
) is illegal in ECMAScript 3. ECMAScript 4 makes this legal syntax for expressions with
units. This syntax extension has the unfortunate consequence of silently changing the meaning of the following ECMAScript
3 program:
a = b + 4 "in".print()
from:
a = b + 4; "in".print();
to:
a = b + 4"in".print();
ECMAScript 4 gets around this incompatibility by adding a [no line break] restriction in the grammar that requires the numeric and string literals to be on the same line. Unfortunately, this compatibility is a double-edged sword. Due to ECMAScript 3 compatibility, ECMAScript 4 has to have a large number of these [no line break] restrictions. It is hard to remember all of them, and forgetting one of them often silently causes an ECMAScript 4 program to be reinterpreted. Some programmers will be dismayed to find that:
internal function f(x) {return x*x}
turns into:
internal; function f(x) {return x*x}
(where internal;
is an expression statement) instead of:
internal function f(x) {return x*x}
An earlier version of ECMAScript 4 disallowed line-break semicolon insertion. The current version allows it but only in non-strict mode. Strict mode removes all [no line break] restrictions, simplifying the language again. As a side effect, it is possible to write a program that does different things in strict and non-strict modes (the last example above is one such program), but this is the price to pay to achieve simplicity.
Regular Expression Literals
ECMAScript 4 retains compatibility with ECMAScript 3 by adopting the same rules for detecting regular expression literals. This complicates the design of programs such as syntax-directed text editors and machine scanners because it makes it impossible to find all of the tokens in an ECMAScript program without parsing the program.
Making ECMAScript 4’s lexical grammar independent of its syntactic grammar significantly would have allowed tools to
easily process an ECMAScript program and escape all instances of, say, </
to properly embed an ECMAScript 4
or later program in an HTML page. The full parser changes for each version of ECMAScript. To illustrate the difficulties,
compare such ECMAScript 3 gems as:
for (var x = a in foo && "</x>" || mot ? z:/x:3;x<5;y</g/i) {xyz(x++);} for (var x = a in foo && "</x>" || mot ? z/x:3;x<5;y</g/i) {xyz(x++);}
Alternate Regular Expression Syntax
One idea explored early in the design of ECMAScript 4 was providing an alternate, unambiguous syntax for regular expressions
and encouraging the use of the new syntax. A RegularExpression could have been specified unambiguously
using «
and »
as its opening and closing delimiters instead of /
and /
.
For example, «3*»
would be a regular expression that matches zero or more 3
’s. Such
a regular expression could be empty: «»
is a regular expression that matches only the empty string,
while //
starts a comment. To write such a regular expression using the slash syntax one needs to write /(?:)/
.
Syntactic Resynchronization
Syntactic resynchronization occurs when the lexer needs to find the end of a block (the matching }
) in order
to skip a portion of a program written in a future version of ECMAScript. Ordinarily this would not be a problem, but regular
expressions complicate matters because they make lexing dependent on parsing. The rules for recognizing regular expression
literals must be changed for those portions of the program. The rule below might work, or a simplified parse might be performed
on the input to determine the locations of regular expressions. This is an area that needs further work.
During syntax resynchronization ECMAScript 4 determines whether a /
starts a regular expression or is a
division (or /=
) operator solely based on the previous token:
/ interpretation |
Previous token |
---|---|
/ or /= |
Identifier Number RegularExpression
String) ++ --
] } class false null
private protected public
super this true get set Any other punctuation |
RegularExpression | ! != !==
# % %=
& && &&=
&= ( *
*= + +=
, - -=
-> . ..
... / /=
: :: ;
< << <<=
<= = ==
=== > >=
>> >>= >>>
>>>= ? @
[ ^ ^=
^^ ^^= {
| |= ||
||= ~ abstract break case
catch const continue
debugger default delete
do else enum
export extends final
finally for function
goto if implements
import in instanceof
interface is namespace
native new package
return static switch
synchronized throw throws
transient try typeof
use var volatile
while with |
Regardless of the previous token, //
is interpreted as the beginning of a comment.
The only controversial choices are )
and }
. A /
after either a )
or
}
token can be either a division symbol (if the )
or }
closes a subexpression or an
object literal) or a regular expression token (if the )
or }
closes a preceding statement or an
if
, while
, or for
expression). Having /
be interpreted as a RegularExpression
in expressions such as (x+y)/2
would be problematic, so it is interpreted as a division operator after )
or }
. If one wants to place a regular expression literal at the very beginning of an expression statement, it’s
best to put the regular expression in parentheses. Fortunately, this is not common since one usually assigns the result of
the regular expression operation to a variable.
Type Declarations
The current ECMAScript 4 proposal uses Pascal-style colons to introduce types in declarations. For example:
var x:Integer = 7; function square(a:Number):Number {return a*a}
This is due to a consensus decision of the ECMA working group, with Waldemar the only dissenter. There are a couple of alternative syntaxes:
C-Style
We could allow modified C-style type declarations as long as a function’s return type is listed after its parameters:
var Integer x = 7; var Integer y = 8, Integer z = 9; // Declares two Integer variables function square(Number a) Number {return a*a}
A function’s return type cannot be listed before the parameters because this would make the grammar ambiguous.
In fact, an implementation could unambiguously admit both the Pascal-style and the modified C-style declarations by replacing the TypedIdentifier and Result grammar rules with the ones listed below. The resulting grammar is still LALR(1).
Advantages of using the modified C-style syntax include:
- On the Pascal/Modula/Ada vs. C/C++/Java syntax debate, ECMAScript tends to use syntax more similar to Java.
- We already use the colon syntax for statement labels and object literal elements (for example
{a:17, b:33}
). The latter would present a conundrum if we ever wanted to declare field types in an object literal. Some programmers have been using these as a convenient facility for passing named arguments to functions.
Attribute-Style
Since attributes are simple expressions, we could allow attributes that evaluate to types. For var
and const
declarations, these attributes would specify the type of the declared variables. For function
declarations, these
attributes would specify the function’s return type. For stylistic consistency, types of arguments would also be listed before
their identifiers.
Integer var x = 7; Integer var y = 8, z = 9; // Declares two Integer variables Number function square(Number a) {return a*a}
This style is simple and reads fairly naturally.
Again, an implementation could unambiguously admit both the Pascal-style and the attribute-style declarations, with the resulting grammar still being LALR(1). However, it’s better if the language made a choice rather than propagating the confusion of having two or three styles; this flexibility could be used for compatibility with existing programs.
Type Expressions
ECMAScript 4 uses the same syntax for type expressions as for value expressions for the following reasons:
- Creating two different syntaxes for two kinds of expressions would add to the complexity of the language.
- ECMAScript is a dynamic language and it is useful to manipulate types as though they were first-class values.
- It’s difficult to unambiguously distinguish type expressions from value expressions. In the expression
(expr1)(expr2)
, isexpr1
a type or a value expression? If the two have the same syntax, it doesn’t matter.
Function Declarations
Getters and Setters
By consensus in the ECMA TC39 modularity subcommittee, we decided to use the syntax function get
id (
...)
rather than getter function
id (
...)
for defining a getter
and function set
id (
...)
rather than setter function
id (
...)
for defining a setter. The latter would have simplified the FunctionName
rule to:
while creating two additional attributes, getter
and setter
. The decision was based on aesthetics;
neither syntax is more difficult to implement than the other.
Language Directives
An alternative to pragmas that was considered early was to report syntax errors at the
time the relevant statement was executed rather than at the time it was parsed. This way a single program could include parts
written in a future version of ECMAScript without getting an error unless it tries to execute those portions on a system that
does not understand that version of ECMAScript. If a program part that contains an error is never executed, the error never
breaks the script. For example, the following function finishes successfully if whizBangFeature
is false
:
function move(x:Integer, y:Integer, d:Integer) {
x += 10;
y += 3;
if (whizBangFeature
) {
simulate{@x and #y} along path
} else {
x += d; y += d;
}
return [x,y];
}
The code simulate{@x and #y} along path
is a syntax error, but this error does not break the script unless
the script attempts to execute that piece of code.
One problem with this approach is that it frustrates debugging; a script author benefits from knowing about syntax errors at compile time rather than at run time.
ECMAScript 4 Netscape Proposal
Rationale
Miscellaneous
|
Wednesday, June 4, 2003
This section presents a number of miscellaneous alternatives that were considered while developing this proposal.
Types
Object
The root of the type hierarchy was chosen to be the existing ECMAScript 3 type Object
.
Primitive numbers, strings, and booleans were made into instances of subclasses of Object
and the existing wrapper
classes eliminated, thereby eliminating the confusing distinction between boolean primitives and objects, etc. in ECMAScript
3.
The alternative was to define a new root type, any
, that is an ancestor of Object
as well as
primitive numbers, strings, and booleans, which could have their own types number
, string
, and boolean
respectively. In this scenario the ECMAScript 3 classes Number
, String
, and Boolean
would still be subclasses of Object
, but the primitive values could not be members of these classes — for
example, false
would be a member of the class boolean
but not Boolean
. The object obtained
by boxing false
would be a member of the class Boolean
but not boolean
and would evaluate
to true (!) if used in an if
statement.
The any
alternative was more compatible with ECMAScript 3, but the added complexity and confusion was not
deemed worthwhile for the extra compatibility. Instead, ECMAScript 4 simplifies and regularizes the behavior of ECMAScript
3 in this area.
Never
and Void
The types Never
and Void
are different and serve different purposes. When used as a return type, Never
describes the inability to return
from a function, while Void
states that the function returns, but the value returned is not useful (it’s
always undefined
).
The following example illustrates the use of Never
and Void
:
// This function returns no useful value. function display(message:String):Void { document.write("<\_P>" + message + "<\/P>\n"); } // This function cannot return. function abort(message:String):Never { display(message); throw AbortException; } function chickenCount(myChickens:Array[Chicken]):Integer { if (notHatched(myChickens)) abort("Can’t count the chickens yet"); else return myChickens.length; }
Note that if the function abort
had no explicit return type or any return type other than Never
,
then the compiler would likely issue a warning inside the function chickenCount
because it contains a code path
(the false case of its if
) that appears to fall out of the function without returning a value, while chickenCount
is declared to return an Integer
. The Never
return type on abort
tells the compiler
that there is no such code path inside chickenCount
.
It might be a good idea for a compiler to issue a warning for a function that is declared as returning type Never
for which the compiler can’t verify that the function can’t return.
Typed Arrays
The proposal originally had additional array types which were dropped for simplicity. The following are some candidates for a future revision of the language:
Type | Set of Values |
---|---|
Array |
Same as Array[Object] |
Array[ t] |
null as well as all arrays (dense or sparse) capable of holding elements of type t |
List[ t] |
null as well as all resizeable, dense arrays capable of holding elements of type t |
t[] |
null as well as all nonresizeable, dense arrays capable of holding elements of type t |
ConstArray |
Same as ConstArray[Object] |
ConstArray[ t] |
null as well as all constant arrays capable of holding elements of type t |
A sparse array may have holes. A dense array always has contiguous elements indexed starting from zero. Resizeable arrays
may be resized by writing to the length
property.
When A is one of the type expressions Array[
t]
, List[
t]
,
t[]
, ConstArray
, or ConstArray[
t]
, the following
three operations can be used to create an array of type A or coerce to one:
new
A(
elt1,
elt2,
...)
creates an instance of the corresponding array type with the given elements; this is true even if there is only one element given.new
A(length:
n)
creates an instance of the corresponding array type with a length of n. The initial elements are holes if the array is sparse or the default value for A’s element type if the array is dense. Note that this form requires the named function parameters extension.- A
(
B)
creates an instance of the corresponding array type. The new instance’s elements are copied from B, which should be any kind of an array. It’s unresolved what should happen if A is a dense array and B has holes.
Instances of non-ConstArray
array types are equal only when they are the same object. Instances of ConstArray
array types are equal when they have the same contents and the same element type t.
Type Expressions
We could define other type operators such as the ones in the table below. s and t are type expressions.
Type | Values | Implicit coercion of value v |
---|---|---|
t[] |
null as well as nonresizable arrays of values of type t |
undefined null |
const t |
Makes type t, which must be an array type, into a read-only array type | None |
- t |
Any value belonging to type t except null |
|
~ t |
undefined or any value belonging to type t |
undefined undefined ;
any other implicit coercions already defined for t |
+ t |
null or any value belonging to type t |
null null ; undefined
null (if undefined is not a
member of t); any other implicit coercions already defined for t |
s + t |
All values belonging to either type s or type t or both | If vs+ t,
then use v; otherwise, if v as s is defined then use v as s;
otherwise, if v as t is defined then use v as t. |
s * t |
All values simultaneously belonging to both type s and type t | If v as s as t is defined
and is a member of s* t, then use v as s as t. |
s / t |
All values belonging to type s but not type t | If v as s is defined and is a member of s/ t,
then use v as s. |
The []
suffix operator could be used to make array types. Unlike Array
, these arrays would be
dense, indexable only by integers, nonresizable, and non-subclassable — t[]
has the class
modifier final
.
A new unary operator, const
, could be added. This operator would
take a PostfixExpressionOrSuper x
as an operand. If x is an instance of a non-ConstArray
array, const
x
would return a ConstArray[
T]
copy of x, where T is the most specific
type such that every element of x is a member of T. If x is an array type, const
x
would be the corresponding ConstArray
type.
- t
[]
is the type of writable arrays of t. const
t[]
is the type of constant arrays of t.- t
[][]
is the type of writable arrays of writable arrays of t. const
t[][]
is the type of constant arrays of writable arrays of t —const
binds looser than[]
so it’s the same asconst (
t[])[]
.(const
t[])[]
is the type of writable arrays of constant arrays of t.
Multiple Constructors
Earlier ECMAScript 4 proposals allowed a class to have multiple constructors with different names, callable using the
same syntax as calling static functions. These were dropped to keep the language simple. Since now each class can have at
most one constructor, the this(
args)
form for calling another constructor from the same
class was also dropped. Constructors had been defined as described below.
A constructor is a function that creates a new instance of a class C. Constructors are defined using the attribute
constructor
. A constructor is usually given the same name as its class, in which case the constructor
attribute is optional and the constructor is known as a default constructor and can be called as new
C.
If a constructor has a different name, it is invoked as though it were a static
function of the class:
class C {
var a:String;
constructor function C(p:String) {this.a = "New "+p} //
The attribute constructor
is optional here
constructor function make(p:String) {this.a = "Make "+p}
static function obtain(p:String):C {return new C(p)}
}
var c:C = new C("one");
var d:C = C.C("two");
var e:C = C.make("three");
var f:C = C.obtain("four");
c.a; //
Returns "New one"
d.a; //
Returns "New two"
e.a; //
Returns "Make three"
f.a; //
Returns "New four"
A constructor can refer to its class’s instance variables via this
. If a class C inherits
from class B, then when B’s constructor is called while creating an instance of C,
B’s constructor will be able to call virtual methods of class C on the partially constructed instance.
Likewise, B’s constructor could store this
into a global variable v and some other
function could call a method of C on the partially constructed object v. Class C’s
methods can be assured that they are only called on fully initialized instances of C only if neither C
nor any of its ancestors contains a constructor that exhibits either of the behaviors above.
A constructor should not return a value with a return
statement; the newly created object is returned automatically.
A constructor’s return type must be omitted.
A constructor
function always returns a new instance. On the other hand, a static
function can
return an existing instance of its class. It is possible to define a static
function with the same name as its
class C; such a function looks like a constructor to the outside (it can be called as new
C)
but can hand out existing instances of its class. However, subclasses will see that C is not a constructor in such
a class because they will not be able to invoke C’s pseudo-constructor from their constructors.
If a class C does not define the default constructor C.
C, the default constructor
C.
C is automatically defined; that constructor takes the arguments that C’s
superclass’s default constructor B.
B takes. C.
C
calls B.
B and then initializes C’s new instance members to their default
values.
Calling a Superconstructor
Let C be a class and B its superclass. Any constructor C.
n must
call a constructor B.
m or C.
m before it accesses this
or super
or before it returns. The call can be either explicit or implicit; if C.
n
does not contain any calls to a constructor B.
m or C.
m,
then a call to B.
B with no arguments is automatically inserted as the first statement of
C.
n. The constructor C.
n does not have to call another
constructor if it exits by throwing an exception. C.
n may not make a constructor call more
than once.
A constructor C.
n can call another constructor using one of the following statements:
super( args) |
Calls B’s default constructor B. B. |
super. m( args) |
Calls B’s constructor B. m. m must be a QualifiedIdentifier
that names one of B’s constructors. |
this( args) |
Calls C’s default constructor C. C. |
this. m( args) |
Calls C’s constructor C. m. m must be a QualifiedIdentifier
that names one of C’s constructors. |
The above must be complete statements, not subexpressions of larger expressions. The first of the four forms above is unambiguously
a SuperStatement, but the remaining three are parsed as ExpressionStatements.
The following rules indicate whether one of these three forms (super.
m(
args)
,
this(
args)
, or this.
m(
args)
)
is treated as an expression or a call to a constructor:
- If the form is part of a larger expression (for example,
this(3,4)+5
), then it is an expression. - If the form is not located inside a constructor (excluding nested functions), then it is an expression.
- If m in
super.
m(
args)
does not name one of the superclass’s constructors, then the form is an expression (that, in this case, looks up the superclass’s property m onthis
and invokes it as a method with the arguments args). - If m in
this.
m(
args)
does not name one of the current class’s constructors, then the form is an expression (that, in this case, looks up the property m onthis
and invokes it as a method with the arguments args). - Otherwise, the form is a constructor call.
It is not possible to skip class hierarchy levels while constructing an object — if C’s superclass is B and B’s superclass is A, then one of C’s constructors may not directly call one of A’s constructors.
Class Extensions
A class extension is the ability to add methods to a class defined somewhere else. Class extensions were part of the ECMAScript 4 proposal but were dropped to keep the language simple; although useful, they significantly complicated the design of classes and introduced some pesky problems such as what to do when a package P first imports a base class C from package Q, then defines a subclass D of C, and finally extends C with an extension with the same name and namespace as a method of D.
The proposed class extensions were created using the extend
attribute described below.
extend
Attribute
The extend
attribute takes a parameter C, which should be a compile-time
constant expression that evaluates to a class, and adds the definition as a new member of class C. This allows
one to add a method to an existing class C even if C is in an already defined package P.
There are several restrictions:
- The
extend
attribute can only be used onfunction
,const
,class
, ornamespace
definitions, or onstatic var
definitions. Thus, a class extension cannot add new instance variables to objects of class C; it can, however, add getters and setters. Operators cannot be defined in class extensions. - The new member must be either
static
orfinal
, and it cannot override any existing member. If the new member is afunction
, the default isfinal
. - The new member must be defined in a namespace that has been defined in the current package. In particular, it means
that the new member cannot be
public
, because thepublic
namespace is predefined by the system and not by any package. The default namespace isinternal
, which makes the new member of C visible only from inside the current package. - The new member does not get any special access privileges granted to C’s indigenous members. For example,
it cannot see C’s
private
members, and it cannot see P’sinternal
members if P is not the current package. On the other hand, it can see the current package’sinternal
members.
The following example indicates adding methods to the system class String, using a newly created namespace StringExtension
:
namespace StringExtension; StringExtension extend(String) function scramble():String {...} StringExtension extend(String) function unscramble():String {...} use namespace(StringExtension); var x:String = "abc".scramble();
Once the class extension is evaluated, methods scramble
and unscramble
become available on all
strings in code within the scope of a use namespace(StringExtension)
. There is no possibility of name clashes
with extensions of class String
in other, unrelated packages because the names scramble
and unscramble
only acquire their special meanings when qualified by the namespace StringExtension
.
Unless one desires to export a class extension to other packages, the default namespace internal
generally
works best and simplifies the above example to:
extend(String) { function scramble():String {...} function unscramble():String {...} } var x:String = "abc".scramble();
Interfaces
Interfaces were considered for ECMAScript 4 but were dropped to keep the language simple — they are not needed as much in a dynamically typed language than in a statically typed one.
Interfaces might be defined by adding the syntax and semantics below.
Interface Definition
Interfaces behave much like classes except that an interface I is not a supertype of a class C that
implements I. Instead, an instance c of C may be implicitly
coerced to type I, which creates an instance i of I. Implicitly
coercing i to type C yields the original instance c. It is unspecified whether c ==
i.
An interface may have both concrete and abstract members, but it may not have constructors.
In the absence of name conflicts, an interface I’s members may be accessed as properties of any instance
c of a class C that implements I. However, it is legal to define an interface I
with a member m with the same name as a member of class C and yet have the two members be different.
It is also legal for a class to implement two interfaces I and J both of which have a member named m
and have the two m’s remain distinct. Which one gets extracted when one performs the property lookup operation
c.
m depends on whether c was last coerced to one of the interfaces or to an
object type.
Class Definitions
The Inheritance clause of class definitions would be modified to accommodate
interfaces, as listed in an implements
clause:
The newly defined class inherits instance and static members from the superclass and superinterfaces, if any. In the case of a conflict between a superclass and a superinterface, the superclass prevails. In the case of a conflict between two superinterfaces, neither is preferred and the lookup has to be qualified by the name of the superinterface for instance members or done directly on one of the superinterfaces for static members.
Attributes
primitive
A primitive
class modifier attribute was considered.
This attribute would exclude null
from the set of values that can be stored in variables typed with this class:
if a class C is defined using the primitive
attribute, then null
is not considered to
be a member of the type C (there would have to be a way to specify a default value for uninitialized variables
of type C). This attribute would permit user-defined classes to behave like some predefined classes such as Number
.
This attribute was dropped for now because of circularity problems with classes that haven’t been defined yet. If
class C hasn’t been defined yet, one can still create a variable of type C; such a variable is
initialized to null
. If C turned out to be a primitive class then the variable’s value would
need to be retroactively changed.
Importing Packages
The import
directive can be extended with more options for better
identifier and namespace management, as described below:
A package P can reference another package Q via an import
directive:
If provided, ParenListExpression should be a list
of namespaces provided by the package. These namespaces are use
d by the import
statement. In order
to resolve name conflicts between packages, IncludesExcludes provides
finer-grain control over which names are imported. include
or exclude
clauses specify which sets
of names are shared as top-level variables. If include
is used, only the listed names are made accessible; if
exclude
is used, all names except the listed ones are made accessible. For example:
package My.P1 {
explicit namespace N;
N const a = "global a";
N const b = "global b";
N class C {
static var x = 2;
}
N const c = new C(i:5); //
Initializes c.i
to 5
const x = "global x";
}
package My.P2 {
import P = My.P1, namespace(N), exclude(N::b, x); //
Imports My.P1
and use
s namespace N
, excluding N::b
and x
c; //
OK; evaluates to the instance of class C
N; //
Error: N
not found because it’s explicit
P.N; //
OK; evaluates to namespace N
in package My.P1
a; //
OK; evaluates to "global a"
b; //
Error: N::b
not found
because it’s excluded
P.b; //
OK; evaluates to "global b"
(P.N)::b; //
Error: N::b
not found
because it’s excluded
x; //
Error:
the global x
not found because it’s excluded
C.x; //
OK; evaluates to 2
}
If no include
or exclude
clause is listed, the effect is the same as if exclude()
were listed.
An import
directive does the following:
- Locate the target package specified by PackageName. If the package has not yet been loaded, then load it and wait until the target package’s Block is done evaluating. If loading the target package causes an import of the current package then throw a package circularity exception.
- Let P be the target package object.
- If Identifier is given,
const
-bind it to P in the current scope. - For each non-
explicit
top-level definition N::
n (n in namespace N) in P, if N::
n is excluded by the given IncludesExcludes, then skip that definition; otherwise, bind an alias N::
n to P’s N::
n in the global scope unless N::
n is already defined in the global scope. - If ParenListExpression is provided, evaluate
each expression E in ParenListExpression,
looking up each free identifier in E as though it were prefixed with P
.
. Each such expression E should evaluate to a namespace S. Evaluateuse namespace(
S)
using the given IncludesExcludes.
If package P has a public
top-level definition n and package Q imports P
using import PkgP =
P, then package Q can refer to n as either
n or PkgP.
n. The shorter form n is not available if it conflicts with some other
n. To avoid polluting its top-level scope, package Q can import package P using either import PkgP =
P, include()
or import PkgP =
P, exclude(
n)
, in which
case package Q can refer to n only as PkgP.
n.
If package P has an explicit
top-level definition n and package Q imports
P, then package Q can refer to that n only as PkgP.
n.
If package P has a top-level definition n in namespace N and package Q imports
P using import PkgP =
P, then package Q can refer to n
as either PkgP.
N::
n or N::
n (in either
of these the name N has to be accessible as well, which may require qualifying it if the accessibility of N
is not public
or using (PkgP.
N)
instead of N if the accessibility
of N is explicit
). Package Q can instead import P using import PkgP =
P, namespace(
N)
to be able to refer to n as plain n, barring name collisions. Alternatively, package Q can
execute import PkgP =
P followed by use namespace(
N)
(or use namespace(PkgP.
N)
) to achieve the same effect.
Wrap Mode
Earlier drafts of this proposal had a wrap pragma that caused implicit coercions of
an out-of-range integer to an integral machine type to wrap around instead of
generating an error. The pragma would only affect the portions of the program in which the pragma was lexically in effect.
The basic effect of this pragma would be to make arithmetic on sbyte
, byte
, short
,
ushort
, int
, uint
, long
, and ulong
wrap around instead of
generating errors when the result doesn’t fit in the destination.
Although useful for performance-critical code, this pragma was dropped to keep the language simple. Wrap-around can still be achieved using an explicit cast to one of the integral machine types.
ECMAScript 4 Netscape Proposal
Compatibility
|
Friday, March 14, 2003
ECMAScript 4 is intended to be upwards compatible with almost all ECMAScript 3 and earlier scripts. The following are the current compatibility issues:
- Commas are now significant inside brackets, so expr
[
expr,
expr]
should be replaced by expr[(
expr,
expr)]
. - Initializers are not allowed on a
for
-in
variable; the=b
in the statementfor (var a=b in c)
... is not allowed. - Uses of the identifiers
as
,is
,namespace
, anduse
need to be renamed or escaped with\_
because these are now reserved words. - ECMAScript 4 drops the wrapper classes
Boolean
,Number
, andString
.Boolean
,Number
, andString
now refer to classes that have primitive booleans, numbers, and strings as instances. The methods of these new classes correspond to the methods of ECMAScript 3’s wrapper classes. The results of callingnew
onBoolean
,Number
, orString
are now implementation-defined, so an implementation may choose to retain the wrappers for compatibility with ECMAScript 3, but it is not required to do so. - ECMAScript 4 permits but does not require implementations to allow array indices greater than 4294967295. Code that relies on indices greater than 4294967295 not being recognized as valid array indices may not work.
- Code that modifies the standard ECMAScript 3 objects such as
Object
andString
may not work. - ECMAScript 4 defines additional global constants, which may clash with top-level identifiers in programs.
- Invalid ECMAScript 3 code may parse as valid ECMAScript 4 code. ECMAScript 3 programs which relied on getting an exception for such formerly invalid code may not work.
For applications such as browsers where 100% compatibility is needed, scripts will be assumed to be written ECMAScript
3 unless explicitly marked as being ECMAScript 4 through either an HTML or XML attribute or by including a use ecmascript(4)
statement.
Waldemar Horwat Last modified Monday, June 30, 2003 |