SIMPL+ Expressions & Type conversions

Dealing with type conversions without the ability to explicitly cast to another type isn’t always straightforward. Especially in the context of primitive integer types built into the SIMPL+ language, narrowing conversions can cause problems if you aren’t aware of them.

SIMPL+ is a strange, minimalist, C-like language that has more than a handful of problems; including silencing implicit narrowing type conversions.

 

 

 

 

 

#ENABLE_TRACE
#DEFAULT_VOLATILE
#ENABLE_STACK_CHECKING

// Note: This function is simply here to see if any of the calls to this function result in a compiler warning
// about loss of data from the narrowing conversion.
FUNCTION TestForDataLoss(INTEGER value) { }

FUNCTION Main()
{
    SIGNED_LONG_INTEGER n;
    n = 500096; // Binary: 00000000_00000111_10100001_10000000
    // LSB = 128 (10000000)

    TestForDataLoss(n & 0xFF);   // Expression by itself is recognized as a type of SIGNED_LONG_INTEGER
    TestForDataLoss((n & 0xFF)); // Parentheses promotes the expression to a type of INTEGER
    TestForDataLoss(Low(n));     // Something weird is happening here with an implicit narrowing conversion... Works (?)

    // ... But what if we want the least significant 7 bytes?
    TestForDataLoss(Low(n & 0x7F));     // Works
    TestForDataLoss(LowWord(n & 0x7F)); // Works
    TestForDataLoss(n & 0x7F);          // Does NOT work
    TestForDataLoss((n & 0x7F));        // Works!
    TestForDataLoss((n));               // Should NOT work (but it does)
}

Note: In the code comments, “Works” indicates that the line of code compiles without any compiler warnings.

Without running the code, you can see the results from the comments beside each function call in the source code above. What is interesting, is the effect additional parentheses have on an expression. We can deduce that the expression with additional parentheses is being promoted from a type of SIGNED_LONG_INTEGER to INTEGER, and an expression without the additional parentheses is still recognized as a SIGNED_LONG_INTEGER since a compiler warning complains about that line.

The obvious solution to this problem is to use the built-in functions like Low() and LowWord(). LowWord() converts the type from the SIGNED_LONG_INTEGER passed into the function and returns the low 16 bits as an INTEGER. Low() does not convert the type at all; it takes an INTEGER as a function parameter and returns the least significant 8 bits as an INTEGER. This is important to understand, since passing a SIGNED_LONG_INTEGER to this function which takes in an INTEGER type, does not produce a compiler warning! This is potentially a very bad thing, since the compiler in all other circumstances would at least warn us about a potential loss of data from a narrowing conversion, but it lets you pass in a 32-bit SIGNED_LONG_INTEGER to a function which expects a 16-bit INTEGER no problem. A silent implicit narrowing type conversion like this can lead to inadvertent results that are more difficult to troubleshoot and resolve.

One of the main reasons I wanted to write this article was to show a shorthand for promoting the resultant type of an expression to an INTEGER to avoid the compiler warnings. I now see how this can also lead to unexpected results however, similar to how the compiler will not warn you about passing a SIGNED_LONG_INTEGER into a function which accepts a type of INTEGER. The implicit conversion seems to be based on parentheses and not the expression itself, which is even more concerning since an expression of ‘n’ in the code above–should remain a SIGNED_LONG_INTEGER, but it gets promoted to an INTEGER if you write ‘(n)’.

Note: The implicit type conversion only occurs if the type expected is of a different type than the declared type.

At least with languages like C and C++, you would normally have to explicitly cast for a narrowing conversion. This makes the intent a lot more readable, and the results more in line with what was expected.