current commit
260924.6 · [WATParser] Do not clear earlier annotations when lexing annotations (#9143)
/*
 * Copyright 2023 WebAssembly Community Group participants
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef parser_lexer_h
#define parser_lexer_h

#include <cassert>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <optional>
#include <ostream>
#include <sstream>
#include <string_view>
#include <variant>
#include <vector>

#include "support/bits.h"
#include "support/name.h"
#include "support/result.h"
#include "support/string.h"

namespace wasm::WATParser {

struct TextPos {
  size_t line;
  size_t col;

  bool operator==(const TextPos& other) const {
    return line == other.line && col == other.col;
  }
  bool operator!=(const TextPos& other) const { return !(*this == other); }
};

inline std::ostream& operator<<(std::ostream& os, const TextPos& pos) {
  return os << pos.line << ":" << pos.col;
}

// ===========
// Annotations
// ===========

struct Annotation {
  Name kind;
  std::string_view contents;
};

// =====
// Lexer
// =====

struct Lexer {
private:
  size_t pos = 0;
  std::vector<Annotation> annotations;
  std::optional<std::string> file;

  static bool isSpacechar(uint8_t c);

public:
  std::string_view buffer;

  Lexer(std::string_view buffer,
        std::optional<std::string> file = std::nullopt);

  size_t getPos() const { return pos; }

  void setPos(size_t i) {
    pos = i;
    advance();
  }

  // Consume the next `n` characters.
  void take(size_t n) { pos += n; }
  void takeAll() { pos = buffer.size(); }

  // Whether the unlexed input starts with prefix `sv`.
  size_t startsWith(std::string_view sv) const {
    return next().starts_with(sv);
  }

  std::optional<char> peekChar() const;

  bool peekLParen() { return peek() == uint8_t('('); }

  bool takeLParen();

  bool peekRParen() { return peek() == uint8_t(')'); }

  bool takeRParen();

  bool takeUntilParen();

  std::optional<Name> takeID();

  std::optional<std::string_view> peekKeyword();

  std::optional<std::string_view> takeKeyword();
  bool takeKeyword(std::string_view expected);

  std::optional<uint64_t> takeOffset();
  std::optional<uint32_t> takeAlign();

  std::optional<uint64_t> takeU64() { return takeU<uint64_t>(); }
  std::optional<uint64_t> takeI64() { return takeI<uint64_t>(); }
  std::optional<uint32_t> takeU32() { return takeU<uint32_t>(); }
  std::optional<uint32_t> takeI32() { return takeI<uint32_t>(); }
  std::optional<uint16_t> takeI16() { return takeI<uint16_t>(); }
  std::optional<uint8_t> takeU8() { return takeU<uint8_t>(); }
  std::optional<uint8_t> takeI8() { return takeI<uint8_t>(); }

  std::optional<float> takeF32();
  std::optional<double> takeF64();

  std::optional<std::string> takeString();

  std::optional<Name> takeName();

  bool takeSExprStart(std::string_view expected);

  bool peekSExprStart(std::string_view expected);

  std::string_view next() const { return buffer.substr(pos); }

  std::optional<uint8_t> peek() const {
    if (empty()) {
      return std::nullopt;
    }
    return uint8_t(buffer[pos]);
  }

  void advance() {
    annotations.clear();
    skipSpace();
  }

  bool empty() const { return pos == buffer.size(); }
  size_t remaining() const { return buffer.size() - pos; }

  TextPos position(const char* c) const;

  TextPos position(size_t i) const { return position(buffer.data() + i); }
  TextPos position(std::string_view span) const {
    return position(span.data());
  }
  TextPos position() const { return position(getPos()); }

  [[nodiscard]] Err err(size_t pos, std::string reason);

  [[nodiscard]] Err err(std::string reason) { return err(getPos(), reason); }

  const std::vector<Annotation> getAnnotations() { return annotations; }
  std::vector<Annotation> takeAnnotations() { return std::move(annotations); }

  void setAnnotations(std::vector<Annotation> annotations) {
    this->annotations = std::move(annotations);
  }

private:
  std::optional<int> getDigit(char c);

  std::optional<int> getHexDigit(char c);

  // Consume the prefix and return true if possible.
  bool takePrefix(std::string_view sv);

  std::optional<int> takeDigit();

  std::optional<int> takeHexdigit();

  enum OverflowBehavior { DisallowOverflow, IgnoreOverflow };

  std::optional<uint64_t> takeNum(OverflowBehavior behavior = DisallowOverflow);

  std::optional<uint64_t>
  takeHexnum(OverflowBehavior behavior = DisallowOverflow);

  enum Sign { NoSign, Pos, Neg };

  Sign takeSign();

  struct LexedInteger {
    uint64_t n;
    Sign sign;

    template<typename T> bool isUnsigned();
    template<typename T> bool isSigned();
  };

  std::optional<LexedInteger> takeInteger();

  template<typename T> std::optional<T> takeU();

  template<typename T> std::optional<T> takeS();

  template<typename T> std::optional<T> takeI();

  std::optional<std::string_view> takeDecfloat();

  std::optional<std::string_view> takeHexfloat();

  struct LexedFloat {
    std::optional<uint64_t> nanPayload;
    double d;
  };

  std::optional<LexedFloat> takeFloat();

  struct StringOrView : std::variant<std::string, std::string_view> {
    using std::variant<std::string, std::string_view>::variant;
    std::string_view str() const {
      return std::visit([](auto& s) -> std::string_view { return s; }, *this);
    }
  };

  std::optional<StringOrView> takeStr();

  bool idchar();

  std::optional<StringOrView> takeIdent();

  bool spacechar();

  bool takeSpacechar();

  bool takeComment();

  bool takeSpace();

  std::optional<Annotation> takeAnnotation();

  void skipSpace();

  bool canFinish();
};

inline Lexer::Lexer(std::string_view buffer, std::optional<std::string> file)
  : file(file), buffer(buffer) {
  setPos(0);
}

inline std::optional<char> Lexer::peekChar() const {
  if (auto c = peek()) {
    return char(*c);
  }
  return std::nullopt;
}

inline bool Lexer::takeLParen() {
  if (peekLParen()) {
    take(1);
    advance();
    return true;
  }
  return false;
}

inline bool Lexer::takeRParen() {
  if (peekRParen()) {
    take(1);
    advance();
    return true;
  }
  return false;
}

inline bool Lexer::takeUntilParen() {
  while (true) {
    if (empty()) {
      return false;
    }
    if (peekLParen() || peekRParen()) {
      return true;
    }
    // Do not count the parentheses in strings.
    if (takeString()) {
      continue;
    }
    ++pos;
    advance();
  }
}

inline std::optional<Name> Lexer::takeID() {
  if (auto result = takeIdent()) {
    auto name = Name(result->str());
    advance();
    return name;
  }
  return std::nullopt;
}

inline std::optional<std::string_view> Lexer::peekKeyword() {
  auto start = peek();
  if (!start || *start < 'a' || *start > 'z') {
    return std::nullopt;
  }
  auto startPos = pos;
  take(1);
  while (idchar()) {
    take(1);
  }
  auto ret = buffer.substr(startPos, pos - startPos);
  pos = startPos;
  return ret;
}

inline std::optional<std::string_view> Lexer::takeKeyword() {
  auto keyword = peekKeyword();
  if (keyword) {
    take(keyword->size());
    advance();
  }
  return keyword;
}

inline bool Lexer::takeKeyword(std::string_view expected) {
  if (!startsWith(expected)) {
    return false;
  }
  auto startPos = pos;
  take(expected.size());
  if (canFinish()) {
    advance();
    return true;
  }
  pos = startPos;
  return false;
}

inline std::optional<uint64_t> Lexer::takeOffset() {
  using namespace std::string_view_literals;
  auto startPos = pos;
  if (auto offset = takeKeyword()) {
    if (!offset->starts_with("offset="sv)) {
      pos = startPos;
      return std::nullopt;
    }
    Lexer subLexer(offset->substr(7));
    if (auto o = subLexer.takeU64()) {
      advance();
      return o;
    }
  }
  pos = startPos;
  return std::nullopt;
}

inline std::optional<uint32_t> Lexer::takeAlign() {
  using namespace std::string_view_literals;
  auto startPos = pos;
  if (auto result = takeKeyword()) {
    if (!result->starts_with("align="sv)) {
      pos = startPos;
      return std::nullopt;
    }
    Lexer subLexer(result->substr(6));
    if (auto o = subLexer.takeU32()) {
      if (Bits::popCount(*o) != 1) {
        pos = startPos;
        return std::nullopt;
      }
      advance();
      return o;
    }
  }
  pos = startPos;
  return std::nullopt;
}

inline std::optional<float> Lexer::takeF32() {
  constexpr int signif = 23;
  constexpr uint32_t payloadMask = (1u << signif) - 1;
  constexpr uint64_t nanDefault = 1ull << (signif - 1);
  auto startPos = pos;
  if (auto result = takeFloat()) {
    float f = result->d;
    if (std::isnan(f)) {
      // Validate and inject payload.
      uint64_t payload = result->nanPayload ? *result->nanPayload : nanDefault;
      if (payload == 0 || payload > payloadMask) {
        // TODO: Add error production for out-of-bounds payload.
        pos = startPos;
        return std::nullopt;
      }
      uint32_t bits;
      static_assert(sizeof(bits) == sizeof(f));
      memcpy(&bits, &f, sizeof(bits));
      bits = (bits & ~payloadMask) | payload;
      memcpy(&f, &bits, sizeof(bits));
    }
    advance();
    return f;
  }
  if (auto result = takeInteger()) {
    advance();
    if (result->sign == Neg) {
      if (result->n == 0) {
        return -0.0f;
      }
      return -static_cast<float>(result->n);
    }
    return static_cast<float>(result->n);
  }
  return std::nullopt;
}

inline std::optional<double> Lexer::takeF64() {
  constexpr int signif = 52;
  constexpr uint64_t payloadMask = (1ull << signif) - 1;
  constexpr uint64_t nanDefault = 1ull << (signif - 1);
  auto startPos = pos;
  if (auto result = takeFloat()) {
    double d = result->d;
    if (std::isnan(d)) {
      // Inject payload.
      uint64_t payload = result->nanPayload ? *result->nanPayload : nanDefault;
      if (payload == 0 || payload > payloadMask) {
        // TODO: Add error production for out-of-bounds payload.
        pos = startPos;
        return std::nullopt;
      }
      uint64_t bits;
      static_assert(sizeof(bits) == sizeof(d));
      memcpy(&bits, &d, sizeof(bits));
      bits = (bits & ~payloadMask) | payload;
      memcpy(&d, &bits, sizeof(bits));
    }
    advance();
    return d;
  }
  if (auto result = takeInteger()) {
    advance();
    if (result->sign == Neg) {
      if (result->n == 0) {
        return -0.0;
      }
      return -static_cast<double>(result->n);
    }
    return static_cast<double>(result->n);
  }
  return std::nullopt;
}

inline std::optional<std::string> Lexer::takeString() {
  if (auto str = takeStr()) {
    advance();
    if (auto* s = std::get_if<std::string>(&*str)) {
      return std::move(*s);
    }
    auto view = std::get<std::string_view>(*str);
    return std::string(view);
  }
  return std::nullopt;
}

inline std::optional<Name> Lexer::takeName() {
  auto str = takeString();
  if (!str || !String::isUTF8(*str)) {
    return std::nullopt;
  }
  return Name(*str);
}

inline bool Lexer::takeSExprStart(std::string_view expected) {
  auto original = *this;
  if (takeLParen() && takeKeyword(expected)) {
    return true;
  }
  *this = original;
  return false;
}

inline bool Lexer::peekSExprStart(std::string_view expected) {
  auto original = *this;
  if (!takeLParen()) {
    return false;
  }
  bool ret = takeKeyword(expected);
  *this = original;
  return ret;
}

inline TextPos Lexer::position(const char* c) const {
  assert(size_t(c - buffer.data()) <= buffer.size());
  TextPos pos{1, 0};
  for (const char* p = buffer.data(); p != c; ++p) {
    if (*p == '\n') {
      pos.line++;
      pos.col = 0;
    } else {
      pos.col++;
    }
  }
  return pos;
}

inline Err Lexer::err(size_t pos, std::string reason) {
  std::stringstream msg;
  if (file) {
    msg << *file << ":";
  }
  msg << position(pos) << ": error: " << reason;
  return Err{msg.str()};
}

inline std::optional<int> Lexer::getDigit(char c) {
  if ('0' <= c && c <= '9') {
    return c - '0';
  }
  return std::nullopt;
}

inline std::optional<int> Lexer::getHexDigit(char c) {
  if (auto d = getDigit(c)) {
    return d;
  }
  if ('A' <= c && c <= 'F') {
    return 10 + c - 'A';
  }
  if ('a' <= c && c <= 'f') {
    return 10 + c - 'a';
  }
  return std::nullopt;
}

inline bool Lexer::takePrefix(std::string_view sv) {
  if (startsWith(sv)) {
    take(sv.size());
    return true;
  }
  return false;
}

inline std::optional<int> Lexer::takeDigit() {
  if (auto c = peek()) {
    if (auto d = getDigit(*c)) {
      take(1);
      return d;
    }
  }
  return std::nullopt;
}

inline std::optional<int> Lexer::takeHexdigit() {
  if (auto c = peek()) {
    if (auto h = getHexDigit(*c)) {
      take(1);
      return h;
    }
  }
  return std::nullopt;
}

inline std::optional<uint64_t> Lexer::takeNum(OverflowBehavior behavior) {
  using namespace std::string_view_literals;
  auto startPos = pos;
  bool overflow = false;
  uint64_t n = 0;
  if (auto d = takeDigit()) {
    n = *d;
  } else {
    return std::nullopt;
  }
  while (true) {
    bool under = takePrefix("_"sv);
    if (auto d = takeDigit()) {
      uint64_t newN = n * 10 + *d;
      if (newN < n) {
        overflow = true;
      }
      n = newN;
      continue;
    }
    if (!under && (!overflow || behavior == IgnoreOverflow)) {
      return n;
    }
    // TODO: Add error productions for trailing underscore and overflow.
    pos = startPos;
    return std::nullopt;
  }
}

inline std::optional<uint64_t> Lexer::takeHexnum(OverflowBehavior behavior) {
  using namespace std::string_view_literals;
  auto startPos = pos;
  bool overflow = false;
  uint64_t n = 0;
  if (auto d = takeHexdigit()) {
    n = *d;
  } else {
    return std::nullopt;
  }
  while (true) {
    bool under = takePrefix("_"sv);
    if (auto d = takeHexdigit()) {
      uint64_t newN = n * 16 + *d;
      if (newN < n) {
        overflow = true;
      }
      n = newN;
      continue;
    }
    if (!under && (!overflow || behavior == IgnoreOverflow)) {
      return n;
    }
    // TODO: Add error productions for trailing underscore and overflow.
    pos = startPos;
    return std::nullopt;
  }
}

inline Lexer::Sign Lexer::takeSign() {
  if (auto c = peek()) {
    if (*c == '+') {
      take(1);
      return Pos;
    }
    if (*c == '-') {
      take(1);
      return Neg;
    }
  }
  return NoSign;
}

template<typename T> bool Lexer::LexedInteger::isUnsigned() {
  static_assert(std::is_integral_v<T> && std::is_unsigned_v<T>);
  return sign == NoSign && n <= std::numeric_limits<T>::max();
}

template<typename T> bool Lexer::LexedInteger::isSigned() {
  static_assert(std::is_integral_v<T> && std::is_signed_v<T>);
  if (sign == Neg) {
    // Absolute value of min() for two's complement integers is max() + 1.
    uint64_t absMin = uint64_t(std::numeric_limits<T>::max()) + 1;
    return n <= absMin;
  }
  return n <= uint64_t(std::numeric_limits<T>::max());
}

inline std::optional<Lexer::LexedInteger> Lexer::takeInteger() {
  using namespace std::string_view_literals;
  auto startPos = pos;
  auto sign = takeSign();
  if (takePrefix("0x"sv)) {
    if (auto n = takeHexnum()) {
      if (canFinish()) {
        return LexedInteger{*n, sign};
      }
    }
    // TODO: Add error production for unrecognized hexnum.
    pos = startPos;
    return std::nullopt;
  }
  if (auto n = takeNum()) {
    if (canFinish()) {
      return LexedInteger{*n, sign};
    }
  }
  pos = startPos;
  return std::nullopt;
}

template<typename T> std::optional<T> Lexer::takeU() {
  static_assert(std::is_integral_v<T> && std::is_unsigned_v<T>);
  auto startPos = pos;
  if (auto result = takeInteger(); result && result->isUnsigned<T>()) {
    advance();
    return static_cast<T>(result->n);
  }
  // TODO: Add error production for unsigned overflow.
  pos = startPos;
  return std::nullopt;
}

template<typename T> std::optional<T> Lexer::takeS() {
  static_assert(std::is_integral_v<T> && std::is_signed_v<T>);
  auto startPos = pos;
  if (auto result = takeInteger(); result && result->isSigned<T>()) {
    advance();
    if (result->sign == Neg) {
      return static_cast<T>(-result->n);
    }
    return static_cast<T>(result->n);
  }
  pos = startPos;
  return std::nullopt;
}

template<typename T> std::optional<T> Lexer::takeI() {
  static_assert(std::is_integral_v<T> && std::is_unsigned_v<T>);
  auto startPos = pos;
  if (auto result = takeInteger()) {
    if (result->isUnsigned<T>() || result->isSigned<std::make_signed_t<T>>()) {
      advance();
      if (result->sign == Neg) {
        return static_cast<T>(-result->n);
      }
      return static_cast<T>(result->n);
    }
  }
  pos = startPos;
  return std::nullopt;
}

inline std::optional<std::string_view> Lexer::takeDecfloat() {
  using namespace std::string_view_literals;
  auto startPos = pos;
  if (!takeNum(IgnoreOverflow)) {
    return std::nullopt;
  }
  // Optional '.' followed by optional frac
  if (takePrefix("."sv)) {
    takeNum(IgnoreOverflow);
  }
  if (takePrefix("E"sv) || takePrefix("e"sv)) {
    // Optional sign
    takeSign();
    if (!takeNum(IgnoreOverflow)) {
      // TODO: Add error production for missing exponent.
      pos = startPos;
      return std::nullopt;
    }
  }
  return buffer.substr(startPos, pos - startPos);
}

inline std::optional<std::string_view> Lexer::takeHexfloat() {
  using namespace std::string_view_literals;
  auto startPos = pos;
  if (!takePrefix("0x"sv)) {
    return std::nullopt;
  }
  if (!takeHexnum(IgnoreOverflow)) {
    pos = startPos;
    return std::nullopt;
  }
  // Optional '.' followed by optional hexfrac
  if (takePrefix("."sv)) {
    takeHexnum(IgnoreOverflow);
  }
  if (takePrefix("P"sv) || takePrefix("p"sv)) {
    // Optional sign
    takeSign();
    if (!takeNum(IgnoreOverflow)) {
      // TODO: Add error production for missing exponent.
      pos = startPos;
      return std::nullopt;
    }
  }
  return buffer.substr(startPos, pos - startPos);
}

inline std::optional<Lexer::LexedFloat> Lexer::takeFloat() {
  using namespace std::string_view_literals;
  auto startPos = pos;
  std::optional<uint64_t> nanPayload;
  bool isNan = false;
  // Optional sign
  auto sign = takeSign();
  if (takeHexfloat() || takeDecfloat() || takePrefix("inf"sv)) {
    // nop.
  } else if (takePrefix("nan"sv)) {
    isNan = true;
    if (takePrefix(":0x"sv)) {
      if (auto n = takeHexnum()) {
        nanPayload = n;
      } else {
        // TODO: Add error production for malformed NaN payload.
        pos = startPos;
        return std::nullopt;
      }
    } else {
      // No explicit payload necessary; we will inject the default payload
      // later.
    }
  } else {
    pos = startPos;
    return std::nullopt;
  }
  if (!canFinish()) {
    pos = startPos;
    return std::nullopt;
  }
  // strtod does not return NaNs with the expected signs on all platforms.
  if (isNan) {
    if (sign == Neg) {
      const double negNan = std::copysign(NAN, -1.0);
      assert(std::signbit(negNan) && "expected negative NaN to be negative");
      return LexedFloat{nanPayload, negNan};
    } else {
      const double posNan = std::copysign(NAN, 1.0);
      assert(!std::signbit(posNan) && "expected positive NaN to be positive");
      return LexedFloat{nanPayload, posNan};
    }
  }
  // Do not try to implement fully general and precise float parsing
  // ourselves. Instead, call out to std::strtod to do our parsing. This means
  // we need to strip any underscores since `std::strtod` does not understand
  // them.
  std::stringstream ss;
  for (const char *curr = buffer.data() + startPos, *end = buffer.data() + pos;
       curr != end;
       ++curr) {
    if (*curr != '_') {
      ss << *curr;
    }
  }
  std::string str = ss.str();
  char* last;
  double d = std::strtod(str.data(), &last);
  assert(last == str.data() + str.size() && "could not parse float");
  return LexedFloat{std::nullopt, d};
}

inline std::optional<Lexer::StringOrView> Lexer::takeStr() {
  using namespace std::string_view_literals;
  auto startPos = pos;
  if (!takePrefix("\""sv)) {
    return std::nullopt;
  }
  // Used to build a string with resolved escape sequences. Only used when the
  // parsed string contains escape sequences, otherwise we can just use the
  // parsed string directly.
  std::optional<std::stringstream> escapeBuilder;
  auto ensureBuildingEscaped = [&]() {
    if (escapeBuilder) {
      return;
    }
    // Drop the opening '"'.
    escapeBuilder = std::stringstream{};
    *escapeBuilder << buffer.substr(startPos + 1, pos - startPos - 1);
  };
  while (!takePrefix("\""sv)) {
    if (empty()) {
      // TODO: Add error production for unterminated string.
      pos = startPos;
      return std::nullopt;
    }
    if (startsWith("\\"sv)) {
      // Escape sequences
      ensureBuildingEscaped();
      take(1);
      if (empty()) {
        pos = startPos;
        return std::nullopt;
      }
      auto c = peek();
      if (!c) {
        pos = startPos;
        return std::nullopt;
      }
      take(1);
      switch (*c) {
        case 't':
          *escapeBuilder << '\t';
          break;
        case 'n':
          *escapeBuilder << '\n';
          break;
        case 'r':
          *escapeBuilder << '\r';
          break;
        case '\\':
          *escapeBuilder << '\\';
          break;
        case '"':
          *escapeBuilder << '"';
          break;
        case '\'':
          *escapeBuilder << '\'';
          break;
        case 'u': {
          if (!takePrefix("{"sv)) {
            pos = startPos;
            return std::nullopt;
          }
          auto code = takeHexnum();
          if (!code) {
            // TODO: Add error production for malformed unicode escapes.
            pos = startPos;
            return std::nullopt;
          }
          if (!takePrefix("}"sv)) {
            // TODO: Add error production for malformed unicode escapes.
            pos = startPos;
            return std::nullopt;
          }
          if ((0xd800 <= *code && *code < 0xe000) || 0x110000 <= *code) {
            // TODO: Add error production for invalid unicode values.
            pos = startPos;
            return std::nullopt;
          }
          String::writeWTF8CodePoint(*escapeBuilder, *code);
          break;
        }
        default: {
          // Byte escape: \hh
          // We already took the first h as c.
          auto first = getHexDigit(*c);
          auto second = takeHexdigit();
          if (!first || !second) {
            // TODO: Add error production for unrecognized escape sequence.
            pos = startPos;
            return std::nullopt;
          }
          *escapeBuilder << char(*first * 16 + *second);
        }
      }
    } else {
      // Normal characters
      uint8_t c = *peek();
      if (c >= 0x20 && c != 0x7F) {
        if (escapeBuilder) {
          *escapeBuilder << c;
        }
        take(1);
      } else {
        // TODO: Add error production for unescaped control characters.
        pos = startPos;
        return std::nullopt;
      }
    }
  }
  if (escapeBuilder) {
    return escapeBuilder->str();
  }
  // Drop the quotes.
  return buffer.substr(startPos + 1, pos - startPos - 2);
}

inline bool Lexer::idchar() {
  auto c = peek();
  if (!c) {
    return false;
  }
  // All the allowed characters lie in the range '!' to '~', and within that
  // range the vast majority of characters are allowed, so it is significantly
  // faster to check for the disallowed characters instead.
  if (*c < '!' || *c > '~') {
    return false;
  }
  switch (*c) {
    case '"':
    case '(':
    case ')':
    case ',':
    case ';':
    case '[':
    case ']':
    case '{':
    case '}':
      return false;
  }
  return true;
}

inline std::optional<Lexer::StringOrView> Lexer::takeIdent() {
  using namespace std::string_view_literals;
  auto startPos = pos;
  if (!takePrefix("$"sv)) {
    return {};
  }
  // Quoted identifier e.g. $"foo"
  std::optional<StringOrView> str;
  if ((str = takeStr())) {
    if (str->str().empty() || !String::isUTF8(str->str())) {
      pos = startPos;
      return std::nullopt;
    }
  } else if (idchar()) {
    take(1);
    while (idchar()) {
      take(1);
    }
  } else {
    pos = startPos;
    return std::nullopt;
  }
  if (canFinish()) {
    if (str) {
      return str;
    }
    // Drop the "$".
    return buffer.substr(startPos + 1, pos - startPos - 1);
  }
  pos = startPos;
  return std::nullopt;
}

inline bool Lexer::isSpacechar(uint8_t c) {
  switch (c) {
    case ' ':
    case '\n':
    case '\r':
    case '\t':
      return true;
    default:
      return false;
  }
}

inline bool Lexer::spacechar() {
  if (auto c = peek()) {
    return isSpacechar(*c);
  }
  return false;
}

inline bool Lexer::takeSpacechar() {
  if (spacechar()) {
    take(1);
    return true;
  }
  return false;
}

inline bool Lexer::takeComment() {
  using namespace std::string_view_literals;

  if (remaining() < 2) {
    return false;
  }

  // Line comment
  if (!startsWith(";;@"sv) && takePrefix(";;"sv)) {
    if (auto size = next().find('\n'); size != ""sv.npos) {
      take(size);
    } else {
      takeAll();
    }
    return true;
  }

  // Block comment (possibly nested!)
  auto startPos = pos;
  if (takePrefix("(;"sv)) {
    size_t depth = 1;
    while (depth > 0 && remaining() >= 2) {
      if (takePrefix("(;"sv)) {
        ++depth;
      } else if (takePrefix(";)"sv)) {
        --depth;
      } else {
        take(1);
      }
    }
    if (depth > 0) {
      // TODO: Add error production for non-terminated block comment.
      pos = startPos;
      return false;
    }
    return true;
  }

  return false;
}

inline bool Lexer::takeSpace() {
  bool taken = false;
  while (remaining() && (takeSpacechar() || takeComment())) {
    taken = true;
    continue;
  }
  return taken;
}

inline std::optional<Annotation> Lexer::takeAnnotation() {
  using namespace std::string_view_literals;
  auto startPos = pos;
  std::string_view kind;
  std::string_view contents;
  if (takePrefix(";;@"sv)) {
    kind = "src"sv;
    auto contentPos = pos;
    if (auto size = next().find('\n'); size != ""sv.npos) {
      take(size);
    } else {
      takeAll();
    }
    contents = buffer.substr(contentPos, pos - contentPos);
  } else if (takePrefix("(@"sv)) {
    auto kindPos = pos;
    bool hasIdchar = false;
    while (idchar()) {
      take(1);
      hasIdchar = true;
    }
    if (!hasIdchar) {
      pos = startPos;
      return std::nullopt;
    }
    kind = buffer.substr(kindPos, pos - kindPos);
    auto contentPos = pos;
    size_t depth = 1;
    while (true) {
      if (empty()) {
        pos = startPos;
        return std::nullopt;
      }
      // Use peekKeyword + take rather than takeKeyword to avoid calling
      // advance(), which would clear previously collected annotations.
      if (auto keyword = peekKeyword()) {
        take(keyword->size());
        continue;
      }
      if (takeSpace() || takeInteger() || takeFloat() || takeStr() ||
          takeIdent()) {
        continue;
      }
      if (takePrefix("(@"sv)) {
        bool hasIdchar = false;
        while (idchar()) {
          take(1);
          hasIdchar = true;
        }
        if (!hasIdchar) {
          pos = startPos;
          return std::nullopt;
        }
        ++depth;
        continue;
      }
      // Use takePrefix rather than takeLParen to avoid calling advance().
      if (takePrefix("("sv)) {
        ++depth;
        continue;
      }
      if (takePrefix(")"sv)) {
        --depth;
        if (depth == 0) {
          break;
        }
        continue;
      }
      // Unrecognized token.
      pos = startPos;
      return std::nullopt;
    }
    contents = buffer.substr(contentPos, pos - contentPos - 1);
  } else {
    return std::nullopt;
  }
  return Annotation{Name(kind), contents};
}

inline void Lexer::skipSpace() {
  while (true) {
    if (auto annotation = takeAnnotation()) {
      annotations.emplace_back(*std::move(annotation));
      continue;
    }
    if (takeSpace()) {
      continue;
    }
    break;
  }
}

inline bool Lexer::canFinish() {
  // Logically we want to check for eof, parens, and space. But we don't
  // actually want to parse more than a couple characters of space, so check
  // for individual space chars or comment starts instead.
  using namespace std::string_view_literals;
  auto c = peek();
  if (!c) {
    return true;
  }
  return isSpacechar(*c) || *c == '(' || *c == ')' || startsWith(";;"sv);
}

} // namespace wasm::WATParser

#endif // parser_lexer_h