|  |  | 
|  | /* | 
|  | * 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], ¶ms[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 ? SkScalarFloorToInt(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 = SkScalarFloorToInt(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 = SkScalarFloorToInt(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 = SkScalarFloorToInt(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 } | 
|  | #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 } | 
|  | #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 |