# [The "BSD licence"]
# Copyright (c) 2005 Martin Traverso
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
#    derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

class String
    def each_char(&block)
        each_byte(&block)
    end
end

module ANTLR
    class CharStream
        EOF = -1

        attr_reader :line
        attr_reader :column
        attr_reader :index

        def initialize(input)
            @buffer = ""
            @input = input
            @line = 1
            @column = 0

            @index = 0;
        end

        # returns a Fixnum between 0 and 0xFFFF or CharStream::EOF
        def look_ahead(pos)
            offset = @index + pos - 1
            if @buffer.length < offset + 1
                char = @input.read(offset + 1 - @buffer.length)
                @buffer << char if not char.nil?
            end

            if offset  < @buffer.length
                @buffer[offset]
            else
                EOF
            end
        end

        def mark
            @state = { :index => @index, :line => @line, :column => @column }
            return 0
        end

        def rewind(marker)
            @index = @state[:index]
            @line = @state[:line]
            @column = @state[:column]
        end

        def consume
           look_ahead(1) # force a read from the input if necessary
           @column = @column + 1
           if @buffer[@index] == ?\n
                @line = @line + 1
                @column = 0
           end
           @index = @index + 1
        end

        def substring(start, stop)
            @buffer.slice(start, stop - start + 1)
        end
    end

    class TokenStream
        attr_reader :index
        
        def initialize(input)
            @buffer = []
            @input = input
            @channel = Token::DEFAULT_CHANNEL

            @index = 0;
        end

        # returns a Token
        def look_ahead(pos)
            offset = @index + pos - 1

            while @buffer[-1] != Token::EOF && @buffer.length < offset + 1
                token = @input.next_token
                if token.channel == @channel || token == Token::EOF
                    @buffer << token
                end
            end

            offset = -1 if offset >= @buffer.length
            if offset < @buffer.length
                @buffer[offset]
            end
        end

        def mark
            @state = { :index => @index }
            return 0
        end

        def rewind(marker)
            @index = @state[:index]
        end

        def consume
           look_ahead(1) # force a read from the input if necessary
           @index = @index + 1
        end
    end


    class Token
        INVALID_TYPE = 0
        EOF = Token.new
        INVALID = Token.new
        DEFAULT_CHANNEL = 0

        attr_reader :token_type
        attr_reader :channel

        def initialize(token_type, channel, input, start, stop)
            @token_type = token_type
            @channel = channel
            @input = input
            @start = start
            @stop = stop
        end

        def line=(value)
            @line = value
        end

        def column=(value)
            @column = value
        end

        def to_s
            "[##{@token_type}, '#{text}', #{@line}:#{@column}, #{@start}, #{@stop}]"
        end

        def text
            @input.substring(@start, @stop)
        end

        def EOF.to_s
            "[EOF]"
        end

        def EOF.token_type
            -1
        end

        def INVALID.to_s
            "[INVALID]"
        end

        def INVALID.token_type
            INVALID_TYPE
        end
    end

    class Lexer
        def initialize(input)
            @input = input
            @backtracking = 0
            @failed = false
            @ruleStack = []
        end

        def match_range(from, to)
            char = @input.look_ahead(1)
            if char < from || char > to
                if @backtracking > 0
                    @failed = true
                    return
                end
                mre = MismatchedRangeException.new(from, to, @input)
                #recover(mre);
                raise mre;
            end
            @input.consume()
            @failed = false
        end

        def match_string(str)
            str.each_char { |b|
                if @input.look_ahead(1) != b
                    if @backtracking > 0
                        @failed = true
                        return
                    end
                    mte = MismatchedTokenException.new(b, @input)
                    #recover(mte);
                    raise mte
                end
                @input.consume()
                @failed = false
            }
        end

        def match(char)
            if @input.look_ahead(1) != char
                if @backtracking > 0
                    @failed = true
                    return
                end
                mte = MismatchedTokenException.new(char, @input)
                #recover(mte)
                raise mte
            end
            @input.consume
            @failed = false
        end

        def match_any
            @input.consume
        end

        def synpred(fragment)
            start = @input.mark
            @backtracking = @backtracking + 1
            begin
                send(fragment)
            rescue RecognitionException
                puts "impossible"
            end
            @input.rewind(start)
            @backtracking = @backtracking - 1;

            success = !@failed;
            @failed = false;
            return @success
        end

        def report_error(exception)
            STDERR << "[" + @ruleStack.join(",") + "]: " if !@ruleStack.empty?

            input = exception.input
            line = input.line
            column = input.column
            char = convert(input.look_ahead(1))

            STDERR << case exception
                when EarlyExitException
                    "required (...)+ loop (decision=#{exception.decisionNumber}) did not match anything; on line #{line}:#{column} char='#{char}'"
                when MismatchedTokenException
                    "mismatched char: '#{char}' on line #{line}; expecting char '#{convert(exception.expecting)}'"
                when MismatchedRangeException
                    "mismatched char: '#{char}' on line #{line}:#{column}; expecting set '#{convert(exception.from)}'..'#{convert(exception.to)}'"
                when NoViableAltException
                    "#{exception.description} state #{exception.stateNumber} (decision=#{exception.decisionNumber}) no viable alt line #{line}:#{column}; char='#{char}'"
                when FailedPredicateException
                    "rule #{exception.rule} failed predicate: {#{exception.predicate}}?"
                when MismatchedSetException
                    "mismatched char: '#{char}' on line #{line}:#{column}; expecting set #{exception.expecting}"
                else
                    raise exception
            end << "\n"
        end

        def convert(char)
            case char
                when CharStream::EOF: 'EOF'
                when ?\n: '\n'
                when ?\t: '\t'
                else char.chr
            end
        end
    end

    class DFA
        def DFA.predict(input, start)
            mark = input.mark
            begin
                state = start;
                while true
                    state = state.call(input)
                    return 1 if state.nil? # problem; nothing predicted.  Choose alt 1
                    return state if state.is_a? Fixnum # don't consume atom (then exit) if alt predicted
                    input.consume
                end
            ensure
                input.rewind(mark)
            end
        end
    end

    class Parser
        def initialize(input)
            @input = input
            @backtracking = 0
            @failed = false
            @errorRecovery = false
            @following = []
            @ruleStack = []
        end

        def match(tokenType, bitset)
            if @input.look_ahead(1).token_type == tokenType
                @input.consume
                @errorRecovery = false;
                @failed = false;
                return
            elsif @backtracking > 0
                @failed = true;
                return;
            else
                mte = MismatchedTokenException.new(tokenType, @input);
                #recoverFromMismatchedToken(input, mte, ttype, follow);

                raise mte
            end
        end

        def match_any
            @input.consume
        end

        def synpred(fragment)
            start = @input.mark
            @backtracking = @backtracking + 1
            begin
                send(fragment)
            rescue RecognitionException
                puts "impossible"
            end
            @input.rewind(start)
            @backtracking = @backtracking - 1

            success = !@failed
            @failed = false
            return success
        end

        def report_error(exception)
            return if @errorRecovery
            @errorRecovery = true

            STDERR << "[" + @ruleStack.join(",") + "]: "

            begin
                token = @input.look_ahead(1)
            rescue
                token = Token::INVALID
            end

            STDERR << case exception
                when MismatchedTokenException
                    "mismatched token: #{token_names[token.token_type]}; expecting type #{token_names[exception.expecting]}"
                when MismatchedSetException
                    "mismatched token: #{token_names[token.token_type]}; expecting set #{exception.expecting}"
                when NoViableAltException
                    "decision=<< #{exception.description}>> state #{exception.stateNumber} (decision=#{exception.decisionNumber}) no viable alt; token=#{token_names[token.token_type]}"
                when EarlyExitException
                    "required (...)+ loop (decision=#{exception.decisionNumber}) did not match anything; token=#{token_names[token.token_type]}"
                when FailedPredicateException
                    "rule #{exception.rule} failed predicate: {#{exception.predicate}}?"
                else
                    #raise exception
            end << "\n"
        end
    end

    #class BitSet
    #    def initialize(words)
    #        @value = 0
    #        words.each { |word|
    #            @value << 64
    #            @value = @value + word
    #        }
    #    end
    #end



    class RecognitionException < RuntimeError
        def initialize(input)
            @input = input
        end

        attr_reader :input
        attr :token
    end

    class EarlyExitException < RecognitionException
        def initialize(decisionNumber, input)
            super(input)
            @decisionNumber = decisionNumber
        end

        attr_reader :decisionNumber
    end

    class MismatchedTokenException < RecognitionException
        def initialize(expecting, input)
            super(input)
            @expecting = expecting
        end

        attr_reader :expecting
    end

    class MismatchedRangeException < RecognitionException
        def initialize(from, to, input)
            super(input)
            @from = from
            @to = to
        end

        attr_reader :from
        attr_reader :to
    end

    class NoViableAltException < RecognitionException
        def initialize(description, decisionNumber, stateNumber, input)
            super(input)
            @description = description
            @decisionNumber = decisionNumber
            @stateNumber = stateNumber
        end

        attr_reader :decisionNumber
        attr_reader :stateNumber
        attr_reader :description
    end

    class FailedPredicateException < RecognitionException
        def initialize(input, rule, predicate)
            super(input)
            @rule = rule
            @predicate = predicate
        end

        def to_s
            "FailedPredicateException(#{ruleName},{#{predicateText}}?)";
        end

        attr_reader :rule
        attr_reader :predicate
    end

    class MismatchedSetException < RecognitionException
        def initialize(expecting, input)
            super(input)
            @expecting = expecting
        end

        attr_reader :expecting
    end
end
