From 47d84d4170c19ed5894122158904e1a4b196dc4d Mon Sep 17 00:00:00 2001 From: Mark Roberts Date: Mon, 20 May 2024 12:05:45 +0200 Subject: [PATCH 1/2] fix: complete unary operator/expression support --- src/lexer.ts | 3 ++ src/syntax/ast.ts | 21 +++++++++++++- src/syntax/expr.ne | 2 +- src/syntax/expr.spec.ts | 62 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 85 insertions(+), 3 deletions(-) diff --git a/src/lexer.ts b/src/lexer.ts index eafe785..632f3ac 100644 --- a/src/lexer.ts +++ b/src/lexer.ts @@ -78,6 +78,9 @@ export const lexer = compile({ op_like: /~~/, // ~~ =LIKE op_mod: '%', op_exp: '^', + op_others_unary: { + match: ['|/', '||/', '@', '~', '@-@', '@@', '#', '?-', '?|', '!!'], + }, op_additive: { // group other additive operators match: ['||', '-', '#-', '&&'], diff --git a/src/syntax/ast.ts b/src/syntax/ast.ts index e405263..ae37286 100644 --- a/src/syntax/ast.ts +++ b/src/syntax/ast.ts @@ -862,7 +862,26 @@ export interface ExprCast extends PGNode { } -export type UnaryOperator = '+' | '-' | 'NOT' | 'IS NULL' | 'IS NOT NULL' | 'IS TRUE' | 'IS FALSE' | 'IS NOT TRUE' | 'IS NOT FALSE'; +export type UnaryOperator = '+' + | '-' + | 'NOT' + | 'IS NULL' + | 'IS NOT NULL' + | 'IS TRUE' + | 'IS FALSE' + | 'IS NOT TRUE' + | 'IS NOT FALSE' + | '|/' + | '||/' + | '@' + | '~' + | '@-@' + | '@@' + | '#' + | '?-' + | '?|' + | '!!' + export interface ExprUnary extends PGNode { type: 'unary'; operand: Expr; diff --git a/src/syntax/expr.ne b/src/syntax/expr.ne index aff8c67..9c94668 100644 --- a/src/syntax/expr.ne +++ b/src/syntax/expr.ne @@ -95,7 +95,7 @@ expr_in -> expr_binary[op_single[ops_in], expr_in, expr_add] expr_add -> expr_binary[op_scopable[(%op_plus | %op_minus | %op_additive)], expr_add, expr_mult] expr_mult -> expr_binary[op_scopable[(%star | %op_div | %op_mod)], expr_mult, expr_exp] expr_exp -> expr_binary[op_scopable[%op_exp], expr_exp, expr_unary_add] -expr_unary_add -> expr_left_unary[op_scopable[(%op_plus | %op_minus)], expr_unary_add, expr_various_constructs] +expr_unary_add -> expr_left_unary[op_scopable[(%op_plus | %op_minus | %op_others_unary)], expr_unary_add, expr_various_constructs] expr_various_constructs -> expr_binary[op_single[various_binaries], expr_various_constructs, expr_array_index] expr_array_index diff --git a/src/syntax/expr.spec.ts b/src/syntax/expr.spec.ts index 5cb2efd..bb5b4d2 100644 --- a/src/syntax/expr.spec.ts +++ b/src/syntax/expr.spec.ts @@ -835,6 +835,66 @@ line`, operand: { type: 'ref', name: 'a' } }); + checkTreeExpr('|/ a', { + type: 'unary', + op: '|/', + operand: { type: 'ref', name: 'a' } + }); + + checkTreeExpr('||/ a', { + type: 'unary', + op: '||/', + operand: { type: 'ref', name: 'a' } + }); + + checkTreeExpr('@ a', { + type: 'unary', + op: '@', + operand: { type: 'ref', name: 'a' } + }); + + checkTreeExpr('~ a', { + type: 'unary', + op: '~', + operand: { type: 'ref', name: 'a' } + }); + + checkTreeExpr('@-@ a', { + type: 'unary', + op: '@-@', + operand: { type: 'ref', name: 'a' } + }); + + checkTreeExpr('@@ a', { + type: 'unary', + op: '@@', + operand: { type: 'ref', name: 'a' } + }); + + checkTreeExpr('# a', { + type: 'unary', + op: '#', + operand: { type: 'ref', name: 'a' } + }); + + checkTreeExpr('?- a', { + type: 'unary', + op: '?-', + operand: { type: 'ref', name: 'a' } + }); + + checkTreeExpr('?| a', { + type: 'unary', + op: '?|', + operand: { type: 'ref', name: 'a' } + }); + + checkTreeExpr('!! a', { + type: 'unary', + op: '!!', + operand: { type: 'ref', name: 'a' } + }); + }); @@ -1635,4 +1695,4 @@ line`, args: [], }); }) -}); \ No newline at end of file +}); From c10f89e2f5ca8c745f5518bd1218afe7914a0136 Mon Sep 17 00:00:00 2001 From: Mark Roberts Date: Mon, 20 May 2024 14:26:00 +0200 Subject: [PATCH 2/2] chore: rework lexer and fix tests --- src/lexer.ts | 17 +++++++++++++---- src/syntax/expr.ne | 4 ++-- src/to-sql.ts | 10 ++++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/lexer.ts b/src/lexer.ts index 632f3ac..20d834d 100644 --- a/src/lexer.ts +++ b/src/lexer.ts @@ -78,9 +78,15 @@ export const lexer = compile({ op_like: /~~/, // ~~ =LIKE op_mod: '%', op_exp: '^', - op_others_unary: { - match: ['|/', '||/', '@', '~', '@-@', '@@', '#', '?-', '?|', '!!'], - }, + op_square_root: '|/', // https://www.postgresql.org/docs/16/functions-math.html + op_cube_root: '||/', // https://www.postgresql.org/docs/16/functions-math.html + op_double_at: '@@', // https://www.postgresql.org/docs/16/functions-geometry.html and https://www.postgresql.org/docs/16/functions-json.html + op_tilde_star: '~*', // https://www.postgresql.org/docs/16/functions-matching.html + op_tilde: '~', // https://www.postgresql.org/docs/16/functions-bitstring.html and https://www.postgresql.org/docs/current/functions-matching.html + op_question_mark_dash: '?-', // https://www.postgresql.org/docs/16/functions-geometry.html + op_question_mark_pipe: '?|', // https://www.postgresql.org/docs/16/functions-geometry.html + op_hash_rangle_rangle: '#>>', // https://www.postgresql.org/docs/16/functions-json.html + op_hash: '#', // https://www.postgresql.org/docs/16/functions-geometry.html op_additive: { // group other additive operators match: ['||', '-', '#-', '&&'], @@ -88,7 +94,10 @@ export const lexer = compile({ op_compare: { // group other comparison operators // ... to add: "IN" and "NOT IN" that are matched by keywords - match: ['>', '>=', '<', '<=', '@>', '<@', '?', '?|', '?&', '#>>', '>>', '<<', '~', '~*', '!~', '!~*', '@@'], + match: ['>', '>=', '<', '<=', '@>', '<@', '?', '?&', '>>', '<<', '!~', '!~*'], + }, + op_others_unary: { + match: ['@', '@-@', '!!'], }, ops_others: { // referenced as (any other operator) in https://www.postgresql.org/docs/12/sql-syntax-lexical.html#SQL-PRECEDENCE diff --git a/src/syntax/expr.ne b/src/syntax/expr.ne index 9c94668..d40fe18 100644 --- a/src/syntax/expr.ne +++ b/src/syntax/expr.ne @@ -87,7 +87,7 @@ expr_is | expr_compare {% unwrap %} -expr_compare -> expr_binary[op_scopable[%op_compare], expr_compare, expr_range] +expr_compare -> expr_binary[op_scopable[(%op_question_mark_pipe | %op_tilde_star | %op_tilde | %op_double_at | %op_hash_rangle_rangle | %op_compare)], expr_compare, expr_range] expr_range -> expr_ternary[ops_between, %kw_and, expr_range, expr_others] expr_others -> expr_binary[op_scopable[%ops_others], expr_others, expr_like] expr_like -> expr_binary[op_single[ops_like], expr_like, expr_in] @@ -95,7 +95,7 @@ expr_in -> expr_binary[op_single[ops_in], expr_in, expr_add] expr_add -> expr_binary[op_scopable[(%op_plus | %op_minus | %op_additive)], expr_add, expr_mult] expr_mult -> expr_binary[op_scopable[(%star | %op_div | %op_mod)], expr_mult, expr_exp] expr_exp -> expr_binary[op_scopable[%op_exp], expr_exp, expr_unary_add] -expr_unary_add -> expr_left_unary[op_scopable[(%op_plus | %op_minus | %op_others_unary)], expr_unary_add, expr_various_constructs] +expr_unary_add -> expr_left_unary[op_scopable[(%op_plus | %op_minus | %op_square_root | %op_cube_root | %op_double_at | %op_tilde | %op_question_mark_dash | %op_question_mark_pipe | %op_hash | %op_others_unary)], expr_unary_add, expr_various_constructs] expr_various_constructs -> expr_binary[op_single[various_binaries], expr_various_constructs, expr_array_index] expr_array_index diff --git a/src/to-sql.ts b/src/to-sql.ts index 158e7a4..e3b98a6 100644 --- a/src/to-sql.ts +++ b/src/to-sql.ts @@ -1484,6 +1484,16 @@ const visitor = astVisitor(m => ({ switch (t.op) { case '+': case '-': + case '|/': + case '||/': + case '@': + case '~': + case '@-@': + case '@@': + case '#': + case '?-': + case '?|': + case '!!': // prefix ops visitOp(t); m.expr(t.operand);