#pragma once

#include <cassert>
#include <cctype>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <initializer_list>
#include <iterator>
#include <tuple>
#include <utility>

#include "musttail.h"
#include "tables.h"
#include "weaseljson.h"

namespace parser3 {

enum Status {
  // Accept input
  S_OK,
  // Consumed all available input.
  S_AGAIN,
  // Invalid json
  S_REJECT,
  // json is too deeply nested
  S_OVERFLOW,
};

typedef Status (*Continuation)(struct Parser3 *);

// These appear in the stack of the pushdown
// automata
enum Symbol : uint8_t {
  N_JSON,
  N_VALUE,
  N_OBJECT,
  N_OBJECT2,
  N_OBJECT3,
  N_ARRAY,
  N_ARRAY2,
  N_ARRAY3,
  N_ELEMENT,
  N_STRING,
  N_STRING2,
  N_STRING_FOLLOWING_ESCAPE,
  N_NUMBER,
  N_INTEGER,
  N_INTEGER2,
  N_DIGITS,
  N_DIGITS2,
  N_FRACTION,
  N_EXPONENT,
  N_SIGN,
  N_WHITESPACE,
  N_TRUE,
  N_FALSE,
  N_NULL,
  T_R,
  T_U,
  T_A,
  T_L,
  T_S,
  T_COLON,
  T_UTF8_CONTINUATION_BYTE,
  T_UTF8_LAST_CONTINUATION_BYTE,
  T_HEX,
  T_DIGIT,
  T_ONENINE,
  T_EOF,
  N_SYMBOL_COUNT, // Must be last
};
struct Parser3 {
  Parser3(const Callbacks *callbacks, void *data)
      : callbacks(callbacks), data(data) {
    std::ignore = push({N_JSON, T_EOF});
  }

  [[nodiscard]] Status parse(char *buf, int len) {
    complete = len == 0;
    this->buf = buf;
    this->bufEnd = buf + len;
    return keepGoing(this);
  }

  [[nodiscard]] bool empty() const { return stackPtr == stack; }
  void pop() {
    assert(!empty());
    --stackPtr;
  }
  [[nodiscard]] Status push(std::initializer_list<Symbol> symbols) {
    if (stackPtr >= std::end(stack) - symbols.size()) [[unlikely]] {
      return S_OVERFLOW;
    }
    for (int i = symbols.size() - 1; i >= 0; --i) {
      *stackPtr++ = *(symbols.begin() + i);
    }
    return S_OK;
  }
  [[nodiscard]] int len() const {
    auto result = bufEnd - buf;
    assert(result >= 0);
    return result;
  }
  Symbol top() const {
    assert(!empty());
    return *(stackPtr - 1);
  }

  static Status keepGoing(Parser3 *self);

  constexpr static int kMaxStackSize = 1024;

  [[maybe_unused]] void debugPrint();
  char *buf = nullptr;
  char *bufEnd = nullptr;
  const Callbacks *const callbacks;
  void *const data;
  Symbol stack[kMaxStackSize];
  Symbol *stackPtr = stack;
  bool complete = false;
  uint32_t utf8Codepoint;
  uint32_t minCodepoint;
};

inline Status n_json(Parser3 *self) {
  self->pop();
  if (auto s = self->push({N_ELEMENT})) {
    return s;
  }
  MUSTTAIL return Parser3::keepGoing(self);
}

inline Status n_value(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  switch (*self->buf) {
  case '{':
    self->pop();
    if (auto s = self->push({N_OBJECT})) {
      return s;
    }
    break;
  case '[':
    self->pop();
    if (auto s = self->push({N_ARRAY})) {
      return s;
    }
    break;
  case '"':
    self->pop();
    if (auto s = self->push({N_STRING})) {
      return s;
    }
    break;
  case '0':
  case '1':
  case '2':
  case '3':
  case '4':
  case '5':
  case '6':
  case '7':
  case '8':
  case '9':
  case '-':
    self->pop();
    if (auto s = self->push({N_NUMBER})) {
      return s;
    }
    break;
  case 't':
    ++self->buf;
    self->pop();
    if (auto s = self->push({T_R, T_U, N_TRUE})) {
      return s;
    }
    break;
  case 'f':
    ++self->buf;
    self->pop();
    if (auto s = self->push({T_A, T_L, T_S, N_FALSE})) {
      return s;
    }
    break;
  case 'n':
    ++self->buf;
    self->pop();
    if (auto s = self->push({T_U, T_L, N_NULL})) {
      return s;
    }
    break;
  default:
    return S_REJECT;
  }
  MUSTTAIL return Parser3::keepGoing(self);
}

inline Status n_object(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  if (*self->buf != '{') {
    return S_REJECT;
  }
  self->callbacks->on_begin_object(self->data);
  ++self->buf;
  self->pop();
  if (auto s = self->push({N_WHITESPACE, N_OBJECT2})) {
    return s;
  }
  MUSTTAIL return Parser3::keepGoing(self);
}

inline Status n_object2(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  switch (*self->buf) {
  case '}':
    ++self->buf;
    self->pop();
    self->callbacks->on_end_object(self->data);
    MUSTTAIL return Parser3::keepGoing(self);
  case '"':
    self->pop();
    if (auto s = self->push(
            {N_STRING, N_WHITESPACE, T_COLON, N_ELEMENT, N_OBJECT3})) {
      return s;
    }
    MUSTTAIL return Parser3::keepGoing(self);
  default:
    return S_REJECT;
  }
}

inline Status n_object3(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  switch (*self->buf) {
  case '}':
    ++self->buf;
    self->pop();
    self->callbacks->on_end_object(self->data);
    MUSTTAIL return Parser3::keepGoing(self);
  case ',':
    ++self->buf;
    self->pop();
    if (auto s = self->push({N_WHITESPACE, N_STRING, N_WHITESPACE, T_COLON,
                             N_ELEMENT, N_OBJECT3})) {
      return s;
    }
    MUSTTAIL return Parser3::keepGoing(self);
  default:
    return S_REJECT;
  }
}

inline Status n_array(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  if (*self->buf != '[') {
    return S_REJECT;
  }
  self->callbacks->on_begin_array(self->data);
  ++self->buf;
  self->pop();
  if (auto s = self->push({N_WHITESPACE, N_ARRAY2})) {
    return s;
  }
  MUSTTAIL return Parser3::keepGoing(self);
}

inline Status n_array2(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  switch (*self->buf) {
  case ']':
    ++self->buf;
    self->pop();
    self->callbacks->on_end_array(self->data);
    MUSTTAIL return Parser3::keepGoing(self);
  default:
    self->pop();
    if (auto s = self->push({N_VALUE, N_WHITESPACE, N_ARRAY3})) {
      return s;
    }
    MUSTTAIL return Parser3::keepGoing(self);
  }
}

inline Status n_array3(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  switch (*self->buf) {
  case ']':
    ++self->buf;
    self->pop();
    self->callbacks->on_end_array(self->data);
    MUSTTAIL return Parser3::keepGoing(self);
  case ',':
    ++self->buf;
    self->pop();
    if (auto s = self->push({N_ELEMENT, N_ARRAY3})) {
      return s;
    }
    MUSTTAIL return Parser3::keepGoing(self);
  default:
    return S_REJECT;
  }
}

inline Status n_element(Parser3 *self) {
  self->pop();
  if (auto s = self->push({N_WHITESPACE, N_VALUE, N_WHITESPACE})) {
    return s;
  }
  MUSTTAIL return Parser3::keepGoing(self);
}

inline Status n_string(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  if (*self->buf != '"') {
    return S_REJECT;
  }
  self->callbacks->on_begin_string(self->data);
  ++self->buf;
  self->pop();
  if (auto s = self->push({N_STRING2})) {
    return s;
  }
  MUSTTAIL return Parser3::keepGoing(self);
}

inline Status n_string2(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  // Try subtract and unsigned compare to save a branch?
  if (uint8_t(*self->buf) < 0x20) {
    return S_REJECT;
  }
  if (*self->buf != '"') {
    self->callbacks->on_string_data(self->data, self->buf, 1);
  }
  if (tables.invalidUtf8[uint8_t(*self->buf)]) {
    return S_REJECT;
  }
  if (int8_t(*self->buf) > 0) {
    // one byte utf-8 encoding
    switch (*self->buf) {
    case '"':
      ++self->buf;
      self->pop();
      self->callbacks->on_end_string(self->data);
      MUSTTAIL return Parser3::keepGoing(self);
    case '\\':
      ++self->buf;
      self->pop();
      if (auto s = self->push({N_STRING_FOLLOWING_ESCAPE})) {
        return s;
      }
      MUSTTAIL return Parser3::keepGoing(self);
    default:
      ++self->buf;
      MUSTTAIL return Parser3::keepGoing(self);
    }
  } else if ((*self->buf & 0b11100000) == 0b11000000) {
    // two byte utf-8 encoding
    self->utf8Codepoint = *self->buf & 0b00011111;
    self->minCodepoint = 0x80;
    ++self->buf;
    self->pop();
    if (auto s = self->push({T_UTF8_LAST_CONTINUATION_BYTE, N_STRING2})) {
      return s;
    }
    MUSTTAIL return Parser3::keepGoing(self);
  }
  if ((*self->buf & 0b11110000) == 0b11100000) {
    // three byte utf-8 encoding
    self->utf8Codepoint = *self->buf & 0b00001111;
    self->minCodepoint = 0x800;
    ++self->buf;
    self->pop();
    if (auto s = self->push({T_UTF8_CONTINUATION_BYTE,
                             T_UTF8_LAST_CONTINUATION_BYTE, N_STRING2})) {
      return s;
    }
    MUSTTAIL return Parser3::keepGoing(self);
  } else if ((*self->buf & 0b11111000) == 0b11110000) {
    // four byte utf-8 encoding
    self->utf8Codepoint = *self->buf & 0b00000111;
    self->minCodepoint = 0x10000;
    ++self->buf;
    self->pop();
    if (auto s = self->push({T_UTF8_CONTINUATION_BYTE, T_UTF8_CONTINUATION_BYTE,
                             T_UTF8_LAST_CONTINUATION_BYTE, N_STRING2})) {
      return s;
    }
    MUSTTAIL return Parser3::keepGoing(self);
  }
  return S_REJECT;
}

inline Status n_string_following_escape(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  self->callbacks->on_string_data(self->data, self->buf, 1);
  switch (*self->buf) {
  case '"':
  case '\\':
  case '/':
  case 'b':
  case 'f':
  case 'n':
  case 'r':
  case 't':
    ++self->buf;
    self->pop();
    if (auto s = self->push({N_STRING2})) {
      return s;
    }
    MUSTTAIL return Parser3::keepGoing(self);
  case 'u':
    ++self->buf;
    self->pop();
    if (auto s = self->push({T_HEX, T_HEX, T_HEX, T_HEX, N_STRING2})) {
      return s;
    }
    MUSTTAIL return Parser3::keepGoing(self);
  default:
    return S_REJECT;
  }
}

inline Status t_utf8_continuation_byte(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  if (tables.invalidUtf8[uint8_t(*self->buf)]) {
    return S_REJECT;
  }
  if ((*self->buf & 0b11000000) == 0b10000000) {
    self->utf8Codepoint <<= 6;
    self->utf8Codepoint |= *self->buf & 0b00111111;
    self->callbacks->on_string_data(self->data, self->buf, 1);
    ++self->buf;
    self->pop();
    MUSTTAIL return Parser3::keepGoing(self);
  }
  return S_REJECT;
}

inline Status t_utf8_last_continuation_byte(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  if (tables.invalidUtf8[uint8_t(*self->buf)]) {
    return S_REJECT;
  }
  if ((*self->buf & 0b11000000) == 0b10000000) {
    self->utf8Codepoint <<= 6;
    self->utf8Codepoint |= *self->buf & 0b00111111;
    if (self->utf8Codepoint < self->minCodepoint ||
        self->utf8Codepoint > 0x10ffff ||
        (0xd800 <= self->utf8Codepoint && self->utf8Codepoint <= 0xdfff)) {
      return S_REJECT;
    }
    // TODO tell valgrind utf8Codepoint and minCodepoint are uninitialized
    self->callbacks->on_string_data(self->data, self->buf, 1);
    ++self->buf;
    self->pop();
    MUSTTAIL return Parser3::keepGoing(self);
  }
  return S_REJECT;
}

inline Status t_digit(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  if ('0' <= *self->buf && *self->buf <= '9') {
    ++self->buf;
    self->pop();
    MUSTTAIL return Parser3::keepGoing(self);
  }
  return S_REJECT;
}

inline Status t_onenine(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  if ('1' <= *self->buf && *self->buf <= '9') {
    ++self->buf;
    self->pop();
    MUSTTAIL return Parser3::keepGoing(self);
  }
  return S_REJECT;
}

inline Status t_hex(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  if (('0' <= *self->buf && *self->buf <= '9') ||
      ('a' <= *self->buf && *self->buf <= 'f') ||
      ('A' <= *self->buf && *self->buf <= 'F')) {
    self->callbacks->on_string_data(self->data, self->buf, 1);
    ++self->buf;
    self->pop();
    MUSTTAIL return Parser3::keepGoing(self);
  }
  return S_REJECT;
}

inline Status n_number(Parser3 *self) {
  self->pop();
  if (auto s = self->push({N_INTEGER, N_FRACTION, N_EXPONENT})) {
    return s;
  }
  MUSTTAIL return Parser3::keepGoing(self);
}

inline Status n_integer(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  self->callbacks->on_begin_number(self->data);
  switch (*self->buf) {
  case '0':
    self->callbacks->on_number_data(self->data, self->buf, 1);
    ++self->buf;
    self->pop();
    MUSTTAIL return Parser3::keepGoing(self);
  case '1':
  case '2':
  case '3':
  case '4':
  case '5':
  case '6':
  case '7':
  case '8':
  case '9':
    self->callbacks->on_number_data(self->data, self->buf, 1);
    ++self->buf;
    self->pop();
    if (auto s = self->push({N_DIGITS2})) {
      return s;
    }
    MUSTTAIL return Parser3::keepGoing(self);
  case '-':
    self->callbacks->on_number_data(self->data, self->buf, 1);
    ++self->buf;
    self->pop();
    if (auto s = self->push({N_INTEGER2})) {
      return s;
    }
    MUSTTAIL return Parser3::keepGoing(self);
  default:
    return S_REJECT;
  }
}

inline Status n_integer2(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  switch (*self->buf) {
  case '0':
    self->callbacks->on_number_data(self->data, self->buf, 1);
    ++self->buf;
    self->pop();
    MUSTTAIL return Parser3::keepGoing(self);
  case '1':
  case '2':
  case '3':
  case '4':
  case '5':
  case '6':
  case '7':
  case '8':
  case '9':
    self->callbacks->on_number_data(self->data, self->buf, 1);
    ++self->buf;
    self->pop();
    if (auto s = self->push({N_DIGITS2})) {
      return s;
    }
    MUSTTAIL return Parser3::keepGoing(self);
  default:
    return S_REJECT;
  }
}

inline Status n_digits(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  switch (*self->buf) {
  case '0':
  case '1':
  case '2':
  case '3':
  case '4':
  case '5':
  case '6':
  case '7':
  case '8':
  case '9':
    self->callbacks->on_number_data(self->data, self->buf, 1);
    ++self->buf;
    self->pop();
    if (auto s = self->push({N_DIGITS2})) {
      return s;
    }
    MUSTTAIL return Parser3::keepGoing(self);
  default:
    return S_REJECT;
  }
}

inline Status n_digits2(Parser3 *self) {
  if (self->len() == 0) {
    self->pop();
    MUSTTAIL return Parser3::keepGoing(self);
  }
  switch (*self->buf) {
  case '0':
  case '1':
  case '2':
  case '3':
  case '4':
  case '5':
  case '6':
  case '7':
  case '8':
  case '9':
    self->callbacks->on_number_data(self->data, self->buf, 1);
    ++self->buf;
    MUSTTAIL return Parser3::keepGoing(self);
  default:
    self->pop();
    MUSTTAIL return Parser3::keepGoing(self);
  }
}

inline Status n_fraction(Parser3 *self) {
  if (self->len() == 0) {
    self->pop();
    MUSTTAIL return Parser3::keepGoing(self);
  }
  switch (*self->buf) {
  case '.':
    self->callbacks->on_number_data(self->data, self->buf, 1);
    ++self->buf;
    self->pop();
    if (auto s = self->push({N_DIGITS})) {
      return s;
    }
    MUSTTAIL return Parser3::keepGoing(self);
  default:
    self->pop();
    MUSTTAIL return Parser3::keepGoing(self);
  }
}

inline Status n_exponent(Parser3 *self) {
  if (self->len() == 0) {
    self->pop();
    MUSTTAIL return Parser3::keepGoing(self);
  }
  switch (*self->buf) {
  case 'e':
  case 'E':
    self->callbacks->on_number_data(self->data, self->buf, 1);
    ++self->buf;
    self->pop();
    if (auto s = self->push({N_SIGN, N_DIGITS})) {
      return s;
    }
    MUSTTAIL return Parser3::keepGoing(self);
  default:
    self->pop();
    MUSTTAIL return Parser3::keepGoing(self);
  }
}

inline Status n_sign(Parser3 *self) {
  if (self->len() == 0) {
    self->pop();
    MUSTTAIL return Parser3::keepGoing(self);
  }
  switch (*self->buf) {
  case '+':
  case '-':
    self->callbacks->on_number_data(self->data, self->buf, 1);
    ++self->buf;
    self->pop();
    MUSTTAIL return Parser3::keepGoing(self);
  default:
    self->pop();
    MUSTTAIL return Parser3::keepGoing(self);
  }
}

inline Status n_whitespace(Parser3 *self) {
  if (self->len() == 0) {
    self->pop();
    MUSTTAIL return Parser3::keepGoing(self);
  }
  if (tables.whitespace[uint8_t(*self->buf)]) {
    ++self->buf;
    MUSTTAIL return Parser3::keepGoing(self);
  }
  self->pop();
  MUSTTAIL return Parser3::keepGoing(self);
}

inline Status n_true(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  if (*self->buf == 'e') {
    ++self->buf;
    self->pop();
    self->callbacks->on_true_literal(self->data);
    MUSTTAIL return Parser3::keepGoing(self);
  }
  return S_REJECT;
}

inline Status n_false(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  if (*self->buf == 'e') {
    ++self->buf;
    self->pop();
    self->callbacks->on_false_literal(self->data);
    MUSTTAIL return Parser3::keepGoing(self);
  }
  return S_REJECT;
}

inline Status n_null(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  if (*self->buf == 'l') {
    ++self->buf;
    self->pop();
    self->callbacks->on_null_literal(self->data);
    MUSTTAIL return Parser3::keepGoing(self);
  }
  return S_REJECT;
}

template <char kChar> inline Status singleChar(Parser3 *self) {
  if (self->len() == 0) {
    return S_REJECT;
  }
  if (*self->buf == kChar) {
    ++self->buf;
    self->pop();
    MUSTTAIL return Parser3::keepGoing(self);
  }
  return S_REJECT;
}

inline Status t_eof(Parser3 *self) {
  if (self->len() > 0) {
    return S_REJECT;
  }
  return self->complete ? S_OK : S_AGAIN;
}

constexpr inline struct ContinuationTable {
  constexpr ContinuationTable() {
    // Defaults
    for (int i = 0; i < N_SYMBOL_COUNT; ++i) {
      continuations[i] = +[](struct Parser3 *) {
        printf("unimplemented\n");
        return S_REJECT;
      };
    }
    continuations[N_JSON] = n_json;
    continuations[N_VALUE] = n_value;
    continuations[N_OBJECT] = n_object;
    continuations[N_OBJECT2] = n_object2;
    continuations[N_OBJECT3] = n_object3;
    continuations[N_ARRAY] = n_array;
    continuations[N_ARRAY2] = n_array2;
    continuations[N_ARRAY3] = n_array3;
    continuations[N_ELEMENT] = n_element;
    continuations[N_STRING] = n_string;
    continuations[N_STRING2] = n_string2;
    continuations[N_STRING_FOLLOWING_ESCAPE] = n_string_following_escape;
    continuations[N_NUMBER] = n_number;
    continuations[N_INTEGER] = n_integer;
    continuations[N_INTEGER2] = n_integer2;
    continuations[N_DIGITS] = n_digits;
    continuations[N_DIGITS2] = n_digits2;
    continuations[N_FRACTION] = n_fraction;
    continuations[N_EXPONENT] = n_exponent;
    continuations[N_SIGN] = n_sign;
    continuations[N_WHITESPACE] = n_whitespace;
    continuations[N_TRUE] = n_true;
    continuations[N_FALSE] = n_false;
    continuations[N_NULL] = n_null;
    continuations[T_R] = singleChar<'r'>;
    continuations[T_U] = singleChar<'u'>;
    continuations[T_A] = singleChar<'a'>;
    continuations[T_L] = singleChar<'l'>;
    continuations[T_S] = singleChar<'s'>;
    continuations[T_COLON] = singleChar<':'>;
    continuations[T_UTF8_CONTINUATION_BYTE] = t_utf8_continuation_byte;
    continuations[T_UTF8_LAST_CONTINUATION_BYTE] =
        t_utf8_last_continuation_byte;
    continuations[T_HEX] = t_hex;
    continuations[T_DIGIT] = t_digit;
    continuations[T_ONENINE] = t_onenine;
    continuations[T_EOF] = t_eof;
    symbolNames[N_JSON] = "n_json";
    symbolNames[N_VALUE] = "n_value";
    symbolNames[N_OBJECT] = "n_object";
    symbolNames[N_OBJECT2] = "n_object2";
    symbolNames[N_OBJECT3] = "n_object3";
    symbolNames[N_ARRAY] = "n_array";
    symbolNames[N_ARRAY2] = "n_array2";
    symbolNames[N_ARRAY3] = "n_array3";
    symbolNames[N_ELEMENT] = "n_element";
    symbolNames[N_STRING] = "n_string";
    symbolNames[N_STRING2] = "n_string2";
    symbolNames[N_STRING_FOLLOWING_ESCAPE] = "n_string_following_escape";
    symbolNames[N_NUMBER] = "n_number";
    symbolNames[N_INTEGER] = "n_integer";
    symbolNames[N_INTEGER2] = "n_integer2";
    symbolNames[N_DIGITS] = "n_digits";
    symbolNames[N_DIGITS2] = "n_digits2";
    symbolNames[N_FRACTION] = "n_fraction";
    symbolNames[N_EXPONENT] = "n_exponent";
    symbolNames[N_SIGN] = "n_sign";
    symbolNames[N_WHITESPACE] = "n_whitespace";
    symbolNames[N_TRUE] = "n_true";
    symbolNames[N_FALSE] = "n_false";
    symbolNames[N_NULL] = "n_null";
    symbolNames[T_R] = "singleChar<'r'>";
    symbolNames[T_U] = "singleChar<'u'>";
    symbolNames[T_A] = "singleChar<'a'>";
    symbolNames[T_L] = "singleChar<'l'>";
    symbolNames[T_S] = "singleChar<'s'>";
    symbolNames[T_COLON] = "singleChar<':'>";
    symbolNames[T_UTF8_CONTINUATION_BYTE] = "t_utf8_continuation_byte";
    symbolNames[T_HEX] = "t_hex";
    symbolNames[T_DIGIT] = "t_digit";
    symbolNames[T_ONENINE] = "t_onenine";
    symbolNames[T_EOF] = "t_eof";
  }
  Continuation continuations[N_SYMBOL_COUNT]{};
  const char *symbolNames[N_SYMBOL_COUNT]{};
} symbolTables;

inline Status Parser3::keepGoing(Parser3 *self) {
  if (self->len() == 0 && !self->complete) {
    return S_AGAIN;
  }
  // self->debugPrint();
  MUSTTAIL return symbolTables.continuations[self->top()](self);
}

inline void Parser3::debugPrint() {
  for (int i = 0; i < stackPtr - stack; ++i) {
    printf("%s ", symbolTables.symbolNames[stack[i]]);
  }
  printf("\n");
  for (int i = 0; i < len(); ++i) {
    if (isprint(buf[i])) {
      printf("%c", buf[i]);
    } else {
      printf("\\x%02x", uint8_t(buf[i]));
    }
  }
  printf("\n");
}

} // namespace parser3
