Freeing parsers requires a garbage collector
At the moment, parsers can only be freed by allocating them in an arena using the __m
variants of each combinator and then freeing the entire arena at once. This, unfortunately, makes code using those combinators much uglier, and makes it more difficult to free parsers later.
This can be fixed by introducing a simple garbage collector, to be implemented using mark and sweep for now. (It may be possible to use reference counting by treating h_indirect
specially, but the following proposed interface allows that to be added later)
Each parser object will have two additional vtable methods:
-
free(HParser* parser)
: deallocate the parser itself and any instance data (theenv
pointer) -
trace(HParser* parser, void(*callback)(HParser* parser, void* trace_env), void* trace_env)
: Call the callback with each directly referenced parser and the passed trace_env object
Further, HParser
itself will gain a flags field with only one flag defined (mark
is set iff there is an active garbage collection and the parser is referenced by the collection's root set). HParser
may also gain a pointer to its managing garbage collector, to detect misuse (it is probably a bad idea to have multiple GC roots manage a single parser
Finally, a new type HGC
will be added to represent a garbage collection root set, with the following operations (plus any variants that are appropriate):
-
HGC* h_gc_new()
: Create a new GC root set -
void h_gc_ref(HGC* gc, HParser* p)
: Add an HParser to the root set; may be called multiple times with one parser. -
void h_gc_unref(HGC* gc, HParser* p)
: Remove an HParser from the root set; must be called exactly as many times ash_gc_ref
was. Once the reference count of a parser reaches 0, accessing it results in undefined behavior. (In particular, the parser may be freed immediately) -
void h_gc_collect(HGC* gc)
: Perform any deferred collection -
void h_gc_free(HGC* gc)
: Free the root set. Performs a collection if necessary, but does not remove anything from the root set. -
void h_gc_free_all(HGC* gc)
: Frees every parser in the root set, and then frees itself.
The GC facility is intended to be used as follows:
HGC* gc = h_gc_new();
HParser* json_parser = create_json_parser();
HParser* json_rpc_parser = create_jsonrpc_parser(json_parser);
h_gc_ref(gc, json_parser);
h_gc_ref(gc, json_rpc_parser);
// use parsers at will
h_gc_unref(gc, json_rpc_parser);
// json_parser is still accessible, but accessing the json_rpc_parser is undefined.
h_collect(gc); // optional
// The memory used by json_rpc_parser is guaranteed to be deallocated
h_gc_unref(gc, json_parser);
// Accessing json_parser is also undefined now
h_gc_free_all(gc);
// All parsers registered with gc are freed