@mfissehaye/string-to-drizzle-orm-filters
Version:
253 lines (226 loc) • 9.74 kB
text/typescript
import { Lexer } from "../src/lexer";
import { Parser } from '../src/parser'
import { describe, it, expect } from "vitest";
import { CallExpression, StringLiteral } from "../src/ast";
describe('Parser', () => {
it('should parse a simple eq expression', () => {
const input = 'eq("a", "b")';
const lexer = new Lexer(input);
const parser = new Parser(lexer);
const ast = parser.parse();
expect(ast).toEqual({
kind: 'Program',
expression: {
kind: 'CallExpression',
functionName: 'eq',
args: [
{ kind: 'StringLiteral', value: 'a' },
{ kind: 'StringLiteral', value: 'b' },
],
},
})
})
it('should parse a simple logical expression (and)', () => {
const input = 'and(eq("a", "b"), gt("c", "10"))';
const lexer = new Lexer(input)
const parser = new Parser(lexer);
const ast = parser.parse()
expect(ast).toEqual({
kind: 'Program',
expression: {
kind: 'CallExpression',
functionName: 'and',
args: [
{
kind: 'CallExpression',
functionName: 'eq',
args: [
{ kind: 'StringLiteral', value: 'a' },
{ kind: 'StringLiteral', value: 'b' },
]
},
{
kind: 'CallExpression',
functionName: 'gt',
args: [
{ kind: 'StringLiteral', value: 'c' },
{ kind: 'StringLiteral', value: '10' }, // Numbers are still string literals for now
]
}
]
}
})
})
it('should parse a nested logical expression as per example', () => {
const input = 'and(or(eq("a", "b"), like("c", "d")), and(gt("e", "f"), ilike("g", "h")))'
const lexer = new Lexer(input)
const parser = new Parser(lexer)
const ast = parser.parse()
expect(ast).toEqual({
kind: 'Program',
expression: {
kind: 'CallExpression',
functionName: 'and',
args: [
{
kind: 'CallExpression',
functionName: 'or',
args: [
{
kind: 'CallExpression',
functionName: 'eq',
args: [
{ kind: 'StringLiteral', value: 'a' },
{ kind: 'StringLiteral', value: 'b' },
]
},
{
kind: 'CallExpression',
functionName: 'like',
args: [
{ kind: 'StringLiteral', value: 'c' },
{ kind: 'StringLiteral', value: 'd' },
]
}
]
},
{
kind: 'CallExpression',
functionName: 'and',
args: [
{
kind: 'CallExpression',
functionName: 'gt',
args: [
{ kind: 'StringLiteral', value: 'e' },
{ kind: 'StringLiteral', value: 'f' },
]
},
{
kind: 'CallExpression',
functionName: 'ilike',
args: [
{ kind: 'StringLiteral', value: 'g' },
{ kind: 'StringLiteral', value: 'h' },
]
}
]
}
]
}
})
})
it('should parse an expression with leading/trailing whitespace', () => {
const input = ' or ( eq ( "x", "y" ) , ne ( "z", "w" ) ) ';
const lexer = new Lexer(input)
const parser = new Parser(lexer)
const ast = parser.parse()
expect(ast.expression.functionName).toBe('or')
expect((ast.expression.args[0] as CallExpression).functionName).toBe('eq')
expect(((ast.expression.args[0] as CallExpression).args[0] as StringLiteral).value).toBe('x')
})
it('should parse an expression with no arguments (e.g., `isNull`)', () => {
const input = 'isNull("column")'
const lexer = new Lexer(input)
const parser = new Parser(lexer)
const ast = parser.parse()
expect(ast).toEqual({
kind: 'Program',
expression: {
kind: 'CallExpression',
functionName: 'isNull',
args: [
{ kind: 'StringLiteral', value: 'column' }
]
}
})
})
it('should throw ParseError for unexpected EOF', () => {
const input = 'eq("a", "b"'; // Missing closing parentheses
const lexer = new Lexer(input);
const parser = new Parser(lexer);
// expect(() => parser.parse()).toThrow(ParserError)
expect(() => parser.parse()).toThrow('Expected \')\' to close function call \'eq\'.')
})
// it('should throw ParseError for unknown identifier where an expression is expected', () => {
// const input = 'and(eq("a", "b"), 123)'; // 123 is an unknown token type
// const lexer = new Lexer(input);
// const parser = new Parser(lexer);
// // expect(() => parser.parse()).toThrow(ParserError);
// expect(() => parser.parse()).toThrow('Unexpected token \'1\' (type UNKNOWN). Expected a string literal or a nested function call as an argument.')
// })
// it('should throw ParserError for unmatched parenthesis', () => {
// const input = '(eq("a", "b")'; // Missing closing ')'
// const lexer = new Lexer(input);
// const parser = new Parser(lexer);
// // expect(() => parser.parse()).toThrow(ParserError);
// expect(() => parser.parse()).toThrow('Expected \')\' to close expression started at position 0.')
// })
it('should throw ParserError for empty input', () => {
const input = '';
const lexer = new Lexer(input);
const parser = new Parser(lexer);
expect(() => parser.parse()).toThrow('Unexpected token \'\' (type EOF). Expected a function call or a \'(\'.')
})
it('should handle nested logical expressions deeply', () => {
const input = 'and(eq("a", "b"), or(gt("c", "d"), not(isNull("e"))))';
const lexer = new Lexer(input);
const parser = new Parser(lexer);
const ast = parser.parse()
expect(ast.expression.functionName).toBe('and')
const orCall = ast.expression.args[1] as CallExpression
expect(orCall.functionName).toBe('or')
const notCall = orCall.args[1] as CallExpression;
expect(notCall.functionName).toBe('not')
const isNullCall = notCall.args[0] as CallExpression;
expect(isNullCall.functionName).toBe('isNull')
expect((isNullCall.args[0] as StringLiteral).value).toBe('e')
})
it('should throw ParseError for an identifier not followed by parenthesis (if expecting a call)', () => {
const input = 'eq "a"';
const lexer = new Lexer(input);
const parser = new Parser(lexer);
expect(() => parser.parse()).toThrow('Expected \'(\' after function name \'eq\'.');
})
it('should parse an eq expression with a number literal', () => {
const input = 'eq("age", 30)';
const lexer = new Lexer(input);
const parser = new Parser(lexer);
const ast = parser.parse();
expect(ast).toEqual({
kind: 'Program',
expression: {
kind: 'CallExpression',
functionName: 'eq',
args: [
{ kind: 'StringLiteral', value: 'age' },
{ kind: 'NumberLiteral', value: 30 },
],
},
});
});
it('should parse a gt expression with a floating-point number literal', () => {
const input = 'gt("price", 99.99)';
const lexer = new Lexer(input);
const parser = new Parser(lexer);
const ast = parser.parse();
expect(ast).toEqual({
kind: 'Program',
expression: {
kind: 'CallExpression',
functionName: 'gt',
args: [
{ kind: 'StringLiteral', value: 'price' },
{ kind: 'NumberLiteral', value: 99.99 },
],
},
});
});
// it('should throw ParserError for an invalid number literal', () => {
// const input = 'eq("a", 123.4.5)'; // Malformed number
// const lexer = new Lexer(input);
// const parser = new Parser(lexer);
// // expect(() => parser.parse()).toThrow(ParserError);
// expect(() => parser.parse()).toThrow('Invalid number literal: \'123.4.5\'');
// });
})