| // Copyright (c) 2015, 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. |
| |
| import 'dart:collection'; |
| |
| import 'package:analyzer/dart/ast/ast.dart'; |
| |
| // ASCII character codes. |
| |
| const _zero = 0x30; |
| const _nine = 0x39; |
| const _backslash = 0x5C; |
| const _openCurly = 0x7B; |
| const _closeCurly = 0x7D; |
| const _capitalA = 0x41; |
| const _capitalZ = 0x5A; |
| const _a = 0x61; |
| const _n = 0x6E; |
| const _r = 0x72; |
| const _f = 0x66; |
| const _b = 0x62; |
| const _t = 0x74; |
| const _u = 0x75; |
| const _v = 0x76; |
| const _x = 0x78; |
| const _z = 0x7A; |
| const _newline = 0xA; |
| const _carriageReturn = 0xD; |
| const _formFeed = 0xC; |
| const _backspace = 0x8; |
| const _tab = 0x9; |
| const _verticalTab = 0xB; |
| |
| /// An iterator over the runes in the value of a [StringLiteral]. |
| /// |
| /// In addition to exposing the values of the runes themselves, this also |
| /// exposes the offset of the current rune in the Dart source file. |
| class StringLiteralIterator implements Iterator<int> { |
| @override |
| int get current => _current!; |
| int? _current; |
| |
| /// The offset of the beginning of [current] in the Dart source file that |
| /// contains the string literal. |
| /// |
| /// Before iteration begins, this points to the character before the first |
| /// rune. |
| int get offset => _offset; |
| late int _offset; |
| |
| /// The offset of the next rune. |
| /// |
| /// This isn't necessarily just `offset + 1`, since a single rune may be |
| /// represented by multiple characters in the source file, or a string literal |
| /// may be composed of several adjacent string literals. |
| int? _nextOffset; |
| |
| /// All [SimpleStringLiteral]s that compose the input literal. |
| /// |
| /// If the input literal is itself a [SimpleStringLiteral], this just contains |
| /// that literal; otherwise, the literal is an [AdjacentStrings], and this |
| /// contains its component literals. |
| final _strings = Queue<SimpleStringLiteral>(); |
| |
| /// Whether this is a raw string that begins with `r`. |
| /// |
| /// This is necessary for knowing how to parse escape sequences. |
| bool? _isRaw; |
| |
| /// The iterator over the runes in the Dart source file. |
| /// |
| /// When switching to a new string in [_strings], this is updated to point to |
| /// that string's component runes. |
| Iterator<int>? _runes; |
| |
| /// The result of the last call to `_runes.moveNext`. |
| bool _runesHasCurrent = false; |
| |
| /// Creates a new [StringLiteralIterator] iterating over the contents of |
| /// [literal]. |
| /// |
| /// Throws an [ArgumentError] if [literal] contains interpolated strings. |
| StringLiteralIterator(StringLiteral literal) { |
| if (literal is StringInterpolation) { |
| throw ArgumentError("Can't iterate over an interpolated string."); |
| } else if (literal is SimpleStringLiteral) { |
| _strings.add(literal); |
| } else { |
| assert(literal is AdjacentStrings); |
| |
| for (var string in (literal as AdjacentStrings).strings) { |
| if (string is StringInterpolation) { |
| throw ArgumentError("Can't iterate over an interpolated string."); |
| } |
| _strings.add(string as SimpleStringLiteral); |
| } |
| } |
| |
| _offset = _strings.first.contentsOffset - 1; |
| } |
| |
| @override |
| bool moveNext() { |
| // If we're at beginning of a [SimpleStringLiteral], move forward until |
| // there's actually text to consume. |
| while (_runes == null || !_runesHasCurrent) { |
| if (_strings.isEmpty) { |
| // Move the offset past the end of the text. |
| _offset = _nextOffset!; |
| _current = null; |
| return false; |
| } |
| |
| var string = _strings.removeFirst(); |
| var start = string.contentsOffset - string.offset; |
| |
| // Compensate for the opening and closing quotes. |
| var end = start + |
| string.literal.lexeme.length - |
| 2 * (string.isMultiline ? 3 : 1) - |
| (string.isRaw ? 1 : 0); |
| var text = string.literal.lexeme.substring(start, end); |
| |
| _nextOffset = string.contentsOffset; |
| _isRaw = string.isRaw; |
| _runes = text.runes.iterator; |
| _runesHasCurrent = _runes!.moveNext(); |
| } |
| |
| _offset = _nextOffset!; |
| _current = _nextRune(); |
| if (_current != null) return true; |
| |
| // If we encounter a parse failure, stop moving forward immediately. |
| _strings.clear(); |
| return false; |
| } |
| |
| /// Consume and return the next rune. |
| int? _nextRune() { |
| if (_isRaw! || _runes!.current != _backslash) { |
| var rune = _runes!.current; |
| _moveRunesNext(); |
| return (rune < 0) ? null : rune; |
| } |
| |
| if (!_moveRunesNext()) return null; |
| return _parseEscapeSequence(); |
| } |
| |
| /// Parse an escape sequence in the underlying Dart text. |
| /// |
| /// This assumes that a backslash has already been consumed. It leaves the |
| /// [_runes] cursor on the first character after the escape sequence. |
| int? _parseEscapeSequence() { |
| switch (_runes!.current) { |
| case _n: |
| _moveRunesNext(); |
| return _newline; |
| case _r: |
| _moveRunesNext(); |
| return _carriageReturn; |
| case _f: |
| _moveRunesNext(); |
| return _formFeed; |
| case _b: |
| _moveRunesNext(); |
| return _backspace; |
| case _t: |
| _moveRunesNext(); |
| return _tab; |
| case _v: |
| _moveRunesNext(); |
| return _verticalTab; |
| case _x: |
| if (!_moveRunesNext()) return null; |
| return _parseHex(2); |
| case _u: |
| if (!_moveRunesNext()) return null; |
| if (_runes!.current != _openCurly) return _parseHex(4); |
| if (!_moveRunesNext()) return null; |
| |
| var number = _parseHexSequence(); |
| if (_runes!.current != _closeCurly) return null; |
| if (!_moveRunesNext()) return null; |
| return number; |
| default: |
| var rune = _runes!.current; |
| _moveRunesNext(); |
| return rune; |
| } |
| } |
| |
| /// Parse a variable-length sequence of hexadecimal digits and returns their |
| /// value as an [int]. |
| /// |
| /// This parses digits as they appear in a unicode escape sequence: one to six |
| /// hex digits. |
| int? _parseHexSequence() { |
| var number = _parseHexDigit(_runes!.current); |
| if (number == null) return null; |
| if (!_moveRunesNext()) return null; |
| |
| for (var i = 0; i < 5; i++) { |
| var digit = _parseHexDigit(_runes!.current); |
| if (digit == null) break; |
| number = number! * 16 + digit; |
| if (!_moveRunesNext()) return null; |
| } |
| |
| return number; |
| } |
| |
| /// Parses [digits] hexadecimal digits and returns their value as an [int]. |
| int? _parseHex(int digits) { |
| var number = 0; |
| for (var i = 0; i < digits; i++) { |
| if (_runes!.current == -1) return null; |
| var digit = _parseHexDigit(_runes!.current); |
| if (digit == null) return null; |
| number = number * 16 + digit; |
| _moveRunesNext(); |
| } |
| return number; |
| } |
| |
| /// Parses a single hexadecimal digit. |
| int? _parseHexDigit(int rune) { |
| if (rune < _zero) return null; |
| if (rune <= _nine) return rune - _zero; |
| if (rune < _capitalA) return null; |
| if (rune <= _capitalZ) return 10 + rune - _capitalA; |
| if (rune < _a) return null; |
| if (rune <= _z) return 10 + rune - _a; |
| return null; |
| } |
| |
| /// Move [_runes] to the next rune and update [_nextOffset]. |
| bool _moveRunesNext() { |
| var result = _runesHasCurrent = _runes!.moveNext(); |
| _nextOffset = _nextOffset! + 1; |
| return result; |
| } |
| } |