scpi_tlst_ex.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. #define SCPI_TLST_EX_C
  2. #include "app/scpi/scpi_tlst_ex.h"
  3. #include "app/scpi/scpi_parser.h"
  4. #include <ctype.h> // toupper, islower
  5. // Author: Sychov A., Jan 2021
  6. // Extension module to adopt the Tree-List algorithms to the SCPI module.
  7. //----------------------------------------------------------------
  8. // Refer to:
  9. // [1] IEEE 488.2 Standard, revision IEEE Std 488.2-1987 (1992)
  10. // "IEEE Standard Codes, Formats, Protocols, and Common Commands for Use With IEEE Std 488.1-1987, IEEE
  11. // Standard Digital Interface for Programmable Instrumentation"
  12. // [2] SCPI Specification, revision 1999.0
  13. // "Standard Commands for Programmable Instruments (SCPI), VERSION 1999.0, May 1999"
  14. //----------------------------------------------------------------
  15. #if !TREE_LIST_DEEP_ENABLE || !TREE_LIST_DEEP_PARENT_SEARCH_ENABLE
  16. #error Tree List deep search is required or parent search feature must be enabled
  17. #else
  18. // @scpi_commands_tlds - functional set for @tree_list_search_deep
  19. const sTreeListSearchDeep_FSet_t scpi_commands_tlds = {
  20. .fnext = treelist_getnext_key,
  21. .fcmp = treelist_header_compare,
  22. .fisdef = treelist_is_default_node,
  23. #if TREE_LIST_RECURSIVE_LIMIT_ENABLE
  24. .freclim = treelist_recursive_limiter,
  25. #endif
  26. .fagree = treelist_result_agree,
  27. };
  28. #endif
  29. // ========================================================================================================
  30. // @scpi_is_default_node
  31. // Checks if the passed string is the default (optional) SCPI command mnemonic
  32. // Is used to determine the default node in the tree-list.
  33. // References:
  34. // "5.1 Interpreting Command Tables", [2]
  35. // "7.6.1 <COMMAND PROGPRAM HEADER>", [1]
  36. // "7.6.1.5 Header Compounding Rules", [1]
  37. // @commandString - SCPI command simple mnemonic;
  38. // Note: compound mnemonics are not supported;
  39. // Note: the passed mnemonic must be valid, the mnemonic will not be validated.
  40. bool scpi_is_default_node( const char * commandString )
  41. {
  42. my_assert( commandString );
  43. if( NULL == commandString ) return false;
  44. bool optional_open = false;
  45. bool optional_close = false;
  46. while( '\0' != *commandString )
  47. {
  48. if( scpi_isopt_open( *commandString ) )
  49. {
  50. if( !optional_open )
  51. {
  52. optional_open = true;
  53. ++commandString;
  54. continue; // skip character
  55. }
  56. return false; // invalid mnemonic: extra optional sign '['
  57. }
  58. if( scpi_isopt_close( *commandString ) )
  59. {
  60. if( optional_open && !optional_close )
  61. {
  62. optional_close = true;
  63. ++commandString;
  64. continue; // skip character
  65. }
  66. return false; // invalid mnemonic: invalid syntax (optional sign ']')
  67. }
  68. ++commandString;
  69. }
  70. my_assert( !(optional_open ^ optional_close) ); // must be 'open===close'
  71. // if both @optional_open and @optional_close signs are present, this mnemonic is considered as default
  72. return ( optional_open && optional_close );
  73. }
  74. // ========================================================================================================
  75. // @scpi_command_header_compare
  76. // Compares the program mnemonic of registered command (@command) with
  77. // ... the program mnemonic of received command header (@src_key).
  78. // Function ignores the tailing question mark of the command header mnemonic.
  79. // References:
  80. // "7.6 Program Header Functional Elements", [1]
  81. // "5.1 Interpreting Command Tables", [2]
  82. // @commandString - the registered command header part, null-term string,
  83. // ... attached to the node using @nodekey_ parameter of DECLARE_TREE_LIST_LEAF.
  84. // @comparingToken - the searching command header part, a pair of (@shead, @stail)
  85. // ... pointers grouped into the @sStrToken_t.
  86. // Note: the @sStrToken_t structure referred by @src_key shall be writable.
  87. // Note: @command can points to the part of the original command string.
  88. // Returns: The function returns true in case the part specified by @src_key
  89. // ... matchs to the part of the command specified by @command and nearest
  90. // ... mnemonic separator (colon).
  91. bool scpi_command_header_compare( const char * commandString,
  92. const sStrToken_t * comparingToken )
  93. {
  94. bool result = true; // forward set, true by default.
  95. bool longform = false;
  96. const char * shead = comparingToken->shead;
  97. const char * stail = comparingToken->stail;
  98. my_assert( shead );
  99. my_assert( stail );
  100. my_assert( commandString );
  101. if( scpi_isnl( *(stail-1) )) --stail;
  102. if( scpi_queryind( *(stail-1) )) --stail;
  103. // skip white characters before comparing
  104. while( scpi_iswhite( *shead ) )
  105. shead++;
  106. if( shead >= stail )
  107. {
  108. return false; // invalid mnemonic
  109. }
  110. // Optional mnemonic indicators (e.g., "[:SYSTem]:ERRor"):
  111. bool optional_open = false;
  112. bool optional_close = false;
  113. // Until the end of the searching mnemonic is reached:
  114. for( ;shead != stail; ++commandString )
  115. {
  116. if( scpi_isopt_open( *commandString ) )
  117. {
  118. if( !optional_open )
  119. {
  120. optional_open = true;
  121. continue; // skip character
  122. }
  123. return false; // error: extra optional sign '['
  124. }
  125. if( scpi_isopt_close( *commandString ) )
  126. {
  127. if( optional_open && !optional_close )
  128. {
  129. optional_close = true;
  130. continue; // skip character
  131. }
  132. return false; // error: invalid syntax (optional sign ']')
  133. }
  134. if( '\0' == *commandString )
  135. {
  136. return false; // end of source mnemonic when @shead != @stail
  137. }
  138. if( toupper(*shead) != toupper(*commandString) )
  139. {
  140. return false; // case insensetive characters mismatch
  141. }
  142. if( islower(*commandString) )
  143. {
  144. longform = true; // long form found while @shead != @stail
  145. }
  146. ++shead;
  147. }
  148. // check if long form of mnemonic is found:
  149. if( longform )
  150. {
  151. // the searching mnemonic shall have the same length as
  152. // ... long form of source mnemonic.
  153. // Check if the end of long form reached: NULL-char or ']' is in the end of @commandString
  154. result = ('\0' == *commandString || scpi_isopt_close( *commandString ) );
  155. }
  156. else
  157. {
  158. // the searching mnemonic shall have the same length as
  159. // ... short form of source mnemonic.
  160. // Note: @commandString has been incremented at least once.
  161. // Check if the end of short form reached, check the character's
  162. // ... case at @commandString position and @commandString-1 position, the
  163. // ... register of characters must differ:
  164. // (the same character's register -> error)
  165. result = ( islower(*(commandString-1)) != islower(*commandString) || '\0' == *commandString );
  166. }
  167. // if short form of command is received, it is required
  168. // to validate the passed registered command til the end.
  169. // Fix: if long form is received, it is requried to process the @commandString til the end
  170. if( optional_open && !optional_close )
  171. {
  172. while( '\0' != *commandString )
  173. {
  174. if( scpi_isopt_close( *commandString ) )
  175. {
  176. if( optional_close )
  177. {
  178. return false; // error: invalid syntax (optional sign ']')
  179. }
  180. optional_close = true;
  181. }
  182. ++commandString;
  183. }
  184. }
  185. my_assert( !(optional_open ^ optional_close) ); // must be 'open===close'
  186. return result;
  187. }
  188. #if TREE_LIST_RECURSIVE_LIMIT_ENABLE
  189. // @scpi_search_recursive_limiter
  190. // Limits the depth of recursive calls of @tree_list_search_deep
  191. // Is used to prevent stack overflow.
  192. // @dir - direction: true - enter recursive call, false - leave call;
  193. // @ctx - mandatory user dependent context.
  194. // Returns, if @dir is 'true':
  195. // - true: recursive call is possible;
  196. // - false: recursive call is impossible;
  197. // Returns, if @dir is 'false': does not matter;
  198. bool scpi_search_recursive_limiter( bool dir, void * _ctx )
  199. {
  200. my_assert( _ctx );
  201. sTreeListSearchCtx_t * ctx = (sTreeListSearchCtx_t*)_ctx;
  202. if( dir )
  203. if( ctx->call_depth >= SCPI_MAX_CMD_SEARCH_DEPTH )
  204. {
  205. my_assert( ctx->call_depth < SCPI_MAX_CMD_SEARCH_DEPTH );
  206. return false; // limit is reached
  207. }
  208. return true;
  209. }
  210. #endif
  211. // @scpi_command_getnext_prepare
  212. // Prepare context for @scpi_command_getnext_key before calling @tree_list_search_deep
  213. // @ctx - search key context, is used to iterate upon the command mnemonic chain (compound command)
  214. // @cmd_shead - compound SCPI command mnemonic head
  215. // @cmd_stail - compound SCPI command mnemonic tail
  216. void scpi_command_getnext_prepare( sScpiCmdGetNextKey_t * ctx, const char * cmd_shead, const char * cmd_stail )
  217. {
  218. my_assert( ctx );
  219. my_assert( cmd_shead );
  220. my_assert( cmd_stail );
  221. my_assert( cmd_shead <= cmd_stail );
  222. ctx->full_token.shead = cmd_shead;
  223. ctx->full_token.stail = cmd_stail;
  224. ctx->found_token.shead = NULL;
  225. ctx->found_token.stail = NULL;
  226. }
  227. // @scpi_command_search_prepare
  228. // Prepare the SCPI Command search context before calling @tree_list_search_deep
  229. // @ctx - search context, is used in call of @tree_list_search_deep
  230. // @cmd_shead - compound SCPI command mnemonic head
  231. // @cmd_stail - compound SCPI command mnemonic tail
  232. void scpi_command_search_prepare( sTreeListSearchCtx_t * ctx, const char * cmd_shead, const char * cmd_stail )
  233. {
  234. my_assert( ctx );
  235. scpi_command_getnext_prepare( &ctx->cmdKey, cmd_shead, cmd_stail );
  236. #if TREE_LIST_RECURSIVE_LIMIT_ENABLE
  237. ctx->call_depth = 0;
  238. #endif
  239. }
  240. // @scpi_command_getnext_key
  241. // Get next command mnemonic token in the chain specified in @ctx
  242. // @ctx - rotine context;
  243. // Returns:
  244. // NULL - no key found / end of the command line;
  245. // NON NULL: string token containing command mnemonic (key)
  246. const sStrToken_t* scpi_command_getnext_key( sScpiCmdGetNextKey_t * ctx )
  247. {
  248. my_assert( ctx->full_token.shead );
  249. my_assert( ctx->full_token.stail );
  250. my_assert( ctx->full_token.shead <= ctx->full_token.stail );
  251. my_assert( NULL == ctx->found_token.shead || ctx->found_token.shead >= ctx->full_token.shead );
  252. my_assert( NULL == ctx->found_token.shead || ctx->found_token.shead <= ctx->full_token.stail );
  253. my_assert( NULL == ctx->found_token.stail || ctx->found_token.stail >= ctx->full_token.shead );
  254. my_assert( NULL == ctx->found_token.stail || ctx->found_token.stail <= ctx->full_token.stail );
  255. if( NULL == ctx->found_token.shead )
  256. {
  257. ctx->found_token.shead = ctx->full_token.shead;
  258. ctx->found_token.stail = scpi_cmdsep( ctx->full_token.shead );
  259. my_assert( ctx->found_token.stail ); // @tail never will be NULL
  260. return &ctx->found_token;
  261. }
  262. if( ctx->found_token.shead >= ctx->full_token.stail
  263. && ctx->found_token.stail >= ctx->full_token.stail )
  264. {
  265. return NULL; // end-of-command line
  266. }
  267. // all the command mnemonics are separated with SCPI_CHARACTER_COMPOUNDCOMMAND_SEPARATOR
  268. if( scpi_iscmdsep( *ctx->found_token.stail ) )
  269. {
  270. ctx->found_token.shead = (ctx->found_token.stail + 1); // skip separator
  271. ctx->found_token.stail = scpi_cmdsep( ctx->found_token.shead );
  272. my_assert( ctx->found_token.stail ); // @tail never will be NULL
  273. return &ctx->found_token;
  274. }
  275. return NULL; // invalid command chain
  276. }
  277. // @scpi_command_command_found
  278. // SCPI search command result notifee.
  279. void scpi_command_command_found( sTreeListSearchCtx_t * ctx, const sTreeList_t * found, const sTreeList_t * parent )
  280. {
  281. ctx->parent = parent; // pass the parent node pointer to the caller (@scpiSearchCommandHandler)
  282. }