Skip to content

Lrpar codegen - #657

Open
ratmice wants to merge 19 commits into
softdevteam:masterfrom
ratmice:lrpar_codegen4
Open

Lrpar codegen#657
ratmice wants to merge 19 commits into
softdevteam:masterfrom
ratmice:lrpar_codegen4

Conversation

@ratmice

@ratmice ratmice commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

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...

  1. BuildEnvArgs is kind of a minimalist equivalent of the current builder, it's just full of Option values.
  2. BuildEnv is full of derived values, it mostly strips off the Option, but it also contains values like ASTWithValidityInfo that are derived from all the other args.
  3. Codegen then owns the YaccGrammar, StateTable and StateGraphs, 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.

Comment thread lrpar/src/lib/codegen.rs
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,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@ratmice ratmice Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

e.g. often in the case of stdin this will just be the text "stdin" or something to that effect, for instance the gnu assembler will use the text "standard input"

$ echo bad_instruction | as -
Assembler messages:
{standard input}:1: Error: no such instruction: `bad_instruction'

Part of the difficulty with turning it into string then is also that SpannedDiagnosticFormatter::new() accepts a path, but it seems like maybe despite it's convenience the diagnostic formatter might not actually belong in the SrcEnv, if we just derive a module name from it we should be able to turn it into a string.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I wasn't very clear! What I mean is: if we only use this for a later mod name, we could create the mod name here, rather than carry around a Path.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense, so once we've removed SpannedDiagnosticsFormatter I think that should work!

Comment thread lrpar/src/lib/codegen.rs Outdated
Comment thread lrpar/src/lib/codegen.rs Outdated
Comment thread lrpar/src/lib/codegen.rs Outdated
}
}

fn extract_ast_validation(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@ratmice ratmice Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went ahead and renamed these resolve_* in e53376e and docs added in c452dd4.

Let me know if you prefer/can think of a better naming scheme

Comment thread lrpar/src/lib/codegen.rs
}
}

pub(crate) fn build_env<LexerTypesT>(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dumb question: could/should this ever be called more than once? If "no" should this consume self?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@ratmice ratmice Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

s.push_str(&diag.underline_span_with_text(
rule_span,
"Rule missing action type".to_string(),
'^',
));

I'm assuming if we can't catch it earlier for some reason, we could always add a CodegenError type?

@ratmice ratmice Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the reason this is caught so late, was that long ago GrammarAST could be constructed manually (without a YaccKind), and you could create a YaccGrammar from that. Thus there was nowhere to know whether or not an action type was required or not, because by that point the yacc_kind was lost.

There seems multiple places we could do it, but I might gravitate towards passing the YaccKind in as a parameter to complete_and_validate? Though perhaps not, I don't immediately see how to get the actiontype from the ast, because the YaccParser.global_actiontype is outside the AST. There is the Rule.actiont, which global_actiontype gets cloned into for YaccKind::Original, and part of the rule syntax for YaccKind::Grmtools.

@ratmice ratmice Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, there was more error handling in gen_user_actions I hadn't noticed regarding action code like $var parsing during gen_user_actions.

I do think it'll probably end up needing a enum CodegenError, just for other kinds of errors though
for separating YaccGrammarError from those originating from proc_macro2, etc.
So I don't know if we absolutely need to catch these earlier?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've confused myself as to whether this could be self now! :)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can quite yet, still working on it. Got to move all the yacc_diag() back out into CTParserBuilder, and make sure there isn't any more yacc_diag() usage inside codegen.
We've gotten rid of the major impediment I've looked at so far though.

Comment thread lrpar/src/lib/codegen.rs
Comment thread lrpar/src/lib/codegen.rs
&self.stable
}

pub(crate) fn take_parser(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one's interesting because it consumes self. Why/when do we use it in that way?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering if fn finish(self) -> (YaccGrammar, StateGraph, StateTable) or finished might be a better
name, since technically it's taking more than just the parser?

I don't know if it's really finishing or finalizing anything, but maybe it conveys better that we're done with self and taking ownership of all the interesting values.

Comment thread lrpar/src/lib/codegen.rs Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants