Very rough first draft at codegen module - #652
Conversation
|
Well, I did manage to get a second draft up and working, and it feels like it's starting to resemble an API... So the way I tried to work around this was
So it isn't exactly a 2 pass system of validation/generation, but it kind of behaves like one unless you're taking mutable references. I still haven't made anything |
|
|
||
| pub(crate) fn codegen_lex_flags(lex_flags: LexFlags) -> TokenStream { | ||
| pub(crate) fn build<LexerTypesT>( | ||
| &mut self, |
There was a problem hiding this comment.
It was only towards the end of getting this working that I realized we need to keep the builder around
after build() to call cgb.lex_diag() some more.
Thus we should probably come up with a better name than LexCodegenBuilder since it isn't exactly the builder pattern anymore. I was thinking maybe LexCodegenSourceEnv or something
There was a problem hiding this comment.
One more issue with this build method, is the list of arguments isn't easily extensible in the way a real builder would be. Maybe we should put all these args actually into the struct.
Edit: For this we'll probably need to add the Clone trait to LexerKind. I'm a bit uncertain about that
In particular when it comes to future expansion with LRStreamingLexerer I'd always imagine we would need to pass a data channel in through LexerKind using some kind of spsc which wouldn't necessarily need to impl clone?
I'll explore a different option.
There was a problem hiding this comment.
ba0891b was my attempt at both these issues,
I renamed LexCodegenBuilder to LexSrcEnv, and introduced a struct LexCodegenArgs for stuff which LexSrcEnv takes ownership of but can't easily be owned by it's Self.
|
This is starting to feel like we could start to consider pulling out of draft mode, and discuss making these Edit: Regarding lrpar, at least one thing I'm noticing is that lrpar needs mechanisms for dealing with warnings. Edit 2x: The caveat to this is that I'm pretty uncertain in my ability to actually anticipate where warnings would arise from though in the api |
| pub(crate) fn new( | ||
| src: &'a str, | ||
| path: &'a Path, | ||
| header: MarkMap<String, HeaderValue<Location>>, |
There was a problem hiding this comment.
Perhaps this header value seems like the biggest impediment to making any of this API public, as it is currently a #[doc(hidden)] part of the API. It stores all the things like all the LexFlags coming from the CTLexerBuilder.
It the parsed_header is subsequently built by the %grmtools section. Those two header values then get merged below.
I say this partly because there was a moment at some point where I was confused why we weren't dropping a bunch of CTLexerBuilder options on the floor.
One thing we can perhaps consider is making the constructor public,
so downstream you can construct an empty one, outside of CTLexerBuilder codegen options could only be set when embedded in the %grmtools section.
I haven't had much in the way of other good ideas here. It isn't something I actually paid any mind to when initially making the patch.
There was a problem hiding this comment.
Probably a better way is that we could just make a pub version of new without the header argument at all like...
pub fn new(src: &'a str, path: &'a path) -> Self {
Self::new_with_header(src, path, Header::new())
}
Not sure why I didn't think of it earlier.
There was a problem hiding this comment.
One slight step further to allow configurability of default values without making header public might be to make the type public but keep all the methods hidden, then we can have a way to obtain a Header from a CTLexerBuilder,
So you could call LexSrcEnv::new(src, path, ct_lexer_builder.header()). With that people could still configure it through the builders, but the type stays mostly opaque.
Edit:
Alternately we could consider just adding method like code_generator to CTLexerBuilder so the LexerSrcEnv could stay entirely private. I suppose the difficulty with that is that then there are two routes to generating code from the builder, the one people should use build(), and code_generator() that people will trip over.
|
Should mention this implements the lrlex parts of #648 |
|
I'm a bit behind on reviewing, but will hopefully get to this one soon! |
|
no worries, I understand, and also am also still trying to figure out an impl for lrpar. |
|
One thing that has occurred to me is that maybe it would have been nicer if I did one commit for each moved code block, instead of just moving a bunch of code blocks. Let me know if you think that kind of approach would help, I could probably split it up. Edit: On second thought it may not be possible without adding |
|
Apologies again for being slow on this one: I wanted to do my best to digest it and think about the consequences! I broadly get where this is going: I agree with you that trying to think of the right names is always challenging when doing this sort of refactoring. I'm intrigued by this:
Let's assume we could get such an API past the test suite: what would the impact on users be? Would we be able to provide them a painless upgrade path or would they have to hack things around? |
|
I did actually manage to get a two-pass API to work (I guess it would be two-ish, i'll explain), there were two primary things that allowed it to work, When I say logical error handling, I mean cases where the control flow is roughly like The thing to note is that if These two things, grabbing mutable borrows, and the reordering went a bit hand-in-hand. At least if you consider "code_generator(args)?" and I did forget to update my initial message. |
|
Interesting! I wonder if we should take a brief step back and ask ourselves: knowing what we know now, about how people use these tools, and how we would like them to be able to use them in the future, what would the perfect API look like? We could sketch that out, and then work out (a) how big a change it would be for us to make a reality (b) if there's a plausible upgrade path for users. The reason for this is that it might free us from some of the accidental nastiness that, inevitably, has been baked in over time as we've evolved things (and, of course, many bad decisions I took back in the early days!). |
|
I'm actually pretty happy with they way (this series) turned out at the moment. It's funny actually a lot of the things that were proving difficult are some of the things I've added (fairly recently even). The other thing that requires some finesse is things like the communication with cargo being So I think because of cargo we're always going to have this kind of control flow where we're borrowing references |
|
I'll definitely have a think on these things though, but definitely nothing instantly jumps out at me! |
|
Dumb idea: perhaps one of the problems is that we've hard-coded the output of codegen? I wonder if we had some sort of |
Here is a (not very pretty) first draft which is mostly just pulling code as-is out of
CTLexerBuilder::build()into smaller, more manageable functions. I don't think anything here is really properly named,codegenat times seems like it's doing more parsing/validation than actual code generation (where it never uses it's self type!)I thought that the splitting of the
CTTokenMapBuilderturned out okay, however it is quite a bit simpler with less moving parts.My thoughts are to try and rename the
LexCodegenstructure to something likeLexCodegenBuilder,and then try see if we can manage to come up with a good
Selftype for the codegen parts.Edit:
I haven't tried to make anything
pubhere yet justpub(crate), aiming for close to verbatim code movement.I do have an idea of what a second draft might look like, we'll see if it works soon...
But so far trying to make e.g. a two pass API for checking then codegen has proven elusive,
Primarily because of data dependencies between the checking phases, and callbacks like
lrpar_config, I've managed to get some nicer API to compile, but not pass the testsuite.