TextRun SVGInlineTextBox::constructTextRun(RenderStyle* style, const SVGTextFragment& fragment) const { ASSERT(style); TextRun run(StringView(renderer().text()).substring(fragment.characterOffset, fragment.length) , 0 /* xPos, only relevant with allowTabs=true */ , 0 /* padding, only relevant for justified text, not relevant for SVG */ , AllowTrailingExpansion , direction() , dirOverride() || style->rtlOrdering() == VisualOrder /* directionalOverride */); // We handle letter & word spacing ourselves. run.disableSpacing(); // Propagate the maximum length of the characters buffer to the TextRun, even when we're only processing a substring. run.setCharactersLength(renderer().textLength() - fragment.characterOffset); ASSERT(run.charactersLength() >= run.length()); return run; }
inline SearchBuffer::SearchBuffer(const String& target, FindOptions options) : m_options(options), m_prefixLength(0), m_numberOfCharactersJustAppended(0), m_atBreak(true), m_needsMoreContext(options & AtWordStarts), m_targetRequiresKanaWorkaround(containsKanaLetters(target)) { DCHECK(!target.isEmpty()) << target; target.appendTo(m_target); // FIXME: We'd like to tailor the searcher to fold quote marks for us instead // of doing it in a separate replacement pass here, but ICU doesn't offer a // way to add tailoring on top of the locale-specific tailoring as of this // writing. foldQuoteMarksAndSoftHyphens(m_target.data(), m_target.size()); size_t targetLength = m_target.size(); m_buffer.reserveInitialCapacity( std::max(targetLength * 8, kMinimumSearchBufferSize)); m_overlap = m_buffer.capacity() / 4; if ((m_options & AtWordStarts) && targetLength) { const UChar32 targetFirstCharacter = getCodePointAt(m_target.data(), 0, targetLength); // Characters in the separator category never really occur at the beginning // of a word, so if the target begins with such a character, we just ignore // the AtWordStart option. if (isSeparator(targetFirstCharacter)) { m_options &= ~AtWordStarts; m_needsMoreContext = false; } } m_textSearcher = WTF::makeUnique<TextSearcherICU>(); m_textSearcher->setPattern(StringView(m_target.data(), m_target.size()), !(m_options & CaseInsensitive)); // The kana workaround requires a normalized copy of the target string. if (m_targetRequiresKanaWorkaround) normalizeCharactersIntoNFCForm(m_target.data(), m_target.size(), m_normalizedTarget); }
Vector<TextCheckingResult> TextChecker::checkTextOfParagraph(int64_t spellDocumentTag, const UChar* text, int length, uint64_t checkingTypes) { Vector<TextCheckingResult> paragraphCheckingResult; #if ENABLE(SPELLCHECK) if (checkingTypes & TextCheckingTypeSpelling) { TextBreakIterator* textIterator = wordBreakIterator(StringView(text, length)); if (!textIterator) return paragraphCheckingResult; // Omit the word separators at the beginning/end of the text to don't unnecessarily // involve the client to check spelling for them. int offset = nextWordOffset(text, length, 0); int lengthStrip = length; while (lengthStrip > 0 && isTextBreak(textIterator, lengthStrip - 1)) --lengthStrip; while (offset >= 0 && offset < lengthStrip) { int32_t misspellingLocation = -1; int32_t misspellingLength = 0; checkSpellingOfString(spellDocumentTag, text + offset, lengthStrip - offset, misspellingLocation, misspellingLength); if (!misspellingLength) break; TextCheckingResult misspellingResult; misspellingResult.type = TextCheckingTypeSpelling; misspellingResult.location = offset + misspellingLocation; misspellingResult.length = misspellingLength; paragraphCheckingResult.append(misspellingResult); offset += misspellingLocation + misspellingLength; // Generally, we end up checking at the word separator, move to the adjacent word. offset = nextWordOffset(text, lengthStrip, offset); } } #else UNUSED_PARAM(spellDocumentTag); UNUSED_PARAM(text); UNUSED_PARAM(length); UNUSED_PARAM(checkingTypes); #endif return paragraphCheckingResult; }
ExceptionOr<void> SVGLengthValue::setValueAsString(const String& string) { if (string.isEmpty()) return { }; float convertedNumber = 0; auto upconvertedCharacters = StringView(string).upconvertedCharacters(); const UChar* ptr = upconvertedCharacters; const UChar* end = ptr + string.length(); if (!parseNumber(ptr, end, convertedNumber, false)) return Exception { SYNTAX_ERR }; auto type = parseLengthType(ptr, end); if (type == LengthTypeUnknown) return Exception { SYNTAX_ERR }; m_unit = storeUnit(extractMode(m_unit), type); m_valueInSpecifiedUnits = convertedNumber; return { }; }
ExceptionOr<void> SVGAngleValue::setValueAsString(const String& value) { if (value.isEmpty()) { m_unitType = SVG_ANGLETYPE_UNSPECIFIED; return { }; } auto upconvertedCharacters = StringView(value).upconvertedCharacters(); const UChar* ptr = upconvertedCharacters; const UChar* end = ptr + value.length(); float valueInSpecifiedUnits = 0; if (!parseNumber(ptr, end, valueInSpecifiedUnits, false)) return Exception { SYNTAX_ERR }; auto unitType = parseAngleType(ptr, end); if (unitType == SVG_ANGLETYPE_UNKNOWN) return Exception { SYNTAX_ERR }; m_unitType = unitType; m_valueInSpecifiedUnits = valueInSpecifiedUnits; return { }; }
void SVGLengthListValues::parse(const String& value, SVGLengthMode mode) { clear(); auto upconvertedCharacters = StringView(value).upconvertedCharacters(); const UChar* ptr = upconvertedCharacters; const UChar* end = ptr + value.length(); while (ptr < end) { const UChar* start = ptr; while (ptr < end && *ptr != ',' && !isSVGSpace(*ptr)) ptr++; if (ptr == start) break; SVGLengthValue length(mode); String valueString(start, ptr - start); if (valueString.isEmpty()) return; if (length.setValueAsString(valueString).hasException()) return; append(length); skipOptionalSVGSpacesOrDelimiter(ptr, end); } }
static bool parsePoint(const String& s, FloatPoint& point) { if (s.isEmpty()) return false; auto upconvertedCharacters = StringView(s).upconvertedCharacters(); const UChar* cur = upconvertedCharacters; const UChar* end = cur + s.length(); if (!skipOptionalSVGSpaces(cur, end)) return false; float x = 0; if (!parseNumber(cur, end, x)) return false; float y = 0; if (!parseNumber(cur, end, y)) return false; point = FloatPoint(x, y); // disallow anything except spaces at the end return !skipOptionalSVGSpaces(cur, end); }
bool UrlView::parse(const StringView& _url) { clear(); const char* start = _url.getPtr(); const char* term = _url.getTerm(); const char* schemeEnd = strFind(StringView(start, term), "://"); const char* hostStart = NULL != schemeEnd ? schemeEnd+3 : start; const char* pathStart = strFind(StringView(hostStart, term), '/'); if (NULL == schemeEnd && NULL == pathStart) { return false; } if (NULL != schemeEnd && (NULL == pathStart || pathStart > schemeEnd) ) { StringView scheme(start, schemeEnd); if (!isAlpha(scheme) ) { return false; } m_tokens[Scheme].set(scheme); } if (NULL != pathStart) { const char* queryStart = strFind(StringView(pathStart, term), '?'); const char* fragmentStart = strFind(StringView(pathStart, term), '#'); if (NULL != fragmentStart && fragmentStart < queryStart) { return false; } m_tokens[Path].set(pathStart , NULL != queryStart ? queryStart : NULL != fragmentStart ? fragmentStart : term ); if (NULL != queryStart) { m_tokens[Query].set(queryStart+1 , NULL != fragmentStart ? fragmentStart : term ); } if (NULL != fragmentStart) { m_tokens[Fragment].set(fragmentStart+1, term); } term = pathStart; } const char* userPassEnd = strFind(StringView(hostStart, term), '@'); const char* userPassStart = NULL != userPassEnd ? hostStart : NULL; hostStart = NULL != userPassEnd ? userPassEnd+1 : hostStart; const char* portStart = strFind(StringView(hostStart, term), ':'); m_tokens[Host].set(hostStart, NULL != portStart ? portStart : term); if (NULL != portStart) { m_tokens[Port].set(portStart+1, term); } if (NULL != userPassStart) { const char* passStart = strFind(StringView(userPassStart, userPassEnd), ':'); m_tokens[UserName].set(userPassStart , NULL != passStart ? passStart : userPassEnd ); if (NULL != passStart) { m_tokens[Password].set(passStart+1, userPassEnd); } } return true; }
void SVGPreserveAspectRatio::parse(const String& value) { auto upconvertedCharacters = StringView(value).upconvertedCharacters(); const UChar* begin = upconvertedCharacters; parseInternal(begin, begin + value.length(), true); }
StringView StringView::slice(Size startIndex, Size sliceSize) const { assert(data != nullptr || size == 0); assert(startIndex <= size && startIndex + sliceSize <= size); return StringView(data + startIndex, sliceSize); }
void DesktopShellSettings::setKeymap(const char *layout) { for (Seat *seat: m_shell->compositor()->seats()) { seat->setKeymap(Keymap(StringView(layout), Maybe<StringView>(), Maybe<StringView>())); } }
bool parseManifest(const URL& manifestURL, const char* data, int length, Manifest& manifest) { ASSERT(manifest.explicitURLs.isEmpty()); ASSERT(manifest.onlineWhitelistedURLs.isEmpty()); ASSERT(manifest.fallbackURLs.isEmpty()); manifest.allowAllNetworkRequests = false; Mode mode = Explicit; String s = TextResourceDecoder::create("text/cache-manifest", "UTF-8")->decodeAndFlush(data, length); // Look for the magic signature: "^\xFEFF?CACHE MANIFEST[ \t]?" (the BOM is removed by TextResourceDecoder). // Example: "CACHE MANIFEST #comment" is a valid signature. // Example: "CACHE MANIFEST;V2" is not. if (!s.startsWith("CACHE MANIFEST")) return false; StringView manifestAfterSignature = StringView(s).substring(14); // "CACHE MANIFEST" is 14 characters. auto upconvertedCharacters = manifestAfterSignature.upconvertedCharacters(); const UChar* p = upconvertedCharacters; const UChar* end = p + manifestAfterSignature.length(); if (p < end && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') return false; // Skip to the end of the line. while (p < end && *p != '\r' && *p != '\n') p++; while (1) { // Skip whitespace while (p < end && (*p == '\n' || *p == '\r' || *p == ' ' || *p == '\t')) p++; if (p == end) break; const UChar* lineStart = p; // Find the end of the line while (p < end && *p != '\r' && *p != '\n') p++; // Check if we have a comment if (*lineStart == '#') continue; // Get rid of trailing whitespace const UChar* tmp = p - 1; while (tmp > lineStart && (*tmp == ' ' || *tmp == '\t')) tmp--; String line(lineStart, tmp - lineStart + 1); if (line == "CACHE:") mode = Explicit; else if (line == "FALLBACK:") mode = Fallback; else if (line == "NETWORK:") mode = OnlineWhitelist; else if (line.endsWith(':')) mode = Unknown; else if (mode == Unknown) continue; else if (mode == Explicit || mode == OnlineWhitelist) { auto upconvertedLineCharacters = StringView(line).upconvertedCharacters(); const UChar* p = upconvertedLineCharacters; const UChar* lineEnd = p + line.length(); // Look for whitespace separating the URL from subsequent ignored tokens. while (p < lineEnd && *p != '\t' && *p != ' ') p++; if (mode == OnlineWhitelist && p - upconvertedLineCharacters == 1 && line[0] == '*') { // Wildcard was found. manifest.allowAllNetworkRequests = true; continue; } URL url(manifestURL, line.substring(0, p - upconvertedLineCharacters)); if (!url.isValid()) continue; if (url.hasFragmentIdentifier()) url.removeFragmentIdentifier(); if (!equalIgnoringASCIICase(url.protocol(), manifestURL.protocol())) continue; if (mode == Explicit && manifestURL.protocolIs("https") && !protocolHostAndPortAreEqual(manifestURL, url)) continue; if (mode == Explicit) manifest.explicitURLs.add(url.string()); else manifest.onlineWhitelistedURLs.append(url); } else if (mode == Fallback) { auto upconvertedLineCharacters = StringView(line).upconvertedCharacters(); const UChar* p = upconvertedLineCharacters; const UChar* lineEnd = p + line.length(); // Look for whitespace separating the two URLs while (p < lineEnd && *p != '\t' && *p != ' ') p++; if (p == lineEnd) { // There was no whitespace separating the URLs. continue; } URL namespaceURL(manifestURL, line.substring(0, p - upconvertedLineCharacters)); if (!namespaceURL.isValid()) continue; if (namespaceURL.hasFragmentIdentifier()) namespaceURL.removeFragmentIdentifier(); if (!protocolHostAndPortAreEqual(manifestURL, namespaceURL)) continue; // Skip whitespace separating fallback namespace from URL. while (p < lineEnd && (*p == '\t' || *p == ' ')) p++; // Look for whitespace separating the URL from subsequent ignored tokens. const UChar* fallbackStart = p; while (p < lineEnd && *p != '\t' && *p != ' ') p++; URL fallbackURL(manifestURL, String(fallbackStart, p - fallbackStart)); if (!fallbackURL.isValid()) continue; if (fallbackURL.hasFragmentIdentifier()) fallbackURL.removeFragmentIdentifier(); if (!protocolHostAndPortAreEqual(manifestURL, fallbackURL)) continue; manifest.fallbackURLs.append(std::make_pair(namespaceURL, fallbackURL)); } else ASSERT_NOT_REACHED(); } return true; }
void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, RenderSVGInlineText* text, const RenderStyle* style) { if (m_inPathLayout && m_textPath.isEmpty()) return; RenderElement* textParent = text->parent(); ASSERT(textParent); SVGElement* lengthContext = downcast<SVGElement>(textParent->element()); bool definesTextLength = parentDefinesTextLength(textParent); const SVGRenderStyle& svgStyle = style->svgStyle(); m_visualMetricsListOffset = 0; m_visualCharacterOffset = 0; Vector<SVGTextMetrics>& visualMetricsValues = text->layoutAttributes()->textMetricsValues(); ASSERT(!visualMetricsValues.isEmpty()); auto upconvertedCharacters = StringView(text->text()).upconvertedCharacters(); const UChar* characters = upconvertedCharacters; const Font& font = style->font(); SVGTextLayoutEngineSpacing spacingLayout(font); SVGTextLayoutEngineBaseline baselineLayout(font); bool didStartTextFragment = false; bool applySpacingToNextCharacter = false; float lastAngle = 0; float baselineShift = baselineLayout.calculateBaselineShift(&svgStyle, lengthContext); baselineShift -= baselineLayout.calculateAlignmentBaselineShift(m_isVerticalText, text); // Main layout algorithm. while (true) { // Find the start of the current text box in this list, respecting ligatures. SVGTextMetrics visualMetrics(SVGTextMetrics::SkippedSpaceMetrics); if (!currentVisualCharacterMetrics(textBox, visualMetricsValues, visualMetrics)) break; if (visualMetrics.isEmpty()) { advanceToNextVisualCharacter(visualMetrics); continue; } SVGTextLayoutAttributes* logicalAttributes = 0; if (!currentLogicalCharacterAttributes(logicalAttributes)) break; ASSERT(logicalAttributes); SVGTextMetrics logicalMetrics(SVGTextMetrics::SkippedSpaceMetrics); if (!currentLogicalCharacterMetrics(logicalAttributes, logicalMetrics)) break; SVGCharacterDataMap& characterDataMap = logicalAttributes->characterDataMap(); SVGCharacterData data; SVGCharacterDataMap::iterator it = characterDataMap.find(m_logicalCharacterOffset + 1); if (it != characterDataMap.end()) data = it->value; float x = data.x; float y = data.y; // When we've advanced to the box start offset, determine using the original x/y values, // whether this character starts a new text chunk, before doing any further processing. if (m_visualCharacterOffset == textBox->start()) textBox->setStartsNewTextChunk(logicalAttributes->context().characterStartsNewTextChunk(m_logicalCharacterOffset)); float angle = data.rotate == SVGTextLayoutAttributes::emptyValue() ? 0 : data.rotate; // Calculate glyph orientation angle. const UChar* currentCharacter = characters + m_visualCharacterOffset; float orientationAngle = baselineLayout.calculateGlyphOrientationAngle(m_isVerticalText, &svgStyle, *currentCharacter); // Calculate glyph advance & x/y orientation shifts. float xOrientationShift = 0; float yOrientationShift = 0; float glyphAdvance = baselineLayout.calculateGlyphAdvanceAndOrientation(m_isVerticalText, visualMetrics, orientationAngle, xOrientationShift, yOrientationShift); // Assign current text position to x/y values, if needed. updateCharacerPositionIfNeeded(x, y); // Apply dx/dy value adjustments to current text position, if needed. updateRelativePositionAdjustmentsIfNeeded(data.dx, data.dy); // Calculate SVG Fonts kerning, if needed. float kerning = spacingLayout.calculateSVGKerning(m_isVerticalText, visualMetrics.glyph()); // Calculate CSS 'kerning', 'letter-spacing' and 'word-spacing' for next character, if needed. float spacing = spacingLayout.calculateCSSKerningAndSpacing(&svgStyle, lengthContext, currentCharacter); float textPathOffset = 0; if (m_inPathLayout) { float scaledGlyphAdvance = glyphAdvance * m_textPathScaling; if (m_isVerticalText) { // If there's an absolute y position available, it marks the beginning of a new position along the path. if (y != SVGTextLayoutAttributes::emptyValue()) m_textPathCurrentOffset = y + m_textPathStartOffset; m_textPathCurrentOffset += m_dy - kerning; m_dy = 0; // Apply dx/dy correction and setup translations that move to the glyph midpoint. xOrientationShift += m_dx + baselineShift; yOrientationShift -= scaledGlyphAdvance / 2; } else { // If there's an absolute x position available, it marks the beginning of a new position along the path. if (x != SVGTextLayoutAttributes::emptyValue()) m_textPathCurrentOffset = x + m_textPathStartOffset; m_textPathCurrentOffset += m_dx - kerning; m_dx = 0; // Apply dx/dy correction and setup translations that move to the glyph midpoint. xOrientationShift -= scaledGlyphAdvance / 2; yOrientationShift += m_dy - baselineShift; } // Calculate current offset along path. textPathOffset = m_textPathCurrentOffset + scaledGlyphAdvance / 2; // Move to next character. m_textPathCurrentOffset += scaledGlyphAdvance + m_textPathSpacing + spacing * m_textPathScaling; // Skip character, if we're before the path. if (textPathOffset < 0) { advanceToNextLogicalCharacter(logicalMetrics); advanceToNextVisualCharacter(visualMetrics); continue; } // Stop processing, if the next character lies behind the path. if (textPathOffset > m_textPathLength) break; bool ok = false; FloatPoint point = m_textPath.pointAtLength(textPathOffset, ok); ASSERT(ok); x = point.x(); y = point.y(); angle = m_textPath.normalAngleAtLength(textPathOffset, ok); ASSERT(ok); // For vertical text on path, the actual angle has to be rotated 90 degrees anti-clockwise, not the orientation angle! if (m_isVerticalText) angle -= 90; } else { // Apply all previously calculated shift values. if (m_isVerticalText) { x += baselineShift; y -= kerning; } else { x -= kerning; y -= baselineShift; } x += m_dx; y += m_dy; } // Determine whether we have to start a new fragment. bool shouldStartNewFragment = m_dx || m_dy || m_isVerticalText || m_inPathLayout || angle || angle != lastAngle || orientationAngle || kerning || applySpacingToNextCharacter || definesTextLength; // If we already started a fragment, close it now. if (didStartTextFragment && shouldStartNewFragment) { applySpacingToNextCharacter = false; recordTextFragment(textBox, visualMetricsValues); } // Eventually start a new fragment, if not yet done. if (!didStartTextFragment || shouldStartNewFragment) { ASSERT(!m_currentTextFragment.characterOffset); ASSERT(!m_currentTextFragment.length); didStartTextFragment = true; m_currentTextFragment.characterOffset = m_visualCharacterOffset; m_currentTextFragment.metricsListOffset = m_visualMetricsListOffset; m_currentTextFragment.x = x; m_currentTextFragment.y = y; // Build fragment transformation. if (angle) m_currentTextFragment.transform.rotate(angle); if (xOrientationShift || yOrientationShift) m_currentTextFragment.transform.translate(xOrientationShift, yOrientationShift); if (orientationAngle) m_currentTextFragment.transform.rotate(orientationAngle); m_currentTextFragment.isTextOnPath = m_inPathLayout && m_textPathScaling != 1; if (m_currentTextFragment.isTextOnPath) { if (m_isVerticalText) m_currentTextFragment.lengthAdjustTransform.scaleNonUniform(1, m_textPathScaling); else m_currentTextFragment.lengthAdjustTransform.scaleNonUniform(m_textPathScaling, 1); } } // Update current text position, after processing of the current character finished. if (m_inPathLayout) updateCurrentTextPosition(x, y, glyphAdvance); else { // Apply CSS 'kerning', 'letter-spacing' and 'word-spacing' to next character, if needed. if (spacing) applySpacingToNextCharacter = true; float xNew = x - m_dx; float yNew = y - m_dy; if (m_isVerticalText) xNew -= baselineShift; else yNew += baselineShift; updateCurrentTextPosition(xNew, yNew, glyphAdvance + spacing); } advanceToNextLogicalCharacter(logicalMetrics); advanceToNextVisualCharacter(visualMetrics); lastAngle = angle; } if (!didStartTextFragment) return; // Close last open fragment, if needed. recordTextFragment(textBox, visualMetricsValues); }
void TextWriter::writeln(const char32 ch) { pImpl->write(StringView(&ch, 1)); pImpl->writeNewLine(); }
HRESULT WINAPI DDStringView(DWORD dwAddress, DEBUGHELPER *pHelper, int nBase, BOOL bUniStrings, char *pResult, size_t max, DWORD reserved) { return StringView(dwAddress, pHelper, nBase, bUniStrings, pResult, max, 4); }
StringView getKeywordName(Keyword keyword) { return StringView(keywordNames[static_cast<std::size_t>(keyword)]); }
StringView getSimpleTokenName(Token token) { return StringView(tokenNames[static_cast<std::size_t>(token.type)]); }
bool SVGViewSpec::parseViewSpec(const String& viewSpec) { auto upconvertedCharacters = StringView(viewSpec).upconvertedCharacters(); const UChar* currViewSpec = upconvertedCharacters; const UChar* end = currViewSpec + viewSpec.length(); if (currViewSpec >= end || !m_contextElement) return false; if (!skipString(currViewSpec, end, svgViewSpec, WTF_ARRAY_LENGTH(svgViewSpec))) return false; if (currViewSpec >= end || *currViewSpec != '(') return false; currViewSpec++; while (currViewSpec < end && *currViewSpec != ')') { if (*currViewSpec == 'v') { if (skipString(currViewSpec, end, viewBoxSpec, WTF_ARRAY_LENGTH(viewBoxSpec))) { if (currViewSpec >= end || *currViewSpec != '(') return false; currViewSpec++; FloatRect viewBox; if (!SVGFitToViewBox::parseViewBox(&m_contextElement->document(), currViewSpec, end, viewBox, false)) return false; setViewBoxBaseValue(viewBox); if (currViewSpec >= end || *currViewSpec != ')') return false; currViewSpec++; } else if (skipString(currViewSpec, end, viewTargetSpec, WTF_ARRAY_LENGTH(viewTargetSpec))) { if (currViewSpec >= end || *currViewSpec != '(') return false; const UChar* viewTargetStart = ++currViewSpec; while (currViewSpec < end && *currViewSpec != ')') currViewSpec++; if (currViewSpec >= end) return false; setViewTargetString(String(viewTargetStart, currViewSpec - viewTargetStart)); currViewSpec++; } else return false; } else if (*currViewSpec == 'z') { if (!skipString(currViewSpec, end, zoomAndPanSpec, WTF_ARRAY_LENGTH(zoomAndPanSpec))) return false; if (currViewSpec >= end || *currViewSpec != '(') return false; currViewSpec++; if (!parse(currViewSpec, end, m_zoomAndPan)) return false; if (currViewSpec >= end || *currViewSpec != ')') return false; currViewSpec++; } else if (*currViewSpec == 'p') { if (!skipString(currViewSpec, end, preserveAspectRatioSpec, WTF_ARRAY_LENGTH(preserveAspectRatioSpec))) return false; if (currViewSpec >= end || *currViewSpec != '(') return false; currViewSpec++; SVGPreserveAspectRatio preserveAspectRatio; if (!preserveAspectRatio.parse(currViewSpec, end, false)) return false; setPreserveAspectRatioBaseValue(preserveAspectRatio); if (currViewSpec >= end || *currViewSpec != ')') return false; currViewSpec++; } else if (*currViewSpec == 't') { if (!skipString(currViewSpec, end, transformSpec, WTF_ARRAY_LENGTH(transformSpec))) return false; if (currViewSpec >= end || *currViewSpec != '(') return false; currViewSpec++; SVGTransformable::parseTransformAttribute(m_transform, currViewSpec, end, SVGTransformable::DoNotClearList); if (currViewSpec >= end || *currViewSpec != ')') return false; currViewSpec++; } else return false; if (currViewSpec < end && *currViewSpec == ';') currViewSpec++; } if (currViewSpec >= end || *currViewSpec != ')') return false; return true; }
static String truncateString(const String& string, float maxWidth, const FontCascade& font, TruncationFunction truncateToBuffer, bool disableRoundingHacks, float* resultWidth = nullptr, bool shouldInsertEllipsis = true, float customTruncationElementWidth = 0, bool alwaysTruncate = false) { if (string.isEmpty()) return string; if (resultWidth) *resultWidth = 0; ASSERT(maxWidth >= 0); float currentEllipsisWidth = shouldInsertEllipsis ? stringWidth(font, &horizontalEllipsis, 1, disableRoundingHacks) : customTruncationElementWidth; UChar stringBuffer[STRING_BUFFER_SIZE]; unsigned truncatedLength; unsigned keepCount; unsigned length = string.length(); if (length > STRING_BUFFER_SIZE) { if (shouldInsertEllipsis) keepCount = STRING_BUFFER_SIZE - 1; // need 1 character for the ellipsis else keepCount = 0; truncatedLength = centerTruncateToBuffer(string, length, keepCount, stringBuffer, shouldInsertEllipsis); } else { keepCount = length; StringView(string).getCharactersWithUpconvert(stringBuffer); truncatedLength = length; } float width = stringWidth(font, stringBuffer, truncatedLength, disableRoundingHacks); if (!shouldInsertEllipsis && alwaysTruncate) width += customTruncationElementWidth; if ((width - maxWidth) < 0.0001) { // Ignore rounding errors. if (resultWidth) *resultWidth = width; return string; } unsigned keepCountForLargestKnownToFit = 0; float widthForLargestKnownToFit = currentEllipsisWidth; unsigned keepCountForSmallestKnownToNotFit = keepCount; float widthForSmallestKnownToNotFit = width; if (currentEllipsisWidth >= maxWidth) { keepCountForLargestKnownToFit = 1; keepCountForSmallestKnownToNotFit = 2; } while (keepCountForLargestKnownToFit + 1 < keepCountForSmallestKnownToNotFit) { ASSERT_WITH_SECURITY_IMPLICATION(widthForLargestKnownToFit <= maxWidth); ASSERT_WITH_SECURITY_IMPLICATION(widthForSmallestKnownToNotFit > maxWidth); float ratio = (keepCountForSmallestKnownToNotFit - keepCountForLargestKnownToFit) / (widthForSmallestKnownToNotFit - widthForLargestKnownToFit); keepCount = static_cast<unsigned>(maxWidth * ratio); if (keepCount <= keepCountForLargestKnownToFit) keepCount = keepCountForLargestKnownToFit + 1; else if (keepCount >= keepCountForSmallestKnownToNotFit) keepCount = keepCountForSmallestKnownToNotFit - 1; ASSERT_WITH_SECURITY_IMPLICATION(keepCount < length); ASSERT(keepCount > 0); ASSERT_WITH_SECURITY_IMPLICATION(keepCount < keepCountForSmallestKnownToNotFit); ASSERT_WITH_SECURITY_IMPLICATION(keepCount > keepCountForLargestKnownToFit); truncatedLength = truncateToBuffer(string, length, keepCount, stringBuffer, shouldInsertEllipsis); width = stringWidth(font, stringBuffer, truncatedLength, disableRoundingHacks); if (!shouldInsertEllipsis) width += customTruncationElementWidth; if (width <= maxWidth) { keepCountForLargestKnownToFit = keepCount; widthForLargestKnownToFit = width; if (resultWidth) *resultWidth = width; } else { keepCountForSmallestKnownToNotFit = keepCount; widthForSmallestKnownToNotFit = width; } } if (keepCountForLargestKnownToFit == 0) { keepCountForLargestKnownToFit = 1; } if (keepCount != keepCountForLargestKnownToFit) { keepCount = keepCountForLargestKnownToFit; truncatedLength = truncateToBuffer(string, length, keepCount, stringBuffer, shouldInsertEllipsis); } return String(stringBuffer, truncatedLength); }
bool SVGFitToViewBox::parseViewBox(Document* doc, const String& s, FloatRect& viewBox) { auto upconvertedCharacters = StringView(s).upconvertedCharacters(); const UChar* characters = upconvertedCharacters; return parseViewBox(doc, characters, characters + s.length(), viewBox, true); }
void ThreadableWebSocketChannelClientWrapper::setSubprotocol(const String& subprotocol) { unsigned length = subprotocol.length(); m_subprotocol.resize(length); StringView(subprotocol).getCharactersWithUpconvert(m_subprotocol.data()); }
static void appendDescriptorAndReset(const CharType*& descriptorStart, const CharType* position, Vector<StringView>& descriptors) { if (position > descriptorStart) descriptors.append(StringView(descriptorStart, position - descriptorStart)); descriptorStart = nullptr; }
float StringTruncator::width(const String& string, const FontCascade& font, EnableRoundingHacksOrNot enableRoundingHacks) { return stringWidth(font, StringView(string).upconvertedCharacters(), string.length(), !enableRoundingHacks); }
StringView StringView::unhead(Size headSize) const { assert(data != nullptr || size == 0); assert(headSize <= size); return StringView(data + headSize, size - headSize); }
StringView StringView::untail(Size tailSize) const { assert(data != nullptr || size == 0); assert(tailSize <= size); return StringView(data, size - tailSize); }
void IntlNumberFormat::initializeNumberFormat(ExecState& state, JSValue locales, JSValue optionsValue) { VM& vm = state.vm(); auto scope = DECLARE_THROW_SCOPE(vm); // 11.1.2 InitializeNumberFormat (numberFormat, locales, options) (ECMA-402) // https://tc39.github.io/ecma402/#sec-initializenumberformat auto requestedLocales = canonicalizeLocaleList(state, locales); RETURN_IF_EXCEPTION(scope, void()); JSObject* options; if (optionsValue.isUndefined()) options = constructEmptyObject(&state, state.lexicalGlobalObject()->nullPrototypeObjectStructure()); else { options = optionsValue.toObject(&state); RETURN_IF_EXCEPTION(scope, void()); } HashMap<String, String> opt; String matcher = intlStringOption(state, options, vm.propertyNames->localeMatcher, { "lookup", "best fit" }, "localeMatcher must be either \"lookup\" or \"best fit\"", "best fit"); RETURN_IF_EXCEPTION(scope, void()); opt.add("localeMatcher"_s, matcher); auto& availableLocales = state.jsCallee()->globalObject(vm)->intlNumberFormatAvailableLocales(); auto result = resolveLocale(state, availableLocales, requestedLocales, opt, relevantNumberExtensionKeys, WTF_ARRAY_LENGTH(relevantNumberExtensionKeys), IntlNFInternal::localeData); m_locale = result.get("locale"_s); if (m_locale.isEmpty()) { throwTypeError(&state, scope, "failed to initialize NumberFormat due to invalid locale"_s); return; } m_numberingSystem = result.get("nu"_s); String styleString = intlStringOption(state, options, Identifier::fromString(&vm, "style"), { "decimal", "percent", "currency" }, "style must be either \"decimal\", \"percent\", or \"currency\"", "decimal"); RETURN_IF_EXCEPTION(scope, void()); if (styleString == "decimal") m_style = Style::Decimal; else if (styleString == "percent") m_style = Style::Percent; else if (styleString == "currency") m_style = Style::Currency; else ASSERT_NOT_REACHED(); String currency = intlStringOption(state, options, Identifier::fromString(&vm, "currency"), { }, nullptr, nullptr); RETURN_IF_EXCEPTION(scope, void()); if (!currency.isNull()) { if (currency.length() != 3 || !currency.isAllSpecialCharacters<isASCIIAlpha>()) { throwException(&state, scope, createRangeError(&state, "currency is not a well-formed currency code"_s)); return; } } unsigned currencyDigits = 0; if (m_style == Style::Currency) { if (currency.isNull()) { throwTypeError(&state, scope, "currency must be a string"_s); return; } currency = currency.convertToASCIIUppercase(); m_currency = currency; currencyDigits = computeCurrencyDigits(currency); } String currencyDisplayString = intlStringOption(state, options, Identifier::fromString(&vm, "currencyDisplay"), { "code", "symbol", "name" }, "currencyDisplay must be either \"code\", \"symbol\", or \"name\"", "symbol"); RETURN_IF_EXCEPTION(scope, void()); if (m_style == Style::Currency) { if (currencyDisplayString == "code") m_currencyDisplay = CurrencyDisplay::Code; else if (currencyDisplayString == "symbol") m_currencyDisplay = CurrencyDisplay::Symbol; else if (currencyDisplayString == "name") m_currencyDisplay = CurrencyDisplay::Name; else ASSERT_NOT_REACHED(); } unsigned minimumIntegerDigits = intlNumberOption(state, options, Identifier::fromString(&vm, "minimumIntegerDigits"), 1, 21, 1); RETURN_IF_EXCEPTION(scope, void()); m_minimumIntegerDigits = minimumIntegerDigits; unsigned minimumFractionDigitsDefault = (m_style == Style::Currency) ? currencyDigits : 0; unsigned minimumFractionDigits = intlNumberOption(state, options, Identifier::fromString(&vm, "minimumFractionDigits"), 0, 20, minimumFractionDigitsDefault); RETURN_IF_EXCEPTION(scope, void()); m_minimumFractionDigits = minimumFractionDigits; unsigned maximumFractionDigitsDefault; if (m_style == Style::Currency) maximumFractionDigitsDefault = std::max(minimumFractionDigits, currencyDigits); else if (m_style == Style::Percent) maximumFractionDigitsDefault = minimumFractionDigits; else maximumFractionDigitsDefault = std::max(minimumFractionDigits, 3u); unsigned maximumFractionDigits = intlNumberOption(state, options, Identifier::fromString(&vm, "maximumFractionDigits"), minimumFractionDigits, 20, maximumFractionDigitsDefault); RETURN_IF_EXCEPTION(scope, void()); m_maximumFractionDigits = maximumFractionDigits; JSValue minimumSignificantDigitsValue = options->get(&state, Identifier::fromString(&vm, "minimumSignificantDigits")); RETURN_IF_EXCEPTION(scope, void()); JSValue maximumSignificantDigitsValue = options->get(&state, Identifier::fromString(&vm, "maximumSignificantDigits")); RETURN_IF_EXCEPTION(scope, void()); if (!minimumSignificantDigitsValue.isUndefined() || !maximumSignificantDigitsValue.isUndefined()) { unsigned minimumSignificantDigits = intlDefaultNumberOption(state, minimumSignificantDigitsValue, Identifier::fromString(&vm, "minimumSignificantDigits"), 1, 21, 1); RETURN_IF_EXCEPTION(scope, void()); unsigned maximumSignificantDigits = intlDefaultNumberOption(state, maximumSignificantDigitsValue, Identifier::fromString(&vm, "maximumSignificantDigits"), minimumSignificantDigits, 21, 21); RETURN_IF_EXCEPTION(scope, void()); m_minimumSignificantDigits = minimumSignificantDigits; m_maximumSignificantDigits = maximumSignificantDigits; } bool usesFallback; bool useGrouping = intlBooleanOption(state, options, Identifier::fromString(&vm, "useGrouping"), usesFallback); if (usesFallback) useGrouping = true; RETURN_IF_EXCEPTION(scope, void()); m_useGrouping = useGrouping; UNumberFormatStyle style = UNUM_DEFAULT; switch (m_style) { case Style::Decimal: style = UNUM_DECIMAL; break; case Style::Percent: style = UNUM_PERCENT; break; case Style::Currency: switch (m_currencyDisplay) { case CurrencyDisplay::Code: style = UNUM_CURRENCY_ISO; break; case CurrencyDisplay::Symbol: style = UNUM_CURRENCY; break; case CurrencyDisplay::Name: style = UNUM_CURRENCY_PLURAL; break; default: ASSERT_NOT_REACHED(); } break; default: ASSERT_NOT_REACHED(); } UErrorCode status = U_ZERO_ERROR; m_numberFormat = std::unique_ptr<UNumberFormat, UNumberFormatDeleter>(unum_open(style, nullptr, 0, m_locale.utf8().data(), nullptr, &status)); if (U_FAILURE(status)) { throwTypeError(&state, scope, "failed to initialize NumberFormat"_s); return; } if (m_style == Style::Currency) { unum_setTextAttribute(m_numberFormat.get(), UNUM_CURRENCY_CODE, StringView(m_currency).upconvertedCharacters(), m_currency.length(), &status); if (U_FAILURE(status)) { throwTypeError(&state, scope, "failed to initialize NumberFormat"_s); return; } } if (!m_minimumSignificantDigits) { unum_setAttribute(m_numberFormat.get(), UNUM_MIN_INTEGER_DIGITS, m_minimumIntegerDigits); unum_setAttribute(m_numberFormat.get(), UNUM_MIN_FRACTION_DIGITS, m_minimumFractionDigits); unum_setAttribute(m_numberFormat.get(), UNUM_MAX_FRACTION_DIGITS, m_maximumFractionDigits); } else { unum_setAttribute(m_numberFormat.get(), UNUM_SIGNIFICANT_DIGITS_USED, true); unum_setAttribute(m_numberFormat.get(), UNUM_MIN_SIGNIFICANT_DIGITS, m_minimumSignificantDigits); unum_setAttribute(m_numberFormat.get(), UNUM_MAX_SIGNIFICANT_DIGITS, m_maximumSignificantDigits); } unum_setAttribute(m_numberFormat.get(), UNUM_GROUPING_USED, m_useGrouping); unum_setAttribute(m_numberFormat.get(), UNUM_ROUNDING_MODE, UNUM_ROUND_HALFUP); m_initializedNumberFormat = true; }
StringView StringView::range(Size startIndex, Size endIndex) const { assert(data != nullptr || size == 0); assert(startIndex <= endIndex && endIndex <= size); return StringView(data + startIndex, endIndex - startIndex); }
void ThreadableWebSocketChannelClientWrapper::setExtensions(const String& extensions) { unsigned length = extensions.length(); m_extensions.resize(length); StringView(extensions).getCharactersWithUpconvert(m_extensions.data()); }
Token Scanner::next() { std::string text; while (true) { while (position < buffer.length()) { char c = buffer[position]; switch (state) { case State::Start: switch (c) { case '0': state = State::LeadingZero; text += c; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': state = State::IntegerDigits; text += c; break; case '_': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': state = State::Identifier; text += c; break; case '\'': case '\"': terminator = c; state = State::String; break; case ' ': case '\t': case '\r': case '\n': break; case ':': position++; return Token(TokenType::Colon); case ',': position++; return Token(TokenType::Comma); case '.': state = State::Dot; break; case '(': position++; return Token(TokenType::LeftParenthesis); case ')': position++; return Token(TokenType::RightParenthesis); case '[': position++; return Token(TokenType::LeftBracket); case ']': position++; return Token(TokenType::RightBracket); case '{': position++; return Token(TokenType::LeftBrace); case '}': position++; return Token(TokenType::RightBrace); case ';': position++; return Token(TokenType::Semicolon); case '#': state = State::Hash; break; case '|': state = State::Bar; break; case '*': state = State::Asterisk; break; case '%': state = State::Percent; break; case '@': position++; return Token(TokenType::At); case '&': state = State::Ampersand; break; case '^': state = State::Caret; break; case '!': state = State::Exclamation; break; case '`': position++; return Token(TokenType::Backtick); case '~': position++; return Token(TokenType::Tilde); case '+': state = State::Plus; break; case '-': state = State::Minus; break; case '/': state = State::Slash; break; case '=': state = State::Equals; break; case '<': state = State::LessThan; break; case '>': state = State::GreaterThan; break; case '$': position++; return Token(TokenType::Dollar); default: report->error("unrecognized character `\\" + std::string(1, c) + "` found", location); break; } break; case State::Identifier: switch (c) { case '_': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': text += c; break; default: { state = State::Start; const auto internedText = stringPool->intern(text); return Token(TokenType::Identifier, findKeyword(internedText), internedText); } } break; case State::String: if (c == terminator) { position++; state = State::Start; switch (terminator) { case '\'': if (text.size() != 1) { report->error("invalid character literal '" + text::escape(StringView(text), '\'') + "' (character literals must be exactly one character)", location); return Token(TokenType::Character, StringView(ErrorText)); } else { return Token(TokenType::Character, stringPool->intern(text)); } default: return Token(TokenType::String, stringPool->intern(text)); } } else switch (c) { case '\\': state = State::StringEscape; break; default: text += c; break; } break; case State::StringEscape: state = State::String; switch (c) { case '\"': case '\'': case '\\': text += c; break; case 't': text += '\t'; break; case 'r': text += '\r'; break; case 'n': text += '\n'; break; case 'f': text += '\f'; break; case 'b': text += '\b'; break; case 'a': text += '\a'; break; case '0': text += '\0'; break; case 'x': state = State::HexEscapeFirstDigit; break; default: report->error("invalid escape sequence `\\" + std::string(1, c) + "` in quoted literal", location); break; } break; case State::HexEscapeFirstDigit: { state = State::HexEscapeSecondDigit; switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': intermediateCharCode = static_cast<std::uint8_t>(c - '0') << 4; break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': intermediateCharCode = static_cast<std::uint8_t>(c - 'a' + 10) << 4; break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': intermediateCharCode = static_cast<std::uint8_t>(c - 'A' + 10) << 4; break; default: state = State::String; report->error("hex escape `\\x` contains illegal character `" + std::string(1, c) + "`", location); break; } break; } case State::HexEscapeSecondDigit: { state = State::String; switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': text += static_cast<char>(intermediateCharCode | static_cast<std::uint8_t>(c - '0')); break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': text += static_cast<char>(intermediateCharCode | static_cast<std::uint8_t>(c - 'a' + 10)); break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': text += static_cast<char>(intermediateCharCode | static_cast<std::uint8_t>(c - 'A' + 10)); break; default: state = State::String; report->error("hex escape `\\x` contains illegal character `" + std::string(1, c) + "`", location); break; } break; } case State::LeadingZero: switch (c) { case '_': state = State::IntegerDigits; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': state = State::IntegerDigits; text += c; break; case 'x': state = State::HexadecimalDigits; text += c; break; case 'b': state = State::BinaryDigits; text += c; break; case 'o': state = State::OctalDigits; text += c; break; case 'u': case 'i': text += c; baseTokenType = TokenType::Integer; state = State::LiteralSuffix; break; default: state = State::Start; return Token(TokenType::Integer, stringPool->intern(text)); } break; case State::IntegerDigits: switch (c) { case '_': break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': text += c; break; case 'u': case 'i': text += c; baseTokenType = TokenType::Integer; state = State::LiteralSuffix; break; default: state = State::Start; return Token(TokenType::Integer, stringPool->intern(text)); } break; case State::HexadecimalDigits: switch (c) { case '_': break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': text += c; break; case 'u': case 'i': text += c; baseTokenType = TokenType::Hexadecimal; state = State::LiteralSuffix; break; default: state = State::Start; return Token(TokenType::Hexadecimal, stringPool->intern(text)); } break; case State::OctalDigits: switch (c) { case '_': break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': text += c; break; case 'u': case 'i': text += c; baseTokenType = TokenType::Octal; state = State::LiteralSuffix; break; default: state = State::Start; return Token(TokenType::Octal, stringPool->intern(text)); } break; case State::BinaryDigits: switch (c) { case '_': break; case '0': case '1': text += c; break; case 'u': case 'i': text += c; baseTokenType = TokenType::Binary; state = State::LiteralSuffix; break; default: state = State::Start; return Token(TokenType::Binary, stringPool->intern(text)); } break; case State::LiteralSuffix: switch (c) { case '_': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': text += c; break; default: { state = State::Start; return Token(baseTokenType, Keyword::None, stringPool->intern(text)); } } break; case State::Exclamation: state = State::Start; switch (c) { case '=': position++; return Token(TokenType::ExclamationEquals); default: return Token(TokenType::Exclamation); } break; case State::Plus: state = State::Start; switch (c) { case '#': state = State::PlusHash; break; case '+': position++; return Token(TokenType::DoublePlus); case '=': position++; return Token(TokenType::PlusEquals); default: return Token(TokenType::Plus); } break; case State::PlusHash: state = State::Start; switch (c) { case '=': position++; return Token(TokenType::PlusHashEquals); default: return Token(TokenType::PlusHash); } break; case State::Minus: state = State::Start; switch (c) { case '#': state = State::MinusHash; break; case '-': position++; return Token(TokenType::DoubleMinus); case '=': position++; return Token(TokenType::MinusEquals); case '0': state = State::LeadingZero; text += '-'; text += c; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': state = State::IntegerDigits; text += '-'; text += c; break; default: return Token(TokenType::Minus); } break; case State::MinusHash: state = State::Start; switch (c) { case '=': position++; return Token(TokenType::MinusHashEquals); default: return Token(TokenType::MinusHash); } break; case State::Asterisk: state = State::Start; switch (c) { case '=': position++; return Token(TokenType::AsteriskEquals); default: return Token(TokenType::Asterisk); } break; case State::Percent: state = State::Start; switch (c) { case '=': position++; return Token(TokenType::PercentEquals); default: return Token(TokenType::Percent); } break; case State::Slash: state = State::Start; switch (c) { case '/': state = State::DoubleSlashComment; break; case '*': state = State::SlashStarComment; commentStartLocation.line = location.line; break; case '=': position++; return Token(TokenType::SlashEquals); default: return Token(TokenType::Slash); } break; case State::DoubleSlashComment: break; case State::SlashStarComment: switch (c) { case '*': state = State::SlashStarCommentStar; default: break; } break; case State::SlashStarCommentStar: switch (c) { case '/': state = State::Start; break; default: state = State::SlashStarComment; break; } break; case State::Equals: state = State::Start; switch (c) { case '=': position++; return Token(TokenType::DoubleEquals); default: return Token(TokenType::Equals); } break; case State::Ampersand: state = State::Start; switch (c) { case '&': position++; return Token(TokenType::DoubleAmpersand); case '=': position++; return Token(TokenType::AmpersandEquals); default: return Token(TokenType::Ampersand); } break; case State::Bar: state = State::Start; switch (c) { case '|': position++; return Token(TokenType::DoubleBar); case '=': position++; return Token(TokenType::BarEquals); default: return Token(TokenType::Bar); } break; case State::Caret: state = State::Start; switch (c) { case '=': position++; return Token(TokenType::CaretEquals); default: return Token(TokenType::Caret); } break; case State::LessThan: state = State::Start; switch (c) { case '<': state = State::DoubleLessThan; break; case '=': position++; return Token(TokenType::LessThanEquals); case ':': position++; return Token(TokenType::LessColon); default: return Token(TokenType::LessThan); } break; case State::DoubleLessThan: state = State::Start; switch (c) { case '<': state = State::TripleLessThan; break; case '=': position++; return Token(TokenType::DoubleLessThanEquals); default: return Token(TokenType::DoubleLessThan); } break; case State::TripleLessThan: state = State::Start; switch (c) { case '<': state = State::QuadrupleLessThan; break; case '=': position++; return Token(TokenType::TripleLessThanEquals); default: return Token(TokenType::TripleLessThan); } break; case State::QuadrupleLessThan: state = State::Start; switch (c) { case '#': state = State::QuadrupleLessThanHash; break; case '=': position++; return Token(TokenType::QuadrupleLessThanEquals); default: return Token(TokenType::QuadrupleLessThan); } break; case State::QuadrupleLessThanHash: state = State::Start; switch (c) { case '=': position++; return Token(TokenType::QuadrupleLessThanHashEquals); default: return Token(TokenType::QuadrupleLessThanHash); } break; case State::GreaterThan: state = State::Start; switch (c) { case '>': state = State::DoubleGreaterThan; break; case '=': position++; return Token(TokenType::GreaterThanEquals); case ':': position++; return Token(TokenType::GreaterColon); default: return Token(TokenType::GreaterThan); } break; case State::DoubleGreaterThan: state = State::Start; switch (c) { case '>': state = State::TripleGreaterThan; break; case '=': position++; return Token(TokenType::DoubleGreaterThanEquals); default: return Token(TokenType::DoubleGreaterThan); } break; case State::TripleGreaterThan: state = State::Start; switch (c) { case '>': state = State::QuadrupleGreaterThan; break; case '=': position++; return Token(TokenType::TripleGreaterThanEquals); default: return Token(TokenType::TripleGreaterThan); } break; case State::QuadrupleGreaterThan: state = State::Start; switch (c) { case '#': state = State::QuadrupleGreaterThanHash; break; case '=': position++; return Token(TokenType::QuadrupleGreaterThanEquals); default: return Token(TokenType::QuadrupleGreaterThan); } break; case State::QuadrupleGreaterThanHash: state = State::Start; switch (c) { case '=': position++; return Token(TokenType::QuadrupleGreaterThanHashEquals); default: return Token(TokenType::QuadrupleGreaterThanHash); } break; case State::Dot: state = State::Start; switch (c) { case '.': state = State::DoubleDot; break; default: return Token(TokenType::Dot); } break; case State::DoubleDot: state = State::Start; switch (c) { case '.': position++; return Token(TokenType::DotDotDot); default: return Token(TokenType::DotDot); } break; case State::Hash: state = State::Start; switch (c) { case '[': position++; return Token(TokenType::HashBracket); default: return Token(TokenType::Hash); } break; } position++; } if (reader && reader->isOpen() && reader->readLine(buffer)) { // Special handling in states for end-of-line. switch (state) { case State::DoubleSlashComment: state = State::Start; break; case State::String: state = State::Start; report->error("expected closing quote `" + std::string(1, terminator) + "`, but got end-of-line", location); break; case State::StringEscape: state = State::Start; report->error("expected string escape sequence, but got end-of-line", location); break; default: break; } position = 0; location.line++; } else { // End-of-file. switch (state) { case State::Identifier: { state = State::Start; const auto internedText = stringPool->intern(text); return Token(TokenType::Identifier, findKeyword(internedText), internedText); } case State::LeadingZero: case State::IntegerDigits: state = State::Start; return Token(TokenType::Integer, stringPool->intern(text)); case State::HexadecimalDigits: state = State::Start; return Token(TokenType::Hexadecimal, stringPool->intern(text)); case State::BinaryDigits: state = State::Start; return Token(TokenType::Binary, stringPool->intern(text)); case State::String: state = State::Start; report->error("expected closing quote `" + std::string(1, terminator) + "`, but got end-of-file", location); break; case State::StringEscape: state = State::Start; report->error("expected string escape sequence, but got end-of-file", location); break; case State::SlashStarComment: report->error("expected `*/` to close comment `/*`, but got end-of-file", location, ReportErrorFlags(ReportErrorFlagType::Continued)); report->error("comment `/*` started here", commentStartLocation); break; default: break; } return Token(TokenType::EndOfFile); } } return Token(TokenType::EndOfFile); }