// 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('//'); @override 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); _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]); } @override 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(); } String? consumeString() { StringBuffer buf = StringBuffer(); String startQuotation = advance()!.text!; if (startQuotation != '"' && startQuotation != "'") { return null; } while (peek()!.text != startQuotation) { Token t = advance()!; if (t.text == null) { throw FormatException('Reached EOF'); } buf.write('${t.text} '); } advance(); return buf.toString().trim(); } void validate(bool result, String message) { if (!result) throw 'expected $message'; } void fail(String message) => throw message; }