You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

@filtron/core

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@filtron/core - npm Package Compare versions

Comparing version
1.2.0
to
1.3.1
+3
-3
dist/index.js

@@ -1,5 +0,5 @@

var D=new Uint8Array(128);for(let O=0;O<128;O++)if(O===32||O===9||O===10||O===13)D[O]=1;else if(O>=48&&O<=57)D[O]=2;else if(O>=65&&O<=90||O>=97&&O<=122||O===95)D[O]=4;class z extends Error{position;constructor(O,V){super(O);this.position=V;this.name="LexerError"}}class F{pos=0;input;length;constructor(O){this.input=O,this.length=O.length}skipWhitespace(){let O=this.input,V=this.length,N=this.pos;while(N<V){let A=O.charCodeAt(N);if(A===32||A===9||A===10||A===13){N++;continue}if(A===47&&N+1<V&&O.charCodeAt(N+1)===47){N+=2;while(N<V&&O.charCodeAt(N)!==10)N++;continue}break}this.pos=N}readString(){let O=this.input,V=this.length,N=this.pos,A=this.pos+1,M=!1,B=A;while(A<V){let R=O.charCodeAt(A);if(R===34)return this.pos=A+1,{type:"STRING",value:O.slice(B,A),start:N,end:this.pos};if(R===92){M=!0;break}A++}if(M){A=B;let R="",j=A;while(A<V){let G=O.charCodeAt(A);if(G===34)return R+=O.slice(j,A),this.pos=A+1,{type:"STRING",value:R,start:N,end:this.pos};if(G===92){R+=O.slice(j,A),A++;let H=O.charCodeAt(A);switch(A++,H){case 110:R+=`
`;break;case 116:R+="\t";break;case 114:R+="\r";break;case 92:R+="\\";break;case 34:R+='"';break;default:R+=String.fromCharCode(H)}j=A;continue}A++}}throw new z("Unterminated string literal",N)}readNumber(){let O=this.input,V=this.length,N=this.pos,A=this.pos;if(O.charCodeAt(A)===45)A++;while(A<V){let M=O.charCodeAt(A);if(M<48||M>57)break;A++}if(A<V&&O.charCodeAt(A)===46&&A+1<V){let M=O.charCodeAt(A+1);if(M>=48&&M<=57){A++;while(A<V){let B=O.charCodeAt(A);if(B<48||B>57)break;A++}return this.pos=A,{type:"NUMBER",value:parseFloat(O.slice(N,A)),start:N,end:A}}}return this.pos=A,{type:"NUMBER",value:parseInt(O.slice(N,A),10),start:N,end:A}}readIdentifier(){let O=this.input,V=this.length,N=this.pos,A=this.pos;while(A<V){let B=O.charCodeAt(A);if(B>=97&&B<=122||B>=65&&B<=90||B>=48&&B<=57||B===95)A++;else break}this.pos=A;let M=A-N;if(M>=2&&M<=6){let B=O.charCodeAt(N)|32;if(M===2){if(B===111&&(O.charCodeAt(N+1)|32)===114)return{type:"OR",value:"or",start:N,end:A}}else if(M===3){if(B===97){if((O.charCodeAt(N+1)|32)===110&&(O.charCodeAt(N+2)|32)===100)return{type:"AND",value:"and",start:N,end:A}}else if(B===110){if((O.charCodeAt(N+1)|32)===111&&(O.charCodeAt(N+2)|32)===116)return{type:"NOT",value:"not",start:N,end:A}}}else if(M===4){if(B===116){if((O.charCodeAt(N+1)|32)===114&&(O.charCodeAt(N+2)|32)===117&&(O.charCodeAt(N+3)|32)===101)return{type:"TRUE",value:!0,start:N,end:A}}}else if(M===5){if(B===102){if((O.charCodeAt(N+1)|32)===97&&(O.charCodeAt(N+2)|32)===108&&(O.charCodeAt(N+3)|32)===115&&(O.charCodeAt(N+4)|32)===101)return{type:"FALSE",value:!1,start:N,end:A}}}else if(M===6){if(B===101){if((O.charCodeAt(N+1)|32)===120&&(O.charCodeAt(N+2)|32)===105&&(O.charCodeAt(N+3)|32)===115&&(O.charCodeAt(N+4)|32)===116&&(O.charCodeAt(N+5)|32)===115)return{type:"EXISTS",value:"exists",start:N,end:A}}}}return{type:"IDENT",value:O.slice(N,A),start:N,end:A}}next(){this.skipWhitespace();let O=this.input,V=this.pos;if(V>=this.length)return{type:"EOF",value:"",start:V,end:V};let N=O.charCodeAt(V);switch(N){case 40:return this.pos=V+1,{type:"LPAREN",value:"(",start:V,end:V+1};case 41:return this.pos=V+1,{type:"RPAREN",value:")",start:V,end:V+1};case 91:return this.pos=V+1,{type:"LBRACKET",value:"[",start:V,end:V+1};case 93:return this.pos=V+1,{type:"RBRACKET",value:"]",start:V,end:V+1};case 44:return this.pos=V+1,{type:"COMMA",value:",",start:V,end:V+1};case 63:return this.pos=V+1,{type:"QUESTION",value:"?",start:V,end:V+1};case 61:return this.pos=V+1,{type:"EQ",value:"=",start:V,end:V+1};case 126:return this.pos=V+1,{type:"LIKE",value:"~",start:V,end:V+1};case 58:return this.pos=V+1,{type:"COLON",value:":",start:V,end:V+1}}let A=V+1<this.length?O.charCodeAt(V+1):0;if(N===33){if(A===61)return this.pos=V+2,{type:"NEQ",value:"!=",start:V,end:V+2};if(A===58)return this.pos=V+2,{type:"NOT_COLON",value:"!:",start:V,end:V+2}}if(N===62){if(A===61)return this.pos=V+2,{type:"GTE",value:">=",start:V,end:V+2};return this.pos=V+1,{type:"GT",value:">",start:V,end:V+1}}if(N===60){if(A===61)return this.pos=V+2,{type:"LTE",value:"<=",start:V,end:V+2};return this.pos=V+1,{type:"LT",value:"<",start:V,end:V+1}}if(N===46){if(A===46)return this.pos=V+2,{type:"DOTDOT",value:"..",start:V,end:V+2};return this.pos=V+1,{type:"DOT",value:".",start:V,end:V+1}}if(N===34)return this.readString();if(N>=48&&N<=57)return this.readNumber();if(N===45&&A>=48&&A<=57)return this.readNumber();if(N>=97&&N<=122||N>=65&&N<=90||N===95)return this.readIdentifier();throw new z(`Unexpected character: '${O[V]}'`,V)}}class b extends Error{position;constructor(O,V){super(O);this.position=V;this.name="ParseError"}}class I{lexer;current;nextToken=null;constructor(O){this.lexer=new F(O),this.current=this.lexer.next()}advance(){let O=this.current;if(this.nextToken)this.current=this.nextToken,this.nextToken=null;else this.current=this.lexer.next();return O}check(O){return this.current.type===O}checkAny(...O){let V=this.current.type;for(let N=0;N<O.length;N++)if(V===O[N])return!0;return!1}expect(O,V){if(this.current.type!==O){let N=V??`Expected ${O}, got ${this.current.type}`;throw new b(N,this.current.start)}return this.advance()}parse(){if(this.check("EOF"))throw new b("Empty query",0);let O=this.parseOrExpression();if(!this.check("EOF"))throw new b(`Unexpected token: ${this.current.type}`,this.current.start);return O}parseOrExpression(){let O=this.parseAndExpression();while(this.check("OR")){this.advance();let V=this.parseAndExpression();O={type:"or",left:O,right:V}}return O}parseAndExpression(){let O=this.parseNotExpression();while(this.check("AND")){this.advance();let V=this.parseNotExpression();O={type:"and",left:O,right:V}}return O}parseNotExpression(){if(this.check("NOT"))return this.advance(),{type:"not",expression:this.parseNotExpression()};return this.parsePrimaryExpression()}parsePrimaryExpression(){if(this.check("LPAREN")){this.advance();let O=this.parseOrExpression();return this.expect("RPAREN","Expected closing parenthesis"),O}return this.parseFieldExpression()}parseFieldExpression(){let O=this.parseFieldName();if(this.check("QUESTION"))return this.advance(),{type:"exists",field:O};if(this.check("EXISTS"))return this.advance(),{type:"exists",field:O};if(this.check("COLON")&&this.peekNextIsLBracket())return this.advance(),this.parseOneOfArray(O,"oneOf");if(this.check("NOT_COLON"))return this.advance(),this.parseOneOfArray(O,"notOneOf");if(this.isComparisonOperator()){let V=this.advance(),N=this.tokenToOperator(V);if(N==="="&&this.check("NUMBER")){let B=this.advance().value;if(this.check("DOTDOT")){this.advance();let j=this.expect("NUMBER","Expected number after '..'").value;return{type:"range",field:O,min:B,max:j}}return{type:"comparison",field:O,operator:N,value:{type:"number",value:B}}}let A=this.parseValue();return{type:"comparison",field:O,operator:N,value:A}}return{type:"booleanField",field:O}}peekNextIsLBracket(){if(!this.nextToken)this.nextToken=this.lexer.next();return this.nextToken.type==="LBRACKET"}isComparisonOperator(){let O=this.current.type;return O==="EQ"||O==="NEQ"||O==="GT"||O==="GTE"||O==="LT"||O==="LTE"||O==="LIKE"||O==="COLON"}tokenToOperator(O){switch(O.type){case"EQ":return"=";case"NEQ":return"!=";case"GT":return">";case"GTE":return">=";case"LT":return"<";case"LTE":return"<=";case"LIKE":return"~";case"COLON":return":";default:throw new b(`Invalid operator: ${O.type}`,O.start)}}parseFieldName(){let V=this.expect("IDENT","Expected field name").value;while(this.check("DOT")){this.advance();let N=this.expect("IDENT","Expected identifier after '.'");V+="."+N.value}return V}parseOneOfArray(O,V){this.expect("LBRACKET","Expected '[' after operator");let N=[];if(!this.check("RBRACKET")){N.push(this.parseValue());while(this.check("COMMA"))this.advance(),N.push(this.parseValue())}if(this.expect("RBRACKET","Expected ']' to close array"),N.length===0)throw new b("Array cannot be empty",this.current.start);return{type:V,field:O,values:N}}parseValue(){if(this.check("STRING"))return{type:"string",value:this.advance().value};if(this.check("NUMBER"))return{type:"number",value:this.advance().value};if(this.check("TRUE"))return this.advance(),{type:"boolean",value:!0};if(this.check("FALSE"))return this.advance(),{type:"boolean",value:!1};if(this.check("IDENT")){let V=this.advance().value;while(this.check("DOT")){this.advance();let N=this.expect("IDENT","Expected identifier after '.'");V+="."+N.value}return{type:"identifier",value:V}}throw new b(`Expected value, got ${this.current.type}`,this.current.start)}}function J(O){return new I(O).parse()}var K=(O)=>{try{return{success:!0,ast:J(O)}}catch(V){let N;if(V instanceof b||V instanceof z)N=V.message;else if(V instanceof Error)N=V.message;else N=String(V);return{success:!1,error:N,message:N}}},W=(O)=>{let V=K(O);if(V.success)return V.ast;throw Error(`Failed to parse Filtron query: ${V.error}`)};export{W as parseOrThrow,K as parse};
var A={Tab:9,Newline:10,CarriageReturn:13,Space:32,Bang:33,Quote:34,LParen:40,RParen:41,Comma:44,Minus:45,Dot:46,Slash:47,Zero:48,Nine:57,Colon:58,LessThan:60,Equals:61,GreaterThan:62,Question:63,UpperA:65,UpperZ:90,LBracket:91,Backslash:92,RBracket:93,Underscore:95,LowerA:97,LowerN:110,LowerR:114,LowerT:116,LowerZ:122,Tilde:126};class z extends Error{position;constructor(V,B){super(V);this.position=B;this.name="LexerError"}}class G{pos=0;input;length;constructor(V){this.input=V,this.length=V.length}readString(){let V=this.input,B=this.length,O=this.pos,N=this.pos+1,R=!1,M=N;while(N<B){let b=V.charCodeAt(N);if(b===A.Quote)return this.pos=N+1,{type:"STRING",value:V.slice(M,N),start:O,end:this.pos};if(b===A.Backslash){R=!0;break}N++}if(R){N=M;let b="",D=N;while(N<B){let F=V.charCodeAt(N);if(F===A.Quote)return b+=V.slice(D,N),this.pos=N+1,{type:"STRING",value:b,start:O,end:this.pos};if(F===A.Backslash){b+=V.slice(D,N),N++;let H=V.charCodeAt(N);switch(N++,H){case A.LowerN:b+=`
`;break;case A.LowerT:b+="\t";break;case A.LowerR:b+="\r";break;case A.Backslash:b+="\\";break;case A.Quote:b+='"';break;default:b+=String.fromCharCode(H)}D=N;continue}N++}}throw new z("Unterminated string literal",O)}readNumber(){let V=this.input,B=this.length,O=this.pos,N=this.pos;if(V.charCodeAt(N)===A.Minus)N++;while(N<B){let R=V.charCodeAt(N);if(R<A.Zero||R>A.Nine)break;N++}if(N<B&&V.charCodeAt(N)===A.Dot&&N+1<B){let R=V.charCodeAt(N+1);if(R>=A.Zero&&R<=A.Nine){N++;while(N<B){let M=V.charCodeAt(N);if(M<A.Zero||M>A.Nine)break;N++}return this.pos=N,{type:"NUMBER",value:parseFloat(V.slice(O,N)),start:O,end:N}}}return this.pos=N,{type:"NUMBER",value:parseInt(V.slice(O,N),10),start:O,end:N}}readIdentifier(){let V=this.input,B=this.length,O=this.pos,N=this.pos;while(N<B){let M=V.charCodeAt(N);if(M>=A.LowerA&&M<=A.LowerZ||M>=A.UpperA&&M<=A.UpperZ||M>=A.Zero&&M<=A.Nine||M===A.Underscore)N++;else break}this.pos=N;let R=N-O;if(R>=2&&R<=6){let M=V.charCodeAt(O)|32;if(R===2){if(M===111&&(V.charCodeAt(O+1)|32)===114)return{type:"OR",value:"or",start:O,end:N}}else if(R===3){if(M===97){if((V.charCodeAt(O+1)|32)===110&&(V.charCodeAt(O+2)|32)===100)return{type:"AND",value:"and",start:O,end:N}}else if(M===110){if((V.charCodeAt(O+1)|32)===111&&(V.charCodeAt(O+2)|32)===116)return{type:"NOT",value:"not",start:O,end:N}}}else if(R===4){if(M===116){if((V.charCodeAt(O+1)|32)===114&&(V.charCodeAt(O+2)|32)===117&&(V.charCodeAt(O+3)|32)===101)return{type:"TRUE",value:!0,start:O,end:N}}}else if(R===5){if(M===102){if((V.charCodeAt(O+1)|32)===97&&(V.charCodeAt(O+2)|32)===108&&(V.charCodeAt(O+3)|32)===115&&(V.charCodeAt(O+4)|32)===101)return{type:"FALSE",value:!1,start:O,end:N}}}else if(R===6){if(M===101){if((V.charCodeAt(O+1)|32)===120&&(V.charCodeAt(O+2)|32)===105&&(V.charCodeAt(O+3)|32)===115&&(V.charCodeAt(O+4)|32)===116&&(V.charCodeAt(O+5)|32)===115)return{type:"EXISTS",value:"exists",start:O,end:N}}}}return{type:"IDENT",value:V.slice(O,N),start:O,end:N}}next(){let V=this.input,B=this.length,O=this.pos;while(O<B){let M=V.charCodeAt(O);if(M===A.Space||M===A.Tab||M===A.Newline||M===A.CarriageReturn){O++;continue}if(M===A.Slash&&O+1<B&&V.charCodeAt(O+1)===A.Slash){O+=2;while(O<B&&V.charCodeAt(O)!==A.Newline)O++;continue}break}if(this.pos=O,O>=this.length)return{type:"EOF",value:"",start:O,end:O};let N=V.charCodeAt(O);switch(N){case A.LParen:return this.pos=O+1,{type:"LPAREN",value:"(",start:O,end:O+1};case A.RParen:return this.pos=O+1,{type:"RPAREN",value:")",start:O,end:O+1};case A.LBracket:return this.pos=O+1,{type:"LBRACKET",value:"[",start:O,end:O+1};case A.RBracket:return this.pos=O+1,{type:"RBRACKET",value:"]",start:O,end:O+1};case A.Comma:return this.pos=O+1,{type:"COMMA",value:",",start:O,end:O+1};case A.Question:return this.pos=O+1,{type:"QUESTION",value:"?",start:O,end:O+1};case A.Equals:return this.pos=O+1,{type:"EQ",value:"=",start:O,end:O+1};case A.Tilde:return this.pos=O+1,{type:"LIKE",value:"~",start:O,end:O+1};case A.Colon:return this.pos=O+1,{type:"COLON",value:":",start:O,end:O+1}}let R=O+1<this.length?V.charCodeAt(O+1):0;if(N===A.Bang){if(R===A.Equals)return this.pos=O+2,{type:"NEQ",value:"!=",start:O,end:O+2};if(R===A.Colon)return this.pos=O+2,{type:"NOT_COLON",value:"!:",start:O,end:O+2}}if(N===A.GreaterThan){if(R===A.Equals)return this.pos=O+2,{type:"GTE",value:">=",start:O,end:O+2};return this.pos=O+1,{type:"GT",value:">",start:O,end:O+1}}if(N===A.LessThan){if(R===A.Equals)return this.pos=O+2,{type:"LTE",value:"<=",start:O,end:O+2};return this.pos=O+1,{type:"LT",value:"<",start:O,end:O+1}}if(N===A.Dot){if(R===A.Dot)return this.pos=O+2,{type:"DOTDOT",value:"..",start:O,end:O+2};return this.pos=O+1,{type:"DOT",value:".",start:O,end:O+1}}if(N===A.Quote)return this.readString();if(N>=A.Zero&&N<=A.Nine)return this.readNumber();if(N===A.Minus&&R>=A.Zero&&R<=A.Nine)return this.readNumber();if(N>=A.LowerA&&N<=A.LowerZ||N>=A.UpperA&&N<=A.UpperZ||N===A.Underscore)return this.readIdentifier();throw new z(`Unexpected character: '${V[O]}'`,O)}}class j extends Error{position;constructor(V,B){super(V);this.position=B;this.name="ParseError"}}class I{lexer;current;nextToken=null;constructor(V){this.lexer=new G(V),this.current=this.lexer.next()}advance(){let V=this.current;if(this.nextToken)this.current=this.nextToken,this.nextToken=null;else this.current=this.lexer.next();return V}check(V){return this.current.type===V}expect(V,B){if(this.current.type!==V){let O=B??`Expected ${V}, got ${this.current.type}`;throw new j(O,this.current.start)}return this.advance()}parse(){if(this.check("EOF"))throw new j("Empty query",0);let V=this.parseOrExpression();if(!this.check("EOF"))throw new j(`Unexpected token: ${this.current.type}`,this.current.start);return V}parseOrExpression(){let V=this.parseAndExpression();while(this.check("OR")){this.advance();let B=this.parseAndExpression();V={type:"or",left:V,right:B}}return V}parseAndExpression(){let V=this.parseNotExpression();while(this.check("AND")){this.advance();let B=this.parseNotExpression();V={type:"and",left:V,right:B}}return V}parseNotExpression(){if(this.check("NOT"))return this.advance(),{type:"not",expression:this.parseNotExpression()};return this.parsePrimaryExpression()}parsePrimaryExpression(){if(this.check("LPAREN")){this.advance();let V=this.parseOrExpression();return this.expect("RPAREN","Expected closing parenthesis"),V}return this.parseFieldExpression()}parseFieldExpression(){let V=this.parseFieldName(),B=this.current.type;if(B==="QUESTION"||B==="EXISTS")return this.advance(),{type:"exists",field:V};if(B==="COLON"&&this.peekNextIsLBracket())return this.advance(),this.parseOneOfArray(V,"oneOf");if(B==="NOT_COLON")return this.advance(),this.parseOneOfArray(V,"notOneOf");if(B==="EQ"||B==="NEQ"||B==="GT"||B==="GTE"||B==="LT"||B==="LTE"||B==="LIKE"||B==="COLON"){let O=this.advance(),N=this.tokenToOperator(O);if(N==="="&&this.check("NUMBER")){let b=this.advance().value;if(this.check("DOTDOT")){this.advance();let F=this.expect("NUMBER","Expected number after '..'").value;return{type:"range",field:V,min:b,max:F}}return{type:"comparison",field:V,operator:N,value:{type:"number",value:b}}}let R=this.parseValue();return{type:"comparison",field:V,operator:N,value:R}}return{type:"booleanField",field:V}}peekNextIsLBracket(){if(!this.nextToken)this.nextToken=this.lexer.next();return this.nextToken.type==="LBRACKET"}tokenToOperator(V){switch(V.type){case"EQ":return"=";case"NEQ":return"!=";case"GT":return">";case"GTE":return">=";case"LT":return"<";case"LTE":return"<=";case"LIKE":return"~";case"COLON":return":";default:throw new j(`Invalid operator: ${V.type}`,V.start)}}parseFieldName(){let B=this.expect("IDENT","Expected field name").value;while(this.check("DOT")){this.advance();let O=this.expect("IDENT","Expected identifier after '.'");B+="."+O.value}return B}parseOneOfArray(V,B){this.expect("LBRACKET","Expected '[' after operator");let O=[];if(!this.check("RBRACKET")){O.push(this.parseValue());while(this.check("COMMA"))this.advance(),O.push(this.parseValue())}if(this.expect("RBRACKET","Expected ']' to close array"),O.length===0)throw new j("Array cannot be empty",this.current.start);return{type:B,field:V,values:O}}parseValue(){let V=this.current.type;if(V==="STRING")return{type:"string",value:this.advance().value};if(V==="NUMBER")return{type:"number",value:this.advance().value};if(V==="TRUE")return this.advance(),{type:"boolean",value:!0};if(V==="FALSE")return this.advance(),{type:"boolean",value:!1};if(V==="IDENT"){let O=this.advance().value;while(this.current.type==="DOT"){this.advance();let N=this.expect("IDENT","Expected identifier after '.'");O+="."+N.value}return{type:"identifier",value:O}}throw new j(`Expected value, got ${V}`,this.current.start)}}function J(V){return new I(V).parse()}var K=(V)=>{try{return{success:!0,ast:J(V)}}catch(B){let O;if(B instanceof j||B instanceof z)O=B.message;else if(B instanceof Error)O=B.message;else O=String(B);return{success:!1,error:O,message:O}}},W=(V)=>{let B=K(V);if(B.success)return B.ast;throw Error(`Failed to parse Filtron query: ${B.error}`)};export{W as parseOrThrow,K as parse};
//# debugId=35B38798C3E436E864756E2164756E21
//# debugId=839D03E8D259923E64756E2164756E21
//# sourceMappingURL=index.js.map

@@ -5,9 +5,9 @@ {

"sourcesContent": [
"/**\n * Optimized Lexer for Filtron query language\n *\n * Optimizations:\n * - Works with char codes directly (avoids string creation)\n * - Lookup table for character classification\n * - Inlined hot paths\n * - Fast keyword detection\n * - Fast path for strings without escapes\n */\n\n/**\n * Token types produced by the lexer\n */\nexport type TokenType =\n\t| \"LPAREN\"\n\t| \"RPAREN\"\n\t| \"LBRACKET\"\n\t| \"RBRACKET\"\n\t| \"COMMA\"\n\t| \"QUESTION\"\n\t| \"DOT\"\n\t| \"DOTDOT\"\n\t| \"AND\"\n\t| \"OR\"\n\t| \"NOT\"\n\t| \"EXISTS\"\n\t| \"TRUE\"\n\t| \"FALSE\"\n\t| \"EQ\"\n\t| \"NEQ\"\n\t| \"GT\"\n\t| \"GTE\"\n\t| \"LT\"\n\t| \"LTE\"\n\t| \"LIKE\"\n\t| \"COLON\"\n\t| \"NOT_COLON\"\n\t| \"STRING\"\n\t| \"NUMBER\"\n\t| \"IDENT\"\n\t| \"EOF\";\n\n/**\n * The generic token produced by the lexer\n */\nexport interface Token {\n\ttype: TokenType;\n\tvalue: string | number | boolean;\n\tstart: number;\n\tend: number;\n}\n\n// Character codes\nconst enum CharCode {\n\tTab = 9,\n\tNewline = 10,\n\tCarriageReturn = 13,\n\tSpace = 32,\n\tBang = 33, // !\n\tQuote = 34, // \"\n\tDot = 46, // .\n\tSlash = 47, // /\n\tZero = 48,\n\tNine = 57,\n\tColon = 58, // :\n\tLessThan = 60, // <\n\tEquals = 61, // =\n\tGreaterThan = 62, // >\n\tQuestion = 63, // ?\n\tUpperA = 65,\n\tUpperZ = 90,\n\tLBracket = 91, // [\n\tBackslash = 92, // \\\n\tRBracket = 93, // ]\n\tUnderscore = 95, // _\n\tLowerA = 97,\n\tLowerZ = 122,\n\tTilde = 126, // ~\n\tLParen = 40, // (\n\tRParen = 41, // )\n\tComma = 44, // ,\n\tMinus = 45, // -\n\tLowerN = 110,\n\tLowerR = 114,\n\tLowerT = 116,\n}\n\n// Character classification lookup table (0-127)\nconst enum CharClass {\n\tOther = 0,\n\tWhitespace = 1,\n\tDigit = 2,\n\tAlpha = 4,\n\tAlphaNum = 6, // Alpha | Digit\n}\n\n// Build lookup table at module load\nconst charClass = new Uint8Array(128);\nfor (let i = 0; i < 128; i++) {\n\tif (\n\t\ti === CharCode.Space ||\n\t\ti === CharCode.Tab ||\n\t\ti === CharCode.Newline ||\n\t\ti === CharCode.CarriageReturn\n\t) {\n\t\tcharClass[i] = CharClass.Whitespace;\n\t} else if (i >= CharCode.Zero && i <= CharCode.Nine) {\n\t\tcharClass[i] = CharClass.Digit;\n\t} else if (\n\t\t(i >= CharCode.UpperA && i <= CharCode.UpperZ) ||\n\t\t(i >= CharCode.LowerA && i <= CharCode.LowerZ) ||\n\t\ti === CharCode.Underscore\n\t) {\n\t\tcharClass[i] = CharClass.Alpha;\n\t}\n}\n\n/**\n * Lexer error with position information\n */\nexport class LexerError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic position: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"LexerError\";\n\t}\n}\n\n/**\n * Optimized Lexer for tokenizing Filtron query strings\n */\nexport class Lexer {\n\tprivate pos = 0;\n\tprivate readonly input: string;\n\tprivate readonly length: number;\n\n\tconstructor(input: string) {\n\t\tthis.input = input;\n\t\tthis.length = input.length;\n\t}\n\n\t/**\n\t * Skip whitespace and comments (inlined for performance)\n\t */\n\tprivate skipWhitespace(): void {\n\t\tconst input = this.input;\n\t\tconst length = this.length;\n\t\tlet pos = this.pos;\n\n\t\twhile (pos < length) {\n\t\t\tconst code = input.charCodeAt(pos);\n\n\t\t\t// Fast whitespace check\n\t\t\tif (\n\t\t\t\tcode === CharCode.Space ||\n\t\t\t\tcode === CharCode.Tab ||\n\t\t\t\tcode === CharCode.Newline ||\n\t\t\t\tcode === CharCode.CarriageReturn\n\t\t\t) {\n\t\t\t\tpos++;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Comment check\n\t\t\tif (\n\t\t\t\tcode === CharCode.Slash &&\n\t\t\t\tpos + 1 < length &&\n\t\t\t\tinput.charCodeAt(pos + 1) === CharCode.Slash\n\t\t\t) {\n\t\t\t\tpos += 2;\n\t\t\t\twhile (pos < length && input.charCodeAt(pos) !== CharCode.Newline) {\n\t\t\t\t\tpos++;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\n\t\tthis.pos = pos;\n\t}\n\n\t/**\n\t * Read a string literal - fast path for no escapes\n\t */\n\tprivate readString(): Token {\n\t\tconst input = this.input;\n\t\tconst length = this.length;\n\t\tconst start = this.pos;\n\t\tlet pos = this.pos + 1; // skip opening quote\n\n\t\t// Fast path: scan for closing quote or backslash\n\t\tlet hasEscape = false;\n\t\tconst scanStart = pos;\n\t\twhile (pos < length) {\n\t\t\tconst code = input.charCodeAt(pos);\n\t\t\tif (code === CharCode.Quote) {\n\t\t\t\t// No escapes - just slice\n\t\t\t\tthis.pos = pos + 1;\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"STRING\",\n\t\t\t\t\tvalue: input.slice(scanStart, pos),\n\t\t\t\t\tstart,\n\t\t\t\t\tend: this.pos,\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (code === CharCode.Backslash) {\n\t\t\t\thasEscape = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tpos++;\n\t\t}\n\n\t\t// Slow path: handle escapes\n\t\tif (hasEscape) {\n\t\t\tpos = scanStart;\n\t\t\tlet result = \"\";\n\t\t\tlet chunkStart = pos;\n\n\t\t\twhile (pos < length) {\n\t\t\t\tconst code = input.charCodeAt(pos);\n\n\t\t\t\tif (code === CharCode.Quote) {\n\t\t\t\t\tresult += input.slice(chunkStart, pos);\n\t\t\t\t\tthis.pos = pos + 1;\n\t\t\t\t\treturn { type: \"STRING\", value: result, start, end: this.pos };\n\t\t\t\t}\n\n\t\t\t\tif (code === CharCode.Backslash) {\n\t\t\t\t\tresult += input.slice(chunkStart, pos);\n\t\t\t\t\tpos++;\n\t\t\t\t\tconst escaped = input.charCodeAt(pos);\n\t\t\t\t\tpos++;\n\t\t\t\t\tswitch (escaped) {\n\t\t\t\t\t\tcase CharCode.LowerN:\n\t\t\t\t\t\t\tresult += \"\\n\";\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase CharCode.LowerT:\n\t\t\t\t\t\t\tresult += \"\\t\";\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase CharCode.LowerR:\n\t\t\t\t\t\t\tresult += \"\\r\";\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase CharCode.Backslash:\n\t\t\t\t\t\t\tresult += \"\\\\\";\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase CharCode.Quote:\n\t\t\t\t\t\t\tresult += '\"';\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tresult += String.fromCharCode(escaped);\n\t\t\t\t\t}\n\t\t\t\t\tchunkStart = pos;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tpos++;\n\t\t\t}\n\t\t}\n\n\t\tthrow new LexerError(\"Unterminated string literal\", start);\n\t}\n\n\t/**\n\t * Read a number literal\n\t */\n\tprivate readNumber(): Token {\n\t\tconst input = this.input;\n\t\tconst length = this.length;\n\t\tconst start = this.pos;\n\t\tlet pos = this.pos;\n\n\t\t// Handle negative sign\n\t\tif (input.charCodeAt(pos) === CharCode.Minus) {\n\t\t\tpos++;\n\t\t}\n\n\t\t// Read integer part\n\t\twhile (pos < length) {\n\t\t\tconst code = input.charCodeAt(pos);\n\t\t\tif (code < CharCode.Zero || code > CharCode.Nine) break;\n\t\t\tpos++;\n\t\t}\n\n\t\t// Check for decimal\n\t\tif (pos < length && input.charCodeAt(pos) === CharCode.Dot && pos + 1 < length) {\n\t\t\tconst nextCode = input.charCodeAt(pos + 1);\n\t\t\tif (nextCode >= CharCode.Zero && nextCode <= CharCode.Nine) {\n\t\t\t\tpos++; // skip dot\n\t\t\t\twhile (pos < length) {\n\t\t\t\t\tconst code = input.charCodeAt(pos);\n\t\t\t\t\tif (code < CharCode.Zero || code > CharCode.Nine) break;\n\t\t\t\t\tpos++;\n\t\t\t\t}\n\t\t\t\tthis.pos = pos;\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"NUMBER\",\n\t\t\t\t\tvalue: parseFloat(input.slice(start, pos)),\n\t\t\t\t\tstart,\n\t\t\t\t\tend: pos,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tthis.pos = pos;\n\t\treturn {\n\t\t\ttype: \"NUMBER\",\n\t\t\tvalue: parseInt(input.slice(start, pos), 10),\n\t\t\tstart,\n\t\t\tend: pos,\n\t\t};\n\t}\n\n\t/**\n\t * Read identifier with fast keyword detection\n\t */\n\tprivate readIdentifier(): Token {\n\t\tconst input = this.input;\n\t\tconst length = this.length;\n\t\tconst start = this.pos;\n\t\tlet pos = this.pos;\n\n\t\t// Read all alphanumeric characters\n\t\twhile (pos < length) {\n\t\t\tconst code = input.charCodeAt(pos);\n\t\t\tif (\n\t\t\t\t(code >= CharCode.LowerA && code <= CharCode.LowerZ) ||\n\t\t\t\t(code >= CharCode.UpperA && code <= CharCode.UpperZ) ||\n\t\t\t\t(code >= CharCode.Zero && code <= CharCode.Nine) ||\n\t\t\t\tcode === CharCode.Underscore\n\t\t\t) {\n\t\t\t\tpos++;\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tthis.pos = pos;\n\t\tconst len = pos - start;\n\n\t\t// Fast keyword detection based on length and first char\n\t\tif (len >= 2 && len <= 6) {\n\t\t\tconst firstCode = input.charCodeAt(start) | 0x20; // lowercase\n\n\t\t\tif (len === 2) {\n\t\t\t\t// \"or\"\n\t\t\t\tif (firstCode === 111 && (input.charCodeAt(start + 1) | 0x20) === 114) {\n\t\t\t\t\treturn { type: \"OR\", value: \"or\", start, end: pos };\n\t\t\t\t}\n\t\t\t} else if (len === 3) {\n\t\t\t\t// \"and\", \"not\"\n\t\t\t\tif (firstCode === 97) {\n\t\t\t\t\t// 'a'\n\t\t\t\t\tif (\n\t\t\t\t\t\t(input.charCodeAt(start + 1) | 0x20) === 110 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 2) | 0x20) === 100\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn { type: \"AND\", value: \"and\", start, end: pos };\n\t\t\t\t\t}\n\t\t\t\t} else if (firstCode === 110) {\n\t\t\t\t\t// 'n'\n\t\t\t\t\tif (\n\t\t\t\t\t\t(input.charCodeAt(start + 1) | 0x20) === 111 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 2) | 0x20) === 116\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn { type: \"NOT\", value: \"not\", start, end: pos };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (len === 4) {\n\t\t\t\t// \"true\"\n\t\t\t\tif (firstCode === 116) {\n\t\t\t\t\t// 't'\n\t\t\t\t\tif (\n\t\t\t\t\t\t(input.charCodeAt(start + 1) | 0x20) === 114 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 2) | 0x20) === 117 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 3) | 0x20) === 101\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn { type: \"TRUE\", value: true, start, end: pos };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (len === 5) {\n\t\t\t\t// \"false\"\n\t\t\t\tif (firstCode === 102) {\n\t\t\t\t\t// 'f'\n\t\t\t\t\tif (\n\t\t\t\t\t\t(input.charCodeAt(start + 1) | 0x20) === 97 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 2) | 0x20) === 108 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 3) | 0x20) === 115 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 4) | 0x20) === 101\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn { type: \"FALSE\", value: false, start, end: pos };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (len === 6) {\n\t\t\t\t// \"exists\"\n\t\t\t\tif (firstCode === 101) {\n\t\t\t\t\t// 'e'\n\t\t\t\t\tif (\n\t\t\t\t\t\t(input.charCodeAt(start + 1) | 0x20) === 120 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 2) | 0x20) === 105 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 3) | 0x20) === 115 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 4) | 0x20) === 116 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 5) | 0x20) === 115\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn { type: \"EXISTS\", value: \"exists\", start, end: pos };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn { type: \"IDENT\", value: input.slice(start, pos), start, end: pos };\n\t}\n\n\t/**\n\t * Get the next token\n\t */\n\tnext(): Token {\n\t\tthis.skipWhitespace();\n\n\t\tconst input = this.input;\n\t\tconst pos = this.pos;\n\n\t\tif (pos >= this.length) {\n\t\t\treturn { type: \"EOF\", value: \"\", start: pos, end: pos };\n\t\t}\n\n\t\tconst code = input.charCodeAt(pos);\n\n\t\t// Single character tokens (most common first)\n\t\tswitch (code) {\n\t\t\tcase CharCode.LParen:\n\t\t\t\tthis.pos = pos + 1;\n\t\t\t\treturn { type: \"LPAREN\", value: \"(\", start: pos, end: pos + 1 };\n\t\t\tcase CharCode.RParen:\n\t\t\t\tthis.pos = pos + 1;\n\t\t\t\treturn { type: \"RPAREN\", value: \")\", start: pos, end: pos + 1 };\n\t\t\tcase CharCode.LBracket:\n\t\t\t\tthis.pos = pos + 1;\n\t\t\t\treturn { type: \"LBRACKET\", value: \"[\", start: pos, end: pos + 1 };\n\t\t\tcase CharCode.RBracket:\n\t\t\t\tthis.pos = pos + 1;\n\t\t\t\treturn { type: \"RBRACKET\", value: \"]\", start: pos, end: pos + 1 };\n\t\t\tcase CharCode.Comma:\n\t\t\t\tthis.pos = pos + 1;\n\t\t\t\treturn { type: \"COMMA\", value: \",\", start: pos, end: pos + 1 };\n\t\t\tcase CharCode.Question:\n\t\t\t\tthis.pos = pos + 1;\n\t\t\t\treturn { type: \"QUESTION\", value: \"?\", start: pos, end: pos + 1 };\n\t\t\tcase CharCode.Equals:\n\t\t\t\tthis.pos = pos + 1;\n\t\t\t\treturn { type: \"EQ\", value: \"=\", start: pos, end: pos + 1 };\n\t\t\tcase CharCode.Tilde:\n\t\t\t\tthis.pos = pos + 1;\n\t\t\t\treturn { type: \"LIKE\", value: \"~\", start: pos, end: pos + 1 };\n\t\t\tcase CharCode.Colon:\n\t\t\t\tthis.pos = pos + 1;\n\t\t\t\treturn { type: \"COLON\", value: \":\", start: pos, end: pos + 1 };\n\t\t}\n\n\t\t// Two-character operators\n\t\tconst nextCode = pos + 1 < this.length ? input.charCodeAt(pos + 1) : 0;\n\n\t\tif (code === CharCode.Bang) {\n\t\t\tif (nextCode === CharCode.Equals) {\n\t\t\t\tthis.pos = pos + 2;\n\t\t\t\treturn { type: \"NEQ\", value: \"!=\", start: pos, end: pos + 2 };\n\t\t\t}\n\t\t\tif (nextCode === CharCode.Colon) {\n\t\t\t\tthis.pos = pos + 2;\n\t\t\t\treturn { type: \"NOT_COLON\", value: \"!:\", start: pos, end: pos + 2 };\n\t\t\t}\n\t\t}\n\n\t\tif (code === CharCode.GreaterThan) {\n\t\t\tif (nextCode === CharCode.Equals) {\n\t\t\t\tthis.pos = pos + 2;\n\t\t\t\treturn { type: \"GTE\", value: \">=\", start: pos, end: pos + 2 };\n\t\t\t}\n\t\t\tthis.pos = pos + 1;\n\t\t\treturn { type: \"GT\", value: \">\", start: pos, end: pos + 1 };\n\t\t}\n\n\t\tif (code === CharCode.LessThan) {\n\t\t\tif (nextCode === CharCode.Equals) {\n\t\t\t\tthis.pos = pos + 2;\n\t\t\t\treturn { type: \"LTE\", value: \"<=\", start: pos, end: pos + 2 };\n\t\t\t}\n\t\t\tthis.pos = pos + 1;\n\t\t\treturn { type: \"LT\", value: \"<\", start: pos, end: pos + 1 };\n\t\t}\n\n\t\tif (code === CharCode.Dot) {\n\t\t\tif (nextCode === CharCode.Dot) {\n\t\t\t\tthis.pos = pos + 2;\n\t\t\t\treturn { type: \"DOTDOT\", value: \"..\", start: pos, end: pos + 2 };\n\t\t\t}\n\t\t\tthis.pos = pos + 1;\n\t\t\treturn { type: \"DOT\", value: \".\", start: pos, end: pos + 1 };\n\t\t}\n\n\t\t// String literal\n\t\tif (code === CharCode.Quote) {\n\t\t\treturn this.readString();\n\t\t}\n\n\t\t// Number\n\t\tif (code >= CharCode.Zero && code <= CharCode.Nine) {\n\t\t\treturn this.readNumber();\n\t\t}\n\t\tif (code === CharCode.Minus && nextCode >= CharCode.Zero && nextCode <= CharCode.Nine) {\n\t\t\treturn this.readNumber();\n\t\t}\n\n\t\t// Identifier or keyword\n\t\tif (\n\t\t\t(code >= CharCode.LowerA && code <= CharCode.LowerZ) ||\n\t\t\t(code >= CharCode.UpperA && code <= CharCode.UpperZ) ||\n\t\t\tcode === CharCode.Underscore\n\t\t) {\n\t\t\treturn this.readIdentifier();\n\t\t}\n\n\t\tthrow new LexerError(`Unexpected character: '${input[pos]}'`, pos);\n\t}\n}\n",
"/**\n * Recursive Descent Parser for Filtron\n *\n * Grammar (in order of precedence, lowest to highest):\n *\n * Query = OrExpression\n * OrExpression = AndExpression (OR AndExpression)*\n * AndExpression = NotExpression (AND NotExpression)*\n * NotExpression = NOT NotExpression | PrimaryExpression\n * PrimaryExpression = '(' OrExpression ')' | FieldExpression\n * FieldExpression = FieldName ('?' | EXISTS | ComparisonOp Value RangeSuffix? | OneOfOp '[' Values ']')?\n * FieldName = IDENT ('.' IDENT)*\n * Value = STRING | NUMBER | BOOLEAN | DottedIdent\n * Values = Value (',' Value)*\n * RangeSuffix = '..' NUMBER\n */\n\nimport type {\n\tASTNode,\n\tValue,\n\tComparisonOperator,\n\tComparisonExpression,\n\tOneOfExpression,\n\tNotOneOfExpression,\n\tExistsExpression,\n\tBooleanFieldExpression,\n\tRangeExpression,\n} from \"./types\";\nimport { Lexer, type Token, type TokenType } from \"./lexer\";\n\n/**\n * Parser error with position information\n */\nexport class ParseError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic position: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"ParseError\";\n\t}\n}\n\n/**\n * Parser for Filtron queries\n */\nclass Parser {\n\tprivate lexer: Lexer;\n\tprivate current: Token;\n\tprivate nextToken: Token | null = null;\n\n\tconstructor(input: string) {\n\t\tthis.lexer = new Lexer(input);\n\t\tthis.current = this.lexer.next();\n\t}\n\n\t/**\n\t * Advance to the next token and return the previous one\n\t */\n\tprivate advance(): Token {\n\t\tconst prev = this.current;\n\t\tif (this.nextToken) {\n\t\t\tthis.current = this.nextToken;\n\t\t\tthis.nextToken = null;\n\t\t} else {\n\t\t\tthis.current = this.lexer.next();\n\t\t}\n\t\treturn prev;\n\t}\n\n\t/**\n\t * Check if current token matches the given type\n\t */\n\tprivate check(type: TokenType): boolean {\n\t\treturn this.current.type === type;\n\t}\n\n\t/**\n\t * Check if current token matches any of the given types\n\t * Note: Prefer explicit checks over this method for better performance\n\t */\n\tprivate checkAny(...types: TokenType[]): boolean {\n\t\tconst currentType = this.current.type;\n\t\tfor (let i = 0; i < types.length; i++) {\n\t\t\tif (currentType === types[i]) return true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Consume a token of the expected type, or throw an error\n\t */\n\tprivate expect(type: TokenType, message?: string): Token {\n\t\tif (this.current.type !== type) {\n\t\t\tconst msg = message ?? `Expected ${type}, got ${this.current.type}`;\n\t\t\tthrow new ParseError(msg, this.current.start);\n\t\t}\n\t\treturn this.advance();\n\t}\n\n\t/**\n\t * Parse a complete query\n\t */\n\tparse(): ASTNode {\n\t\tif (this.check(\"EOF\")) {\n\t\t\tthrow new ParseError(\"Empty query\", 0);\n\t\t}\n\n\t\tconst result = this.parseOrExpression();\n\n\t\tif (!this.check(\"EOF\")) {\n\t\t\tthrow new ParseError(`Unexpected token: ${this.current.type}`, this.current.start);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Parse OR expression\n\t * OrExpression = AndExpression (OR AndExpression)*\n\t */\n\tprivate parseOrExpression(): ASTNode {\n\t\tlet left = this.parseAndExpression();\n\n\t\twhile (this.check(\"OR\")) {\n\t\t\tthis.advance();\n\t\t\tconst right = this.parseAndExpression();\n\t\t\tleft = { type: \"or\", left, right };\n\t\t}\n\n\t\treturn left;\n\t}\n\n\t/**\n\t * Parse AND expression\n\t * AndExpression = NotExpression (AND NotExpression)*\n\t */\n\tprivate parseAndExpression(): ASTNode {\n\t\tlet left = this.parseNotExpression();\n\n\t\twhile (this.check(\"AND\")) {\n\t\t\tthis.advance();\n\t\t\tconst right = this.parseNotExpression();\n\t\t\tleft = { type: \"and\", left, right };\n\t\t}\n\n\t\treturn left;\n\t}\n\n\t/**\n\t * Parse NOT expression\n\t * NotExpression = NOT NotExpression | PrimaryExpression\n\t */\n\tprivate parseNotExpression(): ASTNode {\n\t\tif (this.check(\"NOT\")) {\n\t\t\tthis.advance();\n\t\t\tconst expression = this.parseNotExpression();\n\t\t\treturn { type: \"not\", expression };\n\t\t}\n\n\t\treturn this.parsePrimaryExpression();\n\t}\n\n\t/**\n\t * Parse primary expression (highest precedence)\n\t * PrimaryExpression = '(' OrExpression ')' | FieldExpression\n\t */\n\tprivate parsePrimaryExpression(): ASTNode {\n\t\t// Parenthesized expression\n\t\tif (this.check(\"LPAREN\")) {\n\t\t\tthis.advance();\n\t\t\tconst expr = this.parseOrExpression();\n\t\t\tthis.expect(\"RPAREN\", \"Expected closing parenthesis\");\n\t\t\treturn expr;\n\t\t}\n\n\t\treturn this.parseFieldExpression();\n\t}\n\n\t/**\n\t * Parse field expression\n\t * FieldExpression = FieldName ('?' | EXISTS | ComparisonOp Value RangeSuffix? | OneOfOp '[' Values ']')?\n\t */\n\tprivate parseFieldExpression(): ASTNode {\n\t\tconst field = this.parseFieldName();\n\n\t\t// Exists check with ?\n\t\tif (this.check(\"QUESTION\")) {\n\t\t\tthis.advance();\n\t\t\treturn { type: \"exists\", field } as ExistsExpression;\n\t\t}\n\n\t\t// Exists check with EXISTS keyword\n\t\tif (this.check(\"EXISTS\")) {\n\t\t\tthis.advance();\n\t\t\treturn { type: \"exists\", field } as ExistsExpression;\n\t\t}\n\n\t\t// OneOf: field : [values]\n\t\tif (this.check(\"COLON\") && this.peekNextIsLBracket()) {\n\t\t\tthis.advance();\n\t\t\treturn this.parseOneOfArray(field, \"oneOf\");\n\t\t}\n\n\t\t// NotOneOf: field !: [values]\n\t\tif (this.check(\"NOT_COLON\")) {\n\t\t\tthis.advance();\n\t\t\treturn this.parseOneOfArray(field, \"notOneOf\");\n\t\t}\n\n\t\t// Comparison with operator\n\t\tif (this.isComparisonOperator()) {\n\t\t\tconst opToken = this.advance();\n\t\t\tconst operator = this.tokenToOperator(opToken);\n\n\t\t\t// Check for range expression: field = min..max\n\t\t\tif (operator === \"=\" && this.check(\"NUMBER\")) {\n\t\t\t\tconst minToken = this.advance();\n\t\t\t\tconst min = minToken.value as number;\n\n\t\t\t\tif (this.check(\"DOTDOT\")) {\n\t\t\t\t\tthis.advance();\n\t\t\t\t\tconst maxToken = this.expect(\"NUMBER\", \"Expected number after '..'\");\n\t\t\t\t\tconst max = maxToken.value as number;\n\t\t\t\t\treturn { type: \"range\", field, min, max } as RangeExpression;\n\t\t\t\t}\n\n\t\t\t\t// Not a range, just a regular comparison with a number\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"comparison\",\n\t\t\t\t\tfield,\n\t\t\t\t\toperator,\n\t\t\t\t\tvalue: { type: \"number\", value: min },\n\t\t\t\t} as ComparisonExpression;\n\t\t\t}\n\n\t\t\tconst value = this.parseValue();\n\t\t\treturn {\n\t\t\t\ttype: \"comparison\",\n\t\t\t\tfield,\n\t\t\t\toperator,\n\t\t\t\tvalue,\n\t\t\t} as ComparisonExpression;\n\t\t}\n\n\t\t// Boolean field shorthand (just the field name)\n\t\treturn { type: \"booleanField\", field } as BooleanFieldExpression;\n\t}\n\n\t/**\n\t * Check if the token after the current one is LBRACKET\n\t * (used to distinguish : as oneOf vs : as comparison operator)\n\t */\n\tprivate peekNextIsLBracket(): boolean {\n\t\tif (!this.nextToken) {\n\t\t\tthis.nextToken = this.lexer.next();\n\t\t}\n\t\treturn this.nextToken.type === \"LBRACKET\";\n\t}\n\n\t/**\n\t * Check if current token is a comparison operator\n\t */\n\tprivate isComparisonOperator(): boolean {\n\t\tconst t = this.current.type;\n\t\treturn (\n\t\t\tt === \"EQ\" ||\n\t\t\tt === \"NEQ\" ||\n\t\t\tt === \"GT\" ||\n\t\t\tt === \"GTE\" ||\n\t\t\tt === \"LT\" ||\n\t\t\tt === \"LTE\" ||\n\t\t\tt === \"LIKE\" ||\n\t\t\tt === \"COLON\"\n\t\t);\n\t}\n\n\t/**\n\t * Convert a token to its corresponding comparison operator\n\t */\n\tprivate tokenToOperator(token: Token): ComparisonOperator {\n\t\tswitch (token.type) {\n\t\t\tcase \"EQ\":\n\t\t\t\treturn \"=\";\n\t\t\tcase \"NEQ\":\n\t\t\t\treturn \"!=\";\n\t\t\tcase \"GT\":\n\t\t\t\treturn \">\";\n\t\t\tcase \"GTE\":\n\t\t\t\treturn \">=\";\n\t\t\tcase \"LT\":\n\t\t\t\treturn \"<\";\n\t\t\tcase \"LTE\":\n\t\t\t\treturn \"<=\";\n\t\t\tcase \"LIKE\":\n\t\t\t\treturn \"~\";\n\t\t\tcase \"COLON\":\n\t\t\t\treturn \":\";\n\t\t\tdefault:\n\t\t\t\tthrow new ParseError(`Invalid operator: ${token.type}`, token.start);\n\t\t}\n\t}\n\n\t/**\n\t * Parse a field name (possibly dotted)\n\t * FieldName = IDENT ('.' IDENT)*\n\t */\n\tprivate parseFieldName(): string {\n\t\tconst first = this.expect(\"IDENT\", \"Expected field name\");\n\t\tlet name = first.value as string;\n\n\t\twhile (this.check(\"DOT\")) {\n\t\t\tthis.advance(); // consume .\n\t\t\tconst next = this.expect(\"IDENT\", \"Expected identifier after '.'\");\n\t\t\tname += \".\" + next.value;\n\t\t}\n\n\t\treturn name;\n\t}\n\n\t/**\n\t * Parse oneOf/notOneOf array\n\t * '[' Values ']'\n\t */\n\tprivate parseOneOfArray(\n\t\tfield: string,\n\t\ttype: \"oneOf\" | \"notOneOf\",\n\t): OneOfExpression | NotOneOfExpression {\n\t\tthis.expect(\"LBRACKET\", \"Expected '[' after operator\");\n\n\t\tconst values: Value[] = [];\n\n\t\t// Handle non-empty array\n\t\tif (!this.check(\"RBRACKET\")) {\n\t\t\tvalues.push(this.parseValue());\n\n\t\t\twhile (this.check(\"COMMA\")) {\n\t\t\t\tthis.advance(); // consume ,\n\t\t\t\tvalues.push(this.parseValue());\n\t\t\t}\n\t\t}\n\n\t\tthis.expect(\"RBRACKET\", \"Expected ']' to close array\");\n\n\t\tif (values.length === 0) {\n\t\t\tthrow new ParseError(\"Array cannot be empty\", this.current.start);\n\t\t}\n\n\t\treturn { type, field, values };\n\t}\n\n\t/**\n\t * Parse a value\n\t * Value = STRING | NUMBER | BOOLEAN | DottedIdent\n\t */\n\tprivate parseValue(): Value {\n\t\t// String literal\n\t\tif (this.check(\"STRING\")) {\n\t\t\tconst token = this.advance();\n\t\t\treturn { type: \"string\", value: token.value as string };\n\t\t}\n\n\t\t// Number literal\n\t\tif (this.check(\"NUMBER\")) {\n\t\t\tconst token = this.advance();\n\t\t\treturn { type: \"number\", value: token.value as number };\n\t\t}\n\n\t\t// Boolean literal\n\t\tif (this.check(\"TRUE\")) {\n\t\t\tthis.advance();\n\t\t\treturn { type: \"boolean\", value: true };\n\t\t}\n\t\tif (this.check(\"FALSE\")) {\n\t\t\tthis.advance();\n\t\t\treturn { type: \"boolean\", value: false };\n\t\t}\n\n\t\t// Identifier (possibly dotted)\n\t\tif (this.check(\"IDENT\")) {\n\t\t\tconst first = this.advance();\n\t\t\tlet value = first.value as string;\n\n\t\t\twhile (this.check(\"DOT\")) {\n\t\t\t\tthis.advance(); // consume .\n\t\t\t\tconst next = this.expect(\"IDENT\", \"Expected identifier after '.'\");\n\t\t\t\tvalue += \".\" + next.value;\n\t\t\t}\n\n\t\t\treturn { type: \"identifier\", value };\n\t\t}\n\n\t\tthrow new ParseError(`Expected value, got ${this.current.type}`, this.current.start);\n\t}\n}\n\n/**\n * Parse a Filtron query string into an AST\n *\n * @param input - The query string to parse\n * @returns The parsed AST\n * @throws ParseError if the query is invalid\n */\nexport function parseQuery(input: string): ASTNode {\n\tconst parser = new Parser(input);\n\treturn parser.parse();\n}\n",
"/**\n * Filtron Parser\n *\n * High-performance recursive descent parser for Filtron query language.\n */\n\nimport type { ASTNode } from \"./types\";\nimport { LexerError } from \"./lexer\";\nimport { parseQuery, ParseError as RDParseError } from \"./rd-parser\";\n\n/**\n * Result of a successful parse operation\n */\nexport interface ParseSuccess {\n\tsuccess: true;\n\tast: ASTNode;\n}\n\n/**\n * Result of a failed parse operation\n */\nexport interface ParseError {\n\tsuccess: false;\n\terror: string;\n\tmessage: string;\n}\n\n/**\n * The generic parse result\n */\nexport type ParseResult = ParseSuccess | ParseError;\n\n/**\n * Parses a Filtron query string into an Abstract Syntax Tree (AST).\n *\n * @param query - The Filtron query string to parse\n * @returns A ParseResult containing either the AST or an error message\n *\n * @example\n * ```typescript\n * const result = parse('age > 18 AND status = \"active\"');\n * if (result.success) {\n * console.log(result.ast);\n * } else {\n * console.error(result.error);\n * }\n * ```\n */\nexport const parse = (query: string): ParseResult => {\n\ttry {\n\t\tconst ast = parseQuery(query);\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tast,\n\t\t};\n\t} catch (error) {\n\t\tlet message: string;\n\n\t\tif (error instanceof RDParseError || error instanceof LexerError) {\n\t\t\tmessage = error.message;\n\t\t} else if (error instanceof Error) {\n\t\t\tmessage = error.message;\n\t\t} else {\n\t\t\tmessage = String(error);\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: message,\n\t\t\tmessage,\n\t\t};\n\t}\n};\n\n/**\n * Parses a Filtron query string and throws an error if parsing fails.\n * Use this when you want to handle errors with try/catch instead of checking the result.\n *\n * @param query - The Filtron query string to parse\n * @returns The parsed AST\n * @throws Error if parsing fails\n *\n * @example\n * ```typescript\n * try {\n * const ast = parseOrThrow('age > 18');\n * console.log(ast);\n * } catch (error) {\n * console.error('Parse failed:', error.message);\n * }\n * ```\n */\nexport const parseOrThrow = (query: string): ASTNode => {\n\tconst result = parse(query);\n\n\tif (result.success) {\n\t\treturn result.ast;\n\t}\n\n\tthrow new Error(`Failed to parse Filtron query: ${result.error}`);\n};\n"
"/**\n * A Lexer for the Filtron query language\n */\n\n/**\n * Token types produced by the lexer\n */\nexport type TokenType =\n\t| \"LPAREN\"\n\t| \"RPAREN\"\n\t| \"LBRACKET\"\n\t| \"RBRACKET\"\n\t| \"COMMA\"\n\t| \"QUESTION\"\n\t| \"DOT\"\n\t| \"DOTDOT\"\n\t| \"AND\"\n\t| \"OR\"\n\t| \"NOT\"\n\t| \"EXISTS\"\n\t| \"TRUE\"\n\t| \"FALSE\"\n\t| \"EQ\"\n\t| \"NEQ\"\n\t| \"GT\"\n\t| \"GTE\"\n\t| \"LT\"\n\t| \"LTE\"\n\t| \"LIKE\"\n\t| \"COLON\"\n\t| \"NOT_COLON\"\n\t| \"STRING\"\n\t| \"NUMBER\"\n\t| \"IDENT\"\n\t| \"EOF\";\n\n/** Base token properties */\ninterface TokenBase {\n\tstart: number;\n\tend: number;\n}\n\n/** Tokens with string values (punctuation, operators, keywords, identifiers) */\nexport interface StringToken extends TokenBase {\n\ttype:\n\t\t| \"LPAREN\"\n\t\t| \"RPAREN\"\n\t\t| \"LBRACKET\"\n\t\t| \"RBRACKET\"\n\t\t| \"COMMA\"\n\t\t| \"QUESTION\"\n\t\t| \"DOT\"\n\t\t| \"DOTDOT\"\n\t\t| \"AND\"\n\t\t| \"OR\"\n\t\t| \"NOT\"\n\t\t| \"EXISTS\"\n\t\t| \"EQ\"\n\t\t| \"NEQ\"\n\t\t| \"GT\"\n\t\t| \"GTE\"\n\t\t| \"LT\"\n\t\t| \"LTE\"\n\t\t| \"LIKE\"\n\t\t| \"COLON\"\n\t\t| \"NOT_COLON\"\n\t\t| \"STRING\"\n\t\t| \"IDENT\"\n\t\t| \"EOF\";\n\tvalue: string;\n}\n\n/** Tokens with number values */\nexport interface NumberToken extends TokenBase {\n\ttype: \"NUMBER\";\n\tvalue: number;\n}\n\n/** Tokens with boolean values */\nexport interface BooleanToken extends TokenBase {\n\ttype: \"TRUE\" | \"FALSE\";\n\tvalue: boolean;\n}\n\n/**\n * Discriminated union of all token types for proper type narrowing\n */\nexport type Token = StringToken | NumberToken | BooleanToken;\n\n// Character codes\nconst C = {\n\tTab: 9,\n\tNewline: 10,\n\tCarriageReturn: 13,\n\tSpace: 32,\n\tBang: 33,\n\tQuote: 34,\n\tLParen: 40,\n\tRParen: 41,\n\tComma: 44,\n\tMinus: 45,\n\tDot: 46,\n\tSlash: 47,\n\tZero: 48,\n\tNine: 57,\n\tColon: 58,\n\tLessThan: 60,\n\tEquals: 61,\n\tGreaterThan: 62,\n\tQuestion: 63,\n\tUpperA: 65,\n\tUpperZ: 90,\n\tLBracket: 91,\n\tBackslash: 92,\n\tRBracket: 93,\n\tUnderscore: 95,\n\tLowerA: 97,\n\tLowerN: 110,\n\tLowerR: 114,\n\tLowerT: 116,\n\tLowerZ: 122,\n\tTilde: 126,\n} as const;\n\n/**\n * Lexer error with position information\n */\nexport class LexerError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic position: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"LexerError\";\n\t}\n}\n\n/**\n * Lexer for tokenizing Filtron query strings\n */\nexport class Lexer {\n\tprivate pos = 0;\n\tprivate readonly input: string;\n\tprivate readonly length: number;\n\n\tconstructor(input: string) {\n\t\tthis.input = input;\n\t\tthis.length = input.length;\n\t}\n\n\t/**\n\t * Read a string literal - fast path for no escapes\n\t */\n\tprivate readString(): Token {\n\t\tconst input = this.input;\n\t\tconst length = this.length;\n\t\tconst start = this.pos;\n\t\tlet pos = this.pos + 1; // skip opening quote\n\n\t\t// Fast path: scan for closing quote or backslash\n\t\tlet hasEscape = false;\n\t\tconst scanStart = pos;\n\t\twhile (pos < length) {\n\t\t\tconst code = input.charCodeAt(pos);\n\t\t\tif (code === C.Quote) {\n\t\t\t\t// No escapes - just slice\n\t\t\t\tthis.pos = pos + 1;\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"STRING\",\n\t\t\t\t\tvalue: input.slice(scanStart, pos),\n\t\t\t\t\tstart,\n\t\t\t\t\tend: this.pos,\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (code === C.Backslash) {\n\t\t\t\thasEscape = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tpos++;\n\t\t}\n\n\t\t// Slow path: handle escapes\n\t\tif (hasEscape) {\n\t\t\tpos = scanStart;\n\t\t\tlet result = \"\";\n\t\t\tlet chunkStart = pos;\n\n\t\t\twhile (pos < length) {\n\t\t\t\tconst code = input.charCodeAt(pos);\n\n\t\t\t\tif (code === C.Quote) {\n\t\t\t\t\tresult += input.slice(chunkStart, pos);\n\t\t\t\t\tthis.pos = pos + 1;\n\t\t\t\t\treturn { type: \"STRING\", value: result, start, end: this.pos };\n\t\t\t\t}\n\n\t\t\t\tif (code === C.Backslash) {\n\t\t\t\t\tresult += input.slice(chunkStart, pos);\n\t\t\t\t\tpos++;\n\t\t\t\t\tconst escaped = input.charCodeAt(pos);\n\t\t\t\t\tpos++;\n\t\t\t\t\tswitch (escaped) {\n\t\t\t\t\t\tcase C.LowerN:\n\t\t\t\t\t\t\tresult += \"\\n\";\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase C.LowerT:\n\t\t\t\t\t\t\tresult += \"\\t\";\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase C.LowerR:\n\t\t\t\t\t\t\tresult += \"\\r\";\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase C.Backslash:\n\t\t\t\t\t\t\tresult += \"\\\\\";\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase C.Quote:\n\t\t\t\t\t\t\tresult += '\"';\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tresult += String.fromCharCode(escaped);\n\t\t\t\t\t}\n\t\t\t\t\tchunkStart = pos;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tpos++;\n\t\t\t}\n\t\t}\n\n\t\tthrow new LexerError(\"Unterminated string literal\", start);\n\t}\n\n\t/**\n\t * Read a number literal\n\t */\n\tprivate readNumber(): Token {\n\t\tconst input = this.input;\n\t\tconst length = this.length;\n\t\tconst start = this.pos;\n\t\tlet pos = this.pos;\n\n\t\t// Handle negative sign\n\t\tif (input.charCodeAt(pos) === C.Minus) {\n\t\t\tpos++;\n\t\t}\n\n\t\t// Read integer part\n\t\twhile (pos < length) {\n\t\t\tconst code = input.charCodeAt(pos);\n\t\t\tif (code < C.Zero || code > C.Nine) break;\n\t\t\tpos++;\n\t\t}\n\n\t\t// Check for decimal\n\t\tif (pos < length && input.charCodeAt(pos) === C.Dot && pos + 1 < length) {\n\t\t\tconst nextCode = input.charCodeAt(pos + 1);\n\t\t\tif (nextCode >= C.Zero && nextCode <= C.Nine) {\n\t\t\t\tpos++; // skip dot\n\t\t\t\twhile (pos < length) {\n\t\t\t\t\tconst code = input.charCodeAt(pos);\n\t\t\t\t\tif (code < C.Zero || code > C.Nine) break;\n\t\t\t\t\tpos++;\n\t\t\t\t}\n\t\t\t\tthis.pos = pos;\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"NUMBER\",\n\t\t\t\t\tvalue: parseFloat(input.slice(start, pos)),\n\t\t\t\t\tstart,\n\t\t\t\t\tend: pos,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tthis.pos = pos;\n\t\treturn {\n\t\t\ttype: \"NUMBER\",\n\t\t\tvalue: parseInt(input.slice(start, pos), 10),\n\t\t\tstart,\n\t\t\tend: pos,\n\t\t};\n\t}\n\n\t/**\n\t * Read identifier with fast keyword detection\n\t */\n\tprivate readIdentifier(): Token {\n\t\tconst input = this.input;\n\t\tconst length = this.length;\n\t\tconst start = this.pos;\n\t\tlet pos = this.pos;\n\n\t\t// Read all alphanumeric characters\n\t\twhile (pos < length) {\n\t\t\tconst code = input.charCodeAt(pos);\n\t\t\tif (\n\t\t\t\t(code >= C.LowerA && code <= C.LowerZ) ||\n\t\t\t\t(code >= C.UpperA && code <= C.UpperZ) ||\n\t\t\t\t(code >= C.Zero && code <= C.Nine) ||\n\t\t\t\tcode === C.Underscore\n\t\t\t) {\n\t\t\t\tpos++;\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tthis.pos = pos;\n\t\tconst len = pos - start;\n\n\t\t// Keyword detection based on length and first char\n\t\tif (len >= 2 && len <= 6) {\n\t\t\tconst firstCode = input.charCodeAt(start) | 0x20; // lowercase\n\n\t\t\tif (len === 2) {\n\t\t\t\t// \"or\"\n\t\t\t\tif (firstCode === 111 && (input.charCodeAt(start + 1) | 0x20) === 114) {\n\t\t\t\t\treturn { type: \"OR\", value: \"or\", start, end: pos };\n\t\t\t\t}\n\t\t\t} else if (len === 3) {\n\t\t\t\t// \"and\", \"not\"\n\t\t\t\tif (firstCode === 97) {\n\t\t\t\t\t// 'a'\n\t\t\t\t\tif (\n\t\t\t\t\t\t(input.charCodeAt(start + 1) | 0x20) === 110 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 2) | 0x20) === 100\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn { type: \"AND\", value: \"and\", start, end: pos };\n\t\t\t\t\t}\n\t\t\t\t} else if (firstCode === 110) {\n\t\t\t\t\t// 'n'\n\t\t\t\t\tif (\n\t\t\t\t\t\t(input.charCodeAt(start + 1) | 0x20) === 111 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 2) | 0x20) === 116\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn { type: \"NOT\", value: \"not\", start, end: pos };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (len === 4) {\n\t\t\t\t// \"true\"\n\t\t\t\tif (firstCode === 116) {\n\t\t\t\t\t// 't'\n\t\t\t\t\tif (\n\t\t\t\t\t\t(input.charCodeAt(start + 1) | 0x20) === 114 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 2) | 0x20) === 117 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 3) | 0x20) === 101\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn { type: \"TRUE\", value: true, start, end: pos };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (len === 5) {\n\t\t\t\t// \"false\"\n\t\t\t\tif (firstCode === 102) {\n\t\t\t\t\t// 'f'\n\t\t\t\t\tif (\n\t\t\t\t\t\t(input.charCodeAt(start + 1) | 0x20) === 97 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 2) | 0x20) === 108 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 3) | 0x20) === 115 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 4) | 0x20) === 101\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn { type: \"FALSE\", value: false, start, end: pos };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (len === 6) {\n\t\t\t\t// \"exists\"\n\t\t\t\tif (firstCode === 101) {\n\t\t\t\t\t// 'e'\n\t\t\t\t\tif (\n\t\t\t\t\t\t(input.charCodeAt(start + 1) | 0x20) === 120 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 2) | 0x20) === 105 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 3) | 0x20) === 115 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 4) | 0x20) === 116 &&\n\t\t\t\t\t\t(input.charCodeAt(start + 5) | 0x20) === 115\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn { type: \"EXISTS\", value: \"exists\", start, end: pos };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn { type: \"IDENT\", value: input.slice(start, pos), start, end: pos };\n\t}\n\n\t/**\n\t * Get the next token\n\t */\n\tnext(): Token {\n\t\tconst input = this.input;\n\t\tconst length = this.length;\n\n\t\tlet pos = this.pos;\n\t\twhile (pos < length) {\n\t\t\tconst c = input.charCodeAt(pos);\n\t\t\tif (c === C.Space || c === C.Tab || c === C.Newline || c === C.CarriageReturn) {\n\t\t\t\tpos++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (c === C.Slash && pos + 1 < length && input.charCodeAt(pos + 1) === C.Slash) {\n\t\t\t\tpos += 2;\n\t\t\t\twhile (pos < length && input.charCodeAt(pos) !== C.Newline) {\n\t\t\t\t\tpos++;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tthis.pos = pos;\n\n\t\tif (pos >= this.length) {\n\t\t\treturn { type: \"EOF\", value: \"\", start: pos, end: pos };\n\t\t}\n\n\t\tconst code = input.charCodeAt(pos);\n\n\t\t// Single character tokens (most common first)\n\t\tswitch (code) {\n\t\t\tcase C.LParen:\n\t\t\t\tthis.pos = pos + 1;\n\t\t\t\treturn { type: \"LPAREN\", value: \"(\", start: pos, end: pos + 1 };\n\t\t\tcase C.RParen:\n\t\t\t\tthis.pos = pos + 1;\n\t\t\t\treturn { type: \"RPAREN\", value: \")\", start: pos, end: pos + 1 };\n\t\t\tcase C.LBracket:\n\t\t\t\tthis.pos = pos + 1;\n\t\t\t\treturn { type: \"LBRACKET\", value: \"[\", start: pos, end: pos + 1 };\n\t\t\tcase C.RBracket:\n\t\t\t\tthis.pos = pos + 1;\n\t\t\t\treturn { type: \"RBRACKET\", value: \"]\", start: pos, end: pos + 1 };\n\t\t\tcase C.Comma:\n\t\t\t\tthis.pos = pos + 1;\n\t\t\t\treturn { type: \"COMMA\", value: \",\", start: pos, end: pos + 1 };\n\t\t\tcase C.Question:\n\t\t\t\tthis.pos = pos + 1;\n\t\t\t\treturn { type: \"QUESTION\", value: \"?\", start: pos, end: pos + 1 };\n\t\t\tcase C.Equals:\n\t\t\t\tthis.pos = pos + 1;\n\t\t\t\treturn { type: \"EQ\", value: \"=\", start: pos, end: pos + 1 };\n\t\t\tcase C.Tilde:\n\t\t\t\tthis.pos = pos + 1;\n\t\t\t\treturn { type: \"LIKE\", value: \"~\", start: pos, end: pos + 1 };\n\t\t\tcase C.Colon:\n\t\t\t\tthis.pos = pos + 1;\n\t\t\t\treturn { type: \"COLON\", value: \":\", start: pos, end: pos + 1 };\n\t\t}\n\n\t\t// Two-character operators\n\t\tconst nextCode = pos + 1 < this.length ? input.charCodeAt(pos + 1) : 0;\n\n\t\tif (code === C.Bang) {\n\t\t\tif (nextCode === C.Equals) {\n\t\t\t\tthis.pos = pos + 2;\n\t\t\t\treturn { type: \"NEQ\", value: \"!=\", start: pos, end: pos + 2 };\n\t\t\t}\n\t\t\tif (nextCode === C.Colon) {\n\t\t\t\tthis.pos = pos + 2;\n\t\t\t\treturn { type: \"NOT_COLON\", value: \"!:\", start: pos, end: pos + 2 };\n\t\t\t}\n\t\t}\n\n\t\tif (code === C.GreaterThan) {\n\t\t\tif (nextCode === C.Equals) {\n\t\t\t\tthis.pos = pos + 2;\n\t\t\t\treturn { type: \"GTE\", value: \">=\", start: pos, end: pos + 2 };\n\t\t\t}\n\t\t\tthis.pos = pos + 1;\n\t\t\treturn { type: \"GT\", value: \">\", start: pos, end: pos + 1 };\n\t\t}\n\n\t\tif (code === C.LessThan) {\n\t\t\tif (nextCode === C.Equals) {\n\t\t\t\tthis.pos = pos + 2;\n\t\t\t\treturn { type: \"LTE\", value: \"<=\", start: pos, end: pos + 2 };\n\t\t\t}\n\t\t\tthis.pos = pos + 1;\n\t\t\treturn { type: \"LT\", value: \"<\", start: pos, end: pos + 1 };\n\t\t}\n\n\t\tif (code === C.Dot) {\n\t\t\tif (nextCode === C.Dot) {\n\t\t\t\tthis.pos = pos + 2;\n\t\t\t\treturn { type: \"DOTDOT\", value: \"..\", start: pos, end: pos + 2 };\n\t\t\t}\n\t\t\tthis.pos = pos + 1;\n\t\t\treturn { type: \"DOT\", value: \".\", start: pos, end: pos + 1 };\n\t\t}\n\n\t\t// String literal\n\t\tif (code === C.Quote) {\n\t\t\treturn this.readString();\n\t\t}\n\n\t\t// Number\n\t\tif (code >= C.Zero && code <= C.Nine) {\n\t\t\treturn this.readNumber();\n\t\t}\n\t\tif (code === C.Minus && nextCode >= C.Zero && nextCode <= C.Nine) {\n\t\t\treturn this.readNumber();\n\t\t}\n\n\t\t// Identifier or keyword\n\t\tif (\n\t\t\t(code >= C.LowerA && code <= C.LowerZ) ||\n\t\t\t(code >= C.UpperA && code <= C.UpperZ) ||\n\t\t\tcode === C.Underscore\n\t\t) {\n\t\t\treturn this.readIdentifier();\n\t\t}\n\n\t\tthrow new LexerError(`Unexpected character: '${input[pos]}'`, pos);\n\t}\n}\n",
"/**\n * Recursive Descent Parser for Filtron\n * https://en.wikipedia.org/wiki/Recursive_descent_parser\n *\n * Grammar (in order of precedence, lowest to highest):\n *\n * Query = OrExpression\n * OrExpression = AndExpression (OR AndExpression)*\n * AndExpression = NotExpression (AND NotExpression)*\n * NotExpression = NOT NotExpression | PrimaryExpression\n * PrimaryExpression = '(' OrExpression ')' | FieldExpression\n * FieldExpression = FieldName ('?' | EXISTS | ComparisonOp Value RangeSuffix? | OneOfOp '[' Values ']')?\n * FieldName = IDENT ('.' IDENT)*\n * Value = STRING | NUMBER | BOOLEAN | DottedIdent\n * Values = Value (',' Value)*\n * RangeSuffix = '..' NUMBER\n */\n\nimport type {\n\tASTNode,\n\tValue,\n\tComparisonOperator,\n\tComparisonExpression,\n\tOneOfExpression,\n\tNotOneOfExpression,\n\tExistsExpression,\n\tBooleanFieldExpression,\n\tRangeExpression,\n} from \"./types\";\nimport { Lexer, type Token, type TokenType } from \"./lexer\";\n\n/**\n * Parser error with position information\n */\nexport class ParseError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic position: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"ParseError\";\n\t}\n}\n\n/**\n * Parser for Filtron queries\n */\nclass Parser {\n\tprivate lexer: Lexer;\n\tprivate current: Token;\n\tprivate nextToken: Token | null = null;\n\n\tconstructor(input: string) {\n\t\tthis.lexer = new Lexer(input);\n\t\tthis.current = this.lexer.next();\n\t}\n\n\t/**\n\t * Advance to the next token and return the previous one\n\t */\n\tprivate advance(): Token {\n\t\tconst prev = this.current;\n\t\tif (this.nextToken) {\n\t\t\tthis.current = this.nextToken;\n\t\t\tthis.nextToken = null;\n\t\t} else {\n\t\t\tthis.current = this.lexer.next();\n\t\t}\n\t\treturn prev;\n\t}\n\n\t/**\n\t * Check if current token matches the given type\n\t */\n\tprivate check(type: TokenType): boolean {\n\t\treturn this.current.type === type;\n\t}\n\n\t/**\n\t * Consume a token of the expected type, or throw an error\n\t */\n\tprivate expect(type: TokenType, message?: string): Token {\n\t\tif (this.current.type !== type) {\n\t\t\tconst msg = message ?? `Expected ${type}, got ${this.current.type}`;\n\t\t\tthrow new ParseError(msg, this.current.start);\n\t\t}\n\t\treturn this.advance();\n\t}\n\n\t/**\n\t * Parse a complete query\n\t */\n\tparse(): ASTNode {\n\t\tif (this.check(\"EOF\")) {\n\t\t\tthrow new ParseError(\"Empty query\", 0);\n\t\t}\n\n\t\tconst result = this.parseOrExpression();\n\n\t\tif (!this.check(\"EOF\")) {\n\t\t\tthrow new ParseError(`Unexpected token: ${this.current.type}`, this.current.start);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Parse OR expression\n\t * OrExpression = AndExpression (OR AndExpression)*\n\t */\n\tprivate parseOrExpression(): ASTNode {\n\t\tlet left = this.parseAndExpression();\n\n\t\twhile (this.check(\"OR\")) {\n\t\t\tthis.advance();\n\t\t\tconst right = this.parseAndExpression();\n\t\t\tleft = { type: \"or\", left, right };\n\t\t}\n\n\t\treturn left;\n\t}\n\n\t/**\n\t * Parse AND expression\n\t * AndExpression = NotExpression (AND NotExpression)*\n\t */\n\tprivate parseAndExpression(): ASTNode {\n\t\tlet left = this.parseNotExpression();\n\n\t\twhile (this.check(\"AND\")) {\n\t\t\tthis.advance();\n\t\t\tconst right = this.parseNotExpression();\n\t\t\tleft = { type: \"and\", left, right };\n\t\t}\n\n\t\treturn left;\n\t}\n\n\t/**\n\t * Parse NOT expression\n\t * NotExpression = NOT NotExpression | PrimaryExpression\n\t */\n\tprivate parseNotExpression(): ASTNode {\n\t\tif (this.check(\"NOT\")) {\n\t\t\tthis.advance();\n\t\t\tconst expression = this.parseNotExpression();\n\t\t\treturn { type: \"not\", expression };\n\t\t}\n\n\t\treturn this.parsePrimaryExpression();\n\t}\n\n\t/**\n\t * Parse primary expression (highest precedence)\n\t * PrimaryExpression = '(' OrExpression ')' | FieldExpression\n\t */\n\tprivate parsePrimaryExpression(): ASTNode {\n\t\t// Parenthesized expression\n\t\tif (this.check(\"LPAREN\")) {\n\t\t\tthis.advance();\n\t\t\tconst expr = this.parseOrExpression();\n\t\t\tthis.expect(\"RPAREN\", \"Expected closing parenthesis\");\n\t\t\treturn expr;\n\t\t}\n\n\t\treturn this.parseFieldExpression();\n\t}\n\n\t/**\n\t * Parse field expression\n\t * FieldExpression = FieldName ('?' | EXISTS | ComparisonOp Value RangeSuffix? | OneOfOp '[' Values ']')?\n\t */\n\tprivate parseFieldExpression(): ASTNode {\n\t\tconst field = this.parseFieldName();\n\t\tconst t = this.current.type;\n\n\t\t// Exists check with ? or EXISTS\n\t\tif (t === \"QUESTION\" || t === \"EXISTS\") {\n\t\t\tthis.advance();\n\t\t\treturn { type: \"exists\", field } as ExistsExpression;\n\t\t}\n\n\t\t// OneOf: field : [values]\n\t\tif (t === \"COLON\" && this.peekNextIsLBracket()) {\n\t\t\tthis.advance();\n\t\t\treturn this.parseOneOfArray(field, \"oneOf\");\n\t\t}\n\n\t\t// NotOneOf: field !: [values]\n\t\tif (t === \"NOT_COLON\") {\n\t\t\tthis.advance();\n\t\t\treturn this.parseOneOfArray(field, \"notOneOf\");\n\t\t}\n\n\t\t// Comparison with operator\n\t\tif (\n\t\t\tt === \"EQ\" ||\n\t\t\tt === \"NEQ\" ||\n\t\t\tt === \"GT\" ||\n\t\t\tt === \"GTE\" ||\n\t\t\tt === \"LT\" ||\n\t\t\tt === \"LTE\" ||\n\t\t\tt === \"LIKE\" ||\n\t\t\tt === \"COLON\"\n\t\t) {\n\t\t\tconst opToken = this.advance();\n\t\t\tconst operator = this.tokenToOperator(opToken);\n\n\t\t\t// Check for range expression: field = min..max\n\t\t\tif (operator === \"=\" && this.check(\"NUMBER\")) {\n\t\t\t\tconst minToken = this.advance();\n\t\t\t\tconst min = minToken.value as number;\n\n\t\t\t\tif (this.check(\"DOTDOT\")) {\n\t\t\t\t\tthis.advance();\n\t\t\t\t\tconst maxToken = this.expect(\"NUMBER\", \"Expected number after '..'\");\n\t\t\t\t\tconst max = maxToken.value as number;\n\t\t\t\t\treturn { type: \"range\", field, min, max } as RangeExpression;\n\t\t\t\t}\n\n\t\t\t\t// Not a range, just a regular comparison with a number\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"comparison\",\n\t\t\t\t\tfield,\n\t\t\t\t\toperator,\n\t\t\t\t\tvalue: { type: \"number\", value: min },\n\t\t\t\t} as ComparisonExpression;\n\t\t\t}\n\n\t\t\tconst value = this.parseValue();\n\t\t\treturn {\n\t\t\t\ttype: \"comparison\",\n\t\t\t\tfield,\n\t\t\t\toperator,\n\t\t\t\tvalue,\n\t\t\t} as ComparisonExpression;\n\t\t}\n\n\t\t// Boolean field shorthand (just the field name)\n\t\treturn { type: \"booleanField\", field } as BooleanFieldExpression;\n\t}\n\n\t/**\n\t * Check if the token after the current one is LBRACKET\n\t * (used to distinguish : as oneOf vs : as comparison operator)\n\t */\n\tprivate peekNextIsLBracket(): boolean {\n\t\tif (!this.nextToken) {\n\t\t\tthis.nextToken = this.lexer.next();\n\t\t}\n\t\treturn this.nextToken.type === \"LBRACKET\";\n\t}\n\n\t/**\n\t * Convert a token to its corresponding comparison operator\n\t */\n\tprivate tokenToOperator(token: Token): ComparisonOperator {\n\t\tswitch (token.type) {\n\t\t\tcase \"EQ\":\n\t\t\t\treturn \"=\";\n\t\t\tcase \"NEQ\":\n\t\t\t\treturn \"!=\";\n\t\t\tcase \"GT\":\n\t\t\t\treturn \">\";\n\t\t\tcase \"GTE\":\n\t\t\t\treturn \">=\";\n\t\t\tcase \"LT\":\n\t\t\t\treturn \"<\";\n\t\t\tcase \"LTE\":\n\t\t\t\treturn \"<=\";\n\t\t\tcase \"LIKE\":\n\t\t\t\treturn \"~\";\n\t\t\tcase \"COLON\":\n\t\t\t\treturn \":\";\n\t\t\tdefault:\n\t\t\t\tthrow new ParseError(`Invalid operator: ${token.type}`, token.start);\n\t\t}\n\t}\n\n\t/**\n\t * Parse a field name (possibly dotted)\n\t * FieldName = IDENT ('.' IDENT)*\n\t */\n\tprivate parseFieldName(): string {\n\t\tconst first = this.expect(\"IDENT\", \"Expected field name\");\n\t\tlet name = first.value as string;\n\n\t\twhile (this.check(\"DOT\")) {\n\t\t\tthis.advance(); // consume .\n\t\t\tconst next = this.expect(\"IDENT\", \"Expected identifier after '.'\");\n\t\t\tname += \".\" + next.value;\n\t\t}\n\n\t\treturn name;\n\t}\n\n\t/**\n\t * Parse oneOf/notOneOf array\n\t * '[' Values ']'\n\t */\n\tprivate parseOneOfArray(\n\t\tfield: string,\n\t\ttype: \"oneOf\" | \"notOneOf\",\n\t): OneOfExpression | NotOneOfExpression {\n\t\tthis.expect(\"LBRACKET\", \"Expected '[' after operator\");\n\n\t\tconst values: Value[] = [];\n\n\t\t// Handle non-empty array\n\t\tif (!this.check(\"RBRACKET\")) {\n\t\t\tvalues.push(this.parseValue());\n\n\t\t\twhile (this.check(\"COMMA\")) {\n\t\t\t\tthis.advance(); // consume ,\n\t\t\t\tvalues.push(this.parseValue());\n\t\t\t}\n\t\t}\n\n\t\tthis.expect(\"RBRACKET\", \"Expected ']' to close array\");\n\n\t\tif (values.length === 0) {\n\t\t\tthrow new ParseError(\"Array cannot be empty\", this.current.start);\n\t\t}\n\n\t\treturn { type, field, values };\n\t}\n\n\t/**\n\t * Parse a value\n\t * Value = STRING | NUMBER | BOOLEAN | DottedIdent\n\t */\n\tprivate parseValue(): Value {\n\t\tconst t = this.current.type;\n\n\t\t// String literal\n\t\tif (t === \"STRING\") {\n\t\t\tconst token = this.advance();\n\t\t\treturn { type: \"string\", value: token.value as string };\n\t\t}\n\n\t\t// Number literal\n\t\tif (t === \"NUMBER\") {\n\t\t\tconst token = this.advance();\n\t\t\treturn { type: \"number\", value: token.value as number };\n\t\t}\n\n\t\t// Boolean literal\n\t\tif (t === \"TRUE\") {\n\t\t\tthis.advance();\n\t\t\treturn { type: \"boolean\", value: true };\n\t\t}\n\t\tif (t === \"FALSE\") {\n\t\t\tthis.advance();\n\t\t\treturn { type: \"boolean\", value: false };\n\t\t}\n\n\t\t// Identifier (possibly dotted)\n\t\tif (t === \"IDENT\") {\n\t\t\tconst first = this.advance();\n\t\t\tlet value = first.value as string;\n\n\t\t\twhile (this.current.type === \"DOT\") {\n\t\t\t\tthis.advance(); // consume .\n\t\t\t\tconst next = this.expect(\"IDENT\", \"Expected identifier after '.'\");\n\t\t\t\tvalue += \".\" + next.value;\n\t\t\t}\n\n\t\t\treturn { type: \"identifier\", value };\n\t\t}\n\n\t\tthrow new ParseError(`Expected value, got ${t}`, this.current.start);\n\t}\n}\n\n/**\n * Parse a Filtron query string into an AST\n *\n * @param input - The query string to parse\n * @returns The parsed AST\n * @throws ParseError if the query is invalid\n */\nexport function parseQuery(input: string): ASTNode {\n\tconst parser = new Parser(input);\n\treturn parser.parse();\n}\n",
"/**\n * The Filtron parser public API\n */\n\nimport type { ASTNode } from \"./types\";\nimport { LexerError } from \"./lexer\";\nimport { parseQuery, ParseError as RDParseError } from \"./rd-parser\";\n\n/**\n * Result of a successful parse operation\n */\nexport interface ParseSuccess {\n\tsuccess: true;\n\tast: ASTNode;\n}\n\n/**\n * Result of a failed parse operation\n */\nexport interface ParseError {\n\tsuccess: false;\n\terror: string;\n\tmessage: string;\n}\n\n/**\n * The generic parse result\n */\nexport type ParseResult = ParseSuccess | ParseError;\n\n/**\n * Parses a Filtron query string into an Abstract Syntax Tree (AST).\n *\n * @param query - The Filtron query string to parse\n * @returns A ParseResult containing either the AST or an error message\n *\n * @example\n * ```typescript\n * const result = parse('age > 18 AND status = \"active\"');\n * if (result.success) {\n * console.log(result.ast);\n * } else {\n * console.error(result.error);\n * }\n * ```\n */\nexport const parse = (query: string): ParseResult => {\n\ttry {\n\t\tconst ast = parseQuery(query);\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tast,\n\t\t};\n\t} catch (error) {\n\t\tlet message: string;\n\n\t\tif (error instanceof RDParseError || error instanceof LexerError) {\n\t\t\tmessage = error.message;\n\t\t} else if (error instanceof Error) {\n\t\t\tmessage = error.message;\n\t\t} else {\n\t\t\tmessage = String(error);\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: message,\n\t\t\tmessage,\n\t\t};\n\t}\n};\n\n/**\n * Parses a Filtron query string and throws an error if parsing fails.\n * Use this when you want to handle errors with try/catch instead of checking the result.\n *\n * @param query - The Filtron query string to parse\n * @returns The parsed AST\n * @throws Error if parsing fails\n *\n * @example\n * ```typescript\n * try {\n * const ast = parseOrThrow('age > 18');\n * console.log(ast);\n * } catch (error) {\n * console.error('Parse failed:', error.message);\n * }\n * ```\n */\nexport const parseOrThrow = (query: string): ASTNode => {\n\tconst result = parse(query);\n\n\tif (result.success) {\n\t\treturn result.ast;\n\t}\n\n\tthrow new Error(`Failed to parse Filtron query: ${result.error}`);\n};\n"
],
"mappings": "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",
"debugId": "35B38798C3E436E864756E2164756E21",
"mappings": "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",
"debugId": "839D03E8D259923E64756E2164756E21",
"names": []
}
/**
* Optimized Lexer for Filtron query language
*
* Optimizations:
* - Works with char codes directly (avoids string creation)
* - Lookup table for character classification
* - Inlined hot paths
* - Fast keyword detection
* - Fast path for strings without escapes
* A Lexer for the Filtron query language
*/

@@ -15,12 +8,27 @@ /**

export type TokenType = "LPAREN" | "RPAREN" | "LBRACKET" | "RBRACKET" | "COMMA" | "QUESTION" | "DOT" | "DOTDOT" | "AND" | "OR" | "NOT" | "EXISTS" | "TRUE" | "FALSE" | "EQ" | "NEQ" | "GT" | "GTE" | "LT" | "LTE" | "LIKE" | "COLON" | "NOT_COLON" | "STRING" | "NUMBER" | "IDENT" | "EOF";
/**
* The generic token produced by the lexer
*/
export interface Token {
type: TokenType;
value: string | number | boolean;
/** Base token properties */
interface TokenBase {
start: number;
end: number;
}
/** Tokens with string values (punctuation, operators, keywords, identifiers) */
export interface StringToken extends TokenBase {
type: "LPAREN" | "RPAREN" | "LBRACKET" | "RBRACKET" | "COMMA" | "QUESTION" | "DOT" | "DOTDOT" | "AND" | "OR" | "NOT" | "EXISTS" | "EQ" | "NEQ" | "GT" | "GTE" | "LT" | "LTE" | "LIKE" | "COLON" | "NOT_COLON" | "STRING" | "IDENT" | "EOF";
value: string;
}
/** Tokens with number values */
export interface NumberToken extends TokenBase {
type: "NUMBER";
value: number;
}
/** Tokens with boolean values */
export interface BooleanToken extends TokenBase {
type: "TRUE" | "FALSE";
value: boolean;
}
/**
* Discriminated union of all token types for proper type narrowing
*/
export type Token = StringToken | NumberToken | BooleanToken;
/**
* Lexer error with position information

@@ -33,3 +41,3 @@ */

/**
* Optimized Lexer for tokenizing Filtron query strings
* Lexer for tokenizing Filtron query strings
*/

@@ -42,6 +50,2 @@ export declare class Lexer {

/**
* Skip whitespace and comments (inlined for performance)
*/
private skipWhitespace;
/**
* Read a string literal - fast path for no escapes

@@ -63,2 +67,3 @@ */

}
export {};
//# sourceMappingURL=lexer.d.ts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"lexer.d.ts","sourceRoot":"","sources":["../../src/lexer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;GAEG;AACH,MAAM,MAAM,SAAS,GAClB,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,UAAU,GACV,OAAO,GACP,UAAU,GACV,KAAK,GACL,QAAQ,GACR,KAAK,GACL,IAAI,GACJ,KAAK,GACL,QAAQ,GACR,MAAM,GACN,OAAO,GACP,IAAI,GACJ,KAAK,GACL,IAAI,GACJ,KAAK,GACL,IAAI,GACJ,KAAK,GACL,MAAM,GACN,OAAO,GACP,WAAW,GACX,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,KAAK,CAAC;AAET;;GAEG;AACH,MAAM,WAAW,KAAK;IACrB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACZ;AAmED;;GAEG;AACH,qBAAa,UAAW,SAAQ,KAAK;IAG5B,QAAQ,EAAE,MAAM;gBADvB,OAAO,EAAE,MAAM,EACR,QAAQ,EAAE,MAAM;CAKxB;AAED;;GAEG;AACH,qBAAa,KAAK;IACjB,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,KAAK,EAAE,MAAM;IAKzB;;OAEG;IACH,OAAO,CAAC,cAAc;IAsCtB;;OAEG;IACH,OAAO,CAAC,UAAU;IA8ElB;;OAEG;IACH,OAAO,CAAC,UAAU;IA+ClB;;OAEG;IACH,OAAO,CAAC,cAAc;IAiGtB;;OAEG;IACH,IAAI,IAAI,KAAK;CA4Gb"}
{"version":3,"file":"lexer.d.ts","sourceRoot":"","sources":["../../src/lexer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,MAAM,SAAS,GAClB,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,UAAU,GACV,OAAO,GACP,UAAU,GACV,KAAK,GACL,QAAQ,GACR,KAAK,GACL,IAAI,GACJ,KAAK,GACL,QAAQ,GACR,MAAM,GACN,OAAO,GACP,IAAI,GACJ,KAAK,GACL,IAAI,GACJ,KAAK,GACL,IAAI,GACJ,KAAK,GACL,MAAM,GACN,OAAO,GACP,WAAW,GACX,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,KAAK,CAAC;AAET,4BAA4B;AAC5B,UAAU,SAAS;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACZ;AAED,gFAAgF;AAChF,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC7C,IAAI,EACD,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,UAAU,GACV,OAAO,GACP,UAAU,GACV,KAAK,GACL,QAAQ,GACR,KAAK,GACL,IAAI,GACJ,KAAK,GACL,QAAQ,GACR,IAAI,GACJ,KAAK,GACL,IAAI,GACJ,KAAK,GACL,IAAI,GACJ,KAAK,GACL,MAAM,GACN,OAAO,GACP,WAAW,GACX,QAAQ,GACR,OAAO,GACP,KAAK,CAAC;IACT,KAAK,EAAE,MAAM,CAAC;CACd;AAED,gCAAgC;AAChC,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC7C,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACd;AAED,iCAAiC;AACjC,MAAM,WAAW,YAAa,SAAQ,SAAS;IAC9C,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,KAAK,EAAE,OAAO,CAAC;CACf;AAED;;GAEG;AACH,MAAM,MAAM,KAAK,GAAG,WAAW,GAAG,WAAW,GAAG,YAAY,CAAC;AAqC7D;;GAEG;AACH,qBAAa,UAAW,SAAQ,KAAK;IAG5B,QAAQ,EAAE,MAAM;gBADvB,OAAO,EAAE,MAAM,EACR,QAAQ,EAAE,MAAM;CAKxB;AAED;;GAEG;AACH,qBAAa,KAAK;IACjB,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,KAAK,EAAE,MAAM;IAKzB;;OAEG;IACH,OAAO,CAAC,UAAU;IA8ElB;;OAEG;IACH,OAAO,CAAC,UAAU;IA+ClB;;OAEG;IACH,OAAO,CAAC,cAAc;IAiGtB;;OAEG;IACH,IAAI,IAAI,KAAK;CA4Hb"}
/**
* Filtron Parser
*
* High-performance recursive descent parser for Filtron query language.
* The Filtron parser public API
*/

@@ -6,0 +4,0 @@ import type { ASTNode } from "./types";

@@ -1,1 +0,1 @@

{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/parser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAIvC;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,IAAI,CAAC;IACd,GAAG,EAAE,OAAO,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,UAAU,CAAC;AAEpD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,KAAK,GAAI,OAAO,MAAM,KAAG,WAwBrC,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,KAAG,OAQ5C,CAAC"}
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/parser.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAIvC;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,IAAI,CAAC;IACd,GAAG,EAAE,OAAO,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,UAAU,CAAC;AAEpD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,KAAK,GAAI,OAAO,MAAM,KAAG,WAwBrC,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,KAAG,OAQ5C,CAAC"}
/**
* Recursive Descent Parser for Filtron
* https://en.wikipedia.org/wiki/Recursive_descent_parser
*
* Grammar (in order of precedence, lowest to highest):
*
* Query = OrExpression
* OrExpression = AndExpression (OR AndExpression)*
* AndExpression = NotExpression (AND NotExpression)*
* NotExpression = NOT NotExpression | PrimaryExpression
* Query = OrExpression
* OrExpression = AndExpression (OR AndExpression)*
* AndExpression = NotExpression (AND NotExpression)*
* NotExpression = NOT NotExpression | PrimaryExpression
* PrimaryExpression = '(' OrExpression ')' | FieldExpression
* FieldExpression = FieldName ('?' | EXISTS | ComparisonOp Value RangeSuffix? | OneOfOp '[' Values ']')?
* FieldName = IDENT ('.' IDENT)*
* Value = STRING | NUMBER | BOOLEAN | DottedIdent
* Values = Value (',' Value)*
* RangeSuffix = '..' NUMBER
* FieldExpression = FieldName ('?' | EXISTS | ComparisonOp Value RangeSuffix? | OneOfOp '[' Values ']')?
* FieldName = IDENT ('.' IDENT)*
* Value = STRING | NUMBER | BOOLEAN | DottedIdent
* Values = Value (',' Value)*
* RangeSuffix = '..' NUMBER
*/

@@ -17,0 +18,0 @@ import type { ASTNode } from "./types";

@@ -1,1 +0,1 @@

{"version":3,"file":"rd-parser.d.ts","sourceRoot":"","sources":["../../src/rd-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EACX,OAAO,EASP,MAAM,SAAS,CAAC;AAGjB;;GAEG;AACH,qBAAa,UAAW,SAAQ,KAAK;IAG5B,QAAQ,EAAE,MAAM;gBADvB,OAAO,EAAE,MAAM,EACR,QAAQ,EAAE,MAAM;CAKxB;AAmWD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGjD"}
{"version":3,"file":"rd-parser.d.ts","sourceRoot":"","sources":["../../src/rd-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EACX,OAAO,EASP,MAAM,SAAS,CAAC;AAGjB;;GAEG;AACH,qBAAa,UAAW,SAAQ,KAAK;IAG5B,QAAQ,EAAE,MAAM;gBADvB,OAAO,EAAE,MAAM,EACR,QAAQ,EAAE,MAAM;CAKxB;AA4UD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGjD"}
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@filtron/core",
"version": "1.2.0",
"description": "A fast, type-safe query language suitable for filter-like patterns in real-time APIs",
"version": "1.3.1",
"description": "Filtron parses human-friendly filter strings into structured queries you can use anywhere — SQL databases, in-memory arrays, or your own custom backend.",
"keywords": [

@@ -46,3 +46,3 @@ "filter",

"bench": "bun run build && bun --expose-gc run benchmark.ts",
"build": "bun build --minify --splitting --sourcemap=linked --outdir=dist index.ts && tsc --emitDeclarationOnly --declaration --declarationMap --outDir dist --skipLibCheck",
"build": "bun build --production --splitting --sourcemap=linked --outdir=dist index.ts && tsc --emitDeclarationOnly --declaration --declarationMap --outDir dist --skipLibCheck",
"prepublishOnly": "bun run build && bun test",

@@ -53,8 +53,8 @@ "test": "bun test",

"devDependencies": {
"@types/bun": "1.3.5",
"mitata": "1.0.34",
"typescript": "5.9.3"
"@filtron/benchmark": "workspace:*",
"@types/bun": "catalog:",
"typescript": "catalog:"
},
"peerDependencies": {
"typescript": "5.9.3"
"typescript": "catalog:"
},

@@ -61,0 +61,0 @@ "peerDependenciesMeta": {

+130
-101
# @filtron/core
A fast, type-safe query language suitable for filter-like patterns in real-time APIs.
A fast, human-friendly filter language for JavaScript and TypeScript. Parse expressions like `age > 18 AND verified` into a type-safe AST.
![npm version](https://img.shields.io/npm/v/@filtron/core.svg)
![npm bundle size](https://img.shields.io/bundlephobia/min/%40filtron%2Fcore)
![codecov](https://codecov.io/gh/jbergstroem/filtron/graph/badge.svg?token=FXIWJKJ9RI&component=core)
[![npm version](https://img.shields.io/npm/v/@filtron/core.svg)](https://www.npmjs.com/package/@filtron/core)
[![npm bundle size](https://img.shields.io/bundlephobia/min/%40filtron%2Fcore)](https://bundlephobia.com/package/@filtron/core)
[![codecov](https://codecov.io/gh/jbergstroem/filtron/graph/badge.svg?token=FXIWJKJ9RI&component=core)](https://codecov.io/gh/jbergstroem/filtron)
## Features
## Why Filtron?
- **Fast**: High-performance recursive descent parser — 90ns-1.5μs per query
- **Small**: ~8 KB minified, zero runtime dependencies
- **Type-safe**: Full TypeScript support with discriminated union AST
Let users filter data with readable expressions instead of building complex query UIs:
```
price < 100 AND category : ["electronics", "books"] AND inStock
```
Filtron parses these expressions into a structured AST you can use to generate SQL, filter arrays, or build custom query backends — safely, with no risk of injection attacks.
**Use cases:** Search UIs, API query parameters, admin dashboards, real-time data filtering.
## Installation
```bash
bun add @filtron/core
# or
npm install @filtron/core
```
## Quick Start
**Optional helpers:**
- `@filtron/sql` — Generate parameterized SQL WHERE clauses
- `@filtron/js` — Filter JavaScript arrays in-memory
## Usage
```typescript

@@ -32,3 +41,2 @@ import { parse } from "@filtron/core";

console.log(result.ast);
// Use AST to build SQL, filter data, etc.
} else {

@@ -39,45 +47,32 @@ console.error(result.error);

..or use `parseOrThrow` for try/catch style error handling:
```typescript
import { parseOrThrow } from "@filtron/core";
try {
const ast = parseOrThrow('age > 18 AND status = "active"');
console.log(ast);
// Use AST to build SQL, filter data, etc.
} catch (error) {
console.error(error.message);
}
```
## Syntax
```typescript
// Comparison operators
parse("age > 18");
// Comparisons
parse('age > 18');
parse('status = "active"');
parse("score >= 4.5");
parse('role != "guest"');
// Boolean operators
parse("age > 18 AND verified");
parse('role = "admin" OR role = "moderator"');
parse("NOT suspended");
// Boolean logic
parse('age > 18 AND verified');
parse('admin OR moderator');
parse('NOT suspended');
parse('(admin OR mod) AND active');
// Field existence
parse("email?");
parse("name EXISTS");
parse('email?');
parse('profile EXISTS');
// One-of expressions
parse('status : ["pending", "approved", "active"]');
// Contains (substring)
parse('name ~ "john"');
// Range expressions
parse("age = 18..65");
parse("price = 9.99..99.99");
// One-of (IN)
parse('status : ["pending", "approved"]');
// Complex queries
parse('(role = "admin" OR role = "mod") AND status = "active"');
// Ranges
parse('age = 18..65');
parse('price = 9.99..99.99');
// Nested fields
parse("user.profile.age >= 18");
parse('user.profile.age >= 18');
```

@@ -87,85 +82,119 @@

| Operator | Meaning | Example |
| -------------------- | ------------- | ------------------- |
| `=`, `:` | Equal | `status = "active"` |
| `!=`, `!:` | Not equal | `role != "guest"` |
| `>`, `>=`, `<`, `<=` | Comparison | `age >= 18` |
| `~` | Contains | `name ~ "john"` |
| `?`, `EXISTS` | Field exists | `email?` |
| `..` | Range | `age = 18..65` |
| `AND`, `OR`, `NOT` | Boolean logic | `a AND b OR c` |
| Operator | Meaning | Example |
| -------------------- | ------------- | --------------------- |
| `=`, `:` | Equal | `status = "active"` |
| `!=`, `!:` | Not equal | `role != "guest"` |
| `>`, `>=`, `<`, `<=` | Comparison | `age >= 18` |
| `~` | Contains | `name ~ "john"` |
| `?`, `EXISTS` | Field exists | `email?` |
| `..` | Range | `age = 18..65` |
| `: [...]` | One of | `status : ["a", "b"]` |
| `AND`, `OR`, `NOT` | Boolean logic | `a AND (b OR c)` |
## AST Structure
## API
### `parse(input: string): ParseResult`
Parses a filter expression and returns a result object.
```typescript
parse('age > 18 AND status = "active"')
const result = parse('age > 18');
// Returns:
{
type: "and",
left: {
type: "comparison",
field: "age",
operator: ">",
value: { type: "number", value: 18 }
},
right: {
type: "comparison",
field: "status",
operator: "=",
value: { type: "string", value: "active" }
}
if (result.success) {
result.ast; // ASTNode
} else {
result.error; // string
}
```
## TypeScript
### `parseOrThrow(input: string): ASTNode`
Full type definitions included:
Parses a filter expression, throwing on invalid input.
```typescript
try {
const ast = parseOrThrow('age > 18');
} catch (error) {
console.error(error.message);
}
```
## Types
All AST types are exported for building custom consumers:
```typescript
import type {
ParseResult,
ASTNode,
AndExpression,
OrExpression,
NotExpression,
ComparisonExpression,
// ... all types exported
ExistsExpression,
BooleanFieldExpression,
OneOfExpression,
NotOneOfExpression,
RangeExpression,
Value,
ComparisonOperator,
StringValue,
NumberValue,
BooleanValue,
IdentifierValue,
} from "@filtron/core";
function handleAST(node: ASTNode) {
switch (node.type) {
case "comparison":
// TypeScript knows node.field, node.operator, node.value exist
break;
case "and":
// TypeScript knows node.left, node.right exist
break;
// ... fully typed
}
}
```
## Use Cases
### AST structure
- **SQL Generation**: Build WHERE clauses from user queries
- **In-Memory Filtering**: Filter arrays/collections with complex logic
- **Search APIs**: Parse user search filters safely
- **Access Control**: Define permission rules with queries
| Node Type | Fields | Example input |
| -------------- | ---------------------------- | ---------------------- |
| `and` | `left`, `right` | `a AND b` |
| `or` | `left`, `right` | `a OR b` |
| `not` | `expression` | `NOT a` |
| `comparison` | `field`, `operator`, `value` | `age > 18` |
| `exists` | `field` | `email?` |
| `booleanField` | `field` | `verified` |
| `oneOf` | `field`, `values` | `status : ["a", "b"]` |
| `notOneOf` | `field`, `values` | `status !: ["a", "b"]` |
| `range` | `field`, `min`, `max` | `age = 18..65` |
## Performance
**Example output:**
Filtron uses a hand-written recursive descent parser optimized for speed:
```typescript
parse('age > 18 AND status = "active"')
| Query Type | Parse Time | Throughput |
| ---------- | ---------- | ----------------- |
| Simple | ~90-250ns | 4-11M ops/sec |
| Medium | ~360-870ns | 1.1-2.8M ops/sec |
| Complex | ~0.9-1.5μs | 650K-1.1M ops/sec |
// Returns:
{
success: true,
ast: {
type: "and",
left: {
type: "comparison",
field: "age",
operator: ">",
value: { type: "number", value: 18 }
},
right: {
type: "comparison",
field: "status",
operator: "=",
value: { type: "string", value: "active" }
}
}
}
```
Run benchmarks: `bun run bench`
## Performance
## Documentation
Hand-written recursive descent parser. ~9 KB minified, zero dependencies.
- **[Contributing Guide](../../CONTRIBUTING.md)** - Development setup and workflow
| Query complexity | Parse time | Throughput |
| ---------------- | ---------- | ----------------- |
| Simple | ~90-250ns | 4-11M ops/sec |
| Medium | ~360-870ns | 1.1-2.8M ops/sec |
| Complex | ~0.9-1.5μs | 650K-1.1M ops/sec |
## Inspiration
## License
The Filtron query language syntax was strongly inspired by [dumbql](https://github.com/tomakado/dumbql).
MIT