#define SCPI_PARSER_C #include "app/scpi/scpi_parser.h" #include "app/scpi/scpi_core.h" #include "app/scpi/scpi_numeric.h" #include "my_assert.h" #include // isdigit(), isalpha(), isalnum() //---------------------------------------------------------------- // 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" //---------------------------------------------------------------- // ================================================================================================================= // Notes: /* White Space 7.4.1.2 Encoding Syntax, [1] is de?ned as a single ASCII-encoded byte in the range of 00-09, 0B-20 (0-9, 11-32 decimal). This range includes the ASCII control characters and the space but excludes the newline. New Line 7.5 , [1] NL is defined as a single ASCII-encoded byte 0A (10 decimal). NOTE: An END message cannot be sent without an accompanying data byte. See IEEE Std 488.1-1987 [4]. The ^END syntactic element implies the IEEE 488.1 END message is sent with the last data byte of the preceding syntactic element. Elements Summary 7.3.3 Functional Element Summary, [1] */ // ================================================================================================================= // @sParserEntryPoint_t // Parser context backup/restore point data structure // see @parserBackupContext and @parserRestoreContext typedef struct { const char * str; // temportary buffer iterator, shall not be accessed by user. const char * head; // the head of the buffer, points to the begining of the entity after // ... the call of any paraser-function. const char * tail; // the tail of the buffer, points to the end of entity after the call // ... of any paraser-function. int32_t value; // Generic-Purpose integer value } sParserEntryPoint_t; typedef struct { struct // common context { int32_t error; // parser error code const char * pErrorMsg; // parser error message const char * str; // temportary buffer iterator, shall not be accessed by user. /* --- unused, the (@tail - @head) expression is used instead size_t length; // input/output field, specifies the length of the buffer before // ... calling the parser-function, and holds the length of parsed // ... element after the call. */ const char * head; // the head of the buffer, points to the begining of the entity after // ... the call of any paraser-function. const char * tail; // the tail of the buffer, points to the end of entity after the call // ... of any paraser-function. bool bEndMessage; // End-of-message indicator. Specifies if the specified buffer holds // ... a complete message or not. If the @str points to a portion // ... of message's data and there no information about the whole // ... message length, the @bEndMessage value is 'false'. Otherwise, // ... if known that the @str buffer contains the final portion of // ... data (inclidung the terminating characters, maybe), the value // ... is 'true'. bool bContextKept; // Clean context indicator. If @prepareParserContext has been called // ... with keeping context, this variable is 'true'. Otherwise, if // ... @prepareParserContext has been called with cleaning the context // ... then this variable is 'false'. uint8_t type; // the entity type, encoded as described in @eScpiEntityType_t enum. }; sParserEntryPoint_t parseNumber_xSaveObj; // dedicated parser backup/restore structure for @parseNumber routine union // private context union { struct // private context for @parseString function { bool QuoteOpened; // Quote-opened indicator: used to help to interpretate each quote // ... either as an opening or closing one. char QuoteSignature; // Quote signature: can be either '\"' or '\'' char LastCharacter; // The temporary character buffer to store the last processed byte } parseString; struct // private context for @parseArbitraryBlock function { size_t LengthDigits; // the number of demical digits used to encode the actual length // ... of the block uint32_t Length; // Actual length of the block in bytes, encoded as demical value // ... in range 0..999999999 bool KeySign; // start-character indicator: used to determine if the starting // ... character have already processed or not yet. bool DataStarted; // mode indicator: used to determine if all block's header characters // ... have already processed and the current character is a part of payload. } parseArbitraryBlock; struct // private context for @parseProgramMnemonic function { size_t CharsNumber; // amount of characters in the mnemonic. bool FirstAlpha; // start-character indicator: used to determine if the first alpha- // ...-character has been processed or not. } parseProgramMnemonic; struct // private context for @parseCommandProgramHeader function { // NOTE: @parseCommandProgramHeader function uses the @parseProgramMnemonic // ... calls as a part of implementation, thus it is required to keep // ... the @parseProgramMnemonic's context valid! const size_t CharsNumber; // reserved, keep unchanged (@parseProgramMnemonic) const bool FirstAlpha; // reserved, keep unchanged (@parseProgramMnemonic) bool LastCallContinue; // last call context indicator: shows if it is required to continue // ... the last call context or it is needed to reset it. bool LastCallSuccess; // last call success indicator: shows if the last call succeded or not. bool CommonForm; // common form indicator: shows if the command program header have a // ... common form (begins from askterisk, 7.6.1.2 Encoding Syntax, [1]) const char * _head; // cached @head value const char * _tail; // cached @tail value char LastCharacter; // the last processed character } parseCommandProgramHeader; struct // private context for @parseDemicalNumber function { bool dotAllowed; // dot-character indicator: shows if the character is // ... allowed in current state; bool signAllowed; // sign-character indicator: shows if the character is // ... allowed in current state; bool digitAllowed; // digit-character indicator: shows if the character is // ... allowed in current state; bool whiteAllowed; // white-character indicator: shows if the character is // ... allowed in current state; bool expAllowed; // exponent-character indicator: shows if the character is // ... allowed in current state; bool termAllowed; // unit terminator indicator: shows if a group of terminating characters are // ... allowed in current state; bool manitssaOk; // mantissa indicator: shows if mantissa received or not. bool exponentOk; // exponent indicator: shows if exponent received or not. bool dotFound; // dot-character indicator: show if the dot character is faced in mantissa bool parseStage; // the parser state: // false - search for mantissa; // true - search for exponent; } parseDemicalNumber; struct // private context for @parseNonDemicalNumber function { size_t digits; // number of digits processed bool allow0_1; // allow binary characters ( 0..1 ) bool allow0_7; // allow octal characters ( 0..7 ) bool allow0_f; // allow hex characters ( 0..9, a-f ) bool allowNumChar; // allow numeric-system designator character bool foundNumericChar; // numeric character indicator (#) } parseNonDemicalNumber; struct // private context for @parseProgramDataSeparator function { bool sepFound; // indicator showing if separator-character found or not } parseProgramDataSeparator; struct // private context for @parseProgramHeaderSeparator function { bool sepFound; // indicator showing if separator-character found or not } parseProgramHeaderSeparator; struct // private context for @parseProgramMessageSeparator function { bool sepFound; // indicator showing if separator-character found or not } parseProgramMessageSeparator; struct // private context for @parseEndOfCommand function { bool sepFound; // indicator showing if separator-character found or not } parseEndOfCommand; struct // private context for @parseWhiteSequence function { bool whiteFound; // indicator showing if separator-character found or not bool nlFound; // indicator showing if NL-character found or not } parseWhiteSequence; }; } sParseEntry_t; // ================================================================================================================= STATIC_ASSERT( sizeof(xParseEntry_t) == sizeof(sParseEntry_t), "Invalid xParseEntry_t/sParseEntry_t size" ); // ================================================================================================================= // @parserBackupContext // Internal only function. // Saves the parser entry point before the parsing to give ability to restore the context iterators after // unsuccessful parsing. // Does not guaranee full context backup. // @pObj = parser context; // @to = parser backup structure static inline void parserBackupContext( const sParseEntry_t * pObj, sParserEntryPoint_t * to ) { my_assert( pObj ); my_assert( to ); to->head = pObj->head; to->tail = pObj->tail; to->str = pObj->str; } // @parserBackupContextValue // Internal only function. // Saves parser-dependent general puspose value // @to = parser backup structure // @value = generic purpose value static inline void parserBackupContextValue( sParserEntryPoint_t * to, int32_t value ) { my_assert( to ); to->value = value; } // @parserRestoreContext // Internal only function. // Loads the parser entry point after the one has been saved by @parserBackupContext to // restore parser entry point after unsuccessful parsing. // Does not guaranee full context restore. // @pObj = parser context to restore to; // @from = parser restore structure // NOTE: resets @bContextKept variable to prevent using invalid data static inline void parserRestoreContext( sParseEntry_t * pObj, const sParserEntryPoint_t * from ) { my_assert( from ); pObj->head = from->head; pObj->tail = from->tail; pObj->str = from->str; // reset context keeping indicator to reset the next parser's internal variables pObj->bContextKept = false; } // @parserRestoreContext // Internal only function. // Loads the parser dependent general purpose value after the one has been saved by @parserBackupContextValue // @pObj = parser context to restore to, optional, can be NULL; // @from = parser restore structure // @pvalue = pointer to the variable to load generic purpose value saved by @parserBackupContext in 'value' argument, // ... optional, can be NULL static inline void parserRestoreContextValue( const sParserEntryPoint_t * from, int32_t * pvalue ) { my_assert( from ); my_assert( pvalue ); *pvalue = from->value; } // ================================================================================================================= // @scpi_isalnum // Detects if the passed character @ch is ASCII alpha-numeric character // Refer to: // "7.6.1 ", [1] // "7.6.1.2 Encoding Syntax", [1] // Note: the implementation is depending on the current locale. // This implementation uses the default functions from the standard // ...-header. static inline bool scpi_isalnum( char ch ) { // for the default locale only A-Za-z0-9 are allowed return isalnum(ch); } // ================================================================================================================= // @scpi_isalpha // Detects if the passed character @ch is ASCII alpha character // Refer to: // "7.6.1 ", [1] // "7.6.1.2 Encoding Syntax", [1] // Note: the implementation is depending on the current locale. // This implementation uses the default functions from the standard // ...-header. static inline bool scpi_isalpha( char ch ) { // for the default locale only A-Za-z0-9 are allowed return isalpha(ch); } // ================================================================================================================= // @scpi_isdigit // Detects if the passed character @ch is ASCII digit character // Refer to: // "7.6.1 ", [1] // "7.6.1.2 Encoding Syntax", [1] static inline bool scpi_isdigit( char ch ) { // for the locale does not affect to the implementation, only 0-9 are allowed return isdigit(ch); } // ================================================================================================================= // @scpi_ishex // Detects if the passed character @ch is a HEX character (0..9, A-F, a-f) static inline bool scpi_ishex( char ch ) { // for the locale does not affect to the implementation, only 0-9 are allowed return isdigit(ch) || ( 'a' <= ch && 'f' >= ch ) || ( 'A' <= ch && 'F' >= ch ); } // ================================================================================================================= // @prepareParserContext // Preapres a parser context @xObj before a parser-function call. // The function must be called before each call of any parser-function. // If it is required to call the parser function multiple times, user // ... shall analyze the last return status and specify the @bKeepContext // ... parameter to the corresponding value. See also @eScpiParserStatus_t. // Parameters: // @xObj - the parse context to prepare; // @pBuffer - the memory buffer containing the SCPI element to be parsed; // @length - the memory buffer length; // @bEndMessage - specifies if the specified buffer holds a complete // ... message or not. If the @pBuffer contains a portion of received // ... data and there no information about the whole message length, // ... the @bEndMessage value must be 'false'. Otherwise, if known that // ... the buffer contains the final portion of data (even inclidung the // ... terminating characters), the value must be 'true'. // @bKeepContext - specifies if it is required to cleanup the context // ... to restart the parsing ('false'), or it is required to keep the // ... context to continue ('true'). // Returns: 'true' in success case, // 'false' otherwise. // Note in case the function returns 'false' user must not use the // ... @xObj context due to it is still not prepared. bool prepareParserContext( xParseEntry_t * xObj, const void * pBuffer, size_t length, bool bEndMessage, bool bKeepContext ) { sParseEntry_t * pObj = (sParseEntry_t*)xObj; if( NULL != pObj && NULL != pBuffer && 0 != length ) { if( !bKeepContext ) { // Cleanup the private context memset( pObj, 0, sizeof(sParseEntry_t) ); pObj->bContextKept = false; } else { pObj->bContextKept = true; } //pObj->length = length; // init the buffer length pObj->head = (const char*)pBuffer; // save the buffer start pObj->tail = pObj->head + length; // save the buffer end pObj->str = pObj->head; // init the @str iterator to the beginning of the buffer pObj->bEndMessage = bEndMessage; // save end-of-message flag pObj->type = eScpiEntityTypeInvalid; // forward type set: invalid (default) pObj->error = SCPI_ERROR_SUCCESS; // reset error code pObj->pErrorMsg = NULL; // reset error message return true; } return false; } // ================================================================================================================= // @getParsedEntityDetails // Retrieves the parsing results from the parser context. // The function shall not be called until either the // ... @eScpiParserStatus_success status or @eScpiParserStatus_need_data // ... are returned by last parser-function call. // Note: take in account, that in case @eScpiParserStatus_need_data had // ... been returned during the last parser-function call, the @eEntityType // ... will be set to @eScpiEntityTypeInvalid due to the entity is still // ... not identified (need more data). // Parameters: // @xObj - the parser context to get results from; // @pxEntityBegin - pointer to the pointer-variable to store the // ... parsed entity beginning, can be NULL; // @pxEntityEnd - pointer to the pointer-variable to store the // ... parsed entity ending, can be NULL; // @peEntityType - pointer to the uint8-variable to store the // ... parsed entity type, the type is encoded as described // ... in @eScpiEntityType_t enum, can be NULL; // Note: in case the parameters described above are NULL an appropriate // ... information will not be returned and can be retrieved by // ... another call of this function with non-NULL argument until // ... the @prepareParserContext is called for the specified context. // Returns: 'true' in success case, // 'false' otherwise. bool getParsedEntityDetails( const xParseEntry_t * xObj, const void * * pxEntityBegin, const void * * pxEntityEnd, uint8_t * peEntityType ) { bool status = false; if( NULL != xObj ) { if( NULL != pxEntityBegin ) { *pxEntityBegin = ((const sParseEntry_t*)xObj)->head; status = status || true; } if( NULL != pxEntityEnd ) { *pxEntityEnd = ((const sParseEntry_t*)xObj)->tail; status = status || true; } if( NULL != peEntityType ) { *peEntityType = ((const sParseEntry_t*)xObj)->type; status = status || true; } } my_assert( status ); return status; } // ================================================================================================================= // @getParserError // Retrieves the last parser error code and message // The function shall not be called until either the @eScpiParserStatus_failed status // is returned by last parser-function call. // @xObj - the parser context to get results from; // @pErrorCode - optional pointer to the variable to store the error code, can be NULL; // Returns: // The parser error message string. Use @getParsedEntityDetails to retrieve more details. // Possible values: // - NULL: no error occurred in specified context or invalid context; // - non-null: valid constant null-terminated string message, no memory free is required; const char * getParserError( const xParseEntry_t * xObj, int32_t * pErrorCode ) { if( NULL != xObj ) { if( NULL != pErrorCode ) { *pErrorCode = ((const sParseEntry_t*)xObj)->error; } return ((const sParseEntry_t*)xObj)->pErrorMsg; } return NULL; } // ================================================================================================================= // @setParserError // Assigne the last parser error code and message // The function shall not be called after calling any parser function that return error // status code to prevent overwriting actual error code and description. // @xObj - the parser context to get results from; // @errorCode - SCPI Error code, [2] // @pErrorDesc - Error description, constant null-terminated string; // Returns: // true - successful operation, // false - failed bool setParserError( const xParseEntry_t * xObj, int32_t errorCode, const char * pErrorDesc ) { my_assert( xObj ); if( NULL != xObj ) { ((sParseEntry_t*)xObj)->error = errorCode; ((sParseEntry_t*)xObj)->pErrorMsg = pErrorDesc; return true; } return false; } // ================================================================================================================= // @parseArbitraryBlock // "Parser-function", parses an arbitrary block (ARBITRARY BLOCK PROGRAM DATA) // References: // "7.7.6 ", [1] // "7.7.6.2 Encoding Syntax", [1] // Parameters: // @xObj - parser context, must be prepared by @prepareParserContext // ... before each call. If it is needed to continue the processing, the // ... @bKeepContext parameter duing the call @prepareParserContext must // ... be set to 'true', and otherwise, to restart parsing the parameter // ... must be set to 'false'. // Returns: // eScpiParserStatus_failed - error, can not parse data; // eScpiParserStatus_success - success, the string has been successfully parsed; // eScpiParserStatus_need_data - warning, the string can not be parsed due to // there no enough data to process. eScpiParserStatus_t parseArbitraryBlock( xParseEntry_t * xObj ) { sParseEntry_t * pObj = (sParseEntry_t*)xObj; eScpiParserStatus_t status = eScpiParserStatus_invalid; if( NULL != pObj && NULL != pObj->str && 0 != ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->head) && (0 < ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->str)) ) { status = eScpiParserStatus_failed; // forward status set: the default 'eScpiParserStatus_invalid' shall // ... not be returned due to the context will be changed. // if the context has been cleaned up if( ! pObj->bContextKept ) { // initialize private context variables pObj->parseArbitraryBlock.KeySign = false; pObj->parseArbitraryBlock.DataStarted = false; pObj->parseArbitraryBlock.LengthDigits = 0; pObj->parseArbitraryBlock.Length = 0; } // walk til the end of the buffer while( pObj->str != pObj->tail ) { char character = *(pObj->str++); if( ! pObj->parseArbitraryBlock.KeySign ) { if( '#' == character ) // !: starting character { pObj->parseArbitraryBlock.KeySign = true; // starting character found } else { pObj->error = SCPI_ERROR_INVALID_BLOCKDATA; pObj->pErrorMsg = SCPI_ERROR_INVALID_BLOCKDATA_MSG; status = eScpiParserStatus_failed; // error: the data does not start from the starting character break; } } else { // check if the data payload is already being processed: if( ! pObj->parseArbitraryBlock.DataStarted ) { // nope, need to process the header // check if the number of digits of the length-entity is loaded if( 0 == pObj->parseArbitraryBlock.LengthDigits ) { // nope, need to load a number of digits // check if the current character is a digit: if( ! scpi_isdigit( character ) ) { pObj->error = SCPI_ERROR_INVALID_BLOCKDATA; pObj->pErrorMsg = SCPI_ERROR_INVALID_BLOCKDATA_MSG; // nope, it is a fiasco status = eScpiParserStatus_failed; // error: invalid data block header break; } else // true == scpi_isdigit(character) { // yep, load the value pObj->parseArbitraryBlock.LengthDigits = (size_t)(character - '0'); // check the number loaded: if( 0 == pObj->parseArbitraryBlock.LengthDigits ) { // indefnite format: the data block must be terminated by the // ... couple of end-of-line and end-of-message terminators (NL + ^END). // In case there no way to detemine the length of the payload, // ... the length field must be set to zero and function shall return // ... the warining status "eScpiParserStatus_need_data". Otherwise, in case // ... it is defenitely known that this portion of data is the last one, // ... the function shall not to change the length @Length (it is already // ... set to the passed buffer length) and return success status. if( pObj->bEndMessage ) { pObj->type = eScpiEntityTypeArbitraryBlock; // assign the entity type // An arbirary block takes the whole buffer, the @tail pointer refers to the // ... end of the buffer. The @tail and @head pointers specify the entity length as // ... whole buffer length. status = eScpiParserStatus_success; // success: an arbirary block takes the whole buffer } else { (void)pObj->type; // do not assign the entity type, it is not identified yet. // No information about the whole message length on this level: return "eScpiParserStatus_need_data" pObj->tail = pObj->head; // set to zero length to indicate "indefine format" by // ... assigning the @head to the @tail. status = eScpiParserStatus_need_data; // warning: no closing quote found, need more data } // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; break; } else // 0 != pObj->parseArbitraryBlock.LengthDigits { // the next @LengthDigits characters must be interpretated as a // ... part of the length entity of the header. // Nothing to do right here, see "if( 0 != pObj->parseArbitraryBlock.LengthDigits )" } } } else // 0 != pObj->parseArbitraryBlock.LengthDigits { // the next @LengthDigits characters must be interpretated as a // ... part of the length entity of the header. // check if the current character is a digit: if( ! scpi_isdigit( character ) ) { pObj->error = SCPI_ERROR_INVALID_BLOCKDATA; pObj->pErrorMsg = SCPI_ERROR_INVALID_BLOCKDATA_MSG; // nope, it is a fiasco status = eScpiParserStatus_failed; // error: invalid data block header } else { pObj->parseArbitraryBlock.LengthDigits--; // decrease a number of digits // To get the length of the block it is required to process the // ... length-entity of the header. On each cycle the taken character // ... is a remainder of devision to 10, so it is required to // ... multiply the @Length by 10 and the remainder. pObj->parseArbitraryBlock.Length = pObj->parseArbitraryBlock.Length * 10 + (size_t)(character - '0'); // As soon as all the digits are "swallowed": if( 0 == pObj->parseArbitraryBlock.LengthDigits ) { // check if the length-entity is greater than zero if( 0 < pObj->parseArbitraryBlock.Length ) { // Set the "data" indicator: all the @Length next characters // are the part of the payload. pObj->parseArbitraryBlock.DataStarted = true; } else { pObj->type = eScpiEntityTypeArbitraryBlock; // assign the entity type // Dummy block detected: the @Length is actually zero // The arbitrary data block has zero-length payload. // Calculate the whole block size in the buffer: // LENGTH = @str - @head // Assigning the @tail pointer to indicate the block length: pObj->tail = pObj->str; // update the tail pointer status = eScpiParserStatus_success; // The block was successfully recognized. // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; break; } } } } } else { // Swallow each byte until all the @Length bytes is counted if( 0 < pObj->parseArbitraryBlock.Length ) { // It is still a payload... pObj->parseArbitraryBlock.Length--; } // As soon as all the bytes of the block's payload are processed: if( 0 == pObj->parseArbitraryBlock.Length ) { pObj->type = eScpiEntityTypeArbitraryBlock; // assign the entity type // the entity size in the buffer // LENGTH = @str - @head // Assign the @tail pointer to indicate the entity length: pObj->tail = pObj->str; // update the tail pointer status = eScpiParserStatus_success; // The block was successfully recognized. // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; break; } } } } } return status; } // ================================================================================================================= // @parseString // "Parser-function", parses a SCPI string (STRING PROGRAM DATA) // References: // " 7.7.5", [1] // "7.7.5.2 Encoding Syntax", [1] // Parameters: // @xObj - parser context, must be prepared by @prepareParserContext // ... before each call. If it is needed to continue the processing, the // ... @bKeepContext parameter duing the call @prepareParserContext must // ... be set to 'true', and otherwise, to restart parsing the parameter // ... must be set to 'false'. // Returns: // eScpiParserStatus_failed - error, can not parse data; // eScpiParserStatus_success - success, the string has been successfully parsed; // eScpiParserStatus_need_data - warning, the string can not be parsed due to // there no enough data to process. eScpiParserStatus_t parseString( xParseEntry_t * xObj ) { sParseEntry_t * pObj = (sParseEntry_t*)xObj; eScpiParserStatus_t status = eScpiParserStatus_invalid; if( NULL != pObj && NULL != pObj->str && 0 != ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->head) && (0 < ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->str)) ) { status = eScpiParserStatus_failed; // forward status set: the default 'eScpiParserStatus_invalid' shall // ... not be returned due to the context will be changed. // if the context has been cleaned up if( ! pObj->bContextKept ) { // initialize private context variables pObj->parseString.QuoteOpened = false; pObj->parseString.LastCharacter = '\0'; // initialize the @LastCharacter to the any character, but not // ... single nor double quote. } // walk til the end of the buffer while( pObj->str != pObj->tail ) { char character = *(pObj->str++); if( ! pObj->parseString.QuoteOpened ) { if( '\"' == character || '\'' == character ) // !: double/signle quoted string { pObj->parseString.QuoteSignature = character; pObj->parseString.QuoteOpened = true; continue; // avoid of remembering current character in @LastCharacter } else // !: non-quote character { pObj->error = SCPI_ERROR_INVALID_STRINGDATA; pObj->pErrorMsg = SCPI_ERROR_INVALID_STRINGDATA_MSG; status = eScpiParserStatus_failed; // error: the data does not start from a quote break; } } else { // Analyze the character with 1-character delay to detect quote-escaping: if( pObj->parseString.QuoteSignature == pObj->parseString.LastCharacter ) // maybe, it is a nesting quoting (escaping): { // Need to analyze a couple of characters to ensure, if current character is a // ... closing quote character, or an ecaping one. if( pObj->parseString.QuoteSignature != character ) // check current character: { pObj->type = eScpiEntityTypeString; // assign the entity type pObj->parseString.QuoteOpened = false; // last character was a closing quote // calculate the string length: // LENGTH = @str - @head - 1 // Assign the @tail pointer to indicate the entity length: pObj->tail = pObj->str - 1; // update the tail pointer status = eScpiParserStatus_success; // The string was successfully recognized. // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; break; } else { pObj->parseString.LastCharacter = '\0'; // initialize the @LastCharacter to the any character, but not // ... single nor double quote. continue; // avoid of remembering current character in @LastCharacter } } } pObj->parseString.LastCharacter = character; } // Check if is there an opend quote: if( pObj->parseString.QuoteOpened ) { if( pObj->bEndMessage ) { pObj->error = SCPI_ERROR_INVALID_STRINGDATA; pObj->pErrorMsg = SCPI_ERROR_INVALID_STRINGDATA_MSG; status = eScpiParserStatus_failed; // error: last quote had not been closed } else { (void)pObj->type; // do not assign the entity type, it is not identified yet. status = eScpiParserStatus_need_data; // warning: no closing quote found, need more data } } } return status; } // ================================================================================================================= // @parseProgramMenmonic // "Parser-function", parses a SCPI Program Mnemonic () // References: // "", "7.6.1.2 Encoding Syntax", [1] // Parameters: // @xObj - parser context, must be prepared by @prepareParserContext // ... before each call. If it is needed to continue the processing, the // ... @bKeepContext parameter duing the call @prepareParserContext must // ... be set to 'true', and otherwise, to restart parsing the parameter // ... must be set to 'false'. // Returns: // eScpiParserStatus_failed - error, can not parse data; // eScpiParserStatus_success - success, the string has been successfully parsed; // eScpiParserStatus_need_data - warning, the string can not be parsed due to // there no enough data to process. eScpiParserStatus_t parseProgramMenmonic( xParseEntry_t * xObj ) { sParseEntry_t * pObj = (sParseEntry_t*)xObj; eScpiParserStatus_t status = eScpiParserStatus_invalid; if( NULL != pObj && NULL != pObj->str && 0 != ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->head) && (0 < ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->str)) ) { status = eScpiParserStatus_failed; // forward status set: the default 'eScpiParserStatus_invalid' shall // ... not be returned due to the context will be changed. // if the context has been cleaned up if( ! pObj->bContextKept ) { // initialize private context variables pObj->parseProgramMnemonic.FirstAlpha = false; // reset the start-character indicator pObj->parseProgramMnemonic.CharsNumber = 0; // reset the number of processed characters } // walk til the end of the buffer while( pObj->str != pObj->tail ) { char character = *(pObj->str++); // If the start-character have not found yet: if( ! pObj->parseProgramMnemonic.FirstAlpha ) { // the first character must be the upper/lower case // ... ALPHA character, 7.6.1.2 Encoding Syntax, [1] if( scpi_isalpha( character ) ) { pObj->parseProgramMnemonic.FirstAlpha = true; // ok, the first character found pObj->parseProgramMnemonic.CharsNumber = 1; // 1 character has been processed } else { pObj->error = SCPI_ERROR_INVALID_CHARACTER; pObj->pErrorMsg = SCPI_ERROR_INVALID_CHARACTER_MSG; #if 0 if( scpi_iscmdsep( character ) || scpi_commonhdrind( character ) ) { // conditionally valid character. // must be analyzed by caller } #endif // indicate invalid character pObj->head = (pObj->str - 1); // update head to identify the character pObj->tail = pObj->str; status = eScpiParserStatus_failed; // the first character does not match // ... to the rule, error. // avoid of "break" operator here due to it is required to skip all the // ... code section right after the while-cycle. goto L_parseProgramMenmonic_EXIT; } } else { #if 1 // check the length of already processed characters: if( pObj->parseProgramMnemonic.CharsNumber >= 12 ) // 7.6.1.4.1 Length, [1] { pObj->error = SCPI_ERROR_MNEMONIC_TOOLONG; pObj->pErrorMsg = SCPI_ERROR_MNEMONIC_TOOLONG_MSG; status = eScpiParserStatus_failed; // error: maximum mnemoic length exceeded // avoid of "break" operator here due to it is required to skip all the // ... code section right after the while-cycle. goto L_parseProgramMenmonic_EXIT; } #endif // check the character: if( scpi_isalnum( character ) || ( '_' == character ) ) { pObj->parseProgramMnemonic.CharsNumber++; // increase number of characters } else { // Since this character does not match to the rule, // ... it is required to roll back the iterator @str // ... due to it have been incremented illegally for // ... for this condition. pObj->str--; // roll back the iterator break; } } } // while(...) // check if the whole buffer has been processed without errors: if( pObj->str >= pObj->tail ) { // yes, no separation element found, maybe, it is not complete entity. // calculate the entity length: // LENGTH = WHOLE_BUFFER_LENGTH // To indicate the entity length as a whole buffer length, // ... do not change the @tail pointer since it refers to // ... the end of the buffer. (void)pObj->type; // do not assign the entity type, it is not identified yet. // check if the end-of-message indicator is set: if( pObj->bEndMessage ) { pObj->error = SCPI_ERROR_COMMAND_ERROR; pObj->pErrorMsg = SCPI_ERROR_COMMAND_ERROR_MSG; // no more data available: error status = eScpiParserStatus_failed; } else { // need more data to analyze: status = eScpiParserStatus_need_data; } } else // check if at least one matching character found: if( pObj->parseProgramMnemonic.FirstAlpha ) { // assign the entity type pObj->type = eScpiEntityTypeProgramMnemonic; // calculate the entity length: // LENGTH = @str - @head // Assign the @tail pointer to indicate the entity length: pObj->tail = pObj->str; // assign the @tail pointer to the current iterator status = eScpiParserStatus_success; // success: program mnemonic has been found // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; } else { pObj->error = SCPI_ERROR_COMMAND_ERROR; pObj->pErrorMsg = SCPI_ERROR_COMMAND_ERROR_MSG; status = eScpiParserStatus_failed; // error: no matching characters found } } L_parseProgramMenmonic_EXIT: return status; } // ================================================================================================================= // @parseCharacter // "Parser-function", parses a SCPI character (CHARACTER PROGRAM DATA) // References: // "7.7.1 ", [1] // "7.7.1.2 Encoding Syntax", [1] // Parameters: // @xObj - parser context, must be prepared by @prepareParserContext // ... before each call. If it is needed to continue the processing, the // ... @bKeepContext parameter duing the call @prepareParserContext must // ... be set to 'true', and otherwise, to restart parsing the parameter // ... must be set to 'false'. // Returns: // eScpiParserStatus_failed - error, can not parse data; // eScpiParserStatus_success - success, the string has been successfully parsed; // eScpiParserStatus_need_data - warning, the string can not be parsed due to // there no enough data to process. eScpiParserStatus_t parseCharacter( xParseEntry_t * xObj ) { sParseEntry_t * pObj = (sParseEntry_t*)xObj; // In accordance with "7.7.1 ", [1], the character // ... entity shall be parsed the same way as program mnemonic entity. eScpiParserStatus_t status = parseProgramMenmonic( xObj ); if( eScpiParserStatus_success == status ) { // override the entity type: pObj->type = eScpiEntityTypeCharacter; } return status; } // ================================================================================================================= // @parseArray // "Parser-function", parses a SCPI array (ARRAY PROGRAM DATA) // References: // "7.7.1 ", [1] // "7.7.1.2 Encoding Syntax", [1] // Parameters: // @xObj - parser context, must be prepared by @prepareParserContext // ... before each call. If it is needed to continue the processing, the // ... @bKeepContext parameter duing the call @prepareParserContext must // ... be set to 'true', and otherwise, to restart parsing the parameter // ... must be set to 'false'. // Returns: // eScpiParserStatus_failed - error, can not parse data; // eScpiParserStatus_success - success, the string has been successfully parsed; // eScpiParserStatus_need_data - warning, the string can not be parsed due to // there no enough data to process. eScpiParserStatus_t parseArrayNumber( xParseEntry_t * xObj ) { sParseEntry_t * pObj = (sParseEntry_t*)xObj; // In accordance with "7.7.1 ", [1], the character // ... entity shall be parsed the same way as program mnemonic entity. eScpiParserStatus_t status = parseProgramMenmonic( xObj ); if( eScpiParserStatus_success == status ) { // override the entity type: pObj->type = eScpiEntityTypeCharacter; } return status; } // ================================================================================================================= // @parseCommandProgramHeader // "Parser-function", parses a SCPI Command/Query Program Header (, ) // References: // "7.6.1 ", [1] // "7.6.1.2 Encoding Syntax", [1] // "7.6.2 ", [1] // "7.6.2.2 Encoding Syntax", [1] // Parameters: // @xObj - parser context, must be prepared by @prepareParserContext // ... before each call. If it is needed to continue the processing, the // ... @bKeepContext parameter duing the call @prepareParserContext must // ... be set to 'true', and otherwise, to restart parsing the parameter // ... must be set to 'false'. // Returns: // eScpiParserStatus_failed - error, can not parse data; // eScpiParserStatus_success - success, the string has been successfully parsed; // eScpiParserStatus_need_data - warning, the string can not be parsed due to // ... there no enough data to process. // Notes: All white spaces before the found command program header is skipped, // ... the @pxEntityBegin parameter during @getParsedEntityDetails call will refer // ... to the command program header beginning. eScpiParserStatus_t parseCommandProgramHeader( xParseEntry_t * xObj ) { sParseEntry_t * pObj = (sParseEntry_t*)xObj; eScpiParserStatus_t status = eScpiParserStatus_invalid; if( NULL != pObj && NULL != pObj->str && 0 != ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->head) && (0 < ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->str)) ) { status = eScpiParserStatus_need_data; // forward status set: the default 'eScpiParserStatus_invalid' shall // ... not be returned due to the context will be changed. // if the context has been cleaned up if( ! pObj->bContextKept ) { // initialize private context variables (void)pObj->parseCommandProgramHeader.FirstAlpha; // do not touch (void)pObj->parseCommandProgramHeader.CharsNumber; // do not touch pObj->parseCommandProgramHeader._tail = NULL; // it will be initialzed later pObj->parseCommandProgramHeader.LastCallContinue = false; pObj->parseCommandProgramHeader.LastCallSuccess = false; pObj->parseCommandProgramHeader.CommonForm = false; pObj->parseCommandProgramHeader.LastCharacter = ' '; // Rule 1: initialize the @LastCharacter to the // ... any character, but not a semicolon character. // Rule 2: initialize the @LastCharacter to the // ... white character to indicate that no non-white // ... character has been faced } // walk til the end of the buffer while( pObj->str != pObj->tail ) { char character = *(pObj->str); // DO NOT INCREMENT ITERATOR // skip white characters: if( scpi_iswhite( character ) ) { // In accordance with "7.6.1.2 ": // Check if the last character is a white character: if( scpi_iswhite( pObj->parseCommandProgramHeader.LastCharacter ) ) { // yes, thus no Non-white character faced yet -> any amount // ... of white-characters can be skipped in the beginning. pObj->parseCommandProgramHeader.LastCharacter = character; pObj->str++; // skip white character pObj->head = pObj->str; // update @head to skip white-spaces continue; } if( scpi_ismsgsep( pObj->parseCommandProgramHeader.LastCharacter ) ) { // this white character can be skipped because the previous was a // command separator pObj->parseCommandProgramHeader.LastCharacter = character; pObj->str++; // skip separator pObj->head = pObj->str; // update @head to skip separator continue; } } if( scpi_ismsgsep( character ) ) { if( scpi_ismsgsep( pObj->parseCommandProgramHeader.LastCharacter ) || scpi_iswhite( pObj->parseCommandProgramHeader.LastCharacter ) ) { pObj->parseCommandProgramHeader.LastCharacter = character; pObj->str++; // skip separator character pObj->head = pObj->str; // update @head to skip character continue; } } // In accordance with "7.6.1 ", [1], the command // ... program mnemonic entity shall be parsed the similar way as program // ... mnemonic entity. Thus this implementation uses @parseProgramMenmonic // ... as a part. // // Need to preserve the @tail pointer to recover it after the call: pObj->parseCommandProgramHeader._tail = pObj->tail; // preserve @tail // Need to preserve the @head pointer to recover it after the call: pObj->parseCommandProgramHeader._head = pObj->head; // preserve @head // Nested @parseProgramMenmonic call uses it's own context needed to be kept // between calls. If the last call failed, the context required to be reset. // Reset @bContextKept indicator if the last call failed: pObj->bContextKept = pObj->parseCommandProgramHeader.LastCallContinue; // It is required to pass the character to the @parseProgramMenmonic() even // ... it is a separator character to terminate @parseProgramMenmonic's context. // Check if the @parseProgramMenmonic() have enough data to recognize entity: if( eScpiParserStatus_need_data == (status = parseProgramMenmonic( xObj ), status ) ) { // The processed chunk is a part of program mnemonic. // Not enough data for complete recognition. // The length of incomleted entity is a whole buffer size, // ... it is need to assign the @tail to indicate the entity length. // In this case the assigning the @tail is useless due to it is // ... already refers to the end of the buffer (@parseProgramMenmonic // ... call hasn't change it). (void)pObj->tail; // keep the @status code (void)status; // Remember the last chacracter processed. // It is required to retrieve the last character by referring @str iterator. // @str has been incremented since the last @parseProgramMenmonic call. pObj->parseCommandProgramHeader.LastCharacter = *(pObj->str - 1); // set the last-call indicator. // It helps to determine if at least one call has been performed without error pObj->parseCommandProgramHeader.LastCallContinue = true; // set last-call indicator // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; break; } else if( eScpiParserStatus_success == status ) { // Last call of @parseProgramMenmonic found progrma mnemonic as a part of command program header // The @str iterator refers to the next character after the last mnemonic character // reset the last-call indicator. // It helps to determine if it is requried to reset the context before call. // As soon as a command mnemonic found, it is requried to restart the context pObj->parseCommandProgramHeader.LastCallContinue = false; // reset last-call indicator // Remember that at least last call was succeeded, // ... this fact will be helpful if the call returns 'eScpiParserStatus_faliled': pObj->parseCommandProgramHeader.LastCallSuccess = true; // set last-call success indicator // @str iterator already refers to the next character to continue processing. (void)pObj->str; // Check if the end of the buffer is reached: if( pObj->parseCommandProgramHeader._tail <= pObj->str ) { // Yes, the end of the buffer is reached // Set 'eScpiParserStatus_success' status (void)status; // @status already is eScpiParserStatus_success // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; break; } // The entity had been recognized successfully, but it is not end of the buffer. // Recover @tail before the next call: pObj->tail = pObj->parseCommandProgramHeader._tail; // Remember the last chacracter processed. // It is required to retrieve the last character by referring @str iterator. // @str has been incremented since the last @parseProgramMenmonic call. pObj->parseCommandProgramHeader.LastCharacter = *(pObj->str - 1); } else // eScpiParserStatus_failed (the code 'eScpiParserStatus_invalid' is not considered as possible) { // Need to restore the @tail pointer to recover it after the unsuccessful call: pObj->tail = pObj->parseCommandProgramHeader._tail; // restore @tail // Need to restore the @head pointer to recover it after the unsuccessful call: pObj->head = pObj->parseCommandProgramHeader._head; // restore @head // The @parseProgramMenmonic call failed, maybe it is a separator? if( scpi_iscmdsep( character ) ) // character ':' { // Check if the command program header is expected in common form: // In common form the program header can not be split up by semicolons. if( ! pObj->parseCommandProgramHeader.CommonForm ) { // Compound command program header detected. // Yes, it is a program mnemonic separator // Check if this separator is the only separator by checking last character: if( scpi_iscmdsep( pObj->parseCommandProgramHeader.LastCharacter ) ) // character ':' { // Last character is a colon-character too. // error: double separator detected status = eScpiParserStatus_failed; pObj->error = SCPI_ERROR_INVSEP_ERROR; pObj->pErrorMsg = SCPI_ERROR_INVSEP_ERROR_MSG; break; } else { // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; // Last character is not a colon-character (maybe, there was no last character at all). // Ok, it is a single program mnemonic separator. // Let's remember this character and continue processing. // @str is already incremented. pObj->parseCommandProgramHeader.LastCharacter = character; // remember the last processed character // warning: need more data to determine if this character is a leading // ... semicolon or not. status = eScpiParserStatus_need_data; } } else { // Error: common command program header can not contain semicolons status = eScpiParserStatus_failed; // error pObj->error = SCPI_ERROR_COMMAND_HEADER_SEP; pObj->pErrorMsg = SCPI_ERROR_COMMAND_HEADER_SEP_MSG; break; } } else // ':' !== character if( scpi_commonhdrind( character ) ) // check for leading '*' ("common query program header", 7.6.1.2 Encoding Syntax, [1]) { // Since the command program header can not contain white-character as described in 7.6 [1], // ... if the @LastCharacter is a white-character then this character is the first header character // processed. The Asterisk character in the beginnning of the command program header means that this // ... is a common command program header. Let's check it: if( scpi_iswhite(pObj->parseCommandProgramHeader.LastCharacter) || scpi_ismsgsep(pObj->parseCommandProgramHeader.LastCharacter) ) { // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; // Let's remember this character and continue processing. pObj->parseCommandProgramHeader.LastCharacter = character; // remember the last processed character // Set the common form indicator pObj->parseCommandProgramHeader.CommonForm = true; // common program header is expected // warning: need more data to determine if this character is a legal Asterisk character // ... in the beginning of the command program header. status = eScpiParserStatus_need_data; } else { // Asterisk character detected inside the command program header (not in the beginning); // Asterisk character is illeagal inside the command program header. status = eScpiParserStatus_failed; pObj->error = SCPI_ERROR_COMMAND_HEADER_SEP; // missing separator? pObj->pErrorMsg = SCPI_ERROR_COMMAND_HEADER_SEP_MSG; break; } } else // '*' !== character if( scpi_queryind( character ) ) // check for ending '?' ("common query program header", 7.6.2.2 Encoding Syntax, [1]) { // Since the command program header can not contain null-character as described in 7.6 [1], // ... if the @LastCharacter is '\0' then this character is the first character processed. // If the question mark character is faced in the end of the entity, it is allowed (7.6.2, [1]). // If the @LastCharacter is not null and the @LastCallSuccess is true, it seems to be ending of // ... the entity, but it's required to analyze next character. if( '\0' != pObj->parseCommandProgramHeader.LastCharacter && (!scpi_queryind(pObj->parseCommandProgramHeader.LastCharacter)) && (!scpi_iscmdsep(pObj->parseCommandProgramHeader.LastCharacter)) && pObj->parseCommandProgramHeader.LastCallSuccess ) { // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; // need to continue analyzing to determine if this character is legal. status = eScpiParserStatus_need_data; // Let's remember this character and continue processing. pObj->parseCommandProgramHeader.LastCharacter = character; // remember the last processed character } else { // error: illegal character faced status = eScpiParserStatus_failed; pObj->head = (pObj->str - 1); // update head to identify the character pObj->tail = pObj->str; pObj->error = SCPI_ERROR_INVALID_CHARACTER; pObj->pErrorMsg = SCPI_ERROR_INVALID_CHARACTER_MSG; break; } } else // '?' !== character // 7.4 Separator Functional Elements", [1] // -check for terminating character '\n' (7.5 , [1]) // -check for white character // -check for program message unit separator (7.4.1 , [1]) if( scpi_isnl( character ) || scpi_iswhite( character ) || scpi_ismsgsep( character ) ) { // it is not a program mnemonic separator => it is a message terminator // Check if the last @parseProgramMenmonic was successeded? // Check if the last processed character is not a colon character? if( pObj->parseCommandProgramHeader.LastCallSuccess && ( !scpi_iscmdsep(pObj->parseCommandProgramHeader.LastCharacter)) ) { // yes, the last program mnemonic has been found successfully. // Calculate the entity length: // LENGTH = @str - 1; // minus one due to the last call have incremented it pObj->tail = pObj->str - 1; // It is end of command program header. status = eScpiParserStatus_success; // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; } else { pObj->tail = pObj->str - 1; // store the @tail pointer to help the caller to identify the NL-character pObj->error = SCPI_ERROR_COMMAND_HEADER; // no header? pObj->pErrorMsg = SCPI_ERROR_COMMAND_HEADER_MSG; status = eScpiParserStatus_failed; } break; } else // '\n' !== character, undefined character { // store the @tail pointer to help the caller to identify the invalid character pObj->tail = pObj->str; if( pObj->parseCommandProgramHeader.LastCallSuccess && (pObj->error != SCPI_ERROR_SUCCESS) ) { pObj->head = (pObj->str - 1); pObj->error = SCPI_ERROR_INVALID_CHARACTER; pObj->pErrorMsg = SCPI_ERROR_INVALID_CHARACTER_MSG; } status = eScpiParserStatus_failed; break; } } } // while(...) if( eScpiParserStatus_success == status ) { // override the entity type: // If the last processed character is question mark character, // ... the entity type is the "query program header" if( scpi_queryind( pObj->parseCommandProgramHeader.LastCharacter ) ) { // Check for "Common form": if( pObj->parseCommandProgramHeader.CommonForm ) { pObj->type = eScpiEntityTypeCmnQueryProgHdr; // Common form } else { pObj->type = eScpiEntityTypeCmpQueryProgHdr; // Simple or Compound form } } else // else: the "command program header" { // Check for "Common form": if( pObj->parseCommandProgramHeader.CommonForm ) { pObj->type = eScpiEntityTypeCmnCommandProgHdr; // Common form } else { pObj->type = eScpiEntityTypeCmpCommandProgHdr; // Simple or Compound form } } } else if( eScpiParserStatus_need_data == status ) // if it is requried more data to analyze: { // check if the end-of-message indicator is set: if( pObj->bEndMessage ) { pObj->error = SCPI_ERROR_COMMAND_HEADER; // no header, imcomplete string? pObj->pErrorMsg = SCPI_ERROR_COMMAND_HEADER_MSG; // no more data available: error status = eScpiParserStatus_failed; } } } // L_parseCommandProgramHeader_EXIT: return status; } // ================================================================================================================= // @parseDemicalNumber // "Parser-function", parses a SCPI Demical Numeric Program Data () // References: // "7.7.2 ", [1] // "7.7.2.2 Encoding Syntax", [1] // Parameters: // @xObj - parser context, must be prepared by @prepareParserContext // ... before each call. If it is needed to continue the processing, the // ... @bKeepContext parameter duing the call @prepareParserContext must // ... be set to 'true', and otherwise, to restart parsing the parameter // ... must be set to 'false'. // Returns: // eScpiParserStatus_failed - error, can not parse data; // eScpiParserStatus_success - success, the string has been successfully parsed; // eScpiParserStatus_need_data - warning, the string can not be parsed due to // there no enough data to process. static eScpiParserStatus_t parseDemicalNumber( xParseEntry_t * xObj ) { sParseEntry_t * pObj = (sParseEntry_t*)xObj; eScpiParserStatus_t status = eScpiParserStatus_invalid; if( NULL != pObj && NULL != pObj->str && 0 != ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->head) && (0 < ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->str)) ) { status = eScpiParserStatus_need_data; // forward status set: the default 'eScpiParserStatus_invalid' shall // ... not be returned due to the context will be changed. // if the context has been cleaned up if( ! pObj->bContextKept ) { // "7.7.2.2 Encoding Syntax", [1]: pObj->parseDemicalNumber.parseStage = false; // false - search for mantissa pObj->parseDemicalNumber.signAllowed = true; // sign - allowed pObj->parseDemicalNumber.dotAllowed = true; // dot - allowed pObj->parseDemicalNumber.digitAllowed = true; // digit - allowed pObj->parseDemicalNumber.whiteAllowed = false; // white - disallowed pObj->parseDemicalNumber.expAllowed = false; // exp - disallowed pObj->parseDemicalNumber.termAllowed = false; // msgsep - disallowed pObj->parseDemicalNumber.dotFound = false; // reset dot indicator pObj->parseDemicalNumber.manitssaOk = false; // reset mantissa indicator pObj->parseDemicalNumber.exponentOk = false; // reset exponent indicator } // walk til the end of the buffer or message separator is faced while( pObj->str != pObj->tail ) { char character = *(pObj->str++); // Check if the sign-character is allowed in current state: if( pObj->parseDemicalNumber.signAllowed ) { // Check if the character is a sign-character: if( '-' == character || '+' == character ) { if( ! pObj->parseDemicalNumber.parseStage ) // search for mantissa { // Only digits and dot-character are allowed after the sign-character for mantissa // since the sign-character faced, it shall be disallowed. pObj->parseDemicalNumber.signAllowed = false; // since the sign-character faced, white-character shall be disallowed. pObj->parseDemicalNumber.whiteAllowed = false; // since the sign-character faced, dot-character shall be allowed. pObj->parseDemicalNumber.dotAllowed = true; // since the sign-character faced, exp-character shall be disallowed. pObj->parseDemicalNumber.expAllowed = false; // * since the sign-character faced, digits shall be allowed. pObj->parseDemicalNumber.digitAllowed = true; // * since the sign-character faced, message unit separator is disallowed pObj->parseDemicalNumber.termAllowed = false; } else // search for exponent { // Only digits are allowed after the sign-character for exponent // since the sign-character faced, it shall be disallowed. pObj->parseDemicalNumber.signAllowed = false; // since the sign-character faced, white-character shall be disallowed. pObj->parseDemicalNumber.whiteAllowed = false; // since the sign-character faced, dot-character shall be disallowed. pObj->parseDemicalNumber.dotAllowed = false; // since the sign-character faced, exp-character shall be disallowed. pObj->parseDemicalNumber.expAllowed = false; // * since the sign-character faced, digits shall be allowed. pObj->parseDemicalNumber.digitAllowed = true; // * since the sign-character faced, message unit separator is disallowed pObj->parseDemicalNumber.termAllowed = false; } // skip character continue; } } // Check if the dot-character is allowed in current state: if( pObj->parseDemicalNumber.dotAllowed ) { // Check if the character is a dot-character: if( '.' == character ) { // dot faced pObj->parseDemicalNumber.dotFound = true; if( ! pObj->parseDemicalNumber.parseStage ) // search for mantissa { // Digits, white-character and exp-character are allowed after the dot-character in mantissa. // since the dot-character faced, it shall be disallowed. pObj->parseDemicalNumber.dotAllowed = false; // since the dot-character faced, sign-character shall be disallowed. pObj->parseDemicalNumber.signAllowed = false; // since the dot-character faced, white-character shall be allowed, // ... (switching to exponent mode) pObj->parseDemicalNumber.whiteAllowed = true; // * since the dot-character faced, digits shall be allowed. pObj->parseDemicalNumber.digitAllowed = true; // since the dot-character faced, exp-character shall be allowed, // ... (switching to exponent mode) pObj->parseDemicalNumber.expAllowed = true; // * since the dot-character faced, message unit separator is disallowed pObj->parseDemicalNumber.termAllowed = false; } // skip character continue; } } // Check if the digit-character is allowed in current state: if( pObj->parseDemicalNumber.digitAllowed ) { // Check if the character is a digit-character: if( scpi_isdigit( character ) ) { if( ! pObj->parseDemicalNumber.parseStage ) // search for mantissa { // Digits, dot-character, exp-character and white-character are allowed after digit in mantissa // since the digit-character faced, sign-character shall be disallowed. pObj->parseDemicalNumber.signAllowed = false; // since the digit-character faced, white-character shall be allowed, // ... (switching to exponent mode) pObj->parseDemicalNumber.whiteAllowed = true; // since the digit-character faced, exp-character shall be allowed, // ... (switching to exponent mode) pObj->parseDemicalNumber.expAllowed = true; // * since the digit-character faced, dot-character shall be allowed. #if 0 // false-positive: "-3.1.e-1", second dot shall not be allowed after digit '1' pObj->parseDemicalNumber.dotAllowed = true; #else (void)pObj->parseDemicalNumber.dotAllowed; // already enabled since start #endif // * since the digit-character faced, message unit separator is allowed pObj->parseDemicalNumber.termAllowed = true; // since at least one digit faced in mantissa, the mantissa is OK pObj->parseDemicalNumber.manitssaOk = true; (void)pObj->parseDemicalNumber.digitAllowed; // already enabled } else // 2 - search for exponent; { // Only digits are allowed after digit in exponent or message separator // since the digit-character faced, sign-character shall be disallowed. pObj->parseDemicalNumber.signAllowed = false; // since the digit-character faced, dot-character shall be disallowed. pObj->parseDemicalNumber.dotAllowed = false; // since the digit-character faced, white-character shall be disallowed (note*). pObj->parseDemicalNumber.whiteAllowed = false; // since the digit-character faced, sign-character shall be disallowed. pObj->parseDemicalNumber.signAllowed = false; // since the digit-character faced in exponent, message unit separator is allowed // Note*: @termAllowed covers a white-characters too only if @exponentOk is set. pObj->parseDemicalNumber.termAllowed = true; (void)pObj->parseDemicalNumber.digitAllowed; // already enabled // As soon as first digit faced in exponent, the exponent can be // ... considered as a valid exponent. pObj->parseDemicalNumber.exponentOk = true; } // skip character continue; } } // Check if the message-unit-separator-character is allowed in current state: if( pObj->parseDemicalNumber.termAllowed ) { // Check if the character is: // - a message-unit separator; // - a data-unit separator; // - a message-unit terminator; // - a white character (a kind of terminating character only if no entities expected more); if( scpi_isdatasep( character ) || scpi_ismsgsep( character ) || scpi_isnl( character ) || (scpi_iswhite( character ) && pObj->parseDemicalNumber.exponentOk) ) { if( ! pObj->parseDemicalNumber.parseStage ) // search for mantissa { // Facing the separator-character in mantissa means end of number: pObj->str--; // roll back the iterator to previous mantissa character break; // interrupt processing } else // search for exponent { // In general the separator-character in exponent is allowable only if @exponentOk is true. // But if message separator found and no valid exponend anounced, it is required to consider // found entity as a number containing only mantissa. // only if no exponent is found if( ! pObj->parseDemicalNumber.exponentOk ) // switch stage back to mantissa mode pObj->parseDemicalNumber.parseStage = false; // guarantees the number is valid { // discard all found white spaces after mantissa: pObj->str--; // roll back iterator to the separator character pObj->str--; // roll back iterator to the character before the separator while( scpi_iswhite( *pObj->str ) ) pObj->str--; // discard white characters found pObj->str++; // skip the last mantissa character } break; // interrupt processing } } } // Check if the white-character is allowed in current state: if( pObj->parseDemicalNumber.whiteAllowed ) { // Check if the character is a white-character: if( scpi_iswhite( character ) ) { if( ! pObj->parseDemicalNumber.parseStage ) // search for mantissa { // Due to facing the white-character in mantissa means switching to // ... searching-for-exponent mode, only exp-character is allowed: // since the white-character faced, the stage shall be changed: pObj->parseDemicalNumber.parseStage = true; // search for exponent // since the white-character faced, exp-character shall be allowed. pObj->parseDemicalNumber.expAllowed = true; // since the white-character faced, dot-character shall be disallowed. pObj->parseDemicalNumber.dotAllowed = false; // since the white-character faced, sign-character shall be disallowed. pObj->parseDemicalNumber.signAllowed = false; // since the white-character faced, digits shall be disallowed. pObj->parseDemicalNumber.digitAllowed = false; // since the mode is being changed, the mantissa has been processed (void)pObj->parseDemicalNumber.manitssaOk; // already set to true in digit-handler (void)pObj->parseDemicalNumber.whiteAllowed; // already enabled } else // search for exponent { // Only digits, sign-character and exp-character are allowed in exponent // since the white-character faced, sign-character shall be allowed in exponent, // ... but white-character may be faced either before and after exp-character, thus // ... do not change the allowing of sign-character here: (void)pObj->parseDemicalNumber.signAllowed; // do not touch // * since the white-character faced, dot-character shall be disallowed. pObj->parseDemicalNumber.dotAllowed = false; // already disabled ealier // Due to white-character may be faced either before and after the exp-character, // ... the digit-character allowing shall not be changed here, but it shall be // ... enabled on either the dot-character and digit-character handler. (void)pObj->parseDemicalNumber.digitAllowed; // do not touch // Due to white-character may be faced either before and after the exp-character, // ... the exp-character allowing shall not be changed here, but it shall be // ... enabled on either the dot-character and digit-character handler. (void)pObj->parseDemicalNumber.expAllowed; // do not touch (void)pObj->parseDemicalNumber.whiteAllowed; // already enabled } // skip character continue; } } // Check if the white-character is allowed in current state: if( pObj->parseDemicalNumber.expAllowed ) { // Check if the character is a digit-character: if( 'E' == character || 'e' == character ) { if( ! pObj->parseDemicalNumber.parseStage ) // search for mantissa { // Since the exp-character is faced in mantissa the mode shall be // ... changed to exponent mode. Thus only digits, white-character // ... and sign-character are allowed. // since the exp-character faced, the stage shall be changed: pObj->parseDemicalNumber.parseStage = true; // search for exponent // since the exp-character faced, it shall be disallowed. pObj->parseDemicalNumber.expAllowed = false; // since the exp-character faced, sign-character shall be allowed. pObj->parseDemicalNumber.signAllowed = true; // since the exp-character faced, dot-character shall be disallowed. pObj->parseDemicalNumber.dotAllowed = false; // * since the exp-character faced, white-character shall be allowed. pObj->parseDemicalNumber.whiteAllowed = true; // * since the exp-character faced, digits shall be allowed. pObj->parseDemicalNumber.digitAllowed = true; } else // possibly if white-character faced after mantissa: { // since the exp-character faced, the stage shall be changed: (void)pObj->parseDemicalNumber.parseStage; // already set // since the exp-character faced, it shall be disallowed. pObj->parseDemicalNumber.expAllowed = false; // since the exp-character faced, dot-character shall be disallowed. pObj->parseDemicalNumber.dotAllowed = false; // since the exp-character faced, white-character shall be allowed, // ... but this situation is only possible if white-characters has // ... been already faced before exp-character, thus white-character // ... is already allowed. (void)pObj->parseDemicalNumber.whiteAllowed; // already set // * since the exp-character faced, digits shall be allowed. pObj->parseDemicalNumber.digitAllowed = true; // since the exp-character faced, sign-character shall be allowed. pObj->parseDemicalNumber.signAllowed = true; } // * since the exp-character faced, terminating characters are disallowed pObj->parseDemicalNumber.termAllowed = false; // skip character continue; } } // unsupported character faced: break with 'failed' status pObj->error = SCPI_ERROR_INVALID_NUMERIC_CHAR; pObj->pErrorMsg = SCPI_ERROR_INVALID_NUMERIC_CHAR_MSG; pObj->head = pObj->str - 1; pObj->tail = pObj->str; status = eScpiParserStatus_failed; break; } // If no unsupported character faced (just buffer ran out): eScpiParserStatus_need_data and // ... end-of-message indicator is set, or: // ... if unsupported character faced: eScpiParserStatus_failed if( eScpiParserStatus_failed == status || ( eScpiParserStatus_need_data == status && pObj->bEndMessage ) ) { if( eScpiParserStatus_failed == status ) { pObj->str--; // roll back the iterator to previous supported character } // easy... Let's check the mantissa: if( pObj->parseDemicalNumber.manitssaOk ) { // the mantissa is ok, then let's check // ... the exponent part only if the parse stage is search-for-exponent: if( pObj->parseDemicalNumber.parseStage ) { // exponent expected: // Let's check if valid exponent found: if( pObj->parseDemicalNumber.exponentOk ) { // valid exponent found! // Calculate the entity length: // LENGTH = @str - @head // Set the length: pObj->tail = pObj->str; pObj->type = eScpiEntityTypeDemicalExpNumber; // set the entity type: mantissa+exponent status = eScpiParserStatus_success; // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; } else { /* no valid mantissa found -> error */ } } else { // no exponent even announced, just end of mantissa! // Calculate the entity length: // LENGTH = @str - @head // Set the length: pObj->tail = pObj->str; if( pObj->parseDemicalNumber.dotFound ) pObj->type = eScpiEntityTypeDemicalFloatNumber; // set the entity type: only mantissa else pObj->type = eScpiEntityTypeDemicalIntegerNumber; // set the entity type: integer (mantissa) // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; status = eScpiParserStatus_success; } } else { /* no valid mantissa found -> error */ } } else { /* Impossible: eScpiParserStatus_success; Useless: eScpiParserStatus_invalid */ } } return status; } // ================================================================================================================= static eScpiParserStatus_t parseNonDemicalNumber( xParseEntry_t * xObj ); static eScpiParserStatus_t parseDemicalNumber( xParseEntry_t * xObj ); // @parseNumber_fParsers[] // Array of parser functions to be used in attempts to parse numeric value by @parseNumber routine eScpiParserStatus_t (* const parseNumber_fParsers[2])( xParseEntry_t * xObj ) = { parseNonDemicalNumber , parseDemicalNumber // order is important! }; // ================================================================================================================= // @parseNumber // "Parser-function", parses a SCPI Demical Numeric Program Data and SCPI Hexademical/Octal/Binary Numeric Program Data // References: // "7.7.2 ", [1] // "7.7.2.2 Encoding Syntax", [1] // "7.7.4 ", [1] // "7.7.4.2 Encoding Syntax", [1] // Parameters: // @xObj - parser context, must be prepared by @prepareParserContext // ... before each call. If it is needed to continue the processing, the // ... @bKeepContext parameter duing the call @prepareParserContext must // ... be set to 'true', and otherwise, to restart parsing the parameter // ... must be set to 'false'. // Returns: // eScpiParserStatus_failed - error, can not parse data; // eScpiParserStatus_success - success, the string has been successfully parsed; // eScpiParserStatus_need_data - warning, the string can not be parsed due to // there no enough data to process. eScpiParserStatus_t parseNumber( xParseEntry_t * xObj ) { eScpiParserStatus_t status = eScpiParserStatus_failed; sParseEntry_t * pObj = (sParseEntry_t*)xObj; int32_t nParserIdx = 0; if( ! pObj->bContextKept ) { // backup parser entry point (save iterators) parserBackupContext( pObj, &pObj->parseNumber_xSaveObj ); // backup parser index (reset index in the storage) parserBackupContextValue( &pObj->parseNumber_xSaveObj, nParserIdx ); } else { // restore parser index parserRestoreContextValue( &pObj->parseNumber_xSaveObj, &nParserIdx ); } L_parseNumber_DO: // try parse switch( status = parseNumber_fParsers[nParserIdx]( xObj ), status ) { // parser need more data to decide case eScpiParserStatus_need_data: { // backup parser index (update index in the storage) parserBackupContextValue( &pObj->parseNumber_xSaveObj, nParserIdx ); // return original status to the caller (void)status; } break; case eScpiParserStatus_failed: { // parser failed // Check for special condition: if( parseNumber_fParsers[nParserIdx] == parseNonDemicalNumber ) if( pObj->parseNonDemicalNumber.foundNumericChar ) { // @parseNonDemicalNumber failed and the numeric system character was found. // Do not try to parse using @parseDemicalNumber to keep error code and condition. // Parsing using @parseDemicalNumber is impossible any way ('#' character) break; } // is there another parser? if( (1+nParserIdx) < cellsof(parseNumber_fParsers) ) { // yep, try this nParserIdx++; // backup parser context again (update index in the storage) parserBackupContextValue( &pObj->parseNumber_xSaveObj, nParserIdx ); // restore parser iterators parserRestoreContext( pObj, &pObj->parseNumber_xSaveObj ); // reset error status code pObj->error = SCPI_ERROR_SUCCESS; // try another parser goto L_parseNumber_DO; } // return original status to the caller (void)status; } break; case eScpiParserStatus_success: { // succeeded // return original status to the caller (void)status; } break; case eScpiParserStatus_invalid: { // wrong param // return original status to the caller (void)status; } break; default: // unexpected code status = eScpiParserStatus_invalid; my_assert( false ); } return status; } // ================================================================================================================= // @parseNonDemicalNumber // "Parser-function", parses a SCPI Hexademical/Octal/Binary Numeric Program Data () // References: // "7.7.4 ", [1] // "7.7.4.2 Encoding Syntax", [1] // Parameters: // @xObj - parser context, must be prepared by @prepareParserContext // ... before each call. If it is needed to continue the processing, the // ... @bKeepContext parameter duing the call @prepareParserContext must // ... be set to 'true', and otherwise, to restart parsing the parameter // ... must be set to 'false'. // Returns: // eScpiParserStatus_failed - error, can not parse data; // eScpiParserStatus_success - success, the string has been successfully parsed; // eScpiParserStatus_need_data - warning, the string can not be parsed due to // there no enough data to process. static eScpiParserStatus_t parseNonDemicalNumber( xParseEntry_t * xObj ) { sParseEntry_t * pObj = (sParseEntry_t*)xObj; eScpiParserStatus_t status = eScpiParserStatus_invalid; if( NULL != pObj && NULL != pObj->str && 0 != ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->head) && (0 < ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->str)) ) { status = eScpiParserStatus_need_data; // forward status set: the default 'eScpiParserStatus_invalid' shall // ... not be returned due to the context will be changed. // if the context has been cleaned up if( ! pObj->bContextKept ) { pObj->parseNonDemicalNumber.allow0_1 = false; pObj->parseNonDemicalNumber.allow0_7 = false; pObj->parseNonDemicalNumber.allow0_f = false; pObj->parseNonDemicalNumber.allowNumChar = false; pObj->parseNonDemicalNumber.foundNumericChar = false; pObj->parseNonDemicalNumber.digits = 0; } // walk til the end of the buffer or message separator is faced while( pObj->str != pObj->tail && (!scpi_ismsgsep(*pObj->str)) ) { char character = *(pObj->str++); // check if binary character are allowed: if( pObj->parseNonDemicalNumber.allow0_1 ) { // if the character fits to the filter: digits 0..1 if( '0' == character || '1' == character ) { pObj->parseNonDemicalNumber.digits++; // take the digit in account // skip character continue; } } // check if octal character are allowed: if( pObj->parseNonDemicalNumber.allow0_7 ) { // if the character fits to the filter: digits 0..7 if( '0' <= character && '7' >= character ) { pObj->parseNonDemicalNumber.digits++; // take the digit in account // skip character continue; } } // check if octal character are allowed: if( pObj->parseNonDemicalNumber.allow0_f ) { // if the character fits to the filter: hex-chars if( scpi_ishex( character) ) { pObj->parseNonDemicalNumber.digits++; // take the digit in account // skip character continue; } } // check if numeric system designator is allowed: if( ! pObj->parseNonDemicalNumber.allowNumChar ) { // not yet, thus the '#' is expected to detect the begining of the entity // check if the '#' character is faced: if( '#' == character ) { // yes, the entity start is detected pObj->parseNonDemicalNumber.allowNumChar = true; // set indicator: numeric system character found pObj->parseNonDemicalNumber.foundNumericChar = true; // pass to parseNumber // skip character continue; } status = eScpiParserStatus_failed; // invalid character faced pObj->error = SCPI_ERROR_INVALID_NUMERIC_CHAR; pObj->pErrorMsg = SCPI_ERROR_INVALID_NUMERIC_CHAR_MSG; pObj->head = pObj->str - 1; pObj->tail = pObj->str; break; } else { // yes, check for the numeric system designator only if no the numeric // ... system designator has been faced yet: if( !pObj->parseNonDemicalNumber.allow0_1 && !pObj->parseNonDemicalNumber.allow0_7 && !pObj->parseNonDemicalNumber.allow0_f ) { switch( character ) { case 'B': case 'b': pObj->parseNonDemicalNumber.allow0_1 = true; continue; case 'Q': case 'q': pObj->parseNonDemicalNumber.allow0_7 = true; continue; case 'H': case 'h': pObj->parseNonDemicalNumber.allow0_f = true; continue; } status = eScpiParserStatus_failed; // invalid character faced: unknown designator pObj->error = SCPI_ERROR_NUMERIC_DATAERROR; pObj->pErrorMsg = SCPI_ERROR_NUMERIC_DATAERROR_MSG; pObj->head = pObj->str - 1; pObj->tail = pObj->str; break; } else { // If @character here is a digit or a letter, it is probably invalid character in the number // Do not consider this character as an end of the number, instead of this it is better to generate an error. if( 0 == pObj->parseNonDemicalNumber.digits || isdigit(character) || isalpha(character) ) { // No one of @.allow0_* rules match, and no digits faced yet status = eScpiParserStatus_failed; // invalid character faced: pObj->error = SCPI_ERROR_INVALID_NUMERIC_CHAR; pObj->pErrorMsg = SCPI_ERROR_INVALID_NUMERIC_CHAR_MSG; pObj->head = pObj->str - 1; pObj->tail = pObj->str; break; } else { status = eScpiParserStatus_success; // the entity end found // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; // roll back the iterator due to current character is invalid pObj->str--; // exit with success status break; } } } } // check the conditions: // - if it is required more data and no more data is expected ( @bEndMessage is set) // - if the success status set if( (eScpiParserStatus_need_data == status && pObj->bEndMessage) || (eScpiParserStatus_success == status ) ) { // check if at least one numeric character found: if( 0 < pObj->parseNonDemicalNumber.digits ) { // set the entity length: // LENGTH = @str - @head pObj->tail = pObj->str; // set @tail to indicate the length // set the entity type if( pObj->parseNonDemicalNumber.allow0_1 ) { pObj->type = eScpiEntityTypeBinNumber; // binary pObj->head += 2; // skip '#B' } else if( pObj->parseNonDemicalNumber.allow0_7 ) { pObj->type = eScpiEntityTypeOctNumber; // octal pObj->head += 2; // skip '#Q' } else { pObj->type = eScpiEntityTypeHexNumber; // hexademical pObj->head += 2; // skip '#H' } status = eScpiParserStatus_success; } else { status = eScpiParserStatus_failed; // error, no one numeric character found pObj->error = SCPI_ERROR_NUMERIC_DATAERROR; pObj->pErrorMsg = SCPI_ERROR_NUMERIC_DATAERROR_MSG; } } } return status; } // ================================================================================================================= // @parseProgramDataSeparator // "Parser-function", parses a SCPI Program Data Separator // References: // "7.4 Separator Functional Elements", [1] // "7.4.2.2 Encoding Syntax", [1] // "7.5 ", [1] // Parameters: // @xObj - parser context, must be prepared by @prepareParserContext // ... before each call. If it is needed to continue the processing, the // ... @bKeepContext parameter duing the call @prepareParserContext must // ... be set to 'true', and otherwise, to restart parsing the parameter // ... must be set to 'false'. // Returns: // eScpiParserStatus_failed - error, can not parse data; // eScpiParserStatus_success - success, the string has been successfully parsed; // eScpiParserStatus_need_data - warning, the string can not be parsed due to // there no enough data to process. eScpiParserStatus_t parseProgramDataSeparator( xParseEntry_t * xObj ) { sParseEntry_t * pObj = (sParseEntry_t*)xObj; eScpiParserStatus_t status = eScpiParserStatus_invalid; if( NULL != pObj && NULL != pObj->str && 0 != ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->head) && (0 < ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->str)) ) { status = eScpiParserStatus_need_data; // forward status set: the default 'eScpiParserStatus_invalid' shall // ... not be returned due to the context will be changed. // if the context has been cleaned up if( ! pObj->bContextKept ) { pObj->parseProgramDataSeparator.sepFound = false; } // walk til the end of the buffer while( pObj->str != pObj->tail ) { char character = *(pObj->str++); // check for white-character if( scpi_iswhite( character ) ) { continue; // skip character } // check for NL-character if( scpi_isnl( character ) ) { pObj->parseProgramDataSeparator.sepFound = true; pObj->type = eScpiEntityType_Service_MsgTerm; status = eScpiParserStatus_success; // NL-character terminates Program Data entity pObj->head = (pObj->str - 1); pObj->tail = pObj->str; // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; break; } // check if the data separator character is found if( !pObj->parseProgramDataSeparator.sepFound ) { // no, search for data separator character if( scpi_isdatasep( character ) ) { pObj->parseProgramDataSeparator.sepFound = true; // found pObj->type = eScpiEntityType_Service_DataSep; pObj->head = (pObj->str - 1); pObj->tail = pObj->str; status = eScpiParserStatus_success; // valid character // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; break; // end of search } } pObj->head = (pObj->str - 1); pObj->tail = pObj->str; status = eScpiParserStatus_failed; // unexpected character pObj->error = SCPI_ERROR_INVSEP_ERROR; pObj->pErrorMsg = SCPI_ERROR_INVSEP_ERROR_MSG; break; } // check if it is required to receive more data to recognize: if( eScpiParserStatus_need_data == status ) { // check if this message is the last one? if( pObj->bEndMessage ) { // it is not required to the separator must be found // ... the last message's character is reached. // The end-of-message signal represents another data // ... separator, so thus, the data separator is found. status = eScpiParserStatus_success; pObj->type = eScpiEntityType_Service_MsgTerm; // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; } // Note: if no unexpected character has been faced yet it means that // ... the data separator still continues. } else // check if an unexpected character faced: if( eScpiParserStatus_failed == status ) { // check if the separator character has been found? if( pObj->parseProgramDataSeparator.sepFound ) { status = eScpiParserStatus_success; pObj->type = eScpiEntityType_Service_MsgTerm; // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; } // Note: if no separator character has been faced yet while an unexpected // ... character already faced it means that the data separator not found. } } return status; } // ================================================================================================================= // @parseProgramDataSeparator // "Parser-function", parses a SCPI Program Header Separator // References: // "7.4.3 , [1] // "7.4.3.2 Encoding Syntax", [1] // "7.5 ", [1] // Parameters: // @xObj - parser context, must be prepared by @prepareParserContext // ... before each call. If it is needed to continue the processing, the // ... @bKeepContext parameter duing the call @prepareParserContext must // ... be set to 'true', and otherwise, to restart parsing the parameter // ... must be set to 'false'. // Returns: // eScpiParserStatus_failed - error, can not parse data; // eScpiParserStatus_success - success, the string has been successfully parsed; // eScpiParserStatus_need_data - warning, the string can not be parsed due to // there no enough data to process. eScpiParserStatus_t parseProgramHeaderSeparator( xParseEntry_t * xObj ) { sParseEntry_t * pObj = (sParseEntry_t*)xObj; eScpiParserStatus_t status = eScpiParserStatus_invalid; if( NULL != pObj && NULL != pObj->str && 0 != ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->head) && (0 < ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->str)) ) { status = eScpiParserStatus_need_data; // forward status set: the default 'eScpiParserStatus_invalid' shall // ... not be returned due to the context will be changed. // if the context has been cleaned up if( ! pObj->bContextKept ) { pObj->parseProgramHeaderSeparator.sepFound = false; } // walk til the end of the buffer while( pObj->str != pObj->tail ) { char character = *(pObj->str); // check for white-character if( scpi_iswhite( character ) ) { if( ! pObj->parseProgramHeaderSeparator.sepFound ) { pObj->head = pObj->str; } pObj->parseProgramHeaderSeparator.sepFound = true; // at least one white-character found pObj->type = eScpiEntityType_Service_HdrSep; pObj->str++; // skip character continue; // skip character } // check for NL-character if( scpi_isnl( character ) ) { pObj->parseProgramHeaderSeparator.sepFound = true; // at least one white-character found pObj->type = eScpiEntityType_Service_MsgTerm; pObj->head = pObj->str - 1; pObj->tail = pObj->str; status = eScpiParserStatus_success; // NL character terminates the program header // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; break; } // check if an unexpected character found // check if at least one white-character found if( pObj->parseProgramHeaderSeparator.sepFound ) { // yes, it is success // At least white character found thus the separator found too status = eScpiParserStatus_success; pObj->type = eScpiEntityType_Service_HdrSep; (void)pObj->head; pObj->tail = pObj->str; // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; break; } pObj->head = (pObj->str - 1); pObj->tail = pObj->str; status = eScpiParserStatus_failed; // unexpected character found pObj->error = SCPI_ERROR_INVSEP_ERROR; pObj->pErrorMsg = SCPI_ERROR_INVSEP_ERROR_MSG; break; } // check if there no enough data to detect the end of separator: if( eScpiParserStatus_need_data == status ) { // Note: AT LEAST ONE WHITE CHARACTER FOUND due to the @eScpiParserStatus_need_data status set // check if this data portion is the last one: if( pObj->bEndMessage ) { // At least white character found thus the separator found too status = eScpiParserStatus_success; pObj->type = eScpiEntityType_Service_MsgTerm; pObj->tail = pObj->str; // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; } } } return status; } // ================================================================================================================= // @parseProgramMessageSeparator // "Parser-function", parses a SCPI Program Message Separator // References: // "7.4.1 ", [1] // "7.4.1.2 Encoding Syntax", [1] // "7.5 ", [1] // Parameters: // @xObj - parser context, must be prepared by @prepareParserContext // ... before each call. If it is needed to continue the processing, the // ... @bKeepContext parameter duing the call @prepareParserContext must // ... be set to 'true', and otherwise, to restart parsing the parameter // ... must be set to 'false'. // Returns: // eScpiParserStatus_failed - error, can not parse data; // eScpiParserStatus_success - success, the string has been successfully parsed; // eScpiParserStatus_need_data - warning, the string can not be parsed due to // there no enough data to process. eScpiParserStatus_t parseProgramMessageSeparator( xParseEntry_t * xObj ) { sParseEntry_t * pObj = (sParseEntry_t*)xObj; eScpiParserStatus_t status = eScpiParserStatus_invalid; if( NULL != pObj && NULL != pObj->str && 0 != ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->head) && (0 < ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->str)) ) { status = eScpiParserStatus_need_data; // forward status set: the default 'eScpiParserStatus_invalid' shall // ... not be returned due to the context will be changed. // if the context has been cleaned up if( ! pObj->bContextKept ) { pObj->parseProgramMessageSeparator.sepFound = false; } // walk til the end of the buffer while( pObj->str != pObj->tail ) { char character = *(pObj->str++); if( !pObj->parseProgramMessageSeparator.sepFound ) { // check for white-character if( scpi_iswhite( character ) ) { pObj->tail = pObj->str; continue; // skip character } // check for message-separator-character if( scpi_ismsgsep( character ) ) { pObj->parseProgramMessageSeparator.sepFound = true; pObj->type = eScpiEntityType_Service_MsgSep; pObj->head = pObj->str - 1; pObj->tail = pObj->str; break; // separator found } } pObj->head = (pObj->str - 1); pObj->tail = pObj->str; status = eScpiParserStatus_failed; // unexpected character pObj->error = SCPI_ERROR_INVSEP_ERROR; pObj->pErrorMsg = SCPI_ERROR_INVSEP_ERROR_MSG; break; } if( eScpiParserStatus_need_data == status ) { if( pObj->parseProgramMessageSeparator.sepFound ) { status = eScpiParserStatus_success; // separator found pObj->type = eScpiEntityType_Service_MsgTerm; // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; } } } return status; } // ================================================================================================================= // @parseEndOfCommand // "Parser-function", searches end of program command unit. // References: // "7.4.1 ", [1] // "7.4.1.2 Encoding Syntax", [1] // "7.5 ", [1] // Parameters: // @xObj - parser context, must be prepared by @prepareParserContext // ... before each call. If it is needed to continue the processing, the // ... @bKeepContext parameter duing the call @prepareParserContext must // ... be set to 'true', and otherwise, to restart parsing the parameter // ... must be set to 'false'. // Returns: // eScpiParserStatus_failed - error, can not parse data; // eScpiParserStatus_success - success, the string has been successfully parsed; // eScpiParserStatus_need_data - warning, the string can not be parsed due to // there no enough data to process. eScpiParserStatus_t parseEndOfCommand( xParseEntry_t * xObj ) { sParseEntry_t * pObj = (sParseEntry_t*)xObj; eScpiParserStatus_t status = eScpiParserStatus_invalid; if( NULL != pObj && NULL != pObj->str && 0 != ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->head) && (0 < ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->str)) ) { status = eScpiParserStatus_need_data; // forward status set: the default 'eScpiParserStatus_invalid' shall // ... not be returned due to the context will be changed. // if the context has been cleaned up if( ! pObj->bContextKept ) { pObj->parseEndOfCommand.sepFound = false; } // walk til the end of the buffer while( pObj->str != pObj->tail ) { char character = *(pObj->str++); if( !pObj->parseEndOfCommand.sepFound ) { // check for white-character if( scpi_iswhite( character ) ) { continue; // skip character } // check for message-separator-character if( scpi_ismsgsep( character ) ) { pObj->parseEndOfCommand.sepFound = true; pObj->head = (pObj->str - 1); pObj->tail = pObj->str; pObj->type = eScpiEntityType_Service_MsgSep; break; // separator found } // check for message-terminator-character if( scpi_isnl( character ) ) { pObj->parseEndOfCommand.sepFound = true; pObj->head = (pObj->str - 1); pObj->tail = pObj->str; pObj->type = eScpiEntityType_Service_MsgTerm; break; // separator found } } pObj->head = (pObj->str - 1); pObj->tail = pObj->str; status = eScpiParserStatus_failed; // unexpected character pObj->error = SCPI_ERROR_INVSEP_ERROR; pObj->pErrorMsg = SCPI_ERROR_INVSEP_ERROR_MSG; break; } if( eScpiParserStatus_need_data == status ) { if( pObj->parseEndOfCommand.sepFound ) { status = eScpiParserStatus_success; // separator found pObj->type = eScpiEntityType_Service_MsgTerm; // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; } } } return status; } // ================================================================================================================= // @parseWhiteSequence // "Parser-function", searches for white characters sequence or NL-terminator // References: // "7.4.1.2 Encoding Syntax", [1] // "7.5 ", [1] // Parameters: // @xObj - parser context, must be prepared by @prepareParserContext // ... before each call. If it is needed to continue the processing, the // ... @bKeepContext parameter duing the call @prepareParserContext must // ... be set to 'true', and otherwise, to restart parsing the parameter // ... must be set to 'false'. // Note: this function does not modify @str iterator if no white character found // Returns: // eScpiParserStatus_failed - error, can not parse data; // eScpiParserStatus_success - success, the string has been successfully parsed; // eScpiParserStatus_need_data - warning, the string can not be parsed due to // there no enough data to process. eScpiParserStatus_t parseWhiteSequence( xParseEntry_t * xObj ) { sParseEntry_t * pObj = (sParseEntry_t*)xObj; eScpiParserStatus_t status = eScpiParserStatus_invalid; if( NULL != pObj && NULL != pObj->str && 0 != ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->head) && (0 < ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->str)) ) { status = eScpiParserStatus_need_data; // forward status set: the default 'eScpiParserStatus_invalid' shall // ... not be returned due to the context will be changed. // if the context has been cleaned up if( ! pObj->bContextKept ) { pObj->parseWhiteSequence.whiteFound = false; pObj->parseWhiteSequence.nlFound = false; } // walk til the end of the buffer while( pObj->str != pObj->tail ) { char character = *(pObj->str); // check for white-character if( scpi_iswhite( character ) ) { pObj->parseWhiteSequence.whiteFound = true; pObj->str++; // skip white character (void)pObj->head; // keep @head pointer to point to the start of white-sequence continue; // skip character } // check for white-character if( scpi_isnl( character ) ) { pObj->parseWhiteSequence.nlFound = true; (void)pObj->str++; // skip NL-character (void)pObj->head; // keep @head pointer to point to the start of white-sequence break; // stop searching } if( !pObj->parseWhiteSequence.whiteFound ) { pObj->head = (pObj->str - 1); pObj->tail = pObj->str; status = eScpiParserStatus_failed; // unexpected character pObj->error = SCPI_ERROR_INVALID_CHARACTER; pObj->pErrorMsg = SCPI_ERROR_INVALID_CHARACTER_MSG; } break; // non-white character found } if( eScpiParserStatus_need_data == status ) { if( pObj->parseWhiteSequence.whiteFound ) { if( pObj->bEndMessage ) { // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; pObj->tail = pObj->str; // update the tail pointer pObj->type = eScpiEntityType_Service_White; status = eScpiParserStatus_success; } } else if( pObj->parseWhiteSequence.nlFound ) { // reset error code/status pObj->error = SCPI_ERROR_SUCCESS; pObj->pErrorMsg = NULL; pObj->tail = pObj->str; // update the tail pointer pObj->type = eScpiEntityType_Service_MsgTerm; status = eScpiParserStatus_success; } } } return status; } // ================================================================================================================= // @checkNotEndOfString // "Parser-function", checks if the parser context not reached the end of string // Parameters: // @xObj - parser context, must be prepared by @prepareParserContext // ... before each call. If it is needed to continue the processing, the // ... @bKeepContext parameter duing the call @prepareParserContext must // ... be set to 'true', and otherwise, to restart parsing the parameter // ... must be set to 'false'. // Returns: // eScpiParserStatus_failed - error, end-of-string is reached // eScpiParserStatus_success - success, end-of-string is not reached // eScpiParserStatus_invalid - error, invalid parameters eScpiParserStatus_t checkNotEndOfString( xParseEntry_t * xObj ) { sParseEntry_t * pObj = (sParseEntry_t*)xObj; eScpiParserStatus_t status = eScpiParserStatus_invalid; if( NULL != pObj && NULL != pObj->str && 0 <= ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->head) && 0 <= ((ptrdiff_t)pObj->str - (ptrdiff_t)pObj->head) ) { if( 0 < ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->str) ) { status = eScpiParserStatus_success; } else if( 0 == ((ptrdiff_t)pObj->tail - (ptrdiff_t)pObj->str) ) { status = eScpiParserStatus_failed; } } return status; } // ================================================================================================================= #pragma pack(push,1) static const struct { eScpiParserStatus_t (*f)( xParseEntry_t * xObj ); uint8_t type; } parseArgumentsMap[] = { { parseCharacter, eScpiArg_Character }, { parseString, eScpiArg_String }, { parseArbitraryBlock, eScpiArg_Datablock }, { parseNumber, eScpiArg_Numeric }, { parseDemicalNumber, eScpiArg_Numeric_Demical }, { parseNonDemicalNumber, eScpiArg_Numeric_NonDemical }, { parseDemicalNumber , eScpiArg_Array_Decimal } }; #pragma pack(pop) // ================================================================================================================= // @parseArguments // Common parser function to analyze and recognize command arguments using the // template preassigned by @DECLARE_SCPI_ARGS macro. // Parameters: // @xObj - parser context, must be prepared by @prepareParserContext // ... before each call. If it is needed to continue the processing, the // ... @bKeepContext parameter duing the call @prepareParserContext must // ... be set to 'true', and otherwise, to restart parsing the parameter // ... must be set to 'false'. // @argTokens - array of argument string tokens to store parameters iterators; // @argTypes - array of argument types [eScpiArgType_t] to set the parameters parsing methods; // @entityTypes - array of entity types [eScpiEntityType_t] to be written after successful call; optional; // @argN - number of arguments (including optional and mandatory ones); // @argOptionalN - number of optional only arguments; // @pArgParsed - pointer to the variable to store the number of successfully // ... parsed arguments, valid only if eScpiParserStatus_success status is returned, // ... Otherwise, if the status is eScpiParserStatus_failed this value indicates // ... the index of erroneous parameter. Can not be NULL. // Returns: // eScpiParserStatus_failed - error, can not parse data; // eScpiParserStatus_invalid - error, invalid parameters; // eScpiParserStatus_success - success, the string has been successfully parsed; // eScpiParserStatus_need_data - warning, the string can not be parsed due to // there no enough data to process. eScpiParserStatus_t parseArguments( xParseEntry_t * xObj, sStrToken_t * argTokens, const uint8_t * argTypes, uint8_t * entityTypes, size_t argN, size_t argOptionalN, uint8_t * pArgParsed ) { my_assert( xObj ); my_assert( argTokens ); my_assert( pArgParsed ); my_assert( argN >= argOptionalN ); my_assert( argTypes || argN == 0 ); sParseEntry_t * pObj = (sParseEntry_t*)xObj; eScpiParserStatus_t status = eScpiParserStatus_invalid; pObj->error = SCPI_ERROR_SYNTAX_ERROR; pObj->pErrorMsg = SCPI_ERROR_SYNTAX_ERROR_PARAM_MSG; // check parameters if( NULL == xObj && NULL == argTokens && (NULL == argTypes && 0 != argN) && NULL == pArgParsed && (argN < argOptionalN) ) { return eScpiParserStatus_invalid; } // special condition: no parameters are allowed if( 0 == argN ) { status = parseEndOfCommand( xObj ); if( status != eScpiParserStatus_success ) { pObj->error = SCPI_ERROR_PARAMETER_NOTALLOWED; pObj->pErrorMsg = SCPI_ERROR_PARAMETER_NOTALLOWED_MSG; } // no break, no return, 'for' will not be executed due to argN==0 } bool parseBreak = false; bool trailingDataSep = false; size_t nArgs = 0; for( size_t a = 0; (a < argN) && (!parseBreak); ++a ) { *pArgParsed = a; // indicate current parameter index (failed case) eScpiParserStatus_t (*fParser)( xParseEntry_t * xObj ) = NULL; // search for parser routine by the specified argument type for( size_t f = 0; f < sizeof(parseArgumentsMap)/sizeof(*parseArgumentsMap); ++f ) { if( parseArgumentsMap[f].type == argTypes[a] ) { // parser found fParser = parseArgumentsMap[f].f; break; } } if( NULL == fParser ) { // parser not found status = eScpiParserStatus_failed; break; } // keep tail pointer before searching for white characters const char * _tail = pObj->tail; // skip white characters status = parseWhiteSequence( xObj ); // check if NL-character found if( status == eScpiParserStatus_success ) { if( eScpiEntityType_Service_MsgTerm == pObj->type ) { goto L_parseArguments_TERM; } } // restore tail pointer before searching for parameter pObj->tail = _tail; // move forward @head pointer to skip white sequence pObj->head = pObj->str; // check for end-of-string if( eScpiParserStatus_success == checkNotEndOfString( xObj ) ) { // call the parser status = fParser( xObj ); // check result if( status != eScpiParserStatus_success ) { // parsing error // check for specific error case: message separator (intercept) if( SCPI_ERROR_INVALID_CHARACTER == pObj->error && scpi_ismsgsep( *pObj->head ) ) { // replace "error condition" -> "message termination" pObj->type = eScpiEntityType_Service_MsgTerm; status = eScpiParserStatus_success; goto L_parseArguments_TERM; } break; } else { trailingDataSep = false; } } else { // end of string: error condition pObj->type = eScpiEntityType_Service_MsgTerm; goto L_parseArguments_TERM; } argTokens[nArgs].shead = pObj->head; argTokens[nArgs].stail = pObj->tail; if( NULL != entityTypes ) { // write entity type to the output buffer entityTypes[nArgs] = pObj->type; } nArgs++; // increment number of parsed parameters // restore tail pointer before searching for separator pObj->tail = _tail; // move forward @head pointer to skip the separator pObj->head = pObj->str; // check for end-of-string if( eScpiParserStatus_success == checkNotEndOfString( xObj ) ) { status = parseProgramDataSeparator( xObj ); if( eScpiParserStatus_success != status ) { // check for specific error case: message separator (intercept) if( SCPI_ERROR_INVSEP_ERROR == pObj->error && scpi_ismsgsep( *pObj->head ) ) { // replace "error condition" -> "message termination" pObj->type = eScpiEntityType_Service_MsgTerm; pObj->error = SCPI_ERROR_SUCCESS; status = eScpiParserStatus_success; goto L_parseArguments_TERM; } break; } } else { pObj->type = eScpiEntityType_Service_MsgTerm; goto L_parseArguments_TERM; } L_parseArguments_TERM: switch( pObj->type ) { case eScpiEntityType_Service_MsgTerm: case eScpiEntityType_Service_MsgSep: { if( (nArgs) < argN - argOptionalN ) // not all mandatory parameters parsed { // error: missing parameter status = eScpiParserStatus_failed; pObj->head = pObj->tail = pObj->str = _tail; // reset all the token stats to not point to any token pObj->error = SCPI_ERROR_MISSING_PARAMETER; pObj->pErrorMsg = SCPI_ERROR_MISSING_PARAMETER_MSG; } else { // Check for trailing data separator if( trailingDataSep ) { // error: extra parameter status = eScpiParserStatus_failed; pObj->error = SCPI_ERROR_INVSEP_ERROR; pObj->pErrorMsg = SCPI_ERROR_INVSEP_ERROR_MSG; // reset all the token stats to not point to any token pObj->head = pObj->tail = pObj->str = _tail; } } (void)status; parseBreak = true; // message terminator faced } break; case eScpiEntityType_Service_DataSep: { if( nArgs >= argN ) // all parameters already parsed { // error: extra parameter status = eScpiParserStatus_failed; pObj->error = SCPI_ERROR_PARAMETER_NOTALLOWED; pObj->pErrorMsg = SCPI_ERROR_PARAMETER_NOTALLOWED_MSG; // reset all the token stats to not point to any token due to it is impossible // to get know where is the end of extra parameter in general case (e.g. if the string is passed). pObj->head = pObj->tail = pObj->str = _tail; parseBreak = true; } trailingDataSep = true; } break; default: status = eScpiParserStatus_failed; pObj->error = SCPI_ERROR_SYNTAX_ERROR; pObj->pErrorMsg = SCPI_ERROR_SYNTAX_ERROR_PARAM_MSG; } // Restore iterators only if parsing is continuing. // Otherwise it is required to leave @head/@tail pointing to the // ... last found entity to search next command correctly. if( ! parseBreak ) { // restore iterators before searching next entity pObj->head = pObj->str; pObj->tail = _tail; } *pArgParsed = (nArgs); // indicate number of parameters parsed (success state) } return status; } eScpiParserStatus_t parseArrayArguments( xParseEntry_t * xObj, uint8_t * argTokens, size_t * argNumber, const uint8_t * argTypes, uint8_t * entityTypes, size_t argN, size_t argOptionalN, uint8_t * pArgParsed ) { my_assert( xObj ); my_assert( argTokens ); my_assert( pArgParsed ); my_assert( argN >= argOptionalN ); my_assert( argTypes || argN == 0 ); my_assert( argNumber ); sParseEntry_t * pObj = (sParseEntry_t*)xObj; eScpiParserStatus_t status = eScpiParserStatus_invalid; pObj->error = SCPI_ERROR_SYNTAX_ERROR; pObj->pErrorMsg = SCPI_ERROR_SYNTAX_ERROR_PARAM_MSG; // check parameters if( NULL == xObj && NULL == argTokens && (NULL == argTypes && 0 != argN) && NULL == pArgParsed && (argN < argOptionalN) ) { return eScpiParserStatus_invalid; } // special condition: no parameters are allowed if( 0 == argN ) { status = parseEndOfCommand( xObj ); if( status != eScpiParserStatus_success ) { pObj->error = SCPI_ERROR_PARAMETER_NOTALLOWED; pObj->pErrorMsg = SCPI_ERROR_PARAMETER_NOTALLOWED_MSG; } // no break, no return, 'for' will not be executed due to argN==0 } bool parseBreak = false; bool trailingDataSep = false; size_t nArgs = 0; for( size_t a = 0; (!parseBreak); ++a ) { *pArgParsed = a; // indicate current parameter index (failed case) eScpiParserStatus_t (*fParser)( xParseEntry_t * xObj ) = NULL; // search for parser routine by the specified argument type for( size_t f = 0; f < sizeof(parseArgumentsMap)/sizeof(*parseArgumentsMap); ++f ) { if( parseArgumentsMap[f].type == argTypes[0] ) { // parser found fParser = parseArgumentsMap[f].f; break; } } if( NULL == fParser ) { // parser not found status = eScpiParserStatus_failed; break; } // keep tail pointer before searching for white characters const char * _tail = pObj->tail; // skip white characters status = parseWhiteSequence( xObj ); // check if NL-character found if( status == eScpiParserStatus_success ) { if( eScpiEntityType_Service_MsgTerm == pObj->type ) { goto L_parseArguments_TERM; } } // restore tail pointer before searching for parameter pObj->tail = _tail; // move forward @head pointer to skip white sequence pObj->head = pObj->str; // check for end-of-string if( eScpiParserStatus_success == checkNotEndOfString( xObj ) ) { // call the parser status = fParser( xObj ); // check result if( status != eScpiParserStatus_success ) { // parsing error // check for specific error case: message separator (intercept) if( SCPI_ERROR_INVALID_CHARACTER == pObj->error && scpi_ismsgsep( *pObj->head ) ) { // replace "error condition" -> "message termination" pObj->type = eScpiEntityType_Service_MsgTerm; status = eScpiParserStatus_success; goto L_parseArguments_TERM; } break; } else { trailingDataSep = false; } } else { // end of string: error condition pObj->type = eScpiEntityType_Service_MsgTerm; goto L_parseArguments_TERM; } { sStrToken_t currentToken = { .shead = pObj->head, .stail = pObj->tail }; sProcessNumericEntry_t Entry; sNumericRange_t range = { .max.demicalInteger = 99, .min.demicalInteger = 0, .type = SCPI_NUMERIC_LIMIT_TYPE_I32 }; if(processNumericArgument( &Entry, ¤tToken, pObj->type, &range ) && *argNumber > nArgs) { argTokens[nArgs] = Entry.valueEntry.Value.demicalInteger; } else { parseBreak = true; goto L_parseArguments_TERM; } } if( NULL != entityTypes ) { // write entity type to the output buffer entityTypes[nArgs] = pObj->type; } nArgs++; // increment number of parsed parameters // restore tail pointer before searching for separator pObj->tail = _tail; // move forward @head pointer to skip the separator pObj->head = pObj->str; // check for end-of-string if( eScpiParserStatus_success == checkNotEndOfString( xObj ) ) { status = parseProgramDataSeparator( xObj ); if( eScpiParserStatus_success != status ) { // check for specific error case: message separator (intercept) if( SCPI_ERROR_INVSEP_ERROR == pObj->error && scpi_ismsgsep( *pObj->head ) ) { // replace "error condition" -> "message termination" pObj->type = eScpiEntityType_Service_MsgTerm; pObj->error = SCPI_ERROR_SUCCESS; status = eScpiParserStatus_success; goto L_parseArguments_TERM; } break; } } else { pObj->type = eScpiEntityType_Service_MsgTerm; goto L_parseArguments_TERM; } L_parseArguments_TERM: switch( pObj->type ) { case eScpiEntityType_Service_MsgTerm: case eScpiEntityType_Service_MsgSep: { if( (nArgs) < argN - argOptionalN ) // not all mandatory parameters parsed { // error: missing parameter status = eScpiParserStatus_failed; pObj->head = pObj->tail = pObj->str = _tail; // reset all the token stats to not point to any token pObj->error = SCPI_ERROR_MISSING_PARAMETER; pObj->pErrorMsg = SCPI_ERROR_MISSING_PARAMETER_MSG; } else { // Check for trailing data separator if( trailingDataSep ) { // error: extra parameter status = eScpiParserStatus_failed; pObj->error = SCPI_ERROR_INVSEP_ERROR; pObj->pErrorMsg = SCPI_ERROR_INVSEP_ERROR_MSG; // reset all the token stats to not point to any token pObj->head = pObj->tail = pObj->str = _tail; } } (void)status; parseBreak = true; // message terminator faced } break; case eScpiEntityType_Service_DataSep: { //if( nArgs >= argN ) // all parameters already parsed //{ // error: extra parameter //status = eScpiParserStatus_failed; //pObj->error = SCPI_ERROR_PARAMETER_NOTALLOWED; //pObj->pErrorMsg = SCPI_ERROR_PARAMETER_NOTALLOWED_MSG; // reset all the token stats to not point to any token due to it is impossible // to get know where is the end of extra parameter in general case (e.g. if the string is passed). //pObj->head = pObj->tail = pObj->str = _tail; //parseBreak = true; //} trailingDataSep = true; } break; default: status = eScpiParserStatus_failed; pObj->error = SCPI_ERROR_SYNTAX_ERROR; pObj->pErrorMsg = SCPI_ERROR_SYNTAX_ERROR_PARAM_MSG; } // Restore iterators only if parsing is continuing. // Otherwise it is required to leave @head/@tail pointing to the // ... last found entity to search next command correctly. if( ! parseBreak ) { // restore iterators before searching next entity pObj->head = pObj->str; pObj->tail = _tail; } *pArgParsed = (nArgs); // indicate number of parameters parsed (success state) } return status; } // ================================================================================================================= static xParseEntry_t ctx; static char scpi_test_buffer[128] = {0}; static volatile bool scpi_test_exit = false; void scpi_test_buf_example( size_t scpi_test_id, size_t scpi_test_subid, char ** _scpi_buffer_ptr, size_t * _scpi_buffer_len, size_t * _scpi_message_len, bool * _scpi_end_of_message, bool * _scpi_keep_ctx, const char * * _entity_start, size_t * _entity_length ) { *_scpi_buffer_ptr = scpi_test_buffer; *_scpi_buffer_len = 1; *_scpi_end_of_message = false; *_scpi_keep_ctx = false; *_entity_start = NULL; *_entity_length = 0; *_scpi_message_len = 0; switch( scpi_test_id ) { case 3: // Numbers: { switch( scpi_test_subid ) { // Correct examples: case 0: strcpy( scpi_test_buffer, "3" ); break; case 1: strcpy( scpi_test_buffer, "-3" ); break; case 2: strcpy( scpi_test_buffer, ".3" ); break; case 3: strcpy( scpi_test_buffer, "3.e+1" ); break; case 4: strcpy( scpi_test_buffer, "3e14" ); break; case 5: strcpy( scpi_test_buffer, "+3 e-1" ); break; case 6: strcpy( scpi_test_buffer, "3 E 14" ); break; case 7: strcpy( scpi_test_buffer, "-3.14e-159" ); break; case 8: strcpy( scpi_test_buffer, "+3. E -14159" ); break; case 9: strcpy( scpi_test_buffer, "3 E15 " ); break; case 10: strcpy( scpi_test_buffer, "-.3 E -15 " ); break; // Incorrect examples: case 11: strcpy( scpi_test_buffer, "-3.1.e-1" ); break; // pass: 4 chars returned => actually succeeded case 12: strcpy( scpi_test_buffer, "3e1.4" ); break; // pass: 3 chars returned => actually succeeded case 13: strcpy( scpi_test_buffer, "+3. E -14159." ); break; // pass: 12 chars returned => actually succeeded case 14: strcpy( scpi_test_buffer, "3 E15 E15" ); break; // pass: 5 chars returned => actually succeeded case 15: strcpy( scpi_test_buffer, "-3.1-E -15 " ); break; // pass: 4 chars returned => actually succeeded case 16: strcpy( scpi_test_buffer, " 3" ); break; // pass case 17: strcpy( scpi_test_buffer, "+-3" ); break; // pass case 18: strcpy( scpi_test_buffer, ". 3" ); break; // pass case 19: strcpy( scpi_test_buffer, "3.e++1" ); break; // pass case 20: strcpy( scpi_test_buffer, "3 E E 14" ); break; // pass case 21: strcpy( scpi_test_buffer, "-3e.14e-159" ); break; // pass case 22: strcpy( scpi_test_buffer, "3.e+e+1" ); break; // pass case 23: strcpy( scpi_test_buffer, "3. E-E 14" ); break; // pass case 24: strcpy( scpi_test_buffer, "-3e..1-159" ); break; // pass case 25: strcpy( scpi_test_buffer, "3-.e+1" ); break; // pass: 1 chars returned => actually succeeded case 26: strcpy( scpi_test_buffer, "3.E.+1E2" ); break; // pass case 27: strcpy( scpi_test_buffer, "-3e.e1-159" ); break; // pass } *_scpi_message_len = strlen( scpi_test_buffer ); } break; case 4: // Non-demical numbers: { switch( scpi_test_subid ) { // Correct examples: case 0: strcpy( scpi_test_buffer, "#b00110001" ); break; // CORRECT : passed case 1: strcpy( scpi_test_buffer, "#Haf020ffc" ); break; // CORRECT : passed case 2: strcpy( scpi_test_buffer, "#Q7777111" ); break; // CORRECT : passed case 3: strcpy( scpi_test_buffer, "b0011000" ); break; // INCORRECT: passed case 4: strcpy( scpi_test_buffer, "Haf020ff" ); break; // INCORRECT: passed case 5: strcpy( scpi_test_buffer, "q7777111" ); break; // INCORRECT: passed case 6: strcpy( scpi_test_buffer, "0011000" ); break; // INCORRECT: passed case 7: strcpy( scpi_test_buffer, "af020ff" ); break; // INCORRECT: passed case 8: strcpy( scpi_test_buffer, "7777111" ); break; // INCORRECT: passed case 9: strcpy( scpi_test_buffer, "##b0011000" ); break; // INCORRECT: passed case 10:strcpy( scpi_test_buffer, "##haf020ff" ); break; // INCORRECT: passed case 11:strcpy( scpi_test_buffer, "##q7777111" ); break; // INCORRECT: passed case 12:strcpy( scpi_test_buffer, "#bb0011000" ); break; // INCORRECT: passed case 13:strcpy( scpi_test_buffer, "#hhaf020ff" ); break; // INCORRECT: passed case 14:strcpy( scpi_test_buffer, "#qq7777111" ); break; // INCORRECT: passed } *_scpi_message_len = strlen( scpi_test_buffer ); } break; } } void scpi_parser_test() { size_t scpi_test_id = 4; // 0 = string // 1 = arbitrary block // 2 = command program header // 3 = demical numbers // 4 = non-demical numbers eScpiParserStatus_t scpi_status = eScpiParserStatus_invalid; size_t scpi_buffer_len = 1;// sizeof( scpi_test_buffer ); size_t scpi_message_len = sizeof(scpi_test_buffer); char * scpi_buffer_ptr = scpi_test_buffer; bool scpi_end_of_message = false; bool scpi_keep_ctx = false; const char * entity_start = NULL; size_t entity_length = 0; size_t scpi_test_subid = 0; switch( scpi_test_id ) { case 0: strcpy( scpi_test_buffer, "\"Test string with a \"\"quotted\"\" part.\"" ); break; //30 case 1: strcpy( scpi_test_buffer, "#2160123456789abcdef-----" ); break; //25 case 2: strcpy( scpi_test_buffer, ":MEM:TABLE:DATA PARAM" ); break; //26 case 3: scpi_test_buf_example ( scpi_test_id, scpi_test_subid, &scpi_buffer_ptr, &scpi_buffer_len, &scpi_message_len, &scpi_end_of_message, &scpi_keep_ctx, &entity_start, &entity_length ); break; case 4: scpi_test_buf_example ( scpi_test_id, scpi_test_subid, &scpi_buffer_ptr, &scpi_buffer_len, &scpi_message_len, &scpi_end_of_message, &scpi_keep_ctx, &entity_start, &entity_length ); break; } asm( "bkpt #0" ); while( !scpi_test_exit ) { scpi_end_of_message = ( scpi_buffer_ptr >= scpi_test_buffer + scpi_message_len - 1 ); if( ! prepareParserContext( &ctx, scpi_buffer_ptr, scpi_buffer_len, scpi_end_of_message, scpi_keep_ctx ) ) { asm( "bkpt #0" ); break; } else { //asm( "bkpt #0" ); } switch( scpi_test_id ) { case 0: scpi_status = parseString( &ctx ); break; case 1: scpi_status = parseArbitraryBlock( &ctx ); break; case 2: scpi_status = parseCommandProgramHeader( &ctx ); break; case 3: scpi_status = parseDemicalNumber( &ctx ); break; case 4: scpi_status = parseNonDemicalNumber( &ctx ); break; default: scpi_status = eScpiParserStatus_invalid; } switch( scpi_status ) { case eScpiParserStatus_invalid: { scpi_keep_ctx = false; asm( "bkpt #0" ); } break; case eScpiParserStatus_failed: { scpi_keep_ctx = false; asm( "bkpt #0" ); scpi_test_buf_example ( scpi_test_id, ++scpi_test_subid, &scpi_buffer_ptr, &scpi_buffer_len, &scpi_message_len, &scpi_end_of_message, &scpi_keep_ctx, &entity_start, &entity_length ); } break; case eScpiParserStatus_success: { const char * pEntityBegin = NULL; const char * pEntityEnd = NULL; uint8_t eEntityType; scpi_keep_ctx = false; getParsedEntityDetails( &ctx, (void const**)&pEntityBegin, (void const**)&pEntityEnd, &eEntityType ); if( NULL == entity_start ) { entity_start = pEntityBegin; } entity_length = (pEntityEnd - entity_start); asm( "bkpt #0" ); scpi_test_buf_example ( scpi_test_id, ++scpi_test_subid, &scpi_buffer_ptr, &scpi_buffer_len, &scpi_message_len, &scpi_end_of_message, &scpi_keep_ctx, &entity_start, &entity_length ); } break; case eScpiParserStatus_need_data: { const char * pEntityBegin = NULL; const char * pEntityEnd = NULL; scpi_keep_ctx = true; getParsedEntityDetails( &ctx, (void const**)&pEntityBegin, (void const**)&pEntityEnd, NULL ); scpi_buffer_ptr += (pEntityEnd - pEntityBegin); //asm( "bkpt #0" ); if( NULL == entity_start ) { entity_start = pEntityBegin; } } break; } } asm( "bkpt #0" ); }