GLW View Expression Evaluator Analysis (glw_view_eval.c)¶
File: movian/src/ui/glw/glw_view_eval.c
Purpose: Runtime evaluation engine for GLW view expressions and property subscriptions
Lines: 7421 lines
Last Analyzed: 2024-11-06
Overview¶
The GLW view expression evaluator is the runtime engine that executes parsed expressions, manages property subscriptions, handles dynamic content cloning, and provides the reactive data binding system for GLW widgets. It implements a stack-based RPN evaluator with sophisticated property subscription management.
Core Architecture¶
Evaluation Context¶
typedef struct glw_view_eval_context {
glw_t *w; // Target widget
glw_root_t *gr; // GLW root
glw_scope_t *scope; // Variable scope
struct glw_prop_sub_slist *sublist; // Property subscriptions
errorinfo_t *ei; // Error information
const glw_rctx_t *rc; // Render context
prop_t *tgtprop; // Target property for assignments
token_t *stack; // Evaluation stack
token_t *alloc; // Allocated tokens for cleanup
int dynamic_eval; // Dynamic evaluation flags
int mask; // Evaluation mask
} glw_view_eval_context_t;
Property Subscription Types¶
enum {
GPS_VALUE, // Simple value subscription
GPS_VALUE_SLAVE, // Slave subscription (shares master's value)
GPS_CLONER, // Dynamic content cloning
GPS_COUNTER, // Count-only subscription
GPS_VECTORIZER, // Vector/array handling
GPS_EVENT_INJECTOR, // Event injection
};
Expression Evaluation Engine¶
Stack-Based RPN Evaluator¶
The evaluator uses a stack-based approach for RPN expressions:
static void eval_push(glw_view_eval_context_t *ec, token_t *t)
{
t->tmp = ec->stack;
ec->stack = t;
}
static token_t *eval_pop(glw_view_eval_context_t *ec)
{
token_t *r = ec->stack;
if(r != NULL)
ec->stack = r->tmp;
return r;
}
Arithmetic Operations¶
// Float operations
static float eval_op_fadd(float a, float b) { return a + b; }
static float eval_op_fsub(float a, float b) { return a - b; }
static float eval_op_fmul(float a, float b) { return a * b; }
static float eval_op_fdiv(float a, float b) { return a / b; }
static float eval_op_fmod(float a, float b) { return (int)a % (int)b; }
// Integer operations
static int eval_op_iadd(int a, int b) { return a + b; }
static int eval_op_isub(int a, int b) { return a - b; }
static int eval_op_imul(int a, int b) { return a * b; }
static int eval_op_imod(int a, int b) { return a % b; }
// Boolean operations
static int eval_op_xor(int a, int b) { return a ^ b; }
static int eval_op_or (int a, int b) { return a | b; }
static int eval_op_and(int a, int b) { return a & b; }
Type Conversion System¶
static int token2int(glw_view_eval_context_t *ec, token_t *t)
{
switch(t->type) {
case TOKEN_INT:
return t->t_int;
case TOKEN_EM:
ec->dynamic_eval |= GLW_VIEW_EVAL_EM;
return ec->w->glw_root->gr_current_size * t->t_float;
case TOKEN_FLOAT:
return t->t_float;
default:
return 0;
}
}
static float token2float(glw_view_eval_context_t *ec, token_t *t)
{
switch(t->type) {
case TOKEN_INT:
return t->t_int;
case TOKEN_EM:
ec->dynamic_eval |= GLW_VIEW_EVAL_EM;
return ec->w->glw_root->gr_current_size * t->t_float;
case TOKEN_FLOAT:
return t->t_float;
default:
return 0;
}
}
static int token2bool(token_t *t)
{
switch(t->type) {
case TOKEN_VOID:
return 0;
case TOKEN_RSTRING:
return !!rstr_get(t->t_rstring)[0];
case TOKEN_CSTRING:
return !!t->t_cstring[0];
case TOKEN_INT:
return !!t->t_int;
case TOKEN_FLOAT:
return !!t->t_float;
case TOKEN_IDENTIFIER:
return !strcmp(rstr_get(t->t_rstring), "true");
default:
return 1;
}
}
Property Subscription System¶
Property Subscription Structure¶
typedef struct glw_prop_sub {
SLIST_ENTRY(glw_prop_sub) gps_link;
SLIST_ENTRY(glw_prop_sub) gps_rpn_link;
glw_t *gps_widget; // Owner widget
prop_sub_t *gps_sub; // Property subscription
glw_scope_t *gps_scope; // Variable scope
token_t *gps_rpn; // RPN expression
struct glw_prop_sub_slist gps_slaves; // Slave subscriptions
union {
token_t *gps_token; // Current value token
struct glw_prop_sub *gps_master; // Master subscription (for slaves)
};
rstr_t *gps_file; // Source file
int gps_prop_name_id; // Property name ID
uint16_t gps_line; // Source line
uint16_t gps_type; // Subscription type
} glw_prop_sub_t;
Property Resolution¶
static int resolve_property_name(glw_view_eval_context_t *ec, token_t *a,
int follow_symlinks)
{
const char *pname[16];
prop_t *p;
glw_propname_to_array(pname, a);
p = prop_get_by_name(pname, follow_symlinks,
PROP_TAG_ROOT_VECTOR,
ec->scope->gs_roots, ec->scope->gs_num_roots,
PROP_TAG_ROOT, ec->w->glw_root->gr_prop_ui,
PROP_TAG_NAMED_ROOT, ec->w->glw_root->gr_prop_nav, "nav",
NULL);
// Transform TOKEN_PROPERTY_NAME -> TOKEN_PROPERTY_REF
a->type = TOKEN_PROPERTY_REF;
a->t_prop = p;
return 0;
}
Dynamic Content Cloning¶
Cloner Structure¶
typedef struct sub_cloner {
glw_prop_sub_t sc_sub;
token_t *sc_cloner_body; // Template to clone
const glw_class_t *sc_cloner_class; // Widget class for clones
struct glw_prop_sub_pending_queue sc_pending; // Pending items
prop_t *sc_pending_select; // Selection state
int sc_entries; // Number of cloned items
prop_t *sc_originating_prop; // Source property
glw_t *sc_anchor; // Anchor widget
char sc_positions_valid; // Position tracking valid
int sc_lowest_active; // Pagination tracking
int sc_highest_active;
char sc_have_more; // More items available
char sc_pending_more; // More items requested
struct clone_list sc_clones; // List of cloned widgets
} sub_cloner_t;
Clone Lifecycle¶
static void clone_eval(glw_clone_t *c, glw_scope_t *scope)
{
sub_cloner_t *sc = c->c_sc;
glw_view_eval_context_t n;
token_t *body = glw_view_clone_chain(c->c_w->glw_root, sc->sc_cloner_body, NULL);
const glw_class_t *gc = c->c_w->glw_class;
if(gc->gc_freeze != NULL)
gc->gc_freeze(c->c_w);
memset(&n, 0, sizeof(n));
n.scope = scope;
n.gr = c->c_w->glw_root;
n.w = c->c_w;
n.sublist = &n.w->glw_prop_subscriptions;
glw_view_eval_block(body, &n, NULL);
glw_view_free_chain(n.gr, body);
if(gc->gc_thaw != NULL)
gc->gc_thaw(c->c_w);
}
Pagination Support¶
static void cloner_pagination_check(sub_cloner_t *sc)
{
if(sc->sc_pending_more || !sc->sc_have_more)
return;
if(sc->sc_highest_active >= sc->sc_entries * 0.95 ||
sc->sc_highest_active == sc->sc_entries - 1) {
sc->sc_pending_more = 1;
if(sc->sc_sub.gps_sub != NULL)
prop_want_more_childs(sc->sc_sub.gps_sub);
}
}
Assignment Operations¶
Standard Assignment¶
static int eval_assign(glw_view_eval_context_t *ec, struct token *self, int how)
{
token_t *right = eval_pop(ec), *left = eval_pop(ec);
// Handle different assignment types
switch(left->type) {
case TOKEN_RESOLVED_ATTRIBUTE:
r = left->t_attrib->set(ec, left->t_attrib, right);
break;
case TOKEN_PROPERTY_REF:
switch(right->type) {
case TOKEN_RSTRING:
prop_set_rstring(left->t_prop, right->t_rstring);
break;
case TOKEN_INT:
prop_set_int(left->t_prop, right->t_int);
break;
case TOKEN_FLOAT:
prop_set_float(left->t_prop, right->t_float);
break;
case TOKEN_VOID:
if(left->t_flags & TOKEN_F_PROP_LINK) {
prop_unlink(left->t_prop);
left->t_flags &= ~TOKEN_F_PROP_LINK;
} else {
prop_set_void(left->t_prop);
}
break;
}
break;
}
eval_push(ec, right);
return r;
}
Link Assignment¶
static int eval_link_assign(glw_view_eval_context_t *ec, struct token *self)
{
token_t *right = eval_pop(ec), *left = eval_pop(ec);
// Resolve property references
if(right->type == TOKEN_PROPERTY_REF && left->type == TOKEN_PROPERTY_REF)
prop_link_ex(right->t_prop, left->t_prop, NULL, PROP_LINK_NORMAL, 0);
return 0;
}
Conditional Assignment¶
// Conditional assignment: rvalue of (void) results in doing nothing
if(how == 1 && right->type == TOKEN_VOID) {
eval_push(ec, right);
return 0;
}
Comparison Operations¶
Equality Testing¶
static int eval_eq(glw_view_eval_context_t *ec, struct token *self, int neq)
{
token_t *b = eval_pop(ec), *a = eval_pop(ec), *r;
int rr;
const char *aa, *bb;
if((aa = token_as_string(a)) != NULL &&
(bb = token_as_string(b)) != NULL) {
rr = !strcmp(aa, bb);
} else if(a->type == TOKEN_INT && b->type == TOKEN_FLOAT) {
rr = a->t_int == b->t_float;
} else if(a->type == TOKEN_FLOAT && b->type == TOKEN_INT) {
rr = a->t_float == b->t_int;
} else if(a->type != b->type) {
rr = 0;
} else {
switch(a->type) {
case TOKEN_INT:
rr = a->t_int == b->t_int;
break;
case TOKEN_FLOAT:
rr = a->t_float == b->t_float;
break;
case TOKEN_VOID:
rr = 1;
break;
default:
rr = 0;
}
}
r = eval_alloc(self, ec, TOKEN_INT);
r->t_int = rr ^ neq;
eval_push(ec, r);
return 0;
}
Null Coalescing¶
static int eval_null_coalesce(glw_view_eval_context_t *ec, struct token *self)
{
token_t *b = eval_pop(ec), *a = eval_pop(ec);
if((a = token_resolve(ec, a)) == NULL)
return -1;
if((b = token_resolve(ec, b)) == NULL)
return -1;
if(a->type == TOKEN_VOID) {
eval_push(ec, b);
} else {
eval_push(ec, a);
}
return 0;
}
Dynamic Evaluation System¶
Evaluation Triggers¶
static uint8_t signal_to_eval_mask[GLW_SIGNAL_num] = {
[GLW_SIGNAL_INACTIVE] = GLW_VIEW_EVAL_ACTIVE,
[GLW_SIGNAL_FHP_PATH_CHANGED] = GLW_VIEW_EVAL_FHP_CHANGE,
[GLW_SIGNAL_FOCUS_CHILD_INTERACTIVE] = GLW_VIEW_EVAL_OTHER,
[GLW_SIGNAL_FOCUS_CHILD_AUTOMATIC] = GLW_VIEW_EVAL_OTHER,
[GLW_SIGNAL_CAN_SCROLL_CHANGED] = GLW_VIEW_EVAL_OTHER,
[GLW_SIGNAL_FULLWINDOW_CONSTRAINT_CHANGED] = GLW_VIEW_EVAL_OTHER,
[GLW_SIGNAL_STATUS_CHANGED] = GLW_VIEW_EVAL_OTHER,
[GLW_SIGNAL_RESELECT_CHANGED] = GLW_VIEW_EVAL_OTHER,
};
Dynamic Expression Execution¶
static void run_dynamics(glw_t *w, glw_view_eval_context_t *ec, int mask)
{
ec->mask = mask;
ec->w = w;
ec->gr = w->glw_root;
ec->sublist = &w->glw_prop_subscriptions;
ec->scope = w->glw_scope;
token_t *t = w->glw_dynamic_expressions;
uint8_t all_flags = 0;
while(t != NULL) {
if(t->t_dynamic_eval & mask) {
glw_view_eval_rpn0(t, ec);
t->t_dynamic_eval = ec->dynamic_eval;
}
all_flags |= t->t_dynamic_eval;
t = t->next;
}
w->glw_dynamic_eval = all_flags;
glw_view_free_chain(ec->gr, ec->alloc);
}
Evaluation Flags¶
#define GLW_VIEW_EVAL_PROP 0x01 // Property changes
#define GLW_VIEW_EVAL_EM 0x02 // Font size changes
#define GLW_VIEW_EVAL_ACTIVE 0x04 // Widget activity changes
#define GLW_VIEW_EVAL_FHP_CHANGE 0x08 // Focus path changes
#define GLW_VIEW_EVAL_OTHER 0x10 // Other dynamic changes
Vector and Color Handling¶
RGB Color Parsing¶
static token_t *token_rgbstr_to_vec(token_t *t, glw_view_eval_context_t *ec)
{
const char *s, *s0;
int n = 0;
switch(t->type) {
case TOKEN_RSTRING:
s = rstr_get(t->t_rstring);
if(s[0] != '#')
return t;
s++;
s0 = s;
for(; *s; s++, n++) {
if(hexnibble(*s) == -1)
return t;
}
if(n == 3 || n == 6) {
t = eval_alloc(t, ec, TOKEN_VECTOR_FLOAT);
t->t_elements = 3;
rgbstr_to_floatvec(s0, t->t_float_vector);
}
return t;
default:
return t;
}
}
Vector Arithmetic¶
if(a->type == TOKEN_VECTOR_FLOAT && b->type == TOKEN_VECTOR_FLOAT) {
if(a->t_elements != b->t_elements)
return glw_view_seterr(ec->ei, self,
"Arithmetic op is invalid for "
"non-equal sized vectors");
r = eval_alloc(self, ec, TOKEN_VECTOR_FLOAT);
r->t_elements = a->t_elements;
for(i = 0; i < a->t_elements; i++)
r->t_float_vector[i] = f_fn(a->t_float_vector[i], b->t_float_vector[i]);
}
String Operations¶
String Concatenation¶
if((aa = token_as_string(a)) != NULL &&
(bb = token_as_string(b)) != NULL) {
// Concatenation of strings
int rich =
(a->type == TOKEN_RSTRING && a->t_rstrtype == PROP_STR_RICH) ||
(b->type == TOKEN_RSTRING && b->t_rstrtype == PROP_STR_RICH);
int al = rich && a->t_rstrtype == PROP_STR_UTF8 ?
html_enteties_escape(aa, NULL) - 1 : strlen(aa);
int bl = rich && b->t_rstrtype == PROP_STR_UTF8 ?
html_enteties_escape(bb, NULL) - 1 : strlen(bb);
r = eval_alloc(self, ec, TOKEN_RSTRING);
r->t_rstring = rstr_allocl(NULL, al + bl);
r->t_rstrtype = rich ? PROP_STR_RICH : PROP_STR_UTF8;
// Copy with HTML entity escaping if needed
if(rich && a->t_rstrtype == PROP_STR_UTF8)
html_enteties_escape(aa, rstr_data(r->t_rstring));
else
memcpy(rstr_data(r->t_rstring), aa, al);
if(rich && b->t_rstrtype == PROP_STR_UTF8)
html_enteties_escape(bb, rstr_data(r->t_rstring) + al);
else
memcpy(rstr_data(r->t_rstring) + al, bb, bl);
eval_push(ec, r);
return 0;
}
Memory Management¶
Token Allocation¶
static token_t *eval_alloc(token_t *src, glw_view_eval_context_t *ec, token_type_t type)
{
token_t *r = glw_view_token_alloc(ec->gr);
if(src->file != NULL)
r->file = rstr_dup(src->file);
r->line = src->line;
r->type = type;
r->next = ec->alloc;
ec->alloc = r;
return r;
}
Cleanup Management¶
All temporarily allocated tokens are tracked in ec->alloc and cleaned up after evaluation:
Performance Optimizations¶
Property Subscription Merging¶
Properties with the same name within an RPN expression share subscriptions:
// Property name IDs assigned during parsing enable subscription merging
if(t->t_prop_name_id == existing_id) {
// Reuse existing subscription
gps->gps_type = GPS_VALUE_SLAVE;
gps->gps_master = existing_subscription;
}
Lazy Evaluation¶
Dynamic expressions are only re-evaluated when their dependencies change:
Error Handling¶
Evaluation Errors¶
if((a = token_resolve(ec, a)) == NULL)
return -1; // Propagate resolution error
return glw_view_seterr(ec->ei, self, "Invalid assignment %s = %s",
token2name(left), token2name(right));
Resource Cleanup¶
Error paths ensure proper cleanup:
err:
while(stack != NULL)
glw_view_token_free(gr, tokenstack_pop(&stack));
while(outq.head != NULL)
glw_view_token_free(gr, tokenstack_pop(&outq.head));
return -1;
Accuracy Status¶
🟢 Verified: All information directly from source code analysis
Version: Based on Movian source as of 2024-11-06
Completeness: Analysis covers core evaluation engine, property system, and cloning mechanism
See Also¶
- GLW View Parser Analysis - Expression parsing and RPN conversion
- GLW View Attributes Analysis - Attribute handling system
- GLW Architecture Overview - Overall system architecture