// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "vm/regexp_parser.h" #include "vm/longjump.h" #include "vm/object_store.h" namespace dart { #define Z zone() // Enables possessive quantifier syntax for testing. static const bool FLAG_regexp_possessive_quantifier = false; RegExpBuilder::RegExpBuilder() : zone_(Thread::Current()->zone()), pending_empty_(false), characters_(NULL), terms_(), text_(), alternatives_() #ifdef DEBUG , last_added_(ADD_NONE) #endif { } void RegExpBuilder::FlushCharacters() { pending_empty_ = false; if (characters_ != NULL) { RegExpTree* atom = new (Z) RegExpAtom(characters_); characters_ = NULL; text_.Add(atom); LAST(ADD_ATOM); } } void RegExpBuilder::FlushText() { FlushCharacters(); intptr_t num_text = text_.length(); if (num_text == 0) { return; } else if (num_text == 1) { terms_.Add(text_.Last()); } else { RegExpText* text = new (Z) RegExpText(); for (intptr_t i = 0; i < num_text; i++) text_[i]->AppendToText(text); terms_.Add(text); } text_.Clear(); } void RegExpBuilder::AddCharacter(uint16_t c) { pending_empty_ = false; if (characters_ == NULL) { characters_ = new (Z) ZoneGrowableArray(4); } characters_->Add(c); LAST(ADD_CHAR); } void RegExpBuilder::AddEmpty() { pending_empty_ = true; } void RegExpBuilder::AddAtom(RegExpTree* term) { if (term->IsEmpty()) { AddEmpty(); return; } if (term->IsTextElement()) { FlushCharacters(); text_.Add(term); } else { FlushText(); terms_.Add(term); } LAST(ADD_ATOM); } void RegExpBuilder::AddAssertion(RegExpTree* assert) { FlushText(); terms_.Add(assert); LAST(ADD_ASSERT); } void RegExpBuilder::NewAlternative() { FlushTerms(); } void RegExpBuilder::FlushTerms() { FlushText(); intptr_t num_terms = terms_.length(); RegExpTree* alternative; if (num_terms == 0) { alternative = RegExpEmpty::GetInstance(); } else if (num_terms == 1) { alternative = terms_.Last(); } else { ZoneGrowableArray* terms = new (Z) ZoneGrowableArray(); for (intptr_t i = 0; i < terms_.length(); i++) { terms->Add(terms_[i]); } alternative = new (Z) RegExpAlternative(terms); } alternatives_.Add(alternative); terms_.Clear(); LAST(ADD_NONE); } RegExpTree* RegExpBuilder::ToRegExp() { FlushTerms(); intptr_t num_alternatives = alternatives_.length(); if (num_alternatives == 0) { return RegExpEmpty::GetInstance(); } if (num_alternatives == 1) { return alternatives_.Last(); } ZoneGrowableArray* alternatives = new (Z) ZoneGrowableArray(); for (intptr_t i = 0; i < alternatives_.length(); i++) { alternatives->Add(alternatives_[i]); } return new (Z) RegExpDisjunction(alternatives); } bool RegExpBuilder::AddQuantifierToAtom( intptr_t min, intptr_t max, RegExpQuantifier::QuantifierType quantifier_type) { if (pending_empty_) { pending_empty_ = false; return true; } RegExpTree* atom; if (characters_ != NULL) { DEBUG_ASSERT(last_added_ == ADD_CHAR); // Last atom was character. ZoneGrowableArray* char_vector = new (Z) ZoneGrowableArray(); char_vector->AddArray(*characters_); intptr_t num_chars = char_vector->length(); if (num_chars > 1) { ZoneGrowableArray* prefix = new (Z) ZoneGrowableArray(); for (intptr_t i = 0; i < num_chars - 1; i++) { prefix->Add(char_vector->At(i)); } text_.Add(new (Z) RegExpAtom(prefix)); ZoneGrowableArray* tail = new (Z) ZoneGrowableArray(); tail->Add(char_vector->At(num_chars - 1)); char_vector = tail; } characters_ = NULL; atom = new (Z) RegExpAtom(char_vector); FlushText(); } else if (text_.length() > 0) { DEBUG_ASSERT(last_added_ == ADD_ATOM); atom = text_.RemoveLast(); FlushText(); } else if (terms_.length() > 0) { DEBUG_ASSERT(last_added_ == ADD_ATOM); atom = terms_.RemoveLast(); if (auto lookaround = atom->AsLookaround()) { // Lookbehinds are not quantifiable. if (lookaround->type() == RegExpLookaround::LOOKBEHIND) { return false; } } if (atom->max_match() == 0) { // Guaranteed to only match an empty string. LAST(ADD_TERM); if (min == 0) { return true; } terms_.Add(atom); return true; } } else { // Only call immediately after adding an atom or character! UNREACHABLE(); } terms_.Add(new (Z) RegExpQuantifier(min, max, quantifier_type, atom)); LAST(ADD_TERM); return true; } // ---------------------------------------------------------------------------- // Implementation of Parser RegExpParser::RegExpParser(const String& in, String* error, bool multiline) : zone_(Thread::Current()->zone()), captures_(nullptr), named_captures_(nullptr), named_back_references_(nullptr), in_(in), current_(kEndMarker), next_pos_(0), captures_started_(0), capture_count_(0), has_more_(true), multiline_(multiline), simple_(false), contains_anchor_(false), is_scanned_for_captures_(false), has_named_captures_(false) { Advance(); } uint32_t RegExpParser::Next() { if (has_next()) { return in().CharAt(next_pos_); } else { return kEndMarker; } } void RegExpParser::Advance() { if (next_pos_ < in().Length()) { current_ = in().CharAt(next_pos_); next_pos_++; } else { current_ = kEndMarker; has_more_ = false; } } void RegExpParser::Reset(intptr_t pos) { next_pos_ = pos; has_more_ = (pos < in().Length()); Advance(); } void RegExpParser::Advance(intptr_t dist) { next_pos_ += dist - 1; Advance(); } bool RegExpParser::simple() { return simple_; } void RegExpParser::ReportError(const char* message) { // Zip to the end to make sure the no more input is read. current_ = kEndMarker; next_pos_ = in().Length(); // Throw a FormatException on parsing failures. const String& msg = String::Handle( String::Concat(String::Handle(String::New(message)), in())); const Array& args = Array::Handle(Array::New(1)); args.SetAt(0, msg); Exceptions::ThrowByType(Exceptions::kFormat, args); UNREACHABLE(); } // Pattern :: // Disjunction RegExpTree* RegExpParser::ParsePattern() { RegExpTree* result = ParseDisjunction(); PatchNamedBackReferences(); ASSERT(!has_more()); // If the result of parsing is a literal string atom, and it has the // same length as the input, then the atom is identical to the input. if (result->IsAtom() && result->AsAtom()->length() == in().Length()) { simple_ = true; } return result; } // Disjunction :: // Alternative // Alternative | Disjunction // Alternative :: // [empty] // Term Alternative // Term :: // Assertion // Atom // Atom Quantifier RegExpTree* RegExpParser::ParseDisjunction() { // Used to store current state while parsing subexpressions. RegExpParserState initial_state(nullptr, INITIAL, RegExpLookaround::LOOKAHEAD, 0, nullptr, Z); RegExpParserState* stored_state = &initial_state; // Cache the builder in a local variable for quick access. RegExpBuilder* builder = initial_state.builder(); while (true) { switch (current()) { case kEndMarker: if (stored_state->IsSubexpression()) { // Inside a parenthesized group when hitting end of input. ReportError("Unterminated group"); UNREACHABLE(); } ASSERT(INITIAL == stored_state->group_type()); // Parsing completed successfully. return builder->ToRegExp(); case ')': { if (!stored_state->IsSubexpression()) { ReportError("Unmatched ')'"); UNREACHABLE(); } ASSERT(INITIAL != stored_state->group_type()); Advance(); // End disjunction parsing and convert builder content to new single // regexp atom. RegExpTree* body = builder->ToRegExp(); intptr_t end_capture_index = captures_started(); intptr_t capture_index = stored_state->capture_index(); SubexpressionType group_type = stored_state->group_type(); // Build result of subexpression. if (group_type == CAPTURE) { if (stored_state->IsNamedCapture()) { CreateNamedCaptureAtIndex(stored_state->capture_name(), capture_index); } RegExpCapture* capture = GetCapture(capture_index); capture->set_body(body); body = capture; } else if (group_type != GROUPING) { ASSERT(group_type == POSITIVE_LOOKAROUND || group_type == NEGATIVE_LOOKAROUND); bool is_positive = (group_type == POSITIVE_LOOKAROUND); body = new (Z) RegExpLookaround( body, is_positive, end_capture_index - capture_index, capture_index, stored_state->lookaround_type()); } // Restore previous state. stored_state = stored_state->previous_state(); builder = stored_state->builder(); builder->AddAtom(body); // For compatibility with JSC and ES3, we allow quantifiers after // lookaheads, and break in all cases. break; } case '|': { Advance(); builder->NewAlternative(); continue; } case '*': case '+': case '?': ReportError("Nothing to repeat"); UNREACHABLE(); case '^': { Advance(); if (multiline_) { builder->AddAssertion( new (Z) RegExpAssertion(RegExpAssertion::START_OF_LINE)); } else { builder->AddAssertion( new (Z) RegExpAssertion(RegExpAssertion::START_OF_INPUT)); set_contains_anchor(); } continue; } case '$': { Advance(); RegExpAssertion::AssertionType assertion_type = multiline_ ? RegExpAssertion::END_OF_LINE : RegExpAssertion::END_OF_INPUT; builder->AddAssertion(new RegExpAssertion(assertion_type)); continue; } case '.': { Advance(); // everything except \x0a, \x0d, \u2028 and \u2029 ZoneGrowableArray* ranges = new ZoneGrowableArray(2); CharacterRange::AddClassEscape('.', ranges); RegExpTree* atom = new RegExpCharacterClass(ranges, false); builder->AddAtom(atom); break; } case '(': { stored_state = ParseOpenParenthesis(stored_state); builder = stored_state->builder(); continue; } case '[': { RegExpTree* atom = ParseCharacterClass(); builder->AddAtom(atom); break; } // Atom :: // \ AtomEscape case '\\': switch (Next()) { case kEndMarker: ReportError("\\ at end of pattern"); UNREACHABLE(); case 'b': Advance(2); builder->AddAssertion( new RegExpAssertion(RegExpAssertion::BOUNDARY)); continue; case 'B': Advance(2); builder->AddAssertion( new RegExpAssertion(RegExpAssertion::NON_BOUNDARY)); continue; // AtomEscape :: // CharacterClassEscape // // CharacterClassEscape :: one of // d D s S w W case 'd': case 'D': case 's': case 'S': case 'w': case 'W': { uint32_t c = Next(); Advance(2); ZoneGrowableArray* ranges = new ZoneGrowableArray(2); CharacterRange::AddClassEscape(c, ranges); RegExpTree* atom = new RegExpCharacterClass(ranges, false); builder->AddAtom(atom); break; } case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { intptr_t index = 0; if (ParseBackReferenceIndex(&index)) { if (stored_state->IsInsideCaptureGroup(index)) { // The back reference is inside the capture group it refers to. // Nothing can possibly have been captured yet, so we use empty // instead. This ensures that, when checking a back reference, // the capture registers of the referenced capture are either // both set or both cleared. builder->AddEmpty(); } else { RegExpCapture* capture = GetCapture(index); RegExpTree* atom = new RegExpBackReference(capture); builder->AddAtom(atom); } break; } uint32_t first_digit = Next(); if (first_digit == '8' || first_digit == '9') { // Treat as identity escape builder->AddCharacter(first_digit); Advance(2); break; } FALL_THROUGH; } case '0': { Advance(); uint32_t octal = ParseOctalLiteral(); builder->AddCharacter(octal); break; } // ControlEscape :: one of // f n r t v case 'f': Advance(2); builder->AddCharacter('\f'); break; case 'n': Advance(2); builder->AddCharacter('\n'); break; case 'r': Advance(2); builder->AddCharacter('\r'); break; case 't': Advance(2); builder->AddCharacter('\t'); break; case 'v': Advance(2); builder->AddCharacter('\v'); break; case 'c': { Advance(); uint32_t controlLetter = Next(); // Special case if it is an ASCII letter. // Convert lower case letters to uppercase. uint32_t letter = controlLetter & ~('a' ^ 'A'); if (letter < 'A' || 'Z' < letter) { // controlLetter is not in range 'A'-'Z' or 'a'-'z'. // This is outside the specification. We match JSC in // reading the backslash as a literal character instead // of as starting an escape. builder->AddCharacter('\\'); } else { Advance(2); builder->AddCharacter(controlLetter & 0x1f); } break; } case 'x': { Advance(2); uint32_t value; if (ParseHexEscape(2, &value)) { builder->AddCharacter(value); } else { builder->AddCharacter('x'); } break; } case 'u': { Advance(2); uint32_t value; if (ParseHexEscape(4, &value)) { builder->AddCharacter(value); } else { builder->AddCharacter('u'); } break; } case 'k': // Either an identity escape or a named back-reference. The two // interpretations are mutually exclusive: '\k' is interpreted as // an identity escape for non-Unicode patterns without named // capture groups, and as the beginning of a named back-reference // in all other cases. if (HasNamedCaptures()) { Advance(2); ParseNamedBackReference(builder, stored_state); break; } FALL_THROUGH; default: // Identity escape. builder->AddCharacter(Next()); Advance(2); break; } break; case '{': { intptr_t dummy; if (ParseIntervalQuantifier(&dummy, &dummy)) { ReportError("Nothing to repeat"); UNREACHABLE(); } FALL_THROUGH; } default: builder->AddCharacter(current()); Advance(); break; } // end switch(current()) intptr_t min; intptr_t max; switch (current()) { // QuantifierPrefix :: // * // + // ? // { case '*': min = 0; max = RegExpTree::kInfinity; Advance(); break; case '+': min = 1; max = RegExpTree::kInfinity; Advance(); break; case '?': min = 0; max = 1; Advance(); break; case '{': if (ParseIntervalQuantifier(&min, &max)) { if (max < min) { ReportError("numbers out of order in {} quantifier."); UNREACHABLE(); } break; } else { continue; } default: continue; } RegExpQuantifier::QuantifierType quantifier_type = RegExpQuantifier::GREEDY; if (current() == '?') { quantifier_type = RegExpQuantifier::NON_GREEDY; Advance(); } else if (FLAG_regexp_possessive_quantifier && current() == '+') { // FLAG_regexp_possessive_quantifier is a debug-only flag. quantifier_type = RegExpQuantifier::POSSESSIVE; Advance(); } if (!builder->AddQuantifierToAtom(min, max, quantifier_type)) { ReportError("invalid quantifier."); UNREACHABLE(); } } } #ifdef DEBUG // Currently only used in an ASSERT. static bool IsSpecialClassEscape(uint32_t c) { switch (c) { case 'd': case 'D': case 's': case 'S': case 'w': case 'W': return true; default: return false; } } #endif RegExpParser::RegExpParserState* RegExpParser::ParseOpenParenthesis( RegExpParserState* state) { RegExpLookaround::Type lookaround_type = state->lookaround_type(); bool is_named_capture = false; const RegExpCaptureName* capture_name = nullptr; SubexpressionType subexpr_type = CAPTURE; Advance(); if (current() == '?') { switch (Next()) { case ':': Advance(2); subexpr_type = GROUPING; break; case '=': Advance(2); lookaround_type = RegExpLookaround::LOOKAHEAD; subexpr_type = POSITIVE_LOOKAROUND; break; case '!': Advance(2); lookaround_type = RegExpLookaround::LOOKAHEAD; subexpr_type = NEGATIVE_LOOKAROUND; break; case '<': Advance(); if (Next() == '=') { Advance(2); lookaround_type = RegExpLookaround::LOOKBEHIND; subexpr_type = POSITIVE_LOOKAROUND; break; } else if (Next() == '!') { Advance(2); lookaround_type = RegExpLookaround::LOOKBEHIND; subexpr_type = NEGATIVE_LOOKAROUND; break; } is_named_capture = true; has_named_captures_ = true; Advance(); break; default: ReportError("Invalid group"); UNREACHABLE(); } } if (subexpr_type == CAPTURE) { if (captures_started_ >= kMaxCaptures) { ReportError("Too many captures"); UNREACHABLE(); } captures_started_++; if (is_named_capture) { capture_name = ParseCaptureGroupName(); } } // Store current state and begin new disjunction parsing. return new RegExpParserState(state, subexpr_type, lookaround_type, captures_started_, capture_name, Z); } // In order to know whether an escape is a backreference or not we have to scan // the entire regexp and find the number of capturing parentheses. However we // don't want to scan the regexp twice unless it is necessary. This mini-parser // is called when needed. It can see the difference between capturing and // noncapturing parentheses and can skip character classes and backslash-escaped // characters. void RegExpParser::ScanForCaptures() { ASSERT(!is_scanned_for_captures_); const intptr_t saved_position = position(); // Start with captures started previous to current position intptr_t capture_count = captures_started(); // Add count of captures after this position. uintptr_t n; while ((n = current()) != kEndMarker) { Advance(); switch (n) { case '\\': Advance(); break; case '[': { uintptr_t c; while ((c = current()) != kEndMarker) { Advance(); if (c == '\\') { Advance(); } else { if (c == ']') break; } } break; } case '(': // At this point we could be in // * a non-capturing group '(:', // * a lookbehind assertion '(?<=' '(? kMaxCaptures) { Reset(start); return false; } Advance(); } else { break; } } if (value > captures_started()) { if (!is_scanned_for_captures_) ScanForCaptures(); if (value > capture_count_) { Reset(start); return false; } } *index_out = value; return true; } namespace { inline constexpr bool IsIdentifierStart(uint16_t ch) { return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_' || ch == '$'; } inline constexpr bool IsIdentifierPart(uint16_t ch) { return IsIdentifierStart(ch) || (ch >= '0' && ch <= '9'); } bool IsSameName(const RegExpCaptureName* name1, const RegExpCaptureName* name2) { if (name1->length() != name2->length()) return false; for (intptr_t i = 0; i < name1->length(); i++) { if (name1->At(i) != name2->At(i)) return false; } return true; } } // end namespace const RegExpCaptureName* RegExpParser::ParseCaptureGroupName() { auto name = new (Z) RegExpCaptureName(); bool at_start = true; while (true) { const uint16_t c = current(); Advance(); // The backslash char is misclassified as both ID_Start and ID_Continue. if (c == '\\') { ReportError("Invalid capture group name"); UNREACHABLE(); } if (at_start) { if (!IsIdentifierStart(c)) { ReportError("Invalid capture group name"); UNREACHABLE(); } name->Add(c); at_start = false; } else { if (c == '>') { break; } else if (IsIdentifierPart(c)) { name->Add(c); } else { ReportError("Invalid capture group name"); UNREACHABLE(); } } } return name; } intptr_t RegExpParser::GetNamedCaptureIndex(const RegExpCaptureName* name) { for (const auto& capture : *named_captures_) { if (IsSameName(name, capture->name())) return capture->index(); } return -1; } void RegExpParser::CreateNamedCaptureAtIndex(const RegExpCaptureName* name, intptr_t index) { ASSERT(0 < index && index <= captures_started_); ASSERT(name != nullptr); if (named_captures_ == nullptr) { named_captures_ = new (Z) ZoneGrowableArray(1); } else { // Check for duplicates and bail if we find any. Currently O(n^2). if (GetNamedCaptureIndex(name) >= 0) { ReportError("Duplicate capture group name"); UNREACHABLE(); } } RegExpCapture* capture = GetCapture(index); ASSERT(capture->name() == nullptr); capture->set_name(name); named_captures_->Add(capture); } bool RegExpParser::ParseNamedBackReference(RegExpBuilder* builder, RegExpParserState* state) { // The parser is assumed to be on the '<' in \k. if (current() != '<') { ReportError("Invalid named reference"); UNREACHABLE(); } Advance(); const RegExpCaptureName* name = ParseCaptureGroupName(); if (name == nullptr) { return false; } if (state->IsInsideCaptureGroup(name)) { builder->AddEmpty(); } else { RegExpBackReference* atom = new (Z) RegExpBackReference(); atom->set_name(name); builder->AddAtom(atom); if (named_back_references_ == nullptr) { named_back_references_ = new (Z) ZoneGrowableArray(1); } named_back_references_->Add(atom); } return true; } void RegExpParser::PatchNamedBackReferences() { if (named_back_references_ == nullptr) return; if (named_captures_ == nullptr) { ReportError("Invalid named capture referenced"); return; } // Look up and patch the actual capture for each named back reference. // Currently O(n^2), optimize if necessary. for (intptr_t i = 0; i < named_back_references_->length(); i++) { RegExpBackReference* ref = named_back_references_->At(i); intptr_t index = GetNamedCaptureIndex(ref->name()); if (index < 0) { ReportError("Invalid named capture referenced"); UNREACHABLE(); } ref->set_capture(GetCapture(index)); } } RegExpCapture* RegExpParser::GetCapture(intptr_t index) { // The index for the capture groups are one-based. Its index in the list is // zero-based. const intptr_t know_captures = is_scanned_for_captures_ ? capture_count_ : captures_started_; ASSERT(index <= know_captures); if (captures_ == nullptr) { captures_ = new (Z) ZoneGrowableArray(know_captures); } while (captures_->length() < know_captures) { captures_->Add(new (Z) RegExpCapture(captures_->length() + 1)); } return captures_->At(index - 1); } RawArray* RegExpParser::CreateCaptureNameMap() { if (named_captures_ == nullptr || named_captures_->is_empty()) { return Array::null(); } const intptr_t len = named_captures_->length() * 2; const Array& array = Array::Handle(Array::New(len)); auto& name = String::Handle(); auto& smi = Smi::Handle(); for (intptr_t i = 0; i < named_captures_->length(); i++) { RegExpCapture* capture = named_captures_->At(i); name = String::FromUTF16(capture->name()->data(), capture->name()->length()); smi = Smi::New(capture->index()); array.SetAt(i * 2, name); array.SetAt(i * 2 + 1, smi); } return array.raw(); } bool RegExpParser::HasNamedCaptures() { if (has_named_captures_ || is_scanned_for_captures_) { return has_named_captures_; } ScanForCaptures(); ASSERT(is_scanned_for_captures_); return has_named_captures_; } bool RegExpParser::RegExpParserState::IsInsideCaptureGroup(intptr_t index) { for (RegExpParserState* s = this; s != nullptr; s = s->previous_state()) { if (s->group_type() != CAPTURE) continue; // Return true if we found the matching capture index. if (index == s->capture_index()) return true; // Abort if index is larger than what has been parsed up till this state. if (index > s->capture_index()) return false; } return false; } bool RegExpParser::RegExpParserState::IsInsideCaptureGroup( const RegExpCaptureName* name) { ASSERT(name != nullptr); for (RegExpParserState* s = this; s != nullptr; s = s->previous_state()) { if (s->capture_name() == nullptr) continue; if (IsSameName(s->capture_name(), name)) return true; } return false; } // QuantifierPrefix :: // { DecimalDigits } // { DecimalDigits , } // { DecimalDigits , DecimalDigits } // // Returns true if parsing succeeds, and set the min_out and max_out // values. Values are truncated to RegExpTree::kInfinity if they overflow. bool RegExpParser::ParseIntervalQuantifier(intptr_t* min_out, intptr_t* max_out) { ASSERT(current() == '{'); intptr_t start = position(); Advance(); intptr_t min = 0; if (!IsDecimalDigit(current())) { Reset(start); return false; } while (IsDecimalDigit(current())) { intptr_t next = current() - '0'; if (min > (RegExpTree::kInfinity - next) / 10) { // Overflow. Skip past remaining decimal digits and return -1. do { Advance(); } while (IsDecimalDigit(current())); min = RegExpTree::kInfinity; break; } min = 10 * min + next; Advance(); } intptr_t max = 0; if (current() == '}') { max = min; Advance(); } else if (current() == ',') { Advance(); if (current() == '}') { max = RegExpTree::kInfinity; Advance(); } else { while (IsDecimalDigit(current())) { intptr_t next = current() - '0'; if (max > (RegExpTree::kInfinity - next) / 10) { do { Advance(); } while (IsDecimalDigit(current())); max = RegExpTree::kInfinity; break; } max = 10 * max + next; Advance(); } if (current() != '}') { Reset(start); return false; } Advance(); } } else { Reset(start); return false; } *min_out = min; *max_out = max; return true; } uint32_t RegExpParser::ParseOctalLiteral() { ASSERT(('0' <= current() && current() <= '7') || current() == kEndMarker); // For compatibility with some other browsers (not all), we parse // up to three octal digits with a value below 256. uint32_t value = current() - '0'; Advance(); if ('0' <= current() && current() <= '7') { value = value * 8 + current() - '0'; Advance(); if (value < 32 && '0' <= current() && current() <= '7') { value = value * 8 + current() - '0'; Advance(); } } return value; } // Returns the value (0 .. 15) of a hexadecimal character c. // If c is not a legal hexadecimal character, returns a value < 0. static inline intptr_t HexValue(uint32_t c) { c -= '0'; if (static_cast(c) <= 9) return c; c = (c | 0x20) - ('a' - '0'); // detect 0x11..0x16 and 0x31..0x36. if (static_cast(c) <= 5) return c + 10; return -1; } bool RegExpParser::ParseHexEscape(intptr_t length, uint32_t* value) { intptr_t start = position(); uint32_t val = 0; bool done = false; for (intptr_t i = 0; !done; i++) { uint32_t c = current(); intptr_t d = HexValue(c); if (d < 0) { Reset(start); return false; } val = val * 16 + d; Advance(); if (i == length - 1) { done = true; } } *value = val; return true; } uint32_t RegExpParser::ParseClassCharacterEscape() { ASSERT(current() == '\\'); DEBUG_ASSERT(has_next() && !IsSpecialClassEscape(Next())); Advance(); switch (current()) { case 'b': Advance(); return '\b'; // ControlEscape :: one of // f n r t v case 'f': Advance(); return '\f'; case 'n': Advance(); return '\n'; case 'r': Advance(); return '\r'; case 't': Advance(); return '\t'; case 'v': Advance(); return '\v'; case 'c': { uint32_t controlLetter = Next(); uint32_t letter = controlLetter & ~('A' ^ 'a'); // For compatibility with JSC, inside a character class // we also accept digits and underscore as control characters. if ((controlLetter >= '0' && controlLetter <= '9') || controlLetter == '_' || (letter >= 'A' && letter <= 'Z')) { Advance(2); // Control letters mapped to ASCII control characters in the range // 0x00-0x1f. return controlLetter & 0x1f; } // We match JSC in reading the backslash as a literal // character instead of as starting an escape. return '\\'; } case '0': FALL_THROUGH; case '1': FALL_THROUGH; case '2': FALL_THROUGH; case '3': FALL_THROUGH; case '4': FALL_THROUGH; case '5': FALL_THROUGH; case '6': FALL_THROUGH; case '7': // For compatibility, we interpret a decimal escape that isn't // a back reference (and therefore either \0 or not valid according // to the specification) as a 1..3 digit octal character code. return ParseOctalLiteral(); case 'x': { Advance(); uint32_t value; if (ParseHexEscape(2, &value)) { return value; } // If \x is not followed by a two-digit hexadecimal, treat it // as an identity escape. return 'x'; } case 'u': { Advance(); uint32_t value; if (ParseHexEscape(4, &value)) { return value; } // If \u is not followed by a four-digit hexadecimal, treat it // as an identity escape. return 'u'; } default: { // Extended identity escape. We accept any character that hasn't // been matched by a more specific case, not just the subset required // by the ECMAScript specification. uint32_t result = current(); Advance(); return result; } } return 0; } CharacterRange RegExpParser::ParseClassAtom(uint16_t* char_class) { ASSERT(0 == *char_class); uint32_t first = current(); if (first == '\\') { switch (Next()) { case 'w': case 'W': case 'd': case 'D': case 's': case 'S': { *char_class = Next(); Advance(2); return CharacterRange::Singleton(0); // Return dummy value. } case kEndMarker: ReportError("\\ at end of pattern"); UNREACHABLE(); default: uint32_t c = ParseClassCharacterEscape(); return CharacterRange::Singleton(c); } } else { Advance(); return CharacterRange::Singleton(first); } } static const uint16_t kNoCharClass = 0; // Adds range or pre-defined character class to character ranges. // If char_class is not kInvalidClass, it's interpreted as a class // escape (i.e., 's' means whitespace, from '\s'). static inline void AddRangeOrEscape(ZoneGrowableArray* ranges, uint16_t char_class, CharacterRange range) { if (char_class != kNoCharClass) { CharacterRange::AddClassEscape(char_class, ranges); } else { ranges->Add(range); } } RegExpTree* RegExpParser::ParseCharacterClass() { static const char* kUnterminated = "Unterminated character class"; static const char* kRangeOutOfOrder = "Range out of order in character class"; ASSERT(current() == '['); Advance(); bool is_negated = false; if (current() == '^') { is_negated = true; Advance(); } ZoneGrowableArray* ranges = new (Z) ZoneGrowableArray(2); while (has_more() && current() != ']') { uint16_t char_class = kNoCharClass; CharacterRange first = ParseClassAtom(&char_class); if (current() == '-') { Advance(); if (current() == kEndMarker) { // If we reach the end we break out of the loop and let the // following code report an error. break; } else if (current() == ']') { AddRangeOrEscape(ranges, char_class, first); ranges->Add(CharacterRange::Singleton('-')); break; } uint16_t char_class_2 = kNoCharClass; CharacterRange next = ParseClassAtom(&char_class_2); if (char_class != kNoCharClass || char_class_2 != kNoCharClass) { // Either end is an escaped character class. Treat the '-' verbatim. AddRangeOrEscape(ranges, char_class, first); ranges->Add(CharacterRange::Singleton('-')); AddRangeOrEscape(ranges, char_class_2, next); continue; } if (first.from() > next.to()) { ReportError(kRangeOutOfOrder); UNREACHABLE(); } ranges->Add(CharacterRange::Range(first.from(), next.to())); } else { AddRangeOrEscape(ranges, char_class, first); } } if (!has_more()) { ReportError(kUnterminated); UNREACHABLE(); } Advance(); if (ranges->length() == 0) { ranges->Add(CharacterRange::Everything()); is_negated = !is_negated; } return new (Z) RegExpCharacterClass(ranges, is_negated); } // ---------------------------------------------------------------------------- // The Parser interface. void RegExpParser::ParseRegExp(const String& input, bool multiline, RegExpCompileData* result) { ASSERT(result != NULL); RegExpParser parser(input, &result->error, multiline); // Throws an exception if 'input' is not valid. RegExpTree* tree = parser.ParsePattern(); ASSERT(tree != NULL); ASSERT(result->error.IsNull()); result->tree = tree; intptr_t capture_count = parser.captures_started(); result->simple = tree->IsAtom() && parser.simple() && capture_count == 0; result->contains_anchor = parser.contains_anchor(); result->capture_name_map = parser.CreateCaptureNameMap(); result->capture_count = capture_count; } } // namespace dart