Skip to content

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;
}
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:

glw_view_free_chain(ec->gr, ec->alloc);

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:

if(t->t_dynamic_eval & mask) {
  glw_view_eval_rpn0(t, ec);
  t->t_dynamic_eval = ec->dynamic_eval;
}

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