Lrpar codegen - #657
Conversation
| src: &'a str, | ||
| // We store the path here so we can generate a module name from it if needed. | ||
| // But should never use it for filesystem interaction within this module. | ||
| path: &'a Path, |
There was a problem hiding this comment.
I think that in some future use-cases we might not even have a Path for a given input (e.g. if it's from stdin or a buffer in memory)? If so, I wonder if we should just make this Option<String> for the path? [Maybe not for this PR, but as a simple follow-up.] That might avoid a (borderline-but-not-quite pathological) case where the Path disappears and we don't get a valid pathname out of it later.
There was a problem hiding this comment.
I think we kind of need something here stdin, e.g. we derive the default mod_name from it, and error formatting will need some label for error printing, I do think it could be a string though.
| pub(crate) struct ParserBuildEnvArgs<'a> { | ||
| /// This allows the parser to originate from from a pre-parsed AST, rather than | ||
| /// parsing a grammar definition given as source string into an AST. | ||
| ast_originated: Option<&'a ASTWithValidityInfo>, |
There was a problem hiding this comment.
I ideally wondered if there are only two routes to us getting the AST: pre-parsed AST or string. I think "there are only those two routes" but perhaps I'm being narrow-minded!
Either way, I wondered if we could maybe tweak this field name a bit. Maybe just ast_validity_info with a docstring like "If the input came from a preparsed AST, this will be Some"?
| } | ||
|
|
||
| impl<'a> ParserSrcEnv<'a> { | ||
| pub(crate) fn new_with_defaults( |
There was a problem hiding this comment.
Will there ever be another new method? Should we just call this new?
There was a problem hiding this comment.
I originally had it named new, but changed it in case we want a method new like so:
pub fn new(src, path) -> Self {
self.new_with_defaults(src, path, Header::new())
}
The thought was that this could allow us to make Codegen public without making Header public,
the public API though would only be usable with grammars that have filled out grmtools sections.
And wouldn't allow specifying default values via the header. So a little less flexible than what the builder can do, but something.
There was a problem hiding this comment.
Maybe this method should be new_with_header?
There was a problem hiding this comment.
Definitely a better name.
| } | ||
| } | ||
|
|
||
| fn extract_ast_validation( |
There was a problem hiding this comment.
Any reason to prefix these methods with extract_? I'm not saying it's wrong, but I wondered if it has more semantic implication than is immediately obvious to me. I also suspect these should document that they mark headers as used?
There was a problem hiding this comment.
I had meant to rename these resolve_ but I forgot, is resolve any better?
Essentially these are resolving them to some final/absolute value, from all the possible ways they could have defaults set for them.
| } | ||
| } | ||
|
|
||
| pub(crate) fn build_env<LexerTypesT>( |
There was a problem hiding this comment.
Dumb question: could/should this ever be called more than once? If "no" should this consume self?
There was a problem hiding this comment.
I think calling it more than once would be a waste and inefficient, there is seems no actual harm in it though.
(I should say, there is no reason to, but there is no reason it couldn't either)
However his can't really consume self though:
Codegen::generate() and many functions (all error handling) after build_env require a &ParserSrcEnv reference. I didn't really think about this, and whether BuildEnv should take ownership of the SrcEnv after the call to build_env though.
There was a problem hiding this comment.
FWIW, it looks like Codegen::generate also uses it for error handling.
One thing to note though is what I said in the PR description:
This may not be totally perfect basis for traits and external usage, for example it currently returns Box instead of typed errors.
As an example nimbleparse_lsp wants to send spans directly to the editor, so it wants a totally separate error formatting code than tools like nimbleparse or CTParserBuilder.
Perhaps that changes things.
There was a problem hiding this comment.
So, it appears that not all of these errors actually have typed errors (aren't coming from a YaccGrammarError).
For instance the following error, I'm not exactly certain why this error is only being caught so late.
It's the only one that can happen during generate(...), and so is why we need to pass in a src_env.
grmtools/lrpar/src/lib/ctbuilder.rs
Lines 1659 to 1663 in 3a9d88f
I'm assuming if we can't catch it earlier for some reason, we could always add a CodegenError type?
| } | ||
| } | ||
|
|
||
| impl<'a, LexerTypesT> ParserBuildEnv<'a, LexerTypesT> |
There was a problem hiding this comment.
Given that this is struct is private to the crate, I wondered if these getter methods are serving a useful purpose?
There was a problem hiding this comment.
So, the only real vital getter that I recall is fn ast_validation it is vital, because that that is how we get the list of warnings, to error from warnings_are_errors in CTParserBuilder.
A lot of the other getters are less vital, the reason they were added was that I couldn't keep into self while codegen was partially migrated from CTParserBuilder to codegen, where we didn't have access to the private variables from outside the module.
But what that does mean, is that if we have an external Codegen trait, that the ParserCodegen struct could be derived from an external source. So if we did make it public, there are getters for all the things needed by the codegen module itself.
I don't exactly know about trait boundaries and the like given that e.g. BuildEnv::code_generator actually returns a CodeGen whether that would make it impossible to use with some external codegen impl.
That was my thinking behind keeping them anyways.
| &self.stable | ||
| } | ||
|
|
||
| pub(crate) fn take_parser( |
There was a problem hiding this comment.
This one's interesting because it consumes self. Why/when do we use it in that way?
There was a problem hiding this comment.
This is for the return values to CTParserBuilder::build we construct a CTParser from it in the Ok case.
And I think we need to take ownership of the StateTable and StateGraph for CTConflictsError in some of the error cases too.
| // `lrpar::Node`` is deprecated within the lrpar crate, but not from within this module, | ||
| // Once it is removed from `lrpar`, we should move the declaration here entirely. | ||
| Some(quote! { | ||
| #[allow(unused_imports)] |
There was a problem hiding this comment.
We can probably dedent this a bit (cargo fmt doesn't tend to deal well with macros, so we often have to do it by hand).
I think this is probably as reviewable as I'm going to manage to make this large of a patch.
The basic idea behind this patch is to have a kind of "functional pipeline", for doing code generation,
(SrcEnv?, BuildEnvArgs) -> BuildEnv? -> Codegen(SrcEnv, BuildEnv) -> rust_code?A bit of an oversimplification, as there are some other minor details...
BuildEnvArgsis kind of a minimalist equivalent of the current builder, it's just full ofOptionvalues.BuildEnvis full ofderivedvalues, it mostly strips off theOption, but it also contains values likeASTWithValidityInfothat are derived from all the other args.Codegenthen owns theYaccGrammar,StateTableandStateGraphs, which you can take ownership of after generating code.This may not be totally perfect basis for traits and external usage, for example it currently returns
Box<dyn Error>instead of typed errors. But it should be a pretty faithful conversion of the existing process, into a more targeted/self contained module.