Index: tool/input_sdk/lib/convert/line_splitter.dart |
diff --git a/tool/input_sdk/lib/convert/line_splitter.dart b/tool/input_sdk/lib/convert/line_splitter.dart |
index 53337f00fffc0bf049c08659a3978df5e1e82773..a9c94488a6c366e35d8650785403ef71338aac88 100644 |
--- a/tool/input_sdk/lib/convert/line_splitter.dart |
+++ b/tool/input_sdk/lib/convert/line_splitter.dart |
@@ -4,22 +4,77 @@ |
part of dart.convert; |
+// Character constants. |
+const int _LF = 10; |
+const int _CR = 13; |
+ |
/** |
- * This class splits [String] values into individual lines. |
+ * A [Converter] that splits a [String] into individual lines. |
+ * |
+ * A line is terminated by either a CR (U+000D), a LF (U+000A), a |
+ * CR+LF sequence (DOS line ending), |
+ * and a final non-empty line can be ended by the end of the string. |
+ * |
+ * The returned lines do not contain the line terminators. |
*/ |
-class LineSplitter extends Converter<String, List<String>> { |
+class LineSplitter extends |
+ ChunkedConverter<String, List<String>, String, String> { |
const LineSplitter(); |
- List<String> convert(String data) { |
- var lines = new List<String>(); |
- |
- _LineSplitterSink._addSlice(data, 0, data.length, true, lines.add); |
+ /// Split [lines] into individual lines. |
+ /// |
+ /// If [start] and [end] are provided, only split the contents of |
+ /// `lines.substring(start, end)`. The [start] and [end] values must |
+ /// specify a valid sub-range of [lines] |
+ /// (`0 <= start <= end <= lines.length`). |
+ static Iterable<String> split(String lines, [int start = 0, int end]) sync* { |
+ end = RangeError.checkValidRange(start, end, lines.length); |
+ int sliceStart = start; |
+ int char = 0; |
+ for (int i = start; i < end; i++) { |
+ int previousChar = char; |
+ char = lines.codeUnitAt(i); |
+ if (char != _CR) { |
+ if (char != _LF) continue; |
+ if (previousChar == _CR) { |
+ sliceStart = i + 1; |
+ continue; |
+ } |
+ } |
+ yield lines.substring(sliceStart, i); |
+ sliceStart = i + 1; |
+ } |
+ if (sliceStart < end) { |
+ yield lines.substring(sliceStart, end); |
+ } |
+ } |
+ List<String> convert(String data) { |
+ List<String> lines = <String>[]; |
+ int end = data.length; |
+ int sliceStart = 0; |
+ int char = 0; |
+ for (int i = 0; i < end; i++) { |
+ int previousChar = char; |
+ char = data.codeUnitAt(i); |
+ if (char != _CR) { |
+ if (char != _LF) continue; |
+ if (previousChar == _CR) { |
+ sliceStart = i + 1; |
+ continue; |
+ } |
+ } |
+ lines.add(data.substring(sliceStart, i)); |
+ sliceStart = i + 1; |
+ } |
+ if (sliceStart < end) { |
+ lines.add(data.substring(sliceStart, end)); |
+ } |
return lines; |
} |
- StringConversionSink startChunkedConversion(Sink<dynamic> sink) { |
+ StringConversionSink startChunkedConversion(Sink<String> sink) { |
if (sink is! StringConversionSink) { |
sink = new StringConversionSink.from(sink); |
} |
@@ -29,65 +84,76 @@ class LineSplitter extends Converter<String, List<String>> { |
// TODO(floitsch): deal with utf8. |
class _LineSplitterSink extends StringConversionSinkBase { |
- static const int _LF = 10; |
- static const int _CR = 13; |
- |
final StringConversionSink _sink; |
+ /// The carry-over from the previous chunk. |
+ /// |
+ /// If the previous slice ended in a line without a line terminator, |
+ /// then the next slice may continue the line. |
String _carry; |
+ /// Whether to skip a leading LF character from the next slice. |
+ /// |
+ /// If the previous slice ended on a CR character, a following LF |
+ /// would be part of the same line termination, and should be ignored. |
+ /// |
+ /// Only `true` when [_carry] is `null`. |
+ bool _skipLeadingLF = false; |
+ |
_LineSplitterSink(this._sink); |
void addSlice(String chunk, int start, int end, bool isLast) { |
+ end = RangeError.checkValidRange(start, end, chunk.length); |
+ // If the chunk is empty, it's probably because it's the last one. |
+ // Handle that here, so we know the range is non-empty below. |
+ if (start >= end) { |
+ if (isLast) close(); |
+ return; |
+ } |
if (_carry != null) { |
+ assert(!_skipLeadingLF); |
chunk = _carry + chunk.substring(start, end); |
start = 0; |
end = chunk.length; |
_carry = null; |
+ } else if (_skipLeadingLF) { |
+ if (chunk.codeUnitAt(start) == _LF) { |
+ start += 1; |
+ } |
+ _skipLeadingLF = false; |
} |
- _carry = _addSlice(chunk, start, end, isLast, _sink.add); |
- if (isLast) _sink.close(); |
+ _addLines(chunk, start, end); |
+ if (isLast) close(); |
} |
void close() { |
- addSlice('', 0, 0, true); |
+ if (_carry != null) { |
+ _sink.add(_carry); |
+ _carry = null; |
+ } |
+ _sink.close(); |
} |
- static String _addSlice(String chunk, int start, int end, bool isLast, |
- void adder(String val)) { |
- |
- int pos = start; |
- while (pos < end) { |
- int skip = 0; |
- int char = chunk.codeUnitAt(pos); |
- if (char == _LF) { |
- skip = 1; |
- } else if (char == _CR) { |
- skip = 1; |
- if (pos + 1 < end) { |
- if (chunk.codeUnitAt(pos + 1) == _LF) { |
- skip = 2; |
- } |
- } else if (!isLast) { |
- return chunk.substring(start, end); |
+ void _addLines(String lines, int start, int end) { |
+ int sliceStart = start; |
+ int char = 0; |
+ for (int i = start; i < end; i++) { |
+ int previousChar = char; |
+ char = lines.codeUnitAt(i); |
+ if (char != _CR) { |
+ if (char != _LF) continue; |
+ if (previousChar == _CR) { |
+ sliceStart = i + 1; |
+ continue; |
} |
} |
- if (skip > 0) { |
- adder(chunk.substring(start, pos)); |
- start = pos = pos + skip; |
- } else { |
- pos++; |
- } |
+ _sink.add(lines.substring(sliceStart, i)); |
+ sliceStart = i + 1; |
} |
- if (pos != start) { |
- var carry = chunk.substring(start, pos); |
- if (isLast) { |
- // Add remaining |
- adder(carry); |
- } else { |
- return carry; |
- } |
+ if (sliceStart < end) { |
+ _carry = lines.substring(sliceStart, end); |
+ } else { |
+ _skipLeadingLF = (char == _CR); |
} |
- return null; |
} |
} |