From 1ab411cfef531712a36e1bb349939a090e6f4c76 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Thu, 16 Feb 2017 15:03:30 -0500 Subject: [PATCH 1/2] support Ruby 2.4's frozen string literals Note this change is made relative to master. --- bin/racc | 5 +++-- lib/racc/color.rb | 18 +++++++++--------- lib/racc/grammar.rb | 16 ++++++++-------- lib/racc/source.rb | 2 +- lib/racc/state_transition_table.rb | 2 +- lib/racc/warning.rb | 16 ++++++++-------- test/test_scanner.rb | 4 ++-- 7 files changed, 32 insertions(+), 31 deletions(-) diff --git a/bin/racc b/bin/racc index 4e2f404a..af8cf568 100755 --- a/bin/racc +++ b/bin/racc @@ -165,9 +165,10 @@ def main profiler.report rescue Racc::CompileError, Errno::ENOENT, Errno::EPERM => err raise if $DEBUG - lineno = err.message.slice!(/\A\d+:/).to_s + message = err.message.dup # string may be frozen literal + lineno = message.slice!(/\A\d+:/).to_s location = lineno.empty? ? bright("#{input}:") : bright("#{input}:#{lineno}") - $stderr.puts "#{red('Error: ')}#{location} #{err.message.strip}" + $stderr.puts "#{red('Error: ')}#{location} #{message.strip}" exit 1 end end diff --git a/lib/racc/color.rb b/lib/racc/color.rb index 04e818ba..27194f73 100644 --- a/lib/racc/color.rb +++ b/lib/racc/color.rb @@ -23,43 +23,43 @@ def self.without_color def bright(text) return text unless Color.enabled? text = text.gsub(/\e\[.*?m[^\e]*\e\[0m/, "\e[0m\\0\e[1m") - "\e[1m#{text}\e[0m" + String.new "\e[1m#{text}\e[0m" end def red(text) return text unless Color.enabled? - "\e[31m#{text}\e[0m" + String.new "\e[31m#{text}\e[0m" end def green(text) return text unless Color.enabled? - "\e[32m#{text}\e[0m" + String.new "\e[32m#{text}\e[0m" end def violet(text) return text unless Color.enabled? - "\e[1;35m#{text}\e[0m" + String.new "\e[1;35m#{text}\e[0m" end # Syntax highlighting for various types of symbols... def nonterminal(text) return text unless Color.enabled? - "\e[1;34m#{text}\e[0m" # blue + String.new "\e[1;34m#{text}\e[0m" # blue end def terminal(text) return text unless Color.enabled? - "\e[1;36m\e[4m#{text}\e[0m" # cyan, with underline + String.new "\e[1;36m\e[4m#{text}\e[0m" # cyan, with underline end def string(text) return text unless Color.enabled? - "\e[1;33m#{text}\e[0m" # bright yellow + String.new "\e[1;33m#{text}\e[0m" # bright yellow end def explicit_prec(text) return text unless Color.enabled? - "\e[1;31m#{text}\e[0m" # bright reddish orange + String.new "\e[1;31m#{text}\e[0m" # bright reddish orange end end -end \ No newline at end of file +end diff --git a/lib/racc/grammar.rb b/lib/racc/grammar.rb index b50da7e6..0ee50fb8 100644 --- a/lib/racc/grammar.rb +++ b/lib/racc/grammar.rb @@ -298,7 +298,7 @@ def check_terminals locations = undeclared.flat_map(&:locate).map(&:rule).uniq raise CompileError, "terminal#{'s' unless undeclared.one?} " \ "#{Racc.to_sentence(undeclared)} #{undeclared.one? ? 'was' : 'were'} " \ - "not declared in a 'token' block:\n" << + "not declared in a 'token' block:\n" + Source::SparseLines.render(locations.map(&:source)) end @@ -308,7 +308,7 @@ def check_terminals raise CompileError, "token#{'s' unless wrongly_declared.one?} " \ "#{Racc.to_sentence(wrongly_declared)} were declared in a 'token'" \ " block, but #{wrongly_declared.one? ? 'it also has' : 'they also have'}" \ - " derivation rules:\n" << Source::SparseLines.render(bad_rules.map(&:source)) + " derivation rules:\n" + Source::SparseLines.render(bad_rules.map(&:source)) end end @@ -316,7 +316,7 @@ def check_terminals unless bad_strings.empty? bad_rules = bad_strings.flat_map(&:heads).map(&:rule) raise CompileError, 'you may not create derivation rules for a ' \ - "string literal:\n" << Source::SparseLines.render(bad_rules.map(&:source)) + "string literal:\n" + Source::SparseLines.render(bad_rules.map(&:source)) end bad_prec = @symbols.select { |s| s.assoc && s.nonterminal? } @@ -325,7 +325,7 @@ def check_terminals raise CompileError, "token#{'s' unless bad_prec.one?} " \ "#{Racc.to_sentence(bad_prec)} appeared in a prechigh/preclow " \ "block, but #{bad_prec.one? ? 'it is not a' : 'they are not'} " \ - "terminal#{'s' unless bad_prec.one?}:\n" << + "terminal#{'s' unless bad_prec.one?}:\n" + Source::SparseLines.render(bad_rules.map(&:source)) end @@ -335,7 +335,7 @@ def check_terminals unless bad_prec.empty? raise CompileError, "The following rule#{'s' unless bad_prec.one?} " \ "use#{'s' if bad_prec.one?} nonterminals for explicit precedence, " \ - "which is not allowed:\n" << + "which is not allowed:\n" + Source::SparseLines.render(bad_prec.map(&:source)) end end @@ -344,13 +344,13 @@ def check_rules @rules.group_by(&:target).each_value do |same_lhs| same_lhs.group_by { |r| r.symbols.reject(&:hidden?) }.each_value do |same_rhs| next unless same_rhs.size > 1 - raise CompileError, "The following rules are duplicates:\n" << + raise CompileError, "The following rules are duplicates:\n" + Source::SparseLines.render(same_rhs.map(&:source)) end end unless @error.heads.empty? - raise CompileError, "You cannot create rules for the error symbol:\n" << + raise CompileError, "You cannot create rules for the error symbol:\n" + Source::SparseLines.render(@error.heads.map { |ptr| ptr.rule.source} ) end end @@ -568,7 +568,7 @@ def following end def to_s - result = "#{@rule.target} : " + result = String.new "#{@rule.target} : " if @index > 0 result << "#{preceding.reject(&:hidden?).map(&:to_s).join(' ')} ." else diff --git a/lib/racc/source.rb b/lib/racc/source.rb index 1e4663d4..8d4decc8 100644 --- a/lib/racc/source.rb +++ b/lib/racc/source.rb @@ -31,7 +31,7 @@ def spiffy highlights.sort_by!(&:from) raw = text - cooked = '' + cooked = String.new offset = 0 highlights.each do |hilite| diff --git a/lib/racc/state_transition_table.rb b/lib/racc/state_transition_table.rb index 1c01c321..da2de1be 100644 --- a/lib/racc/state_transition_table.rb +++ b/lib/racc/state_transition_table.rb @@ -202,7 +202,7 @@ def add_entry(all, array, chkval, ptr_array) end def mkmapexp(arr) - map = '' + map = String.new maxdup = RE_DUP_MAX arr.chunk(&:nil?).each do |is_nil, items| diff --git a/lib/racc/warning.rb b/lib/racc/warning.rb index 233f1946..b9e6486e 100644 --- a/lib/racc/warning.rb +++ b/lib/racc/warning.rb @@ -53,7 +53,7 @@ def initialize(type, title, details = nil) end def to_s - msg = violet('Warning: ') << bright(title) + msg = violet('Warning: ') + bright(title) msg << "\n" << details if details msg end @@ -104,7 +104,7 @@ def title def details "Its derivation rule#{'s all' unless @sym.heads.one?} contain" \ "#{'s' if @sym.heads.one?} #{'an ' if @sym.heads.one?}infinite loop" \ - "#{'s' unless @sym.heads.one?}:\n" << + "#{'s' unless @sym.heads.one?}:\n" + @sym.heads.map { |ptr| ptr.rule.to_s }.join("\n") end @@ -175,7 +175,7 @@ def details "When the next token is #{connective}#{Racc.to_sentence(tokens, 'or')}" \ ", it is overridden by #{rules.one? ? 'this' : 'these'} " \ - "higher-precedence rule#{'s' unless rules.one?}:\n" << + "higher-precedence rule#{'s' unless rules.one?}:\n" + Source::SparseLines.render(rules.map(&:source)) end.join("\n\n") end @@ -196,7 +196,7 @@ def initialize(conflict, grammar, verbose) end def title - "Shift/reduce conflict on #{@sym}," << + "Shift/reduce conflict on #{@sym}," + (@path.reject(&:hidden?).empty? ? ' at the beginning of the parse.' : ' after the following input:') @@ -204,7 +204,7 @@ def title def details if @path.reject(&:hidden?).empty? - result = '' + result = String.new else result = @path.reject(&:hidden?).map(&:to_s).join(' ') << "\n" end @@ -253,7 +253,7 @@ def initialize(conflict, grammar, verbose) end def title - "Reduce/reduce conflict on #{@sym}," << + "Reduce/reduce conflict on #{@sym}," + (@path.reject(&:hidden?).empty? ? ' at the beginning of the parse.' : ' after the following input:') @@ -261,7 +261,7 @@ def title def details if @path.reject(&:hidden?).empty? - result = '' + result = String.new else result = @path.reject(&:hidden?).map(&:to_s).join(' ') << "\n" end @@ -300,4 +300,4 @@ def type end end end -end \ No newline at end of file +end diff --git a/test/test_scanner.rb b/test/test_scanner.rb index 3232e8b2..b7b35004 100644 --- a/test/test_scanner.rb +++ b/test/test_scanner.rb @@ -9,11 +9,11 @@ class TestScanner < TestCase define_method("test_scan_#{File.basename(testfile)}".to_sym) do original = File.read(testfile) # wrap the Ruby source code in an action block - wrapped = "class Test\nrule\na : '*' {" << original << "\n}" + wrapped = "class Test\nrule\na : '*' {" + original + "\n}" file = Source::Buffer.new(testfile, wrapped) scanner = Racc::GrammarFileScanner.new(file) - rebuilt = '' + rebuilt = String.new scanner.yylex do |token| break if token.nil? rebuilt << token[1][0].text if token[0] == :ACTION From 3f8e7f3960a3501ed0dea2dcdd7d14de2f140ccb Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Tue, 21 Nov 2017 23:03:13 -0500 Subject: [PATCH 2/2] ensure test suite enables frozen string literals for ruby 2.4 and higher, to prevent regressing on frozen literal support. --- test/run_tests.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/run_tests.sh b/test/run_tests.sh index 5fb354cd..b071f446 100755 --- a/test/run_tests.sh +++ b/test/run_tests.sh @@ -1,4 +1,14 @@ #!/usr/bin/env bash +set -eux + +test_frozen_strings=$(ruby -e 'puts (RUBY_ENGINE == "ruby" && RUBY_VERSION > "2.4")') + +if [[ $test_frozen_strings == "true" ]] ; then + echo "NOTE: enabling frozen string literals" + rvm install rubygems 2.6.12 --force # because of an issue in rubygems 2.7 with ruby 2.5 and frozen string literals + export RUBYOPT="--enable-frozen-string-literal --debug=frozen-string-literal" +fi + bundle exec rake test bundle exec rake test_pure