[gjs] [gjs] Add a byteArray module.
- From: Johan Dahlin <johan src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs] [gjs] Add a byteArray module.
- Date: Wed, 23 Jun 2010 03:09:40 +0000 (UTC)
commit 25dc86f11f6ac1a4bc22f0dc4141f007bc24e86e
Author: Johan Dahlin <johan gnome org>
Date: Mon Jun 21 21:21:51 2010 -0300
[gjs] Add a byteArray module.
docs/gjs-byte-array.txt explains how to use it and its features.
The primary purpose of this for now will be to bind C APIs that use
byte buffers, such as read and write operations. As such, the
ByteArray class is pretty simple and is mostly just a blob you can
shovel between C APIs.
In the initial patch, ByteArray does not even support array operations
such as slice().
The enumeration support relies on defining each byte as a property
on the byte array object, which makes enumerating bytes in the array
even more inefficient than it already would be.
https://bugzilla.gnome.org/show_bug.cgi?id=571000
Makefile-test.am | 1 +
Makefile.am | 3 +
doc/gjs-byte-array.txt | 161 ++++++++
gi/arg.c | 49 +++-
gjs/byteArray.c | 873 ++++++++++++++++++++++++++++++++++++++++++
gjs/byteArray.h | 43 ++
gjs/context.c | 4 +
test/js/testByteArray.js | 119 ++++++
test/js/testGIMarshalling.js | 11 +
util/log.c | 3 +
util/log.h | 1 +
11 files changed, 1265 insertions(+), 3 deletions(-)
---
diff --git a/Makefile-test.am b/Makefile-test.am
index cd3c56e..ff3cc42 100644
--- a/Makefile-test.am
+++ b/Makefile-test.am
@@ -124,6 +124,7 @@ EXTRA_DIST += \
test/js/modules/subA/subB/foobar.js \
test/js/modules/subA/subB/baz.js \
test/js/testself.js \
+ test/js/testByteArray.js \
test/js/testCairo.js \
test/js/testEverythingBasic.js \
test/js/testEverythingEncapsulated.js \
diff --git a/Makefile.am b/Makefile.am
index 11fa15e..e2016ac 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -19,12 +19,14 @@ gjsincludedir = $(includedir)/gjs-1.0
########################################################################
nobase_gjsinclude_HEADERS = \
+ gjs/byteArray.h \
gjs/context.h \
gjs/importer.h \
gjs/gjs.h \
gjs/jsapi-util.h \
gjs/mem.h \
gjs/native.h
+
noinst_HEADERS += \
gjs/compat.h \
gjs/context-jsapi.h \
@@ -66,6 +68,7 @@ libgjs_la_LIBADD = \
$(GJS_LIBS)
libgjs_la_SOURCES = \
+ gjs/byteArray.c \
gjs/context.c \
gjs/importer.c \
gjs/jsapi-private.cpp \
diff --git a/doc/gjs-byte-array.txt b/doc/gjs-byte-array.txt
new file mode 100644
index 0000000..b6224a7
--- /dev/null
+++ b/doc/gjs-byte-array.txt
@@ -0,0 +1,161 @@
+The ByteArray type in the imports.byteArray module is based on an
+ECMAScript 4 proposal that was never adopted. This can be found at:
+http://wiki.ecmascript.org/doku.php?id=proposals:bytearray
+and the wikitext of that page is appended to this file.
+
+The main difference from the ECMA proposal is that gjs's ByteArray is
+inside a module, and toString()/fromString() default to UTF-8 and take
+optional encoding arguments.
+
+There are a number of more elaborate byte array proposals in the
+Common JS project at http://wiki.commonjs.org/wiki/Binary
+
+We went with the ECMA proposal because it seems simplest, and the main
+goal for most gjs users will be to shovel bytes between various C
+APIs, for example reading from an IO stream and then pushing the bytes
+into a parser. Actually manipulating bytes in JS is likely to be
+pretty rare, and slow ... an advantage of the
+gjs/gobject-introspection setup is that stuff best done in C, like
+messing with bytes, can be done in C.
+
+
+========================
+ECMAScript proposal follows; remember it's almost but not quite like
+gjs ByteArray, in particular we use UTF-8 instead of busted Latin-1 as
+default encoding.
+========================
+
+
+====== ByteArray ======
+
+(Also see the [[discussion:bytearray|discussion page]] for this proposal)
+
+===== Overview ======
+In previous versions of ECMAScript, there wasn't a good way to efficiently represent a packed array of arbitrary bytes. The predefined core object Array is inefficient for this purpose; some developers have (mis)used character strings for this purpose, which may be slightly more efficient for some implementations, but still a misuse of the string type and either a less efficient use of memory (if one byte per character was stored) or cycles (if two bytes per char).
+
+Edition 4 will add a new predefined core object, ByteArray. A ByteArray can be thought of as similar to an Array of uint ([uint]) with each element truncated to the integer range of 0..255.
+
+
+===== Creating a ByteArray =====
+
+To create a ByteArray object:
+
+
+<code>
+byteArrayObject = new ByteArray(byteArrayLength:uint)
+</code>
+
+byteArrayLength is the initial length of the ByteArray, in bytes. If omitted, the initial length is zero.
+
+All elements in a ByteArray are initialized to the value of zero.
+
+Unlike Array, there is no special form that allows you to list the initial values for the ByteArray's elements. However, the ByteArray class has an ''intrinsic::to'' static method that can convert an Array to a ByteArray, and implementations are free to optimize away the Array instance if it is used exclusively to initialize a ByteArray:
+
+<code>
+var values:ByteArray = [1, 2, 3, 4]; // legal by virtue of ByteArray.intrinsic::to
+</code>
+
+===== Populating a ByteArray =====
+
+You can populate a ByteArray by assigning values to its elements. Each element can hold only an unsigned integer in the range 0..255 (inclusive). Values will be converted to unsigned integer (if necessary), then truncated to the least-significant 8 bits.
+
+For example,
+
+<code>
+var e = new ByteArray(3);
+
+e[0] = 0x12; // stores 18
+e[1] = Math.PI; // stores 3
+e[2] = "foo"; // stores 0 (generates compile error in strict mode)
+e[2] = "42"; // stores 42 (generates compile error in strict mode)
+e[2] = null; // stores 0
+e[2] = undefined; // stores 0
+</code>
+
+
+===== Referring to ByteArray Elements =====
+
+You refer to a ByteArray's elements by using the element's ordinal number; you refer to the first element of the array as myArray[0] and the second element of the array as myArray[1], etc. The index of the elements begins with zero (0), but the length of array (for example, myArray.length) reflects the number of elements in the array.
+
+===== ByteArray Methods =====
+
+
+The ByteArray object has the follow methods:
+
+1. static function fromString(s:String):ByteArray
+
+Convert a String into newly constructed ByteArray; this creates a new ByteArray of the same length as the String, then assigns each ByteArray entry the corresponding charCodeAt() value of the String. As with other ByteArray assignments, only the lower 8 bits of the charCode value will be retained.
+
+<code>
+class ByteArray {
+ ...
+ static function fromString(s:String):ByteArray
+ {
+ var a:ByteArray = new Bytearray(s.length);
+ for (var i:int = 0; i < s.length; ++i)
+ a[i] = s.charCodeAt(i);
+ return a;
+ }
+ ...
+}
+</code>
+
+2. static function fromArray(s:Array):ByteArray
+
+Converts an Array into a newly constructed ByteArray; this creates a new ByteArray of the same length as the input Array, then assigns each ByteArray entry the corresponding entry value of the Array. As with other ByteArray assignments, only the lower 8 bits of the charCode value will be retained.
+
+<code>
+class ByteArray {
+ ...
+ static function fromArray(s:Array):ByteArray
+ {
+ var a:ByteArray = new Bytearray(s.length);
+ for (var i:int = 0; i < s.length; ++i)
+ a[i] = s[i];
+ return a;
+ ...
+}
+</code>
+
+3. function toString():String
+
+Converts the ByteArray into a literal string, with each character entry set to the value of the corresponding ByteArray entry.
+
+The resulting string is guaranteed to round-trip back into an identical ByteArray by passing the result to ByteArray.fromString(), i.e., b === ByteArray.fromString(b.toString()). (Note that the reverse is not guaranteed to be true: ByteArray.fromString(s).toString != s unless all character codes in s are <= 255)
+
+<code>
+class ByteArray {
+ ...
+ function toString():String
+ {
+ // XXX return String.fromCharCode.apply(String, this);
+ var s:String = "";
+ for (var i:int = 0; i < this.length; ++i)
+ s += String.fromCharCode(this[i]);
+ return s;
+ }
+ ...
+}
+</code>
+
+===== ByteArray Properties =====
+
+The ByteArray object has the following properties:
+
+1. length:uint
+
+This property contains the number of bytes in the ByteArray. Setting the length property to a smaller value decreases the size of the ByteArray, discarding the unused bytes. Setting the length property to a larger value increases the size of the ByteArray, initializing all newly-allocated elements to zero.
+
+2. prototype:Object
+
+This property contains the methods of ByteArray.
+
+
+
+===== Prior Art =====
+
+Adobe's ActionScript 3.0 provides [[http://livedocs.macromedia.com/flex/2/langref/flash/utils/ByteArray.html|flash.utils.ByteArray]], which was the primary inspiration for this proposal. Note that the ActionScript version of ByteArray includes additional functionality which has been omitted here for the sake of allowing small implementations; it is anticipated that the extra functionality can be written in ES4 itself and provided as a standard library.
+
+[Synopsis of ActionScript's implementation too detailed and moved to [[discussion:bytearray|discussion]] page]
+
+
diff --git a/gi/arg.c b/gi/arg.c
index e727819..130f286 100644
--- a/gi/arg.c
+++ b/gi/arg.c
@@ -29,6 +29,7 @@
#include "boxed.h"
#include "union.h"
#include "value.h"
+#include "gjs/byteArray.h"
#include <gjs/gjs.h>
#include <util/log.h>
@@ -645,6 +646,21 @@ gjs_array_to_g_array(JSContext *context,
return JS_TRUE;
}
+static JSBool
+gjs_array_to_byte_array(JSContext *context,
+ jsval value,
+ void **arr_p)
+{
+ GByteArray *byte_array;
+ byte_array = gjs_byte_array_get_byte_array(context,
+ JSVAL_TO_OBJECT(value));
+ if (!byte_array)
+ return JS_FALSE;
+
+ *arr_p = byte_array;
+ return JS_TRUE;
+}
+
static gchar *
get_argument_display_name(const char *arg_name,
GjsArgumentType arg_type)
@@ -1161,7 +1177,12 @@ gjs_value_to_g_argument(JSContext *context,
param_info,
&arg->v_pointer))
wrong = TRUE;
- /* FIXME: support ByteArray and PtrArray */
+ } else if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY) {
+ if (!gjs_array_to_byte_array(context,
+ value,
+ &arg->v_pointer))
+ wrong = TRUE;
+ /* FIXME: support PtrArray */
} else {
g_assert_not_reached();
}
@@ -1169,8 +1190,16 @@ gjs_value_to_g_argument(JSContext *context,
g_base_info_unref((GIBaseInfo*) param_info);
}
} else {
- wrong = TRUE;
- report_type_mismatch = TRUE;
+ GIArrayType array_type = g_type_info_get_array_type(type_info);
+ if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY) {
+ if (!gjs_array_to_byte_array(context,
+ value,
+ &arg->v_pointer))
+ wrong = TRUE;
+ } else {
+ wrong = TRUE;
+ report_type_mismatch = TRUE;
+ }
}
break;
@@ -1836,6 +1865,14 @@ gjs_value_from_g_argument (JSContext *context,
gjs_throw(context, "FIXME: Only supporting zero-terminated ARRAYs");
return JS_FALSE;
}
+ } else if (g_type_info_get_array_type(type_info) == GI_ARRAY_TYPE_BYTE_ARRAY) {
+ JSObject *array = gjs_byte_array_from_byte_array(context,
+ (GByteArray*)arg->v_pointer);
+ if (!array) {
+ gjs_throw(context, "Couldn't convert GByteArray to a ByteArray");
+ return JS_FALSE;
+ }
+ *value_p = OBJECT_TO_JSVAL(array);
} else {
/* this assumes the array type is one of GArray, GPtrArray or
* GByteArray */
@@ -2188,6 +2225,12 @@ gjs_g_arg_release_internal(JSContext *context,
}
g_base_info_unref((GIBaseInfo*) param_info);
+ } else if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY) {
+ if (transfer == GI_TRANSFER_CONTAINER) {
+ g_byte_array_free ((GByteArray*)arg->v_pointer, FALSE);
+ } else if (transfer == GI_TRANSFER_EVERYTHING) {
+ g_byte_array_free ((GByteArray*)arg->v_pointer, TRUE);
+ }
} else {
g_assert_not_reached();
}
diff --git a/gjs/byteArray.c b/gjs/byteArray.c
new file mode 100644
index 0000000..597e457
--- /dev/null
+++ b/gjs/byteArray.c
@@ -0,0 +1,873 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2010 litl, LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <glib.h>
+#include "byteArray.h"
+#include <gjs/gjs.h>
+#include <util/log.h>
+#include <jsapi.h>
+
+typedef struct {
+ GByteArray *array;
+} ByteArrayInstance;
+
+static struct JSClass gjs_byte_array_class;
+static struct JSObject* gjs_byte_array_prototype;
+GJS_DEFINE_PRIV_FROM_JS(ByteArrayInstance, gjs_byte_array_class)
+
+static JSBool byte_array_get_prop (JSContext *context,
+ JSObject *obj,
+ jsval id,
+ jsval *value_p);
+static JSBool byte_array_set_prop (JSContext *context,
+ JSObject *obj,
+ jsval id,
+ jsval *value_p);
+static JSBool byte_array_new_resolve (JSContext *context,
+ JSObject *obj,
+ jsval id,
+ uintN flags,
+ JSObject **objp);
+static JSBool byte_array_constructor (JSContext *context,
+ JSObject *obj,
+ uintN argc,
+ jsval *argv,
+ jsval *retval);
+static void byte_array_finalize (JSContext *context,
+ JSObject *obj);
+
+
+/* The bizarre thing about this vtable is that it applies to both
+ * instances of the object, and to the prototype that instances of the
+ * class have.
+ *
+ * Also, there's a constructor field in here, but as far as I can
+ * tell, it would only be used if no constructor were provided to
+ * JS_InitClass. The constructor from JS_InitClass is not applied to
+ * the prototype unless JSCLASS_CONSTRUCT_PROTOTYPE is in flags.
+ */
+static struct JSClass gjs_byte_array_class = {
+ "ByteArray",
+ JSCLASS_HAS_PRIVATE |
+ JSCLASS_CONSTRUCT_PROTOTYPE |
+ JSCLASS_NEW_RESOLVE |
+ JSCLASS_NEW_RESOLVE_GETS_START,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ byte_array_get_prop,
+ byte_array_set_prop,
+ NULL,
+ (JSResolveOp) byte_array_new_resolve, /* cast due to new sig */
+ JS_ConvertStub,
+ byte_array_finalize,
+ NULL,
+ NULL,
+ NULL,
+ NULL, NULL, NULL, NULL, NULL
+};
+
+
+static JSBool
+gjs_value_from_gsize(JSContext *context,
+ gsize v,
+ jsval *value_p)
+{
+ if (v > (gsize) JSVAL_INT_MAX) {
+ *value_p = INT_TO_JSVAL(v);
+ return JS_TRUE;
+ } else {
+ return JS_NewDoubleValue(context, v, value_p);
+ }
+}
+
+static JSBool
+gjs_value_to_gsize(JSContext *context,
+ jsval value,
+ gsize *v_p)
+{
+ /* Just JS_ValueToECMAUint32() would work. However,
+ * we special case ints for two reasons:
+ * - JS_ValueToECMAUint32() always goes via a double which is slow
+ * - nicer error message on negative indices
+ */
+ if (JSVAL_IS_INT(value)) {
+ int i = JSVAL_TO_INT(value);
+ if (i < 0) {
+ gjs_throw(context, "Negative length or index %d is not allowed for ByteArray",
+ i);
+ return JS_FALSE;
+ }
+ *v_p = i;
+ return JS_TRUE;
+ } else {
+ /* This is pretty liberal (it converts about anything to
+ * a number) but it's what we use elsewhere in gjs too.
+ */
+ return JS_ValueToECMAUint32(context, value,
+ v_p);
+ }
+}
+
+static JSBool
+gjs_value_to_byte(JSContext *context,
+ jsval value,
+ guint8 *v_p)
+{
+ gsize v;
+
+ if (!gjs_value_to_gsize(context, value, &v))
+ return JS_FALSE;
+
+ if (v >= 256) {
+ gjs_throw(context,
+ "Value %u is not a valid byte; must be in range [0,255]",
+ v);
+ return JS_FALSE;
+ }
+
+ *v_p = v;
+ return JS_TRUE;
+}
+
+static JSBool
+byte_array_get_index(JSContext *context,
+ JSObject *obj,
+ ByteArrayInstance *priv,
+ gsize idx,
+ jsval *value_p)
+{
+ guint8 v;
+
+ if (idx >= priv->array->len) {
+ gjs_throw(context,
+ "Index %u is out of range for ByteArray length %u",
+ idx,
+ priv->array->len);
+ return JS_FALSE;
+ }
+
+ v = g_array_index(priv->array, guint8, idx);
+ *value_p = INT_TO_JSVAL(v);
+
+ return JS_TRUE;
+}
+
+/* a hook on getting a property; set value_p to override property's value.
+ * Return value is JS_FALSE on OOM/exception.
+ */
+static JSBool
+byte_array_get_prop(JSContext *context,
+ JSObject *obj,
+ jsval id,
+ jsval *value_p)
+{
+ ByteArrayInstance *priv;
+
+ priv = priv_from_js(context, obj);
+
+ if (priv == NULL)
+ return JS_FALSE; /* wrong class passed in */
+ if (priv->array == NULL)
+ return JS_TRUE; /* prototype, not an instance. */
+
+ /* First handle array indexing */
+ if (JSVAL_IS_NUMBER(id)) {
+ unsigned int idx;
+ if (!gjs_value_to_gsize(context, id, &idx))
+ return JS_FALSE;
+ return byte_array_get_index(context, obj, priv, idx, value_p);
+ }
+
+ /* We don't special-case anything else for now. Regular JS arrays
+ * allow string versions of ints for the index, we don't bother.
+ */
+
+ return JS_TRUE;
+}
+
+static JSBool
+byte_array_length_getter(JSContext *context,
+ JSObject *obj,
+ jsval id,
+ jsval *value_p)
+{
+ ByteArrayInstance *priv;
+
+ priv = priv_from_js(context, obj);
+
+ if (priv == NULL)
+ return JS_FALSE; /* wrong class passed in */
+ if (priv->array == NULL)
+ return JS_TRUE; /* prototype, not an instance. */
+
+ return gjs_value_from_gsize(context, priv->array->len,
+ value_p);
+}
+
+static JSBool
+byte_array_length_setter(JSContext *context,
+ JSObject *obj,
+ jsval id,
+ jsval *value_p)
+{
+ ByteArrayInstance *priv;
+ guint32 len = 0;
+
+ priv = priv_from_js(context, obj);
+
+ if (priv == NULL)
+ return JS_FALSE; /* wrong class passed in */
+ if (priv->array == NULL)
+ return JS_TRUE; /* prototype, not an instance. */
+
+ if (!gjs_value_to_gsize(context, *value_p,
+ &len)) {
+ gjs_throw(context,
+ "Can't set ByteArray length to non-integer");
+ return JS_FALSE;
+ }
+ g_byte_array_set_size(priv->array, len);
+ return JS_TRUE;
+}
+
+static JSBool
+byte_array_set_index(JSContext *context,
+ JSObject *obj,
+ ByteArrayInstance *priv,
+ gsize idx,
+ jsval *value_p)
+{
+ guint8 v;
+
+ if (!gjs_value_to_byte(context, *value_p,
+ &v)) {
+ return JS_FALSE;
+ }
+
+ /* grow the array if necessary */
+ if (idx >= priv->array->len) {
+ g_byte_array_set_size(priv->array,
+ idx + 1);
+ }
+
+ g_array_index(priv->array, guint8, idx) = v;
+
+ /* we could have coerced a double or something, be sure
+ * *value_p is set to our actual set value
+ */
+ *value_p = INT_TO_JSVAL(v);
+
+ return JS_TRUE;
+}
+
+/* a hook on setting a property; set value_p to override property value to
+ * be set. Return value is JS_FALSE on OOM/exception.
+ */
+static JSBool
+byte_array_set_prop(JSContext *context,
+ JSObject *obj,
+ jsval id,
+ jsval *value_p)
+{
+ ByteArrayInstance *priv;
+
+ priv = priv_from_js(context, obj);
+
+ if (priv == NULL)
+ return JS_FALSE; /* wrong class passed in */
+ if (priv->array == NULL)
+ return JS_TRUE; /* prototype, not an instance. */
+
+ /* First handle array indexing */
+ if (JSVAL_IS_NUMBER(id)) {
+ gsize idx;
+ if (!gjs_value_to_gsize(context, id, &idx))
+ return JS_FALSE;
+
+ return byte_array_set_index(context, obj, priv, idx, value_p);
+ }
+
+ /* We don't special-case anything else for now */
+
+ /* FIXME: note that the prop will also have been set in JS in the
+ * usual hash table... this is pretty wasteful and bloated. But I
+ * don't know how to turn it off. The set property function
+ * is only a hook, not a replacement.
+ */
+ return JS_TRUE;
+}
+
+/*
+ * Like JSResolveOp, but flags provide contextual information as follows:
+ *
+ * JSRESOLVE_QUALIFIED a qualified property id: obj.id or obj[id], not id
+ * JSRESOLVE_ASSIGNING obj[id] is on the left-hand side of an assignment
+ * JSRESOLVE_DETECTING 'if (o.p)...' or similar detection opcode sequence
+ * JSRESOLVE_DECLARING var, const, or function prolog declaration opcode
+ * JSRESOLVE_CLASSNAME class name used when constructing
+ *
+ * The *objp out parameter, on success, should be null to indicate that id
+ * was not resolved; and non-null, referring to obj or one of its prototypes,
+ * if id was resolved.
+ *
+ * *objp will be the original object the property access was on, rather than the
+ * prototype that "obj" may be, due to JSCLASS_NEW_RESOLVE_GETS_START
+ */
+static JSBool
+byte_array_new_resolve(JSContext *context,
+ JSObject *obj,
+ jsval id,
+ uintN flags,
+ JSObject **objp)
+{
+ ByteArrayInstance *priv;
+
+ priv = priv_from_js(context, *objp);
+
+ if (priv == NULL)
+ return JS_FALSE; /* wrong class passed in */
+ if (priv->array == NULL)
+ return JS_TRUE; /* prototype, not an instance. */
+
+ if (JSVAL_IS_NUMBER(id)) {
+ unsigned int idx;
+ if (!gjs_value_to_gsize(context, id, &idx))
+ return JS_FALSE;
+ if (idx >= priv->array->len) {
+ *objp = NULL;
+ } else {
+ /* leave objp set */
+ g_assert(*objp != NULL);
+
+ /* define the property - AAARGH. Best I can tell from
+ * reading the source, this is unavoidable...
+ * which means using "for each" or "for ... in" on byte
+ * arrays is a horrible, horrible idea. FIXME - but how?
+ *
+ * The issue is that spidermonkey only calls resolve,
+ * not get, as it iterates. So you can lazy-define
+ * a property but must define it.
+ */
+ if (!JS_DefinePropertyById(context,
+ *objp,
+ id,
+ JSVAL_VOID,
+ byte_array_get_prop,
+ byte_array_set_prop,
+ JSPROP_ENUMERATE))
+ return JS_FALSE;
+ }
+ }
+
+ return JS_TRUE;
+}
+
+static GByteArray *
+gjs_g_byte_array_new(int preallocated_length)
+{
+ GByteArray *array;
+
+ /* can't use g_byte_array_new() because we need to clear to zero.
+ * We nul-terminate too for ease of toString() and for security
+ * paranoia.
+ */
+ array = (GByteArray*) g_array_sized_new (TRUE, /* nul-terminated */
+ TRUE, /* clear to zero */
+ 1, /* element size */
+ preallocated_length);
+ if (preallocated_length > 0) {
+ /* we want to not only allocate the size, but have it
+ * already be the array's length.
+ */
+ g_byte_array_set_size(array, preallocated_length);
+ }
+
+ return array;
+}
+
+/* If we set JSCLASS_CONSTRUCT_PROTOTYPE flag, then this is called on
+ * the prototype in addition to on each instance. When called on the
+ * prototype, "obj" is the prototype, and "retval" is the prototype
+ * also, but can be replaced with another object to use instead as the
+ * prototype.
+ */
+static JSBool
+byte_array_constructor(JSContext *context,
+ JSObject *obj,
+ uintN argc,
+ jsval *argv,
+ jsval *retval)
+{
+ ByteArrayInstance *priv;
+ JSObject *proto;
+ gboolean is_proto;
+ JSClass *obj_class;
+ JSClass *proto_class;
+ guint32 preallocated_length;
+
+ if (!gjs_check_constructing(context))
+ return JS_FALSE;
+
+ preallocated_length = 0;
+ if (argc >= 1) {
+ if (!gjs_value_to_gsize(context, argv[0], &preallocated_length)) {
+ gjs_throw(context,
+ "Argument to ByteArray constructor should be a positive number for array length");
+ return JS_FALSE;
+ }
+ }
+
+ priv = g_slice_new0(ByteArrayInstance);
+
+ g_assert(priv_from_js(context, obj) == NULL);
+
+ JS_SetPrivate(context, obj, priv);
+
+ proto = JS_GetPrototype(context, obj);
+
+ /* If we're constructing the prototype, its __proto__ is not the same
+ * class as us, but if we're constructing an instance, the prototype
+ * has the same class.
+ */
+ obj_class = JS_GetClass(context, obj);
+ proto_class = JS_GetClass(context, proto);
+
+ is_proto = (obj_class != proto_class);
+
+ if (!is_proto) {
+ priv->array = gjs_g_byte_array_new(preallocated_length);
+ }
+
+ return JS_TRUE;
+}
+
+static void
+byte_array_finalize(JSContext *context,
+ JSObject *obj)
+{
+ ByteArrayInstance *priv;
+
+ priv = priv_from_js(context, obj);
+
+ if (priv == NULL)
+ return; /* possible? probably not */
+
+ if (priv->array) {
+ g_byte_array_free(priv->array, TRUE);
+ priv->array = NULL;
+ }
+
+ g_slice_free(ByteArrayInstance, priv);
+}
+
+/* implement toString() with an optional encoding arg */
+static JSBool
+to_string_func(JSContext *context,
+ JSObject *obj,
+ uintN argc,
+ jsval *argv,
+ jsval *retval)
+{
+ ByteArrayInstance *priv;
+ const char *encoding;
+ gboolean encoding_is_utf8;
+
+ *retval = JSVAL_VOID;
+
+ priv = priv_from_js(context, obj);
+
+ if (priv == NULL)
+ return JS_FALSE; /* wrong class passed in */
+
+ encoding_is_utf8 = TRUE;
+ if (argc >= 1 &&
+ JSVAL_IS_STRING(argv[0])) {
+ encoding = gjs_string_get_ascii_checked(context, argv[0]);
+ if (encoding == NULL)
+ return JS_FALSE;
+
+ /* maybe we should be smarter about utf8 synonyms here.
+ * doesn't matter much though. encoding_is_utf8 is
+ * just an optimization anyway.
+ */
+ if (strcmp(encoding, "UTF-8") == 0) {
+ encoding_is_utf8 = TRUE;
+ } else {
+ encoding_is_utf8 = FALSE;
+ }
+ } else {
+ encoding = "UTF-8";
+ }
+
+ if (encoding_is_utf8) {
+ /* optimization, avoids iconv overhead and runs
+ * glib's hardwired utf8-to-utf16
+ */
+ return gjs_string_from_utf8(context,
+ (char*) priv->array->data,
+ priv->array->len,
+ retval);
+ } else {
+ JSBool ok;
+ gsize bytes_written;
+ GError *error;
+ JSString *s;
+ char *u16_str;
+
+ error = NULL;
+ u16_str = g_convert((char*) priv->array->data,
+ priv->array->len,
+ "UTF-16",
+ encoding,
+ NULL, /* bytes read */
+ &bytes_written,
+ &error);
+ if (u16_str == NULL) {
+ /* frees the GError */
+ gjs_throw_g_error(context, error);
+ return JS_FALSE;
+ }
+
+ /* bytes_written should be bytes in a UTF-16 string so
+ * should be a multiple of 2
+ */
+ g_assert((bytes_written % 2) == 0);
+
+ s = JS_NewUCStringCopyN(context,
+ (jschar*) u16_str,
+ bytes_written / 2);
+ if (s == NULL) {
+ ok = FALSE;
+ *retval = JSVAL_VOID;
+ } else {
+ ok = TRUE;
+ *retval = STRING_TO_JSVAL(s);
+ }
+
+ g_free(u16_str);
+ return ok;
+ }
+}
+
+static JSObject*
+byte_array_new(JSContext *context,
+ JSObject *module)
+{
+ JSObject *array;
+ ByteArrayInstance *priv;
+
+ array = JS_NewObject(context, &gjs_byte_array_class, gjs_byte_array_prototype, NULL);
+
+ priv = g_slice_new0(ByteArrayInstance);
+ priv->array = gjs_g_byte_array_new(0);
+
+ g_assert(priv_from_js(context, array) == NULL);
+ JS_SetPrivate(context, array, priv);
+
+ return array;
+}
+
+/* fromString() function implementation */
+static JSBool
+from_string_func(JSContext *context,
+ JSObject *module,
+ uintN argc,
+ jsval *argv,
+ jsval *retval)
+{
+ ByteArrayInstance *priv;
+ const char *encoding;
+ gboolean encoding_is_utf8;
+ JSObject *obj;
+
+ *retval = JSVAL_VOID;
+
+ obj = byte_array_new(context, module);
+ if (obj == NULL)
+ return JS_FALSE;
+ *retval = OBJECT_TO_JSVAL(obj); /* side effect: roots obj */
+
+ priv = priv_from_js(context, obj);
+
+ if (priv == NULL)
+ return JS_FALSE; /* wrong class passed in */
+
+ g_assert(argc > 0); /* because we specified min args 1 */
+
+ if (!JSVAL_IS_STRING(argv[0])) {
+ gjs_throw(context,
+ "byteArray.fromString() called with non-string as first arg");
+ return JS_FALSE;
+ }
+
+ encoding_is_utf8 = TRUE;
+ if (argc > 1 &&
+ JSVAL_IS_STRING(argv[1])) {
+ encoding = gjs_string_get_ascii_checked(context, argv[1]);
+ if (encoding == NULL)
+ return JS_FALSE;
+
+ /* maybe we should be smarter about utf8 synonyms here.
+ * doesn't matter much though. encoding_is_utf8 is
+ * just an optimization anyway.
+ */
+ if (strcmp(encoding, "UTF-8") == 0) {
+ encoding_is_utf8 = TRUE;
+ } else {
+ encoding_is_utf8 = FALSE;
+ }
+ } else {
+ encoding = "UTF-8";
+ }
+
+ if (encoding_is_utf8) {
+ /* optimization? avoids iconv overhead and runs
+ * glib's hardwired utf16-to-utf8.
+ * Does a gratuitous copy/strlen, but
+ * the generic path below also has
+ * gratuitous copy. Could be fixed for this path,
+ * if it ever turns out to matter.
+ */
+ char *utf8 = NULL;
+ if (!gjs_string_to_utf8(context,
+ argv[0],
+ &utf8))
+ return JS_FALSE;
+
+ g_byte_array_set_size(priv->array, 0);
+ g_byte_array_append(priv->array, (guint8*) utf8, strlen(utf8));
+ g_free(utf8);
+ } else {
+ char *encoded;
+ gsize bytes_written;
+ GError *error;
+ jschar *u16_chars;
+ gsize u16_len;
+
+ u16_chars = JS_GetStringChars(JSVAL_TO_STRING(argv[0]));
+ u16_len = JS_GetStringLength(JSVAL_TO_STRING(argv[0]));
+
+ error = NULL;
+ encoded = g_convert((char*) u16_chars,
+ u16_len * 2,
+ encoding, /* to_encoding */
+ "UTF-16", /* from_encoding */
+ NULL, /* bytes read */
+ &bytes_written,
+ &error);
+ if (encoded == NULL) {
+ /* frees the GError */
+ gjs_throw_g_error(context, error);
+ return JS_FALSE;
+ }
+
+ g_byte_array_set_size(priv->array, 0);
+ g_byte_array_append(priv->array, (guint8*) encoded, bytes_written);
+
+ g_free(encoded);
+ }
+
+ return JS_TRUE;
+}
+
+/* fromArray() function implementation */
+static JSBool
+from_array_func(JSContext *context,
+ JSObject *module,
+ uintN argc,
+ jsval *argv,
+ jsval *retval)
+{
+ ByteArrayInstance *priv;
+ jsuint len;
+ jsuint i;
+ JSObject *obj;
+
+ *retval = JSVAL_VOID;
+
+ obj = byte_array_new(context, module);
+ if (obj == NULL)
+ return JS_FALSE;
+
+ *retval = OBJECT_TO_JSVAL(obj); /* side effect: roots obj */
+
+ priv = priv_from_js(context, obj);
+
+ if (priv == NULL)
+ return JS_FALSE; /* wrong class passed in */
+
+ g_assert(argc > 0); /* because we specified min args 1 */
+
+ if (!JS_IsArrayObject(context, JSVAL_TO_OBJECT(argv[0]))) {
+ gjs_throw(context,
+ "byteArray.fromArray() called with non-array as first arg");
+ return JS_FALSE;
+ }
+
+ if (!JS_GetArrayLength(context, JSVAL_TO_OBJECT(argv[0]), &len)) {
+ gjs_throw(context,
+ "byteArray.fromArray() can't get length of first array arg");
+ return JS_FALSE;
+ }
+
+ g_byte_array_set_size(priv->array, len);
+
+ for (i = 0; i < len; ++i) {
+ jsval elem;
+ guint8 b;
+
+ elem = JSVAL_VOID;
+ if (!JS_GetElement(context, JSVAL_TO_OBJECT(argv[0]), i, &elem)) {
+ /* this means there was an exception, while elem == JSVAL_VOID
+ * means no element found
+ */
+ return JS_FALSE;
+ }
+
+ if (elem == JSVAL_VOID)
+ continue;
+
+ if (!gjs_value_to_byte(context, elem, &b))
+ return JS_FALSE;
+
+ g_array_index(priv->array, guint8, i) = b;
+ }
+
+ return JS_TRUE;
+}
+
+JSObject *
+gjs_byte_array_from_byte_array (JSContext *context,
+ GByteArray *array)
+{
+ JSObject *object;
+ ByteArrayInstance *priv;
+ static gboolean init = FALSE;
+
+ g_return_val_if_fail(context != NULL, NULL);
+ g_return_val_if_fail(array != NULL, NULL);
+
+ if (!init) {
+ jsval rval;
+ JS_EvaluateScript(context, JS_GetGlobalObject(context),
+ "imports.byteArray.ByteArray;", 27,
+ "<internal>", 1, &rval);
+ init = TRUE;
+ }
+ object = JS_NewObject(context, &gjs_byte_array_class,
+ gjs_byte_array_prototype, NULL);
+ if (!object) {
+ gjs_throw(context, "failed to create byte array");
+ return NULL;
+ }
+
+ priv = g_slice_new0(ByteArrayInstance);
+ g_assert(priv_from_js(context, object) == NULL);
+ JS_SetPrivate(context, object, priv);
+ priv->array = g_new(GByteArray, 1);
+ priv->array->data = g_memdup(array->data, array->len);
+ priv->array->len = array->len;
+
+ return object;
+}
+
+GByteArray*
+gjs_byte_array_get_byte_array (JSContext *context,
+ JSObject *object)
+{
+ ByteArrayInstance *priv;
+
+ priv = priv_from_js(context, object);
+ if (priv == NULL)
+ return NULL; /* wrong class passed in */
+
+ return priv->array;
+}
+
+/* no idea what this is used for. examples in
+ * spidermonkey use -1, -2, -3, etc. for tinyids.
+ */
+enum ByteArrayTinyId {
+ BYTE_ARRAY_TINY_ID_LENGTH = -1
+};
+
+static JSPropertySpec gjs_byte_array_proto_props[] = {
+ { "length", BYTE_ARRAY_TINY_ID_LENGTH,
+ JSPROP_PERMANENT | JSPROP_SHARED,
+ byte_array_length_getter,
+ byte_array_length_setter
+ },
+ { NULL }
+};
+
+static JSFunctionSpec gjs_byte_array_proto_funcs[] = {
+ { "toString", to_string_func, 0, 0 },
+ { NULL }
+};
+
+static JSFunctionSpec gjs_byte_array_module_funcs[] = {
+ { "fromString", from_string_func, 1, 0 },
+ { "fromArray", from_array_func, 1, 0 },
+ { NULL }
+};
+
+JSBool
+gjs_define_byte_array_stuff(JSContext *context,
+ JSObject *in_object)
+{
+ JSContext *load_context = gjs_runtime_get_load_context(JS_GetRuntime(context));
+ JSObject *global = JS_GetGlobalObject(context);
+ gjs_byte_array_prototype = JS_InitClass(load_context, global,
+ NULL,
+ &gjs_byte_array_class,
+ byte_array_constructor,
+ 0,
+ &gjs_byte_array_proto_props[0],
+ &gjs_byte_array_proto_funcs[0],
+ NULL,
+ NULL);
+ jsval rval;
+
+ if (gjs_byte_array_prototype == NULL) {
+ gjs_move_exception(load_context, context);
+ return JS_FALSE;
+ }
+
+ if (!gjs_object_require_property(
+ load_context, global, NULL,
+ "ByteArray", &rval)) {
+ gjs_move_exception(load_context, context);
+ return JS_FALSE;
+ }
+
+ if (!JS_DefineProperty(context, in_object, "ByteArray",
+ rval, NULL, NULL, GJS_MODULE_PROP_FLAGS))
+ return JS_FALSE;
+
+ if (!JS_DefineFunctions(context, in_object, &gjs_byte_array_module_funcs[0]))
+ return JS_FALSE;
+
+ return JS_TRUE;
+}
diff --git a/gjs/byteArray.h b/gjs/byteArray.h
new file mode 100644
index 0000000..1ea0c2e
--- /dev/null
+++ b/gjs/byteArray.h
@@ -0,0 +1,43 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2010 litl, LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef __GJS_BYTE_ARRAY_H__
+#define __GJS_BYTE_ARRAY_H__
+
+#include <glib.h>
+
+#include <jsapi.h>
+
+G_BEGIN_DECLS
+
+JSBool gjs_define_byte_array_stuff (JSContext *context,
+ JSObject *in_object);
+
+JSObject * gjs_byte_array_from_byte_array (JSContext *context,
+ GByteArray *array);
+GByteArray * gjs_byte_array_get_byte_array (JSContext *context,
+ JSObject *object);
+
+G_END_DECLS
+
+#endif /* __GJS_BYTE_ARRAY_H__ */
diff --git a/gjs/context.c b/gjs/context.c
index 743bb1a..635e31d 100644
--- a/gjs/context.c
+++ b/gjs/context.c
@@ -28,6 +28,8 @@
#include "importer.h"
#include "jsapi-util.h"
#include "profiler.h"
+#include "native.h"
+#include "byteArray.h"
#include <util/log.h>
#include <util/error.h>
@@ -332,6 +334,8 @@ gjs_context_class_init(GjsContextClass *klass)
g_object_class_install_property(object_class,
PROP_IS_LOAD_CONTEXT,
pspec);
+
+ gjs_register_native_module("byteArray", gjs_define_byte_array_stuff, 0);
}
static void
diff --git a/test/js/testByteArray.js b/test/js/testByteArray.js
new file mode 100644
index 0000000..3ead00d
--- /dev/null
+++ b/test/js/testByteArray.js
@@ -0,0 +1,119 @@
+// tests for imports.lang module
+
+const ByteArray = imports.byteArray;
+const Gio = imports.gi.Gio;
+
+function testEmptyByteArray() {
+ let a = new ByteArray.ByteArray();
+ assertEquals("length is 0 for empty array", 0, a.length);
+}
+
+function testInitialSizeByteArray() {
+ let a = new ByteArray.ByteArray(10);
+ assertEquals("length is 10 for initially-sized-10 array", 10, a.length);
+
+ let i;
+
+ for (i = 0; i < a.length; ++i) {
+ assertEquals("new array initialized to zeroes", 0, a[i]);
+ }
+
+ assertEquals("array had proper number of elements post-construct (counting for)",
+ 10, i);
+}
+
+function testAssignment() {
+ let a = new ByteArray.ByteArray(256);
+ assertEquals("length is 256 for initially-sized-256 array", 256, a.length);
+
+ let i;
+ let count;
+
+ count = 0;
+ for (i = 0; i < a.length; ++i) {
+ assertEquals("new array initialized to zeroes", 0, a[i]);
+ a[i] = 255 - i;
+ count += 1;
+ }
+
+ assertEquals("set proper number of values", 256, count);
+
+ count = 0;
+ for (i = 0; i < a.length; ++i) {
+ assertEquals("assignment set expected value", 255 - i, a[i]);
+ count += 1;
+ }
+
+ assertEquals("checked proper number of values", 256, count);
+}
+
+function testAssignmentPastEnd() {
+ let a = new ByteArray.ByteArray();
+ assertEquals("length is 0 for empty array", 0, a.length);
+
+ a[2] = 5;
+ assertEquals("implicitly made length 3", 3, a.length);
+ assertEquals("implicitly-created zero byte", 0, a[0]);
+ assertEquals("implicitly-created zero byte", 0, a[1]);
+ assertEquals("stored 5 in autocreated position", 5, a[2]);
+}
+
+function testAssignmentToLength() {
+ let a = new ByteArray.ByteArray(20);
+ assertEquals("length is 20 for new array", 20, a.length);
+
+ a.length = 5;
+
+ assertEquals("length is 5 after setting it to 5", 5, a.length);
+}
+
+function testNonIntegerAssignment() {
+ let a = new ByteArray.ByteArray();
+
+ a[0] = 5;
+ assertEquals("assigning 5 gives a byte 5", 5, a[0]);
+
+ a[0] = null;
+ assertEquals("assigning null gives a zero byte", 0, a[0]);
+
+ a[0] = 5;
+ assertEquals("assigning 5 gives a byte 5", 5, a[0]);
+
+ a[0] = undefined;
+ assertEquals("assigning undefined gives a zero byte", 0, a[0]);
+
+ a[0] = 3.14;
+ assertEquals("assigning a double rounds off", 3, a[0]);
+}
+
+function testFromString() {
+ let a = ByteArray.fromString('abcd');
+ assertEquals("from string 'abcd' gives length 4", 4, a.length);
+ assertEquals("'a' results in 97", 97, a[0]);
+ assertEquals("'b' results in 98", 98, a[1]);
+ assertEquals("'c' results in 99", 99, a[2]);
+ assertEquals("'d' results in 100", 100, a[3]);
+}
+
+function testFromArray() {
+ let a = ByteArray.fromArray([ 1, 2, 3, 4 ]);
+ assertEquals("from array [1,2,3,4] gives length 4", 4, a.length);
+ assertEquals("a[0] == 1", 1, a[0]);
+ assertEquals("a[1] == 2", 2, a[1]);
+ assertEquals("a[2] == 3", 3, a[2]);
+ assertEquals("a[3] == 4", 4, a[3]);
+}
+
+function testToString() {
+ let a = new ByteArray.ByteArray();
+ a[0] = 97;
+ a[1] = 98;
+ a[2] = 99;
+ a[3] = 100;
+
+ let s = a.toString();
+ assertEquals("toString() on 4 ascii bytes gives length 4", 4, s.length);
+ assertEquals("toString() gives 'abcd'", "abcd", s);
+}
+
+gjstestRun();
diff --git a/test/js/testGIMarshalling.js b/test/js/testGIMarshalling.js
index a4a4420..49ec4ca 100644
--- a/test/js/testGIMarshalling.js
+++ b/test/js/testGIMarshalling.js
@@ -43,4 +43,15 @@ function testGArray() {
assertEquals("2", array[2]);
}
+function testByteArray() {
+ var byteArray = GIMarshallingTests.bytearray_full_return();
+ assertEquals("arrayLength", 4, byteArray.length);
+ assertEquals("a[0]", '0'.charCodeAt(0), byteArray[0]);
+ assertEquals("a[1]", '1'.charCodeAt(0), byteArray[1])
+ assertEquals("a[2]", '2'.charCodeAt(0), byteArray[2]);
+ assertEquals("a[3]", '3'.charCodeAt(0), byteArray[3]);
+ let ba = imports.byteArray.fromString("0123");
+ GIMarshallingTests.bytearray_none_in(ba);
+}
+
gjstestRun();
diff --git a/util/log.c b/util/log.c
index 7359c6a..3f0e06a 100644
--- a/util/log.c
+++ b/util/log.c
@@ -274,6 +274,9 @@ gjs_debug(GjsDebugTopic topic,
case GJS_DEBUG_HTTP:
prefix = "JS HTTP";
break;
+ case GJS_DEBUG_BYTE_ARRAY:
+ prefix = "JS BYTE ARRAY";
+ break;
}
if (!is_allowed_prefix(prefix))
diff --git a/util/log.h b/util/log.h
index dc43c2c..f9d3c54 100644
--- a/util/log.h
+++ b/util/log.h
@@ -60,6 +60,7 @@ typedef enum {
GJS_DEBUG_PROPS,
GJS_DEBUG_SCOPE,
GJS_DEBUG_HTTP,
+ GJS_DEBUG_BYTE_ARRAY
} GjsDebugTopic;
/* These defines are because we have some pretty expensive and
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]