Add noline grammar statement to skip emitting #line directives

This commit is contained in:
Josh Holtrop 2026-06-29 22:41:07 -04:00
parent 45843da0df
commit 7b698d7b31
4 changed files with 80 additions and 8 deletions

View File

@ -1,5 +1,9 @@
## v4.5.0 ## v4.5.0
### New Features
- Add `noline` grammar statement to skip emitting `#line` directives
### Fixes ### Fixes
- Fix #line reset directives - Fix #line reset directives

View File

@ -383,6 +383,27 @@ module proj.parser;
If a module statement is not present, then the generated D module will not If a module statement is not present, then the generated D module will not
contain a module statement and the default module name will be used. contain a module statement and the default module name will be used.
##> `noline` statement - disabling `#line` directives
By default, Propane emits `#line` directives into the generated output around
user code blocks.
These directives instruct the compiler to report any warnings or errors in the
user code using the file name and line number of the original grammar file,
rather than the generated output file.
This makes it easier to locate the source of a compiler diagnostic in the
grammar file.
The `noline` statement disables the emission of these `#line` directives.
```
noline;
```
When the `noline` statement is present, user code blocks are copied to the
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.
##> `on_tree_node` statement - custom initialization of a token tree node ##> `on_tree_node` statement - custom initialization of a token tree node
The `on_token_node` statement can be used to provide code that initializes The `on_token_node` statement can be used to provide code that initializes

View File

@ -88,6 +88,7 @@ class Propane
elsif parse_rule_statement! elsif parse_rule_statement!
elsif parse_code_block_statement! elsif parse_code_block_statement!
elsif parse_prefix_statement! elsif parse_prefix_statement!
elsif parse_noline_statement!
else else
if @input.size > 25 if @input.size > 25
@input = @input.slice(0..20) + "..." @input = @input.slice(0..20) + "..."
@ -306,11 +307,13 @@ class Propane
if md = consume!(/<<([a-z]*)(.*?)>>\n/m) if md = consume!(/<<([a-z]*)(.*?)>>\n/m)
name, code = md[1..2] name, code = md[1..2]
code = code.chomp code = code.chomp
unless @noline
if code.start_with?("\n") if code.start_with?("\n")
code = %[#line #{@line_number + 1} "#{@filename}"#{code}\n#linereset\n] code = %[#line #{@line_number + 1} "#{@filename}"#{code}\n#linereset\n]
else else
code = %[#line #{@line_number} "#{@filename}"\n#{code}\n#linereset\n] code = %[#line #{@line_number} "#{@filename}"\n#{code}\n#linereset\n]
end end
end
if @code_blocks[name] if @code_blocks[name]
@code_blocks[name] += code @code_blocks[name] += code
else else
@ -328,6 +331,13 @@ class Propane
end end
end end
def parse_noline_statement!
if md = consume!(/noline\s*;/)
@noline = true
true
end
end
def parse_pattern! def parse_pattern!
if md = consume!(%r{/}) if md = consume!(%r{/})
pattern = "" pattern = ""
@ -352,11 +362,13 @@ class Propane
def parse_code_block! def parse_code_block!
if md = consume!(/<<(.*?)>>\n/m) if md = consume!(/<<(.*?)>>\n/m)
code = md[1].chomp code = md[1].chomp
unless @noline
if code.start_with?("\n") if code.start_with?("\n")
code = %[#line #{@line_number + 1} "#{@filename}"#{code}\n#linereset\n] code = %[#line #{@line_number + 1} "#{@filename}"#{code}\n#linereset\n]
else else
code = %[#line #{@line_number} "#{@filename}"\n#{code}\n#linereset\n] code = %[#line #{@line_number} "#{@filename}"\n#{code}\n#linereset\n]
end end
end
code code
end end
end end

View File

@ -133,6 +133,41 @@ EOF
expect(o.code).to eq %[#line 7 "test.propane"\n writeln("Hello there");\n#linereset\n] expect(o.code).to eq %[#line 7 "test.propane"\n writeln("Hello there");\n#linereset\n]
end end
it "does not emit #line directives with noline statement" do
input = <<EOF
noline;
token code1 <<
a = b;
return c;
>>
token code2 <<
writeln("Hello there");
>>
tokenid token_with_no_pattern;
prefix myparser_;
EOF
grammar = Grammar.new(input, "test.propane")
expect(grammar.prefix).to eq "myparser_"
o = grammar.tokens.find {|token| token.name == "code1"}
expect(o).to_not be_nil
o = grammar.patterns.find {|pattern| pattern.token == o}
expect(o).to_not be_nil
expect(o.code).to eq %[\n a = b;\n return c;]
o = grammar.tokens.find {|token| token.name == "code2"}
expect(o).to_not be_nil
o = grammar.patterns.find {|pattern| pattern.token == o}
expect(o).to_not be_nil
expect(o.code).to eq %[\n writeln("Hello there");]
end
it "supports mode labels" do it "supports mode labels" do
input = <<EOF input = <<EOF
token a; token a;