#!/usr/bin/env python
"""
$URL: svn+ssh://svn/repos/trunk/grouch/lib/test/utest_parsetype.py $
$Id: utest_parsetype.py 25104 2004-09-14 12:41:32Z dbinger $
"""

from types import IntType, StringType, FloatType
from sancho.utest import UTest

from grouch.valuetype import UnionType
from grouch.type_parser import TypeScanner, TypeParser


# Set up a fake module hierarchy:
#   module 'foo' provides class Foo (and sub-module bar)
#   module 'foo.bar' provides class FooBar
#   module '__main__' provides class Plain
from grouch.test import fake_module

_scanner = TypeScanner()
_parser = TypeParser()

# Emulate the old standalone 'parse_type()' function.
def parse_type (typestr, schema):
    global _scanner, _parser
    _parser.set_schema(schema)
    return _parser.parse(_scanner.tokenize(typestr))


class ParseTypeTest (UTest):

    def _pre (self):
        self.schema = fake_module.make_schema()

    def _post (self):
        del self.schema


    test_cases = ['atomic',
                  'misc',
                  'instance',
                  'list',
                  'tuple',
                  'dict',
                  'instcont',
                  'recursive_1',
                  'recursive_2',
                  'errors',
                  'alias']


    def check_atomic (self):
        "parse atomic and 'any' types"
        t = parse_type('int', self.schema)
        assert str(t) == "int"
        assert t.is_atomic_type()
        assert t.type == IntType

        try:
            parse_type('list', self.schema)
            assert 0
        except ValueError: pass
        try:
            parse_type('foo', self.schema)
            assert 0
        except ValueError: pass
        try:
            parse_type(42, self.schema)
            assert 0
        except TypeError: pass

    def check_misc (self):
        "parse miscellaneous types (any, boolean)"
        t = parse_type('any', self.schema)
        assert t.is_any_type()

        t = parse_type('boolean', self.schema)
        assert t.is_boolean_type()

        t = parse_type('[boolean]', self.schema)
        assert str(t) == "[boolean]"
        assert t.get_element_type().is_boolean_type()

        t = parse_type('{boolean : (any,any)}', self.schema)
        assert str(t) == "{ boolean : (any, any) }"
        assert t.get_key_type().is_boolean_type()
        assert t.get_value_type().get_element_types()[0].is_any_type()

    def check_instance (self):
        "parse instance types"
        from foo import Foo, bar
        _used = [Foo, bar] # so that import checker does not complain.
        t = parse_type('foo.Foo', self.schema)
        assert t.klass_name == "foo.Foo"
        assert str(t) == "foo.Foo"
        assert t.is_instance_type()

        t = parse_type('foo.bar.FooBar', self.schema)
        assert t.klass_name == "foo.bar.FooBar"
        assert str(t) == "foo.bar.FooBar"

        try:
            parse_type('bar.FooBar', self.schema)
            assert 0
        except ValueError: pass
        try:
            parse_type('Foo', self.schema)
            assert 0
        except ValueError: pass

        t = parse_type('Plain', self.schema)
        assert t.klass_name == "Plain"
        assert str(t) == "Plain"

    def check_list (self):
        "parse simple list types"
        t = parse_type('[int]', self.schema)
        assert t.is_container_type()
        assert t.element_type.type == IntType
        assert str(t) == "[int]"

        try:
            parse_type('[StringType]', self.schema)
            assert 0
        except ValueError: pass
        try:
            parse_type('[list]', self.schema)
            assert 0
        except ValueError: pass
        try:
            parse_type('[string', self.schema)
            assert 0
        except ValueError: pass

        t = parse_type('[foo.Foo]', self.schema)
        assert t.is_container_type()
        assert t.element_type.is_instance_type()
        assert str(t) == "[foo.Foo]"

        try:
            parse_type('[foo.FooBar]', self.schema)
            assert 0
        except ValueError: pass
        t = parse_type('[Plain]', self.schema)
        assert str(t) == "[Plain]"
        assert t.get_element_type().get_class_name() == "Plain"

        # should allow empty list (tuple, dict) someday, but for now we don't
        try:
            parse_type('[]', self.schema)
            assert 0
        except ValueError: pass
        try:
            parse_type('[int, string]', self.schema)
            assert 0
        except ValueError: pass

        # but as long as [any] works, [] isn't really needed!
        t = parse_type('[any]', self.schema)
        assert str(t) == "[any]"
        assert t.is_container_type()
        assert not t.is_any_type()
        assert t.get_element_type().is_any_type()

    def check_tuple (self):
        "parse simple tuple types"
        t = parse_type('(string,)', self.schema)
        assert str(t) == "(string,)"
        assert t.is_container_type()
        assert t.container_type == "tuple"
        assert len(t.element_types) == 1
        assert t.element_types[0].type == StringType

        t = parse_type('(int,string)', self.schema)
        assert str(t) == "(int, string)"
        assert not t.is_extended()
        assert len(t.element_types) == 2
        assert t.element_types[0].type == IntType
        assert t.element_types[1].type == StringType

        t = parse_type('(int, string*)', self.schema)
        assert str(t) == "(int, string*)"
        assert t.is_extended()
        assert len(t.element_types) == 2
        assert t.element_types[0].type == IntType
        assert t.element_types[1].type == StringType

        try:
            parse_type('(IntType)', self.schema)
            assert 0
        except ValueError: pass
        try:
            parse_type('(IntType,)', self.schema)
            assert 0
        except ValueError: pass
        try:
            parse_type('(list)', self.schema)
            assert 0
        except ValueError: pass

        try:
            parse_type('()', self.schema)
            assert 0
        except ValueError: pass
        t = parse_type('(any, string)', self.schema)
        assert str(t) == "(any, string)"
        assert t.get_element_types()[0].is_any_type()

    def check_dict (self):
        "parse simple dictionary types"
        t = parse_type('{float: string}', self.schema)
        assert str(t) == "{ float : string }"
        assert t.is_container_type()
        assert t.container_type == "dictionary"
        assert t.key_type.type == FloatType
        assert t.value_type.type == StringType

        try:
            parse_type('{float:}', self.schema)
            assert 0
        except ValueError: pass
        try:
            parse_type('{string: float ', self.schema)
            assert 0
        except ValueError: pass
        try:
            parse_type('{IntType : string}', self.schema)
            assert 0
        except ValueError: pass

        try:
            parse_type('{}', self.schema)
            assert 0
        except ValueError: pass
        try:
            parse_type('{int:string, int:float}', self.schema)
            assert 0
        except ValueError: pass

        t = parse_type('{string : any}', self.schema)
        assert str(t) == "{ string : any }"
        assert t.get_value_type().is_any_type()

    def check_set (self):
        "parse simple set types"
        t = parse_type('{int}', self.schema)
        assert t.is_container_type()
        assert t.element_type.type == IntType
        assert str(t) == "{int}"

        t = parse_type('{foo.Foo}', self.schema)
        assert t.is_container_type()
        assert t.element_type.is_instance_type()
        assert str(t) == "{foo.Foo}"

        t = parse_type('{any}', self.schema)
        assert str(t) == "{any}"
        assert t.is_container_type()
        assert not t.is_any_type()
        assert t.get_element_type().is_any_type()

    def check_instcont (self):
        "parse instance-container types"
        from foo import Foo
        _used = Foo # so that import checker does not complain.
        t = parse_type('foo.Foo [int]', self.schema)
        assert t.is_instance_type()
        assert t.is_container_type()
        assert t.is_instance_container_type()
        assert str(t) == "foo.Foo [int]"
        assert t.get_class_name() == "foo.Foo"
        assert t.get_element_type().type == IntType

        t = parse_type('foo.Foo (float,string)', self.schema)
        assert str(t) == "foo.Foo (float, string)"
        assert t.get_class_name() == "foo.Foo"
        assert [t.type for t in t.get_element_types()] == [FloatType,
                                                           StringType]

        t = parse_type('foo.Foo {string:int}', self.schema)
        assert str(t) == "foo.Foo { string : int }"
        assert t.get_class_name() == "foo.Foo"
        assert t.get_item_types()[0].get_type() == StringType
        assert t.get_item_types()[1].get_type() == IntType

        # An instance-container that contains a container
        t = parse_type('foo.Foo {string : (int,string)}', self.schema)
        assert str(t) == "foo.Foo { string : (int, string) }"
        # And one that contains another instance-container
        t = parse_type('foo.Foo { int : foo.bar.FooBar [string] }',
                       self.schema)
        assert str(t) == "foo.Foo { int : foo.bar.FooBar [string] }"

    def check_recursive_1 (self):
        "parse simple recursive type specifications"
        t = parse_type('[ (int,float) ]', self.schema)
        assert str(t) == "[(int, float)]"
        assert t.is_container_type()
        subtype = t.element_type
        assert subtype.is_container_type()
        assert (subtype.element_types[0].type,
                subtype.element_types[1].type) == (IntType, FloatType)

        t = parse_type('int | [string]', self.schema)
        assert str(t) == "int | [string]"
        assert t.is_union_type()
        assert len(t.union_types) == 2
        (alt1, alt2) = t.union_types
        assert alt1.type == IntType
        assert alt2.is_container_type()
        assert alt2.container_type == "list"
        assert alt2.element_type.type == StringType

    def check_recursive_2 (self):
        "parse complex recursive type specifications"

        # Of course, anyone who would use a dictionary mapping
        # (int,string) tuples to either foo.Foo or foo.bar.FooBar
        # instances in production code should be shot... ;-)

        t1 = parse_type("{(int,string):foo.Foo|foo.bar.FooBar}", self.schema)
        assert str(t1) == "{ (int, string) : foo.Foo | foo.bar.FooBar }"
        (keytype, valuetype) = t1.get_item_types()
        assert str(keytype) == "(int, string)"
        assert str(valuetype) == "foo.Foo | foo.bar.FooBar"

        # Now this is just silly.
        t2 = parse_type("[{float|long : [(string, foo.Foo)]}]", self.schema)
        assert str(t2) == "[{ float | long : [(string, foo.Foo)] }]"
        eltype = t2.element_type
        (keytype, valuetype) = eltype.get_item_types()
        assert str(keytype) == "float | long"
        assert str(valuetype) == "[(string, foo.Foo)]"

        # And this is plain gratuitous.
        t = UnionType(self.schema)
        t.set_union_types([t1, t2])
        assert str(t) == "%s | %s" % (t1, t2)

        # Not much better
        t = self.schema.make_tuple_type((t1, parse_type("int", self.schema)))
        assert str(t) == "(%s, int)" % t1

    def check_errors (self):
        "parse erroneous names and dotted-named in various contexts"
        # Mainly for checking the error messages and ensuring that we
        # hit every branch of 'parse_type()' and 'create_subtype()'
        msg_fmt = ("invalid type '%s': "
                   "not an atomic type, alias, or known class")
        try:
            parse_type('FooBar', self.schema)
            assert 0
        except ValueError, exc:
            assert str(exc) == msg_fmt % "FooBar"

        try:
            parse_type('foo.bar', self.schema)
            assert 0
        except ValueError, exc:
            assert str(exc) == msg_fmt % "foo.bar"

        try:
            parse_type('[FooBar]', self.schema)
            assert 0
        except ValueError, exc:
            assert str(exc) == msg_fmt % "FooBar"

        try:
            parse_type('FooBar|int', self.schema)
            assert 0
        except ValueError, exc:
            assert str(exc) == msg_fmt % "FooBar"

        try:
            parse_type('int|FooBar', self.schema)
            assert 0
        except ValueError, exc:
            assert str(exc) == msg_fmt % "FooBar"

        try:
            parse_type('[ding.dong]', self.schema)
            assert 0
        except ValueError, exc:
            assert str(exc) == msg_fmt % "ding.dong"

    def check_alias (self):
        "parse types with aliases"
        schema = self.schema
        schema.add_alias("real", "int|float|long")
        schema.add_alias("range", "(real,real)")

        t = parse_type('{string:range}', schema)
        assert str(t) == "{ string : range }"
        #self.test_val("t.expand_aliases()",
        #              "{ string : (int | float | long, int | float | long) }")
        assert not t.is_alias_type()

        vtype = t.get_value_type()
        assert vtype.is_alias_type()
        assert str(vtype) == "range"
        assert vtype.get_alias_type().container_type == "tuple"
        etypes = vtype.get_alias_type().get_element_types()
        assert etypes[0].is_alias_type()
        assert etypes[0].get_alias_type() == schema.get_alias("real")
        assert etypes[0].get_alias_type().is_union_type()

        from foo import Foo
        _used = Foo # so that import checker does not complain.
        schema.add_alias("Foo", "foo.Foo")
        t = parse_type('Foo [int]', schema)
        assert t.is_instance_container_type()
        assert t.get_class_name() == "foo.Foo"
        assert t.get_element_type().get_type() == IntType


if __name__ == "__main__":
    ParseTypeTest()



