Project

General

Profile

1
/* ***** BEGIN LICENSE BLOCK *****
2
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3
 *
4
 * The contents of this file are subject to the Mozilla Public License Version
5
 * 1.1 (the "License"); you may not use this file except in compliance with
6
 * the License. You may obtain a copy of the License at
7
 * http://www.mozilla.org/MPL/
8
 *
9
 * Software distributed under the License is distributed on an "AS IS" basis,
10
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11
 * for the specific language governing rights and limitations under the
12
 * License.
13
 *
14
 * The Original Code is the Narcissus JavaScript engine.
15
 *
16
 * The Initial Developer of the Original Code is
17
 * Brendan Eich <brendan@mozilla.org>.
18
 * Portions created by the Initial Developer are Copyright (C) 2004
19
 * the Initial Developer. All Rights Reserved.
20
 *
21
 * Contributor(s): Richard Hundt <www.plextk.org>
22
 *
23
 * Alternatively, the contents of this file may be used under the terms of
24
 * either the GNU General Public License Version 2 or later (the "GPL"), or
25
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26
 * in which case the provisions of the GPL or the LGPL are applicable instead
27
 * of those above. If you wish to allow use of your version of this file only
28
 * under the terms of either the GPL or the LGPL, and not to allow others to
29
 * use your version of this file under the terms of the MPL, indicate your
30
 * decision by deleting the provisions above and replace them with the notice
31
 * and other provisions required by the GPL or the LGPL. If you do not delete
32
 * the provisions above, a recipient may use your version of this file under
33
 * the terms of any one of the MPL, the GPL or the LGPL.
34
 *
35
 * ***** END LICENSE BLOCK ***** */
36

    
37
/*
38
 * Narcissus - JS implemented in JS.
39
 *
40
 * Lexical scanner and parser.
41
 */
42

    
43
// jrh
44
//module('JS.Parse');
45

    
46
// Build a regexp that recognizes operators and punctuators (except newline).
47
var opRegExp =
48
/^;|^,|^\?|^:|^\|\||^\&\&|^\||^\^|^\&|^===|^==|^=|^!==|^!=|^<<|^<=|^<|^>>>|^>>|^>=|^>|^\+\+|^\-\-|^\+|^\-|^\*|^\/|^%|^!|^~|^\.|^\[|^\]|^\{|^\}|^\(|^\)/;
49

    
50
// A regexp to match floating point literals (but not integer literals).
51
var fpRegExp = /^\d+\.\d*(?:[eE][-+]?\d+)?|^\d+(?:\.\d*)?[eE][-+]?\d+|^\.\d+(?:[eE][-+]?\d+)?/;
52

    
53
function Tokenizer(s, f, l) {
54
    this.cursor = 0;
55
    this.source = String(s);
56
    this.tokens = [];
57
    this.tokenIndex = 0;
58
    this.lookahead = 0;
59
    this.scanNewlines = false;
60
    this.scanOperand = true;
61
    this.filename = f || "";
62
    this.lineno = l || 1;
63
}
64

    
65
Tokenizer.prototype = {
66
    input : function() {
67
        return this.source.substring(this.cursor);
68
    },
69

    
70
    done : function() {
71
        return this.peek() == END;
72
    },
73

    
74
    token : function() {
75
        return this.tokens[this.tokenIndex];
76
    },
77

    
78
    match: function (tt) {
79
        return this.get() == tt || this.unget();
80
    },
81

    
82
    mustMatch: function (tt) {
83
        if (!this.match(tt))
84
            throw this.newSyntaxError("Missing " + this.tokens[tt].toLowerCase());
85
        return this.token();
86
    },
87

    
88
    peek: function () {
89
        var tt;
90
        if (this.lookahead) {
91
            tt = this.tokens[(this.tokenIndex + this.lookahead) & 3].type;
92
        } else {
93
            tt = this.get();
94
            this.unget();
95
        }
96
        return tt;
97
    },
98

    
99
    peekOnSameLine: function () {
100
        this.scanNewlines = true;
101
        var tt = this.peek();
102
        this.scanNewlines = false;
103
        return tt;
104
    },
105

    
106
    get: function () {
107
        var token;
108
        while (this.lookahead) {
109
            --this.lookahead;
110
            this.tokenIndex = (this.tokenIndex + 1) & 3;
111
            token = this.tokens[this.tokenIndex];
112
            if (token.type != NEWLINE || this.scanNewlines)
113
                return token.type;
114
        }
115

    
116
        for (;;) {
117
            var input = this.input();
118
            var rx = this.scanNewlines ? /^[ \t]+/ : /^\s+/;
119
            var match = input.match(rx);
120
            if (match) {
121
                var spaces = match[0];
122
                this.cursor += spaces.length;
123
                var newlines = spaces.match(/\n/g);
124
                if (newlines)
125
                    this.lineno += newlines.length;
126
                input = this.input();
127
            }
128

    
129
            if (!(match = input.match(/^\/(?:\*(?:.|\n)*?\*\/|\/.*)/)))
130
                break;
131
            var comment = match[0];
132
            this.cursor += comment.length;
133
            newlines = comment.match(/\n/g);
134
            if (newlines)
135
                this.lineno += newlines.length
136
        }
137

    
138
        this.tokenIndex = (this.tokenIndex + 1) & 3;
139
        token = this.tokens[this.tokenIndex];
140
        if (!token)
141
            this.tokens[this.tokenIndex] = token = {};
142
        if (!input)
143
            return token.type = END;
144
        if ((match = input.match(fpRegExp))) {
145
            token.type = NUMBER;
146
            token.value = parseFloat(match[0]);
147
        } else if ((match = input.match(/^0[xX][\da-fA-F]+|^0[0-7]*|^\d+/))) {
148
            token.type = NUMBER;
149
            token.value = parseInt(match[0]);
150
        } else if ((match = input.match(/^((\$\w*)|(\w+))/))) {
151
            var id = match[0];
152
            token.type = keywords[id] || IDENTIFIER;
153
            token.value = id;
154
        } else if ((match = input.match(/^"(?:\\.|[^"])*"|^'(?:[^']|\\.)*'/))) {
155
            token.type = STRING;
156
            token.value = eval(match[0]);
157
        } else if (this.scanOperand &&
158
                   (match = input.match(/^\/((?:\\.|[^\/])+)\/([gi]*)/))) {
159
            token.type = REGEXP;
160
            token.value = new RegExp(match[1], match[2]);
161
        } else if ((match = input.match(opRegExp))) {
162
            var op = match[0];
163
            if (assignOps[op] && input[op.length] == '=') {
164
                token.type = ASSIGN;
165
                token.assignOp = GLOBAL[opTypeNames[op]];
166
                match[0] += '=';
167
            } else {
168
                token.type = GLOBAL[opTypeNames[op]];
169
                if (this.scanOperand &&
170
                    (token.type == PLUS || token.type == MINUS)) {
171
                    token.type += UNARY_PLUS - PLUS;
172
                }
173
                token.assignOp = null;
174
            }
175
            //debug('token.value => '+op+', token.type => '+token.type);
176
            token.value = op;
177
        } else {
178
            throw this.newSyntaxError("Illegal token");
179
        }
180

    
181
        token.start = this.cursor;
182
        this.cursor += match[0].length;
183
        token.end = this.cursor;
184
        token.lineno = this.lineno;
185
        return token.type;
186
    },
187

    
188
    unget: function () {
189
        if (++this.lookahead == 4) throw "PANIC: too much lookahead!";
190
        this.tokenIndex = (this.tokenIndex - 1) & 3;
191
    },
192

    
193
    newSyntaxError: function (m) {
194
        var e = new SyntaxError(m, this.filename, this.lineno);
195
        e.source = this.source;
196
        e.cursor = this.cursor;
197
        return e;
198
    }
199
};
200

    
201
function CompilerContext(inFunction) {
202
    this.inFunction = inFunction;
203
    this.stmtStack = [];
204
    this.funDecls = [];
205
    this.varDecls = [];
206
}
207

    
208
var CCp = CompilerContext.prototype;
209
CCp.bracketLevel = CCp.curlyLevel = CCp.parenLevel = CCp.hookLevel = 0;
210
CCp.ecmaStrictMode = CCp.inForLoopInit = false;
211

    
212
function Script(t, x) {
213
    var n = Statements(t, x);
214
    n.type = SCRIPT;
215
    n.funDecls = x.funDecls;
216
    n.varDecls = x.varDecls;
217
    return n;
218
}
219

    
220
// Node extends Array, which we extend slightly with a top-of-stack method.
221
Array.prototype.top = function() {
222
    return this.length && this[this.length-1]; 
223
}
224

    
225
function NarcNode(t, type) {
226
    var token = t.token();
227
    if (token) {
228
        this.type = type || token.type;
229
        this.value = token.value;
230
        this.lineno = token.lineno;
231
        this.start = token.start;
232
        this.end = token.end;
233
    } else {
234
        this.type = type;
235
        this.lineno = t.lineno;
236
    }
237
    this.tokenizer = t;
238
    for (var i = 2; i < arguments.length; i++)
239
        this.push(arguments[i]);
240
}
241

    
242
var Np = NarcNode.prototype = new Array();
243
Np.constructor = NarcNode;
244
Np.$length = 0;
245
Np.toSource = Object.prototype.toSource;
246

    
247
// Always use push to add operands to an expression, to update start and end.
248
Np.push = function (kid) {
249
    if (kid.start < this.start)
250
        this.start = kid.start;
251
    if (this.end < kid.end)
252
        this.end = kid.end;
253
    //debug('length before => '+this.$length);
254
    this[this.$length] = kid;
255
    this.$length++;
256
    //debug('length after => '+this.$length);
257
}
258

    
259
NarcNode.indentLevel = 0;
260

    
261
function tokenstr(tt) {
262
    var t = tokens[tt];
263
    return /^\W/.test(t) ? opTypeNames[t] : t.toUpperCase();
264
}
265

    
266
Np.toString = function () {
267
    var a = [];
268
    for (var i in this) {
269
        if (this.hasOwnProperty(i) && i != 'type')
270
            a.push({id: i, value: this[i]});
271
    }
272
    a.sort(function (a,b) { return (a.id < b.id) ? -1 : 1; });
273
    INDENTATION = "    ";
274
    var n = ++NarcNode.indentLevel;
275
    var s = "{\n" + INDENTATION.repeat(n) + "type: " + tokenstr(this.type);
276
    for (i = 0; i < a.length; i++)
277
        s += ",\n" + INDENTATION.repeat(n) + a[i].id + ": " + a[i].value;
278
    n = --NarcNode.indentLevel;
279
    s += "\n" + INDENTATION.repeat(n) + "}";
280
    return s;
281
}
282

    
283
Np.getSource = function () {
284
    return this.tokenizer.source.slice(this.start, this.end);
285
};
286

    
287
Np.filename = function () { return this.tokenizer.filename; };
288

    
289
String.prototype.repeat = function (n) {
290
    var s = "", t = this + s;
291
    while (--n >= 0)
292
        s += t;
293
    return s;
294
}
295

    
296
// Statement stack and nested statement handler.
297
function nest(t, x, node, func, end) {
298
    x.stmtStack.push(node);
299
    var n = func(t, x);
300
    x.stmtStack.pop();
301
    end && t.mustMatch(end);
302
    return n;
303
}
304

    
305
function Statements(t, x) {
306
    var n = new NarcNode(t, BLOCK);
307
    x.stmtStack.push(n);
308
    while (!t.done() && t.peek() != RIGHT_CURLY)
309
        n.push(Statement(t, x));
310
    x.stmtStack.pop();
311
    return n;
312
}
313

    
314
function Block(t, x) {
315
    t.mustMatch(LEFT_CURLY);
316
    var n = Statements(t, x);
317
    t.mustMatch(RIGHT_CURLY);
318
    return n;
319
}
320

    
321
DECLARED_FORM = 0; EXPRESSED_FORM = 1; STATEMENT_FORM = 2;
322

    
323
function Statement(t, x) {
324
    var i, label, n, n2, ss, tt = t.get();
325

    
326
    // Cases for statements ending in a right curly return early, avoiding the
327
    // common semicolon insertion magic after this switch.
328
    switch (tt) {
329
      case FUNCTION:
330
        return FunctionDefinition(t, x, true,
331
                                  (x.stmtStack.length > 1)
332
                                  ? STATEMENT_FORM
333
                                  : DECLARED_FORM);
334

    
335
      case LEFT_CURLY:
336
        n = Statements(t, x);
337
        t.mustMatch(RIGHT_CURLY);
338
        return n;
339

    
340
      case IF:
341
        n = new NarcNode(t);
342
        n.condition = ParenExpression(t, x);
343
        x.stmtStack.push(n);
344
        n.thenPart = Statement(t, x);
345
        n.elsePart = t.match(ELSE) ? Statement(t, x) : null;
346
        x.stmtStack.pop();
347
        return n;
348

    
349
      case SWITCH:
350
        n = new NarcNode(t);
351
        t.mustMatch(LEFT_PAREN);
352
        n.discriminant = Expression(t, x);
353
        t.mustMatch(RIGHT_PAREN);
354
        n.cases = [];
355
        n.defaultIndex = -1;
356
        x.stmtStack.push(n);
357
        t.mustMatch(LEFT_CURLY);
358
        while ((tt = t.get()) != RIGHT_CURLY) {
359
            switch (tt) {
360
              case DEFAULT:
361
                if (n.defaultIndex >= 0)
362
                    throw t.newSyntaxError("More than one switch default");
363
                // FALL THROUGH
364
              case CASE:
365
                n2 = new NarcNode(t);
366
                if (tt == DEFAULT)
367
                    n.defaultIndex = n.cases.length;
368
                else
369
                    n2.caseLabel = Expression(t, x, COLON);
370
                break;
371
              default:
372
                throw t.newSyntaxError("Invalid switch case");
373
            }
374
            t.mustMatch(COLON);
375
            n2.statements = new NarcNode(t, BLOCK);
376
            while ((tt=t.peek()) != CASE && tt != DEFAULT && tt != RIGHT_CURLY)
377
                n2.statements.push(Statement(t, x));
378
            n.cases.push(n2);
379
        }
380
        x.stmtStack.pop();
381
        return n;
382

    
383
      case FOR:
384
        n = new NarcNode(t);
385
        n.isLoop = true;
386
        t.mustMatch(LEFT_PAREN);
387
        if ((tt = t.peek()) != SEMICOLON) {
388
            x.inForLoopInit = true;
389
            if (tt == VAR || tt == CONST) {
390
                t.get();
391
                n2 = Variables(t, x);
392
            } else {
393
                n2 = Expression(t, x);
394
            }
395
            x.inForLoopInit = false;
396
        }
397
        if (n2 && t.match(IN)) {
398
            n.type = FOR_IN;
399
            if (n2.type == VAR) {
400
                if (n2.$length != 1) {
401
                    throw new SyntaxError("Invalid for..in left-hand side",
402
                                          t.filename, n2.lineno);
403
                }
404

    
405
                // NB: n2[0].type == IDENTIFIER and n2[0].value == n2[0].name.
406
                n.iterator = n2[0];
407
                n.varDecl = n2;
408
            } else {
409
                n.iterator = n2;
410
                n.varDecl = null;
411
            }
412
            n.object = Expression(t, x);
413
        } else {
414
            n.setup = n2 || null;
415
            t.mustMatch(SEMICOLON);
416
            n.condition = (t.peek() == SEMICOLON) ? null : Expression(t, x);
417
            t.mustMatch(SEMICOLON);
418
            n.update = (t.peek() == RIGHT_PAREN) ? null : Expression(t, x);
419
        }
420
        t.mustMatch(RIGHT_PAREN);
421
        n.body = nest(t, x, n, Statement);
422
        return n;
423

    
424
      case WHILE:
425
        n = new NarcNode(t);
426
        n.isLoop = true;
427
        n.condition = ParenExpression(t, x);
428
        n.body = nest(t, x, n, Statement);
429
        return n;
430

    
431
      case DO:
432
        n = new NarcNode(t);
433
        n.isLoop = true;
434
        n.body = nest(t, x, n, Statement, WHILE);
435
        n.condition = ParenExpression(t, x);
436
        if (!x.ecmaStrictMode) {
437
            // <script language="JavaScript"> (without version hints) may need
438
            // automatic semicolon insertion without a newline after do-while.
439
            // See http://bugzilla.mozilla.org/show_bug.cgi?id=238945.
440
            t.match(SEMICOLON);
441
            return n;
442
        }
443
        break;
444

    
445
      case BREAK:
446
      case CONTINUE:
447
        n = new NarcNode(t);
448
        if (t.peekOnSameLine() == IDENTIFIER) {
449
            t.get();
450
            n.label = t.token().value;
451
        }
452
        ss = x.stmtStack;
453
        i = ss.length;
454
        label = n.label;
455
        if (label) {
456
            do {
457
                if (--i < 0)
458
                    throw t.newSyntaxError("Label not found");
459
            } while (ss[i].label != label);
460
        } else {
461
            do {
462
                if (--i < 0) {
463
                    throw t.newSyntaxError("Invalid " + ((tt == BREAK)
464
                                                         ? "break"
465
                                                         : "continue"));
466
                }
467
            } while (!ss[i].isLoop && (tt != BREAK || ss[i].type != SWITCH));
468
        }
469
        n.target = ss[i];
470
        break;
471

    
472
      case TRY:
473
        n = new NarcNode(t);
474
        n.tryBlock = Block(t, x);
475
        n.catchClauses = [];
476
        while (t.match(CATCH)) {
477
            n2 = new NarcNode(t);
478
            t.mustMatch(LEFT_PAREN);
479
            n2.varName = t.mustMatch(IDENTIFIER).value;
480
            if (t.match(IF)) {
481
                if (x.ecmaStrictMode)
482
                    throw t.newSyntaxError("Illegal catch guard");
483
                if (n.catchClauses.length && !n.catchClauses.top().guard)
484
                    throw t.newSyntaxError("Guarded catch after unguarded");
485
                n2.guard = Expression(t, x);
486
            } else {
487
                n2.guard = null;
488
            }
489
            t.mustMatch(RIGHT_PAREN);
490
            n2.block = Block(t, x);
491
            n.catchClauses.push(n2);
492
        }
493
        if (t.match(FINALLY))
494
            n.finallyBlock = Block(t, x);
495
        if (!n.catchClauses.length && !n.finallyBlock)
496
            throw t.newSyntaxError("Invalid try statement");
497
        return n;
498

    
499
      case CATCH:
500
      case FINALLY:
501
        throw t.newSyntaxError(tokens[tt] + " without preceding try");
502

    
503
      case THROW:
504
        n = new NarcNode(t);
505
        n.exception = Expression(t, x);
506
        break;
507

    
508
      case RETURN:
509
        if (!x.inFunction)
510
            throw t.newSyntaxError("Invalid return");
511
        n = new NarcNode(t);
512
        tt = t.peekOnSameLine();
513
        if (tt != END && tt != NEWLINE && tt != SEMICOLON && tt != RIGHT_CURLY)
514
            n.value = Expression(t, x);
515
        break;
516

    
517
      case WITH:
518
        n = new NarcNode(t);
519
        n.object = ParenExpression(t, x);
520
        n.body = nest(t, x, n, Statement);
521
        return n;
522

    
523
      case VAR:
524
      case CONST:
525
        n = Variables(t, x);
526
        break;
527

    
528
      case DEBUGGER:
529
        n = new NarcNode(t);
530
        break;
531

    
532
      case REQUIRE:
533
        n = new NarcNode(t);
534
        n.classPath = ParenExpression(t, x);
535
        break;
536

    
537
      case NEWLINE:
538
      case SEMICOLON:
539
        n = new NarcNode(t, SEMICOLON);
540
        n.expression = null;
541
        return n;
542

    
543
      default:
544
        if (tt == IDENTIFIER && t.peek() == COLON) {
545
            label = t.token().value;
546
            ss = x.stmtStack;
547
            for (i = ss.length-1; i >= 0; --i) {
548
                if (ss[i].label == label)
549
                    throw t.newSyntaxError("Duplicate label");
550
            }
551
            t.get();
552
            n = new NarcNode(t, LABEL);
553
            n.label = label;
554
            n.statement = nest(t, x, n, Statement);
555
            return n;
556
        }
557

    
558
        n = new NarcNode(t, SEMICOLON);
559
        t.unget();
560
        n.expression = Expression(t, x);
561
        n.end = n.expression.end;
562
        break;
563
    }
564

    
565
    if (t.lineno == t.token().lineno) {
566
        tt = t.peekOnSameLine();
567
        if (tt != END && tt != NEWLINE && tt != SEMICOLON && tt != RIGHT_CURLY)
568
            throw t.newSyntaxError("Missing ; before statement");
569
    }
570
    t.match(SEMICOLON);
571
    return n;
572
}
573

    
574
function FunctionDefinition(t, x, requireName, functionForm) {
575
    var f = new NarcNode(t);
576
    if (f.type != FUNCTION)
577
        f.type = (f.value == "get") ? GETTER : SETTER;
578
    if (t.match(IDENTIFIER)) {
579
        f.name = t.token().value;
580
    }
581
    else if (requireName)
582
        throw t.newSyntaxError("Missing function identifier");
583

    
584
    t.mustMatch(LEFT_PAREN);
585
    f.params = [];
586
    var tt;
587
    while ((tt = t.get()) != RIGHT_PAREN) {
588
        if (tt != IDENTIFIER)
589
            throw t.newSyntaxError("Missing formal parameter");
590
        f.params.push(t.token().value);
591
        if (t.peek() != RIGHT_PAREN)
592
            t.mustMatch(COMMA);
593
    }
594

    
595
    t.mustMatch(LEFT_CURLY);
596
    var x2 = new CompilerContext(true);
597
    f.body = Script(t, x2);
598
    t.mustMatch(RIGHT_CURLY);
599
    f.end = t.token().end;
600

    
601
    f.functionForm = functionForm;
602
    if (functionForm == DECLARED_FORM) {
603
        x.funDecls.push(f);
604
    }
605

    
606
    return f;
607
}
608

    
609
function Variables(t, x) {
610
    var n = new NarcNode(t);
611
    do {
612
        t.mustMatch(IDENTIFIER);
613
        var n2 = new NarcNode(t);
614
        n2.name = n2.value;
615
        if (t.match(ASSIGN)) {
616
            if (t.token().assignOp)
617
                throw t.newSyntaxError("Invalid variable initialization");
618
            n2.initializer = Expression(t, x, COMMA);
619
        }
620
        n2.readOnly = (n.type == CONST);
621
        n.push(n2);
622
        x.varDecls.push(n2);
623
    } while (t.match(COMMA));
624
    return n;
625
}
626

    
627
function ParenExpression(t, x) {
628
    t.mustMatch(LEFT_PAREN);
629
    var n = Expression(t, x);
630
    t.mustMatch(RIGHT_PAREN);
631
    return n;
632
}
633

    
634
var opPrecedence = {
635
    SEMICOLON: 0,
636
    COMMA: 1,
637
    ASSIGN: 2, HOOK: 2, COLON: 2, CONDITIONAL: 2,
638
    // The above all have to have the same precedence, see bug 330975.
639
    OR: 4,
640
    AND: 5,
641
    BITWISE_OR: 6,
642
    BITWISE_XOR: 7,
643
    BITWISE_AND: 8,
644
    EQ: 9, NE: 9, STRICT_EQ: 9, STRICT_NE: 9,
645
    LT: 10, LE: 10, GE: 10, GT: 10, IN: 10, INSTANCEOF: 10,
646
    LSH: 11, RSH: 11, URSH: 11,
647
    PLUS: 12, MINUS: 12,
648
    MUL: 13, DIV: 13, MOD: 13,
649
    DELETE: 14, VOID: 14, TYPEOF: 14, // PRE_INCREMENT: 14, PRE_DECREMENT: 14,
650
    NOT: 14, BITWISE_NOT: 14, UNARY_PLUS: 14, UNARY_MINUS: 14,
651
    INCREMENT: 15, DECREMENT: 15,     // postfix
652
    NEW: 16,
653
    DOT: 17
654
};
655

    
656
// Map operator type code to precedence.
657
for (i in opPrecedence)
658
    opPrecedence[GLOBAL[i]] = opPrecedence[i];
659

    
660
var opArity = {
661
    COMMA: -2,
662
    ASSIGN: 2,
663
    CONDITIONAL: 3,
664
    OR: 2,
665
    AND: 2,
666
    BITWISE_OR: 2,
667
    BITWISE_XOR: 2,
668
    BITWISE_AND: 2,
669
    EQ: 2, NE: 2, STRICT_EQ: 2, STRICT_NE: 2,
670
    LT: 2, LE: 2, GE: 2, GT: 2, IN: 2, INSTANCEOF: 2,
671
    LSH: 2, RSH: 2, URSH: 2,
672
    PLUS: 2, MINUS: 2,
673
    MUL: 2, DIV: 2, MOD: 2,
674
    DELETE: 1, VOID: 1, TYPEOF: 1,  // PRE_INCREMENT: 1, PRE_DECREMENT: 1,
675
    NOT: 1, BITWISE_NOT: 1, UNARY_PLUS: 1, UNARY_MINUS: 1,
676
    INCREMENT: 1, DECREMENT: 1,     // postfix
677
    NEW: 1, NEW_WITH_ARGS: 2, DOT: 2, INDEX: 2, CALL: 2,
678
    ARRAY_INIT: 1, OBJECT_INIT: 1, GROUP: 1
679
};
680

    
681
// Map operator type code to arity.
682
for (i in opArity)
683
    opArity[GLOBAL[i]] = opArity[i];
684

    
685
function Expression(t, x, stop) {
686
    var n, id, tt, operators = [], operands = [];
687
    var bl = x.bracketLevel, cl = x.curlyLevel, pl = x.parenLevel,
688
        hl = x.hookLevel;
689

    
690
    function reduce() {
691
        //debug('OPERATORS => '+operators);
692
        var n = operators.pop();
693
        var op = n.type;
694
        var arity = opArity[op];
695
        if (arity == -2) {
696
            // Flatten left-associative trees.
697
            var left = operands.length >= 2 && operands[operands.length-2];
698
            if (left.type == op) {
699
                var right = operands.pop();
700
                left.push(right);
701
                return left;
702
            }
703
            arity = 2;
704
        }
705

    
706
        // Always use push to add operands to n, to update start and end.
707
        var a = operands.splice(operands.length - arity, operands.length);
708
        for (var i = 0; i < arity; i++) {
709
            n.push(a[i]);
710
        }
711

    
712
        // Include closing bracket or postfix operator in [start,end).
713
        if (n.end < t.token().end)
714
            n.end = t.token().end;
715

    
716
        operands.push(n);
717
        return n;
718
    }
719

    
720
loop:
721
    while ((tt = t.get()) != END) {
722
        //debug('TT => '+tokens[tt]);
723
        if (tt == stop &&
724
            x.bracketLevel == bl && x.curlyLevel == cl && x.parenLevel == pl &&
725
            x.hookLevel == hl) {
726
            // Stop only if tt matches the optional stop parameter, and that
727
            // token is not quoted by some kind of bracket.
728
            break;
729
        }
730
        switch (tt) {
731
          case SEMICOLON:
732
            // NB: cannot be empty, Statement handled that.
733
            break loop;
734

    
735
          case ASSIGN:
736
          case HOOK:
737
          case COLON:
738
            if (t.scanOperand)
739
                break loop;
740
            // Use >, not >=, for right-associative ASSIGN and HOOK/COLON.
741
            while (operators.length && opPrecedence[operators.top().type] > opPrecedence[tt] ||
742
                   (tt == COLON && operators.top().type == ASSIGN)) {
743
                reduce();
744
            }
745
            if (tt == COLON) {
746
                n = operators.top();
747
                if (n.type != HOOK)
748
                    throw t.newSyntaxError("Invalid label");
749
                n.type = CONDITIONAL;
750
                --x.hookLevel;
751
            } else {
752
                operators.push(new NarcNode(t));
753
                if (tt == ASSIGN)
754
                    operands.top().assignOp = t.token().assignOp;
755
                else
756
                    ++x.hookLevel;      // tt == HOOK
757
            }
758
            t.scanOperand = true;
759
            break;
760

    
761
          case IN:
762
            // An in operator should not be parsed if we're parsing the head of
763
            // a for (...) loop, unless it is in the then part of a conditional
764
            // expression, or parenthesized somehow.
765
            if (x.inForLoopInit && !x.hookLevel &&
766
                !x.bracketLevel && !x.curlyLevel && !x.parenLevel) {
767
                break loop;
768
            }
769
            // FALL THROUGH
770
          case COMMA:
771
            // Treat comma as left-associative so reduce can fold left-heavy
772
            // COMMA trees into a single array.
773
            // FALL THROUGH
774
          case OR:
775
          case AND:
776
          case BITWISE_OR:
777
          case BITWISE_XOR:
778
          case BITWISE_AND:
779
          case EQ: case NE: case STRICT_EQ: case STRICT_NE:
780
          case LT: case LE: case GE: case GT:
781
          case INSTANCEOF:
782
          case LSH: case RSH: case URSH:
783
          case PLUS: case MINUS:
784
          case MUL: case DIV: case MOD:
785
          case DOT:
786
            if (t.scanOperand)
787
                break loop;
788
            while (operators.length && opPrecedence[operators.top().type] >= opPrecedence[tt])
789
                reduce();
790
            if (tt == DOT) {
791
                t.mustMatch(IDENTIFIER);
792
                operands.push(new NarcNode(t, DOT, operands.pop(), new NarcNode(t)));
793
            } else {
794
                operators.push(new NarcNode(t));
795
                t.scanOperand = true;
796
            }
797
            break;
798

    
799
          case DELETE: case VOID: case TYPEOF:
800
          case NOT: case BITWISE_NOT: case UNARY_PLUS: case UNARY_MINUS:
801
          case NEW:
802
            if (!t.scanOperand)
803
                break loop;
804
            operators.push(new NarcNode(t));
805
            break;
806

    
807
          case INCREMENT: case DECREMENT:
808
            if (t.scanOperand) {
809
                operators.push(new NarcNode(t));  // prefix increment or decrement
810
            } else {
811
                // Use >, not >=, so postfix has higher precedence than prefix.
812
                while (operators.length && opPrecedence[operators.top().type] > opPrecedence[tt])
813
                    reduce();
814
                n = new NarcNode(t, tt, operands.pop());
815
                n.postfix = true;
816
                operands.push(n);
817
            }
818
            break;
819

    
820
          case FUNCTION:
821
            if (!t.scanOperand)
822
                break loop;
823
            operands.push(FunctionDefinition(t, x, false, EXPRESSED_FORM));
824
            t.scanOperand = false;
825
            break;
826

    
827
          case NULL: case THIS: case TRUE: case FALSE:
828
          case IDENTIFIER: case NUMBER: case STRING: case REGEXP:
829
            if (!t.scanOperand)
830
                break loop;
831
            operands.push(new NarcNode(t));
832
            t.scanOperand = false;
833
            break;
834

    
835
          case LEFT_BRACKET:
836
            if (t.scanOperand) {
837
                // Array initialiser.  Parse using recursive descent, as the
838
                // sub-grammar here is not an operator grammar.
839
                n = new NarcNode(t, ARRAY_INIT);
840
                while ((tt = t.peek()) != RIGHT_BRACKET) {
841
                    if (tt == COMMA) {
842
                        t.get();
843
                        n.push(null);
844
                        continue;
845
                    }
846
                    n.push(Expression(t, x, COMMA));
847
                    if (!t.match(COMMA))
848
                        break;
849
                }
850
                t.mustMatch(RIGHT_BRACKET);
851
                operands.push(n);
852
                t.scanOperand = false;
853
            } else {
854
                // Property indexing operator.
855
                operators.push(new NarcNode(t, INDEX));
856
                t.scanOperand = true;
857
                ++x.bracketLevel;
858
            }
859
            break;
860

    
861
          case RIGHT_BRACKET:
862
            if (t.scanOperand || x.bracketLevel == bl)
863
                break loop;
864
            while (reduce().type != INDEX)
865
                continue;
866
            --x.bracketLevel;
867
            break;
868

    
869
          case LEFT_CURLY:
870
            if (!t.scanOperand)
871
                break loop;
872
            // Object initialiser.  As for array initialisers (see above),
873
            // parse using recursive descent.
874
            ++x.curlyLevel;
875
            n = new NarcNode(t, OBJECT_INIT);
876
          object_init:
877
            if (!t.match(RIGHT_CURLY)) {
878
                do {
879
                    tt = t.get();
880
                    if ((t.token().value == "get" || t.token().value == "set") &&
881
                        t.peek() == IDENTIFIER) {
882
                        if (x.ecmaStrictMode)
883
                            throw t.newSyntaxError("Illegal property accessor");
884
                        n.push(FunctionDefinition(t, x, true, EXPRESSED_FORM));
885
                    } else {
886
                        switch (tt) {
887
                          case IDENTIFIER:
888
                          case NUMBER:
889
                          case STRING:
890
                            id = new NarcNode(t);
891
                            break;
892
                          case RIGHT_CURLY:
893
                            if (x.ecmaStrictMode)
894
                                throw t.newSyntaxError("Illegal trailing ,");
895
                            break object_init;
896
                          default:
897
                            throw t.newSyntaxError("Invalid property name");
898
                        }
899
                        t.mustMatch(COLON);
900
                        n.push(new NarcNode(t, PROPERTY_INIT, id,
901
                                        Expression(t, x, COMMA)));
902
                    }
903
                } while (t.match(COMMA));
904
                t.mustMatch(RIGHT_CURLY);
905
            }
906
            operands.push(n);
907
            t.scanOperand = false;
908
            --x.curlyLevel;
909
            break;
910

    
911
          case RIGHT_CURLY:
912
            if (!t.scanOperand && x.curlyLevel != cl)
913
                throw "PANIC: right curly botch";
914
            break loop;
915

    
916
          case LEFT_PAREN:
917
            if (t.scanOperand) {
918
                operators.push(new NarcNode(t, GROUP));
919
            } else {
920
                while (operators.length && opPrecedence[operators.top().type] > opPrecedence[NEW])
921
                    reduce();
922

    
923
                // Handle () now, to regularize the n-ary case for n > 0.
924
                // We must set scanOperand in case there are arguments and
925
                // the first one is a regexp or unary+/-.
926
                n = operators.top();
927
                t.scanOperand = true;
928
                if (t.match(RIGHT_PAREN)) {
929
                    if (n.type == NEW) {
930
                        --operators.length;
931
                        n.push(operands.pop());
932
                    } else {
933
                        n = new NarcNode(t, CALL, operands.pop(),
934
                                     new NarcNode(t, LIST));
935
                    }
936
                    operands.push(n);
937
                    t.scanOperand = false;
938
                    break;
939
                }
940
                if (n.type == NEW)
941
                    n.type = NEW_WITH_ARGS;
942
                else
943
                    operators.push(new NarcNode(t, CALL));
944
            }
945
            ++x.parenLevel;
946
            break;
947

    
948
          case RIGHT_PAREN:
949
            if (t.scanOperand || x.parenLevel == pl)
950
                break loop;
951
            while ((tt = reduce().type) != GROUP && tt != CALL &&
952
                   tt != NEW_WITH_ARGS) {
953
                continue;
954
            }
955
            if (tt != GROUP) {
956
                n = operands.top();
957
                if (n[1].type != COMMA)
958
                    n[1] = new NarcNode(t, LIST, n[1]);
959
                else
960
                    n[1].type = LIST;
961
            }
962
            --x.parenLevel;
963
            break;
964

    
965
          // Automatic semicolon insertion means we may scan across a newline
966
          // and into the beginning of another statement.  If so, break out of
967
          // the while loop and let the t.scanOperand logic handle errors.
968
          default:
969
            break loop;
970
        }
971
    }
972
    if (x.hookLevel != hl)
973
        throw t.newSyntaxError("Missing : after ?");
974
    if (x.parenLevel != pl)
975
        throw t.newSyntaxError("Missing ) in parenthetical");
976
    if (x.bracketLevel != bl)
977
        throw t.newSyntaxError("Missing ] in index expression");
978
    if (t.scanOperand)
979
        throw t.newSyntaxError("Missing operand");
980

    
981
    // Resume default mode, scanning for operands, not operators.
982
    t.scanOperand = true;
983
    t.unget();
984

    
985
    while (operators.length)
986
        reduce();
987
    return operands.pop();
988
}
989

    
990
function parse(s, f, l) {
991
    var t = new Tokenizer(s, f, l);
992
    var x = new CompilerContext(false);
993
    var n = Script(t, x);
994
    if (!t.done())
995
        throw t.newSyntaxError("Syntax error");
996
    return n;
997
}
998

    
999
debug = function(msg) {
1000
    document.body.appendChild(document.createTextNode(msg));
1001
    document.body.appendChild(document.createElement('br'));
1002
}
1003

    
(8-8/20)