March 1999 Draft
JavaScript 2.0
Packages
|
Wednesday, March 24, 1999
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. JavaScript 2.0 offers a number of facilities to make packages robust for dynamic linking:
A package is a file (or analogous container) of JavaScript 2.0 code. There is no specific JavaScript statement that introduces or names a package -- every file is presumed to be a package. A package itself has no name, but it has a specific URI by which other packages can import it.
A package P typically starts with import
statements that import other packages used by package
P. A package that is meant to be used by other packages typically has one or more version
declarations that declare versions available for export.
A package's body is described by the Program grammar nonterminal. 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 may also be loaded when the JavaScript engine first starts up.
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.
When a package is loaded, all of its statements are evaluated in order, which may cause other packages to be loaded along
the way when import
statements are encountered. A package's symbols are available for export to other packages
only after the package's body has been successfully evaluated. Unlike in Java, circularities are not allowed in the graph
of package imports.
To create packages A and B that access each others' symbols, we need to instead define a hidden package C that consists of all of the code that would have gone into A and B. Package C should define versions verA and verB and tag the symbols it exports with either verA or verB to indicate whether these symbols belong in package A or B. Package A should then be empty except for a directive (or several directives if there are multiple versions of A and verA) that reexports C's symbols tagged with verA. Similarly, package B should reexport C's symbols tagged with verB. To make this work we need a reexport directive. Is this really necessary? Also, do we want a mechanism for hiding package C from general view so that users can only use it through A or B?
We can export a symbol in a package by giving it public
Visibility.
To import symbols from a package we use the import
statement:
import
ImportList ;
import
ImportList Blockimport
ImportList Block else
CodeStatement,
... ,
ImportItemprotected
] Identifier =
] NonAssignmentExpression [:
Version]The first form of the import
statement (without a Block) imports symbols into
the current lexical scope. The second and third forms import symbols into the lexical scope of the Block.
If the imports are unsuccessful, the first two forms of the import
statement throw an exception, while the last
form executes the CodeStatement after the else
keyword.
An import
statement can import one or more packages separated by commas. Each ImportItem
specifies one package to be imported. The NonAssignmentExpression should evaluate to a string
that contains a URI where the package may be found. If present, Version indicates the version
of the package's exports to be imported; if not present, Version defaults to version 1.
An ImportItem can introduce a name for the imported package if the NonAssignmentExpression
is preceded by Identifier =
. Identifier
becomes bound (either in the current lexical scope or in the Block's scope) to the imported package
as a whole. Individual symbols can be extracted from the package by using Identifier with the
::
operator. For example, if package at URI P has public
symbols
a
and b
, then after the statement
import x=
P;
P's symbols can be referenced as either a
, b
, x::a
, or x::b
.
If an ImportItem contains the keyword protected
, then
the imported symbols can only be accessed using the ::
operator. If we were to import
package P using
import protected x=
P;
then we'd have to access P's symbols using either x::a
or x::b
.
If two imports in the same scope import packages with clashing symbols, then neither symbol is accessible unless qualified
using the ::
operator. If an imported symbol clashes with a symbol declared in the same
scope, then the declared symbol shadows the imported symbol. Scope rules 3 and
4 apply here as well, so the following code is illegal because a
is referenced and then redefined:
import x=
P;
References P's
var y=a; //a
Redefines
const a=17; //a
in same scope
Version names cannot be imported.
Do we want to use URIs to locate packages, or do we want to invent our own, separate mechanism to do this?
Should we make private
illegal outside a class rather than making it equivalent to
package
?
Should we introduce a local
Visibility prefix that explicitly
means that the declaration is visible locally? This wouldn't provide any additional functionality but it would provide a
convenient name for talking about the four kinds of visibility prefixes.
What should the default visibilities be? The current defaults are loosely modeled after Java:
Definition Location | Default visibility |
---|---|
Package top level | package (equivalent to local in this case) |
Inside a statement outside a function or class | local |
Function or method code's top level | local |
Inside a statement inside a function or method | local |
Class declaration block's top level | package |
Inside a statement inside a class declaration block | local |
Waldemar Horwat Last modified Wednesday, March 24, 1999 |