<reponame>earwig/bitshift
from ..languages import LANGS

__all__ = ["String", "Regex", "Text", "Language", "Author", "Date", "Symbol",
           "BinaryOp", "UnaryOp"]

class _Node(object):
    """Represents a single node in a query tree.

    Generally speaking, a node is a constraint applied to the database. Thus,
    a :py:class:`~.Language` node represents a constraint where only codelets
    of a specific language are selected.
    """

    def _null_regex(self, expr):
        """Implements a regex search with support for a null expression."""
        return "IF(ISNULL(%s), 0, %s REGEXP ?)" % (expr, expr)

    def sortkey(self):
        """Return a string sort key for the node."""
        return ""

    def parameterize(self, tables):
        """Parameterize the node.

        Returns a 4-tuple of (conditional string, parameter list, rank list,
        should-we-rank boolean). If the rank list is empty, then it is assumed
        to contain the conditional string.
        """
        return "", [], [], False


class _Literal(object):
    """Represents a literal component of a search query, present at the leaves.

    A literal might be a string or a regular expression.
    """
    pass


class String(_Literal):
    """Represents a string literal."""

    def __init__(self, string):
        """
        :type string: unicode
        """
        self.string = string

    def __repr__(self):
        return "String({0!r})".format(self.string)

    def sortkey(self):
        return self.string


class Regex(_Literal):
    """Represents a regular expression literal."""

    def __init__(self, regex):
        """
        :type string: unicode
        """
        self.regex = regex

    def __repr__(self):
        return "Regex({0!r})".format(self.regex)

    def sortkey(self):
        return self.regex


class Text(_Node):
    """Represents a text node.

    Searches in codelet names (full-text search), symbols (equality), and
    source code (full-text search).
    """

    def __init__(self, text):
        """
        :type text: :py:class:`._Literal`
        """
        self.text = text

    def __repr__(self):
        return "Text({0})".format(self.text)

    def sortkey(self):
        return self.text.sortkey()

    def parameterize(self, tables):
        tables |= {"code", "symbols"}
        if isinstance(self.text, Regex):
            ranks = ["(codelet_name REGEXP ?)", "(code_code REGEXP ?)",
                     self._null_regex("symbol_name")]
            text = self.text.regex
        else:
            ranks = ["(MATCH(codelet_name) AGAINST (? IN BOOLEAN MODE))",
                     "(MATCH(code_code) AGAINST (? IN BOOLEAN MODE))",
                     "(symbol_name <=> ?)"]
            text = self.text.string
        cond = "(" + " OR ".join(ranks) + ")"
        return cond, [text] * 3, ranks, True


class Language(_Node):
    """Represents a language node.

    Searches in the code_lang field.
    """

    def __init__(self, lang):
        """
        :type lang: int
        """
        self.lang = lang

    def __repr__(self):
        return "Language({0})".format(LANGS[self.lang])

    def sortkey(self):
        return LANGS[self.lang]

    def parameterize(self, tables):
        tables |= {"code"}
        return "(code_lang <=> ?)", [self.lang], [], False


class Author(_Node):
    """Represents a author node.

    Searches in the author_name field (full-text search).
    """

    def __init__(self, name):
        """
        :type name: :py:class:`_Literal`
        """
        self.name = name

    def __repr__(self):
        return "Author({0})".format(self.name)

    def sortkey(self):
        return self.name.sortkey()

    def parameterize(self, tables):
        tables |= {"authors"}
        if isinstance(self.name, Regex):
            cond = self._null_regex("author_name")
            return cond, [self.name.regex], [], False
        cond = "(MATCH(author_name) AGAINST (? IN BOOLEAN MODE))"
        return cond, [self.name.string], [], True


class Date(_Node):
    """Represents a date node.

    Searches in the codelet_date_created or codelet_date_modified fields.
    """
    CREATE = 1
    MODIFY = 2

    BEFORE = 1
    AFTER = 2

    def __init__(self, type_, relation, date):
        """
        :type type_: int (``CREATE`` or ``MODIFY``)
        :type relation: int (``BEFORE``, ``AFTER``)
        :type date: datetime.datetime
        """
        self.type = type_
        self.relation = relation
        self.date = date

    def __repr__(self):
        types = {self.CREATE: "CREATE", self.MODIFY: "MODIFY"}
        relations = {self.BEFORE: "BEFORE", self.AFTER: "AFTER"}
        tm = "Date({0}, {1}, {2})"
        return tm.format(types[self.type], relations[self.relation], self.date)

    def sortkey(self):
        return self.date.strftime("%Y%m%d%H%M%S")

    def parameterize(self, tables):
        column = {self.CREATE: "codelet_date_created",
                  self.MODIFY: "codelet_date_modified"}[self.type]
        op = {self.BEFORE: "<=", self.AFTER: ">="}[self.relation]
        cond = "IF(ISNULL(%s), 0, %s %s ?)" % (column, column, op)
        return cond, [self.date], [], False


class Symbol(_Node):
    """Represents a symbol node.

    Searches in symbol_type and symbol_name.
    """
    ALL = -1
    DEFINE = 0
    USE = 1

    FUNCTION = 0
    CLASS = 1
    VARIABLE = 2
    NAMESPACE = 3
    INTERFACE = 4
    IMPORT = 5
    TYPES = ["functions", "classes", "vars", "namespaces", "interfaces",
             "imports"]
    TYPE_REPR = ["FUNCTION", "CLASS", "VARIABLE", "NAMESPACE", "INTERFACE",
                 "IMPORT"]

    def __init__(self, context, type_, name):
        """
        :type context: int (``DEFINE`` or ``USE``)
        :type type_: int (``ALL``, ``FUNCTION``, ``CLASS``, etc.)
        :type name: :py:class:`._Literal`
        """
        self.context = context
        self.type = type_
        self.name = name

    def __repr__(self):
        context = ["DEFINE", "USE", "ALL"][self.context]
        type_ = self.TYPE_REPR[self.type] if self.type >= 0 else "ALL"
        return "Symbol({0}, {1}, {2})".format(context, type_, self.name)

    def sortkey(self):
        return self.name.sortkey()

    def parameterize(self, tables):
        tables |= {"code", "symbols"}
        if isinstance(self.name, Regex):
            cond, name = self._null_regex("symbol_name"), self.name.regex
        else:
            cond, name = "symbol_name <=> ?", self.name.string
            if self.type == self.ALL:
                types = ", ".join(str(typ) for typ in xrange(len(self.TYPES)))
                part = " AND IF(ISNULL(symbol_type), 0, symbol_type IN (%s))"
                cond += part % types
        if self.type != self.ALL:
            cond += " AND symbol_type <=> %d" % self.type
        if self.context != self.ALL:
            tables |= {"symbol_locations"}
            cond += " AND sloc_type <=> %d" % self.context
        return "(" + cond + ")", [name], [], False


class BinaryOp(_Node):
    """Represents a relationship between two nodes: ``and``, ``or``."""
    AND = object()
    OR = object()
    OPS = {AND: "AND", OR: "OR"}

    def __init__(self, left, op, right):
        self.left = left
        self.op = op
        self.right = right

    def __repr__(self):
        tmpl = "BinaryOp({0}, {1}, {2})"
        return tmpl.format(self.left, self.OPS[self.op], self.right)

    def sortkey(self):
        return self.left.sortkey() + self.right.sortkey()

    def parameterize(self, tables):
        lcond, largs, lranks, need_lranks = self.left.parameterize(tables)
        rcond, rargs, rranks, need_rranks = self.right.parameterize(tables)
        lranks, rranks = lranks or [lcond], rranks or [rcond]
        op = self.OPS[self.op]
        cond = "(" + lcond  + " " + op + " " + rcond + ")"
        need_ranks = need_lranks or need_rranks or self.op == self.OR
        return cond, largs + rargs, lranks + rranks, need_ranks


class UnaryOp(_Node):
    """Represents a transformation applied to one node: ``not``."""
    NOT = object()
    OPS = {NOT: "NOT"}

    def __init__(self, op, node):
        self.op = op
        self.node = node

    def __repr__(self):
        return "UnaryOp({0}, {1})".format(self.OPS[self.op], self.node)

    def sortkey(self):
        return self.node.sortkey()

    def parameterize(self, tables):
        cond, args, ranks, need_ranks = self.node.parameterize(tables)
        new_cond = "(" + self.OPS[self.op] + " " + cond + ")"
        ranks = ranks or [cond]
        return new_cond, args, ranks, need_ranks

from ..languages import LANGS

__all__ = ["String", "Regex", "Text", "Language", "Author", "Date", "Symbol",
           "BinaryOp", "UnaryOp"]

class _Node(object):
    """Represents a single node in a query tree.

    Generally speaking, a node is a constraint applied to the database. Thus,
    a :py:class:`~.Language` node represents a constraint where only codelets
    of a specific language are selected.
    """

    def _null_regex(self, expr):
        """Implements a regex search with support for a null expression."""
        return "IF(ISNULL(%s), 0, %s REGEXP ?)" % (expr, expr)

    def sortkey(self):
        """Return a string sort key for the node."""
        return ""

    def parameterize(self, tables):
        """Parameterize the node.

        Returns a 4-tuple of (conditional string, parameter list, rank list,
        should-we-rank boolean). If the rank list is empty, then it is assumed
        to contain the conditional string.
        """
        return "", [], [], False


class _Literal(object):
    """Represents a literal component of a search query, present at the leaves.

    A literal might be a string or a regular expression.
    """
    pass


class String(_Literal):
    """Represents a string literal."""

    def __init__(self, string):
        """
        :type string: unicode
        """
        self.string = string

    def __repr__(self):
        return "String({0!r})".format(self.string)

    def sortkey(self):
        return self.string


class Regex(_Literal):
    """Represents a regular expression literal."""

    def __init__(self, regex):
        """
        :type string: unicode
        """
        self.regex = regex

    def __repr__(self):
        return "Regex({0!r})".format(self.regex)

    def sortkey(self):
        return self.regex


class Text(_Node):
    """Represents a text node.

    Searches in codelet names (full-text search), symbols (equality), and
    source code (full-text search).
    """

    def __init__(self, text):
        """
        :type text: :py:class:`._Literal`
        """
        self.text = text

    def __repr__(self):
        return "Text({0})".format(self.text)

    def sortkey(self):
        return self.text.sortkey()

    def parameterize(self, tables):
        tables |= {"code", "symbols"}
        if isinstance(self.text, Regex):
            ranks = ["(codelet_name REGEXP ?)", "(code_code REGEXP ?)",
                     self._null_regex("symbol_name")]
            text = self.text.regex
        else:
            ranks = ["(MATCH(codelet_name) AGAINST (? IN BOOLEAN MODE))",
                     "(MATCH(code_code) AGAINST (? IN BOOLEAN MODE))",
                     "(symbol_name <=> ?)"]
            text = self.text.string
        cond = "(" + " OR ".join(ranks) + ")"
        return cond, [text] * 3, ranks, True


class Language(_Node):
    """Represents a language node.

    Searches in the code_lang field.
    """

    def __init__(self, lang):
        """
        :type lang: int
        """
        self.lang = lang

    def __repr__(self):
        return "Language({0})".format(LANGS[self.lang])

    def sortkey(self):
        return LANGS[self.lang]

    def parameterize(self, tables):
        tables |= {"code"}
        return "(code_lang <=> ?)", [self.lang], [], False


class Author(_Node):
    """Represents a author node.

    Searches in the author_name field (full-text search).
    """

    def __init__(self, name):
        """
        :type name: :py:class:`_Literal`
        """
        self.name = name

    def __repr__(self):
        return "Author({0})".format(self.name)

    def sortkey(self):
        return self.name.sortkey()

    def parameterize(self, tables):
        tables |= {"authors"}
        if isinstance(self.name, Regex):
            cond = self._null_regex("author_name")
            return cond, [self.name.regex], [], False
        cond = "(MATCH(author_name) AGAINST (? IN BOOLEAN MODE))"
        return cond, [self.name.string], [], True


class Date(_Node):
    """Represents a date node.

    Searches in the codelet_date_created or codelet_date_modified fields.
    """
    CREATE = 1
    MODIFY = 2

    BEFORE = 1
    AFTER = 2

    def __init__(self, type_, relation, date):
        """
        :type type_: int (``CREATE`` or ``MODIFY``)
        :type relation: int (``BEFORE``, ``AFTER``)
        :type date: datetime.datetime
        """
        self.type = type_
        self.relation = relation
        self.date = date

    def __repr__(self):
        types = {self.CREATE: "CREATE", self.MODIFY: "MODIFY"}
        relations = {self.BEFORE: "BEFORE", self.AFTER: "AFTER"}
        tm = "Date({0}, {1}, {2})"
        return tm.format(types[self.type], relations[self.relation], self.date)

    def sortkey(self):
        return self.date.strftime("%Y%m%d%H%M%S")

    def parameterize(self, tables):
        column = {self.CREATE: "codelet_date_created",
                  self.MODIFY: "codelet_date_modified"}[self.type]
        op = {self.BEFORE: "<=", self.AFTER: ">="}[self.relation]
        cond = "IF(ISNULL(%s), 0, %s %s ?)" % (column, column, op)
        return cond, [self.date], [], False


class Symbol(_Node):
    """Represents a symbol node.

    Searches in symbol_type and symbol_name.
    """
    ALL = -1
    DEFINE = 0
    USE = 1

    FUNCTION = 0
    CLASS = 1
    VARIABLE = 2
    NAMESPACE = 3
    INTERFACE = 4
    IMPORT = 5
    TYPES = ["functions", "classes", "vars", "namespaces", "interfaces",
             "imports"]
    TYPE_REPR = ["FUNCTION", "CLASS", "VARIABLE", "NAMESPACE", "INTERFACE",
                 "IMPORT"]

    def __init__(self, context, type_, name):
        """
        :type context: int (``DEFINE`` or ``USE``)
        :type type_: int (``ALL``, ``FUNCTION``, ``CLASS``, etc.)
        :type name: :py:class:`._Literal`
        """
        self.context = context
        self.type = type_
        self.name = name

    def __repr__(self):
        context = ["DEFINE", "USE", "ALL"][self.context]
        type_ = self.TYPE_REPR[self.type] if self.type >= 0 else "ALL"
        return "Symbol({0}, {1}, {2})".format(context, type_, self.name)

    def sortkey(self):
        return self.name.sortkey()

    def parameterize(self, tables):
        tables |= {"code", "symbols"}
        if isinstance(self.name, Regex):
            cond, name = self._null_regex("symbol_name"), self.name.regex
        else:
            cond, name = "symbol_name <=> ?", self.name.string
            if self.type == self.ALL:
                types = ", ".join(str(typ) for typ in xrange(len(self.TYPES)))
                part = " AND IF(ISNULL(symbol_type), 0, symbol_type IN (%s))"
                cond += part % types
        if self.type != self.ALL:
            cond += " AND symbol_type <=> %d" % self.type
        if self.context != self.ALL:
            tables |= {"symbol_locations"}
            cond += " AND sloc_type <=> %d" % self.context
        return "(" + cond + ")", [name], [], False


class BinaryOp(_Node):
    """Represents a relationship between two nodes: ``and``, ``or``."""
    AND = object()
    OR = object()
    OPS = {AND: "AND", OR: "OR"}

    def __init__(self, left, op, right):
        self.left = left
        self.op = op
        self.right = right

    def __repr__(self):
        tmpl = "BinaryOp({0}, {1}, {2})"
        return tmpl.format(self.left, self.OPS[self.op], self.right)

    def sortkey(self):
        return self.left.sortkey() + self.right.sortkey()

    def parameterize(self, tables):
        lcond, largs, lranks, need_lranks = self.left.parameterize(tables)
        rcond, rargs, rranks, need_rranks = self.right.parameterize(tables)
        lranks, rranks = lranks or [lcond], rranks or [rcond]
        op = self.OPS[self.op]
        cond = "(" + lcond  + " " + op + " " + rcond + ")"
        need_ranks = need_lranks or need_rranks or self.op == self.OR
        return cond, largs + rargs, lranks + rranks, need_ranks


class UnaryOp(_Node):
    """Represents a transformation applied to one node: ``not``."""
    NOT = object()
    OPS = {NOT: "NOT"}

    def __init__(self, op, node):
        self.op = op
        self.node = node

    def __repr__(self):
        return "UnaryOp({0}, {1})".format(self.OPS[self.op], self.node)

    def sortkey(self):
        return self.node.sortkey()

    def parameterize(self, tables):
        cond, args, ranks, need_ranks = self.node.parameterize(tables)
        new_cond = "(" + self.OPS[self.op] + " " + cond + ")"
        ranks = ranks or [cond]
        return new_cond, args, ranks, need_ranks
Represents a single node in a query tree.      Generally speaking, a node is a constraint applied to the database. Thus,     a :py:class:`~.Language` node represents a constraint where only codelets     of a specific language are selected. Implements a regex search with support for a null expression. Return a string sort key for the node. Parameterize the node.          Returns a 4-tuple of (conditional string, parameter list, rank list,         should-we-rank boolean). If the rank list is empty, then it is assumed         to contain the conditional string. Represents a literal component of a search query, present at the leaves.      A literal might be a string or a regular expression. Represents a string literal. :type string: unicode Represents a regular expression literal. :type string: unicode Represents a text node.      Searches in codelet names (full-text search), symbols (equality), and     source code (full-text search). :type text: :py:class:`._Literal` Represents a language node.      Searches in the code_lang field. :type lang: int Represents a author node.      Searches in the author_name field (full-text search). :type name: :py:class:`_Literal` Represents a date node.      Searches in the codelet_date_created or codelet_date_modified fields. :type type_: int (``CREATE`` or ``MODIFY``)         :type relation: int (``BEFORE``, ``AFTER``)         :type date: datetime.datetime Represents a symbol node.      Searches in symbol_type and symbol_name. :type context: int (``DEFINE`` or ``USE``)         :type type_: int (``ALL``, ``FUNCTION``, ``CLASS``, etc.)         :type name: :py:class:`._Literal` Represents a relationship between two nodes: ``and``, ``or``. Represents a transformation applied to one node: ``not``.