Line data Source code
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 0 : : _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 0 : if (_next == null) _next = _getNext();
47 0 : 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 0 : var token = _next == null ? _getNext() : _next;
56 0 : _endOfFileEmitted = token.type == TokenType.endOfFile;
57 0 : _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 0 : if (peek().type != type) return false;
68 0 : next();
69 : return true;
70 : }
71 :
72 : /// Scan and return the next token in the stream.
73 : Token _getNext() {
74 0 : if (_endOfFileEmitted) throw new StateError("No more tokens.");
75 :
76 0 : _consumeWhitespace();
77 0 : if (_scanner.isDone) {
78 0 : return new Token(
79 0 : TokenType.endOfFile, _scanner.spanFrom(_scanner.state));
80 : }
81 :
82 0 : switch (_scanner.peekChar()) {
83 0 : case 0x28 /* ( */: return _scanOperator(TokenType.leftParen);
84 0 : case 0x29 /* ) */: return _scanOperator(TokenType.rightParen);
85 0 : case 0x3F /* ? */: return _scanOperator(TokenType.questionMark);
86 0 : case 0x3A /* : */: return _scanOperator(TokenType.colon);
87 0 : case 0x21 /* ! */: return _scanOperator(TokenType.not);
88 0 : case 0x7C /* | */: return _scanOr();
89 0 : case 0x26 /* & */: return _scanAnd();
90 0 : 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 0 : var start = _scanner.state;
100 0 : _scanner.readChar();
101 0 : 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 0 : var start = _scanner.state;
109 0 : _scanner.expect("||");
110 0 : 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 0 : var start = _scanner.state;
118 0 : _scanner.expect("&&");
119 0 : return new Token(TokenType.and, _scanner.spanFrom(start));
120 : }
121 :
122 : /// Scans and returns an identifier token.
123 : Token _scanIdentifier() {
124 0 : _scanner.expect(_hyphenatedIdentifier, name: "expression");
125 0 : 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 0 : while (_scanner.scan(_whitespaceAndSingleLineComments) ||
132 0 : _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 0 : if (!_scanner.scan("/*")) return false;
142 :
143 0 : while (_scanner.scan(_multiLineCommentBody) || _multiLineComment()) {
144 : // Do nothing.
145 : }
146 0 : _scanner.expect("*/");
147 :
148 : return true;
149 : }
150 : }
|