Field aliases for AST node fields could alias incorrect field when multiple rule alternatives present for one rule set - close #33

This commit is contained in:
Josh Holtrop 2024-12-28 23:08:53 -05:00
parent b02c9205c0
commit 1b4ca59158
2 changed files with 85 additions and 12 deletions

View File

@ -119,6 +119,8 @@ class Propane
def build_ast_fields(grammar) def build_ast_fields(grammar)
field_ast_node_indexes = {} field_ast_node_indexes = {}
field_indexes_across_all_rules = {} field_indexes_across_all_rules = {}
# Stores the index into @ast_fields by field alias name.
field_aliases = {}
@ast_fields = [] @ast_fields = []
@rules.each do |rule| @rules.each do |rule|
rule.components.each_with_index do |component, i| rule.components.each_with_index do |component, i|
@ -136,6 +138,16 @@ class Propane
field_ast_node_indexes[field_name] = @ast_fields.size field_ast_node_indexes[field_name] = @ast_fields.size
@ast_fields << {field_name => struct_name} @ast_fields << {field_name => struct_name}
end end
rule.aliases.each do |alias_name, index|
if index == i
alias_ast_fields_index = field_ast_node_indexes[field_name]
if field_aliases[alias_name] && field_aliases[alias_name] != alias_ast_fields_index
raise Error.new("Error: conflicting AST node field positions for alias `#{alias_name}` in rule #{rule.name} defined on line #{rule.line_number}")
end
field_aliases[alias_name] = alias_ast_fields_index
@ast_fields[alias_ast_fields_index][alias_name] = @ast_fields[alias_ast_fields_index].first[1]
end
end
field_indexes_across_all_rules[node_name] ||= Set.new field_indexes_across_all_rules[node_name] ||= Set.new
field_indexes_across_all_rules[node_name] << field_ast_node_indexes[field_name] field_indexes_across_all_rules[node_name] << field_ast_node_indexes[field_name]
rule.rule_set_node_field_index_map[i] = field_ast_node_indexes[field_name] rule.rule_set_node_field_index_map[i] = field_ast_node_indexes[field_name]
@ -150,18 +162,6 @@ class Propane
"#{grammar.ast_prefix}#{node_name}#{grammar.ast_suffix}" "#{grammar.ast_prefix}#{node_name}#{grammar.ast_suffix}"
end end
end end
# Now merge in the field aliases as given by the user in the
# grammar.
field_aliases = {}
@rules.each do |rule|
rule.aliases.each do |alias_name, index|
if field_aliases[alias_name] && field_aliases[alias_name] != index
raise Error.new("Error: conflicting AST node field positions for alias `#{alias_name}` in rule #{rule.name} defined on line #{rule.line_number}")
end
field_aliases[alias_name] = index
@ast_fields[index][alias_name] = @ast_fields[index].first[1]
end
end
end end
end end

View File

@ -1219,6 +1219,29 @@ EOF
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
it "aliases the correct field when multiple rules are in a rule set in AST mode" do
write_grammar <<EOF
ast;
token a;
token b;
token c;
drop /\\s+/;
Start -> a;
Start -> Foo;
Start -> T:first T:second T:third;
Foo -> b;
T -> a;
T -> b;
T -> c;
EOF
run_propane(language: language)
compile("spec/test_ast_field_aliases.#{language}", language: language)
results = run_test
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
it "allows specifying field aliases when AST mode is not enabled" do it "allows specifying field aliases when AST mode is not enabled" do
if language == "d" if language == "d"
write_grammar <<EOF write_grammar <<EOF
@ -1253,6 +1276,56 @@ Start -> id:first id:second <<
printf("first is %s\\n", ${first}); printf("first is %s\\n", ${first});
printf("second is %s\\n", ${second}); printf("second is %s\\n", ${second});
>> >>
EOF
end
run_propane(language: language)
compile("spec/test_field_aliases.#{language}", language: language)
results = run_test
expect(results.stderr).to eq ""
expect(results.status).to eq 0
expect(results.stdout).to match /first is foo1.*second is bar2/m
end
it "aliases the correct field when multiple rules are in a rule set when AST mode is not enabled" do
if language == "d"
write_grammar <<EOF
<<
import std.stdio;
>>
ptype string;
token id /[a-zA-Z_][a-zA-Z0-9_]*/ <<
$$ = match;
>>
drop /\\s+/;
Start -> id;
Start -> Foo;
Start -> id:first id:second <<
writeln("first is ", ${first});
writeln("second is ", ${second});
>>
Foo -> ;
EOF
else
write_grammar <<EOF
<<
#include <stdio.h>
#include <string.h>
>>
ptype char const *;
token id /[a-zA-Z_][a-zA-Z0-9_]*/ <<
char * s = malloc(match_length + 1);
strncpy(s, (char const *)match, match_length);
s[match_length] = 0;
$$ = s;
>>
drop /\\s+/;
Start -> id;
Start -> Foo;
Start -> id:first id:second <<
printf("first is %s\\n", ${first});
printf("second is %s\\n", ${second});
>>
Foo -> ;
EOF EOF
end end
run_propane(language: language) run_propane(language: language)