class Eggshell::ExpressionEvaluator::Parser::DefaultParser

Builds a parse tree from the lexer.

Public Class Methods

new() click to toggle source
# File lib/eggshell/expression-evaluator/parser.rb, line 49
def initialize()
        @lexer = Eggshell::ExpressionEvaluator::Lexer.new(self)
end

Public Instance Methods

debug(io = nil) click to toggle source
# File lib/eggshell/expression-evaluator/parser.rb, line 405
def debug(io = nil)
        io = $stderr if !io
        io.write "STATE : "
        @state.each do |st|
                io.write STATE_NAMES[st]
                io.write ", "
        end
        io.write "\n"
        io.write "TREE  : #{@tree.inspect}\n"
        io.write "  PTR : #{@ptr.inspect}\n"
        io.write "TOKENS: #{@tokens.inspect}\n"
end
emit(type, data = nil, ts = nil, te = nil) click to toggle source
# File lib/eggshell/expression-evaluator/parser.rb, line 69
def emit(type, data = nil, ts = nil, te = nil)
        lst = @state[-1]
        # pop last state if appropriate
        if type == :end
                while @state[-1] == ST_OPERATOR || @state[-1] == ST_OPERATOR_TERN
                        @state.pop
                end
                return
        end

        word = data[ts...te].pack('c*')
        @tokens << word # if type != :space
        
        # before inserting term, need to make sure it follows semantics of function/array
        # @todo look out for case of '(,' or '[,'
        arg_check = true
        if lst == ST_LABEL_CALL
                arg_check = @ptr.length == 0 || @last_comma
        elsif lst == ST_ARRAY
                arg_check = @ptr.length == 1 || @last_comma
        end
        #arg_check = arg_check && (@hash_state[-1] == :colon || @hash_state[-1] == :comma)
        @last_comma = false if type != :space
        
        expr_frag = ''
        if @tokens.length > 5
                expr_frag = @tokens[@tokens.length-5..-2].join('')
        else
                expr_frag = @tokens[0..-2].join('')
        end

        # @todo need to track last literal/var/func for operator syntax check
        if type == :escape
                raise Exception.new("expecting identifier after '#{@ptr[-1][1]}'") if @expect_label
                raise Exception.new("escaping character outside of string") if lst != ST_STRING
                word_unesc = ESCAPE_MAP[word]
                raise Exception.new("invalid escape sequence: #{word}") if !word_unesc

                @term_str += word_unesc
                return
        elsif type == :str_delim
                raise Exception.new("expecting identifier after '#{@ptr[-1][1]}', not #{word}") if @expect_label
                if !@quote_delim
                        @term_last.pop if lst != ST_INDEX_ACCESS
                        raise Exception.new("missing a comma after: #{expr_frag}") if !arg_check
                        if @hash_state[-1] == :key
                                @hash_state[-1] = :colon
                        elsif @hash_state[-1] == :value
                                @hash_state[-1] = :comma
                        elsif @hash_state[-1] == :comma
                                raise Exception.new("expecting comma after: #{expr_frag}")
                        end
                        @quote_delim = word
                        @state << ST_STRING
                        @term_str = ''
                elsif @quote_delim == word
                        @quote_delim = nil
                        @state.pop
                        @ptr << @term_str
                        @term_str = nil
                end
        elsif lst == ST_STRING
                @term_str += word
        elsif type == :number_literal
                @term_last.pop if lst != ST_INDEX_ACCESS
                raise Exception.new("expecting identifier after: '#{@ptr[-1][1]}'") if @expect_label
                raise Exception.new("missing a comma after: #{expr_frag}") if !arg_check
                @ptr << (word.index('.') ? word.to_f : word.to_i)

                if @hash_state[-1] == :key
                        @hash_state[-1] = :colon
                elsif @hash_state[-1] == :value
                        @hash_state[-1] = :comma
                end
        elsif type == :identifier
                if lst == ST_LABEL_MEMBER
                        @state.pop
                        @ptr << word
                        @last_ptr[-1][-1] << @ptr
                        @ptr = @last_ptr.pop
                elsif @expect_label
                        @ptr[-1][1] += word
                        @expect_label = false
                else
                        raise Exception.new("missing a comma after: #{expr_frag}") if !arg_check
                        
                        if CONSTS.has_key?(word)
                                @ptr << CONSTS[word]
                        else
                                @ptr << [:var, word]
                        end

                        if @hash_state[-1] == :key
                                @hash_state[-1] = :colon
                        elsif @hash_state[-1] == :value
                                @hash_state[-1] = :comma
                        end
                end
                @term_last << ST_LABEL
        elsif type == :logical_op
                # STORED AS: `[:op, [operand1, operator1, operand2, operator2, ...]]
                # > @last_ptr holds entire structure, @ptr holds structure[1]
                # @todo deal with '-' prefix
                
                # direct assignment unsupported for now, cast to equivalence
                word = '==' if word == '='
                @term_last.pop
                raise Exception.new("expecting identifier after '#{@ptr[-1][1]}'") if @expect_label
                if word == '?'
                        tern = [:op_tern, nil, [], nil]
                        tern[1] = @last_ptr[-1].pop
                        @last_ptr[-1] << tern
                        @ptr = tern[2]
                        @state[-1] = ST_OPERATOR_TERN
                elsif lst == ST_OPERATOR
                        op = word.to_sym
                        prec_l = OPERATOR_MAP[@ptr[-2]]
                        prec_r = OPERATOR_MAP[op]

                        if prec_r > prec_l
                                # need to group next 2 terms since this operator has higher precedence
                                nop = [:op, [@ptr.pop]]
                                @state << ST_OPERATOR
                                @ptr << nop
                                @last_ptr << @ptr
                                @ptr = nop[1]
                        elsif prec_l > prec_r
                                # check if @last_ptr[-1] is an op; pop if so
                                type = @last_ptr[-2].is_a?(Array) ? @last_ptr[-2][0][0] : nil
                                if type == :op
                                        @state.pop
                                        @last_ptr.pop
                                        @ptr = @last_ptr[-1][0][1]
                                end
                        end
                        @ptr << op
                else
                        lele = @ptr.pop
                        op = [:op, [lele, word.to_sym]]
                        @last_ptr << @ptr
                        @ptr << op
                        @ptr = op[1]
                        @state << ST_OPERATOR
                end
        elsif type == :paren_group
                # STORED AS: [:group, [nested_statement]]
                # STORED AS: [:func, 'funcname', [args*]]
                raise Exception.new("expecting identifier after '#{@ptr[-1][1]}'") if @expect_label
                if word == '('
                        if @term_last[-1] == ST_LABEL
                                @state << ST_LABEL_CALL
                                @ptr[-1][0] = :func
                                @ptr[-1][2] = []
                                
                                @last_ptr << @ptr
                                @ptr = @ptr[-1][2]
                                @expect_separator = ','
                        else
                                raise Exception.new("missing a comma after: #{expr_frag}") if !arg_check
                                @state << ST_GROUP
                                @last_ptr << @ptr
                                @ptr << [:group, []]
                                @ptr = @ptr[-1][1]
                        end
                        @term_last.pop
                else
                        do_close = false
                        if lst == ST_OPERATOR
                                do_close = @state[-2] == ST_GROUP
                                if do_close
                                        @state.pop
                                        @ptr = @last_ptr.pop[1]
                                end
                        else
                                do_close = lst == ST_LABEL_CALL || lst == ST_GROUP
                        end
                                
                        if do_close
                                s = @state.pop
                                @ptr = @last_ptr.pop
                                @expect_separator = nil

                                if @hash_state[-1] == :key
                                        @hash_state[-1] = :colon
                                elsif @hash_state[-1] == :value
                                        @hash_state[-1] = :comma
                                end
                        else
                                # @todo exception
                        end
                end
        elsif type == :brace_group
                raise Exception.new("missing a comma after: #{expr_frag}") if !arg_check
                if word == '{'
                        if @hash_state[-1] && @hash_state[-1] != :value
                                raise Exception.new("invalid hash start")
                        end

                        @state << ST_HASH
                        @last_ptr << @ptr
                        @ptr = [:hash]
                        @hash_state << :key
                else
                        if lst == ST_HASH
                                if @hash_state[-1] != :comma
                                        msg = "missing value for key #{@ptr[-1]}"
                                        msg = "missing ':' and a value for key #{@ptr[-1]}" if @hash_state[-1] == :key
                                        msg = "missing a value for key #{@ptr[-1]}" if @hash_state[-1] == :value
                                        raise Exception.new(msg)
                                end

                                @last_ptr[-1] << @ptr
                                @ptr = @last_ptr.pop
                                @state.pop
                                @hash_state.pop

                                if @hash_state[-1] == :key
                                        @hash_state[-1] = :colon
                                elsif @hash_state[-1] == :value
                                        @hash_state[-1] = :comma
                                end
                        else
                                # @todo throw exception
                        end
                end
        elsif type == :index_group
                if word == '['
                        if @term_last[-1] == ST_LABEL
                                @state << ST_INDEX_ACCESS
                                @last_ptr << @ptr
                                @ptr = [:index_access]
                        else
                                raise Exception.new("missing a comma after: #{expr_frag}") if !arg_check
                                # @todo check if array being defined within index_access
                                if @hash_state[-1] && @hash_state[-1] != :value
                                        raise Exception.new("invalid array start near: #{expr_frag}")
                                end

                                @ptr << [:array]
                                @state << ST_ARRAY
                                @last_ptr << @ptr
                                @ptr = @ptr[-1]
                        end
                else
                        if lst == ST_INDEX_ACCESS
                                acc = @ptr
                                @ptr = @last_ptr.pop
                                @ptr[-1] << acc
                                @state.pop
                        elsif lst == ST_ARRAY
                                @ptr = @last_ptr.pop
                                @state.pop

                                if @hash_state[-1] == :key
                                        @hash_state[-1] = :colon
                                elsif @hash_state[-1] == :value
                                        @hash_state[-1] = :comma
                                end
                        else
                                # @todo throw exception
                        end
                end
        elsif type == :separator
                # @todo need to ensure proper separation!!!
                if word == '.'
                        if @term_last[-1] == ST_LABEL
                                @state << ST_LABEL_MEMBER
                                @last_ptr << @ptr
                                @ptr = [:member_access]
                        else
                                # @todo throw exception
                        end
                elsif word == ','
                        if lst == ST_HASH
                                if @hash_state[-1] == :comma
                                        @hash_state[-1] = :key
                                else
                                        raise Exception.new("misplaced comma near: #{expr_frag}")
                                end
                        elsif lst == ST_ARRAY
                                @last_comma = true
                        elsif lst == ST_LABEL_CALL
                                @last_comma = true
                        end
                elsif word == ';'
                end
        elsif type == :modifier
                if word == ':'
                        if lst == ST_HASH
                                if @hash_state[-1] == :colon
                                        @hash_state[-1] = :value
                                else
                                        raise Exception.new("misplaced colon near: #{expr_frag} -- #{@hash_state[-1]}")
                                end
                        elsif lst == ST_OPERATOR_TERN
                                # assumes [:op_tern, cond, true, false] structure
                                tern = @last_ptr[-1][-1]
                                if tern[3] == nil
                                        tern[3] = []
                                        @ptr = tern[3]
                                else
                                        # @todo throw exception. more than 1 ':' encountered
                                end
                        elsif @term_last[-1] == ST_LABEL
                                @ptr[-1][1] += ':'
                                @expect_label = true
                                @term_last[-1] = nil
                        end
                end
        elsif type == :space
        else
                $stderr.write "! parser else: #{word}\n"
        end
end
parse(src, flags = 0) click to toggle source
# File lib/eggshell/expression-evaluator/parser.rb, line 384
def parse(src, flags = 0)
        reset
        begin
                @lexer.process(src)
                emit(:end)
                if @state[-1] != ST_NULL
                        err = []
                        @state[1..-1].each do |st|
                                err << STATE_NAMES[st]
                        end
                        raise Exception.new("Unbalanced state: #{err.join(' -> ')}")
                end
                @tree
        rescue Exception => ex
                $stderr.write("WARN: returning nil, parse exception for: #{src}\n#{ex}\n")
                $stderr.write("\t#{ex.backtrace.join("\n\t")}\n")
                debug
                nil
        end
end
reset() click to toggle source
# File lib/eggshell/expression-evaluator/parser.rb, line 53
def reset()
        @tree = []
        @ptr = @tree
        @last_ptr = [@tree]
        @tokens = []
        @state = [ST_NULL]
        @term_last = [nil]
        @term_str = nil
        @quote_delim = nil
        @word_pos = 0
        @expect_label = false
        @array_state = [0]
        @hash_state = [nil]
        @last_comma = false
end