Commit bac8e5a7 authored by Thanassis Tsiodras's avatar Thanassis Tsiodras

Merge remote-tracking branch 'ESA-DMT/master'

parents 83c4daf4 eca3de9b
......@@ -46,9 +46,11 @@ def OnStartup(unused_modelingLanguage: str, asnFiles: List[str], outputDir: str,
asn1SccPath = spawn.find_executable('asn1.exe')
if not asn1SccPath:
panic("ASN1SCC seems to be missing from your system (asn1.exe not found in PATH).\n") # pragma: no cover
# allow externally-defined flags when calling the asn1 compiler (e.g. to set word size based on target)
extraFlags = os.getenv ("ASN1SCC_FLAGS") or ""
os.system(
("mono " if sys.platform.startswith('linux') else "") +
"\"{}\" -typePrefix asn1Scc -Ada -uPER -o \"".format(asn1SccPath) +
"\"{}\" -typePrefix asn1Scc -Ada {} -uPER -o \"".format(asn1SccPath, extraFlags) +
outputDir + "\" \"" + "\" \"".join(asnFiles) + "\"")
os.system("rm -f \"" + outputDir + "\"/*.adb")
......
......@@ -442,30 +442,30 @@ def DumpTypeDumper(
codeIndent +
'lines.append("%s"+str(%s.Get()!=0).upper())' % (outputIndent, variableName))
if variableName.startswith("path[i]"):
lines.append(codeIndent + 'self.Reset(state)')
lines.append(codeIndent + 'path.Reset(state)')
elif isinstance(node, AsnInt):
lines.append(
codeIndent + 'lines.append("%s"+str(%s.Get()))' % (outputIndent, variableName))
if variableName.startswith("path[i]"):
lines.append(codeIndent + 'self.Reset(state)')
lines.append(codeIndent + 'path.Reset(state)')
elif isinstance(node, AsnReal):
lines.append(
codeIndent + 'lines.append("%s"+str(%s.Get()))' % (outputIndent, variableName))
if variableName.startswith("path[i]"):
lines.append(codeIndent + 'self.Reset(state)')
lines.append(codeIndent + 'path.Reset(state)')
elif isinstance(node, AsnString):
lines.append(
codeIndent +
'lines.append("%s\\\""+str(%s.GetPyString()) + "\\\"")' % (outputIndent, variableName))
if variableName.startswith("path[i]"):
lines.append(codeIndent + 'self.Reset(state)')
lines.append(codeIndent + 'path.Reset(state)')
elif isinstance(node, AsnEnumerated):
mapping = str({val: name for name, val in node._members})
lines.append(
codeIndent +
'lines.append("%s"+%s[str(%s.Get())])' % (outputIndent, mapping, variableName))
if variableName.startswith("path[i]"):
lines.append(codeIndent + 'self.Reset(state)')
lines.append(codeIndent + 'path.Reset(state)')
elif isinstance(node, (AsnChoice, AsnSet, AsnSequence)):
if not isinstance(node, AsnChoice):
lines.append(codeIndent + 'lines.append("{")')
......@@ -475,10 +475,14 @@ def DumpTypeDumper(
extraIndent = " "
for idx, child in enumerate(node._members):
if isinstance(node, AsnChoice):
if variableName.startswith("path[i]"):
lines.append(codeIndent + 'path.Reset(state)')
lines.append(
codeIndent + 'if %s.kind.Get() == DV.%s:' % (
variableName,
CleanNameAsPythonWants(child[2])))
if variableName.startswith("path[i]"):
lines.append(codeIndent + ' path.Reset(state)')
sep = ": "
elif idx > 0:
# Separate fields with comas:
......@@ -497,13 +501,15 @@ def DumpTypeDumper(
variableName + "." + CleanNameAsPythonWants(child[0]), childNode, names)
if not isinstance(node, AsnChoice):
lines.append(codeIndent + 'lines.append("}")')
if variableName.startswith("path[i]"):
lines.append(codeIndent + 'path.Reset(state)')
elif isinstance(node, (AsnSetOf, AsnSequenceOf)):
lines.append(codeIndent + 'lines.append("{")')
containedNode = node._containedType
if isinstance(containedNode, str):
containedNode = names[containedNode]
lines.append(codeIndent + 'def emitElem(path, i):')
lines.append(codeIndent + ' state = self.GetState()')
lines.append(codeIndent + ' state = path.GetState()')
lines.append(codeIndent + ' if i > 0:')
lines.append(codeIndent + ' lines.append(",")')
DumpTypeDumper(codeIndent + " ",
......
......@@ -100,11 +100,17 @@ class ASynchronousToolGlueGenerator:
ID = re.sub(r'[^A-Za-z0-9_]', '_', ID).upper()
self.C_HeaderFile.write("#ifndef __%s_H__\n" % ID)
self.C_HeaderFile.write("#define __%s_H__\n\n" % ID)
self.C_HeaderFile.write("#ifdef __unix__\n")
self.C_HeaderFile.write("#include <stdlib.h> /* for size_t */\n")
self.C_HeaderFile.write("#else\n")
self.C_HeaderFile.write("typedef unsigned size_t;\n")
self.C_HeaderFile.write("#endif\n\n")
self.C_HeaderFile.write("\n")
self.C_SourceFile.write("#ifdef __unix__\n")
self.C_SourceFile.write("#include <stdio.h>\n")
self.C_SourceFile.write("#include <assert.h>\n")
self.C_SourceFile.write("#endif\n\n")
self.C_SourceFile.write("#include <string.h>\n\n")
self.C_SourceFile.write("#include <assert.h>\n\n")
self.C_SourceFile.write("#include \"%s\"\n\n" % outputCheaderFilename)
self.HeadersOnStartup(asnFile, outputDir, maybeFVname)
......
......@@ -265,13 +265,13 @@ class C_GlueGenerator(ASynchronousToolGlueGenerator):
encoding.upper())
self.C_HeaderFile.write(needDefine)
self.C_HeaderFile.write(
"size_t %s(void *pBuffer, size_t iMaxBufferSize, %sasn1Scc%s *pSrc);\n" %
"int %s(void *pBuffer, size_t iMaxBufferSize, %sasn1Scc%s *pSrc);\n" %
(tmpSpName, "" if encoding.lower() == "acn" else "const ",
self.CleanNameAsToolWants(nodeTypename)))
self.C_HeaderFile.write("#endif\n\n")
self.C_SourceFile.write(needDefine)
self.C_SourceFile.write(
"size_t %s(void *pBuffer, size_t iMaxBufferSize, %sasn1Scc%s *pSrc)\n{\n (void)iMaxBufferSize;\n" %
"int %s(void *pBuffer, size_t iMaxBufferSize, %sasn1Scc%s *pSrc)\n{\n (void)iMaxBufferSize;\n" %
(tmpSpName, "" if encoding.lower() == "acn" else "const ",
self.CleanNameAsToolWants(nodeTypename)))
......@@ -306,8 +306,10 @@ class C_GlueGenerator(ASynchronousToolGlueGenerator):
self.C_SourceFile.write(" if (ossEncode(g_world, OSS_%s_PDU, &var_%s, &strm) != 0) {\n" %
(self.CleanNameAsToolWants(nodeTypename),
self.CleanNameAsToolWants(nodeTypename)))
self.C_SourceFile.write("#ifdef __unix__\n")
self.C_SourceFile.write(
'\tfprintf(stderr, "Could not encode %s (at %%s, %%d), errorMessage was %%s\\n", __FILE__, __LINE__, ossGetErrMsg(g_world));\n' % nodeTypename)
self.C_SourceFile.write("#endif\n")
self.C_SourceFile.write(" return -1;\n")
self.C_SourceFile.write(" } else {\n")
self.C_SourceFile.write(" assert(strm.length <= iMaxBufferSize);\n")
......@@ -321,8 +323,10 @@ class C_GlueGenerator(ASynchronousToolGlueGenerator):
self.C_SourceFile.write(" if (asn1Scc%s_%sEncode(pSrc, &strm, &errorCode, TRUE) == FALSE) {\n" %
(self.CleanNameAsToolWants(nodeTypename),
("ACN_" if encoding.lower() == "acn" else "")))
self.C_SourceFile.write("#ifdef __unix__\n")
self.C_SourceFile.write(
'\tfprintf(stderr, "Could not encode %s (at %%s, %%d), errorCode was %%d\\n", __FILE__, __LINE__, errorCode);\n' % nodeTypename)
self.C_SourceFile.write("#endif\n")
self.C_SourceFile.write(" return -1;\n")
self.C_SourceFile.write(" } else {\n")
self.C_SourceFile.write(" return BitStream_GetLength(&strm);\n")
......@@ -399,8 +403,10 @@ class C_GlueGenerator(ASynchronousToolGlueGenerator):
self.CleanNameAsToolWants(nodeTypename))
self.C_SourceFile.write(" return 0;\n")
self.C_SourceFile.write(" } else {\n")
self.C_SourceFile.write("#ifdef __unix__\n")
self.C_SourceFile.write(
'\tfprintf(stderr, "Could not decode %s (at %%s, %%d), error message was %%s\\n", __FILE__, __LINE__, ossGetErrMsg(g_world));\n' % nodeTypename)
self.C_SourceFile.write("#endif\n")
self.C_SourceFile.write(" return -1;\n")
self.C_SourceFile.write(" }\n")
self.C_SourceFile.write("}\n")
......@@ -408,8 +414,10 @@ class C_GlueGenerator(ASynchronousToolGlueGenerator):
elif encoding.lower() in ["uper", "acn"]:
self.C_SourceFile.write(" return 0;\n")
self.C_SourceFile.write(" } else {\n")
self.C_SourceFile.write("#ifdef __unix__\n")
self.C_SourceFile.write(
'\tfprintf(stderr, "Could not decode %s (at %%s, %%d), error code was %%d\\n", __FILE__, __LINE__, errorCode);\n' % nodeTypename)
self.C_SourceFile.write("#endif\n")
self.C_SourceFile.write(" return -1;\n")
self.C_SourceFile.write(" }\n")
self.C_SourceFile.write("}\n")
......
......@@ -216,7 +216,7 @@ def setSharedLib(dll=None):
global shared_lib
global {tcName}_via_shared_lib
shared_lib = True
{tcName}_via_shared_lib = dll.{fvName}_{tcName}
{tcName}_via_shared_lib = dll.{fvName}_PI_{tcName}
'''.format(fvName=FVname, tcName=CleanSP))
g_PyDataModel.write('\ntc["{tcName}"] = '.format(tcName=CleanSP))
......@@ -257,7 +257,12 @@ def {tmName}(tm_ptr, size):
# Callback function prototype - a void* param, and returning nothing
func = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_long)
# Apparently c_void_p does not work on 64 bits machines... use a workaround:
class ReturnPointer (ctypes.Structure):
pass
ReturnHandle = ctypes.POINTER(ReturnPointer)
func = ctypes.CFUNCTYPE(None, ReturnHandle, ctypes.c_long)
#func = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_long)
cmp_func = func({tmName})
def setSharedLib(dll=None):
......@@ -573,13 +578,20 @@ def WriteCodeForGUIControls(prefixes: List[str], # pylint: disable=invalid-sequ
pyStr += '''["{prefixKey}"]'''.format(prefixKey=item)
if isinstance(node, (AsnInt, AsnReal, AsnOctetString)):
if isinstance(node, (AsnInt, AsnReal)):
if isinstance(node, AsnInt):
if g_onceOnly:
g_PyDataModel.write(
'''{'nodeTypename': '%s', 'type': '%s', 'id': '%s', 'minR': %d, 'maxR': %d}''' % (
nodeTypename, node._name, txtPrefix,
node._range[0], node._range[1]))
elif isinstance(node, AsnReal):
if g_onceOnly:
g_PyDataModel.write(
'''{'nodeTypename': '%s', 'type': '%s', 'id': '%s', 'minR': %20.20f, 'maxR': %20.20f}''' % (
nodeTypename, node._name, txtPrefix,
node._range[0], node._range[1]))
elif isinstance(node, AsnOctetString):
if g_onceOnly:
g_PyDataModel.write(
......
......@@ -172,9 +172,11 @@ class SynchronousToolGlueGeneratorGeneric(Generic[TSource, TDestin]):
self.C_HeaderFile.write("#include <stdlib.h> /* for size_t */\n")
self.C_HeaderFile.write("\n")
self.C_SourceFile.write("#ifdef __unix__\n")
self.C_SourceFile.write("#include <stdio.h>\n")
self.C_SourceFile.write("#include <string.h>\n\n")
self.C_SourceFile.write("#include <assert.h>\n\n")
self.C_SourceFile.write("#endif\n")
self.C_SourceFile.write("#include \"%s\"\n" % outputCheaderFilename)
......@@ -294,8 +296,10 @@ class SynchronousToolGlueGeneratorGeneric(Generic[TSource, TDestin]):
self.C_SourceFile.write(
" if (ossEncode(g_world, OSS_%s_PDU, &var_%s, &strm) != 0) {\n" %
(self.CleanNameAsToolWants(nodeTypename), self.CleanNameAsToolWants(nodeTypename)))
self.C_SourceFile.write("#ifdef __unix__\n")
self.C_SourceFile.write(
' fprintf(stderr, "Could not encode %s (at %%s, %%d), errorMessage was %%s\\n", __FILE__, __LINE__, ossGetErrMsg(g_world));\n' % nodeTypename)
self.C_SourceFile.write("#endif\n")
self.C_SourceFile.write(" return -1;\n")
self.C_SourceFile.write(" } else {\n")
self.C_SourceFile.write(" assert(strm.length <= iMaxBufferSize);\n")
......@@ -310,8 +314,10 @@ class SynchronousToolGlueGeneratorGeneric(Generic[TSource, TDestin]):
(self.CleanNameAsToolWants(nodeTypename),
"ACN_" if encoding.lower() == "acn" else "",
self.CleanNameAsToolWants(nodeTypename)))
self.C_SourceFile.write("#ifdef __unix__\n")
self.C_SourceFile.write(
' fprintf(stderr, "Could not encode %s (at %%s, %%d), errorCode was %%d\\n", __FILE__, __LINE__, errorCode);\n' % nodeTypename)
self.C_SourceFile.write("#endif\n")
self.C_SourceFile.write(" return -1;\n")
self.C_SourceFile.write(" } else {\n")
self.C_SourceFile.write(" return BitStream_GetLength(&strm);\n")
......@@ -438,16 +444,20 @@ class SynchronousToolGlueGeneratorGeneric(Generic[TSource, TDestin]):
self.C_SourceFile.write(" ossFreeBuf(g_world, pVar_%s);\n" % self.CleanNameAsToolWants(nodeTypename))
self.C_SourceFile.write(" return 0;\n")
self.C_SourceFile.write(" } else {\n")
self.C_SourceFile.write("#ifdef __unix__\n")
self.C_SourceFile.write(
' fprintf(stderr, "Could not decode %s (at %%s, %%d), error message was %%s\\n", __FILE__, __LINE__, ossGetErrMsg(g_world));\n' % nodeTypename)
self.C_SourceFile.write("#endif\n")
self.C_SourceFile.write(" return -1;\n")
self.C_SourceFile.write(" }\n")
self.C_SourceFile.write("}\n\n")
elif encoding.lower() in ["uper", "acn"]:
self.C_SourceFile.write(" return 0;\n")
self.C_SourceFile.write(" } else {\n")
self.C_SourceFile.write("#ifdef __unix__\n")
self.C_SourceFile.write(
' fprintf(stderr, "Could not decode %s (at %%s, %%d), error code was %%d\\n", __FILE__, __LINE__, errorCode);\n' % nodeTypename)
self.C_SourceFile.write("#endif\n")
self.C_SourceFile.write(" return -1;\n")
self.C_SourceFile.write(" }\n")
self.C_SourceFile.write("}\n\n")
......
......@@ -204,11 +204,13 @@ the scope of individual parameters (e.g. it needs access to all ASN.1
types). This used to cover Dumpable C/Ada Types and OG headers.'''
outputDir = commonPy.configMT.outputDir
asn1SccPath = spawn.find_executable('asn1.exe')
# allow externally-defined flags when calling the asn1 compiler (e.g. to set word size based on target)
extraFlags = os.getenv ("ASN1SCC_FLAGS") or ""
if asnFile is not None:
if not asn1SccPath:
panic("ASN1SCC seems not installed on your system (asn1.exe not found in PATH).\n") # pragma: no cover
os.system('mono "{}" -typePrefix asn1Scc -Ada -equal -o "{}" "{}"'
.format(asn1SccPath, outputDir, '" "'.join([asnFile])))
os.system('mono "{}" -typePrefix asn1Scc {} -Ada -equal -o "{}" "{}"'
.format(asn1SccPath, extraFlags, outputDir, '" "'.join([asnFile])))
def getSyncBackend(modelingLanguage: str) -> Sync_B_Mapper:
......
......@@ -157,6 +157,7 @@ def calculateForNativeAndASN1SCC(absASN1SCCpath, autosrc, names, inputFiles):
os.chdir(autosrc)
path_to_compiler = spawn.find_executable(cc.decode('utf-8'))
if path_to_compiler is None:
os.chdir(pwd)
continue
for cfile in os.listdir("."):
if cfile.endswith(".c"):
......@@ -463,6 +464,8 @@ end Stream_Element_Buffer;
if asnTypename in typesUnusableAsInterfaceParameters:
o.write(' TASTE::Forbid_in_PI => true;\n')
o.write('END ' + cleanName + ';\n\n')
o.write('DATA IMPLEMENTATION ' + cleanName + '.impl\n')
o.write('END ' + cleanName + '.impl;\n\n')
if os.getenv('UPD') is None:
o.write('DATA ' + cleanName + '_Buffer_Max\n')
o.write('END ' + cleanName + '_Buffer_Max;\n\n')
......@@ -503,7 +506,7 @@ end Stream_Element_Buffer;
if node._isArtificial:
continue
cleanName = cleanNameAsAADLWants(asnTypename)
o.write(' %s : DATA %s;\n' % (cleanName, cleanName))
o.write(' %s : DATA %s.impl;\n' % (cleanName, cleanName))
o.write('END Taste_DataView.others;\n')
listOfAsn1Files = {}
......
......@@ -19,7 +19,7 @@ import pkg_resources # pragma: no cover
try:
__version__ = pkg_resources.require("dmt")[0].version # pragma: no cover
except:
__version__ = "2.1.2"
__version__ = "2.1.3"
def print_version() -> None:
......
......@@ -385,6 +385,9 @@ def CheckForInvalidKeywords(node_or_str: Union[str, AsnNode]) -> None:
def ParseAsnFileList(listOfFilenames: List[str]) -> None: # pylint: disable=invalid-sequence-index
# Add basic ASN.1 caching to avoid calling the ASN.1 compiler over and over
projectCache = os.getenv ("PROJECT_CACHE")
if projectCache is not None and not os.path.isdir(projectCache):
utility.panic(
"The configured cache folder:\n\n\t" + projectCache + "\n\n...is not there!\n")
xmlAST = xmlAST2 = None
someFilesHaveChanged = False
if projectCache is not None:
......
......@@ -1212,6 +1212,9 @@ class Parser(antlr.LLkParser):
if property._name[-15:].lower() == "source_language":
stripQuotes = property._propertyExpressionOrList.replace("\"", "")
sp.SetLanguage(stripQuotes)
elif property._name[-10:].lower() == "fpga_modes":
stripQuotes = property._propertyExpressionOrList.replace("\"", "")
sp.SetFPGAModes(stripQuotes)
elif la1 and la1 in [END,ANNEX]:
pass
else:
......
......@@ -275,6 +275,9 @@ subprogram_type
if property._name[-15:].lower() == "source_language":
stripQuotes = property._propertyExpressionOrList.replace("\"", "")
sp.SetLanguage(stripQuotes)
elif property._name[-10:].lower() == "fpga_modes":
stripQuotes = property._propertyExpressionOrList.replace("\"", "")
sp.SetFPGAModes(stripQuotes)
} )?
( annex_subclause )?
END eid:IDENT SEMI
......
......@@ -151,6 +151,7 @@ class ApLevelContainer:
self._calls = []
self._params = []
self._connections = []
self._fpgaModes = ''
self._language = None
def AddCalledAPLC(self, idAPLC):
......@@ -169,6 +170,8 @@ class ApLevelContainer:
def SetLanguage(self, language):
self._language = language
def SetFPGAModes(self, fpgaModes):
self._fpgaModes = fpgaModes
class Param:
def __init__(self, aplcID, id, signal, sourceElement):
......
......@@ -207,7 +207,9 @@ def main():
C_HeaderFile.write('#endif\n\n')
C_SourceFile = open(configMT.outputDir + os.sep + "PrintTypes.c", "w")
C_SourceFile.write('#include <stdio.h>\n\n')
C_SourceFile.write('#ifdef __unix__\n')
C_SourceFile.write('#include <stdio.h>\n')
C_SourceFile.write('#endif\n')
C_SourceFile.write('#include "PrintTypes.h"\n\n')
C_SourceFile.write('#ifdef __linux__\n')
C_SourceFile.write('#include <pthread.h>\n\n')
......@@ -240,9 +242,12 @@ def main():
C_HeaderFile.write('void Print%s(const char *paramName, const asn1Scc%s *pData);\n' % (cleanNodeTypename, cleanNodeTypename))
C_SourceFile.write('void Print%s(const char *paramName, const asn1Scc%s *pData)\n{\n' % (cleanNodeTypename, cleanNodeTypename))
C_SourceFile.write(' (void)paramName;\n')
C_SourceFile.write(' (void)pData;\n')
C_SourceFile.write('#ifdef __linux__\n')
C_SourceFile.write(' pthread_mutex_lock(&g_printing_mutex);\n')
C_SourceFile.write('#endif\n')
C_SourceFile.write('#ifdef __unix__\n')
lines = [" " + x
for x in printer.Map(
'(*pData)',
......@@ -251,7 +256,8 @@ def main():
leafTypeDict,
asnParser.g_names)]
C_SourceFile.write("\n".join(lines))
C_SourceFile.write('\n#ifdef __linux__\n')
C_SourceFile.write('\n#endif\n')
C_SourceFile.write('#ifdef __linux__\n')
C_SourceFile.write(' pthread_mutex_unlock(&g_printing_mutex);\n')
C_SourceFile.write('#endif\n')
C_SourceFile.write('}\n\n')
......
......@@ -226,7 +226,9 @@ def main():
C_HeaderFile.write('#endif\n\n')
C_SourceFile = open(configMT.outputDir + os.sep + "PrintTypesAsASN1.c", "w")
C_SourceFile.write('#include <stdio.h>\n\n')
C_SourceFile.write('#ifdef __unix__\n')
C_SourceFile.write('#include <stdio.h>\n')
C_SourceFile.write('#endif\n\n')
C_SourceFile.write('#include "PrintTypesAsASN1.h"\n\n')
C_SourceFile.write('#ifdef __linux__\n')
C_SourceFile.write('#include <pthread.h>\n\n')
......@@ -259,15 +261,19 @@ def main():
C_HeaderFile.write('void PrintASN1%s(const char *paramName, const asn1Scc%s *pData);\n' % (cleanNodeTypename, cleanNodeTypename))
C_SourceFile.write('void PrintASN1%s(const char *paramName, const asn1Scc%s *pData)\n{\n' % (cleanNodeTypename, cleanNodeTypename))
C_SourceFile.write(' (void)paramName;\n')
C_SourceFile.write(' (void)pData;\n')
C_SourceFile.write('#ifdef __linux__\n')
C_SourceFile.write(' pthread_mutex_lock(&g_printing_mutex);\n')
C_SourceFile.write('#endif\n')
C_SourceFile.write('#ifdef __unix__\n')
C_SourceFile.write(' //printf("%%s %s ::= ", paramName);\n' % nodeTypename)
C_SourceFile.write(' printf("%s ", paramName);\n')
# C_SourceFile.write('\n'.join(printer.Map('(*pData)', '', node, leafTypeDict, asnParser.g_names)))
lines = [" " + x for x in printer.Map('(*pData)', '', node, leafTypeDict, asnParser.g_names)]
C_SourceFile.write("\n".join(lines))
C_SourceFile.write('\n#ifdef __linux__\n')
C_SourceFile.write('\n#endif\n')
C_SourceFile.write('#ifdef __linux__\n')
C_SourceFile.write(' pthread_mutex_unlock(&g_printing_mutex);\n')
C_SourceFile.write('#endif\n')
C_SourceFile.write('}\n\n')
......
......@@ -11,7 +11,7 @@ from setuptools import setup, find_packages
setup(
name='dmt',
version="2.1.11",
version="2.1.22",
packages=find_packages(),
author='Thanassis Tsiodras',
author_email='Thanassis.Tsiodras@esa.int',
......
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