[gjs/wip/ptomato/warnings: 4/14] arg: Support gunichar-valued arrays



commit 6ab76cba8474b69661ca1f6310caa38000323bd4
Author: Philip Chimento <philip chimento gmail com>
Date:   Tue Oct 11 22:03:00 2016 -0700

    arg: Support gunichar-valued arrays
    
    Compiling with -Wswitch revealed that we do not support gunichar arrays.
    This adds support for marshalling JS strings into arrays of gunichar,
    containing UCS-4 encoded strings, and vice versa. For JS-to-C, one can
    additionally supply a JS array of integers which will be marshalled into
    a gunichar array.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=772790

 gi/arg.cpp                              |   51 +++++++++++++++--
 gjs/jsapi-util-string.cpp               |   95 +++++++++++++++++++++++++++++++
 gjs/jsapi-util.h                        |   10 +++
 installed-tests/js/testGIMarshalling.js |   10 +++
 4 files changed, 161 insertions(+), 5 deletions(-)
---
diff --git a/gi/arg.cpp b/gi/arg.cpp
index 96693f3..3d252e7 100644
--- a/gi/arg.cpp
+++ b/gi/arg.cpp
@@ -540,6 +540,16 @@ gjs_string_to_intarray(JSContext   *context,
         *arr_p = result16;
         return true;
 
+    case GI_TYPE_TAG_UNICHAR:
+    {
+        gunichar *result_ucs4;
+        JS::RootedValue root(context, string_val);
+        if (!gjs_string_to_ucs4(context, root, &result_ucs4, length))
+            return false;
+        *arr_p = result_ucs4;
+        return true;
+    }
+
     default:
         /* can't convert a string to this type */
         gjs_throw(context, "Cannot convert string to array of '%s'",
@@ -924,6 +934,9 @@ gjs_array_to_array(JSContext   *context,
         return gjs_array_to_strv (context, array_value, length, arr_p);
     case GI_TYPE_TAG_BOOLEAN:
         return gjs_array_to_gboolean_array(context, array_value, length, arr_p);
+    case GI_TYPE_TAG_UNICHAR:
+        return gjs_array_to_intarray(context, array_value, length, arr_p,
+            sizeof(gunichar), UNSIGNED);
     case GI_TYPE_TAG_UINT8:
         return gjs_array_to_intarray
             (context, array_value, length, arr_p, 1, UNSIGNED);
@@ -1004,6 +1017,9 @@ gjs_g_array_new_for_type(JSContext    *context,
     case GI_TYPE_TAG_BOOLEAN:
         element_size = sizeof(gboolean);
         break;
+    case GI_TYPE_TAG_UNICHAR:
+        element_size = sizeof(gunichar);
+        break;
     case GI_TYPE_TAG_UINT8:
     case GI_TYPE_TAG_INT8:
       element_size = sizeof(guint8);
@@ -2127,7 +2143,16 @@ gjs_array_from_carray_internal (JSContext  *context,
             return false;
         *value_p = JS::ObjectValue(*obj);
         return true;
-    } 
+    }
+
+    /* Special case array(unichar) to be a string in JS */
+    if (element_type == GI_TYPE_TAG_UNICHAR) {
+        JS::RootedValue value_out(context);
+        if (!gjs_string_from_ucs4(context, (gunichar *) array, length, &value_out))
+            return false;
+        *value_p = value_out.get();
+        return true;
+    }
 
     obj = JS_NewArrayObject(context, 0, NULL);
     if (obj == NULL)
@@ -2149,9 +2174,10 @@ gjs_array_from_carray_internal (JSContext  *context,
     }
 
     switch (element_type) {
+        /* Special cases handled above */
         case GI_TYPE_TAG_UINT8:
-          ITERATE(uint8);
-          break;
+        case GI_TYPE_TAG_UNICHAR:
+            g_assert_not_reached();
         case GI_TYPE_TAG_BOOLEAN:
             ITERATE(boolean);
             break;
@@ -2347,7 +2373,17 @@ gjs_array_from_zero_terminated_c_array (JSContext  *context,
             return false;
         *value_p = JS::ObjectValue(*obj);
         return true;
-    } 
+    }
+
+    /* Special case array(gunichar) to JS string */
+    if (element_type == GI_TYPE_TAG_UNICHAR) {
+        JS::RootedValue value_out(context);
+
+        if (!gjs_string_from_ucs4(context, (gunichar *) c_array, -1, &value_out))
+            return false;
+        *value_p = value_out.get();
+        return true;
+    }
 
     obj = JS_NewArrayObject(context, 0, NULL);
     if (obj == NULL)
@@ -2372,7 +2408,10 @@ gjs_array_from_zero_terminated_c_array (JSContext  *context,
     } while(0);
 
     switch (element_type) {
-        /* We handle GI_TYPE_TAG_UINT8 above. */
+        /* Special cases handled above. */
+        case GI_TYPE_TAG_UINT8:
+        case GI_TYPE_TAG_UNICHAR:
+            g_assert_not_reached();
         case GI_TYPE_TAG_INT8:
           ITERATE(int8);
           break;
@@ -3159,6 +3198,7 @@ gjs_g_arg_release_internal(JSContext  *context,
             case GI_TYPE_TAG_INT64:
             case GI_TYPE_TAG_FLOAT:
             case GI_TYPE_TAG_DOUBLE:
+            case GI_TYPE_TAG_UNICHAR:
             case GI_TYPE_TAG_GTYPE:
                 g_free (arg->v_pointer);
                 break;
@@ -3224,6 +3264,7 @@ gjs_g_arg_release_internal(JSContext  *context,
 
             switch (element_type) {
             case GI_TYPE_TAG_BOOLEAN:
+            case GI_TYPE_TAG_UNICHAR:
             case GI_TYPE_TAG_UINT8:
             case GI_TYPE_TAG_UINT16:
             case GI_TYPE_TAG_UINT32:
diff --git a/gjs/jsapi-util-string.cpp b/gjs/jsapi-util-string.cpp
index f845d15..88a757f 100644
--- a/gjs/jsapi-util-string.cpp
+++ b/gjs/jsapi-util-string.cpp
@@ -208,6 +208,101 @@ out:
 }
 
 /**
+ * gjs_string_to_ucs4:
+ * @cx: a #JSContext
+ * @value: JS::Value containing a string
+ * @ucs4_string_p: return location for a #gunichar array
+ * @len_p: return location for @ucs4_string_p length
+ *
+ * Returns: true on success, false otherwise in which case a JS error is thrown
+ */
+bool
+gjs_string_to_ucs4(JSContext      *cx,
+                   JS::HandleValue value,
+                   gunichar      **ucs4_string_p,
+                   size_t         *len_p)
+{
+    if (ucs4_string_p == NULL)
+        return true;
+
+    if (!value.isString()) {
+        gjs_throw(cx, "Value is not a string, cannot convert to UCS-4");
+        return false;
+    }
+
+    JSAutoRequest ar(cx);
+    JS::RootedString str(cx, value.toString());
+    size_t utf16_len;
+    GError *error = NULL;
+
+    const jschar *utf16 = JS_GetStringCharsAndLength(cx, str, &utf16_len);
+    if (utf16 == NULL) {
+        gjs_throw(cx, "Failed to get UTF-16 string data");
+        return false;
+    }
+
+    if (ucs4_string_p != NULL) {
+        long length;
+        *ucs4_string_p = g_utf16_to_ucs4(utf16, utf16_len, NULL, &length, &error);
+        if (*ucs4_string_p == NULL) {
+            gjs_throw(cx, "Failed to convert UTF-16 string to UCS-4: %s",
+                      error->message);
+            g_clear_error(&error);
+            return false;
+        }
+        if (len_p != NULL)
+            *len_p = (size_t) length;
+    }
+
+    return true;
+}
+
+/**
+ * gjs_string_from_ucs4:
+ * @cx: a #JSContext
+ * @ucs4_string: string of #gunichar
+ * @n_chars: number of characters in @ucs4_string or -1 for zero-terminated
+ * @value_p: JS::Value that will be filled with a string
+ *
+ * Returns: true on success, false otherwise in which case a JS error is thrown
+ */
+bool
+gjs_string_from_ucs4(JSContext             *cx,
+                     const gunichar        *ucs4_string,
+                     ssize_t                n_chars,
+                     JS::MutableHandleValue value_p)
+{
+    uint16_t *u16_string;
+    long u16_string_length;
+    GError *error = NULL;
+
+    /* intentionally using n_bytes even though glib api suggests n_chars; with
+     * n_chars (from g_utf8_strlen()) the result appears truncated
+     */
+
+    u16_string = g_ucs4_to_utf16(ucs4_string, n_chars, NULL,
+                                 &u16_string_length, &error);
+    if (!u16_string) {
+        gjs_throw(cx, "Failed to convert UCS-4 string to UTF-16: %s",
+                  error->message);
+        g_error_free(error);
+        return false;
+    }
+
+    JSAutoRequest ar(cx);
+    /* Avoid a copy - assumes that g_malloc == js_malloc == malloc */
+    JS::RootedString str(cx, JS_NewUCString(cx, u16_string, u16_string_length));
+
+    if (str == NULL) {
+        gjs_throw(cx, "Failed to convert UCS-4 string to UTF-16");
+        return false;
+    }
+
+    value_p.setString(str);
+    return true;
+}
+
+/**
  * gjs_get_string_id:
  * @context: a #JSContext
  * @id: a jsid that is an object hash key (could be an int or string)
diff --git a/gjs/jsapi-util.h b/gjs/jsapi-util.h
index 6cc164a..2016a86 100644
--- a/gjs/jsapi-util.h
+++ b/gjs/jsapi-util.h
@@ -418,6 +418,16 @@ bool        gjs_string_get_uint16_data       (JSContext       *context,
                                               JS::Value        value,
                                               guint16        **data_p,
                                               gsize           *len_p);
+
+bool gjs_string_to_ucs4(JSContext      *cx,
+                        JS::HandleValue value,
+                        gunichar      **ucs4_string_p,
+                        size_t         *len_p);
+bool gjs_string_from_ucs4(JSContext             *cx,
+                          const gunichar        *ucs4_string,
+                          ssize_t                n_chars,
+                          JS::MutableHandleValue value_p);
+
 bool        gjs_get_string_id                (JSContext       *context,
                                               jsid             id,
                                               char           **name_p);
diff --git a/installed-tests/js/testGIMarshalling.js b/installed-tests/js/testGIMarshalling.js
index c8db124..3cfd5c7 100644
--- a/installed-tests/js/testGIMarshalling.js
+++ b/installed-tests/js/testGIMarshalling.js
@@ -76,6 +76,10 @@ function testCArray() {
     assertEquals(true, array[2]);
     assertEquals(true, array[3]);
 
+    assertEquals('const \u2665 utf8', GIMarshallingTests.array_unichar_out());
+    assertEquals('const \u2665 utf8',
+        GIMarshallingTests.array_zero_terminated_return_unichar());
+
     array = GIMarshallingTests.array_inout([-1, 0, 1, 2]);
     assertEquals(5, array.length);
     assertEquals(-2, array[0]);
@@ -108,6 +112,9 @@ function testCArray() {
     GIMarshallingTests.array_struct_take_in(array);
 
     GIMarshallingTests.array_uint8_in ("abcd");
+    GIMarshallingTests.array_unichar_in('const \u2665 utf8');
+    GIMarshallingTests.array_unichar_in([0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20,
+        0x2665, 0x20, 0x75, 0x74, 0x66, 0x38]);
     GIMarshallingTests.array_enum_in([GIMarshallingTests.Enum.VALUE1,
                                      GIMarshallingTests.Enum.VALUE2,
                                      GIMarshallingTests.Enum.VALUE3]);
@@ -146,6 +153,9 @@ function testGArray() {
     GIMarshallingTests.garray_int_none_in([-1, 0, 1, 2]);
     GIMarshallingTests.garray_utf8_none_in(["0", "1", "2"]);
     GIMarshallingTests.garray_bool_none_in([-1, 0, 1, 2]);
+    GIMarshallingTests.garray_unichar_none_in('const \u2665 utf8');
+    GIMarshallingTests.garray_unichar_none_in([0x63, 0x6f, 0x6e, 0x73, 0x74,
+        0x20, 0x2665, 0x20, 0x75, 0x74, 0x66, 0x38]);
 
     array = GIMarshallingTests.garray_utf8_none_out();
     assertEquals("0", array[0]);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]