Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions lib/lrama/grammar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,9 @@ def add_error_token(ident_or_tags:, token_code:, lineno:)
@error_tokens << ErrorToken.new(ident_or_tags: ident_or_tags, token_code: token_code, lineno: lineno)
end

# @rbs (id: Lexer::Token::Base, tag: Lexer::Token::Tag) -> Array[Type]
def add_type(id:, tag:)
@types << Type.new(id: id, tag: tag)
# @rbs (id: Lexer::Token::Base, tag: Lexer::Token::Tag?, ?alias_name: String?) -> Array[Type]
def add_type(id:, tag:, alias_name: nil)
@types << Type.new(id: id, tag: tag, alias_name: alias_name)
end

# @rbs (Grammar::Symbol sym, Integer precedence, String s_value, Integer lineno) -> Precedence
Expand Down
1 change: 1 addition & 0 deletions lib/lrama/grammar/symbols/resolver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ def fill_nterm_type(types)
types.each do |type|
nterm = find_nterm_by_id!(type.id)
nterm.tag = type.tag
nterm.alias_name = type.alias_name if type.alias_name
end
end

Expand Down
14 changes: 9 additions & 5 deletions lib/lrama/grammar/type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,26 @@ class Type
#
# @rbs!
# @id: Lexer::Token::Base
# @tag: Lexer::Token::Tag
# @tag: Lexer::Token::Tag?
# @alias_name: String?

attr_reader :id #: Lexer::Token::Base
attr_reader :tag #: Lexer::Token::Tag
attr_reader :tag #: Lexer::Token::Tag?
attr_reader :alias_name #: String?

# @rbs (id: Lexer::Token::Base, tag: Lexer::Token::Tag) -> void
def initialize(id:, tag:)
# @rbs (id: Lexer::Token::Base, tag: Lexer::Token::Tag?, ?alias_name: String?) -> void
def initialize(id:, tag:, alias_name: nil)
@id = id
@tag = tag
@alias_name = alias_name
end

# @rbs (Grammar::Type other) -> bool
def ==(other)
self.class == other.class &&
self.id == other.id &&
self.tag == other.tag
self.tag == other.tag &&
self.alias_name == other.alias_name
end
end
end
Expand Down
693 changes: 378 additions & 315 deletions lib/lrama/parser.rb

Large diffs are not rendered by default.

28 changes: 25 additions & 3 deletions parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,16 @@ rule
}
}
}
| "%nterm" symbol_declarations
| "%nterm" nterm_declarations
{
val[1].each {|hash|
hash[:tokens].each {|id|
hash[:tokens].each {|token_info|
id = token_info[:id]
alias_name = token_info[:alias_name]
if @grammar.find_term_by_s_value(id.s_value)
on_action_error("symbol #{id.s_value} redeclared as a nonterminal", id)
else
@grammar.add_type(id: id, tag: hash[:tag])
@grammar.add_type(id: id, tag: hash[:tag], alias_name: alias_name)
end
}
}
Expand Down Expand Up @@ -312,6 +314,26 @@ rule
}
| symbol_declarations TAG symbol+ { result = val[0].append({tag: val[1], tokens: val[2]}) }

nterm_declarations:
TAG? nterm_declaration+
{
result = [{tag: val[0], tokens: val[1]}]
}
| nterm_declarations TAG nterm_declaration+
{
result = val[0].append({tag: val[1], tokens: val[2]})
}

nterm_declaration:
id
{
result = {id: val[0], alias_name: nil}
}
| id STRING
{
result = {id: val[0], alias_name: val[1].s_value}
}

symbol:
id
| string_as_id
Expand Down
4 changes: 2 additions & 2 deletions sig/generated/lrama/grammar.rbs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 8 additions & 4 deletions sig/generated/lrama/grammar/type.rbs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

181 changes: 180 additions & 1 deletion spec/lrama/parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4221,13 +4221,192 @@ class : keyword_class tSTRING keyword_end { code 1 }
end
end

context "when %nterm with alias" do
context "basic alias" do
it "stores alias_name for nonterminal" do
y = <<~INPUT
%{
// Prologue
%}

%nterm expr "expression"

%%

expr: /* empty */
;
INPUT

grammar = Lrama::Parser.new(y, "nterm_alias.y").parse
grammar.prepare
grammar.validate!

expr = grammar.nterms.find { |n| n.id.s_value == "expr" }
expect(expr.alias_name).to eq("\"expression\"")
end
end

context "with tag" do
it "stores both tag and alias_name" do
y = <<~INPUT
%{
// Prologue
%}

%union {
int i;
}

%nterm <i> expr "expression"

%%

expr: /* empty */
;
INPUT

grammar = Lrama::Parser.new(y, "nterm_alias.y").parse
grammar.prepare
grammar.validate!

expr = grammar.nterms.find { |n| n.id.s_value == "expr" }
expect(expr.tag.s_value).to eq("<i>")
expect(expr.alias_name).to eq("\"expression\"")
end
end

context "multiple declarations with aliases" do
it "stores aliases for all symbols" do
y = <<~INPUT
%{
// Prologue
%}

%nterm expr "expression" stmt "statement"

%%

expr: /* empty */
;

stmt: /* empty */
;
INPUT

grammar = Lrama::Parser.new(y, "nterm_alias.y").parse
grammar.prepare
grammar.validate!

expr = grammar.nterms.find { |n| n.id.s_value == "expr" }
stmt = grammar.nterms.find { |n| n.id.s_value == "stmt" }
expect(expr.alias_name).to eq("\"expression\"")
expect(stmt.alias_name).to eq("\"statement\"")
end
end

context "without alias (backward compatibility)" do
it "keeps alias_name as nil" do
y = <<~INPUT
%{
// Prologue
%}

%nterm expr

%%

expr: /* empty */
;
INPUT

grammar = Lrama::Parser.new(y, "nterm_alias.y").parse
grammar.prepare
grammar.validate!

expr = grammar.nterms.find { |n| n.id.s_value == "expr" }
expect(expr.alias_name).to be_nil
end
end

context "mixed declarations with and without aliases" do
it "correctly handles mixed declarations" do
y = <<~INPUT
%{
// Prologue
%}

%union {
int i;
}

%nterm <i> expr "expression" stmt

%%

expr: /* empty */
;

stmt: /* empty */
;
INPUT

grammar = Lrama::Parser.new(y, "nterm_alias.y").parse
grammar.prepare
grammar.validate!

expr = grammar.nterms.find { |n| n.id.s_value == "expr" }
stmt = grammar.nterms.find { |n| n.id.s_value == "stmt" }
expect(expr.alias_name).to eq("\"expression\"")
expect(stmt.alias_name).to be_nil
expect(expr.tag.s_value).to eq("<i>")
expect(stmt.tag.s_value).to eq("<i>")
end
end

context "multiple tag groups" do
it "handles multiple tag groups with aliases" do
y = <<~INPUT
%{
// Prologue
%}

%union {
int i;
char *str;
}

%nterm <i> expr "expression" <str> name "identifier"

%%

expr: /* empty */
;

name: /* empty */
;
INPUT

grammar = Lrama::Parser.new(y, "nterm_alias.y").parse
grammar.prepare
grammar.validate!

expr = grammar.nterms.find { |n| n.id.s_value == "expr" }
name = grammar.nterms.find { |n| n.id.s_value == "name" }
expect(expr.tag.s_value).to eq("<i>")
expect(expr.alias_name).to eq("\"expression\"")
expect(name.tag.s_value).to eq("<str>")
expect(name.alias_name).to eq("\"identifier\"")
end
end
end

context "when pass the terminal symbol to `%nterm`" do
it "raises an error" do
y = <<~INPUT
%{
// Prologue
%}

%token EOI 0 "EOI"
%nterm EOI

Expand Down