| |
| /* |
| * 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 = 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 |