'''Basic parsers

Created on 7 august 2012

@author: fredo
'''

from collections import defaultdict

from popparser.llparser import ParseResult, ParseError


#==============================================================================
# ABSTRACT BASE CLASS FOR PARSERS
#==============================================================================

class Parser:
    '''The root abstract class for parsers.

    Remark: it is possible to apply transformations to
    parse results or result contents, e.g. transforming
    a token content by a simple string,  a string by
    a numeral, etc.
    The two method available are:
      1) xform_result: ParseResult -> ParseResult
      2) xform_content: ParseResult -> Content

    Transforming result has priority over transforming content
    '''
    def __init__(self):
        self.xform_result = None
        self.xform_content = None
        self.forget_parsers = {}

    def forget(self, parser):
        self.forget_parsers[parser.token_type] = parser
        return self

    def forget_parse(self, llparsing):
        while True:
            next_token = llparsing.peek_token()
            if next_token.token_type in self.forget_parsers:
                forget_parser = self.forget_parsers[next_token.token_type]
                result = forget_parser.parse(llparsing)
                if result.iserror:
                    return result # ?
            else:
                return None

    @property
    def token_type(self):
        raise NotImplementedError("Abstract method")

    def parse(self, llparsing):
        if llparsing.debug_mode:
            llparsing.debug.enter(llparsing, self)

        result = self.forget_parse(llparsing)
        if result is not None and result.iserror:
            return result

        result = self.do_parse(llparsing)

        fresult = self.forget_parse(llparsing)
        if fresult is not None and fresult.iserror:
            return fresult

        if llparsing.debug_mode:
            llparsing.debug.leave(llparsing, self)

        if not result.iserror:
            if self.xform_result:
                result = self.xform_result(result)
            elif self.xform_content:
                result = ParseResult(self.xform_content(result),
                                     result.start_pos, result.end_pos)
        return result

    def do_parse(self, llparsing):
        raise NotImplementedError("Abstract method")


#==============================================================================
# TOKEN PARSING
#==============================================================================


class Token(Parser):
    '''Parse a single token.
    '''
    def __init__(self, token_type):
        Parser.__init__(self)
        self.__token_type = token_type

    @property
    def token_type(self):
        return self.__token_type

    def do_parse(self, llparsing):
        start_pos = llparsing.position
        token = llparsing.peek_token()
        if token.token_type == self.__token_type:
            return Pa
rseResult(llparsing.next_token(),
                               start_pos, llparsing.position)
        else:
          
  return ParseError("Expecting '{0}' token"\
                              .format(self.__token_type),
                              start_pos, llparsing.position)


class EOF(Token):
    '''Parse End of file (EOF).
    '''
    def __init__(self):
        Token.__init__(self, '<<EOF>>')


#==============================================================================
# TUPLE PARSER
#==============================================================================

class Tuple(Parser):
    '''Tuple parser.
    '''
    def __init__(self):
        Parser.__init__(self)
        self.__parsers = []
        self.__skips = defaultdict()

    def element(self, parser):
        self.__parsers.append(parser)
        return self  # chaining API

    def skip(self, parser):
        if len(self.__parsers) not in self.__skips:
            self.__skips[len(self.__parsers)] = []
        skips = self.__skips[len(self.__parsers)]
        skips.append(parser)
        return self

    @property
    def token_type(self):
        if 0 in self.__skips:
            first = self.__skips[0]
            if first:
                return first[0].token_type
        elif self.__parsers:
            return self.__parsers[0].token_type
        else:
            return None

    def do_parse(self, llparser):
        start_pos = llparser.position
        results = []
        for i in range(len(self.__parsers)):
            # forget parsers
            result = self.forget_parse(llparser)
            if result is not None and result.iserror:
                return result

            # skip parsers
            skips = None
            if i in self.__skips:
                skips = self.__skips[i]
            if skips:
                for skip in skips:
                    result = skip.parse(llparser)
                    if result.iserror:
                        return result
            # element
            parser = self.__parsers[i]
            result = parser.parse(llparser)
            if result.iserror:
                return result
            results.append(result)

        # last skips
        skips = None
        if len(self.__parsers) in self.__skips:
            skips = self.__skips[len(self.__parsers)]
        if skips:
            for skip in skips:
                result = skip.parse(llparser)
                if result.iserror:
                    return result

        end_pos = llparser.position
        if len(results) == 1:
            results = results[0]
        return ParseResult(results, start_pos, end_pos)


#==============================================================================
# REPEAT PARSER
#==============================================================================

class Repeat(Parser):
    '''Repeat parser.
    '''
    def __init__(self, parser, minimum=0):
        Parser.__init__(self)
        self.minimum = minimum
        self.parser = parser

    @property
    def token_type(self):
        return self.parser.token_type

    def do_parse(self, llparser):
        start_pos = llparser.position
        count = 0
        results = []
        while True:
            # forget parsers
            result = self.forget_parse(llparser)
            if result is not None and result.iserror:
                return result

            result = self.parser.parse(llparser)
            if result.iserror:
                if count == 0 and self.minimum == 0:
                    return ParseResult(None, start_pos, llparser.position)
                elif 0 < count < self.minimum:
                    return ParseError('{0} repetition(s) is not enough '
                                      '(minimum={1})'\
                                      .format(count, self.minimum),\
                                      start_pos,\
                                      llparser.position)
                    # TODO: stacking errors ?
                else:
                    return ParseResult(results, start_pos, llparser.position)
            results.append(result)


#==============================================================================
# LIST PARSER
#==============================================================================

class List(Parser):
    '''List parser.
    '''
    def __init__(self, of, open=None, close=None, sep=None, minimum=0):
        Parser.__init__(self)
        self.minimum = minimum
        self.open_token = open
        self.close_token = close
        self.sep_token = sep
        self.parser = of

    @property
    def token_type(self):
        if self.open_token is None:
            return self.parser.token_type
        else:
            return self.open_token

    def do_parse(self, llparser):
        start_pos = llparser.position
        count = 0
        results = []
        if self.open_token is not None:
            next_token = llparser.peek_token()
            if next_token.token_type != self.open_token:
                return ParseError("Expecting open list token '{0}' got: {1}"\
                        .f