1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set sw=4 ts=8 et tw=78:
4 * ***** BEGIN LICENSE BLOCK *****
5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
17 * The Original Code is Mozilla Communicator client code, released
20 * The Initial Developer of the Original Code is
21 * Netscape Communications Corporation.
22 * Portions created by the Initial Developer are Copyright (C) 1998
23 * the Initial Developer. All Rights Reserved.
27 * Alternatively, the contents of this file may be used under the terms of
28 * either of the GNU General Public License Version 2 or later (the "GPL"),
29 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
39 * ***** END LICENSE BLOCK ***** */
44 #include <stdio.h> /* first to avoid trouble on some systems */
63 #include "jsversion.h"
72 #include "jsstaticcheck.h"
75 #include "jsscriptinlines.h"
77 #if JS_HAS_XML_SUPPORT
83 #define JS_KEYWORD(keyword, type, op, version) \
84 const char js_##keyword##_str[] = #keyword;
85 #include "jskeyword.tbl"
88 static const KeywordInfo keywords[] = {
89 #define JS_KEYWORD(keyword, type, op, version) \
90 {js_##keyword##_str, type, op, version},
91 #include "jskeyword.tbl"
98 FindKeyword(const jschar *s, size_t length)
100 JS_ASSERT(length != 0);
103 const struct KeywordInfo *kw;
106 #define JSKW_LENGTH() length
107 #define JSKW_AT(column) s[column]
108 #define JSKW_GOT_MATCH(index) i = (index); goto got_match;
109 #define JSKW_TEST_GUESS(index) i = (index); goto test_guess;
110 #define JSKW_NO_MATCH() goto no_match;
111 #include "jsautokw.h"
113 #undef JSKW_TEST_GUESS
114 #undef JSKW_GOT_MATCH
125 if (*s++ != (unsigned char)(*chars++))
127 } while (--length != 0);
137 js_IsIdentifier(JSLinearString *str)
139 const jschar *chars = str->chars();
140 size_t length = str->length();
145 if (!JS_ISIDSTART(c))
147 const jschar *end = chars + length;
148 while (++chars != end) {
157 #pragma warning(push)
158 #pragma warning(disable:4351)
161 /* Initialize members that aren't initialized in |init|. */
162 TokenStream::TokenStream(JSContext *cx)
163 : cx(cx), tokens(), cursor(), lookahead(), flags(), listenerTSData(), tokenbuf(cx)
171 TokenStream::init(const jschar *base, size_t length, const char *fn, uintN ln, JSVersion v)
176 xml = VersionHasXML(v);
178 userbuf.base = (jschar *)base;
179 userbuf.limit = (jschar *)base + length;
180 userbuf.ptr = (jschar *)base;
182 linebase = userbuf.base;
185 listener = cx->debugHooks->sourceHandler;
186 listenerData = cx->debugHooks->sourceHandlerData;
188 /* See getChar() for an explanation of maybeEOL[]. */
189 memset(maybeEOL, 0, sizeof(maybeEOL));
190 maybeEOL['\n'] = true;
191 maybeEOL['\r'] = true;
192 maybeEOL[LINE_SEPARATOR & 0xff] = true;
193 maybeEOL[PARA_SEPARATOR & 0xff] = true;
195 /* See getTokenInternal() for an explanation of maybeStrSpecial[]. */
196 memset(maybeStrSpecial, 0, sizeof(maybeStrSpecial));
197 maybeStrSpecial['"'] = true;
198 maybeStrSpecial['\''] = true;
199 maybeStrSpecial['\\'] = true;
200 maybeStrSpecial['\n'] = true;
201 maybeStrSpecial['\r'] = true;
202 maybeStrSpecial[LINE_SEPARATOR & 0xff] = true;
203 maybeStrSpecial[PARA_SEPARATOR & 0xff] = true;
204 maybeStrSpecial[EOF & 0xff] = true;
211 if (flags & TSF_OWNFILENAME)
212 cx->free((void *) filename);
215 /* Use the fastest available getc. */
216 #if defined(HAVE_GETC_UNLOCKED)
217 # define fast_getc getc_unlocked
218 #elif defined(HAVE__GETC_NOLOCK)
219 # define fast_getc _getc_nolock
221 # define fast_getc getc
225 js_fgets(char *buf, int size, FILE *file)
235 for (i = 0; i < n && (c = fast_getc(file)) != EOF; i++) {
237 if (c == '\n') { /* any \n ends a line */
238 i++; /* keep the \n; we know there is room for \0 */
241 if (crflag) { /* \r not followed by \n ends line at the \r */
243 break; /* and overwrite c in buf with \0 */
245 crflag = (c == '\r');
252 /* This gets the next char, normalizing all EOL sequences to '\n' as it goes. */
254 TokenStream::getChar()
257 if (JS_LIKELY(userbuf.ptr < userbuf.limit)) {
261 * Normalize the jschar if it was a newline. We need to detect any of
262 * these four characters: '\n' (0x000a), '\r' (0x000d),
263 * LINE_SEPARATOR (0x2028), PARA_SEPARATOR (0x2029). Testing for each
264 * one in turn is slow, so we use a single probabilistic check, and if
265 * that succeeds, test for them individually.
267 * We use the bottom 8 bits to index into a lookup table, succeeding
268 * when d&0xff is 0xa, 0xd, 0x28 or 0x29. Among ASCII chars (which
269 * are by the far the most common) this gives false positives for '('
270 * (0x0028) and ')' (0x0029). We could avoid those by incorporating
271 * the 13th bit of d into the lookup, but that requires extra shifting
272 * and masking and isn't worthwhile. See TokenStream::init() for the
273 * initialization of the relevant entries in the table.
275 if (JS_UNLIKELY(maybeEOL[c & 0xff])) {
279 if (userbuf.ptr < userbuf.limit && *userbuf.ptr == '\n') {
280 /* a \r\n sequence: treat as a single EOL, skip over the \n */
285 if (c == LINE_SEPARATOR || c == PARA_SEPARATOR)
295 prevLinebase = linebase;
296 linebase = userbuf.ptr;
302 * This gets the next char. It does nothing special with EOL sequences, not
303 * even updating the line counters.
306 TokenStream::getCharIgnoreEOL()
308 if (JS_LIKELY(userbuf.ptr < userbuf.limit))
309 return *userbuf.ptr++;
316 TokenStream::ungetChar(int32 c)
320 JS_ASSERT(userbuf.ptr > userbuf.base);
324 int32 c2 = *userbuf.ptr;
325 JS_ASSERT(c2 == '\n' || c2 == '\r' || c2 == LINE_SEPARATOR || c2 == PARA_SEPARATOR);
327 if (userbuf.ptr > userbuf.base && *(userbuf.ptr - 1) == '\r')
328 userbuf.ptr--; /* also unget the \r in a \r\n sequence */
329 JS_ASSERT(prevLinebase); /* we should never get more than one EOL char */
330 linebase = prevLinebase;
334 JS_ASSERT(*userbuf.ptr == c);
339 TokenStream::ungetCharIgnoreEOL(int32 c)
341 JS_ASSERT(c == '\n' || c == '\r' || c == LINE_SEPARATOR || c == PARA_SEPARATOR || c == EOF);
344 JS_ASSERT(userbuf.ptr > userbuf.base);
349 * Peek n chars ahead into ts. Return true if n chars were read, false if
350 * there weren't enough characters in the input stream. This function cannot
351 * be used to peek into or past a newline.
354 TokenStream::peekChars(intN n, jschar *cp)
359 for (i = 0; i < n; i++) {
369 for (j = i - 1; j >= 0; j--)
375 TokenStream::findEOL()
377 TokenBuf tmpUserbuf = userbuf;
378 jschar *tmpLinebase = linebase;
379 jschar *tmpPrevLinebase = prevLinebase;
380 uintN tmpFlags = flags;
381 uintN tmpLineno = lineno;
385 if (c == '\n' || c == EOF)
388 jschar *linelimit = userbuf.ptr;
390 /* Need to restore everything changed by getChar(). */
391 userbuf = tmpUserbuf;
392 linebase = tmpLinebase;
393 prevLinebase = tmpPrevLinebase;
401 TokenStream::reportCompileErrorNumberVA(JSParseNode *pn, uintN flags, uintN errorNumber,
404 JSErrorReport report;
414 JSErrorReporter onError;
416 if (JSREPORT_IS_STRICT(flags) && !cx->hasStrictOption())
419 warning = JSREPORT_IS_WARNING(flags);
420 if (warning && cx->hasWErrorOption()) {
421 flags &= ~JSREPORT_WARNING;
426 report.flags = flags;
427 report.errorNumber = errorNumber;
432 MUST_FLOW_THROUGH("out");
433 ok = js_ExpandErrorArguments(cx, js_GetErrorMessage, NULL,
434 errorNumber, &message, &report,
435 !(flags & JSREPORT_UC), ap);
441 report.filename = filename;
444 report.lineno = pn->pn_pos.begin.lineno;
445 if (report.lineno != lineno)
449 /* Point to the current token, not the next one to get. */
450 tp = &tokens[cursor].pos;
452 report.lineno = lineno;
454 linelimit = findEOL();
455 linelength = linelimit - linebase;
457 linechars = (jschar *)cx->malloc((linelength + 1) * sizeof(jschar));
462 memcpy(linechars, linebase, linelength * sizeof(jschar));
463 linechars[linelength] = 0;
464 linebytes = js_DeflateString(cx, linechars, linelength);
469 report.linebuf = linebytes; /* the offending source line, without final \n */
471 index = (tp->begin.lineno == tp->end.lineno)
472 ? tp->begin.index /* the column number of the start of the bad token */
475 report.tokenptr = report.linebuf + index;
476 report.uclinebuf = linechars;
477 report.uctokenptr = report.uclinebuf + index;
480 * If there's a runtime exception type associated with this error
481 * number, set that as the pending exception. For errors occuring at
482 * compile time, this is very likely to be a JSEXN_SYNTAXERR.
484 * If an exception is thrown but not caught, the JSREPORT_EXCEPTION
485 * flag will be set in report.flags. Proper behavior for an error
486 * reporter is to ignore a report with this flag for all but top-level
487 * compilation errors. The exception will remain pending, and so long
488 * as the non-top-level "load", "eval", or "compile" native function
489 * returns false, the top-level reporter will eventually receive the
490 * uncaught exception report.
492 * XXX it'd probably be best if there was only one call to this
493 * function, but there seem to be two error reporter call points.
496 onError = cx->errorReporter;
499 * Try to raise an exception only if there isn't one already set --
500 * otherwise the exception will describe the last compile-time error,
501 * which is likely spurious.
503 if (!(flags & TSF_ERROR)) {
504 if (js_ErrorToException(cx, message, &report, NULL, NULL))
509 * Suppress any compile-time errors that don't occur at the top level.
510 * This may still fail, as interplevel may be zero in contexts where we
511 * don't really want to call the error reporter, as when js is called
512 * by other code which could catch the error.
514 if (cx->interpLevel != 0 && !JSREPORT_IS_WARNING(flags))
518 JSDebugErrorHook hook = cx->debugHooks->debugErrorHook;
521 * If debugErrorHook is present then we give it a chance to veto
522 * sending the error on to the regular error reporter.
524 if (hook && !hook(cx, message, &report,
525 cx->debugHooks->debugErrorHookData)) {
530 (*onError)(cx, message, &report);
539 if (report.ucmessage)
540 cx->free((void *)report.ucmessage);
542 if (report.messageArgs) {
543 if (!(flags & JSREPORT_UC)) {
545 while (report.messageArgs[i])
546 cx->free((void *)report.messageArgs[i++]);
548 cx->free((void *)report.messageArgs);
551 if (!JSREPORT_IS_WARNING(flags)) {
552 /* Set the error flag to suppress spurious reports. */
560 js::ReportStrictModeError(JSContext *cx, TokenStream *ts, JSTreeContext *tc, JSParseNode *pn,
561 uintN errorNumber, ...)
564 JS_ASSERT(cx == ts->getContext());
566 /* In strict mode code, this is an error, not merely a warning. */
568 if ((ts && ts->isStrictMode()) || (tc && (tc->flags & TCF_STRICT_MODE_CODE))) {
569 flags = JSREPORT_ERROR;
571 if (!cx->hasStrictOption())
573 flags = JSREPORT_WARNING;
577 va_start(ap, errorNumber);
578 bool result = ts->reportCompileErrorNumberVA(pn, flags, errorNumber, ap);
585 js::ReportCompileErrorNumber(JSContext *cx, TokenStream *ts, JSParseNode *pn,
586 uintN flags, uintN errorNumber, ...)
591 * We don't accept a JSTreeContext argument, so we can't implement
592 * JSREPORT_STRICT_MODE_ERROR here. Use ReportStrictModeError instead,
593 * or do the checks in the caller and pass plain old JSREPORT_ERROR.
595 JS_ASSERT(!(flags & JSREPORT_STRICT_MODE_ERROR));
597 va_start(ap, errorNumber);
598 JS_ASSERT(cx == ts->getContext());
599 bool result = ts->reportCompileErrorNumberVA(pn, flags, errorNumber, ap);
605 #if JS_HAS_XML_SUPPORT
608 TokenStream::getXMLEntity()
610 ptrdiff_t offset, length, i;
617 CharBuffer &tb = tokenbuf;
619 /* Put the entity, including the '&' already scanned, in tokenbuf. */
620 offset = tb.length();
623 while ((c = getChar()) != ';') {
624 if (c == EOF || c == '\n') {
625 ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR, JSMSG_END_OF_XML_ENTITY);
632 /* Let length be the number of jschars after the '&', including the ';'. */
633 length = tb.length() - offset;
634 bp = tb.begin() + offset;
637 if (length > 2 && bp[1] == '#') {
638 /* Match a well-formed XML Character Reference. */
640 if (length > 3 && JS_TOLOWER(bp[i]) == 'x') {
641 if (length > 9) /* at most 6 hex digits allowed */
643 while (++i < length) {
645 if (!JS7_ISHEX(digit))
647 c = (c << 4) + JS7_UNHEX(digit);
652 if (!JS7_ISDEC(digit))
654 c = (c * 10) + JS7_UNDEC(digit);
660 if (0x10000 <= c && c <= 0x10FFFF) {
661 /* Form a surrogate pair (c, d) -- c is the high surrogate. */
662 d = 0xDC00 + (c & 0x3FF);
663 c = 0xD7C0 + (c >> 10);
666 /* Enforce the http://www.w3.org/TR/REC-xml/#wf-Legalchar WFC. */
667 if (c != 0x9 && c != 0xA && c != 0xD &&
668 !(0x20 <= c && c <= 0xD7FF) &&
669 !(0xE000 <= c && c <= 0xFFFD)) {
674 /* Try to match one of the five XML 1.0 predefined entities. */
680 else if (bp[1] == 'g')
685 if (bp[1] == 'a' && bp[2] == 'm' && bp[3] == 'p')
690 if (bp[1] == 'a' && bp[2] == 'p' && bp[4] == 's')
692 else if (bp[1] == 'q' && bp[2] == 'u' && bp[4] == 't')
698 msg = JSMSG_UNKNOWN_XML_ENTITY;
703 /* If we matched, retract tokenbuf and store the entity's value. */
707 tb.shrinkBy(tb.end() - bp);
711 msg = JSMSG_BAD_XML_NCR;
713 /* No match: throw a TypeError per ECMA-357 10.3.2.1 step 8(a). */
714 JS_ASSERT((tb.end() - bp) >= 1);
715 bytes = js_DeflateString(cx, bp + 1, (tb.end() - bp) - 1);
717 ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR, msg, bytes);
723 #endif /* JS_HAS_XML_SUPPORT */
726 * We have encountered a '\': check for a Unicode escape sequence after it.
727 * Return 'true' and the character code value (by value) if we found a
728 * Unicode escape sequence. Otherwise, return 'false'. In both cases, do not
729 * advance along the buffer.
732 TokenStream::peekUnicodeEscape(int *result)
736 if (peekChars(5, cp) && cp[0] == 'u' &&
737 JS7_ISHEX(cp[1]) && JS7_ISHEX(cp[2]) &&
738 JS7_ISHEX(cp[3]) && JS7_ISHEX(cp[4]))
740 *result = (((((JS7_UNHEX(cp[1]) << 4)
741 + JS7_UNHEX(cp[2])) << 4)
742 + JS7_UNHEX(cp[3])) << 4)
750 TokenStream::matchUnicodeEscapeIdStart(int32 *cp)
752 if (peekUnicodeEscape(cp) && JS_ISIDSTART(*cp)) {
760 TokenStream::matchUnicodeEscapeIdent(int32 *cp)
762 if (peekUnicodeEscape(cp) && JS_ISIDENT(*cp)) {
770 TokenStream::newToken(ptrdiff_t adjust)
772 cursor = (cursor + 1) & ntokensMask;
773 Token *tp = &tokens[cursor];
774 tp->ptr = userbuf.ptr + adjust;
775 tp->pos.begin.index = tp->ptr - linebase;
776 tp->pos.begin.lineno = tp->pos.end.lineno = lineno;
780 static JS_ALWAYS_INLINE JSBool
781 ScanAsSpace(jschar c)
783 /* Treat little- and big-endian BOMs as whitespace for compatibility. */
784 if (JS_ISSPACE(c) || c == 0xfffe || c == 0xfeff)
789 JS_ALWAYS_INLINE JSAtom *
790 TokenStream::atomize(JSContext *cx, CharBuffer &cb)
792 return js_AtomizeChars(cx, cb.begin(), cb.length(), 0);
796 TokenStream::getTokenInternal()
802 bool hadUnicodeEscape;
803 #if JS_HAS_XML_SUPPORT
806 ptrdiff_t contentIndex;
809 #if JS_HAS_XML_SUPPORT
814 if (flags & TSF_XMLTEXTMODE) {
815 tt = TOK_XMLSPACE; /* veto if non-space, return TOK_XMLTEXT */
818 qc = (flags & TSF_XMLONLYMODE) ? '<' : '{';
820 while ((c = getChar()) != qc && c != '<' && c != EOF) {
821 if (c == '&' && qc == '<') {
828 if (!JS_ISXMLSPACE(c))
830 if (!tokenbuf.append(c))
835 if (tokenbuf.empty()) {
838 atom = atomize(cx, tokenbuf);
842 tp->pos.end.lineno = lineno;
843 tp->t_op = JSOP_STRING;
852 if (flags & TSF_XMLTAGMODE) {
855 if (JS_ISXMLSPACE(c)) {
858 } while (JS_ISXMLSPACE(c));
860 tp->pos.end.lineno = lineno;
871 if (JS_ISXMLNSSTART(c)) {
872 JSBool sawColon = JS_FALSE;
874 if (!tokenbuf.append(c))
876 while ((c = getChar()) != EOF && JS_ISXMLNAME(c)) {
882 ((flags & TSF_XMLONLYMODE) || nextc != '{') &&
883 !JS_ISXMLNAME(nextc))) {
884 ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR,
885 JSMSG_BAD_XML_QNAME);
891 if (!tokenbuf.append(c))
896 atom = atomize(cx, tokenbuf);
899 tp->t_op = JSOP_STRING;
907 if (flags & TSF_XMLONLYMODE)
919 while ((c = getChar()) != qc) {
921 ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR,
922 JSMSG_UNTERMINATED_STRING);
927 * XML attribute values are double-quoted when pretty-printed,
928 * so escape " if it is expressed directly in a single-quoted
931 if (c == '"' && !(flags & TSF_XMLONLYMODE)) {
932 JS_ASSERT(qc == '\'');
933 if (!tokenbuf.append(js_quot_entity_str,
934 strlen(js_quot_entity_str)))
939 if (c == '&' && (flags & TSF_XMLONLYMODE)) {
945 if (!tokenbuf.append(c))
948 atom = atomize(cx, tokenbuf);
951 tp->pos.end.lineno = lineno;
952 tp->t_op = JSOP_STRING;
962 if (matchChar('>')) {
970 ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR, JSMSG_BAD_XML_CHARACTER);
975 #endif /* JS_HAS_XML_SUPPORT */
979 * This gets the next non-space char and starts the token.
984 flags &= ~TSF_DIRTYLINE;
985 if (flags & TSF_NEWLINES)
988 } while (ScanAsSpace((jschar)c));
997 * Look for an identifier.
1000 hadUnicodeEscape = false;
1001 if (JS_ISIDSTART(c) ||
1002 (c == '\\' && (hadUnicodeEscape = matchUnicodeEscapeIdStart(&qc))))
1004 if (hadUnicodeEscape)
1008 if (!tokenbuf.append(c))
1012 if (!matchUnicodeEscapeIdent(&qc))
1015 hadUnicodeEscape = true;
1024 * Check for keywords unless we saw Unicode escape or parser asks
1025 * to ignore keywords.
1027 const KeywordInfo *kw;
1028 if (!hadUnicodeEscape &&
1029 !(flags & TSF_KEYWORD_IS_NAME) &&
1030 (kw = FindKeyword(tokenbuf.begin(), tokenbuf.length()))) {
1031 if (kw->tokentype == TOK_RESERVED) {
1032 if (!ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR,
1033 JSMSG_RESERVED_ID, kw->chars)) {
1036 } else if (kw->tokentype == TOK_STRICT_RESERVED) {
1038 ? !ReportStrictModeError(cx, this, NULL, NULL, JSMSG_RESERVED_ID, kw->chars)
1039 : !ReportCompileErrorNumber(cx, this, NULL,
1040 JSREPORT_STRICT | JSREPORT_WARNING,
1041 JSMSG_RESERVED_ID, kw->chars)) {
1045 if (kw->version <= versionNumber()) {
1047 tp->t_op = (JSOp) kw->op;
1052 * let/yield are a Mozilla extension starting in JS1.7. If we
1053 * aren't parsing for a version supporting these extensions,
1054 * conform to ES5 and forbid these names in strict mode.
1056 if ((kw->tokentype == TOK_LET || kw->tokentype == TOK_YIELD) &&
1057 !ReportStrictModeError(cx, this, NULL, NULL, JSMSG_RESERVED_ID, kw->chars))
1064 atom = atomize(cx, tokenbuf);
1067 tp->t_op = JSOP_NAME;
1074 * Look for a number.
1077 if (JS7_ISDEC(c) || (c == '.' && JS7_ISDEC(peekChar()))) {
1083 if (JS_TOLOWER(c) == 'x') {
1086 if (!JS7_ISHEX(c)) {
1087 ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR,
1088 JSMSG_MISSING_HEXDIGITS);
1091 } else if (JS7_ISDEC(c)) {
1096 while (JS7_ISHEX(c)) {
1102 /* Octal integer literals are not permitted in strict mode code. */
1103 if (!ReportStrictModeError(cx, this, NULL, NULL, JSMSG_DEPRECATED_OCTAL))
1107 * Outside strict mode, we permit 08 and 09 as decimal numbers, which
1108 * makes our behaviour a superset of the ECMA numeric grammar. We
1109 * might not always be so permissive, so we warn about it.
1112 if (!ReportCompileErrorNumber(cx, this, NULL, JSREPORT_WARNING,
1113 JSMSG_BAD_OCTAL, c == '8' ? "08" : "09")) {
1120 if (!tokenbuf.append(c))
1125 if (radix == 10 && (c == '.' || JS_TOLOWER(c) == 'e')) {
1128 if (!tokenbuf.append(c))
1131 } while (JS7_ISDEC(c));
1133 if (JS_TOLOWER(c) == 'e') {
1134 if (!tokenbuf.append(c))
1137 if (c == '+' || c == '-') {
1138 if (!tokenbuf.append(c))
1142 if (!JS7_ISDEC(c)) {
1143 ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR,
1144 JSMSG_MISSING_EXPONENT);
1148 if (!tokenbuf.append(c))
1151 } while (JS7_ISDEC(c));
1155 if (JS_ISIDSTART(c)) {
1156 ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR, JSMSG_IDSTART_AFTER_NUMBER);
1160 /* Put back the next char and NUL-terminate tokenbuf for js_strto*. */
1162 if (!tokenbuf.append(0))
1166 const jschar *dummy;
1168 if (!js_strtod(cx, tokenbuf.begin(), tokenbuf.end(), &dummy, &dval))
1171 if (!GetPrefixInteger(cx, tokenbuf.begin(), tokenbuf.end(), radix, &dummy, &dval))
1180 * Look for a string.
1183 if (c == '"' || c == '\'') {
1188 * We need to detect any of these chars: " or ', \n (or its
1189 * equivalents), \\, EOF. We use maybeStrSpecial[] in a manner
1190 * similar to maybeEOL[], see above. Because we detect EOL
1191 * sequences here and put them back immediately, we can use
1192 * getCharIgnoreEOL().
1194 c = getCharIgnoreEOL();
1195 if (maybeStrSpecial[c & 0xff]) {
1199 switch (c = getChar()) {
1200 case 'b': c = '\b'; break;
1201 case 'f': c = '\f'; break;
1202 case 'n': c = '\n'; break;
1203 case 'r': c = '\r'; break;
1204 case 't': c = '\t'; break;
1205 case 'v': c = '\v'; break;
1208 if ('0' <= c && c < '8') {
1209 int32 val = JS7_UNDEC(c);
1212 /* Strict mode code allows only \0, then a non-digit. */
1213 if (val != 0 || JS7_ISDEC(c)) {
1214 if (!ReportStrictModeError(cx, this, NULL, NULL,
1215 JSMSG_DEPRECATED_OCTAL)) {
1218 setOctalCharacterEscape();
1220 if ('0' <= c && c < '8') {
1221 val = 8 * val + JS7_UNDEC(c);
1224 if ('0' <= c && c < '8') {
1226 val = 8 * val + JS7_UNDEC(c);
1235 } else if (c == 'u') {
1237 if (peekChars(4, cp) &&
1238 JS7_ISHEX(cp[0]) && JS7_ISHEX(cp[1]) &&
1239 JS7_ISHEX(cp[2]) && JS7_ISHEX(cp[3])) {
1240 c = (((((JS7_UNHEX(cp[0]) << 4)
1241 + JS7_UNHEX(cp[1])) << 4)
1242 + JS7_UNHEX(cp[2])) << 4)
1246 } else if (c == 'x') {
1248 if (peekChars(2, cp) &&
1249 JS7_ISHEX(cp[0]) && JS7_ISHEX(cp[1])) {
1250 c = (JS7_UNHEX(cp[0]) << 4) + JS7_UNHEX(cp[1]);
1253 } else if (c == '\n') {
1254 /* ECMA follows C by removing escaped newlines. */
1259 } else if (c == '\n' || c == '\r' || c == LINE_SEPARATOR || c == PARA_SEPARATOR ||
1262 ungetCharIgnoreEOL(c);
1263 ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR,
1264 JSMSG_UNTERMINATED_STRING);
1268 if (!tokenbuf.append(c))
1271 atom = atomize(cx, tokenbuf);
1274 tp->pos.end.lineno = lineno;
1275 tp->t_op = JSOP_STRING;
1282 * This handles everything else.
1286 case '\n': tt = TOK_EOL; goto eol_out;
1287 case ';': tt = TOK_SEMI; break;
1288 case '[': tt = TOK_LB; break;
1289 case ']': tt = TOK_RB; break;
1290 case '{': tt = TOK_LC; break;
1291 case '}': tt = TOK_RC; break;
1292 case '(': tt = TOK_LP; break;
1293 case ')': tt = TOK_RP; break;
1294 case ',': tt = TOK_COMMA; break;
1295 case '?': tt = TOK_HOOK; break;
1298 #if JS_HAS_XML_SUPPORT
1307 #if JS_HAS_XML_SUPPORT
1314 * Default so compiler can modify to JSOP_GETTER if 'p getter: v' in an
1315 * object initializer, likewise for setter.
1317 tp->t_op = JSOP_NOP;
1324 } else if (matchChar('=')) {
1325 tp->t_op = JSOP_BITOR;
1333 if (matchChar('=')) {
1334 tp->t_op = JSOP_BITXOR;
1344 } else if (matchChar('=')) {
1345 tp->t_op = JSOP_BITAND;
1354 tp->t_op = matchChar(c) ? JSOP_STRICTEQ : JSOP_EQ;
1357 tp->t_op = JSOP_NOP;
1363 if (matchChar('=')) {
1364 tp->t_op = matchChar('=') ? JSOP_STRICTNE : JSOP_NE;
1367 tp->t_op = JSOP_NOT;
1372 #if JS_HAS_XML_SUPPORT
1379 #if JS_HAS_XML_SUPPORT
1381 * After much testing, it's clear that Postel's advice to protocol
1382 * designers ("be liberal in what you accept, and conservative in what
1383 * you send") invites a natural-law repercussion for JS as "protocol":
1385 * "If you are liberal in what you accept, others will utterly fail to
1386 * be conservative in what they send."
1388 * Which means you will get <!-- comments to end of line in the middle
1389 * of .js files, and after if conditions whose then statements are on
1390 * the next line, and other wonders. See at least the following bugs:
1391 * https://bugzilla.mozilla.org/show_bug.cgi?id=309242
1392 * https://bugzilla.mozilla.org/show_bug.cgi?id=309712
1393 * https://bugzilla.mozilla.org/show_bug.cgi?id=310993
1395 * So without JSOPTION_XML, we changed around Firefox 1.5 never to scan
1396 * an XML comment or CDATA literal. Instead, we always scan <! as the
1397 * start of an HTML comment hack to end of line, used since Netscape 2
1398 * to hide script tag content from script-unaware browsers.
1400 * But this still leaves XML resources with certain internal structure
1401 * vulnerable to being loaded as script cross-origin, and some internal
1402 * data stolen, so for Firefox 3.5 and beyond, we reject programs whose
1403 * source consists only of XML literals. See:
1405 * https://bugzilla.mozilla.org/show_bug.cgi?id=336551
1407 * The check for this is in jsparse.cpp, Compiler::compileScript.
1409 if ((flags & TSF_OPERAND) && (hasXML() || peekChar() != '!')) {
1410 /* Check for XML comment or CDATA section. */
1411 if (matchChar('!')) {
1414 /* Scan XML comment. */
1415 if (matchChar('-')) {
1416 if (!matchChar('-'))
1417 goto bad_xml_markup;
1418 while ((c = getChar()) != '-' || !matchChar('-')) {
1420 goto bad_xml_markup;
1421 if (!tokenbuf.append(c))
1424 tt = TOK_XMLCOMMENT;
1425 tp->t_op = JSOP_XMLCOMMENT;
1426 goto finish_xml_markup;
1429 /* Scan CDATA section. */
1430 if (matchChar('[')) {
1432 if (peekChars(6, cp) &&
1440 while ((c = getChar()) != ']' ||
1441 !peekChars(2, cp) ||
1445 goto bad_xml_markup;
1446 if (!tokenbuf.append(c))
1449 getChar(); /* discard ] but not > */
1451 tp->t_op = JSOP_XMLCDATA;
1452 goto finish_xml_markup;
1454 goto bad_xml_markup;
1458 /* Check for processing instruction. */
1459 if (matchChar('?')) {
1465 while ((c = getChar()) != '?' || peekChar() != '>') {
1467 goto bad_xml_markup;
1469 if (JS_ISXMLSPACE(c)) {
1470 if (tokenbuf.empty())
1471 goto bad_xml_markup;
1472 inTarget = JS_FALSE;
1474 if (!(tokenbuf.empty()
1475 ? JS_ISXMLNSSTART(c)
1477 goto bad_xml_markup;
1482 if (contentIndex < 0 && !JS_ISXMLSPACE(c))
1483 contentIndex = tokenbuf.length();
1485 if (!tokenbuf.append(c))
1488 if (targetLength == 0)
1489 goto bad_xml_markup;
1490 if (contentIndex < 0) {
1491 atom = cx->runtime->atomState.emptyAtom;
1493 atom = js_AtomizeChars(cx, tokenbuf.begin() + contentIndex,
1494 tokenbuf.length() - contentIndex, 0);
1498 tokenbuf.shrinkBy(tokenbuf.length() - targetLength);
1503 if (!matchChar('>'))
1504 goto bad_xml_markup;
1505 atom = atomize(cx, tokenbuf);
1509 tp->pos.end.lineno = lineno;
1513 /* An XML start-of-tag character. */
1514 tt = matchChar('/') ? TOK_XMLETAGO : TOK_XMLSTAGO;
1518 ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR, JSMSG_BAD_XML_MARKUP);
1521 #endif /* JS_HAS_XML_SUPPORT */
1523 /* NB: treat HTML begin-comment as comment-till-end-of-line */
1524 if (matchChar('!')) {
1525 if (matchChar('-')) {
1526 if (matchChar('-')) {
1527 flags |= TSF_IN_HTML_COMMENT;
1535 tp->t_op = JSOP_LSH;
1536 tt = matchChar('=') ? TOK_ASSIGN : TOK_SHOP;
1538 tp->t_op = matchChar('=') ? JSOP_LE : JSOP_LT;
1545 tp->t_op = matchChar(c) ? JSOP_URSH : JSOP_RSH;
1546 tt = matchChar('=') ? TOK_ASSIGN : TOK_SHOP;
1548 tp->t_op = matchChar('=') ? JSOP_GE : JSOP_GT;
1554 tp->t_op = JSOP_MUL;
1555 tt = matchChar('=') ? TOK_ASSIGN : TOK_STAR;
1559 if (matchChar('/')) {
1561 * Hack for source filters such as the Mozilla XUL preprocessor:
1562 * "//@line 123\n" sets the number of the *next* line after the
1565 if (cx->hasAtLineOption()) {
1567 uintN i, line, temp;
1568 char filenameBuf[1024];
1570 if (peekChars(5, cp) &&
1577 while ((c = getChar()) != '\n' && ScanAsSpace((jschar)c))
1580 line = JS7_UNDEC(c);
1581 while ((c = getChar()) != EOF && JS7_ISDEC(c)) {
1582 temp = 10 * line + JS7_UNDEC(c);
1584 /* Ignore overlarge line numbers. */
1589 while (c != '\n' && ScanAsSpace((jschar)c))
1593 while ((c = getChar()) != EOF && c != '"') {
1598 if ((c >> 8) != 0 || i >= sizeof filenameBuf - 1)
1600 filenameBuf[i++] = (char) c;
1603 while ((c = getChar()) != '\n' &&
1604 ScanAsSpace((jschar)c)) {
1609 filenameBuf[i] = '\0';
1610 if (c == EOF || c == '\n') {
1612 if (flags & TSF_OWNFILENAME)
1613 cx->free((void *) filename);
1614 filename = JS_strdup(cx, filenameBuf);
1617 flags |= TSF_OWNFILENAME;
1627 /* Optimize line skipping if we are not in an HTML comment. */
1628 if (flags & TSF_IN_HTML_COMMENT) {
1629 while ((c = getChar()) != EOF && c != '\n') {
1630 if (c == '-' && matchChar('-') && matchChar('>'))
1631 flags &= ~TSF_IN_HTML_COMMENT;
1634 while ((c = getChar()) != EOF && c != '\n')
1638 cursor = (cursor - 1) & ntokensMask;
1642 if (matchChar('*')) {
1643 uintN linenoBefore = lineno;
1644 while ((c = getChar()) != EOF &&
1645 !(c == '*' && matchChar('/'))) {
1646 /* Ignore all characters until comment close. */
1649 ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR,
1650 JSMSG_UNTERMINATED_COMMENT);
1653 if ((flags & TSF_NEWLINES) && linenoBefore != lineno) {
1654 flags &= ~TSF_DIRTYLINE;
1658 cursor = (cursor - 1) & ntokensMask;
1662 if (flags & TSF_OPERAND) {
1663 uintN reflags, length;
1664 JSBool inCharClass = JS_FALSE;
1670 if (!tokenbuf.append(c))
1673 } else if (c == '[') {
1674 inCharClass = JS_TRUE;
1675 } else if (c == ']') {
1676 inCharClass = JS_FALSE;
1677 } else if (c == '/' && !inCharClass) {
1678 /* For compat with IE, allow unescaped / in char classes. */
1681 if (c == '\n' || c == EOF) {
1683 ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR,
1684 JSMSG_UNTERMINATED_REGEXP);
1687 if (!tokenbuf.append(c))
1690 for (reflags = 0, length = tokenbuf.length() + 1; ; length++) {
1692 if (c == 'g' && !(reflags & JSREG_GLOB))
1693 reflags |= JSREG_GLOB;
1694 else if (c == 'i' && !(reflags & JSREG_FOLD))
1695 reflags |= JSREG_FOLD;
1696 else if (c == 'm' && !(reflags & JSREG_MULTILINE))
1697 reflags |= JSREG_MULTILINE;
1698 else if (c == 'y' && !(reflags & JSREG_STICKY))
1699 reflags |= JSREG_STICKY;
1706 char buf[2] = { '\0' };
1707 tp->pos.begin.index += length + 1;
1709 ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR, JSMSG_BAD_REGEXP_FLAG,
1714 tp->t_reflags = reflags;
1719 tp->t_op = JSOP_DIV;
1720 tt = matchChar('=') ? TOK_ASSIGN : TOK_DIVOP;
1724 tp->t_op = JSOP_MOD;
1725 tt = matchChar('=') ? TOK_ASSIGN : TOK_DIVOP;
1729 tp->t_op = JSOP_BITNOT;
1734 if (matchChar('=')) {
1735 tp->t_op = JSOP_ADD;
1737 } else if (matchChar(c)) {
1740 tp->t_op = JSOP_POS;
1746 if (matchChar('=')) {
1747 tp->t_op = JSOP_SUB;
1749 } else if (matchChar(c)) {
1750 if (peekChar() == '>' && !(flags & TSF_DIRTYLINE)) {
1751 flags &= ~TSF_IN_HTML_COMMENT;
1756 tp->t_op = JSOP_NEG;
1761 #if JS_HAS_SHARP_VARS
1767 if (!JS7_ISDEC(c)) {
1771 n = (uint32)JS7_UNDEC(c);
1776 n = 10 * n + JS7_UNDEC(c);
1777 if (n >= UINT16_LIMIT) {
1778 ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR, JSMSG_SHARPVAR_TOO_BIG);
1782 tp->t_dval = (jsdouble) n;
1783 if (cx->hasStrictOption() &&
1784 (c == '=' || c == '#')) {
1786 JS_snprintf(buf, sizeof buf, "#%u%c", n, c);
1787 if (!ReportCompileErrorNumber(cx, this, NULL, JSREPORT_WARNING | JSREPORT_STRICT,
1788 JSMSG_DEPRECATED_USAGE, buf)) {
1800 #endif /* JS_HAS_SHARP_VARS */
1802 #if JS_HAS_SHARP_VARS || JS_HAS_XML_SUPPORT
1807 ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR, JSMSG_ILLEGAL_CHARACTER);
1812 JS_ASSERT(tt != TOK_EOL);
1813 flags |= TSF_DIRTYLINE;
1816 JS_ASSERT(tt < TOK_LIMIT);
1817 tp->pos.end.index = userbuf.ptr - linebase;