CBFalconer wrote:
Frank Heckenbach wrote:
CBFalconer wrote:
Frank Heckenbach wrote:
... snip ...
and I strongly favor making these syntactical extensions functions (so they can be easily replaced in purely standard systems)
Sorry, but this discussion is not really about syntax. We want to be compatible with other compilers where that's easily possible, and several other compilers use such operators. This is just about the semantics which, in other compilers, after often only defined poorly (or accidentally, or for special circumstances).
If you make them available as standard procedures, you can then add code to call them with other syntax when emulating those other systems. Yet you preserve the ability to write new portable code using the operations.
It's a point. OTOH, it also works the other way around, i.e. have them built-in as operators, then code that wants to use them in a "portable" way can declare procedures (or functions) that uses the operators, possibly in the same module that would provide those routines with different implementations for other compilers.
and naming them asl, asr when they consider the sign. That allows clear distinction from lsl, lsr which insert 0 bits at one end or the other, and can be defined to never create an overflow by using modular arithmetic as in C unsigned.
As I wrote, this makes the operations dependent on the type size.
Not for asl, asr. They simply do whatever is needed for the multiply or divide by 2. Only asl can overflow.
Again, "ignoring overflow" means being dependent on the size. E.g., 1 shl 40 is ok if 1 is represented in 64 bits and overflows in 32 bits. This means that the operation is not well-defined when talking of integer *values*, as Pascal normally does.
lsl does the same thing, but ignores overflow and works as if the representation were unsigned binary.
The point is that the compiler and/or executable know whether the value is positive or negative and thus can (and IMHO should) select the appropriate kind of shift themselves (unlike assembler where the programmer can treat data as signed or unsigned at will in each operation, and therefore can, and has to, select the kind of shift intended).
This holds for operators (and is IMHO an advantage of them). For functions, as you suggested, (and barring overloading, either explicit which is non-standard, or built-it which would also require non-standard features to replace it on another system), one would indeed need different versions for signed and unsigned types (jsut to declare the parameters accordingly) and, possibly, different sizes. (Though `Integer' and its unsigned counterpart, if any, could catch all smaller types, if we provide (non-standard) larger types, as we do, we might need different versions for them, since doing everything in the large types would be inefficient.) So we'd have at least 4 times as many routines than operators. For someone writing a compatibility module (see above) this might be ok (and they'd only need to write those routines they actually need, probably not all that are conceivable); for built-it identifiers I'm reluctant to add so many instead of a single operator.
lsr inputs a 0 bit, and is a special case.
That is probably useful on the assembler level. On the Pascal level, it would seem easier to add in this new bit after the operation than adding a new parameter just for this special case.
One potential problem with shl is that it can easily produce overflows. Currently it's not a practical problem, as GPC doesn't do overflow checks, but when it does, we'll have to consider this case. We could define `shl' to "ignore overflows", but this would again make the semantics type-size-dependent. Of course, a
This is why the arithmetic and logical shift operations should be firmly separated. The logicals will be defined in terms of modulo arithmetic, just as C does.
Overflow can happen with signed and unsigned types just as well. I fail to see your point here.
Not when you say you will resolve an unsigned overflow by adding or subtracting the maximum + 1 of that unsigned entity until it fits.
And for signed over-/underflow, I'd subtract/add the maximum - minimum + 1 until it fits, so it's just the same (since the minimum of an unsigned type is 0).
Rotates are problematic since they indeed depend on the type size. I see no way to define them independently. I've had a discussion with Mirsad Todorovad about these, and we came to the conclusion that it's probably more trouble than it helps. E.g., if a is a constant and we use the term `a ror 4', suddenly the type of the constant becomes very important (not just for whether there's an overflow, but it would change the result completely) which is contrary to Pascal's rules.
Again, define them as functions, and let the function parameters include the size in terms of binary bit count:
FUNCTION ror(in : T, places, bits : int) : T;
This would be possible, but could easiy lead to less efficient code when places or bits is variable. I'm writing "or" because I'm not really sure which means what -- to me "places" and "bits" means basically the same in this context. Which one is the rotation count and which one corresponds to the "type size"?
Pick it. Maybe size is a better name than bits. There has to be a limit on the value such that the result can be represented in an integer. You really want still another, rorc and rolc (rotate through carry) but that may be awkward to implement and describe.
I know these exist in assembler, but do we really want/need them in Pascal? Again, how often are rotates used at all (as I said, only once in all my code, and that's a 32 bit rotation, not 33 bit (32 bit + carry)). So, while there are certainly a few more interesting assembler instructions, the question is which ones do we need built-into GPC.
Frank