Strängparsning i C

Detta ska vara strikt ANSI C89 pedantisk kod. Den ska extrahera word1, word2 och word3 från en strängformaterad [word1] word2 [ word3] och returnera fel i något annat format.

Det verkar fungera, men det verkar så ful. Du behöver inte kommentera att GetTokenBetweenSquareBraces och GetTokenBtweenOpositeSquareBraces är dubbletter.

Jag skulle gärna ha några tips om hur städa upp detta.

#include <stdio.h> #include <string.h> #include <ctype.h> char * TrimWhiteSpaces(char *str) { char *out = str; int i; int len = strlen(str); for (i=0; i<len && isspace(str[i]); i++, out++); /*scan forward*/ for (i=len-1; i>=0 && isspace(str[i]); str[i]=0, i--);/*scan backward*/ return out; } char * GetTokenBetweenSquareBraces(char * input, char **output, int * output_size) { char *p = TrimWhiteSpaces(input); *output_size=0; if (p[0] == "[") { *output = TrimWhiteSpaces(p + 1); do { (*output_size)++; }while((*output)[*output_size] != "]" && isalnum((*output)[*output_size])); } else { return NULL; } return (*output) + *output_size; } char * GetTokenBtweenOpositeSquareBraces(char * input, char **output, int * output_size) { char *p = TrimWhiteSpaces(input); *output_size=0; if (p[0] == "]") { *output = TrimWhiteSpaces(p + 1); do { (*output_size)++; }while((*output)[*output_size] != "[" && isalnum((*output)[*output_size])); } else { return NULL; } return (*output) + *output_size; } int GetWords(char * str,char * word1,char * word2,char * word3) { char * next=NULL,*output=NULL; int outputsize; printf ("\nSplitting string \"%s\" into tokens:\n",str); next = GetTokenBetweenSquareBraces (str,&output,&outputsize); strncpy(word1,output,outputsize); word1[outputsize] = "\0"; strcpy(word1,TrimWhiteSpaces(word1)); if(!next) return 0; next = GetTokenBtweenOpositeSquareBraces (next,&output,&outputsize); strncpy(word2,output,outputsize); word2[outputsize] = "\0"; strcpy(word2,TrimWhiteSpaces(word2)); if(!next) return 0; next = GetTokenBetweenSquareBraces (next,&output,&outputsize); strncpy(word3,output,outputsize); word3[outputsize] = "\0"; strcpy(word3,TrimWhiteSpaces(word3)); if(!next) return 0; return 1; } void TestGetWords(char * str ) { char word1[20],word2[20],word3[20]; if (GetWords(str,word1,word2,word3)) { printf("|%s|%s|%s|\n",word1,word2,word3); } else { printf("3ViLLLL\n"); } } int main (void) { char str[] ="[ hello ] gfd [ hello2 ] "; char str2[] ="[ hello [ gfd [ hello2 ] "; char str3[] ="the wie321vg42g42g!@#"; char str4[] ="][123[]23][231["; TestGetWords(str); TestGetWords(str2); TestGetWords(str3); TestGetWords(str4); getchar(); return 1; } 

Kommentarer

  • Först och främst fixa din indragning. Markdown-motorn gillar inte ’ flikar – ersätt dem med mellanslag.
  • @LokiAstari: det verkar som om du ersatte hans ursprungliga kod.
  • Opps. Förlåt. Fixat min skruv hoppas jag. Jag fick koden från artikeln om meta. fixade flikproblemet och sätt tillbaka det. Om detta inte stämmer är jag ledsen, men jag kan ’ t verkar återställa en version.
  • @LokiAstari: Japp, ser mycket bättre ut.

Svar

#include <stdio.h> #include <string.h> #include <ctype.h> char * TrimWhiteSpaces(char *str) { char *out = str; int i; int len = strlen(str); for (i=0; i<len && isspace(str[i]); i++, out++); /*scan forward*/ 

Jag skulle åtminstone ha en kropp med en kommentar i det här. Det är lätt att missa det semikolonet. Jag tror inte att du behöver testet i < len. 0 i slutet av strängen ska misslyckas med isspace -testet, så du behöver inte kontrollera längden också. Det är inte heller meningsfullt att hålla koll av i. Använd istället bara out.

 for (i=len-1; i>=0 && isspace(str[i]); str[i]=0, i--);/*scan backward*/ 

Det är inte nödvändigt att ställa in alla dessa mellanslag till 0. Sammantaget gör du för mycket arbete på den här raden. Du bör åtminstone bara göra 0-inställningen inuti loopkroppen eftersom det inte har något att göra med loopkontrollen.

 return out; 

Generellt sett är det bäst att antingen ändra dina parametrar eller returnera nya. Gör inte båda. Här returnerar du en ny strängpekare och ändrar originalsträngen.

} char * GetTokenBetweenSquareBraces(char * input, char **output, int * output_size) { char *p = TrimWhiteSpaces(input); *output_size=0; if (p[0] == "[") { *output = TrimWhiteSpaces(p + 1); do { (*output_size)++; }while((*output)[*output_size] != "]" && isalnum((*output)[*output_size])); 

] är inte ett nummer eller en bokstav. Du behöver inte båda dessa tester.

 } else { return NULL; } return (*output) + *output_size; } char * GetTokenBtweenOpositeSquareBraces(char * input, char **output, int * output_size) { char *p = TrimWhiteSpaces(input); *output_size=0; if (p[0] == "]") { *output = TrimWhiteSpaces(p + 1); do { (*output_size)++; }while((*output)[*output_size] != "[" && isalnum((*output)[*output_size])); } else { return NULL; } return (*output) + *output_size; } 

Deja Vu! Detta är nästan exakt samma som den tidigare funktionen. Endast fästanvisningarna har vänds om. Det verkar som att du borde kunna dela den koden.

int GetWords(char * str,char * word1,char * word2,char * word3) { char * next=NULL,*output=NULL; int outputsize; printf ("\nSplitting string \"%s\" into tokens:\n",str); 

Generellt sett rekommenderar jag att du inte gör att dina arbetsfunktioner gör något. Också udda val av var du ska placera nya rader.

 next = GetTokenBetweenSquareBraces (str,&output,&outputsize); strncpy(word1,output,outputsize); word1[outputsize] = "\0"; strcpy(word1,TrimWhiteSpaces(word1)); 

Varför trimmar du det vita utrymmet här? Har du inte redan gjort det. Du gör mycket arbete för att kopiera texten. Kanske är det något som GetTokenBetweenSquareBraces borde ha gjort?

 if(!next) return 0; next = GetTokenBtweenOpositeSquareBraces (next,&output,&outputsize); strncpy(word2,output,outputsize); word2[outputsize] = "\0"; strcpy(word2,TrimWhiteSpaces(word2)); if(!next) return 0; 

Deja vu!

 next = GetTokenBetweenSquareBraces (next,&output,&outputsize); strncpy(word3,output,outputsize); word3[outputsize] = "\0"; strcpy(word3,TrimWhiteSpaces(word3)); if(!next) return 0; 

Deja Vu!

 return 1; } void TestGetWords(char * str ) { char word1[20],word2[20],word3[20]; 

Din kod är inte försiktig så att du inte överflödar dessa variabler. Du kanske vill göra något åt det

 if (GetWords(str,word1,word2,word3)) { printf("|%s|%s|%s|\n",word1,word2,word3); } else { printf("3ViLLLL\n"); } } int main (void) { char str[] ="[ hello ] gfd [ hello2 ] "; char str2[] ="[ hello [ gfd [ hello2 ] "; char str3[] ="the wie321vg42g42g!@#"; char str4[] ="][123[]23][231["; TestGetWords(str); TestGetWords(str2); TestGetWords(str3); TestGetWords(str4); 

För automatisk testning är det faktiskt bättre om du ger rätt svar och kontrollerar det i koden. På det sättet kommer programmet att berätta när det är fel.

 getchar(); return 1; 

0 används för att indikera en lyckad programkörning.

} 

Sammantaget är ditt program ful eftersom du använder fel ordförråd. Du har tagit ordförrådet som givet istället för att definiera ordförrådet som gjorde uppgiften lätt att beskriva. Här är min inställning till ditt problem

char * Whitespace(char * str) /* This function return the `str` pointer incremented past any whitespace. */ { /* when an error occurs, we return NULL. If an error has already occurred, just pass it on */ if(!str) return str; while(isspace(*str)) { str++; } return str; } char * Character(char * str, char c) /* This function tries to match a specific character. It returns `str` incremented past the character or NULL if the character was not found */ { if(!str) return str; /* Eat any whitespace before the character */ str = Whitespace(str); if(c != *str) { return NULL; } else { return str + 1; } } char * Word(char * str, char * word) /* This function reads a sequence of numbers and letter into word and then returns a pointer to the position after the word */ { /* Handle errors and whitespace */ if(!str) return str; str = Whitespace(str); /* copy characters */ while(isalnum(*str)) { *word++ = *str++; } *word = 0; /* don"t forget null!*/ return str; } int GetWords(char * str,char * word1,char * word2,char * word3) { str = Character(str, "["); str = Word(str, word1); str = Character(str, "]"); str = Word(str, word2); str = Character(str, "["); str = Word(str, word3); str = Character(str, "]"); str = Character(str, "\0"); return str != NULL; } 

Vad jag ” vi har gjort (eller försökt göra) är att skriva funktionerna Tecken, Mellanslag och Word så att de verkligen är väldigt enkla. Om du förstår char * borde du inte ha några problem med dem. Men dessa enkla verktyg kombineras mycket snyggt för att möjliggöra en enkel implementering av din parser.

Kommentarer

  • +1 för ” Generellt rekommenderar jag att dina arbetsfunktioner inte gör någon utdata ”. Också mycket fin och ren lösning.

Svar

Det här är kanske lite mindre ful, men stränghantering kommer aldrig att bli vackert i C.

static const char * skip_space(const char *s) { return s + strspn(s, " "); } static const char * skip_bracket(const char * s, int bracket) { s = skip_space(s); if (*s != bracket) return NULL; return skip_space(++s); } static const char * skip_word(const char * s) { return s + strcspn(s, " []"); } static const char * copy_word(char *w, const char *s, size_t size) { const char * end = skip_word(s); size_t len = end - s; if (len >= size) /* silently truncate word to buffer size */ len = size - 1; memcpy(w, s, len); w[len] = "\0"; return skip_space(end); } static int get_words(const char *s, char *w1, char *w2, char *w3, size_t size) { if ((s = skip_bracket(s, "[")) == NULL) return 0; s = copy_word(w1, s, size); if ((s = skip_bracket(s, "]")) == NULL) return 0; s = copy_word(w2, s, size); if ((s = skip_bracket(s, "[")) == NULL) return 0; s = copy_word(w3, s, size); if ((s = skip_bracket(s, "]")) == NULL) return 0; return 1; } 

Svar

Du kan använda en tillståndsmaskin för att slutföra den här uppgiften,

#include <stdio.h> #include <string.h> void Tokenize(char* s) { // the following array return new state based on current state and current scanned char // Input: * [ ] space Print Tokenize Current State Expression /*Next state:*/char StateArray[12][3][4] = {{{11,1,11,0} ,{0,0,0,0},{0,0,0,0} }, //0 {space}*{[} {{2,11,11,1} ,{1,0,0,0},{0,0,0,0}}, //1 {space}*{char} {{2,11,4,3} ,{1,0,0,0},{0,0,1,0}}, //2 {char}*{space}?{]} {{11,11,4,3} ,{0,0,0,0},{0,0,1,0}}, //3 {space}*{]} {{5,11,11,4} ,{1,0,0,0},{0,0,0,0}}, //4 {space)*{char} {{5,7,11,6} ,{1,0,0,0},{0,1,0,0}}, //5 {char}*{space}?{[} {{11,7,11,6} ,{0,0,0,0},{0,1,0,0}}, //6 {space}*{[} {{8,11,11,7} ,{1,0,0,0},{0,0,0,0}}, //7 {space}*{char} {{8,11,10,9} ,{1,0,0,0},{0,0,1,0}}, //8 {char}*{space}?{]} {{11,11,10,9} ,{0,0,0,0},{0,0,1,0}}, //9 {space}*{]} {{11,11,11,10} ,{0,0,0,0},{0,0,0,0}}, //10 {space}* {{11,11,11,11} ,{0,0,0,0},{0,0,0,0}} }; char state=0; int len = strlen(s); for(int i =0;i<len;i++) { if(StateArray[state][1][(s[i]^91)? ((s[i]^93)?((s[i]^32)? 0:3):2):1]) printf("%c",s[i]); if(StateArray[state][2][(s[i]^91)? ((s[i]^93)?((s[i]^32)? 0:3):2):1]) printf("\n"); state=StateArray[state][0][(s[i]^91)? ((s[i]^93)?((s[i]^32)? 0:3):2):1]; switch(state) { case 11: printf("Error at column %d",i); case 10: if(i==len-1) { printf("\nParsing completed"); } } } } int main(void) { char* s= " [ word1 ] word2word [ 3 ] "; // test string Tokenize(s); } 

Kommentarer

  • Hej och Välkommen till Code Review. Den här koden är egentligen inte en recension. Det är snarare ett alternativt sätt att göra saker med liten förklaring till vad den gör, varför den fungerar och varför den är bättre än originalet. Dessutom ser jag inte ut genom det och oroa dig för saknade hängslen, fall-genom uttalanden och dunkla bitvisa manipulationer som inte är dokumenterade. Överväg att lägga till detaljer om varför detta är bättre och vad det löser annorlunda än OP och varför dessa val ger bättre kod.
  • Skapade du det här för hand eller är det något verktyg involverat? Jag gillar konceptet men jag ’ skulle vara livrädd för att stödja detta. Det finns så många magiska siffror.

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *