1 /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
\r
3 * The contents of this file are subject to the Netscape Public
\r
4 * License Version 1.1 (the "License"); you may not use this file
\r
5 * except in compliance with the License. You may obtain a copy of
\r
6 * the License at http://www.mozilla.org/NPL/
\r
8 * Software distributed under the License is distributed on an "AS
\r
9 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
\r
10 * implied. See the License for the specific language governing
\r
11 * rights and limitations under the License.
\r
13 * The Original Code is Rhino code, released
\r
16 * The Initial Developer of the Original Code is Netscape
\r
17 * Communications Corporation. Portions created by Netscape are
\r
18 * Copyright (C) 1997-1999 Netscape Communications Corporation. All
\r
25 * Alternatively, the contents of this file may be used under the
\r
26 * terms of the GNU Public License (the "GPL"), in which case the
\r
27 * provisions of the GPL are applicable instead of those above.
\r
28 * If you wish to allow use of your version of this file only
\r
29 * under the terms of the GPL and not to allow others to use your
\r
30 * version of this file under the NPL, indicate your decision by
\r
31 * deleting the provisions above and replace them with the notice
\r
32 * and other provisions required by the GPL. If you do not delete
\r
33 * the provisions above, a recipient may use your version of this
\r
34 * file under either the NPL or the GPL.
\r
37 package org.mozilla.javascript;
\r
39 import org.mozilla.javascript.ErrorReporter;
\r
40 import org.mozilla.javascript.Context;
\r
41 import java.io.IOException;
\r
44 * This class implements the JavaScript parser.
\r
46 * It is based on the C source files jsparse.c and jsparse.h
\r
47 * in the jsref package.
\r
51 * @author Mike McCabe
\r
52 * @author Brendan Eich
\r
57 public Parser(IRFactory nf) {
\r
61 private void mustMatchToken(TokenStream ts, int toMatch, String messageId)
\r
62 throws IOException, JavaScriptException
\r
65 if ((tt = ts.getToken()) != toMatch) {
\r
66 reportError(ts, messageId);
\r
67 ts.ungetToken(tt); // In case the parser decides to continue
\r
71 private void reportError(TokenStream ts, String messageId)
\r
72 throws JavaScriptException
\r
75 ts.reportSyntaxError(messageId, null);
\r
77 /* Throw an exception to unwind the recursive descent parse.
\r
78 * We use JavaScriptException here even though it is really
\r
79 * a different use of the exception than it is usually used
\r
82 throw new JavaScriptException(messageId);
\r
86 * Build a parse tree from the given TokenStream.
\r
88 * @param ts the TokenStream to parse
\r
90 * @return an Object representing the parsed
\r
91 * program. If the parse fails, null will be returned. (The
\r
92 * parse failure will result in a call to the current Context's
\r
95 public Object parse(TokenStream ts)
\r
99 Source source = new Source();
\r
101 int tt; // last token from getToken();
\r
102 int baseLineno = ts.getLineno(); // line number where source starts
\r
104 /* so we have something to add nodes to until
\r
105 * we've collected all the source */
\r
106 Object tempBlock = nf.createLeaf(TokenStream.BLOCK);
\r
109 ts.flags |= ts.TSF_REGEXP;
\r
110 tt = ts.getToken();
\r
111 ts.flags &= ~ts.TSF_REGEXP;
\r
113 if (tt <= ts.EOF) {
\r
117 if (tt == ts.FUNCTION) {
\r
119 nf.addChildToBack(tempBlock, function(ts, source, false));
\r
120 /* function doesn't add its own final EOL,
\r
121 * because it gets SEMI + EOL from Statement when it's
\r
122 * a nested function; so we need to explicitly add an
\r
125 source.append((char)ts.EOL);
\r
126 wellTerminated(ts, ts.FUNCTION);
\r
127 } catch (JavaScriptException e) {
\r
133 nf.addChildToBack(tempBlock, statement(ts, source));
\r
138 // XXX ts.clearPushback() call here?
\r
142 Object pn = nf.createScript(tempBlock, ts.getSourceName(),
\r
143 baseLineno, ts.getLineno(),
\r
144 source.buf.toString());
\r
149 * The C version of this function takes an argument list,
\r
150 * which doesn't seem to be needed for tree generation...
\r
151 * it'd only be useful for checking argument hiding, which
\r
152 * I'm not doing anyway...
\r
154 private Object parseFunctionBody(TokenStream ts, Source source)
\r
157 int oldflags = ts.flags;
\r
158 ts.flags &= ~(TokenStream.TSF_RETURN_EXPR
\r
159 | TokenStream.TSF_RETURN_VOID);
\r
160 ts.flags |= TokenStream.TSF_FUNCTION;
\r
162 Object pn = nf.createBlock(ts.getLineno());
\r
165 while((tt = ts.peekToken()) > ts.EOF && tt != ts.RC) {
\r
166 if (tt == TokenStream.FUNCTION) {
\r
168 nf.addChildToBack(pn, function(ts, source, false));
\r
169 /* function doesn't add its own final EOL,
\r
170 * because it gets SEMI + EOL from Statement when it's
\r
171 * a nested function; so we need to explicitly add an
\r
174 source.append((char)ts.EOL);
\r
175 wellTerminated(ts, ts.FUNCTION);
\r
177 nf.addChildToBack(pn, statement(ts, source));
\r
180 } catch (JavaScriptException e) {
\r
183 // also in finally block:
\r
184 // flushNewLines, clearPushback.
\r
186 ts.flags = oldflags;
\r
192 private Object function(TokenStream ts, Source source, boolean isExpr)
\r
193 throws IOException, JavaScriptException
\r
195 String name = null;
\r
196 Object args = nf.createLeaf(ts.LP);
\r
198 int baseLineno = ts.getLineno(); // line number where source starts
\r
200 // save a reference to the function in the enclosing source.
\r
201 source.append((char) ts.FUNCTION);
\r
202 source.append(source.functionNumber);
\r
203 source.functionNumber++;
\r
205 // make a new Source for the enclosed function
\r
206 source = new Source();
\r
208 // FUNCTION as the first token in a Source means it's a function
\r
209 // definition, and not a reference.
\r
210 source.append((char) ts.FUNCTION);
\r
212 if (ts.matchToken(ts.NAME)) {
\r
213 name = ts.getString();
\r
214 source.addString(ts.NAME, name);
\r
217 ; // it's an anonymous function
\r
219 mustMatchToken(ts, ts.LP, "msg.no.paren.parms");
\r
220 source.append((char) ts.LP);
\r
222 if (!ts.matchToken(ts.RP)) {
\r
223 boolean first = true;
\r
226 source.append((char)ts.COMMA);
\r
228 mustMatchToken(ts, ts.NAME, "msg.no.parm");
\r
229 String s = ts.getString();
\r
230 nf.addChildToBack(args, nf.createName(s));
\r
232 source.addString(ts.NAME, s);
\r
233 } while (ts.matchToken(ts.COMMA));
\r
235 mustMatchToken(ts, ts.RP, "msg.no.paren.after.parms");
\r
237 source.append((char)ts.RP);
\r
239 mustMatchToken(ts, ts.LC, "msg.no.brace.body");
\r
240 source.append((char)ts.LC);
\r
241 source.append((char)ts.EOL);
\r
242 body = parseFunctionBody(ts, source);
\r
243 mustMatchToken(ts, ts.RC, "msg.no.brace.after.body");
\r
244 source.append((char)ts.RC);
\r
245 // skip the last EOL so nested functions work...
\r
247 // name might be null;
\r
248 return nf.createFunction(name, args, body,
\r
249 ts.getSourceName(),
\r
250 baseLineno, ts.getLineno(),
\r
251 source.buf.toString(),
\r
255 private Object statements(TokenStream ts, Source source)
\r
258 Object pn = nf.createBlock(ts.getLineno());
\r
261 while((tt = ts.peekToken()) > ts.EOF && tt != ts.RC) {
\r
262 nf.addChildToBack(pn, statement(ts, source));
\r
268 private Object condition(TokenStream ts, Source source)
\r
269 throws IOException, JavaScriptException
\r
272 mustMatchToken(ts, ts.LP, "msg.no.paren.cond");
\r
273 source.append((char)ts.LP);
\r
274 pn = expr(ts, source, false);
\r
275 mustMatchToken(ts, ts.RP, "msg.no.paren.after.cond");
\r
276 source.append((char)ts.RP);
\r
278 // there's a check here in jsparse.c that corrects = to ==
\r
283 private boolean wellTerminated(TokenStream ts, int lastExprType)
\r
284 throws IOException, JavaScriptException
\r
286 int tt = ts.peekTokenSameLine();
\r
287 if (tt == ts.ERROR) {
\r
291 if (tt != ts.EOF && tt != ts.EOL
\r
292 && tt != ts.SEMI && tt != ts.RC)
\r
294 int version = Context.getContext().getLanguageVersion();
\r
295 if ((tt == ts.FUNCTION || lastExprType == ts.FUNCTION) &&
\r
296 (version < Context.VERSION_1_2)) {
\r
298 * Checking against version < 1.2 and version >= 1.0
\r
299 * in the above line breaks old javascript, so we keep it
\r
300 * this way for now... XXX warning needed?
\r
304 reportError(ts, "msg.no.semi.stmt");
\r
310 // match a NAME; return null if no match.
\r
311 private String matchLabel(TokenStream ts)
\r
312 throws IOException, JavaScriptException
\r
314 int lineno = ts.getLineno();
\r
316 String label = null;
\r
318 tt = ts.peekTokenSameLine();
\r
319 if (tt == ts.NAME) {
\r
321 label = ts.getString();
\r
324 if (lineno == ts.getLineno())
\r
325 wellTerminated(ts, ts.ERROR);
\r
330 private Object statement(TokenStream ts, Source source)
\r
334 return statementHelper(ts, source);
\r
335 } catch (JavaScriptException e) {
\r
336 // skip to end of statement
\r
337 int lineno = ts.getLineno();
\r
341 } while (t != TokenStream.SEMI && t != TokenStream.EOL &&
\r
342 t != TokenStream.EOF && t != TokenStream.ERROR);
\r
343 return nf.createExprStatement(nf.createName("error"), lineno);
\r
348 * Whether the "catch (e: e instanceof Exception) { ... }" syntax
\r
352 private Object statementHelper(TokenStream ts, Source source)
\r
353 throws IOException, JavaScriptException
\r
357 // If skipsemi == true, don't add SEMI + EOL to source at the
\r
358 // end of this statment. For compound statements, IF/FOR etc.
\r
359 boolean skipsemi = false;
\r
363 int lastExprType = 0; // For wellTerminated. 0 to avoid warning.
\r
365 tt = ts.getToken();
\r
368 case TokenStream.IF: {
\r
371 source.append((char)ts.IF);
\r
372 int lineno = ts.getLineno();
\r
373 Object cond = condition(ts, source);
\r
374 source.append((char)ts.LC);
\r
375 source.append((char)ts.EOL);
\r
376 Object ifTrue = statement(ts, source);
\r
377 Object ifFalse = null;
\r
378 if (ts.matchToken(ts.ELSE)) {
\r
379 source.append((char)ts.RC);
\r
380 source.append((char)ts.ELSE);
\r
381 source.append((char)ts.LC);
\r
382 source.append((char)ts.EOL);
\r
383 ifFalse = statement(ts, source);
\r
385 source.append((char)ts.RC);
\r
386 source.append((char)ts.EOL);
\r
387 pn = nf.createIf(cond, ifTrue, ifFalse, lineno);
\r
391 case TokenStream.SWITCH: {
\r
394 source.append((char)ts.SWITCH);
\r
395 pn = nf.createSwitch(ts.getLineno());
\r
397 Object cur_case = null; // to kill warning
\r
398 Object case_statements;
\r
400 mustMatchToken(ts, ts.LP, "msg.no.paren.switch");
\r
401 source.append((char)ts.LP);
\r
402 nf.addChildToBack(pn, expr(ts, source, false));
\r
403 mustMatchToken(ts, ts.RP, "msg.no.paren.after.switch");
\r
404 source.append((char)ts.RP);
\r
405 mustMatchToken(ts, ts.LC, "msg.no.brace.switch");
\r
406 source.append((char)ts.LC);
\r
407 source.append((char)ts.EOL);
\r
409 while ((tt = ts.getToken()) != ts.RC && tt != ts.EOF) {
\r
411 case TokenStream.CASE:
\r
412 source.append((char)ts.CASE);
\r
413 cur_case = nf.createUnary(ts.CASE, expr(ts, source, false));
\r
414 source.append((char)ts.COLON);
\r
415 source.append((char)ts.EOL);
\r
418 case TokenStream.DEFAULT:
\r
419 cur_case = nf.createLeaf(ts.DEFAULT);
\r
420 source.append((char)ts.DEFAULT);
\r
421 source.append((char)ts.COLON);
\r
422 source.append((char)ts.EOL);
\r
423 // XXX check that there isn't more than one default
\r
427 reportError(ts, "msg.bad.switch");
\r
430 mustMatchToken(ts, ts.COLON, "msg.no.colon.case");
\r
432 case_statements = nf.createLeaf(TokenStream.BLOCK);
\r
434 while ((tt = ts.peekToken()) != ts.RC && tt != ts.CASE &&
\r
435 tt != ts.DEFAULT && tt != ts.EOF)
\r
437 nf.addChildToBack(case_statements, statement(ts, source));
\r
440 nf.addChildToBack(cur_case, case_statements);
\r
442 nf.addChildToBack(pn, cur_case);
\r
444 source.append((char)ts.RC);
\r
445 source.append((char)ts.EOL);
\r
449 case TokenStream.WHILE: {
\r
452 source.append((char)ts.WHILE);
\r
453 int lineno = ts.getLineno();
\r
454 Object cond = condition(ts, source);
\r
455 source.append((char)ts.LC);
\r
456 source.append((char)ts.EOL);
\r
457 Object body = statement(ts, source);
\r
458 source.append((char)ts.RC);
\r
459 source.append((char)ts.EOL);
\r
461 pn = nf.createWhile(cond, body, lineno);
\r
466 case TokenStream.DO: {
\r
467 source.append((char)ts.DO);
\r
468 source.append((char)ts.LC);
\r
469 source.append((char)ts.EOL);
\r
471 int lineno = ts.getLineno();
\r
473 Object body = statement(ts, source);
\r
475 source.append((char)ts.RC);
\r
476 mustMatchToken(ts, ts.WHILE, "msg.no.while.do");
\r
477 source.append((char)ts.WHILE);
\r
478 Object cond = condition(ts, source);
\r
480 pn = nf.createDoWhile(body, cond, lineno);
\r
484 case TokenStream.FOR: {
\r
487 source.append((char)ts.FOR);
\r
488 int lineno = ts.getLineno();
\r
490 Object init; // Node init is also foo in 'foo in Object'
\r
491 Object cond; // Node cond is also object in 'foo in Object'
\r
492 Object incr = null; // to kill warning
\r
495 mustMatchToken(ts, ts.LP, "msg.no.paren.for");
\r
496 source.append((char)ts.LP);
\r
497 tt = ts.peekToken();
\r
498 if (tt == ts.SEMI) {
\r
499 init = nf.createLeaf(ts.VOID);
\r
501 if (tt == ts.VAR) {
\r
502 // set init to a var list or initial
\r
503 ts.getToken(); // throw away the 'var' token
\r
504 init = variables(ts, source, true);
\r
507 init = expr(ts, source, true);
\r
511 tt = ts.peekToken();
\r
512 if (tt == ts.RELOP && ts.getOp() == ts.IN) {
\r
513 ts.matchToken(ts.RELOP);
\r
514 source.append((char)ts.IN);
\r
515 // 'cond' is the object over which we're iterating
\r
516 cond = expr(ts, source, false);
\r
517 } else { // ordinary for loop
\r
518 mustMatchToken(ts, ts.SEMI,
\r
519 "msg.no.semi.for");
\r
520 source.append((char)ts.SEMI);
\r
521 if (ts.peekToken() == ts.SEMI) {
\r
522 // no loop condition
\r
523 cond = nf.createLeaf(ts.VOID);
\r
525 cond = expr(ts, source, false);
\r
528 mustMatchToken(ts, ts.SEMI,
\r
529 "msg.no.semi.for.cond");
\r
530 source.append((char)ts.SEMI);
\r
531 if (ts.peekToken() == ts.RP) {
\r
532 incr = nf.createLeaf(ts.VOID);
\r
534 incr = expr(ts, source, false);
\r
538 mustMatchToken(ts, ts.RP, "msg.no.paren.for.ctrl");
\r
539 source.append((char)ts.RP);
\r
540 source.append((char)ts.LC);
\r
541 source.append((char)ts.EOL);
\r
542 body = statement(ts, source);
\r
543 source.append((char)ts.RC);
\r
544 source.append((char)ts.EOL);
\r
546 if (incr == null) {
\r
547 // cond could be null if 'in obj' got eaten by the init node.
\r
548 pn = nf.createForIn(init, cond, body, lineno);
\r
550 pn = nf.createFor(init, cond, incr, body, lineno);
\r
555 case TokenStream.TRY: {
\r
556 int lineno = ts.getLineno();
\r
559 Object catchblocks = null;
\r
560 Object finallyblock = null;
\r
563 source.append((char)ts.TRY);
\r
564 source.append((char)ts.LC);
\r
565 source.append((char)ts.EOL);
\r
566 tryblock = statement(ts, source);
\r
567 source.append((char)ts.RC);
\r
568 source.append((char)ts.EOL);
\r
570 catchblocks = nf.createLeaf(TokenStream.BLOCK);
\r
572 boolean sawDefaultCatch = false;
\r
573 int peek = ts.peekToken();
\r
574 if (peek == ts.CATCH) {
\r
575 while (ts.matchToken(ts.CATCH)) {
\r
576 if (sawDefaultCatch) {
\r
577 reportError(ts, "msg.catch.unreachable");
\r
579 source.append((char)ts.CATCH);
\r
580 mustMatchToken(ts, ts.LP, "msg.no.paren.catch");
\r
581 source.append((char)ts.LP);
\r
583 mustMatchToken(ts, ts.NAME, "msg.bad.catchcond");
\r
584 String varName = ts.getString();
\r
585 source.addString(ts.NAME, varName);
\r
587 Object catchCond = null;
\r
588 if (ts.matchToken(ts.IF)) {
\r
589 source.append((char)ts.IF);
\r
590 catchCond = expr(ts, source, false);
\r
592 sawDefaultCatch = true;
\r
595 mustMatchToken(ts, ts.RP, "msg.bad.catchcond");
\r
596 source.append((char)ts.RP);
\r
597 mustMatchToken(ts, ts.LC, "msg.no.brace.catchblock");
\r
598 source.append((char)ts.LC);
\r
599 source.append((char)ts.EOL);
\r
601 nf.addChildToBack(catchblocks,
\r
602 nf.createCatch(varName, catchCond,
\r
603 statements(ts, source),
\r
606 mustMatchToken(ts, ts.RC, "msg.no.brace.after.body");
\r
607 source.append((char)ts.RC);
\r
608 source.append((char)ts.EOL);
\r
610 } else if (peek != ts.FINALLY) {
\r
611 mustMatchToken(ts, ts.FINALLY, "msg.try.no.catchfinally");
\r
614 if (ts.matchToken(ts.FINALLY)) {
\r
615 source.append((char)ts.FINALLY);
\r
617 source.append((char)ts.LC);
\r
618 source.append((char)ts.EOL);
\r
619 finallyblock = statement(ts, source);
\r
620 source.append((char)ts.RC);
\r
621 source.append((char)ts.EOL);
\r
624 pn = nf.createTryCatchFinally(tryblock, catchblocks,
\r
625 finallyblock, lineno);
\r
629 case TokenStream.THROW: {
\r
630 int lineno = ts.getLineno();
\r
631 source.append((char)ts.THROW);
\r
632 pn = nf.createThrow(expr(ts, source, false), lineno);
\r
633 if (lineno == ts.getLineno())
\r
634 wellTerminated(ts, ts.ERROR);
\r
637 case TokenStream.BREAK: {
\r
638 int lineno = ts.getLineno();
\r
640 source.append((char)ts.BREAK);
\r
642 // matchLabel only matches if there is one
\r
643 String label = matchLabel(ts);
\r
644 if (label != null) {
\r
645 source.addString(ts.NAME, label);
\r
647 pn = nf.createBreak(label, lineno);
\r
650 case TokenStream.CONTINUE: {
\r
651 int lineno = ts.getLineno();
\r
653 source.append((char)ts.CONTINUE);
\r
655 // matchLabel only matches if there is one
\r
656 String label = matchLabel(ts);
\r
657 if (label != null) {
\r
658 source.addString(ts.NAME, label);
\r
660 pn = nf.createContinue(label, lineno);
\r
663 case TokenStream.WITH: {
\r
666 source.append((char)ts.WITH);
\r
667 int lineno = ts.getLineno();
\r
668 mustMatchToken(ts, ts.LP, "msg.no.paren.with");
\r
669 source.append((char)ts.LP);
\r
670 Object obj = expr(ts, source, false);
\r
671 mustMatchToken(ts, ts.RP, "msg.no.paren.after.with");
\r
672 source.append((char)ts.RP);
\r
673 source.append((char)ts.LC);
\r
674 source.append((char)ts.EOL);
\r
676 Object body = statement(ts, source);
\r
678 source.append((char)ts.RC);
\r
679 source.append((char)ts.EOL);
\r
681 pn = nf.createWith(obj, body, lineno);
\r
684 case TokenStream.VAR: {
\r
685 int lineno = ts.getLineno();
\r
686 pn = variables(ts, source, false);
\r
687 if (ts.getLineno() == lineno)
\r
688 wellTerminated(ts, ts.ERROR);
\r
691 case TokenStream.ASSERT: {
\r
692 Object retExpr = null;
\r
694 source.append((char)ts.ASSERT);
\r
696 // bail if we're not in a (toplevel) function
\r
697 if ((ts.flags & ts.TSF_FUNCTION) == 0)
\r
698 reportError(ts, "msg.bad.return");
\r
700 /* This is ugly, but we don't want to require a semicolon. */
\r
701 ts.flags |= ts.TSF_REGEXP;
\r
702 tt = ts.peekTokenSameLine();
\r
703 ts.flags &= ~ts.TSF_REGEXP;
\r
705 if (tt != ts.EOF && tt != ts.EOL && tt != ts.SEMI && tt != ts.RC) {
\r
706 lineno = ts.getLineno();
\r
707 retExpr = expr(ts, source, false);
\r
708 if (ts.getLineno() == lineno)
\r
709 wellTerminated(ts, ts.ERROR);
\r
710 ts.flags |= ts.TSF_RETURN_EXPR;
\r
712 ts.flags |= ts.TSF_RETURN_VOID;
\r
716 pn = nf.createAssert(retExpr, lineno);
\r
720 case TokenStream.RETURN: {
\r
721 Object retExpr = null;
\r
724 source.append((char)ts.RETURN);
\r
726 // bail if we're not in a (toplevel) function
\r
727 if ((ts.flags & ts.TSF_FUNCTION) == 0)
\r
728 reportError(ts, "msg.bad.return");
\r
730 /* This is ugly, but we don't want to require a semicolon. */
\r
731 ts.flags |= ts.TSF_REGEXP;
\r
732 tt = ts.peekTokenSameLine();
\r
733 ts.flags &= ~ts.TSF_REGEXP;
\r
735 if (tt != ts.EOF && tt != ts.EOL && tt != ts.SEMI && tt != ts.RC) {
\r
736 lineno = ts.getLineno();
\r
737 retExpr = expr(ts, source, false);
\r
738 if (ts.getLineno() == lineno)
\r
739 wellTerminated(ts, ts.ERROR);
\r
740 ts.flags |= ts.TSF_RETURN_EXPR;
\r
742 ts.flags |= ts.TSF_RETURN_VOID;
\r
746 pn = nf.createReturn(retExpr, lineno);
\r
749 case TokenStream.LC:
\r
752 pn = statements(ts, source);
\r
753 mustMatchToken(ts, ts.RC, "msg.no.brace.block");
\r
756 case TokenStream.ERROR:
\r
757 // Fall thru, to have a node for error recovery to work on
\r
758 case TokenStream.EOL:
\r
759 case TokenStream.SEMI:
\r
760 pn = nf.createLeaf(ts.VOID);
\r
766 int tokenno = ts.getTokenno();
\r
768 int lineno = ts.getLineno();
\r
770 pn = expr(ts, source, false);
\r
772 if (ts.peekToken() == ts.COLON) {
\r
773 /* check that the last thing the tokenizer returned was a
\r
774 * NAME and that only one token was consumed.
\r
776 if (lastExprType != ts.NAME || (ts.getTokenno() != tokenno))
\r
777 reportError(ts, "msg.bad.label");
\r
779 ts.getToken(); // eat the COLON
\r
781 /* in the C source, the label is associated with the
\r
782 * statement that follows:
\r
783 * nf.addChildToBack(pn, statement(ts));
\r
785 String name = ts.getString();
\r
786 pn = nf.createLabel(name, lineno);
\r
788 // depend on decompiling lookahead to guess that that
\r
789 // last name was a label.
\r
790 source.append((char)ts.COLON);
\r
791 source.append((char)ts.EOL);
\r
795 if (lastExprType == ts.FUNCTION)
\r
796 nf.setFunctionExpressionStatement(pn);
\r
798 pn = nf.createExprStatement(pn, lineno);
\r
801 * Check explicitly against (multi-line) function
\r
804 * lastExprEndLine is a hack to fix an
\r
805 * automatic semicolon insertion problem with function
\r
806 * expressions; the ts.getLineno() == lineno check was
\r
807 * firing after a function definition even though the
\r
808 * next statement was on a new line, because
\r
809 * speculative getToken calls advanced the line number
\r
810 * even when they didn't succeed.
\r
812 if (ts.getLineno() == lineno ||
\r
813 (lastExprType == ts.FUNCTION &&
\r
814 ts.getLineno() == lastExprEndLine))
\r
816 wellTerminated(ts, lastExprType);
\r
821 ts.matchToken(ts.SEMI);
\r
823 source.append((char)ts.SEMI);
\r
824 source.append((char)ts.EOL);
\r
830 private Object variables(TokenStream ts, Source source, boolean inForInit)
\r
831 throws IOException, JavaScriptException
\r
833 Object pn = nf.createVariables(ts.getLineno());
\r
834 boolean first = true;
\r
836 source.append((char)ts.VAR);
\r
841 mustMatchToken(ts, ts.NAME, "msg.bad.var");
\r
842 String s = ts.getString();
\r
845 source.append((char)ts.COMMA);
\r
848 source.addString(ts.NAME, s);
\r
849 name = nf.createName(s);
\r
851 // omitted check for argument hiding
\r
853 if (ts.matchToken(ts.ASSIGN)) {
\r
854 if (ts.getOp() != ts.NOP)
\r
855 reportError(ts, "msg.bad.var.init");
\r
857 source.append((char)ts.ASSIGN);
\r
858 source.append((char)ts.NOP);
\r
860 init = assignExpr(ts, source, inForInit);
\r
861 nf.addChildToBack(name, init);
\r
863 nf.addChildToBack(pn, name);
\r
864 if (!ts.matchToken(ts.COMMA))
\r
870 private Object expr(TokenStream ts, Source source, boolean inForInit)
\r
871 throws IOException, JavaScriptException
\r
873 Object pn = assignExpr(ts, source, inForInit);
\r
874 while (ts.matchToken(ts.COMMA)) {
\r
875 source.append((char)ts.COMMA);
\r
876 pn = nf.createBinary(ts.COMMA, pn, assignExpr(ts, source,
\r
882 private Object assignExpr(TokenStream ts, Source source, boolean inForInit)
\r
883 throws IOException, JavaScriptException
\r
885 Object pn = condExpr(ts, source, inForInit);
\r
887 if (ts.matchToken(ts.ASSIGN)) {
\r
888 // omitted: "invalid assignment left-hand side" check.
\r
889 source.append((char)ts.ASSIGN);
\r
890 source.append((char)ts.getOp());
\r
891 pn = nf.createBinary(ts.ASSIGN, ts.getOp(), pn,
\r
892 assignExpr(ts, source, inForInit));
\r
898 private Object condExpr(TokenStream ts, Source source, boolean inForInit)
\r
899 throws IOException, JavaScriptException
\r
904 Object pn = orExpr(ts, source, inForInit);
\r
906 if (ts.matchToken(ts.HOOK)) {
\r
907 source.append((char)ts.HOOK);
\r
908 ifTrue = assignExpr(ts, source, false);
\r
909 mustMatchToken(ts, ts.COLON, "msg.no.colon.cond");
\r
910 source.append((char)ts.COLON);
\r
911 ifFalse = assignExpr(ts, source, inForInit);
\r
912 return nf.createTernary(pn, ifTrue, ifFalse);
\r
918 private Object orExpr(TokenStream ts, Source source, boolean inForInit)
\r
919 throws IOException, JavaScriptException
\r
921 Object pn = andExpr(ts, source, inForInit);
\r
922 if (ts.matchToken(ts.OR)) {
\r
923 source.append((char)ts.OR);
\r
924 pn = nf.createBinary(ts.OR, pn, orExpr(ts, source, inForInit));
\r
930 private Object andExpr(TokenStream ts, Source source, boolean inForInit)
\r
931 throws IOException, JavaScriptException
\r
933 Object pn = bitOrExpr(ts, source, inForInit);
\r
934 if (ts.matchToken(ts.AND)) {
\r
935 source.append((char)ts.AND);
\r
936 pn = nf.createBinary(ts.AND, pn, andExpr(ts, source, inForInit));
\r
942 private Object bitOrExpr(TokenStream ts, Source source, boolean inForInit)
\r
943 throws IOException, JavaScriptException
\r
945 Object pn = bitXorExpr(ts, source, inForInit);
\r
946 while (ts.matchToken(ts.BITOR)) {
\r
947 source.append((char)ts.BITOR);
\r
948 pn = nf.createBinary(ts.BITOR, pn, bitXorExpr(ts, source,
\r
954 private Object bitXorExpr(TokenStream ts, Source source, boolean inForInit)
\r
955 throws IOException, JavaScriptException
\r
957 Object pn = bitAndExpr(ts, source, inForInit);
\r
958 while (ts.matchToken(ts.BITXOR)) {
\r
959 source.append((char)ts.BITXOR);
\r
960 pn = nf.createBinary(ts.BITXOR, pn, bitAndExpr(ts, source,
\r
966 private Object bitAndExpr(TokenStream ts, Source source, boolean inForInit)
\r
967 throws IOException, JavaScriptException
\r
969 Object pn = eqExpr(ts, source, inForInit);
\r
970 while (ts.matchToken(ts.BITAND)) {
\r
971 source.append((char)ts.BITAND);
\r
972 pn = nf.createBinary(ts.BITAND, pn, eqExpr(ts, source, inForInit));
\r
977 private Object eqExpr(TokenStream ts, Source source, boolean inForInit)
\r
978 throws IOException, JavaScriptException
\r
980 Object pn = relExpr(ts, source, inForInit);
\r
981 while (ts.matchToken(ts.EQOP)) {
\r
982 source.append((char)ts.EQOP);
\r
983 source.append((char)ts.getOp());
\r
984 pn = nf.createBinary(ts.EQOP, ts.getOp(), pn,
\r
985 relExpr(ts, source, inForInit));
\r
990 private Object relExpr(TokenStream ts, Source source, boolean inForInit)
\r
991 throws IOException, JavaScriptException
\r
993 Object pn = shiftExpr(ts, source);
\r
994 while (ts.matchToken(ts.RELOP)) {
\r
995 int op = ts.getOp();
\r
996 if (inForInit && op == ts.IN) {
\r
997 ts.ungetToken(ts.RELOP);
\r
1000 source.append((char)ts.RELOP);
\r
1001 source.append((char)op);
\r
1002 pn = nf.createBinary(ts.RELOP, op, pn,
\r
1003 shiftExpr(ts, source));
\r
1008 private Object shiftExpr(TokenStream ts, Source source)
\r
1009 throws IOException, JavaScriptException
\r
1011 Object pn = addExpr(ts, source);
\r
1012 while (ts.matchToken(ts.SHOP)) {
\r
1013 source.append((char)ts.SHOP);
\r
1014 source.append((char)ts.getOp());
\r
1015 pn = nf.createBinary(ts.getOp(), pn, addExpr(ts, source));
\r
1020 private Object addExpr(TokenStream ts, Source source)
\r
1021 throws IOException, JavaScriptException
\r
1024 Object pn = mulExpr(ts, source);
\r
1026 while ((tt = ts.getToken()) == ts.ADD || tt == ts.SUB) {
\r
1027 source.append((char)tt);
\r
1029 pn = nf.createBinary(tt, pn, mulExpr(ts, source));
\r
1031 ts.ungetToken(tt);
\r
1036 private Object mulExpr(TokenStream ts, Source source)
\r
1037 throws IOException, JavaScriptException
\r
1041 Object pn = unaryExpr(ts, source);
\r
1043 while ((tt = ts.peekToken()) == ts.MUL ||
\r
1046 tt = ts.getToken();
\r
1047 source.append((char)tt);
\r
1048 pn = nf.createBinary(tt, pn, unaryExpr(ts, source));
\r
1055 private Object unaryExpr(TokenStream ts, Source source)
\r
1056 throws IOException, JavaScriptException
\r
1060 ts.flags |= ts.TSF_REGEXP;
\r
1061 tt = ts.getToken();
\r
1062 ts.flags &= ~ts.TSF_REGEXP;
\r
1065 case TokenStream.UNARYOP:
\r
1066 source.append((char)ts.UNARYOP);
\r
1067 source.append((char)ts.getOp());
\r
1068 return nf.createUnary(ts.UNARYOP, ts.getOp(),
\r
1069 unaryExpr(ts, source));
\r
1071 case TokenStream.ADD:
\r
1072 case TokenStream.SUB:
\r
1073 source.append((char)ts.UNARYOP);
\r
1074 source.append((char)tt);
\r
1075 return nf.createUnary(ts.UNARYOP, tt, unaryExpr(ts, source));
\r
1077 case TokenStream.INC:
\r
1078 case TokenStream.DEC:
\r
1079 source.append((char)tt);
\r
1080 return nf.createUnary(tt, ts.PRE, memberExpr(ts, source, true));
\r
1082 case TokenStream.DELPROP:
\r
1083 source.append((char)ts.DELPROP);
\r
1084 return nf.createUnary(ts.DELPROP, unaryExpr(ts, source));
\r
1086 case TokenStream.ERROR:
\r
1090 ts.ungetToken(tt);
\r
1092 int lineno = ts.getLineno();
\r
1094 Object pn = memberExpr(ts, source, true);
\r
1096 /* don't look across a newline boundary for a postfix incop.
\r
1098 * the rhino scanner seems to work differently than the js
\r
1099 * scanner here; in js, it works to have the line number check
\r
1100 * precede the peekToken calls. It'd be better if they had
\r
1101 * similar behavior...
\r
1104 if (((peeked = ts.peekToken()) == ts.INC ||
\r
1105 peeked == ts.DEC) &&
\r
1106 ts.getLineno() == lineno)
\r
1108 int pf = ts.getToken();
\r
1109 source.append((char)pf);
\r
1110 return nf.createUnary(pf, ts.POST, pn);
\r
1114 return nf.createName("err"); // Only reached on error. Try to continue.
\r
1118 private Object argumentList(TokenStream ts, Source source, Object listNode)
\r
1119 throws IOException, JavaScriptException
\r
1122 ts.flags |= ts.TSF_REGEXP;
\r
1123 matched = ts.matchToken(ts.RP);
\r
1124 ts.flags &= ~ts.TSF_REGEXP;
\r
1126 boolean first = true;
\r
1129 source.append((char)ts.COMMA);
\r
1131 nf.addChildToBack(listNode, assignExpr(ts, source, false));
\r
1132 } while (ts.matchToken(ts.COMMA));
\r
1134 mustMatchToken(ts, ts.RP, "msg.no.paren.arg");
\r
1136 source.append((char)ts.RP);
\r
1140 private Object memberExpr(TokenStream ts, Source source,
\r
1141 boolean allowCallSyntax)
\r
1142 throws IOException, JavaScriptException
\r
1148 /* Check for new expressions. */
\r
1149 ts.flags |= ts.TSF_REGEXP;
\r
1150 tt = ts.peekToken();
\r
1151 ts.flags &= ~ts.TSF_REGEXP;
\r
1152 if (tt == ts.NEW) {
\r
1153 /* Eat the NEW token. */
\r
1155 source.append((char)ts.NEW);
\r
1157 /* Make a NEW node to append to. */
\r
1158 pn = nf.createLeaf(ts.NEW);
\r
1159 nf.addChildToBack(pn, memberExpr(ts, source, false));
\r
1161 if (ts.matchToken(ts.LP)) {
\r
1162 source.append((char)ts.LP);
\r
1163 /* Add the arguments to pn, if any are supplied. */
\r
1164 pn = argumentList(ts, source, pn);
\r
1167 /* XXX there's a check in the C source against
\r
1168 * "too many constructor arguments" - how many
\r
1169 * do we claim to support?
\r
1172 /* Experimental syntax: allow an object literal to follow a new expression,
\r
1173 * which will mean a kind of anonymous class built with the JavaAdapter.
\r
1174 * the object literal will be passed as an additional argument to the constructor.
\r
1176 tt = ts.peekToken();
\r
1177 if (tt == ts.LC) {
\r
1178 nf.addChildToBack(pn, primaryExpr(ts, source));
\r
1181 pn = primaryExpr(ts, source);
\r
1184 lastExprEndLine = ts.getLineno();
\r
1185 while ((tt = ts.getToken()) > ts.EOF) {
\r
1186 if (tt == ts.DOT) {
\r
1187 source.append((char)ts.DOT);
\r
1188 mustMatchToken(ts, ts.NAME, "msg.no.name.after.dot");
\r
1189 String s = ts.getString();
\r
1190 source.addString(ts.NAME, s);
\r
1191 pn = nf.createBinary(ts.DOT, pn,
\r
1192 nf.createName(ts.getString()));
\r
1193 /* pn = nf.createBinary(ts.DOT, pn, memberExpr(ts))
\r
1194 * is the version in Brendan's IR C version. Not in ECMA...
\r
1195 * does it reflect the 'new' operator syntax he mentioned?
\r
1197 lastExprEndLine = ts.getLineno();
\r
1198 } else if (tt == ts.LB) {
\r
1199 source.append((char)ts.LB);
\r
1200 pn = nf.createBinary(ts.LB, pn, expr(ts, source, false));
\r
1202 mustMatchToken(ts, ts.RB, "msg.no.bracket.index");
\r
1203 source.append((char)ts.RB);
\r
1204 lastExprEndLine = ts.getLineno();
\r
1205 } else if (allowCallSyntax && tt == ts.LP) {
\r
1206 /* make a call node */
\r
1208 pn = nf.createUnary(ts.CALL, pn);
\r
1209 source.append((char)ts.LP);
\r
1211 /* Add the arguments to pn, if any are supplied. */
\r
1212 pn = argumentList(ts, source, pn);
\r
1213 lastExprEndLine = ts.getLineno();
\r
1215 ts.ungetToken(tt);
\r
1224 private Object primaryExpr(TokenStream ts, Source source)
\r
1225 throws IOException, JavaScriptException
\r
1231 ts.flags |= ts.TSF_REGEXP;
\r
1232 tt = ts.getToken();
\r
1233 ts.flags &= ~ts.TSF_REGEXP;
\r
1237 case TokenStream.FUNCTION:
\r
1238 return function(ts, source, true);
\r
1240 case TokenStream.LB:
\r
1242 source.append((char)ts.LB);
\r
1243 pn = nf.createLeaf(ts.ARRAYLIT);
\r
1245 ts.flags |= ts.TSF_REGEXP;
\r
1246 boolean matched = ts.matchToken(ts.RB);
\r
1247 ts.flags &= ~ts.TSF_REGEXP;
\r
1250 boolean first = true;
\r
1252 ts.flags |= ts.TSF_REGEXP;
\r
1253 tt = ts.peekToken();
\r
1254 ts.flags &= ~ts.TSF_REGEXP;
\r
1257 source.append((char)ts.COMMA);
\r
1261 if (tt == ts.RB) { // to fix [,,,].length behavior...
\r
1265 if (tt == ts.COMMA) {
\r
1266 nf.addChildToBack(pn, nf.createLeaf(ts.PRIMARY,
\r
1269 nf.addChildToBack(pn, assignExpr(ts, source, false));
\r
1272 } while (ts.matchToken(ts.COMMA));
\r
1273 mustMatchToken(ts, ts.RB, "msg.no.bracket.arg");
\r
1275 source.append((char)ts.RB);
\r
1276 return nf.createArrayLiteral(pn);
\r
1279 case TokenStream.LC: {
\r
1280 pn = nf.createLeaf(ts.OBJLIT);
\r
1282 source.append((char)ts.LC);
\r
1283 if (!ts.matchToken(ts.RC)) {
\r
1285 boolean first = true;
\r
1291 source.append((char)ts.COMMA);
\r
1295 tt = ts.getToken();
\r
1297 // map NAMEs to STRINGs in object literal context.
\r
1298 case TokenStream.NAME:
\r
1299 case TokenStream.STRING:
\r
1300 String s = ts.getString();
\r
1301 source.addString(ts.NAME, s);
\r
1302 property = nf.createString(ts.getString());
\r
1304 case TokenStream.NUMBER:
\r
1305 Number n = ts.getNumber();
\r
1306 source.addNumber(n);
\r
1307 property = nf.createNumber(n);
\r
1309 case TokenStream.RC:
\r
1310 // trailing comma is OK.
\r
1311 ts.ungetToken(tt);
\r
1314 reportError(ts, "msg.bad.prop");
\r
1317 mustMatchToken(ts, ts.COLON, "msg.no.colon.prop");
\r
1319 // OBJLIT is used as ':' in object literal for
\r
1320 // decompilation to solve spacing ambiguity.
\r
1321 source.append((char)ts.OBJLIT);
\r
1322 nf.addChildToBack(pn, property);
\r
1323 nf.addChildToBack(pn, assignExpr(ts, source, false));
\r
1325 } while (ts.matchToken(ts.COMMA));
\r
1327 mustMatchToken(ts, ts.RC, "msg.no.brace.prop");
\r
1329 source.append((char)ts.RC);
\r
1330 return nf.createObjectLiteral(pn);
\r
1333 case TokenStream.LP:
\r
1335 /* Brendan's IR-jsparse.c makes a new node tagged with
\r
1336 * TOK_LP here... I'm not sure I understand why. Isn't
\r
1337 * the grouping already implicit in the structure of the
\r
1338 * parse tree? also TOK_LP is already overloaded (I
\r
1339 * think) in the C IR as 'function call.' */
\r
1340 source.append((char)ts.LP);
\r
1341 pn = expr(ts, source, false);
\r
1342 source.append((char)ts.RP);
\r
1343 mustMatchToken(ts, ts.RP, "msg.no.paren");
\r
1346 case TokenStream.NAME:
\r
1347 String name = ts.getString();
\r
1348 source.addString(ts.NAME, name);
\r
1349 return nf.createName(name);
\r
1351 case TokenStream.NUMBER:
\r
1352 Number n = ts.getNumber();
\r
1353 source.addNumber(n);
\r
1354 return nf.createNumber(n);
\r
1356 case TokenStream.STRING:
\r
1357 String s = ts.getString();
\r
1358 source.addString(ts.STRING, s);
\r
1359 return nf.createString(s);
\r
1361 case TokenStream.OBJECT:
\r
1363 String flags = ts.regExpFlags;
\r
1364 ts.regExpFlags = null;
\r
1365 String re = ts.getString();
\r
1366 source.addString(ts.OBJECT, '/' + re + '/' + flags);
\r
1367 return nf.createRegExp(re, flags);
\r
1370 case TokenStream.PRIMARY:
\r
1371 source.append((char)ts.PRIMARY);
\r
1372 source.append((char)ts.getOp());
\r
1373 return nf.createLeaf(ts.PRIMARY, ts.getOp());
\r
1375 case TokenStream.RESERVED:
\r
1376 reportError(ts, "msg.reserved.id");
\r
1379 case TokenStream.ERROR:
\r
1380 /* the scanner or one of its subroutines reported the error. */
\r
1384 reportError(ts, "msg.syntax");
\r
1388 return null; // should never reach here
\r
1391 private int lastExprEndLine; // Hack to handle function expr termination.
\r
1392 private IRFactory nf;
\r
1393 private ErrorReporter er;
\r
1394 private boolean ok; // Did the parse encounter an error?
\r
1398 * This class saves decompilation information about the source.
\r
1399 * Source information is returned from the parser as a String
\r
1400 * associated with function nodes and with the toplevel script. When
\r
1401 * saved in the constant pool of a class, this string will be UTF-8
\r
1402 * encoded, and token values will occupy a single byte.
\r
1404 * Source is saved (mostly) as token numbers. The tokens saved pretty
\r
1405 * much correspond to the token stream of a 'canonical' representation
\r
1406 * of the input program, as directed by the parser. (There were a few
\r
1407 * cases where tokens could have been left out where decompiler could
\r
1408 * easily reconstruct them, but I left them in for clarity). (I also
\r
1409 * looked adding source collection to TokenStream instead, where I
\r
1410 * could have limited the changes to a few lines in getToken... but
\r
1411 * this wouldn't have saved any space in the resulting source
\r
1412 * representation, and would have meant that I'd have to duplicate
\r
1413 * parser logic in the decompiler to disambiguate situations where
\r
1414 * newlines are important.) NativeFunction.decompile expands the
\r
1415 * tokens back into their string representations, using simple
\r
1416 * lookahead to correct spacing and indentation.
\r
1418 * Token types with associated ops (ASSIGN, SHOP, PRIMARY, etc.) are
\r
1419 * saved as two-token pairs. Number tokens are stored inline, as a
\r
1420 * NUMBER token, a character representing the type, and either 1 or 4
\r
1421 * characters representing the bit-encoding of the number. String
\r
1422 * types NAME, STRING and OBJECT are currently stored as a token type,
\r
1423 * followed by a character giving the length of the string (assumed to
\r
1424 * be less than 2^16), followed by the characters of the string
\r
1425 * inlined into the source string. Changing this to some reference to
\r
1426 * to the string in the compiled class' constant pool would probably
\r
1427 * save a lot of space... but would require some method of deriving
\r
1428 * the final constant pool entry from information available at parse
\r
1431 * Nested functions need a similar mechanism... fortunately the nested
\r
1432 * functions for a given function are generated in source order.
\r
1433 * Nested functions are encoded as FUNCTION followed by a function
\r
1434 * number (encoded as a character), which is enough information to
\r
1435 * find the proper generated NativeFunction instance.
\r
1437 * OPT source info collection is a potential performance bottleneck;
\r
1438 * Source wraps a java.lang.StringBuffer, which is synchronized. It
\r
1439 * might be faster to implement Source with its own char buffer and
\r
1440 * toString method.
\r
1444 final class Source {
\r
1446 // OPT the default 16 is probably too small, but it's not
\r
1447 // clear just what size would work best for most javascript.
\r
1448 // It'd be nice to know what characterizes the javascript
\r
1449 // that's out there.
\r
1450 buf = new StringBuffer(64);
\r
1453 void append(char c) {
\r
1457 void addString(int type, String str) {
\r
1458 buf.append((char)type);
\r
1459 // java string length < 2^16?
\r
1460 buf.append((char)str.length());
\r
1464 void addNumber(Number n) {
\r
1465 buf.append((char)TokenStream.NUMBER);
\r
1467 /* encode the number in the source stream.
\r
1468 * Save as NUMBER type (char | char char char char)
\r
1470 * 'D' - double, 'S' - short, 'J' - long.
\r
1472 * We need to retain float vs. integer type info to keep the
\r
1473 * behavior of liveconnect type-guessing the same after
\r
1474 * decompilation. (Liveconnect tries to present 1.0 to Java
\r
1475 * as a float/double)
\r
1476 * OPT: This is no longer true. We could compress the format.
\r
1478 * This may not be the most space-efficient encoding;
\r
1479 * the chars created below may take up to 3 bytes in
\r
1480 * constant pool UTF-8 encoding, so a Double could take
\r
1484 if (n instanceof Double || n instanceof Float) {
\r
1485 // if it's floating point, save as a Double bit pattern.
\r
1486 // (12/15/97 our scanner only returns Double for f.p.)
\r
1488 long lbits = Double.doubleToLongBits(n.doubleValue());
\r
1490 buf.append((char)((lbits >> 48) & 0xFFFF));
\r
1491 buf.append((char)((lbits >> 32) & 0xFFFF));
\r
1492 buf.append((char)((lbits >> 16) & 0xFFFF));
\r
1493 buf.append((char)(lbits & 0xFFFF));
\r
1495 long lbits = n.longValue();
\r
1496 // will it fit in a char?
\r
1497 // (we can ignore negative values, bc they're already prefixed
\r
1498 // by UNARYOP SUB)
\r
1499 // this gives a short encoding for integer values up to 2^16.
\r
1500 if (lbits <= Character.MAX_VALUE) {
\r
1502 buf.append((char)lbits);
\r
1503 } else { // Integral, but won't fit in a char. Store as a long.
\r
1505 buf.append((char)((lbits >> 48) & 0xFFFF));
\r
1506 buf.append((char)((lbits >> 32) & 0xFFFF));
\r
1507 buf.append((char)((lbits >> 16) & 0xFFFF));
\r
1508 buf.append((char)(lbits & 0xFFFF));
\r
1513 char functionNumber;
\r