parser_builder 2.0.12 parser_builder: ^2.0.12 copied to clipboard
Lightweight template-based parser build system. Simple prototyping. Comfortable debugging. Effective developing.
import 'package:source_span/source_span.dart';
dynamic parse(String source) {
final state = State(source);
final result = _json(state);
if (!state.ok) {
final message = _errorMessage(source, state.errors);
throw FormatException('\n$message');
}
return result;
}
void _ws(State<String> state) {
final source = state.source;
while (state.pos < source.length) {
final c = source.codeUnitAt(state.pos);
final ok = c <= 13
? c <= 10
? c >= 9
: c == 13
: c == 32;
if (!ok) {
break;
}
state.pos++;
}
state.ok = true;
}
dynamic _json(State<String> state) {
dynamic $0;
final source = state.source;
final $pos = state.pos;
_ws(state);
if (state.ok) {
$0 = _value(state);
if (state.ok) {
state.ok = state.pos >= source.length;
if (!state.ok) {
state.fail(state.pos, ParseError.expected, 0, 'EOF');
}
}
}
if (!state.ok) {
$0 = null;
state.pos = $pos;
}
return $0;
}
@pragma('vm:prefer-inline')
int? _escapeHex(State<String> state) {
int? $0;
final source = state.source;
final $pos = state.pos;
state.ok = state.pos < source.length;
if (state.ok) {
final c = source.codeUnitAt(state.pos);
state.ok = c == 117;
if (state.ok) {
state.pos++;
} else {
state.fail(state.pos, ParseError.character, 0, 0);
}
} else {
state.fail(state.pos, ParseError.character, 0, 0);
}
if (state.ok) {
String? $1;
final $last = state.setLastErrorPos(-1);
String? $2;
final $pos2 = state.pos;
var $count = 0;
while ($count < 4 && state.pos < source.length) {
final c = source.codeUnitAt(state.pos);
final ok = c <= 70
? c <= 57
? c >= 48
: c >= 65
: c <= 102 && c >= 97;
if (!ok) {
break;
}
state.pos++;
$count++;
}
state.ok = $count >= 4;
if (state.ok) {
$2 = source.substring($pos2, state.pos);
} else {
state.fail(state.pos, ParseError.character, 0, 0);
state.pos = $pos2;
}
if (state.ok) {
$1 = $2;
} else {
final pos = state.lastErrorPos;
final length = state.pos - pos;
state.fail(pos, ParseError.message, length,
'An escape sequence starting with \'\\u\' must be followed by 4 hexadecimal digits');
}
state.restoreLastErrorPos($last);
if (state.ok) {
final v1 = $1!;
$0 = _toHexValue(v1);
}
}
if (!state.ok) {
state.pos = $pos;
}
return $0;
}
int? _escaped(State<String> state) {
int? $0;
final source = state.source;
state.ok = state.pos < source.length;
if (state.ok) {
final c = source.codeUnitAt(state.pos);
int? v;
switch (c) {
case 34:
case 47:
case 92:
v = c;
break;
case 98:
v = 8;
break;
case 102:
v = 12;
break;
case 110:
v = 10;
break;
case 114:
v = 13;
break;
case 116:
v = 9;
break;
}
state.ok = v != null;
if (state.ok) {
state.pos++;
$0 = v;
} else {
state.fail(state.pos, ParseError.character, 0, 0);
}
} else {
state.fail(state.pos, ParseError.character, 0, 0);
}
if (!state.ok) {
$0 = _escapeHex(state);
}
return $0;
}
@pragma('vm:prefer-inline')
void _quote(State<String> state) {
final source = state.source;
final $pos = state.pos;
state.ok = state.pos < source.length && source.codeUnitAt(state.pos) == 34;
if (state.ok) {
state.pos += 1;
} else {
state.fail(state.pos, ParseError.expected, 0, '"');
}
if (state.ok) {
_ws(state);
}
if (!state.ok) {
state.pos = $pos;
}
}
String? _string(State<String> state) {
String? $0;
final source = state.source;
final $min = state.minErrorPos;
state.minErrorPos = state.pos + 1;
String? $1;
final $pos = state.pos;
state.ok = state.pos < source.length && source.codeUnitAt(state.pos) == 34;
if (state.ok) {
state.pos += 1;
} else {
state.fail(state.pos, ParseError.expected, 0, '"');
}
if (state.ok) {
state.ok = true;
final $pos1 = state.pos;
final $list = <String>[];
var $str = '';
while (state.pos < source.length) {
final $start = state.pos;
var $c = 0;
while (state.pos < source.length) {
final pos = state.pos;
$c = source.readRune(state);
final ok = $c >= 0x20 && $c != 0x22 && $c != 0x5c;
if (!ok) {
state.pos = pos;
break;
}
}
$str = state.pos == $start ? '' : source.substring($start, state.pos);
if ($str != '' && $list.isNotEmpty) {
$list.add($str);
}
if ($c != 92) {
break;
}
state.pos += 1;
int? $2;
$2 = _escaped(state);
if (!state.ok) {
state.pos = $pos1;
break;
}
if ($list.isEmpty && $str != '') {
$list.add($str);
}
$list.add(String.fromCharCode($2!));
}
if (state.ok) {
if ($list.isEmpty) {
$1 = $str;
} else {
$1 = $list.join();
}
}
if (state.ok) {
_quote(state);
}
}
if (!state.ok) {
$1 = null;
state.pos = $pos;
}
state.minErrorPos = $min;
if (state.ok) {
$0 = $1;
} else {
state.fail(state.pos, ParseError.expected, 0, 'string');
}
return $0;
}
num? _number(State<String> state) {
num? $0;
final source = state.source;
final $log = state.log;
state.log = false;
num? $1;
state.ok = true;
final $pos = state.pos;
num? $v;
while (true) {
// '-'?('0'|[1-9][0-9]*)('.'[0-9]+)?([eE][+-]?[0-9]+)?
const eof = 0x110000;
const mask = 0x30;
const powersOfTen = [
1.0,
1e1,
1e2,
1e3,
1e4,
1e5,
1e6,
1e7,
1e8,
1e9,
1e10,
1e11,
1e12,
1e13,
1e14,
1e15,
1e16,
1e17,
1e18,
1e19,
1e20,
1e21,
1e22,
];
final length = source.length;
var pos = state.pos;
var c = eof;
if (pos < length) {
c = source.codeUnitAt(pos);
} else {
c = eof;
}
var hasSign = false;
if (c == 0x2d) {
pos++;
if (pos < length) {
c = source.codeUnitAt(pos);
} else {
c = eof;
}
hasSign = true;
}
var digit = c ^ mask;
if (digit > 9) {
state.ok = false;
state.pos = pos;
break;
}
final intPartPos = pos;
var intPartLen = 0;
intPartLen = 1;
var intValue = 0;
if (digit == 0) {
pos++;
if (pos < length) {
c = source.codeUnitAt(pos);
} else {
c = eof;
}
} else {
pos++;
if (pos < length) {
c = source.codeUnitAt(pos);
} else {
c = eof;
}
intValue = digit;
while (true) {
digit = c ^ mask;
if (digit > 9) {
break;
}
pos++;
if (pos < length) {
c = source.codeUnitAt(pos);
} else {
c = eof;
}
if (intPartLen++ < 18) {
intValue = intValue * 10 + digit;
}
}
}
var hasDot = false;
var decPartLen = 0;
var decValue = 0;
if (c == 0x2e) {
pos++;
if (pos < length) {
c = source.codeUnitAt(pos);
} else {
c = eof;
}
hasDot = true;
digit = c ^ mask;
if (digit > 9) {
state.ok = false;
state.pos = pos;
break;
}
pos++;
if (pos < length) {
c = source.codeUnitAt(pos);
} else {
c = eof;
}
decPartLen = 1;
decValue = digit;
while (true) {
digit = c ^ mask;
if (digit > 9) {
break;
}
pos++;
if (pos < length) {
c = source.codeUnitAt(pos);
} else {
c = eof;
}
if (decPartLen++ < 18) {
decValue = decValue * 10 + digit;
}
}
}
var hasExp = false;
var hasExpSign = false;
var expPartLen = 0;
var exp = 0;
if (c == 0x45 || c == 0x65) {
pos++;
if (pos < length) {
c = source.codeUnitAt(pos);
} else {
c = eof;
}
hasExp = true;
switch (c) {
case 0x2b:
pos++;
if (pos < length) {
c = source.codeUnitAt(pos);
} else {
c = eof;
}
break;
case 0x2d:
pos++;
if (pos < length) {
c = source.codeUnitAt(pos);
} else {
c = eof;
}
hasExpSign = true;
break;
}
digit = c ^ mask;
if (digit > 9) {
state.ok = false;
state.pos = pos;
break;
}
pos++;
if (pos < length) {
c = source.codeUnitAt(pos);
} else {
c = eof;
}
expPartLen = 1;
exp = digit;
while (true) {
digit = c ^ mask;
if (digit > 9) {
break;
}
pos++;
if (pos < length) {
c = source.codeUnitAt(pos);
} else {
c = eof;
}
if (expPartLen++ < 18) {
exp = exp * 10 + digit;
}
}
if (expPartLen > 18) {
state.pos = pos;
$v = double.parse(source.substring($pos, pos));
break;
}
if (hasExpSign) {
exp = -exp;
}
}
state.pos = pos;
final singlePart = !hasDot && !hasExp;
if (singlePart && intPartLen <= 18) {
$v = hasSign ? -intValue : intValue;
break;
}
if (singlePart && intPartLen == 19) {
if (intValue == 922337203685477580) {
final digit = source.codeUnitAt(intPartPos + 18) - 0x30;
if (digit <= 7) {
intValue = intValue * 10 + digit;
$v = hasSign ? -intValue : intValue;
break;
}
}
}
var doubleValue = intValue * 1.0;
var expRest = intPartLen - 18;
expRest = expRest < 0 ? 0 : expRest;
exp = expRest + exp;
final modExp = exp < 0 ? -exp : exp;
if (modExp > 22) {
state.pos = pos;
$v = double.parse(source.substring($pos, pos));
break;
}
final k = powersOfTen[modExp];
if (exp > 0) {
doubleValue *= k;
} else {
doubleValue /= k;
}
if (decValue != 0) {
var value = decValue * 1.0;
final diff = exp - decPartLen;
final modDiff = diff < 0 ? -diff : diff;
final sign = diff < 0;
var rest = modDiff;
while (rest != 0) {
var i = rest;
if (i > 20) {
i = 20;
}
rest -= i;
final k = powersOfTen[i];
if (sign) {
value /= k;
} else {
value *= k;
}
}
doubleValue += value;
}
$v = hasSign ? -doubleValue : doubleValue;
break;
}
if (state.ok) {
$1 = $v;
} else {
state.fail(state.pos, ParseError.character, 0, 0);
state.pos = $pos;
}
state.log = $log;
if (state.ok) {
$0 = $1;
} else {
state.fail(state.pos, ParseError.expected, 0, 'number');
}
return $0;
}
@pragma('vm:prefer-inline')
void _openBracket(State<String> state) {
final source = state.source;
final $pos = state.pos;
state.ok = state.pos < source.length && source.codeUnitAt(state.pos) == 91;
if (state.ok) {
state.pos += 1;
} else {
state.fail(state.pos, ParseError.expected, 0, '[');
}
if (state.ok) {
_ws(state);
}
if (!state.ok) {
state.pos = $pos;
}
}
List<dynamic>? _values(State<String> state) {
List<dynamic>? $0;
final source = state.source;
var $pos = state.pos;
final $list = <dynamic>[];
while (true) {
dynamic $1;
$1 = _value(state);
if (!state.ok) {
state.pos = $pos;
break;
}
$list.add($1);
$pos = state.pos;
final $pos1 = state.pos;
state.ok = state.pos < source.length && source.codeUnitAt(state.pos) == 44;
if (state.ok) {
state.pos += 1;
} else {
state.fail(state.pos, ParseError.expected, 0, ',');
}
if (state.ok) {
_ws(state);
}
if (!state.ok) {
state.pos = $pos1;
}
if (!state.ok) {
break;
}
}
state.ok = true;
if (state.ok) {
$0 = $list;
}
return $0;
}
@pragma('vm:prefer-inline')
void _closeBracket(State<String> state) {
final source = state.source;
final $pos = state.pos;
state.ok = state.pos < source.length && source.codeUnitAt(state.pos) == 93;
if (state.ok) {
state.pos += 1;
} else {
state.fail(state.pos, ParseError.expected, 0, ']');
}
if (state.ok) {
_ws(state);
}
if (!state.ok) {
state.pos = $pos;
}
}
List<dynamic>? _array(State<String> state) {
List<dynamic>? $0;
final $pos = state.pos;
_openBracket(state);
if (state.ok) {
$0 = _values(state);
if (state.ok) {
_closeBracket(state);
}
}
if (!state.ok) {
$0 = null;
state.pos = $pos;
}
return $0;
}
@pragma('vm:prefer-inline')
void _openBrace(State<String> state) {
final source = state.source;
final $pos = state.pos;
state.ok = state.pos < source.length && source.codeUnitAt(state.pos) == 123;
if (state.ok) {
state.pos += 1;
} else {
state.fail(state.pos, ParseError.expected, 0, '{');
}
if (state.ok) {
_ws(state);
}
if (!state.ok) {
state.pos = $pos;
}
}
MapEntry<String, dynamic>? _keyValue(State<String> state) {
MapEntry<String, dynamic>? $0;
final source = state.source;
final $pos = state.pos;
String? $1;
$1 = _string(state);
if (state.ok) {
final $pos1 = state.pos;
state.ok = state.pos < source.length && source.codeUnitAt(state.pos) == 58;
if (state.ok) {
state.pos += 1;
} else {
state.fail(state.pos, ParseError.expected, 0, ':');
}
if (state.ok) {
_ws(state);
}
if (!state.ok) {
state.pos = $pos1;
}
if (state.ok) {
dynamic $2;
$2 = _value(state);
if (state.ok) {
final v1 = $1!;
final v2 = $2;
$0 = MapEntry(v1, v2);
}
}
}
if (!state.ok) {
state.pos = $pos;
}
return $0;
}
List<MapEntry<String, dynamic>>? _keyValues(State<String> state) {
List<MapEntry<String, dynamic>>? $0;
final source = state.source;
var $pos = state.pos;
final $list = <MapEntry<String, dynamic>>[];
while (true) {
MapEntry<String, dynamic>? $1;
$1 = _keyValue(state);
if (!state.ok) {
state.pos = $pos;
break;
}
$list.add($1!);
$pos = state.pos;
final $pos1 = state.pos;
state.ok = state.pos < source.length && source.codeUnitAt(state.pos) == 44;
if (state.ok) {
state.pos += 1;
} else {
state.fail(state.pos, ParseError.expected, 0, ',');
}
if (state.ok) {
_ws(state);
}
if (!state.ok) {
state.pos = $pos1;
}
if (!state.ok) {
break;
}
}
state.ok = true;
if (state.ok) {
$0 = $list;
}
return $0;
}
@pragma('vm:prefer-inline')
void _closeBrace(State<String> state) {
final source = state.source;
final $pos = state.pos;
state.ok = state.pos < source.length && source.codeUnitAt(state.pos) == 125;
if (state.ok) {
state.pos += 1;
} else {
state.fail(state.pos, ParseError.expected, 0, '}');
}
if (state.ok) {
_ws(state);
}
if (!state.ok) {
state.pos = $pos;
}
}
dynamic _object(State<String> state) {
dynamic $0;
final $pos = state.pos;
_openBrace(state);
if (state.ok) {
List<MapEntry<String, dynamic>>? $1;
$1 = _keyValues(state);
if (state.ok) {
_closeBrace(state);
if (state.ok) {
final v1 = $1!;
$0 = Map.fromEntries(v1);
}
}
}
if (!state.ok) {
state.pos = $pos;
}
return $0;
}
dynamic _primitives(State<String> state) {
dynamic $0;
final source = state.source;
state.ok = state.pos < source.length;
if (state.ok) {
final pos = state.pos;
final c = source.codeUnitAt(pos);
dynamic v;
state.ok = false;
if (c == 102) {
if (source.startsWith('false', pos)) {
state.ok = true;
state.pos += 5;
v = false;
}
} else if (c == 116) {
if (source.startsWith('true', pos)) {
state.ok = true;
state.pos += 4;
v = true;
}
} else if (c == 110) {
if (source.startsWith('null', pos)) {
state.ok = true;
state.pos += 4;
v = null;
}
}
if (state.ok) {
$0 = v;
}
}
if (!state.ok) {
state.fail(state.pos, ParseError.expected, 0, 'false');
state.fail(state.pos, ParseError.expected, 0, 'true');
state.fail(state.pos, ParseError.expected, 0, 'null');
}
return $0;
}
dynamic _value(State<String> state) {
dynamic $0;
final $pos = state.pos;
$0 = _string(state);
if (!state.ok) {
$0 = _number(state);
if (!state.ok) {
$0 = _array(state);
if (!state.ok) {
$0 = _object(state);
if (!state.ok) {
$0 = _primitives(state);
}
}
}
}
if (state.ok) {
_ws(state);
}
if (!state.ok) {
$0 = null;
state.pos = $pos;
}
return $0;
}
String _errorMessage(String source, List<ParseError> errors,
[Object? color, int maxCount = 10, String? url]) {
final sb = StringBuffer();
for (var i = 0; i < errors.length; i++) {
if (i > maxCount) {
break;
}
final error = errors[i];
final start = error.start;
final end = error.end + 1;
if (end > source.length) {
source += ' ' * (end - source.length);
}
final file = SourceFile.fromString(source, url: url);
final span = file.span(start, end);
if (sb.isNotEmpty) {
sb.writeln();
}
sb.write(span.message(error.toString(), color: color));
}
if (errors.length > maxCount) {
sb.writeln();
sb.write('(${errors.length - maxCount} more errors...)');
}
return sb.toString();
}
class ParseError {
static const character = 0;
static const expected = 1;
static const message = 2;
static const unexpected = 3;
final int end;
final int start;
final String text;
ParseError(this.start, this.end, this.text);
@override
int get hashCode => end.hashCode ^ start.hashCode ^ text.hashCode;
@override
bool operator ==(other) =>
other is ParseError &&
other.end == end &&
other.start == start &&
other.text == text;
@override
String toString() {
return text;
}
}
class State<T> {
dynamic context;
int errorPos = -1;
int lastErrorPos = -1;
int minErrorPos = -1;
bool log = true;
bool ok = false;
int pos = 0;
final T source;
final List<int> _kinds = List.filled(150, 0);
int _length = 0;
final List<int> _lengths = List.filled(150, 0);
final List<_Memo?> _memos = List.filled(150, null);
final List<Object?> _values = List.filled(150, null);
State(this.source);
List<ParseError> get errors => _buildErrors();
@pragma('vm:prefer-inline')
void fail(int pos, int kind, int length, Object? value) {
if (log) {
if (errorPos <= pos && minErrorPos <= pos) {
if (errorPos < pos) {
errorPos = pos;
_length = 0;
}
_kinds[_length] = kind;
_lengths[_length] = length;
_values[_length] = value;
_length++;
}
if (lastErrorPos < pos) {
lastErrorPos = pos;
}
}
}
@pragma('vm:prefer-inline')
void memoize<R>(int id, bool fast, int start, [R? result]) =>
_memos[id] = _Memo<R>(id, fast, start, pos, ok, result);
@pragma('vm:prefer-inline')
_Memo<R>? memoized<R>(int id, bool fast, int start) {
final memo = _memos[id];
return (memo != null &&
memo.start == start &&
(memo.fast == fast || !memo.fast))
? memo as _Memo<R>
: null;
}
@pragma('vm:prefer-inline')
void restoreLastErrorPos(int pos) {
if (lastErrorPos < pos) {
lastErrorPos = pos;
}
}
@pragma('vm:prefer-inline')
int setLastErrorPos(int pos) {
final result = lastErrorPos;
lastErrorPos = pos;
return result;
}
@override
String toString() {
if (source is String) {
final s = source as String;
if (pos >= s.length) {
return '$pos:';
}
var length = s.length - pos;
length = length > 40 ? 40 : length;
final string = s.substring(pos, pos + length);
return '$pos:$string';
} else {
return super.toString();
}
}
List<ParseError> _buildErrors() {
final result = <ParseError>[];
final expected = <String>[];
for (var i = 0; i < _length; i++) {
final kind = _kinds[i];
if (kind == ParseError.expected) {
var value = _values[i];
final escaped = _escape(value);
expected.add(escaped);
}
}
if (expected.isNotEmpty) {
final text = 'Expected: ${expected.toSet().join(', ')}';
final error = ParseError(errorPos, errorPos, text);
result.add(error);
}
for (var i = 0; i < _length; i++) {
final kind = _kinds[i];
var length = _lengths[i];
var value = _values[i];
var start = errorPos;
final sign = length >= 0 ? 1 : -1;
length = length * sign;
if (sign == -1) {
start = start - length;
}
final end = start + (length > 0 ? length - 1 : 0);
switch (kind) {
case ParseError.character:
if (source is String) {
final string = source as String;
if (start < string.length) {
value = string.runeAt(errorPos);
final escaped = _escape(value);
final error =
ParseError(errorPos, errorPos, 'Unexpected $escaped');
result.add(error);
} else {
final error = ParseError(errorPos, errorPos, "Unexpected 'EOF'");
result.add(error);
}
} else {
final error =
ParseError(errorPos, errorPos, 'Unexpected character');
result.add(error);
}
break;
case ParseError.expected:
break;
case ParseError.message:
final error = ParseError(start, end, '$value');
result.add(error);
break;
case ParseError.unexpected:
final escaped = _escape(value);
final error = ParseError(start, end, 'Unexpected $escaped');
result.add(error);
break;
default:
final error = ParseError(start, end, '$value');
result.add(error);
}
}
return result.toSet().toList();
}
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;
}
}
extension on String {
@pragma('vm:prefer-inline')
// ignore: unused_element
int readRune(State<String> state) {
final w1 = codeUnitAt(state.pos++);
if (w1 > 0xd7ff && w1 < 0xe000) {
if (state.pos < length) {
final w2 = codeUnitAt(state.pos++);
if ((w2 & 0xfc00) == 0xdc00) {
return 0x10000 + ((w1 & 0x3ff) << 10) + (w2 & 0x3ff);
}
state.pos--;
}
throw FormatException('Invalid UTF-16 character', this, state.pos - 1);
}
return w1;
}
@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;
}
/// Returns a slice (substring) of the string from [start] to [end].
@pragma('vm:prefer-inline')
// ignore: unused_element
String slice(int start, int end) {
return substring(start, end);
}
}
class _Memo<T> {
final int end;
final bool fast;
final int id;
final bool ok;
final T? result;
final int start;
_Memo(this.id, this.fast, this.start, this.end, this.ok, this.result);
@pragma('vm:prefer-inline')
T? restore(State state) {
state.ok = ok;
state.pos = end;
return result;
}
}
@pragma('vm:prefer-inline')
int _toHexValue(String s) {
final l = s.codeUnits;
var r = 0;
for (var i = l.length - 1, j = 0; i >= 0; i--, j += 4) {
final c = l[i];
var v = 0;
if (c >= 0x30 && c <= 0x39) {
v = c - 0x30;
} else if (c >= 0x41 && c <= 0x46) {
v = c - 0x41 + 10;
} else if (c >= 0x61 && c <= 0x66) {
v = c - 0x61 + 10;
} else {
throw StateError('Internal error');
}
r += v * (1 << j);
}
return r;
}