platform/packages/range_header/lib/src/parser.dart

153 lines
3.9 KiB
Dart
Raw Normal View History

2021-05-01 02:48:36 +00:00
import 'package:charcode/charcode.dart';
import 'package:source_span/source_span.dart';
import 'package:string_scanner/string_scanner.dart';
import 'exception.dart';
import 'range_header.dart';
import 'range_header_impl.dart';
import 'range_header_item.dart';
2021-05-18 11:58:51 +00:00
final RegExp _rgxInt = RegExp(r'[0-9]+');
final RegExp _rgxWs = RegExp(r'[ \n\r\t]');
2021-05-01 02:48:36 +00:00
enum TokenType { RANGE_UNIT, COMMA, INT, DASH, EQUALS }
class Token {
final TokenType type;
2021-05-01 03:39:09 +00:00
final SourceSpan? span;
2021-05-01 02:48:36 +00:00
Token(this.type, this.span);
}
List<Token> scan(String text, List<String> allowedRangeUnits) {
2021-05-18 11:58:51 +00:00
var tokens = <Token>[];
var scanner = SpanScanner(text);
2021-05-01 02:48:36 +00:00
while (!scanner.isDone) {
// Skip whitespace
scanner.scan(_rgxWs);
2021-05-18 11:58:51 +00:00
if (scanner.scanChar($comma)) {
tokens.add(Token(TokenType.COMMA, scanner.lastSpan));
} else if (scanner.scanChar($dash)) {
tokens.add(Token(TokenType.DASH, scanner.lastSpan));
} else if (scanner.scan(_rgxInt)) {
tokens.add(Token(TokenType.INT, scanner.lastSpan));
} else if (scanner.scanChar($equal)) {
tokens.add(Token(TokenType.EQUALS, scanner.lastSpan));
} else {
var matched = false;
2021-05-01 02:48:36 +00:00
for (var unit in allowedRangeUnits) {
if (scanner.scan(unit)) {
2021-05-18 11:58:51 +00:00
tokens.add(Token(TokenType.RANGE_UNIT, scanner.lastSpan));
2021-05-01 02:48:36 +00:00
matched = true;
break;
}
}
if (!matched) {
var ch = scanner.readChar();
2021-05-18 11:58:51 +00:00
throw RangeHeaderParseException(
'Unexpected character: "${String.fromCharCode(ch)}"');
2021-05-01 02:48:36 +00:00
}
}
}
return tokens;
}
class Parser {
2021-05-01 03:39:09 +00:00
Token? _current;
2021-05-01 02:48:36 +00:00
int _index = -1;
final List<Token> tokens;
Parser(this.tokens);
2021-05-01 03:39:09 +00:00
Token? get current => _current;
2021-05-01 02:48:36 +00:00
bool get done => _index >= tokens.length - 1;
RangeHeaderParseException _expected(String type) {
2021-05-18 11:58:51 +00:00
var offset = current?.span?.start.offset;
2021-05-01 02:48:36 +00:00
2021-05-18 11:58:51 +00:00
if (offset == null) return RangeHeaderParseException('Expected $type.');
2021-05-01 02:48:36 +00:00
2021-05-01 03:39:09 +00:00
Token? peek;
2021-05-01 02:48:36 +00:00
if (_index < tokens.length - 1) peek = tokens[_index + 1];
if (peek != null && peek.span != null) {
2021-05-18 11:58:51 +00:00
return RangeHeaderParseException(
2021-05-01 03:39:09 +00:00
'Expected $type at offset $offset, found "${peek.span!.text}" instead. \nSource:\n${peek.span?.highlight() ?? peek.type}');
2021-05-18 11:58:51 +00:00
} else {
return RangeHeaderParseException(
2021-05-01 03:39:09 +00:00
'Expected $type at offset $offset, but the header string ended without one.\nSource:\n${current!.span?.highlight() ?? current!.type}');
2021-05-18 11:58:51 +00:00
}
2021-05-01 02:48:36 +00:00
}
bool next(TokenType type) {
if (done) return false;
var tok = tokens[_index + 1];
if (tok.type == type) {
_index++;
_current = tok;
return true;
2021-05-18 11:58:51 +00:00
} else {
2021-05-01 02:48:36 +00:00
return false;
2021-05-18 11:58:51 +00:00
}
2021-05-01 02:48:36 +00:00
}
2021-05-01 03:39:09 +00:00
RangeHeader? parseRangeHeader() {
2021-05-01 02:48:36 +00:00
if (next(TokenType.RANGE_UNIT)) {
2021-05-01 03:39:09 +00:00
var unit = current!.span!.text;
2021-05-01 02:48:36 +00:00
next(TokenType.EQUALS); // Consume =, if any.
2021-05-18 11:58:51 +00:00
var items = <RangeHeaderItem>[];
var item = parseHeaderItem();
2021-05-01 02:48:36 +00:00
while (item != null) {
items.add(item);
// Parse comma
if (next(TokenType.COMMA)) {
item = parseHeaderItem();
2021-05-18 11:58:51 +00:00
} else {
2021-05-01 02:48:36 +00:00
item = null;
2021-05-18 11:58:51 +00:00
}
2021-05-01 02:48:36 +00:00
}
2021-05-18 11:58:51 +00:00
if (items.isEmpty) {
2021-05-01 02:48:36 +00:00
throw _expected('range');
2021-05-18 11:58:51 +00:00
} else {
return RangeHeaderImpl(unit, items);
}
} else {
2021-05-01 02:48:36 +00:00
return null;
2021-05-18 11:58:51 +00:00
}
2021-05-01 02:48:36 +00:00
}
2021-05-01 03:39:09 +00:00
RangeHeaderItem? parseHeaderItem() {
2021-05-01 02:48:36 +00:00
if (next(TokenType.INT)) {
// i.e 500-544, or 600-
2021-05-01 03:39:09 +00:00
var start = int.parse(current!.span!.text);
2021-05-01 02:48:36 +00:00
if (next(TokenType.DASH)) {
if (next(TokenType.INT)) {
2021-05-18 11:58:51 +00:00
return RangeHeaderItem(start, int.parse(current!.span!.text));
} else {
return RangeHeaderItem(start);
}
} else {
2021-05-01 02:48:36 +00:00
throw _expected('"-"');
2021-05-18 11:58:51 +00:00
}
2021-05-01 02:48:36 +00:00
} else if (next(TokenType.DASH)) {
// i.e. -599
if (next(TokenType.INT)) {
2021-05-18 11:58:51 +00:00
return RangeHeaderItem(-1, int.parse(current!.span!.text));
} else {
2021-05-01 02:48:36 +00:00
throw _expected('integer');
2021-05-18 11:58:51 +00:00
}
} else {
2021-05-01 02:48:36 +00:00
return null;
2021-05-18 11:58:51 +00:00
}
2021-05-01 02:48:36 +00:00
}
}