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