diff --git a/sources/ast.h b/sources/ast.h index 62ea3f3a45..b352cfe3df 100644 --- a/sources/ast.h +++ b/sources/ast.h @@ -236,7 +236,7 @@ cql_noexport CSTR _Nonnull get_compound_operator_name(int32_t compound_operator) PRI_BINARY << >> & | PRI_ADD + - PRI_MUL * / % - PRI_CONCAT || (nyi) + PRI_CONCAT || */ /* diff --git a/sources/cg_c.c b/sources/cg_c.c index 92e01ff8fb..329c8a4157 100644 --- a/sources/cg_c.c +++ b/sources/cg_c.c @@ -153,6 +153,28 @@ static void cg_error_on_not_sqlite_ok() { cg_error_on_expr("_rc_ != SQLITE_OK"); } +// This tells us if a subtree should be wrapped in () +// Basically we know the binding strength of the context (pri) and the current element (pri_new) +// Weaker contexts get parens. Equal contexts get parens on the right side because all ops +// are left to right associtive in SQL. Stronger child contexts never need parens because +// the operator already binds tighter than its parent in the tree. +static bool_t needs_paren(ast_node *ast, int32_t pri_new, int32_t pri) { + // if the priorities are different then parens are needed + // if and only if the new priority (this node) is weaker than the + // containing priority (the parent node) + + if (pri_new != pri) { + return pri_new < pri; + } + + // If equal binding strength, put parens on the right of the expression + // because our entire world is left associative. + // + // so e.g. *(a, /(b,c)) becomes a*(b/c); + + return ast->parent->right == ast; +} + // We have a series of masks to remember if we have emitted any given scratch variable. // We might need several temporaries at the same level if different types appear // at the same level but in practice we tend not to run into such things. Mostly @@ -684,7 +706,7 @@ static void cg_binary_compare(ast_node *ast, CSTR op, charbuf *is_null, charbuf CHARBUF_OPEN(comparison); - if (pri_new < pri) { + if (needs_paren(ast, pri_new, pri)) { bprintf(&comparison, "("); } @@ -705,7 +727,7 @@ static void cg_binary_compare(ast_node *ast, CSTR op, charbuf *is_null, charbuf bprintf(&comparison, "%s(%s, %s) %s 0", rt->cql_string_compare, l_value.ptr, r_value.ptr, op); } - if (pri_new < pri) { + if (needs_paren(ast, pri_new, pri)) { bprintf(&comparison, ")"); } @@ -731,7 +753,7 @@ static void cg_binary_compare(ast_node *ast, CSTR op, charbuf *is_null, charbuf // * is_null and value are the usual outputs // * pri is the strength of the caller // * pri_new is the strength of "op" -// * so if pri_new < pri we need parens. That's the canonical formula +// The helper needs_paren() tells us if we should wrap this subtree in parens (see above) // If the inputs are not nullable then we can make the easy case of returning the // result in the value string (and 0 for is null). Otherwise, cg_combine_nullables // does the job. @@ -755,7 +777,7 @@ static void cg_binary(ast_node *ast, CSTR op, charbuf *is_null, charbuf *value, bprintf(&result, "%s %s %s", l_value.ptr, op, r_value.ptr); if (is_not_nullable(sem_type_left) && is_not_nullable(sem_type_right)) { - if (pri_new < pri) { + if (needs_paren(ast, pri_new, pri)) { bprintf(value, "(%s)", result.ptr); } else { @@ -873,7 +895,7 @@ static void cg_expr_is(ast_node *ast, CSTR op, charbuf *is_null, charbuf *value, bool_t notnull = is_not_nullable(sem_type_left) && is_not_nullable(sem_type_right); if (notnull || refs) { - if (pri_new < pri) { + if (needs_paren(ast, pri_new, pri)) { bprintf(value, "(%s == %s)", l_value.ptr, r_value.ptr); } else { @@ -938,7 +960,7 @@ static void cg_expr_is_not(ast_node *ast, CSTR op, charbuf *is_null, charbuf *va bool_t notnull = is_not_nullable(sem_type_left) && is_not_nullable(sem_type_right); if (notnull || refs) { - if (pri_new < pri) { + if (needs_paren(ast, pri_new, pri)) { bprintf(value, "(%s != %s)", l_value.ptr, r_value.ptr); } else { @@ -1037,7 +1059,7 @@ static void cg_expr_or(ast_node *ast, CSTR str, charbuf *is_null, charbuf *value // it's ok if statement generation is needed for the left because that never needs to short circuit (left // is always evaluated). if (!is_nullable(sem_type_result) && right_eval.used == 1) { - if (pri_new < pri) { + if (needs_paren(ast, pri_new, pri)) { bprintf(value, "("); } @@ -1048,7 +1070,7 @@ static void cg_expr_or(ast_node *ast, CSTR str, charbuf *is_null, charbuf *value CG_POP_EVAL(l); - if (pri_new < pri) { + if (needs_paren(ast, pri_new, pri)) { bprintf(value, ")"); } } @@ -1154,7 +1176,7 @@ static void cg_expr_and(ast_node *ast, CSTR str, charbuf *is_null, charbuf *valu // it's ok if statement generation is needed for the left because that never needs to short circuit (left // is always evaluated). if (!is_nullable(sem_type_result) && right_eval.used == 1) { - if (pri_new < pri) { + if (needs_paren(ast, pri_new, pri)) { bprintf(value, "("); } @@ -1165,7 +1187,7 @@ static void cg_expr_and(ast_node *ast, CSTR str, charbuf *is_null, charbuf *valu CG_POP_EVAL(l); - if (pri_new < pri) { + if (needs_paren(ast, pri_new, pri)) { bprintf(value, ")"); } } diff --git a/sources/test/cg_test.sql b/sources/test/cg_test.sql index 07ad87812d..72ffe80d75 100644 --- a/sources/test/cg_test.sql +++ b/sources/test/cg_test.sql @@ -889,7 +889,7 @@ begin 2 as _integer, cast(3 as long integer) as _longint, 3.0 as _real, - 'xxx' as _text, + 'xyz' as _text, cast(null as bool) as _nullable_bool; end; @@ -2773,6 +2773,101 @@ begin end; end; +DECLARE x INTEGER NOT NULL; + +-- TEST: a series of paren checks on left association + +-- + x = 1 * (2 / 3); +SET x := 1 * (2 / 3); + +-- + x = 1 * 2 / 3; +SET x := 1 * 2 / 3; + +-- + x = 1 + 2 / 3; +SET x := 1 + 2 / 3; + +-- + x = 1 + (2 - 3); +SET x := 1 + (2 - 3); + +-- + x = 1 + 2 * 3; +SET x := 1 + 2 * 3; + +-- + x = 1 * (2 + 3); +SET x := 1 * (2 + 3); + +-- + x = 1 - (2 + 3); +SET x := 1 - (2 + 3); + +-- + x = 1 - (2 - 3); +SET x := 1 - (2 - 3); + +-- + x = 1 - 2 - (2 - 3); +SET x := 1 - 2 - (2 - 3); + +-- the first parens do not change eval order from left to right at all +-- + x = 1 - 2 - (2 - 3); +SET x := (1 - 2) - (2 - 3); + +-- + x = 1 / 2 / 3; +SET x := 1 / 2 / 3; + +-- + x = 1 / (2 / 3); +SET x := 1 / (2 / 3); + +-- + x = 1 / 2; +SET x := 1 / 2; + +-- + x = 1 * 2 * (3 * 4) +SET x := 1 * 2 * (3 * 4); + +-- the first parens don't change anything +-- the second parens could matter if it was floating point +-- + x = 1 * 2 * (3 * 4) +SET x := (1 * 2) * (3 * 4); + +-- note that in C & binds tighter than | so parens are required in C +-- note that in SQL | and & are equal so this expression left associates +-- + x = (1 | 2) & 3; +SET x := 1 | 2 & 3; + +-- + x = 1 | 2 & 3; +SET x := 1 | (2 & 3); + +-- + x = 1 | 2 | 3 +SET x := 1 | 2 | 3; + +-- sub optimal but we're trying to preserve written order due to floating point +-- + x = 1 | (2 | 3) +SET x := 1 | (2 | 3); + +-- + x = 1 | (3 + 4 | 5); +SET x := 1 | (3 + 4 | 5); + +-- + x = 1 | 3 + (4 | 5); +SET x := 1 | 3 + (4 | 5); + +-- + x = (1 | 3) + (4 | 5); +SET x := (1 | 3) + (4 | 5); + +-- + x = (1 + 2) * 5; +set x := (1 + 2) * 5; + +-- + x = 1 + 2 - 1; +set x := (1 + 2) - 1; + +-- + x = 1 << 2 | 3; +set x := 1 << 2 | 3; + +-- + x = 1 << (2 | 3); +set x := 1 << (2 | 3); + +-- + x = 1 | 2 << 3 +set x := 1 | (2 << 3); + +-- + x = 1 << (2 << 3); +set x := 1 << (2 << 3); + + -------------------------------------------------------------------- -------------------- add new tests before this point --------------- -------------------------------------------------------------------- diff --git a/sources/test/cg_test_c.c.ref b/sources/test/cg_test_c.c.ref index ee8b3941a0..0ee45efa71 100644 --- a/sources/test/cg_test_c.c.ref +++ b/sources/test/cg_test_c.c.ref @@ -1012,7 +1012,7 @@ DECLARE PROC plugh (id INTEGER); /* CREATE PROC complex_return () BEGIN - SELECT CAST(1 AS BOOL) AS _bool, 2 AS _integer, CAST(3 AS LONG_INT) AS _longint, 3.0 AS _real, 'xxx' AS _text, CAST(NULL AS BOOL) AS _nullable_bool; + SELECT CAST(1 AS BOOL) AS _bool, 2 AS _integer, CAST(3 AS LONG_INT) AS _longint, 3.0 AS _real, 'xyz' AS _text, CAST(NULL AS BOOL) AS _nullable_bool; END; */ @@ -1113,7 +1113,7 @@ CQL_WARN_UNUSED cql_code complex_return(sqlite3 *_Nonnull _db_, sqlite3_stmt *_N cql_code _rc_ = SQLITE_OK; *_result_ = NULL; _rc_ = cql_prepare(_db_, _result_, - "SELECT CAST(1 AS BOOL), 2, CAST(3 AS LONG_INT), 3.0, 'xxx', NULL"); + "SELECT CAST(1 AS BOOL), 2, CAST(3 AS LONG_INT), 3.0, 'xyz', NULL"); if (_rc_ != SQLITE_OK) { cql_error_trace(); goto cql_cleanup; } _rc_ = SQLITE_OK; @@ -6659,6 +6659,13 @@ cql_cleanup: // The statement ending at line XXXX +/* +DECLARE x INTEGER NOT NULL; +*/ +cql_int32 x = 0; + +// The statement ending at line XXXX + /* CREATE PROC end_proc () BEGIN @@ -8407,6 +8414,202 @@ cql_code cql_startup(sqlite3 *_Nonnull _db_) { SET l2 := cql_get_blob_size(blob_var2); */ l2 = cql_get_blob_size(blob_var2); + + // The statement ending at line XXXX + + /* + SET x := 1 * (2 / 3); + */ + x = 1 * (2 / 3); + + // The statement ending at line XXXX + + /* + SET x := 1 * 2 / 3; + */ + x = 1 * 2 / 3; + + // The statement ending at line XXXX + + /* + SET x := 1 + 2 / 3; + */ + x = 1 + 2 / 3; + + // The statement ending at line XXXX + + /* + SET x := 1 + (2 - 3); + */ + x = 1 + (2 - 3); + + // The statement ending at line XXXX + + /* + SET x := 1 + 2 * 3; + */ + x = 1 + 2 * 3; + + // The statement ending at line XXXX + + /* + SET x := 1 * (2 + 3); + */ + x = 1 * (2 + 3); + + // The statement ending at line XXXX + + /* + SET x := 1 - (2 + 3); + */ + x = 1 - (2 + 3); + + // The statement ending at line XXXX + + /* + SET x := 1 - (2 - 3); + */ + x = 1 - (2 - 3); + + // The statement ending at line XXXX + + /* + SET x := 1 - 2 - (2 - 3); + */ + x = 1 - 2 - (2 - 3); + + // The statement ending at line XXXX + + /* + SET x := 1 - 2 - (2 - 3); + */ + x = 1 - 2 - (2 - 3); + + // The statement ending at line XXXX + + /* + SET x := 1 / 2 / 3; + */ + x = 1 / 2 / 3; + + // The statement ending at line XXXX + + /* + SET x := 1 / (2 / 3); + */ + x = 1 / (2 / 3); + + // The statement ending at line XXXX + + /* + SET x := 1 / 2; + */ + x = 1 / 2; + + // The statement ending at line XXXX + + /* + SET x := 1 * 2 * (3 * 4); + */ + x = 1 * 2 * (3 * 4); + + // The statement ending at line XXXX + + /* + SET x := 1 * 2 * (3 * 4); + */ + x = 1 * 2 * (3 * 4); + + // The statement ending at line XXXX + + /* + SET x := 1 | 2 & 3; + */ + x = (1 | 2) & 3; + + // The statement ending at line XXXX + + /* + SET x := 1 | (2 & 3); + */ + x = 1 | 2 & 3; + + // The statement ending at line XXXX + + /* + SET x := 1 | 2 | 3; + */ + x = 1 | 2 | 3; + + // The statement ending at line XXXX + + /* + SET x := 1 | (2 | 3); + */ + x = 1 | (2 | 3); + + // The statement ending at line XXXX + + /* + SET x := 1 | (3 + 4 | 5); + */ + x = 1 | (3 + 4 | 5); + + // The statement ending at line XXXX + + /* + SET x := 1 | 3 + (4 | 5); + */ + x = 1 | 3 + (4 | 5); + + // The statement ending at line XXXX + + /* + SET x := (1 | 3) + (4 | 5); + */ + x = (1 | 3) + (4 | 5); + + // The statement ending at line XXXX + + /* + SET x := (1 + 2) * 5; + */ + x = (1 + 2) * 5; + + // The statement ending at line XXXX + + /* + SET x := 1 + 2 - 1; + */ + x = 1 + 2 - 1; + + // The statement ending at line XXXX + + /* + SET x := 1 << 2 | 3; + */ + x = 1 << 2 | 3; + + // The statement ending at line XXXX + + /* + SET x := 1 << (2 | 3); + */ + x = 1 << (2 | 3); + + // The statement ending at line XXXX + + /* + SET x := 1 | (2 << 3); + */ + x = 1 | 2 << 3; + + // The statement ending at line XXXX + + /* + SET x := 1 << (2 << 3); + */ + x = 1 << (2 << 3); cql_cleanup: cql_string_release(t0_nullable); diff --git a/sources/test/cg_test_c.h.ref b/sources/test/cg_test_c.h.ref index bb6744ce02..dddb32a264 100644 --- a/sources/test/cg_test_c.h.ref +++ b/sources/test/cg_test_c.h.ref @@ -1738,6 +1738,65 @@ extern CQL_WARN_UNUSED cql_code base_proc_savepoint_commit_return(sqlite3 *_Nonn // The statement ending at line XXXX extern CQL_WARN_UNUSED cql_code base_proc_savepoint_rollback_return(sqlite3 *_Nonnull _db_); +// The statement ending at line XXXX +extern cql_int32 x; + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + // The statement ending at line XXXX extern void end_proc(void); diff --git a/sources/test/cg_test_c_with_namespace.c.ref b/sources/test/cg_test_c_with_namespace.c.ref index e2ba24175b..d1b407632f 100644 --- a/sources/test/cg_test_c_with_namespace.c.ref +++ b/sources/test/cg_test_c_with_namespace.c.ref @@ -1012,7 +1012,7 @@ DECLARE PROC plugh (id INTEGER); /* CREATE PROC complex_return () BEGIN - SELECT CAST(1 AS BOOL) AS _bool, 2 AS _integer, CAST(3 AS LONG_INT) AS _longint, 3.0 AS _real, 'xxx' AS _text, CAST(NULL AS BOOL) AS _nullable_bool; + SELECT CAST(1 AS BOOL) AS _bool, 2 AS _integer, CAST(3 AS LONG_INT) AS _longint, 3.0 AS _real, 'xyz' AS _text, CAST(NULL AS BOOL) AS _nullable_bool; END; */ @@ -1113,7 +1113,7 @@ CQL_WARN_UNUSED cql_code complex_return(sqlite3 *_Nonnull _db_, sqlite3_stmt *_N cql_code _rc_ = SQLITE_OK; *_result_ = NULL; _rc_ = cql_prepare(_db_, _result_, - "SELECT CAST(1 AS BOOL), 2, CAST(3 AS LONG_INT), 3.0, 'xxx', NULL"); + "SELECT CAST(1 AS BOOL), 2, CAST(3 AS LONG_INT), 3.0, 'xyz', NULL"); if (_rc_ != SQLITE_OK) { cql_error_trace(); goto cql_cleanup; } _rc_ = SQLITE_OK; @@ -6659,6 +6659,13 @@ cql_cleanup: // The statement ending at line XXXX +/* +DECLARE x INTEGER NOT NULL; +*/ +cql_int32 x = 0; + +// The statement ending at line XXXX + /* CREATE PROC end_proc () BEGIN @@ -8407,6 +8414,202 @@ cql_code cql_startup(sqlite3 *_Nonnull _db_) { SET l2 := cql_get_blob_size(blob_var2); */ l2 = cql_get_blob_size(blob_var2); + + // The statement ending at line XXXX + + /* + SET x := 1 * (2 / 3); + */ + x = 1 * (2 / 3); + + // The statement ending at line XXXX + + /* + SET x := 1 * 2 / 3; + */ + x = 1 * 2 / 3; + + // The statement ending at line XXXX + + /* + SET x := 1 + 2 / 3; + */ + x = 1 + 2 / 3; + + // The statement ending at line XXXX + + /* + SET x := 1 + (2 - 3); + */ + x = 1 + (2 - 3); + + // The statement ending at line XXXX + + /* + SET x := 1 + 2 * 3; + */ + x = 1 + 2 * 3; + + // The statement ending at line XXXX + + /* + SET x := 1 * (2 + 3); + */ + x = 1 * (2 + 3); + + // The statement ending at line XXXX + + /* + SET x := 1 - (2 + 3); + */ + x = 1 - (2 + 3); + + // The statement ending at line XXXX + + /* + SET x := 1 - (2 - 3); + */ + x = 1 - (2 - 3); + + // The statement ending at line XXXX + + /* + SET x := 1 - 2 - (2 - 3); + */ + x = 1 - 2 - (2 - 3); + + // The statement ending at line XXXX + + /* + SET x := 1 - 2 - (2 - 3); + */ + x = 1 - 2 - (2 - 3); + + // The statement ending at line XXXX + + /* + SET x := 1 / 2 / 3; + */ + x = 1 / 2 / 3; + + // The statement ending at line XXXX + + /* + SET x := 1 / (2 / 3); + */ + x = 1 / (2 / 3); + + // The statement ending at line XXXX + + /* + SET x := 1 / 2; + */ + x = 1 / 2; + + // The statement ending at line XXXX + + /* + SET x := 1 * 2 * (3 * 4); + */ + x = 1 * 2 * (3 * 4); + + // The statement ending at line XXXX + + /* + SET x := 1 * 2 * (3 * 4); + */ + x = 1 * 2 * (3 * 4); + + // The statement ending at line XXXX + + /* + SET x := 1 | 2 & 3; + */ + x = (1 | 2) & 3; + + // The statement ending at line XXXX + + /* + SET x := 1 | (2 & 3); + */ + x = 1 | 2 & 3; + + // The statement ending at line XXXX + + /* + SET x := 1 | 2 | 3; + */ + x = 1 | 2 | 3; + + // The statement ending at line XXXX + + /* + SET x := 1 | (2 | 3); + */ + x = 1 | (2 | 3); + + // The statement ending at line XXXX + + /* + SET x := 1 | (3 + 4 | 5); + */ + x = 1 | (3 + 4 | 5); + + // The statement ending at line XXXX + + /* + SET x := 1 | 3 + (4 | 5); + */ + x = 1 | 3 + (4 | 5); + + // The statement ending at line XXXX + + /* + SET x := (1 | 3) + (4 | 5); + */ + x = (1 | 3) + (4 | 5); + + // The statement ending at line XXXX + + /* + SET x := (1 + 2) * 5; + */ + x = (1 + 2) * 5; + + // The statement ending at line XXXX + + /* + SET x := 1 + 2 - 1; + */ + x = 1 + 2 - 1; + + // The statement ending at line XXXX + + /* + SET x := 1 << 2 | 3; + */ + x = 1 << 2 | 3; + + // The statement ending at line XXXX + + /* + SET x := 1 << (2 | 3); + */ + x = 1 << (2 | 3); + + // The statement ending at line XXXX + + /* + SET x := 1 | (2 << 3); + */ + x = 1 | 2 << 3; + + // The statement ending at line XXXX + + /* + SET x := 1 << (2 << 3); + */ + x = 1 << (2 << 3); cql_cleanup: cql_string_release(t0_nullable); diff --git a/sources/test/cg_test_c_with_namespace.h.ref b/sources/test/cg_test_c_with_namespace.h.ref index bb6744ce02..dddb32a264 100644 --- a/sources/test/cg_test_c_with_namespace.h.ref +++ b/sources/test/cg_test_c_with_namespace.h.ref @@ -1738,6 +1738,65 @@ extern CQL_WARN_UNUSED cql_code base_proc_savepoint_commit_return(sqlite3 *_Nonn // The statement ending at line XXXX extern CQL_WARN_UNUSED cql_code base_proc_savepoint_rollback_return(sqlite3 *_Nonnull _db_); +// The statement ending at line XXXX +extern cql_int32 x; + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + +// The statement ending at line XXXX + // The statement ending at line XXXX extern void end_proc(void);