mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-31 11:41:33 +00:00
492 lines
15 KiB
C
492 lines
15 KiB
C
#include "../lib/mlr_arch.h"
|
|
#include "../lib/string_builder.h"
|
|
#include "../lib/free_flags.h"
|
|
#include "../containers/xvfuncs.h"
|
|
|
|
// ----------------------------------------------------------------
|
|
boxed_xval_t b_xx_haskey_xfunc(boxed_xval_t* pmapval, boxed_xval_t* pkeyval) {
|
|
boxed_xval_t rv;
|
|
if (pmapval->xval.is_terminal) {
|
|
rv = box_ephemeral_val(mv_from_bool(FALSE));
|
|
} else if (!pkeyval->xval.is_terminal) {
|
|
rv = box_ephemeral_val(mv_from_bool(FALSE));
|
|
} else {
|
|
rv = box_ephemeral_val(
|
|
mv_from_bool(
|
|
mlhmmv_level_has_key(pmapval->xval.pnext_level, &pkeyval->xval.terminal_mlrval)
|
|
)
|
|
);
|
|
}
|
|
if (pmapval->is_ephemeral)
|
|
mlhmmv_xvalue_free(&pmapval->xval);
|
|
if (pkeyval->is_ephemeral)
|
|
mlhmmv_xvalue_free(&pkeyval->xval);
|
|
return rv;
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
boxed_xval_t i_x_length_xfunc(boxed_xval_t* pbxval1) {
|
|
boxed_xval_t rv;
|
|
if (pbxval1->xval.is_terminal) {
|
|
rv = box_ephemeral_val(
|
|
mv_from_int(1)
|
|
);
|
|
} else {
|
|
rv = box_ephemeral_val(
|
|
mv_from_int(
|
|
pbxval1->xval.pnext_level->num_occupied
|
|
)
|
|
);
|
|
}
|
|
if (pbxval1->is_ephemeral)
|
|
mlhmmv_xvalue_free(&pbxval1->xval);
|
|
return rv;
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
static int depth_aux(mlhmmv_xvalue_t* pxval) {
|
|
if (pxval->is_terminal) {
|
|
return 0;
|
|
} else {
|
|
int max = 0;
|
|
for (mlhmmv_level_entry_t* pe = pxval->pnext_level->phead; pe != NULL; pe = pe->pnext) {
|
|
int curr = depth_aux(&pe->level_xvalue);
|
|
max = (curr > max) ? curr : max;
|
|
}
|
|
return 1 + max;
|
|
}
|
|
}
|
|
|
|
boxed_xval_t i_x_depth_xfunc(boxed_xval_t* pbxval1) {
|
|
boxed_xval_t rv = box_ephemeral_val(
|
|
mv_from_int(
|
|
depth_aux(&pbxval1->xval)
|
|
)
|
|
);
|
|
|
|
if (pbxval1->is_ephemeral)
|
|
mlhmmv_xvalue_free(&pbxval1->xval);
|
|
|
|
return rv;
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
static int leafcount_aux(mlhmmv_xvalue_t* pxval) {
|
|
if (pxval->is_terminal) {
|
|
return 1;
|
|
} else {
|
|
int sum = 0;
|
|
for (mlhmmv_level_entry_t* pe = pxval->pnext_level->phead; pe != NULL; pe = pe->pnext) {
|
|
sum += leafcount_aux(&pe->level_xvalue);
|
|
}
|
|
return sum;
|
|
}
|
|
}
|
|
|
|
boxed_xval_t i_x_leafcount_xfunc(boxed_xval_t* pbxval1) {
|
|
boxed_xval_t rv = box_ephemeral_val(
|
|
mv_from_int(
|
|
leafcount_aux(&pbxval1->xval)
|
|
)
|
|
);
|
|
|
|
if (pbxval1->is_ephemeral)
|
|
mlhmmv_xvalue_free(&pbxval1->xval);
|
|
|
|
return rv;
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
boxed_xval_t variadic_mapsum_xfunc(boxed_xval_t* pbxvals, int nxvals) {
|
|
// xxx to-do optmization: transfer arg 1 if it's ephemeral
|
|
mlhmmv_xvalue_t sum = mlhmmv_xvalue_alloc_empty_map();
|
|
for (int i = 0; i < nxvals; i++) {
|
|
if (pbxvals[i].xval.is_terminal)
|
|
continue;
|
|
mlhmmv_level_t* plevel = pbxvals[i].xval.pnext_level;
|
|
for (mlhmmv_level_entry_t* pe = plevel->phead; pe != NULL; pe = pe->pnext) {
|
|
// xxx do refs/copies correctly
|
|
mlhmmv_xvalue_t xval_copy = mlhmmv_xvalue_copy(&pe->level_xvalue);
|
|
sllmve_t e = (sllmve_t) { .value = pe->level_key, .free_flags = 0, .pnext = NULL };
|
|
mlhmmv_level_put_xvalue(sum.pnext_level, &e, &xval_copy);
|
|
}
|
|
}
|
|
boxed_xval_t rv = box_ephemeral_xval(sum);
|
|
|
|
for (int i = 0; i < nxvals; i++) {
|
|
if (pbxvals[i].is_ephemeral)
|
|
mlhmmv_xvalue_free(&pbxvals[i].xval);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
boxed_xval_t variadic_mapdiff_xfunc(boxed_xval_t* pbxvals, int nxvals) {
|
|
mlhmmv_xvalue_t diff = mlhmmv_xvalue_alloc_empty_map();
|
|
if (nxvals == 0) {
|
|
return box_ephemeral_xval(diff);
|
|
}
|
|
// xxx to-do optmization: transfer arg 1 if it's ephemeral
|
|
|
|
// xxx methodize
|
|
int i = 0;
|
|
if (!pbxvals[i].xval.is_terminal) {
|
|
mlhmmv_level_t* plevel = pbxvals[i].xval.pnext_level;
|
|
for (mlhmmv_level_entry_t* pe = plevel->phead; pe != NULL; pe = pe->pnext) {
|
|
// xxx do refs/copies correctly
|
|
mlhmmv_xvalue_t xval_copy = mlhmmv_xvalue_copy(&pe->level_xvalue);
|
|
sllmve_t e = (sllmve_t) { .value = pe->level_key, .free_flags = 0, .pnext = NULL };
|
|
mlhmmv_level_put_xvalue(diff.pnext_level, &e, &xval_copy);
|
|
}
|
|
if (pbxvals[i].is_ephemeral)
|
|
mlhmmv_xvalue_free(&pbxvals[i].xval);
|
|
}
|
|
|
|
for (i = 1; i < nxvals; i++) {
|
|
if (!pbxvals[i].xval.is_terminal) {
|
|
mlhmmv_level_t* plevel = pbxvals[i].xval.pnext_level;
|
|
for (mlhmmv_level_entry_t* pe = plevel->phead; pe != NULL; pe = pe->pnext) {
|
|
sllmve_t e = (sllmve_t) { .value = pe->level_key, .free_flags = 0, .pnext = NULL };
|
|
mlhmmv_level_remove(diff.pnext_level, &e);
|
|
}
|
|
}
|
|
if (pbxvals[i].is_ephemeral)
|
|
mlhmmv_xvalue_free(&pbxvals[i].xval);
|
|
}
|
|
|
|
return box_ephemeral_xval(diff);
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
// Precondition (validated before we're called): there is at least one argument
|
|
// which is the map to be unkeyed.
|
|
boxed_xval_t variadic_mapexcept_xfunc(
|
|
boxed_xval_t* pbxvals,
|
|
int nxvals)
|
|
{
|
|
MLR_INTERNAL_CODING_ERROR_IF(nxvals < 1);
|
|
|
|
boxed_xval_t* pinbxval = &pbxvals[0];
|
|
boxed_xval_t outbxval = box_ephemeral_xval(mlhmmv_xvalue_copy(&pinbxval->xval));
|
|
|
|
if (pinbxval->xval.is_terminal) { // non-map
|
|
return outbxval;
|
|
}
|
|
|
|
mlhmmv_level_t* poutlevel = outbxval.xval.pnext_level;
|
|
|
|
for (int i = 1; i < nxvals; i++) {
|
|
if (pbxvals[i].xval.is_terminal) {
|
|
mv_t* pkey = &pbxvals[i].xval.terminal_mlrval;
|
|
sllmve_t e = (sllmve_t) { .value = *pkey, .free_flags = 0, .pnext = NULL };
|
|
mlhmmv_level_remove(poutlevel, &e);
|
|
}
|
|
if (pbxvals[i].is_ephemeral)
|
|
mlhmmv_xvalue_free(&pbxvals[i].xval);
|
|
}
|
|
|
|
return outbxval;
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
// Precondition (validated before we're called): there is at least one argument
|
|
// which is the map to be unkeyed.
|
|
boxed_xval_t variadic_mapselect_xfunc(
|
|
boxed_xval_t* pbxvals,
|
|
int nxvals)
|
|
{
|
|
MLR_INTERNAL_CODING_ERROR_IF(nxvals < 1);
|
|
|
|
boxed_xval_t* pinbxval = &pbxvals[0];
|
|
boxed_xval_t outbxval = box_ephemeral_xval(mlhmmv_xvalue_alloc_empty_map());
|
|
|
|
if (pinbxval->xval.is_terminal) { // non-map
|
|
return outbxval;
|
|
}
|
|
|
|
mlhmmv_level_t* pinlevel = pinbxval->xval.pnext_level;
|
|
mlhmmv_level_t* poutlevel = outbxval.xval.pnext_level;
|
|
|
|
for (int i = 1; i < nxvals; i++) {
|
|
|
|
if (pbxvals[i].xval.is_terminal) {
|
|
mv_t* pkey = &pbxvals[i].xval.terminal_mlrval;
|
|
sllmv_t* pkeylist = sllmv_single_no_free(pkey);
|
|
int unused = 0;
|
|
mlhmmv_xvalue_t* pval = mlhmmv_level_look_up_and_ref_xvalue(pinlevel, pkeylist, &unused);
|
|
if (pval != NULL) {
|
|
mlhmmv_xvalue_t copyval = mlhmmv_xvalue_copy(pval);
|
|
// mlhmmv_level_put_xvalue copies key not value
|
|
mlhmmv_level_put_xvalue(poutlevel, pkeylist->phead, ©val);
|
|
}
|
|
sllmv_free(pkeylist);
|
|
}
|
|
if (pbxvals[i].is_ephemeral)
|
|
mlhmmv_xvalue_free(&pbxvals[i].xval);
|
|
|
|
}
|
|
|
|
return outbxval;
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
// Precondition: the caller has ensured that both arguments are string-valued terminals.
|
|
boxed_xval_t m_ss_splitnv_xfunc(boxed_xval_t* pstringval, boxed_xval_t* psepval) {
|
|
mlhmmv_xvalue_t map = mlhmmv_xvalue_alloc_empty_map();
|
|
char* input = mlr_strdup_or_die(pstringval->xval.terminal_mlrval.u.strv);
|
|
char* sep = psepval->xval.terminal_mlrval.u.strv;
|
|
int seplen = strlen(sep);
|
|
|
|
int i = 1;
|
|
char* walker = input;
|
|
char* piece;
|
|
while ((piece = mlr_strmsep(&walker, sep, seplen)) != NULL) {
|
|
mv_t key = mv_from_int(i);
|
|
mv_t val = mv_ref_type_infer_string_or_float_or_int(piece);
|
|
mlhmmv_level_put_terminal_singly_keyed(map.pnext_level, &key, &val);
|
|
i++;
|
|
}
|
|
free(input);
|
|
|
|
if (pstringval->is_ephemeral)
|
|
mlhmmv_xvalue_free(&pstringval->xval);
|
|
if (psepval->is_ephemeral)
|
|
mlhmmv_xvalue_free(&psepval->xval);
|
|
|
|
return box_ephemeral_xval(map);
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
// Precondition: the caller has ensured that both arguments are string-valued terminals.
|
|
boxed_xval_t m_ss_splitnvx_xfunc(boxed_xval_t* pstringval, boxed_xval_t* psepval) {
|
|
mlhmmv_xvalue_t map = mlhmmv_xvalue_alloc_empty_map();
|
|
char* input = mlr_strdup_or_die(pstringval->xval.terminal_mlrval.u.strv);
|
|
char* sep = psepval->xval.terminal_mlrval.u.strv;
|
|
int seplen = strlen(sep);
|
|
|
|
int i = 1;
|
|
char* walker = input;
|
|
char* piece;
|
|
while ((piece = mlr_strmsep(&walker, sep, seplen)) != NULL) {
|
|
mv_t key = mv_from_int(i);
|
|
// xxx do not ref here
|
|
mv_t val = mv_ref_type_infer_string(piece);
|
|
// xxx make clear the copy/ref semantics for mlhmmv put with scalar value.
|
|
// at the moment this does an mv_copy.
|
|
mlhmmv_level_put_terminal_singly_keyed(map.pnext_level, &key, &val);
|
|
i++;
|
|
}
|
|
|
|
free(input);
|
|
|
|
if (pstringval->is_ephemeral)
|
|
mlhmmv_xvalue_free(&pstringval->xval);
|
|
if (psepval->is_ephemeral)
|
|
mlhmmv_xvalue_free(&psepval->xval);
|
|
|
|
return box_ephemeral_xval(map);
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
// Precondition: the caller has ensured that all arguments are string-valued terminals.
|
|
boxed_xval_t m_sss_splitkv_xfunc(boxed_xval_t* pstringval, boxed_xval_t* ppairsepval, boxed_xval_t* plistsepval) {
|
|
mlhmmv_xvalue_t map = mlhmmv_xvalue_alloc_empty_map();
|
|
char* input = mlr_strdup_or_die(pstringval->xval.terminal_mlrval.u.strv);
|
|
char* listsep = plistsepval->xval.terminal_mlrval.u.strv;
|
|
char* pairsep = ppairsepval->xval.terminal_mlrval.u.strv;
|
|
int listseplen = strlen(listsep);
|
|
int pairseplen = strlen(pairsep);
|
|
|
|
int i = 1;
|
|
char* walker = input;
|
|
char* piece;
|
|
while ((piece = mlr_strmsep(&walker, listsep, listseplen)) != NULL) {
|
|
char* pair = piece;
|
|
char* left = mlr_strmsep(&pair, pairsep, pairseplen);
|
|
if (pair == NULL) {
|
|
mv_t key = mv_from_int(i);
|
|
mv_t val = mv_ref_type_infer_string_or_float_or_int(left);
|
|
mlhmmv_level_put_terminal_singly_keyed(map.pnext_level, &key, &val);
|
|
} else {
|
|
char* right = mlr_strmsep(&pair, pairsep, pairseplen);
|
|
mv_t key = mv_from_string(left, NO_FREE);
|
|
mv_t val = mv_ref_type_infer_string_or_float_or_int(right);
|
|
mlhmmv_level_put_terminal_singly_keyed(map.pnext_level, &key, &val);
|
|
}
|
|
i++;
|
|
}
|
|
|
|
free(input);
|
|
|
|
if (pstringval->is_ephemeral)
|
|
mlhmmv_xvalue_free(&pstringval->xval);
|
|
if (ppairsepval->is_ephemeral)
|
|
mlhmmv_xvalue_free(&ppairsepval->xval);
|
|
if (plistsepval->is_ephemeral)
|
|
mlhmmv_xvalue_free(&plistsepval->xval);
|
|
|
|
return box_ephemeral_xval(map);
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
// Precondition: the caller has ensured that all arguments are string-valued terminals.
|
|
boxed_xval_t m_sss_splitkvx_xfunc(boxed_xval_t* pstringval, boxed_xval_t* ppairsepval, boxed_xval_t* plistsepval) {
|
|
mlhmmv_xvalue_t map = mlhmmv_xvalue_alloc_empty_map();
|
|
char* input = mlr_strdup_or_die(pstringval->xval.terminal_mlrval.u.strv);
|
|
char* listsep = plistsepval->xval.terminal_mlrval.u.strv;
|
|
char* pairsep = ppairsepval->xval.terminal_mlrval.u.strv;
|
|
int listseplen = strlen(listsep);
|
|
int pairseplen = strlen(pairsep);
|
|
|
|
int i = 1;
|
|
char* walker = input;
|
|
char* piece;
|
|
while ((piece = mlr_strmsep(&walker, listsep, listseplen)) != NULL) {
|
|
char* pair = piece;
|
|
char* left = mlr_strmsep(&pair, pairsep, pairseplen);
|
|
if (pair == NULL) {
|
|
mv_t key = mv_from_int(i);
|
|
mv_t val = mv_ref_type_infer_string(left);
|
|
mlhmmv_level_put_terminal_singly_keyed(map.pnext_level, &key, &val);
|
|
} else {
|
|
char* right = mlr_strmsep(&pair, pairsep, pairseplen);
|
|
mv_t key = mv_from_string(left, NO_FREE);
|
|
mv_t val = mv_ref_type_infer_string(right);
|
|
mlhmmv_level_put_terminal_singly_keyed(map.pnext_level, &key, &val);
|
|
}
|
|
i++;
|
|
}
|
|
|
|
free(input);
|
|
|
|
if (pstringval->is_ephemeral)
|
|
mlhmmv_xvalue_free(&pstringval->xval);
|
|
if (ppairsepval->is_ephemeral)
|
|
mlhmmv_xvalue_free(&ppairsepval->xval);
|
|
if (plistsepval->is_ephemeral)
|
|
mlhmmv_xvalue_free(&plistsepval->xval);
|
|
|
|
return box_ephemeral_xval(map);
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
#define SB_JOIN_ALLOC_SIZE 128
|
|
|
|
// ----------------------------------------------------------------
|
|
// Precondition: the caller has ensured that the separator is a string-valued terminal.
|
|
boxed_xval_t s_ms_joink_xfunc(boxed_xval_t* pmapval, boxed_xval_t* psepval) {
|
|
if (pmapval->xval.is_terminal) {
|
|
return box_ephemeral_val(mv_absent());
|
|
}
|
|
|
|
string_builder_t* psb = sb_alloc(SB_JOIN_ALLOC_SIZE);
|
|
for (mlhmmv_level_entry_t* pentry = pmapval->xval.pnext_level->phead; pentry != NULL; pentry = pentry->pnext) {
|
|
// The string_builder object will copy the string so we can point into source string space,
|
|
// without a copy here, when possible.
|
|
char free_flags = 0;
|
|
char* sval = mv_maybe_alloc_format_val(&pentry->level_key, &free_flags);
|
|
sb_append_string(psb, sval);
|
|
if (free_flags)
|
|
free(sval);
|
|
if (pentry->pnext != NULL) {
|
|
sb_append_string(psb, psepval->xval.terminal_mlrval.u.strv);
|
|
}
|
|
}
|
|
|
|
char* sretval = sb_finish(psb);
|
|
sb_free(psb);
|
|
|
|
if (pmapval->is_ephemeral)
|
|
mlhmmv_xvalue_free(&pmapval->xval);
|
|
if (psepval->is_ephemeral)
|
|
mlhmmv_xvalue_free(&psepval->xval);
|
|
|
|
return box_ephemeral_val(
|
|
mv_from_string(sretval, FREE_ENTRY_VALUE)
|
|
);
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
// Precondition: the caller has ensured that the separator is a string-valued terminal.
|
|
boxed_xval_t s_ms_joinv_xfunc(boxed_xval_t* pmapval, boxed_xval_t* psepval) {
|
|
if (pmapval->xval.is_terminal) {
|
|
return box_ephemeral_val(mv_absent());
|
|
}
|
|
|
|
string_builder_t* psb = sb_alloc(SB_JOIN_ALLOC_SIZE);
|
|
for (mlhmmv_level_entry_t* pentry = pmapval->xval.pnext_level->phead; pentry != NULL; pentry = pentry->pnext) {
|
|
if (pentry->level_xvalue.is_terminal) {
|
|
// The string_builder object will copy the string so we can point into source string space,
|
|
// without a copy here, when possible.
|
|
char free_flags = 0;
|
|
char* sval = mv_maybe_alloc_format_val(&pentry->level_xvalue.terminal_mlrval, &free_flags);
|
|
sb_append_string(psb, sval);
|
|
if (free_flags)
|
|
free(sval);
|
|
if (pentry->pnext != NULL) {
|
|
sb_append_string(psb, psepval->xval.terminal_mlrval.u.strv);
|
|
}
|
|
}
|
|
}
|
|
|
|
char* sretval = sb_finish(psb);
|
|
sb_free(psb);
|
|
|
|
if (pmapval->is_ephemeral)
|
|
mlhmmv_xvalue_free(&pmapval->xval);
|
|
if (psepval->is_ephemeral)
|
|
mlhmmv_xvalue_free(&psepval->xval);
|
|
|
|
return box_ephemeral_val(
|
|
mv_from_string(sretval, FREE_ENTRY_VALUE)
|
|
);
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
// Precondition: the caller has ensured that the separators are string-valued terminals.
|
|
boxed_xval_t s_mss_joinkv_xfunc(boxed_xval_t* pmapval, boxed_xval_t* ppairsepval, boxed_xval_t* plistsepval) {
|
|
if (pmapval->xval.is_terminal) {
|
|
return box_ephemeral_val(mv_absent());
|
|
}
|
|
|
|
string_builder_t* psb = sb_alloc(SB_JOIN_ALLOC_SIZE);
|
|
for (mlhmmv_level_entry_t* pentry = pmapval->xval.pnext_level->phead; pentry != NULL; pentry = pentry->pnext) {
|
|
if (pentry->level_xvalue.is_terminal) {
|
|
// The string_builder object will copy the string so we can point into source string space,
|
|
// without a copy here, when possible.
|
|
|
|
char kfree_flags = 0;
|
|
char* skval = mv_maybe_alloc_format_val(&pentry->level_key, &kfree_flags);
|
|
sb_append_string(psb, skval);
|
|
if (kfree_flags)
|
|
free(skval);
|
|
|
|
sb_append_string(psb, ppairsepval->xval.terminal_mlrval.u.strv);
|
|
|
|
char vfree_flags = 0;
|
|
char* svval = mv_maybe_alloc_format_val(&pentry->level_xvalue.terminal_mlrval, &vfree_flags);
|
|
sb_append_string(psb, svval);
|
|
if (vfree_flags)
|
|
free(svval);
|
|
|
|
if (pentry->pnext != NULL) {
|
|
sb_append_string(psb, plistsepval->xval.terminal_mlrval.u.strv);
|
|
}
|
|
}
|
|
}
|
|
|
|
char* sretval = sb_finish(psb);
|
|
sb_free(psb);
|
|
|
|
if (pmapval->is_ephemeral)
|
|
mlhmmv_xvalue_free(&pmapval->xval);
|
|
if (ppairsepval->is_ephemeral)
|
|
mlhmmv_xvalue_free(&ppairsepval->xval);
|
|
if (plistsepval->is_ephemeral)
|
|
mlhmmv_xvalue_free(&plistsepval->xval);
|
|
|
|
return box_ephemeral_val(
|
|
mv_from_string(sretval, FREE_ENTRY_VALUE)
|
|
);
|
|
}
|