I hate code generation. It makes it difficult for callers to extend the grammar at runtime, as it require a build phase and write access to the disk. There's no reason we should need to generate code. Instead, let's just build up the grammar interpreter-style and return a Parser instance or something. If it's slow, we could be clever and create a class at runtime, which callers could instantiate when they need to use it.
Another possibility is to call make_parser once per unique parser and make its result deep-copyable such that the copies share everything but the memoization cache.
I hate code generation. It makes it difficult for callers to extend the grammar at runtime, as it require a build phase and write access to the disk. There's no reason we should need to generate code. Instead, let's just build up the grammar interpreter-style and return a Parser instance or something. If it's slow, we could be clever and create a class at runtime, which callers could instantiate when they need to use it.