This is an interesting change. While I think generally passing in
constant userdata is not terribly useful, the previous implementation
precluded it entirely. Interface types, for example, are often passed
directly and stored as constants (they hold pointers to their mutable
state).
Since we type erase this so it can be bound to the generic interface
object, non-pointer objects must be passed by reference to avoid
binding the parser interface to a temporary stack copy of the object.
This means we have to handle these cases slightly differently. Also,
while technically being classified as pointers, slices don't really
behave like pointers, which is understandable but annoying. There's a
bit of asymmetry here, as CommandBuilder(*u32) and CommandBuilder
(u32) both require an *u32 when binding the parser interface. This is
of course because pointers do not need to be rewrapped to be type
erased. The same code path could be used for both cases, but then the
user would have to pass in a pointer to a pointer, which actually
looks a bit silly because then it potentially means having to
do &&my_var.
Having thought about this more, it seems likely that complex converters
could benefit from being able to parse their arguments on the fly
without having to structure them into a rigid type. This is sort of a
get out of jail free card for custom converters as they can dump into
the user context type or whatever they want directly.
This needs a bit more thought. To support outside-in error writing,
converters need access to an allocator so they can create new buffer
writers. This can be done by passing in a pointer to an ArrayList
object directly, rather than an already bound writer.
I believe we've produced a superset of the functionality that was
present before rewriting all of the code.
There are still a lot of fiddly little details that need to be thought
through in order to produce something that is righteously flexible,
but I think this is in reasonable shape to start inventing real-world
uses for it.
Adding some tests, cleaning up a little bit of the allocation handling
(make better use of the arena allocators—we are definitely sort of
leaking memory at the moment), and writing documentation are still on
the roadmap.
I am resisting the urge to try to codegolf this into fewer lines. It's
going to end up being a sprawl, but it is what it is. The main part of
this that will actually require intelligent thought is the column
wrapping and alignment. I think I will probably be implementing a
custom writer style thing to handle that.
There are a lot of annoying loose odds and ends here. Choice types
should list all the choices. But we can't necessarily assume that an
enum-typed parameter is a choice type (only if it uses the default
converter). Perhaps the conversion stuff should be turned into an
interface that can also be responsible for converting the default
value and providing additional information. For now I will probably
just hack it so that I can move on to other things.
The main difference is that regular conversion now only happens after
the entire command line (including all subcommands) have been parsed.
This means that a failing normal converter won't prevent subcommand
eager converters from running. However, parsing errors can still
preclude eager converters and subcommand parsing from happening.
the checklist of things to do is continuing to dwindle. hooray. last
big feature push is help text generation. Then improving error
reporting. Then writing some tests. Then writing documentation.
Ay carumba.