1048 lines
30 KiB
C
1048 lines
30 KiB
C
/* PIKA - Photo and Image Kooker Application
|
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
|
*
|
|
* Original copyright, applying to most contents (license remains unchanged):
|
|
* Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
|
|
*
|
|
* pikapropgui-eval.c
|
|
* Copyright (C) 2017 Ell
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Less General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* This is a simple interpreter for the GUM language (the GEGL UI Meta
|
|
* language), used in certain property keys of GEGL operations. What follows
|
|
* is a hand-wavy summary of the syntax and semantics (no BNF for you!)
|
|
*
|
|
* There are currently two types of expressions:
|
|
*
|
|
* Boolean expressions
|
|
* -------------------
|
|
*
|
|
* There are three types of simple boolean expressions:
|
|
*
|
|
* - Literal: Either `0` or `1`, evaluating to FALSE and TRUE, respectively.
|
|
*
|
|
* - Reference: Has the form `$key` or `$property.key`. Evaluates to the
|
|
* value of key `key`, which should itself be a boolean expression. In
|
|
* the first form, `key` refers to a key of the same property to which the
|
|
* currently-evaluated key belongs. In the second form, `key` refers to a
|
|
* key of `property`.
|
|
*
|
|
* - Dependency: Dependencies begin with the name of a property, on which
|
|
* the result depends. Currently supported property types are:
|
|
*
|
|
* - Boolean: The expression consists of the property name alone, and
|
|
* its value is the value of the property.
|
|
*
|
|
* - Enum: The property name shall be followed by a brace-enclosed,
|
|
* comma-separated list of enum values, given as nicks. The expression
|
|
* evaluates to TRUE iff the property matches any of the values.
|
|
*
|
|
* Complex boolean expressions can be formed using `!` (negation), `&`
|
|
* (conjunction), `|` (disjunction), and parentheses (grouping), following the
|
|
* usual precedence rules.
|
|
*
|
|
* String expressions
|
|
* ------------------
|
|
*
|
|
* There are three types of simple string expressions:
|
|
*
|
|
* - Literal: A string literal, surrounded by single quotes (`'`). Special
|
|
* characters (in particular, single quotes) can be escaped using a
|
|
* backslash (`\`).
|
|
*
|
|
* - Reference: Same as a boolean reference, but should refer to a key
|
|
* containing a string expression.
|
|
*
|
|
* - Deferred literal: Names a key, in the same fashion as a reference, but
|
|
* without the leading `$`. The value of this key is taken literally as
|
|
* the value of the expression. Deferred literals should usually be
|
|
* favored over inline string literals, because they can be translated
|
|
* independently of the expression.
|
|
*
|
|
* Currently, the only complex string expression is string selection: It has
|
|
* the form of a bracket-enclosed, comma-separated list of expressions of the
|
|
* form `<condition> : <value>`, where `<condition>` is a boolean expression,
|
|
* and `<value>` is a string expression. The result of the expression is the
|
|
* associated value of the first condition that evaluates to TRUE. If no
|
|
* condition is met, the result is NULL.
|
|
*
|
|
*
|
|
* Whitespace separating subexpressions is insignificant.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <gegl.h>
|
|
#include <gegl-paramspecs.h>
|
|
#include <gtk/gtk.h>
|
|
|
|
#include "propgui-types.h"
|
|
|
|
#include "pikapropgui-eval.h"
|
|
|
|
|
|
typedef enum
|
|
{
|
|
PIKA_PROP_EVAL_FAILED /* generic error condition */
|
|
} PikaPropEvalErrorCode;
|
|
|
|
|
|
static gboolean pika_prop_eval_boolean_impl (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar *key,
|
|
gint default_value,
|
|
GError **error,
|
|
gint depth);
|
|
static gboolean pika_prop_eval_boolean_or (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar **expr,
|
|
gchar **t,
|
|
GError **error,
|
|
gint depth);
|
|
static gboolean pika_prop_eval_boolean_and (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar **expr,
|
|
gchar **t,
|
|
GError **error,
|
|
gint depth);
|
|
static gboolean pika_prop_eval_boolean_not (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar **expr,
|
|
gchar **t,
|
|
GError **error,
|
|
gint depth);
|
|
static gboolean pika_prop_eval_boolean_group (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar **expr,
|
|
gchar **t,
|
|
GError **error,
|
|
gint depth);
|
|
static gboolean pika_prop_eval_boolean_simple (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar **expr,
|
|
gchar **t,
|
|
GError **error,
|
|
gint depth);
|
|
|
|
static gchar * pika_prop_eval_string_impl (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar *key,
|
|
const gchar *default_value,
|
|
GError **error,
|
|
gint depth);
|
|
static gchar * pika_prop_eval_string_selection (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar **expr,
|
|
gchar **t,
|
|
GError **error,
|
|
gint depth);
|
|
static gchar * pika_prop_eval_string_simple (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar **expr,
|
|
gchar **t,
|
|
GError **error,
|
|
gint depth);
|
|
|
|
static gboolean pika_prop_eval_parse_reference (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar **expr,
|
|
gchar **t,
|
|
GError **error,
|
|
GParamSpec **ref_pspec,
|
|
gchar **ref_key);
|
|
|
|
static gboolean pika_prop_eval_depth_test (gint depth,
|
|
GError **error);
|
|
|
|
static gchar * pika_prop_eval_read_token (const gchar **expr,
|
|
gchar **t,
|
|
GError **error);
|
|
static gboolean pika_prop_eval_is_name (const gchar *token);
|
|
|
|
#define PIKA_PROP_EVAL_ERROR (pika_prop_eval_error_quark ())
|
|
|
|
static GQuark pika_prop_eval_error_quark (void);
|
|
|
|
|
|
/* public functions */
|
|
|
|
gboolean
|
|
pika_prop_eval_boolean (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar *key,
|
|
gboolean default_value)
|
|
{
|
|
GError *error = NULL;
|
|
gboolean result;
|
|
|
|
result = pika_prop_eval_boolean_impl (config, pspec,
|
|
key, default_value, &error, 0);
|
|
|
|
if (error)
|
|
{
|
|
g_warning ("in object of type '%s': %s",
|
|
G_OBJECT_TYPE_NAME (config),
|
|
error->message);
|
|
|
|
g_error_free (error);
|
|
|
|
return default_value;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
gchar *
|
|
pika_prop_eval_string (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar *key,
|
|
const gchar *default_value)
|
|
{
|
|
GError *error = NULL;
|
|
gchar *result;
|
|
|
|
result = pika_prop_eval_string_impl (config, pspec,
|
|
key, default_value, &error, 0);
|
|
|
|
if (error)
|
|
{
|
|
g_warning ("in object of type '%s': %s",
|
|
G_OBJECT_TYPE_NAME (config),
|
|
error->message);
|
|
|
|
g_error_free (error);
|
|
|
|
return g_strdup (default_value);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/* private functions */
|
|
|
|
static gboolean
|
|
pika_prop_eval_boolean_impl (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar *key,
|
|
gint default_value,
|
|
GError **error,
|
|
gint depth)
|
|
{
|
|
const gchar *expr;
|
|
gchar *t = NULL;
|
|
gboolean result = FALSE;
|
|
|
|
if (! pika_prop_eval_depth_test (depth, error))
|
|
return FALSE;
|
|
|
|
expr = gegl_param_spec_get_property_key (pspec, key);
|
|
|
|
if (! expr)
|
|
{
|
|
/* we use `default_value < 0` to specify that the key must exist */
|
|
if (default_value < 0)
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"key '%s' of property '%s' not found",
|
|
key,
|
|
g_param_spec_get_name (pspec));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return default_value;
|
|
}
|
|
|
|
pika_prop_eval_read_token (&expr, &t, error);
|
|
|
|
if (! *error)
|
|
{
|
|
result = pika_prop_eval_boolean_or (config, pspec,
|
|
&expr, &t, error, depth);
|
|
}
|
|
|
|
/* check for trailing tokens at the end of the expression */
|
|
if (! *error && t)
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"invalid expression");
|
|
}
|
|
|
|
g_free (t);
|
|
|
|
if (*error)
|
|
{
|
|
g_prefix_error (error,
|
|
"in key '%s' of property '%s': ",
|
|
key,
|
|
g_param_spec_get_name (pspec));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static gboolean
|
|
pika_prop_eval_boolean_or (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar **expr,
|
|
gchar **t,
|
|
GError **error,
|
|
gint depth)
|
|
{
|
|
gboolean result;
|
|
|
|
if (! pika_prop_eval_depth_test (depth, error))
|
|
return FALSE;
|
|
|
|
result = pika_prop_eval_boolean_and (config, pspec,
|
|
expr, t, error, depth);
|
|
|
|
while (! *error && ! g_strcmp0 (*t, "|"))
|
|
{
|
|
pika_prop_eval_read_token (expr, t, error);
|
|
|
|
if (*error)
|
|
return FALSE;
|
|
|
|
/* keep evaluating even if `result` is TRUE, because we still need to
|
|
* parse the rest of the subexpression.
|
|
*/
|
|
result |= pika_prop_eval_boolean_and (config, pspec,
|
|
expr, t, error, depth);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static gboolean
|
|
pika_prop_eval_boolean_and (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar **expr,
|
|
gchar **t,
|
|
GError **error,
|
|
gint depth)
|
|
{
|
|
gboolean result;
|
|
|
|
if (! pika_prop_eval_depth_test (depth, error))
|
|
return FALSE;
|
|
|
|
result = pika_prop_eval_boolean_not (config, pspec,
|
|
expr, t, error, depth);
|
|
|
|
while (! *error && ! g_strcmp0 (*t, "&"))
|
|
{
|
|
pika_prop_eval_read_token (expr, t, error);
|
|
|
|
if (*error)
|
|
return FALSE;
|
|
|
|
/* keep evaluating even if `result` is FALSE, because we still need to
|
|
* parse the rest of the subexpression.
|
|
*/
|
|
result &= pika_prop_eval_boolean_not (config, pspec,
|
|
expr, t, error, depth);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static gboolean
|
|
pika_prop_eval_boolean_not (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar **expr,
|
|
gchar **t,
|
|
GError **error,
|
|
gint depth)
|
|
{
|
|
if (! pika_prop_eval_depth_test (depth, error))
|
|
return FALSE;
|
|
|
|
if (! g_strcmp0 (*t, "!"))
|
|
{
|
|
pika_prop_eval_read_token (expr, t, error);
|
|
|
|
if (*error)
|
|
return FALSE;
|
|
|
|
return ! pika_prop_eval_boolean_not (config, pspec,
|
|
expr, t, error, depth + 1);
|
|
}
|
|
|
|
return pika_prop_eval_boolean_group (config, pspec,
|
|
expr, t, error, depth);
|
|
}
|
|
|
|
static gboolean
|
|
pika_prop_eval_boolean_group (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar **expr,
|
|
gchar **t,
|
|
GError **error,
|
|
gint depth)
|
|
{
|
|
if (! pika_prop_eval_depth_test (depth, error))
|
|
return FALSE;
|
|
|
|
if (! g_strcmp0 (*t, "("))
|
|
{
|
|
gboolean result;
|
|
|
|
pika_prop_eval_read_token (expr, t, error);
|
|
|
|
if (*error)
|
|
return FALSE;
|
|
|
|
result = pika_prop_eval_boolean_or (config, pspec,
|
|
expr, t, error, depth + 1);
|
|
|
|
if (*error)
|
|
return FALSE;
|
|
|
|
if (g_strcmp0 (*t, ")"))
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"unterminated group");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
pika_prop_eval_read_token (expr, t, error);
|
|
|
|
return result;
|
|
}
|
|
|
|
return pika_prop_eval_boolean_simple (config, pspec,
|
|
expr, t, error, depth);
|
|
}
|
|
|
|
static gboolean
|
|
pika_prop_eval_boolean_simple (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar **expr,
|
|
gchar **t,
|
|
GError **error,
|
|
gint depth)
|
|
{
|
|
gboolean result;
|
|
|
|
if (! pika_prop_eval_depth_test (depth, error))
|
|
return FALSE;
|
|
|
|
if (! *t)
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"invalid expression");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* literal */
|
|
if (! strcmp (*t, "0"))
|
|
{
|
|
result = FALSE;
|
|
|
|
pika_prop_eval_read_token (expr, t, error);
|
|
}
|
|
else if (! strcmp (*t, "1"))
|
|
{
|
|
result = TRUE;
|
|
|
|
pika_prop_eval_read_token (expr, t, error);
|
|
}
|
|
/* reference */
|
|
else if (! strcmp (*t, "$"))
|
|
{
|
|
gchar *key;
|
|
|
|
pika_prop_eval_read_token (expr, t, error);
|
|
|
|
if (*error)
|
|
return FALSE;
|
|
|
|
if (! pika_prop_eval_parse_reference (config, pspec,
|
|
expr, t, error, &pspec, &key))
|
|
return FALSE;
|
|
|
|
result = pika_prop_eval_boolean_impl (config, pspec,
|
|
key, -1, error, depth + 1);
|
|
|
|
g_free (key);
|
|
}
|
|
/* dependency */
|
|
else if (pika_prop_eval_is_name (*t))
|
|
{
|
|
const gchar *property_name;
|
|
GParamSpec *pspec;
|
|
GType type;
|
|
|
|
property_name = *t;
|
|
|
|
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (config),
|
|
property_name);
|
|
|
|
if (! pspec)
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"property '%s' not found",
|
|
property_name);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
property_name = g_param_spec_get_name (pspec);
|
|
type = G_PARAM_SPEC_VALUE_TYPE (pspec);
|
|
|
|
if (g_type_is_a (type, G_TYPE_BOOLEAN))
|
|
{
|
|
g_object_get (config, property_name, &result, NULL);
|
|
}
|
|
else if (g_type_is_a (type, G_TYPE_ENUM))
|
|
{
|
|
GEnumClass *enum_class;
|
|
gint value;
|
|
|
|
pika_prop_eval_read_token (expr, t, error);
|
|
|
|
if (*error)
|
|
return FALSE;
|
|
|
|
if (g_strcmp0 (*t , "{"))
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"missing enum value set "
|
|
"for property '%s'",
|
|
property_name);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
enum_class = g_type_class_peek (type);
|
|
|
|
g_object_get (config, property_name, &value, NULL);
|
|
|
|
result = FALSE;
|
|
|
|
while (pika_prop_eval_read_token (expr, t, error) &&
|
|
pika_prop_eval_is_name (*t))
|
|
{
|
|
const gchar *nick;
|
|
GEnumValue *enum_value;
|
|
|
|
nick = *t;
|
|
enum_value = g_enum_get_value_by_nick (enum_class, nick);
|
|
|
|
if (! enum_value)
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"invalid enum value '%s' "
|
|
"for property '%s'",
|
|
nick,
|
|
property_name);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (value == enum_value->value)
|
|
result = TRUE;
|
|
|
|
pika_prop_eval_read_token (expr, t, error);
|
|
|
|
if (*error)
|
|
return FALSE;
|
|
|
|
if (! g_strcmp0 (*t, ","))
|
|
{
|
|
continue;
|
|
}
|
|
else if (! g_strcmp0 (*t, "}"))
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"invalid enum value set "
|
|
"for property '%s'",
|
|
property_name);
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (*error)
|
|
return FALSE;
|
|
|
|
if (g_strcmp0 (*t, "}"))
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"unterminated enum value set "
|
|
"for property '%s'",
|
|
property_name);
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"invalid type "
|
|
"for property '%s'",
|
|
property_name);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
pika_prop_eval_read_token (expr, t, error);
|
|
}
|
|
else
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"invalid expression");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static gchar *
|
|
pika_prop_eval_string_impl (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar *key,
|
|
const gchar *default_value,
|
|
GError **error,
|
|
gint depth)
|
|
{
|
|
const gchar *expr;
|
|
gchar *t = NULL;
|
|
gchar *result = NULL;
|
|
|
|
if (! pika_prop_eval_depth_test (depth, error))
|
|
return NULL;
|
|
|
|
expr = gegl_param_spec_get_property_key (pspec, key);
|
|
|
|
if (! expr)
|
|
return g_strdup (default_value);
|
|
|
|
pika_prop_eval_read_token (&expr, &t, error);
|
|
|
|
if (! *error)
|
|
{
|
|
result = pika_prop_eval_string_selection (config, pspec,
|
|
&expr, &t, error, depth);
|
|
}
|
|
|
|
/* check for trailing tokens at the end of the expression */
|
|
if (! *error && t)
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"invalid expression");
|
|
|
|
g_clear_pointer (&result, g_free);
|
|
}
|
|
|
|
g_free (t);
|
|
|
|
if (*error)
|
|
{
|
|
g_prefix_error (error,
|
|
"in key '%s' of property '%s': ",
|
|
key,
|
|
g_param_spec_get_name (pspec));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if (result)
|
|
return result;
|
|
else
|
|
return g_strdup (default_value);
|
|
}
|
|
|
|
static gchar *
|
|
pika_prop_eval_string_selection (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar **expr,
|
|
gchar **t,
|
|
GError **error,
|
|
gint depth)
|
|
{
|
|
if (! pika_prop_eval_depth_test (depth, error) || ! t)
|
|
return NULL;
|
|
|
|
if (! g_strcmp0 (*t, "["))
|
|
{
|
|
gboolean match = FALSE;
|
|
gchar *result = NULL;
|
|
|
|
if (! g_strcmp0 (pika_prop_eval_read_token (expr, t, error), "]"))
|
|
return NULL;
|
|
|
|
while (! *error)
|
|
{
|
|
gboolean cond;
|
|
gchar *value;
|
|
|
|
cond = pika_prop_eval_boolean_or (config, pspec,
|
|
expr, t, error, depth + 1);
|
|
|
|
if (*error)
|
|
break;
|
|
|
|
if (g_strcmp0 (*t, ":"))
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"missing string selection value");
|
|
|
|
break;
|
|
}
|
|
|
|
pika_prop_eval_read_token (expr, t, error);
|
|
|
|
if (*error)
|
|
break;
|
|
|
|
value = pika_prop_eval_string_selection (config, pspec,
|
|
expr, t, error, depth + 1);
|
|
|
|
if (*error)
|
|
break;
|
|
|
|
if (! match && cond)
|
|
{
|
|
match = TRUE;
|
|
result = value;
|
|
}
|
|
else
|
|
{
|
|
g_free (value);
|
|
}
|
|
|
|
if (! g_strcmp0 (*t, ","))
|
|
{
|
|
pika_prop_eval_read_token (expr, t, error);
|
|
|
|
continue;
|
|
}
|
|
else if (! g_strcmp0 (*t, "]"))
|
|
{
|
|
pika_prop_eval_read_token (expr, t, error);
|
|
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (*t)
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"invalid string selection");
|
|
}
|
|
else
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"unterminated string selection");
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (*error)
|
|
{
|
|
g_free (result);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
return pika_prop_eval_string_simple (config,
|
|
pspec, expr, t, error, depth);
|
|
}
|
|
|
|
static gchar *
|
|
pika_prop_eval_string_simple (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar **expr,
|
|
gchar **t,
|
|
GError **error,
|
|
gint depth)
|
|
{
|
|
gchar *result = NULL;
|
|
|
|
if (! pika_prop_eval_depth_test (depth, error))
|
|
return NULL;
|
|
|
|
/* literal */
|
|
if (*t && **t == '\'')
|
|
{
|
|
gchar *escaped;
|
|
|
|
escaped = g_strndup (*t + 1, strlen (*t + 1) - 1);
|
|
|
|
result = g_strcompress (escaped);
|
|
|
|
g_free (escaped);
|
|
|
|
pika_prop_eval_read_token (expr, t, error);
|
|
|
|
if (*error)
|
|
{
|
|
g_free (result);
|
|
|
|
return NULL;
|
|
}
|
|
}
|
|
/* reference */
|
|
else if (! g_strcmp0 (*t, "$"))
|
|
{
|
|
gchar *key;
|
|
|
|
pika_prop_eval_read_token (expr, t, error);
|
|
|
|
if (*error)
|
|
return NULL;
|
|
|
|
if (! pika_prop_eval_parse_reference (config, pspec,
|
|
expr, t, error, &pspec, &key))
|
|
return NULL;
|
|
|
|
result = pika_prop_eval_string_impl (config, pspec,
|
|
key, NULL, error, depth + 1);
|
|
|
|
g_free (key);
|
|
}
|
|
/* deferred literal */
|
|
else if (pika_prop_eval_is_name (*t))
|
|
{
|
|
GParamSpec *str_pspec;
|
|
gchar *str_key;
|
|
const gchar *str;
|
|
|
|
if (! pika_prop_eval_parse_reference (config, pspec,
|
|
expr, t, error,
|
|
&str_pspec, &str_key))
|
|
return NULL;
|
|
|
|
str = gegl_param_spec_get_property_key (str_pspec, str_key);
|
|
|
|
if (! str)
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"key '%s' of property '%s' not found",
|
|
str_key,
|
|
g_param_spec_get_name (str_pspec));
|
|
|
|
g_free (str_key);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
g_free (str_key);
|
|
|
|
result = g_strdup (str);
|
|
}
|
|
else
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"invalid expression");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static gboolean
|
|
pika_prop_eval_parse_reference (GObject *config,
|
|
GParamSpec *pspec,
|
|
const gchar **expr,
|
|
gchar **t,
|
|
GError **error,
|
|
GParamSpec **ref_pspec,
|
|
gchar **ref_key)
|
|
{
|
|
if (! pika_prop_eval_is_name (*t))
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"invalid reference");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
*ref_pspec = pspec;
|
|
*ref_key = g_strdup (*t);
|
|
|
|
pika_prop_eval_read_token (expr, t, error);
|
|
|
|
if (*error)
|
|
{
|
|
g_free (*ref_key);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (! g_strcmp0 (*t, "."))
|
|
{
|
|
gchar *property_name;
|
|
|
|
property_name = *ref_key;
|
|
|
|
if (! pika_prop_eval_read_token (expr, t, error) ||
|
|
! pika_prop_eval_is_name (*t))
|
|
{
|
|
if (! *error)
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"invalid reference");
|
|
}
|
|
|
|
g_free (property_name);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
*ref_pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (config),
|
|
property_name);
|
|
|
|
if (! *ref_pspec)
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"property '%s' not found",
|
|
property_name);
|
|
|
|
g_free (property_name);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
g_free (property_name);
|
|
|
|
*ref_key = g_strdup (*t);
|
|
|
|
pika_prop_eval_read_token (expr, t, error);
|
|
|
|
if (*error)
|
|
{
|
|
g_free (*ref_key);
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
pika_prop_eval_depth_test (gint depth,
|
|
GError **error)
|
|
{
|
|
/* make sure we don't recurse too deep. in particular, guard against
|
|
* circular references.
|
|
*/
|
|
if (depth == 100)
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"maximal nesting level exceeded");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gchar *
|
|
pika_prop_eval_read_token (const gchar **expr,
|
|
gchar **t,
|
|
GError **error)
|
|
{
|
|
const gchar *token;
|
|
|
|
g_free (*t);
|
|
*t = NULL;
|
|
|
|
/* skip whitespace */
|
|
while (g_ascii_isspace (**expr))
|
|
++*expr;
|
|
|
|
token = *expr;
|
|
|
|
if (*token == '\0')
|
|
return NULL;
|
|
|
|
/* name */
|
|
if (pika_prop_eval_is_name (token))
|
|
{
|
|
do { ++*expr; } while (g_ascii_isalnum (**expr) ||
|
|
**expr == '_' ||
|
|
**expr == '-');
|
|
}
|
|
/* string literal */
|
|
else if (token[0] == '\'')
|
|
{
|
|
for (++*expr; **expr != '\0' && **expr != '\''; ++*expr)
|
|
{
|
|
if (**expr == '\\')
|
|
{
|
|
++*expr;
|
|
|
|
if (**expr == '\0')
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (**expr == '\0')
|
|
{
|
|
g_set_error (error, PIKA_PROP_EVAL_ERROR, PIKA_PROP_EVAL_FAILED,
|
|
"unterminated string literal");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
++*expr;
|
|
}
|
|
/* punctuation or boolean literal */
|
|
else
|
|
{
|
|
++*expr;
|
|
}
|
|
|
|
*t = g_strndup (token, *expr - token);
|
|
|
|
return *t;
|
|
}
|
|
|
|
static gboolean
|
|
pika_prop_eval_is_name (const gchar *token)
|
|
{
|
|
return token && (g_ascii_isalpha (*token) || *token == '_');
|
|
}
|
|
|
|
static GQuark
|
|
pika_prop_eval_error_quark (void)
|
|
{
|
|
return g_quark_from_static_string ("pika-prop-eval-error-quark");
|
|
}
|