ruby/libexec/y2racc

340 строки
7.4 KiB
Ruby
Executable File

#!/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