Commit 1074a3ac authored by Michael Büsch's avatar Michael Büsch

Add source manager

Signed-off-by: Michael Büsch's avatarMichael Buesch <m@bues.ch>
parent bbd02e57
......@@ -23,6 +23,7 @@ from __future__ import division, absolute_import, print_function, unicode_litera
from awlsim.common.compat import *
from awlsim.common.cpuspecs import *
from awlsim.common.sources import *
from awlsim.common.util import *
from awlsim.library.libselection import *
......@@ -30,7 +31,6 @@ from awlsim.library.libselection import *
import base64, binascii
import datetime
import os
import hashlib
if isPy2Compat:
from ConfigParser import SafeConfigParser as _ConfigParser
......@@ -40,124 +40,6 @@ else:
from configparser import Error as _ConfigParserError
class GenericSource(object):
SRCTYPE = "<generic>"
IDENT_HASH = "sha256"
def __init__(self, name="", filepath="", sourceBytes=b""):
self.name = name
self.filepath = filepath
self.sourceBytes = sourceBytes
self.__identHash = None
@property
def name(self):
return self.__name
@name.setter
def name(self, newName):
self.__name = newName
self.__identHash = None
@property
def filepath(self):
return self.__filepath
@filepath.setter
def filepath(self, newFilepath):
self.__filepath = newFilepath
self.__identHash = None
@property
def sourceBytes(self):
return self.__sourceBytes
@sourceBytes.setter
def sourceBytes(self, newSourceBytes):
self.__sourceBytes = newSourceBytes
self.__identHash = None
@property
def identHash(self):
if not self.__identHash:
# Calculate the ident hash
h = hashlib.new(self.IDENT_HASH, self.SRCTYPE.encode("utf-8"))
if self.name is not None:
h.update(self.name.encode("utf-8"))
if self.filepath is not None:
h.update(self.filepath.encode("utf-8"))
h.update(self.sourceBytes)
self.__identHash = h.digest()
return self.__identHash
@identHash.setter
def identHash(self, identHash):
# Force the ident hash.
self.__identHash = identHash
@property
def identHashStr(self):
return binascii.b2a_hex(self.identHash).decode("ascii")
def dup(self):
raise NotImplementedError
def isFileBacked(self):
return bool(self.filepath)
def writeFileBacking(self):
"Write the backing file, if any."
if not self.isFileBacked():
return
awlFileWrite(self.filepath, self.sourceBytes, encoding="binary")
def forceNonFileBacked(self, newName):
"Convert this source to a non-file-backed source."
if self.isFileBacked():
self.filepath = ""
self.name = newName
def toBase64(self):
return base64.b64encode(self.sourceBytes).decode("ascii")
@classmethod
def fromFile(cls, name, filepath):
try:
data = awlFileRead(filepath, encoding="binary")
except AwlSimError as e:
raise AwlSimError("Project: Could not read %s "
"source file '%s':\n%s" %\
(cls.SRCTYPE, filepath, str(e)))
return cls(name, filepath, data)
@classmethod
def fromBase64(cls, name, b64):
try:
data = base64.b64decode(b64.encode("ascii"))
except (TypeError, binascii.Error, UnicodeError) as e:
raise AwlSimError("Project: %s source '%s' "
"has invalid base64 encoding." %\
(cls.SRCTYPE, name))
return cls(name, None, data)
def __repr__(self):
return "%s%s %s %s" % ("" if self.isFileBacked() else "project ",
self.SRCTYPE, self.name, self.identHashStr)
class AwlSource(GenericSource):
SRCTYPE = "AWL/STL"
def dup(self):
return AwlSource(self.name, self.filepath,
self.sourceBytes[:])
class SymTabSource(GenericSource):
SRCTYPE = "symbol table"
def dup(self):
return SymTabSource(self.name, self.filepath,
self.sourceBytes[:])
class HwmodDescriptor(object):
"""Hardware module descriptor."""
......
# -*- coding: utf-8 -*-
#
# AWL simulator - object reference manager
#
# Copyright 2015 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 *
class ObjRef(object):
"""An object reference."""
@classmethod
def make(cls, name, managerOrRef, obj = None, inheritRef = False):
"""Make a new ObjRef instance.
name -> A name string (or callable returning a string).
managerOrRef -> An ObjRefManager instance or ObjRef instance.
obj -> The object that is managed.
inheritRef -> If False, a new ref is created.
If True and managerOrRef is an ObjRef, the ref is inherited.
"""
if managerOrRef is None:
return None
elif isinstance(managerOrRef, ObjRefManager):
manager = managerOrRef
return cls(name, manager, obj)
elif isinstance(managerOrRef, ObjRef):
oldRef = managerOrRef
newRef = cls(name, oldRef.__manager, obj)
if inheritRef and oldRef.alive:
oldRef.destroy()
return newRef
else:
assert(0)
def __init__(self, name, manager, obj = None):
"""Contruct object reference.
name: Informational name string or callable returing a string.
manager: An ObjRefManager instance.
obj: The object that is managed (optional).
"""
self.__name = name
self.__obj = obj
self.__manager = manager
self.__manager._addRef(self)
def destroy(self):
"""Destroy (unref) this reference.
This removes the reference from the manager.
"""
assert(self.alive)
self.__manager.refDestroyed(self)
self.__name = None
self.__obj = None
self.__manager = None
@property
def name(self):
"""The reference name string.
"""
if callable(self.__name):
return self.__name(self)
return self.__name
@property
def obj(self):
"""The referenced object.
"""
return self.__obj
@property
def alive(self):
"""True, if this reference is alive.
False, if this reference was destroyed.
"""
return self.__manager is not None
def __repr__(self):
return str(self.name)
class ObjRefManager(object):
"""Object reference manager."""
def __init__(self, name):
"""Contruct reference manager.
name: Informational name string or callable returing a string.
"""
self.__name = name
self.__refs = set()
@property
def name(self):
"""The manager name string.
"""
if callable(self.__name):
return self.__name(self)
return self.__name
def _addRef(self, objRef):
self.__refs.add(objRef)
def refDestroyed(self, objRef):
"""Callback: Called if one reference was destroyed.
Override this method, if you want to be notified.
"""
self.__refs.remove(objRef)
if not self.__refs:
self.allRefsDestroyed()
def allRefsDestroyed(self):
"""Callback: Called if all references were destroyed.
Override this method, if you want to be notified.
"""
pass
def __repr__(self):
return str(self.name)
# -*- coding: utf-8 -*-
#
# AWL simulator - source management
#
# Copyright 2014-2015 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.common.refmanager import *
from awlsim.common.util import *
import base64, binascii
import hashlib
class GenericSource(object):
SRCTYPE = "<generic>"
IDENT_HASH = "sha256"
def __init__(self, name="", filepath="", sourceBytes=b""):
self.name = name
self.filepath = filepath
self.sourceBytes = sourceBytes
self.__identHash = None
@property
def name(self):
return self.__name
@name.setter
def name(self, newName):
self.__name = newName
self.__identHash = None
@property
def filepath(self):
return self.__filepath
@filepath.setter
def filepath(self, newFilepath):
self.__filepath = newFilepath
self.__identHash = None
@property
def sourceBytes(self):
return self.__sourceBytes
@sourceBytes.setter
def sourceBytes(self, newSourceBytes):
self.__sourceBytes = newSourceBytes
self.__identHash = None
@property
def identHash(self):
if not self.__identHash:
# Calculate the ident hash
h = hashlib.new(self.IDENT_HASH, self.SRCTYPE.encode("utf-8"))
if self.name is not None:
h.update(self.name.encode("utf-8"))
if self.filepath is not None:
h.update(self.filepath.encode("utf-8"))
h.update(self.sourceBytes)
self.__identHash = h.digest()
return self.__identHash
@identHash.setter
def identHash(self, identHash):
# Force the ident hash.
self.__identHash = identHash
@property
def identHashStr(self):
return binascii.b2a_hex(self.identHash).decode("ascii")
def dup(self):
raise NotImplementedError
def isFileBacked(self):
return bool(self.filepath)
def writeFileBacking(self):
"Write the backing file, if any."
if not self.isFileBacked():
return
awlFileWrite(self.filepath, self.sourceBytes, encoding="binary")
def forceNonFileBacked(self, newName):
"Convert this source to a non-file-backed source."
if self.isFileBacked():
self.filepath = ""
self.name = newName
def toBase64(self):
return base64.b64encode(self.sourceBytes).decode("ascii")
@classmethod
def fromFile(cls, name, filepath):
try:
data = awlFileRead(filepath, encoding="binary")
except AwlSimError as e:
raise AwlSimError("Project: Could not read %s "
"source file '%s':\n%s" %\
(cls.SRCTYPE, filepath, str(e)))
return cls(name, filepath, data)
@classmethod
def fromBase64(cls, name, b64):
try:
data = base64.b64decode(b64.encode("ascii"))
except (TypeError, binascii.Error, UnicodeError) as e:
raise AwlSimError("Project: %s source '%s' "
"has invalid base64 encoding." %\
(cls.SRCTYPE, name))
return cls(name, None, data)
def __repr__(self):
return "%s%s %s %s" % ("" if self.isFileBacked() else "project ",
self.SRCTYPE, self.name, self.identHashStr)
class AwlSource(GenericSource):
SRCTYPE = "AWL/STL"
def dup(self):
return AwlSource(self.name, self.filepath,
self.sourceBytes[:])
class SymTabSource(GenericSource):
SRCTYPE = "symbol table"
def dup(self):
return SymTabSource(self.name, self.filepath,
self.sourceBytes[:])
class SourceManager(ObjRefManager):
"""Manages one source."""
def __init__(self, source, container):
"""source -> An AwlSource or SymTabSource instance.
container -> A SourceContainer instance.
"""
super(SourceManager, self).__init__(
name = lambda slf: "%s/%s" % (slf.source.name,
slf.source.identHashStr))
self.source = source
self.container = container
def allRefsDestroyed(self):
"""Called, if all source references are destroyed.
"""
super(SourceManager, self).allRefsDestroyed()
if self.container:
self.container.removeManager(self)
self.source = self.container = None
class SourceContainer(object):
"""Container for source managers."""
def __init__(self):
self.__sourceManagers = []
def addManager(self, sourceManager):
"""Add a SourceManager instance to this container.
"""
self.__sourceManagers.append(sourceManager)
def removeManager(self, sourceManager):
"""Remove a SourceManager instance from this container.
"""
try:
self.__sourceManagers.remove(sourceManager)
except ValueError as e:
# The removed manager did not exist.
# This might happen in rare conditions, for example if a
# previous download/translation attempt failed.
# Just ignore this.
pass
def clear(self):
"""Remove all managers from the container.
"""
for sourceManager in self.__sourceManagers[:]:
self.removeManager(sourceManager)
def getSourceManagers(self):
"""Return a list of source managers in this container.
"""
return self.__sourceManagers
def getSources(self):
"""Return a list of sources in this container.
"""
return [ m.source for m in self.getSourceManagers() ]
......@@ -22,6 +22,8 @@
from __future__ import division, absolute_import, print_function, unicode_literals
from awlsim.common.compat import *
from awlsim.common.refmanager import *
from awlsim.core.labels import *
from awlsim.core.datastructure import *
from awlsim.core.datatypes import *
......@@ -463,6 +465,23 @@ class Block(object):
def __init__(self, index):
self.index = index
self.sourceRef = None
def __del__(self):
if self.sourceRef:
assert(not self.sourceRef.alive)
def setSourceRef(self, sourceManagerOrRef, inheritRef = False):
self.sourceRef = ObjRef.make(
name = lambda ref: str(ref.obj),
managerOrRef = sourceManagerOrRef,
obj = self,
inheritRef = inheritRef)
def destroySourceRef(self):
if self.sourceRef:
self.sourceRef.destroy()
self.sourceRef = None
def __repr__(self):
return "Block %d" % self.index
......
......@@ -91,16 +91,31 @@ class S7Prog(object):
def __init__(self, cpu):
self.cpu = cpu
self.pendingRawDBs = []
self.pendingRawFBs = []
self.pendingRawFCs = []
self.pendingRawOBs = []
self.pendingRawUDTs = []
self.pendingLibSelections = []
self.symbolTable = SymbolTable()
self.reset()
def reset(self):
for rawBlock in allElementsIn(self.pendingRawDBs,
self.pendingRawFBs,
self.pendingRawFCs,
self.pendingRawOBs,
self.pendingRawUDTs):
rawBlock.destroySourceRef()
self.pendingRawDBs = []
self.pendingRawFBs = []
self.pendingRawFCs = []
self.pendingRawOBs = []
self.pendingRawUDTs = []
self.pendingLibSelections = []
self.symbolTable = SymbolTable()
self.symbolTable.clear()
def addRawDB(self, rawDB):
assert(isinstance(rawDB, RawAwlDB))
......@@ -293,9 +308,6 @@ class S7Prog(object):
# Mnemonics autodetection
self.__detectMnemonics()
#TODO: Annotate the blocks with the source idents, so translated blocks
# can be removed, if sources are removed from the system.
# Translate UDTs
udts = {}
for rawUDT in self.pendingRawUDTs:
......@@ -305,8 +317,11 @@ class S7Prog(object):
raise AwlSimError("Multiple definitions of "\
"UDT %d." % udtNumber)
rawUDT.index = udtNumber
udts[udtNumber] = UDT.makeFromRaw(rawUDT)
self.cpu.udts.update(udts)
udt = UDT.makeFromRaw(rawUDT)
if udtNumber in self.cpu.udts:
self.cpu.udts[udtNumber].destroySourceRef()
udts[udtNumber] = udt
self.cpu.udts[udtNumber] = udt
self.pendingRawUDTs = []
# Build all UDTs (Resolve sizes of all fields)
......@@ -323,14 +338,16 @@ class S7Prog(object):
"OB %d." % obNumber)
rawOB.index = obNumber
ob = translator.translateCodeBlock(rawOB, OB)
if obNumber in self.cpu.obs:
self.cpu.obs[obNumber].destroySourceRef()
obs[obNumber] = ob
self.cpu.obs[obNumber] = ob
# Create the TEMP-preset handler table
try:
presetHandlerClass = OBTempPresets_table[obNumber]
except KeyError:
presetHandlerClass = OBTempPresets_dummy
self.cpu.obTempPresetHandlers[obNumber] = presetHandlerClass(self.cpu)
self.cpu.obs.update(obs)
self.pendingRawOBs = []
# Translate FBs
......@@ -350,8 +367,10 @@ class S7Prog(object):
self.cpu.fbs[fbNumber].libraryName))
rawFB.index = fbNumber
fb = translator.translateCodeBlock(rawFB, FB)
if fbNumber in self.cpu.fbs:
self.cpu.fbs[fbNumber].destroySourceRef()
fbs[fbNumber] = fb
self.cpu.fbs.update(fbs)
self.cpu.fbs[fbNumber] = fb
self.pendingRawFBs = []
# Translate FCs
......@@ -371,8 +390,10 @@ class S7Prog(object):
self.cpu.fcs[fcNumber].libraryName))
rawFC.index = fcNumber
fc = translator.translateCodeBlock(rawFC, FC)
if fcNumber in self.cpu.fcs:
self.cpu.fcs[fcNumber].destroySourceRef()
fcs[fcNumber] = fc
self.cpu.fcs.update(fcs)
self.cpu.fcs[fcNumber] = fc
self.pendingRawFCs = []
if not self.cpu.sfbs:
......@@ -407,8 +428,10 @@ class S7Prog(object):
"DB %d." % dbNumber)
rawDB.index = dbNumber
db = translator.translateDB(rawDB)
if dbNumber in self.cpu.dbs:
self.cpu.dbs[dbNumber].destroySourceRef()
dbs[dbNumber] = db
self.cpu.dbs.update(dbs)
self.cpu.dbs[dbNumber] = db
self.pendingRawDBs = []
# Resolve symbolic instructions and operators
......@@ -433,6 +456,11 @@ class S7CPU(object): #+cdef
self.setPeripheralReadCallback(None)
self.setPeripheralWriteCallback(None)
self.setScreenUpdateCallback(None)
self.udts = {}
self.dbs = {}
self.obs = {}
self.fcs = {}
self.fbs = {}
self.reset()
self.enableExtendedInsns(False)
self.enableObTempPresets(False)
......@@ -552,16 +580,21 @@ class S7CPU(object): #+cdef
yield insn, codeBlock, dataBlock
def load(self, parseTree, rebuild = False):
def load(self, parseTree, rebuild = False, sourceManager = None):
for rawDB in parseTree.dbs.values():
rawDB.setSourceRef(sourceManager)
self.prog.addRawDB(rawDB)
for rawFB in parseTree.fbs.values():
rawFB.setSourceRef(sourceManager)
self.prog.addRawFB(rawFB)
for rawFC in parseTree.fcs.values():
rawFC.setSourceRef(sourceManager)
self.prog.addRawFC(rawFC)
for rawOB in parseTree.obs.values():
rawOB.setSourceRef(sourceManager)
self.prog.addRawOB(rawOB)
for rawUDT in parseTree.udts.values():
rawUDT.setSourceRef(sourceManager)
self.prog.addRawUDT(rawUDT)
if rebuild:
self.prog.build()
......@@ -601,15 +634,15 @@ class S7CPU(object): #+cdef
def reset(self):
self.prog.reset()
self.udts = {
# UDTs
}
self.dbs = {
# DBs
for block in allElementsIn(self.udts.values(), self.dbs.values(),
self.obs.values(), self.fcs.values(),
self.fbs.values()):
block.destroySourceRef()
self.udts = {} # UDTs
self.dbs = { # DBs
0 : DB(0, permissions = 0), # read/write-protected system-DB
}
self.obs = {
# OBs
self.obs = { # OBs
1 : OB([], 1), # Empty OB1
}
self.obTempPresetHandlers = {
......@@ -617,18 +650,11 @@ class S7CPU(object): #+cdef
1 : OBTempPresets_table[1](self), # Default OB1 handler
# This table is extended as OBs are loaded.
}
self.fcs = {
# User FCs
}
self.fbs = {
# User FBs
}
self.sfcs = {
# System SFCs
}
self.sfbs = {
# System SFBs
}
self.fcs = {} # User FCs
self.fbs = {} # User FBs
self.sfcs = {} # System SFCs
self.sfbs = {} # System SFBs
self.is4accu = False
self.reallocate(force=True)
self.ar1 = Adressregister()
......
......@@ -116,12 +116,12 @@ class AwlSim(object):
except AwlSimError as e:
self.__handleSimException(e)
def load(self, parseTree, rebuild = False):
def load(self, parseTree, rebuild = False, sourceManager = None):
if self.__profileLevel >= 2:
self.__profileStart()
try:
self.cpu.load(parseTree, rebuild)
self.cpu.load(parseTree, rebuild, sourceManager)
except AwlSimError as e:
self.__handleSimException(e)
......
......@@ -26,6 +26,7 @@ import sys
import re
from awlsim.common.project import *
from awlsim.common.refmanager import *
from awlsim.core.util import *
from awlsim.core.datatypes import *
......@@ -111,12 +112,12 @@ class RawAwlBlock(object):
"tree",
"index",
"descriptors",
"sourceRef",
)