mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
339 lines
7.4 KiB
Ruby
Executable file
339 lines
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
|