| 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..3c2763c9b8a45393ce8ebd6bba05e198d49ee123
|
| --- /dev/null
|
| +++ b/lib/src/backend/platform_selector/scanner.dart
|
| @@ -0,0 +1,153 @@
|
| +// 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 a [TokenType.endOfFile] token has already been
|
| + /// consumed.
|
| + Token peek() {
|
| + if (_next == null) _next = _getNext();
|
| + return _next;
|
| + }
|
| +
|
| + /// Consumes and returns the next token in the stream.
|
| + ///
|
| + /// Throws a [StateError] if a [TokenType.endOfFile] token has already been
|
| + /// consumed.
|
| + Token next() {
|
| + var token = _next == null ? _getNext() : _next;
|
| + _endOfFileEmitted = token.type == TokenType.endOfFile;
|
| + _next = null;
|
| + return token;
|
| + }
|
| +
|
| + /// If the next token matches [type], consumes it and returns `true`;
|
| + /// otherwise, returns `false`.
|
| + ///
|
| + /// Throws a [StateError] if a [TokenType.endOfFile] token has already been
|
| + /// consumed.
|
| + bool scan(TokenType type) {
|
| + if (peek().type != type) return false;
|
| + next();
|
| + return true;
|
| + }
|
| +
|
| + /// Scan and return the next token in the stream.
|
| + Token _getNext() {
|
| + if (_endOfFileEmitted) throw new StateError("No more tokens.");
|
| +
|
| + _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;
|
| + }
|
| +}
|
|
|