Add comments around user code blocks to generated Rust module

This takes the place of #line directives
This commit is contained in:
Josh Holtrop 2026-08-20 15:52:18 -04:00
parent 3626bc2caf
commit 7fd1505710
3 changed files with 68 additions and 8 deletions

View File

@ -695,10 +695,21 @@ generated output without any surrounding `#line` directives.
This can be useful when debugging the generated parser itself, or when the This can be useful when debugging the generated parser itself, or when the
`#line` directives interfere with other tooling. `#line` directives interfere with other tooling.
The `noline` statement only affects the C, C++, and D targets. Rust has no `#line` directive equivalent.
Rust has no `#line` directive equivalent, so `#line` directives are never For a Rust target, Propane instead emits a comment before and after each
emitted into Rust output and the `noline` statement has no effect for the Rust section of user code naming the grammar file and the line number the code was
target. taken from:
```
/* Begin user code from myparser.propane line 42. */
let mut v: i64 = 0;
/* End user code from myparser.propane line 42. */
```
A compiler diagnostic that points into the generated Rust module can be traced
back to the grammar by reading up to the nearest such comment.
The `noline` statement suppresses these comments for a Rust target, in the same
way that it suppresses `#line` directives for the other targets.
##> `on_tree_node` statement - custom initialization of a token tree node ##> `on_tree_node` statement - custom initialization of a token tree node

View File

@ -41,12 +41,23 @@ class Propane
output_file = @output_file output_file = @output_file
end end
erb = ERB.new(template, trim_mode: "<>") erb = ERB.new(template, trim_mode: "<>")
# Rust has no #line directive support. For a Rust target the directives
# that the grammar embeds around user code blocks are replaced with
# comments naming the grammar file and line number the code came from,
# so that the origin of a section of user code can still be found by
# reading up from a compiler diagnostic pointing into the generated
# module.
user_code_origin = nil
result = erb.result(binding.clone).lines.each_with_index.map do |line, i| result = erb.result(binding.clone).lines.each_with_index.map do |line, i|
if @language == "rust" if @language == "rust"
# Rust has no #line directive support, so strip the directives that if md = line.match(/^#line (\d+) "([^"]*)"/)
# the grammar embeds in user code blocks. user_code_origin = "#{md[2]} line #{md[1]}"
line = line.sub(/^#line \d+ "[^"]*"/, "") line.sub(/^#line \d+ "[^"]*"/, %[/* Begin user code from #{user_code_origin}. */])
line == "#linereset\n" ? "" : line elsif line == "#linereset\n"
%[/* End user code from #{user_code_origin}. */\n]
else
line
end
elsif line == "#linereset\n" elsif line == "#linereset\n"
%[#line #{i + 2} "#{output_file}"\n] %[#line #{i + 2} "#{output_file}"\n]
else else

View File

@ -2113,6 +2113,44 @@ EOF
end end
end end
if language == "rust"
it "marks user code sections with their grammar file and line number" do
write_grammar <<EOF
ptype i64;
drop /\\s+/;
token num /\\d+/ <<
$$ = 42;
>>
Start -> num << $$ = $1; >>
EOF
run_propane(language: language)
parser = File.binread("spec/run/testparser.rs")
# The lexer code block body begins on grammar line 4 and the parser
# rule code block is on grammar line 6.
expect(parser).to include %[/* Begin user code from spec/run/testparser.propane line 4. */]
expect(parser).to include %[/* End user code from spec/run/testparser.propane line 4. */]
expect(parser).to include %[/* Begin user code from spec/run/testparser.propane line 6. */]
expect(parser).to include %[/* End user code from spec/run/testparser.propane line 6. */]
expect(parser).to_not include "#line"
end
it "omits user code section markers when noline is specified" do
write_grammar <<EOF
noline;
ptype i64;
drop /\\s+/;
token num /\\d+/ <<
$$ = 42;
>>
Start -> num << $$ = $1; >>
EOF
run_propane(language: language)
parser = File.binread("spec/run/testparser.rs")
expect(parser).to_not include "user code from"
expect(parser).to_not include "#line"
end
end
it "executes code blocks associated with drop statements" do it "executes code blocks associated with drop statements" do
if language == "rust" if language == "rust"
write_grammar <<EOF write_grammar <<EOF