February 1999 Draft
JavaScript 2.0
Declarations
previousupnext

Thursday, February 18, 1999

Execution Model

JavaScript 2.0 follows the streaming execution model -- the entire program is processed in one pass. Unlike in JavaScript 1.x, declarations take effect only when they are executed. A declaration that is never executed is ignored.

The streaming execution model considerably simplifies the language and allows a JavaScript 2.0 interpreter to treat programs read from a file identically to programs typed in via an interactive console. Also, a JavaScript 2.0 interpreter or just-in-time compiler may start to execute a script even before it has finished downloading all of it. This was not possible in JavaScript 1.x because the interpreter was required to scan the entire program for declarations in one pass before executing any of the code in the following pass. Streaming also simplifies the execution model for web pages that contain multiple JavaScript scripts and permits one to safely coalesce adjacent SCRIPT tags on a page or divide a single SCRIPT tag into several.

One of the most significant advantages of streaming is that it allows JavaScript 2.0 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 supports CSS unit arithmetic while still working on environments that do not.

The streaming 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 is resolved only when execution reaches the reference. References from within the body of a function are resolved only after the function is called and execution reaches those references.

According to these rules, the following program is correct and would print 7:

function f(int a) int {
  return a+b;
}

var int b = 4;
print(f(3));

Assuming that variable b is predefined by the host if featurePresent is true, this program would also work:

function f(int a) int {
  return a+b;
}

if (!featurePresent) {
  package var int b = 4;
}

print(f(3));

On the other hand, the following program would produce an error because a is referenced before it is defined:

print(f(3));

function f(int a) int {
  return a+b;
}

Defining mutually recursive functions is not a problem as long as one defines all of them before calling them.

Should we define a more eager identifier resolving mechanism for some functions (for example all functions that are not declared volatile and do not contain a call to eval)? This would let the compiler resolve all of the identifiers in a function at the first time the function is called.

Declarations

Several different kinds of declarations that can be present in JavaScript 2.0 programs:

Variable declarations
VariableDefinition 
   [Visibilityvar [TypeExpressionIdentifier [= AssignmentExpression, ... , [TypeExpressionIdentifier [= AssignmentExpression;
|  [Visibilityconst [TypeExpressionIdentifier = AssignmentExpression , ... , [TypeExpressionIdentifier = AssignmentExpression ;
Function declarations
FunctionDefinition 
   [Visibility] [getter | setterfunction Identifier ( Parameters ) [TypeExpressionBlock
|  [Visibilitytraditional function Identifier ( Identifier , ... , Identifier ) Block
Field, method and constructor declarations
MemberDefinition 
   [Visibilityfield [TypeExpressionIdentifier [= AssignmentExpression, ... , [TypeExpressionIdentifier [= AssignmentExpression;
|  [Visibility] [getter | setter] [final] [overridemethod Identifier ( Parameters ) [TypeExpressionBlock
|  [Visibility] [getter | setter] [final] [overridemethod Identifier ( Parameters ) [TypeExpression;
|  [Visibilityconstructor Identifier ( Parameters ) Block
Class declarations
ClassDefinition 
   [Visibilityclass Identifier [extends TypeExpressionBlock
|  [Visibilityclass extends TypeExpression Block
Version declarations
VersionDefinition 
   [Visibilityversion Version [> VersionList;

All of these declarations share several common scoping rules:

  1. A declaration without a Visibility prefix applies locally to the current Block scope (which may be the current package if the declaration is at the top level of a Program, or a class if the declaration is at the top level of a ClassDefinition's Block). A declaration with a Visibility prefix applies either to the current package (if outside a ClassDefinition's Block) or to the current class (if inside a ClassDefinition's Block).
  2. A declaration that applies to a scope can be referenced lexically from anywhere within that scope unless shadowed by a more local declaration.
  3. A declaration that applies to a scope lasts until that scope is exited. No other declaration may be executed for the same identifier applying to the same scope (with the exceptions that both a getter and a setter may be defined with the same name and that versions have a namespace separate from other declarations).
  4. If code executing inside a scope s has already made an attempt to resolve identifier i and that resolution either bound i to a definition of i in a scope enclosing s or failed because i wasn't defined, then no declaration of i applying to scope s may be executed.

Rules 3 and 4 state that once an identifier is resolved to a variable or function in a scope, that resolution cannot be changed. This permits efficient compilation and avoids confusion with programs such as:

const int b = 7;

function f() int {
  function g() int {return b}

  var a = g();
  const int b = 8;
  return g() - a;
}

Scopes

Most lexical scopes are established by Block productions in the grammar. Lexical scopes nest, and a declaration in an inner scope can shadow declarations in outer ones.

Do we want to collapse all block scopes into one inside functions? On one hand this complicates the language conceptually and surprises Java and C++ programmers. On the other hand, this would match JavaScript 1.x better and simplify closure creation when a closure is created nested inside several blocks in a function.

Declaration Visibility

A declaration with a Visibility prefix does not apply to the current Block. Instead, it applies either to the current package (if outside a ClassDefinition's Block) or to the current class (if inside a ClassDefinition's Block). In addition to making the declaration global in this way, Visibility also specifies the declaration's visibility from other packages or classes. Visibility can take one of the following forms:

Visibility   Access allowed from
private only within current class
package only within current package
public VersionsAndRenames   within any package that imports this package

Declarations at the top level of a Program or at the top level of a ClassDefinition's Block may omit Visibility, in which case they are treated as if they had package visibility. When used outside a ClassDefinition's Block, private is equivalent to package.

In the example below the comments indicate the scope and visibility of each declaration:

var a0;                 // Package-visible global variable
private var a1 = true;  // Package-visible global variable
package var a2;         // Package-visible global variable
public var a3;          // Public global variable

if (a1) {
  var b0;               // Local to this block
  private var b1;       // Package-visible global variable
  package var b2;       // Package-visible global variable
  public var b3;        // Public global variable
}

public function F() {   // Public global function
  var c0;               // Local to this function
  private var c1;       // Package-visible global variable
  package var c2;       // Package-visible global variable
  public var c3;        // Public global variable
}

function G() {          // Package-visible global function
  var d0;               // Never defined because G isn't called
  private var d1;       // Never defined because G isn't called
  package var d2;       // Never defined because G isn't called
  public var d3;        // Never defined because G isn't called
}

class C {               // Package-visible global class
  var e0;               // Package-visible class variable
  private var e1;       // Class-visible class variable
  package var e2;       // Package-visible class variable
  public var e3;        // Public class variable
  field e4;             // Package-visible instance variable
  private field e5;     // Class-visible instance variable
  package field e6;     // Package-visible instance variable
  public field e7;      // Public instance variable

  function H() {        // Package-visible class function
    var f0;             // Local to this function
    private var f1;     // Class-visible class variable
    package var f2;     // Package-visible class variable
    public var f3;      // Public class variable
    private field f4;   // Class-visible instance variable
    package field f5;   // Package-visible instance variable
    public field f6;    // Public instance variable
  }
  public method I() {}  // Public class method

  H();
}

F();

Versioning Public Identifiers

A public declaration's identifier is exported to other packages. To help avoid accidental collisions between identifiers declared in different packages, identifiers can be selectively exported depending on the version requested by an importing package. An identifier declaration with a version number newer than that requested by the importer will not be seen by that importer. The versioning facilities also include additional facilities that allow robust removal and renaming of identifiers.

VersionsAndRenames describes the set of versions in which an identifier is exported, together with a possible alias for the identifier:

VersionsAndRenames 
   [< VersionRange [: Identifier, ... , VersionRange [: Identifier>]
VersionRange 
   Version
|  [Version.. [Version]

Suppose a client package C imports version V of package P that exports identifier N with some VersionsAndRenames. If the VersionsAndRenames's VersionRange includes version V, then package C can use the corresponding Identifier alias to access package P's N. If the Identifier alias is omitted, then package C can use N to access package P's N. Multiple VersionRanges operate independently.

In most cases VersionsAndRenames is just a version number between < and >:

public<1.2> const z = 3;

If VersionsAndRenames is omitted, version 1.0 is assumed.


Waldemar Horwat
Last modified Thursday, February 18, 1999
previousupnext