libcwal  Update of "DataTypes"

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview

Artifact ID: 864495a3d36f449bda202d337879c7d5f52f9867
Page Name:DataTypes
Date: 2014-07-31 18:27:37
Original User: stephan
Parent: 0aec4263e1775b30993e6871b0f9ab6a3835b758
Content

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:

  • Object
  • Array (in addition to integer-indexed properties)
  • Native
  • Function
  • Exception
  • Hash Tables
Sidebar: Buffers should arguably be properties-aware but are not because the buffer class has existed since long before it was exposed as a Value type, and retrofitting it to support per-instance properties would be rather intrusive on both the library and existing client code which uses buffers in non-Value capacities (that is to say, the majority of use cases).

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_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 );

// Optional cleanup ...
cwal_value_unref(e,i);
cwal_value_unref(e,d);

Strings

Strings are basically opaque byte arrays, and cwal generally does not care about encoding, but will use strlen() in some cases if the user does not provide a length, so it will not work with UTF16 unless clients provide all lengths.

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);

// Optional cleanup ...
cwal_string_unref(e, s);
// OR (DO NOT DO BOTH!)...
cwal_value_unref(e, sv);

Arrays

Arrays represent sequential lists of arbitrary values, basically analog to C arrays except that they can grow. The API does not yet have a full set of manipulators for Arrays - they will be added as needed.

Example usages:

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

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

Objects

Objects are basically collections of key/value pairs. Keys are currently always strings, but there are plans to allow arbitrary Value types as keys (the infrastructure is in place, but other parts need to be refactored to use it).

Note that object properties are stored in a list, and thus have O(N) performance for search/set/unset. This is because it's cheap to implement and provides the reverse-of-creation destruction order we want (well, it does for most cases). There are plans to add a separate HashTable class which functions like Object guarantees fast performance at the cost of (much) more memory.

Example usages:

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

// Optional cleanup:
cwal_object_unref(e, o);
// OR (DO NOT TO BOTH!)...
cwal_value_unref(e, 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.

i do not think that the current cwal code can handle true closures, but closure-like behaviour "should work" (in the sense that such closures should be able to reference variables defined in lower scopes) as long as the Function value does not wander down the scope chain. Functions which are self-contained and do not rely on variables from lower scopes "should" work just fine vis-a-vis scoping/ownership/lifetimes. In any case, full support for closures is not on the list of TODOs (it would be interesting, but is not a required feature).

Example usages:

TODO

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.

Example usages:

TODO

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):

  • A void pointer representing the native value (e.g. a C struct).
  • A const void pointer which acts as a type identifier. This can be used to ensure that the client is dealing with the expected type of pointer down-stream. i.e. it provides a type-safety mechanism for the otherwise completely opaque void pointer.
  • An optional finalizer function which cleans up the native pointer when the Value handle is finalized.

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_exception * x = cwal_new_exception(e, 42, 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.

Design note: there is a case where we can strand multiple exceptions in a scope until that scope is cleaned, but (A) a cwal_scope_sweep() will clean them up and (B) it can only happen when a single scope keeps catching exceptions and re-setting/clearing the exception state. When setting the exception state we (unfortunately) cannot generically unref any previous exception state because we do not (cannot) know if another object has a reference to it, other than its owning scope. We "could"... let me go try that... nope. Sweeping is the current workaround for long-lived, many-catching scopes.

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).

Example usages:

TODO