Хеши строковых команд для МК
Oct. 18th, 2021 11:47 pm![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Долго я к этому шел, но, похоже, пора уже: однобуквенные команды сложно запоминать (особенно если команд толпа, и большая часть с этими буквами вообще никак не ассоциируются), а тупой разбор "в лоб" функциями вроде strncmp для больших списков может занять прилично времени. Вот и решил я простой хеш попробовать.
Чтобы сгенерировать кусок файла, необходимо составить простой список вроде такого:
Первый столбец — сама текстовая команда, второй — функция, которая будет выполняться с остатком аргументов (сразу же после этой команды - начиная с пробела), третий — справка, которая будет выведена, если получена неправильная команда.
До этого у меня все делалось довольно-таки черезжопно: справку я писал вручную отдельно, так что легко можно было где-нибудь накосячить. А так — все "в одном флаконе".
Для вспомогательных нужд составляем простенький файлик gen.c:
Его задача — считать хеши (лень мне заморачиваться и выдумывать, как в баше посчитать в uint32_t).
Ну и вот такенный скрипт на баше, чтобы сгенерировать нужный кусок сишного файла (выхлоп — в outp.c):
Полученный сгенерированный файл стоит проверить на компьютере, для этого в начало дописываю недостающую преамбулу, а в конец добавляю main():
Теперь можно смело проверять:
Единственное, что нужно — следить, чтобы не было повторяющихся хэшей, т.к. это хоть и редко, но вполне возможно. В таком случае нужно поменять множитель MULTIPLIER на другое простое число.
Чтобы сгенерировать кусок файла, необходимо составить простой список вроде такого:
andmore andmore "and more functions"
another theanother "another veird function"
command thecommand "any command"
sort thesort "sort some things"
Первый столбец — сама текстовая команда, второй — функция, которая будет выполняться с остатком аргументов (сразу же после этой команды - начиная с пробела), третий — справка, которая будет выведена, если получена неправильная команда.
До этого у меня все делалось довольно-таки черезжопно: справку я писал вручную отдельно, так что легко можно было где-нибудь накосячить. А так — все "в одном флаконе".
Для вспомогательных нужд составляем простенький файлик gen.c:
#include <stdio.h>
#include <stdint.h>
#define MULTIPLIER (31)
uint32_t hash(const char *str){
uint32_t h = 0;
for(; *str; ++str)
h = MULTIPLIER * h + *str;
return h;
}
int main(int argc, char **argv){
if(argc != 2){
printf("Usage: %s string\n", argv[0]);
return 1;
}
printf("%u\n", hash(argv[1]));
return 0;
}
Его задача — считать хеши (лень мне заморачиваться и выдумывать, как в баше посчитать в uint32_t).
Ну и вот такенный скрипт на баше, чтобы сгенерировать нужный кусок сишного файла (выхлоп — в outp.c):
#!/bin/bash
O="outp.c"
T="tmpfile"
> $T
if [ $# -ne 1 ]; then
echo "Usage: $0 filename - convert list 'command function_pointer' into hash switch"
exit 1
fi
while read cmd fptr cmnt; do
echo "$(./gen $cmd) $cmd $fptr $cmnt" >> $T
done < $1
sort -n $T > somememe
mv somememe $T
cat > $O << EOF
#define MULTIPLIER (31)
char *hash(char *str, uint32_t *hs){
if(!str) return NULL;
uint32_t h = 0;
for(; *str && *str != ' '; ++str)
h = MULTIPLIER * h + *str;
if(hs) *hs = h;
return str;
}
typedef struct{const char *cmd; const char *help;} comhelp;
const comhelp cmdarray[] = {
EOF
while read H cmd fptr cmnt; do
echo -e "\t\t{\"$cmd\", $cmnt}," >> $O
done < $T
echo -e "\t\t{NULL, NULL},\n\t};" >> $O
cat >> $O << EOF
TRUE_INLINE void sendhelp(){
const comhelp *h = cmdarray;
SEND("Usage:\n");
while(h->cmd){
SEND(h->cmd); SEND(" - ");
SEND(h->help); newline();
++h;
}
}
void cmd_parser(char *str){
uint32_t h;
char *nxt = hash(str, &h);
if(!nxt){
sendhelp();
return;
}
switch(h){
EOF
while read H cmd fptr cmnt; do
cat >> $O << EOF
case $H:
${fptr}(nxt);
break;
EOF
done < $T
cat >> $O << EOF
default:
sendhelp(); // wrong command
}
}
EOF
rm $T
Полученный сгенерированный файл стоит проверить на компьютере, для этого в начало дописываю недостающую преамбулу, а в конец добавляю main():
#include <stdio.h>
#include <stdint.h>
#define TRUE_INLINE
#define SEND(x) printf("%s", x)
#define newline() printf("\n")
#define NM(n, x) printf(n "(%s)\n", x)
#define thesort(x) NM("thesort", x)
#define thecommand(x) NM("thecommand", x)
#define andmore(x) NM("andmore", x)
#define theanother(x) NM("theanother", x)
#define MULTIPLIER (31)
char *hash(char *str, uint32_t *hs){
if(!str) return NULL;
uint32_t h = 0;
for(; *str && *str != ' '; ++str)
h = MULTIPLIER * h + *str;
if(hs) *hs = h;
return str;
}
typedef struct{const char *cmd; const char *help;} comhelp;
const comhelp cmdarray[] = {
{"sort", "sort some things"},
{"command", "any command"},
{"andmore", "and more functions"},
{"another", "another veird function"},
{NULL, NULL},
};
TRUE_INLINE void sendhelp(){
const comhelp *h = cmdarray;
SEND("Usage:\n");
while(h->cmd){
SEND(h->cmd); SEND(" - ");
SEND(h->help); newline();
++h;
}
}
void cmd_parser(char *str){
uint32_t h;
char *nxt = hash(str, &h);
if(!nxt){
sendhelp();
return;
}
switch(h){
case 3536286:
thesort(nxt);
break;
case 950394699:
thecommand(nxt);
break;
case 3433427372:
andmore(nxt);
break;
case 3443787523:
theanother(nxt);
break;
default:
sendhelp(); // wrong command
}
}
int main(int argc, char **argv){
if(argc != 2){
sendhelp();
return 1;
}
cmd_parser(argv[1]);
return 0;
}
Теперь можно смело проверять:
./outp_test "another some data" theanother( some data) ./outp_test "andmore some data" andmore( some data) ./outp_test "commandd some data" Usage: sort - sort some things command - any command andmore - and more functions another - another veird function ./outp_test "sort some data" thesort( some data)
Единственное, что нужно — следить, чтобы не было повторяющихся хэшей, т.к. это хоть и редко, но вполне возможно. В таком случае нужно поменять множитель MULTIPLIER на другое простое число.