Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(679)

Side by Side Diff: glob/lib/src/ast.dart

Issue 1400473008: Roll Observatory packages and add a roll script (Closed) Base URL: git@github.com:dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « glob/lib/glob.dart ('k') | glob/lib/src/list_tree.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2014, 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 glob.ast;
6
7 import 'package:path/path.dart' as p;
8 import 'package:collection/collection.dart';
9
10 import 'utils.dart';
11
12 const _SEPARATOR = 0x2F; // "/"
13
14 /// A node in the abstract syntax tree for a glob.
15 abstract class AstNode {
16 /// The cached regular expression that this AST was compiled into.
17 RegExp _regExp;
18
19 /// Whether this glob could match an absolute path.
20 ///
21 /// Either this or [canMatchRelative] or both will be true.
22 final bool canMatchAbsolute = false;
23
24 /// Whether this glob could match a relative path.
25 ///
26 /// Either this or [canMatchRelative] or both will be true.
27 final bool canMatchRelative = true;
28
29 /// Returns a new glob with all the options bubbled to the top level.
30 ///
31 /// In particular, this returns a glob AST with two guarantees:
32 ///
33 /// 1. There are no [OptionsNode]s other than the one at the top level.
34 /// 2. It matches the same set of paths as [this].
35 ///
36 /// For example, given the glob `{foo,bar}/{click/clack}`, this would return
37 /// `{foo/click,foo/clack,bar/click,bar/clack}`.
38 OptionsNode flattenOptions() => new OptionsNode([new SequenceNode([this])]);
39
40 /// Returns whether this glob matches [string].
41 bool matches(String string) {
42 if (_regExp == null) _regExp = new RegExp('^${_toRegExp()}\$');
43 return _regExp.hasMatch(string);
44 }
45
46 /// Subclasses should override this to return a regular expression component.
47 String _toRegExp();
48 }
49
50 /// A sequence of adjacent AST nodes.
51 class SequenceNode extends AstNode {
52 /// The nodes in the sequence.
53 final List<AstNode> nodes;
54
55 bool get canMatchAbsolute => nodes.first.canMatchAbsolute;
56 bool get canMatchRelative => nodes.first.canMatchRelative;
57
58 SequenceNode(Iterable<AstNode> nodes)
59 : nodes = nodes.toList();
60
61 OptionsNode flattenOptions() {
62 if (nodes.isEmpty) return new OptionsNode([this]);
63
64 var sequences = nodes.first.flattenOptions().options
65 .map((sequence) => sequence.nodes);
66 for (var node in nodes.skip(1)) {
67 // Concatenate all sequences in the next options node ([nextSequences])
68 // onto all previous sequences ([sequences]).
69 var nextSequences = node.flattenOptions().options;
70 sequences = sequences.expand((sequence) {
71 return nextSequences.map((nextSequence) {
72 return sequence.toList()..addAll(nextSequence.nodes);
73 });
74 });
75 }
76
77 return new OptionsNode(sequences.map((sequence) {
78 // Combine any adjacent LiteralNodes in [sequence].
79 return new SequenceNode(sequence.fold([], (combined, node) {
80 if (combined.isEmpty || combined.last is! LiteralNode ||
81 node is! LiteralNode) {
82 return combined..add(node);
83 }
84
85 combined[combined.length - 1] =
86 new LiteralNode(combined.last.text + node.text);
87 return combined;
88 }));
89 }));
90 }
91
92 /// Splits this glob into components along its path separators.
93 ///
94 /// For example, given the glob `foo/*/*.dart`, this would return three globs:
95 /// `foo`, `*`, and `*.dart`.
96 ///
97 /// Path separators within options nodes are not split. For example,
98 /// `foo/{bar,baz/bang}/qux` will return three globs: `foo`, `{bar,baz/bang}`,
99 /// and `qux`.
100 ///
101 /// [context] is used to determine what absolute roots look like for this
102 /// glob.
103 List<SequenceNode> split(p.Context context) {
104 var componentsToReturn = [];
105 var currentComponent;
106
107 addNode(node) {
108 if (currentComponent == null) currentComponent = [];
109 currentComponent.add(node);
110 }
111
112 finishComponent() {
113 if (currentComponent == null) return;
114 componentsToReturn.add(new SequenceNode(currentComponent));
115 currentComponent = null;
116 }
117
118 for (var node in nodes) {
119 if (node is! LiteralNode || !node.text.contains('/')) {
120 addNode(node);
121 continue;
122 }
123
124 var text = node.text;
125 if (context.style == p.Style.windows) text = text.replaceAll("/", "\\");
126 var components = context.split(text);
127
128 // If the first component is absolute, that means it's a separator (on
129 // Windows some non-separator things are also absolute, but it's invalid
130 // to have "C:" show up in the middle of a path anyway).
131 if (context.isAbsolute(components.first)) {
132 // If this is the first component, it's the root.
133 if (componentsToReturn.isEmpty && currentComponent == null) {
134 var root = components.first;
135 if (context.style == p.Style.windows) {
136 // Above, we switched to backslashes to make [context.split] handle
137 // roots properly. That means that if there is a root, it'll still
138 // have backslashes, where forward slashes are required for globs.
139 // So we switch it back here.
140 root = root.replaceAll("\\", "/");
141 }
142 addNode(new LiteralNode(root));
143 }
144 finishComponent();
145 components = components.skip(1);
146 if (components.isEmpty) continue;
147 }
148
149 // For each component except the last one, add a separate sequence to
150 // [sequences] containing only that component.
151 for (var component in components.take(components.length - 1)) {
152 addNode(new LiteralNode(component));
153 finishComponent();
154 }
155
156 // For the final component, only end its sequence (by adding a new empty
157 // sequence) if it ends with a separator.
158 addNode(new LiteralNode(components.last));
159 if (node.text.endsWith('/')) finishComponent();
160 }
161
162 finishComponent();
163 return componentsToReturn;
164 }
165
166 String _toRegExp() => nodes.map((node) => node._toRegExp()).join();
167
168 bool operator==(Object other) => other is SequenceNode &&
169 const IterableEquality().equals(nodes, other.nodes);
170
171 int get hashCode => const IterableEquality().hash(nodes);
172
173 String toString() => nodes.join();
174 }
175
176 /// A node matching zero or more non-separator characters.
177 class StarNode extends AstNode {
178 StarNode();
179
180 String _toRegExp() => '[^/]*';
181
182 bool operator==(Object other) => other is StarNode;
183
184 int get hashCode => 0;
185
186 String toString() => '*';
187 }
188
189 /// A node matching zero or more characters that may be separators.
190 class DoubleStarNode extends AstNode {
191 /// The path context for the glob.
192 ///
193 /// This is used to determine what absolute paths look like.
194 final p.Context _context;
195
196 DoubleStarNode(this._context);
197
198 String _toRegExp() {
199 // Double star shouldn't match paths with a leading "../", since these paths
200 // wouldn't be listed with this glob. We only check for "../" at the
201 // beginning since the paths are normalized before being checked against the
202 // glob.
203 var buffer = new StringBuffer()..write(r'(?!^(?:\.\./|');
204
205 // A double star at the beginning of the glob also shouldn't match absolute
206 // paths, since those also wouldn't be listed. Which root patterns we look
207 // for depends on the style of path we're matching.
208 if (_context.style == p.Style.posix) {
209 buffer.write(r'/');
210 } else if (_context.style == p.Style.windows) {
211 buffer.write(r'//|[A-Za-z]:/');
212 } else {
213 assert(_context.style == p.Style.url);
214 buffer.write(r'[a-zA-Z][-+.a-zA-Z\d]*://|/');
215 }
216
217 // Use `[^]` rather than `.` so that it matches newlines as well.
218 buffer.write(r'))[^]*');
219
220 return buffer.toString();
221 }
222
223 bool operator==(Object other) => other is DoubleStarNode;
224
225 int get hashCode => 1;
226
227 String toString() => '**';
228 }
229
230 /// A node matching a single non-separator character.
231 class AnyCharNode extends AstNode {
232 AnyCharNode();
233
234 String _toRegExp() => '[^/]';
235
236 bool operator==(Object other) => other is AnyCharNode;
237
238 int get hashCode => 2;
239
240 String toString() => '?';
241 }
242
243 /// A node matching a single character in a range of options.
244 class RangeNode extends AstNode {
245 /// The ranges matched by this node.
246 ///
247 /// The ends of the ranges are unicode code points.
248 final Set<Range> ranges;
249
250 /// Whether this range was negated.
251 final bool negated;
252
253 RangeNode(Iterable<Range> ranges, {this.negated})
254 : ranges = ranges.toSet();
255
256 OptionsNode flattenOptions() {
257 if (negated || ranges.any((range) => !range.isSingleton)) {
258 return super.flattenOptions();
259 }
260
261 // If a range explicitly lists a set of characters, return each character as
262 // a separate expansion.
263 return new OptionsNode(ranges.map((range) {
264 return new SequenceNode([
265 new LiteralNode(new String.fromCharCodes([range.min]))
266 ]);
267 }));
268 }
269
270 String _toRegExp() {
271 var buffer = new StringBuffer();
272
273 var containsSeparator = ranges.any((range) => range.contains(_SEPARATOR));
274 if (!negated && containsSeparator) {
275 // Add `(?!/)` because ranges are never allowed to match separators.
276 buffer.write('(?!/)');
277 }
278
279 buffer.write('[');
280 if (negated) {
281 buffer.write('^');
282 // If the range doesn't itself exclude separators, exclude them ourselves,
283 // since ranges are never allowed to match them.
284 if (!containsSeparator) buffer.write('/');
285 }
286
287 for (var range in ranges) {
288 var start = new String.fromCharCodes([range.min]);
289 buffer.write(regExpQuote(start));
290 if (range.isSingleton) continue;
291 buffer.write('-');
292 buffer.write(regExpQuote(new String.fromCharCodes([range.max])));
293 }
294
295 buffer.write(']');
296 return buffer.toString();
297 }
298
299 bool operator==(Object other) {
300 if (other is! RangeNode) return false;
301 if ((other as RangeNode).negated != negated) return false;
302 return const SetEquality().equals(ranges, (other as RangeNode).ranges);
303 }
304
305 int get hashCode => (negated ? 1 : 3) * const SetEquality().hash(ranges);
306
307 String toString() {
308 var buffer = new StringBuffer()..write('[');
309 for (var range in ranges) {
310 buffer.writeCharCode(range.min);
311 if (range.isSingleton) continue;
312 buffer.write('-');
313 buffer.writeCharCode(range.max);
314 }
315 buffer.write(']');
316 return buffer.toString();
317 }
318 }
319
320 /// A node that matches one of several options.
321 class OptionsNode extends AstNode {
322 /// The options to match.
323 final List<SequenceNode> options;
324
325 bool get canMatchAbsolute => options.any((node) => node.canMatchAbsolute);
326 bool get canMatchRelative => options.any((node) => node.canMatchRelative);
327
328 OptionsNode(Iterable<SequenceNode> options)
329 : options = options.toList();
330
331 OptionsNode flattenOptions() => new OptionsNode(
332 options.expand((option) => option.flattenOptions().options));
333
334 String _toRegExp() =>
335 '(?:${options.map((option) => option._toRegExp()).join("|")})';
336
337 bool operator==(Object other) => other is OptionsNode &&
338 const UnorderedIterableEquality().equals(options, other.options);
339
340 int get hashCode => const UnorderedIterableEquality().hash(options);
341
342 String toString() => '{${options.join(',')}}';
343 }
344
345 /// A node that matches a literal string.
346 class LiteralNode extends AstNode {
347 /// The string to match.
348 final String text;
349
350 /// The path context for the glob.
351 ///
352 /// This is used to determine whether this could match an absolute path.
353 final p.Context _context;
354
355 bool get canMatchAbsolute {
356 var nativeText = _context.style == p.Style.windows ?
357 text.replaceAll('/', '\\') : text;
358 return _context.isAbsolute(nativeText);
359 }
360
361 bool get canMatchRelative => !canMatchAbsolute;
362
363 LiteralNode(this.text, [this._context]);
364
365 String _toRegExp() => regExpQuote(text);
366
367 bool operator==(Object other) => other is LiteralNode && other.text == text;
368
369 int get hashCode => text.hashCode;
370
371 String toString() => text;
372 }
OLDNEW
« no previous file with comments | « glob/lib/glob.dart ('k') | glob/lib/src/list_tree.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698