/*
* 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 ? SkScalarFloor(indexOperand.fScalar) :
indexOperand.fS32;
SkOpType arrayType;
fTypeStack.pop(&arrayType);
if ((unsigned)arrayType != (unsigned)kArray) {
fError = kExpectedArray;
return -1;
}
SkOperand arrayOperand;
fOperandStack.pop(&arrayOperand);
SkTypedArray* array = arrayOperand.fArray;
SkOperand operand;
if (array->getIndex(index, &operand) == false) {
fError = kIndexOutOfRange;
return -1;
}
SkOpType resultType = array->getOpType();
fTypeStack.push(resultType);
fOperandStack.push(operand);
}
break;
case kIf: {
SkScriptValue ifValue;
SkOpType ifType;
fTypeStack.pop(&ifType);
ifValue.fType = ToDisplayType(ifType);
fOperandStack.pop(&ifValue.fOperand);
if (convertTo(SkType_Int, &ifValue) == false)
return -1;
if (ifValue.fType != SkType_Int) {
fError = kExpectedIntForConditionOperator;
return -1;
}
suppress.fSuppress = ifValue.fOperand.fS32 == 0;
suppress.fOperator = kIf;
suppress.fOpStackDepth = fOpStack.count();
suppress.fElse = false;
fSuppressStack.push(suppress);
// if left is true, do only up to colon
// if left is false, do only after colon
} break;
case kElse:
flipSuppress:
if (fSuppressStack.top().fElse)
fSuppressStack.pop();
fSuppressStack.top().fElse = true;
fSuppressStack.top().fSuppress ^= true;
// flip last do / don't do consideration from last '?'
break;
case kLogicalAnd:
case kLogicalOr: {
if (fTypeStack.top() != kInt) {
fError = kExpectedBooleanExpression;
return -1;
}
int32_t topInt = fOperandStack.top().fS32;
if (fOpStack.top() != kLogicalAnd)
*fOpStack.push() = kLogicalAnd; // really means 'to bool', and is appropriate for 'or'
if (match == kLogicalOr ? topInt != 0 : topInt == 0) {
suppress.fSuppress = true;
suppress.fOperator = match;
suppress.fOpStackDepth = fOpStack.count();
suppress.fElse = false;
fSuppressStack.push(suppress);
} else {
fTypeStack.pop();
fOperandStack.pop();
}
} break;
default:
SkASSERT(0);
}
goHome:
return advance;
}
SkScriptEngine::Error SkScriptEngine::opError() {
int opCount = fOpStack.count();
int operandCount = fOperandStack.count();
if (opCount == 0) {
if (operandCount != 1)
return kExpectedOperator;
return kNoError;
}
SkOp op = (SkOp) (fOpStack.top() & ~kArtificialOp);
const SkOperatorAttributes* attributes = &gOpAttributes[op];
if (attributes->fLeftType != kNoType && operandCount < 2)
return kExpectedValue;
if (attributes->fLeftType == kNoType && operandCount < 1)
return kExpectedValue;
return kNoError;
}
bool SkScriptEngine::processOp() {
SkOp op;
fOpStack.pop(&op);
op = (SkOp) (op & ~kArtificialOp);
const SkOperatorAttributes* attributes = &gOpAttributes[op];
SkOpType type2;
fTypeStack.pop(&type2);
SkOpType type1 = type2;
SkOperand operand2;
fOperandStack.pop(&operand2);
SkOperand operand1 = operand2; // !!! not really needed, suppresses warning
if (attributes->fLeftType != kNoType) {
fTypeStack.pop(&type1);
fOperandStack.pop(&operand1);
if (op == kFlipOps) {
SkTSwap(type1, type2);
SkTSwap(operand1, operand2);
fOpStack.pop(&op);
op = (SkOp) (op & ~kArtificialOp);
attributes = &gOpAttributes[op];
}
if (type1 == kObject && (type1 & attributes->fLeftType) == 0) {
SkScriptValue val;
val.fType = ToDisplayType(type1);
val.fOperand = operand1;
bool success = handleUnbox(&val);
if (success == false)
return false;
type1 = ToOpType(val.fType);
operand1 = val.fOperand;
}
}
if (type2 == kObject && (type2 & attributes->fLeftType) == 0) {
SkScriptValue val;
val.fType = ToDisplayType(type2);
val.fOperand = operand2;
bool success = handleUnbox(&val);
if (success == false)
return false;
type2 = ToOpType(val.fType);
operand2 = val.fOperand;
}
if (attributes->fLeftType != kNoType) {
if (type1 != type2) {
if ((attributes->fLeftType & kString) && attributes->fBias & kTowardsString && ((type1 | type2) & kString)) {
if (type1 == kInt || type1 == kScalar) {
convertToString(operand1, type1 == kInt ? SkType_Int : SkType_Float);
type1 = kString;
}
if (type2 == kInt || type2 == kScalar) {
convertToString(operand2, type2 == kInt ? SkType_Int : SkType_Float);
type2 = kString;
}
} else if (attributes->fLeftType & kScalar && ((type1 | type2) & kScalar)) {
if (type1 == kInt) {
operand1.fScalar = IntToScalar(operand1.fS32);
type1 = kScalar;
}
if (type2 == kInt) {
operand2.fScalar = IntToScalar(operand2.fS32);
type2 = kScalar;
}
}
}
if ((type1 & attributes->fLeftType) == 0 || type1 != type2) {
if (type1 == kString) {
const char* result = SkParse::FindScalar(operand1.fString->c_str(), &operand1.fScalar);
if (result == NULL) {
fError = kExpectedNumber;
return false;
}
type1 = kScalar;
}
if (type1 == kScalar && (attributes->fLeftType == kInt || type2 == kInt)) {
operand1.fS32 = SkScalarFloor(operand1.fScalar);
type1 = kInt;
}
}
}
if ((type2 & attributes->fRightType) == 0 || type1 != type2) {
if (type2 == kString) {
const char* result = SkParse::FindScalar(operand2.fString->c_str(), &operand2.fScalar);
if (result == NULL) {
fError = kExpectedNumber;
return false;
}
type2 = kScalar;
}
if (type2 == kScalar && (attributes->fRightType == kInt || type1 == kInt)) {
operand2.fS32 = SkScalarFloor(operand2.fScalar);
type2 = kInt;
}
}
if (type2 == kScalar)
op = (SkOp) (op + 1);
else if (type2 == kString)
op = (SkOp) (op + 2);
switch(op) {
case kAddInt:
operand2.fS32 += operand1.fS32;
break;
case kAddScalar:
operand2.fScalar += operand1.fScalar;
break;
case kAddString:
if (fTrackString.find(operand1.fString) < 0) {
operand1.fString = SkNEW_ARGS(SkString, (*operand1.fString));
track(operand1.fString);
}
operand1.fString->append(*operand2.fString);
operand2 = operand1;
break;
case kBitAnd:
operand2.fS32 &= operand1.fS32;
break;
case kBitNot:
operand2.fS32 = ~operand2.fS32;
break;
case kBitOr:
operand2.fS32 |= operand1.fS32;
break;
case kDivideInt:
if (operand2.fS32 == 0) {
operand2.fS32 = operand1.fS32 == 0 ? SK_NaN32 : operand1.fS32 > 0 ? SK_MaxS32 : -SK_MaxS32;
break;
} else {
int32_t original = operand2.fS32;
operand2.fS32 = operand1.fS32 / operand2.fS32;
if (original * operand2.fS32 == operand1.fS32)
break; // integer divide was good enough
operand2.fS32 = original;
type2 = kScalar;
}
case kDivideScalar:
if (operand2.fScalar == 0)
operand2.fScalar = operand1.fScalar == 0 ? SK_ScalarNaN : operand1.fScalar > 0 ? SK_ScalarMax : -SK_ScalarMax;
else
operand2.fScalar = SkScalarDiv(operand1.fScalar, operand2.fScalar);
break;
case kEqualInt:
operand2.fS32 = operand1.fS32 == operand2.fS32;
break;
case kEqualScalar:
operand2.fS32 = operand1.fScalar == operand2.fScalar;
type2 = kInt;
break;
case kEqualString:
operand2.fS32 = *operand1.fString == *operand2.fString;
type2 = kInt;
break;
case kGreaterEqualInt:
operand2.fS32 = operand1.fS32 >= operand2.fS32;
break;
case kGreaterEqualScalar:
operand2.fS32 = operand1.fScalar >= operand2.fScalar;
type2 = kInt;
break;
case kGreaterEqualString:
operand2.fS32 = strcmp(operand1.fString->c_str(), operand2.fString->c_str()) >= 0;
type2 = kInt;
break;
case kLogicalAnd:
operand2.fS32 = !! operand2.fS32; // really, ToBool
break;
case kLogicalNot:
operand2.fS32 = ! operand2.fS32;
break;
case kLogicalOr:
SkASSERT(0); // should have already been processed
break;
case kMinusInt:
operand2.fS32 = -operand2.fS32;
break;
case kMinusScalar:
operand2.fScalar = -operand2.fScalar;
break;
case kModuloInt:
operand2.fS32 = operand1.fS32 % operand2.fS32;
break;
case kModuloScalar:
operand2.fScalar = SkScalarMod(operand1.fScalar, operand2.fScalar);
break;
case kMultiplyInt:
operand2.fS32 *= operand1.fS32;
break;
case kMultiplyScalar:
operand2.fScalar = SkScalarMul(operand1.fScalar, operand2.fScalar);
break;
case kShiftLeft:
operand2.fS32 = operand1.fS32 << operand2.fS32;
break;
case kShiftRight:
operand2.fS32 = operand1.fS32 >> operand2.fS32;
break;
case kSubtractInt:
operand2.fS32 = operand1.fS32 - operand2.fS32;
break;
case kSubtractScalar:
operand2.fScalar = operand1.fScalar - operand2.fScalar;
break;
case kXor:
operand2.fS32 ^= operand1.fS32;
break;
default:
SkASSERT(0);
}
fTypeStack.push(type2);
fOperandStack.push(operand2);
return true;
}
void SkScriptEngine::propertyCallBack(_propertyCallBack prop, void* userStorage) {
UserCallBack callBack;
callBack.fPropertyCallBack = prop;
commonCallBack(kProperty, callBack, userStorage);
}
void SkScriptEngine::track(SkTypedArray* array) {
SkASSERT(fTrackArray.find(array) < 0);
*(fTrackArray.end() - 1) = array;
fTrackArray.appendClear();
}
void SkScriptEngine::track(SkString* string) {
SkASSERT(fTrackString.find(string) < 0);
*(fTrackString.end() - 1) = string;
fTrackString.appendClear();
}
void SkScriptEngine::unboxCallBack(_unboxCallBack func, void* userStorage) {
UserCallBack callBack;
callBack.fUnboxCallBack = func;
commonCallBack(kUnbox, callBack, userStorage);
}
bool SkScriptEngine::ConvertTo(SkScriptEngine* engine, SkDisplayTypes toType, SkScriptValue* value ) {
SkASSERT(value);
if (SkDisplayType::IsEnum(NULL /* fMaker */, toType))
toType = SkType_Int;
if (toType == SkType_Point || toType == SkType_3D_Point)
toType = SkType_Float;
if (toType == SkType_Drawable)
toType = SkType_Displayable;
SkDisplayTypes type = value->fType;
if (type == toType)
return true;
SkOperand& operand = value->fOperand;
bool success = true;
switch (toType) {
case SkType_Int:
if (type == SkType_Boolean)
break;
if (type == SkType_Float)
operand.fS32 = SkScalarFloor(operand.fScalar);
else {
if (type != SkType_String) {
success = false;
break; // error
}
success = SkParse::FindS32(operand.fString->c_str(), &operand.fS32) != NULL;
}
break;
case SkType_Float:
if (type == SkType_Int) {
if (operand.fS32 == SK_NaN32)
operand.fScalar = SK_ScalarNaN;
else if (SkAbs32(operand.fS32) == SK_MaxS32)
operand.fScalar = SkSign32(operand.fS32) * SK_ScalarMax;
else
operand.fScalar = SkIntToScalar(operand.fS32);
} else {
if (type != SkType_String) {
success = false;
break; // error
}
success = SkParse::FindScalar(operand.fString->c_str(), &operand.fScalar) != NULL;
}
break;
case SkType_String: {
SkString* strPtr = new SkString();
SkASSERT(engine);
engine->track(strPtr);
if (type == SkType_Int) {
strPtr->appendS32(operand.fS32);
} else if (type == SkType_Displayable) {
SkASSERT(0); // must call through instance version instead of static version
} else {
if (type != SkType_Float) {
success = false;
break;
}
strPtr->appendScalar(operand.fScalar);
}
operand.fString = strPtr;
} break;
case SkType_Array: {
SkTypedArray* array = new SkTypedArray(type);
*array->append() = operand;
engine->track(array);
operand.fArray = array;
} break;
default:
SkASSERT(0);
}
value->fType = toType;
if (success == false)
engine->fError = kTypeConversionFailed;
return success;
}
SkScalar SkScriptEngine::IntToScalar(int32_t s32) {
SkScalar scalar;
if (s32 == SK_NaN32)
scalar = SK_ScalarNaN;
else if (SkAbs32(s32) == SK_MaxS32)
scalar = SkSign32(s32) * SK_ScalarMax;
else
scalar = SkIntToScalar(s32);
return scalar;
}
SkDisplayTypes SkScriptEngine::ToDisplayType(SkOpType type) {
int val = type;
switch (val) {
case kNoType:
return SkType_Unknown;
case kInt:
return SkType_Int;
case kScalar:
return SkType_Float;
case kString:
return SkType_String;
case kArray:
return SkType_Array;
case kObject:
return SkType_Displayable;
// case kStruct:
// return SkType_Structure;
default:
SkASSERT(0);
return SkType_Unknown;
}
}
SkScriptEngine::SkOpType SkScriptEngine::ToOpType(SkDisplayTypes type) {
if (SkDisplayType::IsDisplayable(NULL /* fMaker */, type))
return (SkOpType) kObject;
if (SkDisplayType::IsEnum(NULL /* fMaker */, type))
return kInt;
switch (type) {
case SkType_ARGB:
case SkType_MSec:
case SkType_Int:
return kInt;
case SkType_Float:
case SkType_Point:
case SkType_3D_Point:
return kScalar;
case SkType_Base64:
case SkType_DynamicString:
case SkType_String:
return kString;
case SkType_Array:
return (SkOpType) kArray;
case SkType_Unknown:
return kNoType;
default:
SkASSERT(0);
return kNoType;
}
}
bool SkScriptEngine::ValueToString(SkScriptValue value, SkString* string) {
switch (value.fType) {
case kInt:
string->reset();
string->appendS32(value.fOperand.fS32);
break;
case kScalar:
string->reset();
string->appendScalar(value.fOperand.fScalar);
break;
case kString:
string->set(*value.fOperand.fString);
break;
default:
SkASSERT(0);
return false;
}
return true; // no error
}
#ifdef SK_SUPPORT_UNITTEST
#include "SkFloatingPoint.h"
#define DEF_SCALAR_ANSWER 0
#define DEF_STRING_ANSWER NULL
#define testInt(expression) { #expression, SkType_Int, expression, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER }
#ifdef SK_SCALAR_IS_FLOAT
#define testScalar(expression) { #expression, SkType_Float, 0, (float) expression, DEF_STRING_ANSWER }
#define testRemainder(exp1, exp2) { #exp1 "%" #exp2, SkType_Float, 0, sk_float_mod(exp1, exp2), DEF_STRING_ANSWER }
#else
#define testScalar(expression) { #expression, SkType_Float, 0, (int) ((expression) * 65536.0f), DEF_STRING_ANSWER }
#define testRemainder(exp1, exp2) { #exp1 "%" #exp2, SkType_Float, 0, (int) (sk_float_mod(exp1, exp2) * 65536.0f), DEF_STRING_ANSWER }
#endif
#define testTrue(expression) { #expression, SkType_Int, 1, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER }
#define testFalse(expression) { #expression, SkType_Int, 0, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER }
static const SkScriptNAnswer scriptTests[] = {
testInt(1>1/2),
testInt((6+7)*8),
testInt(0&&1?2:3),
testInt(3*(4+5)),
testScalar(1.0+2.0),
testScalar(1.0+5),
testScalar(3.0-1.0),
testScalar(6-1.0),
testScalar(- -5.5- -1.5),
testScalar(2.5*6.),
testScalar(0.5*4),
testScalar(4.5/.5),
testScalar(9.5/19),
testRemainder(9.5, 0.5),
testRemainder(9.,2),
testRemainder(9,2.5),
testRemainder(-9,2.5),
testTrue(-9==-9.0),
testTrue(-9.==-4.0-5),
testTrue(-9.*1==-4-5),
testFalse(-9!=-9.0),
testFalse(-9.!=-4.0-5),
testFalse(-9.*1!=-4-5),
testInt(0x123),
testInt(0XABC),
testInt(0xdeadBEEF),
{ "'123'+\"456\"", SkType_String, 0, 0, "123456" },
{ "123+\"456\"", SkType_String, 0, 0, "123456" },
{ "'123'+456", SkType_String, 0, 0, "123456" },
{ "'123'|\"456\"", SkType_Int, 123|456, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER },
{ "123|\"456\"", SkType_Int, 123|456, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER },
{ "'123'|456", SkType_Int, 123|456, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER },
{ "'2'<11", SkType_Int, 1, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER },
{ "2<'11'", SkType_Int, 1, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER },
{ "'2'<'11'", SkType_Int, 0, DEF_SCALAR_ANSWER, DEF_STRING_ANSWER },
testInt(123),
testInt(-345),
testInt(+678),
testInt(1+2+3),
testInt(3*4+5),
testInt(6+7*8),
testInt(-1-2-8/4),
testInt(-9%4),
testInt(9%-4),
testInt(-9%-4),
testInt(123|978),
testInt(123&978),
testInt(123^978),
testInt(2<<4),
testInt(99>>3),
testInt(~55),
testInt(~~55),
testInt(!55),
testInt(!!55),
// both int
testInt(2<2),
testInt(2<11),
testInt(20<11),
testInt(2<=2),
testInt(2<=11),
testInt(20<=11),
testInt(2>2),
testInt(2>11),
testInt(20>11),
testInt(2>=2),
testInt(2>=11),
testInt(20>=11),
testInt(2==2),
testInt(2==11),
testInt(20==11),
testInt(2!=2),
testInt(2!=11),
testInt(20!=11),
// left int, right scalar
testInt(2<2.),
testInt(2<11.),
testInt(20<11.),
testInt(2<=2.),
testInt(2<=11.),
testInt(20<=11.),
testInt(2>2.),
testInt(2>11.),
testInt(20>11.),
testInt(2>=2.),
testInt(2>=11.),
testInt(20>=11.),
testInt(2==2.),
testInt(2==11.),
testInt(20==11.),
testInt(2!=2.),
testInt(2!=11.),
testInt(20!=11.),
// left scalar, right int
testInt(2.<2),
testInt(2.<11),
testInt(20.<11),
testInt(2.<=2),
testInt(2.<=11),
testInt(20.<=11),
testInt(2.>2),
testInt(2.>11),
testInt(20.>11),
testInt(2.>=2),
testInt(2.>=11),
testInt(20.>=11),
testInt(2.==2),
testInt(2.==11),
testInt(20.==11),
testInt(2.!=2),
testInt(2.!=11),
testInt(20.!=11),
// both scalar
testInt(2.<11.),
testInt(20.<11.),
testInt(2.<=2.),
testInt(2.<=11.),
testInt(20.<=11.),
testInt(2.>2.),
testInt(2.>11.),
testInt(20.>11.),
testInt(2.>=2.),
testInt(2.>=11.),
testInt(20.>=11.),
testInt(2.==2.),
testInt(2.==11.),
testInt(20.==11.),
testInt(2.!=2.),
testInt(2.!=11.),
testInt(20.!=11.),
// int, string (string is int)
testFalse(2<'2'),
testTrue(2<'11'),
testFalse(20<'11'),
testTrue(2<='2'),
testTrue(2<='11'),
testFalse(20<='11'),
testFalse(2>'2'),
testFalse(2>'11'),
testTrue(20>'11'),
testTrue(2>='2'),
testFalse(2>='11'),
testTrue(20>='11'),
testTrue(2=='2'),
testFalse(2=='11'),
testFalse(2!='2'),
testTrue(2!='11'),
// int, string (string is scalar)
testFalse(2<'2.'),
testTrue(2<'11.'),
testFalse(20<'11.'),
testTrue(2=='2.'),
testFalse(2=='11.'),
// scalar, string
testFalse(2.<'2.'),
testTrue(2.<'11.'),
testFalse(20.<'11.'),
testTrue(2.=='2.'),
testFalse(2.=='11.'),
// string, int
testFalse('2'<2),
testTrue('2'<11),
testFalse('20'<11),
testTrue('2'==2),
testFalse('2'==11),
// string, scalar
testFalse('2'<2.),
testTrue('2'<11.),
testFalse('20'<11.),
testTrue('2'==2.),
testFalse('2'==11.),
// string, string
testFalse('2'<'2'),
testFalse('2'<'11'),
testFalse('20'<'11'),
testTrue('2'=='2'),
testFalse('2'=='11'),
// logic
testInt(1?2:3),
testInt(0?2:3),
testInt((1&&2)||3),
testInt((1&&0)||3),
testInt((1&&0)||0),
testInt(1||(0&&3)),
testInt(0||(0&&3)),
testInt(0||(1&&3)),
testInt(1?(2?3:4):5),
testInt(0?(2?3:4):5),
testInt(1?(0?3:4):5),
testInt(0?(0?3:4):5),
testInt(1?2?3:4:5),
testInt(0?2?3:4:5),
testInt(1?0?3:4:5),
testInt(0?0?3:4:5),
testInt(1?2:(3?4:5)),
testInt(0?2:(3?4:5)),
testInt(1?0:(3?4:5)),
testInt(0?0:(3?4:5)),
testInt(1?2:3?4:5),
testInt(0?2:3?4:5),
testInt(1?0:3?4:5),
testInt(0?0:3?4:5)
, { "123.5", SkType_Float, 0, SkIntToScalar(123) + SK_Scalar1/2, DEF_STRING_ANSWER }
};
#define SkScriptNAnswer_testCount SK_ARRAY_COUNT(scriptTests)
void SkScriptEngine::UnitTest() {
for (unsigned index = 0; index < SkScriptNAnswer_testCount; index++) {
SkScriptEngine engine(SkScriptEngine::ToOpType(scriptTests[index].fType));
SkScriptValue value;
const char* script = scriptTests[index].fScript;
SkASSERT(engine.evaluateScript(&script, &value) == true);
SkASSERT(value.fType == scriptTests[index].fType);
SkScalar error;
switch (value.fType) {
case SkType_Int:
SkASSERT(value.fOperand.fS32 == scriptTests[index].fIntAnswer);
break;
case SkType_Float:
error = SkScalarAbs(value.fOperand.fScalar - scriptTests[index].fScalarAnswer);
SkASSERT(error < SK_Scalar1 / 10000);
break;
case SkType_String:
SkASSERT(strcmp(value.fOperand.fString->c_str(), scriptTests[index].fStringAnswer) == 0);
break;
default:
SkASSERT(0);
}
}
}
#endif