|
rcbench
Servo and ESC test bench: panel, IOmcu and shared C
|
Turning a text cell into a number, when "1,024" might be either. More...
#include "log_numbers.h"#include <ctype.h>#include <math.h>#include <stdlib.h>#include <string.h>Data Structures | |
| struct | [struct].k_time_units |
| Seconds per unit for the time units an exporter is likely to write. More... | |
Functions | |
| static int | grouping_len (const char *p) |
| Two character classes that overlap, and the overlap is the whole reason this is written by hand rather than with strtod(): | |
| static int | space_len (const char *p) |
| Length of a white-space character at a position. | |
| static const char * | skip_space (const char *p) |
| Skip white-space characters. | |
| static bool | copy_digits (const char *begin, const char *end, char *out, size_t out_size) |
| Copy [begin, end) into out, dropping grouping-only characters. | |
| static const char * | unit_begin (const char *p) |
| Truncate without splitting a UTF-8 sequence: half a ° is worse than none. | |
| static bool | unit_is_plausible (const char *p) |
| Whether a unit text can be a unit. | |
| static void | copy_trimmed (const char *begin, const char *end, char *out, size_t out_size) |
| Copy a range without surrounding white space. | |
| bool | log_split_value (const char *raw, log_value_t *out) |
| Split a raw cell into numeric text, exponent and unit. | |
| static bool | leading_group_is_legal (const char *digits, char sep) |
| The integer part, read as a thousands group: 1-3 digits and no leading zero (a bare "0" cannot be followed by a group either). | |
| static bool | grouping_is_legal (const char *digits, char sep) |
| Every group of a repeated separator: leading 1-3 digits, the rest exactly three, digits throughout. | |
| log_evidence_t | log_evidence_of (const char *digits) |
| Inspect one already-split numeric string and report what it proves. | |
| void | log_votes_reset (log_votes_t *v) |
| Clear the tally. | |
| void | log_votes_add (log_votes_t *v, const char *raw) |
| Feed one raw cell. | |
| log_conv_t | log_votes_result (const log_votes_t *v, log_ambig_t fallback, bool *confident, bool *conflict) |
| Decide the convention from the tally. | |
| static bool | all_digits (const char *begin, const char *end) |
| Whether a range holds only digits and is not empty. | |
| bool | log_is_well_formed (const char *digits, log_conv_t convention) |
Is digits well formed under convention? | |
| bool | log_parse_with (const char *raw, log_conv_t convention, double *value, char *unit, size_t unit_size) |
| Parse one cell with a known convention. | |
| bool | log_parse_loose (const char *raw, log_ambig_t fallback, double *value) |
| Parse a standalone value, inferring the convention from it alone. | |
| void | log_unit_tally_reset (log_unit_tally_t *t) |
| Clear a unit tally. | |
| void | log_unit_tally_add (log_unit_tally_t *t, const char *unit) |
| Add a unit suffix to a tally. | |
| void | log_unit_dominant (const log_unit_tally_t *t, char *out, size_t out_size, bool *mixed) |
The most common non-empty suffix. mixed reports more than one. | |
| static bool | ieq (const char *a, const char *b) |
| Compare two strings without regard to case. | |
| static bool | is_bare_unit (const char *s) |
| Whether a text is a unit in the bare unit list. | |
| static void | copy_range (const char *begin, const char *end, char *out, size_t out_size) |
| Copy a range, cut without splitting a UTF-8 sequence. | |
| void | log_unit_from_header (const char *header, char *name, size_t name_size, char *unit, size_t unit_size) |
| Pull a unit out of a column header: "voltage (V)", "current [A]", "speed km/h", "power (W)". "gyroADC[0]" keeps its index – that is an axis number, not a unit. | |
| double | log_time_unit_scale (const char *unit) |
| Seconds per unit for a time column, or 0 when the name is not a time unit. | |
Variables | |
| static const char *const | k_bare_units [] |
| Units accepted after a space, e.g. "altitude m". | |
| static const struct | k_time_units [] |
Turning a text cell into a number, when "1,024" might be either.
The hard problem in a locale-blind log reader is not splitting rows, it is the decimal point: "1,024" is a thousand-and-twenty-four to an English writer and one-point-oh-two-four to a German one, and the cell itself cannot say which. So this does not decide per cell. log_evidence_of() classifies a cell as evidence for a convention, evidence against, or genuinely ambiguous, and the caller votes across the whole column – one unambiguous "1.5" settles a file that is otherwise all thousands-shaped values.
The care is in what counts as no evidence. A repeated separator is usually grouping, but "192.168.0.1", "15.01.2024" and "1.2.3" are an address, a date and a version, not numbers, and voting on them hands the file to the wrong convention. A three-digit tail is the ambiguous shape, except when the integer part cannot be a thousands group (a leading zero, or more than three digits), which is the millisecond-timestamp case that would otherwise read an English log as German.
SPDX-License-Identifier: MIT
|
static |
Whether a range holds only digits and is not empty.
| begin | First byte of the range. |
| end | One past the last byte of the range. |
|
static |
Copy [begin, end) into out, dropping grouping-only characters.
| begin | First byte of the body. |
| end | One past the last byte of the body. |
| out | Receives the digits, NUL-terminated. |
| out_size | Size of out, bytes. |
|
static |
Copy a range, cut without splitting a UTF-8 sequence.
| begin | First byte of the range. |
| end | One past the last byte of the range. |
| out | Receives the text, NUL-terminated. |
| out_size | Size of out, bytes. |
|
static |
Copy a range without surrounding white space.
Truncate without splitting a UTF-8 sequence: half a degree sign is worse than none.
| begin | First byte of the range. |
| end | One past the last byte of the range. |
| out | Receives the text, NUL-terminated. |
| out_size | Size of out, bytes. |
|
static |
Every group of a repeated separator: leading 1-3 digits, the rest exactly three, digits throughout.
| digits | Numeric text as log_split_value() gives it. |
| sep | The separator that groups. |
|
static |
Two character classes that overlap, and the overlap is the whole reason this is written by hand rather than with strtod():
grouping-only ' ' '\'' U+00A0 U+202F – never a decimal point whitespace ASCII space and friends, plus U+00A0 and U+202F
ASCII space is in both, so "1 234,56" is one number and "12%" is a number with a unit. What separates them is that the numeric body must end on a digit: the scanner remembers where the last digit was and hands everything after it to the unit.
| p | The text at the position. |
|
static |
Compare two strings without regard to case.
| a | The first string. |
| b | The second string. |
|
static |
Whether a text is a unit in the bare unit list.
| s | The text. |
s is in k_bare_units, in any case.
|
static |
The integer part, read as a thousands group: 1-3 digits and no leading zero (a bare "0" cannot be followed by a group either).
| digits | Numeric text as log_split_value() gives it. |
| sep | The separator that groups. |
| log_evidence_t log_evidence_of | ( | const char * | digits | ) |
Inspect one already-split numeric string and report what it proves.
| digits | Numeric text as log_split_value() gives it. |
| bool log_is_well_formed | ( | const char * | digits, |
| log_conv_t | convention ) |
Is digits well formed under convention?
Strict on purpose. Stripping the grouping separator and parsing what is left turns the German "1.234,56" into 1.23456 when read as English: a silently wrong number. Rejecting lets the caller surface the mismatch.
| digits | Numeric text as log_split_value() gives it. |
| convention | The convention to check against. |
| bool log_parse_loose | ( | const char * | raw, |
| log_ambig_t | fallback, | ||
| double * | value ) |
Parse a standalone value, inferring the convention from it alone.
| raw | The raw cell text. |
| fallback | The default for an ambiguous value. |
| value | Receives the number. |
| bool log_parse_with | ( | const char * | raw, |
| log_conv_t | convention, | ||
| double * | value, | ||
| char * | unit, | ||
| size_t | unit_size ) |
Parse one cell with a known convention.
| raw | The raw cell text. |
| convention | The convention of the file. |
| value | Receives the number. |
| unit | Receives the unit suffix. |
| unit_size | Size of unit, bytes. |
| bool log_split_value | ( | const char * | raw, |
| log_value_t * | out ) |
Split a raw cell into numeric text, exponent and unit.
Grouping characters that are never decimal points – ASCII space, the apostrophe Swiss exporters use, U+00A0 and U+202F – are removed here, so digits carries only digits, '.' and ','.
| raw | The raw cell text. |
| out | Receives the parts. |
| double log_time_unit_scale | ( | const char * | unit | ) |
Seconds per unit for a time column, or 0 when the name is not a time unit.
| unit | The unit name. |
| void log_unit_dominant | ( | const log_unit_tally_t * | t, |
| char * | out, | ||
| size_t | out_size, | ||
| bool * | mixed ) |
The most common non-empty suffix. mixed reports more than one.
| t | The tally. |
| out | Receives the suffix. |
| out_size | Size of out, bytes. |
| mixed | Receives true when more than one suffix was seen. |
| void log_unit_from_header | ( | const char * | header, |
| char * | name, | ||
| size_t | name_size, | ||
| char * | unit, | ||
| size_t | unit_size ) |
Pull a unit out of a column header: "voltage (V)", "current [A]", "speed km/h", "power (W)". "gyroADC[0]" keeps its index – that is an axis number, not a unit.
| header | The column header. |
| name | Receives the name without the unit. |
| name_size | Size of name, bytes. |
| unit | Receives the unit. |
| unit_size | Size of unit, bytes. |
| void log_unit_tally_add | ( | log_unit_tally_t * | t, |
| const char * | unit ) |
Add a unit suffix to a tally.
| t | The tally. |
| unit | The unit suffix. |
| void log_unit_tally_reset | ( | log_unit_tally_t * | t | ) |
Clear a unit tally.
| t | The tally. |
| void log_votes_add | ( | log_votes_t * | v, |
| const char * | raw ) |
Feed one raw cell.
Cells that hold no number are ignored.
| v | The tally. |
| raw | The raw cell text. |
| void log_votes_reset | ( | log_votes_t * | v | ) |
Clear the tally.
| v | The tally. |
| log_conv_t log_votes_result | ( | const log_votes_t * | v, |
| log_ambig_t | fallback, | ||
| bool * | confident, | ||
| bool * | conflict ) |
Decide the convention from the tally.
| v | The tally. |
| fallback | The default for a wholly ambiguous file. |
| confident | set false when nothing proved anything and the fallback had to decide. |
| conflict | set true when both conventions were proven, which means the file itself is inconsistent; the caller reports it rather than silently picking the winner. |
|
static |
Skip white-space characters.
| p | The text. |
|
static |
Length of a white-space character at a position.
| p | The text at the position. |
|
static |
Truncate without splitting a UTF-8 sequence: half a ° is worse than none.
What is left after the numeric body, read as a unit. A trailing decimal separator belongs to the number ("1500," is 1500), and a residual carrying a ':' or a signed number is the tail of something the scan stopped inside: a clock time, an ISO 8601 date, a hyphenated part number. Those are not numbers with units, and accepting them makes a whole column look numeric.
| p | The text after the numeric body. |
|
static |
Whether a unit text can be a unit.
What is left after the numeric body, read as a unit. A residual carrying a ':' or a signed number is the tail of something the scan stopped inside: a clock time, an ISO 8601 date, a hyphenated part number. Those are not numbers with units, and accepting them makes a whole column look numeric.
| p | The unit text. |
|
static |
Units accepted after a space, e.g. "altitude m".
A list rather than a shape test, because "power output" also has the shape of a name followed by a short word.
| const struct [struct].k_time_units k_time_units[] |