lib/ebnf/ll1.rb in ebnf-0.1.0 vs lib/ebnf/ll1.rb in ebnf-0.2.0

- old
+ new

@@ -48,130 +48,152 @@ start_rule.start = true end # Comprehnsion rule, create shorter versions of all non-terminal sequences comprehensions = [] - begin - comprehensions = [] - ast.select {|r| r.seq? && r.kind == :rule && r.expr.length > 2}.each do |rule| - new_expr = rule.expr[2..-1].unshift(:seq) - unless ast.any? {|r| r.expr == new_expr} - debug("first_follow") {"add comprehension rule for #{rule.sym} => #{new_expr.inspect}"} - new_rule = rule.build(new_expr) - rule.comp = new_rule - comprehensions << new_rule + ittr = 0 + depth do + begin + comprehensions = [] + ast.select {|r| r.seq? && r.kind == :rule && r.expr.length > 2}.each do |rule| + new_expr = rule.expr[2..-1].unshift(:seq) + unless ast.any? {|r| r.expr == new_expr} + debug("FF.c") {"(#{ittr}) add comprehension rule for #{rule.sym} => #{new_expr.inspect}"} + new_rule = rule.build(new_expr) + rule.comp = new_rule + comprehensions << new_rule + end end - end - @ast += comprehensions - progress("first_follow") {"comprehensions #{comprehensions.length}"} - end while !comprehensions.empty? + @ast += comprehensions + progress("FF.c") {"(#{ittr}) comprehensions #{comprehensions.length}"} + ittr += 1 + end while !comprehensions.empty? - # Fi(a w' ) = { a } for every terminal a - # For each rule who's expr's first element of a seq a terminal, or having any element of alt a terminal, add that terminal to the first set for this rule - each(:rule) do |rule| - each(:terminal) do |terminal| - rule.add_first([terminal.sym]) if rule.starts_with(terminal.sym) + # Fi(a w' ) = { a } for every terminal a + # For each rule who's expr's first element of a seq a terminal, or having any element of alt a terminal, add that terminal to the first set for this rule + each(:rule) do |rule| + each(:terminal) do |terminal| + if rule.starts_with?(terminal.sym) + debug("FF.t") {"(0) add first #{terminal.sym} to #{rule.sym}"} + rule.add_first([terminal.sym]) + end + end + + # Add strings to first for strings which are start elements + start_strs = rule.starts_with?(String) + if start_strs + debug("FF.t") {"(0) add firsts #{start_strs.join(", ")} to #{rule.sym}"} + rule.add_first(start_strs) + end end - # Add strings to first for strings which are start elements - start_strs = rule.starts_with(String) - rule.add_first(start_strs) if start_strs - end + # # Fi(ε) = { ε } + # Add _eps as a first of _empty + find_rule(:_empty).add_first([:_eps]) - # # Fi(ε) = { ε } - # Add _eps as a first of _empty - empty = ast.detect {|r| r.sym == :_empty} - empty.add_first([:_eps]) + # Loop until no more first elements are added + firsts, follows, ittr = 0, 0, 0 + begin + firsts, follows = 0, 0 + each(:rule) do |rule| + each(:rule) do |first_rule| + next if first_rule == rule || first_rule.first.nil? - # Loop until no more first elements are added - firsts, follows = 0, 0 - begin - firsts, follows = 0, 0 - each(:rule) do |rule| - each(:rule) do |first_rule| - next if first_rule == rule || first_rule.first.nil? + # Fi(A w' ) = Fi(A) for every nonterminal A with ε not in Fi(A) + # For each rule that starts with another rule having firsts which don't include _eps, add the firsts of that rule to this rule, unless it already has those terminals in its first. + # Note that it's simpler to promote all fi(A) to fi(A w') and exclude _eps, as this covers corner cases of the following rule. + if rule.starts_with?(first_rule.sym) && first_rule.first != [:_eps] + debug("FF.1") {"(#{ittr}) add first #{first_rule.first.inspect} from #{first_rule.sym} to #{rule.sym}"} + firsts += rule.add_first(first_rule.first - [:_eps]) + end - # Fi(A w' ) = Fi(A) for every nonterminal A with ε not in Fi(A) - # For each rule that starts with another rule having firsts, add the firsts of that rule to this rule, unless it already has those terminals in its first - if rule.starts_with(first_rule.sym) - depth {debug("FF.1") {"add first #{first_rule.first.inspect} to #{rule.sym}"}} - firsts += rule.add_first(first_rule.first) + # Fi(A w' ) = Fi(A) \ { ε } ∪ Fi(w' ) for every nonterminal A with ε in Fi(A) + # For each rule starting with eps, add the terminals for the comprehension of this rule + if rule.seq? && + rule.expr.fetch(1, nil) == first_rule.sym && + first_rule.first_includes_eps? && + (comp = rule.comp) && + comp.first && + !(comp.first - [:_eps]).empty? + + to_add = comp.first - [:_eps] + debug("FF.2") {"(#{ittr}) add first #{to_add.inspect} from #{comp.sym} to #{rule.sym}"} + firsts += rule.add_first(to_add) + end end - # Fi(A w' ) = Fi(A) \ { ε } ∪ Fi(w' ) for every nonterminal A with ε in Fi(A) - # For each rule starting with eps, add the terminals for the comprehension of this rule - if rule.seq? && - rule.expr.fetch(1, nil) == first_rule && - first_rule.first.include?(:_eps) && - (comp = rule.comp) + # Only run these rules if the rule is a sequence having two or more elements, whos first element is also a sequence and first_rule is the comprehension of rule + if rule.seq? && (comp = rule.comp) + #if there is a rule of the form Aj → wAiw' , then + # + if (ai = find_rule(rule.expr[1])) && ai.kind == :rule && comp.first + # * if the terminal a is in Fi(w' ), then add a to Fo(Ai) + # + # Add follow terminals based on the first terminals + # of a comprehension of this rule (having the same + # sequence other than the first rule in the sequence) + # + # @example + # rule: (seq a b c) + # first_rule: (seq b c) + # if first_rule.first == [T] + # => a.follow += [T] + debug("FF.3") {"(#{ittr}) add follow #{comp.first.inspect} from #{comp.sym} to #{ai.sym}"} + follows += ai.add_follow(comp.first) + end - depth {debug("FF.2") {"add first #{first_rule.first.inspect} to #{comp.sym}"}} - firsts += comp.add_first(first_rule.first) - end - end + # Follows of a rule are also follows of the comprehension of the rule. + if rule.follow + debug("FF.4") {"(#{ittr}) add follow #{rule.follow.inspect} to from #{rule.sym} #{comp.sym}"} + follows += comp.add_follow(rule.follow) + end - # Only run these rules if the rule is a sequence having two or more elements, whos first element is also a sequence and first_rule is the comprehension of rule - if rule.seq? && (comp = rule.comp) - #if there is a rule of the form Aj → wAiw' , then - # - if (ai = find_rule(rule.expr[1])) && ai.kind == :rule && comp.first - # * if the terminal a is in Fi(w' ), then add a to Fo(Ai) + # * if ε is in Fi(w' ), then add Fo(Aj) to Fo(Ai) # - # Add follow terminals based on the first terminals - # of a comprehension of this rule (having the same - # sequence other than the first rule in the sequence) - # - # @example - # rule: (seq a b c) - # first_rule: (seq b c) - # if first_rule.first == [T] - # => a.follow += [T] - depth {debug("FF.3") {"add follow #{comp.first.inspect} to #{ai.sym}"}} - follows += ai.add_follow(comp.first) + # If the comprehension of a sequence has an _eps first, then the follows of the rule also become the follows of the first member of the rule + if comp.first && comp.first.include?(:_eps) && rule.first && + (member = find_rule(rule.expr.fetch(1, nil))) && + member.kind == :rule + + debug("FF.5") {"(#{ittr}) add follow #{rule.follow.inspect} from #{rule.sym} to #{member.sym}"} + follows += member.add_follow(rule.first) + end end - # Follows of a rule are also follows of the comprehension of the rule. - if rule.follow - depth {debug("FF.4") {"add follow #{rule.follow.inspect} to #{comp.sym}"}} - follows += comp.add_follow(rule.follow) + # Firsts of elements of an alt are firsts of the alt + if rule.alt? + rule.expr[1..-1].map {|s| find_rule(s)}.compact.select(&:first).each do |mem| + debug("FF.6") {"(#{ittr}) add first #{mem.first.inspect} from #{mem.sym} to #{rule.sym}"} + rule.add_first(mem.first) + end end - # * if ε is in Fi(w' ), then add Fo(Aj) to Fo(Ai) - # - # If the comprehension of a sequence has an _eps first, then the follows of the rule also become the follows of the first member of the rule - if comp.first && comp.first.include?(:_eps) && rule.first && - (member = find_rule(rule.expr.fetch(1, nil))) && + # Follows of a rule are also follows of the last production in the rule + if rule.seq? && rule.follow && + (member = find_rule(rule.expr.last)) && member.kind == :rule - depth {debug("FF.5") {"add follow #{rule.follow.inspect} to #{member.sym}"}} - follows += member.add_follow(rule.first) + debug("FF.7") {"(#{ittr}) add follow #{rule.follow.inspect} to #{member.sym}"} + follows += member.add_follow(rule.follow) end - end - # Follows of a rule are also follows of the last production in the rule - if rule.seq? && rule.follow && - (member = find_rule(rule.expr.last)) && - member.kind == :rule - - depth {debug("FF.6") {"add follow #{rule.follow.inspect} to #{member.sym}"}} - follows += member.add_follow(rule.follow) - end - - # For alts, anything that follows the rule follows each member of the rule - if rule.alt? && rule.follow - rule.expr[1..-1].map {|s| find_rule(s)}.each do |mem| - if mem && mem.kind == :rule - depth {debug("FF.7") {"add follow #{rule.first.inspect} to #{mem.sym}"}} - follows += mem.add_follow(rule.follow) + # For alts, anything that follows the rule follows each member of the rule + if rule.alt? && rule.follow + rule.expr[1..-1].map {|s| find_rule(s)}.each do |mem| + if mem && mem.kind == :rule + debug("FF.8") {"(#{ittr}) add follow #{rule.first.inspect} to #{mem.sym}"} + follows += mem.add_follow(rule.follow) + end end end end - end - progress("first_follow") {"firsts #{firsts}, follows #{follows}"} - end while (firsts + follows) > 0 + progress("first_follow") {"(#{ittr}) firsts #{firsts}, follows #{follows}"} + ittr += 1 + end while (firsts + follows) > 0 + end end ## # Generate parser tables, {#branch}, {#first}, {#follow}, and {#terminals} def build_tables @@ -181,23 +203,23 @@ } @first = ast. select(&:first). inject({}) {|memo, r| - memo[r.sym] = r.first.reject {|t| t == :_eps}; + memo[r.sym] = r.first if r.first memo } @follow = ast. select(&:follow). inject({}) {|memo, r| - memo[r.sym] = r.first.reject {|t| t == :_eps}; + memo[r.sym] = r.first if r.first memo } @terminals = ast.map do |r| (r.first || []) + (r.follow || []) end.flatten.uniq - @terminals = (@terminals - [:_eps, :_eof, :_empty]).sort_by(&:to_s) + @terminals = (@terminals - [:_eps, :_eof, :_empty]).sort_by(&:inspect) @branch = {} @already = [] @agenda = [] do_production(@start) @@ -226,18 +248,18 @@ ind1 = ind0 + ' ' ind2 = ind1 + ' ' if table.is_a?(Hash) io.puts "#{ind0}#{name} = {" - table.keys.sort_by(&:to_s).each do |prod| + table.keys.sort_by(&:inspect).each do |prod| case table[prod] when Array list = table[prod].map(&:inspect).join(",\n#{ind2}") io.puts "#{ind1}#{prod.inspect} => [\n#{ind2}#{list}]," when Hash io.puts "#{ind1}#{prod.inspect} => {" - table[prod].keys.sort_by(&:to_s).each do |term| + table[prod].keys.sort_by(&:inspect).each do |term| list = table[prod][term].map(&:inspect).join(", ") io.puts "#{ind2}#{term.inspect} => [#{list}]," end io.puts "#{ind1}}," else @@ -245,10 +267,10 @@ end end io.puts "#{ind0}}.freeze\n" else io.puts "#{ind0}#{name} = [\n#{ind1}" + - table.sort_by(&:to_s).map(&:inspect).join(",\n#{ind1}") + + table.sort_by(&:inspect).map(&:inspect).join(",\n#{ind1}") + "\n#{ind0}].freeze\n" end end private