eddy_em: (Default)
[personal profile] eddy_em
Долго я к этому шел, но, похоже, пора уже: однобуквенные команды сложно запоминать (особенно если команд толпа, и большая часть с этими буквами вообще никак не ассоциируются), а тупой разбор "в лоб" функциями вроде 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 на другое простое число.

April 2025

S M T W T F S
  1 23 45
67 89101112
13141516171819
20212223242526
27282930   

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated May. 22nd, 2025 06:54 am
Powered by Dreamwidth Studios