first commit
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Insert implementation into an official CODESYS-generated external stub.
|
||||
Fails closed on unexpected syntax; never invents ABI, signature or checksum.
|
||||
"""
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
def prepare(source, interface, sdk, destination):
|
||||
if source.stem != 'CmpAtomModbus' or interface.name != 'CmpAtomModbusItf.m4':
|
||||
raise ValueError('Generate component CmpAtomModbus, with its matching Itf.m4')
|
||||
c=source.read_text(); m4=interface.read_text()
|
||||
match=re.search(r'\batmbcall_cext\s*\(\s*(\w+)\s*\*\s*(\w+)\s*\)\s*\{',c,re.I)
|
||||
if not match: raise ValueError('Cannot find one-struct-pointer atMbCall_cext stub')
|
||||
typ,param=match.groups()
|
||||
structs=re.findall(r'typedef\s+struct(?:\s+\w+)?\s*\{([^{}]*)\}\s*(\w+)\s*;',m4,re.S)
|
||||
fields=next((body for body,name in structs if name==typ),None)
|
||||
if fields is None: raise ValueError('Cannot find the generated parameter struct in M4')
|
||||
names=re.findall(r'\b(\w+)\s*(?:\[[^\]]*\])?\s*;',fields)
|
||||
lookup={n.lower():n for n in names}
|
||||
args=['command','handle','ipaddress','port','unitid','functioncode','startaddress',
|
||||
'quantity','pdata','datacapacity','pstatus','statuscapacity','nowms',
|
||||
'connecttimeoutms','responsetimeoutms','retrydelayms']
|
||||
for n in args+['atmbcall_cext']:
|
||||
if n not in lookup: raise ValueError('Missing generated field '+n)
|
||||
# Match the stub body, preserving all surrounding generated source.
|
||||
start=match.end()-1; depth=1; end=start+1
|
||||
# Stub bodies must be empty or comments, not application code.
|
||||
while end<len(c) and depth:
|
||||
if c[end]=='{': depth+=1
|
||||
elif c[end]=='}': depth-=1
|
||||
end+=1
|
||||
if depth: raise ValueError('Unbalanced generated stub')
|
||||
old=c[start+1:end-1]
|
||||
old=re.sub(r'/\*.*?\*/|//[^\n]*','',old,flags=re.S).strip()
|
||||
if old: raise ValueError('Stub contains code; inspect manually instead of replacing it')
|
||||
body='{\n '+param+'->'+lookup['atmbcall_cext']+' = ab_call(\n '
|
||||
body+=',\n '.join(param+'->'+lookup[n] for n in args)+');\n}'
|
||||
c=c[:start]+body+c[end:]
|
||||
# Use one component C translation unit because the supplied SDK derives
|
||||
# component identity from the top-level C filename list.
|
||||
c='#include "../src/atom_bridge.h"\n'+c
|
||||
c+='\n#include "../src/atom_mb.c"\n#include "../src/atom_bridge.c"\n'
|
||||
frame=(sdk/'src/CmpFrame.c').read_text()
|
||||
hook=re.search(r'(static\s+RTS_RESULT\s+CDECL\s+HookFunction\([^)]*\)\s*\{)',frame)
|
||||
if not hook: raise ValueError('Unexpected SDK HookFunction; inspect manually')
|
||||
frame=frame[:hook.end()]+'\n if (ulHook == CH_EXIT) ab_shutdown();\n'+frame[hook.end():]
|
||||
frame='#include "../src/atom_bridge.h"\n'+frame
|
||||
destination.mkdir(parents=True,exist_ok=True)
|
||||
if any(destination.iterdir()): raise ValueError('Destination must be empty; preserve existing build work')
|
||||
(destination/'CmpAtomModbus.c').write_text(c)
|
||||
(destination/interface.name).write_text(m4)
|
||||
(destination/'CmpFrame.c').write_text(frame)
|
||||
(destination/'makefile').write_text('''# Generated from supplied ExtensionSDK; native build on ARM64 Pi.
|
||||
SDKDIR ?= ../../ExtensionSDK
|
||||
MAJORVER=0
|
||||
MINORVER=2
|
||||
CMPVERSION=0x00020000
|
||||
# Local development ID; check no other component uses this ID.
|
||||
CMPID=0x2001
|
||||
DOS2UNIX=dos2unix
|
||||
M4=m4
|
||||
CC ?= gcc
|
||||
# The SDK otherwise includes CmpFrame in its component identity wildcard.
|
||||
override CSRC := CmpAtomModbus.c
|
||||
include $(SDKDIR)/makefile
|
||||
''')
|
||||
|
||||
if __name__=='__main__':
|
||||
p=argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument('source',type=Path); p.add_argument('interface',type=Path)
|
||||
p.add_argument('--sdk',required=True,type=Path)
|
||||
p.add_argument('--out',type=Path,default=Path('component'))
|
||||
a=p.parse_args()
|
||||
prepare(a.source,a.interface,a.sdk,a.out)
|
||||
print('Prepared component; compile and validate the generated interface on target.')
|
||||
Reference in New Issue
Block a user