1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3 4# 5# Copyright 2017, Data61 6# Commonwealth Scientific and Industrial Research Organisation (CSIRO) 7# ABN 41 687 119 230. 8# 9# This software may be distributed and modified according to the terms of 10# the BSD 2-Clause license. Note that NO WARRANTY is provided. 11# See "LICENSE_BSD2.txt" for details. 12# 13# @TAG(DATA61_BSD) 14# 15 16''' 17Stage 7 parser. The following parser is designed to accept a stage 6 parser, 18whose output it consumes. This parser's purpose is to resolve hierarchical 19systems into a flat, top-level assembly. 20''' 21 22from __future__ import absolute_import, division, print_function, \ 23 unicode_literals 24from camkes.internal.seven import cmp, filter, map, zip 25 26from .base import Transformer 27from .exception import ParseError 28from camkes.ast import Assembly, ASTObject, AttributeReference, \ 29 Component, Composition, Configuration, Consumes, Dataport, Emits, \ 30 Instance, Interface, Provides, Setting, Uses 31import copy, six 32 33# The pre-condition of this stage is simply the post-condition of the previous 34# stage; that only a single assembly remains. 35from .stage6 import postcondition as precondition 36 37class InterfacePointer(object): 38 ''' 39 A representation of a connection end that we will potentially relabel. See 40 usage of this below. 41 ''' 42 def __init__(self, instance, interface): 43 assert isinstance(instance, Instance) 44 assert isinstance(interface, Interface) 45 self.instance = instance 46 self.interface = interface 47 48 # We need to implement the following methods to allow `InterfacePointer`s 49 # to be stored sensibly in dicts. Note that we just implement equality 50 # based on memory addresses because we know precisely how this will be 51 # used. 52 def __hash__(self): 53 return hash(self.instance) ^ hash(self.interface) 54 def __eq__(self, other): 55 if not isinstance(other, InterfacePointer): 56 return False 57 return self.instance is other.instance and \ 58 self.interface is other.interface 59 def __ne__(self, other): 60 return not self.__eq__(other) 61 62def derive(obj, namespace): 63 ''' 64 Make a copy of the given object, mangling the new object's name such that 65 it will not conflict with existing objects. 66 ''' 67 assert isinstance(obj, ASTObject) 68 assert namespace is None or isinstance(namespace, six.string_types) 69 if namespace is None: 70 # No replication necessary. 71 return obj 72 new = copy.copy(obj) 73 if isinstance(new, Setting): 74 new.instance = '%s.%s' % (namespace, new.instance) 75 if isinstance(new.value, AttributeReference): 76 new.value = copy.copy(new.value) 77 new.value.reference = '%s.%s' % (namespace, new.value.reference) 78 else: 79 new.name = '%s.%s' % (namespace, new.name) 80 # If this is a component instance, we need to name-mangle its address 81 # space as well. If we don't do this, their address space (custom or 82 # implicit) can collide with another entity in the hierarchy and users' 83 # components can accidentally be combined into a single address space. 84 if isinstance(new, Instance): 85 new.address_space = '%s.%s' % (namespace, new.address_space) 86 return new 87 88def infer_all(item, parent=None): 89 ''' 90 Infer all relevant objects that are direct or indirect children of this 91 item. We do this by recursively "hoisting" things from nested compositions 92 and configurations. Though the lifted AST has a built-in traversal 93 mechanism, we do not use it here because we want to only visit certain 94 entities and we want to propagate learned information upwards. 95 ''' 96 assert isinstance(item, (Assembly, Component)) 97 assert parent is None or isinstance(parent, Instance) 98 99 # A prefix we'll use to name-mangle children of the current item. 100 if parent is None: 101 prefix = None 102 else: 103 prefix = parent.name 104 105 # Below we'll discover and then derive two types of instances: our 106 # immediate children and indirect children, which will be accumulated in 107 # these lists, respectively. We use two separate lists because we want to 108 # maintain the ordering of immediate children first. 109 immediate_instances = [] 110 child_instances = [] 111 112 # Indirect settings we'll accumulate. 113 child_settings = [] 114 115 # Final connections and settings we'll return. 116 connections = [] 117 final_settings = [] 118 119 # In the context of connections, we may need to adjust their ends based on 120 # export statements. Rather than trying to do this as we go, we just track 121 # which interfaces are just aliases for others. These will be eventually 122 # used by our outermost caller. 123 aliases = {} 124 125 if item.composition is not None: 126 127 # As we go through the AST, we'll derive new instances. These will have 128 # new names (and addresses), but will potentially be referred to later 129 # under their old pointers. We track this derivation in a mapping from 130 # old instances to new instances in order to adjust these references. 131 derived = {} 132 133 # We'll accumulate indirect child connections here. 134 child_connections = [] 135 136 for i in item.composition.instances: 137 assert i.name is not None, 'unnamed instance in AST (bug in ' \ 138 'stage 3 parser?)' 139 140 # Hoist everything from this instance. 141 insts, conns, alias, settings = infer_all(i.type, i) 142 143 # Name-mangle the instances. 144 for i2 in insts: 145 n = derive(i2, prefix) 146 child_instances.append(n) 147 derived[i2] = n 148 n = derive(i, prefix) 149 immediate_instances.append(n) 150 derived[i] = n 151 152 child_connections.extend(conns) 153 154 # Adjust the connection aliases for the name-mangling we just 155 # performed. 156 for k, v in alias.items(): 157 assert k.instance in derived 158 assert v.instance in derived 159 k = InterfacePointer(derived[k.instance], k.interface) 160 v = InterfacePointer(derived[v.instance], v.interface) 161 aliases[k] = v 162 163 child_settings.extend(settings) 164 165 for c in item.composition.connections + child_connections: 166 167 # Derive and then re-adjust the ends of each connection. 168 n = derive(c, prefix) 169 170 from_ends = [] 171 for f in n.from_ends: 172 e = copy.copy(f) 173 if e.instance is None: 174 if isinstance(item, Assembly): 175 raise ParseError('top-level connection end with no ' 176 'instance', e.location) 177 e.instance = parent 178 else: 179 assert e.instance in derived 180 e.instance = derived[e.instance] 181 from_ends.append(e) 182 n.from_ends = from_ends 183 184 to_ends = [] 185 for t in n.to_ends: 186 e = copy.copy(t) 187 if e.instance is None: 188 if isinstance(item, Assembly): 189 raise ParseError('top-level connection end with no ' 190 'instance', e.location) 191 e.instance = parent 192 else: 193 assert e.instance in derived 194 e.instance = derived[e.instance] 195 to_ends.append(e) 196 n.to_ends = to_ends 197 198 n.claim_children() 199 connections.append(n) 200 201 assert len(item.composition.exports) == 0 or not \ 202 isinstance(item, Assembly), 'export statement in assembly block ' \ 203 '(bug in stage 4 parser?)' 204 205 # Accrue any new aliases we have. 206 for e in item.composition.exports: 207 p = InterfacePointer(parent, e.destination) 208 assert e.source_instance in derived 209 d = InterfacePointer(derived[e.source_instance], e.source_interface) 210 aliases[p] = d 211 212 # Accrue any settings we have. 213 for s in (item.configuration.settings if item.configuration is not None 214 else []) + child_settings: 215 n = derive(s, prefix) 216 final_settings.append(n) 217 218 return immediate_instances + child_instances, connections, aliases, \ 219 final_settings 220 221class Parse7(Transformer): 222 def precondition(self, ast_lifted, _): 223 return precondition(ast_lifted) 224 225 def postcondition(self, ast_lifted, _): 226 ''' 227 There is no natural post-condition for this transformation because the 228 action taken has been to augment the top-level assembly with 229 information that still remains in the AST. This could be formulated 230 with an expensive and complex traversal, but it is not worth it. 231 ''' 232 return True 233 234 def transform(self, ast_lifted, read): 235 assembly = ast_lifted.assembly 236 237 # Hoist everything relevant from the assembly and its children. 238 instances, connections, aliases, settings = infer_all(assembly) 239 240 assembly.composition.instances = instances 241 242 # Replace the connections. Now we take into account the interface 243 # aliases. 244 assembly.composition.connections = [] 245 for c in connections: 246 for e in c.from_ends + c.to_ends: 247 p = InterfacePointer(e.instance, e.interface) 248 while p in aliases: 249 p = aliases[p] 250 e.instance = p.instance 251 e.interface = p.interface 252 assembly.composition.connections.append(c) 253 254 # Replace the settings. 255 assembly.configuration.settings = settings 256 assembly.claim_children() 257 return ast_lifted, read 258