Where to attach the "not" operator

Soliciting some opinions on how BoxLang should handle the “not” operator, which can be the actual word not or an exclamation point (!). In most other languages (Java, JavaScript, PHP, Ruby, C/C+/C#, Swift. Typescript, Go) the not operator attaches itself directly to the next immediate expression it comes before. Therefore, code like this

!foo == bar

does the following

  • negate the foo variable
  • then compares the result of step 1 with bar

In other words, it’s the same as

(!foo) == bar

However, ColdFusion is a little special unicorn, because the same code behaves like so

  • first compare foo and bar
  • THEN, negate the result

In other words:

!( foo == bar )

The only language I found that matched CF is Python, which also uses a not operator.

So, here’s the question. If we were making a dynamic JVM language from scratch in 2024, would we want to follow CF/Python’s example, or go with what most other languages do?

Of course, we’ll provide transpilation in our cf compat module so your CF code runs the same, but as far as BoxLang source code goes, should we break this precedent or keep it?

Related ticket - BL-663

I’m leaning in the camp of “do what most languages do”. Reading the last example, my brain goes “ya sure, makes sense”, but I also can’t think of a time I have written code like that, let alone with that intention.

1 Like

In more “linguistic” terms, this is about the precedence of ! (or not) – and unary operators in general, I suspect?

In C/C++ and Java, unary operators are all higher precedence than any binary operators, so !foo == bar is (!foo) == bar and -foo == bar is (-foo) == bar which is certainly what I expect.

I’m pretty shocked that CFML has unary operators that have lower precedence than (some) binary operators… that’s really counter-intuitive to me!

And, indeed, yes, CFML does it differently to almost every other language: Expressions-Developing guide:

^
*, /
\
MOD
+, -
&
EQ, NEQ, LT, LTE, GT, GTE, CONTAINS, DOES NOT CONTAIN, ==, !=, >, >=, <, <=
NOT, ! <!--- OMG! UNARY OPERATORS WITH LOWER PRECEDENCE?!?! WTF?!?! --->
AND, &&
OR, ||
XOR
EQV
IMP

So it’s just ! (not). Thankfully, I suppose!

For BoxLang, please fix this so it’s like C/C++/Java/etc.

And not like Python: Operator Precedence in Python - Python Geeks Ugh! :frowning:

JavaScript, for contrast: Operator precedence - JavaScript | MDN

8 Likes

I agree with all the comments here @bdw429s . I say we break off and treat it like it should, which is what we expected anyways.

Then have the compact module treat it like CFML.

1 Like

Ok, I have left the “not” operator with a higher precedence in BoxLang to match most other languages. I have modified our CF transpiler to rewrite the AST on the fly for the following expressions

!foo ^ bar
!foo * bar
!foo / bar
!foo \ bar
!foo MOD bar
!foo + bar
!foo - bar
!foo & bar
!foo EQ bar
!foo NEQ bar
!foo LT bar
!foo LTE bar
!foo GT bar
!foo GTE bar
!foo CONTAINS bar
!foo DOES NOT CONTAIN bar
!foo == bar
!foo != bar
!foo > bar
!foo >= bar
!foo < bar
!foo <= bar

into this

!(foo ^ bar);
!(foo * bar);
!(foo / bar);
!(foo \ bar);
!(foo % bar);
!(foo + bar);
!(foo - bar);
!(foo & bar);
!(foo == bar);
!(foo != bar);
!(foo < bar);
!(foo <= bar);
!(foo > bar);
!(foo >= bar);
!(foo contains bar);
!(foo not contains bar);
!(foo == bar);
!(foo != bar);
!(foo > bar);
!(foo >= bar);
!(foo < bar);
!(foo <= bar);

You can actually test this by putting the first block of code into a .cfs file and then running it through the BL transpiler, and it will spit out the second block in a .bxs file :slight_smile:

Please let me know if I’ve missed anything. Also note, I’m only handling binary concatenation. Our parser optimizes code like

foo & bar & baz & bum

into a single BoxStringConcat AST node with 4 expressions inside, and I’m only rewriting concats with 2 expressions for now.

@bdw429s, this is a good approach. Whenever I use similar logic, I always include parentheses for clarity. While they may not be strictly necessary, they make the expression much clearer and help avoid “nuances” like this behavior.

On Twitter, someone reported that modulus “-1 % 5” should return 4, but indicated that Rust, Go, PHP, JS and C all incorrectly returned -1. (Python was the only language to get it right.) I discovered that CFML and ACF, Lucee & BoxLang all return -1. Should this be corrected or retained for consistency?

NOTE: When using Qalculate, it outputs -1 % 5 as rem(-1, 5) = -1 instead of mod(-1, 5) = 4.

1 Like

Thanks for the input. The modulus behavior has nothing to do with operator precedence though. It has to do with different mathematical approaches for how to calculate modulus with a negative dividend.