summaryrefslogtreecommitdiffstats
path: root/awlsim/core/translator.py
blob: f9046845343645b8a0e63d9bd9a0667f6f551a72 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
# -*- coding: utf-8 -*-
#
# AWL simulator - AWL translator and symbol resolver
#
# Copyright 2012-2014 Michael Buesch <m@bues.ch>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#

from __future__ import division, absolute_import, print_function, unicode_literals
from awlsim.common.compat import *

#from awlsim.core.instructions.all_insns cimport * #@cy

from awlsim.core.instructions.all_insns import * #@nocy
from awlsim.core.optrans import *
from awlsim.core.insntrans import *
from awlsim.core.parser import *
from awlsim.core.util import *


class AwlTranslator(object):
	"AWL instruction and operator translator."

	def __init__(self, cpu):
		self.cpu = cpu

	def __translateInsn(self, rawInsn, ip):
		ex = None
		try:
			insn = AwlInsnTranslator.fromRawInsn(self.cpu, rawInsn)
			insn.setIP(ip)
		except AwlSimError as e:
			if e.getRawInsn() is None:
				e.setRawInsn(rawInsn)
			raise e
		return insn

	def __translateInsns(self, rawInsns):
		insns = []
		# Translate raw instructions to simulator instructions
		for ip, rawInsn in enumerate(rawInsns):
			insns.append(self.__translateInsn(rawInsn, ip))
		# If the last instruction is not BE or BEA, add an implicit BE
		if not insns or insns[-1].insnType not in (AwlInsn.TYPE_BE,
							   AwlInsn.TYPE_BEA):
			insns.append(AwlInsn_BE(cpu = self.cpu, rawInsn = None))
		return insns

	def __translateInterfaceField(self, rawField):
		name, dataType, initBytes = self.rawFieldTranslate(rawField)
		field = BlockInterfaceField(name = name,
					    dataType = dataType)
		return field

	def translateLibraryCodeBlock(self, block):
		# Switch mnemonics to DE for translation of library code.
		oldMnemonics = self.cpu.getSpecs().getConfiguredMnemonics()
		self.cpu.getSpecs().setConfiguredMnemonics(S7CPUSpecs.MNEMONICS_DE)

		# Enable extended instructions for library code.
		oldExtEn = self.cpu.extendedInsnsEnabled()
		self.cpu.enableExtendedInsns(True)

		# Parse the library code
		p = AwlParser()
		p.parseText(block.getCode())
		tree = p.getParseTree()
		if block.isFC:
			assert(len(tree.fcs) == 1)
			rawBlock = tuple(tree.fcs.values())[0]
		elif block.isFB:
			assert(len(tree.fbs) == 1)
			rawBlock = tuple(tree.fbs.values())[0]
		else:
			assert(0)
		# Translate the library block instructions.
		block.insns = self.__translateInsns(rawBlock.insns)
		block.resolveLabels()

		# Switch back to old extended-instructions state.
		self.cpu.enableExtendedInsns(oldExtEn)

		# Switch back to old mnemonics.
		self.cpu.getSpecs().setConfiguredMnemonics(oldMnemonics)

		return block

	def translateCodeBlock(self, rawBlock, blockClass):
		insns = self.__translateInsns(rawBlock.insns)
		block = blockClass(insns, rawBlock.index)

		# Construct the block interface
		for rawVar in rawBlock.vars_in:
			block.interface.addField_IN(self.__translateInterfaceField(rawVar))
		for rawVar in rawBlock.vars_out:
			block.interface.addField_OUT(self.__translateInterfaceField(rawVar))
		if rawBlock.retTypeTokens:
			# ARRAY is not supported for RET_VAL. So make non-array dtype.
			dtype = AwlDataType.makeByName(rawBlock.retTypeTokens)
			if dtype.type != AwlDataType.TYPE_VOID:
				# Ok, we have a RET_VAL.
				field = BlockInterfaceField(name = "RET_VAL",
							    dataType = dtype)
				block.interface.addField_OUT(field)
		for rawVar in rawBlock.vars_inout:
			block.interface.addField_INOUT(self.__translateInterfaceField(rawVar))
		for rawVar in rawBlock.vars_static:
			block.interface.addField_STAT(self.__translateInterfaceField(rawVar))
		for rawVar in rawBlock.vars_temp:
			block.interface.addField_TEMP(self.__translateInterfaceField(rawVar))
		return block

	def rawFieldTranslate(self, rawField):
		# Get the field name
		name = rawField.getIdentString()
		# Get the field data type
		if len(rawField.typeTokens) == 1 and\
		   rawField.typeTokens[0].startswith('"') and\
		   rawField.typeTokens[0].endswith('"') and\
		   self.cpu:
			# The data type is symbolic.
			# (symbolic multi instance field)
			# Resolve it.
			assert(not rawField.dimensions)
			symStr = rawField.typeTokens[0][1:-1] # Strip quotes
			resolver = AwlSymResolver(self.cpu)
			fbNumber, sym = resolver.resolveBlockName((AwlDataType.TYPE_FB_X,
								   AwlDataType.TYPE_SFB_X),
								  symStr)
			# Get the data type from the symbol.
			dataType = sym.type
		else:
			# Parse the data type.
			dataType = AwlDataType.makeByName(rawField.typeTokens,
							  rawField.dimensions)
		# Get the field inits
		if rawField.defaultInits:
			# Translate the initialization values and
			# put them into a ByteArray.
			initBytes = ByteArray(intDivRoundUp(dataType.width, 8))
			if dataType.type == AwlDataType.TYPE_ARRAY:
				for rawDataInit in rawField.defaultInits:
					value = dataType.parseMatchingImmediate(rawDataInit.valueTokens)
					linArrayIndex = dataType.arrayIndicesCollapse(
						rawDataInit.idents[-1].indices)
					offset = AwlOffset.fromBitOffset(linArrayIndex *
									 dataType.children[0].width)
					try:
						initBytes.store(offset, dataType.children[0].width,
								value)
					except AwlSimError as e:
						raise AwlSimError("Data field '%s' initialization "
							"is out of range." % str(rawField))
			else:
				assert(len(rawField.defaultInits) == 1)
				value = dataType.parseMatchingImmediate(rawField.defaultInits[0].valueTokens)
				try:
					initBytes.store(AwlOffset(), dataType.width, value)
				except AwlSimError as e:
					raise AwlSimError("Data field '%s' initialization "
						"is out of range." % str(rawField))
		else:
			initBytes = None
		return name, dataType, initBytes

	# Initialize a DB (global or instance) data field from a raw data-init.
	def __initDBField(self, db, dataType, rawDataInit):
		#TODO no structs, yet
		fieldName = rawDataInit.idents[-1].name
		if dataType.type == AwlDataType.TYPE_ARRAY:
			index = dataType.arrayIndicesCollapse(rawDataInit.idents[-1].indices)
		else:
			index = None
		value = dataType.parseMatchingImmediate(rawDataInit.valueTokens)
		db.structInstance.setFieldDataByName(fieldName, index, value)

	def __translateGlobalDB(self, rawDB):
		db = DB(rawDB.index, None)
		# Create the data structure fields
		for field in rawDB.fields:
			name, dataType, initBytes = self.rawFieldTranslate(field)
			db.struct.addFieldNaturallyAligned(self.cpu, name,
							   dataType, initBytes)
		# Allocate the data structure fields
		db.allocate()
		# Assign data structure initializations.
		for field, init in rawDB.allFieldInits():
			if not field:
				raise AwlSimError(
					"DB %d assigns field '%s', "
					"but does not declare it." %\
					(rawDB.index, init.getIdentString()))
			assert(len(field.idents) == 1 and len(init.idents) == 1) #TODO no structs, yet
			dtype = AwlDataType.makeByName(field.typeTokens,
						       field.dimensions)
			self.__initDBField(db, dtype, init)
		return db

	def __translateInstanceDB(self, rawDB):
		if rawDB.fields:
			raise AwlSimError("DB %d is an "
				"instance DB, but it also "
				"declares a data structure." %\
				rawDB.index)

		if rawDB.fb.fbSymbol is None:
			# The FB name is absolute.
			fbNumber = rawDB.fb.fbNumber
			fbStr = "SFB" if rawDB.fb.isSFB else "FB"
			fbStr += " %d" % fbNumber
			isSFB = rawDB.fb.isSFB
		else:
			# The FB name is symbolic. Resolve it.
			resolver = AwlSymResolver(self.cpu)
			fbStr = '"%s"' % rawDB.fb.fbSymbol
			fbNumber, sym = resolver.resolveBlockName((AwlDataType.TYPE_FB_X,
								   AwlDataType.TYPE_SFB_X),
								  rawDB.fb.fbSymbol)
			isSFB = sym.type.type == AwlDataType.TYPE_SFB_X

		# Get the FB/SFB code block
		try:
			if isSFB:
				fb = self.cpu.sfbs[fbNumber]
			else:
				fb = self.cpu.fbs[fbNumber]
		except KeyError:
			raise AwlSimError("Instance DB %d references %s, "
				"but %s does not exist." %\
				(rawDB.index, fbStr, fbStr))

		# Create an instance data block
		db = DB(rawDB.index, fb)
		interface = fb.interface
		# Allocate the data structure fields
		db.allocate()
		# Initialize the data structure fields
		for init in rawDB.fieldInits:
			assert(len(init.idents) == 1) #TODO no structs, yet
			dtype = interface.getFieldByName(init.idents[-1].name).dataType
			self.__initDBField(db, dtype, init)
		return db

	def translateDB(self, rawDB):
		if rawDB.index < 1 or rawDB.index > 0xFFFF:
			raise AwlSimError("DB number %d is invalid" % rawDB.index)
		if rawDB.isInstanceDB():
			return self.__translateInstanceDB(rawDB)
		return self.__translateGlobalDB(rawDB)

class AwlSymResolver(object):
	"Global and local symbol resolver."

	def __init__(self, cpu):
		self.cpu = cpu

	# Resolve classic symbols ("abc")
	def __resolveClassicSym(self, block, insn, oper):
		if oper.type == AwlOperator.SYMBOLIC:
			symbol = self.cpu.symbolTable.findOneByName(oper.value.varName)
			if not symbol:
				raise AwlSimError("Symbol \"%s\" not found in "
					"symbol table." % oper.value.varName,
					insn = insn)
			newOper = symbol.operator.dup()
			newOper.setInsn(oper.insn)
			return newOper
		return oper

	# Resolve symbolic OB/FB/FC/DB block name
	def resolveBlockName(self, blockTypeIds, blockName):
		if isString(blockName):
			symbol = self.cpu.symbolTable.findOneByName(blockName)
			if not symbol:
				raise AwlSimError("Symbolic block name \"%s\" "
					"not found in symbol table." % blockName)
			if symbol.type.type not in blockTypeIds:
				raise AwlSimError("Symbolic block name \"%s\" "
					"has an invalid type." % blockName)
			return symbol.operator.value.byteOffset, symbol
		return blockName, None

	# Resolve local symbols (#abc or P##abc)
	# If pointer is false, try to resolve #abc.
	# If pointer is true, try to resolve P##abc.
	# If allowWholeArrayAccess is true, unsubscripted accesses
	# to array variables are supported.
	def resolveNamedLocal(self, block, insn, oper,
			      pointer=False, allowWholeArrayAccess=False):
		if pointer:
			if oper.type != AwlOperator.NAMED_LOCAL_PTR:
				return oper
		else:
			if oper.type != AwlOperator.NAMED_LOCAL:
				return oper

		# Get the interface field for this variable
		field = block.interface.getFieldByName(oper.value.varName)

		# Sanity checks
		if field.dataType.type == AwlDataType.TYPE_ARRAY:
			if not oper.value.indices and\
			   oper.type != AwlOperator.NAMED_LOCAL_PTR and\
			   not allowWholeArrayAccess:
				raise AwlSimError("Cannot address array #%s "
					"without subscript list." %\
					oper.value.varName)
		else:
			if oper.value.indices:
				raise AwlSimError("Trying to subscript array, "
					"but #%s is not an array." %\
					oper.value.varName)

		if oper.value.indices:
			assert(field.dataType.type == AwlDataType.TYPE_ARRAY)
			# This is an array access.
			# Resolve the array index to a byte/bit-offset.
			arrayIndex = field.dataType.arrayIndicesCollapse(oper.value.indices)
			elemWidth = field.dataType.children[0].width
			bitOffset = arrayIndex * elemWidth
			byteOffset = bitOffset // 8
			bitOffset %= 8
			oper.value.subOffset = AwlOffset(byteOffset, bitOffset)
			# Store the element-access-width in the operator.
			oper.width = elemWidth
		else:
			# Non-array accesses don't have a sub-offset.
			oper.value.subOffset = AwlOffset()
			# Store the access-width in the operator.
			oper.width = field.dataType.width

		# If this is a compound data type access, mark
		# the operand as such.
		oper.compound = field.dataType.compound

		if block.interface.hasInstanceDB or\
		   field.fieldType == BlockInterfaceField.FTYPE_TEMP:
			# This is an FB or a TEMP access. Translate the operator
			# to a DI/TEMP access.
			newOper = block.interface.getOperatorForField(oper.value.varName,
								      oper.value.indices,
								      pointer)
			newOper.setInsn(oper.insn)
			# If this is a compound data type access, mark
			# the operand as such.
			newOper.compound = field.dataType.compound
			return newOper
		else:
			# This is an FC. Accesses to local symbols
			# are resolved at runtime.
			# Just set interface index in the operator.
			index = block.interface.getFieldIndex(oper.value.varName)
			oper.interfaceIndex = index
		return oper

	# Resolve named fully qualified accesses (DBx.VARx)
	# If allowWholeArrayAccess is true, unsubscripted accesses
	# to array variables are supported.
	def __resolveNamedFullyQualified(self, block, insn, oper,
					 allowWholeArrayAccess=False):
		if oper.type != AwlOperator.NAMED_DBVAR:
			return oper

		# Resolve the symbolic DB name, if needed
		assert(oper.value.dbNumber is not None or\
		       oper.value.dbName is not None)
		if oper.value.dbNumber is None:
			symbol = self.cpu.symbolTable.findOneByName(oper.value.dbName)
			if not symbol:
				raise AwlSimError("Symbol \"%s\" specified as DB in "
					"fully qualified operator not found." %\
					oper.value.dbName)
			if symbol.type.type != AwlDataType.TYPE_DB_X:
				raise AwlSimError("Symbol \"%s\" specified as DB in "
					"fully qualified operator is not a DB-symbol." %\
					oper.value.dbName)
			oper.value.dbNumber = symbol.operator.value.byteOffset

		# Get the DB
		try:
			db = self.cpu.dbs[oper.value.dbNumber]
		except KeyError as e:
			raise AwlSimError("DB %d specified in fully qualified "
				"operator does not exist." % oper.value.dbNumber)

		# Get the data structure field descriptor
		# and construct the AwlOffset.
		field = db.struct.getField(oper.value.varName)
		if oper.value.indices is None:
			# Access without array indices.
			if field.dataType.type == AwlDataType.TYPE_ARRAY:
				# This is a whole-array access.
				#  e.g.  DB1.ARRAYVAR
				if not allowWholeArrayAccess:
					raise AwlSimError("Variable '%s' in fully qualified "
						"DB access is an ARRAY." %\
						oper.value.varName)
		else:
			# This is an array field access.
			if field.dataType.type != AwlDataType.TYPE_ARRAY:
				raise AwlSimError("Indexed variable '%s' in fully qualified "
					"DB access is not an ARRAY." %\
					oper.value.varName)
			index = field.dataType.arrayIndicesCollapse(oper.value.indices)
			# Get the actual data field
			field = db.struct.getField(oper.value.varName, index)
		# Extract the offset data
		offset = field.offset.dup()
		width = field.bitSize
		offset.dbNumber = oper.value.dbNumber

		# Construct an absolute operator
		oper = AwlOperator(type = AwlOperator.MEM_DB,
				   width = width,
				   value = offset,
				   insn = oper.insn)
		# If this is a compound data type access, mark
		# the operand as such.
		oper.compound = field.dataType.compound
		return oper

	# Resolve all symbols in the given code block
	def resolveSymbols_block(self, block):
		block.resolveSymbols()
		for insn in block.insns:
			try:
				for i, oper in enumerate(insn.ops):
					oper = self.__resolveClassicSym(block, insn, oper)
					oper = self.resolveNamedLocal(block, insn, oper, False)
					oper = self.__resolveNamedFullyQualified(block,
										 insn, oper, False)
					if oper.type == AwlOperator.INDIRECT:
						oper.offsetOper = self.__resolveClassicSym(block,
									insn, oper.offsetOper)
						oper.offsetOper = self.resolveNamedLocal(block,
									insn, oper.offsetOper, False)
					oper = self.resolveNamedLocal(block, insn, oper, True)
					insn.ops[i] = oper
				for param in insn.params:
					oper = param.rvalueOp
					oper = self.__resolveClassicSym(block, insn, oper)
					oper = self.resolveNamedLocal(block, insn, oper, False, True)
					oper = self.__resolveNamedFullyQualified(block,
										 insn, oper, True)
					if oper.type == AwlOperator.INDIRECT:
						oper.offsetOper = self.__resolveClassicSym(block,
									insn, oper.offsetOper)
						oper.offsetOper = self.resolveNamedLocal(block,
									insn, oper.offsetOper, False)
					oper = self.resolveNamedLocal(block, insn, oper, False, True)
					param.rvalueOp = oper
			except AwlSimError as e:
				if not e.getInsn():
					e.setInsn(insn)
				raise e
bues.ch cgit interface