Commit 7dd98f2d authored by Michael Büsch's avatar Michael Büsch

Rewrite project file handling

Signed-off-by: Michael Büsch's avatarMichael Buesch <[email protected]>
parent c922f3c7
......@@ -3,7 +3,7 @@
#
# AWL simulator - LinuxCNC HAL module
#
# Copyright 2013 Michael Buesch <[email protected]>
# Copyright 2013-2014 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
......@@ -27,9 +27,9 @@ import sys
import os
import getopt
from awlsim.core.main import AwlSim
from awlsim.core.parser import AwlParser
from awlsim.core.util import AwlSimError, AwlParserError, printInfo, printWarning, printError, str2bool
from awlsim.core.main import *
from awlsim.core.parser import *
from awlsim.core.util import *
from awlsim.core.version import VERSION_MAJOR, VERSION_MINOR
try:
......@@ -177,7 +177,7 @@ def main():
if len(args) != 1:
usage()
return 1
awlSource = args[0]
awlFile = args[0]
# try:
# os.nice(-5)
......@@ -198,7 +198,8 @@ def main():
s = mainwnd.getSim()
else:
p = AwlParser()
p.parseFile(awlSource)
p.parseSource(AwlSource(awlFile, awlFile,
awlFileRead(awlFile, encoding="binary")))
s = AwlSim()
loadHardwareModule(awlSim = s, hal = hal,
......
......@@ -27,12 +27,14 @@ from awlsim.core.enumeration import *
class AwlSimError(Exception):
def __init__(self, message, cpu=None,
rawInsn=None, insn=None, lineNr=None):
rawInsn=None, insn=None, lineNr=None,
fileId=None):
Exception.__init__(self, message)
self.cpu = cpu
self.rawInsn = rawInsn
self.insn = insn
self.lineNr = lineNr
self.fileId = fileId
def setCpu(self, cpu):
self.cpu = cpu
......@@ -52,6 +54,12 @@ class AwlSimError(Exception):
def getInsn(self):
return self.insn
def setFileId(self, fileId):
self.fileId = fileId
def getFileId(self):
return self.fileId
def setLineNr(self, lineNr):
self.lineNr = lineNr
......@@ -88,8 +96,14 @@ class AwlSimError(Exception):
return errorStr
def doGetReport(self, title):
fileId = self.getFileId()
if fileId:
fileId += " "
else:
fileId = ""
ret = [ "-- %s --\n" % title ]
ret.append("ERROR at line %s:\n" % self.getLineNrStr())
ret.append("ERROR at %sline %s:\n" %\
(fileId, self.getLineNrStr()))
ret.append(" \n%s\n" % str(self))
cpu = self.getCpu()
if cpu:
......
......@@ -2,7 +2,7 @@
#
# AWL parser
#
# Copyright 2012-2013 Michael Buesch <[email protected]>
# Copyright 2012-2014 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
......@@ -27,6 +27,7 @@ import re
from awlsim.core.util import *
from awlsim.core.datatypes import *
from awlsim.core.project import *
class RawAwlInsn(object):
......@@ -265,6 +266,8 @@ class AwlParseTree(object):
self.curBlock = None
self.fileId = ""
class AwlParser(object):
EnumGen.start
STATE_GLOBAL = EnumGen.item
......@@ -347,8 +350,9 @@ class AwlParser(object):
return self.__inAnyHeader() or\
self.state == self.STATE_GLOBAL
def __tokenize(self, data):
def __tokenize(self, data, fileId):
self.reset()
self.tree.fileId = fileId
self.lineNr = 1
t = self.TokenizerState(self)
......@@ -966,16 +970,25 @@ class AwlParser(object):
insn = self.__parseInstruction(t)
self.tree.curBlock.insns.append(insn)
def parseFile(self, filename):
self.parseData(awlFileRead(filename))
def parseSource(self, awlSource):
"""Parse an AWL source.
awlSource is an AwlSource instance."""
self.parseData(awlSource.sourceBytes, str(awlSource))
def parseData(self, data):
def parseData(self, dataBytes, fileId=""):
try:
data = dataBytes.decode("latin_1")
except UnicodeError as e:
raise AwlParserError("Could not decode AWL/STL charset.")
#FIXME: This check will trigger, if there is no OB, which may happen
# for projects with multiple awl files.
self.flatLayout = not re.match(r'.*^\s*ORGANIZATION_BLOCK\s+.*',
data, re.DOTALL | re.MULTILINE)
try:
self.__tokenize(data)
self.__tokenize(data, fileId)
except AwlParserError as e:
e.setLineNr(self.lineNr)
e.setFileId(fileId)
raise e
def getParseTree(self):
......
......@@ -25,6 +25,7 @@ from awlsim.core.compat import *
from awlsim.core.cpuspecs import *
from awlsim.core.util import *
import base64, binascii
import datetime
import os
......@@ -36,11 +37,56 @@ else:
from configparser import Error as _ConfigParserError
class GenericSource(object):
SRCTYPE = "<generic>"
def __init__(self, identifier, filepath, sourceBytes):
assert(identifier)
self.identifier = identifier
self.filepath = filepath
self.sourceBytes = sourceBytes
def isFileBacked(self):
return bool(self.filepath)
def toBase64(self):
return base64.b64encode(self.sourceBytes).decode("ascii")
@classmethod
def fromFile(cls, identifier, 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(identifier, filepath, data)
@classmethod
def fromBase64(cls, identifier, b64):
try:
data = base64.b64decode(b64, validate=True)
except (TypeError, binascii.Error) as e:
raise AwlSimError("Project: %s source '%s' "
"has invalid base64 encoding." %\
(cls.SRCTYPE, identifier))
return cls(identifier, None, data)
def __repr__(self):
return "%s%s %s" % ("" if self.isFileBacked() else "project ",
self.SRCTYPE, self.identifier)
class AwlSource(GenericSource):
SRCTYPE = "AWL/STL"
class SymTabSource(GenericSource):
SRCTYPE = "symbol table"
class Project(object):
def __init__(self, projectFile, awlFiles=[], symTabFiles=[], cpuSpecs=None):
def __init__(self, projectFile, awlSources=[], symTabSources=[], cpuSpecs=None):
self.projectFile = projectFile
self.awlFiles = awlFiles
self.symTabFiles = symTabFiles
self.awlSources = awlSources
self.symTabSources = symTabSources
if not cpuSpecs:
cpuSpecs = S7CPUSpecs()
self.cpuSpecs = cpuSpecs
......@@ -67,8 +113,8 @@ class Project(object):
raise AwlSimError("Project file: The data is "\
"not an awlsim project.")
projectDir = os.path.dirname(projectFile)
awlFiles = []
symTabFiles = []
awlSources = []
symTabSources = []
cpuSpecs = S7CPUSpecs()
try:
p = _ConfigParser()
......@@ -82,10 +128,19 @@ class Project(object):
# CPU section
for i in range(0xFFFF):
if not p.has_option("CPU", "awl_file_%d" % i):
option = "awl_file_%d" % i
if not p.has_option("CPU", option):
break
path = p.get("CPU", option)
src = AwlSource.fromFile(path, cls.__generic2path(path, projectDir))
awlSources.append(src)
for i in range(0xFFFF):
option = "awl_%d" % i
if not p.has_option("CPU", option):
break
path = p.get("CPU", "awl_file_%d" % i)
awlFiles.append(cls.__generic2path(path, projectDir))
awlBase64 = p.get("CPU", option)
src = AwlSource.fromBase64("#%d" % i, awlBase64)
awlSources.append(src)
if p.has_option("CPU", "mnemonics"):
mnemonics = p.getint("CPU", "mnemonics")
cpuSpecs.setConfiguredMnemonics(mnemonics)
......@@ -95,17 +150,26 @@ class Project(object):
# SYMBOLS section
for i in range(0xFFFF):
if not p.has_option("SYMBOLS", "sym_tab_file_%d" % i):
option = "sym_tab_file_%d" % i
if not p.has_option("SYMBOLS", option):
break
path = p.get("SYMBOLS", "sym_tab_file_%d" % i)
symTabFiles.append(cls.__generic2path(path, projectDir))
path = p.get("SYMBOLS", option)
src = SymTabSource.fromFile(path, cls.__generic2path(path, projectDir))
symTabSources.append(src)
for i in range(0xFFFF):
option = "sym_tab_%d" % i
if not p.has_option("SYMBOLS", option):
break
symTabBase64 = p.get("SYMBOLS", option)
src = SymTabSource.fromBase64("#%d" % i, symTabBase64)
symTabSources.append(src)
except _ConfigParserError as e:
raise AwlSimError("Project parser error: " + str(e))
return cls(projectFile = projectFile,
awlFiles = awlFiles,
symTabFiles = symTabFiles,
awlSources = awlSources,
symTabSources = symTabSources,
cpuSpecs = cpuSpecs)
@classmethod
......@@ -144,17 +208,28 @@ class Project(object):
lines.append("file_version=0")
lines.append("date=%s" % str(datetime.datetime.utcnow()))
lines.append("")
lines.append("[CPU]")
for i, awlFile in enumerate(self.awlFiles):
path = self.__path2generic(awlFile, projectDir)
fileBackedSources = (src for src in self.awlSources if src.isFileBacked())
embeddedSources = (src for src in self.awlSources if not src.isFileBacked())
for i, awlSrc in enumerate(fileBackedSources):
path = self.__path2generic(awlSrc.filepath, projectDir)
lines.append("awl_file_%d=%s" % (i, path))
for i, awlSrc in enumerate(embeddedSources):
lines.append("awl_%d=%s" % (i, awlSrc.toBase64()))
lines.append("mnemonics=%d" % self.cpuSpecs.getConfiguredMnemonics())
lines.append("nr_accus=%d" % self.cpuSpecs.nrAccus)
lines.append("")
lines.append("[SYMBOLS]")
for i, symTabFile in enumerate(self.symTabFiles):
path = self.__path2generic(symTabFile, projectDir)
lines.append("sym_tab_file_%d=%s" % (i, symTabFile))
fileBackedSources = (src for src in self.symTabSources if src.isFileBacked())
embeddedSources = (src for src in self.symTabSources if not src.isFileBacked())
for i, symSrc in enumerate(fileBackedSources):
path = self.__path2generic(symSrc.filepath, projectDir)
lines.append("sym_tab_file_%d=%s" % (i, path))
for i, symSrc in enumerate(embeddedSources):
lines.append("sym_tab_%d=%s" % (i, symSrc.toBase64()))
return "\r\n".join(lines)
def toFile(self, projectFile=None):
......
......@@ -165,11 +165,10 @@ class SymTabParser(object):
implementations = []
@classmethod
def parseFile(cls, filename,
autodetectFormat=True,
mnemonics=S7CPUSpecs.MNEMONICS_AUTO):
dataBytes = awlFileRead(filename, encoding="binary")
return cls.parseData(dataBytes, autodetectFormat, mnemonics)
def parseSource(cls, source,
autodetectFormat=True,
mnemonics=S7CPUSpecs.MNEMONICS_AUTO):
return cls.parseData(source.sourceBytes, autodetectFormat, mnemonics)
@classmethod
def parseData(cls, dataBytes,
......
......@@ -2,7 +2,7 @@
#
# AWL simulator - PLC core server client
#
# Copyright 2013 Michael Buesch <[email protected]>
# Copyright 2013-2014 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
......@@ -299,17 +299,17 @@ class AwlSimClient(object):
raise AwlSimError("AwlSimClient: Failed to set run state")
return True
def loadCode(self, code):
def loadCode(self, codeSource):
if not self.transceiver:
return False
msg = AwlSimMessage_LOAD_CODE(code)
msg = AwlSimMessage_LOAD_CODE(codeSource)
status = self.__sendAndWaitFor_REPLY(msg, 10.0)
if status != AwlSimMessage_REPLY.STAT_OK:
raise AwlSimError("AwlSimClient: Failed to load code")
return True
def loadSymbolTable(self, symTabBytes):
msg = AwlSimMessage_LOAD_SYMTAB(symTabBytes)
def loadSymbolTable(self, symTabSource):
msg = AwlSimMessage_LOAD_SYMTAB(symTabSource)
status = self.__sendAndWaitFor_REPLY(msg)
if status != AwlSimMessage_REPLY.STAT_OK:
raise AwlSimError("AwlSimClient: Failed to load symbol table")
......
......@@ -2,7 +2,7 @@
#
# AWL simulator - PLC core server messages
#
# Copyright 2013 Michael Buesch <[email protected]>
# Copyright 2013-2014 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
......@@ -26,6 +26,7 @@ from awlsim.coreserver.memarea import *
from awlsim.core.util import *
from awlsim.core.datatypehelpers import *
from awlsim.core.cpuspecs import *
from awlsim.core.project import *
import struct
import socket
......@@ -45,7 +46,7 @@ class AwlSimMessage(object):
# Payload (optional)
hdrStruct = struct.Struct(str(">HHHHI"))
HDR_MAGIC = 0x5710
HDR_MAGIC = 0x5711
HDR_LENGTH = hdrStruct.size
EnumGen.start
......@@ -69,27 +70,46 @@ class AwlSimMessage(object):
MSG_ID_INSNSTATE = EnumGen.item
EnumGen.end
_strLenStruct = struct.Struct(str(">H"))
_bytesLenStruct = struct.Struct(str(">I"))
@classmethod
def packString(cls, string):
try:
data = string.encode("utf-8", "strict")
return cls._strLenStruct.pack(len(data)) + data
except (UnicodeError, struct.error) as e:
if not string:
string = ""
return cls.packBytes(string.encode("utf-8", "strict"))
except UnicodeError as e:
raise ValueError
@classmethod
def packBytes(cls, _bytes):
try:
if not _bytes:
_bytes = b""
if len(_bytes) > 0xFFFFFFFF:
raise ValueError
return cls._bytesLenStruct.pack(len(_bytes)) + _bytes
except struct.error as e:
raise ValueError
@classmethod
def unpackString(cls, data, offset = 0):
try:
(length, ) = cls._strLenStruct.unpack_from(data, offset)
strBytes = data[offset + cls._strLenStruct.size :
offset + cls._strLenStruct.size + length]
if len(strBytes) != length:
_bytes, count = cls.unpackBytes(data, offset)
return (_bytes.decode("utf-8", "strict"), count)
except UnicodeError as e:
raise ValueError
@classmethod
def unpackBytes(cls, data, offset = 0):
try:
(length, ) = cls._bytesLenStruct.unpack_from(data, offset)
_bytes = data[offset + cls._bytesLenStruct.size :
offset + cls._bytesLenStruct.size + length]
if len(_bytes) != length:
raise ValueError
return (strBytes.decode("utf-8", "strict"),
cls._strLenStruct.size + length)
except (UnicodeError, struct.error) as e:
return (_bytes, cls._bytesLenStruct.size + length)
except struct.error as e:
raise ValueError
def __init__(self, msgId, seq=0):
......@@ -200,37 +220,44 @@ class AwlSimMessage_EXCEPTION(AwlSimMessage):
raise TransferError("EXCEPTION: Unicode error")
return cls(text)
class AwlSimMessage_LOAD_SYMTAB(AwlSimMessage):
def __init__(self, symTabBytes):
AwlSimMessage.__init__(self, AwlSimMessage.MSG_ID_LOAD_SYMTAB)
self.symTabBytes = symTabBytes
class _AwlSimMessage_source(AwlSimMessage):
sourceClass = None
def toBytes(self):
return AwlSimMessage.toBytes(self, len(self.symTabBytes)) + self.symTabBytes
@classmethod
def fromBytes(cls, payload):
return cls(payload)
class AwlSimMessage_LOAD_CODE(AwlSimMessage):
def __init__(self, code):
AwlSimMessage.__init__(self, AwlSimMessage.MSG_ID_LOAD_CODE)
self.code = code
def __init__(self, msgId, source):
AwlSimMessage.__init__(self, msgId)
self.source = source
def toBytes(self):
try:
code = self.code.encode()
return AwlSimMessage.toBytes(self, len(code)) + code
except UnicodeError:
raise TransferError("LOAD_CODE: Unicode error")
pl = self.packString(self.source.identifier) +\
self.packString(self.source.filepath) +\
self.packBytes(self.source.sourceBytes)
return AwlSimMessage.toBytes(self, len(pl)) + pl
except ValueError:
raise TransferError("SOURCE: Data format error")
@classmethod
def fromBytes(cls, payload):
try:
code = payload.decode()
except UnicodeError:
raise TransferError("LOAD_CODE: Unicode error")
return cls(code)
identifier, count = cls.unpackString(payload)
filepath, cnt = cls.unpackString(payload, count)
count += cnt
sourceBytes, cnt = cls.unpackBytes(payload, count)
except ValueError:
raise TransferError("SOURCE: Data format error")
return cls(cls.sourceClass(identifier, filepath, sourceBytes))
class AwlSimMessage_LOAD_SYMTAB(_AwlSimMessage_source):
sourceClass = SymTabSource
def __init__(self, source):
_AwlSimMessage_source.__init__(self, AwlSimMessage.MSG_ID_LOAD_SYMTAB, source)
class AwlSimMessage_LOAD_CODE(_AwlSimMessage_source):
sourceClass = AwlSource
def __init__(self, source):
_AwlSimMessage_source.__init__(self, AwlSimMessage.MSG_ID_LOAD_CODE, source)
class AwlSimMessage_LOAD_HW(AwlSimMessage):
def __init__(self, name, paramDict):
......
......@@ -2,7 +2,7 @@
#
# AWL simulator - PLC core server
#
# Copyright 2013 Michael Buesch <[email protected]>
# Copyright 2013-2014 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
......@@ -338,16 +338,16 @@ class AwlSimServer(object):
def __rx_LOAD_CODE(self, client, msg):
status = AwlSimMessage_REPLY.STAT_OK
parser = AwlParser()
parser.parseData(msg.code)
parser.parseSource(msg.source)
self.__setRunState(self.STATE_INIT)
self.sim.load(parser.getParseTree())
client.transceiver.send(AwlSimMessage_REPLY.make(msg, status))
def __rx_LOAD_SYMTAB(self, client, msg):
status = AwlSimMessage_REPLY.STAT_OK
symbolTable = SymTabParser.parseData(msg.symTabBytes,
autodetectFormat = True,
mnemonics = self.sim.cpu.getSpecs().getMnemonics())
symbolTable = SymTabParser.parseSource(msg.source,
autodetectFormat = True,
mnemonics = self.sim.cpu.getSpecs().getMnemonics())
self.__setRunState(self.STATE_INIT)
self.sim.loadSymbolTable(symbolTable)
client.transceiver.send(AwlSimMessage_REPLY.make(msg, status))
......
......@@ -172,11 +172,17 @@ class CpuWidget(QWidget):
self.runButton.setEnabled(False) # Redraws the radio button
self.runButton.setEnabled(True)
ob1_awl = self.mainWidget.getCodeEditWidget().getCode()
if not ob1_awl.strip():
awlCode = self.mainWidget.getCodeEditWidget().getCode()
if not awlCode.strip():
MessageBox.error(self, "No AWL/STL code available. Cannot run.")
self.stop()
return
try:
awlCode = awlCode.encode("latin_1")
except UnicodeError:
MessageBox.error(self, "AWL/STL code contains invalid characters.")
self.stop()
return
try:
if self.mainWidget.coreConfigDialog.shouldSpawnServer():
......@@ -217,7 +223,7 @@ class CpuWidget(QWidget):
self.__setState(self.STATE_LOAD)
client.loadHardwareModule("dummy")
client.loadCode(ob1_awl)
client.loadCode(AwlSource("gui", None, awlCode))
client.setRunState(True)
except AwlParserError as e:
MessageBox.handleAwlParserError(self, e)
......
......@@ -142,8 +142,9 @@ def readInputFile(inputFile):
project = Project.fromFile(inputFile)
else:
# make a fake project
awlSrc = AwlSource.fromFile(inputFile, inputFile)
project = Project(projectFile = None,
awlFiles = [ inputFile, ])
awlSources = [ awlSrc, ])
return project
def run(inputFile):
......@@ -158,18 +159,18 @@ def run(inputFile):
writeStdout("Parsing code...\n")
parseTrees = []
for awlFile in project.awlFiles:
for awlSrc in project.awlSources:
p = AwlParser()
p.parseFile(awlFile)
p.parseSource(awlSrc)
parseTrees.append(p.getParseTree())
symTables = []
for symTabFile in project.symTabFiles:
for symTabSrc in project.symTabSources:
mnemonics = project.cpuSpecs.getConfiguredMnemonics()
if opt_mnemonics is not None:
mnemonics = opt_mnemonics
tab = SymTabParser.parseFile(symTabFile,
autodetectFormat = True,
mnemonics = mnemonics)
tab = SymTabParser.parseSource(symTabSrc,
autodetectFormat = True,
mnemonics = mnemonics)
symTables.append(tab)
writeStdout("Initializing simulator...\n")
......@@ -280,11 +281,10 @@ def runWithServerBackend(inputFile):
# Fire up the core
writeStdout("Initializing CPU...\n")
for symTabFile in project.symTabFiles:
client.loadSymbolTable(awlFileRead(symTabFile,
encoding="binary"))
for awlFile in project.awlFiles:
client.loadCode(awlFileRead(awlFile))
for symTabSrc in project.symTabSources:
client.loadSymbolTable(symTabSrc)
for awlSrc in project.awlSources:
client.loadCode(awlSrc)
client.setRunState(True)
# Run the client-side event loop
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment