blob: 08d3709f2a708b38426631d2ae897c449ac43be5 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library parser;
class Token {
static final RegExp _alpha = RegExp(r'^[0-9a-zA-Z_\-@]+$');
final String? text;
Token? next;
Token(this.text);
bool get eof => text == null;
bool get isName {
if (text == null || text!.isEmpty) return false;
return _alpha.hasMatch(text!);
}
bool get isComment => text != null && text!.startsWith('//');
String toString() => text == null ? 'EOF' : text!;
}
class Tokenizer {
static final alphaNum =
'@abcdefghijklmnopqrstuvwxyz-_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
static final whitespace = ' \n\t\r';
String text;
Token? _head;
Token? _last;
Tokenizer(this.text);
Token? tokenize() {
_emit(null);
for (int i = 0; i < text.length; i++) {
String c = text[i];
if (whitespace.contains(c)) {
// skip
} else if (c == '/' && _peek(i) == '/') {
int index = text.indexOf('\n', i);
if (index == -1) index = text.length;
_emit(text.substring(i, index));
i = index;
} else if (alphaNum.contains(c)) {
int start = i;
while (alphaNum.contains(_peek(i))) {
i++;
}
_emit(text.substring(start, i + 1));
} else {
_emit(c);
}
}
_emit(null);
_head = _head!.next;
return _head;
}
void _emit(String? value) {
Token token = Token(value);
if (_head == null) _head = token;
if (_last != null) _last!.next = token;
_last = token;
}
String _peek(int i) {
i += 1;
return i < text.length ? text[i] : String.fromCharCodes([0]);
}
String toString() {
StringBuffer buf = StringBuffer();
Token t = _head!;
buf.write('[${t}]\n');
while (!t.eof) {
t = t.next!;
buf.write('[${t}]\n');
}
return buf.toString().trim();
}
}
abstract class Parser {
final Token? startToken;
Token? current;
Parser(this.startToken);
Token expect(String text) {
Token t = advance()!;
if (text != t.text) fail('expected ${text}, got ${t}');
return t;
}
bool consume(String text) {
if (peek()!.text == text) {
advance();
return true;
} else {
return false;
}
}
Token? peek() => current == null
? startToken
: current!.eof
? current
: current!.next;
Token expectName() {
Token t = advance()!;
if (!t.isName) fail('expected name token, got ${t}');
return t;
}
Token? advance() {
if (current == null) {
current = startToken;
} else if (!current!.eof) {
current = current!.next;
}
return current;
}
String? collectComments() {
StringBuffer buf = StringBuffer();
while (peek()!.isComment) {
Token t = advance()!;
String str = t.text!.substring(2);
if (str.startsWith(' ')) str = str.substring(1);
if (str.startsWith(' ')) {
buf.write('\n - ${str.substring(2)}');
} else if (str.isEmpty) {
buf.write('\n\n');
} else {
buf.write('${str} ');
}
}
if (buf.isEmpty) return null;
return buf
.toString()
.split('\n')
.map((line) => line.trimRight())
.join('\n')
.trim();
}
void validate(bool result, String message) {
if (!result) throw 'expected ${message}';
}
void fail(String message) => throw message;
}