|
import tokens from './tokens'; |
|
import * as util from '../util'; |
|
import newQuery from './new-query'; |
|
import Type from './type'; |
|
import { stateSelectorRegex } from './state'; |
|
|
|
|
|
|
|
const cleanMetaChars = function( str ){ |
|
return str.replace( new RegExp( '\\\\(' + tokens.metaChar + ')', 'g' ), function( match, $1 ){ |
|
return $1; |
|
} ); |
|
}; |
|
|
|
const replaceLastQuery = ( selector, examiningQuery, replacementQuery ) => { |
|
selector[ selector.length - 1 ] = replacementQuery; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
let exprs = [ |
|
{ |
|
name: 'group', |
|
query: true, |
|
regex: '(' + tokens.group + ')', |
|
populate: function( selector, query, [ group ] ){ |
|
query.checks.push({ |
|
type: Type.GROUP, |
|
value: group === '*' ? group : group + 's' |
|
}); |
|
} |
|
}, |
|
|
|
{ |
|
name: 'state', |
|
query: true, |
|
regex: stateSelectorRegex, |
|
populate: function( selector, query, [ state ] ){ |
|
query.checks.push({ |
|
type: Type.STATE, |
|
value: state |
|
}); |
|
} |
|
}, |
|
|
|
{ |
|
name: 'id', |
|
query: true, |
|
regex: '\\#(' + tokens.id + ')', |
|
populate: function( selector, query,[ id ] ){ |
|
query.checks.push({ |
|
type: Type.ID, |
|
value: cleanMetaChars( id ) |
|
}); |
|
} |
|
}, |
|
|
|
{ |
|
name: 'className', |
|
query: true, |
|
regex: '\\.(' + tokens.className + ')', |
|
populate: function( selector, query, [ className ] ){ |
|
query.checks.push({ |
|
type: Type.CLASS, |
|
value: cleanMetaChars( className ) |
|
}); |
|
} |
|
}, |
|
|
|
{ |
|
name: 'dataExists', |
|
query: true, |
|
regex: '\\[\\s*(' + tokens.variable + ')\\s*\\]', |
|
populate: function( selector, query, [ variable ] ){ |
|
query.checks.push( { |
|
type: Type.DATA_EXIST, |
|
field: cleanMetaChars( variable ) |
|
} ); |
|
} |
|
}, |
|
|
|
{ |
|
name: 'dataCompare', |
|
query: true, |
|
regex: '\\[\\s*(' + tokens.variable + ')\\s*(' + tokens.comparatorOp + ')\\s*(' + tokens.value + ')\\s*\\]', |
|
populate: function( selector, query, [ variable, comparatorOp, value ] ){ |
|
let valueIsString = new RegExp( '^' + tokens.string + '$' ).exec( value ) != null; |
|
|
|
if( valueIsString ){ |
|
value = value.substring( 1, value.length - 1 ); |
|
} else { |
|
value = parseFloat( value ); |
|
} |
|
|
|
query.checks.push( { |
|
type: Type.DATA_COMPARE, |
|
field: cleanMetaChars( variable ), |
|
operator: comparatorOp, |
|
value: value |
|
} ); |
|
} |
|
}, |
|
|
|
{ |
|
name: 'dataBool', |
|
query: true, |
|
regex: '\\[\\s*(' + tokens.boolOp + ')\\s*(' + tokens.variable + ')\\s*\\]', |
|
populate: function( selector, query, [ boolOp, variable ] ){ |
|
query.checks.push( { |
|
type: Type.DATA_BOOL, |
|
field: cleanMetaChars( variable ), |
|
operator: boolOp |
|
} ); |
|
} |
|
}, |
|
|
|
{ |
|
name: 'metaCompare', |
|
query: true, |
|
regex: '\\[\\[\\s*(' + tokens.meta + ')\\s*(' + tokens.comparatorOp + ')\\s*(' + tokens.number + ')\\s*\\]\\]', |
|
populate: function( selector, query, [ meta, comparatorOp, number ] ){ |
|
query.checks.push( { |
|
type: Type.META_COMPARE, |
|
field: cleanMetaChars( meta ), |
|
operator: comparatorOp, |
|
value: parseFloat( number ) |
|
} ); |
|
} |
|
}, |
|
|
|
{ |
|
name: 'nextQuery', |
|
separator: true, |
|
regex: tokens.separator, |
|
populate: function( selector, query ){ |
|
let currentSubject = selector.currentSubject; |
|
let edgeCount = selector.edgeCount; |
|
let compoundCount = selector.compoundCount; |
|
let lastQ = selector[ selector.length - 1 ]; |
|
|
|
if( currentSubject != null ){ |
|
lastQ.subject = currentSubject; |
|
selector.currentSubject = null; |
|
} |
|
|
|
lastQ.edgeCount = edgeCount; |
|
lastQ.compoundCount = compoundCount; |
|
|
|
selector.edgeCount = 0; |
|
selector.compoundCount = 0; |
|
|
|
|
|
let nextQuery = selector[ selector.length++ ] = newQuery(); |
|
|
|
return nextQuery; |
|
} |
|
}, |
|
|
|
{ |
|
name: 'directedEdge', |
|
separator: true, |
|
regex: tokens.directedEdge, |
|
populate: function( selector, query ){ |
|
if( selector.currentSubject == null ){ |
|
let edgeQuery = newQuery(); |
|
let source = query; |
|
let target = newQuery(); |
|
|
|
edgeQuery.checks.push({ type: Type.DIRECTED_EDGE, source, target }); |
|
|
|
|
|
replaceLastQuery( selector, query, edgeQuery ); |
|
|
|
selector.edgeCount++; |
|
|
|
|
|
return target; |
|
} else { |
|
let srcTgtQ = newQuery(); |
|
let source = query; |
|
let target = newQuery(); |
|
|
|
srcTgtQ.checks.push({ type: Type.NODE_SOURCE, source, target }); |
|
|
|
|
|
replaceLastQuery( selector, query, srcTgtQ ); |
|
|
|
selector.edgeCount++; |
|
|
|
return target; |
|
} |
|
} |
|
}, |
|
|
|
{ |
|
name: 'undirectedEdge', |
|
separator: true, |
|
regex: tokens.undirectedEdge, |
|
populate: function( selector, query ){ |
|
if( selector.currentSubject == null ){ |
|
let edgeQuery = newQuery(); |
|
let source = query; |
|
let target = newQuery(); |
|
|
|
edgeQuery.checks.push({ type: Type.UNDIRECTED_EDGE, nodes: [ source, target ] }); |
|
|
|
|
|
replaceLastQuery( selector, query, edgeQuery ); |
|
|
|
selector.edgeCount++; |
|
|
|
|
|
return target; |
|
} else { |
|
let nhoodQ = newQuery(); |
|
let node = query; |
|
let neighbor = newQuery(); |
|
|
|
nhoodQ.checks.push({ type: Type.NODE_NEIGHBOR, node, neighbor }); |
|
|
|
|
|
replaceLastQuery( selector, query, nhoodQ ); |
|
|
|
return neighbor; |
|
} |
|
} |
|
}, |
|
|
|
{ |
|
name: 'child', |
|
separator: true, |
|
regex: tokens.child, |
|
populate: function( selector, query ){ |
|
if( selector.currentSubject == null ){ |
|
let parentChildQuery = newQuery(); |
|
let child = newQuery(); |
|
let parent = selector[selector.length - 1]; |
|
|
|
parentChildQuery.checks.push({ type: Type.CHILD, parent, child }); |
|
|
|
|
|
replaceLastQuery( selector, query, parentChildQuery ); |
|
|
|
selector.compoundCount++; |
|
|
|
|
|
return child; |
|
} else if( selector.currentSubject === query ){ |
|
let compound = newQuery(); |
|
let left = selector[ selector.length - 1 ]; |
|
let right = newQuery(); |
|
let subject = newQuery(); |
|
let child = newQuery(); |
|
let parent = newQuery(); |
|
|
|
|
|
compound.checks.push({ type: Type.COMPOUND_SPLIT, left, right, subject }); |
|
|
|
|
|
subject.checks = query.checks; |
|
query.checks = [ { type: Type.TRUE } ]; |
|
|
|
|
|
parent.checks.push({ type: Type.TRUE }); |
|
right.checks.push({ |
|
type: Type.PARENT, |
|
parent, |
|
child |
|
}); |
|
|
|
replaceLastQuery( selector, left, compound ); |
|
|
|
|
|
selector.currentSubject = subject; |
|
|
|
selector.compoundCount++; |
|
|
|
return child; |
|
} else { |
|
|
|
let parent = newQuery(); |
|
let child = newQuery(); |
|
let pcQChecks = [ { type: Type.PARENT, parent, child } ]; |
|
|
|
|
|
parent.checks = query.checks; |
|
query.checks = pcQChecks; |
|
|
|
selector.compoundCount++; |
|
|
|
return child; |
|
} |
|
} |
|
}, |
|
|
|
{ |
|
name: 'descendant', |
|
separator: true, |
|
regex: tokens.descendant, |
|
populate: function( selector, query ){ |
|
if( selector.currentSubject == null ){ |
|
let ancChQuery = newQuery(); |
|
let descendant = newQuery(); |
|
let ancestor = selector[selector.length - 1]; |
|
|
|
ancChQuery.checks.push({ type: Type.DESCENDANT, ancestor, descendant }); |
|
|
|
|
|
replaceLastQuery( selector, query, ancChQuery ); |
|
|
|
selector.compoundCount++; |
|
|
|
|
|
return descendant; |
|
} else if( selector.currentSubject === query ){ |
|
let compound = newQuery(); |
|
let left = selector[ selector.length - 1 ]; |
|
let right = newQuery(); |
|
let subject = newQuery(); |
|
let descendant = newQuery(); |
|
let ancestor = newQuery(); |
|
|
|
|
|
compound.checks.push({ type: Type.COMPOUND_SPLIT, left, right, subject }); |
|
|
|
|
|
subject.checks = query.checks; |
|
query.checks = [ { type: Type.TRUE } ]; |
|
|
|
|
|
ancestor.checks.push({ type: Type.TRUE }); |
|
right.checks.push({ |
|
type: Type.ANCESTOR, |
|
ancestor, |
|
descendant |
|
}); |
|
|
|
replaceLastQuery( selector, left, compound ); |
|
|
|
|
|
selector.currentSubject = subject; |
|
|
|
selector.compoundCount++; |
|
|
|
return descendant; |
|
} else { |
|
|
|
let ancestor = newQuery(); |
|
let descendant = newQuery(); |
|
let adQChecks = [ { type: Type.ANCESTOR, ancestor, descendant } ]; |
|
|
|
|
|
ancestor.checks = query.checks; |
|
query.checks = adQChecks; |
|
|
|
selector.compoundCount++; |
|
|
|
return descendant; |
|
} |
|
} |
|
}, |
|
|
|
{ |
|
name: 'subject', |
|
modifier: true, |
|
regex: tokens.subject, |
|
populate: function( selector, query ){ |
|
if( selector.currentSubject != null && selector.currentSubject !== query ){ |
|
util.warn( 'Redefinition of subject in selector `' + selector.toString() + '`' ); |
|
return false; |
|
} |
|
|
|
selector.currentSubject = query; |
|
|
|
let topQ = selector[selector.length - 1]; |
|
let topChk = topQ.checks[0]; |
|
let topType = topChk == null ? null : topChk.type; |
|
|
|
if( topType === Type.DIRECTED_EDGE ){ |
|
|
|
|
|
|
|
topChk.type = Type.NODE_TARGET; |
|
|
|
} else if( topType === Type.UNDIRECTED_EDGE ){ |
|
|
|
|
|
|
|
topChk.type = Type.NODE_NEIGHBOR; |
|
topChk.node = topChk.nodes[1]; |
|
topChk.neighbor = topChk.nodes[0]; |
|
|
|
|
|
topChk.nodes = null; |
|
} |
|
} |
|
} |
|
]; |
|
|
|
exprs.forEach( e => e.regexObj = new RegExp( '^' + e.regex ) ); |
|
|
|
export default exprs; |
|
|