eddy_em: (Default)
eddy_em ([personal profile] eddy_em) wrote2021-10-18 11:47 pm

Хеши строковых команд для МК

Долго я к этому шел, но, похоже, пора уже: однобуквенные команды сложно запоминать (особенно если команд толпа, и большая часть с этими буквами вообще никак не ассоциируются), а тупой разбор "в лоб" функциями вроде strncmp для больших списков может занять прилично времени. Вот и решил я простой хеш попробовать.

Чтобы сгенерировать кусок файла, необходимо составить простой список вроде такого:
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 на другое простое число.

Post a comment in response:

This account has disabled anonymous posting.
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting

If you are unable to use this captcha for any reason, please contact us by email at support@dreamwidth.org