Commit eaf1cca4 authored by Michael Büsch's avatar Michael Büsch

Add support for STRING

Signed-off-by: Michael Büsch's avatarMichael Buesch <[email protected]>
parent 921e20ff
awlsim - STEP 7 compatibility
=============================
===============================================================================
Awlsim - STEP 7 compatibility
===============================================================================
The execution of AWL/STL programs by awlsim is supposed to be fully
The execution of AWL/STL programs by Awlsim is supposed to be fully
compatible with the execution of compiled AWL/STL programs on the real
Siemens S7 CPU.
However, there currently are some known differences. These are listed below.
Any undocumented difference between awlsim and STEP 7 is considered to be a
Any undocumented difference between Awlsim and STEP 7 is considered to be a
bug that should be reported.
- awlsim does not implement all features of STEP 7, yet.
- Awlsim does not implement all features of STEP 7, yet.
See TODO.txt for a list of missing features.
- Semicolons: AWL/STL requires semicolons (;) after each declaration,
initialization and statement. As an awlsim convenience service, terminating
semicolons can be omitted in AWL/STL statements.
Data declarations and initializations (in DBs and FB/FC interfaces), however,
must end with a semicolon.
- Changing a symbol's address or data type in Awlsim does change the AWL/STL
semantics of the code that uses this symbol.
This is due to source text being the first class program object in Awlsim.
......@@ -25,13 +20,13 @@ bug that should be reported.
text on each download to the CPU.
The same thing happens in STEP 7, if a source text is imported.
- awlsim does not compile AWL/STL to MC7 code and it cannot execute MC7 code.
On startup awlsim translates the AWL/STL code to an awlsim specific in-memory
- Awlsim does not compile AWL/STL to MC7 code and it cannot execute MC7 code.
On startup Awlsim translates the AWL/STL code to an Awlsim specific in-memory
representation of the code. There is no byte-code representation of this
code.
- Some key concepts, such as CALL or memory indirect addressing are implemented
natively in awlsim. This means to improve runtime performance in awlsim
natively in Awlsim. This means to improve runtime performance in Awlsim
CALL is not a macro. From a user's perspective there should not be any
functional difference visible in CALL. Any such difference is a bug.
However, due to these constraints, it is not possible to call FBs or FCs
......@@ -40,5 +35,34 @@ bug that should be reported.
- Undefined behavior is not emulated.
For example: If reading uninitialized L-stack space in STEP 7 always yields
a certain reproducible result, that does not mean that this AWL/STL code does
the same thing in awlsim.
the same thing in Awlsim.
Reading uninitialized TEMP-memory is undefined.
===============================================================================
Awlsim extensions
(Features that Awlsim supports, but STEP 7 does not support)
===============================================================================
- Semicolons: AWL/STL requires semicolons (;) after each declaration,
initialization and statement. As an Awlsim convenience service, terminating
semicolons can be omitted in AWL/STL statements.
Data declarations and initializations (in DBs and FB/FC interfaces), however,
must end with a semicolon.
- Awlsim supports DATE_AND_TIME immediate constants (for example
DT#2012-01-02-13:37:00.000) to FC and FB DATE_AND_TIME IN-variables. In FC
calls the DATE_AND_TIME constant is copied to VL memory snd passed via
DB-pointer (that is itself stored in VL).
- Awlsim supports passing STRING immediate constants (for example 'Test') to
FC and FB STRING IN-variables. In FC calls the STRING constant is copied to
VL memory and passed via DB-pointer (that is itself stored in VL).
The maximum length of the STRING immediate is casted up to the parameter's
maximum length and added characters are filled with zero-bytes. The actual
length of the string does not change.
- Awlsim supports STRING parameters in FCs with sizes unequal to 254
characters. Only actual-parameters with exactly the specified max-size as
specified in the FC interface are allowed in the CALL assignment.
(One exception being STRING immediates. See above.)
......@@ -168,8 +168,7 @@ class CallStackElem(object):
# Returns the translated rvalueOp.
def __FC_trans_copyToVL(self, param, rvalueOp):
# Allocate space in the caller-L-stack.
lalloc = self.cpu.callStackTop.lalloc
loffset = lalloc.alloc(rvalueOp.width)
loffset = self.cpu.callStackTop.lalloc.alloc(rvalueOp.width)
# Make an operator for the allocated space.
oper = AwlOperator(AwlOperator.MEM_L,
rvalueOp.width,
......@@ -191,8 +190,7 @@ class CallStackElem(object):
# Returns the translated rvalueOp.
def __FC_trans_dbpointerInVL(self, param, rvalueOp):
# Allocate space for the DB-ptr in the caller-L-stack
lalloc = self.cpu.callStackTop.lalloc
loffset = lalloc.alloc(48) # 48 bits
loffset = self.cpu.callStackTop.lalloc.alloc(48) # 48 bits
# Create and store the the DB-ptr to the allocated space.
storeOper = AwlOperator(AwlOperator.MEM_L,
16,
......@@ -220,6 +218,15 @@ class CallStackElem(object):
loffset,
rvalueOp.insn)
# FC parameter translation:
# Copy the r-value to the caller's L-stack (VL) and also create
# a DB-pointer to the copied value in VL.
# Returns the translated rvalueOp.
def __FC_trans_copyToVLWithDBPtr(self, param, rvalueOp):
oper = self.__FC_trans_copyToVL(param, rvalueOp)
oper.type = oper.MEM_L
return self.__FC_trans_dbpointerInVL(param, oper)
# FC parameter translation:
# Translate L-stack access r-value.
# Returns the translated rvalueOp.
......@@ -298,8 +305,9 @@ class CallStackElem(object):
AwlOperator.IMM_TIME : __FC_trans_copyToVL,
AwlOperator.IMM_DATE : __FC_trans_copyToVL,
AwlOperator.IMM_TOD : __FC_trans_copyToVL,
AwlOperator.IMM_DT : __FC_trans_copyToVL,
AwlOperator.IMM_DT : __FC_trans_copyToVLWithDBPtr,
AwlOperator.IMM_PTR : __FC_trans_copyToVL,
AwlOperator.IMM_STR : __FC_trans_copyToVLWithDBPtr,
AwlOperator.MEM_E : __FC_trans_direct,
AwlOperator.MEM_A : __FC_trans_direct,
......
......@@ -2,7 +2,7 @@
#
# AWL simulator - CPU
#
# Copyright 2012-2014 Michael Buesch <[email protected]>
# Copyright 2012-2015 Michael Buesch <[email protected]>
#
# 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
......@@ -240,11 +240,15 @@ class S7CPU(object): #+cdef
# Finalize call instructions
for insn, calledCodeBlock, calledDataBlock in self.__allCallInsns(block):
for param in insn.params:
# Add interface references to the parameter assignment.
param.interface = calledCodeBlock.interface
# Final translation of parameter assignment operand.
translator.translateParamAssignOper(param)
try:
for param in insn.params:
# Add interface references to the parameter assignment.
param.interface = calledCodeBlock.interface
# Final translation of parameter assignment operand.
translator.translateParamAssignOper(param)
except AwlSimError as e:
e.setInsn(insn)
raise e
# Check and account for direct L stack allocations and
# interface L stack allocations.
......@@ -986,7 +990,7 @@ class S7CPU(object): #+cdef
fetchMethod = self.fetchTypeMethods[operator.type]
except KeyError:
raise AwlSimError("Invalid fetch request: %s" %\
AwlOperator.type2str[operator.type])
str(operator))
return fetchMethod(self, operator, enforceWidth)
def __fetchWidthError(self, operator, enforceWidth):
......@@ -1007,6 +1011,23 @@ class S7CPU(object): #+cdef
return operator.value.toNativePointerValue()
def fetchIMM_STR(self, operator, enforceWidth):
if operator.width <= 48 and operator.insn:
insnType = operator.insn.insnType
if insnType == AwlInsn.TYPE_L or\
insnType >= AwlInsn.TYPE_EXTENDED:
# This is a special 0-4 character fetch (L) that
# is transparently translated into an integer.
value, data = 0, operator.value
for i in range(2, operator.width // 8):
value = (value << 8) | data[i]
return value
if operator.width not in enforceWidth and enforceWidth:
self.__fetchWidthError(operator, enforceWidth)
return operator.value
def fetchDBLG(self, operator, enforceWidth):
if operator.width not in enforceWidth and enforceWidth:
self.__fetchWidthError(operator, enforceWidth)
......@@ -1249,8 +1270,10 @@ class S7CPU(object): #+cdef
AwlOperator.IMM_S5T : fetchIMM,
AwlOperator.IMM_TIME : fetchIMM,
AwlOperator.IMM_DATE : fetchIMM,
AwlOperator.IMM_DT : fetchIMM,
AwlOperator.IMM_TOD : fetchIMM,
AwlOperator.IMM_PTR : fetchIMM_PTR,
AwlOperator.IMM_STR : fetchIMM_STR,
AwlOperator.MEM_E : fetchE,
AwlOperator.MEM_A : fetchA,
AwlOperator.MEM_M : fetchM,
......@@ -1288,7 +1311,8 @@ class S7CPU(object): #+cdef
try:
storeMethod = self.storeTypeMethods[operator.type]
except KeyError:
raise AwlSimError("Invalid store request")
raise AwlSimError("Invalid store request: %s" %\
str(operator))
storeMethod(self, operator, value, enforceWidth)
def __storeWidthError(self, operator, enforceWidth):
......
......@@ -82,13 +82,13 @@ class AwlStruct(object):
# Return aligned size, in bytes.
def getSize(self):
size = self.__getUnalignedSize()
size = self.getUnalignedSize()
if size % 2:
size += 1
return size
# Return unaligned size, in bytes.
def __getUnalignedSize(self):
def getUnalignedSize(self):
if not self.fields:
return 0
# Get the offset of the last field and
......@@ -103,7 +103,7 @@ class AwlStruct(object):
# Add zero-length field.
def addDummyField(self, name=None):
offset = AwlOffset(self.__getUnalignedSize())
offset = AwlOffset(self.getUnalignedSize())
field = AwlStructField(name, offset, "VOID")
self.__registerField(field)
......@@ -116,17 +116,21 @@ class AwlStruct(object):
# First add a field with the sub-structure's name.
# This field is used for retrieval of a pointer to the sub-struct,
# for alignment and for informational purposes only.
baseOffset = AwlOffset(self.__getUnalignedSize())
baseOffset = AwlOffset(self.getUnalignedSize())
baseField = AwlStructField(otherStructName,
baseOffset, otherStructDataType,
override = AwlStructField(otherStructName,
baseOffset, "VOID"))
self.__registerField(baseField)
# Add all fields from the other struct.
baseOffset = AwlOffset(self.__getUnalignedSize())
baseOffset = AwlOffset(self.getUnalignedSize())
for otherField in otherStruct.fields:
if otherStructName and otherField.name:
newName = otherStructName + "." + otherField.name
if otherField.name.startswith('[') and\
otherField.name.endswith(']'):
newName = otherStructName + otherField.name
else:
newName = otherStructName + "." + otherField.name
else:
newName = None
field = AwlStructField(name = newName,
......@@ -162,8 +166,9 @@ class AwlStruct(object):
"type is unsupported." %\
(name, str(dataType)))
if dataType.type == dataType.TYPE_STRUCT:
# Add a STRUCT.
if dataType.type == dataType.TYPE_STRUCT or\
dataType.type == dataType.TYPE_STRING:
# Add a STRUCT (or STRING, which is represented as struct).
# The struct is represented by the data types struct.
# Merge the data type struct into this struct.
assert(dataType.struct)
......@@ -171,12 +176,13 @@ class AwlStruct(object):
baseField.override = AwlStructField(baseField.name,
baseField.offset,
"VOID")
baseField.initBytes = initBytes
if dataType.type == dataType.TYPE_ARRAY:
# Add an ARRAY.
# First add a field with the array's name.
# It has the data type 'ARRAY' and is informational only.
offset = AwlOffset(self.__getUnalignedSize())
offset = AwlOffset(self.getUnalignedSize())
baseField = AwlStructField(name, offset, dataType,
override = AwlStructField(name, offset,
"VOID"))
......@@ -204,12 +210,17 @@ class AwlStruct(object):
fieldInitData)
initOffset += AwlOffset.fromBitOffset(childType.width)
childIdent.advanceToNextArrayElement(dataType.arrayDimensions)
if childType.width > 8 and\
intDivRoundUp(childType.width, 8) % 2 != 0:
# Align each element to 2-byte-boundary, if word or bigger.
self.addField(cpu, None, AwlDataType.makeByName("BYTE"))
# Add a zero-length array-end guard field,
# to enforce alignment of following fields.
self.addDummyField()
if dataType.type not in (dataType.TYPE_ARRAY,
dataType.TYPE_STRUCT):
if dataType.type not in {dataType.TYPE_ARRAY,
dataType.TYPE_STRUCT,
dataType.TYPE_STRING}:
# Add a single data type.
if dataType.width == 1 and self.fields and\
self.fields[-1].bitSize == 1 and\
......@@ -218,13 +229,13 @@ class AwlStruct(object):
offset = AwlOffset(self.fields[-1].offset.byteOffset,
self.fields[-1].offset.bitOffset + 1)
else:
offset = AwlOffset(self.__getUnalignedSize())
offset = AwlOffset(self.getUnalignedSize())
baseField = AwlStructField(name, offset, dataType, initBytes)
self.__registerField(baseField)
return baseField
def addFieldAligned(self, cpu, name, dataType, byteAlignment, initBytes=None):
padding = byteAlignment - self.__getUnalignedSize() % byteAlignment
padding = byteAlignment - self.getUnalignedSize() % byteAlignment
if padding == byteAlignment:
padding = 0
while padding:
......
......@@ -231,6 +231,31 @@ class AwlDataType(object):
Each list element is a tuple of (start, end)
with the start and end array index for that dimension."""
if nameTokens[0].upper() == "STRING":
# Construct a data structure for the STRING layout.
strLen = -1
if len(nameTokens) == 1:
# Extension: No dimensions means [254]
strLen = 254
elif len(nameTokens) == 4 and\
nameTokens[1] == '[' and nameTokens[3] == ']' and\
nameTokens[2].isdecimal():
strLen = int(nameTokens[2], 10)
if strLen < 0 or strLen > 254:
raise AwlSimError("Invalid STRING length definition "
"in '%s'" % "".join(nameTokens))
byteType = cls.makeByName("BYTE")
import awlsim.core.datastructure as datastructure
struct = datastructure.AwlStruct()
struct.addField(cpu = None, name = None, dataType = byteType,
initBytes = ByteArray((strLen,)))
struct.addField(cpu = None, name = None, dataType = byteType)
for i in range(strLen):
struct.addField(cpu = None, name = "[%s]" % (i + 1),
dataType = byteType)
else:
struct = None
type = cls._name2typeid(nameTokens)
index = None
......@@ -256,7 +281,8 @@ class AwlDataType(object):
# An ARRAY is to be constructed.
elementType = cls(type = type,
isSigned = (type in cls.signedTypes),
index = index)
index = index,
struct = struct)
return cls(type = cls.TYPE_ARRAY,
isSigned = (type in cls.signedTypes),
index = index,
......@@ -265,7 +291,8 @@ class AwlDataType(object):
else:
return cls(type = type,
isSigned = (type in cls.signedTypes),
index = index)
index = index,
struct = struct)
def __init__(self, type, isSigned,
index=None,
......@@ -284,7 +311,8 @@ class AwlDataType(object):
def setStruct(self, struct):
assert(struct is None or
self.type == self.TYPE_STRUCT or
self.type == self.TYPE_UDT_X)
self.type == self.TYPE_UDT_X or
self.type == self.TYPE_STRING)
self.struct = struct
# Get the type element structure.
......@@ -303,7 +331,7 @@ class AwlDataType(object):
if self.__widthOverride is not None:
return self.__widthOverride
if self.type == self.TYPE_ARRAY:
nrElements = self.arrayDimensionsToNrElements(self.arrayDimensions)
nrElements = self.arrayGetNrElements()
if self.arrayElementType.type == self.TYPE_STRUCT:
if self.arrayElementType.struct:
oneElemWidth = self.arrayElementType.struct.getSize() * 8
......@@ -319,6 +347,8 @@ class AwlDataType(object):
width = self.struct.getSize() * 8
else:
width = -1
elif self.type == self.TYPE_STRING:
width = self.struct.getUnalignedSize() * 8
else:
width = self.__typeWidths[self.type]
return width
......@@ -415,8 +445,10 @@ class AwlDataType(object):
# Parse an immediate, constrained by our datatype.
def parseMatchingImmediate(self, tokens):
typeId = self.type
typeWidth = self.width
if typeId == self.TYPE_ARRAY:
typeId = self.arrayElementType.type
typeWidth = self.arrayElementType.width
value = None
if tokens is None:
......@@ -489,6 +521,21 @@ class AwlDataType(object):
elif typeId == self.TYPE_CHAR:
value = self.tryParseImmediate_CHAR(
tokens[0])
elif typeId == self.TYPE_STRING:
value = self.tryParseImmediate_STRING(
tokens[0])
typeStrLen = intDivRoundUp(typeWidth, 8) - 2
immStrLen = len(value) - 2
assert(typeStrLen >= 0 and immStrLen >= 0)
if immStrLen > typeStrLen:
raise AwlSimError("String immediate is "
"too long. Length is %d characters, "
"but maximum is %d characters." %\
immStrLen, typeStrLen)
# Expand the string to type length.
if immStrLen < typeStrLen:
value[0] = typeStrLen
value.extend(b'\x00' * (typeStrLen - immStrLen))
elif typeId == self.TYPE_DATE:
value = self.tryParseImmediate_DATE(
tokens[0])
......@@ -501,6 +548,8 @@ class AwlDataType(object):
def __repr__(self):
if self.type == self.TYPE_ARRAY:
return "ARRAY" #TODO
elif self.type == self.TYPE_STRING:
return "STRING[%d]" % (self.width // 8 - 2)
elif self.type in (self.TYPE_DB_X,
self.TYPE_OB_X,
self.TYPE_FC_X,
......@@ -659,26 +708,40 @@ class AwlDataType(object):
return "".join(ret)
@classmethod
def __tryParseImmediate_STRING(cls, token, maxLen):
def __tryParseImmediate_STRING(cls, token, maxLen=254):
if not token.startswith("'") or\
not token.endswith("'"):
return None
token = token[1:-1]
if len(token) > maxLen:
raise AwlSimError("String too long (>%d characters)" % maxLen)
value = 0
for c in token:
value <<= 8
value |= ord(c)
return value
try:
import awlsim.core.parser as parser
data = token.encode(parser.AwlParser.TEXT_ENCODING)
data = bytearray(data)
if len(data) != len(token):
raise ValueError
except (UnicodeError, ValueError) as e:
raise AwlSimError("Invalid characters in string '%s'." %\
token)
return data
@classmethod
def tryParseImmediate_STRING(cls, token):
return cls.__tryParseImmediate_STRING(token, 4)
data = cls.__tryParseImmediate_STRING(token)
if data is None:
return None
# Add max-length and actual-length bytes.
data = bytearray((len(data), len(data))) + data
return data
@classmethod
def tryParseImmediate_CHAR(cls, token):
return cls.__tryParseImmediate_STRING(token, 1)
data = cls.__tryParseImmediate_STRING(token, 1)
if data is None:
return None
# Return it as integer.
return data[0]
@classmethod
def tryParseImmediate_S5T(cls, token):
......
......@@ -50,6 +50,7 @@ class AwlOperator(DynAttrs):
IMM_TOD = EnumGen.item # TOD# immediate
IMM_DT = EnumGen.item # DT# immediate
IMM_PTR = EnumGen.item # Pointer immediate (P#x.y, P#area x.y, P#DBn.DBX x.y)
IMM_STR = EnumGen.item # STRING immediate ('abc')
__IMM_END = EnumGen.item
MEM_E = EnumGen.item # Input
......@@ -266,22 +267,13 @@ class AwlOperator(DynAttrs):
"of width %d bits." %\
(str(dataType), dataType.width, str(oper), operWidth))
operWidth = self.width
if dataType.type == AwlDataType.TYPE_CHAR and\
self.type == AwlOperator.IMM:
# Special handling for CHAR immediates.
# Adjust the width, if this is just one character.
if self.value <= 0xFF:
operWidth = 8
if dataType.type == AwlDataType.TYPE_UDT_X:
try:
udt = cpu.udts[dataType.index]
if udt.struct.getSize() * 8 != operWidth:
if udt.struct.getSize() * 8 != self.width:
raise ValueError
except (KeyError, ValueError) as e:
mismatch(dataType, self, operWidth)
mismatch(dataType, self, self.width)
elif dataType.type in {AwlDataType.TYPE_POINTER,
AwlDataType.TYPE_ANY}:
if self.type == AwlOperator.IMM_PTR:
......@@ -297,9 +289,33 @@ class AwlOperator(DynAttrs):
# Try to make pointer from operator.
# This will raise AwlSimError on failure.
self.makePointer()
elif dataType.type == AwlDataType.TYPE_CHAR:
if self.type == AwlOperator.IMM_STR:
if self.width != (2 + 1) * 8:
raise AwlSimError("String to CHAR parameter "
"must be only one single character "
"long.")
else:
if self.isImmediate():
raise AwlSimError("Invalid immediate '%s'"
"for CHAR data type." %\
str(self))
if self.width != dataType.width:
mismatch(dataType, self, self.width)
elif dataType.type == AwlDataType.TYPE_STRING:
if self.type == AwlOperator.IMM_STR:
if self.width > dataType.width:
mismatch(dataType, self, self.width)
else:
if self.isImmediate():
raise AwlSimError("Invalid immediate '%s'"
"for STRING data type." %\
str(self))
if self.width != dataType.width:
mismatch(dataType, self, self.width)
else:
if operWidth != dataType.width:
mismatch(dataType, self, operWidth)
if self.width != dataType.width:
mismatch(dataType, self, self.width)
# Resolve this indirect operator to a direct operator.
def resolve(self, store=True):
......@@ -375,6 +391,10 @@ class AwlOperator(DynAttrs):
return "TOD#" #TODO
elif self.type == self.IMM_PTR:
return self.value.toPointerString()
elif self.type == self.IMM_STR:
strLen = self.value[1]
import awlsim.core.parser as parser
return "'" + self.value[2:2+strLen].decode(parser.AwlParser.TEXT_ENCODING) + "'"
elif self.type in (self.MEM_A, self.MEM_E,
self.MEM_M, self.MEM_L, self.MEM_VL):
pfx = self.type2str[self.type]
......
......@@ -527,6 +527,12 @@ class AwlOpTranslator(object):
if immediate is not None:
return OpDescriptor(AwlOperator(AwlOperator.IMM_DATE, 16,
immediate), 1)
# DATE_AND_TIME immediate
immediate = AwlDataType.tryParseImmediate_DT(rawOps)
if immediate is not None:
return OpDescriptor(AwlOperator(AwlOperator.IMM_DT,
len(immediate) * 8,
immediate), 5)
# Pointer immediate
pointer, fields = AwlDataType.tryParseImmediate_Pointer(rawOps)
if pointer is not None:
......@@ -572,7 +578,8 @@ class AwlOpTranslator(object):
# String immediate
immediate = AwlDataType.tryParseImmediate_STRING(rawOps[0])
if immediate is not None:
return OpDescriptor(AwlOperator(AwlOperator.IMM, 32,
return OpDescriptor(AwlOperator(AwlOperator.IMM_STR,
len(immediate) * 8,
immediate), 1)
# DBx.DBX/B/W/D addressing
match = re.match(r'^DB(\d+)\.DB([XBWD])$', rawOps[0])
......
......@@ -916,6 +916,7 @@ class AwlParser(object):
else:
raise AwlParserError("In variable section: Unknown tokens.\n"\
"Maybe missing semicolon in preceding lines?")
if initTokens:
identChain = (self.tree.curDataField.getIdentChain() if\
self.tree.curDataField else []) +\
......
......@@ -360,10 +360,40 @@ class AwlTranslator(object):
value = ptr,
insn = param.rvalueOp.insn)
# Translate STRING immediates.
# Converts STRING to CHAR, if required, or expands string lengths.
def __translateParamString(self, param):
if param.lValueDataType.type == AwlDataType.TYPE_CHAR:
# CHAR parameter.
if param.rvalueOp.type == AwlOperator.IMM_STR:
# Translate single-character string immediates
# to 8 bit integer immediates.
if param.rvalueOp.width == (2 + 1) * 8:
param.rvalueOp = AwlOperator(
type = AwlOperator.IMM,
width = 8,
value = param.rvalueOp.value[2],
insn = param.rvalueOp.insn)
elif param.lValueDataType.type == AwlDataType.TYPE_STRING:
# STRING parameter.
if param.rvalueOp.type == AwlOperator.IMM_STR:
if param.rvalueOp.width < param.lValueDataType.width:
# Expand the string immediate length.
# This is an awlsim extension.
curLen = param.rvalueOp.width // 8
newLen = param.lValueDataType.width // 8
assert(curLen >= 2 and newLen >= 2)
data = param.rvalueOp.value[:]
data[0] = newLen - 2
data.extend(b'\x00' * (newLen - curLen))
param.rvalueOp.value = data
param.rvalueOp.width = newLen * 8
# Final translation of AwlParamAssign r-value operands.
# Overrides the rvalueOp in place, if required.
def translateParamAssignOper(self, param):
self.__translateParamPointer(param)
self.__translateParamString(param)
class AwlSymResolver(object):
"Global and local symbol resolver."
......@@ -428,7 +458,8 @@ class AwlSymResolver(object):
dataType = block.interface.getFieldDataType(chain)
# Sanity checks
if dataType.type == AwlDataType.TYPE_ARRAY:
if dataType.type in {AwlDataType.TYPE_ARRAY,
AwlDataType.TYPE_STRING}:
if isLastElement and\
not chain[-1].indices and\
oper.type != AwlOperator.NAMED_LOCAL_PTR and\
......@@ -463,15 +494,30 @@ class AwlSymResolver(object):
subOffset += structField.offset
# Add array offset to subOffset,
# if this is an ARRAY element access.
# if this is an ARRAY or STRING element access.
if chain[-1].indices:
# Calculate the array offset.
assert(dataType.type == AwlDataType.TYPE_ARRAY)
arrayIndex = dataType.arrayIndicesCollapse(chain[-1].indices)
elemWidth = dataType.arrayElementType.width
bitOffset = arrayIndex * elemWidth
byteOffset = bitOffset // 8
bitOffset %= 8
if dataType.type == AwlDataType.TYPE_ARRAY:
# Calculate the array offset.
arrayIndex = dataType.arrayIndicesCollapse(chain[-1].indices)
elemWidth = dataType.arrayElementType.width
bitOffset = arrayIndex * elemWidth
byteOffset = bitOffset // 8
bitOffset %= 8
elif dataType.type == AwlDataType.TYPE_STRING:
# Calculate the string offset.
if len(chain[-1].indices) != 1:
raise AwlSimError("Only one index is "
"allowed in STRING indexing.")