########################################################################
#
# File Name: 	        PostProcessor.py
#
# Documentation:	http://docs.ftsuite.com/4ODS/Drivers/PostProcessor.py.html
#
"""
Post process and validate the raw parse tree
WWW: http://4suite.org/4ODS         e-mail: support@4suite.org

Copyright (c) 1999 Fourthought, Inc., USA.   All Rights Reserved.
See  http://4suite.org/COPYRIGHT  for license and copyright information
"""

#There are 3 steps in this file
#1. Validation
#2. Translation
#3. Compression
import string

from Ft.Ods.Exception import FtodsUnsupportedError, FtodsUnknownError, ObjectNameNotFound, FtodsPostProcessorError

from Ft.Ods.MetaData import Module
from Ft.Ods.MetaData import Class
from Ft.Ods.MetaData import Attribute
from Ft.Ods.MetaData import Relationship
from Ft.Ods.MetaData import Collection
from Ft.Ods.MetaData import Repository
from Ft.Ods.MetaData import Literal
from Ft.Ods.MetaData import ConstOperand
from Ft.Ods.MetaData import Enumeration
from Ft.Ods.MetaData import Constant
from Ft.Ods.MetaData import Expression
from Ft.Ods.MetaData import Member
from Ft.Ods.MetaData import Structure
from Ft.Ods.MetaData import DefiningScope
from Ft.Ods.MetaData import PrimitiveKind
from Ft.Ods.MetaData import CollectionKind
from Ft.Ods.MetaData import MetaKind
from Ft.Ods.MetaData import Parameter
from Ft.Ods.MetaData import Direction
from Ft.Ods.MetaData import Operation
from Ft.Ods.MetaData import Operand

from Ft.Ods import Constants
#Need so we can complete both sides

from Ft.Ods.Parsers.Odl import OdlUtil
from Ft.Ods import Collections

class Processor:
    def __init__(self, database):
        self.db = database
        self.relationships = {}
        self.forwards = {}
        self.collections = 0
        self.definingScope = None
        self.repository = database.schema()

    def run(self, parse_tree):
        parent = self.repository.resolve('ODLMetaObjects')
        
        global g_relationships, g_forwards, g_repository
        global g_collectionNameCtr, g_currentDefScope
        g_relationships = self.relationships
        g_forwards = self.forwards
        g_repository = self.repository
        g_collectionNameCtr = self.collections

        g_currentDefScope = self.definingScope

        ProcessSpecification(self.db, parse_tree[0], parent)
        if self.relationships:
            import sys
            for (parent, identifier) in self.relationships.keys():
                sys.stderr.write('No inverse to relationship %s.%s' % (
                    str(parent.name), identifier))
            raise FtodsUnknownError(msg='Inverse relationship error')
        return

# (1) specification ::= definition
#                     | definition specification
def ProcessSpecification(db, spec, parent):
    ProcessDefinition(db, spec[0], parent)
    if spec.get(1):
        ProcessSpecification(db, spec[1], parent)
    return

# (2) definition ::= type_dcl ';'
#                  | const_dcl ';'
#                  | except_dcl ';'
#                  | interface ';'
#                  | module ';'
#                  | class ';'
def ProcessDefinition(db,d,parent):
    #A definition is anything top level
    #Split based on type
    if d[0]['TYPE'] == 'TYPE_DCL':
        ProcessTypeDcl(db,d[0],parent)
    elif d[0]['TYPE'] == 'CONST_DCL':
        ProcessConstDcl(db,d[0],parent)
    elif d[0]['TYPE'] == 'EXCEPT_DCL':
        ProcessExceptDcl(db,d[0],parent)
    elif d[0]['TYPE'] == 'INTERFACE':
        ProcessInterface(db,d[0],parent)
    elif d[0]['TYPE'] == 'MODULE':
        ProcessModule(db,d[0],parent)
    elif d[0]['TYPE'] == 'CLASS':
        ProcessClass(db,d[0],parent)

# (2a) class ::= class_dcl
#              | class_forward_dcl
def ProcessClass(db, klass, parent):
    if klass[0]['TYPE'] == 'CLASS_DCL':
        ProcessClassDcl(db, klass[0], parent)
    elif klass[0]['TYPE'] == 'CLASS_FORWARD_DCL':
        ProcessClassForwardDcl(db, klass[0], parent)

# (2b) class_dcl ::= class_header '{' interface_body '}'
def ProcessClassDcl(db, klass, parent):
    #A class is pretty simple, it can have a header and a body.  That's it
    name, extender, inherits, propList = ProcessClassHeader(db, klass[0], parent)

    #See if this was forward declared
    absoluteName = '%s::%s' % (parent.absolute_name(), name)
    
    c = g_forwards.get(absoluteName)
    if c:
        #This has been declared before, use it and fill in the new information
        if inherits:
            for i in inherits:
                c.add_inherits(i)
        if extender:
            c.form_extender(extender)
    else:
        c = parent.add_class(name, inherits, extender)

    # Allows for multiple forward declarations
    g_forwards[absoluteName] = None

    (extent, keys) = propList
        
    if extent:
        c.extents.insert_element(extent)

    for k in keys:
        c.keys.insert_element(k)

    ProcessInterfaceBody(db,klass[2],c)

    return

# (2c) class_forward_dcl ::= "class" IDENTIFIER
def ProcessClassForwardDcl(db, forward, parent):

    #Create a new instance of a class so it can be used later
    name = forward[1]
    absoluteName = '%s::%s' % (parent.absolute_name(), name)
    if not g_forwards.has_key(absoluteName):
        klass = parent.add_class(name, None, None)
        g_forwards[absoluteName] = klass


# (2d) class_header ::= "class" IDENTIFIER
#                     | "class" IDENTIFIER "extends" scoped_name
#                     | "class" IDENTIFIER "extends" scoped_name ':' inheritance_spec
#                     | "class" IDENTIFIER "extends" scoped_name type_property_list
#                     | "class" IDENTIFIER "extends" scoped_name ':' inheritance_spec type_property_list
#                     | "class" IDENTIFIER ':' inheritance_spec
#                     | "class" IDENTIFIER ':' inheritance_spec type_property_list
#                     | "class" IDENTIFIER type_property_list
def ProcessClassHeader(db, header, parent):
    #This is one of the most complex definitions in the system
    #a class header in logical form is (extends ext_name)? (inheritance_spec)? (type_property_list)?

    #FIXME we do not support extends, inheritance, or type lists
    extends = None
    inherits = None
    propList = (None,[])
    pos = 2
    if header.get(pos) == 'extends':
        extends = ProcessScopedName(db, header[3], parent)
        # skip scope_name
        pos = pos + 2
        
    argument = header.get(pos)
    if argument and argument == ':':
        pos = pos + 1
        inherits = ProcessInheritanceSpec(db, header[pos], parent)
        # possibly followed by type_property_list
        argument = header.get(pos+1)
    if argument and argument['TYPE'] == 'TYPE_PROPERTY_LIST':
        propList = ProcessTypePropertyList(db, argument, parent)

    name = header[1]
    return name, extends, inherits, propList


# (2e) type_property_list ::= '(' ')'
#                           | '(' extent_spec ')'
#                           | '(' key_spec ')'
#                           | '(' extent_spec key_spec ')'
def ProcessTypePropertyList(db, tpl, parent):

    #NOTE the spec says there can be 2 of these, we only support 1
    extent = None
    keys = []

    argument = tpl.get(2) and tpl[1]
    if argument and argument['TYPE'] == 'EXTENT_SPEC':
        extent = ProcessExtentSpec(db, argument, parent)
        argument = tpl.get(3) and tpl[2]
    if argument and argument['TYPE'] == 'KEY_SPEC':
        keys = ProcessKeySpec(db,tpl[curCtr],parent)
    return extent, keys

# (2f) extent_spec ::= "extent" IDENTIFIER
def ProcessExtentSpec(db, extent_spec, parent):
    return extent_spec[1]

# (2g) key_spec ::= "key" key_list
#                 | "keys" key_list
def ProcessKeySpec(db, key_spec, parent):
    raise FtodsUnsupportedError(feature="Key Specification")

# (2h) key_list ::= key
#                 | key ',' key_list
def ProcessKeyList(db, key_list, parent):
    raise FtodsUnsupportedError(feature="Key List")

# (2i) key ::= property_name
#            | '(' property_list ')'
def ProcessKey(db, key, parent):
    raise FtodsUnsupportedError(feature="Key")

# (2j) property_list ::= property_name
#                      | property_name ',' property_list
def ProcessPropertyList(db, property_list, parent):
    raise FtodsUnsupportedError(feature="Property List")

# (2k) property_name ::= IDENTIFIER
def ProcessPropertyName(db, property_name, parent):
    return property_name[0]

# (3) module ::= "module" IDENTIFIER '{' specification '}'
def ProcessModule(db, module, parent):
    #Simple wrapper around a specification list
    name = module[1]
    m = parent.add_module(name)
    ProcessSpecification(db, module[3], m)
    return m

# (4) interface ::= interface_dcl
#              | interface_forward_dcl
def ProcessInterface(db, interface, parent):
    if interface[0]['TYPE'] == 'INTERFACE_DCL':
        ProcessInterfaceDcl(db, interface[0], parent)
    elif interface[0]['TYPE'] == 'INTERFACE_FORWARD_DCL':
        ProcessInterfaceForwardDcl(db, interface[0], parent)
    else:
        print interface[0]
        raise Foo


# (5) interface_dcl ::= interface_header '{' [interface_body] '}'
def ProcessInterfaceDcl(db, interface, parent):

    name, inherits = ProcessInterfaceHeader(db, interface[0], parent)

    #See if this was forward declared
    absoluteName = '%s::%s' % (parent.absolute_name(), name)
    
    i = g_forwards.get(absoluteName)
    if i:
        #This has been declared before, use it and fill in the new information
        if inherits:
            for inh in inherits:
                i.add_inherits(inh)
    else:
        i = parent.add_interface(name, inherits)

    # Allows for multiple forward declarations
    g_forwards[absoluteName] = None


    if interface.has_key(3):
        ProcessInterfaceBody(db,interface[2],i)

    return


# (6) interface_forward_dcl ::= "interface" IDENTIFIER
def ProcessInterfaceForwardDcl(db, forward, parent):

    #Create a new instance of a interface so it can be used later
    name = forward[1]
    absoluteName = '%s::%s' % (parent.absolute_name(), name)
    if not g_forwards.has_key(absoluteName):
        interface = parent.add_interface(name, None)
        g_forwards[absoluteName] = interface


# (7) interface_header ::= "interface" IDENTIFIER
#                         | "interface" IDENT ':' inheritance_spec

def ProcessInterfaceHeader(db, header, parent):

    name = header[1]
    if header.get(2):
        in_sp = ProcessInheritanceSpec(db,header[3],parent)
    else:
        in_sp = []
    return name,in_sp



# (8) interface_body ::= export
#                      | export interface_body
def ProcessInterfaceBody(db, interface_body, parent):
    ProcessExport(db, interface_body[0], parent)
    if interface_body.get(1):
        ProcessInterfaceBody(db, interface_body[1], parent)

# (9) export ::= type_dcl ';'
#              | const_dcl ';'
#              | except_dcl ';'
#              | attr_dcl ';'
#              | rel_dcl ';'
#              | op_dcl ';'
def ProcessExport(db, export, parent):
    dcl = export[0]
    if dcl['TYPE'] == 'TYPE_DCL':
        return ProcessTypeDcl(db, dcl, parent)
    elif dcl['TYPE'] == 'CONST_DCL':
        return ProcessConstDcl(db, dcl, parent)
    elif dcl['TYPE'] == 'EXCEPT_DCL':
        return ProcessExceptDcl(db, dcl, parent)
    elif dcl['TYPE'] == 'ATTR_DCL':
        return ProcessAttrDcl(db, dcl, parent)
    elif dcl['TYPE'] == 'REL_DCL':
        return ProcessRelDcl(db, dcl, parent)
    elif dcl['TYPE'] == 'OP_DCL':
        return ProcessOpDcl(db, dcl, parent)

# (10) inheritance_spec ::= scoped_name [, inheritance_spec]

def ProcessInheritanceSpec(db, inheritance_spec, parent):
    specs = [ProcessScopedName(db,inheritance_spec[0],parent)]
    if inheritance_spec.get(1):
        specs.extend(ProcessInheritanceSpec(db, inheritance_spec[2], parent))
    return specs


# (11) scoped_name ::= IDENTIFIER
#                    | "::" IDENTIFIER
#                    | scoped_name "::" IDENTIFIER
def ProcessScopedName(db, scoped_name, parent):
    name = RebuildScopedName(db, scoped_name)
    rt = ResolveName(db, name, parent)
    if rt == None:
        raise ObjectNameNotFound(name=name)
    return rt


def RebuildScopedName(db, scoped_name):
    name = ''
    if not scoped_name.get(1):
        #Simple Name
        name =  scoped_name[0]
    elif not scoped_name.get(2):
        #Global scoped name
        name =  scoped_name[1]
    else:
        #A name in a chain
        first = RebuildScopedName(db, scoped_name[0])
        name = first + '::' + scoped_name[2]

    return name


# (12) const_dcl ::= "const" const_type IDENTIFIER '=' const_exp
def ProcessConstDcl(db, const_dcl, parent):
    const_type = ProcessConstType(db, const_dcl[1], parent)
    ident = const_dcl[2]
    const_exp = ProcessConstExp(db, const_dcl[4], parent)
    const_exp.name = ident

    #Create the const on the parent
    const = parent.add_constant(ident, const_type, const_exp)
    return const


# (13) const_type ::= interger_type
#                   | char_type
#                   | boolean_type
#                   | floating_pt_type
#                   | string_type
#                   | scoped_name
def ProcessConstType(db, const_type, parent):
    type = const_type[0]
    if type['TYPE'] == 'INTEGER_TYPE':
        return ProcessIntegerType(db, type, parent)
    elif type['TYPE'] == 'CHAR_TYPE':
        return ProcessCharType(db, type, parent)
    elif type['TYPE'] == 'BOOLEAN_TYPE':
        return ProcessBooleanType(db, type, parent)
    elif type['TYPE'] == 'FLOATING_PT_TYPE':
        return ProcessFloatingPtType(db, type, parent)
    elif type['TYPE'] == 'STRING_TYPE':
        return ProcessStringType(db, type, parent)
    elif type['TYPE'] == 'SCOPED_NAME':
        return ProcessScopedName(db, type, parent)

# (14) const_exp ::= or_expr
def ProcessConstExp(db, const_exp, parent):
    #Parent could be none.  Is this bad???
    return ProcessOrExpr(db, const_exp[0], parent)

# (15) or_expr ::= xor_expr
#                | or_expr '|' xor_expr
def ProcessOrExpr(db, or_expr, parent):
    if or_expr.get(1):
        left = ProcessOrExpr(db, or_expr[0], parent)
        right = ProcessXorExpr(db, or_expr[2], parent)
        exp = Expression.Expression(db,None)
        exp.add_the_operands(left)
        exp.add_the_operands(right)
        exp.operator = '|'
        return exp
    else:
        return ProcessXorExpr(db, or_expr[0], parent)

# (16) xor_expr ::= and_expr
#                 | xor_expr '^' and_expr
def ProcessXorExpr(db, xor_expr, parent):
    if xor_expr.get(1):
        left = ProcessXorExpr(db, xor_expr[0],parent)
        right = ProcessAndExpr(db, xor_expr[2],parent)
        exp = Expression.Expression(db,None)
        exp.add_the_operands(left)
        exp.add_the_operands(right)
        exp.operator = '^'
        return exp
    else:
        return ProcessAndExpr(db, xor_expr[0], parent)

# (17) and_expr ::= shift_expr
#                 | and_expr '&' shift_expr
def ProcessAndExpr(db, and_expr, parent):
    if and_expr.get(1):
        left = ProcessAndExpr(db, and_expr[0], parent)
        right = ProcessShiftExpr(db, and_expr[2], parent)
        exp = Expression.Expression(db,None)
        exp.add_the_operands(left)
        exp.add_the_operands(right)
        exp.operator = '&'
        return exp
    else:
        return ProcessShiftExpr(db, and_expr[0], parent)

# (18) shift_expr ::= add_expr
#                   | shift_expr ">>" add_expr
#                   | shift_expr "<<" add_expr
def ProcessShiftExpr(db, shift_expr, parent):
    if shift_expr.get(1):
        left = ProcessShiftExpr(db, shift_expr[0], parent)
        right = ProcessAddExpr(db, shift_expr[2], parent)
        exp = Expression.Expression(db,None)
        exp.add_the_operands(left)
        exp.add_the_operands(right)
        exp.operator = shift_expr[1]
        return exp
    else:
        return ProcessAddExpr(db, shift_expr[0], parent)

# (19) add_expr ::= mult_expr
#                 | add_expr '+' mult_expr
#                 | add_expr '-' mult_expr
def ProcessAddExpr(db, add_expr, parent):
    if add_expr.get(1):
        left = ProcessAddExpr(db, add_expr[0], parent)
        right = ProcessMultExpr(db, add_expr[2], parent)
        exp = Expression.Expression(db,None)
        exp.add_the_operands(left)
        exp.add_the_operands(right)
        exp.operator = add_expr[1]
        return exp
    else:
        return ProcessMultExpr(db, add_expr[0], parent)

# (20) mult_expr ::= unary_expr
#                  | mult_expr '*' unary_expr
#                  | mult_expr '/' unary_expr
#                  | mult_expr '%' unary_expr
def ProcessMultExpr(db, mult_expr, parent):
    if mult_expr.get(1):
        left = ProcessMultExpr(db, mult_expr[0], parent)
        right = ProcessUnaryExpr(db, mult_expr[2], parent)
        exp = Expression.Expression(db,None)
        exp.add_the_operands(left)
        exp.add_the_operands(right)
        exp.operator = mult_expr[1]
        return exp
    else:
        return ProcessUnaryExpr(db, mult_expr[0], parent)

# (21) unary_expr ::= unary_operator primary_expr
#                   | primary_expr
# (22) unary_operator ::= '+' | '-' | '~'
def ProcessUnaryExpr(db, unary_expr, parent):
    if unary_expr.get(1):
        right = ProcessPrimaryExpr(db, unary_expr[1], parent)
        exp = Expression.Expression(db,None)
        exp.add_the_operands(right)
        exp.operator = unary_expr[0][0]
        return exp
    else:
        return ProcessPrimaryExpr(db,unary_expr[0],parent)

# (23) primary_expr ::= scoped_name
#                     | literal
#                     | '(' const_exp ')'
def ProcessPrimaryExpr(db, primary_expr, parent):
    if primary_expr.get(1):
        right = ProcessConstExp(db, primary_expr[1], parent)
        exp = Expression.Expression(db,None)
        exp.add_the_operands(right)
        exp.operator = '()'
        return exp
    defn = primary_expr[0]
    if defn['TYPE'] == 'LITERAL':
        return ProcessLiteral(db, defn, parent)
    else:
        # scoped_name: this needs to be a ConstOperand!!!!
        const = ProcessScopedName(db, defn, parent)
        co = ConstOperand.ConstOperand(db, None)
        co.form_references(const)
        return co
    
# (24) literal ::= integer_literal
#                | string_literal
#                | character_literal
#                | floating_pt_literal
#                | boolean_literal
# (25) boolean_literal ::= "true" | "false"
def ProcessLiteral(db, literal, parent):
    l = Literal.Literal(db, None)
    value = literal[0][0]
    if literal[0]['TYPE'] == 'INTEGER_LITERAL':
        value = int(value)
    elif literal[0]['TYPE'] == 'FLOATING_PT_LITERAL':
        value = float(value)
    elif literal[0]['TYPE'] == 'BOOLEAN_LITERAL':
        value = (value == 'true')
    else:
        # character or string literal
        value = eval(value)
    l.literal_value = value
    return l

# (26) positive_int_const ::= const_exp
def ProcessPositiveIntConstant(db, positive_int, parent):
    const_exp = ProcessConstExp(db, positive_int[0], parent)
    if const_exp.value() <= 0:  # XXX Assuming strictly positive is meant XXX
        raise FtodsPostProcessorError(msg="Positive int constant with a negative value")
    return const_exp;


# (27) type_dcl ::= "typedef" type_declarator
#                 | struct_type
#                 | union_type
#                 | enum_type
def ProcessTypeDcl(db, type_dcl, parent):
    if type_dcl[0] == 'typedef':
        dcl = ProcessTypeDeclarator(db, type_dcl[1], parent)
    elif type_dcl[0]['TYPE'] == 'ENUM_TYPE':
        dcl = ProcessEnumType(db, type_dcl[0], parent)
    elif type_dcl[0]['TYPE'] == 'STRUCT_TYPE':
        dcl = ProcessStructType(db, type_dcl[0], parent)
    elif type_dcl[0]['TYPE'] == 'UNION_TYPE':
        dcl = ProcessUnionType(db, type_dcl[0], parent)
    else:
        #FIXME don't support rest of type dcls
        raise FtodsUnsupportedError(feature=type_dcl[0]['TYPE'])

    #Create a type for this td
    return dcl

# (28) type_declarator ::= type_spec declarators
def ProcessTypeDeclarator(db, type_declarator, parent):
    type_spec = ProcessTypeSpec(db, type_declarator[0], parent)
    declarators = ProcessDeclarators(db, type_declarator[1], parent)
    for dcl in declarators:
        last = parent.add_type_definition(dcl, type_spec)
    return last

# (29) type_spec ::= simple_type_spec
#                  | constr_type_spec
def ProcessTypeSpec(db, type_spec, parent):
    type_spec = type_spec[0]
    if type_spec['TYPE'] == 'SIMPLE_TYPE_SPEC':
        return ProcessSimpleTypeSpec(db, type_spec, parent)
    elif type_spec['TYPE'] == 'CONSTR_TYPE_SPEC':
        return ProcessConstrTypeSpec(db, type_spec, parent)

# (30) simple_type_spec ::= base_type_spec
#                         | tempalte_type_spec
#                         | scoped_name
def ProcessSimpleTypeSpec(db, simple_type_spec, parent):
    type_spec = simple_type_spec[0]
    if type_spec['TYPE'] == 'BASE_TYPE_SPEC':
        return ProcessBaseTypeSpec(db, type_spec, parent)
    elif type_spec['TYPE'] == 'TEMPLATE_TYPE_SPEC':
        return ProcessTemplateTypeSpec(db, type_spec, parent)
    elif type_spec['TYPE'] == 'SCOPED_NAME':
        return ProcessScopedName(db, type_spec, parent)

# (31) base_type_spec ::= floating_pt_type
#                       | integer_type
#                       | char_type
#                       | boolean_type
#                       | octet_type
#                       | date_type
#                       | time_type
#                       | interval_type
#                       | timestamp_type
#                       | blob_type
def ProcessBaseTypeSpec(db, base_type_spec, parent):
    type_spec = base_type_spec[0]
    if type_spec['TYPE'] == 'FLOATING_PT_TYPE':
        return ProcessFloatingPtType(db, type_spec, parent)
    elif type_spec['TYPE'] == 'INTEGER_TYPE':
        return ProcessIntegerType(db, type_spec, parent)
    elif type_spec['TYPE'] == 'CHAR_TYPE':
        return ProcessCharType(db, type_spec, parent)
    elif type_spec['TYPE'] == 'DATE_TYPE':
        return ProcessDateType(db, type_spec, parent)
    elif type_spec['TYPE'] == 'INTERVAL_TYPE':
        return ProcessIntervalType(db, type_spec, parent)
    elif type_spec['TYPE'] == 'TIME_TYPE':
        return ProcessTimeType(db, type_spec, parent)
    elif type_spec['TYPE'] == 'TIMESTAMP_TYPE':
        return ProcessTimeStampType(db, type_spec, parent)
    elif type_spec['TYPE'] == 'BOOLEAN_TYPE':
        return ProcessBooleanType(db, type_spec, parent)
    elif type_spec['TYPE'] == 'BLOB_TYPE':
        return db.schema().resolve(PrimitiveKind.pk_blob._n)
    elif type_spec['TYPE'] == 'OCTET_TYPE':
        return db.schema().resolve(PrimitiveKind.pk_octet._n)
    else:
        raise FtodsUnsupportedError(feature=type_spec['TYPE'])

# (31a) date_type ::= "date"

def ProcessDateType(db,dt,parent):
    kind = PrimitiveKind.pk_date
    return db.schema().resolve(kind._n)
    

# (31b) time_type ::= "time"
def ProcessTimeType(db,dt,parent):
    kind = PrimitiveKind.pk_time
    return db.schema().resolve(kind._n)

# (31c) interval_type ::= "interval"
def ProcessIntervalType(db,dt,parent):
    kind = PrimitiveKind.pk_interval
    return db.schema().resolve(kind._n)


# (31d) timestamp_type ::= "timestamp"
def ProcessTimeStampType(db,dt,parent):
    kind = PrimitiveKind.pk_timestamp
    return db.schema().resolve(kind._n)


# (31-ft) blob_type ::= "blob"

# (32) template_type_spec ::= array_type
#                           | string_type
#                           | coll_type
def ProcessTemplateTypeSpec(db, template_type_spec, parent):
    type_spec = template_type_spec[0]
    if type_spec['TYPE'] == 'ARRAY_TYPE':
        return ProcessArrayType(db, type_spec, parent)
    elif type_spec['TYPE'] == 'STRING_TYPE':
        return ProcessStringType(db, type_spec, parent)
    elif type_spec['TYPE'] == 'COLL_TYPE':
        return ProcessCollType(db, type_spec, parent)

# (32a) coll_type ::= coll_spec '<' simple_type_spec '>'
#                   | "dictionary" '<' simple_type_spec ',' simple_type_spec '>'
def ProcessCollType(db, coll_type, parent):
    if coll_type[3] == ',':
        # dictionary
        keyType = ProcessSimpleTypeSpec(db, coll_type[2], parent)
        valueType = ProcessSimpleTypeSpec(db, coll_type[4], parent)

        coll = parent.create_dictionary_type(keyType, valueType)
        global g_collectionNameCtr
        coll.name = "Dictionary%d" % g_collectionNameCtr
        g_collectionNameCtr = g_collectionNameCtr + 1
    else:
        # collecton
        coll = ProcessCollSpec(db, coll_type[0], parent)
        type_spec = ProcessSimpleTypeSpec(db, coll_type[2], parent)
        coll.form_subtype(type_spec)
    return coll

# (32b) coll_spec ::= "set" | "list" | "bag"
def ProcessCollSpec(db, coll_spec, parent):
    if coll_spec[0] == 'list':
        kind = CollectionKind.ck_list
    elif coll_spec[0] == 'bag':
        kind = CollectionKind.ck_bag
    elif coll_spec[0] == 'set':
        kind = CollectionKind.ck_set
    coll = Collection.Collection(db, None, collection_kind=kind)
    global g_collectionNameCtr
    coll.name = "Collection%d" % g_collectionNameCtr
    g_collectionNameCtr = g_collectionNameCtr + 1
    return coll

# (33) constr_type_spec ::= struct_type
#                         | union_type
#                         | enum_type
def ProcessConstrTypeSpec(db, constr_type_spec, parent):
    type_spec = constr_type_spec[0]
    if type_spec['TYPE'] == 'STRUCT_TYPE':
        return ProcessStructType(db, type_spec, parent)
    elif type_spec['TYPE'] == 'ENUM_TYPE':
        return ProcessEnumType(db, type_spec, parent)
    else:
        raise FtodsUnsupportedError(feature=type_spec['TYPE'])        


# (34) declarators ::= declarator
#                    | declarator ',' declarators
def ProcessDeclarators(db, declarators, parent):
    decl = [ProcessDeclarator(db,declarators[0],parent)]
    if declarators.get(2):
        decl = decl + ProcessDeclarators(db, declarators[2], parent)
    return decl

# (35) declarator ::= simple_declarator
#                   | complex_declarator
# (36) simple_declarator ::= IDENTIFIER
def ProcessDeclarator(db, declarator, parent):
    if declarator[0]['TYPE'] == 'SIMPLE_DECLARATOR':
        return declarator[0][0]
    else:
        raise FtodsUnsupportedError(feature=declarator[0]['TYPE'])        

# (37) complex_declarator ::= array_declarator


# (38) floating_pt_type ::= "float" | "double"
def ProcessFloatingPtType(db, floating_pt_type, parent):
    type = floating_pt_type[0]
    if type == 'float':
        kind = PrimitiveKind.pk_float
    else:
        kind = PrimitiveKind.pk_double
    return db.schema().resolve(kind._n)


# (39) integer_type ::= signed_int
#                     | unsigned_int
# (40) signed_int ::= signed_long_int
#                   | signed_long_long_int
#                   | signed_shoft_int
# (41) signed_long_int ::= "long"
# (41a) signed_long_long_int ::= "long" "long"
# (42) signed_short_int ::= "short"
# (43) unsigned_int ::= unsigned_long_int
#                     | unsigned_short_int
# (44) unsigned_long_int ::= "unsigned" "long"
# (45) unsigned_short_int ::= "unsigned" "short"
def ProcessIntegerType(db, integer_type, parent):
    type = integer_type[0][0]['TYPE']
    name = integer_type[0][0][0]
    if integer_type[0][0].has_key(1):
        name = name + ' ' + integer_type[0][0][1]
    if type == 'UNSIGNED_SHORT_INT':
        kind = PrimitiveKind.pk_ushort
    elif type == 'SIGNED_SHORT_INT':
        kind = PrimitiveKind.pk_short
    elif type == 'UNSIGNED_LONG_INT':
        kind = PrimitiveKind.pk_ulong
    elif type == 'SIGNED_LONG_INT':
        kind = PrimitiveKind.pk_long
    elif type == 'SIGNED_LONG_LONG_INT':
        kind = PrimitiveKind.pk_long_long
    return db.schema().resolve(kind._n)


# (46) char_type ::= "char"
def ProcessCharType(db, char_type, parent):
    pk = PrimitiveKind.pk_char
    return db.schema().resolve(pk._n)

# (47) boolean_type ::= "boolean"
def ProcessBooleanType(db, boolean_type, parent):
    pk = PrimitiveKind.pk_boolean
    return db.schema().resolve(pk._n)

# (48) octet_type ::= "octet"

# carried over from CORBA IDL
# (49) any_type ::= "any"

# (50) struct_type ::= "struct" INDENTIFIER '{' member_list '}'
def ProcessStructType(db, struct_type, parent):
    struct = Structure.Structure(db,None)
    struct.name = struct_type[1]

    #Only structs inside of a defining scope get bound
    if isinstance(parent,DefiningScope.DefiningScope):
        parent.bind(struct.name,struct)
    
    members = ProcessMemberList(db, struct_type[3], struct)
    for m in members:
        struct.bind(m.name,m)
    return struct

# (51) member_list ::= member
#                    | member member_list
def ProcessMemberList(db, member_list, parent):
    list = ProcessMember(db, member_list[0], parent)
    if member_list.get(1):
        list = list + ProcessMemberList(db, member_list[1], parent)
    return list

# (52) member ::= type_spec declarators ';'
def ProcessMember(db, member, parent):
    type_spec = ProcessTypeSpec(db, member[0], parent)
    decls = ProcessDeclarators(db, member[1], parent)
    members = []
    for d in decls:
        m = Member.Member(db,None)
        m.name = d
        m.form_type(type_spec)
        members.append(m)
    return members
        

# (53) union_type ::= "union" IDENTIFIER "switch" '(' switch_type_spec ')' '{' switch_body '}'
def ProcessUnionType(db,union,parent):
    name = union[1]
    st = ProcessSwitchType(db,union[4],parent)
    sb = ProcessSwitchBody(db,union[7],parent,st)

    return parent.add_union(name,st,sb)

# (54) switch_type_spec ::= integer_type
#                         | char_type
#                         | boolean_type
#                         | enum_type
#                         | scoped_name

def ProcessSwitchType(db,switchType,parent):
    if switchType[0]['TYPE'] == 'INTEGER_TYPE':
        return ProcessIntegerType(db,switchType[0],parent)
    elif switchType[0]['TYPE'] == 'CHAR_TYPE':
        return ProcessCharType(db,switchType[0],parent)
    elif switchType[0]['TYPE'] == 'BOOLEAN_TYPE':
        return ProcessBooleanType(db,switchType[0],parent)
    elif switchType[0]['TYPE'] == 'ENUM_TYPE':
        return ProcessEnumType(db,switchType[0],parent)
    else:
        return ProcessScopedName(db,switchType[0],parent)


# (55) switch_body ::= case
#                    | case switch_body

def ProcessSwitchBody(db,switchBody,parent,switchType):
    cases = [ProcessCase(db,switchBody[0],parent,switchType)]
    if switchBody.get(1):
        cases.extend(ProcessSwitchBody(db, switchBody[1], parent,switchType))
    return cases
    

# (56) case ::= case_label_list element_spec ';'
def ProcessCase(db,case,parent,switchType):
    labels = ProcessCaseLabelList(db,case[0],parent,switchType)
    typ,declarator = ProcessElementSpec(db,case[1],parent)
    if len(labels) == 1 and labels[0] == None:
        #The default case
        labels = []
    return parent.create_union_case(declarator,typ,labels)


# (56a) case_label_list ::= case_label
#                        | case_label case_label_list
def ProcessCaseLabelList(db,caseLabelList,parent,switchType):
    caseLabels = [ProcessCaseLabel(db,caseLabelList[0],parent,switchType)]
    if caseLabelList.get(1):
        caseLabels.extend(ProcessCaseLabelList(db,caseLabelList[1],parent,switchType))
    return caseLabels
                          
                  

# (57) case_label ::= "case" const_exp ':'
#                   | "default" ':'
def ProcessCaseLabel(db,caseLabel,parent,switchType):
    if caseLabel[0] == 'case':
        if switchType.meta_kind == MetaKind.mk_enumeration:
            #Could be a special case where they reference a enum element without the full path
            #try:
            if 1:
                return ProcessConstExp(db,caseLabel[1],switchType)
        return ProcessConstExp(db,caseLabel[1],parent)
    return None

# (58) element_spec ::= type_spec declarator
def ProcessElementSpec(db,elementSpec,parent):
    return ProcessTypeSpec(db,elementSpec[0],parent),ProcessDeclarator(db,elementSpec[1],parent)

# (59) enum_type ::= "enum" IDENTIFIER '{' enumerator_list '}'
def ProcessEnumType(db, enum_type, parent):
    name = enum_type[1]
    enum_list = ProcessEnumeratorList(db, enum_type[3], parent)
    #enum_list.reverse()

    if isinstance(parent,DefiningScope.DefiningScope):
        # Turn this into a ODS List
        list = Collections.CollectionFromTypes(db,
                                               Constants.Types.LIST_COLLECTION,
                                               Constants.Types.STRING,
                                               )

        for enum in enum_list:
            list.insert_element(enum)
        enum = parent.add_enumeration(name, list)
        enum.name = name
    else:
        ctr = 0
        enum = Enumeration.Enumeration(db,None)
        enum.name = name
        t = parent.resolve('::pk_short')
        for n in enum_list:
            l = Literal.Literal(db,None)
            l.literal_value = ctr
            c = Constant.Constant(db,None)
            c.name = n
            c.form_type(t)
            c.form_the_Value(l)
            enum.bind(n,c)
            ctr = ctr + 1
        
    return enum

# (59a) enumerator_list ::= enumerator
#                         | enumerator ',' enumerator_list
# (60) enumerator ::= IDENTIFIER
def ProcessEnumeratorList(db, enumerator_list, parent):
    enums = [enumerator_list[0][0]]
    if enumerator_list.get(1):
        enums.extend(ProcessEnumeratorList(db, enumerator_list[2], parent))
    return enums

# (61) array_type ::= array_spec '<' simple_type_spec ',' positive_int_const '>'
#                   | array_spec '<' simple_type_spec '>'

# (61a) array_spec ::= "array" | "sequence"

# (62) string_type ::= "string" '<' positive_int_const '>'
#                    | "string"

def ProcessStringType(db, string_type, parent):
    if not string_type.get(1):
        return db.schema().resolve('pk_string')
    # I don't need a g_collectionCtr or a collection name
    #YES you do!
    # size must be an Operand.  It's a ConstOperand type (Literal, ...) often.
    coll = Collection.Collection(db, None, collection_kind=CollectionKind.ck_string)
    global g_collectionNameCtr
    coll.name = "Collection%d" % g_collectionNameCtr
    g_collectionNameCtr = g_collectionNameCtr + 1
    size = ProcessPositiveIntConstant(db, string_type.get(2), parent)
    coll.form_max_size(size)
    return coll

        
# (63) array_declarator ::= IDENTIFIER array_size_list

# (63a) array_size_list ::= fixed_array_size
#                         | fixed_array_size array_size_list

# (64) fixed_array_size ::= '[' positive_int_const ']'

# (65) attr_dcl ::= "attribute" domain_type attr_list
#                 | "readonly" "attribute" domain_type attr_list
def ProcessAttrDcl(db, attr_dcl, parent):
    # FIXME: No support for arrays
    
    if attr_dcl.get(3):
        # read-only
        read_only = 1
    else:
        read_only = 0

    domain_type = ProcessDomainType(db, attr_dcl[1 + read_only], parent)

    attr_list = ProcessAttrList(db, attr_dcl[2 + read_only], parent)
    for name in attr_list:
        attr = parent.add_attribute(name, domain_type)
        if read_only:
            attr.is_read_only = 1

    return

# (65a) attr_list ::= attribute_name
#                   | attribute_name fixed_array_size
#                   | attribute_name ',' attr_list
#                   | attribute_name fixed_array_size ',' attr_list
# (65b) attribute_name ::= IDENTIFIER
def ProcessAttrList(db, attr_list, parent):
    # FIXME: What does it mean to have an array here???
    if attr_list.get(3) or (attr_list.get(1) and attr_list[1] != ','):
        raise FtodsUnsupportedError(feature="Arrays")

    names = [attr_list[0][0]]
    if attr_list.get(2):
        names = names + ProcessAttrList(db, attr_list[2], parent)
    return names

# (65c) domain_type ::= simple_type_spec
#                     | struct_type
#                     | enum_type
def ProcessDomainType(db, domain_type, parent):
    domain = domain_type[0]
    if domain['TYPE'] == 'SIMPLE_TYPE_SPEC':
        return ProcessSimpleTypeSpec(db, domain, parent)
    elif domain['TYPE'] == 'STRUCT_TYPE':
        return ProcessStructType(db, domain, parent)
    elif domain['TYPE'] == 'ENUM_TYPE':
        return ProcessEnumType(db, domain, parent)

# (65d) rel_dcl ::= "relationship" target_of_path IDENTIFIER "inverse" inverse_traversal_path
def ProcessRelDcl(db, rel_dcl, parent):
    #2 cases?
    #1 this is the first side of the relationship: inwhich we store in the list
    #2 this is the second side, inwhich we get the inverse from the list

    ident = rel_dcl[2]

    #Get the target (it is a type)
    type = ProcessTargetOfPath(db, rel_dcl[1], parent)
    if type == None:
        raise FtodsUnknownError(msg="Unknown type for relationship %s" % ident)

    target, inverse_name = ProcessInverseTraversalPath(db, rel_dcl[4], parent)
    if target == None:
        raise FtodsUnknownError("No target for relationship %s" % ident)

    #We have to lose the type def on relationships.  We just gotta
    uTarget = OdlUtil.NormalizeTypeDefinition(target)

    #See if the inverse rel exists
    global g_relationships
    if g_relationships.has_key((uTarget,inverse_name)):
        #Case 2 the other side was already defined
        inverse_type = g_relationships[(uTarget, inverse_name)]
        parent.add_relationship(ident, type, uTarget, inverse_name, inverse_type)
        del g_relationships[(uTarget, inverse_name)]
    else:
        #Case1 the other side was not declared.
        #Set up to be called later
        g_relationships[(parent, ident)] = type

# (65e) target_of_path ::= scoped_name
#                        | coll_spec '<' scoped_name '>'
def ProcessTargetOfPath(db, target_of_path, parent):
    if target_of_path.get(1):
        # This is a collection
        type_spec = ProcessCollSpec(db, target_of_path[0], parent)
        subtype = ProcessScopedName(db, target_of_path[2], parent)
        type_spec.form_subtype(subtype)
        return type_spec
    else:
        return ProcessScopedName(db, target_of_path[0], parent)

# (65f) inverse_traversal_path ::= scoped_name "::" IDENTIFIER
def ProcessInverseTraversalPath(db, inverse_traversal_path, path):
    target = ProcessScopedName(db, inverse_traversal_path[0], path)
    return target, inverse_traversal_path[2]


# (66) except_dcl ::= "exception" IDENTIFIER '{' '}'
#                   | "exception" IDENTIFIER '{' member_list '}'
def ProcessExceptDcl(db,exception,parent):
    name = exception[1]
    if exception.get(4):
        memberList = ProcessMemberList(db,exception[3],parent)
    else:
        memberList = []

    #Create the result struct
    s = Structure.Structure(db,None)
    s.name = name + 'Result'
    for f in memberList:
        s.bind(f.name,f)


    return parent.add_exception(name,s)
    


# (67) op_dcl ::= op_type_spec IDENTIFIER parameter_dcls
#               | op_attribute op_type_spec IDENTIFIER parameter_dcls
#               | op_type_spec IDENTIFIER parameter_dcls raises_expr
#               | op_attribute op_type_spec IDENTIFIER parameter_dcls raises_expr
#               | op_type_spec IDENTIFIER parameter_dcls context_expr
#               | op_attribute op_type_spec IDENTIFIER parameter_dcls context_expr
#               | op_type_spec IDENTIFIER parameter_dcls raises_expr context_expr
#               | op_attribute op_type_spec IDENTIFIER parameter_dcls raises_expr context_expr
def ProcessOpDcl(db,opdcl,parent):

    ctr = 0
    if opdcl[0]['TYPE'] == 'OP_ATTRIBUTE':
        oneway = 1
        ctr = 1
    else:
        oneway = 0

    ots = ProcessOpTypeSpec(db,opdcl[ctr],parent)
    ctr = ctr + 1
    name = opdcl[ctr]
    ctr = ctr + 1
    params = ProcessParameterDcls(db,opdcl[ctr],parent)
    ctr = ctr + 1
    if opdcl.get(ctr) and opdcl[ctr]['TYPE'] == 'RAISES_EXPR':
        exceptions = ProcessRaisesExpr(db,opdcl[ctr],parent)
        ctr = ctr + 1
    else:
        exceptions = []
    
    if opdcl.get(ctr) and opdcl[ctr]['TYPE'] == 'CONTEXT_EXPR':
        raise FtodsUnsupportedError(feature="Context Expression On Operations")

    o = parent.add_operation(name,ots,params)
    for e in exceptions:
        o.add_exceptions(e)
    return o
    
# (68) op_attribute ::= "oneway"

# (69) op_type_spec ::= simple_type_spec
#                     | "void"

def ProcessOpTypeSpec(db,ots,parent):
    if ots[0] == 'void':
        return db.schema().resolve(PrimitiveKind.pk_void._n)
    else:
        return ProcessSimpleTypeSpec(db,ots[0],parent)

# (70) parameter_dcls ::= '(' ')'
#                       | '(' param_dcl_list ')'

def ProcessParameterDcls(db,pdcls,parent):
    if pdcls.get(2):
        return ProcessParamDclList(db,pdcls[1],parent)
    return []

# (70a) param_dcl_list ::= param_dcl
#                        | param_dcl ',' param_dcl_list
def ProcessParamDclList(db,pdcll,parent):
    l = [ProcessParamDcl(db,pdcll[0],parent)]
    if pdcll.get(1):
        l.extend(ProcessParamDclList(db,pdcll[2],parent))
    return l
    
    
# (71) param_dcl ::= param_attribute simple_type_spec declarator
def ProcessParamDcl(db,pdcl,parent):
    attr = ProcessParamAttribute(db,pdcl[0],parent)
    sts = ProcessSimpleTypeSpec(db,pdcl[1],parent)
    decl = ProcessDeclarator(db,pdcl[2],parent)

    p = Parameter.Parameter(db,None,parameter_mode = attr)
    p.name = decl
    p.form_type(sts)
    return p
    

# (72) param_attribute ::= "in" | "out" | "inout"
def ProcessParamAttribute(db,pa,parent):
    if pa[0] == 'in':
        return Direction.mode_in
    elif pa[0] == 'out':
        return Direction.mode_out
    return Direction.mode_inout

# (73) raises_expr ::= "raises" '(' scoped_name_list ')'
def ProcessRaisesExpr(db,raises,parent):
    return ProcessScopedNameList(db,raises[2],parent)

# (73a) scoped_name_list ::= scoped_name
#                          | scoped_name ',' scoped_name_list
def ProcessScopedNameList(db,snl,parent):
    res = [ProcessScopedName(db,snl[0],parent)]
    if snl.get(1):
        res.extend(ProcessScopedNameList(db,snl[2],parent))
    return res
                  
           
# (74) context_expr ::= "context" '(' string_literal_list ')'

# (74a) string_literal_list ::= string_literal
#                             | string_literal ',' string_literal_list

from Ft.Ods.MetaData import Scope
from Ft.Ods import Database
def ResolveName(db, name, scope):
    #A name can reference something in 2 places:
    #1 the scope of  scope's parent
    #2 the root module ODLMetaObjects
    noGlob = 0
    if name[0] == ':':
        name = name[2:]
        noGlob = 1

    if not noGlob:
        #First try parent's current scope
        p = scope.parent()
        if p:
            try:
                n = p.resolve(name)
                return n
            except Scope.Scope.NameNotFound, e:
                pass
        #try at current scope
        try:
            n = scope.resolve(name)
            return n
        except Scope.Scope.NameNotFound, e:
            pass

    #Now try at the ODLMetaObject module.
    try:
        n = db.schema().resolve("ODLMetaObjects").resolve(name)
        return n
    except Scope.Scope.NameNotFound, e:
        pass

    #Now try at the Repository module.
    try:
        n = db.schema().resolve(name)
        return n
    except Scope.Scope.NameNotFound, e:
        pass

    return None
