// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/torque/ls/json-parser.h"

#include <cctype>
#include "src/torque/earley-parser.h"

namespace v8 {
namespace internal {
namespace torque {

template <>
V8_EXPORT_PRIVATE const ParseResultTypeId ParseResultHolder<ls::JsonValue>::id =
    ParseResultTypeId::kJsonValue;

template <>
V8_EXPORT_PRIVATE const ParseResultTypeId
    ParseResultHolder<std::pair<std::string, ls::JsonValue>>::id =
        ParseResultTypeId::kJsonMember;

template <>
V8_EXPORT_PRIVATE const ParseResultTypeId
    ParseResultHolder<std::vector<ls::JsonValue>>::id =
        ParseResultTypeId::kStdVectorOfJsonValue;

template <>
V8_EXPORT_PRIVATE const ParseResultTypeId
    ParseResultHolder<std::vector<std::pair<std::string, ls::JsonValue>>>::id =
        ParseResultTypeId::kStdVectorOfJsonMember;

namespace ls {

using JsonMember = std::pair<std::string, JsonValue>;

template <bool value>
base::Optional<ParseResult> MakeBoolLiteral(
    ParseResultIterator* child_results) {
  return ParseResult{JsonValue::From(value)};
}

base::Optional<ParseResult> MakeNullLiteral(
    ParseResultIterator* child_results) {
  JsonValue result;
  result.tag = JsonValue::IS_NULL;
  return ParseResult{std::move(result)};
}

base::Optional<ParseResult> MakeNumberLiteral(
    ParseResultIterator* child_results) {
  auto number = child_results->NextAs<std::string>();
  double d = std::stod(number.c_str());
  return ParseResult{JsonValue::From(d)};
}

base::Optional<ParseResult> MakeStringLiteral(
    ParseResultIterator* child_results) {
  std::string literal = child_results->NextAs<std::string>();
  return ParseResult{JsonValue::From(StringLiteralUnquote(literal))};
}

base::Optional<ParseResult> MakeArray(ParseResultIterator* child_results) {
  JsonArray array = child_results->NextAs<JsonArray>();
  return ParseResult{JsonValue::From(std::move(array))};
}

base::Optional<ParseResult> MakeMember(ParseResultIterator* child_results) {
  JsonMember result;
  std::string key = child_results->NextAs<std::string>();
  result.first = StringLiteralUnquote(key);
  result.second = child_results->NextAs<JsonValue>();
  return ParseResult{std::move(result)};
}

base::Optional<ParseResult> MakeObject(ParseResultIterator* child_results) {
  using MemberList = std::vector<JsonMember>;
  MemberList members = child_results->NextAs<MemberList>();

  JsonObject object;
  for (auto& member : members) object.insert(std::move(member));

  return ParseResult{JsonValue::From(std::move(object))};
}

class JsonGrammar : public Grammar {
  static bool MatchWhitespace(InputPosition* pos) {
    while (MatchChar(std::isspace, pos)) {
    }
    return true;
  }

  static bool MatchStringLiteral(InputPosition* pos) {
    InputPosition current = *pos;
    if (MatchString("\"", &current)) {
      while (
          (MatchString("\\", &current) && MatchAnyChar(&current)) ||
          MatchChar([](char c) { return c != '"' && c != '\n'; }, &current)) {
      }
      if (MatchString("\"", &current)) {
        *pos = current;
        return true;
      }
    }
    current = *pos;
    if (MatchString("'", &current)) {
      while (
          (MatchString("\\", &current) && MatchAnyChar(&current)) ||
          MatchChar([](char c) { return c != '\'' && c != '\n'; }, &current)) {
      }
      if (MatchString("'", &current)) {
        *pos = current;
        return true;
      }
    }
    return false;
  }

  static bool MatchHexLiteral(InputPosition* pos) {
    InputPosition current = *pos;
    MatchString("-", &current);
    if (MatchString("0x", &current) && MatchChar(std::isxdigit, &current)) {
      while (MatchChar(std::isxdigit, &current)) {
      }
      *pos = current;
      return true;
    }
    return false;
  }

  static bool MatchDecimalLiteral(InputPosition* pos) {
    InputPosition current = *pos;
    bool found_digit = false;
    MatchString("-", &current);
    while (MatchChar(std::isdigit, &current)) found_digit = true;
    MatchString(".", &current);
    while (MatchChar(std::isdigit, &current)) found_digit = true;
    if (!found_digit) return false;
    *pos = current;
    if ((MatchString("e", &current) || MatchString("E", &current)) &&
        (MatchString("+", &current) || MatchString("-", &current) || true) &&
        MatchChar(std::isdigit, &current)) {
      while (MatchChar(std::isdigit, &current)) {
      }
      *pos = current;
      return true;
    }
    return true;
  }

 public:
  JsonGrammar() : Grammar(&file) { SetWhitespace(MatchWhitespace); }

  Symbol trueLiteral = {Rule({Token("true")})};
  Symbol falseLiteral = {Rule({Token("false")})};
  Symbol nullLiteral = {Rule({Token("null")})};

  Symbol decimalLiteral = {
      Rule({Pattern(MatchDecimalLiteral)}, YieldMatchedInput),
      Rule({Pattern(MatchHexLiteral)}, YieldMatchedInput)};

  Symbol stringLiteral = {
      Rule({Pattern(MatchStringLiteral)}, YieldMatchedInput)};

  Symbol* elementList = List<JsonValue>(&value, Token(","));
  Symbol array = {Rule({Token("["), elementList, Token("]")})};

  Symbol member = {Rule({&stringLiteral, Token(":"), &value}, MakeMember)};
  Symbol* memberList = List<JsonMember>(&member, Token(","));
  Symbol object = {Rule({Token("{"), memberList, Token("}")})};

  Symbol value = {Rule({&trueLiteral}, MakeBoolLiteral<true>),
                  Rule({&falseLiteral}, MakeBoolLiteral<false>),
                  Rule({&nullLiteral}, MakeNullLiteral),
                  Rule({&decimalLiteral}, MakeNumberLiteral),
                  Rule({&stringLiteral}, MakeStringLiteral),
                  Rule({&object}, MakeObject),
                  Rule({&array}, MakeArray)};

  Symbol file = {Rule({&value})};
};

JsonParserResult ParseJson(const std::string& input) {
  // Torque needs a CurrentSourceFile scope during parsing.
  // As JSON lives in memory only, a unknown file scope is created.
  SourceFileMap::Scope source_map_scope;
  CurrentSourceFile::Scope unkown_file(SourceFileMap::AddSource("<json>"));

  JsonParserResult result;
  try {
    result.value = (*JsonGrammar().Parse(input)).Cast<JsonValue>();
  } catch (TorqueError& error) {
    result.error = error;
  }
  return result;
}

}  // namespace ls
}  // namespace torque
}  // namespace internal
}  // namespace v8