a8eaccfd81b06a8d7257a4c577425b8a89de3674
[igor.git] / igor / record / variables.py
1 # Copyright (C) 2012 W. Trevor King <wking@tremily.us>
2 #
3 # This file is part of igor.
4 #
5 # igor is free software: you can redistribute it and/or modify it under the
6 # terms of the GNU Lesser General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option) any
8 # later version.
9 #
10 # igor is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
13 # details.
14 #
15 # You should have received a copy of the GNU Lesser General Public License
16 # along with igor.  If not, see <http://www.gnu.org/licenses/>.
17
18 import io as _io
19
20 from .. import LOG as _LOG
21 from ..binarywave import TYPE_TABLE as _TYPE_TABLE
22 from ..binarywave import NullStaticStringField as _NullStaticStringField
23 from ..binarywave import DynamicStringField as _DynamicStringField
24 from ..struct import Structure as _Structure
25 from ..struct import DynamicStructure as _DynamicStructure
26 from ..struct import Field as _Field
27 from ..struct import DynamicField as _DynamicField
28 from ..util import byte_order as _byte_order
29 from ..util import need_to_reorder_bytes as _need_to_reorder_bytes
30 from .base import Record
31
32
33 class ListedStaticStringField (_NullStaticStringField):
34     """Handle string conversions for multi-count dynamic parents.
35
36     If a field belongs to a multi-count dynamic parent, the parent is
37     called multiple times to parse each count, and the field's
38     post-unpack hook gets called after the field is unpacked during
39     each iteration.  This requires alternative logic for getting and
40     setting the string data.  The actual string formatting code is not
41     affected.
42     """
43     def post_unpack(self, parents, data):
44         parent_structure = parents[-1]
45         parent_data = self._get_structure_data(parents, data, parent_structure)
46         d = self._normalize_string(parent_data[-1][self.name])
47         parent_data[-1][self.name] = d
48
49
50 class ListedStaticStringField (_NullStaticStringField):
51     """Handle string conversions for multi-count dynamic parents.
52
53     If a field belongs to a multi-count dynamic parent, the parent is
54     called multiple times to parse each count, and the field's
55     post-unpack hook gets called after the field is unpacked during
56     each iteration.  This requires alternative logic for getting and
57     setting the string data.  The actual string formatting code is not
58     affected.
59     """
60     def post_unpack(self, parents, data):
61         parent_structure = parents[-1]
62         parent_data = self._get_structure_data(parents, data, parent_structure)
63         d = self._normalize_string(parent_data[-1][self.name])
64         parent_data[-1][self.name] = d
65
66
67 class ListedDynamicStrDataField (_DynamicStringField, ListedStaticStringField):
68     _size_field = 'strLen'
69     _null_terminated = False
70
71     def _get_size_data(self, parents, data):
72         parent_structure = parents[-1]
73         parent_data = self._get_structure_data(parents, data, parent_structure)
74         return parent_data[-1][self._size_field]
75
76
77 class DynamicVarDataField (_DynamicField):
78     def __init__(self, *args, **kwargs):
79         if 'array' not in kwargs:
80             kwargs['array'] = True
81         super(DynamicVarDataField, self).__init__(*args, **kwargs)
82
83     def pre_pack(self, parents, data):
84         raise NotImplementedError()
85
86     def post_unpack(self, parents, data):
87         var_structure = parents[-1]
88         var_data = self._get_structure_data(parents, data, var_structure)
89         data = var_data[self.name]
90         d = {}
91         for i,value in enumerate(data):
92             key,value = self._normalize_item(i, value)
93             d[key] = value
94         var_data[self.name] = d
95
96     def _normalize_item(self, index, value):
97         raise NotImplementedError()
98
99
100 class DynamicSysVarField (DynamicVarDataField):
101     def _normalize_item(self, index, value):
102         name = 'K{}'.format(index)
103         return (name, value)
104
105
106 class DynamicUserVarField (DynamicVarDataField):
107     def _normalize_item(self, index, value):
108         name = value['name']
109         value = value['num']
110         return (name, value)
111
112
113 class DynamicUserStrField (DynamicVarDataField):
114     def _normalize_item(self, index, value):
115         name = value['name']
116         value = value['data']
117         return (name, value)
118
119
120 class DynamicVarNumField (_DynamicField):
121     def post_unpack(self, parents, data):
122         parent_structure = parents[-1]
123         parent_data = self._get_structure_data(parents, data, parent_structure)
124         d = self._normalize_numeric_variable(parent_data[-1][self.name])
125         parent_data[-1][self.name] = d
126
127     def _normalize_numeric_variable(self, num_var):
128         t = _TYPE_TABLE[num_var['numType']]
129         if num_var['numType'] % 2:  # complex number
130             return t(complex(num_var['realPart'], num_var['imagPart']))
131         else:
132             return t(num_var['realPart'])
133
134
135 class DynamicFormulaField (_DynamicStringField):
136     _size_field = 'formulaLen'
137     _null_terminated = True
138
139
140 # From Variables.h
141 VarHeader1 = _Structure(  # `version` field pulled out into VariablesRecord
142     name='VarHeader1',
143     fields=[
144         _Field('h', 'numSysVars', help='Number of system variables (K0, K1, ...).'),
145         _Field('h', 'numUserVars', help='Number of user numeric variables -- may be zero.'),
146         _Field('h', 'numUserStrs', help='Number of user string variables -- may be zero.'),
147         ])
148
149 # From Variables.h
150 VarHeader2 = _Structure(  # `version` field pulled out into VariablesRecord
151     name='VarHeader2',
152     fields=[
153         _Field('h', 'numSysVars', help='Number of system variables (K0, K1, ...).'),
154         _Field('h', 'numUserVars', help='Number of user numeric variables -- may be zero.'),
155         _Field('h', 'numUserStrs', help='Number of user string variables -- may be zero.'),
156         _Field('h', 'numDependentVars', help='Number of dependent numeric variables -- may be zero.'),
157         _Field('h', 'numDependentStrs', help='Number of dependent string variables -- may be zero.'),
158         ])
159
160 # From Variables.h
161 UserStrVarRec1 = _DynamicStructure(
162     name='UserStrVarRec1',
163     fields=[
164         ListedStaticStringField('c', 'name', help='Name of the string variable.', count=32),
165         _Field('h', 'strLen', help='The real size of the following array.'),
166         ListedDynamicStrDataField('c', 'data'),
167         ])
168
169 # From Variables.h
170 UserStrVarRec2 = _DynamicStructure(
171     name='UserStrVarRec2',
172     fields=[
173         ListedStaticStringField('c', 'name', help='Name of the string variable.', count=32),
174         _Field('l', 'strLen', help='The real size of the following array.'),
175         _Field('c', 'data'),
176         ])
177
178 # From Variables.h
179 VarNumRec = _Structure(
180     name='VarNumRec',
181     fields=[
182         _Field('h', 'numType', help='Type from binarywave.TYPE_TABLE'),
183         _Field('d', 'realPart', help='The real part of the number.'),
184         _Field('d', 'imagPart', help='The imag part if the number is complex.'),
185         _Field('l', 'reserved', help='Reserved - set to zero.'),
186         ])
187
188 # From Variables.h
189 UserNumVarRec = _DynamicStructure(
190     name='UserNumVarRec',
191     fields=[
192         ListedStaticStringField('c', 'name', help='Name of the string variable.', count=32),
193         _Field('h', 'type', help='0 = string, 1 = numeric.'),
194         DynamicVarNumField(VarNumRec, 'num', help='Type and value of the variable if it is numeric.  Not used for string.'),
195         ])
196
197 # From Variables.h
198 UserDependentVarRec = _DynamicStructure(
199     name='UserDependentVarRec',
200     fields=[
201         ListedStaticStringField('c', 'name', help='Name of the string variable.', count=32),
202         _Field('h', 'type', help='0 = string, 1 = numeric.'),
203         _Field(VarNumRec, 'num', help='Type and value of the variable if it is numeric.  Not used for string.'),
204         _Field('h', 'formulaLen', help='The length of the dependency formula.'),
205         DynamicFormulaField('c', 'formula', help='Start of the dependency formula. A C string including null terminator.'),
206         ])
207
208
209 class DynamicVarHeaderField (_DynamicField):
210     def pre_pack(self, parents, data):
211         raise NotImplementedError()
212
213     def post_unpack(self, parents, data):
214         var_structure = parents[-1]
215         var_data = self._get_structure_data(
216             parents, data, var_structure)
217         var_header_structure = self.format
218         data = var_data['var_header']
219         sys_vars_field = var_structure.get_field('sysVars')
220         sys_vars_field.count = data['numSysVars']
221         sys_vars_field.setup()
222         user_vars_field = var_structure.get_field('userVars')
223         user_vars_field.count = data['numUserVars']
224         user_vars_field.setup()
225         user_strs_field = var_structure.get_field('userStrs')
226         user_strs_field.count = data['numUserStrs']
227         user_strs_field.setup()
228         if 'numDependentVars' in data:
229             dependent_vars_field = var_structure.get_field('dependentVars')
230             dependent_vars_field.count = data['numDependentVars']
231             dependent_vars_field.setup()
232             dependent_strs_field = var_structure.get_field('dependentStrs')
233             dependent_strs_field.count = data['numDependentStrs']
234             dependent_strs_field.setup()
235         var_structure.setup()
236
237
238 Variables1 = _DynamicStructure(
239     name='Variables1',
240     fields=[
241         DynamicVarHeaderField(VarHeader1, 'var_header', help='Variables header'),
242         DynamicSysVarField('f', 'sysVars', help='System variables', count=0),
243         DynamicUserVarField(UserNumVarRec, 'userVars', help='User numeric variables', count=0),
244         DynamicUserStrField(UserStrVarRec1, 'userStrs', help='User string variables', count=0),
245         ])
246
247
248 Variables2 = _DynamicStructure(
249     name='Variables2',
250     fields=[
251         DynamicVarHeaderField(VarHeader2, 'var_header', help='Variables header'),
252         DynamicSysVarField('f', 'sysVars', help='System variables', count=0),
253         DynamicUserVarField(UserNumVarRec, 'userVars', help='User numeric variables', count=0),
254         DynamicUserStrField(UserStrVarRec2, 'userStrs', help='User string variables', count=0),
255         _Field(UserDependentVarRec, 'dependentVars', help='Dependent numeric variables.', count=0, array=True),
256         _Field(UserDependentVarRec, 'dependentStrs', help='Dependent string variables.', count=0, array=True),
257         ])
258
259
260 class DynamicVersionField (_DynamicField):
261     def pre_pack(self, parents, byte_order):
262         raise NotImplementedError()
263
264     def post_unpack(self, parents, data):
265         variables_structure = parents[-1]
266         variables_data = self._get_structure_data(
267             parents, data, variables_structure)
268         version = variables_data['version']
269         if variables_structure.byte_order in '@=':
270             need_to_reorder_bytes = _need_to_reorder_bytes(version)
271             variables_structure.byte_order = _byte_order(need_to_reorder_bytes)
272             _LOG.debug(
273                 'get byte order from version: {} (reorder? {})'.format(
274                     variables_structure.byte_order, need_to_reorder_bytes))
275         else:
276             need_to_reorder_bytes = False
277
278         old_format = variables_structure.fields[-1].format
279         if version == 1:
280             variables_structure.fields[-1].format = Variables1
281         elif version == 2:
282             variables_structure.fields[-1].format = Variables2
283         elif not need_to_reorder_bytes:
284             raise ValueError(
285                 'invalid variables record version: {}'.format(version))
286
287         if variables_structure.fields[-1].format != old_format:
288             _LOG.debug('change variables record from {} to {}'.format(
289                     old_format, variables_structure.fields[-1].format))
290             variables_structure.setup()
291         elif need_to_reorder_bytes:
292             variables_structure.setup()
293
294         # we might need to unpack again with the new byte order
295         return need_to_reorder_bytes
296
297
298 VariablesRecordStructure = _DynamicStructure(
299     name='VariablesRecord',
300     fields=[
301         DynamicVersionField('h', 'version', help='Version number for this header.'),
302         _Field(Variables1, 'variables', help='The rest of the variables data.'),
303         ])
304
305
306 class VariablesRecord (Record):
307     def __init__(self, *args, **kwargs):
308         super(VariablesRecord, self).__init__(*args, **kwargs)
309         # self.header['version']  # record version always 0?
310         VariablesRecordStructure.byte_order = '='
311         VariablesRecordStructure.setup()
312         stream = _io.BytesIO(bytes(self.data))
313         self.variables = VariablesRecordStructure.unpack_stream(stream)
314         self.namespace = {}
315         for key,value in self.variables['variables'].items():
316             if key not in ['var_header']:
317                 _LOG.debug('update namespace {} with {} for {}'.format(
318                         self.namespace, value, key))
319                 self.namespace.update(value)