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 8 parser. The following parser is designed to accept a stage 7 parser, 18whose output it consumes. This parser's purpose is to resolve settings that 19reference other attributes. 20''' 21 22from __future__ import absolute_import, division, print_function, \ 23 unicode_literals 24from camkes.internal.seven import cmp, filter, map, zip 25 26from camkes.ast import AttributeReference 27from .base import Transformer 28from .exception import ParseError 29import collections 30 31# Re-use the post-condition of the stage 6 parser as our pre-condition; that 32# only a single assembly remains. 33from .stage6 import postcondition as precondition 34 35def postcondition(ast_lifted): 36 ''' 37 All settings are resolved. 38 ''' 39 return all(not isinstance(x.value, AttributeReference) for x in 40 ast_lifted.assembly.configuration.settings) 41 42def resolve(ast_lifted): 43 ''' 44 Recursively resolve all setting references to concrete values. 45 46 There's not a lot of type safety in this resolution process because the 47 attributes themselves inherently aren't strongly typed. We rely on the type 48 checking done when freeze is called on an assembly. 49 ''' 50 to_resolve = [] 51 52 assembly = ast_lifted.assembly 53 new_settings = [] 54 for s in assembly.configuration.settings: 55 if isinstance(s.value, AttributeReference): 56 to_resolve.append(s) 57 else: 58 new_settings.append(s) 59 60 def sub_resolve(setting, depth): 61 ''' 62 Recursive function for resolving setting references. If a setting 63 is a reference to another setting we try and resolve the next one (depth first). If there 64 is a circular reference we error out. If any setting doesn't have 65 a setting to resolve to, but has a default attribute value we use that. 66 If there is no default and no setting then we 'forget' the alias so that we 67 can fall back to defaults that other attributes have. An attribute that doesn't 68 get a setting or a default will not generate a symbol in the generated template. 69 Thus if the attribute requires a setting the code compiler should generate an error later. 70 ''' 71 72 if setting.value.reference in depth: 73 errstring = "" 74 for value in depth: 75 errstring += value + "<-" 76 raise ParseError('Loop detected in attribute references: %s<-...' 77 % (errstring+setting.value.reference), setting.location) 78 # TODO Refactor AttributeReference to handle namespacing better than a 79 # string containing '.' characters. 80 instance_name, attribute_name = setting.value.reference.rsplit('.', 1) 81 referents = [x for x in assembly.configuration.settings 82 if x.instance == instance_name and 83 x.attribute == attribute_name] 84 if len(referents) == 0: 85 # No existing settings for the attribute that our current attribute 86 # refers to. Check if it has a default value and use that. 87 attribute = assembly.get_attribute(instance_name, attribute_name) 88 if attribute is not None and attribute.default is not None: 89 setting.value = attribute.default 90 return True 91 92 # If we didn't find a default, then try and use our own default if we have one 93 attribute = assembly.get_attribute(setting.instance, setting.attribute) 94 if attribute is not None and attribute.default is not None: 95 setting.value = attribute.default 96 return True 97 98 setting.value = None 99 return False 100 101 elif len(referents) > 1: 102 raise ParseError('setting refers to an attribute that ' 103 'is set multiple times', setting.location) 104 105 if isinstance(referents[0].value, AttributeReference): 106 if not sub_resolve(referents[0], depth + [setting.value.reference]): 107 setting.value = None 108 return False 109 setting.value = referents[0].value 110 elif setting.value.dict_lookup and isinstance(referents[0].value, dict): 111 value = referents[0].value 112 for key in setting.value.dict_lookup.lookup: 113 value = value[key] 114 setting.value = value 115 else: 116 setting.value = referents[0].value 117 return True 118 119 # Iterate through each setting we need to resolve 120 for setting in to_resolve: 121 if isinstance(setting.value, AttributeReference): 122 if sub_resolve(setting, []): 123 new_settings.append(setting) 124 elif setting.value != None: 125 # Already resolved 126 new_settings.append(setting) 127 128 assembly.configuration.settings = new_settings 129 assembly.claim_children() 130 131class Parse8(Transformer): 132 def precondition(self, ast_lifted, _): 133 return precondition(ast_lifted) 134 135 def postcondition(self, ast_lifted, _): 136 return postcondition(ast_lifted) 137 138 def transform(self, ast_lifted, read): 139 resolve(ast_lifted) 140 return ast_lifted, read 141