Add a CPDF_PSEngine test.

Change-Id: Ia267f40dc4ebe9e1d28b6a333eddcff749414ffa
Reviewed-on: https://pdfium-review.googlesource.com/17870
Reviewed-by: dsinclair <dsinclair@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index c77c236..39312a0 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1938,6 +1938,7 @@
     "core/fpdfapi/font/cpdf_cmapparser_unittest.cpp",
     "core/fpdfapi/font/cpdf_tounicodemap_unittest.cpp",
     "core/fpdfapi/page/cpdf_devicecs_unittest.cpp",
+    "core/fpdfapi/page/cpdf_psengine_unittest.cpp",
     "core/fpdfapi/page/cpdf_streamcontentparser_unittest.cpp",
     "core/fpdfapi/page/cpdf_streamparser_unittest.cpp",
     "core/fpdfapi/parser/cpdf_array_unittest.cpp",
diff --git a/core/fpdfapi/page/cpdf_psengine.cpp b/core/fpdfapi/page/cpdf_psengine.cpp
index d879ead..9dfa998 100644
--- a/core/fpdfapi/page/cpdf_psengine.cpp
+++ b/core/fpdfapi/page/cpdf_psengine.cpp
@@ -46,39 +46,32 @@
 
 }  // namespace
 
-class CPDF_PSOP {
- public:
-  explicit CPDF_PSOP(PDF_PSOP op) : m_op(op), m_value(0) {
-    ASSERT(m_op != PSOP_CONST);
-    ASSERT(m_op != PSOP_PROC);
-  }
-  explicit CPDF_PSOP(float value) : m_op(PSOP_CONST), m_value(value) {}
-  CPDF_PSOP()
-      : m_op(PSOP_PROC),
-        m_value(0),
-        m_proc(pdfium::MakeUnique<CPDF_PSProc>()) {}
+CPDF_PSOP::CPDF_PSOP()
+    : m_op(PSOP_PROC), m_value(0), m_proc(pdfium::MakeUnique<CPDF_PSProc>()) {}
 
-  float GetFloatValue() const {
-    if (m_op == PSOP_CONST)
-      return m_value;
+CPDF_PSOP::CPDF_PSOP(PDF_PSOP op) : m_op(op), m_value(0) {
+  ASSERT(m_op != PSOP_CONST);
+  ASSERT(m_op != PSOP_PROC);
+}
 
-    NOTREACHED();
-    return 0;
-  }
-  CPDF_PSProc* GetProc() const {
-    if (m_op == PSOP_PROC)
-      return m_proc.get();
-    NOTREACHED();
-    return nullptr;
-  }
+CPDF_PSOP::CPDF_PSOP(float value) : m_op(PSOP_CONST), m_value(value) {}
 
-  PDF_PSOP GetOp() const { return m_op; }
+CPDF_PSOP::~CPDF_PSOP() {}
 
- private:
-  const PDF_PSOP m_op;
-  const float m_value;
-  std::unique_ptr<CPDF_PSProc> m_proc;
-};
+float CPDF_PSOP::GetFloatValue() const {
+  if (m_op == PSOP_CONST)
+    return m_value;
+
+  NOTREACHED();
+  return 0;
+}
+
+CPDF_PSProc* CPDF_PSOP::GetProc() const {
+  if (m_op == PSOP_PROC)
+    return m_proc.get();
+  NOTREACHED();
+  return nullptr;
+}
 
 bool CPDF_PSEngine::Execute() {
   return m_MainProc.Execute(this);
@@ -87,6 +80,29 @@
 CPDF_PSProc::CPDF_PSProc() {}
 CPDF_PSProc::~CPDF_PSProc() {}
 
+bool CPDF_PSProc::Parse(CPDF_SimpleParser* parser, int depth) {
+  if (depth > kMaxDepth)
+    return false;
+
+  while (1) {
+    ByteStringView word = parser->GetWord();
+    if (word.IsEmpty())
+      return false;
+
+    if (word == "}")
+      return true;
+
+    if (word == "{") {
+      m_Operators.push_back(pdfium::MakeUnique<CPDF_PSOP>());
+      if (!m_Operators.back()->GetProc()->Parse(parser, depth + 1))
+        return false;
+      continue;
+    }
+
+    AddOperator(word);
+  }
+}
+
 bool CPDF_PSProc::Execute(CPDF_PSEngine* pEngine) {
   for (size_t i = 0; i < m_Operators.size(); ++i) {
     const PDF_PSOP op = m_Operators[i]->GetOp();
@@ -118,6 +134,23 @@
   return true;
 }
 
+void CPDF_PSProc::AddOperatorForTesting(const ByteStringView& word) {
+  AddOperator(word);
+}
+
+void CPDF_PSProc::AddOperator(const ByteStringView& word) {
+  std::unique_ptr<CPDF_PSOP> op;
+  for (const PDF_PSOpName& op_name : kPsOpNames) {
+    if (word == ByteStringView(op_name.name)) {
+      op = pdfium::MakeUnique<CPDF_PSOP>(op_name.op);
+      break;
+    }
+  }
+  if (!op)
+    op = pdfium::MakeUnique<CPDF_PSOP>(FX_atof(word));
+  m_Operators.push_back(std::move(op));
+}
+
 CPDF_PSEngine::CPDF_PSEngine() : m_StackCount(0) {}
 
 CPDF_PSEngine::~CPDF_PSEngine() {}
@@ -141,38 +174,6 @@
   return word == "{" ? m_MainProc.Parse(&parser, 0) : false;
 }
 
-bool CPDF_PSProc::Parse(CPDF_SimpleParser* parser, int depth) {
-  if (depth > kMaxDepth)
-    return false;
-
-  while (1) {
-    ByteStringView word = parser->GetWord();
-    if (word.IsEmpty())
-      return false;
-
-    if (word == "}")
-      return true;
-
-    if (word == "{") {
-      m_Operators.push_back(pdfium::MakeUnique<CPDF_PSOP>());
-      if (!m_Operators.back()->GetProc()->Parse(parser, depth + 1))
-        return false;
-      continue;
-    }
-
-    std::unique_ptr<CPDF_PSOP> op;
-    for (const PDF_PSOpName& op_name : kPsOpNames) {
-      if (word == ByteStringView(op_name.name)) {
-        op = pdfium::MakeUnique<CPDF_PSOP>(op_name.op);
-        break;
-      }
-    }
-    if (!op)
-      op = pdfium::MakeUnique<CPDF_PSOP>(FX_atof(word));
-    m_Operators.push_back(std::move(op));
-  }
-}
-
 bool CPDF_PSEngine::DoOperator(PDF_PSOP op) {
   int i1;
   int i2;
diff --git a/core/fpdfapi/page/cpdf_psengine.h b/core/fpdfapi/page/cpdf_psengine.h
index 0d5eadd..63d91d0 100644
--- a/core/fpdfapi/page/cpdf_psengine.h
+++ b/core/fpdfapi/page/cpdf_psengine.h
@@ -10,10 +10,11 @@
 #include <memory>
 #include <vector>
 
+#include "core/fxcrt/fx_string.h"
 #include "core/fxcrt/fx_system.h"
 
 class CPDF_PSEngine;
-class CPDF_PSOP;
+class CPDF_PSProc;
 class CPDF_SimpleParser;
 
 enum PDF_PSOP : uint8_t {
@@ -63,6 +64,23 @@
   PSOP_CONST
 };
 
+class CPDF_PSOP {
+ public:
+  CPDF_PSOP();
+  explicit CPDF_PSOP(PDF_PSOP op);
+  explicit CPDF_PSOP(float value);
+  ~CPDF_PSOP();
+
+  float GetFloatValue() const;
+  CPDF_PSProc* GetProc() const;
+  PDF_PSOP GetOp() const { return m_op; }
+
+ private:
+  const PDF_PSOP m_op;
+  const float m_value;
+  std::unique_ptr<CPDF_PSProc> m_proc;
+};
+
 class CPDF_PSProc {
  public:
   CPDF_PSProc();
@@ -71,8 +89,18 @@
   bool Parse(CPDF_SimpleParser* parser, int depth);
   bool Execute(CPDF_PSEngine* pEngine);
 
+  // These methods are exposed for testing.
+  void AddOperatorForTesting(const ByteStringView& word);
+  size_t num_operators() const { return m_Operators.size(); }
+  const std::unique_ptr<CPDF_PSOP>& last_operator() {
+    return m_Operators.back();
+  }
+
  private:
   static const int kMaxDepth = 128;
+
+  void AddOperator(const ByteStringView& word);
+
   std::vector<std::unique_ptr<CPDF_PSOP>> m_Operators;
 };
 
diff --git a/core/fpdfapi/page/cpdf_psengine_unittest.cpp b/core/fpdfapi/page/cpdf_psengine_unittest.cpp
new file mode 100644
index 0000000..f966c3a
--- /dev/null
+++ b/core/fpdfapi/page/cpdf_psengine_unittest.cpp
@@ -0,0 +1,56 @@
+// Copyright 2017 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.
+
+#include "core/fpdfapi/page/cpdf_psengine.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(CPDF_PSProc, AddOperator) {
+  static const struct {
+    const char* name;
+    PDF_PSOP op;
+  } kTestData[] = {
+      {"add", PSOP_ADD},         {"sub", PSOP_SUB},
+      {"mul", PSOP_MUL},         {"div", PSOP_DIV},
+      {"idiv", PSOP_IDIV},       {"mod", PSOP_MOD},
+      {"neg", PSOP_NEG},         {"abs", PSOP_ABS},
+      {"ceiling", PSOP_CEILING}, {"floor", PSOP_FLOOR},
+      {"round", PSOP_ROUND},     {"truncate", PSOP_TRUNCATE},
+      {"sqrt", PSOP_SQRT},       {"sin", PSOP_SIN},
+      {"cos", PSOP_COS},         {"atan", PSOP_ATAN},
+      {"exp", PSOP_EXP},         {"ln", PSOP_LN},
+      {"log", PSOP_LOG},         {"cvi", PSOP_CVI},
+      {"cvr", PSOP_CVR},         {"eq", PSOP_EQ},
+      {"ne", PSOP_NE},           {"gt", PSOP_GT},
+      {"ge", PSOP_GE},           {"lt", PSOP_LT},
+      {"le", PSOP_LE},           {"and", PSOP_AND},
+      {"or", PSOP_OR},           {"xor", PSOP_XOR},
+      {"not", PSOP_NOT},         {"bitshift", PSOP_BITSHIFT},
+      {"true", PSOP_TRUE},       {"false", PSOP_FALSE},
+      {"if", PSOP_IF},           {"ifelse", PSOP_IFELSE},
+      {"pop", PSOP_POP},         {"exch", PSOP_EXCH},
+      {"dup", PSOP_DUP},         {"copy", PSOP_COPY},
+      {"index", PSOP_INDEX},     {"roll", PSOP_ROLL},
+      {"55", PSOP_CONST},        {"123.4", PSOP_CONST},
+      {"-5", PSOP_CONST},        {"invalid", PSOP_CONST},
+  };
+
+  CPDF_PSProc proc;
+  EXPECT_EQ(0U, proc.num_operators());
+  for (size_t i = 0; i < FX_ArraySize(kTestData); ++i) {
+    ByteStringView word(kTestData[i].name);
+    proc.AddOperatorForTesting(word);
+    ASSERT_EQ(i + 1, proc.num_operators());
+    const std::unique_ptr<CPDF_PSOP>& new_psop = proc.last_operator();
+    ASSERT_TRUE(new_psop);
+    PDF_PSOP new_op = new_psop->GetOp();
+    EXPECT_EQ(kTestData[i].op, new_op);
+    if (new_op == PSOP_CONST) {
+      float fv = new_psop->GetFloatValue();
+      if (word == "invalid")
+        EXPECT_FLOAT_EQ(0, fv);
+      else
+        EXPECT_EQ(word, ByteString::FormatFloat(fv));
+    }
+  }
+}