ETLCPP / etl

Embedded Template Library
https://www.etlcpp.com
MIT License
2.25k stars 393 forks source link

etl::format_spec width method not working #971

Closed Garland815 closed 1 month ago

Garland815 commented 1 month ago
etl::format_spec formatSpec;
formatSpec.hex().width(2).fill('0').upper_case(true); 
etl::string<80> strBuffer; // String storage.
etl::string_stream strStream(strBuffer, formatSpec);

Use strStream << to format 1 and 0xAA will get 010, AA0 with extra 0 at the end.

jwellbelove commented 1 month ago

I've just tried this test.

//*************************************************************************
TEST(test_issue_971_etl_format_spec_width_method_not_working)
{
  etl::format_spec formatSpec;
  formatSpec.hex().width(2).fill('0').upper_case(true); 
  etl::string<80> strBuffer;
  etl::string_stream strStream(strBuffer, formatSpec);

  strStream << 1 << ", " << 0xAA;

  std::string expected("01, AA");
  std::string actual(strStream.str().c_str());

  CHECK_EQUAL(expected, actual);
}

This passes and the output is "01, AA"

Are you including the string's terminating zero? The strBuffer contents should be '1' ',' ' ' 'A' 'A' '\0' with a size() of 6.

Garland815 commented 1 month ago

Thanks for responding to this issue quickly.

Here is my exact code and details about the compiler and the target is XMC7200 MCU.

/*
 C++ Standard: 201703
 GCC Version: 11.3.1 20220712

 Compiler command line:
 ModusToolbox/tools_3.2/gcc/bin/arm-none-eabi-g++ -std=c++17 -c -mcpu=cortex-m7 --specs=nano.specs -Og -mfloat-abi=softfp -mfpu=fpv5-d16 -mthumb
 -ffunction-sections -fdata-sections -ffat-lto-objects -g -Wall -pipe -fno-rtti -fno-exceptions -DCOMPONENT_APP_KIT_XMC72_EVK -DCOMPONENT_CAT1
 -DCOMPONENT_CAT1C -DCOMPONENT_CAT1C8M -DCOMPONENT_CM7 -DCOMPONENT_CM7_0 -DCOMPONENT_Debug -DCOMPONENT_FREERTOS -DCOMPONENT_GCC_ARM
 -DCOMPONENT_MW_ABSTRACTION_RTOS -DCOMPONENT_MW_CAT1CM0P -DCOMPONENT_MW_CLIB_SUPPORT -DCOMPONENT_MW_CMSIS -DCOMPONENT_MW_CORE_LIB
 -DCOMPONENT_MW_CORE_MAKE -DCOMPONENT_MW_FREERTOS -DCOMPONENT_MW_MTB_HAL_CAT1 -DCOMPONENT_MW_MTB_PDL_CAT1 -DCOMPONENT_MW_RECIPE_MAKE_CAT1C
 -DCOMPONENT_MW_RETARGET_IO -DCOMPONENT_SOFTFP -DCOMPONENT_XMC7xDUAL_CM0P_SLEEP -DCORE_NAME_CM7_0=1 -DCY_APPNAME_mtb_example_xmc7000_blinky_freertos
 -DCY_SUPPORTS_DEVICE_VALIDATION -DCY_TARGET_BOARD=APP_KIT_XMC72_EVK -DCY_USING_HAL -DDEBUG -DTARGET_APP_KIT_XMC72_EVK -DXMC7200D_E272K8384

 Calling code:
 PrintDataHex(0x01, 0xAA);  // Output: 010 AA0
 */
template<typename ... TArgs>
void PrintDataHex(TArgs &&...args)
{
    etl::format_spec formatSpec;
    formatSpec.hex().width(2).fill('0').upper_case(true); // .width(2) not work, print out 010, AA0 with extra 0 at the end.

    etl::string < 80 > strBuffer; // String storage.
    etl::string_stream strStream(strBuffer, formatSpec);
    ((strStream << args << " "), ...);
    strBuffer.back() = 0; // Null terminated string. Null replaces last Space.
    printf("%s\n", strBuffer.c_str()); // Print line
}
jwellbelove commented 1 month ago

What is happening is that the format is extending the width of every input using the fill character. 0x01 -> 01 " " -> "0 " 0xAA -> AA " " -> "0 "

Results in "010 AA0 "

I need to check if the STL's std::ostringstream does the same or not.

jwellbelove commented 1 month ago

BTW: strBuffer.back() = 0 is not a good way of removing the last character, as it does not affect the value returned by size(). You should use pop_back().

jwellbelove commented 1 month ago

My experiments with std::ostringstream give these (odd) results.

std::ostringstream oss;
oss << std::setfill('0') << std::setw(2) << std::hex << std::uppercase;
oss << " ";                       // "0 "
oss << " " << " ";                // "0  "
oss << " " << " " << 0x01 << " "; // "0  1 "
oss << " " << 0x01 << " " << " "; // "0 1  "
oss << 0x01 << " " << " ";        // "01  "

I'm not sure I can completely follow the logic behind the std::ostringstream output. At least the ETL's version is consistent.

jwellbelove commented 1 month ago

This code works as expected.

template<typename ... TArgs>
void PrintDataHex(TArgs &&...args)
{
  etl::format_spec formatHex;
  formatHex.hex().width(2).fill('0').upper_case(true);

  etl::format_spec formatNormal;

  etl::string<80> strBuffer; // String storage.
  etl::string_stream strStream(strBuffer);
  ((strStream << formatHex << args << formatNormal << " "), ...);
  strBuffer.pop_back();
  printf("%s\n", strBuffer.c_str()); // Print line
}
Garland815 commented 1 month ago

Thanks! This makes sense for the format spec applies to all << outputs afterwards.