libcm is a C development framework with an emphasis on audio signal processing applications.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

cmDspPgmJsonToDot.c 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. //| Copyright: (C) 2009-2020 Kevin Larke <contact AT larke DOT org>
  2. //| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
  3. #include "cmPrefix.h"
  4. #include "cmGlobal.h"
  5. #include "cmFloatTypes.h"
  6. #include "cmComplexTypes.h"
  7. #include "cmRpt.h"
  8. #include "cmErr.h"
  9. #include "cmCtx.h"
  10. #include "cmMem.h"
  11. #include "cmMallocDebug.h"
  12. #include "cmLinkedHeap.h"
  13. #include "cmSymTbl.h"
  14. #include "cmJson.h"
  15. #include "cmText.h"
  16. #include "cmDspPgmJsonToDot.h"
  17. #include "cmFile.h"
  18. #include "cmFileSys.h"
  19. struct cmDotProc_str;
  20. typedef struct cmDotPort_str
  21. {
  22. struct cmDotProc_str* proc;
  23. cmChar_t* labelStr;
  24. unsigned portNo;
  25. unsigned connCnt; // count of connections to this port
  26. struct cmDotPort_str* link;
  27. } cmDotPort_t;
  28. typedef struct cmDotProc_str
  29. {
  30. cmChar_t* classStr;
  31. cmChar_t* instStr;
  32. cmChar_t* outStr;
  33. unsigned portCnt;
  34. bool skipFl;
  35. cmDotPort_t* ports;
  36. struct cmDotProc_str* link;
  37. } cmDotProc_t;
  38. typedef struct cmDotConn_str
  39. {
  40. cmDotPort_t* srcPort; // output
  41. cmDotPort_t* dstPort; // input
  42. bool skipFl;
  43. struct cmDotConn_str* link;
  44. } cmDotConn_t;
  45. typedef struct cmDot_str
  46. {
  47. cmErr_t err;
  48. cmLHeapH_t lH;
  49. cmDotProc_t* procs;
  50. cmDotConn_t* conns;
  51. } cmDot_t;
  52. typedef struct
  53. {
  54. const cmChar_t* s0;
  55. const cmChar_t* s1;
  56. } cmDotSubst_t;
  57. cmDotSubst_t _cmDotSubstArray[] =
  58. {
  59. { "Router", "Rtr" },
  60. { "Scalar", "Sc" },
  61. { "ScaleRange", "SR" },
  62. { "MsgList", "ML" },
  63. { "Button", "Btn" },
  64. { "PortToSym", "PtS" },
  65. { "1ofN", "lOfN"},
  66. { NULL, NULL }
  67. };
  68. const cmChar_t* _cmDotSkipClassArray[] =
  69. {
  70. "Scalar",
  71. NULL
  72. };
  73. void _cmDotReplaceDash( cmChar_t* str )
  74. {
  75. cmChar_t* s = str;
  76. for(; *s; ++s )
  77. if( *s == '-' )
  78. *s = '_';
  79. }
  80. cmChar_t* _cmDotSubstitute( cmDot_t* p, const cmChar_t* label )
  81. {
  82. unsigned i;
  83. cmChar_t* s = cmLhAllocStr(p->lH,label);
  84. for(i=0; _cmDotSubstArray[i].s0 != NULL; ++i)
  85. {
  86. unsigned n0 = cmTextLength(_cmDotSubstArray[i].s0);
  87. if( cmTextCmpN( _cmDotSubstArray[i].s0, s, n0 ) == 0 )
  88. {
  89. unsigned n1 = cmTextLength(_cmDotSubstArray[i].s1);
  90. assert(n0>=n1);
  91. cmTextShrinkS(s,s+n1,n0-n1);
  92. strncpy(s,_cmDotSubstArray[i].s1,n1);
  93. }
  94. }
  95. return s;
  96. }
  97. bool _cmDotIsSkipClass( const cmChar_t* classStr )
  98. {
  99. unsigned i;
  100. for(i=0; _cmDotSkipClassArray[i]!=NULL; ++i)
  101. if( cmTextCmp(_cmDotSkipClassArray[i],classStr) == 0 )
  102. return true;
  103. return false;
  104. }
  105. cmDotPort_t* _cmDotFindPort( cmDotProc_t* proc, const cmChar_t* labelStr )
  106. {
  107. cmDotPort_t* port = proc->ports;
  108. for(; port!=NULL; port=port->link)
  109. if( cmTextCmp(port->labelStr,labelStr) == 0 )
  110. return port;
  111. return NULL;
  112. }
  113. cmDotProc_t* _cmDotFindProc( cmDot_t* p, const cmChar_t* instStr )
  114. {
  115. cmDotProc_t* dp = p->procs;
  116. for(; dp!=NULL; dp=dp->link)
  117. if( cmTextCmp(dp->instStr,instStr) == 0 )
  118. return dp;
  119. return NULL;
  120. }
  121. cmDotPort_t* _cmDotNewPort( cmDot_t* p, cmDotProc_t* proc, const cmChar_t* labelStr )
  122. {
  123. cmDotPort_t* port = NULL;
  124. if( labelStr==NULL || cmTextLength(labelStr)==0 )
  125. {
  126. cmErrMsg(&p->err,kInvalidArgDotRC,"A blank port label was encountered.");
  127. return NULL;
  128. }
  129. if((port = _cmDotFindPort(proc,labelStr)) != NULL )
  130. return port;
  131. port = cmLhAllocZ(p->lH,cmDotPort_t,1);
  132. port->proc = proc;
  133. port->labelStr = cmLhAllocStr(p->lH,labelStr);
  134. port->portNo = proc->portCnt;
  135. proc->portCnt += 1;
  136. cmDotPort_t* p0 = NULL;
  137. cmDotPort_t* p1 = proc->ports;
  138. for(; p1!=NULL; p1=p1->link)
  139. p0 = p1;
  140. if( p0 == NULL )
  141. proc->ports = port;
  142. else
  143. p0->link = port;
  144. return port;
  145. }
  146. cmDotRC_t _cmDotNewConnection( cmDot_t* p, const cmChar_t* srcProcStr, const cmChar_t* srcPortStr, const cmChar_t* dstProcStr, const cmChar_t* dstPortStr )
  147. {
  148. cmDotRC_t rc = kOkDotRC;
  149. cmDotProc_t* srcProc;
  150. cmDotProc_t* dstProc;
  151. cmDotPort_t* srcPort;
  152. cmDotPort_t* dstPort;
  153. cmDotConn_t* conn;
  154. cmDotConn_t* c0 = NULL;
  155. cmDotConn_t* c1 = p->conns;
  156. // find the source (output) proc
  157. if((srcProc = _cmDotFindProc(p,srcProcStr)) == NULL )
  158. {
  159. rc = cmErrMsg(&p->err,kInvalidArgDotRC,"The connection source proc instance '%s' could not be found.",cmStringNullGuard(srcProcStr));
  160. goto errLabel;
  161. }
  162. // find the dest (input) proc
  163. if((dstProc = _cmDotFindProc(p,dstProcStr)) == NULL )
  164. {
  165. rc = cmErrMsg(&p->err,kInvalidArgDotRC,"The connection destination proc instance '%s' could not be found.",cmStringNullGuard(dstProcStr));
  166. goto errLabel;
  167. }
  168. // find the source port
  169. if((srcPort = _cmDotNewPort(p,srcProc,srcPortStr)) == NULL )
  170. {
  171. rc = cmErrMsg(&p->err,kInvalidArgDotRC,"The source port %s:%s could not be found or allocated.",cmStringNullGuard(srcProc->instStr),cmStringNullGuard(srcPortStr));
  172. goto errLabel;
  173. }
  174. // find the dest port
  175. if((dstPort = _cmDotNewPort(p,dstProc,dstPortStr)) == NULL )
  176. {
  177. rc = cmErrMsg(&p->err,kInvalidArgDotRC,"The destination port %s:%s could not be found or allocated.",cmStringNullGuard(dstProc->instStr),cmStringNullGuard(dstPortStr));
  178. goto errLabel;
  179. }
  180. conn = cmLhAllocZ(p->lH,cmDotConn_t,1);
  181. conn->srcPort = srcPort;
  182. conn->dstPort = dstPort;
  183. conn->skipFl = _cmDotIsSkipClass(srcProc->classStr) || _cmDotIsSkipClass(dstProc->classStr);
  184. // track the number of connections to each port
  185. if( !conn->skipFl )
  186. {
  187. conn->dstPort->connCnt += 1;
  188. conn->srcPort->connCnt += 1;
  189. }
  190. // set c0 to point to the last connection record
  191. for(; c1!=NULL; c1=c1->link)
  192. c0 = c1;
  193. // make conn the last connection record
  194. if( c0 == NULL )
  195. p->conns = conn;
  196. else
  197. c0->link = conn;
  198. errLabel:
  199. return rc;
  200. }
  201. cmDotRC_t _cmDotNewProc( cmDot_t* p, const cmChar_t* classStr, const cmChar_t* instStr )
  202. {
  203. cmDotRC_t rc = kOkDotRC;
  204. if( instStr==NULL || cmTextLength(instStr)==0 )
  205. return cmErrMsg(&p->err,kInvalidArgDotRC,"A blank or NULL instance label was encountered.");
  206. if( _cmDotFindProc( p, instStr ) )
  207. return cmErrMsg(&p->err,kInvalidArgDotRC,"A duplicate processor instance was encountered ('%s').",instStr);
  208. cmDotProc_t* ndp = cmLhAllocZ(p->lH,cmDotProc_t,1);
  209. ndp->classStr = cmLhAllocStr(p->lH,classStr);
  210. ndp->instStr = cmLhAllocStr(p->lH,instStr);
  211. ndp->outStr = _cmDotSubstitute(p,instStr);
  212. ndp->skipFl = _cmDotIsSkipClass(classStr);
  213. cmDotProc_t* d0p = NULL;
  214. cmDotProc_t* d1p = p->procs;
  215. for(; d1p!=NULL; d1p=d1p->link )
  216. d0p = d1p;
  217. if( d0p == NULL )
  218. p->procs = ndp;
  219. else
  220. d0p->link = ndp;
  221. return rc;
  222. }
  223. unsigned _cmDotProcConnCount( cmDotProc_t* proc )
  224. {
  225. unsigned connN = 0;
  226. cmDotPort_t* port = proc->ports;
  227. for(; port!=NULL; port=port->link)
  228. connN += port->connCnt;
  229. return connN;
  230. }
  231. cmDotRC_t _cmDotWriteOutput( cmDot_t* p, const cmChar_t* outFn )
  232. {
  233. cmDotRC_t rc = kOkDotRC;
  234. cmFileH_t fH = cmFileNullHandle;
  235. cmFileSysPathPart_t* pathParts = cmFsPathParts(outFn);
  236. const cmChar_t* fn = NULL;
  237. if( pathParts == NULL )
  238. {
  239. rc = cmErrMsg(&p->err,kFileFailDotRC,"The output file name '%s' could be parsed.",cmStringNullGuard(outFn));
  240. goto errLabel;
  241. }
  242. if((fn = cmFsMakeFn( pathParts->dirStr, pathParts->fnStr, "dot", NULL )) == NULL )
  243. {
  244. rc = cmErrMsg(&p->err,kFileFailDotRC,"The output file name could not be formed.");
  245. goto errLabel;
  246. }
  247. if( cmFileOpen(&fH,fn,kWriteFileFl,p->err.rpt) != kOkFileRC )
  248. {
  249. rc = cmErrMsg(&p->err,kFileFailDotRC,"The output file '%s' could not be created.",cmStringNullGuard(outFn));
  250. goto errLabel;
  251. }
  252. cmFilePrintf(fH,"digraph dsppgm\n{\n node [shape=record]\n");
  253. cmDotProc_t* proc = p->procs;
  254. for(; proc!=NULL; proc=proc->link )
  255. if( proc->skipFl==false && _cmDotProcConnCount(proc)>0 )
  256. {
  257. cmFilePrintf(fH,"\"%s\" [label=\"<n> %s",proc->outStr,proc->outStr);
  258. cmDotPort_t* port = proc->ports;
  259. for(; port!=NULL; port=port->link)
  260. if( port->connCnt > 0 )
  261. cmFilePrintf(fH,"|<p%i> %s",port->portNo,port->labelStr);
  262. cmFilePrintf(fH,"\"];\n");
  263. }
  264. cmDotConn_t* c = p->conns;
  265. for(; c!=NULL; c=c->link)
  266. if( !c->skipFl )
  267. {
  268. cmFilePrintf(fH,"\"%s\":p%i -> \"%s\":p%i;\n",
  269. c->srcPort->proc->outStr,c->srcPort->portNo,
  270. c->dstPort->proc->outStr,c->dstPort->portNo );
  271. }
  272. cmFilePrintf(fH,"}\n");
  273. errLabel:
  274. cmFileClose(&fH);
  275. cmFsFreeFn(fn);
  276. cmFsFreePathParts(pathParts);
  277. return rc;
  278. }
  279. cmDotRC_t cmDspPgmJsonToDot( cmCtx_t* ctx, const cmChar_t* inFn, const cmChar_t* outFn )
  280. {
  281. cmDotRC_t rc = kOkDotRC;
  282. cmJsonH_t jsH = cmJsonNullHandle;
  283. cmJsonNode_t* arr = NULL;
  284. cmJsonNode_t* rp = NULL;
  285. const char* errLbl = NULL;
  286. cmDot_t dot;
  287. unsigned i;
  288. cmDot_t* p = &dot;
  289. memset(p,0,sizeof(dot));
  290. cmErrSetup(&p->err,&ctx->rpt,"cmDspPgmJsonToDot");
  291. // open the pgm description json file
  292. if( cmJsonInitializeFromFile( &jsH, inFn, ctx ) != kOkJsRC )
  293. return cmErrMsg(&p->err,kJsonFailDotRC,"The program description file '%s' could not be opened.",cmStringNullGuard(inFn));
  294. // create an lheap to hold internal data objects
  295. if(cmLHeapIsValid( p->lH = cmLHeapCreate( 8192, ctx))==false )
  296. {
  297. rc = cmErrMsg(&p->err,kLHeapFailDotRC,"The internal LHeap could not be created.");
  298. goto errLabel;
  299. }
  300. // locate the proc instance desc. array in the JSON tree
  301. if((arr = cmJsonFindValue(jsH, "inst_array", NULL, kArrayTId )) == NULL )
  302. {
  303. rc = cmErrMsg(&p->err,kJsonSyntaxErrDotRC,"The 'inst_array' tag was not found.");
  304. goto errLabel;
  305. }
  306. // get a count of proc instances
  307. unsigned n = cmJsonChildCount(arr);
  308. // parse each proc instance
  309. for(i=0; i<n; ++i)
  310. {
  311. if((rp = cmJsonArrayElement(arr,i)) == NULL )
  312. {
  313. rc = cmErrMsg(&p->err,kJsonSyntaxErrDotRC,"The 'inst_array' element %i was not found.",i);
  314. goto errLabel;
  315. }
  316. cmChar_t* classStr = NULL;
  317. cmChar_t* instStr = NULL;
  318. if( cmJsonMemberValues(rp, &errLbl,
  319. "class", kStringTId, &classStr,
  320. "label", kStringTId, &instStr,
  321. NULL ) != kOkJsRC )
  322. {
  323. rc = cmErrMsg(&p->err,kJsonSyntaxErrDotRC,"The 'inst_array' element %i parse failed.",i);
  324. goto errLabel;
  325. }
  326. // create a proc instance data record
  327. _cmDotNewProc( p, classStr, instStr );
  328. }
  329. // locate the connection desc array in the JSON tree
  330. if((arr = cmJsonFindValue(jsH, "conn_array", NULL, kArrayTId)) == NULL )
  331. {
  332. rc = cmErrMsg(&p->err,kJsonSyntaxErrDotRC,"The 'conn_array' tag was not found.");
  333. goto errLabel;
  334. }
  335. // get a count of the connections
  336. n = cmJsonChildCount(arr);
  337. // for each connection
  338. for(i=0; i<n; ++i)
  339. {
  340. if((rp = cmJsonArrayElement(arr,i)) == NULL )
  341. {
  342. rc = cmErrMsg(&p->err,kJsonSyntaxErrDotRC,"The 'conn_array' element %i was not found.",i);
  343. goto errLabel;
  344. }
  345. cmChar_t* srcStr = NULL;
  346. cmChar_t* srcPortStr = NULL;
  347. cmChar_t* dstStr = NULL;
  348. cmChar_t* dstPortStr = NULL;
  349. if( cmJsonMemberValues(rp, &errLbl,
  350. "sid", kStringTId, &srcStr,
  351. "svar", kStringTId, &srcPortStr,
  352. "did", kStringTId, &dstStr,
  353. "dvar", kStringTId, &dstPortStr,
  354. NULL) != kOkJsRC )
  355. {
  356. rc = cmErrMsg(&p->err,kJsonSyntaxErrDotRC,"The 'conn_array' element %i parse failed.",i);
  357. goto errLabel;
  358. }
  359. // create a connection data record
  360. _cmDotNewConnection( p, srcStr, srcPortStr, dstStr, dstPortStr );
  361. }
  362. rc = _cmDotWriteOutput(p, outFn );
  363. errLabel:
  364. cmJsonFinalize(&jsH);
  365. cmLHeapDestroy(&p->lH);
  366. return rc;
  367. }