OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 import 'package:string_scanner/string_scanner.dart'; |
| 6 |
| 7 import 'token.dart'; |
| 8 |
| 9 /// A regular expression matching both whitespace and single-line comments. |
| 10 /// |
| 11 /// This will only match if consumes at least one character. |
| 12 final _whitespaceAndSingleLineComments = |
| 13 new RegExp(r"([ \t\n]+|//[^\n]*(\n|$))+"); |
| 14 |
| 15 /// A regular expression matching the body of a multi-line comment, after `/*` |
| 16 /// but before `*/` or a nested `/*`. |
| 17 /// |
| 18 /// This will only match if it consumes at least one character. |
| 19 final _multiLineCommentBody = new RegExp(r"([^/*]|/[^*]|\*[^/])+"); |
| 20 |
| 21 /// A regular expression matching a hyphenated identifier. |
| 22 /// |
| 23 /// This is like a standard Dart identifier, except that it can also contain |
| 24 /// hyphens. |
| 25 final _hyphenatedIdentifier = new RegExp(r"[a-zA-Z_-][a-zA-Z0-9_-]*"); |
| 26 |
| 27 /// A scanner that converts a boolean selector string into a stream of tokens. |
| 28 class Scanner { |
| 29 /// The underlying string scanner. |
| 30 final SpanScanner _scanner; |
| 31 |
| 32 /// The next token to emit. |
| 33 Token _next; |
| 34 |
| 35 /// Whether the scanner has emitted a [TokenType.endOfFile] token. |
| 36 bool _endOfFileEmitted = false; |
| 37 |
| 38 Scanner(String selector) |
| 39 : _scanner = new SpanScanner(selector); |
| 40 |
| 41 /// Returns the next token that will be returned by [next]. |
| 42 /// |
| 43 /// Throws a [StateError] if a [TokenType.endOfFile] token has already been |
| 44 /// consumed. |
| 45 Token peek() { |
| 46 if (_next == null) _next = _getNext(); |
| 47 return _next; |
| 48 } |
| 49 |
| 50 /// Consumes and returns the next token in the stream. |
| 51 /// |
| 52 /// Throws a [StateError] if a [TokenType.endOfFile] token has already been |
| 53 /// consumed. |
| 54 Token next() { |
| 55 var token = _next == null ? _getNext() : _next; |
| 56 _endOfFileEmitted = token.type == TokenType.endOfFile; |
| 57 _next = null; |
| 58 return token; |
| 59 } |
| 60 |
| 61 /// If the next token matches [type], consumes it and returns `true`; |
| 62 /// otherwise, returns `false`. |
| 63 /// |
| 64 /// Throws a [StateError] if a [TokenType.endOfFile] token has already been |
| 65 /// consumed. |
| 66 bool scan(TokenType type) { |
| 67 if (peek().type != type) return false; |
| 68 next(); |
| 69 return true; |
| 70 } |
| 71 |
| 72 /// Scan and return the next token in the stream. |
| 73 Token _getNext() { |
| 74 if (_endOfFileEmitted) throw new StateError("No more tokens."); |
| 75 |
| 76 _consumeWhitespace(); |
| 77 if (_scanner.isDone) { |
| 78 return new Token( |
| 79 TokenType.endOfFile, _scanner.spanFrom(_scanner.state)); |
| 80 } |
| 81 |
| 82 switch (_scanner.peekChar()) { |
| 83 case 0x28 /* ( */: return _scanOperator(TokenType.leftParen); |
| 84 case 0x29 /* ) */: return _scanOperator(TokenType.rightParen); |
| 85 case 0x3F /* ? */: return _scanOperator(TokenType.questionMark); |
| 86 case 0x3A /* : */: return _scanOperator(TokenType.colon); |
| 87 case 0x21 /* ! */: return _scanOperator(TokenType.not); |
| 88 case 0x7C /* | */: return _scanOr(); |
| 89 case 0x26 /* & */: return _scanAnd(); |
| 90 default: return _scanIdentifier(); |
| 91 } |
| 92 } |
| 93 |
| 94 /// Scans a single-character operator and returns a token of type [type]. |
| 95 /// |
| 96 /// This assumes that the caller has already verified that the next character |
| 97 /// is correct for the given operator. |
| 98 Token _scanOperator(TokenType type) { |
| 99 var start = _scanner.state; |
| 100 _scanner.readChar(); |
| 101 return new Token(type, _scanner.spanFrom(start)); |
| 102 } |
| 103 |
| 104 /// Scans a `||` operator and returns the appropriate token. |
| 105 /// |
| 106 /// This validates that the next two characters are `||`. |
| 107 Token _scanOr() { |
| 108 var start = _scanner.state; |
| 109 _scanner.expect("||"); |
| 110 return new Token(TokenType.or, _scanner.spanFrom(start)); |
| 111 } |
| 112 |
| 113 /// Scans a `&&` operator and returns the appropriate token. |
| 114 /// |
| 115 /// This validates that the next two characters are `&&`. |
| 116 Token _scanAnd() { |
| 117 var start = _scanner.state; |
| 118 _scanner.expect("&&"); |
| 119 return new Token(TokenType.and, _scanner.spanFrom(start)); |
| 120 } |
| 121 |
| 122 /// Scans and returns an identifier token. |
| 123 Token _scanIdentifier() { |
| 124 _scanner.expect(_hyphenatedIdentifier, name: "expression"); |
| 125 return new IdentifierToken(_scanner.lastMatch[0], _scanner.lastSpan); |
| 126 } |
| 127 |
| 128 /// Consumes all whitespace and comments immediately following the cursor's |
| 129 /// current position. |
| 130 void _consumeWhitespace() { |
| 131 while (_scanner.scan(_whitespaceAndSingleLineComments) || |
| 132 _multiLineComment()) { |
| 133 // Do nothing. |
| 134 } |
| 135 } |
| 136 |
| 137 /// Consumes a single multi-line comment. |
| 138 /// |
| 139 /// Returns whether or not a comment was consumed. |
| 140 bool _multiLineComment() { |
| 141 if (!_scanner.scan("/*")) return false; |
| 142 |
| 143 while (_scanner.scan(_multiLineCommentBody) || _multiLineComment()) { |
| 144 // Do nothing. |
| 145 } |
| 146 _scanner.expect("*/"); |
| 147 |
| 148 return true; |
| 149 } |
| 150 } |
OLD | NEW |