/*
* Copyright 2011 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkScript2.h"
#include "SkData.h"
#include "SkFloatingPoint.h"
#include "SkMath.h"
#include "SkParse.h"
#include "SkScriptCallBack.h"
#include "SkScriptRuntime.h"
#include "SkString.h"
#include "SkOpArray.h"
const SkScriptEngine2::OperatorAttributes SkScriptEngine2::gOpAttributes[] = {
{ SkOperand2::kNoType, SkOperand2::kNoType, kNoBias, kResultIsNotBoolean },
{ SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar | SkOperand2::kString),
SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar | SkOperand2::kString), kTowardsString, kResultIsNotBoolean }, // kAdd
{ SkOperand2::kS32, SkOperand2::kS32, kNoBias, kResultIsNotBoolean }, // kBitAnd
{ SkOperand2::kNoType, SkOperand2::kS32, kNoBias, kResultIsNotBoolean }, // kBitNot
{ SkOperand2::kS32, SkOperand2::kS32, kNoBias, kResultIsNotBoolean }, // kBitOr
{ SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar),
SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar), kNoBias, kResultIsNotBoolean }, // kDivide
{ SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar | SkOperand2::kString),
SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar |SkOperand2:: kString), kTowardsNumber,
kResultIsBoolean }, // kEqual
{ SkOperand2::kS32, SkOperand2::kNoType, kNoBias, kResultIsNotBoolean }, // kFlipOps
{ SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar | SkOperand2::kString),
SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar | SkOperand2::kString), kTowardsNumber,
kResultIsBoolean }, // kGreaterEqual
{ SkOperand2::kNoType, SkOperand2::kS32, kNoBias, kResultIsNotBoolean }, // kLogicalAnd (really, ToBool)
{ SkOperand2::kNoType, SkOperand2::kS32, kNoBias, kResultIsNotBoolean }, // kLogicalNot
{ SkOperand2::kS32, SkOperand2::kS32, kNoBias, kResultIsNotBoolean }, // kLogicalOr
{ SkOperand2::kNoType, SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar), kNoBias, kResultIsNotBoolean }, // kMinus
{ SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar),
SkOperand2::OpType(SkOperand2::kS32 |SkOperand2:: kScalar), kNoBias, kResultIsNotBoolean }, // kModulo
{ SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar),
SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar), kNoBias, kResultIsNotBoolean }, // kMultiply
{ SkOperand2::kS32, SkOperand2::kS32, kNoBias, kResultIsNotBoolean }, // kShiftLeft
{ SkOperand2::kS32, SkOperand2::kS32, kNoBias, kResultIsNotBoolean }, // kShiftRight
{ SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar),
SkOperand2::OpType(SkOperand2::kS32 | SkOperand2::kScalar), kNoBias, kResultIsNotBoolean }, // kSubtract
{ SkOperand2::kS32, SkOperand2::kS32, kNoBias, kResultIsNotBoolean } // kXor
};
#define kBracketPrecedence 16
#define kIfElsePrecedence 15
const signed char SkScriptEngine2::gPrecedence[] = {
17, // kUnassigned,
6, // kAdd,
10, // kBitAnd,
4, // kBitNot,
12, // kBitOr,
5, // kDivide,
9, // kEqual,
-1, // kFlipOps,
8, // kGreaterEqual,
13, // kLogicalAnd,
4, // kLogicalNot,
14, // kLogicalOr,
4, // kMinus,
5, // kModulo,
5, // kMultiply,
7, // kShiftLeft,
7, // kShiftRight, // signed
6, // kSubtract,
11, // kXor
kBracketPrecedence, // kArrayOp
kIfElsePrecedence, // kElse
kIfElsePrecedence, // kIf
kBracketPrecedence, // kParen
};
const SkScriptEngine2::TypeOp SkScriptEngine2::gTokens[] = {
kNop, // unassigned
kAddInt, // kAdd,
kBitAndInt, // kBitAnd,
kBitNotInt, // kBitNot,
kBitOrInt, // kBitOr,
kDivideInt, // kDivide,
kEqualInt, // kEqual,
kFlipOpsOp, // kFlipOps,
kGreaterEqualInt, // kGreaterEqual,
kLogicalAndInt, // kLogicalAnd,
kLogicalNotInt, // kLogicalNot,
kLogicalOrInt, // kLogicalOr,
kMinusInt, // kMinus,
kModuloInt, // kModulo,
kMultiplyInt, // kMultiply,
kShiftLeftInt, // kShiftLeft,
kShiftRightInt, // kShiftRight, // signed
kSubtractInt, // kSubtract,
kXorInt // 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;
}
SkScriptEngine2::SkScriptEngine2(SkOperand2::OpType returnType) : fActiveStream(&fStream),
fTokenLength(0), fReturnType(returnType), fError(kNoError),
fAccumulatorType(SkOperand2::kNoType),
fBranchPopAllowed(true), fConstExpression(true), fOperandInUse(false)
{
Branch branch(kUnassigned, 0, 0);
fBranchStack.push(branch);
*fOpStack.push() = (Op) kParen;
}
SkScriptEngine2::~SkScriptEngine2() {
for (SkString** stringPtr = fTrackString.begin(); stringPtr < fTrackString.end(); stringPtr++)
delete *stringPtr;
for (SkOpArray** arrayPtr = fTrackArray.begin(); arrayPtr < fTrackArray.end(); arrayPtr++)
delete *arrayPtr;
}
void SkScriptEngine2::addToken(SkScriptEngine2::TypeOp op) {
int limit = fBranchStack.count() - 1;
for (int index = 0; index < limit; index++) {
Branch& branch = fBranchStack.index(index);
if (branch.fPrimed == Branch::kIsPrimed)
resolveBranch(branch);
}
if (fBranchPopAllowed) {
while (fBranchStack.top().fDone == Branch::kIsDone)
fBranchStack.pop();
}
unsigned char charOp = (unsigned char) op;
fActiveStream->write(&charOp, sizeof(charOp));
}
void SkScriptEngine2::addTokenConst(SkScriptValue2* value, AddTokenRegister reg,
SkOperand2::OpType toType, SkScriptEngine2::TypeOp op) {
if (value->fIsConstant == SkScriptValue2::kConstant && convertTo(toType, value))
return;
addTokenValue(*value, reg);
addToken(op);
value->fIsWritten = SkScriptValue2::kWritten;
value->fType = toType;
}
void SkScriptEngine2::addTokenInt(int integer) {
fActiveStream->write(&integer, sizeof(integer));
}
void SkScriptEngine2::addTokenScalar(SkScalar scalar) {
fActiveStream->write(&scalar, sizeof(scalar));
}
void SkScriptEngine2::addTokenString(const SkString& string) {
int size = SkToInt(string.size());
addTokenInt(size);
fActiveStream->write(string.c_str(), size);
}
void SkScriptEngine2::addTokenValue(const SkScriptValue2& value, AddTokenRegister reg) {
if (value.isConstant() == false) {
if (reg == kAccumulator) {
if (fAccumulatorType == SkOperand2::kNoType)
addToken(kAccumulatorPop);
} else {
; // !!! incomplete?
}
return;
}
if (reg == kAccumulator && fAccumulatorType != SkOperand2::kNoType)
addToken(kAccumulatorPush);
switch (value.fType) {
case SkOperand2::kS32:
addToken(reg == kAccumulator ? kIntegerAccumulator : kIntegerOperand);
addTokenInt(value.fOperand.fS32);
if (reg == kAccumulator)
fAccumulatorType = SkOperand2::kS32;
else
fOperandInUse = true;
break;
case SkOperand2::kScalar:
addToken(reg == kAccumulator ? kScalarAccumulator : kScalarOperand);
addTokenScalar(value.fOperand.fScalar);
if (reg == kAccumulator)
fAccumulatorType = SkOperand2::kScalar;
else
fOperandInUse = true;
break;
case SkOperand2::kString:
addToken(reg == kAccumulator ? kStringAccumulator : kStringOperand);
addTokenString(*value.fOperand.fString);
if (reg == kAccumulator)
fAccumulatorType = SkOperand2::kString;
else
fOperandInUse = true;
break;
default:
SkASSERT(0); //!!! not implemented yet
}
}
int SkScriptEngine2::arithmeticOp(char ch, char nextChar, bool lastPush) {
Op 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
return advance;
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 =(Op) kIf;
break;
case ':':
op = (Op) kElse;
break;
case '^':
op = kXor;
break;
case '(':
*fOpStack.push() = (Op) kParen;
return advance;
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;
signed char precedence = gPrecedence[op];
do {
int idx = 0;
Op 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 == SkOperand2::kNoType)) {
break;
}
processOp();
} while (true);
if (negateResult)
*fOpStack.push() = (Op) (kLogicalNot | kArtificialOp);
fOpStack.push(op);
if (reverseOperands)
*fOpStack.push() = (Op) (kFlipOps | kArtificialOp);
return advance;
}
bool SkScriptEngine2::convertParams(SkTDArray<SkScriptValue2>* params,
const SkOperand2::OpType* paramTypes, int paramCount) {
int count = params->count();
if (count > paramCount) {
SkASSERT(0);
return false; // too many parameters passed
}
for (int index = 0; index < count; index++)
convertTo(paramTypes[index], &(*params)[index]);
return true;
}
bool SkScriptEngine2::convertTo(SkOperand2::OpType toType, SkScriptValue2* value ) {
SkOperand2::OpType type = value->fType;
if (type == toType)
return true;
if (type == SkOperand2::kObject) {
if (handleUnbox(value) == false)
return false;
return convertTo(toType, value);
}
return ConvertTo(this, toType, value);
}
bool SkScriptEngine2::evaluateDot(const char*& script) {
size_t fieldLength = token_length(++script); // skip dot
SkASSERT(fieldLength > 0); // !!! add error handling
const char* field = script;
script += fieldLength;
bool success = handleProperty();
if (success == false) {
fError = kCouldNotFindReferencedID;
goto error;
}
return evaluateDotParam(script, field, fieldLength);
error:
return false;
}
bool SkScriptEngine2::evaluateDotParam(const char*& script, const char* field, size_t fieldLength) {
SkScriptValue2& top = fValueStack.top();
if (top.fType != SkOperand2::kObject)
return false;
void* object = top.fOperand.fObject;
fValueStack.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 != '(')
success = handleMember(field, fieldLength, object);
else {
SkTDArray<SkScriptValue2> params;
*fBraceStack.push() = kFunctionBrace;
success = functionParams(&script, ¶ms);
if (success)
success = handleMemberFunction(field, fieldLength, object, ¶ms);
}
return success;
}
bool SkScriptEngine2::evaluateScript(const char** scriptPtr, SkScriptValue2* value) {
// fArrayOffset = 0; // no support for structures for now
bool success;
const char* inner;
if (strncmp(*scriptPtr, "#script:", sizeof("#script:") - 1) == 0) {
*scriptPtr += sizeof("#script:") - 1;
if (fReturnType == SkOperand2::kNoType || fReturnType == SkOperand2::kString) {
success = innerScript(scriptPtr, value);
SkASSERT(success);
inner = value->fOperand.fString->c_str();
scriptPtr = &inner;
}
}
success = innerScript(scriptPtr, value);
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]"
return false;
}
return success;
}
void SkScriptEngine2::forget(SkOpArray* array) {
if (array->getType() == SkOperand2::kString) {
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() == SkOperand2::kArray) {
for (int index = 0; index < array->count(); index++) {
SkOpArray* child = (*array)[index].fArray;
forget(child); // forgets children of child
int found = fTrackArray.find(child);
if (found >= 0)
fTrackArray.remove(found);
}
}
}
bool SkScriptEngine2::functionParams(const char** scriptPtr, SkTDArray<SkScriptValue2>* params) {
(*scriptPtr)++; // skip open paren
*fOpStack.push() = (Op) kParen;
*fBraceStack.push() = kFunctionBrace;
do {
SkScriptValue2 value;
bool success = innerScript(scriptPtr, &value);
SkASSERT(success);
if (success == false)
return false;
*params->append() = value;
} while ((*scriptPtr)[-1] == ',');
fBraceStack.pop();
fOpStack.pop(); // pop paren
(*scriptPtr)++; // advance beyond close paren
return true;
}
size_t SkScriptEngine2::getTokenOffset() {
return fActiveStream->getOffset();
}
SkOperand2::OpType SkScriptEngine2::getUnboxType(SkOperand2 scriptValue) {
for (SkScriptCallBack** callBack = fCallBackArray.begin(); callBack < fCallBackArray.end(); callBack++) {
if ((*callBack)->getType() != SkScriptCallBack::kUnbox)
continue;
return (*callBack)->getReturnType(0, &scriptValue);
}
return SkOperand2::kObject;
}
bool SkScriptEngine2::innerScript(const char** scriptPtr, SkScriptValue2* value) {
const char* script = *scriptPtr;
char ch;
bool lastPush = false;
bool success = true;
int opBalance = fOpStack.count();
int baseBrace = fBraceStack.count();
int branchBalance = fBranchStack.count();
while ((ch = script[0]) != '\0') {
if (is_ws(ch)) {
script++;
continue;
}
SkScriptValue2 operand;
const char* dotCheck;
if (fBraceStack.count() > baseBrace) {
if (fBraceStack.top() == kArrayBrace) {
SkScriptValue2 tokenValue;
success = innerScript(&script, &tokenValue); // terminate and return on comma, close brace
SkASSERT(success);
{
SkOperand2::OpType type = fReturnType;
if (fReturnType == SkOperand2::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)
convertTo(type, &tokenValue);
*value->fOperand.fArray->append() = tokenValue.fOperand;
}
lastPush = false;
continue;
} else {
SkASSERT(token_length(script) > 0);
}
}
if (lastPush != false && fTokenLength > 0) {
if (ch == '(') {
*fBraceStack.push() = kFunctionBrace;
SkString functionName(fToken, fTokenLength);
if (handleFunction(&script) == false)
return false;
lastPush = true;
continue;
} else if (ch == '[') {
if (handleProperty() == false) {
SkASSERT(0);
return false;
}
if (handleArrayIndexer(&script) == false)
return false;
lastPush = true;
continue;
} else if (ch != '.') {
if (handleProperty() == false) {
SkASSERT(0);
return false;
}
lastPush = true;
continue;
}
}
if (ch == '0' && (script[1] & ~0x20) == 'X') {
SkASSERT(lastPush == false);
script += 2;
script = SkParse::FindHex(script, (uint32_t*) &operand.fOperand.fS32);
SkASSERT(script);
goto intCommon;
}
if (lastPush == false && ch == '.')
goto scalarCommon;
if (ch >= '0' && ch <= '9') {
SkASSERT(lastPush == false);
dotCheck = SkParse::FindS32(script, &operand.fOperand.fS32);
if (dotCheck[0] != '.') {
script = dotCheck;
intCommon:
operand.fType = SkOperand2::kS32;
} else {
scalarCommon:
script = SkParse::FindScalar(script, &operand.fOperand.fScalar);
operand.fType = SkOperand2::kScalar;
}
operand.fIsConstant = SkScriptValue2::kConstant;
fValueStack.push(operand);
lastPush = true;
continue;
}
int length = token_length(script);
if (length > 0) {
SkASSERT(lastPush == false);
fToken = script;
fTokenLength = length;
script += length;
lastPush = true;
continue;
}
char startQuote = ch;
if (startQuote == '\'' || startQuote == '\"') {
SkASSERT(lastPush == false);
operand.fOperand.fString = new SkString();
++script;
const char* stringStart = script;
do { // measure string
if (script[0] == '\\')
++script;
++script;
SkASSERT(script[0]); // !!! throw an error
} while (script[0] != startQuote);
operand.fOperand.fString->set(stringStart, script - stringStart);
script = stringStart;
char* stringWrite = operand.fOperand.fString->writable_str();
do { // copy string
if (script[0] == '\\')
++script;
*stringWrite++ = script[0];
++script;
SkASSERT(script[0]); // !!! throw an error
} while (script[0] != startQuote);
++script;
track(operand.fOperand.fString);
operand.fType = SkOperand2::kString;
operand.fIsConstant = SkScriptValue2::kConstant;
fValueStack.push(operand);
lastPush = true;
continue;
}
if (ch == '.') {
if (fTokenLength == 0) {
int tokenLength = token_length(++script);
const char* token = script;
script += tokenLength;
SkASSERT(fValueStack.count() > 0); // !!! add error handling
SkScriptValue2 top;
fValueStack.pop(&top);
addTokenInt(top.fType);
addToken(kBoxToken);
top.fType = SkOperand2::kObject;
top.fIsConstant = SkScriptValue2::kVariable;
fConstExpression = false;
fValueStack.push(top);
success = evaluateDotParam(script, token, tokenLength);
SkASSERT(success);
lastPush = true;
continue;
}
// get next token, and evaluate immediately
success = evaluateDot(script);
if (success == false) {
// SkASSERT(0);
return false;
}
lastPush = true;
continue;
}
if (ch == '[') {
if (lastPush == false) {
script++;
*fBraceStack.push() = kArrayBrace;
operand.fOperand.fArray = value->fOperand.fArray = new SkOpArray(fReturnType);
track(value->fOperand.fArray);
operand.fType = SkOperand2::kArray;
operand.fIsConstant = SkScriptValue2::kVariable;
fValueStack.push(operand);
continue;
}
if (handleArrayIndexer(&script) == false)
return false;
lastPush = true;
continue;
}
#if 0 // structs not supported for now
if (ch == '{') {
if (lastPush == false) {
script++;
*fBraceStack.push() = kStructBrace;
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) {
BraceStyle braceStyle = fBraceStack.top();
if (braceStyle == kFunctionBrace) {
fBraceStack.pop();
break;
}
}
if (ch == ',' || ch == ']') {
if (ch != ',') {
BraceStyle match;
fBraceStack.pop(&match);
SkASSERT(match == kArrayBrace);
}
script++;
// !!! see if brace or bracket is correct closer
break;
}
char nextChar = script[1];
int advance = logicalOp(ch, nextChar);
if (advance == 0)
advance = arithmeticOp(ch, nextChar, lastPush);
if (advance == 0) // unknown token
return false;
if (advance > 0)
script += advance;
lastPush = ch == ']' || ch == ')';
}
if (fTokenLength > 0) {
success = handleProperty();
SkASSERT(success);
}
int branchIndex = 0;
branchBalance = fBranchStack.count() - branchBalance;
fBranchPopAllowed = false;
while (branchIndex < branchBalance) {
Branch& branch = fBranchStack.index(branchIndex++);
if (branch.fPrimed == Branch::kIsPrimed)
break;
Op branchOp = branch.fOperator;
SkOperand2::OpType lastType = fValueStack.top().fType;
addTokenValue(fValueStack.top(), kAccumulator);
fValueStack.pop();
if (branchOp == kLogicalAnd || branchOp == kLogicalOr) {
if (branch.fOperator == kLogicalAnd)
branch.prime();
addToken(kToBool);
} else {
resolveBranch(branch);
SkScriptValue2 operand;
operand.fType = lastType;
// !!! note that many branching expressions could be constant
// today, we always evaluate branches as returning variables
operand.fIsConstant = SkScriptValue2::kVariable;
fValueStack.push(operand);
}
if (branch.fDone == Branch::kIsNotDone)
branch.prime();
}
fBranchPopAllowed = true;
while (fBranchStack.top().fDone == Branch::kIsDone)
fBranchStack.pop();
while (fOpStack.count() > opBalance) { // leave open paren
if (processOp() == false)
return false;
}
SkOperand2::OpType topType = fValueStack.count() > 0 ? fValueStack.top().fType : SkOperand2::kNoType;
if (topType != fReturnType &&
topType == SkOperand2::kString && fReturnType != SkOperand2::kNoType) { // if result is a string, give handle property a chance to convert it to the property value
SkString* string = fValueStack.top().fOperand.fString;
fToken = string->c_str();
fTokenLength = string->size();
fValueStack.pop();
success = handleProperty();
if (success == false) { // if it couldn't convert, return string (error?)
SkScriptValue2 operand;
operand.fType = SkOperand2::kString;
operand.fOperand.fString = string;
operand.fIsConstant = SkScriptValue2::kVariable; // !!! ?
fValueStack.push(operand);
}
}
if (fStream.getOffset() > 0) {
addToken(kEnd);
SkAutoDataUnref data(fStream.copyToData());
#ifdef SK_DEBUG
decompile(data->bytes(), data->size());
#endif
SkScriptRuntime runtime(fCallBackArray);
runtime.executeTokens((unsigned char*) data->bytes());
SkScriptValue2 value1;
runtime.getResult(&value1.fOperand);
value1.fType = fReturnType;
fValueStack.push(value1);
}
if (value) {
if (fValueStack.count() == 0)
return false;
fValueStack.pop(value);
if (value->fType != fReturnType && value->fType == SkOperand2::kObject &&
fReturnType != SkOperand2::kNoType)
convertTo(fReturnType, value);
}
// if (fBranchStack.top().fOpStackDepth > fOpStack.count())
// resolveBranch();
*scriptPtr = script;
return true; // no error
}
bool SkScriptEngine2::handleArrayIndexer(const char** scriptPtr) {
SkScriptValue2 scriptValue;
(*scriptPtr)++;
*fOpStack.push() = (Op) kParen;
*fBraceStack.push() = kArrayBrace;
SkOperand2::OpType saveType = fReturnType;
fReturnType = SkOperand2::kS32;
bool success = innerScript(scriptPtr, &scriptValue);
fReturnType = saveType;
SkASSERT(success);
success = convertTo(SkOperand2::kS32, &scriptValue);
SkASSERT(success);
int index = scriptValue.fOperand.fS32;
fValueStack.pop(&scriptValue);
if (scriptValue.fType == SkOperand2::kObject) {
success = handleUnbox(&scriptValue);
SkASSERT(success);
SkASSERT(scriptValue.fType == SkOperand2::kArray);
}
scriptValue.fType = scriptValue.fOperand.fArray->getType();
// SkASSERT(index >= 0);
if ((unsigned) index >= (unsigned) scriptValue.fOperand.fArray->count()) {
fError = kArrayIndexOutOfBounds;
return false;
}
scriptValue.fOperand = scriptValue.fOperand.fArray->begin()[index];
scriptValue.fIsConstant = SkScriptValue2::kVariable;
fValueStack.push(scriptValue);
fOpStack.pop(); // pop paren
return success;
}
bool SkScriptEngine2::handleFunction(const char** scriptPtr) {
const char* functionName = fToken;
size_t functionNameLen = fTokenLength;
fTokenLength = 0;
SkTDArray<SkScriptValue2> params;
bool success = functionParams(scriptPtr, ¶ms);
if (success == false)
goto done;
{
for (SkScriptCallBack** callBack = fCallBackArray.begin(); callBack < fCallBackArray.end(); callBack++) {
if ((*callBack)->getType() != SkScriptCallBack::kFunction)
continue;
SkScriptValue2 callbackResult;
success = (*callBack)->getReference(functionName, functionNameLen, &callbackResult);
if (success) {
callbackResult.fType = (*callBack)->getReturnType(callbackResult.fOperand.fReference, nullptr);
callbackResult.fIsConstant = SkScriptValue2::kVariable;
fValueStack.push(callbackResult);
goto done;
}
}
}
return false;
done:
fOpStack.pop();
return success;
}
bool SkScriptEngine2::handleMember(const char* field, size_t len, void* object) {
bool success = true;
for (SkScriptCallBack** callBack = fCallBackArray.begin(); callBack < fCallBackArray.end(); callBack++) {
if ((*callBack)->getType() != SkScriptCallBack::kMember)
continue;
SkScriptValue2 callbackResult;
success = (*callBack)->getReference(field, len, &callbackResult);
if (success) {
if (callbackResult.fType == SkOperand2::kString)
track(callbackResult.fOperand.fString);
callbackResult.fIsConstant = SkScriptValue2::kVariable;
fValueStack.push(callbackResult);
goto done;
}
}
return false;
done:
return success;
}
bool SkScriptEngine2::handleMemberFunction(const char* field, size_t len, void* object,
SkTDArray<SkScriptValue2>* params) {
bool success = true;
for (SkScriptCallBack** callBack = fCallBackArray.begin(); callBack < fCallBackArray.end(); callBack++) {
if ((*callBack)->getType() != SkScriptCallBack::kMemberFunction)
continue;
SkScriptValue2 callbackResult;
success = (*callBack)->getReference(field, len, &callbackResult);
if (success) {
if (callbackResult.fType == SkOperand2::kString)
track(callbackResult.fOperand.fString);
callbackResult.fIsConstant = SkScriptValue2::kVariable;
fValueStack.push(callbackResult);
goto done;
}
}
return false;
done:
return success;
}
bool SkScriptEngine2::handleProperty() {
bool success = true;
for (SkScriptCallBack** callBack = fCallBackArray.begin(); callBack < fCallBackArray.end(); callBack++) {
if ((*callBack)->getType() != SkScriptCallBack::kProperty)
continue;
SkScriptValue2 callbackResult;
success = (*callBack)->getReference(fToken, fTokenLength, &callbackResult);
if (success) {
if (callbackResult.fType == SkOperand2::kString && callbackResult.fOperand.fString == nullptr) {
callbackResult.fOperand.fString = new SkString(fToken, fTokenLength);
track(callbackResult.fOperand.fString);
}
callbackResult.fIsConstant = SkScriptValue2::kVariable;
fValueStack.push(callbackResult);
goto done;
}
}
done:
fTokenLength = 0;
return success;
}
bool SkScriptEngine2::handleUnbox(SkScriptValue2* scriptValue) {
bool success = true;
for (SkScriptCallBack** callBack = fCallBackArray.begin(); callBack < fCallBackArray.end(); callBack++) {
if ((*callBack)->getType() != SkScriptCallBack::kUnbox)
continue;
SkScriptCallBackConvert* callBackConvert = (SkScriptCallBackConvert*) *callBack;
success = callBackConvert->convert(scriptValue->fType, &scriptValue->fOperand);
if (success) {
if (scriptValue->fType == SkOperand2::kString)
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 SkScriptEngine2::logicalOp(char ch, char nextChar) {
int advance = 1;
Op op;
signed char precedence;
switch (ch) {
case ')':
op = (Op) kParen;
break;
case ']':
op = (Op) kArrayOp;
break;
case '?':
op = (Op) kIf;
break;
case ':':
op = (Op) kElse;
break;
case '&':
if (nextChar != '&')
goto noMatch;
op = kLogicalAnd;
advance = 2;
break;
case '|':
if (nextChar != '|')
goto noMatch;
op = kLogicalOr;
advance = 2;
break;
default:
noMatch:
return 0;
}
precedence = gPrecedence[op];
int branchIndex = 0;
fBranchPopAllowed = false;
do {
while (gPrecedence[fOpStack.top() & ~kArtificialOp] < precedence)
processOp();
Branch& branch = fBranchStack.index(branchIndex++);
Op branchOp = branch.fOperator;
if (gPrecedence[branchOp] >= precedence)
break;
addTokenValue(fValueStack.top(), kAccumulator);
fValueStack.pop();
if (branchOp == kLogicalAnd || branchOp == kLogicalOr) {
if (branch.fOperator == kLogicalAnd)
branch.prime();
addToken(kToBool);
} else
resolveBranch(branch);
if (branch.fDone == Branch::kIsNotDone)
branch.prime();
} while (true);
fBranchPopAllowed = true;
while (fBranchStack.top().fDone == Branch::kIsDone)
fBranchStack.pop();
processLogicalOp(op);
return advance;
}
void SkScriptEngine2::processLogicalOp(Op op) {
switch (op) {
case kParen:
case kArrayOp:
SkASSERT(fOpStack.count() > 1 && fOpStack.top() == op); // !!! add error handling
if (op == kParen)
fOpStack.pop();
else {
SkScriptValue2 value;
fValueStack.pop(&value);
SkASSERT(value.fType == SkOperand2::kS32 || value.fType == SkOperand2::kScalar); // !!! add error handling (although, could permit strings eventually)
int index = value.fType == SkOperand2::kScalar ? SkScalarFloorToInt(value.fOperand.fScalar) :
value.fOperand.fS32;
SkScriptValue2 arrayValue;
fValueStack.pop(&arrayValue);
SkASSERT(arrayValue.fType == SkOperand2::kArray); // !!! add error handling
SkOpArray* array = arrayValue.fOperand.fArray;
SkOperand2 operand;
SkDEBUGCODE(bool success = ) array->getIndex(index, &operand);
SkASSERT(success); // !!! add error handling
SkScriptValue2 resultValue;
resultValue.fType = array->getType();
resultValue.fOperand = operand;
resultValue.fIsConstant = SkScriptValue2::kVariable;
fValueStack.push(resultValue);
}
break;
case kIf: {
if (fAccumulatorType == SkOperand2::kNoType) {
addTokenValue(fValueStack.top(), kAccumulator);
fValueStack.pop();
}
SkASSERT(fAccumulatorType != SkOperand2::kString); // !!! add error handling
addToken(kIfOp);
Branch branch(op, fOpStack.count(), getTokenOffset());
*fBranchStack.push() = branch;
addTokenInt(0); // placeholder for future branch
fAccumulatorType = SkOperand2::kNoType;
} break;
case kElse: {
addTokenValue(fValueStack.top(), kAccumulator);
fValueStack.pop();
addToken(kElseOp);
size_t newOffset = getTokenOffset();
addTokenInt(0); // placeholder for future branch
Branch& branch = fBranchStack.top();
resolveBranch(branch);
branch.fOperator = op;
branch.fDone = Branch::kIsNotDone;
SkASSERT(branch.fOpStackDepth == fOpStack.count());
branch.fOffset = SkToU16(newOffset);
fAccumulatorType = SkOperand2::kNoType;
} break;
case kLogicalAnd:
case kLogicalOr: {
Branch& oldTop = fBranchStack.top();
Branch::Primed wasPrime = oldTop.fPrimed;
Branch::Done wasDone = oldTop.fDone;
oldTop.fPrimed = Branch::kIsNotPrimed;
oldTop.fDone = Branch::kIsNotDone;
if (fAccumulatorType == SkOperand2::kNoType) {
SkASSERT(fValueStack.top().fType == SkOperand2::kS32); // !!! add error handling, and conversion to int?
addTokenValue(fValueStack.top(), kAccumulator);
fValueStack.pop();
} else {
SkASSERT(fAccumulatorType == SkOperand2::kS32);
}
// if 'and', write beq goto opcode after end of predicate (after to bool)
// if 'or', write bne goto to bool
addToken(op == kLogicalAnd ? kLogicalAndInt : kLogicalOrInt);
Branch branch(op, fOpStack.count(), getTokenOffset());
addTokenInt(0); // placeholder for future branch
oldTop.fPrimed = wasPrime;
oldTop.fDone = wasDone;
*fBranchStack.push() = branch;
fAccumulatorType = SkOperand2::kNoType;
} break;
default:
SkASSERT(0);
}
}
bool SkScriptEngine2::processOp() {
Op op;
fOpStack.pop(&op);
op = (Op) (op & ~kArtificialOp);
const OperatorAttributes* attributes = &gOpAttributes[op];
SkScriptValue2 value1;
memset(&value1, 0, sizeof(SkScriptValue2));
SkScriptValue2 value2;
fValueStack.pop(&value2);
value2.fIsWritten = SkScriptValue2::kUnwritten;
// SkScriptEngine2::SkTypeOp convert1[3];
// SkScriptEngine2::SkTypeOp convert2[3];
// SkScriptEngine2::SkTypeOp* convert2Ptr = convert2;
bool constantOperands = value2.fIsConstant == SkScriptValue2::kConstant;
if (attributes->fLeftType != SkOperand2::kNoType) {
fValueStack.pop(&value1);
constantOperands &= value1.fIsConstant == SkScriptValue2::kConstant;
value1.fIsWritten = SkScriptValue2::kUnwritten;
if (op == kFlipOps) {
SkTSwap(value1, value2);
fOpStack.pop(&op);
op = (Op) (op & ~kArtificialOp);
attributes = &gOpAttributes[op];
if (constantOperands == false)
addToken(kFlipOpsOp);
}
if (value1.fType == SkOperand2::kObject && (value1.fType & attributes->fLeftType) == 0) {
value1.fType = getUnboxType(value1.fOperand);
addToken(kUnboxToken);
}
}
if (value2.fType == SkOperand2::kObject && (value2.fType & attributes->fLeftType) == 0) {
value1.fType = getUnboxType(value2.fOperand);
addToken(kUnboxToken2);
}
if (attributes->fLeftType != SkOperand2::kNoType) {
if (value1.fType != value2.fType) {
if ((attributes->fLeftType & SkOperand2::kString) && attributes->fBias & kTowardsString &&
((value1.fType | value2.fType) & SkOperand2::kString)) {
if (value1.fType == SkOperand2::kS32 || value1.fType == SkOperand2::kScalar) {
addTokenConst(&value1, kAccumulator, SkOperand2::kString,
value1.fType == SkOperand2::kS32 ? kIntToString : kScalarToString);
}
if (value2.fType == SkOperand2::kS32 || value2.fType == SkOperand2::kScalar) {
addTokenConst(&value2, kOperand, SkOperand2::kString,
value2.fType == SkOperand2::kS32 ? kIntToString2 : kScalarToString2);
}
} else if (attributes->fLeftType & SkOperand2::kScalar && ((value1.fType | value2.fType) &
SkOperand2::kScalar)) {
if (value1.fType == SkOperand2::kS32)
addTokenConst(&value1, kAccumulator, SkOperand2::kScalar, kIntToScalar);
if (value2.fType == SkOperand2::kS32)
addTokenConst(&value2, kOperand, SkOperand2::kScalar, kIntToScalar2);
}
}
if ((value1.fType & attributes->fLeftType) == 0 || value1.fType != value2.fType) {
if (value1.fType == SkOperand2::kString)
addTokenConst(&value1, kAccumulator, SkOperand2::kScalar, kStringToScalar);
if (value1.fType == SkOperand2::kScalar && (attributes->fLeftType == SkOperand2::kS32 ||
value2.fType == SkOperand2::kS32))
addTokenConst(&value1, kAccumulator, SkOperand2::kS32, kScalarToInt);
}
}
AddTokenRegister rhRegister = attributes->fLeftType != SkOperand2::kNoType ?
kOperand : kAccumulator;
if ((value2.fType & attributes->fRightType) == 0 || value1.fType != value2.fType) {
if (value2.fType == SkOperand2::kString)
addTokenConst(&value2, rhRegister, SkOperand2::kScalar, kStringToScalar2);
if (value2.fType == SkOperand2::kScalar && (attributes->fRightType == SkOperand2::kS32 ||
value1.fType == SkOperand2::kS32))
addTokenConst(&value2, rhRegister, SkOperand2::kS32, kScalarToInt2);
}
TypeOp typeOp = gTokens[op];
if (value2.fType == SkOperand2::kScalar)
typeOp = (TypeOp) (typeOp + 1);
else if (value2.fType == SkOperand2::kString)
typeOp = (TypeOp) (typeOp + 2);
SkDynamicMemoryWStream stream;
SkOperand2::OpType saveType = SkOperand2::kNoType;
SkBool saveOperand = false;
if (constantOperands) {
fActiveStream = &stream;
saveType = fAccumulatorType;
saveOperand = fOperandInUse;
fAccumulatorType = SkOperand2::kNoType;
fOperandInUse = false;
}
if (attributes->fLeftType != SkOperand2::kNoType) { // two operands
if (value1.fIsWritten == SkScriptValue2::kUnwritten)
addTokenValue(value1, kAccumulator);
}
if (value2.fIsWritten == SkScriptValue2::kUnwritten)
addTokenValue(value2, rhRegister);
addToken(typeOp);
if (constantOperands) {
addToken(kEnd);
SkAutoDataUnref data(fStream.copyToData());
#ifdef SK_DEBUG
decompile(data->bytes(), data->size());
#endif
SkScriptRuntime runtime(fCallBackArray);
runtime.executeTokens((unsigned char*)data->bytes());
runtime.getResult(&value1.fOperand);
if (attributes->fResultIsBoolean == kResultIsBoolean)
value1.fType = SkOperand2::kS32;
else if (attributes->fLeftType == SkOperand2::kNoType) // unary operand
value1.fType = value2.fType;
fValueStack.push(value1);
if (value1.fType == SkOperand2::kString)
runtime.untrack(value1.fOperand.fString);
else if (value1.fType == SkOperand2::kArray)
runtime.untrack(value1.fOperand.fArray);
fActiveStream = &fStream;
fAccumulatorType = saveType;
fOperandInUse = saveOperand;
return true;
}
value2.fIsConstant = SkScriptValue2::kVariable;
fValueStack.push(value2);
return true;
}
void SkScriptEngine2::Branch::resolve(SkDynamicMemoryWStream* stream, size_t off) {
SkASSERT(fDone == kIsNotDone);
fPrimed = kIsNotPrimed;
fDone = kIsDone;
SkASSERT(off > fOffset + sizeof(size_t));
size_t offset = off - fOffset - sizeof(offset);
stream->write(&offset, fOffset, sizeof(offset));
}
void SkScriptEngine2::resolveBranch(SkScriptEngine2::Branch& branch) {
branch.resolve(fActiveStream, getTokenOffset());
}
bool SkScriptEngine2::ConvertTo(SkScriptEngine2* engine, SkOperand2::OpType toType, SkScriptValue2* value ) {
SkASSERT(value);
SkOperand2::OpType type = value->fType;
if (type == toType)
return true;
SkOperand2& operand = value->fOperand;
bool success = true;
switch (toType) {
case SkOperand2::kS32:
if (type == SkOperand2::kScalar)
operand.fS32 = SkScalarFloorToInt(operand.fScalar);
else {
SkASSERT(type == SkOperand2::kString);
success = SkParse::FindS32(operand.fString->c_str(), &operand.fS32) != nullptr;
}
break;
case SkOperand2::kScalar:
if (type == SkOperand2::kS32)
operand.fScalar = IntToScalar(operand.fS32);
else {
SkASSERT(type == SkOperand2::kString);
success = SkParse::FindScalar(operand.fString->c_str(), &operand.fScalar) != nullptr;
}
break;
case SkOperand2::kString: {
SkString* strPtr = new SkString();
SkASSERT(engine);
engine->track(strPtr);
if (type == SkOperand2::kS32)
strPtr->appendS32(operand.fS32);
else {
SkASSERT(type == SkOperand2::kScalar);
strPtr->appendScalar(operand.fScalar);
}
operand.fString = strPtr;
} break;
case SkOperand2::kArray: {
SkOpArray* array = new SkOpArray(type);
*array->append() = operand;
engine->track(array);
operand.fArray = array;
} break;
default:
SkASSERT(0);
}
value->fType = toType;
return success;
}
SkScalar SkScriptEngine2::IntToScalar(int32_t s32) {
SkScalar scalar;
if (s32 == (int32_t) SK_NaN32)
scalar = SK_ScalarNaN;
else if (SkAbs32(s32) == SK_MaxS32)
scalar = SkSign32(s32) * SK_ScalarMax;
else
scalar = SkIntToScalar(s32);
return scalar;
}
bool SkScriptEngine2::ValueToString(const SkScriptValue2& value, SkString* string) {
switch (value.fType) {
case SkOperand2::kS32:
string->reset();
string->appendS32(value.fOperand.fS32);
break;
case SkOperand2::kScalar:
string->reset();
string->appendScalar(value.fOperand.fScalar);
break;
case SkOperand2::kString:
string->set(*value.fOperand.fString);
break;
default:
SkASSERT(0);
return false;
}
return true; // no error
}
#ifdef SK_DEBUG
#if defined(SK_SUPPORT_UNITTEST)
#define testInt(expression) { #expression, SkOperand2::kS32, expression, 0, nullptr }
#define testScalar(expression) { #expression, SkOperand2::kScalar, 0, (float) (expression), nullptr }
#define testRemainder(exp1, exp2) { #exp1 "%" #exp2, SkOperand2::kScalar, 0, fmodf((float) exp1, (float) exp2), nullptr }
#define testTrue(expression) { #expression, SkOperand2::kS32, 1, 0, nullptr }
#define testFalse(expression) { #expression, SkOperand2::kS32, 0, 0, nullptr }
static const SkScriptNAnswer2 scriptTests[] = {
testInt(1||(0&&3)),
testScalar(- -5.5- -1.5),
testScalar(1.0+5),
testInt((6+7)*8),
testInt(3*(4+5)),
testScalar(1.0+2.0),
testScalar(3.0-1.0),
testScalar(6-1.0),
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\"", SkOperand2::kString, 0, 0, "123456" },
{ "123+\"456\"", SkOperand2::kString, 0, 0, "123456" },
{ "'123'+456", SkOperand2::kString, 0, 0, "123456" },
{ "'123'|\"456\"", SkOperand2::kS32, 123|456, 0, nullptr },
{ "123|\"456\"", SkOperand2::kS32, 123|456, 0, nullptr },
{ "'123'|456", SkOperand2::kS32, 123|456, 0, nullptr },
{ "'2'<11", SkOperand2::kS32, 1, 0, nullptr },
{ "2<'11'", SkOperand2::kS32, 1, 0, nullptr },
{ "'2'<'11'", SkOperand2::kS32, 0, 0, nullptr },
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(0&&1?2:3)
, { "123.5", SkOperand2::kScalar, 0, SkIntToScalar(123) + SK_Scalar1/2, nullptr }
};
#define SkScriptNAnswer_testCount SK_ARRAY_COUNT(scriptTests)
#endif // SK_SUPPORT_UNITTEST
void SkScriptEngine2::UnitTest() {
#if defined(SK_SUPPORT_UNITTEST)
ValidateDecompileTable();
for (size_t index = 0; index < SkScriptNAnswer_testCount; index++) {
SkScriptEngine2 engine(scriptTests[index].fType);
SkScriptValue2 value;
const char* script = scriptTests[index].fScript;
const char* scriptPtr = script;
SkASSERT(engine.evaluateScript(&scriptPtr, &value) == true);
SkASSERT(value.fType == scriptTests[index].fType);
SkScalar error;
switch (value.fType) {
case SkOperand2::kS32:
if (value.fOperand.fS32 != scriptTests[index].fIntAnswer)
SkDEBUGF(("script '%s' == value %d != expected answer %d\n", script, value.fOperand.fS32, scriptTests[index].fIntAnswer));
SkASSERT(value.fOperand.fS32 == scriptTests[index].fIntAnswer);
break;
case SkOperand2::kScalar:
error = SkScalarAbs(value.fOperand.fScalar - scriptTests[index].fScalarAnswer);
if (error >= SK_Scalar1 / 10000)
SkDEBUGF(("script '%s' == value %g != expected answer %g\n", script, value.fOperand.fScalar / (1.0f * SK_Scalar1), scriptTests[index].fScalarAnswer / (1.0f * SK_Scalar1)));
SkASSERT(error < SK_Scalar1 / 10000);
break;
case SkOperand2::kString:
SkASSERT(value.fOperand.fString->equals(scriptTests[index].fStringAnswer));
break;
default:
SkASSERT(0);
}
}
#endif // SK_SUPPORT_UNITTEST
}
#endif // SK_DEBUG