Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library glob.ast; | 5 library glob.ast; |
| 6 | 6 |
| 7 import 'package:path/path.dart' as p; | 7 import 'package:path/path.dart' as p; |
| 8 | 8 |
| 9 import 'utils.dart'; | 9 import 'utils.dart'; |
| 10 | 10 |
| 11 const _SEPARATOR = 0x2F; // "/" | 11 const _SEPARATOR = 0x2F; // "/" |
| 12 | 12 |
| 13 /// A node in the abstract syntax tree for a glob. | 13 /// A node in the abstract syntax tree for a glob. |
| 14 abstract class AstNode { | 14 abstract class AstNode { |
| 15 /// The cached regular expression that this AST was compiled into. | 15 /// The cached regular expression that this AST was compiled into. |
| 16 RegExp _regExp; | 16 RegExp _regExp; |
| 17 | 17 |
| 18 /// Whether this glob could match an absolute path. | 18 /// Whether this glob could match an absolute path. |
| 19 /// | 19 /// |
| 20 /// Either this or [canMatchRelative] or both will be true. | 20 /// Either this or [canMatchRelative] or both will be true. |
| 21 final bool canMatchAbsolute = false; | 21 final bool canMatchAbsolute = false; |
| 22 | 22 |
| 23 /// Whether this glob could match a relative path. | 23 /// Whether this glob could match a relative path. |
| 24 /// | 24 /// |
| 25 /// Either this or [canMatchRelative] or both will be true. | 25 /// Either this or [canMatchRelative] or both will be true. |
| 26 final bool canMatchRelative = true; | 26 final bool canMatchRelative = true; |
| 27 | 27 |
| 28 /// Returns a new glob with all the options bubbled to the top level. | |
| 29 /// | |
| 30 /// In particular, this returns a glob AST with two guarantees: | |
| 31 /// | |
| 32 /// 1. There are no [OptionsNode]s other than the one at the top level. | |
| 33 /// 2. It matches the same set of paths as [this]. | |
| 34 OptionsNode expand() => new OptionsNode([new SequenceNode([this])]); | |
|
Bob Nystrom
2014/09/18 22:17:39
How about "flattenOptions"? Using the same name as
nweiz
2014/09/22 23:48:39
Done.
| |
| 35 | |
| 28 /// Returns whether this glob matches [string]. | 36 /// Returns whether this glob matches [string]. |
| 29 bool matches(String string) { | 37 bool matches(String string) { |
| 30 if (_regExp == null) _regExp = new RegExp('^${_toRegExp()}\$'); | 38 if (_regExp == null) _regExp = new RegExp('^${_toRegExp()}\$'); |
| 31 return _regExp.hasMatch(string); | 39 return _regExp.hasMatch(string); |
| 32 } | 40 } |
| 33 | 41 |
| 34 /// Subclasses should override this to return a regular expression component. | 42 /// Subclasses should override this to return a regular expression component. |
| 35 String _toRegExp(); | 43 String _toRegExp(); |
| 36 } | 44 } |
| 37 | 45 |
| 38 /// A sequence of adjacent AST nodes. | 46 /// A sequence of adjacent AST nodes. |
| 39 class SequenceNode extends AstNode { | 47 class SequenceNode extends AstNode { |
| 40 /// The nodes in the sequence. | 48 /// The nodes in the sequence. |
| 41 final List<AstNode> nodes; | 49 final List<AstNode> nodes; |
| 42 | 50 |
| 43 bool get canMatchAbsolute => nodes.first.canMatchAbsolute; | 51 bool get canMatchAbsolute => nodes.first.canMatchAbsolute; |
| 44 bool get canMatchRelative => nodes.first.canMatchRelative; | 52 bool get canMatchRelative => nodes.first.canMatchRelative; |
| 45 | 53 |
| 46 SequenceNode(Iterable<AstNode> nodes) | 54 SequenceNode(Iterable<AstNode> nodes) |
| 47 : nodes = nodes.toList(); | 55 : nodes = nodes.toList(); |
| 48 | 56 |
| 57 OptionsNode expand() { | |
| 58 if (nodes.isEmpty) return new OptionsNode([this]); | |
| 59 | |
| 60 var sequences = nodes.first.expand().options; | |
| 61 for (var node in nodes.skip(1)) { | |
| 62 var nextSequences = node.expand().options; | |
| 63 sequences = sequences.expand((sequence) { | |
| 64 return nextSequences.map((nextSequence) { | |
|
Bob Nystrom
2014/09/18 22:17:39
A loop containing an expand call that's mapping ea
nweiz
2014/09/22 23:48:39
Done.
| |
| 65 var next = nextSequence.nodes.toList(); | |
| 66 var combined = sequence.nodes.toList(); | |
| 67 | |
| 68 // Combine adjacent LiteralNodes. | |
| 69 if (!combined.isEmpty && !next.isEmpty && | |
| 70 combined.last is LiteralNode && next.first is LiteralNode) { | |
| 71 combined[combined.length - 1] = | |
| 72 new LiteralNode(combined.last.text + next.removeAt(0).text); | |
| 73 } | |
| 74 | |
| 75 return new SequenceNode(combined..addAll(next)); | |
| 76 }); | |
| 77 }); | |
| 78 } | |
| 79 return new OptionsNode(sequences.toList()); | |
| 80 } | |
| 81 | |
| 82 /// Splits this glob into components along its path separators. | |
|
Bob Nystrom
2014/09/18 22:17:39
A before/after example would help a lot here.
nweiz
2014/09/22 23:48:39
Done.
| |
| 83 /// | |
| 84 /// This should only be called once the glob has been [expand]ed. [context] is | |
|
Bob Nystrom
2014/09/18 22:17:39
If this is only valid on certain states, maybe it
nweiz
2014/09/22 23:48:39
Rather than doing that (which I think would add a
| |
| 85 /// used to determine what absolute roots look like for this glob. | |
| 86 List<SequenceNode> split(p.Context context) { | |
| 87 // The inner list represents a [SequenceNode] for a single path component; | |
| 88 // the outer list is the list of components. | |
| 89 var sequences = [[]]; | |
|
Bob Nystrom
2014/09/18 22:17:39
I think this code will be easier to read if you ad
nweiz
2014/09/22 23:48:39
Done.
| |
| 90 | |
| 91 for (var node in nodes) { | |
| 92 // This should only be called after [expand]. | |
| 93 assert(node is! OptionsNode); | |
| 94 | |
| 95 if (node is! LiteralNode || !node.text.contains('/')) { | |
| 96 sequences.last.add(node); | |
| 97 continue; | |
| 98 } | |
| 99 | |
| 100 var text = node.text; | |
| 101 if (context.style == p.Style.windows) text.replaceAll("/", "\\"); | |
| 102 var components = context.split(text); | |
|
Bob Nystrom
2014/09/18 22:17:39
"nodeComponents"?
nweiz
2014/09/22 23:48:39
This is referenced a lot more often than the previ
| |
| 103 | |
| 104 // If the first component is absolute, that means it's a separator (on | |
| 105 // Windows some non-separator things are also absolute, but it's invalid | |
| 106 // to have "C:" show up in the middle of a path anyway). | |
| 107 if (context.isAbsolute(components.first)) { | |
| 108 // If this is the first component, it's the root. | |
| 109 if (sequences.length == 1 && sequences.first.isEmpty) { | |
| 110 var root = components.first; | |
| 111 if (context.style == p.Style.windows) { | |
| 112 root = root.replaceAll("\\", "/"); | |
|
Bob Nystrom
2014/09/18 22:17:39
Why switch to forward slashes on Windows?
nweiz
2014/09/22 23:48:39
Above, we switched to backslashes to make [context
| |
| 113 } | |
| 114 sequences.last.add(new LiteralNode(root)); | |
| 115 } | |
| 116 sequences.add([]); | |
| 117 components = components.skip(1); | |
| 118 if (components.isEmpty) continue; | |
| 119 } | |
| 120 | |
| 121 // For each component except the last one, add a separate sequence to | |
| 122 // [sequences] containing only that component. | |
| 123 for (var component in components.take(components.length - 1)) { | |
| 124 sequences.last.add(new LiteralNode(component)); | |
| 125 sequences.add([]); | |
| 126 } | |
| 127 | |
| 128 // For the final component, only end its sequence (by adding a new empty | |
| 129 // sequence) if it ends with a separator. | |
| 130 sequences.last.add(new LiteralNode(components.last)); | |
| 131 if (node.text.endsWith('/')) sequences.add([]); | |
| 132 } | |
| 133 | |
| 134 if (sequences.last.isEmpty) sequences.removeLast(); | |
| 135 return sequences.map((sequence) => new SequenceNode(sequence)).toList(); | |
| 136 } | |
| 137 | |
| 49 String _toRegExp() => nodes.map((node) => node._toRegExp()).join(); | 138 String _toRegExp() => nodes.map((node) => node._toRegExp()).join(); |
| 50 | 139 |
| 51 String toString() => nodes.join(); | 140 String toString() => nodes.join(); |
| 52 } | 141 } |
| 53 | 142 |
| 54 /// A node matching zero or more non-separator characters. | 143 /// A node matching zero or more non-separator characters. |
| 55 class StarNode extends AstNode { | 144 class StarNode extends AstNode { |
| 56 StarNode(); | 145 StarNode(); |
| 57 | 146 |
| 58 String _toRegExp() => '[^/]*'; | 147 String _toRegExp() => '[^/]*'; |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 112 /// | 201 /// |
| 113 /// The ends of the ranges are unicode code points. | 202 /// The ends of the ranges are unicode code points. |
| 114 final Set<Range> ranges; | 203 final Set<Range> ranges; |
| 115 | 204 |
| 116 /// Whether this range was negated. | 205 /// Whether this range was negated. |
| 117 final bool negated; | 206 final bool negated; |
| 118 | 207 |
| 119 RangeNode(Iterable<Range> ranges, {this.negated}) | 208 RangeNode(Iterable<Range> ranges, {this.negated}) |
| 120 : ranges = ranges.toSet(); | 209 : ranges = ranges.toSet(); |
| 121 | 210 |
| 211 OptionsNode expand() { | |
| 212 if (negated || ranges.any((range) => !range.isSingleton)) { | |
| 213 return super.expand(); | |
| 214 } | |
| 215 | |
| 216 // If a range explicitly lists a set of characters, return each character as | |
| 217 // a separate expansion. | |
| 218 return new OptionsNode(ranges.map((range) { | |
| 219 return new SequenceNode([ | |
| 220 new LiteralNode(new String.fromCharCodes([range.min])) | |
| 221 ]); | |
| 222 })); | |
| 223 } | |
| 224 | |
| 122 String _toRegExp() { | 225 String _toRegExp() { |
| 123 var buffer = new StringBuffer(); | 226 var buffer = new StringBuffer(); |
| 124 | 227 |
| 125 var containsSeparator = ranges.any((range) => range.contains(_SEPARATOR)); | 228 var containsSeparator = ranges.any((range) => range.contains(_SEPARATOR)); |
| 126 if (!negated && containsSeparator) { | 229 if (!negated && containsSeparator) { |
| 127 // Add `(?!/)` because ranges are never allowed to match separators. | 230 // Add `(?!/)` because ranges are never allowed to match separators. |
| 128 buffer.write('(?!/)'); | 231 buffer.write('(?!/)'); |
| 129 } | 232 } |
| 130 | 233 |
| 131 buffer.write('['); | 234 buffer.write('['); |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 165 class OptionsNode extends AstNode { | 268 class OptionsNode extends AstNode { |
| 166 /// The options to match. | 269 /// The options to match. |
| 167 final List<SequenceNode> options; | 270 final List<SequenceNode> options; |
| 168 | 271 |
| 169 bool get canMatchAbsolute => options.any((node) => node.canMatchAbsolute); | 272 bool get canMatchAbsolute => options.any((node) => node.canMatchAbsolute); |
| 170 bool get canMatchRelative => options.any((node) => node.canMatchRelative); | 273 bool get canMatchRelative => options.any((node) => node.canMatchRelative); |
| 171 | 274 |
| 172 OptionsNode(Iterable<SequenceNode> options) | 275 OptionsNode(Iterable<SequenceNode> options) |
| 173 : options = options.toList(); | 276 : options = options.toList(); |
| 174 | 277 |
| 278 OptionsNode expand() => | |
| 279 new OptionsNode(options.expand((option) => option.expand().options)); | |
| 280 | |
| 175 String _toRegExp() => | 281 String _toRegExp() => |
| 176 '(?:${options.map((option) => option._toRegExp()).join("|")})'; | 282 '(?:${options.map((option) => option._toRegExp()).join("|")})'; |
| 177 | 283 |
| 178 String toString() => '{${options.join(',')}}'; | 284 String toString() => '{${options.join(',')}}'; |
| 179 } | 285 } |
| 180 | 286 |
| 181 /// A node that matches a literal string. | 287 /// A node that matches a literal string. |
| 182 class LiteralNode extends AstNode { | 288 class LiteralNode extends AstNode { |
| 183 /// The string to match. | 289 /// The string to match. |
| 184 final String text; | 290 final String text; |
| 185 | 291 |
| 186 /// The path context for the glob. | 292 /// The path context for the glob. |
| 187 /// | 293 /// |
| 188 /// This is used to determine whether this could match an absolute path. | 294 /// This is used to determine whether this could match an absolute path. |
| 189 final p.Context _context; | 295 final p.Context _context; |
| 190 | 296 |
| 191 bool get canMatchAbsolute { | 297 bool get canMatchAbsolute { |
| 192 var nativeText = _context.style == p.Style.windows ? | 298 var nativeText = _context.style == p.Style.windows ? |
| 193 text.replaceAll('/', '\\') : text; | 299 text.replaceAll('/', '\\') : text; |
| 194 return _context.isAbsolute(nativeText); | 300 return _context.isAbsolute(nativeText); |
| 195 } | 301 } |
| 196 | 302 |
| 197 bool get canMatchRelative => !canMatchAbsolute; | 303 bool get canMatchRelative => !canMatchAbsolute; |
| 198 | 304 |
| 199 LiteralNode(this.text, this._context); | 305 LiteralNode(this.text, [this._context]); |
| 200 | 306 |
| 201 String _toRegExp() => regExpQuote(text); | 307 String _toRegExp() => regExpQuote(text); |
| 202 | 308 |
| 203 String toString() => text; | 309 String toString() => text; |
| 204 } | 310 } |
| OLD | NEW |