quis
Version:
A simple DSL for data sorting and filtering
1 lines • 11.8 kB
JavaScript
var t;(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"})(t||(t={}));class e{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(t.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 e=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(e,this.position);this.addToken(t.NUMBER,s)}tokenizeString(){const e=this.advance(),i=this.position;for(;this.position<this.input.length&&this.peek()!==e;)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(t.STRING,s)}tokenizeVariable(){const e=this.position;for(this.advance();this.position<this.input.length&&this.isAlphaNumeric(this.peek());)this.advance();const i=this.input.slice(e+1,this.position);this.addToken(t.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 e=this.tokenizeIdentifier();switch(e.toLowerCase()){case"true":this.addToken(t.BOOLEAN,"true");break;case"false":this.addToken(t.BOOLEAN,"false");break;case"null":this.addToken(t.NULL,"null");break;case"and":this.addToken(t.AND,"and");break;case"or":this.addToken(t.OR,"or");break;case"not":this.addToken(t.NOT,"not");break;case"is":this.skipWhitespace(),"not"===this.input.slice(this.position,this.position+3).toLowerCase()?(this.position+=3,this.addToken(t.IS_NOT,"is not")):this.addToken(t.IS,"is");break;case"gt":this.addToken(t.GT,"gt");break;case"gte":this.addToken(t.GTE,"gte");break;case"lt":this.addToken(t.LT,"lt");break;case"lte":this.addToken(t.LTE,"lte");break;case"custom":this.addToken(t.CUSTOM,"custom");break;default:this.addToken(t.IDENTIFIER,e)}}tokenizeTwoCharOperator(e){switch(e){case"==":return this.position+=2,this.addToken(t.EQUALS,"=="),!0;case"!=":return this.position+=2,this.addToken(t.NOT_EQUALS,"!="),!0;case">=":return this.position+=2,this.addToken(t.GREATER_THAN_EQUAL,">="),!0;case"<=":return this.position+=2,this.addToken(t.LESS_THAN_EQUAL,"<="),!0;case"&&":return this.position+=2,this.addToken(t.AND,"&&"),!0;case"||":return this.position+=2,this.addToken(t.OR,"||"),!0;default:return!!this.isLetter(e[0])&&(this.tokenizeKeywordOrIdentifier(),!0)}}tokenizeSingleChar(e){switch(e){case">":this.advance(),this.addToken(t.GREATER_THAN,">");break;case"<":this.advance(),this.addToken(t.LESS_THAN,"<");break;case"(":this.advance(),this.addToken(t.LPAREN,"(");break;case")":this.advance(),this.addToken(t.RPAREN,")");break;case".":this.advance(),this.addToken(t.DOT,".");break;case"[":this.advance(),this.addToken(t.LBRACKET,"[");break;case"]":this.advance(),this.addToken(t.RBRACKET,"]");break;case":":this.advance(),this.addToken(t.COLON,":");break;case"!":this.advance(),this.addToken(t.NOT,"!");break;case"+":this.advance(),this.addToken(t.PLUS,"+");break;case"-":if(this.isDigit(this.peekNext()))throw new Error(`Unexpected character '${e}' at position ${this.position}`);this.advance(),this.addToken(t.MINUS,"-");break;case"*":this.advance(),this.addToken(t.MULTIPLY,"*");break;case"/":this.advance(),this.addToken(t.DIVIDE,"/");break;default:if(!this.isLetter(e))throw new Error(`Unexpected character '${e}' at position ${this.position}`);this.tokenizeKeywordOrIdentifier()}}}class i{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 e=this.parseAndExpression();for(;this.match(t.OR);){const t=this.previous().value,i=this.parseAndExpression();e=this.createBinaryNode(t,e,i)}return e}parseAndExpression(){let e=this.parseComparisonExpression();for(;this.match(t.AND);){const t=this.previous().value,i=this.parseComparisonExpression();e=this.createBinaryNode(t,e,i)}return e}parseComparisonExpression(){if(this.match(t.NOT)){const t=this.previous().value,e=this.parseComparisonExpression();return this.createUnaryNode(t,e)}const e=this.parseArithmeticExpression();if(this.match(t.CUSTOM)){this.consume(t.COLON,"Expected ':' after 'custom'");const i=this.consume(t.IDENTIFIER,"Expected condition name after 'custom:'").value,s=this.parseArithmeticExpression();return this.createCustomConditionNode(i,e,s)}if(this.matchComparison()){const t=this.previous().value,i=this.parseArithmeticExpression();return this.createBinaryNode(t,e,i)}return e}parseArithmeticExpression(){return this.parseAdditionExpression()}parseAdditionExpression(){let e=this.parseMultiplicationExpression();for(;this.match(t.PLUS,t.MINUS);){const t=this.previous().value,i=this.parseMultiplicationExpression();e=this.createBinaryNode(t,e,i)}return e}parseMultiplicationExpression(){let e=this.parseValue();for(;this.match(t.MULTIPLY,t.DIVIDE);){const t=this.previous().value,i=this.parseValue();e=this.createBinaryNode(t,e,i)}return e}parseValue(){if(this.match(t.NUMBER)){const t=parseFloat(this.previous().value);return this.createLiteralNode(t)}if(this.match(t.STRING)){const t=this.previous().value;return this.createLiteralNode(t)}if(this.match(t.BOOLEAN)){const t="true"===this.previous().value;return this.createLiteralNode(t)}if(this.match(t.NULL))return this.createLiteralNode(null);if(this.match(t.LPAREN)){const e=this.parseOrExpression();return this.consume(t.RPAREN,"Expected ')' after expression"),e}if(this.match(t.VARIABLE)){const e=this.previous().value;if(this.match(t.DOT)){const i=this.consume(t.IDENTIFIER,"Expected property name after '.'").value;return this.createPropertyAccessNode(e,i,"dot")}if(this.match(t.LBRACKET)){let i;if(this.check(t.STRING))i=this.advance().value;else{if(!this.check(t.IDENTIFIER))throw new Error("Expected string or identifier in bracket notation");i=this.advance().value}return this.consume(t.RBRACKET,"Expected ']' after property name"),this.createPropertyAccessNode(e,i,"bracket")}return this.createVariableNode(e)}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(t.EQUALS,t.NOT_EQUALS,t.GREATER_THAN,t.GREATER_THAN_EQUAL,t.LESS_THAN,t.LESS_THAN_EQUAL,t.IS,t.IS_NOT,t.GT,t.GTE,t.LT,t.LTE)}check(t){return!this.isAtEnd()&&this.peek().type===t}advance(){return this.isAtEnd()||this.current++,this.previous()}isAtEnd(){return this.peek().type===t.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 s{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 n 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 r={},o={parse:(t,o)=>function(t,r){try{const n=new e(t).tokenize(),o=new i(n).parse();return new s(r).evaluate(o)}catch(t){if(t instanceof Error)throw new n(t.message,[],null);throw t}}(t,{...o,customConditions:{...r,...o?.customConditions}}),SyntaxError:n,addCustomCondition:(t,e)=>{r[t]=e},removeCustomCondition:t=>t in r&&(delete r[t],!0),getCustomConditions:()=>({...r}),clearCustomConditions:()=>{Object.keys(r).forEach(t=>{delete r[t]})}},a=o,h=o.parse,c=o.SyntaxError,u=o.addCustomCondition,p=o.removeCustomCondition,d=o.getCustomConditions,l=o.clearCustomConditions;export{c as SyntaxError,u as addCustomCondition,l as clearCustomConditions,a as default,d as getCustomConditions,h as parse,p as removeCustomCondition};