API C types
Overview
Duktape API uses typedef-wrapped C types such as duk_int_t
almost exclusively to ensure portability to exotic platforms. This article provides some background, summarizes the types, and describes how calling code should use types to maximize portability.
Guidelines for code using the Duktape API
Use Duktape types such as duk_idx_t
and duk_ret_t
(described below) when declaring variables for maximum portability. Alternatively you may use plain types (like int
or long
) but your code will be less portable and you may need to use casts to avoid warnings. Note that long
is a better default integer type than int
which may be only 16 bits wide on some platforms.
In printf()
formatting cast Duktape types to a wide integer type and use a standard format specifier to ensure that the type and the specifier always match. For integers, long
and unsigned long
are usually a good choice because they don't require C99/C++11 and can usually hold all integer values used by Duktape typedefs. For example:
printf("Result: %ld\n", (long) duk_get_int(ctx, -3));
Duktape API calls which support ANSI C format strings simply pass on the format string and call arguments to the platform's vsnprintf()
function. To maximize portability, select format specifiers carefully and cast arguments to ensure types match. For example:
duk_int_t val = 123;
duk_push_sprintf(ctx, "My integer: %ld", (long) val);
A few standard format specifiers:
Type | Specifier |
---|---|
long | %ld |
unsigned long | %lu |
double | %f or %lf for printf(), %lf for scanf() |
size_t | %zu in C99, pre-C99 compilers have various custom specifiers |
intmax_t | %jd in C99 |
uintmax_t | %ju in C99 |
Format specifiers used by printf()
and scanf()
may be different. For scanf()
, use a standard type and a standard format code (so that you can be certain they match), then cast to a Duktape type as necessary. Again, long
and unsigned long
are a good default choice. For example:
long val;
sscanf(my_str, "%ld", &val);
duk_push_int(ctx, (duk_int_t) val);
Use the L
(or UL
) suffix for constants which are larger than 16 bits to maximize portability. Like the int
type, integer constants without a suffix are only guaranteed to be 16 bits wide. With the L
suffix constants are guaranteed to be at least 32 bits wide. Example:
duk_push_int(ctx, 1234567L);
Duktape 1.x API calls with a filesystem path argument simply pass the path to fopen()
(Duktape 2.x no long provides any file I/O API calls). There is no way to specify an encoding or support a wide character set. To do that, you need to implement a platform specific helper yourself.
Wrapped types used in the Duktape API
For the most part you don't need to worry about these type wrappers: they're intended for exotic environments where some common assumptions about type bit counts and such don't hold.
The API documentation uses the Duktape wrapped typedef names (such as duk_idx_t
). The concrete type used by the compiler depends on your platform and compiler. When hovering over a prototype in the API documentation the tool tip will show what concrete types are used when C99/C++11 types are available and the platform int
is at least 32 bits wide which is nowadays almost always the case.
The following table summarizes a few central typedefs and what the concrete type selected will be in various (example) environments. The table also suggests what plain type you should use for printf()
and scanf()
casts for portable formatting/scanning.
Duktape type | C99/C++11 32-bit int | Legacy 32-bit int | Legacy 16-bit int |
printf
|
scanf
|
Notes |
---|---|---|---|---|---|---|
duk_int_t | int | int | long |
%ld long |
%ld long |
All around integer type, range is [DUK_INT_MIN, DUK_INT_MAX] |
duk_uint_t | unsigned int | unsigned int | unsigned long |
%lu unsigned long |
%lu unsigned long |
All around unsigned integer type, range is [0, DUK_UINT_MAX] |
duk_int32_t | int32_t | int | long |
%ld long |
%ld long |
Exact type for ToInt32() coercion |
duk_uint32_t | uint32_t | unsigned int | unsigned long |
%lu unsigned long |
%lu unsigned long |
Exact type for ToUint32() coercion |
duk_uint16_t | uint16_t | unsigned short | unsigned short |
%u unsigned int |
%u unsigned int |
Exact type for ToUint16() coercion |
duk_idx_t | int | int | long |
%ld long |
%ld long |
Value stack index |
duk_uarridx_t | unsigned int | unsigned int | unsigned long |
%lu unsigned long |
%lu unsigned long |
ECMAScript array index |
duk_codepoint_t | int | int | long |
%ld long |
%ld long |
Unicode codepoints |
duk_errcode_t | int | int | long |
%ld long |
%ld long |
Integer error codes used in the Duktape API (range for user codes is [1,16777215]) |
duk_bool_t | int | int | int |
%d int |
%d int |
Boolean return values |
duk_ret_t | int | int | int |
%d int |
%d int |
Return value from Duktape/C function |
duk_size_t | size_t | size_t | size_t |
%lu unsigned long |
%lu unsigned long |
1:1 mapping now, wrapped for future use. Range is [0, DUK_SIZE_MAX]. C99 format specifier is %zu. |
duk_double_t | double | double | double |
%f or %lf double |
%lf double |
1:1 mapping now, wrapped for future use, e.g. custom software floating point library. |
Background on C/C++ typing issues
This section provides some background and rationale for the C typing.
Portable C/C++ typing is a complex issue, involving:
Portable type detection for C99, C++11, and older environments.
Bit sizes and ranges of available types, selecting the most appropriate types, e.g. fastest or smallest with a guaranteed minimum or exact bit size.
Constants for type ranges, such as
INT_MIN
.Format specifiers when types are used in
printf()
andscanf()
format strings.
(Duktape only works on platforms with two's complement arithmetic.)
Bit sizes are not standard (and there's no guaranteed fast 32-bit type)
Bit sizes of common types like int
vary across implementations. C99/C++11 provide standard integer typedefs like int32_t
(exact signed 32-bit type) and int_fast32_t
(fast integer type which has at least signed 32-bit range). These typedefs are not available in older compilers, so platform dependent type detection is necessary.
Duktape needs an integer type which is convenient for the architecture but still guaranteed to be 32 bits wide. Such a type is needed to represent array indices, Unicode points, etc. However, there is no such standard type and at least the following variations are seen:
- a 16-bit
int
and a 32-bitlong
- a 32-bit
int
and a 32-bitlong
- a 32-bit
int
and a 64-bitlong
, with the 64-bitlong
being inefficient for the processor - a 64-bit
int
andlong
As can be seen, no built-in C type would be appropriate in all cases, so type detection is needed. Duktape detects and defines duk_int_t
type for these purposes (at least 32 bits wide, convenient to the CPU). Normally it is mapped to int
if Duktape can reliably detect that int
is 32 bits or wider. When this is not the case, int_fast32_t
is used if C99 types are available; if C99 is not available, Duktape uses platform specific detection to arrive at an appropriate type. The duk_uint_t
is the same but unsigned. Most other types in the API (such as duk_idx_t
) are mapped to duk_(u)int_t
but this may change in the future if necessary.
Other special types are also needed. For instance, exactly N bits wide integers are also needed to ensure proper overflow behavior in some cases.
Format specifiers
C/C++ types are often used with printf()
and scanf()
, with each type having a format specifier. The set of format specifiers is only partially standardized (e.g. %d
is used for an int
, regardless of its bit size), but custom codes are sometimes used.
When using type wrappers, the correct format code depends on type detection. For instance, duk_int_t
is mapped to a convenient integer type which is at least 32 bits wide. On one platform the underlying type might be int
(format specifier %d
) and on another it might be long
(format specifier %ld
). Calling code cannot safely use such a value in string formatting without either getting the proper format specified from a preprocessor define or using a fixed format specifier and casting the argument:
duk_int_t val = /* ... */;
/* Cast value to ensure type and format match. Selecting the appropriate
* cast target is problematic, and caller must "play it safe". Without
* relying on C99 types, "long" is usually good for signed integers.
*/
printf("value is: %ld\n", (long) val);
/* When assuming C99 types (which limits portability), the maxint_t is
* guaranteed to represent all signed integers and has a standard format
* specifiers "%jd". For unsigned values, umaxint_t and "%ju".
*/
printf("value is: %jd\n", (maxint_t) val);
C99 provides preprocessor defines for C99 types in inttypes.h
. For instance, the printf()
decimal format specifier for int_fast32_t
is PRIdFAST32
:
int_fast32_t val = /* ... */;
printf("value is: " PRIdFAST32 "\n", val);
Duktape doesn't currently provide wrappers for format specifier defines.
The printf()
and scanf()
format specifiers may be different. One reason is that float
arguments are automatically promoted to double
in printf()
but they are handled as distinct types by scanf()
. See http://stackoverflow.com/questions/210590/why-does-scanf-need-lf-for-doubles-when-printf-is-okay-with-just-f.
The correct format specifier for a double
in printf()
is %f
(float values are automatically promoted to doubles) but %lf
is also accepted. The latter is used in Duktape examples for clarity. See http://stackoverflow.com/questions/4264127/correct-format-specifier-for-double-in-printf.