# This file assumes that the output of the generator will be placed # within a module or a class. However, the module/class requires a # `type` method, which takes a terminal and gives its type, as a # symbol. These types should line up with the terminals that were # defined in the original grammar. # The actions to take during parsing. In every state, there are a # set of acceptable peek tokens; this table tells the parser what # to do on each acceptable peek token. The possible actions include # `:accept`, `:reduce`, and `:state`; `:accept` means to accept the # input and return the value of the pasing. `:reduce` means to # reduce the top of the stack into a given nonterminal. `:state` # means to transition to another state. # # @return [Array)>>] ACTION_TABLE = {{= generate_action_table }}.freeze # The default action that is taken for most reductions. # # @return [Proc] DEFAULT_PROC = proc { |_| _ } # A list of all of the productions. Only includes the left-hand side, # the number of tokens on the right-hand side, and the block to call # on reduction. # # @return [Array>] PRODUCTIONS = {{= generate_productions_list }}.freeze # Runs the parser. # # @param input [Array] the input to run the parser over. # @return [Object] the result of the accept. def parse(input) stack = [] stack.push([nil, 0]) last = nil input = input.to_a.dup until stack.empty? do last = parse_action(stack, input) end last end # Actually performs the parsing action on the given stack on input. # If you want to implement a push parser, than messing with this # method is probably the way to go. # # @param stack [Array>] the stack of the # parser. The actual order of the stack is important. # @param input [Array] the input to run the parser over. # The elements of this may be passed to the `type` method. # @return [Object] the result of the last accepting reduction. def parse_action(stack, input) last = nil peek_token = if input.empty? :$end else type(input.first) end action = ACTION_TABLE[stack.last.last].fetch(peek_token) do ACTION_TABLE[stack.last.last].fetch(:$default) end case action.first when :accept production = PRODUCTIONS[action.last] last = stack.pop(production[1]).first.first stack.pop when :reduce production = PRODUCTIONS[action.last] removing = stack.pop(production[1]) value = instance_exec(*removing.map(&:first), &production[2]) goto = ACTION_TABLE[stack.last.last][production[0]] stack.push([value, goto.last]) when :state stack.push([input.shift, action.last]) else raise NotImplementedError, "Unknown action #{action.first}" end last rescue KeyError => e peek = input.first if handle_error({ :stack => stack, :peek => peek_token, :remaining => input, :error => e, :line => line_of(peek), :column => column_of(peek), :expected => ACTION_TABLE[stack.last.last].keys }) retry else raise end end private def line_of(peek) if peek.respond_to?(:line) peek.line else 0 end end def column_of(peek) if peek.respond_to?(:column) peek.column else 0 end end {{ if define_own_handler? }} def handle_error(data, _ = false) {{ if panic_mode? }} if _ || data[:peek] == :$end # we can't recover if # we're at the end {{ end }} raise {{= error_class }}, "Unexpected token #{data[:peek]} on line #{data[:line]}, " \ "column #{data[:column]}; expected one of " \ "#{data[:expected].join(', ')}", data[:error].backtrace {{ if panic_mode? }} end new_peek = :$error acceptable_state = false state = nil until data[:stack].empty? or acceptable_state state = data[:stack].last.last if ACTION_TABLE[state].key?(new_peek) acceptable_state = true else data[:stack].pop # discard end end return handle_error(data, true) unless acceptable_state action = ACTION_TABLE[state][new_peek] lookaheads = nil until lookaheads if action[0] == :state lookaheads = ACTION_TABLE[action.last].keys elsif action[0] == :reduce rule = PRODUCTIONS[action.last] action = ACTION_TABLE[stack[-rule[1]].last][rule[0]] end end begin until lookaheads.include?(data[:remaining][0].peek) data[:remaining].next end rescue StopIteration end data[:remaining].unshift([new_peek, data[:error]]) true {{ end }} end {{ end }}