diff options
author | wm4 <wm4@nowhere> | 2017-04-18 05:39:26 +0200 |
---|---|---|
committer | wm4 <wm4@nowhere> | 2017-04-18 05:39:26 +0200 |
commit | fab34a736248f4d1161067871ed2e1f657c4c5a6 (patch) | |
tree | 97ed6706d2fcdb624d5bc0d41cbeb3196a9ad1fe | |
parent | e335e3323932ca7e27ffbe99e7dc2447a83ba857 (diff) | |
download | mpv-property-expand-wip.tar.bz2 mpv-property-expand-wip.tar.xz |
introduce new property expand + function syntaxproperty-expand-wip
proof of concept
-rw-r--r-- | options/m_property.c | 227 | ||||
-rw-r--r-- | options/m_property.h | 13 |
2 files changed, 221 insertions, 19 deletions
diff --git a/options/m_property.c b/options/m_property.c index 9dad34fd1b..c362ed1a32 100644 --- a/options/m_property.c +++ b/options/m_property.c @@ -35,6 +35,8 @@ #include "m_property.h" #include "common/msg.h" #include "common/common.h" +#include "misc/ctype.h" +#include "player/client.h" struct m_property *m_property_list_find(const struct m_property *list, const char *name) @@ -216,7 +218,7 @@ static int m_property_do_bstr(const struct m_property *prop_list, bstr name, return m_property_do(NULL, prop_list, name0, action, arg, ctx); } -static void append_str(char **s, int *len, bstr append) +static void append_str_old(char **s, int *len, bstr append) { MP_TARRAY_GROW(NULL, *s, *len + append.len); if (append.len) @@ -249,28 +251,33 @@ static int expand_property(const struct m_property *prop_list, char **ret, char *append = s; if (!s && !silent_error && !raw) append = (r == M_PROPERTY_UNAVAILABLE) ? "(unavailable)" : "(error)"; - append_str(ret, ret_len, bstr0(append)); + append_str_old(ret, ret_len, bstr0(append)); } talloc_free(s); return skip; } -char *m_properties_expand_string(const struct m_property *prop_list, - const char *str0, void *ctx) +static char *m_legacy_expand(const struct m_property *prop_list, + char **str0, void *ctx) { char *ret = NULL; int ret_len = 0; bool skip = false; int level = 0, skip_level = 0; - bstr str = bstr0(str0); + bstr str = bstr0(*str0); + + goto start; while (str.len) { - if (level > 0 && bstr_eatstart0(&str, "}")) { + if (bstr_eatstart0(&str, "}")) { if (skip && level <= skip_level) skip = false; level--; + if (level == 0) + goto done; } else if (bstr_startswith0(str, "${") && bstr_find0(str, "}") >= 0) { str = bstr_cut(str, 2); + start: level++; // Assume ":" and "}" can't be part of the property name @@ -287,7 +294,7 @@ char *m_properties_expand_string(const struct m_property *prop_list, skip_level = level; } } else if (level == 0 && bstr_eatstart0(&str, "$>")) { - append_str(&ret, &ret_len, str); + append_str_old(&ret, &ret_len, str); break; } else { char c; @@ -307,10 +314,216 @@ char *m_properties_expand_string(const struct m_property *prop_list, } } +done: MP_TARRAY_APPEND(NULL, ret, ret_len, '\0'); + *str0 = str.start; return ret; } +// Special use within property expansion code. -1 (or all bits set, depending +// on signedness) is assumed not to be used by any MPV_FORMAT_* constant. +// If this is set, mpv_node.u.int64 is set to a mp_property_return value. +#define MP_FORMAT_ERROR ((mpv_format)-1) + +// After this, node.format == MPV_FORMAT_STRING, and node.u.string is set. +static void coerce_string(struct mpv_node *node) +{ + switch (node->format) { + case MPV_FORMAT_STRING: + break; + case MP_FORMAT_ERROR: { + int r = node->u.int64; + char *s = (r == M_PROPERTY_UNAVAILABLE) ? "(unavailable)" : "(error)"; + node->format = MPV_FORMAT_STRING; + node->u.string = talloc_strdup(NULL, s); + break; + } + case MPV_FORMAT_NONE: + node->format = MPV_FORMAT_STRING; + node->u.string = talloc_strdup(NULL, ""); + break; + default: ; + static const struct m_option type = { .type = CONF_TYPE_NODE }; + char *res = m_option_print(&type, node); + assert(res); + node->format = MPV_FORMAT_STRING; + node->u.string = res; + break; + } + + assert(node->format == MPV_FORMAT_STRING); + assert(node->u.string); +} + +struct expand_ctx { + const struct m_property *prop_list; + void *ctx; +}; + +static void evaluate_fn(struct expand_ctx *ctx, const char *name, + struct mpv_node *args, int num_args, + struct mpv_node *res) +{ + if (strcmp(name, "time") == 0) { + if (num_args != 1 || args[0].format != MPV_FORMAT_DOUBLE) + goto error; + res->format = MPV_FORMAT_STRING; + res->u.string = mp_format_time(args[0].u.double_, false); + return; + } else if (strcmp(name, "precise_time") == 0) { + if (num_args != 1 || args[0].format != MPV_FORMAT_DOUBLE) + goto error; + res->format = MPV_FORMAT_STRING; + res->u.string = mp_format_time(args[0].u.double_, true); + return; + } + +error: + res->format = MP_FORMAT_ERROR; + res->u.int64 = M_PROPERTY_ERROR; +} + +// Force *node to be MPV_FORMAT_STRING, and append s[0..len]. +static void append_str(struct mpv_node *node, char *s, ptrdiff_t len) +{ + if (len < 0) + len = strlen(s); + coerce_string(node); + bstr dst = bstr0(node->u.string); + bstr_xappend(NULL, &dst, (bstr){s, len}); + node->u.string = dst.start; +} + +// Return the index of the first character that can't be in a property name. +static size_t property_len(const char *s) +{ + const char *start = s; + while (mp_isalnum(s[0]) || s[0] == '/' || s[0] == '_' || s[0] == '-') + s++; + return s - start; +} + +// Interpret a run of text starting at *str (advances *str to the first +// character after the end of the run). If top_level==true, don't assume that +// "," or ")" ends the run. If the run consists of a single function call, and +// is not top_level, its result is returned verbatim, otherwise everything is +// converted and concatenated to a string (even on extra whitespace). +static void expand_text(struct expand_ctx *ctx, char **pstr, bool top_level, + struct mpv_node *res) +{ + char *str = *pstr; + *res = (struct mpv_node){0}; + char *error = NULL; + + while (str[0]) { + // mass-add normal characters + size_t len = strcspn(str, top_level ? "$" : ",)$"); + if (len) + append_str(res, str, len); + str += len; + + if (str[0] != '$') + break; + str++; + + // Various escapes + if (strchr("$},)", str[0])) { + append_str(res, str, 1); + str++; + continue; + } else if (top_level && str[0] == '>') { + append_str(res, str, -1); + str += strlen(str); + continue; + } + + if (str[0] == '{') { + str++; + char *s = m_legacy_expand(ctx->prop_list, &str, ctx->ctx); + append_str(res, s, -1); + talloc_free(s); + continue; + } + + // Parse an actual property query or function invocation + + len = property_len(str); + if (!len) { + error = "property name expected"; + goto done; + } + + char name[64]; + snprintf(name, sizeof(name), "%.*s", (int)len, str); + str += len; + + struct mpv_node val = {0}; + + if (str[0] == '(') { + // It's a function. + str++; + void *tmp = talloc_new(NULL); + struct mpv_node *args = NULL; + int num_args = 0; + while (1) { + expand_text(ctx, &str, false, &val); + MP_TARRAY_APPEND(tmp, args, num_args, val); + talloc_steal(tmp, node_get_alloc(&val)); + if (str[0] == ',') { + str++; + continue; + } else if (str[0] == ')') { + str++; + break; + } else { + error = "syntax error in function call"; + goto done; + } + } + + evaluate_fn(ctx, name, args, num_args, &val); + + talloc_free(tmp); + } else { + int r = m_property_do(NULL, ctx->prop_list, name, M_PROPERTY_GET_NODE, + &val, ctx->ctx); + if (r != M_PROPERTY_OK) { + val.format = MP_FORMAT_ERROR; + val.u.int64 = r; + } + } + + if (res->format == MPV_FORMAT_NONE) { + *res = val; + } else { + coerce_string(&val); + append_str(res, val.u.string, -1); + mpv_free_node_contents(&val); + } + } + +done: + if (error) { + append_str(res, "(error: ", -1); + append_str(res, error, -1); + append_str(res, ")", -1); + append_str(res, str, -1); + str += strlen(str); + } + + *pstr = str; +} + +char *m_properties_expand_string(const struct m_property *prop_list, + const char *str0, void *ctx) +{ + struct expand_ctx ec = {prop_list, ctx}; + struct mpv_node rnode; + expand_text(&ec, (char **)&str0, true, &rnode); + coerce_string(&rnode); + return rnode.u.string; +} + void m_properties_print_help_list(struct mp_log *log, const struct m_property *list) { diff --git a/options/m_property.h b/options/m_property.h index d6c8c5aab3..6fe1b495f2 100644 --- a/options/m_property.h +++ b/options/m_property.h @@ -154,18 +154,7 @@ bool m_property_split_path(const char *path, bstr *prefix, char **rem); void m_properties_print_help_list(struct mp_log *log, const struct m_property *list); -// Expand a property string. -// This function allows to print strings containing property values. -// ${NAME} is expanded to the value of property NAME. -// If NAME starts with '=', use the raw value of the property. -// ${NAME:STR} expands to the property, or STR if the property is not -// available. -// ${?NAME:STR} expands to STR if the property is available. -// ${!NAME:STR} expands to STR if the property is not available. -// General syntax: "${" ["?" | "!"] ["="] NAME ":" STR "}" -// STR is recursively expanded using the same rules. -// "$$" can be used to escape "$", and "$}" to escape "}". -// "$>" disables parsing of "$" for the rest of the string. +// Expand a property string. Free result with talloc_free(). char* m_properties_expand_string(const struct m_property *prop_list, const char *str, void *ctx); |