UNPKG

salt

Version:

State And Logic Traversal, for today's infinite-application.

1,591 lines (1,485 loc) 86.7 kB
/*! * Salt v0.5.5 * http://github.com/bemson/salt/ * * Dependencies: * - Panzer v0.3.13 / Bemi Faison (c) 2012 / MIT (http://github.com/bemson/Panzer/) * * Copyright, Bemi Faison * Released under the MIT License */ /* global define, require, module */ !function (inAMD, inCJS, Array, Math, Object, RegExp, scope, undefined) { // dependent module initializer function initSalt(require) { var Salt = ((inCJS || inAMD) ? require('panzer') : scope.Panzer).create(), rand_string = (Math.ceil(Math.random() * 5000) + 3000).toString(18), corePkgDef = Salt.pkg('core'), staticUnusedArray = [], staticUnusedObject = {}, sharedProxyStateGroupsMember = [], protoSlice = Array.prototype.slice, RxpProto = RegExp.prototype, i, // loop vars isArray = (typeof Array.isArray === 'function') ? Array.isArray : function (obj) { return obj instanceof Array; }, tokenPrefix = '@', defaultPermissions = {world: true, owner: true, sub: true, self: true}, redirectFlag = 1, // regexps r_queryIsTokenized = new RegExp('\\.|\\||^' + tokenPrefix + '|\\|' + tokenPrefix), r_validAbsolutePath = /^\/\/(?:\w+\/)+/, r_trimSlashes = /^\/+|\/+$/g, r_hasNonAlphanumericCharacter = /\W/, r_hasAlphanumericCharacter = /\w/, r_rxpIsForPath = /^\/.*\/.*\//, traversalCallbackOrder = { _on: 0, _in: 1, _out: 2, _over: 3, _bover: 4, '0': 'on', '1': 'in', '2': 'out', '3': 'over', '4': 'bover' }, activeSalts = [], reservedQueryTokens = { 'null': { f: 0, i: 0 }, program: { f: 0, i: 1 }, root: { f: function (node) { return node.rootIndex; } }, parent: { f: function (node) { return node.parentIndex; } }, child: { f: function (node) { return node.firstChildIndex; } }, next: { f: function (node) { return node.nextIndex; } }, previous: { f: function (node) { return node.previousIndex; } }, oldest: { f: function (node, nodes, tokenName) { var parentNode = nodes[node.parentIndex]; if (parentNode) { return parentNode[((tokenName.charAt(0) === 'y') ? 'first' : 'last') + 'ChildIndex']; } return -1; } }, self: { f: function (node) { return node.index; } } }, criteriaCache = {}, // in reverse-order of complexity criteriaKeys = [ 'has', 'within', 'on', 'from', 'is' ], criteriaKeysLength = criteriaKeys.length, coreTags = { // Specifies when a state is the base for rooted queries. _root: function (tagName, exists, tags, node, parentNode, pkg, idx) { if (idx < 2 || (exists && tags._root)) { node.rootIndex = idx; } else { node.rootIndex = parentNode.rootIndex; } }, // Specifies when a state may not be exited with external calls. _restrict: function (tagName, exists, tags, node, parentNode, pkg, idx) { var prop = tagName.substr(1); if (exists && tags[tagName]) { node[prop] = idx; } else if (parentNode) { node[prop] = parentNode[prop]; } else { node[prop] = -1; } }, // Specify cascading group identifiers _group: function (tagName, exists, tags, node, parentNode) { var tagValue, groups, groupIdx, groupName ; if (parentNode) { node.groups = parentNode.groups; node.cGrps = parentNode.cGrps; } if (exists) { groups = merge(node.groups); tagValue = tags[tagName]; if (!isArray(tagValue)) { tagValue = [tagValue]; } groupIdx = tagValue.length; while (groupIdx--) { groupName = tagValue[groupIdx]; if (typeof groupName === 'string' && (groupName = groupName.trim().toLowerCase()) && !defaultPermissions.hasOwnProperty(groupName) ) { groups[groupName] = true; } } node.groups = groups; } node.cGrps = Object.keys(node.groups); }, // Specify cascading permissions when a state is entered and exited _perms: function (tagName, exists, tags, node, parentNode, pkg, idx) { node.perms = 0; if (exists || idx === 1) { // when present or on first node node.perms = node.lp = (exists) ? perms_parse(tags[tagName], parentNode.lp) : merge(defaultPermissions); } else if (parentNode) { // pass-thru parent permmissions data node.lp = parentNode.lp; } else { // place defaults at bottom of stack node.lp = defaultPermissions; } }, // Defines the path to update an owning salt - if any. _owner: function (tagName, exists, tags, node, parentNode, pkg) { node.oGate = 0; if (exists) { pkg.ownable = node.oGate = 1; node.ping = tags._owner; } else if (parentNode) { node.ping = parentNode.ping; } else { node.ping = -1; } }, // Define alias for this state, to use in queries. _alias: function (tagName, exists, tags, node, parentNode, pkg, idx) { if ( exists && tags._alias && typeof tags._alias === 'string' && !r_queryIsTokenized.test(tags._alias) && r_hasAlphanumericCharacter.test(tags._alias) ) { pkg.tokens[tags._alias] = { i: idx, f: 0 }; node.alias = tags._alias; } else { node.alias = ''; } }, // Define criteria for preserving instances created while traversing this branch. _capture: function (tagName, exists, tags, node, parentNode, pkg) { if (exists) { node.caps = subs_sanitizeCriteria(tags._capture); } else if (parentNode) { node.caps = parentNode.caps; } else { node.caps = 0; pkg.caps = []; } }, // Define data names and values for a branch. // // _data: 'foo' // _data: ['foo'] // _data: {foo: 'bar'} // _data: ['foo', {zoo:'baz'}] _data: function (tagName, exists, tags, node) { var cfgs = {}, dataAry, dataIdx = 0, dataLn, data, typeofData, key ; // init dtos property to collect data tracking objects node.dcfgs = []; if (exists) { dataAry = isArray(tags._data) ? tags._data : [tags._data]; dataLn = dataAry.length; for (; dataIdx < dataLn; dataIdx++) { data = dataAry[dataIdx]; typeofData = typeof data; if (typeofData === 'string' && data) { cfgs[data] = { use: 0, name: data, value: undefined }; } if (typeofData === 'object' && data) { for (key in data) { if (data.hasOwnProperty(key)) { cfgs[key] = { use: 1, name: key, value: data[key] }; } } } } for (key in cfgs) { if (cfgs.hasOwnProperty(key)) { node.dcfgs[node.dcfgs.length] = cfgs[key]; } } } }, // Specifies a branch to navigate after targeting this state. _sequence: function (tagName, exists, tags, node, parentNode) { if (exists) { // set walk to a new or copied array, based on the booly value node.seq = tags[tagName] ? [] : 0; // set last walk to this state's walk node.lastWalk = node.seq; } else { // set walk property to nil node.seq = 0; // pass thru the last walk array defined - if any if (parentNode) { node.lastWalk = parentNode.lastWalk; } else { node.lastWalk = 0; } // if there is a lastWalk array... if (node.lastWalk) { // add this node's index to the array node.lastWalk[node.lastWalk.length] = node.index; } } }, // Specifies when a paused state will prevent parent salt's from completing their navigation. _pins: function (tagName, exists, tags, node, parentNode) { if (exists) { node.pins = !!tags._pins; } else if (parentNode) { node.pins = parentNode.pins; } else { node.pins = true; } }, /* Defines one of five callback methods to invoke. */ _on: function (tagName, exists, tags, node) { var tagValue = tags[tagName] ; if (exists && typeof tagValue === 'function') { node.fncs[traversalCallbackOrder[tagName]] = tagValue; } }, _tail: function (tagName, exists, tags, node, parentNode, pkg, idx) { var tagValue; // start with default node.tail = -1; // capture paired value, set default, or use parent if (exists) { tagValue = tags[tagName]; if (tagValue === true) { // target self node.tail = idx; } else if (tagValue !== false) { // don't tail when false node.tail = tagValue; } } else if (parentNode) { node.tail = parentNode.tail; } }, // Specifies when a branch should be invisible to external queries _conceal: function (tagName, exists, tags, node, parentNode, pkg, idx) { node.conceal = -1; if (exists && idx > 1) { if (tags._conceal) { node.conceal = idx; } } else if (parentNode) { node.conceal = parentNode.conceal; } } }, // tags that depend on other tags or require cleanup corePostTags = { // Clean up lastWalk flag _sequence: function (tagName, exists, tags, node) { delete node.lastWalk; }, // ensure node alias was not overridden _alias: function (tagName, exists, tags, node, parentNode, pkg, idx) { var alias = node.alias; if (idx === 0) { node.alias = 'null'; } else if (idx === 1) { node.alias = 'program'; } else if (alias && pkg.tokens[alias].i !== idx) { node.alias = ''; } }, // Process callbacks that are redirects _on: function (tagName, exists, tags, node, parentNode, pkg, idx) { var tgtIdx = -1, phase, typeofTagValue, tagValue, useTarget = 0 ; if (exists && (typeofTagValue = typeof (tagValue = tags[tagName])) !== 'function') { if (typeofTagValue === 'string' && tagValue.length) { if (tagValue.charAt(0) === '>') { useTarget = 1; } tgtIdx = pkg.indexOf(tagValue, node); } else if (typeofTagValue === 'number' && pkg.nodes[tagValue]) { tgtIdx = tagValue; } if (~tgtIdx && (tagName !== '_on' || tgtIdx !== idx)) { phase = traversalCallbackOrder[tagName]; node.reds[phase] = [useTarget, tgtIdx]; node.fncs[phase] = redirectFlag; } } }, // specify delay when entering on traversal _wait: function (tagName, exists, tags, node, parentNode, pkg) { var tagValue = tags[tagName], targetIndex, firstIndexType, waitParamsLn, waitParams = staticUnusedArray ; node.delay = 0; if (exists && tagValue !== false) { if (Array.isArray(tagValue)) { waitParams = tagValue; } else if (tagValue !== true) { waitParams = [tagValue]; } // vet wait query, if present waitParamsLn = waitParams.length; firstIndexType = typeof waitParams[0]; // exit when only arg is not a number if (waitParamsLn === 1 && firstIndexType !== 'number') { return; } if (waitParamsLn > 1 && firstIndexType !== 'function') { if (~(targetIndex = pkg.indexOf(waitParams[0]), node)) { // resolve query waitParams[0] = targetIndex; } else { // exit when first arg is not a valid query return; } } node.delay = waitParams; } }, // specify next node when entering on traversal _next: function (tagName, exists, tags, node, parentNode, pkg) { var tagValue, targetIndex ; node.nxt = -1; node.nxtc = 0; if (exists) { if (typeof (tagValue = tags[tagName]) === 'string' && tagValue.charAt(0) === '>') { // flag that this will clear existing waypoints node.nxtc = 1; } if (~(targetIndex = pkg.indexOf(tagValue, node))) { // capture index when valid node.nxt = targetIndex; } } }, // resolve and keep valid tail targets _tail: function (tagName, exists, tags, node, parentNode, pkg, idx) { var query = node.tail, targetIndex ; // resolve query node.tail = targetIndex = pkg.indexOf(query, node); // ensure resolved target is not pointing to self if (targetIndex === idx) { node.tail = -1; } }, _perms: function (tagName, exists, tags, node) { delete node.lp; } }, // actions to take when entering and exiting a node nodeScopeActions = { // ping owner 0: function (node, pkg) { // notify owner before entering and after exiting this node if (node.oGate && ~node.ping) { pkg.pingOwner(node.ping); } }, // scope data 1: function (node, pkg, add) { var data = pkg.proxy.data, dataCfgs = node.dcfgs, dataCfgLn = dataCfgs.length, dataCfgIdx = 0, dataCfg, dataName, dataTrackingObject, scopeAction ; // exit when there are no configurations for this node if (!node.dcfgs.length) { return; } // define scoping routine if (add) { // scope new value to stack - set value from config scopeAction = function () { // capture current value in stack (if any) if (data.hasOwnProperty(dataName)) { // capture current value in stack dataTrackingObject.stack.unshift(data[dataName]); } if (dataCfg.use) { // set key to value from config data[dataName] = dataCfg.value; } else { // set key to last value or undefined (by default) data[dataName] = dataTrackingObject.stack[0]; } }; } else { // set value form stack and remove scopeAction = function () { if (dataTrackingObject.stack.length) { // use and remove value from stack data[dataName] = dataTrackingObject.stack.shift(); } else { // remove tracking object and data member delete pkg.dtos[dataName]; delete data[dataName]; } }; } for (; dataCfgIdx < dataCfgLn; dataCfgIdx++) { dataCfg = dataCfgs[dataCfgIdx]; dataName = dataCfg.name; dataTrackingObject = pkg.getDTO(dataName); scopeAction(); } }, // permissions stack 2: function (node, pkg, add) { shared_nodeStackHandler(pkg.perms, node.perms, add); // copy to public state.perms pkg.proxy.state.perms = merge(pkg.perms[0]); }, // capture criteria stack 3: function (node, pkg, add) { shared_nodeStackHandler(pkg.caps, node.caps, add); // if exiting program node if (node.index === 1 && !add) { // clear sub-instances pkg.bin = {}; pkg.tin = {}; } } }, nodeScopeActionsLength = 4, // import resolution helpers import_pkgCnt, import_tagKeyTests, // cache of core tag keys coreTagKeys = [], coreTagKeyCount, // cache of core post tag keys corePostTagKeys = [], corePostTagKeyCount ; Salt.version = '0.5.5'; // define remaining core tags and share tag initializers /* _ingress: Defines a state that must be targeted before it's descendents. */ coreTags._ingress = coreTags._restrict; coreTags._in = coreTags._out = coreTags._over = coreTags._bover = coreTags._on; corePostTags._in = corePostTags._out = corePostTags._over = corePostTags._bover = corePostTags._on; // get core tag keys for (i in coreTags) { if (coreTags.hasOwnProperty(i)) { coreTagKeys[coreTagKeys.length] = i; } } coreTagKeyCount = coreTagKeys.length; // get post core tag keys for (i in corePostTags) { if (corePostTags.hasOwnProperty(i)) { corePostTagKeys[corePostTagKeys.length] = i; } } corePostTagKeyCount = corePostTagKeys.length; // reuse dynamic resolution for other tokens reservedQueryTokens['.'] = reservedQueryTokens.self; reservedQueryTokens['..'] = reservedQueryTokens.parent; reservedQueryTokens.youngest = reservedQueryTokens.oldest; function isSalt(thing) { return thing instanceof Salt; } // add or remove from stack when there is an item function shared_nodeStackHandler(stack, item, add) { if (item) { if (add) { stack.unshift(item); } else { stack.shift(); } } } function sharedNullFunction() {} function subs_addInsts (collection, subInsts, pkg) { var node = pkg.nodes[pkg.tank.currentIndex], i = 0, subInst, subId, totalAdded = 0 ; for (; subInst = subInsts[i]; i++) { subId = subInst.tank.id; if (!collection.hasOwnProperty(subId)) { totalAdded++; collection[subId] = { inst: subInst, cap: node }; } } return totalAdded; } function subs_removeInsts (collection, subInsts) { var i = subInsts.length, subInst, subId, totalRemoved = 0 ; while (i--) { subInst = subInsts[i]; subId = subInst.tank.id; if (collection.hasOwnProperty(subId)) { totalRemoved++; delete collection[subId]; } } return totalRemoved; } function subs_getInsts (collection, criteria) { var criteriaKey, criteriaKeyIdx, criteriaOption, criteriaOptions, criteriaOptionIdx, criteriaOptionsLength, subSet, subSetId, matches = [] ; nextSubset: for (subSetId in collection) { if (collection.hasOwnProperty(subSetId)) { // get subSet to test subSet = collection[subSetId]; // reset criteria key index criteriaKeyIdx = criteriaKeysLength; nextCriteria: while (criteriaKeyIdx--) { // get the criteria key criteriaKey = criteriaKeys[criteriaKeyIdx]; // setup criteria option loop vars criteriaOptions = criteria[criteriaKey]; criteriaOptionsLength = criteriaOptions.length; // if this key has options... if (criteriaOptionsLength) { for (criteriaOptionIdx = 0; criteriaOptionIdx < criteriaOptionsLength; criteriaOptionIdx++) { criteriaOption = criteriaOptions[criteriaOptionIdx]; // if this criteria option has not not compiled... if (criteriaOptionIdx >= criteriaOptions.made) { // capture and replace option at index with compiled version criteriaOption = criteriaOptions[criteriaOptionIdx] = subs_compileCriteriaOption(criteriaKey, criteriaOption, typeof criteriaOption); // flag that this option is now compiled criteriaOptions.made++; } // test result of compiled criteria option's function // pass in the subset, and criteria object if (criteriaOption.f(subSet, criteriaOption)) { // add subset instance to matches matches[matches.length] = subSet.inst; // continue to the next criteria, since it's been satisfied continue nextCriteria; } } // since no options of this criteria were satisfied, skip to the next subSet in the collection continue nextSubset; } } } } return matches; } function subs_sanitizeCriteria(raw) { var criteria, criteriaKey, criteriaKeyIdx, criteriaCacheId, rxpOldToJSON, typeofRaw, isRegExp, specifiesCriteria ; // if raw is null or undefined... if (raw === null || raw === undefined) { // set to false (capture nothing) raw = false; } // get type or raw value typeofRaw = typeof raw; // if not an object, or does not have "is" criteria... if (typeofRaw !== 'object' || !raw.hasOwnProperty('is')) { if (RxpProto.hasOwnProperty('toJSON')) { // capture existing .toJSON() method rxpOldToJSON = RxpProto.toJSON; } // augment .toJSON() for any array's with nested RegExp's RxpProto.toJSON = subs_stainRxpInJSON; // get cache id for this criteria - prefix to avoid native collisiions criteriaCacheId = 'c' + JSON.stringify(raw); // reset .toJSON() for RegExp's if (rxpOldToJSON) { RxpProto.toJSON = rxpOldToJSON; } else { delete RxpProto.toJSON; } if (criteriaCache.hasOwnProperty(criteriaCacheId)) { // exit when this criteria has already been sanitized return criteriaCache[criteriaCacheId]; } } // define criteria to return criteria = {buffer: -1}; // handle short-forms if (typeofRaw === 'boolean' && raw) { // `true` means match all sub-instances at or within this index criteria.on = ['/']; criteria.on.made = 0; } else if ( // calculate here - only when necessary (isRegExp = raw instanceof RegExp) || (typeofRaw === 'string' && raw) || (typeofRaw === 'number' && raw === ~~raw) ) { criteria.on = [raw]; criteria.on.made = 0; } else if (typeofRaw === 'object' && !isRegExp) { // sanitize known criteria from the raw object criteriaKeyIdx = criteriaKeysLength; while(criteriaKeyIdx--) { criteriaKey = criteriaKeys[criteriaKeyIdx]; if (raw.hasOwnProperty(criteriaKey)) { // extract object criteria, as a set of options (i.e., an array) criteria[criteriaKey] = isArray(raw[criteriaKey]) ? protoSlice.call(raw[criteriaKey]) : [raw[criteriaKey]]; criteria[criteriaKey].made = 0; specifiesCriteria = 1; } } } // ensure all criteria are present criteriaKeyIdx = criteriaKeysLength; while (criteriaKeyIdx--) { criteriaKey = criteriaKeys[criteriaKeyIdx]; if (!criteria.hasOwnProperty(criteriaKey)) { // use default empty option list (no need to track compiled options) criteria[criteriaKey] = staticUnusedArray; } } // sanitize buffer if (raw.hasOwnProperty('buffer')) { // capture all, when only option is buffer if (!specifiesCriteria) { criteria.on = ['/']; criteria.on.made = 0; } if (raw.buffer !== -1) { criteria.buffer = raw.buffer ? 1 : 0; } } if (criteriaCacheId) { // add sanitized criteria to cache criteriaCache[criteriaCacheId] = criteria; } return criteria; } // pre-cache the criteria compilation of `true` subs_sanitizeCriteria(true); // compilation will usually involve searching a set or string with a regexp or string function subs_compileCriteriaOption(criteriaKey, value, typeofValue) { var isRegExp = value instanceof RegExp, option = { v: value, f: sharedNullFunction } ; if (criteriaKey === 'has') { // compile "has" criteria if (isRegExp) { option.f = subs_filter_searchSetRxp; option.g = subs_filter_getSubInstMember; if (r_rxpIsForPath.test(value)) { option.m = 'pAry'; } else { option.m = 'sAry'; } } else if (value && typeofValue === 'string') { option.g = subs_filter_getSubInstMember; if (~value.indexOf('/')) { option.f = subs_filter_searchSet; option.m = 'pAry'; // wrap in slashes to match whole sub-paths if (option.v.charAt(0) !== '/') { option.v = '/' + option.v; } if (option.v.substr(-1) !== '/') { option.v += '/'; } } else { option.f = subs_filter_matchSet; option.m = 'sAry'; } } else if ( typeofValue === 'number' && value >= 0 && value === ~~value ) { option.f = subs_filter_has_index; } } else if (criteriaKey === 'from') { // compile "from" criteria if (isRegExp) { if (r_rxpIsForPath.test(value)) { option.f = subs_filter_searchRxp; option.g = subs_filter_from_path; } else { option.f = subs_filter_searchSetRxp; option.g = subs_filter_from_pathParts; } } else if (value && typeofValue === 'string') { if (~value.indexOf('/')) { option.f = subs_filter_search; option.g = subs_filter_from_path; // wrap in slashes to match whole sub-paths if (option.v.charAt(0) !== '/') { option.v = '/' + option.v; } if (option.v.substr(-1) !== '/') { option.v += '/'; } } else { option.f = subs_filter_matchSet; option.g = subs_filter_from_pathParts; } } else if ( typeofValue === 'number' && value >= 0 && value === ~~value ) { option.f = subs_filter_match; option.g = subs_filter_from_index; } } else if (criteriaKey === 'is') { // compile "is" criteria option.f = subs_filter_is; } else if (criteriaKey === 'on') { // compile "on" criteria if (isRegExp) { option.f = subs_filter_searchRxp; if (r_rxpIsForPath.test(value)) { option.g = subs_filter_getStateProperty; option.m = 'path'; } else { option.g = subs_filter_getStateProperty; option.m = 'name'; } } else if (value && typeofValue === 'string') { if (~value.indexOf('/')) { option.f = subs_filter_search; option.g = subs_filter_getStateProperty; option.m = 'path'; // wrap in slashes to match whole sub-paths if (option.v.charAt(0) !== '/') { option.v = '/' + option.v; } if (option.v.substr(-1) !== '/') { option.v += '/'; } } else { option.f = subs_filter_match; option.g = subs_filter_getStateProperty; option.m = 'name'; } } else if ( typeofValue === 'number' && value >= 0 && value === ~~value ) { option.f = subs_filter_match; option.g = subs_filter_getStateProperty; option.m = 'index'; } } else if (criteriaKey === 'within') { // compile "within" criteria if (isRegExp) { if (r_rxpIsForPath.test(value)) { option.f = subs_filter_searchRxp; option.g = subs_filter_within_path; } else { option.f = subs_filter_searchSetRxp; option.g = subs_filter_within_pathParts; } } else if (value && typeofValue === 'string') { if (~value.indexOf('/')) { option.f = subs_filter_search; option.g = subs_filter_within_path; // wrap in slashes to match whole sub-paths if (option.v.charAt(0) !== '/') { option.v = '/' + option.v; } if (option.v.substr(-1) !== '/') { option.v += '/'; } } else { option.f = subs_filter_matchSet; option.g = subs_filter_within_pathParts; } } else if ( typeofValue === 'number' && value > -1 && value === ~~value ) { option.f = subs_filter_within_index; } } return option; } // sub-instance helper functions // prepend regex with random string function subs_stainRxpInJSON() { return rand_string + this; } // generic needle search a string // expects params.v to be the needle // expects params.g to be a function that gets the haystack function subs_filter_search(subSet, params) { return ~params.g(subSet, params).indexOf(params.v); } // generic regexp search a string // expects params.v to be the regexp // expects params.g to be a function that gets the string function subs_filter_searchRxp(subSet, params) { return params.v.test(params.g(subSet, params)); } // generic regexp search over set of strings // expects params.v to be the regexp // expects params.g to be a function that gets the set function subs_filter_searchSetRxp(subSet, params) { var set = params.g(subSet, params), setIdx = set.length, rxp = params.v ; while (setIdx--) { if (rxp.test(set[setIdx])) { return 1; } } } // generic string set search // expects params.v to be the needle // expects params.g to be a function that gets the set function subs_filter_searchSet(subSet, params) { var set = params.g(subSet, params), setIdx = set.length, hay, match = params.v ; while (setIdx--) { hay = set[setIdx]; if (~hay.indexOf(match)) { return 1; } } } // generic match between values // expects params.v to be the match // expects params.g to be a function that gets the comparison value function subs_filter_match(subSet, params) { return params.v === params.g(subSet, params); } // generic string set match // expects params.v to be the match // expects params.g to be a function that gets the set function subs_filter_matchSet(subSet, params) { var set = params.g(subSet, params), setIdx = set.length, match = params.v ; while (setIdx--) { if (set[setIdx] === match) { return 1; } } } // returns property of subInst // expects params.m to be a member of the sub-instance function subs_filter_getSubInstMember(subSet, params) { return subSet.inst[params.m]; } // returns property of subInst // expects params.m to be a member of the sub-instance function subs_filter_getStateProperty(subSet, params) { var subInst = subSet.inst; return subInst.nodes[subInst.tank.currentIndex][params.m]; } // criteria "is" filter functions function subs_filter_is(subSet, params) { // the sub-instance must be sourced by the parameterized value return subSet.inst.nodes[1].value === params.v; } // criteria "has" filter functions // returns true when params.v is within the given index function subs_filter_has_index(subSet, params) { return params.v < subSet.inst.nodes.length; } // criteria "from" filter functions // returns subSet capture path function subs_filter_from_path(subSet) { return subSet.cap.path; } // returns subSet capture path as an array of states // returns an empty array when the path is the null or program state function subs_filter_from_pathParts(subSet) { var captureNode = subSet.cap; if (captureNode.index > 1) { return captureNode.path.slice(2, -1).split('/'); } return staticUnusedArray; } // returns index that sub-instance was capture function subs_filter_from_index(subSet) { return subSet.cap.index; } // returns path of current state's parent function subs_filter_within_path(subSet) { var subInst = subSet.inst, currentNode = subInst.nodes[subInst.tank.currentIndex] ; if (~currentNode.parentIndex) { return subInst.nodes[currentNode.parentIndex].path; } return ''; } // returns path parts of current state's parent path function subs_filter_within_pathParts(subSet) { var subInst = subSet.inst, currentNode = subInst.nodes[subInst.tank.currentIndex] ; if (currentNode.parentIndex > 1) { return subInst.nodes[currentNode.parentIndex].path.slice(2, -1).split('/'); } return staticUnusedArray; } // returns true when the sub-instance's current node is within the given state index function subs_filter_within_index(subSet, params) { var subInst = subSet.inst, nodes = subInst.nodes, currentNode = nodes[subInst.tank.currentIndex], targetNode = nodes[params.v] ; if (targetNode) { return currentNode.within(targetNode); } } // shallow object merge function mix(base) { var argumentIdx = 1, source, member ; for (; source = arguments[argumentIdx]; argumentIdx++) { for (member in source) { if (source.hasOwnProperty(member)) { base[member] = source[member]; } } } return base; } // deep object copy function merge() { var argumentIdx = 0, obj = {}, source, member, value ; for (; source = arguments[argumentIdx]; argumentIdx++) { for (member in source) { if (source.hasOwnProperty(member)) { value = source[member]; if (value !== null && typeof value === 'object') { obj[member] = merge(value); } else { obj[member] = value; } } } } return obj; } function perms_parse(option, lastPerms) { var perms, deny, typeofOption = typeof option, optionIdx, optionLength, key, self_perm = 'self' ; if (typeofOption === 'object' && isArray(option)) { optionLength = option.length; for (optionIdx = 0; optionIdx < optionLength; optionIdx++) { lastPerms = perms_parse(option[optionIdx], lastPerms); } } else if ( typeofOption === 'string' || typeofOption === 'object' || typeofOption === 'boolean' ) { perms = merge(lastPerms); if (typeofOption === 'string' && option) { option = option.toLowerCase(); deny = option.charAt(0) === '!'; if (deny) { option = option.substr(1); } if (option !== self_perm) { perms[option] = !deny; } } else if (typeofOption === 'boolean') { for (key in perms) { if (key !== self_perm && perms.hasOwnProperty(key)) { perms[key] = option; } } } else { // lowercase all options lastPerms = {}; for (key in option) { if (option.hasOwnProperty(key) && key.charAt(0) !== '!') { lastPerms[key.toLowerCase()] = option[key]; } } // ensure self is true mix(perms, lastPerms, {self: true}); } return perms; } // return new or old lastPerms return lastPerms; } // gets tag key tests for parsing state tags function import_cacheTagKeyTests () { var pkgNames = Salt.pkg(); // only compile if the number of packages has changed // NOTE: this approach is performant but fails if attrkeys are changed per package if (pkgNames.length !== import_pkgCnt) { // compile list of attribute keys from all packages if ((import_pkgCnt = pkgNames.length) === 1) { import_tagKeyTests = [corePkgDef.attrKey]; } else { import_tagKeyTests = pkgNames .map(import_cacheTagKeyTests_map) .filter(import_cacheTagKeyTests_filter) ; } // cache type of attribute test import_tagKeyTests = import_tagKeyTests .map(import_cacheTagKeyTests_precalc); } } function import_cacheTagKeyTests_map ( pkgName ) { return Salt.pkg(pkgName).attrKey; } function import_cacheTagKeyTests_filter ( tagKeyTest ) { return tagKeyTest; } function import_cacheTagKeyTests_precalc ( tagKeyTest ) { var typeMap = { // flag when the test is a function f:0, // flag when the test is a regular-expression r:0 }; if (typeof tagKeyTest === 'function') { typeMap.f = tagKeyTest; } else { typeMap.r = tagKeyTest; } return typeMap; } // flag when the given state member is a state (otherwise a tag) function import_isState ( name, value ) { var i = 0, tagKeyTest ; // all key tests must fail, in order to be a state for (; tagKeyTest = import_tagKeyTests[i]; i++) { if ( ( tagKeyTest.r && tagKeyTest.r.test(name) ) || ( tagKeyTest.f && tagKeyTest.f(name, value) ) ) { return 0; } } return 1; } // ensure the given value is a state object function import_mergeStates_convertToObject( state ) { var typeofState = typeof state; if (typeofState === 'string') { return { _import: state }; } else if (typeofState === 'function') { return { _on: state }; } else if (typeofState === 'object') { return state; } return {}; } function import_mergeStates ( baseState, sourceState, mergedState) { var merged = mergedState || {}, base = import_mergeStates_convertToObject(baseState), source = import_mergeStates_convertToObject(sourceState), baseKeys = Object.keys(base), sourceKeys = Object.keys(source), keyIsInSource, idx = 0, key ; // import base keys and merge states also in source for (; key = baseKeys[idx]; idx++) { keyIsInSource = source.hasOwnProperty(key); if (import_isState(key)) { if (keyIsInSource) { merged[key] = import_mergeStates(base[key], source[key], {}); } else { merged[key] = base[key]; } } else if (keyIsInSource && key !== '_import') { // override all but the _import tag merged[key] = source[key]; } else { merged[key] = base[key]; } } // append unique source keys that are not "_import" for (idx = 0; key = sourceKeys[idx]; idx++) { if (!base.hasOwnProperty(key) && key !== '_import') { merged[key] = source[key]; } } return merged; } function import_getStateByAbsolutePath( path, program ) { var resolvedState = program, parts = path.slice(2, -1).split('/'), partsLn = parts.length, partIdx = 0, partialPath ; for (; partIdx < partsLn; partIdx++) { partialPath = parts[partIdx]; if ( resolvedState.hasOwnProperty(partialPath) && import_isState(partialPath, resolvedState[partialPath]) ) { resolvedState = resolvedState[partialPath]; } else { return; } } return resolvedState; } function import_resolveBase( sourceState, program, importedPaths ) { var resolvedState, baseState, sourceStateType = typeof sourceState, importTagValue, importTagValueType, importPath ; if (sourceStateType === 'string') { importPath = sourceState; } else if ( sourceStateType === 'object' && sourceState !== null && sourceState.hasOwnProperty('_import') ) { importTagValue = sourceState._import; importTagValueType = typeof importTagValue; // determine whether we're importing a path or an (external) object if (importTagValueType === 'object') { if (importTagValue instanceof Salt) { importTagValue = corePkgDef(importTagValue).nodes[1].value; } baseState = corePkgDef.prepNode(importTagValue, importTagValue) || importTagValue; } else if (importTagValueType === 'function') { // convert functions to states baseState = {_on: importTagValue}; } else { importPath = importTagValue; } } // verify and resolve new program path if ( importPath && !importedPaths.hasOwnProperty(importPath) && r_validAbsolutePath.test(importPath) ) { baseState = import_getStateByAbsolutePath(importPath, program); } // merge resolved state if (baseState) { if (sourceStateType === 'string') { // use base state directly resolvedState = import_mergeStates_convertToObject(baseState); } else if (sourceStateType === 'object') { // merge base state with this state resolvedState = import_mergeStates(baseState, sourceState); } } return resolvedState; } // define package statics mix(corePkgDef, { // collection of active package-trees - exposed to support package integration actives: [], // configure panzer parsing and initialization // pattern for identifying tag keys attrKey: /^_/, // pattern for identifying invalid state names badKey: /^[^a-zA-Z]|\||\/|\.|^toString$/, // tree preprocessor prepTree: function (orig) { import_cacheTagKeyTests(); if (isSalt(orig)) { // when given a Salt instance, return the original instance's program return corePkgDef(orig).nodes[1].value; } }, // node preprocessor prepNode: function ( state, program ) { var finalState, tmpState = state ; // resolve all top-level imports for this state - avoid shallow-recursion // this is similar to sub-classing while (tmpState = import_resolveBase(tmpState, program, {})) { finalState = tmpState; } return finalState; }, // initialize the package instance with custom properties // only argument is the object passed after the program when calling "new Salt(program, extraArg)" init: function () { var pkg = this, activeSalt = activeSalts[0], nodes = pkg.nodes, nodeCount = nodes.length, i, j, node, parentNode, tagName ; // init sub-instances hashes pkg.bin = {}; pkg.tin = {}; // init groups pkg.groups = staticUnusedObject; // sub-instance "has" criteria search helpers pkg.nStr = pkg.pStr = pkg.lastPing = // use same value for lastPingId '|'; pkg.sAry = []; pkg.pAry = []; // define initial vars member pkg.vars = {}; // collection of custom query tokens pkg.tokens = {}; // collection of custom callback queries pkg.cq = {}; // collection of arguments for traversal functions pkg.args = []; // collection of node calls made while traversing pkg.calls = []; // collection of nodes targeted and reached while traversing pkg.trail = []; // permissions stack - begin with default perms pkg.perms = [defaultPermissions]; // state index to add to trail at end of traversal/resume pkg.tgtTrail = -1; // collection of declared variable tracking objects pkg.dtos = {}; // init delay timer, function, and args pkg.waitTimer = pkg.waitFnc = pkg.waitArgs = 0; // collection of cached values pkg.cache = { // token query cache indexOf: {} }; // indicates when this salt is in the stack of navigating salts pkg.active = 0; // flag when being invoked by a blessed function pkg.blessed = 0; // init index of node paths pkg.nids = {}; // the number of child salts fired by this salt's program functions pkg.pinned = 0; // collection of parent salt references pkg.pinning = {}; // count of parent salt references pkg.pinCnt = 0; // collection of targeted nodes pkg.targets = []; // identify the initial phase for this salt, 0 by default pkg.phase = 0; // set owner permission and assignment defaults pkg.owner = pkg.ownable = 0; // set name of first node pkg.nodes[0].name = '_null'; // set name of second node pkg.nodes[1].name = '_program'; // initialize nodes from first to last for (i = 0; i < nodeCount; i++) { node = nodes[i]; parentNode = nodes[node.parentIndex]; // index this node path pkg.nids[node.path] = i; if (i > 1) { // add to delimited string of names (for sub-instances "has" matching) pkg.nStr += node.name + '|'; pkg.sAry[pkg.sAry.length] = node.name; } // add to delimited string of paths (for sub-instances "has" matching) pkg.pStr += node.path + '|'; pkg.pAry[pkg.pAry.length] = node.path; // prep for tag compilation node.pkg = pkg; node.fncs = [0,0,0,0,0]; node.reds = []; // run core tags j = coreTagKeyCount; while (j--) { tagName = coreTagKeys[j]; coreTags[tagName](tagName, node.attrs.hasOwnProperty(tagName), node.attrs, node, parentNode, pkg, i); } // if there is no _on[0] function and this node's value is a function... if (!node.fncs[0] && typeof node.value === 'function') { // use as the _on[0] traversal function node.fncs[0] = node.value; } } // run post core tags for each node i = nodeCount; // order matters less on cleanup while (i--) { node = nodes[i]; parentNode = nodes[node.parentIndex]; j = corePostTagKeyCount; while (j--) { tagName = corePostTagKeys[j]; corePostTags[tagName](tagName, node.attrs.hasOwnProperty(tagName), node.attrs, node, parentNode, pkg, i); } } // reference dat