OpenKODE Core extension: KD_KHR_formatted


NameKHR_formatted
Name stringsKD_KHR_formatted
ContributorsTim Renouf, Phil Huxley
ContactsKhronos OpenKODE working group
StatusApproved by OpenKODE working group February 2008
VersionVersion 10, 2009-04-02
Number5
Dependencies Requires OpenKODE Core 1.0. This extension is written based on the wording of the OpenKODE Core 1.0 specification. This extension also extends the KD_KHR_float64 extension when present, and is written based on the wording of version 6 of that extension.

1. Overview

This OpenKODE Core extension provides formatted input and output functions.

2. Rationale

Many applications use formatted string processing for a variety of reasons: saving and restoring state, file loading, and formatting for use in output, for example in a game console window. The purpose of this extension is to reduce the time and risk of introducing errors when porting an existing game to OpenKODE, as well as providing highly useful functionality.

This extension also specifies OpenKODE equivalents of the C89 variable argument type va_list and macros va_start and va_end. This is enough to allow an application to implement its own printf-like function that uses va_start and passes the va_list into kdVsnprintf.

This extension is specified such that it can be implemented using the existing OpenKODE Core function kdFtostr for its floating point conversion, avoiding the need for additional floating point conversion code.

3. Header file

When this extension is present, its facilities are accessed by including its header file:

#include <KD/KHR_formatted.h>

4. New types

typedescription
KDVaListKHR

A list of variable arguments.

5. New macros

5.1. KD_VA_START_KHR

Initialize a variable argument list.

Synopsis

KD_VA_START_KHR(ap, parmN)

Description

This macro initializes the variable argument list ap so that it contains the variable arguments of unknown number and type passed to the containing function. parmN is the last formal parameter of the function, before the ... syntax that indicates that the function accepts variable arguments.

KD_VA_START_KHR is a macro, which means that it is undefined how many times each of its arguments is referenced.

After using this macro, if the containing function returns without KD_VA_END_KHR being used on ap first, then undefined behavior results.

Rationale

KD_VA_START_KHR is based on C89 va_start.

5.2.  KD_VA_ARG_INT32_KHR, KD_VA_ARG_INT_KHR, KD_VA_ARG_INT64_KHR, KD_VA_ARG_FLOAT32_KHR, KD_VA_ARG_PTR_KHR

Get the next argument from a variable argument list.

Synopsis

KD_VA_ARG_INT32_KHR(ap)
KD_VA_ARG_INT_KHR(ap)
KD_VA_ARG_INT64_KHR(ap)
KD_VA_ARG_FLOAT32_KHR(ap)
KD_VA_ARG_PTR_KHR(ap)

Description

Each of these macros reads the next argument from the variable argument list ap, updating ap to step it beyond that argument.

KD_VA_ARG_INT32_KHR, KD_VA_ARG_INT_KHR, KD_VA_ARG_INT64_KHR, KD_VA_ARG_FLOAT32_KHR or KD_VA_ARG_PTR_KHR is used for an argument of type KDint32, KDint, KDint64, KDfloat32 or any pointer type, respectively. Using the wrong macro variant for the type of the argument, or using one of these macros when there are no more arguments to read, results in undefined behavior.

Rationale

The KD_VA_ARG_*_KHR macros are based on C89 va_arg. va_arg takes the type of the argument; this is not done in OpenKODE Core as there is no portable way of knowing what each type is promoted to in a C variable argument function call. This is a particular issue with KDfloat32, which is likely to be promoted to double, which does not have an equivalent OpenKODE Core type.

5.3. KD_VA_END_KHR

Finish using a variable argument list.

Synopsis

KD_VA_END_KHR(ap)

Description

This macro indicates that the application has finished using the variable argument list ap set up by KD_VA_START_KHR.

If ap is not a variable argument list that has been initialized in this function invocation with KD_VA_START_KHR and has not already been the subject of a KD_VA_END_KHR, then undefined behavior results.

Rationale

KD_VA_END_KHR is based on C89 va_end.

6. New functions

6.1. kdSnprintfKHR, kdVsnprintfKHR, kdSprintfKHR, kdVsprintfKHR

Formatted output to a buffer.

Synopsis

KDint kdSnprintfKHR(KDchar * buf,
 KDsize  bufsize,
 const KDchar * format,
  ...);
KDint kdVsnprintfKHR(KDchar * buf,
 KDsize  bufsize,
 const KDchar * format,
 KDVaListKHR  ap);
KDint kdSprintfKHR(KDchar * buf,
 const KDchar * format,
  ...);
KDint kdVsprintfKHR(KDchar * buf,
 const KDchar * format,
 KDVaListKHR  ap);

Description

These functions write a null-terminated formatted string based on the null-terminated string format, with certain sequences substituted by values of further arguments, into the buffer buf.

kdSnprintfKHR and kdSprintfKHR take the arguments to be formatted as a variable argument list. kdVsnprintfKHR and kdVsprintfKHR take the arguments as a single KDVaListKHR value, from which the arguments are retrieved. The function does not call KD_VA_END_KHR before returning.

The output is written as a null-terminated string into the buffer pointed to by buf. For kdSnprintfKHR and kdVsnprintfKHR, bufsize is the size of the buffer, and the functions never write beyond that size.

If buf does not point to writable memory long enough for the shorter of the requested output or the supplied bufsize (as applicable), or if format is not a readable null-terminated string, then undefined behavior results.

Return value

On success, the functions return the length of the written string without its null termination. For kdSnprintfKHR and kdVsnprintfKHR, if the output (with null termination) would not fit into bufsize bytes, then the function returns what the length would have been if the buffer had been large enough, and null terminates the output so it just fits in the buffer.

The format string

The null-terminated format string consists of zero or more directives. A single character other than % is a directive that causes that character to be written unchanged to the output. A pair of percent signs %% is a directive that causes a single percent sign to be written to the output.

A percent sign % otherwise starts a conversion specification, ending with a conversion character. A conversion specification causes one or more of the further arguments to be used up, typically to convert one to string format and output it.

A percent sign followed by characters not forming a valid conversion specification causes undefined behavior.

If the conversion specifier is invalid, allowing undefined behavior, an implementation is recommended to terminate the application in some way, rather than outputting nothing or something and appearing to succeed. This reduces the danger of the application programmer accidentally relying on non-portable behavior of the format string.

A conversion specification consists, in this order, of:

  • the initial % character;

  • zero or more flags;

  • optional field width;

  • optional precision;

  • optional length modifier;

  • conversion character.

Flags

The flags can be, in any order:

  • - (minus sign) specifies that the converted argument is left justified in its field.

  • + (plus sign) specifies that a converted signed number is always printed with a sign.

  • Space character specifies that if the first character of a converted signed number is not a sign, a space character is prefixed.

  • 0 (zero character) specifies that a converted number is padded to the field width with zero characters. 0 is ignored if the - flag is also present. For integer conversions, 0 is ignored if a precision is also specified. For floating point conversions, specifying both 0 and a precision causes undefined behavior.

  • # alters certain conversions: a number converted to octal always starts with a zero digit; a number converted to hexadecimal has 0x or 0X (respectively, for conversion characters x and X) prefixed; a converted floating-point number always has a decimal point, even if there are no digits after it; for g and G conversions, trailing zeros are not removed.

Field width

When present, the field width specifies the minimum width of the field into which the argument will be converted. The field width is specified either as a non-negative decimal integer, or as a * character, which causes the next argument (which must be a AKDint) to be read and used as the minimum field width. Even in the * case, a negative field width is taken to be a field width of its absolute value, together with a - flag.

Note that a (constant) field width never starts with 0; any 0 character is treated as a flag rather than part of the field width.

Precision

When present, the precision specifies the maximum number of characters to use from a string, or the minimum number of digits in an integer conversion (leading zeros are added to make up the minimum; an explicit precision of 0 used on a value of 0 prints no digits at all; the default precision is 1), or the exact number of digits after the decimal point in a e, E, f or F conversion, or the maximum number of significant digits in a g or G conversion. The precision is specified as a period character, then nothing (giving a precision of 0), or a decimal integer, or a * character, causing the next argument (which must be a AKDint) to be read and used as the precision. A negative precision is treated the same as no precision specification at all.

Length modifier

The optional length modifier is one of:

  • h specifies that an integer conversion has a KDint16 or KDuint16 argument, or an n conversion character has a KDint16 * argument;

  • l (one lower-case letter L) is ignored for an integer conversion;

  • ll (two lower-case letter Ls) specifies that an integer conversion has a KDint64 or KDuint64 argument.

Attempting to use a length modifier and conversion character combination not specified above makes the conversion specification invalid.

Conversion character

The conversion character is one of:

  • d or i reads the next argument, which must be KDint or KDuint, or as specified by any length modifier, and outputs in signed decimal notation;

  • o reads the next argument, which must be KDint or KDuint, or as specified by any length modifier, and outputs in unsigned octal notation;

  • x or X reads the next argument, which must be KDint or KDuint, or as specified by any length modifier, and outputs in unsigned hexadecimal notation, with non-decimal digits in the same case as the conversion character;

  • u reads the next argument, which must be KDint or KDuint, or as specified by any length modifier, and outputs in unsigned decimal notation;

  • c reads the next argument, which must be KDchar or KDint8 or KDuint8, and outputs the single character;

  • s reads the next argument, which must be a const KDchar * value that either points to a null-terminated string or is KD_NULL and outputs the string (truncated by any precision specification), or an implementation-defined representation of KD_NULL;

  • e or E reads the next argument, which must be KDfloat32, or KDfloat64KHR (when the KD_KHR_float64 extension is also present), and outputs a rounded representation: a minus sign if applicable, then a single digit (non-zero if the value is non-zero), then the decimal point, then the number of digits specified by the precision (6 if not specified), then a e or E character (respectively for the two conversion characters), then a plus or minus sign, then two or more digits of exponent (with no leading zeros unless a two digit exponent). If the precision is zero and there is no # flag, the decimal point is omitted. If the value is zero the exponent is zero.

    The converted output for a KDfloat32 is allowed to be up to one out in its ninth significant digit. Thus digits beyond the ninth have undefined values.

    The converted output for a KDfloat64 is allowed to be up to one out in its seventeenth significant digit. Thus digits beyond the seventeenth have undefined values.

    An argument representing an infinity or not-a-number is converted as if by kdFtostr, in the same case (upper or lower) as the conversion character. The specification of kdFtostr regarding infinity and not-a-number was tightened in OpenKODE Core 1.0.1, so the exact semantics depend on the version of OpenKODE Core being implemented.

  • f or F reads the next argument, which must be KDfloat32, or KDfloat64KHR (when the KD_KHR_float64 extension is also present), and outputs a rounded representation: a minus sign if applicable, then at least one digit, then the decimal point, then the number of digits specified by the precision (6 if not specified). If the precision is zero and there is no # flag, the decimal point is omitted.

    The converted output for a KDfloat32 is allowed to be up to one out in its ninth significant digit. Thus digits beyond the ninth have undefined values.

    The converted output for a KDfloat64 is allowed to be up to one out in its seventeenth significant digit. Thus digits beyond the seventeenth have undefined values.

    An argument representing an infinity or not-a-number is converted in the same way as for e or E.

  • g or G reads the next argument, which must be KDfloat32, or KDfloat64KHR (when the KD_KHR_float64 extension is also present), and outputs a rounded representation in one of these forms:

    • as per e or E (for g or G respectively) if the exponent is more than or equal to the specified precision and not zero, or the exponent is less than -4;

    • otherwise, as per f or F (for g or G respectively).

    In either case, the precision (6 if not specified, 1 if specified as 0) determines the number of significant digits in the mantissa.

  • p reads the next argument, which must be of some pointer type, and outputs an implementation-defined representation of the pointer;

  • n reads the next argument, which must be KDint *, or as modified by any length specifier, and writes the number of characters output so far in this call into the location pointed to by the value. Nothing is output.

If an argument is not of the type expected by the conversion specification, then undefined behavior results. However, certain discrepancies are allowed:

  • KDint64 and KDuint64 may be used interchangeably;

  • KDint and KDuint may be used interchangeably;

  • KDint32, KDuint32, KDint16, KDuint16, KDint8, KDuint8 and KDchar may be used interchangeably;

  • KDint * and KDuint * may be used interchangeably;

  • KDint16 * and KDuint16 * may be used interchangeably.

Rationale

kdSprintfKHR and kdVsprintfKHR are based on the C89 functions sprintf and vsprintf. kdSnprintfKHR and kdVsnprintfKHR have an API specification based on C99 snprintf and vsnprintf (which differs in return value from SUSv2 in the case that the output does not fit in the buffer).

The format conversions are those in C89, except for the addition of the C99 ll length modifier to convert 64-bit integer values.

The statement that a floating-point conversion can be up to one out in its ninth significant digit is intended to match kdFtostr, so that this function can be implemented in terms of that one, or vice versa.

Further differences from C standards concern the argument types. OpenKODE Core uses KDint16 or KDuint16 instead of short or unsigned short, and KDint or KDuint instead of int or unsigned int, and (for the C99-like ll length modifier) KDint64 or KDuint64 instead of long long or unsigned long long.

6.2. kdFprintfKHR, kdVfprintfKHR

Formatted output to an open file.

Synopsis

KDint kdFprintfKHR(KDFile * file,
 const KDchar * format,
  ...);
KDint kdVfprintfKHR(KDFile * file,
 const KDchar * format,
 KDVaListKHR  ap);

Description

These functions write a formatted string based on the null-terminated string format, with certain sequences substituted by values of further arguments, to the open-for-writing file file.

kdFprintfKHR takes the arguments to be formatted as a variable argument list. kdVfprintfKHR takes the arguments as a single KDVaListKHR value, from which the arguments are retrieved. The function does not call KD_VA_END_KHR before returning.

Formatting occurs in the same way as in kdSnprintfKHR.

If file is not an open file, or if format is not a readable null-terminated string, then undefined behavior results.

Return value

On success, the function returns the number of characters written (excluding the null termination). On failure, it returns -1, sets the file’s error indicator (as returned by kdFerror) and stores one of the error codes listed below into the error indicator returned by kdGetError.

Error codes

Refer to kdPutc.

Rationale

kdFprintfKHR and kdVfprintfKHR have an API specification based on C89 fprintf and vfprintf.

See the rationale for kdSnprintfKHR for a discussion of the format conversions.

6.3. kdLogMessagefKHR

Formatted output to the platform’s debug logging facility.

Synopsis

KDint kdLogMessagefKHR(const KDchar * format,
  ...);

Description

This function writes a formatted string based on the null-terminated string format, with certain sequences substituted by values of further arguments, to the same place as kdLogMessage. Unlike that function, it does not automatically append a newline character.

Formatting occurs in the same way as in kdSnprintfKHR.

If format is not a readable null-terminated string, then undefined behavior results.

Return value

The function returns the number of characters written (excluding the null termination). It cannot fail, and specifically cannot run out of memory.

Rationale

kdLogMessagefKHR allows a developer to add a convenient single line to a program to aid in its debugging by logging some variable values and so on. Although kdLogMessage could be used for this, it is inconvenient to use when number values need to be output.

See the rationale for kdSnprintfKHR for a discussion of the format conversions.

6.4. kdSscanfKHR, kdVsscanfKHR

Read formatted input from a buffer.

Synopsis

KDint kdSscanfKHR(const KDchar * str,
 const KDchar * format,
  ...);
KDint kdVsscanfKHR(const KDchar * str,
 const KDchar * format,
 KDVaListKHR  ap);

Description

These functions scan the null-terminated string pointed to by str as directed by the null-terminated format string format, with certain sequences in the format string causing part of the input to be converted and the result stored in a location pointed to by one of the further arguments.

kdSscanfKHR takes the further arguments as a variable argument list. kdVsscanfKHR takes the further arguments as a single KDVaListKHR value, from which the arguments are retrieved. The function does not call KD_VA_END_KHR before returning.

If buf does not point to readable memory long enough to read as directed by the format string (a readable null-terminated string always satisfies this criterion), or if format is not a readable null-terminated string, then undefined behavior results.

Return value

The functions return the number of input items successfully matched and assigned before the end (the null termination) of the format string is reached, or before a matching failure occurs. If the end of the input string str is reached, then this is an input failure and the function returns KD_EOF.

The format string

The null-terminated format string consists of zero or more directives. If processing of a directive fails, then the functions return without reading any further input.

A directive is one of:

  • a sequence of one or more whitespace characters (each character is space, newline, form-feed, carriage return, horizontal tab or vertical tab), which matches the maximal sequence of zero or more whitespace characters in the input;

  • a character other than whitespace or %, which matches that exact character in the input;

  • a %% sequence, which matches one % character in the input;

  • a conversion specification, starting with a % character, which causes a sequence of characters to be read and converted according to the specification, and the result placed in the location pointed to by the next of the further arguments. If the next input item does not match the conversion specification, the conversion fails and the functions stop reading input and return.

A percent sign followed by characters not forming a valid conversion specification causes undefined behavior.

If the conversion specifier is invalid, allowing undefined behavior, an implementation is recommended to terminate the application in some way, rather than reading and converting nothing or something and appearing to succeed. This reduces the danger of the application programmer accidentally relying on non-portable behavior of the format string.

Conversion specification

A conversion specification consists, in this order, of:

  • the initial % character;

  • an optional * character to suppress assignment;

  • an optional field width specifier;

  • an optional length modifier character;

  • the conversion specifier.

Except where otherwise specified, initial whitespace is skipped before starting a conversion.

Suppress assignment

The optional * character in the conversion specification causes assignment to be suppressed: once the conversion has been performed, the result is discarded without using the next of the further arguments and without incrementing the count of items successfully matched and assigned.

Field width

The field width is optionally specified by a strictly positive decimal integer. After scanning any initial whitespace where applicable, the field width is the maximum number of characters that will be considered part of the field and used in the conversion.

Length modifier

The length modifier is an optional character sequence that modifies the size of the type of the location (pointed to by the next of the further arguments) that receives the converted value. The length modifier is one of:

  • h specifies that a conversion specifier of d, i, o, u, x, X or n stores a KDint16 or KDuint16.

  • l (lower-case L) is accepted and ignored for a conversion specifier of d, i, o, u, x, X or n.

  • ll (double lower-case L) specifies that a conversion specifier of d, i, o, u, x, X or n stores a KDint64 or KDuint64.

Conversion specifier

The conversion specifier is one of:

  • d reads an optionally signed decimal integer. The value is stored as a KDint (unless there is a length modifier).

  • i reads an optionally signed decimal, octal (starts with 00 or hexadecimal (prefixed with 0x or 0X) integer. The value is stored as a KDint (unless there is a length modifier).

  • o reads an optionally signed octal integer. The value is stored as a KDuint (unless there is a length modifier).

  • x reads an optionally signed and optionally 0x or 0X prefixed hexadecimal integer. The value is stored as a KDuint (unless there is a length modifier).

  • e, f or g reads a floating point number, as if by kdStrtof. The value is stored as a KDfloat32.

    The specification of kdStrtof regarding infinity, not-a-number and hexadecimal floating point was tightened in OpenKODE Core 1.0.1, so the exact semantics depend on the version of OpenKODE.

  • c reads exactly the number of characters specified in the field width (1 if not specified), and copies them without null termination into the KDchar array pointed to by the argument. For this conversion specifier, the usual skipping of initial whitespace is suppressed.

  • s reads a sequence of non-whitespace characters, and copies them with an additional null termination into the KDchar array pointed to by the argument.

  • A conversion specifier consisting of [ followed by one or more characters other than ] (the scanset) (the first character in the scanset can be [, but cannot be ^), then a final ] matches one or more characters in the input where each is in the scanset. The matching character sequence is copied with an additional null termination into the KDchar array pointed to by the argument.

  • A conversion specifier consisting of [^ followed by one or more characters other than ] (the scanset) (the first character in the scanset can be [), then a final ] matches one or more characters in the input where each is not in the scanset. The matching character sequence is copied with an additional null termination into the KDchar array pointed to by the argument.

  • p reads a pointer value in an implementation-defined format which includes the format used by %p in kdSnprintfKHR. The value is stored as a void *.

  • n does not read any input (not even initial whitespace), and stores the current count of bytes consumed as a KDint. This conversion specifier thus consumes an argument, but does not itself increment the count of items successfully matched and assigned.

If an argument is not of the pointer type expected by the conversion specification (i.e. the argument is not of type pointer to the type that the conversion specification stores, other than a change of signedness), or the location pointed to by the argument is not writable and big enough for the value being stored, then undefined behavior results.

Rationale

kdSscanfKHR and kdVsscanfKHR are based on on C89 sscanf, with the addition of C99's ll length modifier.

There are some differences from C standards in the argument types. OpenKODE Core uses KDint16 * or KDuint16 * instead of short * or unsigned short *, and KDint * or KDuint * instead of int * or unsigned int *, and (for the C99-like ll length modifier) KDint64 * or KDuint64 * instead of long long * or unsigned long long *.

6.5. kdFscanfKHR, kdVfscanfKHR

Read formatted input from a file.

Synopsis

KDint kdFscanfKHR(KDFile * file,
 const KDchar * format,
  ...);
KDint kdVfscanfKHR(KDFile * file,
 const KDchar * format,
 KDVaListKHR  ap);

Description

These functions read the open file as directed by the null-terminated format string format, with certain sequences in the format string causing part of the input to be converted and the result stored in a location pointed to by one of the further arguments.

kdFscanfKHR takes the further arguments as a variable argument list. kdVfscanfKHR takes the further arguments as a single KDVaListKHR value, from which the arguments are retrieved. The function does not call KD_VA_END_KHR before returning.

format directs the reading and conversion of the data in the same way as in kdSscanfKHR.

If file is not an open file, or if format is not a readable null-terminated string, then undefined behavior results.

Return value

The functions return the number of input items successfully matched and assigned before the end (the null termination) of the format string is reached, or a matching failure occurs. If end-of-file is reached, the function sets the file’s end-of-file indicator (as returned by kdFEOF) and returns KD_EOF. If an error occurs reading the file, the function sets the file’s error indicator (as returned by kdFerror) and stores one of the error codes listed below into the error indicator returned by kdGetError.

Error codes

Refer to kdGetc.

Rationale

kdFscanfKHR and kdVfscanfKHR are based on C89 fscanf and vfscanf.

See the rationale for kdSscanfKHR for a discussion of the format conversions.

7. Revision history

7.1. Version 10, 2009-04-02

  • Corrected kdSscanfKHR %n conversion to store the number of bytes consumed, not the number of conversions performed, in line with C and POSIX standards.

7.2. Version 9, 2008-06-20

  • Changed back to being based on OpenKODE Core 1.0 specification.

  • Rephrased kdSnprintfKHR treatment of floating-point infinity or not-a-number to be the same as kdFtostr, and noted that this changed in OpenKODE 1.0.1.

  • Noted that the kdStrtof treatment of infinity, not-a-number and hexadecimal floating point changed in OpenKODE 1.0.1, so this affects the semantics of kdSscanfKHR.

7.3. Version 8, 2008-03-31

  • Now based on OpenKODE Core 1.0.1 specification.

7.4. Version 7, 2008-02-01

  • Reduced accuracy of KDfloat64KHR output to one out in 17th digit.

7.5. Version 6, 2008-01-24

  • Fixed incorrect function names.

7.6. Version 5, 2008-01-14

  • Clarified that a precision of 0 in an integer conversion with a value of 0 prints no digits at all. Specified that an integer conversion has a default precision of 1. Allowed for three digit exponent. Stated that the 0 flag, when combined with a precision, is ignored for integer conversions, and causes undefined behavior for floating point conversions. Fixed typos.

7.7. Version 4, 2007-11-12

  • Fixed a minor typo.

7.8. Version 3, 2007-10-19

  • Allowed for *scanf functions returning KF_EOF.

7.9. Version 2, 2007-10-15

  • Added KD_VA_ARG_*_KHR macros for extracting variable arguments one at a time.

7.10. Version 1, 2007-10-08

  • Initial version of official Khronos extension.