Chromium Code Reviews| Index: lib/src/backend/platform_selector/scanner.dart |
| diff --git a/lib/src/backend/platform_selector/scanner.dart b/lib/src/backend/platform_selector/scanner.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..fac4925b920f422e494f12e4d801e25426da90b3 |
| --- /dev/null |
| +++ b/lib/src/backend/platform_selector/scanner.dart |
| @@ -0,0 +1,144 @@ |
| +// 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. |
| + |
| +library unittest.backend.platform_selector.scanner; |
| + |
| +import 'package:string_scanner/string_scanner.dart'; |
| + |
| +import 'token.dart'; |
| + |
| +/// A regular expression matching both whitespace and single-line comments. |
| +/// |
| +/// This will only match if consumes at least one character. |
| +final _whitespaceAndSingleLineComments = |
| + new RegExp(r"([ \t\n]+|//[^\n]*(\n|$))+"); |
| + |
| +/// A regular expression matching the body of a multi-line comment, after `/*` |
| +/// but before `*/` or a nested `/*`. |
| +/// |
| +/// This will only match if it consumes at least one character. |
| +final _multiLineCommentBody = new RegExp(r"([^/*]|/[^*]|\*[^/])+"); |
| + |
| +/// A regular expression matching an identifier. |
| +/// |
| +/// Unlike standard Dart identifiers, platform selector identifiers may |
| +/// contain dashes for consistency with command-line arguments. |
| +final _identifier = new RegExp(r"[a-zA-Z_-][a-zA-Z0-9_-]*"); |
| + |
| +/// A scanner that converts a platform selector string into a stream of |
| +/// tokens. |
| +class Scanner { |
| + /// The underlying string scanner. |
| + final SpanScanner _scanner; |
| + |
| + /// The next token to emit. |
| + Token _next; |
| + |
| + /// Whether the scanner has emitted a [TokenType.endOfFile] token. |
| + bool _endOfFileEmitted = false; |
| + |
| + Scanner(String selector) |
| + : _scanner = new SpanScanner(selector); |
| + |
| + /// Returns the next token that will be returned by [next]. |
| + /// |
| + /// Throws a [StateError] if [next] has already returned a |
| + /// [TokenType.endOfFile] token. |
| + Token peek() { |
| + if (_next == null) _next = _getNext(); |
| + return _next; |
| + } |
| + |
| + /// Consumes and returns the next token in the stream. |
| + /// |
| + /// Throws a [StateError] if this has already returned a |
| + /// [TokenType.endOfFile] token. |
| + Token next() { |
| + var token = _next == null ? _getNext() : _next; |
| + _endOfFileEmitted = token.type == TokenType.endOfFile; |
| + _next = null; |
| + return token; |
| + } |
| + |
| + /// Scan and return the next token in the stream. |
| + Token _getNext() { |
| + if (_endOfFileEmitted) { |
| + throw new StateError("No more tokens."); |
|
Bob Nystrom
2015/03/11 20:05:52
Nit: you could make this a one-line if if you want
nweiz
2015/03/12 19:48:57
Done.
|
| + } |
| + |
| + _consumeWhitespace(); |
| + if (_scanner.isDone) { |
| + return new Token( |
| + TokenType.endOfFile, _scanner.spanFrom(_scanner.state)); |
| + } |
| + |
| + switch (_scanner.peekChar()) { |
| + case 0x28 /* ( */: return _scanOperator(TokenType.leftParen); |
| + case 0x29 /* ) */: return _scanOperator(TokenType.rightParen); |
| + case 0x3F /* ? */: return _scanOperator(TokenType.questionMark); |
| + case 0x3A /* : */: return _scanOperator(TokenType.colon); |
| + case 0x21 /* ! */: return _scanOperator(TokenType.not); |
| + case 0x7C /* | */: return _scanOr(); |
| + case 0x26 /* & */: return _scanAnd(); |
| + default: return _scanIdentifier(); |
| + } |
| + } |
| + |
| + /// Scans a single-character operator and returns a token of type [type]. |
| + /// |
| + /// This assumes that the caller has already verified that the next character |
| + /// is correct for the given operator. |
| + Token _scanOperator(TokenType type) { |
| + var start = _scanner.state; |
| + _scanner.readChar(); |
| + return new Token(type, _scanner.spanFrom(start)); |
| + } |
| + |
| + /// Scans a `||` operator and returns the appropriate token. |
| + /// |
| + /// This validates that the next two characters are `||`. |
| + Token _scanOr() { |
| + var start = _scanner.state; |
| + _scanner.expect("||"); |
| + return new Token(TokenType.or, _scanner.spanFrom(start)); |
| + } |
| + |
| + /// Scans a `&&` operator and returns the appropriate token. |
| + /// |
| + /// This validates that the next two characters are `&&`. |
| + Token _scanAnd() { |
| + var start = _scanner.state; |
| + _scanner.expect("&&"); |
| + return new Token(TokenType.and, _scanner.spanFrom(start)); |
| + } |
| + |
| + /// Scans and returns an identifier token. |
| + Token _scanIdentifier() { |
| + _scanner.expect(_identifier, name: "expression"); |
| + return new IdentifierToken(_scanner.lastMatch[0], _scanner.lastSpan); |
| + } |
| + |
| + /// Consumes all whitespace and comments immediately following the cursor's |
| + /// current position. |
| + void _consumeWhitespace() { |
| + while (_scanner.scan(_whitespaceAndSingleLineComments) || |
| + _multiLineComment()) { |
| + // Do nothing. |
| + } |
| + } |
| + |
| + /// Consumes a single multi-line comment. |
| + /// |
| + /// Returns whether or not a comment was consumed. |
| + bool _multiLineComment() { |
| + if (!_scanner.scan("/*")) return false; |
| + |
| + while (_scanner.scan(_multiLineCommentBody) || _multiLineComment()) { |
| + // Do nothing. |
| + } |
| + _scanner.expect("*/"); |
| + |
| + return true; |
| + } |
| +} |