Browse Source

Implement parsing of HEP-1 Value Annotations

This implements the parsing part of the HEP-1: hipack_value_t is extended to
contain a dictionary (which gets used as a set) of annotations, which gets
initialized and populated lazily, to avoid creating an additional dictionary
per value when no annotations are present.
Adrián Pérez de Castro 3 years ago
parent
commit
b6f267df38
4 changed files with 198 additions and 50 deletions
  1. 27 0
      hipack-dict.c
  2. 79 49
      hipack-parser.c
  3. 52 1
      hipack.h
  4. 40 0
      test/hep-001.hi

+ 27 - 0
hipack-dict.c

@@ -204,6 +204,33 @@ hipack_dict_get (const hipack_dict_t   *dict,
 }
 
 
+void
+hipack_dict_del (hipack_dict_t         *dict,
+                 const hipack_string_t *key)
+{
+    assert (dict);
+    assert (key);
+
+    uint32_t hash_val = hipack_string_hash (key) % dict->size;
+    for (hipack_dict_node_t *node = dict->nodes[hash_val]; node; node = node->next) {
+        if (hipack_string_equal (node->key, key)) {
+            hipack_dict_node_t *prev_node = node->prev_node;
+            hipack_dict_node_t *next_node = node->next_node;
+
+            if (prev_node) prev_node->next_node = next_node;
+            else dict->first = next_node;
+            if (next_node) next_node->prev_node = prev_node;
+
+            dict->nodes[hash_val] = node->next;
+            dict->count--;
+
+            free_node (node);
+            return;
+        }
+    }
+}
+
+
 hipack_value_t*
 hipack_dict_first (const hipack_dict_t    *dict,
                    const hipack_string_t **key)

+ 79 - 49
hipack-parser.c

@@ -44,7 +44,7 @@ struct parser {
 #define DUMMY ) /* Makes autoindentation work. */
 #undef DUMMY
 
-#define DUMMY_VALUE ((hipack_value_t) { .type = HIPACK_BOOL })
+#define DUMMY_VALUE ((hipack_value_t) { .type = HIPACK_BOOL, .annot = NULL })
 
 
 static hipack_value_t parse_value (P, S);
@@ -310,8 +310,8 @@ error:
 }
 
 
-static hipack_value_t
-parse_string (P, S)
+static void
+parse_string (P, hipack_value_t *result, S)
 {
     hipack_string_t *hstr = NULL;
     uint32_t alloc_size = 0;
@@ -353,21 +353,19 @@ parse_string (P, S)
     }
 
     matchchar (p, '"', "unterminated string value", CHECK_OK);
-    return (hipack_value_t) {
-        .type = HIPACK_STRING,
-        .v_string = hstr ? hstr : hipack_string_new_from_lstring ("", 0)
-    };
+    result->type = HIPACK_STRING;
+    result->v_string = hstr ? hstr : hipack_string_new_from_lstring ("", 0);
+    return;
 
 error:
     hipack_string_free (hstr);
-    return DUMMY_VALUE;
+    return;
 }
 
 
-static hipack_value_t
-parse_list (P, S)
+static void
+parse_list (P, hipack_value_t *result, S)
 {
-    hipack_value_t value = DUMMY_VALUE;
     hipack_list_t *list = NULL;
     uint32_t alloc_size = 0;
     uint32_t size = 0;
@@ -376,10 +374,9 @@ parse_list (P, S)
     skipwhite (p, CHECK_OK);
 
     while (p->look != ']') {
-        value = parse_value (p, CHECK_OK);
+        hipack_value_t value = parse_value (p, CHECK_OK);
         list = list_resize (list, &alloc_size, size + 1);
         list->data[size++] = value;
-        value = DUMMY_VALUE;
 
         bool got_whitespace = is_hipack_whitespace (p->look);
         skipwhite (p, CHECK_OK);
@@ -394,65 +391,65 @@ parse_list (P, S)
     }
 
     matchchar (p, ']', "unterminated list value", CHECK_OK);
-    return (hipack_value_t) {
-        .type = HIPACK_LIST,
-        .v_list = list ? list : hipack_list_new (0),
-    };
+    result->type = HIPACK_LIST;
+    result->v_list = list;
+    return;
 
 error:
-    hipack_value_free (&value);
     hipack_list_free (list);
-    return DUMMY_VALUE;
+    return;
 }
 
 
-static hipack_value_t
-parse_dict (P, S)
+static void
+parse_dict (P, hipack_value_t *result, S)
 {
     hipack_dict_t *dict = hipack_dict_new ();
     matchchar (p, '{', NULL, CHECK_OK);
     skipwhite (p, CHECK_OK);
     parse_keyval_items (p, dict, '}', CHECK_OK);
     matchchar (p, '}', "unterminated dict value", CHECK_OK);
-    return (hipack_value_t) { .type = HIPACK_DICT, .v_dict = dict };
+    result->type = HIPACK_DICT;
+    result->v_dict = dict;
+    return;
 
 error:
     hipack_dict_free (dict);
-    return DUMMY_VALUE;
+    return;
 }
 
 
-static hipack_value_t
-parse_bool (P, S)
+static void
+parse_bool (P, hipack_value_t *result, S)
 {
+    result->type = HIPACK_BOOL;
     if (p->look == 'T' || p->look == 't') {
         nextchar (p, CHECK_OK);
         matchchar (p, 'r', NULL, CHECK_OK);
         matchchar (p, 'u', NULL, CHECK_OK);
         matchchar (p, 'e', NULL, CHECK_OK);
-        return (hipack_value_t) { .type = HIPACK_BOOL, .v_bool = true };
+        result->v_bool = true;
     } else if (p->look == 'F' || p->look == 'f') {
         nextchar (p, CHECK_OK);
         matchchar (p, 'a', NULL, CHECK_OK);
         matchchar (p, 'l', NULL, CHECK_OK);
         matchchar (p, 's', NULL, CHECK_OK);
         matchchar (p, 'e', NULL, CHECK_OK);
-        return (hipack_value_t) { .type = HIPACK_BOOL, .v_bool = false };
+        result->v_bool = false;
     }
+    return;
 
 error:
     p->error = "invalid boolean value";
-    return DUMMY_VALUE;
 }
 
 
-static hipack_value_t
-parse_number (P, S)
+static void
+parse_number (P, hipack_value_t *result, S)
 {
     hipack_string_t *hstr = NULL;
     uint32_t alloc_size = 0;
     uint32_t size = 0;
-    hipack_value_t result;
 
 #define SAVE_LOOK( ) \
     hstr = string_resize (hstr, &alloc_size, size + 1); \
@@ -534,8 +531,8 @@ parse_number (P, S)
         char *endptr = NULL;
         long v = strtol ((const char*) hstr->data, &endptr, 16);
         /* TODO: Check for overflow. */
-        result.type = HIPACK_INTEGER;
-        result.v_integer = (int32_t) v;
+        result->type = HIPACK_INTEGER;
+        result->v_integer = (int32_t) v;
     } else if (is_octal) {
         assert (!is_hex);
         if (exp_seen || dot_seen) {
@@ -544,13 +541,13 @@ parse_number (P, S)
         }
         long v = strtol ((const char*) hstr->data, &endptr, 8);
         /* TODO: Check for overflow. */
-        result.type = HIPACK_INTEGER;
-        result.v_integer = (int32_t) v;
+        result->type = HIPACK_INTEGER;
+        result->v_integer = (int32_t) v;
     } else if (dot_seen || exp_seen) {
         assert (!is_hex);
         assert (!is_octal);
-        result.type = HIPACK_FLOAT;
-        result.v_float = strtod ((const char*) hstr->data, &endptr);
+        result->type = HIPACK_FLOAT;
+        result->v_float = strtod ((const char*) hstr->data, &endptr);
     } else {
         assert (!is_hex);
         assert (!is_octal);
@@ -558,8 +555,8 @@ parse_number (P, S)
         assert (!dot_seen);
         long v = strtol ((const char*) hstr->data, &endptr, 10);
         /* TODO: Check for overflow. */
-        result.type = HIPACK_INTEGER;
-        result.v_integer = (int32_t) v;
+        result->type = HIPACK_INTEGER;
+        result->v_integer = (int32_t) v;
     }
 
     if (endptr && *endptr != '\0') {
@@ -568,12 +565,43 @@ parse_number (P, S)
     }
 
     hipack_string_free (hstr);
-    return result;
+    return;
 
 error:
     p->error = "invalid numeric value";
     hipack_string_free (hstr);
-    return DUMMY_VALUE;
+}
+
+
+static void
+parse_annotations (P, hipack_value_t *result, S)
+{
+    hipack_string_t *key = NULL;
+    while (p->look == ':') {
+        p->look = nextchar_raw (p, CHECK_OK);
+        key = parse_key (p, CHECK_OK);
+        skipwhite (p, CHECK_OK);
+
+        /* Check if the annotation is already in the set. */
+        if (result->annot && hipack_dict_get (result->annot, key)) {
+            p->error = "duplicate annotation";
+            *status = kStatusError;
+            goto error;
+        }
+        /* Add the annotation to the set. */
+        if (!result->annot)
+            result->annot = hipack_dict_new ();
+
+        static const hipack_value_t annot_present = {
+            .type   = HIPACK_BOOL,
+            .v_bool = true,
+        };
+        hipack_dict_set_adopt_key (result->annot, &key, &annot_present);
+    }
+
+error:
+    if (key)
+        hipack_string_free (key);
 }
 
 
@@ -582,33 +610,36 @@ parse_value (P, S)
 {
     hipack_value_t result = DUMMY_VALUE;
 
+    parse_annotations (p, &result, CHECK_OK);
+
     switch (p->look) {
         case '"': /* String */
-            result = parse_string (p, CHECK_OK);
+            parse_string (p, &result, CHECK_OK);
             break;
 
         case '[': /* List */
-            result = parse_list (p, CHECK_OK);
+            parse_list (p, &result, CHECK_OK);
             break;
 
         case '{': /* Dict */
-            result = parse_dict (p, CHECK_OK);
+            parse_dict (p, &result, CHECK_OK);
             break;
 
         case 'T': /* Bool */
         case 't':
         case 'F':
         case 'f':
-            result = parse_bool (p, CHECK_OK);
+            parse_bool (p, &result, CHECK_OK);
             break;
 
         default: /* Integer or Float */
-            result = parse_number (p, CHECK_OK);
+            parse_number (p, &result, CHECK_OK);
             break;
     }
 
 error:
-    return result;
+    hipack_value_free (&result);
+    return DUMMY_VALUE;
 }
 
 
@@ -631,8 +662,7 @@ parse_keyval_items (P, hipack_dict_t *result, int eos, S)
         if (is_hipack_whitespace (p->look)) {
             got_separator = true;
             skipwhite (p, CHECK_OK);
-        }
-        switch (p->look) {
+        } else switch (p->look) {
             case ':':
                 nextchar (p, CHECK_OK);
                 skipwhite (p, CHECK_OK);

+ 52 - 1
hipack.h

@@ -61,7 +61,8 @@ typedef struct hipack_list      hipack_list_t;
 
 
 struct hipack_value {
-    hipack_type_t type;
+    hipack_type_t  type;
+    hipack_dict_t *annot;
     union {
         int32_t          v_integer;
         double           v_float;
@@ -141,6 +142,9 @@ extern void hipack_dict_set (hipack_dict_t         *dict,
                              const hipack_string_t *key,
                              const hipack_value_t  *value);
 
+extern void hipack_dict_del (hipack_dict_t         *dict,
+                             const hipack_string_t *key);
+
 extern hipack_value_t* hipack_dict_get (const hipack_dict_t   *dict,
                                         const hipack_string_t *key);
 
@@ -197,6 +201,9 @@ hipack_value_free (hipack_value_t *value)
 {
     assert (value);
 
+    if (value->annot)
+        hipack_dict_free (value->annot);
+
     switch (value->type) {
         case HIPACK_INTEGER:
         case HIPACK_FLOAT:
@@ -219,6 +226,50 @@ hipack_value_free (hipack_value_t *value)
 }
 
 
+static inline void
+hipack_value_add_annot (hipack_value_t *value,
+                        const char     *annot)
+{
+    assert (value);
+    assert (annot);
+
+    if (!value->annot) {
+        value->annot = hipack_dict_new ();
+    }
+
+    static const hipack_value_t bool_true = {
+        .type   = HIPACK_BOOL,
+        .v_bool = true,
+    };
+    hipack_string_t *key = hipack_string_new_from_string (annot);
+    hipack_dict_set_adopt_key (value->annot, &key, &bool_true);
+}
+
+static inline bool
+hipack_value_has_annot (const hipack_value_t *value,
+                        const char           *annot)
+{
+    assert (value);
+    assert (annot);
+
+    /* TODO: Try to avoid the string copy. */
+    hipack_string_t *key = hipack_string_new_from_string (annot);
+    bool result = (value->annot) && hipack_dict_get (value->annot, key);
+    hipack_string_free (key);
+    return result;
+}
+
+static inline void
+hipack_value_del_annot (hipack_value_t *value,
+                        const char     *annot)
+{
+    assert (value);
+    assert (annot);
+
+    hipack_string_t *key = hipack_string_new_from_string (annot);
+}
+
+
 typedef struct {
     int       (*getchar) (void*);
     void       *getchar_data;

+ 40 - 0
test/hep-001.hi

@@ -0,0 +1,40 @@
+single-annotations {
+	integer-value :annot 0
+	float-value :annot 1.1
+	string-value :annot "Hello"
+	bool-value :annot True
+	list-value :annot []
+	dict-value :annot {}
+}
+multiple-annotations {
+	integer-value :annot1 :annot2 0
+	float-value :annot1 :annot2 1.1
+	string-value :annot1 :annot2 :annot3 "Three"
+	bool-value :has :two :annots False
+	list-value :annot1 :annot2 []
+	dict-value :annot1 :annot2 :annot3 :annot4 {}
+}
+annotation-delimiter-variations {
+	double-colon-no-space::annot 0
+	double-colon-space: :annot 0
+	multi-double-colon-no-space::annot1:annot2 0
+	multi-double-colon-space: :anot1:annot2 0
+	multi-spaced-double-colon-nospace::annot1 :annot2 0
+	multi-spaced-double-colon-space: :annot1 :annot2 0
+}
+intrinsic-annotations {
+	integer-value :.int 0
+	float-value :.float 1.1
+	string-value :.string "Hello"
+	bool-value :.bool True
+	list-value :.list []
+	dict-value :.dict {}
+}
+list-item-annotations {
+	spaced-items      [:annot1 "item1" :annot2 "item2"]
+	comma-items       [:annot1 "item1",:annot2 "item2"]
+	commaspaced-items [:annot1 "item1", :annot2 "item2"]
+	leading-space     [ :annot1 "item1" :annot2 "item2"]
+	trailing-space    [:annot1 "item1" :annot2 "item2" ]
+	leadtrail-space   [ :annot1 "item1" :annot2 "item2" ]
+}