Summary

This is a source transformation that modify Python’s syntax to allow the use of increment (x++, ++x) and decrement (x--, --x) operators.

Source code

Unary increment and decrement operators

This implements unary pre/post increments and decrements operators, (++x, --x, x++, x--). These operators are well-liked by some, but can be a huge source of confusion. In case you are not familiar with them:

  • Pre-increment (++x) and pre-decrement (--x) operators modify their operand (x) by 1 and return the value after having done so. One question one might have is what exactly is meant by ‘after’?. For example, in some implementation of C, the expression y = x- --x is considered to be indefined: do we decrement x after doing the assignment or before? Also note that, in Python, we have that ++x == x == --x is syntactically valid.

  • Post-increment (x++) and post-decrement (x--) operators also modify their operand by 1 but return the value before doing so. Note that, in Python, these expressions, x++ and x-- anre not syntactically valid.

The idea to implement these operators via an import hook was inspired by this implementation from Matthias Wippich which uses codecs and token-based transformations. Both Wippich’s implementation, and that of Github user dankeyy (using regular expression and codecs-based) make cleverly use of the walrus operator in the following way:

++x -> (x, x := x + 1)[1]
--x -> (x, x := x - 1)[1]
x++ -> (x, x := x + 1)[0]
x-- -> (x, x := x - 1)[0]

Note that the following are valid Python expressions:

x++y, x+-y, +x+++y, x+ +y, etc.

While greatly inspired by the existing implementations, we have found that they fail to give the expected result in some situations. Our implementation takes care to ensure that these valid expressions remain so with their current meaning; this is done by only making changes when a human reader would be able to unambiguously identify if an increment or decrement operator is used.

So, taking x++ as an example, we will impose the restriction that, in *x++*, * cannot be another unary sign, + or - nor can it be a valid Python identifier. In *x++* and similar, if * represent an identifier, then spaces between * and the increment/decrement are not significant. Since we can’t have two consecutive identifiers in Python, no change will take place. However, in x++, there can be no space between any of these symbols.

Here’s a sample session illustrating the transformations that do or do not occur:

> ideas -a inc_dec
Ideas Console version 0.3.3. [Python version: 3.11.9]
ideas> from ideas import transform
ideas> transform("x++")
(x, x := x + 1)[0]
ideas> transform("x--")
(x, x := x + 1)[0]
ideas> transform("--x")
(x, x := x - 1)[1]
ideas> transform("++x")
(x, x := x + 1)[1]

ideas> transform("x+++")
x+++
ideas> transform("---x")
---x
ideas> transform("x++y")
x++y
ideas> transform("x++ y") # think of 'x++' as a new value 'X', and thus 'X y'
x++ y                     # which would be invalid; so we keep the valid syntax
ideas> transform("x++ +y")
(x, x := x + 1)[0] +y
ideas> transform("'x++'")  # inside string
'x++'
ideas> transform("y-   ++x")
y-   (x, x := x + 1)[1]
ideas> transform("y-++x")  # valid Python syntax
y-++x