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