quis
Version:
A simple DSL for data sorting and filtering
1 lines • 12.9 kB
JavaScript
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.quis=e():t.quis=e()}("undefined"!=typeof self?self:this,()=>(()=>{"use strict";var t={d:(e,i)=>{for(var s in i)t.o(i,s)&&!t.o(e,s)&&Object.defineProperty(e,s,{enumerable:!0,get:i[s]})}};t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),t.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),t.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var e,i={};t.r(i),t.d(i,{SyntaxError:()=>d,addCustomCondition:()=>l,clearCustomConditions:()=>k,default:()=>u,getCustomConditions:()=>T,parse:()=>p,removeCustomCondition:()=>E}),function(t){t.NUMBER="NUMBER",t.STRING="STRING",t.BOOLEAN="BOOLEAN",t.NULL="NULL",t.VARIABLE="VARIABLE",t.DOT="DOT",t.LBRACKET="LBRACKET",t.RBRACKET="RBRACKET",t.EQUALS="EQUALS",t.NOT_EQUALS="NOT_EQUALS",t.GREATER_THAN="GREATER_THAN",t.GREATER_THAN_EQUAL="GREATER_THAN_EQUAL",t.LESS_THAN="LESS_THAN",t.LESS_THAN_EQUAL="LESS_THAN_EQUAL",t.PLUS="PLUS",t.MINUS="MINUS",t.MULTIPLY="MULTIPLY",t.DIVIDE="DIVIDE",t.IS="IS",t.IS_NOT="IS_NOT",t.GT="GT",t.GTE="GTE",t.LT="LT",t.LTE="LTE",t.AND="AND",t.OR="OR",t.NOT="NOT",t.CUSTOM="CUSTOM",t.COLON="COLON",t.IDENTIFIER="IDENTIFIER",t.LPAREN="LPAREN",t.RPAREN="RPAREN",t.EOF="EOF",t.WHITESPACE="WHITESPACE"}(e||(e={}));class s{constructor(t){this.input=t.trim(),this.position=0,this.tokens=[]}tokenize(){for(this.tokens=[],this.position=0;this.position<this.input.length&&(this.skipWhitespace(),!(this.position>=this.input.length));){const t=this.peek();if(this.isDigit(t)||"-"===t&&this.isDigit(this.peekNext()))this.tokenizeNumber();else if('"'===t||"'"===t)this.tokenizeString();else if("$"===t)this.tokenizeVariable();else if(this.position+1<this.input.length){const e=this.input.slice(this.position,this.position+2);if(this.tokenizeTwoCharOperator(e))continue;this.tokenizeSingleChar(t)}else this.tokenizeSingleChar(t)}return this.addToken(e.EOF,""),this.tokens}peek(){return this.position<this.input.length?this.input[this.position]:""}peekNext(){return this.position+1<this.input.length?this.input[this.position+1]:""}advance(){return this.position<this.input.length?this.input[this.position++]:""}skipWhitespace(){for(;this.position<this.input.length&&/\s/.test(this.input[this.position]);)this.position++}isDigit(t){return/[0-9]/.test(t)}isLetter(t){return/[a-zA-Z_]/.test(t)}isAlphaNumeric(t){return/[a-zA-Z0-9_]/.test(t)}addToken(t,e){this.tokens.push({type:t,value:e,position:this.position-e.length})}tokenizeNumber(){const t=this.position;let i=!1;for("-"===this.peek()&&this.advance();this.position<this.input.length;){const t=this.peek();if(this.isDigit(t))this.advance();else{if("."!==t||i)break;i=!0,this.advance()}}const s=this.input.slice(t,this.position);this.addToken(e.NUMBER,s)}tokenizeString(){const t=this.advance(),i=this.position;for(;this.position<this.input.length&&this.peek()!==t;)this.advance();if(this.position>=this.input.length)throw new Error("Unterminated string starting at position "+(i-1));const s=this.input.slice(i,this.position);this.advance(),this.addToken(e.STRING,s)}tokenizeVariable(){const t=this.position;for(this.advance();this.position<this.input.length&&this.isAlphaNumeric(this.peek());)this.advance();const i=this.input.slice(t+1,this.position);this.addToken(e.VARIABLE,i)}tokenizeIdentifier(){const t=this.position;for(;this.position<this.input.length&&this.isAlphaNumeric(this.peek());)this.advance();return this.input.slice(t,this.position)}tokenizeKeywordOrIdentifier(){const t=this.tokenizeIdentifier();switch(t.toLowerCase()){case"true":this.addToken(e.BOOLEAN,"true");break;case"false":this.addToken(e.BOOLEAN,"false");break;case"null":this.addToken(e.NULL,"null");break;case"and":this.addToken(e.AND,"and");break;case"or":this.addToken(e.OR,"or");break;case"not":this.addToken(e.NOT,"not");break;case"is":this.skipWhitespace(),"not"===this.input.slice(this.position,this.position+3).toLowerCase()?(this.position+=3,this.addToken(e.IS_NOT,"is not")):this.addToken(e.IS,"is");break;case"gt":this.addToken(e.GT,"gt");break;case"gte":this.addToken(e.GTE,"gte");break;case"lt":this.addToken(e.LT,"lt");break;case"lte":this.addToken(e.LTE,"lte");break;case"custom":this.addToken(e.CUSTOM,"custom");break;default:this.addToken(e.IDENTIFIER,t)}}tokenizeTwoCharOperator(t){switch(t){case"==":return this.position+=2,this.addToken(e.EQUALS,"=="),!0;case"!=":return this.position+=2,this.addToken(e.NOT_EQUALS,"!="),!0;case">=":return this.position+=2,this.addToken(e.GREATER_THAN_EQUAL,">="),!0;case"<=":return this.position+=2,this.addToken(e.LESS_THAN_EQUAL,"<="),!0;case"&&":return this.position+=2,this.addToken(e.AND,"&&"),!0;case"||":return this.position+=2,this.addToken(e.OR,"||"),!0;default:return!!this.isLetter(t[0])&&(this.tokenizeKeywordOrIdentifier(),!0)}}tokenizeSingleChar(t){switch(t){case">":this.advance(),this.addToken(e.GREATER_THAN,">");break;case"<":this.advance(),this.addToken(e.LESS_THAN,"<");break;case"(":this.advance(),this.addToken(e.LPAREN,"(");break;case")":this.advance(),this.addToken(e.RPAREN,")");break;case".":this.advance(),this.addToken(e.DOT,".");break;case"[":this.advance(),this.addToken(e.LBRACKET,"[");break;case"]":this.advance(),this.addToken(e.RBRACKET,"]");break;case":":this.advance(),this.addToken(e.COLON,":");break;case"!":this.advance(),this.addToken(e.NOT,"!");break;case"+":this.advance(),this.addToken(e.PLUS,"+");break;case"-":if(this.isDigit(this.peekNext()))throw new Error(`Unexpected character '${t}' at position ${this.position}`);this.advance(),this.addToken(e.MINUS,"-");break;case"*":this.advance(),this.addToken(e.MULTIPLY,"*");break;case"/":this.advance(),this.addToken(e.DIVIDE,"/");break;default:if(!this.isLetter(t))throw new Error(`Unexpected character '${t}' at position ${this.position}`);this.tokenizeKeywordOrIdentifier()}}}class o{constructor(t){this.tokens=t,this.current=0}parse(){const t=this.parseOrExpression();if(!this.isAtEnd()){const t=this.peek();throw new Error(`Unexpected token '${t.value}' at position ${t.position}`)}return t}parseOrExpression(){let t=this.parseAndExpression();for(;this.match(e.OR);){const e=this.previous().value,i=this.parseAndExpression();t=this.createBinaryNode(e,t,i)}return t}parseAndExpression(){let t=this.parseComparisonExpression();for(;this.match(e.AND);){const e=this.previous().value,i=this.parseComparisonExpression();t=this.createBinaryNode(e,t,i)}return t}parseComparisonExpression(){if(this.match(e.NOT)){const t=this.previous().value,e=this.parseComparisonExpression();return this.createUnaryNode(t,e)}const t=this.parseArithmeticExpression();if(this.match(e.CUSTOM)){this.consume(e.COLON,"Expected ':' after 'custom'");const i=this.consume(e.IDENTIFIER,"Expected condition name after 'custom:'").value,s=this.parseArithmeticExpression();return this.createCustomConditionNode(i,t,s)}if(this.matchComparison()){const e=this.previous().value,i=this.parseArithmeticExpression();return this.createBinaryNode(e,t,i)}return t}parseArithmeticExpression(){return this.parseAdditionExpression()}parseAdditionExpression(){let t=this.parseMultiplicationExpression();for(;this.match(e.PLUS,e.MINUS);){const e=this.previous().value,i=this.parseMultiplicationExpression();t=this.createBinaryNode(e,t,i)}return t}parseMultiplicationExpression(){let t=this.parseValue();for(;this.match(e.MULTIPLY,e.DIVIDE);){const e=this.previous().value,i=this.parseValue();t=this.createBinaryNode(e,t,i)}return t}parseValue(){if(this.match(e.NUMBER)){const t=parseFloat(this.previous().value);return this.createLiteralNode(t)}if(this.match(e.STRING)){const t=this.previous().value;return this.createLiteralNode(t)}if(this.match(e.BOOLEAN)){const t="true"===this.previous().value;return this.createLiteralNode(t)}if(this.match(e.NULL))return this.createLiteralNode(null);if(this.match(e.LPAREN)){const t=this.parseOrExpression();return this.consume(e.RPAREN,"Expected ')' after expression"),t}if(this.match(e.VARIABLE)){const t=this.previous().value;if(this.match(e.DOT)){const i=this.consume(e.IDENTIFIER,"Expected property name after '.'").value;return this.createPropertyAccessNode(t,i,"dot")}if(this.match(e.LBRACKET)){let i;if(this.check(e.STRING))i=this.advance().value;else{if(!this.check(e.IDENTIFIER))throw new Error("Expected string or identifier in bracket notation");i=this.advance().value}return this.consume(e.RBRACKET,"Expected ']' after property name"),this.createPropertyAccessNode(t,i,"bracket")}return this.createVariableNode(t)}throw new Error(`Unexpected token '${this.peek().value}' at position ${this.peek().position}`)}match(...t){for(const e of t)if(this.check(e))return this.advance(),!0;return!1}matchComparison(){return this.match(e.EQUALS,e.NOT_EQUALS,e.GREATER_THAN,e.GREATER_THAN_EQUAL,e.LESS_THAN,e.LESS_THAN_EQUAL,e.IS,e.IS_NOT,e.GT,e.GTE,e.LT,e.LTE)}check(t){return!this.isAtEnd()&&this.peek().type===t}advance(){return this.isAtEnd()||this.current++,this.previous()}isAtEnd(){return this.peek().type===e.EOF}peek(){return this.tokens[this.current]}previous(){return this.tokens[this.current-1]}consume(t,e){if(this.check(t))return this.advance();const i=this.peek();throw new Error(`${e}. Got '${i.value}' at position ${i.position}`)}createLiteralNode(t){return{type:"literal",value:t}}createVariableNode(t){return{type:"variable",name:t}}createPropertyAccessNode(t,e,i){return{type:"property",object:t,property:e,notation:i}}createBinaryNode(t,e,i){return{type:"binary",operator:t,left:e,right:i}}createUnaryNode(t,e){return{type:"unary",operator:t,operand:e}}createCustomConditionNode(t,e,i){return{type:"custom",name:t,left:e,right:i}}}class n{constructor(t={}){this.options=t}evaluate(t){switch(t.type){case"literal":return this.evaluateLiteral(t);case"variable":return this.evaluateVariable(t);case"property":return this.evaluatePropertyAccess(t);case"binary":return this.evaluateBinaryOperation(t);case"unary":return this.evaluateUnaryOperation(t);case"custom":return this.evaluateCustomCondition(t);default:throw new Error(`Unknown AST node type: ${t.type}`)}}evaluateLiteral(t){return t.value}evaluateVariable(t){if(this.options.values&&"function"==typeof this.options.values)try{return this.options.values(t.name)}catch(t){return null}return null}evaluatePropertyAccess(t){if(this.options.values&&"function"==typeof this.options.values)try{const e=this.options.values(t.object);if(e&&"object"==typeof e&&t.property in e)return e[t.property]}catch(t){return null}return null}evaluateBinaryOperation(t){const e=this.evaluate(t.left),i=this.evaluate(t.right);switch(t.operator.toLowerCase()){case"&&":case"and":return!!e&&!!i;case"||":case"or":return e||i;case"==":case"is":return e==i;case"!=":case"is not":return e!=i;case">":case"gt":return e>i;case">=":case"gte":return e>=i;case"<":case"lt":return e<i;case"<=":case"lte":return e<=i;case"+":return e+i;case"-":return Number(e)-Number(i);case"*":return Number(e)*Number(i);case"/":return Number(e)/Number(i);default:throw new Error(`Unknown binary operator: ${t.operator}`)}}evaluateUnaryOperation(t){const e=this.evaluate(t.operand);switch(t.operator){case"!":case"not":return!e;default:throw new Error(`Unknown unary operator: ${t.operator}`)}}evaluateCustomCondition(t){const e=this.evaluate(t.left),i=this.evaluate(t.right);if(this.options.customConditions&&"function"==typeof this.options.customConditions[t.name])return this.options.customConditions[t.name](e,i);throw new Error(`Custom condition '${t.name}' is not defined`)}}class r extends Error{constructor(t,e,i,s){super(t),this.name="SyntaxError",this.expected=e,this.found=i,this.location=s}format(){let t=this.message;return this.location&&(t+=` at line ${this.location.start.line}, column ${this.location.start.column}`),t}}const a={},h={parse:(t,e)=>function(t,e){try{const i=new s(t).tokenize(),r=new o(i).parse();return new n(e).evaluate(r)}catch(t){if(t instanceof Error)throw new r(t.message,[],null);throw t}}(t,{...e,customConditions:{...a,...e?.customConditions}}),SyntaxError:r,addCustomCondition:(t,e)=>{a[t]=e},removeCustomCondition:t=>t in a&&(delete a[t],!0),getCustomConditions:()=>({...a}),clearCustomConditions:()=>{Object.keys(a).forEach(t=>{delete a[t]})}},c={parse:h.parse,SyntaxError:h.SyntaxError,addCustomCondition:h.addCustomCondition,removeCustomCondition:h.removeCustomCondition,getCustomConditions:h.getCustomConditions,clearCustomConditions:h.clearCustomConditions};"undefined"!=typeof window?window.quis=c:void 0!==t.g&&(t.g.quis=c);const u=c,{parse:p,SyntaxError:d,addCustomCondition:l,removeCustomCondition:E,getCustomConditions:T,clearCustomConditions:k}=c;return i})());