This is the maximum length of the printed representation for a NaN. In our case, it's for something like -NAN(123).
This is a new feature for C23, but since it was a reserved identifier under earlier standards, we just define it unconditionally.
As with printf, the format checker expects 'b' to have its traditional ORCA/C behavior in pre-C23 modes, or the C23 standard behavior in C23 modes.
Also as with printf, it can detect when 'b' is being used in the traditional way and automatically change it to 'p'. Specifically, this is done when 'b' is used with no length modifier and the corresponding argument is a pointer to a character type. This behavior is controlled by the same #pragma extensions bit as for printf, and it also only works when the scanf-family function is called directly and has a string literal for the format string.
This uses the printf format-checking mechanism to parse the format string and associate conversions in it with arguments. If the argument corresponding to 'b' is a pointer (and no length modifier is used), the 'b' is converted to 'P'. The effect is that most code that used 'b' in the old way will keep working, but code using 'b' with integers will get the new behavior (in C23 mode). Note that this only works if the format string is given by a string literal, and the printf-family function is called directly (not via a function pointer).
This is controlled bit a new #pragma extensions bit (bit 2), which is enabled by default when not in strict mode.
When compiling in C23 mode, ORCA/C now generates references to the label ~C23ORLATER, which is defined in the library. The presence of this label in the linked executable can be used by library functions to determine whether they should operate in C23 mode.
This applies to code like the following, which is allowed by C89 but not C99+:
int f(a) { /* code using a, implicitly declared as int */ }
Such code was inadvertently broken in c23compat mode due to changes to handle C23 rules, but it should be allowed in that mode for compatibility with old code. It is still disallowed by strict mode, or by appropriate lint checks.
These are similar to the ones from the last commit, in that there are earlier checks for the pointer being nil, but the nil dereferences can still occur because short-circuit evaluation is not used. These ones have somewhat more complicated control flow, and also the added aspect that a value read through the nil pointer is itself treated as a pointer and dereferenced. This still doesn't cause problems most of the time, but dereferencing that garbage value may possibly hit a soft switch or IO location. Apart from fixing that potential issue, there should be no functional changes.
These were in code like "if (ptr <> nil) and (ptr^.field = XYZ) then ..." or similar. ORCA/Pascal does not use short-circuit evaluation for boolean operators, so the code that would dereference the pointer would get evaluated even if it was nil. In the situations here, this was basically harmless, since the IIGS does not have memory protection and the value read from the nil pointer would not affect the program's behavior. However, it is still cleaner to avoid the nil pointer dereferences.
(Some similar code with more complex control flow is still present.)
This implements the enhancements to enum types in C23. There are three major elements to these enhancements, which are all implemented here:
-Enumeration constants can have values outside the range of int; enum types using the traditional syntax are automatically assigned an underlying type that is able to hold their constant values (WG14 N3029)
-The underlying type for an enumeration can be explicitly specified using a new syntax (WG14 N3030)
-Enumeration types and their constants can be declared multiple times within the same scope (parts of WG14 N3037)
This gives an error when an expression cast to void is used somewhere that an integer constant expression is required, e.g.:
int main(int argc, char *argv[]) {
switch (argc) {
case (void)0: 1;
}
}
One bug is that the scope of the enum constant only begins at the end of the enumerator, so a definition of the same name from an outer scope should be visible and usable within the expression giving the enum constant value. The other bug is that spurious errors would be reported if the enum constant matched the name of a typedef from an outer scope.
In ORCA/C, types with kind = enumType are only used for enum type tags. They do not appear in other places, because an enum type specifier is treated as yielding the type int. Thus, there can never be an expression or variable with an enum type. This has been the case in ORCA/C at least back to 2.1.0 (and likely farther). This is technically not quite standard-conformant, but it makes very little difference in practice, because ORCA/C currently makes all enum types compatible with int.
There was code in some places to handle enum-typed expressions, variables, or function return types, but this is dead code. Most of this code looks reasonable as far as I can tell, but it has not actually been used for a long time (if ever), and it is likely incomplete relative to what would actually be needed to support enum-typed expressions and variables.
This patch comments out that dead code and replaces it with calls to produce an error if those code paths are ever triggered, which effectively act as assertions that they are not used. If ORCA/C was ever changed to use enum types more widely, the code for handling them could be uncommented at that time, but it would need to audited and updated as appropriate.
Previously, the code would remove any number of "pointer to" and "array of" elements in the type, and accept it if this ultimately yielded a function type. Thus, the expressions denoting the function to be called could be, e.g., a pointer to a function pointer or an array of function pointers. The code generated in these cases would not work properly, but no error was reported.
Now only function pointers or expressions of function type are accepted, as they should be.
This affects code like function_ptr1 = *function_ptr2. This should be allowed (if the function types are compatible), since the function type is converted to a function pointer type in this context.
This affects equality comparisons where one side has function type (e.g. *function_pointer) and the other side has function pointer type (pointing to a compatible function type) or is a null pointer constant. These should be permitted, because the function type is converted to a function pointer type in this context. ORCA/C could report a spurious error for such comparisons, because it was not always performing those conversions.
(ORCA/C would treat function names as having function pointer type for these purposes, since for those the type conversion is performed elsewhere.)
Here is an example showing several expressions that gave spurious errors:
void f(void) {}
void (*fp)(void);
int main(void) {
*fp == f;
*fp == 0;
*fp == (void *)0;
}
An empty initializer {} is now allowed for numeric or pointer types, and is equivalent to {0}. ORCA/C already allowed empty initializers for structs/unions/arrays.
This means that if the parameter giving the array to search is a pointer to a const-qualified type, the return type will be modified so that its pointed-to type is also const-qualified. This functionality is implemented using macros, so the underlying functions still have the same types as before. In addition to the functions specified as generic in C23, this change is also applied to the non-standard strrpbrk function, since it is analogous to strpbrk and has the same issue of potentially discarding const qualifiers.
Note that under ORCA/C's default loose type checking rules, assignments that discard qualifiers are permitted, so these changes have a somewhat limited safety benefit (but also should very rarely require code changes). If using strict type checking, the type rules for assignments will be enforced as per the standard, so assignments that discard qualifiers will produce an error.
This affects cases where the argument expression to assert contains an un-parenthesized comma, which can occur when using compound literals.
When NDEBUG is not defined, this still requires the argument to be something that would count as one argument to a function (i.e. it cannot use an un-parenthesized comma operator). We do this by including an unevaluated call to a fake function using that argument as part of the expansion of the assert macro. (This behavior is specified by a note in the standard, and it's not entirely clear to me that it follows from the normative text, but I've implemented it that way since that's apparently the intent.)
This permits code like the following, which the standards seem to allow:
int x;
_Bool b = &x;
(The bool variable is always initialized to one in cases like this. If would be undefined behavior if the address evaluated to 0, so we can assume it doesn't.)
This check is currently only enabled under strict type checking, since ORCA/C traditionally did not require it but the C11 and later standards do. (The left operand has always been required to point to a complete type, and the two types have been required to be compatible, but the traditional ORCA/C rules could allow the right operand to point to an incomplete array type.)
This is a near-complete implementation of _BitInt types as specified in the C23 standard. The only missing element is support for _BitInt bitfields wider than 32 bits.
This implementation supports _BitInt widths up to 64 and stores _BitInt types using either 16, 32, or 64 bits, whichever is required to fit them. They are sign-extended or zero-extended out to the width used to store them, and for calling conventions and most other purposes they are treated like integers of that width. Unsigned _BitInt types are truncated and zero-extended as necessary to implement the standard wrap-around semantics for unsigned types. When a value is converted to a signed _BitInt type, it is truncated and sign-extended. However, signed _BitInt types are generally not truncated and sign-extended in cases of arithmetic overflow, since that would be fairly expensive and overflow is UB according to the standard; thus, overflow involving signed _BitInt types can effectively produce values outside the range of the type.
This also incidentally (due to changes in GetLongLongVal) fixes a bug where constant division operations such as -1 / 12345u could be mis-evaluated because the usual arithmetic conversions were not applied to them correctly. (Both operands would be promoted to wider types, rather than just converting -1 to 0xFFFF.)
If native code peephole optimizations and register optimizations were both in use, incorrect code could be generated for sign extension during a 16-bit to 32/64-bit conversion. This could occur because a lda instruction moved by peephole optimization might subsequently be removed by register optimization, even though it was actually needed for its effect on flags.
This could affect code like the following:
#pragma optimize 7
int main(void) {
int a = -1;
0;
unsigned b = a;
unsigned long c = a;
return c == -1; // should be 1
}
Helper functions are also introduced to perform the usual arithmetic conversions over a pair of integer types and to set a token to hold an integer constant based on its type.
These changes are in preparation for the introduction of _BitInt types.
This removes the 32-bit code paths that were previously used for constant expressions with 16-bit or 32-bit types. The two code paths were mostly duplicates apart from the type of the operands.
For compatibility with old code, a new #pragma ignore bit is also provided to partially reverse these new rules in a way that should maintain compatibility with most K&R-style C code. This bit is enabled by default in C23 compatibility mode, but disabled in strict C23 mode.
These could occur in some situations where a K&R-style function definition followed a prototyped declaration of the function.
There should generally be no functional change, although the old code could potentially have misbehaved if the null pointer dereferences gave certain values.
The code for parsing prototypes in type names did not special-case (void) as declaring no parameters.
This also fixes a slight issue with parsing prototypes in declarations in C23 mode, in that (typeof(void)) should be treated as equivalent to (void) but was not.
This is a fairly straightforward implementation, which works fine for small files but can be slow or run out of memory for sufficiently large ones. This should be good enough for most realistic use cases on the IIGS, but it could be refined in the future if desired.
Also, it behaves more like the version specified in C++26 in that embed parameters are always macro-expanded, rather than following the somewhat complicated C23 rules about macro expansion. GCC and Clang also seem to work similarly, even in C23 mode.
This is consistent with what the assembly code actually does. Previously, the result was treated as a 32-bit integer, but since the assembly code only returns 16 bits, the upper 16 bits would be garbage. This wouldn't end up mattering, because the result was later truncated to 16 bits, but it's cleaner to just declare the functions correctly.
This just specifies dynamic rounding mode that can be configured with <fenv.h> calls, like before C23. The other #pragma STDC FENV_ROUND values (specifying static rounding modes) are still not supported.
This follows the C23 requirement to support VLA types, but not necessarily to support local variables of VLA type (which we still do not). This should be essentially a full implementation of VLA types as specified by C23, except that errors about branching into the scope of a variably modified variable are not detected.
It is now possible to invoke C23 tests in C23 mode by running "DOIT4 TEST.C23".
A test for the new true and false keywords is also added, taking advantage of this. Additional tests for C23-specific features may be added in the future.
This aligns the terminology with the C standard and better reflects how these expressions are used (i.e. not just for arrays). This will be particularly relevant once variable-length array types are supported, since at that point array sizes may not be integer constant expressions.
Note that this overlaps with the old C89/C90 syntax for declarations with "implicit int." In C23 modes, declarations meeting the requirements for type inference will use it, but in "c23compat" mode other declarations using "auto" are still eligible to be interpreted as using implicit int. (C23 also allows for type inference in some implementation-defined situations that go beyond the minimum required to be supported, but this implementation mostly follows the minimum requirements.)
The parsing logic for unnamed parameters sometimes needs to look ahead an extra token to account for this.
Here are examples of function declarations affected by this logic, as well as equivalent and compatible ways of declaring the same functions:
int f(int([[]] int));
int f(int (*)(int));
int g(int([]));
int g(int*);
This affects type names such as "int (int)", which should indicate a function type. Previously, it would not be parsed as such.
Function types are not generally legal where type names are used (other than in typeof, but that's not yet implemented), so use of such type names will still result in an error, but a better error message may be reported.
This also removes the previous special-case code to handle type names like "int ()". They can now be handled by the general code for function abstract declarators.
This still does not actually process prototypes in type names to generate the appropriate prototyped function type. That will be handled later.
This handles function declarations like:
int (int (int));
Note that this also applies to parameter declarations like "int (foo)" where foo is a typedef name, per the rule that it is treated as a typedef name (making this an unnamed parameter declared with function type) rather than the name of the parameter.
This could wind up with the wrong value in some cases involving functions returning function pointers. This could cause spurious errors in cases where a function's parameters were declared in the K&R style relying on "implicit int", but the function returned a prototyped function pointer type, e.g.:
int (*f(x,y))(int, long) { /*...*/ }
There errors stemmed from including parameters from the returned function pointer type in the list of parameters associated with the function being declared. This could cause spurious errors about unnamed parameters for code like:
void (*f(void))(int, long) { /*...*/ }
It could also cause miscompilation resulting from trying to access parameters at the wrong stack positions for "pascal" functions returning a function pointer, like:
int pascal (*f(char x, double y))(int a, long b) {
printf("x = %c, y = %f\n", x, y);
return 0;
}
In C23 mode, this also enforces the new constraints that u- or U-prefixed character constants must only contain a single character, and it must be representable in a single UTF-16 or UTF-32 code unit.