From c6e5374d43a731aabe5894e03d7ab7dbae47a6e1 Mon Sep 17 00:00:00 2001 From: ydah Date: Sat, 5 Sep 2026 07:42:11 +0900 Subject: [PATCH] Support structured semantic value tags --- lib/lrama/lexer.rb | 3 ++- lib/lrama/lexer/token/user_code.rb | 2 +- sig/generated/lrama/lexer.rbs | 2 ++ spec/lrama/grammar/code_spec.rb | 5 +++-- spec/lrama/lexer/token/user_code_spec.rb | 3 +++ spec/lrama/lexer_spec.rb | 19 +++++++++++++++++++ 6 files changed, 30 insertions(+), 4 deletions(-) diff --git a/lib/lrama/lexer.rb b/lib/lrama/lexer.rb index 8e0391255..0054f6944 100644 --- a/lib/lrama/lexer.rb +++ b/lib/lrama/lexer.rb @@ -63,6 +63,7 @@ class Lexer %start ).freeze #: Array[String] IDENTIFIER_PATTERN = /[a-zA-Z_.][-a-zA-Z0-9_.]*/.freeze #: Regexp + TAG_PATTERN = /<[a-zA-Z_][a-zA-Z0-9_]*(?:(?:\.|->)[a-zA-Z_][a-zA-Z0-9_]*)*>/.freeze #: Regexp # @rbs (GrammarFile grammar_file) -> void def initialize(grammar_file) @@ -126,7 +127,7 @@ def lex_token return [@scanner.matched, Lrama::Lexer::Token::Token.new(s_value: @scanner.matched, location: location)] when @scanner.scan(/[\?\+\*]/) return [@scanner.matched, Lrama::Lexer::Token::Token.new(s_value: @scanner.matched, location: location)] - when @scanner.scan(/<\w+>/) + when @scanner.scan(TAG_PATTERN) return [:TAG, Lrama::Lexer::Token::Tag.new(s_value: @scanner.matched, location: location)] when @scanner.scan(/'.'/) return [:CHARACTER, Lrama::Lexer::Token::Char.new(s_value: @scanner.matched, location: location)] diff --git a/lib/lrama/lexer/token/user_code.rb b/lib/lrama/lexer/token/user_code.rb index 166f04954..e902b5496 100644 --- a/lib/lrama/lexer/token/user_code.rb +++ b/lib/lrama/lexer/token/user_code.rb @@ -41,7 +41,7 @@ def scan_reference(scanner) if scanner.scan(/ # $ references # It need to wrap an identifier with brackets to use ".-" for identifiers - \$(<[a-zA-Z0-9_]+>)?(?: + \$(#{Lrama::Lexer::TAG_PATTERN})?(?: (\$) # $$, $$ | (\d+) # $1, $2, $1 | ([a-zA-Z_][a-zA-Z0-9_]*) # $foo, $expr, $program (named reference without brackets) diff --git a/sig/generated/lrama/lexer.rbs b/sig/generated/lrama/lexer.rbs index e0e8c97cd..6d46126cb 100644 --- a/sig/generated/lrama/lexer.rbs +++ b/sig/generated/lrama/lexer.rbs @@ -24,6 +24,8 @@ module Lrama IDENTIFIER_PATTERN: Regexp + TAG_PATTERN: Regexp + # @rbs (GrammarFile grammar_file) -> void def initialize: (GrammarFile grammar_file) -> void diff --git a/spec/lrama/grammar/code_spec.rb b/spec/lrama/grammar/code_spec.rb index 401b00faf..f31121110 100644 --- a/spec/lrama/grammar/code_spec.rb +++ b/spec/lrama/grammar/code_spec.rb @@ -165,6 +165,7 @@ int rule5; int rule6; int rule7; + struct { int integer; } value; } %token keyword_class @@ -209,7 +210,7 @@ rule4: expr '+' expr[expr-right] { @1 + @[expr-right]; @0; } ; - rule5: expr '+' expr { $1 + $3; } + rule5: expr '+' expr { $1 + $3; } ; rule6: expr '+' { $$ = $1; @$ = @1; } expr { $1 + $4; } @@ -268,7 +269,7 @@ it "respects explicit tag in a rule" do code = grammar.rules.find {|r| r.lhs.id.s_value == "rule5" } - expect(code.translated_code(grammar)).to eq(" (yyvsp[-2].expr) + (yyvsp[0].integer); ") + expect(code.translated_code(grammar)).to eq(" (yyvsp[-2].expr) + (yyvsp[0].value.integer); ") end context "midrule action exists" do diff --git a/spec/lrama/lexer/token/user_code_spec.rb b/spec/lrama/lexer/token/user_code_spec.rb index 5607c6813..6ee14a8d9 100644 --- a/spec/lrama/lexer/token/user_code_spec.rb +++ b/spec/lrama/lexer/token/user_code_spec.rb @@ -21,6 +21,9 @@ references = Lrama::Lexer::Token::UserCode.new(s_value: " $1 ", location: location).references expect(references.count).to eq 1 expect(references[0]).to eq Lrama::Grammar::Reference.new(type: :dollar, number: 1, index: 1, ex_tag: Lrama::Lexer::Token::Tag.new(s_value: ""), first_column: 1, last_column: 9) + references = Lrama::Lexer::Token::UserCode.new(s_value: " $1 ", location: location).references + expect(references.count).to eq 1 + expect(references[0]).to eq Lrama::Grammar::Reference.new(type: :dollar, number: 1, index: 1, ex_tag: Lrama::Lexer::Token::Tag.new(s_value: ""), first_column: 1, last_column: 18) # $foo references = Lrama::Lexer::Token::UserCode.new(s_value: " $foo ", location: location).references diff --git a/spec/lrama/lexer_spec.rb b/spec/lrama/lexer_spec.rb index 0b37cf9e1..b038b5303 100644 --- a/spec/lrama/lexer_spec.rb +++ b/spec/lrama/lexer_spec.rb @@ -392,6 +392,25 @@ expect(lexer.next_token).to eq([':', token_class::Token.new(s_value: ':')]) end end + + it 'lexes structured semantic value tags' do + tags = ["", "", "integer>"] + grammar_file = Lrama::Lexer::GrammarFile.new("tags.y", tags.join(" ")) + lexer = Lrama::Lexer.new(grammar_file) + + tags.each do |tag| + expect(lexer.next_token).to eq([:TAG, token_class::Tag.new(s_value: tag)]) + end + end + + it 'rejects unsupported type and default tags' do + ["", ">", "<*>", "<>"].each do |tag| + grammar_file = Lrama::Lexer::GrammarFile.new("tags.y", tag) + lexer = Lrama::Lexer.new(grammar_file) + + expect { lexer.next_token }.to raise_error(ParseError, /Unexpected token/) + end + end end context 'unexpected_token.y' do