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