#define SCPI_TLST_EX_C #include "app/scpi/scpi_tlst_ex.h" #include "app/scpi/scpi_parser.h" #include // toupper, islower // Author: Sychov A., Jan 2021 // Extension module to adopt the Tree-List algorithms to the SCPI module. //---------------------------------------------------------------- // Refer to: // [1] IEEE 488.2 Standard, revision IEEE Std 488.2-1987 (1992) // "IEEE Standard Codes, Formats, Protocols, and Common Commands for Use With IEEE Std 488.1-1987, IEEE // Standard Digital Interface for Programmable Instrumentation" // [2] SCPI Specification, revision 1999.0 // "Standard Commands for Programmable Instruments (SCPI), VERSION 1999.0, May 1999" //---------------------------------------------------------------- #if !TREE_LIST_DEEP_ENABLE || !TREE_LIST_DEEP_PARENT_SEARCH_ENABLE #error Tree List deep search is required or parent search feature must be enabled #else // @scpi_commands_tlds - functional set for @tree_list_search_deep const sTreeListSearchDeep_FSet_t scpi_commands_tlds = { .fnext = treelist_getnext_key, .fcmp = treelist_header_compare, .fisdef = treelist_is_default_node, #if TREE_LIST_RECURSIVE_LIMIT_ENABLE .freclim = treelist_recursive_limiter, #endif .fagree = treelist_result_agree, }; #endif // ======================================================================================================== // @scpi_is_default_node // Checks if the passed string is the default (optional) SCPI command mnemonic // Is used to determine the default node in the tree-list. // References: // "5.1 Interpreting Command Tables", [2] // "7.6.1 ", [1] // "7.6.1.5 Header Compounding Rules", [1] // @commandString - SCPI command simple mnemonic; // Note: compound mnemonics are not supported; // Note: the passed mnemonic must be valid, the mnemonic will not be validated. bool scpi_is_default_node( const char * commandString ) { my_assert( commandString ); if( NULL == commandString ) return false; bool optional_open = false; bool optional_close = false; while( '\0' != *commandString ) { if( scpi_isopt_open( *commandString ) ) { if( !optional_open ) { optional_open = true; ++commandString; continue; // skip character } return false; // invalid mnemonic: extra optional sign '[' } if( scpi_isopt_close( *commandString ) ) { if( optional_open && !optional_close ) { optional_close = true; ++commandString; continue; // skip character } return false; // invalid mnemonic: invalid syntax (optional sign ']') } ++commandString; } my_assert( !(optional_open ^ optional_close) ); // must be 'open===close' // if both @optional_open and @optional_close signs are present, this mnemonic is considered as default return ( optional_open && optional_close ); } // ======================================================================================================== // @scpi_command_header_compare // Compares the program mnemonic of registered command (@command) with // ... the program mnemonic of received command header (@src_key). // Function ignores the tailing question mark of the command header mnemonic. // References: // "7.6 Program Header Functional Elements", [1] // "5.1 Interpreting Command Tables", [2] // @commandString - the registered command header part, null-term string, // ... attached to the node using @nodekey_ parameter of DECLARE_TREE_LIST_LEAF. // @comparingToken - the searching command header part, a pair of (@shead, @stail) // ... pointers grouped into the @sStrToken_t. // Note: the @sStrToken_t structure referred by @src_key shall be writable. // Note: @command can points to the part of the original command string. // Returns: The function returns true in case the part specified by @src_key // ... matchs to the part of the command specified by @command and nearest // ... mnemonic separator (colon). bool scpi_command_header_compare( const char * commandString, const sStrToken_t * comparingToken ) { bool result = true; // forward set, true by default. bool longform = false; const char * shead = comparingToken->shead; const char * stail = comparingToken->stail; my_assert( shead ); my_assert( stail ); my_assert( commandString ); if( scpi_isnl( *(stail-1) )) --stail; if( scpi_queryind( *(stail-1) )) --stail; // skip white characters before comparing while( scpi_iswhite( *shead ) ) shead++; if( shead >= stail ) { return false; // invalid mnemonic } // Optional mnemonic indicators (e.g., "[:SYSTem]:ERRor"): bool optional_open = false; bool optional_close = false; // Until the end of the searching mnemonic is reached: for( ;shead != stail; ++commandString ) { if( scpi_isopt_open( *commandString ) ) { if( !optional_open ) { optional_open = true; continue; // skip character } return false; // error: extra optional sign '[' } if( scpi_isopt_close( *commandString ) ) { if( optional_open && !optional_close ) { optional_close = true; continue; // skip character } return false; // error: invalid syntax (optional sign ']') } if( '\0' == *commandString ) { return false; // end of source mnemonic when @shead != @stail } if( toupper(*shead) != toupper(*commandString) ) { return false; // case insensetive characters mismatch } if( islower(*commandString) ) { longform = true; // long form found while @shead != @stail } ++shead; } // check if long form of mnemonic is found: if( longform ) { // the searching mnemonic shall have the same length as // ... long form of source mnemonic. // Check if the end of long form reached: NULL-char or ']' is in the end of @commandString result = ('\0' == *commandString || scpi_isopt_close( *commandString ) ); } else { // the searching mnemonic shall have the same length as // ... short form of source mnemonic. // Note: @commandString has been incremented at least once. // Check if the end of short form reached, check the character's // ... case at @commandString position and @commandString-1 position, the // ... register of characters must differ: // (the same character's register -> error) result = ( islower(*(commandString-1)) != islower(*commandString) || '\0' == *commandString ); } // if short form of command is received, it is required // to validate the passed registered command til the end. // Fix: if long form is received, it is requried to process the @commandString til the end if( optional_open && !optional_close ) { while( '\0' != *commandString ) { if( scpi_isopt_close( *commandString ) ) { if( optional_close ) { return false; // error: invalid syntax (optional sign ']') } optional_close = true; } ++commandString; } } my_assert( !(optional_open ^ optional_close) ); // must be 'open===close' return result; } #if TREE_LIST_RECURSIVE_LIMIT_ENABLE // @scpi_search_recursive_limiter // Limits the depth of recursive calls of @tree_list_search_deep // Is used to prevent stack overflow. // @dir - direction: true - enter recursive call, false - leave call; // @ctx - mandatory user dependent context. // Returns, if @dir is 'true': // - true: recursive call is possible; // - false: recursive call is impossible; // Returns, if @dir is 'false': does not matter; bool scpi_search_recursive_limiter( bool dir, void * _ctx ) { my_assert( _ctx ); sTreeListSearchCtx_t * ctx = (sTreeListSearchCtx_t*)_ctx; if( dir ) if( ctx->call_depth >= SCPI_MAX_CMD_SEARCH_DEPTH ) { my_assert( ctx->call_depth < SCPI_MAX_CMD_SEARCH_DEPTH ); return false; // limit is reached } return true; } #endif // @scpi_command_getnext_prepare // Prepare context for @scpi_command_getnext_key before calling @tree_list_search_deep // @ctx - search key context, is used to iterate upon the command mnemonic chain (compound command) // @cmd_shead - compound SCPI command mnemonic head // @cmd_stail - compound SCPI command mnemonic tail void scpi_command_getnext_prepare( sScpiCmdGetNextKey_t * ctx, const char * cmd_shead, const char * cmd_stail ) { my_assert( ctx ); my_assert( cmd_shead ); my_assert( cmd_stail ); my_assert( cmd_shead <= cmd_stail ); ctx->full_token.shead = cmd_shead; ctx->full_token.stail = cmd_stail; ctx->found_token.shead = NULL; ctx->found_token.stail = NULL; } // @scpi_command_search_prepare // Prepare the SCPI Command search context before calling @tree_list_search_deep // @ctx - search context, is used in call of @tree_list_search_deep // @cmd_shead - compound SCPI command mnemonic head // @cmd_stail - compound SCPI command mnemonic tail void scpi_command_search_prepare( sTreeListSearchCtx_t * ctx, const char * cmd_shead, const char * cmd_stail ) { my_assert( ctx ); scpi_command_getnext_prepare( &ctx->cmdKey, cmd_shead, cmd_stail ); #if TREE_LIST_RECURSIVE_LIMIT_ENABLE ctx->call_depth = 0; #endif } // @scpi_command_getnext_key // Get next command mnemonic token in the chain specified in @ctx // @ctx - rotine context; // Returns: // NULL - no key found / end of the command line; // NON NULL: string token containing command mnemonic (key) const sStrToken_t* scpi_command_getnext_key( sScpiCmdGetNextKey_t * ctx ) { my_assert( ctx->full_token.shead ); my_assert( ctx->full_token.stail ); my_assert( ctx->full_token.shead <= ctx->full_token.stail ); my_assert( NULL == ctx->found_token.shead || ctx->found_token.shead >= ctx->full_token.shead ); my_assert( NULL == ctx->found_token.shead || ctx->found_token.shead <= ctx->full_token.stail ); my_assert( NULL == ctx->found_token.stail || ctx->found_token.stail >= ctx->full_token.shead ); my_assert( NULL == ctx->found_token.stail || ctx->found_token.stail <= ctx->full_token.stail ); if( NULL == ctx->found_token.shead ) { ctx->found_token.shead = ctx->full_token.shead; ctx->found_token.stail = scpi_cmdsep( ctx->full_token.shead ); my_assert( ctx->found_token.stail ); // @tail never will be NULL return &ctx->found_token; } if( ctx->found_token.shead >= ctx->full_token.stail && ctx->found_token.stail >= ctx->full_token.stail ) { return NULL; // end-of-command line } // all the command mnemonics are separated with SCPI_CHARACTER_COMPOUNDCOMMAND_SEPARATOR if( scpi_iscmdsep( *ctx->found_token.stail ) ) { ctx->found_token.shead = (ctx->found_token.stail + 1); // skip separator ctx->found_token.stail = scpi_cmdsep( ctx->found_token.shead ); my_assert( ctx->found_token.stail ); // @tail never will be NULL return &ctx->found_token; } return NULL; // invalid command chain } // @scpi_command_command_found // SCPI search command result notifee. void scpi_command_command_found( sTreeListSearchCtx_t * ctx, const sTreeList_t * found, const sTreeList_t * parent ) { ctx->parent = parent; // pass the parent node pointer to the caller (@scpiSearchCommandHandler) }