/*
* Copyright (C) 2001 Havoc Pennington
* Copyright (C) 2016 Alberts Muktupāvels
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "config.h"
#include
#include
#include
#include "meta-draw-spec-private.h"
#include "meta-theme.h"
#include "meta-theme-metacity-private.h"
typedef enum
{
POS_TOKEN_INT,
POS_TOKEN_DOUBLE,
POS_TOKEN_OPERATOR,
POS_TOKEN_VARIABLE,
POS_TOKEN_OPEN_PAREN,
POS_TOKEN_CLOSE_PAREN
} PosTokenType;
typedef enum
{
POS_OP_NONE,
POS_OP_ADD,
POS_OP_SUBTRACT,
POS_OP_MULTIPLY,
POS_OP_DIVIDE,
POS_OP_MOD,
POS_OP_MAX,
POS_OP_MIN
} PosOperatorType;
/**
* A token, as output by the tokeniser.
*
* \ingroup tokenizer
*/
typedef struct
{
PosTokenType type;
union
{
struct {
int val;
} i;
struct {
double val;
} d;
struct {
PosOperatorType op;
} o;
struct {
char *name;
GQuark name_quark;
} v;
} d;
} PosToken;
/**
* A computed expression in our simple vector drawing language.
* While it appears to take the form of a tree, this is actually
* merely a list; concerns such as precedence of operators are
* currently recomputed on every recalculation.
*
* Created by meta_draw_spec_new(), destroyed by meta_draw_spec_free().
* pos_eval() fills this with ...FIXME. Are tokens a tree or a list?
* \ingroup parser
*/
struct _MetaDrawSpec
{
/**
* If this spec is constant, this is the value of the constant;
* otherwise it is zero.
*/
gdouble value;
/** A list of tokens in the expression. */
PosToken *tokens;
/** How many tokens are in the tokens list. */
int n_tokens;
/** Does the expression contain any variables? */
gboolean constant : 1;
};
/**
* The type of a PosExpr: either integer, double, or an operation.
* \ingroup parser
*/
typedef enum
{
POS_EXPR_INT,
POS_EXPR_DOUBLE,
POS_EXPR_OPERATOR
} PosExprType;
/**
* Type and value of an expression in a parsed sequence. We don't
* keep expressions in a tree; if this is of type POS_EXPR_OPERATOR,
* the arguments of the operator will be in the array positions
* immediately preceding and following this operator; they cannot
* themselves be operators.
*
* \bug operator is char; it should really be of PosOperatorType.
* \ingroup parser
*/
typedef struct
{
PosExprType type;
union
{
double double_val;
int int_val;
char operator;
} d;
} PosExpr;
/**
* Frees an array of tokens. All the tokens and their associated memory
* will be freed.
*
* \param tokens an array of tokens to be freed
* \param n_tokens how many tokens are in the array.
*/
static void
free_tokens (PosToken *tokens,
int n_tokens)
{
int i;
/* n_tokens can be 0 since tokens may have been allocated more than
* it was initialized
*/
for (i = 0; i < n_tokens; i++)
if (tokens[i].type == POS_TOKEN_VARIABLE)
g_free (tokens[i].d.v.name);
g_free (tokens);
}
/**
* Tokenises a number in an expression.
*
* \param p a pointer into a string representing an operation; part of an
* expression somewhere, so not null-terminated
* \param end_return set to a pointer to the end of the number found; but
* not updated if no number was found at all
* \param next set to either an integer or a float token
* \param[out] err set to the problem if there was a problem
* \return TRUE if a valid number was found, FALSE otherwise (and "err" will
* have been set)
*
* \bug The "while (*start)..." part: what's wrong with strchr-ish things?
* \bug The name is wrong: it doesn't parse anything.
* \ingroup tokenizer
*/
static gboolean
parse_number (const char *p,
const char **end_return,
PosToken *next,
GError **err)
{
const char *start = p;
char *end;
gboolean is_float;
char *num_str;
while (*p && (*p == '.' || g_ascii_isdigit (*p)))
++p;
if (p == start)
{
char buf[7] = { '\0' };
buf[g_unichar_to_utf8 (g_utf8_get_char (p), buf)] = '\0';
g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_BAD_CHARACTER,
_("Coordinate expression contains character '%s' which is not allowed"),
buf);
return FALSE;
}
*end_return = p;
/* we need this to exclude floats like "1e6" */
num_str = g_strndup (start, p - start);
start = num_str;
is_float = FALSE;
while (*start)
{
if (*start == '.')
is_float = TRUE;
++start;
}
if (is_float)
{
next->type = POS_TOKEN_DOUBLE;
next->d.d.val = g_ascii_strtod (num_str, &end);
if (end == num_str)
{
g_set_error (err, META_THEME_ERROR,
META_THEME_ERROR_FAILED,
_("Coordinate expression contains floating point number '%s' which could not be parsed"),
num_str);
g_free (num_str);
return FALSE;
}
}
else
{
next->type = POS_TOKEN_INT;
next->d.i.val = strtol (num_str, &end, 10);
if (end == num_str)
{
g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED,
_("Coordinate expression contains integer '%s' which could not be parsed"),
num_str);
g_free (num_str);
return FALSE;
}
}
g_free (num_str);
g_assert (next->type == POS_TOKEN_INT || next->type == POS_TOKEN_DOUBLE);
return TRUE;
}
/**
* Parses a string and returns an operation.
*
* \param p a pointer into a string representing an operation; part of an
* expression somewhere, so not null-terminated
* \param len set to the length of the string found. Set to 0 if none is.
* \return the operation found. If none was, returns POS_OP_NONE.
*/
static PosOperatorType
op_from_string (const char *p,
int *len)
{
*len = 0;
switch (*p)
{
case '+':
*len = 1;
return POS_OP_ADD;
case '-':
*len = 1;
return POS_OP_SUBTRACT;
case '*':
*len = 1;
return POS_OP_MULTIPLY;
case '/':
*len = 1;
return POS_OP_DIVIDE;
case '%':
*len = 1;
return POS_OP_MOD;
case '`':
if (strncmp (p, "`max`", 5) == 0)
{
*len = 5;
return POS_OP_MAX;
}
else if (strncmp (p, "`min`", 5) == 0)
{
*len = 5;
return POS_OP_MIN;
}
default:
break;
}
return POS_OP_NONE;
}
/**
* Tokenises an expression.
*
* \param expr The expression
* \param[out] tokens_p The resulting tokens
* \param[out] n_tokens_p The number of resulting tokens
* \param[out] err set to the problem if there was a problem
*
* \return True if the expression was successfully tokenised; false otherwise.
*
* \ingroup tokenizer
*/
static gboolean
pos_tokenize (const char *expr,
PosToken **tokens_p,
int *n_tokens_p,
GError **err)
{
PosToken *tokens;
int n_tokens;
int allocated;
const char *p;
*tokens_p = NULL;
*n_tokens_p = 0;
allocated = 3;
n_tokens = 0;
tokens = g_new (PosToken, allocated);
p = expr;
while (*p)
{
PosToken *next;
int len;
if (n_tokens == allocated)
{
allocated *= 2;
tokens = g_renew (PosToken, tokens, allocated);
}
next = &tokens[n_tokens];
switch (*p)
{
case '*':
case '/':
case '+':
case '-': /* negative numbers aren't allowed so this is easy */
case '%':
case '`':
next->type = POS_TOKEN_OPERATOR;
next->d.o.op = op_from_string (p, &len);
if (next->d.o.op != POS_OP_NONE)
{
++n_tokens;
p = p + (len - 1); /* -1 since we ++p later */
}
else
{
g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED,
_("Coordinate expression contained unknown operator at the start of this text: \"%s\""),
p);
goto error;
}
break;
case '(':
next->type = POS_TOKEN_OPEN_PAREN;
++n_tokens;
break;
case ')':
next->type = POS_TOKEN_CLOSE_PAREN;
++n_tokens;
break;
case ' ':
case '\t':
case '\n':
break;
default:
if (g_ascii_isalpha (*p) || *p == '_')
{
/* Assume variable */
const char *start = p;
while (*p && (g_ascii_isalpha (*p) || *p == '_'))
++p;
g_assert (p != start);
next->type = POS_TOKEN_VARIABLE;
next->d.v.name = g_strndup (start, p - start);
++n_tokens;
--p; /* since we ++p again at the end of while loop */
}
else
{
/* Assume number */
const char *end;
if (!parse_number (p, &end, next, err))
goto error;
++n_tokens;
p = end - 1; /* -1 since we ++p again at the end of while loop */
}
break;
}
++p;
}
if (n_tokens == 0)
{
g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED,
_("Coordinate expression was empty or not understood"));
goto error;
}
*tokens_p = tokens;
*n_tokens_p = n_tokens;
return TRUE;
error:
g_assert (err == NULL || *err != NULL);
free_tokens (tokens, n_tokens);
return FALSE;
}
/* To do this we tokenize, replace variable tokens
* that are constants, then reassemble. The purpose
* here is to optimize expressions so we don't do hash
* lookups to eval them. Obviously it's a tradeoff that
* slows down theme load times.
*/
static gboolean
replace_constants (MetaThemeMetacity *metacity,
PosToken *tokens,
int n_tokens,
GError **err)
{
int i;
double dval;
int ival;
gboolean is_constant = TRUE;
/* Loop through tokenized string looking for variables to replace */
for (i = 0; i < n_tokens; i++)
{
PosToken *t = &tokens[i];
if (t->type == POS_TOKEN_VARIABLE)
{
if (meta_theme_metacity_lookup_int (metacity, t->d.v.name, &ival))
{
g_free (t->d.v.name);
t->type = POS_TOKEN_INT;
t->d.i.val = ival;
}
else if (meta_theme_metacity_lookup_float (metacity, t->d.v.name, &dval))
{
g_free (t->d.v.name);
t->type = POS_TOKEN_DOUBLE;
t->d.d.val = dval;
}
else
{
/* If we've found a variable that cannot be replaced then the
expression is not a constant expression and we want to
replace it with a GQuark */
t->d.v.name_quark = g_quark_from_string (t->d.v.name);
is_constant = FALSE;
}
}
}
return is_constant;
}
/**
* pos_eval_get_variable:
* @token: The token representing a variable
* @result: (out): The value of that variable; not set if the token did not
* represent a known variable
* @env: The environment within which @token should be evaluated
* @err: (out): Set to the problem if there was a problem
*
* There is a predefined set of variables which can appear in an expression.
* Here we take a token representing a variable, and return the current value
* of that variable in a particular environment.
* (The value is always an integer.)
*
* Returns: %TRUE if we found the variable asked for, %FALSE if we didn't
*/
static gboolean
pos_eval_get_variable (const PosToken *token,
gdouble *result,
const MetaPositionExprEnv *env,
GError **err)
{
GQuark quark;
quark = token->d.v.name_quark;
if (quark == g_quark_from_static_string ("width"))
{
*result = env->rect.width;
}
else if (quark == g_quark_from_static_string ("height"))
{
*result = env->rect.height;
}
else if (env->object_width >= 0 &&
quark == g_quark_from_static_string ("object_width"))
{
*result = env->object_width;
}
else if (env->object_height >= 0 &&
quark == g_quark_from_static_string ("object_height"))
{
*result = env->object_height;
}
else if (quark == g_quark_from_static_string ("left_width"))
{
*result = env->left_width;
}
else if (quark == g_quark_from_static_string ("right_width"))
{
*result = env->right_width;
}
else if (quark == g_quark_from_static_string ("top_height"))
{
*result = env->top_height;
}
else if (quark == g_quark_from_static_string ("bottom_height"))
{
*result = env->bottom_height;
}
else if (quark == g_quark_from_static_string ("mini_icon_width"))
{
*result = env->mini_icon_width;
}
else if (quark == g_quark_from_static_string ("mini_icon_height"))
{
*result = env->mini_icon_height;
}
else if (quark == g_quark_from_static_string ("icon_width"))
{
*result = env->icon_width;
}
else if (quark == g_quark_from_static_string ("icon_height"))
{
*result = env->icon_height;
}
else if (quark == g_quark_from_static_string ("title_width"))
{
*result = env->title_width;
}
else if (quark == g_quark_from_static_string ("title_height"))
{
*result = env->title_height;
}
else if (quark == g_quark_from_static_string ("frame_x_center"))
{
*result = env->frame_x_center;
}
else if (quark == g_quark_from_static_string ("frame_y_center"))
{
*result = env->frame_y_center;
}
else
{
g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_UNKNOWN_VARIABLE,
_("Coordinate expression had unknown variable or constant '%s'"),
token->d.v.name);
return FALSE;
}
return TRUE;
}
static gboolean
do_operation (PosExpr *a,
PosExpr *b,
PosOperatorType op,
GError **err)
{
/* Promote types to double if required */
if (a->type == POS_EXPR_DOUBLE ||
b->type == POS_EXPR_DOUBLE)
{
int int_val;
if (a->type != POS_EXPR_DOUBLE)
{
int_val = a->d.int_val;
a->type = POS_EXPR_DOUBLE;
a->d.double_val = int_val;
}
if (b->type != POS_EXPR_DOUBLE)
{
int_val = b->d.int_val;
b->type = POS_EXPR_DOUBLE;
b->d.double_val = int_val;
}
}
g_assert (a->type == b->type);
if (a->type == POS_EXPR_INT)
{
switch (op)
{
case POS_OP_MULTIPLY:
a->d.int_val = a->d.int_val * b->d.int_val;
break;
case POS_OP_DIVIDE:
if (b->d.int_val == 0)
{
g_set_error (err, META_THEME_ERROR,
META_THEME_ERROR_DIVIDE_BY_ZERO,
_("Coordinate expression results in division by zero"));
return FALSE;
}
a->d.int_val = a->d.int_val / b->d.int_val;
break;
case POS_OP_MOD:
if (b->d.int_val == 0)
{
g_set_error (err, META_THEME_ERROR,
META_THEME_ERROR_DIVIDE_BY_ZERO,
_("Coordinate expression results in division by zero"));
return FALSE;
}
a->d.int_val = a->d.int_val % b->d.int_val;
break;
case POS_OP_ADD:
a->d.int_val = a->d.int_val + b->d.int_val;
break;
case POS_OP_SUBTRACT:
a->d.int_val = a->d.int_val - b->d.int_val;
break;
case POS_OP_MAX:
a->d.int_val = MAX (a->d.int_val, b->d.int_val);
break;
case POS_OP_MIN:
a->d.int_val = MIN (a->d.int_val, b->d.int_val);
break;
case POS_OP_NONE:
default:
g_assert_not_reached ();
break;
}
}
else if (a->type == POS_EXPR_DOUBLE)
{
switch (op)
{
case POS_OP_MULTIPLY:
a->d.double_val = a->d.double_val * b->d.double_val;
break;
case POS_OP_DIVIDE:
if (b->d.double_val == 0.0)
{
g_set_error (err, META_THEME_ERROR,
META_THEME_ERROR_DIVIDE_BY_ZERO,
_("Coordinate expression results in division by zero"));
return FALSE;
}
a->d.double_val = a->d.double_val / b->d.double_val;
break;
case POS_OP_MOD:
if (b->d.double_val == 0.0)
{
g_set_error (err, META_THEME_ERROR,
META_THEME_ERROR_DIVIDE_BY_ZERO,
_("Coordinate expression results in division by zero"));
return FALSE;
}
a->d.double_val = fmod (a->d.double_val, b->d.double_val);
break;
case POS_OP_ADD:
a->d.double_val = a->d.double_val + b->d.double_val;
break;
case POS_OP_SUBTRACT:
a->d.double_val = a->d.double_val - b->d.double_val;
break;
case POS_OP_MAX:
a->d.double_val = MAX (a->d.double_val, b->d.double_val);
break;
case POS_OP_MIN:
a->d.double_val = MIN (a->d.double_val, b->d.double_val);
break;
case POS_OP_NONE:
default:
g_assert_not_reached ();
break;
}
}
else
g_assert_not_reached ();
return TRUE;
}
/**
* Represents an operation as a string.
*
* \param type an operation, such as addition
* \return a string, such as "+"
*/
static const char*
op_name (PosOperatorType type)
{
switch (type)
{
case POS_OP_ADD:
return "+";
case POS_OP_SUBTRACT:
return "-";
case POS_OP_MULTIPLY:
return "*";
case POS_OP_DIVIDE:
return "/";
case POS_OP_MOD:
return "%";
case POS_OP_MAX:
return "`max`";
case POS_OP_MIN:
return "`min`";
case POS_OP_NONE:
break;
default:
break;
}
return "";
}
static gboolean
do_operations (PosExpr *exprs,
int *n_exprs,
int precedence,
GError **err)
{
int i;
i = 1;
while (i < *n_exprs)
{
gboolean compress;
/* exprs[i-1] first operand
* exprs[i] operator
* exprs[i+1] second operand
*
* we replace first operand with result of mul/div/mod,
* or skip over operator and second operand if we have
* an add/subtract
*/
if (exprs[i-1].type == POS_EXPR_OPERATOR)
{
g_set_error (err, META_THEME_ERROR,
META_THEME_ERROR_FAILED,
_("Coordinate expression has an operator \"%s\" where an operand was expected"),
op_name (exprs[i-1].d.operator));
return FALSE;
}
if (exprs[i].type != POS_EXPR_OPERATOR)
{
g_set_error (err, META_THEME_ERROR,
META_THEME_ERROR_FAILED,
_("Coordinate expression had an operand where an operator was expected"));
return FALSE;
}
if (i == (*n_exprs - 1))
{
g_set_error (err, META_THEME_ERROR,
META_THEME_ERROR_FAILED,
_("Coordinate expression ended with an operator instead of an operand"));
return FALSE;
}
g_assert ((i+1) < *n_exprs);
if (exprs[i+1].type == POS_EXPR_OPERATOR)
{
g_set_error (err, META_THEME_ERROR,
META_THEME_ERROR_FAILED,
_("Coordinate expression has operator \"%c\" following operator \"%c\" with no operand in between"),
exprs[i+1].d.operator,
exprs[i].d.operator);
return FALSE;
}
compress = FALSE;
switch (precedence)
{
case 2:
switch (exprs[i].d.operator)
{
case POS_OP_DIVIDE:
case POS_OP_MOD:
case POS_OP_MULTIPLY:
compress = TRUE;
if (!do_operation (&exprs[i-1], &exprs[i+1],
exprs[i].d.operator,
err))
return FALSE;
break;
default:
break;
}
break;
case 1:
switch (exprs[i].d.operator)
{
case POS_OP_ADD:
case POS_OP_SUBTRACT:
compress = TRUE;
if (!do_operation (&exprs[i-1], &exprs[i+1],
exprs[i].d.operator,
err))
return FALSE;
break;
default:
break;
}
break;
/* I have no rationale at all for making these low-precedence */
case 0:
switch (exprs[i].d.operator)
{
case POS_OP_MAX:
case POS_OP_MIN:
compress = TRUE;
if (!do_operation (&exprs[i-1], &exprs[i+1],
exprs[i].d.operator,
err))
return FALSE;
break;
default:
break;
}
break;
default:
break;
}
if (compress)
{
/* exprs[i-1] first operand (now result)
* exprs[i] operator
* exprs[i+1] second operand
* exprs[i+2] new operator
*
* we move new operator just after first operand
*/
if ((i+2) < *n_exprs)
{
memmove (&exprs[i], &exprs[i+2],
sizeof (PosExpr) * (*n_exprs - i - 2));
}
*n_exprs -= 2;
}
else
{
/* Skip operator and next operand */
i += 2;
}
}
return TRUE;
}
/**
* Evaluates a sequence of tokens within a particular environment context,
* and returns the current value. May recur if parantheses are found.
*
* \param tokens A list of tokens to evaluate.
* \param n_tokens How many tokens are in the list.
* \param env The environment context in which to evaluate the expression.
* \param[out] result The current value of the expression
*
* \bug Yes, we really do reparse the expression every time it's evaluated.
* We should keep the parse tree around all the time and just
* run the new values through it.
* \ingroup parser
*/
static gboolean
pos_eval_helper (PosToken *tokens,
int n_tokens,
const MetaPositionExprEnv *env,
PosExpr *result,
GError **err)
{
/* Lazy-ass hardcoded limit on number of terms in expression */
#define MAX_EXPRS 32
int paren_level;
int first_paren;
int i;
PosExpr exprs[MAX_EXPRS];
int n_exprs;
int precedence;
/* Our first goal is to get a list of PosExpr, essentially
* substituting variables and handling parentheses.
*/
first_paren = 0;
paren_level = 0;
n_exprs = 0;
for (i = 0; i < n_tokens; i++)
{
PosToken *t = &tokens[i];
if (n_exprs >= MAX_EXPRS)
{
g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED,
_("Coordinate expression parser overflowed its buffer."));
return FALSE;
}
if (paren_level == 0)
{
gdouble double_val;
switch (t->type)
{
case POS_TOKEN_INT:
exprs[n_exprs].type = POS_EXPR_INT;
exprs[n_exprs].d.int_val = t->d.i.val;
++n_exprs;
break;
case POS_TOKEN_DOUBLE:
exprs[n_exprs].type = POS_EXPR_DOUBLE;
exprs[n_exprs].d.double_val = t->d.d.val;
++n_exprs;
break;
case POS_TOKEN_OPEN_PAREN:
++paren_level;
if (paren_level == 1)
first_paren = i;
break;
case POS_TOKEN_CLOSE_PAREN:
g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_BAD_PARENS,
_("Coordinate expression had a close parenthesis with no open parenthesis"));
return FALSE;
case POS_TOKEN_VARIABLE:
exprs[n_exprs].type = env->scale > 1 ? POS_EXPR_DOUBLE : POS_EXPR_INT;
/* FIXME we should just dump all this crap
* in a hash, maybe keep width/height out
* for optimization purposes
*/
if (!pos_eval_get_variable (t, &double_val, env, err))
return FALSE;
if (env->scale > 1)
exprs[n_exprs].d.double_val = double_val;
else
exprs[n_exprs].d.int_val = double_val;
++n_exprs;
break;
case POS_TOKEN_OPERATOR:
exprs[n_exprs].type = POS_EXPR_OPERATOR;
exprs[n_exprs].d.operator = t->d.o.op;
++n_exprs;
break;
default:
break;
}
}
else
{
g_assert (paren_level > 0);
switch (t->type)
{
case POS_TOKEN_INT:
case POS_TOKEN_DOUBLE:
case POS_TOKEN_VARIABLE:
case POS_TOKEN_OPERATOR:
break;
case POS_TOKEN_OPEN_PAREN:
++paren_level;
break;
case POS_TOKEN_CLOSE_PAREN:
if (paren_level == 1)
{
/* We closed a toplevel paren group, so recurse */
if (!pos_eval_helper (&tokens[first_paren+1],
i - first_paren - 1,
env,
&exprs[n_exprs],
err))
return FALSE;
++n_exprs;
}
--paren_level;
break;
default:
break;
}
}
}
if (paren_level > 0)
{
g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_BAD_PARENS,
_("Coordinate expression had an open parenthesis with no close parenthesis"));
return FALSE;
}
/* Now we have no parens and no vars; so we just do all the multiplies
* and divides, then all the add and subtract.
*/
if (n_exprs == 0)
{
g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED,
_("Coordinate expression doesn't seem to have any operators or operands"));
return FALSE;
}
/* precedence 1 ops */
precedence = 2;
while (precedence >= 0)
{
if (!do_operations (exprs, &n_exprs, precedence, err))
return FALSE;
--precedence;
}
g_assert (n_exprs == 1);
*result = *exprs;
return TRUE;
}
/*
* expr = int | double | expr * expr | expr / expr |
* expr + expr | expr - expr | (expr)
*
* so very not worth fooling with bison, yet so very painful by hand.
*/
/**
* Evaluates an expression.
*
* \param spec The expression to evaluate.
* \param env The environment context to evaluate the expression in.
* \param[out] val_p The integer value of the expression; if the expression
* is of type float, this will be rounded. If we return
* FALSE because the expression is invalid, this will be
* zero.
* \param[out] err The error, if anything went wrong.
*
* \return True if we evaluated the expression successfully; false otherwise.
*
* \bug Shouldn't spec be const?
* \ingroup parser
*/
static gboolean
pos_eval (MetaDrawSpec *spec,
const MetaPositionExprEnv *env,
gdouble *val_p,
GError **err)
{
PosExpr expr;
*val_p = 0;
if (pos_eval_helper (spec->tokens, spec->n_tokens, env, &expr, err))
{
switch (expr.type)
{
case POS_EXPR_INT:
*val_p = expr.d.int_val;
break;
case POS_EXPR_DOUBLE:
*val_p = expr.d.double_val;
break;
case POS_EXPR_OPERATOR:
default:
g_assert_not_reached ();
break;
}
return TRUE;
}
else
{
return FALSE;
}
}
/* We always return both X and Y, but only one will be meaningful in
* most contexts.
*/
static gboolean
parse_position_expression (MetaDrawSpec *spec,
const MetaPositionExprEnv *env,
gdouble *x_return,
gdouble *y_return,
GError **err)
{
/* All positions are in a coordinate system with x, y at the origin.
* The expression can have -, +, *, / as operators, floating point
* or integer constants, and the variables "width" and "height" and
* optionally "object_width" and object_height". Negative numbers
* aren't allowed.
*/
gdouble val;
if (spec->constant)
val = spec->value;
else
{
if (pos_eval (spec, env, &spec->value, err) == FALSE)
{
g_assert (err == NULL || *err != NULL);
return FALSE;
}
val = spec->value;
}
if (x_return)
*x_return = env->rect.x + val;
if (y_return)
*y_return = env->rect.y + val;
return TRUE;
}
static gboolean
parse_size_expression (MetaDrawSpec *spec,
const MetaPositionExprEnv *env,
gdouble *val_return,
GError **err)
{
gdouble val;
if (spec->constant)
val = spec->value;
else
{
if (pos_eval (spec, env, &spec->value, err) == FALSE)
{
g_assert (err == NULL || *err != NULL);
return FALSE;
}
val = spec->value;
}
if (val_return)
*val_return = MAX (val, 1); /* require that sizes be at least 1x1 */
return TRUE;
}
MetaDrawSpec *
meta_draw_spec_new (MetaThemeMetacity *metacity,
const gchar *expr,
GError **error)
{
MetaDrawSpec *spec;
spec = g_new0 (MetaDrawSpec, 1);
pos_tokenize (expr, &spec->tokens, &spec->n_tokens, NULL);
spec->constant = replace_constants (metacity, spec->tokens,
spec->n_tokens, NULL);
if (spec->constant)
{
gboolean result;
result = pos_eval (spec, NULL, &spec->value, error);
if (result == FALSE)
{
meta_draw_spec_free (spec);
return NULL;
}
}
return spec;
}
void
meta_draw_spec_free (MetaDrawSpec *spec)
{
if (!spec)
return;
free_tokens (spec->tokens, spec->n_tokens);
g_free (spec);
}
gdouble
meta_draw_spec_parse_x_position (MetaDrawSpec *spec,
const MetaPositionExprEnv *env)
{
gdouble retval;
GError *error;
retval = 0;
error = NULL;
if (!parse_position_expression (spec, env, &retval, NULL, &error))
{
g_warning (_("Theme contained an expression that resulted in an error: %s"),
error->message);
g_error_free (error);
}
return retval;
}
gdouble
meta_draw_spec_parse_y_position (MetaDrawSpec *spec,
const MetaPositionExprEnv *env)
{
gdouble retval;
GError *error;
retval = 0;
error = NULL;
if (!parse_position_expression (spec, env, NULL, &retval, &error))
{
g_warning (_("Theme contained an expression that resulted in an error: %s"),
error->message);
g_error_free (error);
}
return retval;
}
gdouble
meta_draw_spec_parse_size (MetaDrawSpec *spec,
const MetaPositionExprEnv *env)
{
gdouble retval;
GError *error;
retval = 0;
error = NULL;
if (!parse_size_expression (spec, env, &retval, &error))
{
g_warning (_("Theme contained an expression that resulted in an error: %s"),
error->message);
g_error_free (error);
}
return retval;
}