Skip to content

Instantly share code, notes, and snippets.

@mezoni
Created September 15, 2023 08:54
Show Gist options
  • Select an option

  • Save mezoni/dd6e7cc288de4d54285be62ad5c82fcd to your computer and use it in GitHub Desktop.

Select an option

Save mezoni/dd6e7cc288de4d54285be62ad5c82fcd to your computer and use it in GitHub Desktop.
csv_parser.dart
void main(List<String> args) {
final parser = CsvParser();
final rows = parseString(parser.parseStart, _source);
for (var i = 0; i < rows.length; i++) {
final row = rows[i];
print('Row #$i');
print(row);
}
}
const _source = '''
123,"multi
line",1
456,def😄,
''';
class CsvParser {
const CsvParser();
List<List<String>>? parseStart(State<StringReader> state) {
List<List<String>>? $0;
// v:Rows Eof
final $1 = state.pos;
List<List<String>>? $2;
$2 = parseRows(state);
if (state.ok) {
fastParseEof(state);
if (state.ok) {
$0 = $2;
}
}
if (!state.ok) {
state.pos = $1;
}
return $0;
}
List<List<String>>? parseRows(State<StringReader> state) {
List<List<String>>? $0;
// h:Row t:(RowEnding v:Row)* Eol?
final $1 = state.pos;
List<String>? $2;
$2 = parseRow(state);
if (state.ok) {
List<List<String>>? $3;
final $4 = <List<String>>[];
while (true) {
List<String>? $5;
// RowEnding v:Row
final $6 = state.pos;
fastParseRowEnding(state);
if (state.ok) {
List<String>? $7;
$7 = parseRow(state);
if (state.ok) {
$5 = $7;
}
}
if (!state.ok) {
state.pos = $6;
}
if (!state.ok) {
state.ok = true;
break;
}
$4.add($5!);
}
if (state.ok) {
$3 = $4;
}
if (state.ok) {
fastParseEol(state);
state.ok = true;
if (state.ok) {
List<List<String>>? $$;
final h = $2!;
final t = $3!;
$$ = [h, ...t];
$0 = $$;
}
}
}
if (!state.ok) {
state.pos = $1;
}
return $0;
}
List<String>? parseRow(State<StringReader> state) {
List<String>? $0;
// h:Field t:(',' v:Field)*
final $1 = state.pos;
String? $2;
$2 = parseField(state);
if (state.ok) {
List<String>? $3;
final $4 = <String>[];
while (true) {
String? $5;
// ',' v:Field
final $6 = state.pos;
const $8 = ',';
matchLiteral1(state, 44, $8, const ErrorExpectedTags([$8]));
if (state.ok) {
String? $7;
$7 = parseField(state);
if (state.ok) {
$5 = $7;
}
}
if (!state.ok) {
state.pos = $6;
}
if (!state.ok) {
state.ok = true;
break;
}
$4.add($5!);
}
if (state.ok) {
$3 = $4;
}
if (state.ok) {
List<String>? $$;
final h = $2!;
final t = $3!;
$$ = [h, ...t];
$0 = $$;
}
}
if (!state.ok) {
state.pos = $1;
}
return $0;
}
String? parseField(State<StringReader> state) {
String? $0;
// String
$0 = parseString(state);
if (state.ok) {
$0 = $0;
}
if (!state.ok) {
// Text
$0 = parseText(state);
if (state.ok) {
$0 = $0;
}
}
return $0;
}
String? parseText(State<StringReader> state) {
String? $0;
// $[^,"\n\r]*
final $2 = state.pos;
while (true) {
state.ok = state.pos < state.input.length;
if (state.ok) {
final $3 = state.input.readChar(state.pos);
state.ok = !($3 == 13 || $3 == 10 || $3 == 34 || $3 == 44);
if (state.ok) {
state.pos += state.input.count;
}
}
if (!state.ok) {
state.fail(const ErrorUnexpectedCharacter());
}
if (!state.ok) {
state.ok = true;
break;
}
}
if (state.ok) {
$0 = state.input.substring($2, state.pos);
}
if (state.ok) {
$0 = $0;
}
return $0;
}
String? parseString(State<StringReader> state) {
String? $0;
// OpenQuote v:Chars CloseQuote
final $1 = state.pos;
fastParseOpenQuote(state);
if (state.ok) {
List<int>? $2;
$2 = parseChars(state);
if (state.ok) {
fastParseCloseQuote(state);
if (state.ok) {
String? $$;
final v = $2!;
$$ = String.fromCharCodes(v);
$0 = $$;
}
}
}
if (!state.ok) {
state.pos = $1;
}
return $0;
}
void fastParseOpenQuote(State<StringReader> state) {
// Spaces '"'
final $0 = state.pos;
fastParseSpaces(state);
if (state.ok) {
const $1 = '"';
matchLiteral1(state, 34, $1, const ErrorExpectedTags([$1]));
}
if (!state.ok) {
state.pos = $0;
}
}
void fastParseSpaces(State<StringReader> state) {
// [ \t]*
while (true) {
state.ok = state.pos < state.input.length;
if (state.ok) {
final $1 = state.input.readChar(state.pos);
state.ok = $1 == 9 || $1 == 32;
if (state.ok) {
state.pos += state.input.count;
}
}
if (!state.ok) {
state.fail(const ErrorUnexpectedCharacter());
}
if (!state.ok) {
state.ok = true;
break;
}
}
}
List<int>? parseChars(State<StringReader> state) {
List<int>? $0;
// ([^"] / '""')*
final $2 = <int>[];
while (true) {
int? $3;
// [^"]
state.ok = state.pos < state.input.length;
if (state.ok) {
final $7 = state.input.readChar(state.pos);
state.ok = $7 != 34;
if (state.ok) {
state.pos += state.input.count;
$3 = $7;
}
}
if (!state.ok) {
state.fail(const ErrorUnexpectedCharacter());
}
if (state.ok) {
$3 = $3;
}
if (!state.ok) {
// '""'
const $5 = '""';
matchLiteral(state, $5, const ErrorExpectedTags([$5]));
if (state.ok) {
int? $$;
$$ = 0x22;
$3 = $$;
}
}
if (!state.ok) {
state.ok = true;
break;
}
$2.add($3!);
}
if (state.ok) {
$0 = $2;
}
if (state.ok) {
$0 = $0;
}
return $0;
}
void fastParseCloseQuote(State<StringReader> state) {
// '"' Spaces
final $0 = state.pos;
const $1 = '"';
matchLiteral1(state, 34, $1, const ErrorExpectedTags([$1]));
if (state.ok) {
fastParseSpaces(state);
}
if (!state.ok) {
state.pos = $0;
}
}
void fastParseRowEnding(State<StringReader> state) {
// Eol !Eof
final $0 = state.pos;
fastParseEol(state);
if (state.ok) {
final $1 = state.pos;
fastParseEof(state);
state.ok = !state.ok;
if (!state.ok) {
state.pos = $1;
}
}
if (!state.ok) {
state.pos = $0;
}
}
void fastParseEol(State<StringReader> state) {
// [\n\r]
state.ok = state.pos < state.input.length;
if (state.ok) {
final $3 = state.input.readChar(state.pos);
state.ok = $3 == 10 || $3 == 13;
if (state.ok) {
state.pos += state.input.count;
}
}
if (!state.ok) {
state.fail(const ErrorUnexpectedCharacter());
}
if (!state.ok) {
// '\r\n'
const $1 = '\r\n';
matchLiteral(state, $1, const ErrorExpectedTags([$1]));
}
}
void fastParseEof(State<StringReader> state) {
// !.
state.ok = state.pos >= state.input.length;
if (!state.ok) {
state.fail(const ErrorExpectedEndOfInput());
}
}
@pragma('vm:prefer-inline')
String? matchLiteral(
State<StringReader> state, String string, ParseError error) {
state.ok = state.input.startsWith(string, state.pos);
if (state.ok) {
state.pos += state.input.count;
return string;
} else {
state.fail(error);
}
return null;
}
@pragma('vm:prefer-inline')
String? matchLiteral1(
State<StringReader> state, int char, String string, ParseError error) {
final input = state.input;
if (state.pos < input.length) {
final c = input.readChar(state.pos);
if (c == char) {
state.pos += state.input.count;
state.ok = true;
return string;
}
}
state.fail(error);
return null;
}
}
void fastParseString(
void Function(State<StringReader> state) fastParse,
String source, {
String Function(StringReader input, int offset, List<ErrorMessage> errors)?
errorMessage,
}) {
final input = StringReader(source);
final result = tryFastParse(
fastParse,
input,
errorMessage: errorMessage,
);
if (result.ok) {
return;
}
errorMessage ??= errorMessage;
final message = result.errorMessage;
throw FormatException(message);
}
O parseInput<I, O>(
O? Function(State<I> state) parse,
I input, {
String Function(I input, int offset, List<ErrorMessage> errors)? errorMessage,
}) {
final result = tryParse(
parse,
input,
errorMessage: errorMessage,
);
return result.getResult();
}
O parseString<O>(
O? Function(State<StringReader> state) parse,
String source, {
String Function(StringReader input, int offset, List<ErrorMessage> errors)?
errorMessage,
}) {
final input = StringReader(source);
final result = tryParse(
parse,
input,
errorMessage: errorMessage,
);
return result.getResult();
}
ParseResult<I, void> tryFastParse<I>(
void Function(State<I> state) fastParse,
I input, {
String Function(I input, int offset, List<ErrorMessage> errors)? errorMessage,
}) {
final result = _parse<I, void>(
fastParse,
input,
errorMessage: errorMessage,
);
return result;
}
ParseResult<I, O> tryParse<I, O>(
O? Function(State<I> state) parse,
I input, {
String Function(I input, int offset, List<ErrorMessage> errors)? errorMessage,
}) {
final result = _parse<I, O>(
parse,
input,
errorMessage: errorMessage,
);
return result;
}
ParseResult<I, O> _createParseResult<I, O>(
State<I> state,
O? result, {
String Function(I input, int offset, List<ErrorMessage> errors)? errorMessage,
}) {
final input = state.input;
if (state.ok) {
return ParseResult(
failPos: state.failPos,
input: input,
ok: true,
pos: state.pos,
result: result,
);
}
final offset = state.failPos;
final normalized = _normalize(input, offset, state.getErrors())
.map((e) => e.getErrorMessage(input, offset))
.toList();
String? message;
if (errorMessage != null) {
message = errorMessage(input, offset, normalized);
} else if (input is StringReader) {
if (input.hasSource) {
message = _errorMessage(input.source, offset, normalized);
} else {
message = _errorMessageWithoutSource(input, offset, normalized);
}
} else if (input is String) {
message = _errorMessage(input, offset, normalized);
} else {
message = normalized.join('\n');
}
return ParseResult(
errors: normalized,
failPos: state.failPos,
input: input,
errorMessage: message,
ok: false,
pos: state.pos,
result: result,
);
}
String _errorMessage(String source, int offset, List<ErrorMessage> errors) {
final sb = StringBuffer();
final errorInfoList = errors
.map((e) => (length: e.length, message: e.toString()))
.toSet()
.toList();
for (var i = 0; i < errorInfoList.length; i++) {
int max(int x, int y) => x > y ? x : y;
int min(int x, int y) => x < y ? x : y;
if (sb.isNotEmpty) {
sb.writeln();
sb.writeln();
}
final errorInfo = errorInfoList[i];
final length = errorInfo.length;
final message = errorInfo.message;
final start = min(offset + length, offset);
final end = max(offset + length, offset);
var row = 1;
var lineStart = 0, next = 0, pos = 0;
while (pos < source.length) {
final c = source.codeUnitAt(pos++);
if (c == 0xa || c == 0xd) {
next = c == 0xa ? 0xd : 0xa;
if (pos < source.length && source.codeUnitAt(pos) == next) {
pos++;
}
if (pos - 1 >= start) {
break;
}
row++;
lineStart = pos;
}
}
final inputLen = source.length;
final lineLimit = min(80, inputLen);
final start2 = start;
final end2 = min(start2 + lineLimit, end);
final errorLen = end2 - start;
final extraLen = lineLimit - errorLen;
final rightLen = min(inputLen - end2, extraLen - (extraLen >> 1));
final leftLen = min(start, max(0, lineLimit - errorLen - rightLen));
var index = start2 - 1;
final list = <int>[];
for (var i = 0; i < leftLen && index >= 0; i++) {
var cc = source.codeUnitAt(index--);
if ((cc & 0xFC00) == 0xDC00 && (index > 0)) {
final pc = source.codeUnitAt(index);
if ((pc & 0xFC00) == 0xD800) {
cc = 0x10000 + ((pc & 0x3FF) << 10) + (cc & 0x3FF);
index--;
}
}
list.add(cc);
}
final column = start - lineStart + 1;
final left = String.fromCharCodes(list.reversed);
final end3 = min(inputLen, start2 + (lineLimit - leftLen));
final indicatorLen = max(1, errorLen);
final right = source.substring(start2, end3);
var text = left + right;
text = text.replaceAll('\n', ' ');
text = text.replaceAll('\r', ' ');
text = text.replaceAll('\t', ' ');
sb.writeln('line $row, column $column: $message');
sb.writeln(text);
sb.write(' ' * leftLen + '^' * indicatorLen);
}
return sb.toString();
}
String _errorMessageWithoutSource(
StringReader input, int offset, List<ErrorMessage> errors) {
final sb = StringBuffer();
final errorInfoList = errors
.map((e) => (length: e.length, message: e.toString()))
.toSet()
.toList();
for (var i = 0; i < errorInfoList.length; i++) {
int max(int x, int y) => x > y ? x : y;
int min(int x, int y) => x < y ? x : y;
if (sb.isNotEmpty) {
sb.writeln();
sb.writeln();
}
final errorInfo = errorInfoList[i];
final length = errorInfo.length;
final message = errorInfo.message;
final start = min(offset + length, offset);
final end = max(offset + length, offset);
final inputLen = input.length;
final lineLimit = min(80, inputLen);
final start2 = start;
final end2 = min(start2 + lineLimit, end);
final errorLen = end2 - start;
final indicatorLen = max(1, errorLen);
var text = input.substring(start, lineLimit);
text = text.replaceAll('\n', ' ');
text = text.replaceAll('\r', ' ');
text = text.replaceAll('\t', ' ');
sb.writeln('offset $offset: $message');
sb.writeln(text);
sb.write('^' * indicatorLen);
}
return sb.toString();
}
List<ParseError> _normalize<I>(I input, int offset, List<ParseError> errors) {
final result = errors.toList();
if (input case final StringReader input) {
if (offset >= input.length) {
result.add(const ErrorUnexpectedEndOfInput());
result.removeWhere((e) => e is ErrorUnexpectedCharacter);
}
} else if (input case final ChunkedData<StringReader> input) {
if (input.isClosed && offset == input.start + input.data.length) {
result.add(const ErrorUnexpectedEndOfInput());
result.removeWhere((e) => e is ErrorUnexpectedCharacter);
}
}
final foundTags =
result.whereType<ErrorExpectedTag>().map((e) => e.tag).toList();
if (foundTags.isNotEmpty) {
result.removeWhere((e) => e is ErrorExpectedTag);
result.add(ErrorExpectedTags(foundTags));
}
final expectedTags = result.whereType<ErrorExpectedTags>().toList();
if (expectedTags.isNotEmpty) {
result.removeWhere((e) => e is ErrorExpectedTags);
final tags = <String>{};
for (final error in expectedTags) {
tags.addAll(error.tags);
}
final tagList = tags.toList();
tagList.sort();
final error = ErrorExpectedTags(tagList);
result.add(error);
}
return result;
}
ParseResult<I, O> _parse<I, O>(
O? Function(State<I> input) parse,
I input, {
String Function(I input, int offset, List<ErrorMessage> errors)? errorMessage,
}) {
final state = State(input);
final result = parse(state);
return _createParseResult<I, O>(
state,
result,
errorMessage: errorMessage,
);
}
abstract class ChunkedData<T> implements Sink<T> {
void Function()? handler;
bool _isClosed = false;
int buffering = 0;
T data;
int end = 0;
bool sleep = false;
int start = 0;
final T _empty;
ChunkedData(T empty)
: data = empty,
_empty = empty;
bool get isClosed => _isClosed;
@override
void add(T data) {
if (_isClosed) {
throw StateError('Chunked data sink already closed');
}
if (buffering != 0) {
this.data = join(this.data, data);
} else {
start = end;
this.data = data;
}
end = start + getLength(this.data);
sleep = false;
while (!sleep) {
final h = handler;
handler = null;
if (h == null) {
break;
}
h();
}
if (buffering == 0) {
//
}
}
@override
void close() {
if (_isClosed) {
return;
}
_isClosed = true;
sleep = false;
while (!sleep) {
final h = handler;
handler = null;
if (h == null) {
break;
}
h();
}
if (buffering != 0) {
throw StateError('On closing, an incomplete buffering was detected');
}
final length = getLength(data);
if (length != 0) {
data = _empty;
}
}
int getLength(T data);
T join(T data1, T data2);
}
class ErrorExpectedCharacter extends ParseError {
static const message = 'Expected a character {0}';
final int char;
const ErrorExpectedCharacter(this.char);
@override
ErrorMessage getErrorMessage(Object? input, int? offset) {
final value = ParseError.escape(char);
final hexValue = char.toRadixString(16);
final argument = '$value (0x$hexValue)';
return ErrorMessage(0, ErrorExpectedCharacter.message, [argument]);
}
}
class ErrorExpectedEndOfInput extends ParseError {
static const message = 'Expected an end of input';
const ErrorExpectedEndOfInput();
@override
ErrorMessage getErrorMessage(Object? input, offset) {
return ErrorMessage(0, ErrorExpectedEndOfInput.message);
}
}
class ErrorExpectedIntegerValue extends ParseError {
static const message = 'Expected an integer value {0}';
final int size;
final int value;
const ErrorExpectedIntegerValue(this.size, this.value);
@override
ErrorMessage getErrorMessage(Object? input, int? offset) {
var argument = value.toRadixString(16);
if (const [8, 16, 24, 32, 40, 48, 56, 64].contains(size)) {
argument = argument.padLeft(size >> 2, '0');
}
argument = '0x$argument';
if (value >= 0 && value <= 0x10ffff) {
argument = '$argument (${ParseError.escape(value)})';
}
return ErrorMessage(0, ErrorExpectedIntegerValue.message, [argument]);
}
}
class ErrorExpectedTag extends ParseError {
static const message = 'Expected: {0}';
final String tag;
const ErrorExpectedTag(this.tag);
@override
ErrorMessage getErrorMessage(Object? input, int? offset) {
return ErrorMessage(0, ErrorExpectedTag.message);
}
}
class ErrorExpectedTags extends ParseError {
static const message = 'Expected: {0}';
final List<String> tags;
const ErrorExpectedTags(this.tags);
@override
ErrorMessage getErrorMessage(Object? input, int? offset) {
final list = tags.map(ParseError.escape).toList();
list.sort();
final argument = list.join(', ');
return ErrorMessage(0, ErrorExpectedTags.message, [argument]);
}
}
class ErrorMessage extends ParseError {
final List<Object?> arguments;
@override
final int length;
final String text;
const ErrorMessage(this.length, this.text, [this.arguments = const []]);
@override
ErrorMessage getErrorMessage(Object? input, int? offset) {
return this;
}
@override
String toString() {
var result = text;
for (var i = 0; i < arguments.length; i++) {
final argument = arguments[i];
result = result.replaceAll('{$i}', argument.toString());
}
return result;
}
}
class ErrorUnexpectedCharacter extends ParseError {
static const message = 'Unexpected character {0}';
final int? char;
const ErrorUnexpectedCharacter([this.char]);
@override
ErrorMessage getErrorMessage(Object? input, int? offset) {
var argument = '<?>';
var char = this.char;
if (input is StringReader && input.hasSource) {
if (offset case final int offset) {
if (offset < input.length) {
char = input.readChar(offset);
} else {
argument = '<EOF>';
}
}
}
if (char != null) {
final hexValue = char.toRadixString(16);
final value = ParseError.escape(char);
argument = '$value (0x$hexValue)';
}
return ErrorMessage(0, ErrorUnexpectedCharacter.message, [argument]);
}
}
class ErrorUnexpectedEndOfInput extends ParseError {
static const message = 'Unexpected end of input';
const ErrorUnexpectedEndOfInput();
@override
ErrorMessage getErrorMessage(Object? input, int? offset) {
return ErrorMessage(0, ErrorUnexpectedEndOfInput.message);
}
}
class ErrorUnexpectedInput extends ParseError {
static const message = 'Unexpected input';
@override
final int length;
const ErrorUnexpectedInput(this.length);
@override
ErrorMessage getErrorMessage(Object? input, int? offset) {
return ErrorMessage(length, ErrorUnexpectedInput.message);
}
}
class ErrorUnknownError extends ParseError {
static const message = 'Unknown error';
const ErrorUnknownError();
@override
ErrorMessage getErrorMessage(Object? input, int? offset) {
return ErrorMessage(0, ErrorUnknownError.message);
}
}
abstract class ParseError {
const ParseError();
int get length => 0;
ErrorMessage getErrorMessage(Object? input, int? offset);
@override
String toString() {
final message = getErrorMessage(null, null);
return message.toString();
}
static String escape(Object? value, [bool quote = true]) {
if (value is int) {
if (value >= 0 && value <= 0xd7ff ||
value >= 0xe000 && value <= 0x10ffff) {
value = String.fromCharCode(value);
} else {
return value.toString();
}
} else if (value is! String) {
return value.toString();
}
final map = {
'\b': '\\b',
'\f': '\\f',
'\n': '\\n',
'\r': '\\r',
'\t': '\\t',
'\v': '\\v',
};
var result = value.toString();
for (final key in map.keys) {
result = result.replaceAll(key, map[key]!);
}
if (quote) {
result = "'$result'";
}
return result;
}
}
class ParseResult<I, O> {
final String errorMessage;
final List<ErrorMessage> errors;
final int failPos;
final I input;
final bool ok;
final int pos;
final O? result;
ParseResult({
this.errorMessage = '',
this.errors = const [],
required this.failPos,
required this.input,
required this.ok,
required this.pos,
required this.result,
});
O getResult() {
if (!ok) {
throw FormatException(errorMessage);
}
return result as O;
}
}
class State<T> {
Object? context;
final List<ParseError?> errors = List.filled(64, null, growable: false);
int errorCount = 0;
int failPos = 0;
final T input;
bool ok = false;
int pos = 0;
State(this.input);
@pragma('vm:prefer-inline')
void fail(ParseError error) {
ok = false;
if (pos >= failPos) {
if (failPos < pos) {
failPos = pos;
errorCount = 0;
}
if (errorCount < errors.length) {
errors[errorCount++] = error;
}
}
}
@pragma('vm:prefer-inline')
void failAll(List<ParseError> errors) {
ok = false;
if (pos >= failPos) {
if (failPos < pos) {
failPos = pos;
errorCount = 0;
}
for (var i = 0; i < errors.length; i++) {
if (errorCount < errors.length) {
this.errors[errorCount++] = errors[i];
}
}
}
}
@pragma('vm:prefer-inline')
void failAllAt(int offset, List<ParseError> errors) {
ok = false;
if (offset >= failPos) {
if (failPos < offset) {
failPos = offset;
errorCount = 0;
}
for (var i = 0; i < errors.length; i++) {
if (errorCount < errors.length) {
this.errors[errorCount++] = errors[i];
}
}
}
}
@pragma('vm:prefer-inline')
void failAt(int offset, ParseError error) {
ok = false;
if (offset >= failPos) {
if (failPos < offset) {
failPos = offset;
errorCount = 0;
}
if (errorCount < errors.length) {
errors[errorCount++] = error;
}
}
}
List<ParseError> getErrors() {
return List.generate(errorCount, (i) => errors[i]!);
}
@override
String toString() {
if (input case final StringReader input) {
if (input.hasSource) {
final source = input.source;
if (pos >= source.length) {
return '$pos:';
}
var length = source.length - pos;
length = length > 40 ? 40 : length;
final string = source.substring(pos, pos + length);
return '$pos:$string';
}
}
return super.toString();
}
@pragma('vm:prefer-inline')
// ignore: unused_element
bool _canHandleError(int failPos, int errorCount) {
return failPos == this.failPos
? errorCount < this.errorCount
: failPos < this.failPos;
}
@pragma('vm:prefer-inline')
// ignore: unused_element
void _removeLastErrors(int failPos, int errorCount) {
if (this.failPos == failPos) {
this.errorCount = errorCount;
} else if (this.failPos > failPos) {
this.errorCount = 0;
}
}
}
abstract interface class StringReader {
factory StringReader(String source) {
return _StringReader(source);
}
int get count;
bool get hasSource;
int get length;
String get source;
int indexOf(String string, int start);
bool matchChar(int char, int offset);
int readChar(int offset);
bool startsWith(String string, [int index = 0]);
String substring(int start, [int? end]);
}
class StringReaderChunkedData extends ChunkedData<StringReader> {
StringReaderChunkedData() : super(StringReader(''));
@override
int getLength(StringReader data) => data.length;
@override
StringReader join(StringReader data1, StringReader data2) => data1.length != 0
? StringReader('${data1.source}${data2.source}')
: data2;
}
class _StringReader implements StringReader {
@override
final bool hasSource = true;
@override
final int length;
@override
int count = 0;
@override
final String source;
_StringReader(this.source) : length = source.length;
@override
int indexOf(String string, int start) {
return source.indexOf(string, start);
}
@override
@pragma('vm:prefer-inline')
bool matchChar(int char, int offset) {
if (offset < length) {
final c = source.runeAt(offset);
count = char > 0xffff ? 2 : 1;
if (c == char) {
return true;
}
}
return false;
}
@override
@pragma('vm:prefer-inline')
int readChar(int offset) {
final result = source.runeAt(offset);
count = result > 0xffff ? 2 : 1;
return result;
}
@override
@pragma('vm:prefer-inline')
bool startsWith(String string, [int index = 0]) {
if (source.startsWith(string, index)) {
count = string.length;
return true;
}
return false;
}
@override
@pragma('vm:prefer-inline')
String substring(int start, [int? end]) {
final result = source.substring(start, end);
count = result.length;
return result;
}
@override
String toString() {
return source;
}
}
extension on String {
@pragma('vm:prefer-inline')
// ignore: unused_element
int runeAt(int index) {
final w1 = codeUnitAt(index++);
if (w1 > 0xd7ff && w1 < 0xe000) {
if (index < length) {
final w2 = codeUnitAt(index);
if ((w2 & 0xfc00) == 0xdc00) {
return 0x10000 + ((w1 & 0x3ff) << 10) + (w2 & 0x3ff);
}
}
throw FormatException('Invalid UTF-16 character', this, index - 1);
}
return w1;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment