Online Book Reader

Home Category

Beautiful Code [142]

By Root 5318 0
and prefix to define the symbols used in the language. Most languages have some notation for defining new symbols, such as variable names. In a very simple language, when we encounter a new word, we might give it a definition and put it in the symbol table. In a more sophisticated language, we would want a notion of scope, giving the programmer convenient control over the lifespan and visibility of a variable.

A scope is a region of a program in which a variable is defined and accessible. Scopes can be nested inside of other scopes. Variables defined in a scope are not visible outside of the scope.

We will keep the current scope object in the scope variable:

var scope;

original_scope is the prototype for all scope objects. It contains a define method that is used to define new variables in the scope. The define method transforms a name token into a variable token. It produces an error if the variable has already been defined in the scope or if the name has already been used as a reserved word:

var original_scope = {

define: function (n) {

var t = this.def[n.value];

if (typeof t === "object") {

n.error(t.reserved ?

"Already reserved." :

"Already defined.");

}

this.def[n.value] = n;

n.reserved = false;

n.nud = itself;

n.led = null;

n.std = null;

n.lbp = 0;

n.scope = scope;

return n;

},

The find method is used to find the definition of a name. It starts with the current scope and will go, if necessary, back through the chain of parent scopes and ultimately to the symbol table. It returns symbol_table["(name")] if it cannot find a definition:

find: function (n) {

var e = this;

while (true) {

var o = e.def[n];

if (o) {

return o;

}

e = e.parent;

if (!e) {

return symbol_table[

symbol_table.hasOwnProperty(n) ?

n : "(name)"];

}

}

},

The pop method closes a scope:

pop: function () {

scope = this.parent;

},

The reserve method is used to indicate that a name has been used as a reserved word in the current scope:

reserve: function (n) {

if (n.arity !== "name" || n.reserved) {

return;

}

var t = this.def[n.value];

if (t) {

if (t.reserved) {

return;

}

if (t.arity === "name") {

n.error("Already defined.");

}

}

this.def[n.value] = n;

n.reserved = true;

}

};

We need a policy for reserved words. In some languages, words that are used structurally (such as if) are reserved and cannot be used as variable names. The flexibility of our parser allows us to have a more useful policy. For example, we can say that in any function, any name may be used as a structure word or as a variable, but not as both. Also, we will reserve words locally only after they are used as reserved words. This makes things better for the language designer because adding new structure words to the language will not break existing programs, and it makes things better for programmers because they are not hampered by irrelevant restrictions on the use of names.

Whenever we want to establish a new scope for a function or a block, we call the new_scope function, which makes a new instance of the original scope prototype:

var new_scope = function () {

var s = scope;

scope = object(original_scope);

scope.def = {};

scope.parent = s;

return scope;

};

Top Down Operator Precedence > Statements

9.11. Statements

Pratt's original formulation worked with functional languages in which everything is an expression. Most mainstream languages have statements. We can easily handle statements by adding another method to tokens, std (statement denotation). An std is like a nud except that it is only used at the beginning of a statement.

The statement function parses one statement. If the current token has an std method, the token is reserved and the std is invoked. Otherwise, we assume an expression statement terminated with a ;. For reliability, we reject an expression statement that is not an assignment or invocation:

var statement = function ( ) {

var n = token, v;

if (n.std) {

Return Main Page Previous Page Next Page

®Online Book Reader