mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
1113 lines
22 KiB
Ruby
1113 lines
22 KiB
Ruby
#
|
|
# $Id: acc33b7e1fe05f28f2d271f1fb9f1c42e50705dc $
|
|
#
|
|
# Copyright (c) 1999-2006 Minero Aoki
|
|
#
|
|
# This program is free software.
|
|
# You can distribute/modify this program under the same terms of ruby.
|
|
# see the file "COPYING".
|
|
|
|
require 'racc/compat'
|
|
require 'racc/iset'
|
|
require 'racc/sourcetext'
|
|
require 'racc/logfilegenerator'
|
|
require 'racc/exception'
|
|
require 'forwardable'
|
|
|
|
module Racc
|
|
|
|
class Grammar
|
|
|
|
def initialize(debug_flags = DebugFlags.new)
|
|
@symboltable = SymbolTable.new
|
|
@debug_symbol = debug_flags.token
|
|
@rules = [] # :: [Rule]
|
|
@start = nil
|
|
@n_expected_srconflicts = nil
|
|
@prec_table = []
|
|
@prec_table_closed = false
|
|
@closed = false
|
|
@states = nil
|
|
end
|
|
|
|
attr_reader :start
|
|
attr_reader :symboltable
|
|
attr_accessor :n_expected_srconflicts
|
|
|
|
def [](x)
|
|
@rules[x]
|
|
end
|
|
|
|
def each_rule(&block)
|
|
@rules.each(&block)
|
|
end
|
|
|
|
alias each each_rule
|
|
|
|
def each_index(&block)
|
|
@rules.each_index(&block)
|
|
end
|
|
|
|
def each_with_index(&block)
|
|
@rules.each_with_index(&block)
|
|
end
|
|
|
|
def size
|
|
@rules.size
|
|
end
|
|
|
|
def to_s
|
|
"<Racc::Grammar>"
|
|
end
|
|
|
|
extend Forwardable
|
|
|
|
def_delegator "@symboltable", :each, :each_symbol
|
|
def_delegator "@symboltable", :each_terminal
|
|
def_delegator "@symboltable", :each_nonterminal
|
|
|
|
def intern(value, dummy = false)
|
|
@symboltable.intern(value, dummy)
|
|
end
|
|
|
|
def symbols
|
|
@symboltable.symbols
|
|
end
|
|
|
|
def nonterminal_base
|
|
@symboltable.nt_base
|
|
end
|
|
|
|
def useless_nonterminal_exist?
|
|
n_useless_nonterminals() != 0
|
|
end
|
|
|
|
def n_useless_nonterminals
|
|
@n_useless_nonterminals ||=
|
|
begin
|
|
n = 0
|
|
@symboltable.each_nonterminal do |sym|
|
|
n += 1 if sym.useless?
|
|
end
|
|
n
|
|
end
|
|
end
|
|
|
|
def useless_rule_exist?
|
|
n_useless_rules() != 0
|
|
end
|
|
|
|
def n_useless_rules
|
|
@n_useless_rules ||=
|
|
begin
|
|
n = 0
|
|
each do |r|
|
|
n += 1 if r.useless?
|
|
end
|
|
n
|
|
end
|
|
end
|
|
|
|
def nfa
|
|
(@states ||= States.new(self)).nfa
|
|
end
|
|
|
|
def dfa
|
|
(@states ||= States.new(self)).dfa
|
|
end
|
|
|
|
alias states dfa
|
|
|
|
def state_transition_table
|
|
states().state_transition_table
|
|
end
|
|
|
|
def parser_class
|
|
states = states() # cache
|
|
if $DEBUG
|
|
srcfilename = caller(1).first.slice(/\A(.*?):/, 1)
|
|
begin
|
|
write_log srcfilename + ".output"
|
|
rescue SystemCallError
|
|
end
|
|
report = lambda {|s| $stderr.puts "racc: #{srcfilename}: #{s}" }
|
|
if states.should_report_srconflict?
|
|
report["#{states.n_srconflicts} shift/reduce conflicts"]
|
|
end
|
|
if states.rrconflict_exist?
|
|
report["#{states.n_rrconflicts} reduce/reduce conflicts"]
|
|
end
|
|
g = states.grammar
|
|
if g.useless_nonterminal_exist?
|
|
report["#{g.n_useless_nonterminals} useless nonterminals"]
|
|
end
|
|
if g.useless_rule_exist?
|
|
report["#{g.n_useless_rules} useless rules"]
|
|
end
|
|
end
|
|
states.state_transition_table.parser_class
|
|
end
|
|
|
|
def write_log(path)
|
|
File.open(path, 'w') {|f|
|
|
LogFileGenerator.new(states()).output f
|
|
}
|
|
end
|
|
|
|
#
|
|
# Grammar Definition Interface
|
|
#
|
|
|
|
def add(rule)
|
|
raise ArgumentError, "rule added after the Grammar closed" if @closed
|
|
@rules.push rule
|
|
end
|
|
|
|
def added?(sym)
|
|
@rules.detect {|r| r.target == sym }
|
|
end
|
|
|
|
def start_symbol=(s)
|
|
raise CompileError, "start symbol set twice'" if @start
|
|
@start = s
|
|
end
|
|
|
|
def declare_precedence(assoc, syms)
|
|
raise CompileError, "precedence table defined twice" if @prec_table_closed
|
|
@prec_table.push [assoc, syms]
|
|
end
|
|
|
|
def end_precedence_declaration(reverse)
|
|
@prec_table_closed = true
|
|
return if @prec_table.empty?
|
|
table = reverse ? @prec_table.reverse : @prec_table
|
|
table.each_with_index do |(assoc, syms), idx|
|
|
syms.each do |sym|
|
|
sym.assoc = assoc
|
|
sym.precedence = idx
|
|
end
|
|
end
|
|
end
|
|
|
|
#
|
|
# Dynamic Generation Interface
|
|
#
|
|
|
|
def Grammar.define(&block)
|
|
env = DefinitionEnv.new
|
|
env.instance_eval(&block)
|
|
env.grammar
|
|
end
|
|
|
|
class DefinitionEnv
|
|
def initialize
|
|
@grammar = Grammar.new
|
|
@seqs = Hash.new(0)
|
|
@delayed = []
|
|
end
|
|
|
|
def grammar
|
|
flush_delayed
|
|
@grammar.each do |rule|
|
|
if rule.specified_prec
|
|
rule.specified_prec = @grammar.intern(rule.specified_prec)
|
|
end
|
|
end
|
|
@grammar.init
|
|
@grammar
|
|
end
|
|
|
|
def precedence_table(&block)
|
|
env = PrecedenceDefinitionEnv.new(@grammar)
|
|
env.instance_eval(&block)
|
|
@grammar.end_precedence_declaration env.reverse
|
|
end
|
|
|
|
def method_missing(mid, *args, &block)
|
|
unless mid.to_s[-1,1] == '='
|
|
super # raises NoMethodError
|
|
end
|
|
target = @grammar.intern(mid.to_s.chop.intern)
|
|
unless args.size == 1
|
|
raise ArgumentError, "too many arguments for #{mid} (#{args.size} for 1)"
|
|
end
|
|
_add target, args.first
|
|
end
|
|
|
|
def _add(target, x)
|
|
case x
|
|
when Sym
|
|
@delayed.each do |rule|
|
|
rule.replace x, target if rule.target == x
|
|
end
|
|
@grammar.symboltable.delete x
|
|
else
|
|
x.each_rule do |r|
|
|
r.target = target
|
|
@grammar.add r
|
|
end
|
|
end
|
|
flush_delayed
|
|
end
|
|
|
|
def _delayed_add(rule)
|
|
@delayed.push rule
|
|
end
|
|
|
|
def _added?(sym)
|
|
@grammar.added?(sym) or @delayed.detect {|r| r.target == sym }
|
|
end
|
|
|
|
def flush_delayed
|
|
return if @delayed.empty?
|
|
@delayed.each do |rule|
|
|
@grammar.add rule
|
|
end
|
|
@delayed.clear
|
|
end
|
|
|
|
def seq(*list, &block)
|
|
Rule.new(nil, list.map {|x| _intern(x) }, UserAction.proc(block))
|
|
end
|
|
|
|
def null(&block)
|
|
seq(&block)
|
|
end
|
|
|
|
def action(&block)
|
|
id = "@#{@seqs["action"] += 1}".intern
|
|
_delayed_add Rule.new(@grammar.intern(id), [], UserAction.proc(block))
|
|
id
|
|
end
|
|
|
|
alias _ action
|
|
|
|
def option(sym, default = nil, &block)
|
|
_defmetasyntax("option", _intern(sym), block) {|target|
|
|
seq() { default } | seq(sym)
|
|
}
|
|
end
|
|
|
|
def many(sym, &block)
|
|
_defmetasyntax("many", _intern(sym), block) {|target|
|
|
seq() { [] }\
|
|
| seq(target, sym) {|list, x| list.push x; list }
|
|
}
|
|
end
|
|
|
|
def many1(sym, &block)
|
|
_defmetasyntax("many1", _intern(sym), block) {|target|
|
|
seq(sym) {|x| [x] }\
|
|
| seq(target, sym) {|list, x| list.push x; list }
|
|
}
|
|
end
|
|
|
|
def separated_by(sep, sym, &block)
|
|
option(separated_by1(sep, sym), [], &block)
|
|
end
|
|
|
|
def separated_by1(sep, sym, &block)
|
|
_defmetasyntax("separated_by1", _intern(sym), block) {|target|
|
|
seq(sym) {|x| [x] }\
|
|
| seq(target, sep, sym) {|list, _, x| list.push x; list }
|
|
}
|
|
end
|
|
|
|
def _intern(x)
|
|
case x
|
|
when Symbol, String
|
|
@grammar.intern(x)
|
|
when Racc::Sym
|
|
x
|
|
else
|
|
raise TypeError, "wrong type #{x.class} (expected Symbol/String/Racc::Sym)"
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def _defmetasyntax(type, id, action, &block)
|
|
if action
|
|
idbase = "#{type}@#{id}-#{@seqs[type] += 1}"
|
|
target = _wrap(idbase, "#{idbase}-core", action)
|
|
_regist("#{idbase}-core", &block)
|
|
else
|
|
target = _regist("#{type}@#{id}", &block)
|
|
end
|
|
@grammar.intern(target)
|
|
end
|
|
|
|
def _regist(target_name)
|
|
target = target_name.intern
|
|
unless _added?(@grammar.intern(target))
|
|
yield(target).each_rule do |rule|
|
|
rule.target = @grammar.intern(target)
|
|
_delayed_add rule
|
|
end
|
|
end
|
|
target
|
|
end
|
|
|
|
def _wrap(target_name, sym, block)
|
|
target = target_name.intern
|
|
_delayed_add Rule.new(@grammar.intern(target),
|
|
[@grammar.intern(sym.intern)],
|
|
UserAction.proc(block))
|
|
target
|
|
end
|
|
end
|
|
|
|
class PrecedenceDefinitionEnv
|
|
def initialize(g)
|
|
@grammar = g
|
|
@prechigh_seen = false
|
|
@preclow_seen = false
|
|
@reverse = false
|
|
end
|
|
|
|
attr_reader :reverse
|
|
|
|
def higher
|
|
if @prechigh_seen
|
|
raise CompileError, "prechigh used twice"
|
|
end
|
|
@prechigh_seen = true
|
|
end
|
|
|
|
def lower
|
|
if @preclow_seen
|
|
raise CompileError, "preclow used twice"
|
|
end
|
|
if @prechigh_seen
|
|
@reverse = true
|
|
end
|
|
@preclow_seen = true
|
|
end
|
|
|
|
def left(*syms)
|
|
@grammar.declare_precedence :Left, syms.map {|s| @grammar.intern(s) }
|
|
end
|
|
|
|
def right(*syms)
|
|
@grammar.declare_precedence :Right, syms.map {|s| @grammar.intern(s) }
|
|
end
|
|
|
|
def nonassoc(*syms)
|
|
@grammar.declare_precedence :Nonassoc, syms.map {|s| @grammar.intern(s)}
|
|
end
|
|
end
|
|
|
|
#
|
|
# Computation
|
|
#
|
|
|
|
def init
|
|
return if @closed
|
|
@closed = true
|
|
@start ||= @rules.map {|r| r.target }.detect {|sym| not sym.dummy? }
|
|
raise CompileError, 'no rule in input' if @rules.empty?
|
|
add_start_rule
|
|
@rules.freeze
|
|
fix_ident
|
|
compute_hash
|
|
compute_heads
|
|
determine_terminals
|
|
compute_nullable_0
|
|
@symboltable.fix
|
|
compute_locate
|
|
@symboltable.each_nonterminal {|t| compute_expand t }
|
|
compute_nullable
|
|
compute_useless
|
|
end
|
|
|
|
private
|
|
|
|
def add_start_rule
|
|
r = Rule.new(@symboltable.dummy,
|
|
[@start, @symboltable.anchor, @symboltable.anchor],
|
|
UserAction.empty)
|
|
r.ident = 0
|
|
r.hash = 0
|
|
r.precedence = nil
|
|
@rules.unshift r
|
|
end
|
|
|
|
# Rule#ident
|
|
# LocationPointer#ident
|
|
def fix_ident
|
|
@rules.each_with_index do |rule, idx|
|
|
rule.ident = idx
|
|
end
|
|
end
|
|
|
|
# Rule#hash
|
|
def compute_hash
|
|
hash = 4 # size of dummy rule
|
|
@rules.each do |rule|
|
|
rule.hash = hash
|
|
hash += (rule.size + 1)
|
|
end
|
|
end
|
|
|
|
# Sym#heads
|
|
def compute_heads
|
|
@rules.each do |rule|
|
|
rule.target.heads.push rule.ptrs[0]
|
|
end
|
|
end
|
|
|
|
# Sym#terminal?
|
|
def determine_terminals
|
|
@symboltable.each do |s|
|
|
s.term = s.heads.empty?
|
|
end
|
|
end
|
|
|
|
# Sym#self_null?
|
|
def compute_nullable_0
|
|
@symboltable.each do |s|
|
|
if s.terminal?
|
|
s.snull = false
|
|
else
|
|
s.snull = s.heads.any? {|loc| loc.reduce? }
|
|
end
|
|
end
|
|
end
|
|
|
|
# Sym#locate
|
|
def compute_locate
|
|
@rules.each do |rule|
|
|
t = nil
|
|
rule.ptrs.each do |ptr|
|
|
unless ptr.reduce?
|
|
tok = ptr.dereference
|
|
tok.locate.push ptr
|
|
t = tok if tok.terminal?
|
|
end
|
|
end
|
|
rule.precedence = t
|
|
end
|
|
end
|
|
|
|
# Sym#expand
|
|
def compute_expand(t)
|
|
puts "expand> #{t.to_s}" if @debug_symbol
|
|
t.expand = _compute_expand(t, ISet.new, [])
|
|
puts "expand< #{t.to_s}: #{t.expand.to_s}" if @debug_symbol
|
|
end
|
|
|
|
def _compute_expand(t, set, lock)
|
|
if tmp = t.expand
|
|
set.update tmp
|
|
return set
|
|
end
|
|
tok = nil
|
|
set.update_a t.heads
|
|
t.heads.each do |ptr|
|
|
tok = ptr.dereference
|
|
if tok and tok.nonterminal?
|
|
unless lock[tok.ident]
|
|
lock[tok.ident] = true
|
|
_compute_expand tok, set, lock
|
|
end
|
|
end
|
|
end
|
|
set
|
|
end
|
|
|
|
# Sym#nullable?, Rule#nullable?
|
|
def compute_nullable
|
|
@rules.each {|r| r.null = false }
|
|
@symboltable.each {|t| t.null = false }
|
|
r = @rules.dup
|
|
s = @symboltable.nonterminals
|
|
begin
|
|
rs = r.size
|
|
ss = s.size
|
|
check_rules_nullable r
|
|
check_symbols_nullable s
|
|
end until rs == r.size and ss == s.size
|
|
end
|
|
|
|
def check_rules_nullable(rules)
|
|
rules.delete_if do |rule|
|
|
rule.null = true
|
|
rule.symbols.each do |t|
|
|
unless t.nullable?
|
|
rule.null = false
|
|
break
|
|
end
|
|
end
|
|
rule.nullable?
|
|
end
|
|
end
|
|
|
|
def check_symbols_nullable(symbols)
|
|
symbols.delete_if do |sym|
|
|
sym.heads.each do |ptr|
|
|
if ptr.rule.nullable?
|
|
sym.null = true
|
|
break
|
|
end
|
|
end
|
|
sym.nullable?
|
|
end
|
|
end
|
|
|
|
# Sym#useless?, Rule#useless?
|
|
# FIXME: what means "useless"?
|
|
def compute_useless
|
|
@symboltable.each_terminal {|sym| sym.useless = false }
|
|
@symboltable.each_nonterminal {|sym| sym.useless = true }
|
|
@rules.each {|rule| rule.useless = true }
|
|
r = @rules.dup
|
|
s = @symboltable.nonterminals
|
|
begin
|
|
rs = r.size
|
|
ss = s.size
|
|
check_rules_useless r
|
|
check_symbols_useless s
|
|
end until r.size == rs and s.size == ss
|
|
end
|
|
|
|
def check_rules_useless(rules)
|
|
rules.delete_if do |rule|
|
|
rule.useless = false
|
|
rule.symbols.each do |sym|
|
|
if sym.useless?
|
|
rule.useless = true
|
|
break
|
|
end
|
|
end
|
|
not rule.useless?
|
|
end
|
|
end
|
|
|
|
def check_symbols_useless(s)
|
|
s.delete_if do |t|
|
|
t.heads.each do |ptr|
|
|
unless ptr.rule.useless?
|
|
t.useless = false
|
|
break
|
|
end
|
|
end
|
|
not t.useless?
|
|
end
|
|
end
|
|
|
|
end # class Grammar
|
|
|
|
|
|
class Rule
|
|
|
|
def initialize(target, syms, act)
|
|
@target = target
|
|
@symbols = syms
|
|
@action = act
|
|
@alternatives = []
|
|
|
|
@ident = nil
|
|
@hash = nil
|
|
@ptrs = nil
|
|
@precedence = nil
|
|
@specified_prec = nil
|
|
@null = nil
|
|
@useless = nil
|
|
end
|
|
|
|
attr_accessor :target
|
|
attr_reader :symbols
|
|
attr_reader :action
|
|
|
|
def |(x)
|
|
@alternatives.push x.rule
|
|
self
|
|
end
|
|
|
|
def rule
|
|
self
|
|
end
|
|
|
|
def each_rule(&block)
|
|
yield self
|
|
@alternatives.each(&block)
|
|
end
|
|
|
|
attr_accessor :ident
|
|
|
|
attr_reader :hash
|
|
attr_reader :ptrs
|
|
|
|
def hash=(n)
|
|
@hash = n
|
|
ptrs = []
|
|
@symbols.each_with_index do |sym, idx|
|
|
ptrs.push LocationPointer.new(self, idx, sym)
|
|
end
|
|
ptrs.push LocationPointer.new(self, @symbols.size, nil)
|
|
@ptrs = ptrs
|
|
end
|
|
|
|
def precedence
|
|
@specified_prec || @precedence
|
|
end
|
|
|
|
def precedence=(sym)
|
|
@precedence ||= sym
|
|
end
|
|
|
|
def prec(sym, &block)
|
|
@specified_prec = sym
|
|
if block
|
|
unless @action.empty?
|
|
raise CompileError, 'both of rule action block and prec block given'
|
|
end
|
|
@action = UserAction.proc(block)
|
|
end
|
|
self
|
|
end
|
|
|
|
attr_accessor :specified_prec
|
|
|
|
def nullable?() @null end
|
|
def null=(n) @null = n end
|
|
|
|
def useless?() @useless end
|
|
def useless=(u) @useless = u end
|
|
|
|
def inspect
|
|
"#<Racc::Rule id=#{@ident} (#{@target})>"
|
|
end
|
|
|
|
def ==(other)
|
|
other.kind_of?(Rule) and @ident == other.ident
|
|
end
|
|
|
|
def [](idx)
|
|
@symbols[idx]
|
|
end
|
|
|
|
def size
|
|
@symbols.size
|
|
end
|
|
|
|
def empty?
|
|
@symbols.empty?
|
|
end
|
|
|
|
def to_s
|
|
"#<rule#{@ident}>"
|
|
end
|
|
|
|
def accept?
|
|
if tok = @symbols[-1]
|
|
tok.anchor?
|
|
else
|
|
false
|
|
end
|
|
end
|
|
|
|
def each(&block)
|
|
@symbols.each(&block)
|
|
end
|
|
|
|
def replace(src, dest)
|
|
@target = dest
|
|
@symbols = @symbols.map {|s| s == src ? dest : s }
|
|
end
|
|
|
|
end # class Rule
|
|
|
|
|
|
class UserAction
|
|
|
|
def UserAction.source_text(src)
|
|
new(src, nil)
|
|
end
|
|
|
|
def UserAction.proc(pr = nil, &block)
|
|
if pr and block
|
|
raise ArgumentError, "both of argument and block given"
|
|
end
|
|
new(nil, pr || block)
|
|
end
|
|
|
|
def UserAction.empty
|
|
new(nil, nil)
|
|
end
|
|
|
|
private_class_method :new
|
|
|
|
def initialize(src, proc)
|
|
@source = src
|
|
@proc = proc
|
|
end
|
|
|
|
attr_reader :source
|
|
attr_reader :proc
|
|
|
|
def source?
|
|
not @proc
|
|
end
|
|
|
|
def proc?
|
|
not @source
|
|
end
|
|
|
|
def empty?
|
|
not @proc and not @source
|
|
end
|
|
|
|
def name
|
|
"{action type=#{@source || @proc || 'nil'}}"
|
|
end
|
|
|
|
alias inspect name
|
|
|
|
end
|
|
|
|
|
|
class OrMark
|
|
def initialize(lineno)
|
|
@lineno = lineno
|
|
end
|
|
|
|
def name
|
|
'|'
|
|
end
|
|
|
|
alias inspect name
|
|
|
|
attr_reader :lineno
|
|
end
|
|
|
|
|
|
class Prec
|
|
def initialize(symbol, lineno)
|
|
@symbol = symbol
|
|
@lineno = lineno
|
|
end
|
|
|
|
def name
|
|
"=#{@symbol}"
|
|
end
|
|
|
|
alias inspect name
|
|
|
|
attr_reader :symbol
|
|
attr_reader :lineno
|
|
end
|
|
|
|
|
|
#
|
|
# A set of rule and position in it's RHS.
|
|
# Note that the number of pointers is more than rule's RHS array,
|
|
# because pointer points right edge of the final symbol when reducing.
|
|
#
|
|
class LocationPointer
|
|
|
|
def initialize(rule, i, sym)
|
|
@rule = rule
|
|
@index = i
|
|
@symbol = sym
|
|
@ident = @rule.hash + i
|
|
@reduce = sym.nil?
|
|
end
|
|
|
|
attr_reader :rule
|
|
attr_reader :index
|
|
attr_reader :symbol
|
|
|
|
alias dereference symbol
|
|
|
|
attr_reader :ident
|
|
alias hash ident
|
|
attr_reader :reduce
|
|
alias reduce? reduce
|
|
|
|
def to_s
|
|
sprintf('(%d,%d %s)',
|
|
@rule.ident, @index, (reduce?() ? '#' : @symbol.to_s))
|
|
end
|
|
|
|
alias inspect to_s
|
|
|
|
def eql?(ot)
|
|
@hash == ot.hash
|
|
end
|
|
|
|
alias == eql?
|
|
|
|
def head?
|
|
@index == 0
|
|
end
|
|
|
|
def next
|
|
@rule.ptrs[@index + 1] or ptr_bug!
|
|
end
|
|
|
|
alias increment next
|
|
|
|
def before(len)
|
|
@rule.ptrs[@index - len] or ptr_bug!
|
|
end
|
|
|
|
private
|
|
|
|
def ptr_bug!
|
|
raise "racc: fatal: pointer not exist: self: #{to_s}"
|
|
end
|
|
|
|
end # class LocationPointer
|
|
|
|
|
|
class SymbolTable
|
|
|
|
include Enumerable
|
|
|
|
def initialize
|
|
@symbols = [] # :: [Racc::Sym]
|
|
@cache = {} # :: {(String|Symbol) => Racc::Sym}
|
|
@dummy = intern(:$start, true)
|
|
@anchor = intern(false, true) # Symbol ID = 0
|
|
@error = intern(:error, false) # Symbol ID = 1
|
|
end
|
|
|
|
attr_reader :dummy
|
|
attr_reader :anchor
|
|
attr_reader :error
|
|
|
|
def [](id)
|
|
@symbols[id]
|
|
end
|
|
|
|
def intern(val, dummy = false)
|
|
@cache[val] ||=
|
|
begin
|
|
sym = Sym.new(val, dummy)
|
|
@symbols.push sym
|
|
sym
|
|
end
|
|
end
|
|
|
|
attr_reader :symbols
|
|
alias to_a symbols
|
|
|
|
def delete(sym)
|
|
@symbols.delete sym
|
|
@cache.delete sym.value
|
|
end
|
|
|
|
attr_reader :nt_base
|
|
|
|
def nt_max
|
|
@symbols.size
|
|
end
|
|
|
|
def each(&block)
|
|
@symbols.each(&block)
|
|
end
|
|
|
|
def terminals(&block)
|
|
@symbols[0, @nt_base]
|
|
end
|
|
|
|
def each_terminal(&block)
|
|
@terms.each(&block)
|
|
end
|
|
|
|
def nonterminals
|
|
@symbols[@nt_base, @symbols.size - @nt_base]
|
|
end
|
|
|
|
def each_nonterminal(&block)
|
|
@nterms.each(&block)
|
|
end
|
|
|
|
def fix
|
|
terms, nterms = @symbols.partition {|s| s.terminal? }
|
|
@symbols = terms + nterms
|
|
@terms = terms
|
|
@nterms = nterms
|
|
@nt_base = terms.size
|
|
fix_ident
|
|
check_terminals
|
|
end
|
|
|
|
private
|
|
|
|
def fix_ident
|
|
@symbols.each_with_index do |t, i|
|
|
t.ident = i
|
|
end
|
|
end
|
|
|
|
def check_terminals
|
|
return unless @symbols.any? {|s| s.should_terminal? }
|
|
@anchor.should_terminal
|
|
@error.should_terminal
|
|
each_terminal do |t|
|
|
t.should_terminal if t.string_symbol?
|
|
end
|
|
each do |s|
|
|
s.should_terminal if s.assoc
|
|
end
|
|
terminals().reject {|t| t.should_terminal? }.each do |t|
|
|
raise CompileError, "terminal #{t} not declared as terminal"
|
|
end
|
|
nonterminals().select {|n| n.should_terminal? }.each do |n|
|
|
raise CompileError, "symbol #{n} declared as terminal but is not terminal"
|
|
end
|
|
end
|
|
|
|
end # class SymbolTable
|
|
|
|
|
|
# Stands terminal and nonterminal symbols.
|
|
class Sym
|
|
|
|
def initialize(value, dummyp)
|
|
@ident = nil
|
|
@value = value
|
|
@dummyp = dummyp
|
|
|
|
@term = nil
|
|
@nterm = nil
|
|
@should_terminal = false
|
|
@precedence = nil
|
|
case value
|
|
when Symbol
|
|
@to_s = value.to_s
|
|
@serialized = value.inspect
|
|
@string = false
|
|
when String
|
|
@to_s = value.inspect
|
|
@serialized = value.dump
|
|
@string = true
|
|
when false
|
|
@to_s = '$end'
|
|
@serialized = 'false'
|
|
@string = false
|
|
when ErrorSymbolValue
|
|
@to_s = 'error'
|
|
@serialized = 'Object.new'
|
|
@string = false
|
|
else
|
|
raise ArgumentError, "unknown symbol value: #{value.class}"
|
|
end
|
|
|
|
@heads = []
|
|
@locate = []
|
|
@snull = nil
|
|
@null = nil
|
|
@expand = nil
|
|
@useless = nil
|
|
end
|
|
|
|
class << self
|
|
def once_writer(nm)
|
|
nm = nm.id2name
|
|
module_eval(<<-EOS)
|
|
def #{nm}=(v)
|
|
raise 'racc: fatal: @#{nm} != nil' unless @#{nm}.nil?
|
|
@#{nm} = v
|
|
end
|
|
EOS
|
|
end
|
|
end
|
|
|
|
once_writer :ident
|
|
attr_reader :ident
|
|
|
|
alias hash ident
|
|
|
|
attr_reader :value
|
|
|
|
def dummy?
|
|
@dummyp
|
|
end
|
|
|
|
def terminal?
|
|
@term
|
|
end
|
|
|
|
def nonterminal?
|
|
@nterm
|
|
end
|
|
|
|
def term=(t)
|
|
raise 'racc: fatal: term= called twice' unless @term.nil?
|
|
@term = t
|
|
@nterm = !t
|
|
end
|
|
|
|
def should_terminal
|
|
@should_terminal = true
|
|
end
|
|
|
|
def should_terminal?
|
|
@should_terminal
|
|
end
|
|
|
|
def string_symbol?
|
|
@string
|
|
end
|
|
|
|
def serialize
|
|
@serialized
|
|
end
|
|
|
|
attr_writer :serialized
|
|
|
|
attr_accessor :precedence
|
|
attr_accessor :assoc
|
|
|
|
def to_s
|
|
@to_s.dup
|
|
end
|
|
|
|
alias inspect to_s
|
|
|
|
def |(x)
|
|
rule() | x.rule
|
|
end
|
|
|
|
def rule
|
|
Rule.new(nil, [self], UserAction.empty)
|
|
end
|
|
|
|
#
|
|
# cache
|
|
#
|
|
|
|
attr_reader :heads
|
|
attr_reader :locate
|
|
|
|
def self_null?
|
|
@snull
|
|
end
|
|
|
|
once_writer :snull
|
|
|
|
def nullable?
|
|
@null
|
|
end
|
|
|
|
def null=(n)
|
|
@null = n
|
|
end
|
|
|
|
attr_reader :expand
|
|
once_writer :expand
|
|
|
|
def useless?
|
|
@useless
|
|
end
|
|
|
|
def useless=(f)
|
|
@useless = f
|
|
end
|
|
|
|
end # class Sym
|
|
|
|
end # module Racc
|