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 // HACK: XWT does not support named functions
214 throw new IOException(
215 "named functions are not supported, use anonymos "+
216 "declaration (myfun = function() {...}"
220 ; // it's an anonymous function
\r
222 mustMatchToken(ts, ts.LP, "msg.no.paren.parms");
\r
223 source.append((char) ts.LP);
\r
225 if (!ts.matchToken(ts.RP)) {
\r
226 boolean first = true;
\r
229 source.append((char)ts.COMMA);
\r
231 mustMatchToken(ts, ts.NAME, "msg.no.parm");
\r
232 String s = ts.getString();
\r
233 nf.addChildToBack(args, nf.createName(s));
\r
235 source.addString(ts.NAME, s);
\r
236 } while (ts.matchToken(ts.COMMA));
\r
238 mustMatchToken(ts, ts.RP, "msg.no.paren.after.parms");
\r
240 source.append((char)ts.RP);
\r
242 mustMatchToken(ts, ts.LC, "msg.no.brace.body");
\r
243 source.append((char)ts.LC);
\r
244 source.append((char)ts.EOL);
\r
245 body = parseFunctionBody(ts, source);
\r
246 mustMatchToken(ts, ts.RC, "msg.no.brace.after.body");
\r
247 source.append((char)ts.RC);
\r
248 // skip the last EOL so nested functions work...
\r
250 // name might be null;
\r
251 return nf.createFunction(name, args, body,
\r
252 ts.getSourceName(),
\r
253 baseLineno, ts.getLineno(),
\r
254 source.buf.toString(),
\r
258 private Object statements(TokenStream ts, Source source)
\r
261 Object pn = nf.createBlock(ts.getLineno());
\r
264 while((tt = ts.peekToken()) > ts.EOF && tt != ts.RC) {
\r
265 nf.addChildToBack(pn, statement(ts, source));
\r
271 private Object condition(TokenStream ts, Source source)
\r
272 throws IOException, JavaScriptException
\r
275 mustMatchToken(ts, ts.LP, "msg.no.paren.cond");
\r
276 source.append((char)ts.LP);
\r
277 pn = expr(ts, source, false);
\r
278 mustMatchToken(ts, ts.RP, "msg.no.paren.after.cond");
\r
279 source.append((char)ts.RP);
\r
281 // there's a check here in jsparse.c that corrects = to ==
\r
286 private boolean wellTerminated(TokenStream ts, int lastExprType)
\r
287 throws IOException, JavaScriptException
\r
289 int tt = ts.peekTokenSameLine();
\r
290 if (tt == ts.ERROR) {
\r
294 if (tt != ts.EOF && tt != ts.EOL
\r
295 && tt != ts.SEMI && tt != ts.RC)
\r
297 int version = Context.getContext().getLanguageVersion();
\r
298 if ((tt == ts.FUNCTION || lastExprType == ts.FUNCTION) &&
\r
299 (version < Context.VERSION_1_2)) {
\r
301 * Checking against version < 1.2 and version >= 1.0
\r
302 * in the above line breaks old javascript, so we keep it
\r
303 * this way for now... XXX warning needed?
\r
307 reportError(ts, "msg.no.semi.stmt");
\r
313 // match a NAME; return null if no match.
\r
314 private String matchLabel(TokenStream ts)
\r
315 throws IOException, JavaScriptException
\r
317 int lineno = ts.getLineno();
\r
319 String label = null;
\r
321 tt = ts.peekTokenSameLine();
\r
322 if (tt == ts.NAME) {
\r
324 label = ts.getString();
\r
327 if (lineno == ts.getLineno())
\r
328 wellTerminated(ts, ts.ERROR);
\r
333 private Object statement(TokenStream ts, Source source)
\r
337 return statementHelper(ts, source);
\r
338 } catch (JavaScriptException e) {
\r
339 // skip to end of statement
\r
340 int lineno = ts.getLineno();
\r
344 } while (t != TokenStream.SEMI && t != TokenStream.EOL &&
\r
345 t != TokenStream.EOF && t != TokenStream.ERROR);
\r
346 return nf.createExprStatement(nf.createName("error"), lineno);
\r
351 * Whether the "catch (e: e instanceof Exception) { ... }" syntax
\r
355 private Object statementHelper(TokenStream ts, Source source)
\r
356 throws IOException, JavaScriptException
\r
360 // If skipsemi == true, don't add SEMI + EOL to source at the
\r
361 // end of this statment. For compound statements, IF/FOR etc.
\r
362 boolean skipsemi = false;
\r
366 int lastExprType = 0; // For wellTerminated. 0 to avoid warning.
\r
368 tt = ts.getToken();
\r
371 case TokenStream.IF: {
\r
374 source.append((char)ts.IF);
\r
375 int lineno = ts.getLineno();
\r
376 Object cond = condition(ts, source);
\r
377 source.append((char)ts.LC);
\r
378 source.append((char)ts.EOL);
\r
379 Object ifTrue = statement(ts, source);
\r
380 Object ifFalse = null;
\r
381 if (ts.matchToken(ts.ELSE)) {
\r
382 source.append((char)ts.RC);
\r
383 source.append((char)ts.ELSE);
\r
384 source.append((char)ts.LC);
\r
385 source.append((char)ts.EOL);
\r
386 ifFalse = statement(ts, source);
\r
388 source.append((char)ts.RC);
\r
389 source.append((char)ts.EOL);
\r
390 pn = nf.createIf(cond, ifTrue, ifFalse, lineno);
\r
394 case TokenStream.SWITCH: {
\r
397 source.append((char)ts.SWITCH);
\r
398 pn = nf.createSwitch(ts.getLineno());
\r
400 Object cur_case = null; // to kill warning
\r
401 Object case_statements;
\r
403 mustMatchToken(ts, ts.LP, "msg.no.paren.switch");
\r
404 source.append((char)ts.LP);
\r
405 nf.addChildToBack(pn, expr(ts, source, false));
\r
406 mustMatchToken(ts, ts.RP, "msg.no.paren.after.switch");
\r
407 source.append((char)ts.RP);
\r
408 mustMatchToken(ts, ts.LC, "msg.no.brace.switch");
\r
409 source.append((char)ts.LC);
\r
410 source.append((char)ts.EOL);
\r
412 while ((tt = ts.getToken()) != ts.RC && tt != ts.EOF) {
\r
414 case TokenStream.CASE:
\r
415 source.append((char)ts.CASE);
\r
416 cur_case = nf.createUnary(ts.CASE, expr(ts, source, false));
\r
417 source.append((char)ts.COLON);
\r
418 source.append((char)ts.EOL);
\r
421 case TokenStream.DEFAULT:
\r
422 cur_case = nf.createLeaf(ts.DEFAULT);
\r
423 source.append((char)ts.DEFAULT);
\r
424 source.append((char)ts.COLON);
\r
425 source.append((char)ts.EOL);
\r
426 // XXX check that there isn't more than one default
\r
430 reportError(ts, "msg.bad.switch");
\r
433 mustMatchToken(ts, ts.COLON, "msg.no.colon.case");
\r
435 case_statements = nf.createLeaf(TokenStream.BLOCK);
\r
437 while ((tt = ts.peekToken()) != ts.RC && tt != ts.CASE &&
\r
438 tt != ts.DEFAULT && tt != ts.EOF)
\r
440 nf.addChildToBack(case_statements, statement(ts, source));
\r
443 nf.addChildToBack(cur_case, case_statements);
\r
445 nf.addChildToBack(pn, cur_case);
\r
447 source.append((char)ts.RC);
\r
448 source.append((char)ts.EOL);
\r
452 case TokenStream.WHILE: {
\r
455 source.append((char)ts.WHILE);
\r
456 int lineno = ts.getLineno();
\r
457 Object cond = condition(ts, source);
\r
458 source.append((char)ts.LC);
\r
459 source.append((char)ts.EOL);
\r
460 Object body = statement(ts, source);
\r
461 source.append((char)ts.RC);
\r
462 source.append((char)ts.EOL);
\r
464 pn = nf.createWhile(cond, body, lineno);
\r
469 case TokenStream.DO: {
\r
470 source.append((char)ts.DO);
\r
471 source.append((char)ts.LC);
\r
472 source.append((char)ts.EOL);
\r
474 int lineno = ts.getLineno();
\r
476 Object body = statement(ts, source);
\r
478 source.append((char)ts.RC);
\r
479 mustMatchToken(ts, ts.WHILE, "msg.no.while.do");
\r
480 source.append((char)ts.WHILE);
\r
481 Object cond = condition(ts, source);
\r
483 pn = nf.createDoWhile(body, cond, lineno);
\r
487 case TokenStream.FOR: {
\r
490 source.append((char)ts.FOR);
\r
491 int lineno = ts.getLineno();
\r
493 Object init; // Node init is also foo in 'foo in Object'
\r
494 Object cond; // Node cond is also object in 'foo in Object'
\r
495 Object incr = null; // to kill warning
\r
498 mustMatchToken(ts, ts.LP, "msg.no.paren.for");
\r
499 source.append((char)ts.LP);
\r
500 tt = ts.peekToken();
\r
501 if (tt == ts.SEMI) {
\r
502 init = nf.createLeaf(ts.VOID);
\r
504 if (tt == ts.VAR) {
\r
505 // set init to a var list or initial
\r
506 ts.getToken(); // throw away the 'var' token
\r
507 init = variables(ts, source, true);
\r
510 init = expr(ts, source, true);
\r
514 tt = ts.peekToken();
\r
515 if (tt == ts.RELOP && ts.getOp() == ts.IN) {
\r
516 ts.matchToken(ts.RELOP);
\r
517 source.append((char)ts.IN);
\r
518 // 'cond' is the object over which we're iterating
\r
519 cond = expr(ts, source, false);
\r
520 } else { // ordinary for loop
\r
521 mustMatchToken(ts, ts.SEMI,
\r
522 "msg.no.semi.for");
\r
523 source.append((char)ts.SEMI);
\r
524 if (ts.peekToken() == ts.SEMI) {
\r
525 // no loop condition
\r
526 cond = nf.createLeaf(ts.VOID);
\r
528 cond = expr(ts, source, false);
\r
531 mustMatchToken(ts, ts.SEMI,
\r
532 "msg.no.semi.for.cond");
\r
533 source.append((char)ts.SEMI);
\r
534 if (ts.peekToken() == ts.RP) {
\r
535 incr = nf.createLeaf(ts.VOID);
\r
537 incr = expr(ts, source, false);
\r
541 mustMatchToken(ts, ts.RP, "msg.no.paren.for.ctrl");
\r
542 source.append((char)ts.RP);
\r
543 source.append((char)ts.LC);
\r
544 source.append((char)ts.EOL);
\r
545 body = statement(ts, source);
\r
546 source.append((char)ts.RC);
\r
547 source.append((char)ts.EOL);
\r
549 if (incr == null) {
\r
550 // cond could be null if 'in obj' got eaten by the init node.
\r
551 pn = nf.createForIn(init, cond, body, lineno);
\r
553 pn = nf.createFor(init, cond, incr, body, lineno);
\r
558 case TokenStream.TRY: {
\r
559 int lineno = ts.getLineno();
\r
562 Object catchblocks = null;
\r
563 Object finallyblock = null;
\r
566 source.append((char)ts.TRY);
\r
567 source.append((char)ts.LC);
\r
568 source.append((char)ts.EOL);
\r
569 tryblock = statement(ts, source);
\r
570 source.append((char)ts.RC);
\r
571 source.append((char)ts.EOL);
\r
573 catchblocks = nf.createLeaf(TokenStream.BLOCK);
\r
575 boolean sawDefaultCatch = false;
\r
576 int peek = ts.peekToken();
\r
577 if (peek == ts.CATCH) {
\r
578 while (ts.matchToken(ts.CATCH)) {
\r
579 if (sawDefaultCatch) {
\r
580 reportError(ts, "msg.catch.unreachable");
\r
582 source.append((char)ts.CATCH);
\r
583 mustMatchToken(ts, ts.LP, "msg.no.paren.catch");
\r
584 source.append((char)ts.LP);
\r
586 mustMatchToken(ts, ts.NAME, "msg.bad.catchcond");
\r
587 String varName = ts.getString();
\r
588 source.addString(ts.NAME, varName);
\r
590 Object catchCond = null;
\r
591 if (ts.matchToken(ts.IF)) {
\r
592 source.append((char)ts.IF);
\r
593 catchCond = expr(ts, source, false);
\r
595 sawDefaultCatch = true;
\r
598 mustMatchToken(ts, ts.RP, "msg.bad.catchcond");
\r
599 source.append((char)ts.RP);
\r
600 mustMatchToken(ts, ts.LC, "msg.no.brace.catchblock");
\r
601 source.append((char)ts.LC);
\r
602 source.append((char)ts.EOL);
\r
604 nf.addChildToBack(catchblocks,
\r
605 nf.createCatch(varName, catchCond,
\r
606 statements(ts, source),
\r
609 mustMatchToken(ts, ts.RC, "msg.no.brace.after.body");
\r
610 source.append((char)ts.RC);
\r
611 source.append((char)ts.EOL);
\r
613 } else if (peek != ts.FINALLY) {
\r
614 mustMatchToken(ts, ts.FINALLY, "msg.try.no.catchfinally");
\r
617 if (ts.matchToken(ts.FINALLY)) {
\r
618 source.append((char)ts.FINALLY);
\r
620 source.append((char)ts.LC);
\r
621 source.append((char)ts.EOL);
\r
622 finallyblock = statement(ts, source);
\r
623 source.append((char)ts.RC);
\r
624 source.append((char)ts.EOL);
\r
627 pn = nf.createTryCatchFinally(tryblock, catchblocks,
\r
628 finallyblock, lineno);
\r
632 case TokenStream.THROW: {
\r
633 int lineno = ts.getLineno();
\r
634 source.append((char)ts.THROW);
\r
635 pn = nf.createThrow(expr(ts, source, false), lineno);
\r
636 if (lineno == ts.getLineno())
\r
637 wellTerminated(ts, ts.ERROR);
\r
640 case TokenStream.BREAK: {
\r
641 int lineno = ts.getLineno();
\r
643 source.append((char)ts.BREAK);
\r
645 // matchLabel only matches if there is one
\r
646 String label = matchLabel(ts);
\r
647 if (label != null) {
\r
648 source.addString(ts.NAME, label);
\r
650 pn = nf.createBreak(label, lineno);
\r
653 case TokenStream.CONTINUE: {
\r
654 int lineno = ts.getLineno();
\r
656 source.append((char)ts.CONTINUE);
\r
658 // matchLabel only matches if there is one
\r
659 String label = matchLabel(ts);
\r
660 if (label != null) {
\r
661 source.addString(ts.NAME, label);
\r
663 pn = nf.createContinue(label, lineno);
\r
666 case TokenStream.WITH: {
\r
669 source.append((char)ts.WITH);
\r
670 int lineno = ts.getLineno();
\r
671 mustMatchToken(ts, ts.LP, "msg.no.paren.with");
\r
672 source.append((char)ts.LP);
\r
673 Object obj = expr(ts, source, false);
\r
674 mustMatchToken(ts, ts.RP, "msg.no.paren.after.with");
\r
675 source.append((char)ts.RP);
\r
676 source.append((char)ts.LC);
\r
677 source.append((char)ts.EOL);
\r
679 Object body = statement(ts, source);
\r
681 source.append((char)ts.RC);
\r
682 source.append((char)ts.EOL);
\r
684 pn = nf.createWith(obj, body, lineno);
\r
687 case TokenStream.VAR: {
\r
688 int lineno = ts.getLineno();
\r
689 pn = variables(ts, source, false);
\r
690 if (ts.getLineno() == lineno)
\r
691 wellTerminated(ts, ts.ERROR);
\r
694 case TokenStream.ASSERT: {
\r
695 Object retExpr = null;
\r
697 source.append((char)ts.ASSERT);
\r
699 // bail if we're not in a (toplevel) function
\r
700 if ((ts.flags & ts.TSF_FUNCTION) == 0)
\r
701 reportError(ts, "msg.bad.return");
\r
703 /* This is ugly, but we don't want to require a semicolon. */
\r
704 ts.flags |= ts.TSF_REGEXP;
\r
705 tt = ts.peekTokenSameLine();
\r
706 ts.flags &= ~ts.TSF_REGEXP;
\r
708 if (tt != ts.EOF && tt != ts.EOL && tt != ts.SEMI && tt != ts.RC) {
\r
709 lineno = ts.getLineno();
\r
710 retExpr = expr(ts, source, false);
\r
711 if (ts.getLineno() == lineno)
\r
712 wellTerminated(ts, ts.ERROR);
\r
713 ts.flags |= ts.TSF_RETURN_EXPR;
\r
715 ts.flags |= ts.TSF_RETURN_VOID;
\r
719 pn = nf.createAssert(retExpr, lineno);
\r
723 case TokenStream.RETURN: {
\r
724 Object retExpr = null;
\r
727 source.append((char)ts.RETURN);
\r
729 // bail if we're not in a (toplevel) function
\r
730 if ((ts.flags & ts.TSF_FUNCTION) == 0)
\r
731 reportError(ts, "msg.bad.return");
\r
733 /* This is ugly, but we don't want to require a semicolon. */
\r
734 ts.flags |= ts.TSF_REGEXP;
\r
735 tt = ts.peekTokenSameLine();
\r
736 ts.flags &= ~ts.TSF_REGEXP;
\r
738 if (tt != ts.EOF && tt != ts.EOL && tt != ts.SEMI && tt != ts.RC) {
\r
739 lineno = ts.getLineno();
\r
740 retExpr = expr(ts, source, false);
\r
741 if (ts.getLineno() == lineno)
\r
742 wellTerminated(ts, ts.ERROR);
\r
743 ts.flags |= ts.TSF_RETURN_EXPR;
\r
745 ts.flags |= ts.TSF_RETURN_VOID;
\r
749 pn = nf.createReturn(retExpr, lineno);
\r
752 case TokenStream.LC:
\r
755 pn = statements(ts, source);
\r
756 mustMatchToken(ts, ts.RC, "msg.no.brace.block");
\r
759 case TokenStream.ERROR:
\r
760 // Fall thru, to have a node for error recovery to work on
\r
761 case TokenStream.EOL:
\r
762 case TokenStream.SEMI:
\r
763 pn = nf.createLeaf(ts.VOID);
\r
769 int tokenno = ts.getTokenno();
\r
771 int lineno = ts.getLineno();
\r
773 pn = expr(ts, source, false);
\r
775 if (ts.peekToken() == ts.COLON) {
\r
776 /* check that the last thing the tokenizer returned was a
\r
777 * NAME and that only one token was consumed.
\r
779 if (lastExprType != ts.NAME || (ts.getTokenno() != tokenno))
\r
780 reportError(ts, "msg.bad.label");
\r
782 ts.getToken(); // eat the COLON
\r
784 /* in the C source, the label is associated with the
\r
785 * statement that follows:
\r
786 * nf.addChildToBack(pn, statement(ts));
\r
788 String name = ts.getString();
\r
789 pn = nf.createLabel(name, lineno);
\r
791 // depend on decompiling lookahead to guess that that
\r
792 // last name was a label.
\r
793 source.append((char)ts.COLON);
\r
794 source.append((char)ts.EOL);
\r
798 if (lastExprType == ts.FUNCTION)
\r
799 nf.setFunctionExpressionStatement(pn);
\r
801 pn = nf.createExprStatement(pn, lineno);
\r
804 * Check explicitly against (multi-line) function
\r
807 * lastExprEndLine is a hack to fix an
\r
808 * automatic semicolon insertion problem with function
\r
809 * expressions; the ts.getLineno() == lineno check was
\r
810 * firing after a function definition even though the
\r
811 * next statement was on a new line, because
\r
812 * speculative getToken calls advanced the line number
\r
813 * even when they didn't succeed.
\r
815 if (ts.getLineno() == lineno ||
\r
816 (lastExprType == ts.FUNCTION &&
\r
817 ts.getLineno() == lastExprEndLine))
\r
819 wellTerminated(ts, lastExprType);
\r
824 ts.matchToken(ts.SEMI);
\r
826 source.append((char)ts.SEMI);
\r
827 source.append((char)ts.EOL);
\r
833 private Object variables(TokenStream ts, Source source, boolean inForInit)
\r
834 throws IOException, JavaScriptException
\r
836 Object pn = nf.createVariables(ts.getLineno());
\r
837 boolean first = true;
\r
839 source.append((char)ts.VAR);
\r
844 mustMatchToken(ts, ts.NAME, "msg.bad.var");
\r
845 String s = ts.getString();
\r
848 source.append((char)ts.COMMA);
\r
851 source.addString(ts.NAME, s);
\r
852 name = nf.createName(s);
\r
854 // omitted check for argument hiding
\r
856 if (ts.matchToken(ts.ASSIGN)) {
\r
857 if (ts.getOp() != ts.NOP)
\r
858 reportError(ts, "msg.bad.var.init");
\r
860 source.append((char)ts.ASSIGN);
\r
861 source.append((char)ts.NOP);
\r
863 init = assignExpr(ts, source, inForInit);
\r
864 nf.addChildToBack(name, init);
\r
866 nf.addChildToBack(pn, name);
\r
867 if (!ts.matchToken(ts.COMMA))
\r
873 private Object expr(TokenStream ts, Source source, boolean inForInit)
\r
874 throws IOException, JavaScriptException
\r
876 Object pn = assignExpr(ts, source, inForInit);
\r
877 while (ts.matchToken(ts.COMMA)) {
\r
878 source.append((char)ts.COMMA);
\r
879 pn = nf.createBinary(ts.COMMA, pn, assignExpr(ts, source,
\r
885 private Object assignExpr(TokenStream ts, Source source, boolean inForInit)
\r
886 throws IOException, JavaScriptException
\r
888 Object pn = condExpr(ts, source, inForInit);
\r
890 if (ts.matchToken(ts.ASSIGN)) {
\r
891 // omitted: "invalid assignment left-hand side" check.
\r
892 source.append((char)ts.ASSIGN);
\r
893 source.append((char)ts.getOp());
\r
894 pn = nf.createBinary(ts.ASSIGN, ts.getOp(), pn,
\r
895 assignExpr(ts, source, inForInit));
\r
901 private Object condExpr(TokenStream ts, Source source, boolean inForInit)
\r
902 throws IOException, JavaScriptException
\r
907 Object pn = orExpr(ts, source, inForInit);
\r
909 if (ts.matchToken(ts.HOOK)) {
\r
910 source.append((char)ts.HOOK);
\r
911 ifTrue = assignExpr(ts, source, false);
\r
912 mustMatchToken(ts, ts.COLON, "msg.no.colon.cond");
\r
913 source.append((char)ts.COLON);
\r
914 ifFalse = assignExpr(ts, source, inForInit);
\r
915 return nf.createTernary(pn, ifTrue, ifFalse);
\r
921 private Object orExpr(TokenStream ts, Source source, boolean inForInit)
\r
922 throws IOException, JavaScriptException
\r
924 Object pn = andExpr(ts, source, inForInit);
\r
925 if (ts.matchToken(ts.OR)) {
\r
926 source.append((char)ts.OR);
\r
927 pn = nf.createBinary(ts.OR, pn, orExpr(ts, source, inForInit));
\r
933 private Object andExpr(TokenStream ts, Source source, boolean inForInit)
\r
934 throws IOException, JavaScriptException
\r
936 Object pn = bitOrExpr(ts, source, inForInit);
\r
937 if (ts.matchToken(ts.AND)) {
\r
938 source.append((char)ts.AND);
\r
939 pn = nf.createBinary(ts.AND, pn, andExpr(ts, source, inForInit));
\r
945 private Object bitOrExpr(TokenStream ts, Source source, boolean inForInit)
\r
946 throws IOException, JavaScriptException
\r
948 Object pn = bitXorExpr(ts, source, inForInit);
\r
949 while (ts.matchToken(ts.BITOR)) {
\r
950 source.append((char)ts.BITOR);
\r
951 pn = nf.createBinary(ts.BITOR, pn, bitXorExpr(ts, source,
\r
957 private Object bitXorExpr(TokenStream ts, Source source, boolean inForInit)
\r
958 throws IOException, JavaScriptException
\r
960 Object pn = bitAndExpr(ts, source, inForInit);
\r
961 while (ts.matchToken(ts.BITXOR)) {
\r
962 source.append((char)ts.BITXOR);
\r
963 pn = nf.createBinary(ts.BITXOR, pn, bitAndExpr(ts, source,
\r
969 private Object bitAndExpr(TokenStream ts, Source source, boolean inForInit)
\r
970 throws IOException, JavaScriptException
\r
972 Object pn = eqExpr(ts, source, inForInit);
\r
973 while (ts.matchToken(ts.BITAND)) {
\r
974 source.append((char)ts.BITAND);
\r
975 pn = nf.createBinary(ts.BITAND, pn, eqExpr(ts, source, inForInit));
\r
980 private Object eqExpr(TokenStream ts, Source source, boolean inForInit)
\r
981 throws IOException, JavaScriptException
\r
983 Object pn = relExpr(ts, source, inForInit);
\r
984 while (ts.matchToken(ts.EQOP)) {
\r
985 source.append((char)ts.EQOP);
\r
986 source.append((char)ts.getOp());
\r
987 pn = nf.createBinary(ts.EQOP, ts.getOp(), pn,
\r
988 relExpr(ts, source, inForInit));
\r
993 private Object relExpr(TokenStream ts, Source source, boolean inForInit)
\r
994 throws IOException, JavaScriptException
\r
996 Object pn = shiftExpr(ts, source);
\r
997 while (ts.matchToken(ts.RELOP)) {
\r
998 int op = ts.getOp();
\r
999 if (inForInit && op == ts.IN) {
\r
1000 ts.ungetToken(ts.RELOP);
\r
1003 source.append((char)ts.RELOP);
\r
1004 source.append((char)op);
\r
1005 pn = nf.createBinary(ts.RELOP, op, pn,
\r
1006 shiftExpr(ts, source));
\r
1011 private Object shiftExpr(TokenStream ts, Source source)
\r
1012 throws IOException, JavaScriptException
\r
1014 Object pn = addExpr(ts, source);
\r
1015 while (ts.matchToken(ts.SHOP)) {
\r
1016 source.append((char)ts.SHOP);
\r
1017 source.append((char)ts.getOp());
\r
1018 pn = nf.createBinary(ts.getOp(), pn, addExpr(ts, source));
\r
1023 private Object addExpr(TokenStream ts, Source source)
\r
1024 throws IOException, JavaScriptException
\r
1027 Object pn = mulExpr(ts, source);
\r
1029 while ((tt = ts.getToken()) == ts.ADD || tt == ts.SUB) {
\r
1030 source.append((char)tt);
\r
1032 pn = nf.createBinary(tt, pn, mulExpr(ts, source));
\r
1034 ts.ungetToken(tt);
\r
1039 private Object mulExpr(TokenStream ts, Source source)
\r
1040 throws IOException, JavaScriptException
\r
1044 Object pn = unaryExpr(ts, source);
\r
1046 while ((tt = ts.peekToken()) == ts.MUL ||
\r
1049 tt = ts.getToken();
\r
1050 source.append((char)tt);
\r
1051 pn = nf.createBinary(tt, pn, unaryExpr(ts, source));
\r
1058 private Object unaryExpr(TokenStream ts, Source source)
\r
1059 throws IOException, JavaScriptException
\r
1063 ts.flags |= ts.TSF_REGEXP;
\r
1064 tt = ts.getToken();
\r
1065 ts.flags &= ~ts.TSF_REGEXP;
\r
1068 case TokenStream.UNARYOP:
\r
1069 source.append((char)ts.UNARYOP);
\r
1070 source.append((char)ts.getOp());
\r
1071 return nf.createUnary(ts.UNARYOP, ts.getOp(),
\r
1072 unaryExpr(ts, source));
\r
1074 case TokenStream.ADD:
\r
1075 case TokenStream.SUB:
\r
1076 source.append((char)ts.UNARYOP);
\r
1077 source.append((char)tt);
\r
1078 return nf.createUnary(ts.UNARYOP, tt, unaryExpr(ts, source));
\r
1080 case TokenStream.INC:
\r
1081 case TokenStream.DEC:
\r
1082 source.append((char)tt);
\r
1083 return nf.createUnary(tt, ts.PRE, memberExpr(ts, source, true));
\r
1085 case TokenStream.DELPROP:
\r
1086 source.append((char)ts.DELPROP);
\r
1087 return nf.createUnary(ts.DELPROP, unaryExpr(ts, source));
\r
1089 case TokenStream.ERROR:
\r
1093 ts.ungetToken(tt);
\r
1095 int lineno = ts.getLineno();
\r
1097 Object pn = memberExpr(ts, source, true);
\r
1099 /* don't look across a newline boundary for a postfix incop.
\r
1101 * the rhino scanner seems to work differently than the js
\r
1102 * scanner here; in js, it works to have the line number check
\r
1103 * precede the peekToken calls. It'd be better if they had
\r
1104 * similar behavior...
\r
1107 if (((peeked = ts.peekToken()) == ts.INC ||
\r
1108 peeked == ts.DEC) &&
\r
1109 ts.getLineno() == lineno)
\r
1111 int pf = ts.getToken();
\r
1112 source.append((char)pf);
\r
1113 return nf.createUnary(pf, ts.POST, pn);
\r
1117 return nf.createName("err"); // Only reached on error. Try to continue.
\r
1121 private Object argumentList(TokenStream ts, Source source, Object listNode)
\r
1122 throws IOException, JavaScriptException
\r
1125 ts.flags |= ts.TSF_REGEXP;
\r
1126 matched = ts.matchToken(ts.RP);
\r
1127 ts.flags &= ~ts.TSF_REGEXP;
\r
1129 boolean first = true;
\r
1132 source.append((char)ts.COMMA);
\r
1134 nf.addChildToBack(listNode, assignExpr(ts, source, false));
\r
1135 } while (ts.matchToken(ts.COMMA));
\r
1137 mustMatchToken(ts, ts.RP, "msg.no.paren.arg");
\r
1139 source.append((char)ts.RP);
\r
1143 private Object memberExpr(TokenStream ts, Source source,
\r
1144 boolean allowCallSyntax)
\r
1145 throws IOException, JavaScriptException
\r
1151 /* Check for new expressions. */
\r
1152 ts.flags |= ts.TSF_REGEXP;
\r
1153 tt = ts.peekToken();
\r
1154 ts.flags &= ~ts.TSF_REGEXP;
\r
1155 if (tt == ts.NEW) {
\r
1156 /* Eat the NEW token. */
\r
1158 source.append((char)ts.NEW);
\r
1160 /* Make a NEW node to append to. */
\r
1161 pn = nf.createLeaf(ts.NEW);
\r
1162 nf.addChildToBack(pn, memberExpr(ts, source, false));
\r
1164 if (ts.matchToken(ts.LP)) {
\r
1165 source.append((char)ts.LP);
\r
1166 /* Add the arguments to pn, if any are supplied. */
\r
1167 pn = argumentList(ts, source, pn);
\r
1170 /* XXX there's a check in the C source against
\r
1171 * "too many constructor arguments" - how many
\r
1172 * do we claim to support?
\r
1175 /* Experimental syntax: allow an object literal to follow a new expression,
\r
1176 * which will mean a kind of anonymous class built with the JavaAdapter.
\r
1177 * the object literal will be passed as an additional argument to the constructor.
\r
1179 tt = ts.peekToken();
\r
1180 if (tt == ts.LC) {
\r
1181 nf.addChildToBack(pn, primaryExpr(ts, source));
\r
1184 pn = primaryExpr(ts, source);
\r
1187 lastExprEndLine = ts.getLineno();
\r
1188 while ((tt = ts.getToken()) > ts.EOF) {
\r
1189 if (tt == ts.DOT) {
\r
1190 source.append((char)ts.DOT);
\r
1191 mustMatchToken(ts, ts.NAME, "msg.no.name.after.dot");
\r
1192 String s = ts.getString();
\r
1193 source.addString(ts.NAME, s);
\r
1194 pn = nf.createBinary(ts.DOT, pn,
\r
1195 nf.createName(ts.getString()));
\r
1196 /* pn = nf.createBinary(ts.DOT, pn, memberExpr(ts))
\r
1197 * is the version in Brendan's IR C version. Not in ECMA...
\r
1198 * does it reflect the 'new' operator syntax he mentioned?
\r
1200 lastExprEndLine = ts.getLineno();
\r
1201 } else if (tt == ts.LB) {
\r
1202 source.append((char)ts.LB);
\r
1203 pn = nf.createBinary(ts.LB, pn, expr(ts, source, false));
\r
1205 mustMatchToken(ts, ts.RB, "msg.no.bracket.index");
\r
1206 source.append((char)ts.RB);
\r
1207 lastExprEndLine = ts.getLineno();
\r
1208 } else if (allowCallSyntax && tt == ts.LP) {
\r
1209 /* make a call node */
\r
1211 pn = nf.createUnary(ts.CALL, pn);
\r
1212 source.append((char)ts.LP);
\r
1214 /* Add the arguments to pn, if any are supplied. */
\r
1215 pn = argumentList(ts, source, pn);
\r
1216 lastExprEndLine = ts.getLineno();
\r
1218 ts.ungetToken(tt);
\r
1227 private Object primaryExpr(TokenStream ts, Source source)
\r
1228 throws IOException, JavaScriptException
\r
1234 ts.flags |= ts.TSF_REGEXP;
\r
1235 tt = ts.getToken();
\r
1236 ts.flags &= ~ts.TSF_REGEXP;
\r
1240 case TokenStream.FUNCTION:
\r
1241 return function(ts, source, true);
\r
1243 case TokenStream.LB:
\r
1245 source.append((char)ts.LB);
\r
1246 pn = nf.createLeaf(ts.ARRAYLIT);
\r
1248 ts.flags |= ts.TSF_REGEXP;
\r
1249 boolean matched = ts.matchToken(ts.RB);
\r
1250 ts.flags &= ~ts.TSF_REGEXP;
\r
1253 boolean first = true;
\r
1255 ts.flags |= ts.TSF_REGEXP;
\r
1256 tt = ts.peekToken();
\r
1257 ts.flags &= ~ts.TSF_REGEXP;
\r
1260 source.append((char)ts.COMMA);
\r
1264 if (tt == ts.RB) { // to fix [,,,].length behavior...
\r
1268 if (tt == ts.COMMA) {
\r
1269 nf.addChildToBack(pn, nf.createLeaf(ts.PRIMARY,
\r
1272 nf.addChildToBack(pn, assignExpr(ts, source, false));
\r
1275 } while (ts.matchToken(ts.COMMA));
\r
1276 mustMatchToken(ts, ts.RB, "msg.no.bracket.arg");
\r
1278 source.append((char)ts.RB);
\r
1279 return nf.createArrayLiteral(pn);
\r
1282 case TokenStream.LC: {
\r
1283 pn = nf.createLeaf(ts.OBJLIT);
\r
1285 source.append((char)ts.LC);
\r
1286 if (!ts.matchToken(ts.RC)) {
\r
1288 boolean first = true;
\r
1294 source.append((char)ts.COMMA);
\r
1298 tt = ts.getToken();
\r
1300 // map NAMEs to STRINGs in object literal context.
\r
1301 case TokenStream.NAME:
\r
1302 case TokenStream.STRING:
\r
1303 String s = ts.getString();
\r
1304 source.addString(ts.NAME, s);
\r
1305 property = nf.createString(ts.getString());
\r
1307 case TokenStream.NUMBER:
\r
1308 Number n = ts.getNumber();
\r
1309 source.addNumber(n);
\r
1310 property = nf.createNumber(n);
\r
1312 case TokenStream.RC:
\r
1313 // trailing comma is OK.
\r
1314 ts.ungetToken(tt);
\r
1317 reportError(ts, "msg.bad.prop");
\r
1320 mustMatchToken(ts, ts.COLON, "msg.no.colon.prop");
\r
1322 // OBJLIT is used as ':' in object literal for
\r
1323 // decompilation to solve spacing ambiguity.
\r
1324 source.append((char)ts.OBJLIT);
\r
1325 nf.addChildToBack(pn, property);
\r
1326 nf.addChildToBack(pn, assignExpr(ts, source, false));
\r
1328 } while (ts.matchToken(ts.COMMA));
\r
1330 mustMatchToken(ts, ts.RC, "msg.no.brace.prop");
\r
1332 source.append((char)ts.RC);
\r
1333 return nf.createObjectLiteral(pn);
\r
1336 case TokenStream.LP:
\r
1338 /* Brendan's IR-jsparse.c makes a new node tagged with
\r
1339 * TOK_LP here... I'm not sure I understand why. Isn't
\r
1340 * the grouping already implicit in the structure of the
\r
1341 * parse tree? also TOK_LP is already overloaded (I
\r
1342 * think) in the C IR as 'function call.' */
\r
1343 source.append((char)ts.LP);
\r
1344 pn = expr(ts, source, false);
\r
1345 source.append((char)ts.RP);
\r
1346 mustMatchToken(ts, ts.RP, "msg.no.paren");
\r
1349 case TokenStream.NAME:
\r
1350 String name = ts.getString();
\r
1351 source.addString(ts.NAME, name);
\r
1352 return nf.createName(name);
\r
1354 case TokenStream.NUMBER:
\r
1355 Number n = ts.getNumber();
\r
1356 source.addNumber(n);
\r
1357 return nf.createNumber(n);
\r
1359 case TokenStream.STRING:
\r
1360 String s = ts.getString();
\r
1361 source.addString(ts.STRING, s);
\r
1362 return nf.createString(s);
\r
1364 case TokenStream.OBJECT:
\r
1366 String flags = ts.regExpFlags;
\r
1367 ts.regExpFlags = null;
\r
1368 String re = ts.getString();
\r
1369 source.addString(ts.OBJECT, '/' + re + '/' + flags);
\r
1370 return nf.createRegExp(re, flags);
\r
1373 case TokenStream.PRIMARY:
\r
1374 source.append((char)ts.PRIMARY);
\r
1375 source.append((char)ts.getOp());
\r
1376 return nf.createLeaf(ts.PRIMARY, ts.getOp());
\r
1378 case TokenStream.RESERVED:
\r
1379 reportError(ts, "msg.reserved.id");
\r
1382 case TokenStream.ERROR:
\r
1383 /* the scanner or one of its subroutines reported the error. */
\r
1387 reportError(ts, "msg.syntax");
\r
1391 return null; // should never reach here
\r
1394 private int lastExprEndLine; // Hack to handle function expr termination.
\r
1395 private IRFactory nf;
\r
1396 private ErrorReporter er;
\r
1397 private boolean ok; // Did the parse encounter an error?
\r
1401 * This class saves decompilation information about the source.
\r
1402 * Source information is returned from the parser as a String
\r
1403 * associated with function nodes and with the toplevel script. When
\r
1404 * saved in the constant pool of a class, this string will be UTF-8
\r
1405 * encoded, and token values will occupy a single byte.
\r
1407 * Source is saved (mostly) as token numbers. The tokens saved pretty
\r
1408 * much correspond to the token stream of a 'canonical' representation
\r
1409 * of the input program, as directed by the parser. (There were a few
\r
1410 * cases where tokens could have been left out where decompiler could
\r
1411 * easily reconstruct them, but I left them in for clarity). (I also
\r
1412 * looked adding source collection to TokenStream instead, where I
\r
1413 * could have limited the changes to a few lines in getToken... but
\r
1414 * this wouldn't have saved any space in the resulting source
\r
1415 * representation, and would have meant that I'd have to duplicate
\r
1416 * parser logic in the decompiler to disambiguate situations where
\r
1417 * newlines are important.) NativeFunction.decompile expands the
\r
1418 * tokens back into their string representations, using simple
\r
1419 * lookahead to correct spacing and indentation.
\r
1421 * Token types with associated ops (ASSIGN, SHOP, PRIMARY, etc.) are
\r
1422 * saved as two-token pairs. Number tokens are stored inline, as a
\r
1423 * NUMBER token, a character representing the type, and either 1 or 4
\r
1424 * characters representing the bit-encoding of the number. String
\r
1425 * types NAME, STRING and OBJECT are currently stored as a token type,
\r
1426 * followed by a character giving the length of the string (assumed to
\r
1427 * be less than 2^16), followed by the characters of the string
\r
1428 * inlined into the source string. Changing this to some reference to
\r
1429 * to the string in the compiled class' constant pool would probably
\r
1430 * save a lot of space... but would require some method of deriving
\r
1431 * the final constant pool entry from information available at parse
\r
1434 * Nested functions need a similar mechanism... fortunately the nested
\r
1435 * functions for a given function are generated in source order.
\r
1436 * Nested functions are encoded as FUNCTION followed by a function
\r
1437 * number (encoded as a character), which is enough information to
\r
1438 * find the proper generated NativeFunction instance.
\r
1440 * OPT source info collection is a potential performance bottleneck;
\r
1441 * Source wraps a java.lang.StringBuffer, which is synchronized. It
\r
1442 * might be faster to implement Source with its own char buffer and
\r
1443 * toString method.
\r
1447 final class Source {
\r
1449 // OPT the default 16 is probably too small, but it's not
\r
1450 // clear just what size would work best for most javascript.
\r
1451 // It'd be nice to know what characterizes the javascript
\r
1452 // that's out there.
\r
1453 buf = new StringBuffer(64);
\r
1456 void append(char c) {
\r
1460 void addString(int type, String str) {
\r
1461 buf.append((char)type);
\r
1462 // java string length < 2^16?
\r
1463 buf.append((char)str.length());
\r
1467 void addNumber(Number n) {
\r
1468 buf.append((char)TokenStream.NUMBER);
\r
1470 /* encode the number in the source stream.
\r
1471 * Save as NUMBER type (char | char char char char)
\r
1473 * 'D' - double, 'S' - short, 'J' - long.
\r
1475 * We need to retain float vs. integer type info to keep the
\r
1476 * behavior of liveconnect type-guessing the same after
\r
1477 * decompilation. (Liveconnect tries to present 1.0 to Java
\r
1478 * as a float/double)
\r
1479 * OPT: This is no longer true. We could compress the format.
\r
1481 * This may not be the most space-efficient encoding;
\r
1482 * the chars created below may take up to 3 bytes in
\r
1483 * constant pool UTF-8 encoding, so a Double could take
\r
1487 if (n instanceof Double || n instanceof Float) {
\r
1488 // if it's floating point, save as a Double bit pattern.
\r
1489 // (12/15/97 our scanner only returns Double for f.p.)
\r
1491 long lbits = Double.doubleToLongBits(n.doubleValue());
\r
1493 buf.append((char)((lbits >> 48) & 0xFFFF));
\r
1494 buf.append((char)((lbits >> 32) & 0xFFFF));
\r
1495 buf.append((char)((lbits >> 16) & 0xFFFF));
\r
1496 buf.append((char)(lbits & 0xFFFF));
\r
1498 long lbits = n.longValue();
\r
1499 // will it fit in a char?
\r
1500 // (we can ignore negative values, bc they're already prefixed
\r
1501 // by UNARYOP SUB)
\r
1502 // this gives a short encoding for integer values up to 2^16.
\r
1503 if (lbits <= Character.MAX_VALUE) {
\r
1505 buf.append((char)lbits);
\r
1506 } else { // Integral, but won't fit in a char. Store as a long.
\r
1508 buf.append((char)((lbits >> 48) & 0xFFFF));
\r
1509 buf.append((char)((lbits >> 32) & 0xFFFF));
\r
1510 buf.append((char)((lbits >> 16) & 0xFFFF));
\r
1511 buf.append((char)(lbits & 0xFFFF));
\r
1516 char functionNumber;
\r