catjs
Version:
(Mobile) Web Automation Framework
1,289 lines (1,071 loc) • 35.5 kB
JavaScript
/**
* JSPath
*
* Copyright (c) 2012 Filatov Dmitry (dfilatov@yandex-team.ru)
* With parts by Marat Dulin (mdevils@gmail.com)
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* @version 0.2.12
*/
(function() {
var SYNTAX = {
PATH : 1,
SELECTOR : 2,
OBJ_PRED : 3,
POS_PRED : 4,
LOGICAL_EXPR : 5,
COMPARISON_EXPR : 6,
MATH_EXPR : 7,
CONCAT_EXPR : 8,
UNARY_EXPR : 9,
POS_EXPR : 10,
LITERAL : 11,
SUBST : 12
};
// parser
var parse = (function() {
var TOKEN = {
ID : 1,
NUM : 2,
STR : 3,
BOOL : 4,
PUNCT : 5,
EOP : 6
},
MESSAGES = {
UNEXP_TOKEN : 'Unexpected token "%0"',
UNEXP_EOP : 'Unexpected end of path'
};
var path, idx, buf, len;
function parse(_path) {
path = _path.split('');
idx = 0;
buf = null;
len = path.length;
var res = parsePath(),
token = lex();
if(token.type !== TOKEN.EOP) {
throwUnexpected(token);
}
return res;
}
function parsePath() {
if(!matchPath()) {
throwUnexpected(lex());
}
var fromRoot = false;
if(match('^')) {
lex();
fromRoot = true;
}
var parts = [],
part;
while(part = parsePathPart()) {
parts.push(part);
}
return {
type : SYNTAX.PATH,
fromRoot : fromRoot,
parts : parts
};
}
function parsePathPart() {
if(matchSelector()) {
return parseSelector();
}
if(match('[')) {
return parsePosPredicate();
}
if(match('{')) {
return parseObjectPredicate();
}
if(match('(')) {
return parseConcatExpr();
}
}
function parseSelector() {
var selector = lex().val,
token = lookahead(),
prop;
if(match('*') || token.type === TOKEN.ID || token.type === TOKEN.STR) {
prop = lex().val;
}
return {
type : SYNTAX.SELECTOR,
selector : selector,
prop : prop
};
}
function parsePosPredicate() {
expect('[');
var expr = parsePosExpr();
expect(']');
return {
type : SYNTAX.POS_PRED,
arg : expr
};
}
function parseObjectPredicate() {
expect('{');
var expr = parseLogicalORExpr();
expect('}');
return {
type : SYNTAX.OBJ_PRED,
arg : expr
};
}
function parseLogicalORExpr() {
var expr = parseLogicalANDExpr(),
operands;
while(match('||')) {
lex();
(operands || (operands = [expr])).push(parseLogicalANDExpr());
}
return operands?
{
type : SYNTAX.LOGICAL_EXPR,
op : '||',
args : operands
} :
expr;
}
function parseLogicalANDExpr() {
var expr = parseEqualityExpr(),
operands;
while(match('&&')) {
lex();
(operands || (operands = [expr])).push(parseEqualityExpr());
}
return operands?
{
type : SYNTAX.LOGICAL_EXPR,
op : '&&',
args : operands
} :
expr;
}
function parseEqualityExpr() {
var expr = parseRelationalExpr();
while(match('==') || match('!=') || match('===') || match('!==') ||
match('^=') || match('^==') || match('$==') || match('$=') || match('*==') || match('*=')) {
expr = {
type : SYNTAX.COMPARISON_EXPR,
op : lex().val,
args : [expr, parseEqualityExpr()]
};
}
return expr;
}
function parseRelationalExpr() {
var expr = parseAdditiveExpr();
while(match('<') || match('>') || match('<=') || match('>=')) {
expr = {
type : SYNTAX.COMPARISON_EXPR,
op : lex().val,
args : [expr, parseRelationalExpr()]
};
}
return expr;
}
function parseAdditiveExpr() {
var expr = parseMultiplicativeExpr();
while(match('+') || match('-')) {
expr = {
type : SYNTAX.MATH_EXPR,
op : lex().val,
args : [expr, parseAdditiveExpr()]
};
}
return expr;
}
function parseMultiplicativeExpr() {
var expr = parseUnaryExpr();
while(match('*') || match('/') || match('%')) {
expr = {
type : SYNTAX.MATH_EXPR,
op : lex().val,
args : [expr, parseMultiplicativeExpr()]
};
}
return expr;
}
function parsePosExpr() {
if(match(':')) {
lex();
return {
type : SYNTAX.POS_EXPR,
toIdx : parseUnaryExpr()
};
}
var fromExpr = parseUnaryExpr();
if(match(':')) {
lex();
if(match(']')) {
return {
type : SYNTAX.POS_EXPR,
fromIdx : fromExpr
};
}
return {
type : SYNTAX.POS_EXPR,
fromIdx : fromExpr,
toIdx : parseUnaryExpr()
};
}
return {
type : SYNTAX.POS_EXPR,
idx : fromExpr
};
}
function parseUnaryExpr() {
if(match('!') || match('-')) {
return {
type : SYNTAX.UNARY_EXPR,
op : lex().val,
arg : parseUnaryExpr()
};
}
return parsePrimaryExpr();
}
function parsePrimaryExpr() {
var token = lookahead(),
type = token.type;
if(type === TOKEN.STR || type === TOKEN.NUM || type === TOKEN.BOOL) {
return {
type : SYNTAX.LITERAL,
val : lex().val
};
}
if(type === TOKEN.ID && token.val[0] === '$') {
return {
type : SYNTAX.SUBST,
name : lex().val.substr(1)
};
}
if(matchPath()) {
return parsePath();
}
if(match('(')) {
return parseGroupExpr();
}
return throwUnexpected(lex());
}
function parseGroupExpr() {
expect('(');
var expr = parseLogicalORExpr();
expect(')');
return expr;
}
function parseConcatExpr() {
expect('(');
var expr = parsePath(),
operands;
while(match('|')) {
lex();
(operands || (operands = [expr])).push(parsePath());
}
expect(')');
return operands?
{
type : SYNTAX.CONCAT_EXPR,
op : '|',
args : operands
} :
expr;
}
function match(val) {
var token = lookahead();
return token.type === TOKEN.PUNCT && token.val === val;
}
function matchPath() {
return matchSelector() || match('^');
}
function matchSelector() {
var token = lookahead();
if(token.type === TOKEN.PUNCT) {
var val = token.val;
return val === '.' || val === '..';
}
return false;
}
function expect(val) {
var token = lex();
if(token.type !== TOKEN.PUNCT || token.val !== val) {
throwUnexpected(token);
}
}
function lookahead() {
if(buf !== null) {
return buf;
}
var pos = idx;
buf = advance();
idx = pos;
return buf;
}
function advance() {
while(isWhiteSpace(path[idx])) {
++idx;
}
if(idx >= len) {
return {
type : TOKEN.EOP,
range : [idx, idx]
};
}
var token = scanPunctuator();
if(token ||
(token = scanId()) ||
(token = scanString()) ||
(token = scanNumeric())) {
return token;
}
throwUnexpected({ val : path[idx], range : [idx, idx] });
}
function lex() {
var token;
if(buf) {
idx = buf.range[1];
token = buf;
buf = null;
return token;
}
return advance();
}
function isDigit(ch) {
return '0123456789'.indexOf(ch) >= 0;
}
function isWhiteSpace(ch) {
return ch === ' ';
}
function isIdStart(ch) {
return (ch === '$') || (ch === '_') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
}
function isIdPart(ch) {
return isIdStart(ch) || (ch >= '0' && ch <= '9');
}
function scanId() {
var ch = path[idx];
if(!isIdStart(ch)) {
return;
}
var start = idx,
id = ch;
while(++idx < len) {
ch = path[idx];
if(!isIdPart(ch)) {
break;
}
id += ch;
}
return id === 'true' || id === 'false'?
{
type : TOKEN.BOOL,
val : id === 'true',
range : [start, idx]
} :
{
type : TOKEN.ID,
val : id,
range : [start, idx]
};
}
function scanString() {
if(path[idx] !== '"') {
return;
}
var start = ++idx,
str = '',
ch;
while(idx < len) {
ch = path[idx++];
if(ch === '"') {
break;
}
str += ch;
}
return {
type : TOKEN.STR,
val : str,
range : [start, idx]
};
}
function scanNumeric() {
var start = idx,
ch = path[idx],
isFloat = ch === '.';
if(isFloat || isDigit(ch)) {
var num = ch;
while(++idx < len) {
ch = path[idx];
if(ch === '.') {
if(isFloat) {
return;
}
isFloat = true;
}
else if(!isDigit(ch)) {
break;
}
num += ch;
}
return {
type : TOKEN.NUM,
val : isFloat? parseFloat(num) : parseInt(num, 10),
range : [start, idx]
};
}
}
function scanPunctuator() {
var start = idx,
ch1 = path[idx],
ch2 = path[idx + 1];
if(ch1 === '.') {
if(isDigit(ch2)) {
return;
}
return path[++idx] === '.'?
{
type : TOKEN.PUNCT,
val : '..',
range : [start, ++idx]
} :
{
type : TOKEN.PUNCT,
val : '.',
range : [start, idx]
};
}
if(ch2 === '=') {
var ch3 = path[idx + 2];
if(ch3 === '=') {
if('=!^$*'.indexOf(ch1) >= 0) {
return {
type : TOKEN.PUNCT,
val : ch1 + ch2 + ch3,
range : [start, idx += 3]
};
}
}
else if('=!^$*><'.indexOf(ch1) >= 0) {
return {
type : TOKEN.PUNCT,
val : ch1 + ch2,
range : [start, idx += 2]
};
}
}
if(ch1 === ch2 && (ch1 === '|' || ch1 === '&')) {
return {
type : TOKEN.PUNCT,
val : ch1 + ch2,
range : [start, idx += 2]
};
}
if(':{}()[]^+-*/%!><|'.indexOf(ch1) >= 0) {
return {
type : TOKEN.PUNCT,
val : ch1,
range : [start, ++idx]
};
}
}
function throwUnexpected(token) {
if(token.type === TOKEN.EOP) {
throwError(token, MESSAGES.UNEXP_EOP);
}
throwError(token, MESSAGES.UNEXP_TOKEN, token.val);
}
function throwError(token, messageFormat) {
var args = Array.prototype.slice.call(arguments, 2),
msg = messageFormat.replace(
/%(\d)/g,
function(_, idx) {
return args[idx] || '';
}),
error = new Error(msg);
error.column = token.range[0];
throw error;
}
return parse;
})();
// translator
var translate = (function() {
var body, vars, lastVarId, unusedVars;
function acquireVar() {
if(unusedVars.length) {
return unusedVars.shift();
}
var varName = 'v' + ++lastVarId;
vars.push(varName);
return varName;
}
function releaseVars() {
var args = arguments, i = args.length;
while(i--) {
unusedVars.push(args[i]);
}
}
function translate(ast) {
body = [];
vars = ['res'];
lastVarId = 0;
unusedVars = [];
translatePath(ast, 'res', 'data', true);
body.unshift(
'var ',
Array.isArray?
'isArr = Array.isArray' :
'toStr = Object.prototype.toString, isArr = function(o) { return toStr.call(o) === "[object Array]"; }',
',', vars.join(','), ';',
'isArr(data) || (data = [data]);');
body.push('return res;');
return body.join('');
}
function translatePath(path, dest, ctx, isTopLevel) {
var parts = path.parts,
i = 0, len = parts.length,
isResArray = true;
body.push(dest, '=', path.fromRoot? 'data' : ctx, ';');
while(i < len) {
var item = parts[i++];
switch(item.type) {
case SYNTAX.SELECTOR:
item.selector === '..'?
translateDescendantSelector(item, dest, dest) :
translateSelector(item, dest, dest);
isResArray = true;
break;
case SYNTAX.OBJ_PRED:
translateObjectPredicate(item, dest, dest);
break;
case SYNTAX.POS_PRED:
isResArray = translatePosPredicate(item, dest, dest) !== false || !isTopLevel;
break;
case SYNTAX.CONCAT_EXPR:
translateConcatExpr(item, dest, dest);
isResArray = true;
break;
}
}
isResArray || body.push(dest, '=', dest, '[0];');
}
function translateSelector(sel, dest, ctx) {
if(sel.prop) {
var propStr = escapeStr(sel.prop),
res = acquireVar(), i = acquireVar(), len = acquireVar(),
curCtx = acquireVar(),
j = acquireVar(), val = acquireVar(), tmpArr = acquireVar();
body.push(
res, '= [],', i, '= 0,', len, '=', ctx, '.length,', tmpArr, '= [];',
'while(', i, '<', len, ') {',
curCtx, '=', ctx, '[', i, '++];',
'if(', curCtx, '!= null) {');
if(sel.prop === '*') {
body.push(
'if(typeof ', curCtx, '=== "object") {',
'if(isArr(', curCtx, ')) {',
res, '=', res, '.concat(', curCtx, ');',
'}',
'else {',
'for(', j, ' in ', curCtx, ') {',
'if(', curCtx, '.hasOwnProperty(', j, ')) {',
val, '=', curCtx, '[', j, '];');
inlineAppendToArray(res, val);
body.push(
'}',
'}',
'}',
'}');
}
else {
body.push(
val, '=', curCtx, '[', propStr, '];');
inlineAppendToArray(res, val, tmpArr, len);
}
body.push(
'}',
'}',
dest, '=', len, '> 1 &&', tmpArr, '.length?', tmpArr, '.length > 1?',
res, '.concat.apply(', res, ',', tmpArr, ') :', res, '.concat(', tmpArr, '[0]) :', res, ';');
releaseVars(res, i, len, curCtx, j, val, tmpArr);
}
}
function translateDescendantSelector(sel, dest, baseCtx) {
var prop = sel.prop,
ctx = acquireVar(), curCtx = acquireVar(), childCtxs = acquireVar(),
i = acquireVar(), j = acquireVar(), val = acquireVar(),
len = acquireVar(), res = acquireVar();
body.push(
ctx, '=', baseCtx, '.slice(),', res, '= [];',
'while(', ctx, '.length) {',
curCtx, '=', ctx, '.shift();');
prop?
body.push(
'if(typeof ', curCtx, '=== "object" &&', curCtx, ') {') :
body.push(
'if(typeof ', curCtx, '!= null) {');
body.push(
childCtxs, '= [];',
'if(isArr(', curCtx, ')) {',
i, '= 0,', len, '=', curCtx, '.length;',
'while(', i, '<', len, ') {',
val, '=', curCtx, '[', i, '++];');
prop && body.push(
'if(typeof ', val, '=== "object") {');
inlineAppendToArray(childCtxs, val);
prop && body.push(
'}');
body.push(
'}',
'}',
'else {');
if(prop) {
if(prop !== '*') {
body.push(
val, '=', curCtx, '["' + prop + '"];');
inlineAppendToArray(res, val);
}
}
else {
inlineAppendToArray(res, curCtx);
body.push(
'if(typeof ', curCtx, '=== "object") {');
}
body.push(
'for(', j, ' in ', curCtx, ') {',
'if(', curCtx, '.hasOwnProperty(', j, ')) {',
val, '=', curCtx, '[', j, '];');
inlineAppendToArray(childCtxs, val);
prop === '*' && inlineAppendToArray(res, val);
body.push(
'}',
'}');
prop || body.push(
'}');
body.push(
'}',
childCtxs, '.length &&', ctx, '.unshift.apply(', ctx, ',', childCtxs, ');',
'}',
'}',
dest, '=', res, ';');
releaseVars(ctx, curCtx, childCtxs, i, j, val, len, res);
}
function translateObjectPredicate(expr, dest, ctx) {
var resVar = acquireVar(), i = acquireVar(), len = acquireVar(),
cond = acquireVar(), curItem = acquireVar();
body.push(
resVar, '= [];',
i, '= 0;',
len, '=', ctx, '.length;',
'while(', i, '<', len, ') {',
curItem, '=', ctx, '[', i, '++];');
translateExpr(expr.arg, cond, curItem);
body.push(
convertToBool(expr.arg, cond), '&&', resVar, '.push(', curItem, ');',
'}',
dest, '=', resVar, ';');
releaseVars(resVar, i, len, curItem, cond);
}
function translatePosPredicate(item, dest, ctx) {
var arrayExpr = item.arg, fromIdx, toIdx;
if(arrayExpr.idx) {
var idx = acquireVar();
translateExpr(arrayExpr.idx, idx, ctx);
body.push(
idx, '< 0 && (', idx, '=', ctx, '.length +', idx, ');',
dest, '=', ctx, '[', idx, '] == null? [] : [', ctx, '[', idx, ']];');
releaseVars(idx);
return false;
}
else if(arrayExpr.fromIdx) {
if(arrayExpr.toIdx) {
translateExpr(arrayExpr.fromIdx, fromIdx = acquireVar(), ctx);
translateExpr(arrayExpr.toIdx, toIdx = acquireVar(), ctx);
body.push(dest, '=', ctx, '.slice(', fromIdx, ',', toIdx, ');');
releaseVars(fromIdx, toIdx);
}
else {
translateExpr(arrayExpr.fromIdx, fromIdx = acquireVar(), ctx);
body.push(dest, '=', ctx, '.slice(', fromIdx, ');');
releaseVars(fromIdx);
}
}
else {
translateExpr(arrayExpr.toIdx, toIdx = acquireVar(), ctx);
body.push(dest, '=', ctx, '.slice(0,', toIdx, ');');
releaseVars(toIdx);
}
}
function translateExpr(expr, dest, ctx) {
switch(expr.type) {
case SYNTAX.PATH:
translatePath(expr, dest, '[' + ctx + ']');
break;
case SYNTAX.COMPARISON_EXPR:
translateComparisonExpr(expr, dest, ctx);
break;
case SYNTAX.MATH_EXPR:
translateMathExpr(expr, dest, ctx);
break;
case SYNTAX.LOGICAL_EXPR:
translateLogicalExpr(expr, dest, ctx);
break;
case SYNTAX.UNARY_EXPR:
translateUnaryExpr(expr, dest, ctx);
break;
case SYNTAX.LITERAL:
var val = expr.val;
body.push(dest, '=', typeof val === 'string'? escapeStr(val) : val, ';');
break;
case SYNTAX.SUBST:
body.push(dest, '= subst.', expr.name, ';');
break;
}
}
function translateComparisonExpr(expr, dest, ctx) {
var val1 = acquireVar(), val2 = acquireVar(),
isVal1Array = acquireVar(), isVal2Array = acquireVar(),
i = acquireVar(), j = acquireVar(),
len1 = acquireVar(), len2 = acquireVar(),
leftArg = expr.args[0], rightArg = expr.args[1];
body.push(dest, '= false;');
translateExpr(leftArg, val1, ctx);
translateExpr(rightArg, val2, ctx);
var isLeftArgPath = leftArg.type === SYNTAX.PATH,
isRightArgLiteral = rightArg.type === SYNTAX.LITERAL;
body.push(isVal1Array, '=');
isLeftArgPath? body.push('true;') : body.push('isArr(', val1, ');');
body.push(isVal2Array, '=');
isRightArgLiteral? body.push('false;') : body.push('isArr(', val2, ');');
body.push(
'if(');
isLeftArgPath || body.push(isVal1Array, '&&');
body.push(val1, '.length === 1) {',
val1, '=', val1, '[0];',
isVal1Array, '= false;',
'}');
isRightArgLiteral || body.push(
'if(', isVal2Array, '&&', val2, '.length === 1) {',
val2, '=', val2, '[0];',
isVal2Array, '= false;',
'}');
body.push(i, '= 0;',
'if(', isVal1Array, ') {',
len1, '=', val1, '.length;');
if(!isRightArgLiteral) {
body.push(
'if(', isVal2Array, ') {',
len2, '=', val2, '.length;',
'while(', i, '<', len1, '&& !', dest, ') {',
j, '= 0;',
'while(', j, '<', len2, ') {');
writeCondition(expr.op, [val1, '[', i, ']'].join(''), [val2, '[', j, ']'].join(''));
body.push(
dest, '= true;',
'break;',
'}',
'++', j, ';',
'}',
'++', i, ';',
'}',
'}',
'else {');
}
body.push(
'while(', i, '<', len1, ') {');
writeCondition(expr.op, [val1, '[', i, ']'].join(''), val2);
body.push(
dest, '= true;',
'break;',
'}',
'++', i, ';',
'}');
isRightArgLiteral || body.push(
'}');
body.push(
'}');
if(!isRightArgLiteral) {
body.push(
'else if(', isVal2Array,') {',
len2, '=', val2, '.length;',
'while(', i, '<', len2, ') {');
writeCondition(expr.op, val1, [val2, '[', i, ']'].join(''));
body.push(
dest, '= true;',
'break;',
'}',
'++', i, ';',
'}',
'}');
}
body.push(
'else {',
dest, '=', binaryOperators[expr.op](val1, val2), ';',
'}');
releaseVars(val1, val2, isVal1Array, isVal2Array, i, j, len1, len2);
}
function writeCondition(op, val1Expr, val2Expr) {
body.push('if(', binaryOperators[op](val1Expr, val2Expr), ') {');
}
function translateLogicalExpr(expr, dest, ctx) {
var conditionVars = [],
args = expr.args, len = args.length,
i = 0, val;
body.push(dest, '= false;');
switch(expr.op) {
case '&&':
while(i < len) {
conditionVars.push(val = acquireVar());
translateExpr(args[i], val, ctx);
body.push('if(', convertToBool(args[i++], val), ') {');
}
body.push(dest, '= true;');
break;
case '||':
while(i < len) {
conditionVars.push(val = acquireVar());
translateExpr(args[i], val, ctx);
body.push(
'if(', convertToBool(args[i], val), ') {',
dest, '= true;',
'}');
if(i++ + 1 < len) {
body.push('else {');
}
}
--len;
break;
}
while(len--) {
body.push('}');
}
releaseVars.apply(null, conditionVars);
}
function translateMathExpr(expr, dest, ctx) {
var val1 = acquireVar(),
val2 = acquireVar(),
args = expr.args;
translateExpr(args[0], val1, ctx);
translateExpr(args[1], val2, ctx);
body.push(
dest, '=',
binaryOperators[expr.op](
convertToSingleValue(args[0], val1),
convertToSingleValue(args[1], val2)),
';');
releaseVars(val1, val2);
}
function translateUnaryExpr(expr, dest, ctx) {
var val = acquireVar(),
arg = expr.arg;
translateExpr(arg, val, ctx);
switch(expr.op) {
case '!':
body.push(dest, '= !', convertToBool(arg, val) + ';');
break;
case '-':
body.push(dest, '= -', convertToSingleValue(arg, val) + ';');
break;
}
releaseVars(val);
}
function translateConcatExpr(expr, dest, ctx) {
var argVars = [],
args = expr.args,
len = args.length,
i = 0;
while(i < len) {
argVars.push(acquireVar());
translatePath(args[i], argVars[i++], ctx);
}
body.push(dest, '= (', dest, '= []).concat.call(', dest, ',', argVars.join(','), ');');
releaseVars.apply(null, argVars);
}
function escapeStr(s) {
return '\'' + s.replace(/\\/g, '\\\\').replace(/'/g, '\\\'') + '\'';
}
function inlineAppendToArray(res, val, tmpArr, len) {
body.push(
'if(', val, '!= null) {',
'if(isArr(', val, ')) {');
if(tmpArr) {
body.push(
len, '> 1?');
inlinePushToArray(tmpArr, val);
body.push(
':');
}
body.push(
res, '=', res, '.concat(', val, ');',
'}',
'else {');
tmpArr && body.push(
'if(', tmpArr, '.length) {',
res, '=', res, '.concat.apply(', res, ',', tmpArr, ');',
tmpArr, '= [];',
'}');
inlinePushToArray(res, val);
body.push(
'}',
'}');
}
function inlinePushToArray(res, val) {
body.push(res, '.length?', res, '.push(', val, ') :', res, '[0] =', val);
}
function convertToBool(arg, varName) {
switch(arg.type) {
case SYNTAX.LOGICAL_EXPR:
return varName;
case SYNTAX.LITERAL:
return '!!' + varName;
case SYNTAX.PATH:
return varName + '.length > 0';
default:
return ['(typeof ', varName, '=== "boolean"?',
varName, ':',
'isArr(', varName, ')?', varName, '.length > 0 : !!', varName, ')'].join('');
}
}
function convertToSingleValue(arg, varName) {
switch(arg.type) {
case SYNTAX.LITERAL:
return varName;
case SYNTAX.PATH:
return varName + '[0]';
default:
return ['(isArr(', varName, ')?', varName, '[0] : ', varName, ')'].join('');
}
}
var binaryOperators = {
'===' : function(val1, val2) {
return val1 + '===' + val2;
},
'==' : function(val1, val2) {
return ['typeof ', val1, '=== "string" && typeof ', val2, '=== "string"?',
val1, '.toLowerCase() ===', val2, '.toLowerCase() :' +
val1, '==', val2].join('');
},
'>=' : function(val1, val2) {
return val1 + '>=' + val2;
},
'>' : function(val1, val2) {
return val1 + '>' + val2;
},
'<=' : function(val1, val2) {
return val1 + '<=' + val2;
},
'<' : function(val1, val2) {
return val1 + '<' + val2;
},
'!==' : function(val1, val2) {
return val1 + '!==' + val2;
},
'!=' : function(val1, val2) {
return val1 + '!=' + val2;
},
'^==' : function(val1, val2) {
return ['typeof ', val1, '=== "string" && typeof ', val2, '=== "string" &&',
val1, '.indexOf(', val2, ') === 0'].join('');
},
'^=' : function(val1, val2) {
return [val1, '!= null &&', val2, '!= null &&',
val1, '.toString().toLowerCase().indexOf(', val2, '.toString().toLowerCase()) === 0'].join('');
},
'$==' : function(val1, val2) {
return ['typeof ', val1, '=== "string" && typeof ', val2, '=== "string" &&',
val1, '.lastIndexOf(', val2, ') ===', val1, '.length -', val2, '.length'].join('');
},
'$=' : function(val1, val2) {
return [val1, '!= null &&', val2, '!= null &&',
'(', val1, '=', val1, '.toLowerCase().toString()).indexOf(',
'(', val2, '=', val2, '.toLowerCase().toLowerCase())) ===',
val1, '.length -', val2, '.length'].join('');
},
'*==' : function(val1, val2) {
return ['typeof ', val1, '=== "string" && typeof ', val2, '=== "string" &&',
val1, '.indexOf(', val2, ') > -1'].join('');
},
'*=' : function(val1, val2) {
return [val1, '!= null && ', val2, '!= null &&',
val1, '.toString().toLowerCase().indexOf(', val2, '.toString().toLowerCase()) > -1'].join('');
},
'+' : function(val1, val2) {
return val1 + '+' + val2;
},
'-' : function(val1, val2) {
return val1 + '-' + val2;
},
'*' : function(val1, val2) {
return val1 + '*' + val2;
},
'/' : function(val1, val2) {
return val1 + '/' + val2;
},
'%' : function(val1, val2) {
return val1 + '%' + val2;
}
};
return translate;
})();
function compile(path) {
return Function('data,subst', translate(parse(path)));
}
var cache = {},
cacheKeys = [],
params = {
cacheSize : 100
},
setParamsHooks = {
cacheSize : function(oldVal, newVal) {
if(newVal < oldVal && cacheKeys.length > newVal) {
var removedKeys = cacheKeys.splice(0, cacheKeys.length - newVal),
i = removedKeys.length;
while(i--) {
delete cache[removedKeys[i]];
}
}
}
};
var decl = function(path, ctx, substs) {
if(!cache[path]) {
cache[path] = compile(path);
if(cacheKeys.push(path) > params.cacheSize) {
delete cache[cacheKeys.shift()];
}
}
return cache[path](ctx, substs || {});
};
decl.version = '0.2.10';
decl.params = function(_params) {
if(!arguments.length) {
return params;
}
for(var name in _params) {
if(_params.hasOwnProperty(name)) {
setParamsHooks[name] && setParamsHooks[name](params[name], _params[name]);
params[name] = _params[name];
}
}
};
decl.compile = compile;
decl.apply = decl;
if(typeof exports === 'object') {
module.exports = decl;
}
else if(typeof modules === 'object') {
modules.define('jspath', function(provide) {
provide(decl);
});
}
else if(typeof define === 'function') {
define(function(require, exports, module) {
module.exports = decl;
});
}
else {
JSPath = decl;
}
})();