import { Utils, DynamicCachedObject, Expression} from '@uino/base-thing';
const cOperators = ['=', '<', '>', '!'];
const cNameEndCodes = ['|', '&', '||', '&&'];
// #region ObjectWrapper
function ObjectWrapper() {
this.object = null;
}
Object.assign(ObjectWrapper.prototype, {
init: function (object) {
this.object = object;
return this;
},
dispose: function () {
this.object = null;
},
});
const _objectWrapper = new ObjectWrapper();
_objectWrapper._name = function (name) {
return _objectWrapper.object.name == name;
}
_objectWrapper._id = function (id) {
return _objectWrapper.object.id == id;
}
_objectWrapper._type = function (type) {
return _objectWrapper.object[type] === true;
}
_objectWrapper._regExp = function (pattern, attribute) {
let value;
if (attribute._startsWith('[')) {
let key = attribute.substr(1, attribute.length - 2)._trimBoth(' \t');
value = _objectWrapper.object.getAttribute(key);
}
else {
value = _objectWrapper.object.getAttribute(pattern);
}
if (Utils.isString(value)) {
let regExps = ObjectExpression.regExps;
let regExp = regExps.get(pattern);
if (!regExp) {
regExp = new RegExp('^' + attribute._trimRight('*'));
regExps.set(pattern, regExp);
}
return regExp.test(value);
}
return false;
}
_objectWrapper._attribute = function (name, pattern) {
let value = _objectWrapper.object.getAttribute(name);
if (value) {
if (pattern) {
return _objectWrapper._regExp(name, pattern);
}
}
return value;
}
_objectWrapper._hasAttribute = function (name) {
return _objectWrapper.object.hasAttribute(name);
}
// add by hzy
// get from userData, it will be faster
_objectWrapper._userData = function(name) {
let value = Utils.getAttribute(_objectWrapper.object.userData, name);
return value
}
_objectWrapper.tags = function (tags) {
let expression = ObjectExpression.buildExpression(tags, null, 'tags');
if (!expression) {
return false;
}
let objectTags = _objectWrapper.object.tags;
let result = expression.evaluate(objectTags, value => {
return objectTags.has(value);
});
return !!result;
}
_objectWrapper.tags_not = function (tags) {
let result = _objectWrapper.tags(tags);
return !result;
}
_objectWrapper.tags_and = function (tags) {
return _objectWrapper.tags(tags);
}
_objectWrapper.tags_or = function (tags) {
return _objectWrapper.tags(tags);
}
// #endregion
// #region Private Functions
function _isStringType(value) {
if (value === 'uuid' || value === 'id' || value === 'name') {
return true;
}
return false
}
function _getTagFuncName(string) {
if (string._startsWith('tags')) {
let code = string[4];
// tags(...)
if (code == '(') {
return 'tags';
}
// tags:not/and/or
else if (code == ':') {
let opString = string.substring(5);
if (opString._startsWith('not(')) {
return 'tags_not';
}
else if (opString._startsWith('and(')) {
return 'tags_and';
}
else if (opString._startsWith('or(')) {
return 'tags_or';
}
}
}
return false;
}
function _isMatchEndCode(string, endCodes) {
for (let i = 0, l = endCodes.length; i < l; i++) {
let endCode = endCodes[i];
if (string.startsWith(endCode)) {
return true;
}
}
return false;
}
function _getValueString(string, endCodes) {
let value = '';
if (Utils.isArray(endCodes)) {
for (let i = 0, l = string.length; i < l; i++) {
if (_isMatchEndCode(string.substring(i), endCodes)) {
break;
}
value += string[i];
}
}
else {
for (let i = 0, l = string.length; i < l; i++) {
let code = string[i];
if (endCodes.indexOf(code) !== -1) {
break;
}
value += code;
}
}
return value;
}
function _getOperatorString(string) {
let opString = '';
for (let i = 0; i < string.length; i++) {
let code = string[i];
if (code != '=' && code != '<' && code != '>' && code != '!') {
break;
}
opString += code;
}
// Use '==' to replace '=' or '=='
if (opString == '=') {
opString = '==';
}
return opString._trimBoth(' \t');
}
function _buildAttributeString(key, string, isStringType, isRegString = false) {
let expression = key + '(' + "'";
let attributeName = '';
let index = 0;
for (; index < string.length; index++) {
let code = string[index];
if (code == '=' || code == '<' || code == '>' || code == '!') {
break;
}
attributeName += code;
}
expression += attributeName._trimBoth('" \t');
expression += "'";
expression += isRegString ? ',' : ')';
// Get the value string
let valueString = string.substring(index);
if (valueString) {
// Get the operator string
let operatorString = _getOperatorString(valueString);
if (operatorString) {
// Get the value string (Remove ' ', '\t' and 0...0 of number string)
valueString = valueString._trimLeft(operatorString)._trimBoth(' \t');
// If it's not a number string then we convert it as string type
if (!Utils.isNumberString(valueString) && !Utils.isBooleanString(valueString)) {
// It's reg-exp
if (valueString[0] == '/' && valueString[valueString.length - 1] == '/') {
// Remove '//' both side, we do not need that
valueString = valueString.substr(1, valueString.length - 2);
// Remove ')' to concat reg-exp string
expression = expression._removeAt(expression.length - 1);
// Do not need operator, here we use to split arguments
operatorString = ',';
// Build reg-exp as 2nd arg and pass to _attribute function
if (valueString[0] != '"' && valueString[0] != "'") {
valueString = "'" + valueString + "'";
}
valueString += ')';
}
// It's string
else {
if (valueString[0] != '"' && valueString[0] != "'") {
valueString = "'" + valueString + "'";
}
}
}
else {
if (isStringType(attributeName)) {
// Be sure it has no '"'.
valueString = valueString._trimBoth('"');
// Get the value string of 'uuid' and 'id', such as '001', '01'.
valueString = '"' + valueString + '"';
}
// Record string length before it was replaced
let isEmptyString = true;
if (valueString.length) {
isEmptyString = false;
}
if (Utils.isNumberString(valueString)) {
valueString = valueString._replaceAll("^(0+)", "");
}
// If now value string is empty and before being replaced value string is not empty
// then indicates it's 0 number value, so we need to set it to 0 forcely
if (!valueString && !isEmptyString) {
valueString = '0';
}
}
}
if (!isRegString) {
expression += operatorString;
}
expression += valueString;
}
if (isRegString) {
expression += ')';
}
return expression;
}
function _buildValueString(key, string, endCodes, isStringType) {
let expression = '';
expression += key + '(';
if (isStringType) {
expression += '"';
}
let value = _getValueString(string, endCodes);
expression += value._trimBoth('\'"')._trimBoth(' ');
if (isStringType) {
expression += '"';
}
expression += ')';
return { expression, value };
}
function _buildEvalExpression(condition, mode) {
let expression = '';
for (let i = 0; i < condition.length; i++) {
let code = condition[i];
if (code == ' ' || code == '\t') {
continue;
}
// OR
if (code == '|') {
if (condition[i + 1] == '|') {
i++;
}
expression += '||';
}
// AND
else if (code == '&') {
if (condition[i + 1] == '&') {
i++;
}
expression += '&&';
}
// NOT and BRACKETS
else if (code == '!' || code == '(' || code == ')') {
expression += code;
}
// Operations
else if (code == '+' || code == '-' || code == '*' || code == '/') {
expression += code;
}
// Mode and Attribute
else if (mode || code == '[') {
let funcName = '';
let isRegString = false;
let info;
if (mode) {
let prefix = (mode === 'userData' ? '' : (mode + '='));
info = _buildValueString('', prefix + condition.substring(i), cNameEndCodes.concat([')']));
i += info.value.length - prefix.length - 1;
}
else {
info = _buildValueString('', condition.substring(i + 1), ']');
i += info.value.length + 1; // ']'
}
if (mode == "userData") {
funcName = '_userData';
}
else if (info.value._endsWith('*')) {
// '[name=car*]'
funcName = '_regExp';
isRegString = true;
}
else if (info.value._contains(cOperators)) {
funcName = '_attribute';
}
else {
funcName = '_hasAttribute';
}
expression += _buildAttributeString(funcName, info.value, _isStringType, isRegString);
}
// ID
else if (code == '#') {
let info = _buildValueString('_id', condition.substring(i + 1), ' \t|&', true);
expression += info.expression;
i += info.value.length;
}
// Type
else if (code == '.') {
let info = _buildValueString('_type', 'is' + condition.substring(i + 1), ' \t|&', true);
expression += info.expression;
i += info.value.length - 2; // expect 'is' string
}
// RegExp
else if (code == '/') {
let info = _buildValueString('_regExp', condition.substring(i + 1), '/', true);
i += info.value.length + 1; // '/'
let startIndex = condition.substring(i).indexOf('(');
if (startIndex !== -1) {
i += startIndex + 1;
let endIndex = condition.substring(i).indexOf(')');
if (endIndex !== -1) {
let argString = condition.substr(i, endIndex);
expression += `_regExp('${info.value}', '${argString}')`;
i += endIndex + 1;
}
}
}
// Name or Tags
else {
let info;
// Check whether it's tags function
let tagFuncName = _getTagFuncName(condition);
if (tagFuncName) {
i += tagFuncName.length + 1;
info = _buildValueString(tagFuncName, condition.substring(i), [')'], true);
// Make tags_and(A, B, C) => tag_and(A && B && C)
if (tagFuncName == 'tags_and') {
info.expression = info.expression._replaceAll(',', '&&');
}
// Make tags_not/tags_or(A, B, C) => tags_not/tags_or(A || B || C)
else if (tagFuncName == 'tags_not' || tagFuncName == 'tags_or') {
info.expression = info.expression._replaceAll(',', '||');
}
}
// It's name
else {
info = _buildValueString('_name', condition.substring(i), cNameEndCodes, true);
}
expression += info.expression;
i += info.value.length;
}
}
return expression;
}
// #endregion
/**
* @class ObjectExpression
* The object expression.
* @memberof THING
*/
class ObjectExpression {
static expressions = new DynamicCachedObject({
liveTime: 10 * 1000,
cleanupTime: 5 * 1000,
});
static tagsExpressions = new DynamicCachedObject({
liveTime: 10 * 1000,
cleanupTime: 5 * 1000,
});
static regExps = new DynamicCachedObject({
liveTime: 10 * 1000,
cleanupTime: 5 * 1000,
});
static buildExpression(exp, onMakeExpression, mode) {
if (Utils.isRegExp(exp)) {
return exp;
}
else {
let expressions = ObjectExpression.expressions;
let key = exp;
// Get tags' expressions
if (mode === 'tags') {
expressions = ObjectExpression.tagsExpressions;
mode = null;
}
else if (mode) {
key = mode + ':' + exp;
}
let evalExpression = expressions.get(key);
if (evalExpression === undefined) {
let expression = onMakeExpression ? onMakeExpression(exp, mode) : exp;
try {
evalExpression = new Expression(expression);
expressions.set(key, evalExpression);
}
catch (ex) {
expressions.set(key, null);
Utils.error(ex);
return null;
}
}
return evalExpression;
}
}
constructor(exp, mode) {
if (_DEBUG) {
this._exp = exp;
}
this._expression = ObjectExpression.buildExpression(exp, _buildEvalExpression, mode);
this._isRegExp = Utils.isRegExp(this._expression);
this._mode = mode;
return this;
}
evaluate(object) {
let expression = this._expression;
if (!expression) {
return null;
}
// Test regular expression with object's name
if (this._isRegExp) {
return expression.test(object.name);
}
// Test expression with object
else {
_objectWrapper.init(object);
let result = expression.evaluate(_objectWrapper);
_objectWrapper.dispose();
return result;
}
}
static update(deltaTime) {
ObjectExpression.expressions.update(deltaTime);
ObjectExpression.regExps.update(deltaTime);
}
}
export { ObjectExpression }