// Copyright 2014 PDFium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com

#include "xfa/fxfa/fm2js/cxfa_fmsimpleexpression.h"

#include <algorithm>
#include <utility>

#include "core/fxcrt/autorestorer.h"
#include "core/fxcrt/cfx_widetextbuf.h"
#include "core/fxcrt/fx_extension.h"
#include "third_party/base/logging.h"
#include "xfa/fxfa/fm2js/cxfa_fmtojavascriptdepth.h"

namespace {

const wchar_t* const g_BuiltInFuncs[] = {
    L"Abs",          L"Apr",       L"At",       L"Avg",
    L"Ceil",         L"Choose",    L"Concat",   L"Count",
    L"Cterm",        L"Date",      L"Date2Num", L"DateFmt",
    L"Decode",       L"Encode",    L"Eval",     L"Exists",
    L"Floor",        L"Format",    L"FV",       L"Get",
    L"HasValue",     L"If",        L"Ipmt",     L"IsoDate2Num",
    L"IsoTime2Num",  L"Left",      L"Len",      L"LocalDateFmt",
    L"LocalTimeFmt", L"Lower",     L"Ltrim",    L"Max",
    L"Min",          L"Mod",       L"NPV",      L"Num2Date",
    L"Num2GMTime",   L"Num2Time",  L"Oneof",    L"Parse",
    L"Pmt",          L"Post",      L"PPmt",     L"Put",
    L"PV",           L"Rate",      L"Ref",      L"Replace",
    L"Right",        L"Round",     L"Rtrim",    L"Space",
    L"Str",          L"Stuff",     L"Substr",   L"Sum",
    L"Term",         L"Time",      L"Time2Num", L"TimeFmt",
    L"UnitType",     L"UnitValue", L"Upper",    L"Uuid",
    L"Within",       L"WordNum",
};

const size_t g_BuiltInFuncsMaxLen = 12;

struct XFA_FMSOMMethod {
  const wchar_t* m_wsSomMethodName;
  uint32_t m_dParameters;
};

const XFA_FMSOMMethod gs_FMSomMethods[] = {
    {L"absPage", 0x01},
    {L"absPageInBatch", 0x01},
    {L"absPageSpan", 0x01},
    {L"append", 0x01},
    {L"clear", 0x01},
    {L"formNodes", 0x01},
    {L"h", 0x01},
    {L"insert", 0x03},
    {L"isRecordGroup", 0x01},
    {L"page", 0x01},
    {L"pageSpan", 0x01},
    {L"remove", 0x01},
    {L"saveFilteredXML", 0x01},
    {L"setElement", 0x01},
    {L"sheet", 0x01},
    {L"sheetInBatch", 0x01},
    {L"sign", 0x61},
    {L"verify", 0x0d},
    {L"w", 0x01},
    {L"x", 0x01},
    {L"y", 0x01},
};

}  // namespace

CXFA_FMSimpleExpression::CXFA_FMSimpleExpression(XFA_FM_TOKEN op)
    : m_op(op), m_bChainable(false) {}

CXFA_FMSimpleExpression::CXFA_FMSimpleExpression(XFA_FM_TOKEN op,
                                                 bool chainable)
    : m_op(op), m_bChainable(chainable) {}

XFA_FM_TOKEN CXFA_FMSimpleExpression::GetOperatorToken() const {
  return m_op;
}

CXFA_FMChainableExpression::CXFA_FMChainableExpression(
    XFA_FM_TOKEN op,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp1,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp2)
    : CXFA_FMSimpleExpression(op, /*chainable=*/true),
      m_pExp1(std::move(pExp1)),
      m_pExp2(std::move(pExp2)) {}

CXFA_FMChainableExpression::~CXFA_FMChainableExpression() {
  DeleteChain(std::move(m_pExp1));
  DeleteChain(std::move(m_pExp2));
}

CXFA_FMSimpleExpression* CXFA_FMChainableExpression::GetFirstExpression() {
  return m_pExp1.get();
}

CXFA_FMSimpleExpression* CXFA_FMChainableExpression::GetSecondExpression() {
  return m_pExp2.get();
}

// static
void CXFA_FMChainableExpression::DeleteChain(
    std::unique_ptr<CXFA_FMSimpleExpression> pRoot) {
  while (pRoot && pRoot->chainable()) {
    auto* pRootChain = static_cast<CXFA_FMChainableExpression*>(pRoot.get());

    // If either child is not a chainable expression (i.e. a leaf node), simply
    // delete it.
    if (pRootChain->m_pExp1 && !pRootChain->m_pExp1->chainable())
      pRootChain->m_pExp1.reset();
    if (pRootChain->m_pExp2 && !pRootChain->m_pExp2->chainable())
      pRootChain->m_pExp2.reset();

    // If the root is missing either child, delete the root and promote the only
    // child to the root.
    if (!pRootChain->m_pExp1) {
      pRoot = std::move(pRootChain->m_pExp2);
      continue;
    }
    if (!pRootChain->m_pExp2) {
      pRoot = std::move(pRootChain->m_pExp1);
      continue;
    }

    // Otherwise, perform a right tree rotation.
    std::unique_ptr<CXFA_FMSimpleExpression> pPivot(
        std::move(pRootChain->m_pExp1));
    auto* pPivotChain = static_cast<CXFA_FMChainableExpression*>(pPivot.get());
    pRootChain->m_pExp1 = std::move(pPivotChain->m_pExp2);
    pPivotChain->m_pExp2 = std::move(pRoot);
    pRoot = std::move(pPivot);
  }
}

CXFA_FMNullExpression::CXFA_FMNullExpression()
    : CXFA_FMSimpleExpression(TOKnull) {}

bool CXFA_FMNullExpression::ToJavaScript(CFX_WideTextBuf* js, ReturnType type) {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(js) || !depthManager.IsWithinMaxDepth())
    return false;

  *js << "null";
  return !CXFA_IsTooBig(js);
}

CXFA_FMNumberExpression::CXFA_FMNumberExpression(WideStringView wsNumber)
    : CXFA_FMSimpleExpression(TOKnumber), m_wsNumber(wsNumber) {}

CXFA_FMNumberExpression::~CXFA_FMNumberExpression() = default;

bool CXFA_FMNumberExpression::ToJavaScript(CFX_WideTextBuf* js,
                                           ReturnType type) {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(js) || !depthManager.IsWithinMaxDepth())
    return false;

  *js << m_wsNumber;
  return !CXFA_IsTooBig(js);
}

CXFA_FMStringExpression::CXFA_FMStringExpression(WideStringView wsString)
    : CXFA_FMSimpleExpression(TOKstring), m_wsString(wsString) {}

CXFA_FMStringExpression::~CXFA_FMStringExpression() = default;

bool CXFA_FMStringExpression::ToJavaScript(CFX_WideTextBuf* js,
                                           ReturnType type) {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(js) || !depthManager.IsWithinMaxDepth())
    return false;

  WideString tempStr(m_wsString);
  if (tempStr.GetLength() <= 2) {
    *js << tempStr;
    return !CXFA_IsTooBig(js);
  }

  *js << "\"";
  for (size_t i = 1; i < tempStr.GetLength() - 1; i++) {
    wchar_t oneChar = tempStr[i];
    switch (oneChar) {
      case L'\"':
        ++i;
        *js << "\\\"";
        break;
      case 0x0d:
        break;
      case 0x0a:
        *js << "\\n";
        break;
      default:
        js->AppendChar(oneChar);
        break;
    }
  }
  *js << "\"";
  return !CXFA_IsTooBig(js);
}

CXFA_FMIdentifierExpression::CXFA_FMIdentifierExpression(
    WideStringView wsIdentifier)
    : CXFA_FMSimpleExpression(TOKidentifier), m_wsIdentifier(wsIdentifier) {}

CXFA_FMIdentifierExpression::~CXFA_FMIdentifierExpression() = default;

bool CXFA_FMIdentifierExpression::ToJavaScript(CFX_WideTextBuf* js,
                                               ReturnType type) {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(js) || !depthManager.IsWithinMaxDepth())
    return false;

  if (m_wsIdentifier.EqualsASCII("$"))
    *js << "this";
  else if (m_wsIdentifier.EqualsASCII("!"))
    *js << "xfa.datasets";
  else if (m_wsIdentifier.EqualsASCII("$data"))
    *js << "xfa.datasets.data";
  else if (m_wsIdentifier.EqualsASCII("$event"))
    *js << "xfa.event";
  else if (m_wsIdentifier.EqualsASCII("$form"))
    *js << "xfa.form";
  else if (m_wsIdentifier.EqualsASCII("$host"))
    *js << "xfa.host";
  else if (m_wsIdentifier.EqualsASCII("$layout"))
    *js << "xfa.layout";
  else if (m_wsIdentifier.EqualsASCII("$template"))
    *js << "xfa.template";
  else if (m_wsIdentifier[0] == L'!')
    *js << "pfm__excl__" << m_wsIdentifier.Last(m_wsIdentifier.GetLength() - 1);
  else
    *js << m_wsIdentifier;

  return !CXFA_IsTooBig(js);
}

CXFA_FMAssignExpression::CXFA_FMAssignExpression(
    XFA_FM_TOKEN op,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp1,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp2)
    : CXFA_FMChainableExpression(op, std::move(pExp1), std::move(pExp2)) {}

CXFA_FMAssignExpression::~CXFA_FMAssignExpression() = default;

bool CXFA_FMAssignExpression::ToJavaScript(CFX_WideTextBuf* js,
                                           ReturnType type) {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(js) || !depthManager.IsWithinMaxDepth())
    return false;

  CFX_WideTextBuf tempExp1;
  CXFA_FMSimpleExpression* exp1 = GetFirstExpression();
  if (!exp1->ToJavaScript(&tempExp1, ReturnType::kInfered))
    return false;

  *js << "if (pfm_rt.is_obj(" << tempExp1 << "))\n{\n";
  if (type == ReturnType::kImplied)
    *js << "pfm_ret = ";

  CFX_WideTextBuf tempExp2;
  CXFA_FMSimpleExpression* exp2 = GetSecondExpression();
  if (!exp2->ToJavaScript(&tempExp2, ReturnType::kInfered))
    return false;

  *js << "pfm_rt.asgn_val_op(" << tempExp1 << ", " << tempExp2 << ");\n}\n";

  if (exp1->GetOperatorToken() == TOKidentifier &&
      !tempExp1.AsStringView().EqualsASCII("this")) {
    *js << "else\n{\n";
    if (type == ReturnType::kImplied)
      *js << "pfm_ret = ";

    *js << tempExp1 << " = pfm_rt.asgn_val_op";
    *js << "(" << tempExp1 << ", " << tempExp2 << ");\n";
    *js << "}\n";
  }
  return !CXFA_IsTooBig(js);
}

CXFA_FMBinExpression::CXFA_FMBinExpression(
    const WideString& opName,
    XFA_FM_TOKEN op,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp1,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp2)
    : CXFA_FMChainableExpression(op, std::move(pExp1), std::move(pExp2)),
      m_OpName(opName) {}

CXFA_FMBinExpression::~CXFA_FMBinExpression() = default;

bool CXFA_FMBinExpression::ToJavaScript(CFX_WideTextBuf* js, ReturnType type) {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(js) || !depthManager.IsWithinMaxDepth())
    return false;

  *js << "pfm_rt." << m_OpName << "(";
  if (!GetFirstExpression()->ToJavaScript(js, ReturnType::kInfered))
    return false;
  *js << ", ";
  if (!GetSecondExpression()->ToJavaScript(js, ReturnType::kInfered))
    return false;
  *js << ")";
  return !CXFA_IsTooBig(js);
}

CXFA_FMLogicalOrExpression::CXFA_FMLogicalOrExpression(
    XFA_FM_TOKEN op,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp1,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp2)
    : CXFA_FMBinExpression(L"log_or_op",
                           op,
                           std::move(pExp1),
                           std::move(pExp2)) {}

CXFA_FMLogicalAndExpression::CXFA_FMLogicalAndExpression(
    XFA_FM_TOKEN op,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp1,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp2)
    : CXFA_FMBinExpression(L"log_and_op",
                           op,
                           std::move(pExp1),
                           std::move(pExp2)) {}

CXFA_FMEqualExpression::CXFA_FMEqualExpression(
    XFA_FM_TOKEN op,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp1,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp2)
    : CXFA_FMBinExpression(L"eq_op", op, std::move(pExp1), std::move(pExp2)) {}

CXFA_FMNotEqualExpression::CXFA_FMNotEqualExpression(
    XFA_FM_TOKEN op,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp1,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp2)
    : CXFA_FMBinExpression(L"neq_op", op, std::move(pExp1), std::move(pExp2)) {}

CXFA_FMGtExpression::CXFA_FMGtExpression(
    XFA_FM_TOKEN op,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp1,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp2)
    : CXFA_FMBinExpression(L"gt_op", op, std::move(pExp1), std::move(pExp2)) {}

CXFA_FMGeExpression::CXFA_FMGeExpression(
    XFA_FM_TOKEN op,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp1,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp2)
    : CXFA_FMBinExpression(L"ge_op", op, std::move(pExp1), std::move(pExp2)) {}

CXFA_FMLtExpression::CXFA_FMLtExpression(
    XFA_FM_TOKEN op,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp1,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp2)
    : CXFA_FMBinExpression(L"lt_op", op, std::move(pExp1), std::move(pExp2)) {}

CXFA_FMLeExpression::CXFA_FMLeExpression(
    XFA_FM_TOKEN op,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp1,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp2)
    : CXFA_FMBinExpression(L"le_op", op, std::move(pExp1), std::move(pExp2)) {}

CXFA_FMPlusExpression::CXFA_FMPlusExpression(
    XFA_FM_TOKEN op,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp1,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp2)
    : CXFA_FMBinExpression(L"plus_op", op, std::move(pExp1), std::move(pExp2)) {
}

CXFA_FMMinusExpression::CXFA_FMMinusExpression(
    XFA_FM_TOKEN op,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp1,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp2)
    : CXFA_FMBinExpression(L"minus_op",
                           op,
                           std::move(pExp1),
                           std::move(pExp2)) {}

CXFA_FMMulExpression::CXFA_FMMulExpression(
    XFA_FM_TOKEN op,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp1,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp2)
    : CXFA_FMBinExpression(L"mul_op", op, std::move(pExp1), std::move(pExp2)) {}

CXFA_FMDivExpression::CXFA_FMDivExpression(
    XFA_FM_TOKEN op,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp1,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp2)
    : CXFA_FMBinExpression(L"div_op", op, std::move(pExp1), std::move(pExp2)) {}

CXFA_FMUnaryExpression::CXFA_FMUnaryExpression(
    const WideString& opName,
    XFA_FM_TOKEN op,
    std::unique_ptr<CXFA_FMSimpleExpression> pExp)
    : CXFA_FMSimpleExpression(op), m_OpName(opName), m_pExp(std::move(pExp)) {}

CXFA_FMUnaryExpression::~CXFA_FMUnaryExpression() = default;

bool CXFA_FMUnaryExpression::ToJavaScript(CFX_WideTextBuf* js,
                                          ReturnType type) {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(js) || !depthManager.IsWithinMaxDepth())
    return false;

  *js << "pfm_rt." << m_OpName.c_str() << "(";
  if (!m_pExp->ToJavaScript(js, ReturnType::kInfered))
    return false;
  *js << ")";
  return !CXFA_IsTooBig(js);
}

CXFA_FMPosExpression::CXFA_FMPosExpression(
    std::unique_ptr<CXFA_FMSimpleExpression> pExp)
    : CXFA_FMUnaryExpression(L"pos_op", TOKplus, std::move(pExp)) {}

CXFA_FMNegExpression::CXFA_FMNegExpression(
    std::unique_ptr<CXFA_FMSimpleExpression> pExp)
    : CXFA_FMUnaryExpression(L"neg_op", TOKminus, std::move(pExp)) {}

CXFA_FMNotExpression::CXFA_FMNotExpression(
    std::unique_ptr<CXFA_FMSimpleExpression> pExp)
    : CXFA_FMUnaryExpression(L"log_not_op", TOKksnot, std::move(pExp)) {}

CXFA_FMCallExpression::CXFA_FMCallExpression(
    std::unique_ptr<CXFA_FMSimpleExpression> pExp,
    std::vector<std::unique_ptr<CXFA_FMSimpleExpression>>&& pArguments,
    bool bIsSomMethod)
    : CXFA_FMSimpleExpression(TOKcall),
      m_pExp(std::move(pExp)),
      m_bIsSomMethod(bIsSomMethod),
      m_Arguments(std::move(pArguments)) {}

CXFA_FMCallExpression::~CXFA_FMCallExpression() = default;

bool CXFA_FMCallExpression::IsBuiltInFunc(CFX_WideTextBuf* funcName) {
  if (funcName->GetLength() > g_BuiltInFuncsMaxLen)
    return false;

  WideString str = funcName->MakeString();
  const wchar_t* const* pMatchResult = std::lower_bound(
      std::begin(g_BuiltInFuncs), std::end(g_BuiltInFuncs), str,
      [](const wchar_t* iter, const WideString& val) -> bool {
        return val.CompareNoCase(iter) > 0;
      });
  if (pMatchResult != std::end(g_BuiltInFuncs) &&
      !str.CompareNoCase(*pMatchResult)) {
    funcName->Clear();
    *funcName << *pMatchResult;
    return true;
  }
  return false;
}

uint32_t CXFA_FMCallExpression::IsMethodWithObjParam(
    const WideString& methodName) {
  const XFA_FMSOMMethod* result = std::lower_bound(
      std::begin(gs_FMSomMethods), std::end(gs_FMSomMethods), methodName,
      [](const XFA_FMSOMMethod iter, const WideString& val) {
        return val.Compare(iter.m_wsSomMethodName) > 0;
      });
  if (result != std::end(gs_FMSomMethods) &&
      !methodName.Compare(result->m_wsSomMethodName)) {
    return result->m_dParameters;
  }
  return 0;
}

bool CXFA_FMCallExpression::ToJavaScript(CFX_WideTextBuf* js, ReturnType type) {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(js) || !depthManager.IsWithinMaxDepth())
    return false;

  CFX_WideTextBuf funcName;
  if (!m_pExp->ToJavaScript(&funcName, ReturnType::kInfered))
    return false;

  if (m_bIsSomMethod) {
    *js << funcName << "(";
    uint32_t methodPara = IsMethodWithObjParam(funcName.MakeString());
    if (methodPara > 0) {
      for (size_t i = 0; i < m_Arguments.size(); ++i) {
        // Currently none of our expressions use objects for a parameter over
        // the 6th. Make sure we don't overflow the shift when doing this
        // check. If we ever need more the 32 object params we can revisit.
        *js << "pfm_rt.get_";
        if (i < 32 && (methodPara & (0x01 << i)) > 0)
          *js << "jsobj";
        else
          *js << "val";

        *js << "(";
        if (!m_Arguments[i]->ToJavaScript(js, ReturnType::kInfered))
          return false;
        *js << ")";
        if (i + 1 < m_Arguments.size())
          *js << ", ";
      }
    } else {
      for (const auto& expr : m_Arguments) {
        *js << "pfm_rt.get_val(";
        if (!expr->ToJavaScript(js, ReturnType::kInfered))
          return false;
        *js << ")";
        if (expr != m_Arguments.back())
          *js << ", ";
      }
    }
    *js << ")";
    return !CXFA_IsTooBig(js);
  }

  bool isEvalFunc = false;
  bool isExistsFunc = false;
  if (!IsBuiltInFunc(&funcName)) {
    // If a function is not a SomMethod or a built-in then the input was
    // invalid, so failing. The scanner/lexer should catch this, but currently
    // doesn't. This failure will bubble up to the top-level and cause the
    // transpile to fail.
    return false;
  }

  if (funcName.AsStringView().EqualsASCII("Eval")) {
    isEvalFunc = true;
    *js << "eval.call(this, pfm_rt.Translate";
  } else {
    if (funcName.AsStringView().EqualsASCII("Exists"))
      isExistsFunc = true;

    *js << "pfm_rt." << funcName;
  }

  *js << "(";
  if (isExistsFunc) {
    *js << "\n(\nfunction ()\n{\ntry\n{\n";
    if (!m_Arguments.empty()) {
      *js << "return ";
      if (!m_Arguments[0]->ToJavaScript(js, ReturnType::kInfered))
        return false;
      *js << ";\n}\n";
    } else {
      *js << "return 0;\n}\n";
    }
    *js << "catch(accessExceptions)\n";
    *js << "{\nreturn 0;\n}\n}\n).call(this)\n";
  } else {
    for (const auto& expr : m_Arguments) {
      if (!expr->ToJavaScript(js, ReturnType::kInfered))
        return false;
      if (expr != m_Arguments.back())
        *js << ", ";
    }
  }
  *js << ")";
  if (isEvalFunc)
    *js << ")";

  return !CXFA_IsTooBig(js);
}

CXFA_FMDotAccessorExpression::CXFA_FMDotAccessorExpression(
    std::unique_ptr<CXFA_FMSimpleExpression> pAccessor,
    XFA_FM_TOKEN op,
    WideStringView wsIdentifier,
    std::unique_ptr<CXFA_FMSimpleExpression> pIndexExp)
    : CXFA_FMChainableExpression(op,
                                 std::move(pAccessor),
                                 std::move(pIndexExp)),
      m_wsIdentifier(wsIdentifier) {}

CXFA_FMDotAccessorExpression::~CXFA_FMDotAccessorExpression() = default;

bool CXFA_FMDotAccessorExpression::ToJavaScript(CFX_WideTextBuf* js,
                                                ReturnType type) {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(js) || !depthManager.IsWithinMaxDepth())
    return false;

  *js << "pfm_rt.dot_acc(";

  CFX_WideTextBuf tempExp1;
  CXFA_FMSimpleExpression* exp1 = GetFirstExpression();
  if (exp1) {
    if (!exp1->ToJavaScript(&tempExp1, ReturnType::kInfered))
      return false;

    *js << tempExp1;
  } else {
    *js << "null";
  }
  *js << ", \"";

  if (exp1 && exp1->GetOperatorToken() == TOKidentifier)
    *js << tempExp1;

  *js << "\", ";
  if (m_op == TOKdotscream)
    *js << "\"#" << m_wsIdentifier << "\", ";
  else if (m_op == TOKdotstar)
    *js << "\"*\", ";
  else if (m_op == TOKcall)
    *js << "\"\", ";
  else
    *js << "\"" << m_wsIdentifier << "\", ";

  CXFA_FMSimpleExpression* exp2 = GetSecondExpression();
  if (!exp2->ToJavaScript(js, ReturnType::kInfered))
    return false;

  *js << ")";
  return !CXFA_IsTooBig(js);
}

CXFA_FMIndexExpression::CXFA_FMIndexExpression(
    XFA_FM_AccessorIndex accessorIndex,
    std::unique_ptr<CXFA_FMSimpleExpression> pIndexExp,
    bool bIsStarIndex)
    : CXFA_FMSimpleExpression(TOKlbracket),
      m_pExp(std::move(pIndexExp)),
      m_accessorIndex(accessorIndex),
      m_bIsStarIndex(bIsStarIndex) {}

CXFA_FMIndexExpression::~CXFA_FMIndexExpression() = default;

bool CXFA_FMIndexExpression::ToJavaScript(CFX_WideTextBuf* js,
                                          ReturnType type) {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(js) || !depthManager.IsWithinMaxDepth())
    return false;

  switch (m_accessorIndex) {
    case ACCESSOR_NO_INDEX:
      *js << "0";
      break;
    case ACCESSOR_NO_RELATIVEINDEX:
      *js << "1";
      break;
    case ACCESSOR_POSITIVE_INDEX:
      *js << "2";
      break;
    case ACCESSOR_NEGATIVE_INDEX:
      *js << "3";
      break;
    default:
      *js << "0";
  }
  if (m_bIsStarIndex)
    return !CXFA_IsTooBig(js);

  *js << ", ";
  if (m_pExp) {
    if (!m_pExp->ToJavaScript(js, ReturnType::kInfered))
      return false;
  } else {
    *js << "0";
  }
  return !CXFA_IsTooBig(js);
}

CXFA_FMDotDotAccessorExpression::CXFA_FMDotDotAccessorExpression(
    std::unique_ptr<CXFA_FMSimpleExpression> pAccessor,
    XFA_FM_TOKEN op,
    WideStringView wsIdentifier,
    std::unique_ptr<CXFA_FMSimpleExpression> pIndexExp)
    : CXFA_FMChainableExpression(op,
                                 std::move(pAccessor),
                                 std::move(pIndexExp)),
      m_wsIdentifier(wsIdentifier) {}

CXFA_FMDotDotAccessorExpression::~CXFA_FMDotDotAccessorExpression() = default;

bool CXFA_FMDotDotAccessorExpression::ToJavaScript(CFX_WideTextBuf* js,
                                                   ReturnType type) {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(js) || !depthManager.IsWithinMaxDepth())
    return false;

  CXFA_FMSimpleExpression* exp1 = GetFirstExpression();
  *js << "pfm_rt.dotdot_acc(";
  if (!exp1->ToJavaScript(js, ReturnType::kInfered))
    return false;
  *js << ", "
      << "\"";
  if (exp1->GetOperatorToken() == TOKidentifier) {
    if (!exp1->ToJavaScript(js, ReturnType::kInfered))
      return false;
  }

  CXFA_FMSimpleExpression* exp2 = GetSecondExpression();
  *js << "\", \"" << m_wsIdentifier << "\", ";
  if (!exp2->ToJavaScript(js, ReturnType::kInfered))
    return false;
  *js << ")";
  return !CXFA_IsTooBig(js);
}

CXFA_FMMethodCallExpression::CXFA_FMMethodCallExpression(
    std::unique_ptr<CXFA_FMSimpleExpression> pAccessorExp1,
    std::unique_ptr<CXFA_FMSimpleExpression> pCallExp)
    : CXFA_FMChainableExpression(TOKdot,
                                 std::move(pAccessorExp1),
                                 std::move(pCallExp)) {}

CXFA_FMMethodCallExpression::~CXFA_FMMethodCallExpression() = default;

bool CXFA_FMMethodCallExpression::ToJavaScript(CFX_WideTextBuf* js,
                                               ReturnType type) {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(js) || !depthManager.IsWithinMaxDepth())
    return false;

  CFX_WideTextBuf buf;
  if (!GetFirstExpression()->ToJavaScript(&buf, ReturnType::kInfered))
    return false;

  *js << "(function() {\n";
  *js << "  return pfm_method_runner(" << buf << ", function(obj) {\n";
  *js << "    return obj.";
  if (!GetSecondExpression()->ToJavaScript(js, ReturnType::kInfered))
    return false;
  *js << ";\n";
  *js << "  });\n";
  *js << "}).call(this)";
  return !CXFA_IsTooBig(js);
}

bool CXFA_IsTooBig(const CFX_WideTextBuf* js) {
  return js->GetSize() >= 256 * 1024 * 1024;
}
