Note: this is not really a README but more of a thought dump, for now. overall ------- obcl wants to be a generic configuration file loader. generic not in the sense that it will parse every single configuration format on the planet, but in the sense that it will parse a versatile, multi purpose format. parser ------ this is the part of obcl that when given a configuration file, it will return a parse tree. the parse tree can be mucked with by the programmer, or it can be checked and processed using the handy checker and processor. (for the most part; the programmer will do some processing work.) GList *config = cl_parse_file("foo.conf"); checker ------- the checker is supposed to help the programmer/application ensure that the configuration file is in the correct format. since obcl has a very general, the parser itself cannot guarantee that the user will enter a correct config file: foo { } fpp { } will both be parsed, but 'fpp' might be a mistake. the checker is intended to fix this. foo 5; blef "foo"; bar 5, "foo", "hi", 43.4; woop "hello" { foo 5; } CLChecker *check = cl_checker_new(); cl_checker_add(check, "foo", CL_NUM, 0); cl_checker_add(check, "blef", CL_STRING, 0); cl_checker_add(check, "bar", CL_NUM, CL_STRING, CL_STRING, CL_NUM, 0); cl_checker_add(check, "woop", CL_STRING, CL_BLOCK, 0); cl_checker_add_checker(check, "woop", check); /* add checker for block */ int val = cl_check(checker, config, stderr); /* write errors to stderr */ if (!val) { fprintf(stderr, "Parse errors found. Loading default settings\n"); ... } cl_checker_free(check); processor --------- the processor is intended to be run after the checker, to do actual 'stuff' with the parse tree. once the parse tree has been checked, we know that we can just access stuff in the parse tree willy nilly without sprinkling asserts all over the code. the processing is done via callbacks. typedef gpointer (*CLCallback)(CLNode *node, gpointer val); gpointer handle_foo(CLNode *node, gpointer conf) { /* here numval is a macro that expands to something like node->u.num. we know we can access this safely since the checker has verified that 'foo' nodes indeed contain numbers. same with NTH. it expands to somethign that we don't have to worry about. */ ((Config*)conf)->foo = NUMVAL(NTH(node, 1)); return 0; } gpointer handle_woop(CLNode *node, gpointer conf, CLProc *proc) { Config *conf1 = new_conf(); conf1->name = STRVAL(NTH(node,1)); cl_process(proc, BLOCK(node), conf1); conf_add_child((Config*)conf, conf1); return 0; } ... Config *process_config_file(char *file) { Config *conf; GList *parse_tree; CLProc *proc; CLChecker *check; int err; config = new_conf(); parse_tree = cl_parse_file(file); if (!parse_tree) { fprintf(stderr, "your config file is completely borked. loading defaults\n"); conf_load_defaults(conf); return conf; } checker = checker_new(); ... /* add checker stuff to checker, as per above */ err = cl_check(checker, parse_tree); if (err) { fprintf(stderr, "you fucked up. loading defaults\n"); config_load_defaults(conf); return conf; } CLProc *proc = cl_proc_new(); cl_proc_add(proc, "foo", handle_foo, conf); /* conf will be passed to callback */ ... cl_proc_add_block(proc, "woop", handle_woop, conf, proc); cl_process(proc, parse_tree); return conf; } something like that. lalala.