Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

Reflection

Reflection is the ability of a program to examine and introspect its own structure and behavior. Since C lacks built-in support, we're going to add a thin layer of types and values on top. By itself, the functionality described here doesn't look very useful, but it enables implementing other ideas.

Example:

struct hc_value v;
hc_value_init(&v, &HC_STRING)->as_string = strdup("foo");
hc_defer(hc_value_deinit(&v));
struct hc_value c;
hc_value_copy(&c, &v);
hc_defer(hc_value_deinit(&c));
assert(strcmp(c.as_string, v.as_string) == 0);

A type contains a name and a set of basic operations we want to be able to perform polymorphically.

struct hc_type {
  const char *name;
  
  void (*copy)(struct hc_value *dst, struct hc_value *src);
  void (*deinit)(struct hc_value *);
  void (*print)(const struct hc_value *, struct hc_stream *out);
  void (*write)(const struct hc_value *, struct hc_stream *out);
};

Values are implemented as tagged unions, with the option to store other kinds of values as void *.

struct hc_value {
  const struct hc_type *type;
  
  union {
    bool as_bool;
    hc_fix_t as_fix;
    int as_int;
    void *as_other;
    char *as_string;
    hc_time_t as_time;
  };
};

deinit is optional.

void hc_value_deinit(struct hc_value *v) {
  if (v->type->deinit) {
    v->type->deinit(v);
  }
}

copy is optional and defaults to bit-wise.

struct hc_value *hc_value_copy(struct hc_value *dst, struct hc_value *src) {
  const struct hc_type *t = src->type;
  
  if (t->copy) {
    dst->type = t;
    t->copy(dst, src);
  } else {
    *dst = *src;
  }

  return dst;
}

We'll add both raw (write) and formatted (print) output, formatted defaults to raw.

void hc_value_print(struct hc_value *v, struct hc_stream *out) {
  if (v->type->print) {
    v->type->print(v, out);
  } else {
    hc_value_write(v, out);
  }
}

The standard types are defined as constants.

Booleans:

static void bool_write(const struct hc_value *v, struct hc_stream *out) {
  hc_puts(out, v->as_bool ? "true" : "false");
}

const struct hc_type HC_BOOL = {
  .name = "Bool",
  .copy = NULL,
  .write = bool_write
};

Fixed-points:

static void fix_write(const struct hc_value *v, struct hc_stream *out) {
  hc_fix_print(v->as_fix, out);
}

const struct hc_type HC_FIX = {
  .name = "Fix",
  .copy = NULL,
  .write = fix_write
};

Integers:

static void int_write(const struct hc_value *v, struct hc_stream *out) {
  hc_printf(out, "%d", v->as_int);
}

const struct hc_type HC_INT = {
  .name = "Int",
  .copy = NULL,
  .write = int_write
};

The string type stands out in two ways; values are dynamically allocated, and it uses different syntax suitable for programmatic reading when being written to a stream.

static void string_copy(struct hc_value *dst, struct hc_value *src) {
  dst->as_string = strdup(src->as_string);
}

static void string_deinit(struct hc_value *v) {
  free(v->as_string);
}

static void string_print(const struct hc_value *v, struct hc_stream *out) {
  hc_puts(out, v->as_string);
}

static void string_write(const struct hc_value *v, struct hc_stream *out) {
  hc_putc(out, '"');
  string_print(v, out);
  hc_putc(out, '"');
}

const struct hc_type HC_STRING = {
  .name = "String",
  .copy = string_copy,
  .deinit = string_deinit,
  .print = string_print,
  .write = string_write
};

Time:

static void time_write(const struct hc_value *v, struct hc_stream *out) {
  hc_time_printf(&v->as_time, HC_TIME_FORMAT, out);
}

const struct hc_type HC_TIME = {
  .name = "Time",
  .copy = NULL,
  .write = time_write
};