ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ra8_esp_hosted_fmt.c File Reference

Bounded printf-subset formatter used by the esp-hosted port logger. More...

#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include "ra8_attributes.h"
#include "ra8_esp_hosted_fmt_internal.h"
Include dependency graph for ra8_esp_hosted_fmt.c:

Go to the source code of this file.

Data Structures

struct  ra8_esp_hosted_fmt_args
 Single-member holder that names this unit's argument list once. More...
struct  ra8_esp_hosted_fmt_cursor
 Write cursor over the caller's output buffer. More...

Typedefs

typedef struct ra8_esp_hosted_fmt_args ra8_esp_hosted_fmt_args_t
typedef struct ra8_esp_hosted_fmt_cursor ra8_esp_hosted_fmt_cursor_t

Enumerations

enum  ra8_esp_hosted_fmt_char_t : char {
  k_ra8_esp_hosted_fmt_ch_nul = '\0' ,
  k_ra8_esp_hosted_fmt_ch_percent = '' ,
  k_ra8_esp_hosted_fmt_ch_zero = '0' ,
  k_ra8_esp_hosted_fmt_ch_nine = '9' ,
  k_ra8_esp_hosted_fmt_ch_minus = '-' ,
  k_ra8_esp_hosted_fmt_ch_space = ' ' ,
  k_ra8_esp_hosted_fmt_ch_ell = 'l' ,
  k_ra8_esp_hosted_fmt_ch_zed = 'z'
}
 Characters the parser and emitter test for by name. More...
enum  ra8_esp_hosted_fmt_radix_t : uint8_t {
  k_ra8_esp_hosted_fmt_radix_min = 2U ,
  k_ra8_esp_hosted_fmt_radix_dec = 10U ,
  k_ra8_esp_hosted_fmt_radix_hex = 16U
}
 Radices the formatter renders. More...

Functions

static void internal_put (ra8_esp_hosted_fmt_cursor_t *cur, char ch)
 Append one character if the buffer still has room for it.
static void internal_pad (ra8_esp_hosted_fmt_cursor_t *cur, char pad, uint16_t count)
 Append the pad character a bounded number of times.
uint8_t priv_ra8_esp_hosted_fmt_utoa (char *buf, uint64_t value, uint8_t base, bool upper)
 Implementation of priv_ra8_esp_hosted_fmt_utoa() – divides down and reverses in place, so no scratch beyond the caller's buffer.
static uint8_t internal_parse_flags (const char *text, ra8_esp_hosted_fmt_spec_t *out)
 Recognise and record the flag characters of a specification.
static uint8_t internal_parse_width (const char *text, uint16_t *out_width)
 Read the optional decimal field width of a specification.
static uint8_t internal_parse_len (const char *text, ra8_esp_hosted_fmt_len_t *out_len)
 Read the optional length modifier of a specification.
static bool internal_is_supported (char conv)
 Whether a character is a conversion this formatter emits.
bool priv_ra8_esp_hosted_fmt_parse (const char *after_percent, ra8_esp_hosted_fmt_spec_t *out)
 Implementation of priv_ra8_esp_hosted_fmt_parse() – flags, width, length modifier and conversion, each bounded by the specification length cap.
static uint64_t internal_next_unsigned (ra8_esp_hosted_fmt_args_t *args, ra8_esp_hosted_fmt_len_t len)
 Pull the next unsigned argument at the width the modifier names.
static int64_t internal_next_signed (ra8_esp_hosted_fmt_args_t *args, ra8_esp_hosted_fmt_len_t len)
 Pull the next signed argument at the width the modifier names.
static void internal_emit_token (ra8_esp_hosted_fmt_cursor_t *cur, const ra8_esp_hosted_fmt_spec_t *spec, const char *token, uint16_t token_len)
 Emit an already-rendered token honouring width and justification.
static uint16_t internal_bounded_len (const char *text)
 Measure a NUL-terminated string, bounded by the width cap.
static void internal_emit_conv (ra8_esp_hosted_fmt_cursor_t *cur, const ra8_esp_hosted_fmt_spec_t *spec, ra8_esp_hosted_fmt_args_t *args)
 Expand one parsed conversion into the cursor.
uint32_t priv_ra8_esp_hosted_fmt_vformat (char *out, uint32_t cap, const char *fmt, va_list ap)
 Implementation of priv_ra8_esp_hosted_fmt_vformat() – single pass, every loop bounded, no allocation.

Variables

static const char s_ra8_esp_hosted_fmt_digits_lower [] = "0123456789abcdef"
 Digit glyphs for lower-case hexadecimal and every smaller base.
static const char s_ra8_esp_hosted_fmt_digits_upper [] = "0123456789ABCDEF"
 Digit glyphs for upper-case hexadecimal.

Detailed Description

Bounded printf-subset formatter used by the esp-hosted port logger.

Tag
[Ring 4 / PORT] {World: NS}

Implementation of the contract in ra8_esp_hosted_fmt_internal.h. The design follows from two constraints that pull in the same direction: the image has no heap, and every loop must have a statically provable bound. So the formatter never allocates, writes only into the caller's buffer, and bounds each of its three loops – specification parsing, digit generation and padding – by a compile-time constant from ra8_esp_hosted_fmt_bound_t.

Emission goes through a small cursor structure rather than a running pointer so the "one byte reserved for the terminator" rule is expressed once, in internal_put, instead of at every call site. The formatter's validated entry point constructs the cursor, so the emitter does not carry unreachable duplicate pointer guards.

Since
0.1.0

Definition in file ra8_esp_hosted_fmt.c.

Typedef Documentation

◆ ra8_esp_hosted_fmt_args_t

◆ ra8_esp_hosted_fmt_cursor_t

Enumeration Type Documentation

◆ ra8_esp_hosted_fmt_char_t

Characters the parser and emitter test for by name.

Named so the parsing logic reads as grammar rather than as arithmetic on character codes, and so the project's no-magic-number rule is satisfied without scattering casts through the control flow.

Invariant
Every value is a 7-bit ASCII code point.
Example:
if (ch == (char)k_ra8_esp_hosted_fmt_ch_percent) { parse_spec(); }
@ k_ra8_esp_hosted_fmt_ch_percent
Starts a conversion.
See also
priv_ra8_esp_hosted_fmt_parse
Since
0.1.0
Enumerator
k_ra8_esp_hosted_fmt_ch_nul 

String terminator.

k_ra8_esp_hosted_fmt_ch_percent 

Starts a conversion.

k_ra8_esp_hosted_fmt_ch_zero 

Zero-pad flag and first digit.

k_ra8_esp_hosted_fmt_ch_nine 

Last decimal digit.

k_ra8_esp_hosted_fmt_ch_minus 

Left-justify flag and sign.

k_ra8_esp_hosted_fmt_ch_space 

Pad character when not zero-padding.

k_ra8_esp_hosted_fmt_ch_ell 

Long length modifier.

k_ra8_esp_hosted_fmt_ch_zed 

Size-type length modifier.

Definition at line 54 of file ra8_esp_hosted_fmt.c.

◆ ra8_esp_hosted_fmt_radix_t

Radices the formatter renders.

Only the two the vendored core's format strings ask for. Any other base is rejected by priv_ra8_esp_hosted_fmt_utoa rather than silently rendered, because a base the caller did not intend would produce a plausible but wrong number.

Invariant
Both values are within the 2..16 range the digit table covers.
Example:
@ k_ra8_esp_hosted_fmt_radix_hex
Hexadecimal, for x, X and p.
uint8_t priv_ra8_esp_hosted_fmt_utoa(char *buf, uint64_t value, uint8_t base, bool upper)
Implementation of priv_ra8_esp_hosted_fmt_utoa() – divides down and reverses in place,...
See also
priv_ra8_esp_hosted_fmt_utoa
Since
0.1.0
Enumerator
k_ra8_esp_hosted_fmt_radix_min 

Smallest base the digit table serves.

k_ra8_esp_hosted_fmt_radix_dec 

Decimal, for the integer conversions.

k_ra8_esp_hosted_fmt_radix_hex 

Hexadecimal, for x, X and p.

Definition at line 86 of file ra8_esp_hosted_fmt.c.

Function Documentation

◆ internal_bounded_len()

uint16_t internal_bounded_len ( const char * text)
static

Measure a NUL-terminated string, bounded by the width cap.

A bounded measurement rather than strlen so a co-processor-supplied string that lost its terminator cannot run off the end of memory. A string longer than the cap is reported as exactly the cap and is therefore truncated in the output, which is visible rather than fatal.

Parameters
[in]textString to measure. Must be non-null.
Returns
Length in characters, at most the emitter's line budget.
Return values
0The string was empty.
Precondition
text is non-null.
The caller tolerates a truncated measurement.
Postcondition
No state is modified.
The result never exceeds the bound.
Note
Reentrant; no module state.
Since
0.1.0

Definition at line 599 of file ra8_esp_hosted_fmt.c.

References k_ra8_esp_hosted_fmt_ch_nul, and k_ra8_esp_hosted_fmt_width_max.

Referenced by internal_emit_conv().

◆ internal_emit_conv()

void internal_emit_conv ( ra8_esp_hosted_fmt_cursor_t * cur,
const ra8_esp_hosted_fmt_spec_t * spec,
ra8_esp_hosted_fmt_args_t * args )
static

Expand one parsed conversion into the cursor.

Dispatches on the conversion character, pulls exactly one argument for the conversions that take one, and hands the rendered token to internal_emit_token so padding is applied uniformly.

Parameters
[in,out]curCursor to append through.
[in]specParsed conversion specification.
[in,out]argsCopied argument list to advance.
Returns
Nothing.
Precondition
cur and spec are non-null and spec->conv is supported.
args is positioned at this conversion's argument.
Postcondition
Exactly one argument is consumed, except for the per-cent conversion which consumes none.
The cursor grows by at most the field width plus the token length.
Note
Reentrant; touches only the caller's cursor and argument list.
Since
0.1.0

Definition at line 635 of file ra8_esp_hosted_fmt.c.

References ra8_esp_hosted_fmt_args::ap, ra8_esp_hosted_fmt_spec::conv, internal_bounded_len(), internal_emit_token(), internal_next_signed(), internal_next_unsigned(), internal_put(), k_ra8_esp_hosted_fmt_ch_minus, k_ra8_esp_hosted_fmt_ch_percent, k_ra8_esp_hosted_fmt_digits_max, k_ra8_esp_hosted_fmt_radix_dec, k_ra8_esp_hosted_fmt_radix_hex, ra8_esp_hosted_fmt_spec::len, and priv_ra8_esp_hosted_fmt_utoa().

Referenced by priv_ra8_esp_hosted_fmt_vformat().

◆ internal_emit_token()

void internal_emit_token ( ra8_esp_hosted_fmt_cursor_t * cur,
const ra8_esp_hosted_fmt_spec_t * spec,
const char * token,
uint16_t token_len )
static

Emit an already-rendered token honouring width and justification.

The single place the padding rules live, so every conversion pads the same way. Zero padding only applies when the token is right-justified; a left-justified field pads with spaces regardless, matching C's own rule.

Parameters
[in,out]curCursor to append through.
[in]specSpecification supplying width and flags.
[in]tokenNUL-terminated text to emit.
[in]token_lenLength of token in characters.
Returns
Nothing.
Precondition
cur, spec and token are non-null.
token_len is the length of token.
Postcondition
The field is at least spec->width characters wide, subject to the buffer's remaining capacity.
No more than k_ra8_esp_hosted_fmt_width_max pad characters are emitted.
Note
Reentrant; touches only the caller's cursor.
Since
0.1.0

Definition at line 556 of file ra8_esp_hosted_fmt.c.

References internal_pad(), internal_put(), k_ra8_esp_hosted_fmt_ch_space, k_ra8_esp_hosted_fmt_ch_zero, ra8_esp_hosted_fmt_spec::left_justify, ra8_esp_hosted_fmt_spec::width, and ra8_esp_hosted_fmt_spec::zero_pad.

Referenced by internal_emit_conv().

◆ internal_is_supported()

bool internal_is_supported ( char conv)
static

Whether a character is a conversion this formatter emits.

The set is closed deliberately: a conversion outside it is copied through verbatim by the driver instead of consuming an argument, which keeps every later argument aligned with its conversion.

Parameters
[in]convCandidate conversion character.
Returns
Whether the formatter handles it.
Return values
trueThe driver will expand it.
falseThe driver will copy the specification through verbatim.
Precondition
None; any character value is accepted.
The caller has already consumed the flags, width and length.
Postcondition
No state is modified.
The answer depends only on conv.
Note
Reentrant; no module state.
Since
0.1.0

Definition at line 408 of file ra8_esp_hosted_fmt.c.

References k_ra8_esp_hosted_fmt_ch_percent.

Referenced by priv_ra8_esp_hosted_fmt_parse().

◆ internal_next_signed()

int64_t internal_next_signed ( ra8_esp_hosted_fmt_args_t * args,
ra8_esp_hosted_fmt_len_t len )
static

Pull the next signed argument at the width the modifier names.

Mirrors internal_next_unsigned for the d and i conversions, including its reason for dispatching with guarded early returns instead of a switch.

Parameters
[in,out]argsCopied argument list to advance.
[in]lenArgument width to read.
Returns
The argument, sign-extended to 64 bits.
Return values
0The argument itself was zero.
Precondition
args is positioned at an argument of the named width.
len is one of the ra8_esp_hosted_fmt_len_t values.
Postcondition
args has advanced past exactly one argument.
The sign of the argument is preserved.
Note
Reentrant; no module state.
Since
0.1.0

Definition at line 513 of file ra8_esp_hosted_fmt.c.

References ra8_esp_hosted_fmt_args::ap, k_ra8_esp_hosted_fmt_len_llong, k_ra8_esp_hosted_fmt_len_long, and k_ra8_esp_hosted_fmt_len_size.

Referenced by internal_emit_conv().

◆ internal_next_unsigned()

uint64_t internal_next_unsigned ( ra8_esp_hosted_fmt_args_t * args,
ra8_esp_hosted_fmt_len_t len )
static

Pull the next unsigned argument at the width the modifier names.

Reading at the wrong width would misalign every later argument, so each modifier reads at exactly the type it names – z at size_t, not at whichever fixed-width type happens to share its size on one target. C leaves va_arg undefined when the requested type is merely the same size as the argument's rather than compatible with it, so the four reads cannot be merged even where two of them are the same number of bytes.

The dispatch is a sequence of guarded early returns rather than a switch: it matches how internal_emit_conv below selects a conversion, and it keeps clang-tidy's bugprone-branch-clone off a construct it cannot read correctly – its statement profiler ignores the type operand of va_arg, so four reads of four different types profile as one.

Parameters
[in,out]argsCopied argument list to advance.
[in]lenArgument width to read.
Returns
The argument, widened to 64 bits.
Return values
0The argument itself was zero.
Precondition
args is positioned at an argument of the named width.
len is one of the ra8_esp_hosted_fmt_len_t values.
Postcondition
args has advanced past exactly one argument.
The result is zero-extended, never sign-extended.
Note
Reentrant; no module state.
Since
0.1.0

Definition at line 473 of file ra8_esp_hosted_fmt.c.

References ra8_esp_hosted_fmt_args::ap, k_ra8_esp_hosted_fmt_len_llong, k_ra8_esp_hosted_fmt_len_long, and k_ra8_esp_hosted_fmt_len_size.

Referenced by internal_emit_conv().

◆ internal_pad()

void internal_pad ( ra8_esp_hosted_fmt_cursor_t * cur,
char pad,
uint16_t count )
static

Append the pad character a bounded number of times.

Used for both leading and trailing padding; which one is decided by the caller, which is what keeps the left-justify decision in one place.

Parameters
[in,out]curCursor to append through. Must be non-null.
[in]padCharacter to repeat.
[in]countRepeat count, already clamped by the caller.
Returns
Nothing.
Precondition
cur is non-null.
count is at most k_ra8_esp_hosted_fmt_width_max.
Postcondition
At most count characters are appended.
The loop runs a statically bounded number of iterations.
Note
Reentrant; touches only the caller's cursor.
Since
0.1.0

Definition at line 218 of file ra8_esp_hosted_fmt.c.

References internal_put(), and k_ra8_esp_hosted_fmt_width_max.

Referenced by internal_emit_token().

◆ internal_parse_flags()

uint8_t internal_parse_flags ( const char * text,
ra8_esp_hosted_fmt_spec_t * out )
static

Recognise and record the flag characters of a specification.

Consumes any run of 0 and - characters, bounded so a pathological format string cannot spin here.

Parameters
[in]textText after the per-cent sign. Must be non-null.
[in,out]outSpecification whose flag fields are set.
Returns
Characters consumed.
Return values
0No flag characters were present.
Precondition
text is NUL terminated.
out is non-null.
Postcondition
out->zero_pad and out->left_justify reflect the run.
The return value is at most k_ra8_esp_hosted_fmt_spec_max.
Note
Reentrant; no module state.
Since
0.1.0

Definition at line 287 of file ra8_esp_hosted_fmt.c.

References k_ra8_esp_hosted_fmt_ch_minus, k_ra8_esp_hosted_fmt_ch_zero, k_ra8_esp_hosted_fmt_spec_max, ra8_esp_hosted_fmt_spec::left_justify, and ra8_esp_hosted_fmt_spec::zero_pad.

Referenced by priv_ra8_esp_hosted_fmt_parse().

◆ internal_parse_len()

uint8_t internal_parse_len ( const char * text,
ra8_esp_hosted_fmt_len_t * out_len )
static

Read the optional length modifier of a specification.

Recognises l, ll and z. Anything else leaves the default int width in place.

Parameters
[in]textText positioned at the modifier, if any.
[out]out_lenParsed argument width.
Returns
Characters consumed.
Return values
0No modifier was present.
Precondition
text is NUL terminated.
out_len is non-null.
Postcondition
*out_len is one of the ra8_esp_hosted_fmt_len_t values.
The return value is at most two.
Note
Reentrant; no module state.
Since
0.1.0

Definition at line 367 of file ra8_esp_hosted_fmt.c.

References k_ra8_esp_hosted_fmt_ch_ell, k_ra8_esp_hosted_fmt_ch_zed, k_ra8_esp_hosted_fmt_len_int, k_ra8_esp_hosted_fmt_len_llong, k_ra8_esp_hosted_fmt_len_long, and k_ra8_esp_hosted_fmt_len_size.

Referenced by priv_ra8_esp_hosted_fmt_parse().

◆ internal_parse_width()

uint8_t internal_parse_width ( const char * text,
uint16_t * out_width )
static

Read the optional decimal field width of a specification.

Accumulates digits, clamping at k_ra8_esp_hosted_fmt_width_max so an absurd width cannot make one conversion fill the whole line.

Parameters
[in]textText positioned at the first width digit, if any.
[out]out_widthParsed width; zero when no digits were present.
Returns
Characters consumed.
Return values
0The next character was not a digit.
Precondition
text is NUL terminated.
out_width is non-null.
Postcondition
*out_width is at most k_ra8_esp_hosted_fmt_width_max.
The return value is at most k_ra8_esp_hosted_fmt_spec_max.
Note
Reentrant; no module state.
Since
0.1.0

Definition at line 325 of file ra8_esp_hosted_fmt.c.

References k_ra8_esp_hosted_fmt_ch_nine, k_ra8_esp_hosted_fmt_ch_zero, k_ra8_esp_hosted_fmt_radix_dec, k_ra8_esp_hosted_fmt_spec_max, and k_ra8_esp_hosted_fmt_width_max.

Referenced by priv_ra8_esp_hosted_fmt_parse().

◆ internal_put()

void internal_put ( ra8_esp_hosted_fmt_cursor_t * cur,
char ch )
static

Append one character if the buffer still has room for it.

Reserves the last byte for the terminator, so a full buffer silently drops further characters rather than overwriting the NUL.

Parameters
[in,out]curCursor to append through. Must be non-null.
[in]chCharacter to append.
Returns
Nothing.
Precondition
cur is non-null and its buffer holds cap bytes.
cur->len is less than cur->cap.
Postcondition
cur->len grows by at most one.
cur->len stays strictly below cur->cap.
Note
Reentrant; touches only the caller's cursor.
Since
0.1.0

Definition at line 187 of file ra8_esp_hosted_fmt.c.

References ra8_esp_hosted_fmt_cursor::cap, ra8_esp_hosted_fmt_cursor::len, and ra8_esp_hosted_fmt_cursor::out.

Referenced by internal_emit_conv(), internal_emit_token(), internal_pad(), and priv_ra8_esp_hosted_fmt_vformat().

◆ priv_ra8_esp_hosted_fmt_parse()

bool priv_ra8_esp_hosted_fmt_parse ( const char * after_percent,
ra8_esp_hosted_fmt_spec_t * out )
nodiscard

Implementation of priv_ra8_esp_hosted_fmt_parse() – flags, width, length modifier and conversion, each bounded by the specification length cap.

Parse one conversion specification.

Definition at line 419 of file ra8_esp_hosted_fmt.c.

References ra8_esp_hosted_fmt_spec::consumed, ra8_esp_hosted_fmt_spec::conv, internal_is_supported(), internal_parse_flags(), internal_parse_len(), internal_parse_width(), ra8_esp_hosted_fmt_spec::len, and ra8_esp_hosted_fmt_spec::width.

Referenced by priv_ra8_esp_hosted_fmt_vformat().

◆ priv_ra8_esp_hosted_fmt_utoa()

uint8_t priv_ra8_esp_hosted_fmt_utoa ( char * buf,
uint64_t value,
uint8_t base,
bool upper )
nodiscard

Implementation of priv_ra8_esp_hosted_fmt_utoa() – divides down and reverses in place, so no scratch beyond the caller's buffer.

Render an unsigned value into a digit buffer, least digit last.

Definition at line 232 of file ra8_esp_hosted_fmt.c.

References k_ra8_esp_hosted_fmt_ch_nul, k_ra8_esp_hosted_fmt_digits_max, k_ra8_esp_hosted_fmt_radix_hex, k_ra8_esp_hosted_fmt_radix_min, s_ra8_esp_hosted_fmt_digits_lower, and s_ra8_esp_hosted_fmt_digits_upper.

Referenced by internal_emit_conv(), and ra8_esp_hosted_log_hexdump().

◆ priv_ra8_esp_hosted_fmt_vformat()

uint32_t priv_ra8_esp_hosted_fmt_vformat ( char * out,
uint32_t cap,
const char * fmt,
va_list ap )

Variable Documentation

◆ s_ra8_esp_hosted_fmt_digits_lower

const char s_ra8_esp_hosted_fmt_digits_lower[] = "0123456789abcdef"
static

Digit glyphs for lower-case hexadecimal and every smaller base.

Indexed by the remainder, so the table doubles as the decimal digit set.

Note
Read-only.
Warning
Must stay sixteen entries; the radix bound assumes it.
Since
0.1.0

Definition at line 154 of file ra8_esp_hosted_fmt.c.

Referenced by priv_ra8_esp_hosted_fmt_utoa().

◆ s_ra8_esp_hosted_fmt_digits_upper

const char s_ra8_esp_hosted_fmt_digits_upper[] = "0123456789ABCDEF"
static

Digit glyphs for upper-case hexadecimal.

Selected by the X conversion only.

Note
Read-only.
Warning
Must stay sixteen entries; the radix bound assumes it.
Since
0.1.0

Definition at line 164 of file ra8_esp_hosted_fmt.c.

Referenced by priv_ra8_esp_hosted_fmt_utoa().