RT_Verm_print.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. #include "rt_Vterm.h"
  2. // #include "rt_Vterm_Conf.h"
  3. #include "RT_tunnel.h"
  4. #include <rtthread.h>
  5. #include <stdio.h>
  6. /*********************************************************************
  7. *
  8. * Defines, configurable
  9. *
  10. **********************************************************************
  11. */
  12. /**< Default buffer size for RT_Vterm_printf (configurable) */
  13. #ifndef RT_Vterm_PRINTF_BUFFER_SIZE
  14. #define RT_Vterm_PRINTF_BUFFER_SIZE (64)
  15. #endif
  16. #include <stdarg.h>
  17. #include <stdlib.h>
  18. #include <string.h>
  19. #include <ctype.h>
  20. #include <stdlib.h>
  21. /**< Format flag: Left-justify the output (default is right-justify) */
  22. #define FORMAT_FLAG_LEFT_JUSTIFY (1u << 0)
  23. /**< Format flag: Pad with '0' instead of spaces */
  24. #define FORMAT_FLAG_PAD_ZERO (1u << 1)
  25. /**< Format flag: Always print sign (+ for positive, - for negative) */
  26. #define FORMAT_FLAG_PRINT_SIGN (1u << 2)
  27. /**< Format flag: Use alternate output format (e.g., 0x prefix for hex) */
  28. #define FORMAT_FLAG_ALTERNATE (1u << 3)
  29. /**
  30. * @struct RT_Vterm_PRINTF_DESC
  31. * @brief Descriptor structure for managing printf buffer and output state
  32. */
  33. typedef struct
  34. {
  35. char *pBuffer; /**< Pointer to the temporary printf buffer */
  36. uint32_t BufferSize; /**< Size of the temporary printf buffer */
  37. uint32_t Cnt; /**< Current number of characters stored in the buffer */
  38. int ReturnValue; /**< Total number of characters output (negative on error) */
  39. } RT_Vterm_PRINTF_DESC;
  40. /*****************************************************************************
  41. * @brief Store a single character to the printf buffer (internal use)
  42. *
  43. * @param[in,out] p Pointer to the printf descriptor structure
  44. * @param[in] c Character to store
  45. *
  46. * @note 1. Automatically adds '\r' before '\n' for line ending consistency
  47. * 2. Writes buffer to Vterm upstream tunnel when buffer is full
  48. * 3. Updates ReturnValue (increments on success, sets to -1 on error)
  49. *****************************************************************************/
  50. static void _StoreChar(RT_Vterm_PRINTF_DESC *p, char c)
  51. {
  52. uint32_t Cnt;
  53. // Add '\r' before '\n' to ensure correct line ending on all terminals
  54. if (c == '\n')
  55. {
  56. Cnt = p->Cnt;
  57. if ((Cnt + 1u) <= p->BufferSize)
  58. {
  59. *(p->pBuffer + Cnt) = '\r';
  60. p->Cnt = Cnt + 1u;
  61. p->ReturnValue++;
  62. }
  63. // Flush buffer if full after adding '\r'
  64. if (p->Cnt == p->BufferSize)
  65. {
  66. if (RT_Vterm_Write(p->pBuffer, p->Cnt) != p->Cnt)
  67. {
  68. p->ReturnValue = -1; // Mark write error
  69. }
  70. else
  71. {
  72. p->Cnt = 0u; // Reset buffer counter after successful flush
  73. }
  74. }
  75. }
  76. // Store the target character
  77. Cnt = p->Cnt;
  78. if ((Cnt + 1u) <= p->BufferSize)
  79. {
  80. *(p->pBuffer + Cnt) = c;
  81. p->Cnt = Cnt + 1u;
  82. p->ReturnValue++;
  83. }
  84. // Flush buffer if full after storing the character
  85. if (p->Cnt == p->BufferSize)
  86. {
  87. if (RT_Vterm_Write(p->pBuffer, p->Cnt) != p->Cnt)
  88. {
  89. p->ReturnValue = -1; // Mark write error
  90. }
  91. else
  92. {
  93. p->Cnt = 0u; // Reset buffer counter after successful flush
  94. }
  95. }
  96. }
  97. /*****************************************************************************
  98. * @brief Print an unsigned integer to Vterm (internal use)
  99. *
  100. * @param[in,out] pBufferDesc Pointer to the printf descriptor structure
  101. * @param[in] v Unsigned integer value to print
  102. * @param[in] Base Numeric base (e.g., 10 for decimal, 16 for hex)
  103. * @param[in] NumDigits Minimum number of digits to print (pads with 0 if needed)
  104. * @param[in] FieldWidth Total field width (pads with spaces/0 if value is shorter)
  105. * @param[in] FormatFlags Format control flags (left-justify, pad-zero, etc.)
  106. *
  107. * @note Handles digit extraction, padding, and alignment per format flags
  108. *****************************************************************************/
  109. static void _Printunsigned(RT_Vterm_PRINTF_DESC *pBufferDesc, uint32_t v, uint32_t Base, uint32_t NumDigits, uint32_t FieldWidth, uint32_t FormatFlags)
  110. {
  111. // Lookup table for converting numeric values to ASCII characters (0-9, A-F)
  112. static const char _aV2C[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
  113. uint32_t Div; // Divisor for extracting digits
  114. uint32_t Digit; // Current digit position (e.g., 1000 for 4th digit in decimal)
  115. uint32_t Number; // Copy of input value (used for calculating digit count)
  116. uint32_t Width; // Actual number of digits in the input value
  117. char c; // Padding character (space or '0')
  118. Number = v;
  119. Digit = 1u;
  120. // Step 1: Calculate the actual number of digits in the input value
  121. Width = 1u;
  122. while (Number >= Base)
  123. {
  124. Number = (Number / Base);
  125. Width++;
  126. }
  127. // Use user-specified minimum digits if larger than actual digit count
  128. if (NumDigits > Width)
  129. {
  130. Width = NumDigits;
  131. }
  132. // Step 2: Print leading padding (spaces or '0') if not left-justified
  133. if ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == 0u)
  134. {
  135. if (FieldWidth != 0u)
  136. {
  137. // Choose padding character: '0' if pad-zero flag is set (and no min digits), else space
  138. if (((FormatFlags & FORMAT_FLAG_PAD_ZERO) == FORMAT_FLAG_PAD_ZERO) && (NumDigits == 0u))
  139. {
  140. c = '0';
  141. }
  142. else
  143. {
  144. c = ' ';
  145. }
  146. // Add padding until field width is satisfied
  147. while ((FieldWidth != 0u) && (Width < FieldWidth))
  148. {
  149. FieldWidth--;
  150. _StoreChar(pBufferDesc, c);
  151. if (pBufferDesc->ReturnValue < 0)
  152. {
  153. break; // Exit on write error
  154. }
  155. }
  156. }
  157. }
  158. // Step 3: Calculate the highest digit position (e.g., 1000 for 4-digit decimal)
  159. if (pBufferDesc->ReturnValue >= 0)
  160. {
  161. while (1)
  162. {
  163. if (NumDigits > 1u)
  164. { // Ensure we loop at least NumDigits times (for leading zeros)
  165. NumDigits--;
  166. }
  167. else
  168. {
  169. Div = v / Digit;
  170. if (Div < Base)
  171. { // Stop when Digit is larger than the highest digit of v
  172. break;
  173. }
  174. }
  175. Digit *= Base;
  176. }
  177. // Step 4: Extract and print each digit from highest to lowest
  178. do
  179. {
  180. Div = v / Digit;
  181. v -= Div * Digit; // Remove the current digit from v
  182. _StoreChar(pBufferDesc, _aV2C[Div]);
  183. if (pBufferDesc->ReturnValue < 0)
  184. {
  185. break; // Exit on write error
  186. }
  187. Digit /= Base; // Move to the next lower digit position
  188. } while (Digit);
  189. // Step 5: Print trailing spaces if left-justified
  190. if ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == FORMAT_FLAG_LEFT_JUSTIFY)
  191. {
  192. if (FieldWidth != 0u)
  193. {
  194. while ((FieldWidth != 0u) && (Width < FieldWidth))
  195. {
  196. FieldWidth--;
  197. _StoreChar(pBufferDesc, ' ');
  198. if (pBufferDesc->ReturnValue < 0)
  199. {
  200. break; // Exit on write error
  201. }
  202. }
  203. }
  204. }
  205. }
  206. }
  207. /*****************************************************************************
  208. * @brief Print a signed integer to Vterm (internal use)
  209. *
  210. * @param[in,out] pBufferDesc Pointer to the printf descriptor structure
  211. * @param[in] v Signed integer value to print
  212. * @param[in] Base Numeric base (e.g., 10 for decimal, 16 for hex)
  213. * @param[in] NumDigits Minimum number of digits to print (pads with 0 if needed)
  214. * @param[in] FieldWidth Total field width (pads with spaces/0 if value is shorter)
  215. * @param[in] FormatFlags Format control flags (left-justify, pad-zero, etc.)
  216. *
  217. * @note 1. Handles negative values by prepending '-'
  218. * 2. Handles sign printing (+ for positive) if FORMAT_FLAG_PRINT_SIGN is set
  219. * 3. Delegates unsigned digit printing to _Printunsigned
  220. *****************************************************************************/
  221. static void _PrintInt(RT_Vterm_PRINTF_DESC *pBufferDesc, int v, uint32_t Base, uint32_t NumDigits, uint32_t FieldWidth, uint32_t FormatFlags)
  222. {
  223. uint32_t Width; // Actual number of digits in the input value
  224. int Number; // Absolute value of the input (for digit count calculation)
  225. // Get absolute value of v (handles negative numbers)
  226. Number = (v < 0) ? -v : v;
  227. // Step 1: Calculate the actual number of digits in the input value
  228. Width = 1u;
  229. while (Number >= (int)Base)
  230. {
  231. Number = (Number / (int)Base);
  232. Width++;
  233. }
  234. // Use user-specified minimum digits if larger than actual digit count
  235. if (NumDigits > Width)
  236. {
  237. Width = NumDigits;
  238. }
  239. // Reserve 1 character width for sign (+/-) if needed
  240. if ((FieldWidth > 0u) && ((v < 0) || ((FormatFlags & FORMAT_FLAG_PRINT_SIGN) == FORMAT_FLAG_PRINT_SIGN)))
  241. {
  242. FieldWidth--;
  243. }
  244. // Step 2: Print leading spaces (if not pad-zero or left-justified)
  245. if ((((FormatFlags & FORMAT_FLAG_PAD_ZERO) == 0u) || (NumDigits != 0u)) && ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == 0u))
  246. {
  247. if (FieldWidth != 0u)
  248. {
  249. while ((FieldWidth != 0u) && (Width < FieldWidth))
  250. {
  251. FieldWidth--;
  252. _StoreChar(pBufferDesc, ' ');
  253. if (pBufferDesc->ReturnValue < 0)
  254. {
  255. break; // Exit on write error
  256. }
  257. }
  258. }
  259. }
  260. // Step 3: Print sign (+/-) if needed
  261. if (pBufferDesc->ReturnValue >= 0)
  262. {
  263. if (v < 0)
  264. {
  265. v = -v; // Convert to positive for digit printing
  266. _StoreChar(pBufferDesc, '-');
  267. }
  268. else if ((FormatFlags & FORMAT_FLAG_PRINT_SIGN) == FORMAT_FLAG_PRINT_SIGN)
  269. {
  270. _StoreChar(pBufferDesc, '+'); // Print '+' for positive values
  271. }
  272. // Step 4: Print leading zeros (if pad-zero flag is set)
  273. if (pBufferDesc->ReturnValue >= 0)
  274. {
  275. if (((FormatFlags & FORMAT_FLAG_PAD_ZERO) == FORMAT_FLAG_PAD_ZERO) && ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == 0u) && (NumDigits == 0u))
  276. {
  277. if (FieldWidth != 0u)
  278. {
  279. while ((FieldWidth != 0u) && (Width < FieldWidth))
  280. {
  281. FieldWidth--;
  282. _StoreChar(pBufferDesc, '0');
  283. if (pBufferDesc->ReturnValue < 0)
  284. {
  285. break; // Exit on write error
  286. }
  287. }
  288. }
  289. }
  290. // Step 5: Print the unsigned part of the number (delegate to _Printunsigned)
  291. if (pBufferDesc->ReturnValue >= 0)
  292. {
  293. _Printunsigned(pBufferDesc, (uint32_t)v, Base, NumDigits, FieldWidth, FormatFlags);
  294. }
  295. }
  296. }
  297. }
  298. /*****************************************************************************
  299. * @brief Formatted output to Vterm upstream tunnel (va_list version)
  300. *
  301. * @param[in] sFormat Format string (supports %c, %d, %u, %x/%X, %s, %p, %%)
  302. * @param[in] pParamList Pointer to va_list containing variable arguments
  303. *
  304. * @retval int Total number of characters printed (positive on success)
  305. * -1 if write error occurs
  306. *
  307. * @note 1. Uses a temporary buffer (size = RT_Vterm_PRINTF_BUFFER_SIZE) to reduce tunnel writes
  308. * 2. Automatically flushes buffer when full or at the end of output
  309. * 3. Handles line endings by converting '\n' to "\r\n"
  310. *****************************************************************************/
  311. int RT_Vterm_vprintf(const char *sFormat, va_list *pParamList)
  312. {
  313. char c; // Current character in format string
  314. RT_Vterm_PRINTF_DESC BufferDesc; // Printf buffer descriptor
  315. int v; // Temporary storage for variable arguments
  316. uint32_t NumDigits; // Minimum digits (from format string: %.2d)
  317. uint32_t FormatFlags; // Format flags (from format string: %-0+#)
  318. uint32_t FieldWidth; // Field width (from format string: %5d)
  319. char acBuffer[RT_Vterm_PRINTF_BUFFER_SIZE]; // Temporary buffer
  320. // Initialize printf descriptor
  321. BufferDesc.pBuffer = acBuffer;
  322. BufferDesc.BufferSize = RT_Vterm_PRINTF_BUFFER_SIZE;
  323. BufferDesc.Cnt = 0u;
  324. BufferDesc.ReturnValue = 0;
  325. // Parse format string character by character
  326. do
  327. {
  328. c = *sFormat;
  329. sFormat++;
  330. if (c == 0u)
  331. {
  332. break; // Exit if end of format string
  333. }
  334. // Handle format specifier (starts with '%')
  335. if (c == '%')
  336. {
  337. // Step 1: Parse format flags (-0+#)
  338. FormatFlags = 0u;
  339. v = 1;
  340. do
  341. {
  342. c = *sFormat;
  343. switch (c)
  344. {
  345. case '-':
  346. FormatFlags |= FORMAT_FLAG_LEFT_JUSTIFY;
  347. sFormat++;
  348. break;
  349. case '0':
  350. FormatFlags |= FORMAT_FLAG_PAD_ZERO;
  351. sFormat++;
  352. break;
  353. case '+':
  354. FormatFlags |= FORMAT_FLAG_PRINT_SIGN;
  355. sFormat++;
  356. break;
  357. case '#':
  358. FormatFlags |= FORMAT_FLAG_ALTERNATE;
  359. sFormat++;
  360. break;
  361. default:
  362. v = 0; // Exit loop if no more flags
  363. break;
  364. }
  365. } while (v);
  366. // Step 2: Parse field width (e.g., %5d → FieldWidth=5)
  367. FieldWidth = 0u;
  368. do
  369. {
  370. c = *sFormat;
  371. if ((c < '0') || (c > '9'))
  372. {
  373. break; // Exit if not a digit
  374. }
  375. sFormat++;
  376. FieldWidth = (FieldWidth * 10u) + ((uint32_t)c - '0');
  377. } while (1);
  378. // Step 3: Parse precision (e.g., %.2d → NumDigits=2)
  379. NumDigits = 0u;
  380. c = *sFormat;
  381. if (c == '.')
  382. {
  383. sFormat++;
  384. do
  385. {
  386. c = *sFormat;
  387. if ((c < '0') || (c > '9'))
  388. {
  389. break; // Exit if not a digit
  390. }
  391. sFormat++;
  392. NumDigits = NumDigits * 10u + ((uint32_t)c - '0');
  393. } while (1);
  394. }
  395. // Step 4: Skip length modifiers (l/h) (not supported in this implementation)
  396. c = *sFormat;
  397. do
  398. {
  399. if ((c == 'l') || (c == 'h'))
  400. {
  401. sFormat++;
  402. c = *sFormat;
  403. }
  404. else
  405. {
  406. break;
  407. }
  408. } while (1);
  409. // Step 5: Handle format specifiers (c/d/u/x/X/s/p/%)
  410. switch (c)
  411. {
  412. case 'c': // Print single character
  413. {
  414. char c0;
  415. v = va_arg(*pParamList, int); // va_arg returns int for char (promotion)
  416. c0 = (char)v;
  417. _StoreChar(&BufferDesc, c0);
  418. break;
  419. }
  420. case 'd': // Print signed decimal integer
  421. v = va_arg(*pParamList, int);
  422. _PrintInt(&BufferDesc, v, 10u, NumDigits, FieldWidth, FormatFlags);
  423. break;
  424. case 'u': // Print unsigned decimal integer
  425. v = va_arg(*pParamList, int);
  426. _Printunsigned(&BufferDesc, (uint32_t)v, 10u, NumDigits, FieldWidth, FormatFlags);
  427. break;
  428. case 'x': // Print unsigned hexadecimal (uppercase, same as 'X' here)
  429. case 'X':
  430. v = va_arg(*pParamList, int);
  431. _Printunsigned(&BufferDesc, (uint32_t)v, 16u, NumDigits, FieldWidth, FormatFlags);
  432. break;
  433. case 's': // Print null-terminated string
  434. {
  435. const char *s = va_arg(*pParamList, const char *);
  436. do
  437. {
  438. c = *s;
  439. s++;
  440. if (c == '\0')
  441. {
  442. break; // Exit at end of string
  443. }
  444. _StoreChar(&BufferDesc, c);
  445. } while (BufferDesc.ReturnValue >= 0);
  446. }
  447. break;
  448. case 'p': // Print pointer (8-digit hexadecimal)
  449. v = va_arg(*pParamList, int);
  450. _Printunsigned(&BufferDesc, (uint32_t)v, 16u, 8u, 8u, 0u);
  451. break;
  452. case '%': // Print literal '%'
  453. _StoreChar(&BufferDesc, '%');
  454. break;
  455. default: // Ignore unknown specifiers
  456. break;
  457. }
  458. sFormat++; // Move past the specifier character
  459. }
  460. else
  461. {
  462. // Not a format specifier: store the character directly
  463. _StoreChar(&BufferDesc, c);
  464. }
  465. } while (BufferDesc.ReturnValue >= 0);
  466. // Flush remaining characters in the buffer (if any)
  467. if (BufferDesc.ReturnValue > 0)
  468. {
  469. if (BufferDesc.Cnt != 0u)
  470. {
  471. RT_Vterm_Write(acBuffer, BufferDesc.Cnt);
  472. }
  473. BufferDesc.ReturnValue += (int)BufferDesc.Cnt;
  474. }
  475. return BufferDesc.ReturnValue;
  476. }
  477. /*****************************************************************************
  478. * @brief Formatted output to Vterm upstream tunnel (user-facing)
  479. *
  480. * @param[in] sFormat Format string (supports %c, %d, %u, %x/%X, %s, %p, %%)
  481. * @param[in] ... Variable arguments matching the format string
  482. *
  483. * @retval int Total number of characters printed (positive on success)
  484. * -1 if write error occurs
  485. *
  486. * @note Wraps RT_Vterm_vprintf and handles variable argument list setup/teardown
  487. *****************************************************************************/
  488. int RT_Vterm_printf(const char *sFormat, ...)
  489. {
  490. int r; // Return value from RT_Vterm_vprintf
  491. va_list ParamList; // Variable argument list
  492. va_start(ParamList, sFormat); // Initialize argument list
  493. r = RT_Vterm_vprintf(sFormat, &ParamList);
  494. va_end(ParamList); // Clean up argument list
  495. return r;
  496. }
  497. /*************************** End of file ****************************/