diff options
Diffstat (limited to 'libexec/y2racc')
-rwxr-xr-x | libexec/y2racc | 339 |
1 files changed, 339 insertions, 0 deletions
diff --git a/libexec/y2racc b/libexec/y2racc new file mode 100755 index 0000000000..38bd3669a2 --- /dev/null +++ b/libexec/y2racc @@ -0,0 +1,339 @@ +#!/usr/local/bin/ruby +# +# $Id$ +# +# Copyright (c) 1999-2006 Minero Aoki +# +# This program is free software. +# You can distribute/modify this program under the terms of +# the GNU LGPL, Lesser General Public Lisence version 2.1. +# For details of the GNU LGPL, see the file "COPYING". +# + +require 'racc/info' +require 'strscan' +require 'forwardable' +require 'optparse' + +def main + @with_action = true + @with_header = false + @with_usercode = false + cname = 'MyParser' + input = nil + output = nil + parser = OptionParser.new + parser.banner = "Usage: #{File.basename($0)} [-Ahu] [-c <classname>] [-o <filename>] <input>" + parser.on('-o', '--output=FILENAME', 'output file name [<input>.racc]') {|name| + output = name + } + parser.on('-c', '--classname=NAME', "Name of the parser class. [#{cname}]") {|name| + cname = name + } + parser.on('-A', '--without-action', 'Does not include actions.') { + @with_action = false + } + parser.on('-h', '--with-header', 'Includes header (%{...%}).') { + @with_header = true + } + parser.on('-u', '--with-user-code', 'Includes user code.') { + @with_usercode = true + } + parser.on('--version', 'Prints version and quit.') { + puts "y2racc version #{Racc::Version}" + exit 0 + } + parser.on('--copyright', 'Prints copyright and quit.') { + puts Racc::Copyright + exit 0 + } + parser.on('--help', 'Prints this message and quit.') { + puts parser.help + exit 1 + } + begin + parser.parse! + rescue OptionParser::ParseError => err + $stderr.puts err.message + $stderr.puts parser.help + exit 1 + end + if ARGV.empty? + $stderr.puts 'no input' + exit 1 + end + if ARGV.size > 1 + $stderr.puts 'too many input' + exit 1 + end + input = ARGV[0] + + begin + result = YaccFileParser.parse_file(input) + File.open(output || "#{input}.racc", 'w') {|f| + convert cname, result, f + } + rescue SystemCallError => err + $stderr.puts err.message + exit 1 + end +end + +def convert(classname, result, f) + init_indent = 'token'.size + f.puts %<# Converted from "#{result.filename}" by y2racc version #{Racc::Version}> + f.puts + f.puts "class #{classname}" + unless result.terminals.empty? + f.puts + f.print 'token' + columns = init_indent + result.terminals.each do |t| + if columns > 60 + f.puts + f.print ' ' * init_indent + columns = init_indent + end + columns += f.write(" #{t}") + end + f.puts + end + unless result.precedence_table.empty? + f.puts + f.puts 'preclow' + result.precedence_table.each do |assoc, toks| + f.printf " %-8s %s\n", assoc, toks.join(' ') unless toks.empty? + end + f.puts 'prechigh' + end + if result.start + f.puts + f.puts "start #{@start}" + end + + f.puts + f.puts 'rule' + texts = @with_action ? result.grammar : result.grammar_without_actions + texts.each do |text| + f.print text + end + + if @with_header and result.header + f.puts + f.puts '---- header' + f.puts result.header + end + if @with_usercode and result.usercode + f.puts + f.puts '---- footer' + f.puts result.usercode + end +end + +class ParseError < StandardError; end + +class StringScanner_withlineno + def initialize(src) + @s = StringScanner.new(src) + @lineno = 1 + end + + extend Forwardable + def_delegator "@s", :eos? + def_delegator "@s", :rest + + attr_reader :lineno + + def scan(re) + advance_lineno(@s.scan(re)) + end + + def scan_until(re) + advance_lineno(@s.scan_until(re)) + end + + def skip(re) + str = advance_lineno(@s.scan(re)) + str ? str.size : nil + end + + def getch + advance_lineno(@s.getch) + end + + private + + def advance_lineno(str) + @lineno += str.count("\n") if str + str + end +end + +class YaccFileParser + + Result = Struct.new(:terminals, :precedence_table, :start, + :header, :grammar, :usercode, :filename) + class Result # reopen + def initialize + super + self.terminals = [] + self.precedence_table = [] + self.start = nil + self.grammar = [] + self.header = nil + self.usercode = nil + self.filename = nil + end + + def grammar_without_actions + grammar().map {|text| text[0,1] == '{' ? '{}' : text } + end + end + + def YaccFileParser.parse_file(filename) + new().parse(File.read(filename), filename) + end + + def parse(src, filename = '-') + @result = Result.new + @filename = filename + @result.filename = filename + s = StringScanner_withlineno.new(src) + parse_header s + parse_grammar s + @result + end + + private + + COMMENT = %r</\*[^*]*\*+(?:[^/*][^*]*\*+)*/> + CHAR = /'((?:[^'\\]+|\\.)*)'/ + STRING = /"((?:[^"\\]+|\\.)*)"/ + + def parse_header(s) + skip_until_percent s + until s.eos? + case + when t = s.scan(/left/) + @result.precedence_table.push ['left', scan_symbols(s)] + when t = s.scan(/right/) + @result.precedence_table.push ['right', scan_symbols(s)] + when t = s.scan(/nonassoc/) + @result.precedence_table.push ['nonassoc', scan_symbols(s)] + when t = s.scan(/token/) + list = scan_symbols(s) + list.shift if /\A<(.*)>\z/ =~ list[0] + @result.terminals.concat list + when t = s.scan(/start/) + @result.start = scan_symbols(s)[0] + when s.skip(%r<(?: + type | union | expect | thong | binary | + semantic_parser | pure_parser | no_lines | + raw | token_table + )\b>x) + skip_until_percent s + when s.skip(/\{/) # header (%{...%}) + str = s.scan_until(/\%\}/) + str.chop! + str.chop! + @result.header = str + skip_until_percent s + when s.skip(/\%/) # grammar (%%...) + return + else + raise ParseError, "#{@filename}:#{s.lineno}: scan error" + end + end + end + + def skip_until_percent(s) + until s.eos? + s.skip /[^\%\/]+/ + next if s.skip(COMMENT) + return if s.getch == '%' + end + end + + def scan_symbols(s) + list = [] + until s.eos? + s.skip /\s+/ + if s.skip(COMMENT) + ; + elsif t = s.scan(CHAR) + list.push t + elsif t = s.scan(STRING) + list.push t + elsif s.skip(/\%/) + break + elsif t = s.scan(/\S+/) + list.push t + else + raise ParseError, "#{@filename}:#{@lineno}: scan error" + end + end + list + end + + def parse_grammar(s) + buf = [] + until s.eos? + if t = s.scan(/[^%'"{\/]+/) + buf.push t + break if s.eos? + end + if s.skip(/\{/) + buf.push scan_action(s) + elsif t = s.scan(/'(?:[^'\\]+|\\.)*'/) then buf.push t + elsif t = s.scan(/"(?:[^"\\]+|\\.)*"/) then buf.push t + elsif t = s.scan(COMMENT) then buf.push t + elsif s.skip(/%prec\b/) then buf.push '=' + elsif s.skip(/%%/) + @result.usercode = s.rest + break + else + buf.push s.getch + end + end + @result.grammar = buf + end + + def scan_action(s) + buf = '{' + nest = 1 + until s.eos? + if t = s.scan(%r<[^/{}'"]+>) + buf << t + break if s.eos? + elsif t = s.scan(COMMENT) + buf << t + elsif t = s.scan(CHAR) + buf << t + elsif t = s.scan(STRING) + buf << t + else + c = s.getch + buf << c + case c + when '{' + nest += 1 + when '}' + nest -= 1 + return buf if nest == 0 + end + end + end + $stderr.puts "warning: unterminated action in #{@filename}" + buf + end + +end + +unless Object.method_defined?(:funcall) + class Object + alias funcall __send__ + end +end + + +main |