Line data Source code
1 : // Copyright 2016 the V8 project authors. All rights reserved.
2 : // Use of this source code is governed by a BSD-style license that can be
3 : // found in the LICENSE file.
4 :
5 : #ifndef V8_REGEXP_REGEXP_AST_H_
6 : #define V8_REGEXP_REGEXP_AST_H_
7 :
8 : #include "src/objects.h"
9 : #include "src/utils.h"
10 : #include "src/zone/zone-containers.h"
11 : #include "src/zone/zone.h"
12 :
13 : namespace v8 {
14 : namespace internal {
15 :
16 : #define FOR_EACH_REG_EXP_TREE_TYPE(VISIT) \
17 : VISIT(Disjunction) \
18 : VISIT(Alternative) \
19 : VISIT(Assertion) \
20 : VISIT(CharacterClass) \
21 : VISIT(Atom) \
22 : VISIT(Quantifier) \
23 : VISIT(Capture) \
24 : VISIT(Group) \
25 : VISIT(Lookaround) \
26 : VISIT(BackReference) \
27 : VISIT(Empty) \
28 : VISIT(Text)
29 :
30 : #define FORWARD_DECLARE(Name) class RegExp##Name;
31 : FOR_EACH_REG_EXP_TREE_TYPE(FORWARD_DECLARE)
32 : #undef FORWARD_DECLARE
33 :
34 : class RegExpCompiler;
35 : class RegExpNode;
36 : class RegExpTree;
37 :
38 :
39 2114 : class RegExpVisitor BASE_EMBEDDED {
40 : public:
41 0 : virtual ~RegExpVisitor() {}
42 : #define MAKE_CASE(Name) \
43 : virtual void* Visit##Name(RegExp##Name*, void* data) = 0;
44 : FOR_EACH_REG_EXP_TREE_TYPE(MAKE_CASE)
45 : #undef MAKE_CASE
46 : };
47 :
48 :
49 : // A simple closed interval.
50 : class Interval {
51 : public:
52 : Interval() : from_(kNone), to_(kNone) {}
53 272129 : Interval(int from, int to) : from_(from), to_(to) {}
54 : Interval Union(Interval that) {
55 48649 : if (that.from_ == kNone)
56 : return *this;
57 5495 : else if (from_ == kNone)
58 : return that;
59 : else
60 : return Interval(Min(from_, that.from_), Max(to_, that.to_));
61 : }
62 76942 : bool Contains(int value) { return (from_ <= value) && (value <= to_); }
63 : bool is_empty() { return from_ == kNone; }
64 : int from() const { return from_; }
65 : int to() const { return to_; }
66 : static Interval Empty() { return Interval(); }
67 : static const int kNone = -1;
68 :
69 : private:
70 : int from_;
71 : int to_;
72 : };
73 :
74 :
75 : // Represents code units in the range from from_ to to_, both ends are
76 : // inclusive.
77 : class CharacterRange {
78 : public:
79 : CharacterRange() : from_(0), to_(0) {}
80 : // For compatibility with the CHECK_OK macro
81 : CharacterRange(void* null) { DCHECK_NULL(null); } // NOLINT
82 : static void AddClassEscape(uc16 type, ZoneList<CharacterRange>* ranges,
83 : Zone* zone);
84 : // Add class escapes. Add case equivalent closure for \w and \W if necessary.
85 : static void AddClassEscape(uc16 type, ZoneList<CharacterRange>* ranges,
86 : bool add_unicode_case_equivalents, Zone* zone);
87 : static Vector<const int> GetWordBounds();
88 : static inline CharacterRange Singleton(uc32 value) {
89 : return CharacterRange(value, value);
90 : }
91 : static inline CharacterRange Range(uc32 from, uc32 to) {
92 : DCHECK(0 <= from && to <= String::kMaxCodePoint);
93 : DCHECK(static_cast<uint32_t>(from) <= static_cast<uint32_t>(to));
94 : return CharacterRange(from, to);
95 : }
96 : static inline CharacterRange Everything() {
97 : return CharacterRange(0, String::kMaxCodePoint);
98 : }
99 60787 : static inline ZoneList<CharacterRange>* List(Zone* zone,
100 : CharacterRange range) {
101 : ZoneList<CharacterRange>* list =
102 60787 : new (zone) ZoneList<CharacterRange>(1, zone);
103 : list->Add(range, zone);
104 60787 : return list;
105 : }
106 24893 : bool Contains(uc32 i) { return from_ <= i && i <= to_; }
107 : uc32 from() const { return from_; }
108 : void set_from(uc32 value) { from_ = value; }
109 : uc32 to() const { return to_; }
110 : void set_to(uc32 value) { to_ = value; }
111 : bool is_valid() { return from_ <= to_; }
112 297971 : bool IsEverything(uc16 max) { return from_ == 0 && to_ >= max; }
113 : bool IsSingleton() { return (from_ == to_); }
114 : static void AddCaseEquivalents(Isolate* isolate, Zone* zone,
115 : ZoneList<CharacterRange>* ranges,
116 : bool is_one_byte);
117 : // Whether a range list is in canonical form: Ranges ordered by from value,
118 : // and ranges non-overlapping and non-adjacent.
119 : static bool IsCanonical(ZoneList<CharacterRange>* ranges);
120 : // Convert range list to canonical form. The characters covered by the ranges
121 : // will still be the same, but no character is in more than one range, and
122 : // adjacent ranges are merged. The resulting list may be shorter than the
123 : // original, but cannot be longer.
124 : static void Canonicalize(ZoneList<CharacterRange>* ranges);
125 : // Negate the contents of a character range in canonical form.
126 : static void Negate(ZoneList<CharacterRange>* src,
127 : ZoneList<CharacterRange>* dst, Zone* zone);
128 : static const int kStartMarker = (1 << 24);
129 : static const int kPayloadMask = (1 << 24) - 1;
130 :
131 : private:
132 : CharacterRange(uc32 from, uc32 to) : from_(from), to_(to) {}
133 :
134 : uc32 from_;
135 : uc32 to_;
136 : };
137 :
138 :
139 : class CharacterSet final BASE_EMBEDDED {
140 : public:
141 : explicit CharacterSet(uc16 standard_set_type)
142 88395 : : ranges_(NULL), standard_set_type_(standard_set_type) {}
143 : explicit CharacterSet(ZoneList<CharacterRange>* ranges)
144 292465 : : ranges_(ranges), standard_set_type_(0) {}
145 : ZoneList<CharacterRange>* ranges(Zone* zone);
146 : uc16 standard_set_type() { return standard_set_type_; }
147 : void set_standard_set_type(uc16 special_set_type) {
148 16583 : standard_set_type_ = special_set_type;
149 : }
150 : bool is_standard() { return standard_set_type_ != 0; }
151 : void Canonicalize();
152 :
153 : private:
154 : ZoneList<CharacterRange>* ranges_;
155 : // If non-zero, the value represents a standard set (e.g., all whitespace
156 : // characters) without having to expand the ranges.
157 : uc16 standard_set_type_;
158 : };
159 :
160 :
161 : class TextElement final BASE_EMBEDDED {
162 : public:
163 : enum TextType { ATOM, CHAR_CLASS };
164 :
165 : static TextElement Atom(RegExpAtom* atom);
166 : static TextElement CharClass(RegExpCharacterClass* char_class);
167 :
168 : int cp_offset() const { return cp_offset_; }
169 391123 : void set_cp_offset(int cp_offset) { cp_offset_ = cp_offset; }
170 : int length() const;
171 :
172 : TextType text_type() const { return text_type_; }
173 :
174 : RegExpTree* tree() const { return tree_; }
175 :
176 8627839 : RegExpAtom* atom() const {
177 : DCHECK(text_type() == ATOM);
178 : return reinterpret_cast<RegExpAtom*>(tree());
179 : }
180 :
181 : RegExpCharacterClass* char_class() const {
182 : DCHECK(text_type() == CHAR_CLASS);
183 : return reinterpret_cast<RegExpCharacterClass*>(tree());
184 : }
185 :
186 : private:
187 : TextElement(TextType text_type, RegExpTree* tree)
188 : : cp_offset_(-1), text_type_(text_type), tree_(tree) {}
189 :
190 : int cp_offset_;
191 : TextType text_type_;
192 : RegExpTree* tree_;
193 : };
194 :
195 :
196 7731696 : class RegExpTree : public ZoneObject {
197 : public:
198 : static const int kInfinity = kMaxInt;
199 0 : virtual ~RegExpTree() {}
200 : virtual void* Accept(RegExpVisitor* visitor, void* data) = 0;
201 : virtual RegExpNode* ToNode(RegExpCompiler* compiler,
202 : RegExpNode* on_success) = 0;
203 1690281 : virtual bool IsTextElement() { return false; }
204 88744 : virtual bool IsAnchoredAtStart() { return false; }
205 89490 : virtual bool IsAnchoredAtEnd() { return false; }
206 : virtual int min_match() = 0;
207 : virtual int max_match() = 0;
208 : // Returns the interval of registers used for captures within this
209 : // expression.
210 1657715 : virtual Interval CaptureRegisters() { return Interval::Empty(); }
211 : virtual void AppendToText(RegExpText* text, Zone* zone);
212 : std::ostream& Print(std::ostream& os, Zone* zone); // NOLINT
213 : #define MAKE_ASTYPE(Name) \
214 : virtual RegExp##Name* As##Name(); \
215 : virtual bool Is##Name();
216 : FOR_EACH_REG_EXP_TREE_TYPE(MAKE_ASTYPE)
217 : #undef MAKE_ASTYPE
218 : };
219 :
220 :
221 0 : class RegExpDisjunction final : public RegExpTree {
222 : public:
223 : explicit RegExpDisjunction(ZoneList<RegExpTree*>* alternatives);
224 : void* Accept(RegExpVisitor* visitor, void* data) override;
225 : RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success) override;
226 : RegExpDisjunction* AsDisjunction() override;
227 : Interval CaptureRegisters() override;
228 : bool IsDisjunction() override;
229 : bool IsAnchoredAtStart() override;
230 : bool IsAnchoredAtEnd() override;
231 32051 : int min_match() override { return min_match_; }
232 35443 : int max_match() override { return max_match_; }
233 : ZoneList<RegExpTree*>* alternatives() { return alternatives_; }
234 :
235 : private:
236 : bool SortConsecutiveAtoms(RegExpCompiler* compiler);
237 : void RationalizeConsecutiveAtoms(RegExpCompiler* compiler);
238 : void FixSingleCharacterDisjunctions(RegExpCompiler* compiler);
239 : ZoneList<RegExpTree*>* alternatives_;
240 : int min_match_;
241 : int max_match_;
242 : };
243 :
244 :
245 0 : class RegExpAlternative final : public RegExpTree {
246 : public:
247 : explicit RegExpAlternative(ZoneList<RegExpTree*>* nodes);
248 : void* Accept(RegExpVisitor* visitor, void* data) override;
249 : RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success) override;
250 : RegExpAlternative* AsAlternative() override;
251 : Interval CaptureRegisters() override;
252 : bool IsAlternative() override;
253 : bool IsAnchoredAtStart() override;
254 : bool IsAnchoredAtEnd() override;
255 47909 : int min_match() override { return min_match_; }
256 78375 : int max_match() override { return max_match_; }
257 : ZoneList<RegExpTree*>* nodes() { return nodes_; }
258 :
259 : private:
260 : ZoneList<RegExpTree*>* nodes_;
261 : int min_match_;
262 : int max_match_;
263 : };
264 :
265 :
266 0 : class RegExpAssertion final : public RegExpTree {
267 : public:
268 : enum AssertionType {
269 : START_OF_LINE,
270 : START_OF_INPUT,
271 : END_OF_LINE,
272 : END_OF_INPUT,
273 : BOUNDARY,
274 : NON_BOUNDARY
275 : };
276 25111 : explicit RegExpAssertion(AssertionType type) : assertion_type_(type) {}
277 : void* Accept(RegExpVisitor* visitor, void* data) override;
278 : RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success) override;
279 : RegExpAssertion* AsAssertion() override;
280 : bool IsAssertion() override;
281 : bool IsAnchoredAtStart() override;
282 : bool IsAnchoredAtEnd() override;
283 24735 : int min_match() override { return 0; }
284 25789 : int max_match() override { return 0; }
285 : AssertionType assertion_type() { return assertion_type_; }
286 :
287 : private:
288 : AssertionType assertion_type_;
289 : };
290 :
291 :
292 0 : class RegExpCharacterClass final : public RegExpTree {
293 : public:
294 : // NEGATED: The character class is negated and should match everything but
295 : // the specified ranges.
296 : // CONTAINS_SPLIT_SURROGATE: The character class contains part of a split
297 : // surrogate and should not be unicode-desugared (crbug.com/641091).
298 : enum Flag {
299 : NEGATED = 1 << 0,
300 : CONTAINS_SPLIT_SURROGATE = 1 << 1,
301 : };
302 : typedef base::Flags<Flag> Flags;
303 :
304 : explicit RegExpCharacterClass(ZoneList<CharacterRange>* ranges,
305 : Flags flags = Flags())
306 584930 : : set_(ranges), flags_(flags) {}
307 88395 : explicit RegExpCharacterClass(uc16 type) : set_(type), flags_(0) {}
308 : void* Accept(RegExpVisitor* visitor, void* data) override;
309 : RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success) override;
310 : RegExpCharacterClass* AsCharacterClass() override;
311 : bool IsCharacterClass() override;
312 219689 : bool IsTextElement() override { return true; }
313 279787 : int min_match() override { return 1; }
314 : // The character class may match two code units for unicode regexps.
315 : // TODO(yangguo): we should split this class for usage in TextElement, and
316 : // make max_match() dependent on the character class content.
317 153220 : int max_match() override { return 2; }
318 : void AppendToText(RegExpText* text, Zone* zone) override;
319 : CharacterSet character_set() { return set_; }
320 : // TODO(lrn): Remove need for complex version if is_standard that
321 : // recognizes a mangled standard set and just do { return set_.is_special(); }
322 : bool is_standard(Zone* zone);
323 : // Returns a value representing the standard character set if is_standard()
324 : // returns true.
325 : // Currently used values are:
326 : // s : unicode whitespace
327 : // S : unicode non-whitespace
328 : // w : ASCII word character (digit, letter, underscore)
329 : // W : non-ASCII word character
330 : // d : ASCII digit
331 : // D : non-ASCII digit
332 : // . : non-newline
333 : // * : All characters, for advancing unanchored regexp
334 23859 : uc16 standard_type() { return set_.standard_set_type(); }
335 941221 : ZoneList<CharacterRange>* ranges(Zone* zone) { return set_.ranges(zone); }
336 : bool is_negated() const { return (flags_ & NEGATED) != 0; }
337 : bool contains_split_surrogate() const {
338 : return (flags_ & CONTAINS_SPLIT_SURROGATE) != 0;
339 : }
340 :
341 : private:
342 : CharacterSet set_;
343 : const Flags flags_;
344 : };
345 :
346 :
347 0 : class RegExpAtom final : public RegExpTree {
348 : public:
349 2758251 : explicit RegExpAtom(Vector<const uc16> data) : data_(data) {}
350 : void* Accept(RegExpVisitor* visitor, void* data) override;
351 : RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success) override;
352 : RegExpAtom* AsAtom() override;
353 : bool IsAtom() override;
354 593 : bool IsTextElement() override { return true; }
355 5512937 : int min_match() override { return data_.length(); }
356 4742658 : int max_match() override { return data_.length(); }
357 : void AppendToText(RegExpText* text, Zone* zone) override;
358 : Vector<const uc16> data() { return data_; }
359 9072374 : int length() { return data_.length(); }
360 :
361 : private:
362 : Vector<const uc16> data_;
363 : };
364 :
365 :
366 0 : class RegExpText final : public RegExpTree {
367 : public:
368 5990 : explicit RegExpText(Zone* zone) : elements_(2, zone), length_(0) {}
369 : void* Accept(RegExpVisitor* visitor, void* data) override;
370 : RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success) override;
371 : RegExpText* AsText() override;
372 : bool IsText() override;
373 0 : bool IsTextElement() override { return true; }
374 3705 : int min_match() override { return length_; }
375 5728 : int max_match() override { return length_; }
376 : void AppendToText(RegExpText* text, Zone* zone) override;
377 20323 : void AddElement(TextElement elm, Zone* zone) {
378 : elements_.Add(elm, zone);
379 20323 : length_ += elm.length();
380 20323 : }
381 : ZoneList<TextElement>* elements() { return &elements_; }
382 :
383 : private:
384 : ZoneList<TextElement> elements_;
385 : int length_;
386 : };
387 :
388 :
389 0 : class RegExpQuantifier final : public RegExpTree {
390 : public:
391 : enum QuantifierType { GREEDY, NON_GREEDY, POSSESSIVE };
392 2300978 : RegExpQuantifier(int min, int max, QuantifierType type, RegExpTree* body)
393 : : body_(body),
394 : min_(min),
395 : max_(max),
396 2300978 : min_match_(min * body->min_match()),
397 4601956 : quantifier_type_(type) {
398 2300978 : if (max > 0 && body->max_match() > kInfinity / max) {
399 46906 : max_match_ = kInfinity;
400 : } else {
401 2254072 : max_match_ = max * body->max_match();
402 : }
403 2300978 : }
404 : void* Accept(RegExpVisitor* visitor, void* data) override;
405 : RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success) override;
406 : static RegExpNode* ToNode(int min, int max, bool is_greedy, RegExpTree* body,
407 : RegExpCompiler* compiler, RegExpNode* on_success,
408 : bool not_at_start = false);
409 : RegExpQuantifier* AsQuantifier() override;
410 : Interval CaptureRegisters() override;
411 : bool IsQuantifier() override;
412 2296749 : int min_match() override { return min_match_; }
413 2302966 : int max_match() override { return max_match_; }
414 : int min() { return min_; }
415 : int max() { return max_; }
416 : bool is_possessive() { return quantifier_type_ == POSSESSIVE; }
417 : bool is_non_greedy() { return quantifier_type_ == NON_GREEDY; }
418 1540393 : bool is_greedy() { return quantifier_type_ == GREEDY; }
419 : RegExpTree* body() { return body_; }
420 :
421 : private:
422 : RegExpTree* body_;
423 : int min_;
424 : int max_;
425 : int min_match_;
426 : int max_match_;
427 : QuantifierType quantifier_type_;
428 : };
429 :
430 :
431 0 : class RegExpCapture final : public RegExpTree {
432 : public:
433 : explicit RegExpCapture(int index)
434 1644385 : : body_(NULL), index_(index), name_(nullptr) {}
435 : void* Accept(RegExpVisitor* visitor, void* data) override;
436 : RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success) override;
437 : static RegExpNode* ToNode(RegExpTree* body, int index,
438 : RegExpCompiler* compiler, RegExpNode* on_success);
439 : RegExpCapture* AsCapture() override;
440 : bool IsAnchoredAtStart() override;
441 : bool IsAnchoredAtEnd() override;
442 : Interval CaptureRegisters() override;
443 : bool IsCapture() override;
444 88105 : int min_match() override { return body_->min_match(); }
445 108677 : int max_match() override { return body_->max_match(); }
446 : RegExpTree* body() { return body_; }
447 1644385 : void set_body(RegExpTree* body) { body_ = body; }
448 : int index() { return index_; }
449 : const ZoneVector<uc16>* name() const { return name_; }
450 1658 : void set_name(const ZoneVector<uc16>* name) { name_ = name; }
451 138326 : static int StartRegister(int index) { return index * 2; }
452 145580 : static int EndRegister(int index) { return index * 2 + 1; }
453 :
454 : private:
455 : RegExpTree* body_;
456 : int index_;
457 : const ZoneVector<uc16>* name_;
458 : };
459 :
460 0 : class RegExpGroup final : public RegExpTree {
461 : public:
462 34923 : explicit RegExpGroup(RegExpTree* body) : body_(body) {}
463 : void* Accept(RegExpVisitor* visitor, void* data) override;
464 13196 : RegExpNode* ToNode(RegExpCompiler* compiler,
465 : RegExpNode* on_success) override {
466 13196 : return body_->ToNode(compiler, on_success);
467 : }
468 : RegExpGroup* AsGroup() override;
469 311 : bool IsAnchoredAtStart() override { return body_->IsAnchoredAtStart(); }
470 299 : bool IsAnchoredAtEnd() override { return body_->IsAnchoredAtEnd(); }
471 : bool IsGroup() override;
472 39443 : int min_match() override { return body_->min_match(); }
473 49956 : int max_match() override { return body_->max_match(); }
474 16723 : Interval CaptureRegisters() override { return body_->CaptureRegisters(); }
475 : RegExpTree* body() { return body_; }
476 :
477 : private:
478 : RegExpTree* body_;
479 : };
480 :
481 0 : class RegExpLookaround final : public RegExpTree {
482 : public:
483 : enum Type { LOOKAHEAD, LOOKBEHIND };
484 :
485 : RegExpLookaround(RegExpTree* body, bool is_positive, int capture_count,
486 : int capture_from, Type type)
487 : : body_(body),
488 : is_positive_(is_positive),
489 : capture_count_(capture_count),
490 : capture_from_(capture_from),
491 4471 : type_(type) {}
492 :
493 : void* Accept(RegExpVisitor* visitor, void* data) override;
494 : RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success) override;
495 : RegExpLookaround* AsLookaround() override;
496 : Interval CaptureRegisters() override;
497 : bool IsLookaround() override;
498 : bool IsAnchoredAtStart() override;
499 4000 : int min_match() override { return 0; }
500 5688 : int max_match() override { return 0; }
501 : RegExpTree* body() { return body_; }
502 : bool is_positive() { return is_positive_; }
503 : int capture_count() { return capture_count_; }
504 : int capture_from() { return capture_from_; }
505 : Type type() { return type_; }
506 :
507 : class Builder {
508 : public:
509 : Builder(bool is_positive, RegExpNode* on_success,
510 : int stack_pointer_register, int position_register,
511 : int capture_register_count = 0, int capture_register_start = 0);
512 : RegExpNode* on_match_success() { return on_match_success_; }
513 : RegExpNode* ForMatch(RegExpNode* match);
514 :
515 : private:
516 : bool is_positive_;
517 : RegExpNode* on_match_success_;
518 : RegExpNode* on_success_;
519 : int stack_pointer_register_;
520 : int position_register_;
521 : };
522 :
523 : private:
524 : RegExpTree* body_;
525 : bool is_positive_;
526 : int capture_count_;
527 : int capture_from_;
528 : Type type_;
529 : };
530 :
531 :
532 0 : class RegExpBackReference final : public RegExpTree {
533 : public:
534 432 : RegExpBackReference() : capture_(nullptr), name_(nullptr) {}
535 : explicit RegExpBackReference(RegExpCapture* capture)
536 6070 : : capture_(capture), name_(nullptr) {}
537 : void* Accept(RegExpVisitor* visitor, void* data) override;
538 : RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success) override;
539 : RegExpBackReference* AsBackReference() override;
540 : bool IsBackReference() override;
541 6437 : int min_match() override { return 0; }
542 : // The back reference may be recursive, e.g. /(\2)(\1)/. To avoid infinite
543 : // recursion, we give up. Ignorance is bliss.
544 8922 : int max_match() override { return kInfinity; }
545 3510 : int index() { return capture_->index(); }
546 : RegExpCapture* capture() { return capture_; }
547 278 : void set_capture(RegExpCapture* capture) { capture_ = capture; }
548 : const ZoneVector<uc16>* name() const { return name_; }
549 432 : void set_name(const ZoneVector<uc16>* name) { name_ = name; }
550 :
551 : private:
552 : RegExpCapture* capture_;
553 : const ZoneVector<uc16>* name_;
554 : };
555 :
556 :
557 0 : class RegExpEmpty final : public RegExpTree {
558 : public:
559 461862 : RegExpEmpty() {}
560 : void* Accept(RegExpVisitor* visitor, void* data) override;
561 : RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success) override;
562 : RegExpEmpty* AsEmpty() override;
563 : bool IsEmpty() override;
564 2377 : int min_match() override { return 0; }
565 2684 : int max_match() override { return 0; }
566 : };
567 :
568 : } // namespace internal
569 : } // namespace v8
570 :
571 : #endif // V8_REGEXP_REGEXP_AST_H_
|