diff --git a/doc/user_guide.md b/doc/user_guide.md index b5446e1..6e1e020 100644 --- a/doc/user_guide.md +++ b/doc/user_guide.md @@ -695,10 +695,21 @@ generated output without any surrounding `#line` directives. This can be useful when debugging the generated parser itself, or when the `#line` directives interfere with other tooling. -The `noline` statement only affects the C, C++, and D targets. -Rust has no `#line` directive equivalent, so `#line` directives are never -emitted into Rust output and the `noline` statement has no effect for the Rust -target. +Rust has no `#line` directive equivalent. +For a Rust target, Propane instead emits a comment before and after each +section of user code naming the grammar file and the line number the code was +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 diff --git a/lib/propane/generator.rb b/lib/propane/generator.rb index 1908373..e75a4a1 100644 --- a/lib/propane/generator.rb +++ b/lib/propane/generator.rb @@ -41,12 +41,23 @@ class Propane output_file = @output_file end 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| if @language == "rust" - # Rust has no #line directive support, so strip the directives that - # the grammar embeds in user code blocks. - line = line.sub(/^#line \d+ "[^"]*"/, "") - line == "#linereset\n" ? "" : line + if md = line.match(/^#line (\d+) "([^"]*)"/) + user_code_origin = "#{md[2]} line #{md[1]}" + line.sub(/^#line \d+ "[^"]*"/, %[/* Begin user code from #{user_code_origin}. */]) + elsif line == "#linereset\n" + %[/* End user code from #{user_code_origin}. */\n] + else + line + end elsif line == "#linereset\n" %[#line #{i + 2} "#{output_file}"\n] else diff --git a/spec/propane_spec.rb b/spec/propane_spec.rb index 5df8677..c62ca26 100644 --- a/spec/propane_spec.rb +++ b/spec/propane_spec.rb @@ -2113,6 +2113,44 @@ EOF end end + if language == "rust" + it "marks user code sections with their grammar file and line number" do + write_grammar <> +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 <> +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 if language == "rust" write_grammar <