basant307/AI_Governance_Project
048
1import { warn } from '../util/index.mjs';2import * as is from '../is.mjs';3import exprs from './expressions.mjs';4import newQuery from './new-query.mjs';5import Type from './type.mjs';6 7/**8 * Of all the expressions, find the first match in the remaining text.9 * @param {string} remaining The remaining text to parse10 * @returns The matched expression and the newly remaining text `{ expr, match, name, remaining }`11 */12const consumeExpr = ( remaining ) => {13 let expr;14 let match;15 let name;16 17 for( let j = 0; j < exprs.length; j++ ){18 let e = exprs[ j ];19 let n = e.name;20 21 let m = remaining.match( e.regexObj );22 23 if( m != null ){24 match = m;25 expr = e;26 name = n;27 28 let consumed = m[0];29 remaining = remaining.substring( consumed.length );30 31 break; // we've consumed one expr, so we can return now32 }33 }34 35 return {36 expr: expr,37 match: match,38 name: name,39 remaining: remaining40 };41};42 43 44/**45 * Consume all the leading whitespace46 * @param {string} remaining The text to consume47 * @returns The text with the leading whitespace removed48 */49const consumeWhitespace = ( remaining ) => {50 let match = remaining.match( /^\s+/ );51 52 if( match ){53 let consumed = match[0];54 remaining = remaining.substring( consumed.length );55 }56 57 return remaining;58};59 60/**61 * Parse the string and store the parsed representation in the Selector.62 * @param {string} selector The selector string63 * @returns `true` if the selector was successfully parsed, `false` otherwise64 */65const parse = function( selector ){66 let self = this;67 68 let remaining = self.inputText = selector;69 70 let currentQuery = self[0] = newQuery();71 self.length = 1;72 73 remaining = consumeWhitespace( remaining ); // get rid of leading whitespace74 75 for( ;; ){76 let exprInfo = consumeExpr( remaining );77 78 if( exprInfo.expr == null ){79 warn( 'The selector `' + selector + '`is invalid' );80 return false;81 } else {82 let args = exprInfo.match.slice( 1 );83 84 // let the token populate the selector object in currentQuery85 let ret = exprInfo.expr.populate( self, currentQuery, args );86 87 if( ret === false ){88 return false; // exit if population failed89 } else if( ret != null ){90 currentQuery = ret; // change the current query to be filled if the expr specifies91 }92 }93 94 remaining = exprInfo.remaining;95 96 // we're done when there's nothing left to parse97 if( remaining.match( /^\s*$/ ) ){98 break;99 }100 }101 102 let lastQ = self[self.length - 1];103 104 if( self.currentSubject != null ){105 lastQ.subject = self.currentSubject;106 }107 108 lastQ.edgeCount = self.edgeCount;109 lastQ.compoundCount = self.compoundCount;110 111 for( let i = 0; i < self.length; i++ ){112 let q = self[i];113 114 // in future, this could potentially be allowed if there were operator precedence and detection of invalid combinations115 if( q.compoundCount > 0 && q.edgeCount > 0 ){116 warn( 'The selector `' + selector + '` is invalid because it uses both a compound selector and an edge selector' );117 return false;118 }119 120 if( q.edgeCount > 1 ){121 warn( 'The selector `' + selector + '` is invalid because it uses multiple edge selectors' );122 return false;123 } else if( q.edgeCount === 1 ){124 warn( 'The selector `' + selector + '` is deprecated. Edge selectors do not take effect on changes to source and target nodes after an edge is added, for performance reasons. Use a class or data selector on edges instead, updating the class or data of an edge when your app detects a change in source or target nodes.' );125 }126 }127 128 return true; // success129};130 131/**132 * Get the selector represented as a string. This value uses default formatting,133 * so things like spacing may differ from the input text passed to the constructor.134 * @returns {string} The selector string135 */136export const toString = function(){137 if( this.toStringCache != null ){138 return this.toStringCache;139 }140 141 let clean = function( obj ){142 if( obj == null ){143 return '';144 } else {145 return obj;146 }147 };148 149 let cleanVal = function( val ){150 if( is.string( val ) ){151 return '"' + val + '"';152 } else {153 return clean( val );154 }155 };156 157 let space = ( val ) => {158 return ' ' + val + ' ';159 };160 161 let checkToString = ( check, subject ) => {162 let { type, value } = check;163 164 switch( type ){165 case Type.GROUP: {166 let group = clean( value );167 168 return group.substring( 0, group.length - 1 );169 }170 171 case Type.DATA_COMPARE: {172 let { field, operator } = check;173 174 return '[' + field + space( clean( operator ) ) + cleanVal( value ) + ']';175 }176 177 case Type.DATA_BOOL: {178 let { operator, field } = check;179 180 return '[' + clean( operator ) + field + ']';181 }182 183 case Type.DATA_EXIST: {184 let { field } = check;185 186 return '[' + field + ']';187 }188 189 case Type.META_COMPARE: {190 let { operator, field } = check;191 192 return '[[' + field + space( clean( operator ) ) + cleanVal( value ) + ']]';193 }194 195 case Type.STATE: {196 return value;197 }198 199 case Type.ID: {200 return '#' + value;201 }202 203 case Type.CLASS: {204 return '.' + value;205 }206 207 case Type.PARENT:208 case Type.CHILD: {209 return queryToString(check.parent, subject) + space('>') + queryToString(check.child, subject);210 }211 212 case Type.ANCESTOR:213 case Type.DESCENDANT: {214 return queryToString(check.ancestor, subject) + ' ' + queryToString(check.descendant, subject);215 }216 217 case Type.COMPOUND_SPLIT: {218 let lhs = queryToString(check.left, subject);219 let sub = queryToString(check.subject, subject);220 let rhs = queryToString(check.right, subject);221 222 return lhs + (lhs.length > 0 ? ' ' : '') + sub + rhs;223 }224 225 case Type.TRUE: {226 return '';227 }228 }229 };230 231 let queryToString = ( query, subject ) => {232 return query.checks.reduce((str, chk, i) => {233 return str + (subject === query && i === 0 ? '$' : '') + checkToString(chk, subject);234 }, '');235 };236 237 let str = '';238 239 for( let i = 0; i < this.length; i++ ){240 let query = this[ i ];241 242 str += queryToString( query, query.subject );243 244 if( this.length > 1 && i < this.length - 1 ){245 str += ', ';246 }247 }248 249 this.toStringCache = str;250 251 return str;252};253 254export default { parse, toString };255 