You know, if all your configurable items are global variables, or items in global structures, you can use
struct config_entry {
const char *const name; /* In Flash/ROM */
void *const addr; /* Address of variable in RAM */
const uint32_t hash:24; /* 24-bit hash */
const uint32_t type:8; /* 8-bit type */
};
/* Example types */
#define TYPE_BIT0 0
#define TYPE_BIT31 31
#define TYPE_BIT(n) (n)
#define TYPE_BYTE 32
#define TYPE_INT 45
#define TYPE_FLOAT 78
static inline uint32_t config_hash(const char *src)
{
uint32_t result = 5381;
while (*src)
result = (result * 33) ^ (uint32_t)(*(src++));
return result;
}
/* Example items */
float origin_x;
float origin_y;
float origin_z;
int speed_v;
struct {
int a;
int b;
int c;
} frob;
/* Configuration array, naming all configurable variables. */
static const struct config_entry config[] = {
/* Keep sorted by vvvvvvv */
{ "origin.z", &origin_z, 5255461, TYPE_FLOAT },
{ "origin.y", &origin_y, 5255462, TYPE_FLOAT },
{ "origin.x", &origin_x, 5255463, TYPE_FLOAT },
{ "speed.v", &speed_v, 6516090, TYPE_INT },
{ "frob.b", &(frob.b), 15471920, TYPE_INT },
{ "frob.c", &(frob.c), 15471921, TYPE_INT },
{ "frob.a", &(frob.a), 15471923, TYPE_INT },
};
The config[] array is in ROM/Flash. When you add new entries, you do need to calculate the 24-bit hash for the third column; but you can use for example the following hash.py Python script:
from sys import argv, stdout, stderr, exit
def hash(string):
result = 5381
for c in string:
result = ((result * 33) ^ ord(c)) & 4294967295
return result & 16777215
if __name__ == '__main__':
if len(argv) < 2 or argv[1] in ('-h', '--help', '/?'):
stderr.write("\n")
stderr.write("Usage: %s [ -h | --help | /? ]\n" % argv[0])
stderr.write(" %s STRING [ STRING ... ]\n" % argv[0])
stderr.write("\n")
stderr.write("This program computes the 24-bit DJB2 XOR hash\n")
stderr.write("of the input strings, and outputs them as\n")
stderr.write("a C hash table array.\n")
stderr.write("\n")
exit(0)
table = []
for arg in argv[1:]:
table.append((hash(arg), arg),)
table.sort()
for entry in table:
stdout.write(" { \"%s\", NULL, %8d, 0 },\n" % (entry[1], entry[0]))
At run time, you can use a binary search to rapidly locate the correct configuration item:
const struct config_entry *find_config(const char *name)
{
const uint32_t hash = config_hash(name);
const int_fast32_t n = (sizeof config / sizeof config[0]);
int_fast32_t imin = 0, iend = n;
int_fast32_t i;
if (hash < config[0].hash || hash > config[n-1].hash)
return NULL;
while (iend > imin) {
i = imin + (iend - imin)/2;
if (config[i].hash < hash)
imin = i;
else
if (config[i].hash > hash)
iend = i;
else {
/* Extend imin, imax to cover all matching hashes */
imin = i;
while (imin > 0 && config[imin-1].hash == hash)
imin--;
iend = i + 1;
while (iend < n && config[iend].hash == hash)
iend++;
break;
}
for (i = imin; i < iend; i++)
if (config[i].hash == hash && !strcmp(name, config[i].name))
return config + i;
return NULL;
}
From there, it is just a matter of examining the ->type, and getting or setting the corresponding value from ->addr.
On a 32-bit microcontroller, this takes 13 bytes plus the name string per configurable variable of ROM/flash.
Note that you can integrate the hash calculation to your parser, so that whenever you find an identifier, you have both the identifier string and its hash.