/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set sw=4 ts=8 et tw=78: * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Communicator client code, released * March 31, 1998. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Nick Fitzgerald * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* * JS lexical scanner. */ #include /* first to avoid trouble on some systems */ #include #include #include #ifdef HAVE_MEMORY_H #include #endif #include #include #include #include "jstypes.h" #include "jsstdint.h" #include "jsutil.h" #include "jsprf.h" #include "jsapi.h" #include "jsatom.h" #include "jscntxt.h" #include "jsversion.h" #include "jsemit.h" #include "jsexn.h" #include "jsnum.h" #include "jsopcode.h" #include "jsparse.h" #include "jsscan.h" #include "jsscript.h" #include "vm/RegExpObject.h" #include "jsscriptinlines.h" #if JS_HAS_XML_SUPPORT #include "jsxml.h" #endif using namespace js; using namespace js::unicode; #define JS_KEYWORD(keyword, type, op, version) \ const char js_##keyword##_str[] = #keyword; #include "jskeyword.tbl" #undef JS_KEYWORD static const KeywordInfo keywords[] = { #define JS_KEYWORD(keyword, type, op, version) \ {js_##keyword##_str, type, op, version}, #include "jskeyword.tbl" #undef JS_KEYWORD }; namespace js { const KeywordInfo * FindKeyword(const jschar *s, size_t length) { JS_ASSERT(length != 0); register size_t i; const struct KeywordInfo *kw; const char *chars; #define JSKW_LENGTH() length #define JSKW_AT(column) s[column] #define JSKW_GOT_MATCH(index) i = (index); goto got_match; #define JSKW_TEST_GUESS(index) i = (index); goto test_guess; #define JSKW_NO_MATCH() goto no_match; #include "jsautokw.h" #undef JSKW_NO_MATCH #undef JSKW_TEST_GUESS #undef JSKW_GOT_MATCH #undef JSKW_AT #undef JSKW_LENGTH got_match: return &keywords[i]; test_guess: kw = &keywords[i]; chars = kw->chars; do { if (*s++ != (unsigned char)(*chars++)) goto no_match; } while (--length != 0); return kw; no_match: return NULL; } } // namespace js JSBool js_IsIdentifier(JSLinearString *str) { const jschar *chars = str->chars(); size_t length = str->length(); if (length == 0) return JS_FALSE; jschar c = *chars; if (!IsIdentifierStart(c)) return JS_FALSE; const jschar *end = chars + length; while (++chars != end) { c = *chars; if (!IsIdentifierPart(c)) return JS_FALSE; } return JS_TRUE; } #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable:4351) #endif /* Initialize members that aren't initialized in |init|. */ TokenStream::TokenStream(JSContext *cx) : cx(cx), tokens(), cursor(), lookahead(), flags(), listenerTSData(), tokenbuf(cx) {} #ifdef _MSC_VER #pragma warning(pop) #endif bool TokenStream::init(const jschar *base, size_t length, const char *fn, uintN ln, JSVersion v) { filename = fn; lineno = ln; version = v; xml = VersionHasXML(v); userbuf.init(base, length); linebase = base; prevLinebase = NULL; sourceMap = NULL; JSSourceHandler listener = cx->debugHooks->sourceHandler; void *listenerData = cx->debugHooks->sourceHandlerData; if (listener) listener(fn, ln, base, length, &listenerTSData, listenerData); /* * This table holds all the token kinds that satisfy these properties: * - A single char long. * - Cannot be a prefix of any longer token (eg. '+' is excluded because * '+=' is a valid token). * - Doesn't need tp->t_op set (eg. this excludes '~'). * * The few token kinds satisfying these properties cover roughly 35--45% * of the tokens seen in practice. * * Nb: oneCharTokens, maybeEOL and maybeStrSpecial could be static, but * initializing them this way is a bit easier. Don't worry, the time to * initialize them for each TokenStream is trivial. See bug 639420. */ memset(oneCharTokens, 0, sizeof(oneCharTokens)); oneCharTokens[unsigned(';')] = TOK_SEMI; oneCharTokens[unsigned(',')] = TOK_COMMA; oneCharTokens[unsigned('?')] = TOK_HOOK; oneCharTokens[unsigned('[')] = TOK_LB; oneCharTokens[unsigned(']')] = TOK_RB; oneCharTokens[unsigned('{')] = TOK_LC; oneCharTokens[unsigned('}')] = TOK_RC; oneCharTokens[unsigned('(')] = TOK_LP; oneCharTokens[unsigned(')')] = TOK_RP; /* See getChar() for an explanation of maybeEOL[]. */ memset(maybeEOL, 0, sizeof(maybeEOL)); maybeEOL[unsigned('\n')] = true; maybeEOL[unsigned('\r')] = true; maybeEOL[unsigned(LINE_SEPARATOR & 0xff)] = true; maybeEOL[unsigned(PARA_SEPARATOR & 0xff)] = true; /* See getTokenInternal() for an explanation of maybeStrSpecial[]. */ memset(maybeStrSpecial, 0, sizeof(maybeStrSpecial)); maybeStrSpecial[unsigned('"')] = true; maybeStrSpecial[unsigned('\'')] = true; maybeStrSpecial[unsigned('\\')] = true; maybeStrSpecial[unsigned('\n')] = true; maybeStrSpecial[unsigned('\r')] = true; maybeStrSpecial[unsigned(LINE_SEPARATOR & 0xff)] = true; maybeStrSpecial[unsigned(PARA_SEPARATOR & 0xff)] = true; maybeStrSpecial[unsigned(EOF & 0xff)] = true; /* * Set |ln| as the beginning line number of the ungot "current token", so * that js::Parser::statements (and potentially other such methods, in the * future) can create parse nodes with good source coordinates before they * explicitly get any tokens. * * Switching the parser/lexer so we always get the next token ahead of the * parser needing it (the so-called "pump-priming" model) might be a better * way to address the dependency from statements on the current token. */ tokens[0].pos.begin.lineno = tokens[0].pos.end.lineno = ln; return true; } TokenStream::~TokenStream() { if (flags & TSF_OWNFILENAME) cx->free_((void *) filename); if (sourceMap) cx->free_(sourceMap); } /* Use the fastest available getc. */ #if defined(HAVE_GETC_UNLOCKED) # define fast_getc getc_unlocked #elif defined(HAVE__GETC_NOLOCK) # define fast_getc _getc_nolock #else # define fast_getc getc #endif JS_FRIEND_API(int) js_fgets(char *buf, int size, FILE *file) { int n, i, c; JSBool crflag; n = size - 1; if (n < 0) return -1; crflag = JS_FALSE; for (i = 0; i < n && (c = fast_getc(file)) != EOF; i++) { buf[i] = c; if (c == '\n') { /* any \n ends a line */ i++; /* keep the \n; we know there is room for \0 */ break; } if (crflag) { /* \r not followed by \n ends line at the \r */ ungetc(c, file); break; /* and overwrite c in buf with \0 */ } crflag = (c == '\r'); } buf[i] = '\0'; return i; } JS_ALWAYS_INLINE void TokenStream::updateLineInfoForEOL() { prevLinebase = linebase; linebase = userbuf.addressOfNextRawChar(); lineno++; } JS_ALWAYS_INLINE void TokenStream::updateFlagsForEOL() { flags &= ~TSF_DIRTYLINE; flags |= TSF_EOL; } /* This gets the next char, normalizing all EOL sequences to '\n' as it goes. */ int32 TokenStream::getChar() { int32 c; if (JS_LIKELY(userbuf.hasRawChars())) { c = userbuf.getRawChar(); /* * Normalize the jschar if it was a newline. We need to detect any of * these four characters: '\n' (0x000a), '\r' (0x000d), * LINE_SEPARATOR (0x2028), PARA_SEPARATOR (0x2029). Testing for each * one in turn is slow, so we use a single probabilistic check, and if * that succeeds, test for them individually. * * We use the bottom 8 bits to index into a lookup table, succeeding * when d&0xff is 0xa, 0xd, 0x28 or 0x29. Among ASCII chars (which * are by the far the most common) this gives false positives for '(' * (0x0028) and ')' (0x0029). We could avoid those by incorporating * the 13th bit of d into the lookup, but that requires extra shifting * and masking and isn't worthwhile. See TokenStream::init() for the * initialization of the relevant entries in the table. */ if (JS_UNLIKELY(maybeEOL[c & 0xff])) { if (c == '\n') goto eol; if (c == '\r') { /* if it's a \r\n sequence: treat as a single EOL, skip over the \n */ if (userbuf.hasRawChars()) userbuf.matchRawChar('\n'); goto eol; } if (c == LINE_SEPARATOR || c == PARA_SEPARATOR) goto eol; } return c; } flags |= TSF_EOF; return EOF; eol: updateLineInfoForEOL(); return '\n'; } /* * This gets the next char. It does nothing special with EOL sequences, not * even updating the line counters. It can be used safely if (a) the * resulting char is guaranteed to be ungotten (by ungetCharIgnoreEOL()) if * it's an EOL, and (b) the line-related state (lineno, linebase) is not used * before it's ungotten. */ int32 TokenStream::getCharIgnoreEOL() { if (JS_LIKELY(userbuf.hasRawChars())) return userbuf.getRawChar(); flags |= TSF_EOF; return EOF; } void TokenStream::ungetChar(int32 c) { if (c == EOF) return; JS_ASSERT(!userbuf.atStart()); userbuf.ungetRawChar(); if (c == '\n') { #ifdef DEBUG int32 c2 = userbuf.peekRawChar(); JS_ASSERT(TokenBuf::isRawEOLChar(c2)); #endif /* if it's a \r\n sequence, also unget the \r */ if (!userbuf.atStart()) userbuf.matchRawCharBackwards('\r'); JS_ASSERT(prevLinebase); /* we should never get more than one EOL char */ linebase = prevLinebase; prevLinebase = NULL; lineno--; } else { JS_ASSERT(userbuf.peekRawChar() == c); } } void TokenStream::ungetCharIgnoreEOL(int32 c) { if (c == EOF) return; JS_ASSERT(!userbuf.atStart()); userbuf.ungetRawChar(); } /* * Return true iff |n| raw characters can be read from this without reading past * EOF or a newline, and copy those characters into |cp| if so. The characters * are not consumed: use skipChars(n) to do so after checking that the consumed * characters had appropriate values. */ bool TokenStream::peekChars(intN n, jschar *cp) { intN i, j; int32 c; for (i = 0; i < n; i++) { c = getCharIgnoreEOL(); if (c == EOF) break; if (c == '\n') { ungetCharIgnoreEOL(c); break; } cp[i] = (jschar)c; } for (j = i - 1; j >= 0; j--) ungetCharIgnoreEOL(cp[j]); return i == n; } const jschar * TokenStream::TokenBuf::findEOL() { const jschar *tmp = ptr; #ifdef DEBUG /* * This is the one exception to the "TokenBuf isn't accessed after * poisoning" rule -- we may end up calling findEOL() in order to set up * an error. */ if (!tmp) tmp = ptrWhenPoisoned; #endif while (true) { if (tmp >= limit) break; if (TokenBuf::isRawEOLChar(*tmp++)) break; } return tmp; } bool TokenStream::reportCompileErrorNumberVA(JSParseNode *pn, uintN flags, uintN errorNumber, va_list ap) { JSErrorReport report; char *message; jschar *linechars; char *linebytes; bool warning; JSBool ok; const TokenPos *tp; uintN i; if (JSREPORT_IS_STRICT(flags) && !cx->hasStrictOption()) return true; warning = JSREPORT_IS_WARNING(flags); if (warning && cx->hasWErrorOption()) { flags &= ~JSREPORT_WARNING; warning = false; } PodZero(&report); report.flags = flags; report.errorNumber = errorNumber; message = NULL; linechars = NULL; linebytes = NULL; MUST_FLOW_THROUGH("out"); ok = js_ExpandErrorArguments(cx, js_GetErrorMessage, NULL, errorNumber, &message, &report, !(flags & JSREPORT_UC), ap); if (!ok) { warning = false; goto out; } report.filename = filename; tp = pn ? &pn->pn_pos : ¤tToken().pos; report.lineno = tp->begin.lineno; /* * Given a token, T, that we want to complain about: if T's (starting) * lineno doesn't match TokenStream's lineno, that means we've scanned past * the line that T starts on, which makes it hard to print some or all of * T's (starting) line for context. * * So we don't even try, leaving report.linebuf and friends zeroed. This * means that any error involving a multi-line token (eg. an unterminated * multi-line string literal) won't have a context printed. */ if (report.lineno == lineno) { size_t linelength = userbuf.findEOL() - linebase; linechars = (jschar *)cx->malloc_((linelength + 1) * sizeof(jschar)); if (!linechars) { warning = false; goto out; } memcpy(linechars, linebase, linelength * sizeof(jschar)); linechars[linelength] = 0; linebytes = DeflateString(cx, linechars, linelength); if (!linebytes) { warning = false; goto out; } /* Unicode and char versions of the offending source line, without final \n */ report.linebuf = linebytes; report.uclinebuf = linechars; /* The lineno check above means we should only see single-line tokens here. */ JS_ASSERT(tp->begin.lineno == tp->end.lineno); report.tokenptr = report.linebuf + tp->begin.index; report.uctokenptr = report.uclinebuf + tp->begin.index; } /* * If there's a runtime exception type associated with this error * number, set that as the pending exception. For errors occuring at * compile time, this is very likely to be a JSEXN_SYNTAXERR. * * If an exception is thrown but not caught, the JSREPORT_EXCEPTION * flag will be set in report.flags. Proper behavior for an error * reporter is to ignore a report with this flag for all but top-level * compilation errors. The exception will remain pending, and so long * as the non-top-level "load", "eval", or "compile" native function * returns false, the top-level reporter will eventually receive the * uncaught exception report. */ if (!js_ErrorToException(cx, message, &report, NULL, NULL)) { /* * If debugErrorHook is present then we give it a chance to veto * sending the error on to the regular error reporter. */ bool reportError = true; if (JSDebugErrorHook hook = cx->debugHooks->debugErrorHook) reportError = hook(cx, message, &report, cx->debugHooks->debugErrorHookData); /* Report the error */ if (reportError && cx->errorReporter) cx->errorReporter(cx, message, &report); } out: if (linebytes) cx->free_(linebytes); if (linechars) cx->free_(linechars); if (message) cx->free_(message); if (report.ucmessage) cx->free_((void *)report.ucmessage); if (report.messageArgs) { if (!(flags & JSREPORT_UC)) { i = 0; while (report.messageArgs[i]) cx->free_((void *)report.messageArgs[i++]); } cx->free_((void *)report.messageArgs); } return warning; } bool js::ReportStrictModeError(JSContext *cx, TokenStream *ts, JSTreeContext *tc, JSParseNode *pn, uintN errorNumber, ...) { JS_ASSERT(ts || tc); JS_ASSERT(cx == ts->getContext()); /* In strict mode code, this is an error, not merely a warning. */ uintN flags; if ((ts && ts->isStrictMode()) || (tc && (tc->flags & TCF_STRICT_MODE_CODE))) { flags = JSREPORT_ERROR; } else { if (!cx->hasStrictOption()) return true; flags = JSREPORT_WARNING; } va_list ap; va_start(ap, errorNumber); bool result = ts->reportCompileErrorNumberVA(pn, flags, errorNumber, ap); va_end(ap); return result; } bool js::ReportCompileErrorNumber(JSContext *cx, TokenStream *ts, JSParseNode *pn, uintN flags, uintN errorNumber, ...) { va_list ap; /* * We don't accept a JSTreeContext argument, so we can't implement * JSREPORT_STRICT_MODE_ERROR here. Use ReportStrictModeError instead, * or do the checks in the caller and pass plain old JSREPORT_ERROR. */ JS_ASSERT(!(flags & JSREPORT_STRICT_MODE_ERROR)); va_start(ap, errorNumber); JS_ASSERT(cx == ts->getContext()); bool result = ts->reportCompileErrorNumberVA(pn, flags, errorNumber, ap); va_end(ap); return result; } #if JS_HAS_XML_SUPPORT bool TokenStream::getXMLEntity() { ptrdiff_t offset, length, i; int c, d; JSBool ispair; jschar *bp, digit; char *bytes; JSErrNum msg; CharBuffer &tb = tokenbuf; /* Put the entity, including the '&' already scanned, in tokenbuf. */ offset = tb.length(); if (!tb.append('&')) return false; while ((c = getChar()) != ';') { if (c == EOF || c == '\n') { ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR, JSMSG_END_OF_XML_ENTITY); return false; } if (!tb.append(c)) return false; } /* Let length be the number of jschars after the '&', including the ';'. */ length = tb.length() - offset; bp = tb.begin() + offset; c = d = 0; ispair = false; if (length > 2 && bp[1] == '#') { /* Match a well-formed XML Character Reference. */ i = 2; if (length > 3 && (bp[i] == 'x' || bp[i] == 'X')) { if (length > 9) /* at most 6 hex digits allowed */ goto badncr; while (++i < length) { digit = bp[i]; if (!JS7_ISHEX(digit)) goto badncr; c = (c << 4) + JS7_UNHEX(digit); } } else { while (i < length) { digit = bp[i++]; if (!JS7_ISDEC(digit)) goto badncr; c = (c * 10) + JS7_UNDEC(digit); if (c < 0) goto badncr; } } if (0x10000 <= c && c <= 0x10FFFF) { /* Form a surrogate pair (c, d) -- c is the high surrogate. */ d = 0xDC00 + (c & 0x3FF); c = 0xD7C0 + (c >> 10); ispair = true; } else { /* Enforce the http://www.w3.org/TR/REC-xml/#wf-Legalchar WFC. */ if (c != 0x9 && c != 0xA && c != 0xD && !(0x20 <= c && c <= 0xD7FF) && !(0xE000 <= c && c <= 0xFFFD)) { goto badncr; } } } else { /* Try to match one of the five XML 1.0 predefined entities. */ switch (length) { case 3: if (bp[2] == 't') { if (bp[1] == 'l') c = '<'; else if (bp[1] == 'g') c = '>'; } break; case 4: if (bp[1] == 'a' && bp[2] == 'm' && bp[3] == 'p') c = '&'; break; case 5: if (bp[3] == 'o') { if (bp[1] == 'a' && bp[2] == 'p' && bp[4] == 's') c = '\''; else if (bp[1] == 'q' && bp[2] == 'u' && bp[4] == 't') c = '"'; } break; } if (c == 0) { msg = JSMSG_UNKNOWN_XML_ENTITY; goto bad; } } /* If we matched, retract tokenbuf and store the entity's value. */ *bp++ = (jschar) c; if (ispair) *bp++ = (jschar) d; tb.shrinkBy(tb.end() - bp); return true; badncr: msg = JSMSG_BAD_XML_NCR; bad: /* No match: throw a TypeError per ECMA-357 10.3.2.1 step 8(a). */ JS_ASSERT((tb.end() - bp) >= 1); bytes = DeflateString(cx, bp + 1, (tb.end() - bp) - 1); if (bytes) { ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR, msg, bytes); cx->free_(bytes); } return false; } bool TokenStream::getXMLTextOrTag(TokenKind *ttp, Token **tpp) { TokenKind tt; int c, qc; Token *tp; JSAtom *atom; /* * Look for XML text. */ if (flags & TSF_XMLTEXTMODE) { tt = TOK_XMLSPACE; /* veto if non-space, return TOK_XMLTEXT */ tp = newToken(0); tokenbuf.clear(); qc = (flags & TSF_XMLONLYMODE) ? '<' : '{'; while ((c = getChar()) != qc && c != '<' && c != EOF) { if (c == '&' && qc == '<') { if (!getXMLEntity()) goto error; tt = TOK_XMLTEXT; continue; } if (!IsXMLSpace(c)) tt = TOK_XMLTEXT; if (!tokenbuf.append(c)) goto error; } ungetChar(c); if (tokenbuf.empty()) { atom = NULL; } else { atom = atomize(cx, tokenbuf); if (!atom) goto error; } tp->pos.end.lineno = lineno; tp->t_op = JSOP_STRING; tp->t_atom = atom; goto out; } /* * XML tags. */ else { JS_ASSERT(flags & TSF_XMLTAGMODE); tp = newToken(0); c = getChar(); if (c != EOF && IsXMLSpace(c)) { do { c = getChar(); if (c == EOF) break; } while (IsXMLSpace(c)); ungetChar(c); tp->pos.end.lineno = lineno; tt = TOK_XMLSPACE; goto out; } if (c == EOF) { tt = TOK_EOF; goto out; } tokenbuf.clear(); if (IsXMLNamespaceStart(c)) { JSBool sawColon = JS_FALSE; if (!tokenbuf.append(c)) goto error; while ((c = getChar()) != EOF && IsXMLNamePart(c)) { if (c == ':') { int nextc; if (sawColon || (nextc = peekChar(), ((flags & TSF_XMLONLYMODE) || nextc != '{') && !IsXMLNamePart(nextc))) { ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR, JSMSG_BAD_XML_QNAME); goto error; } sawColon = JS_TRUE; } if (!tokenbuf.append(c)) goto error; } ungetChar(c); atom = atomize(cx, tokenbuf); if (!atom) goto error; tp->t_op = JSOP_STRING; tp->t_atom = atom; tt = TOK_XMLNAME; goto out; } switch (c) { case '{': if (flags & TSF_XMLONLYMODE) goto bad_xml_char; tt = TOK_LC; goto out; case '=': tt = TOK_ASSIGN; goto out; case '"': case '\'': qc = c; while ((c = getChar()) != qc) { if (c == EOF) { ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR, JSMSG_UNTERMINATED_STRING); goto error; } /* * XML attribute values are double-quoted when pretty-printed, * so escape " if it is expressed directly in a single-quoted * attribute value. */ if (c == '"' && !(flags & TSF_XMLONLYMODE)) { JS_ASSERT(qc == '\''); if (!tokenbuf.append(js_quot_entity_str, strlen(js_quot_entity_str))) goto error; continue; } if (c == '&' && (flags & TSF_XMLONLYMODE)) { if (!getXMLEntity()) goto error; continue; } if (!tokenbuf.append(c)) goto error; } atom = atomize(cx, tokenbuf); if (!atom) goto error; tp->pos.end.lineno = lineno; tp->t_op = JSOP_STRING; tp->t_atom = atom; tt = TOK_XMLATTR; goto out; case '>': tt = TOK_XMLTAGC; goto out; case '/': if (matchChar('>')) { tt = TOK_XMLPTAGC; goto out; } /* FALL THROUGH */ bad_xml_char: default: ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR, JSMSG_BAD_XML_CHARACTER); goto error; } JS_NOT_REACHED("getXMLTextOrTag 1"); } JS_NOT_REACHED("getXMLTextOrTag 2"); out: *ttp = tt; *tpp = tp; return true; error: *ttp = TOK_ERROR; *tpp = tp; return false; } /* * After much testing, it's clear that Postel's advice to protocol designers * ("be liberal in what you accept, and conservative in what you send") invites * a natural-law repercussion for JS as "protocol": * * "If you are liberal in what you accept, others will utterly fail to be * conservative in what they send." * * Which means you will get