// 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;
}
