cwal

DataTypes
Login

DataTypes

See also: MemoryModel, cwal_gc

cwal Data Types

This page describes the various types of Values which cwal exposes to client code. All of these types have basic Value handle representations (via a cwal_value pointer) and many of them also have higher-level representations (e.g. cwal_object or cwal_string). Clients can convert freely between such representations. Most APIs work with basic Value handles, so it is often necessary to convert higher-level handles to the Value form (such conversion is an O(1) operation). How to do such conversions is demonstrated in the examples below.

Container Types and Cycles

Some types (e.g. Objects an Arrays) are generically known as "containers," meaning that they can hold other values. Such types can lead to data structures which contain cycles (circular references). That's okay. The scope-level cleanup mechanism can handle such structures and will clean them up properly, provided the client obeys the rules of the API (as described in the API docs and these wiki pages).

The following concrete types are capable of having key/value properties:

Numeric

cwal differentiates between signed integer and double values. Integer precision depends on the platform and build options (16-64 bits). Double precision is as for the standard double type, but it can be configured to use long doubles as well.

Example usages:

cwal_value * i = cwal_new_integer(e, 42);
cwal_value * d = cwal_new_double(e, 42.24);
assert(cwal_value_is_integer(i));
assert(cwal_value_is_double(d));
cwal_ref(i);
cwal_ref(d);
cwal_int_t xI = cwal_value_get_integer(i);
assert( 42 == xI );
cwal_int_t xD = cwal_value_get_double(d);
assert( 42.24 == xD );
xI = cwal_value_get_integer(d) /* note the double argument! */;
assert( 42 == xI );
xD = cwal_value_get_double(i) /* note the integer argument! */;
assert( 42.0 == xD );


// Cleanup ...
cwal_unref(i);
cwal_unref(d);

Strings

Strings are, as is conventional, immutable byte arrays, and cwal generally assumes UTF8 encoding.

Example usages:

cwal_string * s = cwal_new_string(e, "hi!", 3);
cwal_value * sv = cwal_string_value(s);
assert(cwal_value_get_string(sv)==s);
char const * cstr = cwal_string_cstr(s);
cwal_ref(sv);
// Optional cleanup ...
cwal_string_unref(e, s);
// OR (DO NOT DO BOTH!)...
cwal_unref(sv);

Arrays

Arrays represent sequential lists of arbitrary values, basically analog to C arrays except that they can grow.

Example usages:

cwal_array * a = cwal_new_array(e);
cwal_value * v = cwal_new_integer(e, 42);
cwal_ref(v);
int rc = cwal_array_append(a, v);
cwal_unref(v);
assert(0==rc);
v = cwal_array_value(a);
assert(cwal_value_get_array(v)==a);
cwal_ref(v);

// Optional cleanup:
cwal_array_unref(a);
// OR (DO NOT TO BOTH!)...
cwal_unref(cwal_array_value(a));

Objects

Objects are basically collections of key/value pairs. Keys can be of almost any type. Historically, the main difference between this type has hashtables was lookup speed: O(N) vs amortized O(1). In 2021 the type of underlying property storage was made a build-time option, with the default being hashtable speed at the cost of a bit more memory (generally to within 5% of the slower model's values, thanks to heavy recycling). Neither model supports mutation during iteration, but practice has yet to reveal that as a genuine problem.

Example usages:

cwal_object * o = cwal_new_object(e);
cwal_value * v = cwal_value_new_double(e, 87.23);
cwal_ref(v);
int rc = cwal_object_set(o, "hi", 2, v );
cwal_unref(v);
assert(0==rc);
v = cwal_object_value(o);
assert(cwal_value_get_object(v)==o);
cwal_ref(v);
v = cwal_object_get(o, "hi", 2);
assert(cwal_value_is_double(v));

// Cleanup:
cwal_object_unref(o);
// OR (DO NOT TO BOTH!)...
cwal_unref(cwal_object_value(o));

Functions

Functions are C functions wrapped up in a Value handle. Their C counterparts must implement the cwal_callback_f() interface, and they are called via the cwal_function_call() family of functions. Callbacks receive a single argument which holds all sorts of state about the call, including the arguments list (of course), the current scope (for fetching named parameters), the "callee" handle, a "this object" value, and optionally arbitrary state (a void pointer) which can be assigned by the client when the Function value is created (along with an optional finalizer function to clean it up when the Function Value is finalized).

Note that Functions may contain key/value pairs, just like Objects can.

Buffers

Buffers wrap up the cwal_buffer class (a generic memory buffer) in a Value handle, making them available for script code. This class is intended to be used as a general-purpose byte array. Note that this feature could also be implemented in terms of the "native" support (see below), but i wanted a concrete implementation for some particular scripting ideas i would like to play with.

Natives

"Native" is the generic term for a C/C++-level value which is wrapped up behind a Value handle. cwal "binds" a native handle to a Value using the following information (all provided by the client):

In principal, any type which can be passed around as a void pointer can be wrapped up in a Native Value.

Note that Natives may contain key/value pairs, just like Objects can.

Example usage:

/* The native type we want to wrap... */
typedef struct NativeType NativeType;
struct NativeType {
    int x;
    int y;
};

/* We use NativeType_empty as a _type identifier_. */
static const NativeType NativeType_empty = {0,0};

/* A finalizer function for NativeType values... */
static void finalizer_NativeType( cwal_engine * e, void * V ){
    NativeType * n = (NativeType*)V;
    printf("Freeing NativeType@%p x=%d y=%d\n", V, n->x, n->y);
    cwal_free(e, V);
}
/* Example wrapper which creates a new Native value and runs
   some sanity checks (which clients won't need to do - this code
   is taken from cwal lib-level tests)...
*/
cwal_native * new_native(cwal_engine * e){
    cwal_native * n;
    cwal_value * v;
    void * pCheck = NULL;
    NativeType * foo;
    int rc;
    foo = cwal_malloc(e, sizeof(NativeType));
    foo->x = 3; foo->y = 7;
    n = cwal_new_native( e, foo, finalizer_NativeType, &NativeType_empty );
    assert(n);
    v = cwal_native_value(n);
    assert(v);
    assert(n == cwal_value_get_native(v));
    assert( CWAL_TYPE_NATIVE == cwal_value_type_id(v) );
    assert(0 == cwal_native_get(n, ""));
    rc = cwal_native_fetch(n, &NativeType_empty, &pCheck);
    assert(0==rc);
    assert(pCheck == foo);
    assert(foo == cwal_native_get(n, &NativeType_empty));
    return n;
}

Exception

The Exception class (cwal_exception) is intended to be used as a generic error/exception reporting mechanism. Each exception instance has an associated integer code and optional message value (an arbitrary value), the interpretation of both of which is up to the client. They can also contain key/value properties (e.g. that's where stack trace info could go).

The engine provides internal support for handling a single "propagating" exception value, propagating it down the scope stack as scopes are pushed. This is intended to be used in conjunction with the cwal_exception_set() family of functions and the CWAL_RC_EXCEPTION return value, which is reserved for signaling that an exception was encountered.

Example usages:

cwal_value * msg = cwal_new_string_value(e, "Aarrgghh!", 0);
cwal_ref(msg);
cwal_exception * x = cwal_new_exception(e, 42, msg );
cwal_unref(msg);
int code = cwal_exception_code_get(x);
cwal_value * xV = cwal_exception_value(x);
assert(42 == code);
assert(msg == cwal_exception_message_get(x));
assert(cwal_value_get_exception(xV) == x);

Note that creating a new exception object does not "throw" the exception. i.e. it's just another value which gets no special treatment. To "throw" an exception, pass the exception to cwal_exception_set() or use one of the convenience routines, e.g.:

int rc = cwal_exception_setf( e, MyErrorCode, "Error #%d!", MyErrorCode );
assert(CWAL_RC_EXCEPTION==rc) /* on success! */;
assert(NULL != cwal_exception_get(e));

Unlike most values, the currently "thrown" exception is moved up the scope stack as exceptions are popped. When the last scope is popped, any remaining exception value is freed.

Hash Tables

Hashes are objects capable of storing both "normal" properties (as for Objects) or hashed properties, with amortized O(1) performance for all hash algorithms. They cost notably more memory than Objects, but for large property sets are (on average) faster. Unlike object properties, which use non-strict equivalence comparisons (e.g. the integer 1, double 1.0, and string "1" are equivalent), hashes use type-strict comparisons (so 1, 1.0, and "1" are three distinct keys).

Over the years, improvements in the memory recycling subsystem has cut the amortized memory cost of hashtables to within a negligible distance of Object costs. In 2021 Objects gained a compile-time option to switch their storage to the same one as hashtables use. That change effectively makes hashtables obsolete except for hypothetical uses in highly memory-constrained systems, where the "old-style" Objects might still make sense, with the caveat that that model has much slower properly lookup speed.