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 [next] has already returned a | |
47 /// [TokenType.endOfFile] token. | |
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 this has already returned a | |
56 /// [TokenType.endOfFile] token. | |
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 /// Scan and return the next token in the stream. | |
65 Token _getNext() { | |
66 if (_endOfFileEmitted) { | |
67 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.
| |
68 } | |
69 | |
70 _consumeWhitespace(); | |
71 if (_scanner.isDone) { | |
72 return new Token( | |
73 TokenType.endOfFile, _scanner.spanFrom(_scanner.state)); | |
74 } | |
75 | |
76 switch (_scanner.peekChar()) { | |
77 case 0x28 /* ( */: return _scanOperator(TokenType.leftParen); | |
78 case 0x29 /* ) */: return _scanOperator(TokenType.rightParen); | |
79 case 0x3F /* ? */: return _scanOperator(TokenType.questionMark); | |
80 case 0x3A /* : */: return _scanOperator(TokenType.colon); | |
81 case 0x21 /* ! */: return _scanOperator(TokenType.not); | |
82 case 0x7C /* | */: return _scanOr(); | |
83 case 0x26 /* & */: return _scanAnd(); | |
84 default: return _scanIdentifier(); | |
85 } | |
86 } | |
87 | |
88 /// Scans a single-character operator and returns a token of type [type]. | |
89 /// | |
90 /// This assumes that the caller has already verified that the next character | |
91 /// is correct for the given operator. | |
92 Token _scanOperator(TokenType type) { | |
93 var start = _scanner.state; | |
94 _scanner.readChar(); | |
95 return new Token(type, _scanner.spanFrom(start)); | |
96 } | |
97 | |
98 /// Scans a `||` operator and returns the appropriate token. | |
99 /// | |
100 /// This validates that the next two characters are `||`. | |
101 Token _scanOr() { | |
102 var start = _scanner.state; | |
103 _scanner.expect("||"); | |
104 return new Token(TokenType.or, _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 _scanAnd() { | |
111 var start = _scanner.state; | |
112 _scanner.expect("&&"); | |
113 return new Token(TokenType.and, _scanner.spanFrom(start)); | |
114 } | |
115 | |
116 /// Scans and returns an identifier token. | |
117 Token _scanIdentifier() { | |
118 _scanner.expect(_identifier, name: "expression"); | |
119 return new IdentifierToken(_scanner.lastMatch[0], _scanner.lastSpan); | |
120 } | |
121 | |
122 /// Consumes all whitespace and comments immediately following the cursor's | |
123 /// current position. | |
124 void _consumeWhitespace() { | |
125 while (_scanner.scan(_whitespaceAndSingleLineComments) || | |
126 _multiLineComment()) { | |
127 // Do nothing. | |
128 } | |
129 } | |
130 | |
131 /// Consumes a single multi-line comment. | |
132 /// | |
133 /// Returns whether or not a comment was consumed. | |
134 bool _multiLineComment() { | |
135 if (!_scanner.scan("/*")) return false; | |
136 | |
137 while (_scanner.scan(_multiLineCommentBody) || _multiLineComment()) { | |
138 // Do nothing. | |
139 } | |
140 _scanner.expect("*/"); | |
141 | |
142 return true; | |
143 } | |
144 } | |
OLD | NEW |