diff --git a/CHANGELOG.md b/CHANGELOG.md index 60bdc774..292ed9be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ -# v1.10.3-alpha1 +# v1.10.3-alpha2 * Add `int.of_type(..)` to convert an ID to a stored intance, pr #466. * Improve error message when typing `f()!=x`, ambiguous operator, pr #468. +* Added `dict.vmap(..)` function, pr #469. # v1.10.2 diff --git a/inc/doc.h b/inc/doc.h index 549829a3..3a618eee 100644 --- a/inc/doc.h +++ b/inc/doc.h @@ -234,6 +234,7 @@ #define DOC_DICT_RESTRICTION DOC_SEE("data-types/dict/restriction") #define DOC_DICT_SET DOC_SEE("data-types/dict/set") #define DOC_DICT_VALUES DOC_SEE("data-types/dict/values") +#define DOC_DICT_VMAP DOC_SEE("data-types/dict/vmap") #define DOC_ENUM_NAME DOC_SEE("data-types/enum/name") #define DOC_ENUM_VALUE DOC_SEE("data-types/enum/value") #define DOC_ERROR_CODE DOC_SEE("data-types/error/code") diff --git a/inc/ti/fn/fnvmap.h b/inc/ti/fn/fnvmap.h index 7b721110..46d00786 100644 --- a/inc/ti/fn/fnvmap.h +++ b/inc/ti/fn/fnvmap.h @@ -21,16 +21,37 @@ static int vmap__walk_i(ti_item_t * item, vmap__walk_i_t * w) return 0; } -static int do__f_vmap(ti_query_t * query, cleri_node_t * nd, ex_t * e) +typedef struct +{ + ex_t * e; + ti_closure_t * closure; + ti_query_t * query; + ti_dict_t * dict; +} vmap__pair_t; + +static int vmap__pair(ti_dict_key_t * key, ti_val_t * val, vmap__pair_t * w) +{ + ti_closure_vars_val(w->closure, val); + if (ti_closure_do_statement(w->closure, w->query, w->e) || + ti_val(w->query->rval)->to_nested( + &w->query->rval, + (ti_parent_t *) w->dict, + w->e) || + ti_dict_setr(w->dict, key, w->query->rval)) + return -1; + + ti_val_unsafe_drop(w->query->rval); + w->query->rval = NULL; + return 0; +} + +static int do__f_vmap_thing(ti_query_t * query, cleri_node_t * nd, ex_t * e) { const int nargs = fn_get_nargs(nd); ti_thing_t * othing, * nthing; ti_closure_t * closure; int lock_was_set; - if (!ti_val_is_thing(query->rval)) - return fn_call_try("vmap", query, nd, e); - if (fn_nargs("vmap", DOC_THING_VMAP, 1, nargs, e)) return e->nr; @@ -125,3 +146,70 @@ static int do__f_vmap(ti_query_t * query, cleri_node_t * nd, ex_t * e) ti_val_unsafe_drop((ti_val_t *) othing); return e->nr; } + +static int do__f_vmap_dict(ti_query_t * query, cleri_node_t * nd, ex_t * e) +{ + const int nargs = fn_get_nargs(nd); + ti_dict_t * odict; + ti_closure_t * closure; + int lock_was_set; + + if (fn_nargs("vmap", DOC_DICT_VMAP, 1, nargs, e)) + return e->nr; + + lock_was_set = ti_val_ensure_lock(query->rval); + odict = (ti_dict_t *) query->rval; + query->rval = NULL; + + if (ti_do_statement(query, nd->children, e) || + fn_arg_closure("vmap", DOC_DICT_VMAP, 1, query->rval, e)) + goto fail0; + + closure = (ti_closure_t *) query->rval; + query->rval = NULL; + + if ( ti_closure_try_wse(closure, query, e) || + ti_closure_inc(closure, query, e)) + goto fail1; + + vmap__pair_t w = { + .closure = closure, + .e = e, + .query = query, + .dict = ti_dict_create(), + }; + + if (!w.dict || ti_dict_pairs( + odict, + (ti_dict_pair_cb) vmap__pair, + &w)) + goto fail2; + + query->rval = (ti_val_t *) w.dict; + goto done; + +fail2: + if (!e->nr) + ex_set_mem(e); + ti_val_drop((ti_val_t *) w.dict); + +done: + ti_closure_dec(closure, query); + +fail1: + ti_val_unsafe_drop((ti_val_t *) closure); + +fail0: + ti_val_unlock((ti_val_t *) odict, lock_was_set); + ti_val_unsafe_drop((ti_val_t *) odict); + return e->nr; +} + +static int do__f_vmap(ti_query_t * query, cleri_node_t * nd, ex_t * e) +{ + return ti_val_is_thing(query->rval) + ? do__f_vmap_thing(query, nd, e) + : ti_val_is_dict(query->rval) + ? do__f_vmap_dict(query, nd, e) + : fn_call_try("vmap", query, nd, e); +} \ No newline at end of file diff --git a/inc/ti/version.h b/inc/ti/version.h index 77fd1150..0b50c622 100644 --- a/inc/ti/version.h +++ b/inc/ti/version.h @@ -25,7 +25,7 @@ * "-rc0" * "" */ -#define TI_VERSION_PRE_RELEASE "-alpha1" +#define TI_VERSION_PRE_RELEASE "-alpha2" #define TI_MAINTAINER \ "Jeroen van der Heijden " diff --git a/itest/test_dict.py b/itest/test_dict.py index ec2b8187..ca3152d0 100755 --- a/itest/test_dict.py +++ b/itest/test_dict.py @@ -500,6 +500,46 @@ async def test_dict_map(self, client): self.assertEqual(keys, [f'key: {i}' for i in range(n)]) self.assertEqual(values, [f'value: {i}' for i in range(n)]) + async def test_dict_vmap(self, client): + q = client.query + with self.assertRaisesRegex( + NumArgumentsError, + 'function `vmap` takes 1 argument but 0 were given'): + await q('dict().vmap();') + + with self.assertRaisesRegex( + TypeError, + 'function `vmap` expects argument 1 to be of ' + 'type `closure` but got type `int` instead'): + await q('dict().vmap(0);') + + self.assertEqual(await q('dict().vmap(||nil)'), []) + + self.assertEqual(sorted(await q("""//ti + dict([ + [0, 1], + [1, 2], + [2, 3], + [3, 4] + ]).vmap(|v| v*v); + """)), [ + [0, 1], + [1, 4], + [2, 9], + [3, 16] + ]) + + # test conversion + a1, a2 = await q("""//ti + a = ["a", "b"]; + d = dict([[0, nil]]).vmap(|| a); + a.push("c"); // this is still a list + assert(is_tuple(d[0])); // must be a tuple + [a, d[0]]; + """) + self.assertEqual(a1, ["a", "b", "c"]) + self.assertEqual(a2, ["a", "b"]) + async def test_dict_restriction(self, client): q = client.query with self.assertRaisesRegex(