// Copyright 2014 The PDFium Authors
// 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/formcalc/cxfa_fmexpression.h"

#include <algorithm>
#include <utility>

#include "core/fxcrt/autorestorer.h"
#include "core/fxcrt/check.h"
#include "core/fxcrt/fx_extension.h"
#include "core/fxcrt/widetext_buffer.h"
#include "fxjs/gc/container_trace.h"
#include "v8/include/cppgc/visitor.h"
#include "xfa/fxfa/formcalc/cxfa_fmtojavascriptdepth.h"

namespace {

const wchar_t kLessEqual[] = L" <= ";
const wchar_t kGreaterEqual[] = L" >= ";
const wchar_t kPlusEqual[] = L" += ";
const wchar_t kMinusEqual[] = L" -= ";

const wchar_t* const kBuiltInFuncs[] = {
    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 kBuiltInFuncsMaxLen = 12;

struct XFA_FMSOMMethod {
  const wchar_t* som_method_name_;  // Ok, POD struct.
  uint32_t parameters_;
};

const XFA_FMSOMMethod kFMSomMethods[] = {
    {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},
};

WideString IdentifierToName(const WideString& ident) {
  if (ident.IsEmpty() || ident.Front() != L'!') {
    return ident;
  }
  return L"pfm__excl__" + ident.Substr(1);
}

}  // namespace

CXFA_FMExpression::CXFA_FMExpression() = default;

CXFA_FMExpression::~CXFA_FMExpression() = default;

void CXFA_FMExpression::Trace(cppgc::Visitor* visitor) const {}

CXFA_FMSimpleExpression::CXFA_FMSimpleExpression(XFA_FM_TOKEN op) : op_(op) {}

CXFA_FMSimpleExpression::~CXFA_FMSimpleExpression() = default;

CXFA_FMChainableExpression::CXFA_FMChainableExpression(
    XFA_FM_TOKEN op,
    CXFA_FMSimpleExpression* pExp1,
    CXFA_FMSimpleExpression* pExp2)
    : CXFA_FMSimpleExpression(op), exp_1_(pExp1), exp_2_(pExp2) {}

CXFA_FMChainableExpression::~CXFA_FMChainableExpression() = default;

void CXFA_FMChainableExpression::Trace(cppgc::Visitor* visitor) const {
  CXFA_FMSimpleExpression::Trace(visitor);
  visitor->Trace(exp_1_);
  visitor->Trace(exp_2_);
}

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

CXFA_FMNullExpression::~CXFA_FMNullExpression() = default;

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

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

CXFA_FMNumberExpression::CXFA_FMNumberExpression(WideString wsNumber)
    : CXFA_FMSimpleExpression(TOKnumber), number_(std::move(wsNumber)) {}

CXFA_FMNumberExpression::~CXFA_FMNumberExpression() = default;

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

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

CXFA_FMStringExpression::CXFA_FMStringExpression(WideString wsString)
    : CXFA_FMSimpleExpression(TOKstring), string_(std::move(wsString)) {}

CXFA_FMStringExpression::~CXFA_FMStringExpression() = default;

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

  WideString tempStr(string_);
  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(
    WideString wsIdentifier)
    : CXFA_FMSimpleExpression(TOKidentifier),
      identifier_(std::move(wsIdentifier)) {}

CXFA_FMIdentifierExpression::~CXFA_FMIdentifierExpression() = default;

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

  if (identifier_.EqualsASCII("$")) {
    *js << "this";
  } else if (identifier_.EqualsASCII("!")) {
    *js << "xfa.datasets";
  } else if (identifier_.EqualsASCII("$data")) {
    *js << "xfa.datasets.data";
  } else if (identifier_.EqualsASCII("$event")) {
    *js << "xfa.event";
  } else if (identifier_.EqualsASCII("$form")) {
    *js << "xfa.form";
  } else if (identifier_.EqualsASCII("$host")) {
    *js << "xfa.host";
  } else if (identifier_.EqualsASCII("$layout")) {
    *js << "xfa.layout";
  } else if (identifier_.EqualsASCII("$template")) {
    *js << "xfa.template";
  } else if (identifier_.Front() == L'!') {
    *js << "pfm__excl__" << identifier_.Substr(1);
  } else {
    *js << identifier_;
  }

  return !CXFA_IsTooBig(*js);
}

CXFA_FMAssignExpression::CXFA_FMAssignExpression(XFA_FM_TOKEN op,
                                                 CXFA_FMSimpleExpression* pExp1,
                                                 CXFA_FMSimpleExpression* pExp2)
    : CXFA_FMChainableExpression(op, pExp1, pExp2) {}

CXFA_FMAssignExpression::~CXFA_FMAssignExpression() = default;

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

  WideTextBuffer tempExp1;
  const CXFA_FMSimpleExpression* exp1 = GetFirstExpression();
  if (!exp1->ToJavaScript(&tempExp1, ReturnType::kInferred)) {
    return false;
  }

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

  WideTextBuffer tempExp2;
  const CXFA_FMSimpleExpression* exp2 = GetSecondExpression();
  if (!exp2->ToJavaScript(&tempExp2, ReturnType::kInferred)) {
    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,
                                           CXFA_FMSimpleExpression* pExp1,
                                           CXFA_FMSimpleExpression* pExp2)
    : CXFA_FMChainableExpression(op, pExp1, pExp2), op_name_(opName) {}

CXFA_FMBinExpression::~CXFA_FMBinExpression() = default;

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

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

CXFA_FMLogicalOrExpression::CXFA_FMLogicalOrExpression(
    XFA_FM_TOKEN op,
    CXFA_FMSimpleExpression* pExp1,
    CXFA_FMSimpleExpression* pExp2)
    : CXFA_FMBinExpression(L"log_or_op", op, pExp1, pExp2) {}

CXFA_FMLogicalOrExpression::~CXFA_FMLogicalOrExpression() = default;

CXFA_FMLogicalAndExpression::CXFA_FMLogicalAndExpression(
    XFA_FM_TOKEN op,
    CXFA_FMSimpleExpression* pExp1,
    CXFA_FMSimpleExpression* pExp2)
    : CXFA_FMBinExpression(L"log_and_op", op, pExp1, pExp2) {}

CXFA_FMLogicalAndExpression::~CXFA_FMLogicalAndExpression() = default;

CXFA_FMEqualExpression::CXFA_FMEqualExpression(XFA_FM_TOKEN op,
                                               CXFA_FMSimpleExpression* pExp1,
                                               CXFA_FMSimpleExpression* pExp2)
    : CXFA_FMBinExpression(L"eq_op", op, pExp1, pExp2) {}

CXFA_FMEqualExpression::~CXFA_FMEqualExpression() = default;

CXFA_FMNotEqualExpression::CXFA_FMNotEqualExpression(
    XFA_FM_TOKEN op,
    CXFA_FMSimpleExpression* pExp1,
    CXFA_FMSimpleExpression* pExp2)
    : CXFA_FMBinExpression(L"neq_op", op, pExp1, pExp2) {}

CXFA_FMNotEqualExpression::~CXFA_FMNotEqualExpression() = default;

CXFA_FMGtExpression::CXFA_FMGtExpression(XFA_FM_TOKEN op,
                                         CXFA_FMSimpleExpression* pExp1,
                                         CXFA_FMSimpleExpression* pExp2)
    : CXFA_FMBinExpression(L"gt_op", op, pExp1, pExp2) {}

CXFA_FMGtExpression::~CXFA_FMGtExpression() = default;

CXFA_FMGeExpression::CXFA_FMGeExpression(XFA_FM_TOKEN op,
                                         CXFA_FMSimpleExpression* pExp1,
                                         CXFA_FMSimpleExpression* pExp2)
    : CXFA_FMBinExpression(L"ge_op", op, pExp1, pExp2) {}

CXFA_FMGeExpression::~CXFA_FMGeExpression() = default;

CXFA_FMLtExpression::CXFA_FMLtExpression(XFA_FM_TOKEN op,
                                         CXFA_FMSimpleExpression* pExp1,
                                         CXFA_FMSimpleExpression* pExp2)
    : CXFA_FMBinExpression(L"lt_op", op, pExp1, pExp2) {}

CXFA_FMLtExpression::~CXFA_FMLtExpression() = default;

CXFA_FMLeExpression::CXFA_FMLeExpression(XFA_FM_TOKEN op,
                                         CXFA_FMSimpleExpression* pExp1,
                                         CXFA_FMSimpleExpression* pExp2)
    : CXFA_FMBinExpression(L"le_op", op, pExp1, pExp2) {}

CXFA_FMLeExpression::~CXFA_FMLeExpression() = default;

CXFA_FMPlusExpression::CXFA_FMPlusExpression(XFA_FM_TOKEN op,
                                             CXFA_FMSimpleExpression* pExp1,
                                             CXFA_FMSimpleExpression* pExp2)
    : CXFA_FMBinExpression(L"plus_op", op, pExp1, pExp2) {}

CXFA_FMPlusExpression::~CXFA_FMPlusExpression() = default;

CXFA_FMMinusExpression::CXFA_FMMinusExpression(XFA_FM_TOKEN op,
                                               CXFA_FMSimpleExpression* pExp1,
                                               CXFA_FMSimpleExpression* pExp2)
    : CXFA_FMBinExpression(L"minus_op", op, pExp1, pExp2) {}

CXFA_FMMinusExpression::~CXFA_FMMinusExpression() = default;

CXFA_FMMulExpression::CXFA_FMMulExpression(XFA_FM_TOKEN op,
                                           CXFA_FMSimpleExpression* pExp1,
                                           CXFA_FMSimpleExpression* pExp2)
    : CXFA_FMBinExpression(L"mul_op", op, pExp1, pExp2) {}

CXFA_FMMulExpression::~CXFA_FMMulExpression() = default;

CXFA_FMDivExpression::CXFA_FMDivExpression(XFA_FM_TOKEN op,
                                           CXFA_FMSimpleExpression* pExp1,
                                           CXFA_FMSimpleExpression* pExp2)
    : CXFA_FMBinExpression(L"div_op", op, pExp1, pExp2) {}

CXFA_FMDivExpression::~CXFA_FMDivExpression() = default;

CXFA_FMUnaryExpression::CXFA_FMUnaryExpression(const WideString& opName,
                                               XFA_FM_TOKEN op,
                                               CXFA_FMSimpleExpression* pExp)
    : CXFA_FMSimpleExpression(op), op_name_(opName), exp_(pExp) {}

CXFA_FMUnaryExpression::~CXFA_FMUnaryExpression() = default;

void CXFA_FMUnaryExpression::Trace(cppgc::Visitor* visitor) const {
  CXFA_FMSimpleExpression::Trace(visitor);
  visitor->Trace(exp_);
}

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

  *js << "pfm_rt." << op_name_ << "(";
  if (!exp_->ToJavaScript(js, ReturnType::kInferred)) {
    return false;
  }
  *js << ")";
  return !CXFA_IsTooBig(*js);
}

CXFA_FMPosExpression::CXFA_FMPosExpression(CXFA_FMSimpleExpression* pExp)
    : CXFA_FMUnaryExpression(L"pos_op", TOKplus, pExp) {}

CXFA_FMPosExpression::~CXFA_FMPosExpression() = default;

CXFA_FMNegExpression::CXFA_FMNegExpression(CXFA_FMSimpleExpression* pExp)
    : CXFA_FMUnaryExpression(L"neg_op", TOKminus, pExp) {}

CXFA_FMNegExpression::~CXFA_FMNegExpression() = default;

CXFA_FMNotExpression::CXFA_FMNotExpression(CXFA_FMSimpleExpression* pExp)
    : CXFA_FMUnaryExpression(L"log_not_op", TOKksnot, pExp) {}

CXFA_FMNotExpression::~CXFA_FMNotExpression() = default;

CXFA_FMCallExpression::CXFA_FMCallExpression(
    CXFA_FMSimpleExpression* pExp,
    std::vector<cppgc::Member<CXFA_FMSimpleExpression>>&& pArguments,
    bool bIsSomMethod)
    : CXFA_FMSimpleExpression(TOKcall),
      exp_(pExp),
      arguments_(std::move(pArguments)),
      is_som_method_(bIsSomMethod) {}

CXFA_FMCallExpression::~CXFA_FMCallExpression() = default;

void CXFA_FMCallExpression::Trace(cppgc::Visitor* visitor) const {
  CXFA_FMSimpleExpression::Trace(visitor);
  visitor->Trace(exp_);
  ContainerTrace(visitor, arguments_);
}

bool CXFA_FMCallExpression::IsBuiltInFunc(WideTextBuffer* funcName) const {
  if (funcName->GetLength() > kBuiltInFuncsMaxLen) {
    return false;
  }

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

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

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

  WideTextBuffer funcName;
  if (!exp_->ToJavaScript(&funcName, ReturnType::kInferred)) {
    return false;
  }

  if (is_som_method_) {
    *js << funcName << "(";
    uint32_t methodPara = IsMethodWithObjParam(funcName.MakeString());
    if (methodPara > 0) {
      for (size_t i = 0; i < 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 (!arguments_[i]->ToJavaScript(js, ReturnType::kInferred)) {
          return false;
        }
        *js << ")";
        if (i + 1 < arguments_.size()) {
          *js << ", ";
        }
      }
    } else {
      for (const auto& expr : arguments_) {
        *js << "pfm_rt.get_val(";
        if (!expr->ToJavaScript(js, ReturnType::kInferred)) {
          return false;
        }
        *js << ")";
        if (expr != 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 (!arguments_.empty()) {
      *js << "return ";
      if (!arguments_[0]->ToJavaScript(js, ReturnType::kInferred)) {
        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 : arguments_) {
      if (!expr->ToJavaScript(js, ReturnType::kInferred)) {
        return false;
      }
      if (expr != arguments_.back()) {
        *js << ", ";
      }
    }
  }
  *js << ")";
  if (isEvalFunc) {
    *js << ")";
  }

  return !CXFA_IsTooBig(*js);
}

CXFA_FMDotAccessorExpression::CXFA_FMDotAccessorExpression(
    CXFA_FMSimpleExpression* pAccessor,
    XFA_FM_TOKEN op,
    WideString wsIdentifier,
    CXFA_FMSimpleExpression* pIndexExp)
    : CXFA_FMChainableExpression(op, pAccessor, pIndexExp),
      identifier_(std::move(wsIdentifier)) {}

CXFA_FMDotAccessorExpression::~CXFA_FMDotAccessorExpression() = default;

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

  *js << "pfm_rt.dot_acc(";

  CXFA_FMSimpleExpression* exp1 = GetFirstExpression();
  if (exp1) {
    // Write directly to the buffer with each recursion. Creating
    // and copying temporaries here becomes expensive when there
    // is deep recursion, even though we may need to re-create the
    // same thing again below. See https://crbug.com/1274018.
    if (!exp1->ToJavaScript(js, ReturnType::kInferred)) {
      return false;
    }
  } else {
    *js << "null";
  }
  *js << ", \"";
  if (exp1 && exp1->GetOperatorToken() == TOKidentifier) {
    if (!exp1->ToJavaScript(js, ReturnType::kInferred)) {
      return false;
    }
  }
  *js << "\", ";
  if (GetOperatorToken() == TOKdotscream) {
    *js << "\"#" << identifier_ << "\", ";
  } else if (GetOperatorToken() == TOKdotstar) {
    *js << "\"*\", ";
  } else if (GetOperatorToken() == TOKcall) {
    *js << "\"\", ";
  } else {
    *js << "\"" << identifier_ << "\", ";
  }

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

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

CXFA_FMIndexExpression::CXFA_FMIndexExpression(
    AccessorIndex accessorIndex,
    CXFA_FMSimpleExpression* pIndexExp,
    bool bIsStarIndex)
    : CXFA_FMSimpleExpression(TOKlbracket),
      exp_(pIndexExp),
      accessor_index_(accessorIndex),
      is_star_index_(bIsStarIndex) {}

CXFA_FMIndexExpression::~CXFA_FMIndexExpression() = default;

void CXFA_FMIndexExpression::Trace(cppgc::Visitor* visitor) const {
  CXFA_FMSimpleExpression::Trace(visitor);
  visitor->Trace(exp_);
}

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

  switch (accessor_index_) {
    case AccessorIndex::kNoIndex:
      *js << "0";
      break;
    case AccessorIndex::kNoRelativeIndex:
      *js << "1";
      break;
    case AccessorIndex::kPositiveIndex:
      *js << "2";
      break;
    case AccessorIndex::kNegativeIndex:
      *js << "3";
      break;
  }
  if (is_star_index_) {
    return !CXFA_IsTooBig(*js);
  }

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

CXFA_FMDotDotAccessorExpression::CXFA_FMDotDotAccessorExpression(
    CXFA_FMSimpleExpression* pAccessor,
    XFA_FM_TOKEN op,
    WideString wsIdentifier,
    CXFA_FMSimpleExpression* pIndexExp)
    : CXFA_FMChainableExpression(op, pAccessor, pIndexExp),
      identifier_(std::move(wsIdentifier)) {}

CXFA_FMDotDotAccessorExpression::~CXFA_FMDotDotAccessorExpression() = default;

bool CXFA_FMDotDotAccessorExpression::ToJavaScript(WideTextBuffer* js,
                                                   ReturnType type) const {
  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::kInferred)) {
    return false;
  }
  *js << ", "
      << "\"";
  if (exp1->GetOperatorToken() == TOKidentifier) {
    if (!exp1->ToJavaScript(js, ReturnType::kInferred)) {
      return false;
    }
  }

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

CXFA_FMMethodCallExpression::CXFA_FMMethodCallExpression(
    CXFA_FMSimpleExpression* pAccessorExp1,
    CXFA_FMSimpleExpression* pCallExp)
    : CXFA_FMChainableExpression(TOKdot, pAccessorExp1, pCallExp) {}

CXFA_FMMethodCallExpression::~CXFA_FMMethodCallExpression() = default;

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

  *js << "(function() {\n";
  *js << "  return pfm_method_runner(";
  if (!GetFirstExpression()->ToJavaScript(js, ReturnType::kInferred)) {
    return false;
  }

  *js << ", function(obj) {\n";
  *js << "    return obj.";
  if (!GetSecondExpression()->ToJavaScript(js, ReturnType::kInferred)) {
    return false;
  }

  *js << ";\n";
  *js << "  });\n";
  *js << "}).call(this)";
  return !CXFA_IsTooBig(*js);
}

CXFA_FMFunctionDefinition::CXFA_FMFunctionDefinition(
    WideString wsName,
    std::vector<WideString>&& arguments,
    std::vector<cppgc::Member<CXFA_FMExpression>>&& expressions)
    : name_(std::move(wsName)),
      arguments_(std::move(arguments)),
      expressions_(std::move(expressions)) {
  DCHECK(!name_.IsEmpty());
}

CXFA_FMFunctionDefinition::~CXFA_FMFunctionDefinition() = default;

void CXFA_FMFunctionDefinition::Trace(cppgc::Visitor* visitor) const {
  CXFA_FMExpression::Trace(visitor);
  ContainerTrace(visitor, expressions_);
}

bool CXFA_FMFunctionDefinition::ToJavaScript(WideTextBuffer* js,
                                             ReturnType type) const {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(*js) || !depthManager.IsWithinMaxDepth()) {
    return false;
  }

  if (name_.IsEmpty()) {
    return false;
  }

  *js << "function " << IdentifierToName(name_) << "(";
  for (const auto& identifier : arguments_) {
    if (identifier != arguments_.front()) {
      *js << ", ";
    }

    *js << IdentifierToName(identifier);
  }
  *js << ") {\n";

  *js << "var pfm_ret = null;\n";
  for (const auto& expr : expressions_) {
    ReturnType ret_type = expr == expressions_.back() ? ReturnType::kImplied
                                                      : ReturnType::kInferred;
    if (!expr->ToJavaScript(js, ret_type)) {
      return false;
    }
  }

  *js << "return pfm_ret;\n";
  *js << "}\n";

  return !CXFA_IsTooBig(*js);
}

CXFA_FMAST::CXFA_FMAST(
    std::vector<cppgc::Member<CXFA_FMExpression>> expressions)
    : expressions_(std::move(expressions)) {}

CXFA_FMAST::~CXFA_FMAST() = default;

void CXFA_FMAST::Trace(cppgc::Visitor* visitor) const {
  ContainerTrace(visitor, expressions_);
}

std::optional<WideTextBuffer> CXFA_FMAST::ToJavaScript() const {
  WideTextBuffer js;
  if (expressions_.empty()) {
    js << "// comments only";
    return js;
  }

  js << "(function() {\n";
  js << "let pfm_method_runner = function(obj, cb) {\n";
  js << "  if (pfm_rt.is_ary(obj)) {\n";
  js << "    let pfm_method_return = null;\n";
  js << "    for (var idx = obj.length -1; idx > 1; idx--) {\n";
  js << "      pfm_method_return = cb(obj[idx]);\n";
  js << "    }\n";
  js << "    return pfm_method_return;\n";
  js << "  }\n";
  js << "  return cb(obj);\n";
  js << "};\n";
  js << "var pfm_ret = null;\n";
  for (const auto& expr : expressions_) {
    CXFA_FMAssignExpression::ReturnType ret_type =
        expr == expressions_.back()
            ? CXFA_FMAssignExpression::ReturnType::kImplied
            : CXFA_FMAssignExpression::ReturnType::kInferred;
    if (!expr->ToJavaScript(&js, ret_type)) {
      return std::nullopt;
    }
  }
  js << "return pfm_rt.get_val(pfm_ret);\n";
  js << "}).call(this);";

  if (CXFA_IsTooBig(js)) {
    return std::nullopt;
  }

  return js;
}

CXFA_FMVarExpression::CXFA_FMVarExpression(WideString wsName,
                                           CXFA_FMSimpleExpression* pInit)
    : name_(std::move(wsName)), init_(pInit) {}

CXFA_FMVarExpression::~CXFA_FMVarExpression() = default;

void CXFA_FMVarExpression::Trace(cppgc::Visitor* visitor) const {
  CXFA_FMExpression::Trace(visitor);
  visitor->Trace(init_);
}

bool CXFA_FMVarExpression::ToJavaScript(WideTextBuffer* js,
                                        ReturnType type) const {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(*js) || !depthManager.IsWithinMaxDepth()) {
    return false;
  }

  WideString tempName = IdentifierToName(name_);
  *js << "var " << tempName << " = ";
  if (init_) {
    if (!init_->ToJavaScript(js, ReturnType::kInferred)) {
      return false;
    }

    *js << ";\n";
    *js << tempName << " = pfm_rt.var_filter(" << tempName << ");\n";
  } else {
    *js << "\"\";\n";
  }

  if (type == ReturnType::kImplied) {
    *js << "pfm_ret = " << tempName << ";\n";
  }

  return !CXFA_IsTooBig(*js);
}

CXFA_FMExpExpression::CXFA_FMExpExpression(CXFA_FMSimpleExpression* pExpression)
    : expression_(pExpression) {}

CXFA_FMExpExpression::~CXFA_FMExpExpression() = default;

void CXFA_FMExpExpression::Trace(cppgc::Visitor* visitor) const {
  CXFA_FMExpression::Trace(visitor);
  visitor->Trace(expression_);
}

bool CXFA_FMExpExpression::ToJavaScript(WideTextBuffer* js,
                                        ReturnType type) const {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(*js) || !depthManager.IsWithinMaxDepth()) {
    return false;
  }

  if (type == ReturnType::kInferred) {
    bool ret = expression_->ToJavaScript(js, ReturnType::kInferred);
    if (expression_->GetOperatorToken() != TOKassign) {
      *js << ";\n";
    }

    return ret;
  }

  if (expression_->GetOperatorToken() == TOKassign) {
    return expression_->ToJavaScript(js, ReturnType::kImplied);
  }

  if (expression_->GetOperatorToken() == TOKstar ||
      expression_->GetOperatorToken() == TOKdotstar ||
      expression_->GetOperatorToken() == TOKdotscream ||
      expression_->GetOperatorToken() == TOKdotdot ||
      expression_->GetOperatorToken() == TOKdot) {
    *js << "pfm_ret = pfm_rt.get_val(";
    if (!expression_->ToJavaScript(js, ReturnType::kInferred)) {
      return false;
    }

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

  *js << "pfm_ret = ";
  if (!expression_->ToJavaScript(js, ReturnType::kInferred)) {
    return false;
  }

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

CXFA_FMBlockExpression::CXFA_FMBlockExpression(
    std::vector<cppgc::Member<CXFA_FMExpression>>&& pExpressionList)
    : expression_list_(std::move(pExpressionList)) {}

CXFA_FMBlockExpression::~CXFA_FMBlockExpression() = default;

void CXFA_FMBlockExpression::Trace(cppgc::Visitor* visitor) const {
  CXFA_FMExpression::Trace(visitor);
  ContainerTrace(visitor, expression_list_);
}

bool CXFA_FMBlockExpression::ToJavaScript(WideTextBuffer* js,
                                          ReturnType type) const {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(*js) || !depthManager.IsWithinMaxDepth()) {
    return false;
  }

  *js << "{\n";
  for (const auto& expr : expression_list_) {
    if (type == ReturnType::kInferred) {
      if (!expr->ToJavaScript(js, ReturnType::kInferred)) {
        return false;
      }
    } else {
      ReturnType ret_type = expr == expression_list_.back()
                                ? ReturnType::kImplied
                                : ReturnType::kInferred;
      if (!expr->ToJavaScript(js, ret_type)) {
        return false;
      }
    }
  }
  *js << "}\n";

  return !CXFA_IsTooBig(*js);
}

CXFA_FMDoExpression::CXFA_FMDoExpression(CXFA_FMExpression* pList)
    : list_(pList) {}

CXFA_FMDoExpression::~CXFA_FMDoExpression() = default;

void CXFA_FMDoExpression::Trace(cppgc::Visitor* visitor) const {
  CXFA_FMExpression::Trace(visitor);
  visitor->Trace(list_);
}

bool CXFA_FMDoExpression::ToJavaScript(WideTextBuffer* js,
                                       ReturnType type) const {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(*js) || !depthManager.IsWithinMaxDepth()) {
    return false;
  }

  return list_->ToJavaScript(js, type);
}

CXFA_FMIfExpression::CXFA_FMIfExpression(
    CXFA_FMSimpleExpression* pExpression,
    CXFA_FMExpression* pIfExpression,
    std::vector<cppgc::Member<CXFA_FMIfExpression>>&& pElseIfExpressions,
    CXFA_FMExpression* pElseExpression)
    : expression_(pExpression),
      if_expression_(pIfExpression),
      else_if_expressions_(std::move(pElseIfExpressions)),
      else_expression_(pElseExpression) {
  DCHECK(expression_);
}

CXFA_FMIfExpression::~CXFA_FMIfExpression() = default;

void CXFA_FMIfExpression::Trace(cppgc::Visitor* visitor) const {
  CXFA_FMExpression::Trace(visitor);
  visitor->Trace(expression_);
  visitor->Trace(if_expression_);
  ContainerTrace(visitor, else_if_expressions_);
  visitor->Trace(else_expression_);
}

bool CXFA_FMIfExpression::ToJavaScript(WideTextBuffer* js,
                                       ReturnType type) const {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(*js) || !depthManager.IsWithinMaxDepth()) {
    return false;
  }

  if (type == ReturnType::kImplied) {
    *js << "pfm_ret = 0;\n";
  }

  *js << "if (pfm_rt.get_val(";
  if (!expression_->ToJavaScript(js, ReturnType::kInferred)) {
    return false;
  }
  *js << "))\n";

  if (CXFA_IsTooBig(*js)) {
    return false;
  }

  if (if_expression_) {
    if (!if_expression_->ToJavaScript(js, type)) {
      return false;
    }
    if (CXFA_IsTooBig(*js)) {
      return false;
    }
  }

  for (auto& expr : else_if_expressions_) {
    *js << "else ";
    if (!expr->ToJavaScript(js, ReturnType::kInferred)) {
      return false;
    }
  }

  if (else_expression_) {
    *js << "else ";
    if (!else_expression_->ToJavaScript(js, type)) {
      return false;
    }
  }
  return !CXFA_IsTooBig(*js);
}

CXFA_FMWhileExpression::CXFA_FMWhileExpression(
    CXFA_FMSimpleExpression* pCondition,
    CXFA_FMExpression* pExpression)
    : condition_(pCondition), expression_(pExpression) {}

CXFA_FMWhileExpression::~CXFA_FMWhileExpression() = default;

void CXFA_FMWhileExpression::Trace(cppgc::Visitor* visitor) const {
  CXFA_FMExpression::Trace(visitor);
  visitor->Trace(condition_);
  visitor->Trace(expression_);
}

bool CXFA_FMWhileExpression::ToJavaScript(WideTextBuffer* js,
                                          ReturnType type) const {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(*js) || !depthManager.IsWithinMaxDepth()) {
    return false;
  }

  if (type == ReturnType::kImplied) {
    *js << "pfm_ret = 0;\n";
  }

  *js << "while (";
  if (!condition_->ToJavaScript(js, ReturnType::kInferred)) {
    return false;
  }

  *js << ")\n";
  if (CXFA_IsTooBig(*js)) {
    return false;
  }

  if (!expression_->ToJavaScript(js, type)) {
    return false;
  }

  return !CXFA_IsTooBig(*js);
}

CXFA_FMBreakExpression::CXFA_FMBreakExpression() = default;

CXFA_FMBreakExpression::~CXFA_FMBreakExpression() = default;

bool CXFA_FMBreakExpression::ToJavaScript(WideTextBuffer* js,
                                          ReturnType type) const {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(*js) || !depthManager.IsWithinMaxDepth()) {
    return false;
  }

  *js << "pfm_ret = 0;\nbreak;\n";
  return !CXFA_IsTooBig(*js);
}

CXFA_FMContinueExpression::CXFA_FMContinueExpression() = default;

CXFA_FMContinueExpression::~CXFA_FMContinueExpression() = default;

bool CXFA_FMContinueExpression::ToJavaScript(WideTextBuffer* js,
                                             ReturnType type) const {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(*js) || !depthManager.IsWithinMaxDepth()) {
    return false;
  }

  *js << "pfm_ret = 0;\ncontinue;\n";
  return !CXFA_IsTooBig(*js);
}

CXFA_FMForExpression::CXFA_FMForExpression(WideString wsVariant,
                                           CXFA_FMSimpleExpression* pAssignment,
                                           CXFA_FMSimpleExpression* pAccessor,
                                           int32_t iDirection,
                                           CXFA_FMSimpleExpression* pStep,
                                           CXFA_FMExpression* pList)
    : variant_(std::move(wsVariant)),
      direction_(iDirection == 1),
      assignment_(pAssignment),
      accessor_(pAccessor),
      step_(pStep),
      list_(pList) {}

CXFA_FMForExpression::~CXFA_FMForExpression() = default;

void CXFA_FMForExpression::Trace(cppgc::Visitor* visitor) const {
  CXFA_FMExpression::Trace(visitor);
  visitor->Trace(assignment_);
  visitor->Trace(accessor_);
  visitor->Trace(step_);
  visitor->Trace(list_);
}

bool CXFA_FMForExpression::ToJavaScript(WideTextBuffer* js,
                                        ReturnType type) const {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(*js) || !depthManager.IsWithinMaxDepth()) {
    return false;
  }

  if (type == ReturnType::kImplied) {
    *js << "pfm_ret = 0;\n";
  }

  *js << "{\n";

  WideString tmpName = IdentifierToName(variant_);
  *js << "var " << tmpName << " = null;\n";

  *js << "for (" << tmpName << " = pfm_rt.get_val(";
  if (!assignment_->ToJavaScript(js, ReturnType::kInferred)) {
    return false;
  }
  *js << "); ";

  *js << tmpName << (direction_ ? kLessEqual : kGreaterEqual);
  *js << "pfm_rt.get_val(";
  if (!accessor_->ToJavaScript(js, ReturnType::kInferred)) {
    return false;
  }
  *js << "); ";

  *js << tmpName << (direction_ ? kPlusEqual : kMinusEqual);
  if (step_) {
    *js << "pfm_rt.get_val(";
    if (!step_->ToJavaScript(js, ReturnType::kInferred)) {
      return false;
    }
    *js << ")";
  } else {
    *js << "1";
  }
  *js << ")\n";
  if (CXFA_IsTooBig(*js)) {
    return false;
  }

  if (!list_->ToJavaScript(js, type)) {
    return false;
  }

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

CXFA_FMForeachExpression::CXFA_FMForeachExpression(
    WideString wsIdentifier,
    std::vector<cppgc::Member<CXFA_FMSimpleExpression>>&& pAccessors,
    CXFA_FMExpression* pList)
    : identifier_(std::move(wsIdentifier)),
      accessors_(std::move(pAccessors)),
      list_(pList) {}

CXFA_FMForeachExpression::~CXFA_FMForeachExpression() = default;

void CXFA_FMForeachExpression::Trace(cppgc::Visitor* visitor) const {
  CXFA_FMExpression::Trace(visitor);
  ContainerTrace(visitor, accessors_);
  visitor->Trace(list_);
}

bool CXFA_FMForeachExpression::ToJavaScript(WideTextBuffer* js,
                                            ReturnType type) const {
  CXFA_FMToJavaScriptDepth depthManager;
  if (CXFA_IsTooBig(*js) || !depthManager.IsWithinMaxDepth()) {
    return false;
  }

  if (type == ReturnType::kImplied) {
    *js << "pfm_ret = 0;\n";
  }

  *js << "{\n";

  WideString tmpName = IdentifierToName(identifier_);
  *js << "var " << tmpName << " = null;\n";
  *js << "var pfm_ary = pfm_rt.concat_obj(";
  for (const auto& expr : accessors_) {
    if (!expr->ToJavaScript(js, ReturnType::kInferred)) {
      return false;
    }
    if (expr != accessors_.back()) {
      *js << ", ";
    }
  }
  *js << ");\n";

  *js << "var pfm_ary_idx = 0;\n";
  *js << "while(pfm_ary_idx < pfm_ary.length)\n{\n";
  *js << tmpName << " = pfm_ary[pfm_ary_idx++];\n";
  if (!list_->ToJavaScript(js, type)) {
    return false;
  }

  *js << "}\n";  // while
  *js << "}\n";  // block
  return !CXFA_IsTooBig(*js);
}

bool CXFA_IsTooBig(const WideTextBuffer& js) {
  return js.GetSize() >= 256 * 1024 * 1024;
}
