text: 
  /*
   * Copyright 2006 The Android Open Source Project
   *
   * Use of this source code is governed by a BSD-style license that can be
   * found in the LICENSE file.
   */
  
  
  #include "SkScript.h"
  #include "SkMath.h"
  #include "SkParse.h"
  #include "SkString.h"
  #include "SkTypedArray.h"
  
  /* things to do
      ? re-enable support for struct literals (e.g., for initializing points or rects)
          {x:1, y:2}
      ? use standard XML / script notation like document.getElementById("canvas");
      finish support for typed arrays
          ? allow indexing arrays by string
              this could map to the 'name' attribute of a given child of an array
          ? allow multiple types in the array
      remove SkDisplayType.h  // from SkOperand.h
      merge type and operand arrays into scriptvalue array
  */
  
  #ifdef SK_DEBUG
  static const char* errorStrings[] = {
          "array index of out bounds", // kArrayIndexOutOfBounds
          "could not find reference id", // kCouldNotFindReferencedID
          "dot operator expects object", // kDotOperatorExpectsObject
          "error in array index", // kErrorInArrrayIndex
          "error in function parameters", // kErrorInFunctionParameters
          "expected array", // kExpectedArray
          "expected boolean expression", // kExpectedBooleanExpression
          "expected field name", // kExpectedFieldName
          "expected hex", // kExpectedHex
          "expected int for condition operator", // kExpectedIntForConditionOperator
          "expected number", // kExpectedNumber
          "expected number for array index", // kExpectedNumberForArrayIndex
          "expected operator", // kExpectedOperator
          "expected token", // kExpectedToken
          "expected token before dot operator", // kExpectedTokenBeforeDotOperator
          "expected value", // kExpectedValue
          "handle member failed", // kHandleMemberFailed
          "handle member function failed", // kHandleMemberFunctionFailed
          "handle unbox failed", // kHandleUnboxFailed
          "index out of range", // kIndexOutOfRange
          "mismatched array brace", // kMismatchedArrayBrace
          "mismatched brackets", // kMismatchedBrackets
          "no function handler found", // kNoFunctionHandlerFound
          "premature end", // kPrematureEnd
          "too many parameters", // kTooManyParameters
          "type conversion failed", // kTypeConversionFailed
          "unterminated string" // kUnterminatedString
  };
  #endif
  
  const SkScriptEngine::SkOperatorAttributes SkScriptEngine::gOpAttributes[] = {
      { kNoType, kNoType, kNoBias }, //   kUnassigned,
      { SkOpType(kInt | kScalar | kString), SkOpType(kInt | kScalar | kString), kTowardsString }, // kAdd
      // kAddInt = kAdd,
      { kNoType, kNoType, kNoBias },  // kAddScalar,
      { kNoType, kNoType, kNoBias },  // kAddString,
      { kNoType, kNoType, kNoBias },  // kArrayOp,
      { kInt, kInt, kNoBias }, // kBitAnd
      { kNoType, kInt, kNoBias }, // kBitNot
      { kInt, kInt, kNoBias }, // kBitOr
      { SkOpType(kInt | kScalar), SkOpType(kInt | kScalar), kNoBias }, // kDivide
      // kDivideInt = kDivide
      { kNoType, kNoType, kNoBias },  // kDivideScalar
      { kNoType, kNoType, kNoBias },  // kElse
      { SkOpType(kInt | kScalar | kString), SkOpType(kInt | kScalar | kString), kTowardsNumber }, // kEqual
      // kEqualInt = kEqual
      { kNoType, kNoType, kNoBias },  // kEqualScalar
      { kNoType, kNoType, kNoBias },  // kEqualString
      { kInt, kNoType, kNoBias },     // kFlipOps
      { SkOpType(kInt | kScalar | kString), SkOpType(kInt | kScalar | kString), kTowardsNumber }, // kGreaterEqual
      // kGreaterEqualInt = kGreaterEqual
      { kNoType, kNoType, kNoBias },  // kGreaterEqualScalar
      { kNoType, kNoType, kNoBias },  // kGreaterEqualString
      { kNoType, kNoType, kNoBias },  // kIf
      { kNoType, kInt, kNoBias }, // kLogicalAnd  (really, ToBool)
      { kNoType, kInt, kNoBias }, // kLogicalNot
      { kInt, kInt, kNoBias }, // kLogicalOr
      { kNoType, SkOpType(kInt | kScalar), kNoBias }, // kMinus
      // kMinusInt = kMinus
      { kNoType, kNoType, kNoBias },  // kMinusScalar
      { SkOpType(kInt | kScalar), SkOpType(kInt | kScalar), kNoBias }, // kModulo
      // kModuloInt = kModulo
      { kNoType, kNoType, kNoBias },  // kModuloScalar
      { SkOpType(kInt | kScalar), SkOpType(kInt | kScalar), kNoBias }, // kMultiply
      // kMultiplyInt = kMultiply
      { kNoType, kNoType, kNoBias },  // kMultiplyScalar
      { kNoType, kNoType, kNoBias },  // kParen
      { kInt, kInt, kNoBias }, // kShiftLeft
      { kInt, kInt, kNoBias }, // kShiftRight
      { SkOpType(kInt | kScalar), SkOpType(kInt | kScalar), kNoBias }, // kSubtract
      // kSubtractInt = kSubtract
      { kNoType, kNoType, kNoBias },  // kSubtractScalar
      { kInt, kInt, kNoBias } // kXor
  };
  
  // Note that the real precedence for () [] is '2'
  // but here, precedence means 'while an equal or smaller precedence than the current operator
  // is on the stack, process it. This allows 3+5*2 to defer the add until after the multiply
  // is preformed, since the add precedence is not smaller than multiply.
  // But, (3*4 does not process the '(', since brackets are greater than all other precedences
  #define kBracketPrecedence 16
  #define kIfElsePrecedence 15
  
  const signed char SkScriptEngine::gPrecedence[] = {
          -1, //  kUnassigned,
          6, // kAdd,
          // kAddInt = kAdd,
          6, // kAddScalar,
          6, // kAddString,   // string concat
          kBracketPrecedence, // kArrayOp,
          10, // kBitAnd,
          4, // kBitNot,
          12, // kBitOr,
          5, // kDivide,
          // kDivideInt = kDivide,
          5, // kDivideScalar,
          kIfElsePrecedence, // kElse,
          9, // kEqual,
          // kEqualInt = kEqual,
          9, // kEqualScalar,
          9, // kEqualString,
          -1, // kFlipOps,
          8, // kGreaterEqual,
          // kGreaterEqualInt = kGreaterEqual,
          8, // kGreaterEqualScalar,
          8, // kGreaterEqualString,
          kIfElsePrecedence, // kIf,
          13, // kLogicalAnd,
          4, // kLogicalNot,
          14, // kLogicalOr,
          4, // kMinus,
          // kMinusInt = kMinus,
          4, // kMinusScalar,
          5, // kModulo,
          // kModuloInt = kModulo,
          5, // kModuloScalar,
          5, // kMultiply,
          // kMultiplyInt = kMultiply,
          5, // kMultiplyScalar,
          kBracketPrecedence, // kParen,
          7, // kShiftLeft,
          7, // kShiftRight,  // signed
          6, // kSubtract,
          // kSubtractInt = kSubtract,
          6, // kSubtractScalar,
          11, // kXor
  };
  
  static inline bool is_between(int c, int min, int max)
  {
      return (unsigned)(c - min) <= (unsigned)(max - min);
  }
  
  static inline bool is_ws(int c)
  {
      return is_between(c, 1, 32);
  }
  
  static int token_length(const char* start) {
      char ch = start[0];
      if (! is_between(ch, 'a' , 'z') &&  ! is_between(ch, 'A', 'Z') && ch != '_' && ch != '$')
          return -1;
      int length = 0;
      do
          ch = start[++length];
      while (is_between(ch, 'a' , 'z') || is_between(ch, 'A', 'Z') || is_between(ch, '0', '9') ||
          ch == '_' || ch == '$');
      return length;
  }
  
  SkScriptEngine::SkScriptEngine(SkOpType returnType) :
      fTokenLength(0), fReturnType(returnType), fError(kNoError)
  {
      SkSuppress noInitialSuppress;
      noInitialSuppress.fOperator = kUnassigned;
      noInitialSuppress.fOpStackDepth = 0;
      noInitialSuppress.fSuppress = false;
      noInitialSuppress.fElse = 0;
      fSuppressStack.push(noInitialSuppress);
      *fOpStack.push() = kParen;
      fTrackArray.appendClear();
      fTrackString.appendClear();
  }
  
  SkScriptEngine::~SkScriptEngine() {
      for (SkString** stringPtr = fTrackString.begin(); stringPtr < fTrackString.end(); stringPtr++)
          delete *stringPtr;
      for (SkTypedArray** arrayPtr = fTrackArray.begin(); arrayPtr < fTrackArray.end(); arrayPtr++)
          delete *arrayPtr;
  }
  
  int SkScriptEngine::arithmeticOp(char ch, char nextChar, bool lastPush) {
      SkOp op = kUnassigned;
      bool reverseOperands = false;
      bool negateResult = false;
      int advance = 1;
      switch (ch) {
          case '+':
              // !!! ignoring unary plus as implemented here has the side effect of
              // suppressing errors like +"hi"
              if (lastPush == false)  // unary plus, don't push an operator
                  goto returnAdv;
              op = kAdd;
              break;
          case '-':
              op = lastPush ? kSubtract : kMinus;
              break;
          case '*':
              op = kMultiply;
              break;
          case '/':
              op = kDivide;
              break;
          case '>':
              if (nextChar == '>') {
                  op = kShiftRight;
                  goto twoChar;
              }
              op = kGreaterEqual;
              if (nextChar == '=')
                  goto twoChar;
              reverseOperands = negateResult = true;
              break;
          case '<':
              if (nextChar == '<') {
                  op = kShiftLeft;
                  goto twoChar;
              }
              op = kGreaterEqual;
              reverseOperands = nextChar == '=';
              negateResult = ! reverseOperands;
              advance += reverseOperands;
              break;
          case '=':
              if (nextChar == '=') {
                  op = kEqual;
                  goto twoChar;
              }
              break;
          case '!':
              if (nextChar == '=') {
                  op = kEqual;
                  negateResult = true;
  twoChar:
                  advance++;
                  break;
              }
              op = kLogicalNot;
              break;
          case '?':
              op = kIf;
              break;
          case ':':
              op = kElse;
              break;
          case '^':
              op = kXor;
              break;
          case '(':
              *fOpStack.push() = kParen;  // push even if eval is suppressed
              goto returnAdv;
          case '&':
              SkASSERT(nextChar != '&');
              op = kBitAnd;
              break;
          case '|':
              SkASSERT(nextChar != '|');
              op = kBitOr;
              break;
          case '%':
              op = kModulo;
              break;
          case '~':
              op = kBitNot;
              break;
      }
      if (op == kUnassigned)
          return 0;
      if (fSuppressStack.top().fSuppress == false) {
          signed char precedence = gPrecedence[op];
          do {
              int idx = 0;
              SkOp compare;
              do {
                  compare = fOpStack.index(idx);
                  if ((compare & kArtificialOp) == 0)
                      break;
                  idx++;
              } while (true);
              signed char topPrecedence = gPrecedence[compare];
              SkASSERT(topPrecedence != -1);
              if (topPrecedence > precedence || (topPrecedence == precedence &&
                      gOpAttributes[op].fLeftType == kNoType)) {
                  break;
              }
              if (processOp() == false)
                  return 0;   // error
          } while (true);
          if (negateResult)
              *fOpStack.push() = (SkOp) (kLogicalNot | kArtificialOp);
          fOpStack.push(op);
          if (reverseOperands)
              *fOpStack.push() = (SkOp) (kFlipOps | kArtificialOp);
      }
  returnAdv:
      return advance;
  }
  
  void SkScriptEngine::boxCallBack(_boxCallBack func, void* userStorage) {
      UserCallBack callBack;
      callBack.fBoxCallBack = func;
      commonCallBack(kBox, callBack, userStorage);
  }
  
  void SkScriptEngine::commonCallBack(CallBackType type, UserCallBack& callBack, void* userStorage) {
      callBack.fCallBackType = type;
      callBack.fUserStorage = userStorage;
      *fUserCallBacks.prepend() = callBack;
  }
  
  bool SkScriptEngine::convertParams(SkTDArray<SkScriptValue>& params,
          const SkFunctionParamType* paramTypes, int paramCount) {
      if (params.count() > paramCount) {
          fError = kTooManyParameters;
          return false;   // too many parameters passed
      }
      for (int index = 0; index < params.count(); index++) {
          if (convertTo((SkDisplayTypes) paramTypes[index], &params[index]) == false)
              return false;
      }
      return true;
  }
  
  bool SkScriptEngine::convertTo(SkDisplayTypes toType, SkScriptValue* value ) {
      SkDisplayTypes type = value->fType;
      if (type == toType)
          return true;
      if (ToOpType(type) == kObject) {
  #if 0   // !!! I want object->string to get string from displaystringtype, not id
          if (ToOpType(toType) == kString) {
              bool success = handleObjectToString(value->fOperand.fObject);
              if (success == false)
                  return false;
              SkOpType type;
              fTypeStack.pop(&type);
              value->fType = ToDisplayType(type);
              fOperandStack.pop(&value->fOperand);
              return true;
          }
  #endif
          if (handleUnbox(value) == false) {
              fError = kHandleUnboxFailed;
              return false;
          }
          return convertTo(toType, value);
      }
      return ConvertTo(this, toType, value);
  }
  
  bool SkScriptEngine::evaluateDot(const char*& script, bool suppressed) {
      size_t fieldLength = token_length(++script);        // skip dot
      if (fieldLength == 0) {
          fError = kExpectedFieldName;
          return false;
      }
      const char* field = script;
      script += fieldLength;
      bool success = handleProperty(suppressed);
      if (success == false) {
          fError = kCouldNotFindReferencedID; // note: never generated by standard animator plugins
          return false;
      }
      return evaluateDotParam(script, suppressed, field, fieldLength);
  }
  
  bool SkScriptEngine::evaluateDotParam(const char*& script, bool suppressed,
          const char* field, size_t fieldLength) {
      void* object;
      if (suppressed)
          object = NULL;
      else {
          if (fTypeStack.top() != kObject) {
              fError = kDotOperatorExpectsObject;
              return false;
          }
          object = fOperandStack.top().fObject;
          fTypeStack.pop();
          fOperandStack.pop();
      }
      char ch; // see if it is a simple member or a function
      while (is_ws(ch = script[0]))
          script++;
      bool success = true;
      if (ch != '(') {
              if (suppressed == false) {
                  if ((success = handleMember(field, fieldLength, object)) == false)
                      fError = kHandleMemberFailed;
              }
      } else {
          SkTDArray<SkScriptValue> params;
          *fBraceStack.push() = kFunctionBrace;
          success = functionParams(&script, params);
          if (success && suppressed == false &&
                  (success = handleMemberFunction(field, fieldLength, object, params)) == false)
              fError = kHandleMemberFunctionFailed;
      }
      return success;
  }
  
  bool SkScriptEngine::evaluateScript(const char** scriptPtr, SkScriptValue* value) {
  #ifdef SK_DEBUG
      const char** original = scriptPtr;
  #endif
      bool success;
      const char* inner;
      if (strncmp(*scriptPtr, "#script:", sizeof("#script:") - 1) == 0) {
          *scriptPtr += sizeof("#script:") - 1;
          if (fReturnType == kNoType || fReturnType == kString) {
              success = innerScript(scriptPtr, value);
              if (success == false)
                  goto end;
              inner = value->fOperand.fString->c_str();
              scriptPtr = &inner;
          }
      }
      {
          success = innerScript(scriptPtr, value);
          if (success == false)
              goto end;
          const char* script = *scriptPtr;
          char ch;
          while (is_ws(ch = script[0]))
              script++;
          if (ch != '\0') {
              // error may trigger on scripts like "50,0" that were intended to be written as "[50, 0]"
              fError = kPrematureEnd;
              success = false;
          }
      }
  end:
  #ifdef SK_DEBUG
      if (success == false) {
          SkDebugf("script failed: %s", *original);
          if (fError)
              SkDebugf(" %s", errorStrings[fError - 1]);
          SkDebugf("\n");
      }
  #endif
      return success;
  }
  
  void SkScriptEngine::forget(SkTypedArray* array) {
      if (array->getType() == SkType_String) {
          for (int index = 0; index < array->count(); index++) {
              SkString* string = (*array)[index].fString;
              int found = fTrackString.find(string);
              if (found >= 0)
                  fTrackString.remove(found);
          }
          return;
      }
      if (array->getType() == SkType_Array) {
          for (int index = 0; index < array->count(); index++) {
              SkTypedArray* child = (*array)[index].fArray;
              forget(child);  // forgets children of child
              int found = fTrackArray.find(child);
              if (found >= 0)
                  fTrackArray.remove(found);
          }
      }
  }
  
  void SkScriptEngine::functionCallBack(_functionCallBack func, void* userStorage) {
      UserCallBack callBack;
      callBack.fFunctionCallBack = func;
      commonCallBack(kFunction, callBack, userStorage);
  }
  
  bool SkScriptEngine::functionParams(const char** scriptPtr, SkTDArray<SkScriptValue>& params) {
      (*scriptPtr)++; // skip open paren
      *fOpStack.push() = kParen;
      *fBraceStack.push() = kFunctionBrace;
      SkBool suppressed = fSuppressStack.top().fSuppress;
      do {
          SkScriptValue value;
          bool success = innerScript(scriptPtr, suppressed ? NULL : &value);
          if (success == false) {
              fError = kErrorInFunctionParameters;
              return false;
          }
          if (suppressed)
              continue;
          *params.append() = value;
      } while ((*scriptPtr)[-1] == ',');
      fBraceStack.pop();
      fOpStack.pop(); // pop paren
      (*scriptPtr)++; // advance beyond close paren
      return true;
  }
  
  #ifdef SK_DEBUG
  bool SkScriptEngine::getErrorString(SkString* str) const {
      if (fError)
          str->set(errorStrings[fError - 1]);
      return fError != 0;
  }
  #endif
  
  bool SkScriptEngine::innerScript(const char** scriptPtr, SkScriptValue* value) {
      const char* script = *scriptPtr;
      char ch;
      bool lastPush = false;
      bool success = true;
      int opBalance = fOpStack.count();
      int baseBrace = fBraceStack.count();
      int suppressBalance = fSuppressStack.count();
      while ((ch = script[0]) != '\0') {
          if (is_ws(ch)) {
              script++;
              continue;
          }
          SkBool suppressed = fSuppressStack.top().fSuppress;
          SkOperand operand;
          const char* dotCheck;
          if (fBraceStack.count() > baseBrace) {
  #if 0   // disable support for struct brace
              if (ch == ':') {
                  SkASSERT(fTokenLength > 0);
                  SkASSERT(fBraceStack.top() == kStructBrace);
                  ++script;
                  SkASSERT(fDisplayable);
                  SkString token(fToken, fTokenLength);
                  fTokenLength = 0;
                  const char* tokenName = token.c_str();
                  const SkMemberInfo* tokenInfo SK_INIT_TO_AVOID_WARNING;
                  if (suppressed == false) {
                      SkDisplayTypes type = fInfo->getType();
                      tokenInfo = SkDisplayType::GetMember(type, &tokenName);
                      SkASSERT(tokenInfo);
                  }
                  SkScriptValue tokenValue;
                  success = innerScript(&script, &tokenValue);    // terminate and return on comma, close brace
                  SkASSERT(success);
                  if (suppressed == false) {
                      if (tokenValue.fType == SkType_Displayable) {
                          SkASSERT(SkDisplayType::IsDisplayable(tokenInfo->getType()));
                          fDisplayable->setReference(tokenInfo, tokenValue.fOperand.fDisplayable);
                      } else {
                          if (tokenValue.fType != tokenInfo->getType()) {
                              if (convertTo(tokenInfo->getType(), &tokenValue) == false)
                                  return false;
                          }
                          tokenInfo->writeValue(fDisplayable, NULL, 0, 0,
                              (void*) ((char*) fInfo->memberData(fDisplayable) + tokenInfo->fOffset + fArrayOffset),
                              tokenInfo->getType(), tokenValue);
                      }
                  }
                  lastPush = false;
                  continue;
              } else
  #endif
              if (fBraceStack.top() == kArrayBrace) {
                  SkScriptValue tokenValue;
                  success = innerScript(&script, &tokenValue);    // terminate and return on comma, close brace
                  if (success == false) {
                      fError = kErrorInArrrayIndex;
                      return false;
                  }
                  if (suppressed == false) {
  #if 0 // no support for structures for now
                      if (tokenValue.fType == SkType_Structure) {
                          fArrayOffset += (int) fInfo->getSize(fDisplayable);
                      } else
  #endif
                      {
                          SkDisplayTypes type = ToDisplayType(fReturnType);
                          if (fReturnType == kNoType) {
                              // !!! short sighted; in the future, allow each returned array component to carry
                              // its own type, and let caller do any needed conversions
                              if (value->fOperand.fArray->count() == 0)
                                  value->fOperand.fArray->setType(type = tokenValue.fType);
                              else
                                  type = value->fOperand.fArray->getType();
                          }
                          if (tokenValue.fType != type) {
                              if (convertTo(type, &tokenValue) == false)
                                  return false;
                          }
                          *value->fOperand.fArray->append() = tokenValue.fOperand;
                      }
                  }
                  lastPush = false;
                  continue;
              } else {
                  if (token_length(script) == 0) {
                      fError = kExpectedToken;
                      return false;
                  }
              }
          }
          if (lastPush != false && fTokenLength > 0) {
              if (ch == '(') {
                  *fBraceStack.push() = kFunctionBrace;
                  if (handleFunction(&script, SkToBool(suppressed)) == false)
                      return false;
                  lastPush = true;
                  continue;
              } else if (ch == '[') {
                  if (handleProperty(SkToBool(suppressed)) == false)
                      return false;   // note: never triggered by standard animator plugins
                  if (handleArrayIndexer(&script, SkToBool(suppressed)) == false)
                      return false;
                  lastPush = true;
                  continue;
              } else if (ch != '.') {
                  if (handleProperty(SkToBool(suppressed)) == false)
                      return false;   // note: never triggered by standard animator plugins
                  lastPush = true;
                  continue;
              }
          }
          if (ch == '0' && (script[1] & ~0x20) == 'X') {
              if (lastPush != false) {
                  fError = kExpectedOperator;
                  return false;
              }
              script += 2;
              script = SkParse::FindHex(script, (uint32_t*)&operand.fS32);
              if (script == NULL) {
                  fError = kExpectedHex;
                  return false;
              }
              goto intCommon;
          }
          if (lastPush == false && ch == '.')
              goto scalarCommon;
          if (ch >= '0' && ch <= '9') {
              if (lastPush != false) {
                  fError = kExpectedOperator;
                  return false;
              }
              dotCheck = SkParse::FindS32(script, &operand.fS32);
              if (dotCheck[0] != '.') {
                  script = dotCheck;
  intCommon:
                  if (suppressed == false)
                      *fTypeStack.push() = kInt;
              } else {
  scalarCommon:
                  script = SkParse::FindScalar(script, &operand.fScalar);
                  if (suppressed == false)
                      *fTypeStack.push() = kScalar;
              }
              if (suppressed == false)
                  fOperandStack.push(operand);
              lastPush = true;
              continue;
          }
          int length = token_length(script);
          if (length > 0) {
              if (lastPush != false) {
                  fError = kExpectedOperator;
                  return false;
              }
              fToken = script;
              fTokenLength = length;
              script += length;
              lastPush = true;
              continue;
          }
          char startQuote = ch;
          if (startQuote == '\'' || startQuote == '\"') {
              if (lastPush != false) {
                  fError = kExpectedOperator;
                  return false;
              }
              operand.fString = new SkString();
              track(operand.fString);
              ++script;
  
              // <mrr> this is a lot of calls to append() one char at at time
              // how hard to preflight script so we know how much to grow fString by?
              do {
                  if (script[0] == '\\')
                      ++script;
                  operand.fString->append(script, 1);
                  ++script;
                  if (script[0] == '\0') {
                      fError = kUnterminatedString;
                      return false;
                  }
              } while (script[0] != startQuote);
              ++script;
              if (suppressed == false) {
                  *fTypeStack.push() = kString;
                  fOperandStack.push(operand);
              }
              lastPush = true;
              continue;
          }
          ;
          if (ch ==  '.') {
              if (fTokenLength == 0) {
                  SkScriptValue scriptValue;
                  SkDEBUGCODE(scriptValue.fOperand.fObject = NULL);
                  int tokenLength = token_length(++script);
                  const char* token = script;
                  script += tokenLength;
                  if (suppressed == false) {
                      if (fTypeStack.count() == 0) {
                          fError = kExpectedTokenBeforeDotOperator;
                          return false;
                      }
                      SkOpType topType;
                      fTypeStack.pop(&topType);
                      fOperandStack.pop(&scriptValue.fOperand);
                      scriptValue.fType = ToDisplayType(topType);
                      handleBox(&scriptValue);
                  }
                  success = evaluateDotParam(script, SkToBool(suppressed), token, tokenLength);
                  if (success == false)
                      return false;
                  lastPush = true;
                  continue;
              }
              // get next token, and evaluate immediately
              success = evaluateDot(script, SkToBool(suppressed));
              if (success == false)
                  return false;
              lastPush = true;
              continue;
          }
          if (ch == '[') {
              if (lastPush == false) {
                  script++;
                  *fBraceStack.push() = kArrayBrace;
                  if (suppressed)
                      continue;
                  operand.fArray = value->fOperand.fArray = new SkTypedArray(ToDisplayType(fReturnType));
                  track(value->fOperand.fArray);
                  *fTypeStack.push() = (SkOpType) kArray;
                  fOperandStack.push(operand);
                  continue;
              }
              if (handleArrayIndexer(&script, SkToBool(suppressed)) == false)
                  return false;
              lastPush = true;
              continue;
          }
  #if 0 // structs not supported for now
          if (ch == '{') {
              if (lastPush == false) {
                  script++;
                  *fBraceStack.push() = kStructBrace;
                  if (suppressed)
                      continue;
                  operand.fS32 = 0;
                  *fTypeStack.push() = (SkOpType) kStruct;
                  fOperandStack.push(operand);
                  continue;
              }
              SkASSERT(0); // braces in other contexts aren't supported yet
          }
  #endif
          if (ch == ')' && fBraceStack.count() > 0) {
              SkBraceStyle braceStyle = fBraceStack.top();
              if (braceStyle == kFunctionBrace) {
                  fBraceStack.pop();
                  break;
              }
          }
          if (ch == ',' || ch == ']') {
              if (ch != ',') {
                  SkBraceStyle match;
                  fBraceStack.pop(&match);
                  if (match != kArrayBrace) {
                      fError = kMismatchedArrayBrace;
                      return false;
                  }
              }
              script++;
              // !!! see if brace or bracket is correct closer
              break;
          }
          char nextChar = script[1];
          int advance = logicalOp(ch, nextChar);
          if (advance < 0)     // error
              return false;
          if (advance == 0)
              advance = arithmeticOp(ch, nextChar, lastPush);
          if (advance == 0) // unknown token
              return false;
          if (advance > 0)
              script += advance;
          lastPush = ch == ']' || ch == ')';
      }
      bool suppressed = SkToBool(fSuppressStack.top().fSuppress);
      if (fTokenLength > 0) {
          success = handleProperty(suppressed);
          if (success == false)
              return false;   // note: never triggered by standard animator plugins
      }
      while (fOpStack.count() > opBalance) {   // leave open paren
          if ((fError = opError()) != kNoError)
              return false;
          if (processOp() == false)
              return false;
      }
      SkOpType topType = fTypeStack.count() > 0 ? fTypeStack.top() : kNoType;
      if (suppressed == false && topType != fReturnType &&
              topType == kString && fReturnType != kNoType) { // if result is a string, give handle property a chance to convert it to the property value
          SkString* string = fOperandStack.top().fString;
          fToken = string->c_str();
          fTokenLength = string->size();
          fOperandStack.pop();
          fTypeStack.pop();
          success = handleProperty(SkToBool(fSuppressStack.top().fSuppress));
          if (success == false) { // if it couldn't convert, return string (error?)
              SkOperand operand;
              operand.fS32 = 0;
              *fTypeStack.push() = kString;
              operand.fString = string;
              fOperandStack.push(operand);
          }
      }
      if (value) {
          if (fOperandStack.count() == 0)
              return false;
          SkASSERT(fOperandStack.count() >= 1);
          SkASSERT(fTypeStack.count() >= 1);
          fOperandStack.pop(&value->fOperand);
          SkOpType type;
          fTypeStack.pop(&type);
          value->fType = ToDisplayType(type);
  //      SkASSERT(value->fType != SkType_Unknown);
          if (topType != fReturnType && topType == kObject && fReturnType != kNoType) {
              if (convertTo(ToDisplayType(fReturnType), value) == false)
                  return false;
          }
      }
      while (fSuppressStack.count() > suppressBalance)
          fSuppressStack.pop();
      *scriptPtr = script;
      return true; // no error
  }
  
  void SkScriptEngine::memberCallBack(_memberCallBack member , void* userStorage) {
      UserCallBack callBack;
      callBack.fMemberCallBack = member;
      commonCallBack(kMember, callBack, userStorage);
  }
  
  void SkScriptEngine::memberFunctionCallBack(_memberFunctionCallBack func, void* userStorage) {
      UserCallBack callBack;
      callBack.fMemberFunctionCallBack = func;
      commonCallBack(kMemberFunction, callBack, userStorage);
  }
  
  #if 0
  void SkScriptEngine::objectToStringCallBack(_objectToStringCallBack func, void* userStorage) {
      UserCallBack callBack;
      callBack.fObjectToStringCallBack = func;
      commonCallBack(kObjectToString, callBack, userStorage);
  }
  #endif
  
  bool SkScriptEngine::handleArrayIndexer(const char** scriptPtr, bool suppressed) {
      SkScriptValue scriptValue;
      (*scriptPtr)++;
      *fOpStack.push() = kParen;
      *fBraceStack.push() = kArrayBrace;
      SkOpType saveType = fReturnType;
      fReturnType = kInt;
      bool success = innerScript(scriptPtr, suppressed == false ? &scriptValue : NULL);
      if (success == false)
          return false;
      fReturnType = saveType;
      if (suppressed == false) {
          if (convertTo(SkType_Int, &scriptValue) == false)
              return false;
          int index = scriptValue.fOperand.fS32;
          SkScriptValue scriptValue;
          SkOpType type;
          fTypeStack.pop(&type);
          fOperandStack.pop(&scriptValue.fOperand);
          scriptValue.fType = ToDisplayType(type);
          if (type == kObject) {
              success = handleUnbox(&scriptValue);
              if (success == false)
                  return false;
              if (ToOpType(scriptValue.fType) != kArray) {
                  fError = kExpectedArray;
                  return false;
              }
          }
          *fTypeStack.push() = scriptValue.fOperand.fArray->getOpType();
  //      SkASSERT(index >= 0);
          if ((unsigned) index >= (unsigned) scriptValue.fOperand.fArray->count()) {
              fError = kArrayIndexOutOfBounds;
              return false;
          }
          scriptValue.fOperand = scriptValue.fOperand.fArray->begin()[index];
          fOperandStack.push(scriptValue.fOperand);
      }
      fOpStack.pop(); // pop paren
      return success;
  }
  
  bool SkScriptEngine::handleBox(SkScriptValue* scriptValue) {
      bool success = true;
      for (UserCallBack* callBack = fUserCallBacks.begin(); callBack < fUserCallBacks.end(); callBack++) {
          if (callBack->fCallBackType != kBox)
              continue;
          success = (*callBack->fBoxCallBack)(callBack->fUserStorage, scriptValue);
          if (success) {
              fOperandStack.push(scriptValue->fOperand);
              *fTypeStack.push() = ToOpType(scriptValue->fType);
              goto done;
          }
      }
  done:
      return success;
  }
  
  bool SkScriptEngine::handleFunction(const char** scriptPtr, bool suppressed) {
      SkScriptValue callbackResult;
      SkTDArray<SkScriptValue> params;
      SkString functionName(fToken, fTokenLength);
      fTokenLength = 0;
      bool success = functionParams(scriptPtr, params);
      if (success == false)
          goto done;
      if (suppressed == true)
          return true;
      {
          for (UserCallBack* callBack = fUserCallBacks.begin(); callBack < fUserCallBacks.end(); callBack++) {
              if (callBack->fCallBackType != kFunction)
                  continue;
              success = (*callBack->fFunctionCallBack)(functionName.c_str(), functionName.size(), params,
                  callBack->fUserStorage, &callbackResult);
              if (success) {
                  fOperandStack.push(callbackResult.fOperand);
                  *fTypeStack.push() = ToOpType(callbackResult.fType);
                  goto done;
              }
          }
      }
      fError = kNoFunctionHandlerFound;
      return false;
  done:
      return success;
  }
  
  bool SkScriptEngine::handleMember(const char* field, size_t len, void* object) {
      SkScriptValue callbackResult;
      bool success = true;
      for (UserCallBack* callBack = fUserCallBacks.begin(); callBack < fUserCallBacks.end(); callBack++) {
          if (callBack->fCallBackType != kMember)
              continue;
          success = (*callBack->fMemberCallBack)(field, len, object, callBack->fUserStorage, &callbackResult);
          if (success) {
              if (callbackResult.fType == SkType_String)
                  track(callbackResult.fOperand.fString);
              fOperandStack.push(callbackResult.fOperand);
              *fTypeStack.push() = ToOpType(callbackResult.fType);
              goto done;
          }
      }
      return false;
  done:
      return success;
  }
  
  bool SkScriptEngine::handleMemberFunction(const char* field, size_t len, void* object, SkTDArray<SkScriptValue>& params) {
      SkScriptValue callbackResult;
      bool success = true;
      for (UserCallBack* callBack = fUserCallBacks.begin(); callBack < fUserCallBacks.end(); callBack++) {
          if (callBack->fCallBackType != kMemberFunction)
              continue;
          success = (*callBack->fMemberFunctionCallBack)(field, len, object, params,
              callBack->fUserStorage, &callbackResult);
          if (success) {
              if (callbackResult.fType == SkType_String)
                  track(callbackResult.fOperand.fString);
              fOperandStack.push(callbackResult.fOperand);
              *fTypeStack.push() = ToOpType(callbackResult.fType);
              goto done;
          }
      }
      return false;
  done:
      return success;
  }
  
  #if 0
  bool SkScriptEngine::handleObjectToString(void* object) {
      SkScriptValue callbackResult;
      bool success = true;
      for (UserCallBack* callBack = fUserCallBacks.begin(); callBack < fUserCallBacks.end(); callBack++) {
          if (callBack->fCallBackType != kObjectToString)
              continue;
          success = (*callBack->fObjectToStringCallBack)(object,
              callBack->fUserStorage, &callbackResult);
          if (success) {
              if (callbackResult.fType == SkType_String)
                  track(callbackResult.fOperand.fString);
              fOperandStack.push(callbackResult.fOperand);
              *fTypeStack.push() = ToOpType(callbackResult.fType);
              goto done;
          }
      }
      return false;
  done:
      return success;
  }
  #endif
  
  bool SkScriptEngine::handleProperty(bool suppressed) {
      SkScriptValue callbackResult;
      bool success = true;
      if (suppressed)
          goto done;
      success = false; // note that with standard animator-script plugins, callback never returns false
      {
          for (UserCallBack* callBack = fUserCallBacks.begin(); callBack < fUserCallBacks.end(); callBack++) {
              if (callBack->fCallBackType != kProperty)
                  continue;
              success = (*callBack->fPropertyCallBack)(fToken, fTokenLength,
                  callBack->fUserStorage, &callbackResult);
              if (success) {
                  if (callbackResult.fType == SkType_String && callbackResult.fOperand.fString == NULL) {
                      callbackResult.fOperand.fString = new SkString(fToken, fTokenLength);
                      track(callbackResult.fOperand.fString);
                  }
                  fOperandStack.push(callbackResult.fOperand);
                  *fTypeStack.push() = ToOpType(callbackResult.fType);
                  goto done;
              }
          }
      }
  done:
      fTokenLength = 0;
      return success;
  }
  
  bool SkScriptEngine::handleUnbox(SkScriptValue* scriptValue) {
      bool success = true;
      for (UserCallBack* callBack = fUserCallBacks.begin(); callBack < fUserCallBacks.end(); callBack++) {
          if (callBack->fCallBackType != kUnbox)
              continue;
          success = (*callBack->fUnboxCallBack)(callBack->fUserStorage, scriptValue);
          if (success) {
              if (scriptValue->fType == SkType_String)
                  track(scriptValue->fOperand.fString);
              goto done;
          }
      }
      return false;
  done:
      return success;
  }
  
  // note that entire expression is treated as if it were enclosed in parens
  // an open paren is always the first thing in the op stack
  
  int SkScriptEngine::logicalOp(char ch, char nextChar) {
      int advance = 1;
      SkOp match;
      signed char precedence;
      switch (ch) {
          case ')':
              match = kParen;
              break;
          case ']':
              match = kArrayOp;
              break;
          case '?':
              match = kIf;
              break;
          case ':':
              match = kElse;
              break;
          case '&':
              if (nextChar != '&')
                  goto noMatch;
              match = kLogicalAnd;
              advance = 2;
              break;
          case '|':
              if (nextChar != '|')
                  goto noMatch;
              match = kLogicalOr;
              advance = 2;
              break;
          default:
  noMatch:
              return 0;
      }
      SkSuppress suppress;
      precedence = gPrecedence[match];
      if (fSuppressStack.top().fSuppress) {
          if (fSuppressStack.top().fOpStackDepth < fOpStack.count()) {
              SkOp topOp = fOpStack.top();
              if (gPrecedence[topOp] <= precedence)
                  fOpStack.pop();
              goto goHome;
          }
          bool changedPrecedence = gPrecedence[fSuppressStack.top().fOperator] < precedence;
          if (changedPrecedence)
              fSuppressStack.pop();
          if (precedence == kIfElsePrecedence) {
              if (match == kIf) {
                  if (changedPrecedence)
                      fOpStack.pop();
                  else
                      *fOpStack.push() = kIf;
              } else {
                  if (fSuppressStack.top().fOpStackDepth == fOpStack.count()) {
                      goto flipSuppress;
                  }
                  fOpStack.pop();
              }
          }
          if (changedPrecedence == false)
              goto goHome;
      }
      while (gPrecedence[fOpStack.top() & ~kArtificialOp] < precedence) {
          if (processOp() == false)
              return false;
      }
      if (fSuppressStack.top().fOpStackDepth > fOpStack.count())
          fSuppressStack.pop();
      switch (match) {
          case kParen:
          case kArrayOp:
              if (fOpStack.count() <= 1 || fOpStack.top() != match) {
                  fError = kMismatchedBrackets;
                  return -1;
              }
              if (match == kParen)
                  fOpStack.pop();
              else {
                  SkOpType indexType;
                  fTypeStack.pop(&indexType);
                  if (indexType != kInt && indexType != kScalar) {
                      fError = kExpectedNumberForArrayIndex; // (although, could permit strings eventually)
                      return -1;
                  }
                  SkOperand indexOperand;
                  fOperandStack.pop(&indexOperand);
                  int index = indexType == kScalar ? SkScalarFloor(indexOperand.fScalar) :
                      indexOperand.fS32;
                  SkOpType arrayType;
                  fTypeStack.pop(&arrayType);
                  if ((unsigned)arrayType != (unsigned)kArray) {
                      fError = kExpectedArray;
                      return -1;
                  }
                  SkOperand arrayOperand;
                  fOperandStack.pop(&arrayOperand);
                  SkTypedArray* array = arrayOperand.fArray;
                  SkOperand operand;
                  if (array->getIndex(index, &operand) == false) {
                      fError = kIndexOutOfRange;
                      return -1;
                  }
                  SkOpType resultType = array->getOpType();
                  fTypeStack.push(resultType);
                  fOperandStack.push(operand);
              }
              break;
          case kIf: {
              SkScriptValue ifValue;
              SkOpType ifType;
              fTypeStack.pop(&ifType);
              ifValue.fType = ToDisplayType(ifType);
              fOperandStack.pop(&ifValue.fOperand);
              if (convertTo(SkType_Int, &ifValue) == false)
                  return -1;
              if (ifValue.fType != SkType_Int) {
                  fError = kExpectedIntForConditionOperator;
                  return -1;
              }
              suppress.fSuppress = ifValue.fOperand.fS32 == 0;
              suppress.fOperator = kIf;
              suppress.fOpStackDepth = fOpStack.count();
              suppress.fElse = false;
              fSuppressStack.push(suppress);
              // if left is true, do only up to colon
              // if left is false, do only after colon
              } break;
          case kElse:
  flipSuppress:
              if (fSuppressStack.top().fElse)
                  fSuppressStack.pop();
              fSuppressStack.top().fElse = true;
              fSuppressStack.top().fSuppress ^= true;
              // flip last do / don't do consideration from last '?'
              break;
          case kLogicalAnd:
          case kLogicalOr: {
              if (fTypeStack.top() != kInt) {
                  fError = kExpectedBooleanExpression;
                  return -1;
              }
              int32_t topInt = fOperandStack.top().fS32;
              if (fOpStack.top() != kLogicalAnd)
                  *fOpStack.push() = kLogicalAnd; // really means 'to bool', and is appropriate for 'or'
              if (match == kLogicalOr ? topInt != 0 : topInt == 0) {
                  suppress.fSuppress = true;
                  suppress.fOperator = match;
                  suppress.fOpStackDepth = fOpStack.count();
                  suppress.fElse = false;
                  fSuppressStack.push(suppress);
              } else {
                  fTypeStack.pop();
                  fOperandStack.pop();
              }
          }   break;
          default:
              SkASSERT(0);
      }
  goHome:
      return advance;
  }
  
  SkScriptEngine::Error SkScriptEngine::opError() {
      int opCount = fOpStack.count();
      int operandCount = fOperandStack.count();
      if (opCount == 0) {
          if (operandCount != 1)
              return kExpectedOperator;
          return kNoError;
      }
      SkOp op = (SkOp) (fOpStack.top() & ~kArtificialOp);
      const SkOperatorAttributes* attributes = &gOpAttributes[op];
      if (attributes->fLeftType != kNoType && operandCount < 2)
          return kExpectedValue;
      if (attributes->fLeftType == kNoType && operandCount < 1)
          return kExpectedValue;
      return kNoError;
  }
  
  bool SkScriptEngine::processOp() {
      SkOp op;
      fOpStack.pop(&op);
      op = (SkOp) (op & ~kArtificialOp);
      const SkOperatorAttributes* attributes = &gOpAttributes[op];
      SkOpType type2;
      fTypeStack.pop(&type2);
      SkOpType type1 = type2;
      SkOperand operand2;
      fOperandStack.pop(&operand2);
      SkOperand operand1 = operand2; // !!! not really needed, suppresses warning
      if (attributes->fLeftType != kNoType) {
          fTypeStack.pop(&type1);
          fOperandStack.pop(&operand1);
          if (op == kFlipOps) {
              SkTSwap(type1, type2);
              SkTSwap(operand1, operand2);
              fOpStack.pop(&op);
              op = (SkOp) (op & ~kArtificialOp);
              attributes = &gOpAttributes[op];
          }
          if (type1 == kObject && (type1 & attributes->fLeftType) == 0) {
              SkScriptValue val;
              val.fType = ToDisplayType(type1);
              val.fOperand = operand1;
              bool success = handleUnbox(&val);
              if (success == false)
                  return false;
              type1 = ToOpType(val.fType);
              operand1 = val.fOperand;
          }
      }
      if (type2 == kObject && (type2 & attributes->fLeftType) == 0) {
          SkScriptValue val;
          val.fType = ToDisplayType(type2);
          val.fOperand = operand2;
          bool success = handleUnbox(&val);
          if (success == false)
              return false;
          type2 = ToOpType(val.fType);
          operand2 = val.fOperand;
      }
      if (attributes->fLeftType != kNoType) {
          if (type1 != type2) {
              if ((attributes->fLeftType & kString) && attributes->fBias & kTowardsString && ((type1 | type2) & kString)) {
                  if (type1 == kInt || type1 == kScalar) {
                      convertToString(operand1, type1 == kInt ? SkType_Int : SkType_Float);
                      type1 = kString;
                  }
                  if (type2 == kInt || type2 == kScalar) {
                      convertToString(operand2, type2 == kInt ? SkType_Int : SkType_Float);
                      type2 = kString;
                  }
              } else if (attributes->fLeftType & kScalar && ((type1 | type2) & kScalar)) {
                  if (type1 == kInt) {
                      operand1.fScalar = IntToScalar(operand1.fS32);
                      type1 = kScalar;
                  }
                  if (type2 == kInt) {
                      operand2.fScalar = IntToScalar(operand2.fS32);
                       type2 = kScalar;
                  }
              }
          }
          if ((type1 & attributes->fLeftType) == 0 || type1 != type2) {
              if (type1 == kString) {
                  const char* result = SkParse::FindScalar(operand1.fString->c_str(), &operand1.fScalar);
                  if (result == NULL) {
                      fError = kExpectedNumber;
                      return false;
                  }
                  type1 = kScalar;
              }
              if (type1 == kScalar && (attributes->fLeftType == kInt || type2 == kInt)) {
                  operand1.fS32 = SkScalarFloor(operand1.fScalar);
                  type1 = kInt;
              }
          }
      }
      if ((type2 & attributes->fRightType) == 0 || type1 != type2) {
          if (type2 == kString) {
              const char* result = SkParse::FindScalar(operand2.fString->c_str(), &operand2.fScalar);
              if (result == NULL) {
                  fError = kExpectedNumber;
                  return false;
              }
              type2 = kScalar;
          }
          if (type2 == kScalar && (attributes->fRightType == kInt || type1 == kInt)) {
              operand2.fS32 = SkScalarFloor(operand2.fScalar);
              type2 = kInt;
          }
      }
      if (type2 == kScalar)
          op = (SkOp) (op + 1);
      else if (type2 == kString)
          op = (SkOp) (op + 2);
      switch(op) {
          case kAddInt:
              operand2.fS32 += operand1.fS32;
              break;
          case kAddScalar:
              operand2.fScalar += operand1.fScalar;
              break;
          case kAddString:
              if (fTrackString.find(operand1.fString) < 0) {
                  operand1.fString = SkNEW_ARGS(SkString, (*operand1.fString));
                  track(operand1.fString);
              }
              operand1.fString->append(*operand2.fString);
              operand2 = operand1;
              break;
          case kBitAnd:
              operand2.fS32 &= operand1.fS32;
              break;
          case kBitNot:
              operand2.fS32 = ~operand2.fS32;
              break;
          case kBitOr:
              operand2.fS32 |= operand1.fS32;
              break;
          case kDivideInt:
              if (operand2.fS32 == 0) {
                  operand2.fS32 = operand1.fS32 == 0 ? SK_NaN32 : operand1.fS32 > 0 ? SK_MaxS32 : -SK_MaxS32;
                  break;
              } else {
                  int32_t original = operand2.fS32;
                  operand2.fS32 = operand1.fS32 / operand2.fS32;
                  if (original * operand2.fS32 == operand1.fS32)
                      break;    // integer divide was good enough
                  operand2.fS32 = original;
                  type2 = kScalar;
              }
          case kDivideScalar:
              if (operand2.fScalar == 0)
                  operand2.fScalar = operand1.fScalar == 0 ? SK_ScalarNaN : operand1.fScalar > 0 ? SK_ScalarMax : -SK_ScalarMax;
              else
                  operand2.fScalar = SkScalarDiv(operand1.fScalar, operand2.fScalar);
              break;
          case kEqualInt:
              operand2.fS32 = operand1.fS32 == operand2.fS32;
              break;
          case kEqualScalar:
              operand2.fS32 = operand1.fScalar == operand2.fScalar;
              type2 = kInt;
              break;
          case kEqualString:
              operand2.fS32 = *operand1.fString == *operand2.fString;
              type2 = kInt;
              break;
          case kGreaterEqualInt:
              operand2.fS32 = operand1.fS32 >= operand2.fS32;
              break;
          case kGreaterEqualScalar:
              operand2.fS32 = operand1.fScalar >= operand2.fScalar;
              type2 = kInt;
              break;
          case kGreaterEqualString:
              operand2.fS32 = strcmp(operand1.fString->c_str(), operand2.fString->c_str()) >= 0;
              type2 = kInt;
              break;
          case kLogicalAnd:
              operand2.fS32 = !! operand2.fS32;   // really, ToBool
              break;
          case kLogicalNot:
              operand2.fS32 = ! operand2.fS32;
              break;
          case kLogicalOr:
              SkASSERT(0);    // should have already been processed
              break;
          case kMinusInt:
              operand2.fS32 = -operand2.fS32;
              break;
          case kMinusScalar:
              operand2.fScalar = -operand2.fScalar;
              break;
          case kModuloInt:
              operand2.fS32 = operand1.fS32 % operand2.fS32;
              break;
          case kModuloScalar:
              operand2.fScalar = SkScalarMod(operand1.fScalar, operand2.fScalar);
              break;
          case kMultiplyInt:
              operand2.fS32 *= operand1.fS32;
              break;
          case kMultiplyScalar:
              operand2.fScalar = SkScalarMul(operand1.fScalar, operand2.fScalar);
              break;
          case kShiftLeft:
              operand2.fS32 = operand1.fS32 << operand2.fS32;
              break;
          case kShiftRight:
              operand2.fS32 = operand1.fS32 >> operand2.fS32;
              break;
          case kSubtractInt:
              operand2.fS32 = operand1.fS32 - operand2.fS32;
              break;
          case kSubtractScalar:
              operand2.fScalar = operand1.fScalar - operand2.fScalar;
              break;
          case kXor:
              operand2.fS32 ^= operand1.fS32;
              break;
          default:
              SkASSERT(0);
      }
      fTypeStack.push(type2);
      fOperandStack.push(operand2);
      return true;
  }
  
  void SkScriptEngine::propertyCallBack(_propertyCallBack prop, void* userStorage) {
      UserCallBack callBack;
      callBack.fPropertyCallBack = prop;
      commonCallBack(kProperty, callBack, userStorage);
  }
  
  void SkScriptEngine::track(SkTypedArray* array) {
      SkASSERT(fTrackArray.find(array) < 0);
      *(fTrackArray.end() - 1) = array;
      fTrackArray.appendClear();
  }
  
  void SkScriptEngine::track(SkString* string) {
      SkASSERT(fTrackString.find(string) < 0);
      *(fTrackString.end() - 1) = string;
      fTrackString.appendClear();
  }
  
  void SkScriptEngine::unboxCallBack(_unboxCallBack func, void* userStorage) {
      UserCallBack callBack;
      callBack.fUnboxCallBack = func;
      commonCallBack(kUnbox, callBack, userStorage);
  }
  
  bool SkScriptEngine::ConvertTo(SkScriptEngine* engine, SkDisplayTypes toType, SkScriptValue* value ) {
      SkASSERT(value);
      if (SkDisplayType::IsEnum(NULL /* fMaker */, toType))
          toType = SkType_Int;
      if (toType == SkType_Point || toType == SkType_3D_Point)
          toType = SkType_Float;
      if (toType == SkType_Drawable)
          toType = SkType_Displayable;
      SkDisplayTypes type = value->fType;
      if (type == toType)
          return true;
      SkOperand& operand = value->fOperand;
      bool success = true;
      switch (toType) {
          case SkType_Int:
              if (type == SkType_Boolean)
                  break;
              if (type == SkType_Float)
                  operand.fS32 = SkScalarFloor(operand.fScalar);
              else {
                  if (type != SkType_String) {
                      success = false;
                      break; // error
                  }
                  success = SkParse::FindS32(operand.fString->c_str(), &operand.fS32) != NULL;
              }
              break;
          case SkType_Float:
              if (type == SkType_Int) {
                  if (operand.fS32 == SK_NaN32)
                      operand.fScalar = SK_ScalarNaN;
                  else if (SkAbs32(operand.fS32) == SK_MaxS32)
                      operand.fScalar = SkSign32(operand.fS32) * SK_ScalarMax;
                  else
                      operand.fScalar = SkIntToScalar(operand.fS32);
              } else {
                  if (type != SkType_String) {
                      success = false;
                      break; // error
                  }
                  success = SkParse::FindScalar(operand.fString->c_str(), &operand.fScalar) != NULL;
              }
              break;
          case SkType_String: {
              SkString* strPtr = new SkString();
              SkASSERT(engine);
              engine->track(strPtr);
              if (type == SkType_Int) {
                  strPtr->appendS32(operand.fS32);
              } else if (type == SkType_Displayable) {
                  SkASSERT(0); // must call through instance version instead of static version
              } else {
                  if (type != SkType_Float) {
                      success = false;
                      break;
                  }
                  strPtr->appendScalar(operand.fScalar);
              }
              operand.fString = strPtr;
              } break;
          case SkType_Array: {
              SkTypedArray* array = new SkTypedArray(type);
              *array->append() = operand;
              engine->track(array);
              operand.fArray = array;
              } break;
          default:
              SkASSERT(0);
      }
      value->fType = toType;
      if (success == false)
          engine->fError = kTypeConversionFailed;
      return success;
  }
  
  SkScalar SkScriptEngine::IntToScalar(int32_t s32) {
      SkScalar scalar;
      if (s32 == SK_NaN32)
          scalar = SK_ScalarNaN;
      else if (SkAbs32(s32) == SK_MaxS32)
          scalar = SkSign32(s32) * SK_ScalarMax;
      else
          scalar = SkIntToScalar(s32);
      return scalar;
  }
  
  SkDisplayTypes SkScriptEngine::ToDisplayType(SkOpType type) {
      int val = type;
      switch (val) {
          case kNoType:
              return SkType_Unknown;
          case kInt:
              return SkType_Int;
          case kScalar:
              return SkType_Float;
          case kString:
              return SkType_String;
          case kArray:
              return SkType_Array;
          case kObject:
              return SkType_Displayable;
  //      case kStruct:
  //          return SkType_Structure;
          default:
              SkASSERT(0);
              return SkType_Unknown;
      }
  }
  
  SkScriptEngine::SkOpType SkScriptEngine::ToOpType(SkDisplayTypes type) {
      if (SkDisplayType::IsDisplayable(NULL /* fMaker */, type))
          return (SkOpType) kObject;
      if (SkDisplayType::IsEnum(NULL /* fMaker */, type))
          return kInt;
      switch (type) {
          case SkType_ARGB:
          case SkType_MSec:
          case SkType_Int:
              return kInt;
          case SkType_Float:
          case SkType_Point:
          case SkType_3D_Point:
              return kScalar;
          case SkType_Base64:
          case SkType_DynamicString:
          case SkType_String:
              return kString;
          case SkType_Array:
              return (SkOpType) kArray;
          case SkType_Unknown:
              return kNoType;
          default:
              SkASSERT(0);
              return kNoType;
      }
  }
  
  bool SkScriptEngine::ValueToString(SkScriptValue value, SkString* string) {
      switch (value.fType) {
          case kInt:
              string->reset();
              string->appendS32(value.fOperand.fS32);
              break;
          case kScalar:
              string->reset();
              string->appendScalar(value.fOperand.fScalar);
              break;
          case kString:
              string->set(*value.fOperand.fString);
              break;
          default:
              SkASSERT(0);
              return false;
      }
      return true; // no error
  }
  
  #ifdef SK_SUPPORT_UNITTEST
  
  #include "SkFloatingPoint.h"
  
  #define DEF_SCALAR_ANSWER   0
  #define DEF_STRING_ANSWER   NULL
  
  #define testInt(expression) { #expression, SkType_Int, expression, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER }
  #ifdef SK_SCALAR_IS_FLOAT
      #define testScalar(expression) { #expression, SkType_Float, 0, (float) expression, DEF_STRING_ANSWER }
      #define testRemainder(exp1, exp2) { #exp1 "%" #exp2, SkType_Float, 0, sk_float_mod(exp1, exp2), DEF_STRING_ANSWER }
  #else
      #define testScalar(expression) { #expression, SkType_Float, 0, (int) ((expression) * 65536.0f), DEF_STRING_ANSWER }
      #define testRemainder(exp1, exp2) { #exp1 "%" #exp2, SkType_Float, 0, (int) (sk_float_mod(exp1, exp2)  * 65536.0f), DEF_STRING_ANSWER }
  #endif
  #define testTrue(expression) { #expression, SkType_Int, 1, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER }
  #define testFalse(expression) { #expression, SkType_Int, 0, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER }
  
  static const SkScriptNAnswer scriptTests[]  = {
      testInt(1>1/2),
      testInt((6+7)*8),
      testInt(0&&1?2:3),
      testInt(3*(4+5)),
      testScalar(1.0+2.0),
      testScalar(1.0+5),
      testScalar(3.0-1.0),
      testScalar(6-1.0),
      testScalar(- -5.5- -1.5),
      testScalar(2.5*6.),
      testScalar(0.5*4),
      testScalar(4.5/.5),
      testScalar(9.5/19),
      testRemainder(9.5, 0.5),
      testRemainder(9.,2),
      testRemainder(9,2.5),
      testRemainder(-9,2.5),
      testTrue(-9==-9.0),
      testTrue(-9.==-4.0-5),
      testTrue(-9.*1==-4-5),
      testFalse(-9!=-9.0),
      testFalse(-9.!=-4.0-5),
      testFalse(-9.*1!=-4-5),
      testInt(0x123),
      testInt(0XABC),
      testInt(0xdeadBEEF),
      {   "'123'+\"456\"", SkType_String, 0, 0, "123456" },
      {   "123+\"456\"", SkType_String, 0, 0, "123456" },
      {   "'123'+456", SkType_String, 0, 0, "123456" },
      {   "'123'|\"456\"", SkType_Int, 123|456, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER },
      {   "123|\"456\"", SkType_Int, 123|456, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER },
      {   "'123'|456", SkType_Int, 123|456, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER },
      {   "'2'<11", SkType_Int, 1, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER },
      {   "2<'11'", SkType_Int, 1, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER },
      {   "'2'<'11'", SkType_Int, 0, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER },
      testInt(123),
      testInt(-345),
      testInt(+678),
      testInt(1+2+3),
      testInt(3*4+5),
      testInt(6+7*8),
      testInt(-1-2-8/4),
      testInt(-9%4),
      testInt(9%-4),
      testInt(-9%-4),
      testInt(123|978),
      testInt(123&978),
      testInt(123^978),
      testInt(2<<4),
      testInt(99>>3),
      testInt(~55),
      testInt(~~55),
      testInt(!55),
      testInt(!!55),
      // both int
      testInt(2<2),
      testInt(2<11),
      testInt(20<11),
      testInt(2<=2),
      testInt(2<=11),
      testInt(20<=11),
      testInt(2>2),
      testInt(2>11),
      testInt(20>11),
      testInt(2>=2),
      testInt(2>=11),
      testInt(20>=11),
      testInt(2==2),
      testInt(2==11),
      testInt(20==11),
      testInt(2!=2),
      testInt(2!=11),
      testInt(20!=11),
      // left int, right scalar
      testInt(2<2.),
      testInt(2<11.),
      testInt(20<11.),
      testInt(2<=2.),
      testInt(2<=11.),
      testInt(20<=11.),
      testInt(2>2.),
      testInt(2>11.),
      testInt(20>11.),
      testInt(2>=2.),
      testInt(2>=11.),
      testInt(20>=11.),
      testInt(2==2.),
      testInt(2==11.),
      testInt(20==11.),
      testInt(2!=2.),
      testInt(2!=11.),
      testInt(20!=11.),
      // left scalar, right int
          testInt(2.<2),
      testInt(2.<11),
      testInt(20.<11),
      testInt(2.<=2),
      testInt(2.<=11),
      testInt(20.<=11),
      testInt(2.>2),
      testInt(2.>11),
      testInt(20.>11),
      testInt(2.>=2),
      testInt(2.>=11),
      testInt(20.>=11),
      testInt(2.==2),
      testInt(2.==11),
      testInt(20.==11),
      testInt(2.!=2),
      testInt(2.!=11),
      testInt(20.!=11),
      // both scalar
      testInt(2.<11.),
      testInt(20.<11.),
      testInt(2.<=2.),
      testInt(2.<=11.),
      testInt(20.<=11.),
      testInt(2.>2.),
      testInt(2.>11.),
      testInt(20.>11.),
      testInt(2.>=2.),
      testInt(2.>=11.),
      testInt(20.>=11.),
      testInt(2.==2.),
      testInt(2.==11.),
      testInt(20.==11.),
      testInt(2.!=2.),
      testInt(2.!=11.),
      testInt(20.!=11.),
      // int, string (string is int)
      testFalse(2<'2'),
      testTrue(2<'11'),
      testFalse(20<'11'),
      testTrue(2<='2'),
      testTrue(2<='11'),
      testFalse(20<='11'),
      testFalse(2>'2'),
      testFalse(2>'11'),
      testTrue(20>'11'),
      testTrue(2>='2'),
      testFalse(2>='11'),
      testTrue(20>='11'),
      testTrue(2=='2'),
      testFalse(2=='11'),
      testFalse(2!='2'),
      testTrue(2!='11'),
      // int, string (string is scalar)
      testFalse(2<'2.'),
      testTrue(2<'11.'),
      testFalse(20<'11.'),
      testTrue(2=='2.'),
      testFalse(2=='11.'),
      // scalar, string
      testFalse(2.<'2.'),
      testTrue(2.<'11.'),
      testFalse(20.<'11.'),
      testTrue(2.=='2.'),
      testFalse(2.=='11.'),
      // string, int
      testFalse('2'<2),
      testTrue('2'<11),
      testFalse('20'<11),
      testTrue('2'==2),
      testFalse('2'==11),
      // string, scalar
      testFalse('2'<2.),
      testTrue('2'<11.),
      testFalse('20'<11.),
      testTrue('2'==2.),
      testFalse('2'==11.),
      // string, string
      testFalse('2'<'2'),
      testFalse('2'<'11'),
      testFalse('20'<'11'),
      testTrue('2'=='2'),
      testFalse('2'=='11'),
      // logic
      testInt(1?2:3),
      testInt(0?2:3),
      testInt((1&&2)||3),
      testInt((1&&0)||3),
      testInt((1&&0)||0),
      testInt(1||(0&&3)),
      testInt(0||(0&&3)),
      testInt(0||(1&&3)),
      testInt(1?(2?3:4):5),
      testInt(0?(2?3:4):5),
      testInt(1?(0?3:4):5),
      testInt(0?(0?3:4):5),
      testInt(1?2?3:4:5),
      testInt(0?2?3:4:5),
      testInt(1?0?3:4:5),
      testInt(0?0?3:4:5),
  
      testInt(1?2:(3?4:5)),
      testInt(0?2:(3?4:5)),
      testInt(1?0:(3?4:5)),
      testInt(0?0:(3?4:5)),
      testInt(1?2:3?4:5),
      testInt(0?2:3?4:5),
      testInt(1?0:3?4:5),
      testInt(0?0:3?4:5)
      , { "123.5", SkType_Float, 0, SkIntToScalar(123) + SK_Scalar1/2, DEF_STRING_ANSWER }
  };
  
  #define SkScriptNAnswer_testCount   SK_ARRAY_COUNT(scriptTests)
  
  void SkScriptEngine::UnitTest() {
      for (unsigned index = 0; index < SkScriptNAnswer_testCount; index++) {
          SkScriptEngine engine(SkScriptEngine::ToOpType(scriptTests[index].fType));
          SkScriptValue value;
          const char* script = scriptTests[index].fScript;
          SkASSERT(engine.evaluateScript(&script, &value) == true);
          SkASSERT(value.fType == scriptTests[index].fType);
          SkScalar error;
          switch (value.fType) {
              case SkType_Int:
                  SkASSERT(value.fOperand.fS32 == scriptTests[index].fIntAnswer);
                  break;
              case SkType_Float:
                  error = SkScalarAbs(value.fOperand.fScalar - scriptTests[index].fScalarAnswer);
                  SkASSERT(error < SK_Scalar1 / 10000);
                  break;
              case SkType_String:
                  SkASSERT(strcmp(value.fOperand.fString->c_str(), scriptTests[index].fStringAnswer) == 0);
                  break;
              default:
                  SkASSERT(0);
          }
      }
  }
  #endif
  