#include "SkScript.h"
#include "SkMath.h"
#include "SkParse.h"
#include "SkString.h"
#include "SkTypedArray.h"
#ifdef SK_DEBUG
static const char* errorStrings[] = {
"array index of out bounds", "could not find reference id", "dot operator expects object", "error in array index", "error in function parameters", "expected array", "expected boolean expression", "expected field name", "expected hex", "expected int for condition operator", "expected number", "expected number for array index", "expected operator", "expected token", "expected token before dot operator", "expected value", "handle member failed", "handle member function failed", "handle unbox failed", "index out of range", "mismatched array brace", "mismatched brackets", "no function handler found", "premature end", "too many parameters", "type conversion failed", "unterminated string" };
#endif
const SkScriptEngine::SkOperatorAttributes SkScriptEngine::gOpAttributes[] = {
{ kNoType, kNoType, kNoBias }, { SkOpType(kInt | kScalar | kString), SkOpType(kInt | kScalar | kString), kTowardsString }, { kNoType, kNoType, kNoBias }, { kNoType, kNoType, kNoBias }, { kNoType, kNoType, kNoBias }, { kInt, kInt, kNoBias }, { kNoType, kInt, kNoBias }, { kInt, kInt, kNoBias }, { SkOpType(kInt | kScalar), SkOpType(kInt | kScalar), kNoBias }, { kNoType, kNoType, kNoBias }, { kNoType, kNoType, kNoBias }, { SkOpType(kInt | kScalar | kString), SkOpType(kInt | kScalar | kString), kTowardsNumber }, { kNoType, kNoType, kNoBias }, { kNoType, kNoType, kNoBias }, { kInt, kNoType, kNoBias }, { SkOpType(kInt | kScalar | kString), SkOpType(kInt | kScalar | kString), kTowardsNumber }, { kNoType, kNoType, kNoBias }, { kNoType, kNoType, kNoBias }, { kNoType, kNoType, kNoBias }, { kNoType, kInt, kNoBias }, { kNoType, kInt, kNoBias }, { kInt, kInt, kNoBias }, { kNoType, SkOpType(kInt | kScalar), kNoBias }, { kNoType, kNoType, kNoBias }, { SkOpType(kInt | kScalar), SkOpType(kInt | kScalar), kNoBias }, { kNoType, kNoType, kNoBias }, { SkOpType(kInt | kScalar), SkOpType(kInt | kScalar), kNoBias }, { kNoType, kNoType, kNoBias }, { kNoType, kNoType, kNoBias }, { kInt, kInt, kNoBias }, { kInt, kInt, kNoBias }, { SkOpType(kInt | kScalar), SkOpType(kInt | kScalar), kNoBias }, { kNoType, kNoType, kNoBias }, { kInt, kInt, kNoBias } };
#define kBracketPrecedence 16
#define kIfElsePrecedence 15
const signed char SkScriptEngine::gPrecedence[] = {
-1, 6, 6, 6, kBracketPrecedence, 10, 4, 12, 5, 5, kIfElsePrecedence, 9, 9, 9, -1, 8, 8, 8, kIfElsePrecedence, 13, 4, 14, 4, 4, 5, 5, 5, 5, kBracketPrecedence, 7, 7, 6, 6, 11, };
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 '+':
if (lastPush == false) 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; 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; } 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; }
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#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); if (fieldLength == 0) {
fError = kExpectedFieldName;
return false;
}
const char* field = script;
script += fieldLength;
bool success = handleProperty(suppressed);
if (success == false) {
fError = kCouldNotFindReferencedID; 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; 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') {
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); 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)++; *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(); (*scriptPtr)++; 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#endif
if (fBraceStack.top() == kArrayBrace) {
SkScriptValue tokenValue;
success = innerScript(&script, &tokenValue); if (success == false) {
fError = kErrorInArrrayIndex;
return false;
}
if (suppressed == false) {
#if 0#endif
{
SkDisplayTypes type = ToDisplayType(fReturnType);
if (fReturnType == kNoType) {
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; if (handleArrayIndexer(&script, SkToBool(suppressed)) == false)
return false;
lastPush = true;
continue;
} else if (ch != '.') {
if (handleProperty(SkToBool(suppressed)) == false)
return false; 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;
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;
}
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#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++;
break;
}
char nextChar = script[1];
int advance = logicalOp(ch, nextChar);
if (advance < 0) return false;
if (advance == 0)
advance = arithmeticOp(ch, nextChar, lastPush);
if (advance == 0) 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; }
while (fOpStack.count() > opBalance) { 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) { 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) { 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);
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; }
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#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();
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(); 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#endif
bool SkScriptEngine::handleProperty(bool suppressed) {
SkScriptValue callbackResult;
bool success = true;
if (suppressed)
goto done;
success = 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;
}
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; 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);
} break;
case kElse:
flipSuppress:
if (fSuppressStack.top().fElse)
fSuppressStack.pop();
fSuppressStack.top().fElse = true;
fSuppressStack.top().fSuppress ^= true;
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; 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; 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; 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; break;
case kLogicalNot:
operand2.fS32 = ! operand2.fS32;
break;
case kLogicalOr:
SkASSERT(0); 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 , 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; }
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; }
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); } 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;
default:
SkASSERT(0);
return SkType_Unknown;
}
}
SkScriptEngine::SkOpType SkScriptEngine::ToOpType(SkDisplayTypes type) {
if (SkDisplayType::IsDisplayable(NULL , type))
return (SkOpType) kObject;
if (SkDisplayType::IsEnum(NULL , 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; }
#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),
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),
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.),
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),
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.),
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'),
testFalse(2<'2.'),
testTrue(2<'11.'),
testFalse(20<'11.'),
testTrue(2=='2.'),
testFalse(2=='11.'),
testFalse(2.<'2.'),
testTrue(2.<'11.'),
testFalse(20.<'11.'),
testTrue(2.=='2.'),
testFalse(2.=='11.'),
testFalse('2'<2),
testTrue('2'<11),
testFalse('20'<11),
testTrue('2'==2),
testFalse('2'==11),
testFalse('2'<2.),
testTrue('2'<11.),
testFalse('20'<11.),
testTrue('2'==2.),
testFalse('2'==11.),
testFalse('2'<'2'),
testFalse('2'<'11'),
testFalse('20'<'11'),
testTrue('2'=='2'),
testFalse('2'=='11'),
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