Skip to content

Instantly share code, notes, and snippets.

@mkmik
Created October 15, 2014 14:06
Show Gist options
  • Save mkmik/78fad4daace38a3a6cef to your computer and use it in GitHub Desktop.
Save mkmik/78fad4daace38a3a6cef to your computer and use it in GitHub Desktop.
/*
* This is dummy port of the Go option pattern to C.
*
* The option pattern is described somewhat obscurely in
* http://commandcenter.blogspot.nl/2014/01/self-referential-functions-and-design.html
*
* In Go it already suffers from some boilerplate explosion, but it's somewhat manageable.
* Here it's even worse, so I'm afraid this is just to be used for educational purposes.
*
* To be fair, it should be possible to throw even some more preprocessor magic to
* make it shorter and to avoid relying on some conventions in the naming of functions and types.
*
* However the greater weakness is the fact that C variadic arguments cannot be typed,
* while in Go you can enforce a single type for all subsequent varargs.
*/
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
// This hack allows us to count the number of args passed to a macro.
#define VA_NUM_ARGS(...) \
(sizeof(#__VA_ARGS__) == sizeof("") \
? 0 : VA_NUM_ARGS_IMPL(__VA_ARGS__, 16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1))
#define VA_NUM_ARGS_IMPL(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,N,...) N
typedef void (*opt_mutator_t)(void *args, void *opts);
#define MUTATE_OPTIONS(last_arg, opts) {\
va_list argp; \
va_start(argp, last_arg); \
for(int i=0; i<nargs; i++) { \
opt_mutator_t *f = va_arg(argp, opt_mutator_t*); \
(*f)(f, &(opts)); \
free(f); \
}\
}
#define DECLARE_OPTION_MUTATOR(m, mut) \
mut ## _t* m = malloc(sizeof(mut ## _t)); \
*(opt_mutator_t*)m = (opt_mutator_t)(mut)
#define BEGIN_OPTION(name, m, ...) \
name ## _opt_mutator_t* name(__VA_ARGS__) { \
DECLARE_OPTION_MUTATOR(m, name ## _opt_mutator);
#define END_OPTION(m) return (m); }
// ------------ ns_send
typedef struct {
int deadline;
int n_headers;
} ns_send_opts_t;
// ns_header option
typedef struct {
opt_mutator_t mutator;
char *k;
char *v;
} ns_header_opt_mutator_t;
void ns_header_opt_mutator(ns_header_opt_mutator_t *args, ns_send_opts_t *opts) {
printf("Adding header %s:%s\n", args->k, args->v);
// just an example
opts->n_headers++;
}
BEGIN_OPTION(ns_header, m, char* k, char* v) {
m->k = k;
m->v = v;
}
END_OPTION(m);
// ns_deadline option
typedef struct {
opt_mutator_t mutator;
int deadline;
} ns_deadline_opt_mutator_t;
void ns_deadline_opt_mutator(ns_deadline_opt_mutator_t *args, ns_send_opts_t *opts) {
opts->deadline = args->deadline;
}
BEGIN_OPTION(ns_deadline, m, int d) {
m->deadline = d;
}
END_OPTION(m)
// ns_send
#define ns_send(addr, body, ...) va_send(addr, body, VA_NUM_ARGS(__VA_ARGS__), ## __VA_ARGS__)
void va_send(char* addr, char* body, int nargs, ...) {
// some default values
ns_send_opts_t opts = {-1, 0};
MUTATE_OPTIONS(nargs, opts);
printf("sending %s to %s, deadline: %d, num headers: %d\n", body, addr, opts.deadline, opts.n_headers);
}
int main() {
ns_send("foo", "x", ns_header("content-type", "json"), ns_header("auth-key", "hunter2"));
ns_send("foo", "x", ns_header("content-type", "poo"));
ns_send("bar", "x", ns_deadline(3600), ns_header("foo", "bar"));
ns_send("bar", "x", ns_deadline(3600), ns_header("foo", "bar"));
ns_send("bar", "noopts");
// missing argument caught by the compiler
// ns_send("bar", ns_deadline(3600), ns_header("foo", "bar"));
// not caught by the compiler.
// ns_send("bar", "x", "y");
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment