private static void AssertNextToken(nsCSSScanner lex, nsCSSTokenType type, Func<nsCSSToken, bool> condition) { var token = new nsCSSToken(); Assert.IsTrue(lex.Next(token, true), "Unexpected EOF"); Assert.AreEqual(type, token.mType); Assert.IsTrue(condition(token), "Condition for token {0} failed".Fmt(token.mType)); }
/** * Scan a Hash token. Handles the distinction between nsCSSTokenType.ID * and nsCSSTokenType.Hash, and handles production of Symbol when a '#' * is not followed by identifier characters. */ internal bool ScanHash(nsCSSToken aToken) { Debug.Assert(Peek() == '#', "should not have been called"); // Fall back for when '#' isn't followed by identifier characters. aToken.mSymbol = '#'; Advance(); int32_t ch = Peek(); if (IsIdentChar(ch) || ch == '\\') { nsCSSTokenType type = StartsIdent(ch, Peek(1)) ? nsCSSTokenType.ID : nsCSSTokenType.Hash; aToken.mIdent.SetLength(0); if (GatherText(IS_IDCHAR, aToken.mIdent)) { aToken.mType = type; } } return(true); }
internal bool IsLegacyGradientLine(nsCSSTokenType aType, string aId) { // N.B. ParseBoxPositionValues is not guaranteed to put back // everything it scanned if it fails, so we must only call it // if there is no alternative to consuming a <box-position>. // ParseVariant, as used here, will either succeed and consume // a single token, or fail and consume none, so we can be more // cavalier about calling it. bool haveGradientLine = false; switch (aType) { case nsCSSTokenType.Percentage: case nsCSSTokenType.Number: case nsCSSTokenType.Dimension: haveGradientLine = true; break; case nsCSSTokenType.Function: if (aId.LowerCaseEqualsLiteral("calc") || aId.LowerCaseEqualsLiteral("-moz-calc")) { haveGradientLine = true; break; } // fall through goto case nsCSSTokenType.ID; case nsCSSTokenType.ID: case nsCSSTokenType.Hash: // this is a color break; case nsCSSTokenType.Ident: { // This is only a gradient line if it's a box position keyword. nsCSSKeyword kw = nsCSSKeywords.LookupKeyword(aId); int32_t junk = 0; if (kw != nsCSSKeyword.UNKNOWN && nsCSSProps.FindKeyword(kw, nsCSSProps.kBackgroundPositionKTable, ref junk)) { haveGradientLine = true; } break; } goto default; default: // error break; } return haveGradientLine; }
/** * Scan a Number, Percentage, or Dimension token (all of which begin * like a Number). Can produce a Symbol when a '.' is not followed by * digits, or when '+' or '-' are not followed by either a digit or a * '.' and then a digit. Can also produce a HTMLComment when it * encounters '-.'. */ internal bool ScanNumber(nsCSSToken aToken) { int32_t c = Peek(); #if DEBUG { int32_t c2 = Peek(1); int32_t c3 = Peek(2); Debug.Assert(IsDigit(c) || (IsDigit(c2) && (c == '.' || c == '+' || c == '-')) || (IsDigit(c3) && (c == '+' || c == '-') && c2 == '.'), "should not have been called"); } #endif // Sign of the mantissa (-1 or 1). int32_t sign = c == '-' ? -1 : 1; // Absolute value of the integer part of the mantissa. This is a double so // we don't run into overflow issues for consumers that only care about our // floating-point value while still being able to express the full int32_t // range for consumers who want integers. double intPart = 0; // Fractional part of the mantissa. This is a double so that when we convert // to float at the end we'll end up rounding to nearest float instead of // truncating down (as we would if fracPart were a float and we just // effectively lost the last several digits). double fracPart = 0; // Absolute value of the Math.Power of 10 that we should multiply by (only // relevant for numbers in scientific notation). Has to be a signed integer, // because multiplication of signed by unsigned converts the unsigned to // signed, so if we plan to actually multiply by expSign... int32_t exponent = 0; // Sign of the exponent. int32_t expSign = 1; aToken.mHasSign = (c == '+' || c == '-'); if (aToken.mHasSign) { Advance(); c = Peek(); } bool gotDot = (c == '.'); if (!gotDot) { // Scan the integer part of the mantissa. Debug.Assert(IsDigit(c), "should have been excluded by logic above"); do { intPart = 10 * intPart + DecimalDigitValue(c); Advance(); c = Peek(); } while (IsDigit(c)); gotDot = (c == '.') && IsDigit(Peek(1)); } if (gotDot) { // Scan the fractional part of the mantissa. Advance(); c = Peek(); Debug.Assert(IsDigit(c), "should have been excluded by logic above"); // Power of ten by which we need to divide our next digit double divisor = 10; do { fracPart += DecimalDigitValue(c) / divisor; divisor *= 10; Advance(); c = Peek(); } while (IsDigit(c)); } bool gotE = false; if (IsSVGMode() && (c == 'e' || c == 'E')) { int32_t expSignChar = Peek(1); int32_t nextChar = Peek(2); if (IsDigit(expSignChar) || ((expSignChar == '-' || expSignChar == '+') && IsDigit(nextChar))) { gotE = true; if (expSignChar == '-') { expSign = -1; } Advance(); // consumes the E if (expSignChar == '-' || expSignChar == '+') { Advance(); c = nextChar; } else { c = expSignChar; } Debug.Assert(IsDigit(c), "should have been excluded by logic above"); do { exponent = 10 * exponent + DecimalDigitValue(c); Advance(); c = Peek(); } while (IsDigit(c)); } } nsCSSTokenType type = nsCSSTokenType.Number; // Set mIntegerValid for all cases (except %, below) because we need // it for the "2n" in :nth-child(2n). aToken.mIntegerValid = false; // Time to reassemble our number. // Do all the math in double precision so it's truncated only once. double value = sign * (intPart + fracPart); if (gotE) { // Explicitly cast expSign*exponent to double to avoid issues with // overloaded Math.Pow() on Windows. value *= Math.Pow(10.0, ((double)(expSign * exponent))); } else if (!gotDot) { // Clamp values outside of integer range. if (sign > 0) { aToken.mInteger = ((int32_t)(Math.Min(intPart, ((double)(Int32.MaxValue))))); } else { aToken.mInteger = ((int32_t)(Math.Max(-intPart, ((double)(Int32.MinValue))))); } aToken.mIntegerValid = true; } StringBuilder ident = aToken.mIdent; // Check for Dimension and Percentage tokens. if (c >= 0) { if (StartsIdent(c, Peek(1))) { if (GatherText(IS_IDCHAR, ident)) { type = nsCSSTokenType.Dimension; } } else if (c == '%') { Advance(); type = nsCSSTokenType.Percentage; value = value / 100.0f; aToken.mIntegerValid = false; } } aToken.mNumber = (float)value; aToken.mType = type; return(true); }
/** * Primary scanner entry point. Consume one token and fill in * |aToken| accordingly. Will skip over any number of comments first, * and will also skip over rather than return whitespace tokens if * |aSkipWS| is true. * * Returns true if it successfully consumed a token, false if EOF has * been reached. Will always advance the current read position by at * least one character unless called when already at EOF. */ internal bool Next(nsCSSToken aToken, bool aSkipWS) { int32_t ch; // do this here so we don't have to do it in dozens of other places aToken.mIdent.Truncate(); aToken.mType = nsCSSTokenType.Symbol; for (;;) { // Consume any number of comments, and possibly also whitespace tokens, // in between other tokens. mTokenOffset = mOffset; mTokenLineOffset = mLineOffset; mTokenLineNumber = mLineNumber; ch = Peek(); if (IsWhitespace(ch)) { SkipWhitespace(); if (!aSkipWS) { aToken.mType = nsCSSTokenType.Whitespace; return(true); } continue; // start again at the beginning } if (ch == '/' && !IsSVGMode() && Peek(1) == '*') { // FIXME: Editor wants comments to be preserved (bug 60290). SkipComment(); continue; // start again at the beginning } break; } // EOF if (ch < 0) { return(false); } // 'u' could be UNICODE-RANGE or an identifier-family token if (ch == 'u' || ch == 'U') { int32_t c2 = Peek(1); int32_t c3 = Peek(2); if (c2 == '+' && (IsHexDigit(c3) || c3 == '?')) { return(ScanURange(aToken)); } return(ScanIdent(aToken)); } // identifier family if (IsIdentStart(ch)) { return(ScanIdent(aToken)); } // number family if (IsDigit(ch)) { return(ScanNumber(aToken)); } if (ch == '.' && IsDigit(Peek(1))) { return(ScanNumber(aToken)); } if (ch == '+') { int32_t c2 = Peek(1); if (IsDigit(c2) || (c2 == '.' && IsDigit(Peek(2)))) { return(ScanNumber(aToken)); } } // '-' can start an identifier-family token, a number-family token, // or an HTML-comment if (ch == '-') { int32_t c2 = Peek(1); int32_t c3 = Peek(2); if (IsIdentStart(c2)) { return(ScanIdent(aToken)); } if (IsDigit(c2) || (c2 == '.' && IsDigit(c3))) { return(ScanNumber(aToken)); } if (c2 == '-' && c3 == '>') { Advance(3); aToken.mType = nsCSSTokenType.HTMLComment; aToken.mIdent.AssignLiteral("-."); return(true); } } // the other HTML-comment token if (ch == '<' && Peek(1) == '!' && Peek(2) == '-' && Peek(3) == '-') { Advance(4); aToken.mType = nsCSSTokenType.HTMLComment; aToken.mIdent.AssignLiteral("<!--"); return(true); } // AT_KEYWORD if (ch == '@') { return(ScanAtKeyword(aToken)); } // HASH if (ch == '#') { return(ScanHash(aToken)); } // STRING if (ch == '"' || ch == '\'') { return(ScanString(aToken)); } // Match operators: ~= |= ^= $= *= nsCSSTokenType opType = MatchOperatorType(ch); if (opType != nsCSSTokenType.Symbol && Peek(1) == '=') { aToken.mType = opType; Advance(2); return(true); } // Otherwise, a symbol (DELIM). aToken.mSymbol = (char)ch; Advance(); return(true); }