2014-05-10 34 views
1

所以我一直在研究这一点,并且我遇到了一些奇怪的问题。最终目标是通过空格和引号将输入字符串分开(即,“这是一个非常”非常复杂“的例子,这是一个非常非常复杂的例子)。现在看来,除了第一个字符串之外,它正确地将其分开。尝试使用空格和引号来标记字符串

这(BUFF被传递与来自函数getline的值):

char **tokens = (char **)malloc(sizeof(char)); 
char *temp; 
int count = 0; 
int prev = 0; 
// Get tokens 
for (int i = 0; i <= strlen(command) && running; i++) { 
    if (i > prev && strncmp((buff + i), " ", 1) == 0) { 
     temp = (char **)realloc(tokens, (sizeof(char)) * WORD_SIZE * (++count)); 
     if (temp == NULL) { 
      fprintf(stderr, "Error in parsing: ran out of memory\n"); 
      running = false; 
      free(tokens); 
     } 
     else { 
      tokens = temp; 
      *(temp) = (buff + i); 
      strncpy(*(temp), "\0", 1); 
      temp = tokens + WORD_SIZE * (count - 1); 
      *(temp) = buff+prev; 
      prev = i+1; 
     } 
    } 
    else if (strncmp((buff + i), "\"", 1) == 0) { 
     *(temp) = (buff + i); 
      strncpy(*(temp), "\0", 1); 
     i++; 
     prev = i; 
     for (; strncmp((buff + i), "\"", 1) != 0; i++) { } 
     temp = (char **)realloc(tokens, (sizeof(char)) * WORD_SIZE * (++count)); 
     if (temp == NULL) { 
      fprintf(stderr, "Error in parsing: ran out of memory\n"); 
      running = false; 
      free(tokens); 
     } 
     else { 
      tokens = temp; 
      *(temp) = (buff + i); 
      strncpy(*(temp), "\0", 1); 
      temp = tokens + WORD_SIZE * (count - 1); 
      *(temp) = buff+prev; 
      prev = i+1; 
     } 
    } 
    else if (strncmp((buff + i), "\0", 1) == 0) { 
     temp = (char **)realloc(tokens, (sizeof(char)) * WORD_SIZE * (++count)); 
     if (temp == NULL) { 
      fprintf(stderr, "Error in parsing: ran out of memory\n"); 
      running = false; 
      free(tokens); 
     } 
     else { 
      tokens = temp; 
      temp = tokens + WORD_SIZE * (count - 1); 
      *(temp) = buff+prev; 
      prev = i+1; 
     } 
    } 
} 
for (int i = 0; i < count; i++) 
    printf("\t%i: %s\n", i, *tokens + sizeof(char) * WORD_SIZE * i); 

现在,如果我输入“这是一个测试”(不包括引号),我得到:
0:
1:
2:一个
3:测试

报价多一点搞砸了,因为 “这个\” 是\ “非常\ ”非常复杂的\“ 测试” 我得到:
0:
1:是一个
2:
3:非常复杂
4:测试

+0

首先OBSN。 (但 - 可能 - 与你的问题无关):你需要在'char ** tokens =(char **)malloc(sizeof(char));'中分配sizeof(char *)'',而不是'sizeof char)'(通常是'1')。 (另外,不需要在C中使用'malloc') – usr2564301

+0

@Jongware是的,我继续改变它,但是在添加每个标记(包括第一次)之前重新分配指针,所以malloc实际上只是一个形式上,它的大小并不重要。 – Michael

+0

抓好@Jongware。实际上'sizeof(char)'是_always_ 1. – Gene

回答

1

这里是从头开始一个全新的写,因为这是比较容易重新编写自己的代码(如果道歉那不是你的意图)。一些注意事项:

  1. 无需测试以前的malloc s。你可以安全地用realloc一个NULL指针。
  2. if (strncmp((buff + i), "\"", 1) == 0) - 您可以立即测试buff[i]
  3. 为什么所有那prev洗牌? :)这足以让在您的字符串上循环
  4. 我离开temp测试成功realloc因为你也有。在我的代码中实际上是没有必要的,因为它只是退出main
  5. 增加:字符"也引入了一个新的“单词”,当没有一个空格。

代码:

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

int main (void) 
{ 
    char **tokens = NULL; 
    int i, count = 0, strcount; 
    char **temp, *iterate; 

    char *input = "this \"is a\" very \"very complex\" test"; 

    iterate = input; 

    if (iterate) 
    { 
     while (*iterate) 
     { 
      while (*iterate == ' ') 
       iterate++; 

      if (!*iterate) 
       break; 

      temp = realloc(tokens, sizeof(char *) * (count+1)); 
      if (temp == NULL) 
      { 
       fprintf(stderr, "Error in parsing: ran out of memory\n"); 
       return -1; 
      } 
      tokens = temp; 

      if (*iterate == '\"') 
      { 
       iterate++; 
       strcount = 0; 
       while (iterate[strcount] && iterate[strcount] != '\"') 
        strcount++; 
       tokens[count] = malloc(strcount+1); 
       strncpy (tokens[count], iterate, strcount); 
       tokens[count][strcount] = 0; 
       count++; 
       iterate += strcount; 
       if (*iterate == '\"') 
        iterate++; 
      } else 
      { 
       strcount = 0; 
       while (iterate[strcount] && iterate[strcount] != ' ' && iterate[strcount] != '\"') 
        strcount++; 
       tokens[count] = malloc(strcount+1); 
       strncpy (tokens[count], iterate, strcount); 
       tokens[count][strcount] = 0; 
       count++; 
       iterate += strcount; 
      } 
     } while (*iterate); 
    } 

    for (i = 0; i < count; i++) 
     printf("\t%i: %s\n", i, tokens[i]); 

    return 0; 
} 

输出为this "is a" very "very complex" test

0: this 
1: is a 
2: very 
3: very complex 
4: test 
+0

谢谢,不幸的是我没有最好的教授,所以95%的知识都来自于我自己。这意味着有很多我想念的东西,比如你可以索引指针。 – Michael

+0

@迈克尔:在这种情况下,+1显示的努力!至少这是一次勇敢的尝试。 – usr2564301

+0

是的,我真的不喜欢向非概念性问题寻求帮助,但我觉得我只是在做错事。我足够了解我的代码在我修改时变得多么糟糕。 – Michael

3

你说的替代码会好起来的。如果您使用确定性有限自动机模型来思考它们,简单字符串解析算法几乎总是更容易,并产生更多可维护代码。网上有很多关于DFA的免费参考资料。

以下是解决您问题的DFA。

dfa

的意义[任何]为 “一切”。换句话说,如果没有其他转换匹配,就拿这个。它成为C switch中的default案件。 [eos]的含义是“字符串结尾”或空字符。

请注意,DFA可让您对所有案例进行系统化处理,例如在单词中间出现引号。在这里我把它当作当前单词的结尾和新引用单词的开头。如果规范发生变化,则DFA很容易更改,而且这些更改将转化为代码,而不需要费力思维。

剩下的就是添加“action code”来捕获令牌开始,并在明显的地方覆盖空终止符。在C,我们有:

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

char **tokenize(char *str, int *n_tokens_rtn) 
{ 
    // State of the DFA. 
    enum { Error = -1, Start, InQuoted, InWord } state = Start; 

    // String pointer and current character 
    int cp = 0; 

#define CURRENT_CHAR (str[cp]) 
#define ADVANCE_TO_NEXT_CHAR do { ++cp; } while (0) 
#define MARK_END_OF_TOKEN do { str[cp] = '\0'; } while (0) 

    // Token pointer and buffer. Allocate biggest possible and shrink at end. 
    int tp = 0; 
    char **tokens = safe_malloc((1 + strlen(str)/2) * sizeof *tokens); 

#define SAVE_TOKEN do { tokens[tp++] = &str[cp]; } while (0) 

    // Each iteration is one DFA transition. 
    for (;;) { 
    switch (state) { 
    case Start: 
     switch (CURRENT_CHAR) { 
     case '\0': 
     goto done_scanning; 

     case ' ': case '\t': case '\n': 
     ADVANCE_TO_NEXT_CHAR; 
     break; 

     case '"': 
     state = InQuoted; 
     ADVANCE_TO_NEXT_CHAR; 
     SAVE_TOKEN; 
     break; 

     default: 
     state = InWord; 
     SAVE_TOKEN; 
     ADVANCE_TO_NEXT_CHAR; 
     break; 
     } 
     break; 

    case InQuoted: 
     switch (CURRENT_CHAR) { 
     case '\0': 
     state = Error; // Missing close quote. 
     break; 

     case '"': 
     state = Start; 
     MARK_END_OF_TOKEN; 
     ADVANCE_TO_NEXT_CHAR; 
     break; 

     default: 
     ADVANCE_TO_NEXT_CHAR; 
     break; 
     } 
     break; 

    case InWord: 
     switch (CURRENT_CHAR) { 

     case '\0': 
     goto done_scanning; 

     case ' ': case '\t': case '\n': 
     state = Start; 
     MARK_END_OF_TOKEN; 
     ADVANCE_TO_NEXT_CHAR; 
     break; 

     case '"': // Word ended in quote, not space. 
     state = InQuoted; 
     MARK_END_OF_TOKEN; 
     ADVANCE_TO_NEXT_CHAR; 
     SAVE_TOKEN; 
     break; 

     default: 
     ADVANCE_TO_NEXT_CHAR; 
     break; 
     } 
     break; 

    case Error: 
     fprintf(stderr, "Syntax error.\n"); 
     goto done_scanning; 
    } 
    } 

done_scanning: 
    // Return number of tokens if caller is interested. 
    if (n_tokens_rtn) *n_tokens_rtn = tp; 

    // Append a null terminator for good measure. 
    tokens[tp++] = NULL; 

    // Trim the returned value to the right size. 
    return realloc(tokens, tp * sizeof *tokens); 
} 

int main(void) 
{ 
    char str[] = "this \"is a\" very \"very complex\" example"; 
    char **tokens = tokenize(str, NULL); 
    for (int i = 0; tokens[i]; i++) 
    printf("%s\n", tokens[i]); 
    return 0; 
} 
+0

从状态InQuoted,一个'''应该可能到InWord,而不是开始(至少如果你想要像shell一样处理混合引用/不引用的东西 - 类似'ab“cd”ef“是单个标记' abc def')。 –

+0

@ChrisDodd谢谢。我将一个非引用引号边界作为标记分隔符对待,他没有指定.DFA的好处是它或多或少地强制您明确地决定哪些特殊代码通常不适用DFA的 – Gene

+0

+1除了欣赏解决问题和实现方面的问题外,我还在CS理论课上,所以很高兴看到他们的实际应用 – Michael

0

这看起来像一个比较简单的问题,所以不是写一个完整的解析器,我使用标准C库做繁重的写了一个解决方案。如果此解决方案具有吸引力,请自行判断。可能有些方法可以改善我所做的工作,以使代码更清晰一些,但我会将其留作任何倾向于此的人的练习。

#include <stdlib.h> 
#include <stdio.h> 
#include <string.h> 

int main() 
{ 
    char input_string[] = "this \"is a\" very \"very complex\" test"; 
    char **tokens = NULL; 
    int token_count = 0; 
    char *ptr = input_string; 
    int i; 
    char *next_ptr = ptr; 

    while (*ptr && next_ptr) 
    { 
     while (*ptr == ' ') ptr++; 
     tokens = realloc(tokens, ++token_count * sizeof(char *)); 
     if (tokens == NULL) 
      return -1; 
     if (*ptr == '"') 
      next_ptr = strchr(ptr+1, '"'); 
     else 
      next_ptr = strpbrk(ptr, " \""); 
     if (next_ptr) 
     { 
      tokens[token_count-1] = malloc(sizeof(char) * (next_ptr - (ptr+(*ptr=='"'))) + 1); 
      if (tokens[token_count-1] == NULL) 
       return -1; 
      strncpy(tokens[token_count-1], (ptr+(*ptr=='"')), next_ptr - (ptr+(*ptr=='"'))); 
      tokens[token_count-1][next_ptr - (ptr+(*ptr=='"'))] = 0; 
      ptr = next_ptr + (*ptr=='"'); 
     } 
     else 
      tokens[token_count-1] = strdup(ptr+(*ptr=='"')); 
    } 

    for (i = 0; i < token_count; ++i) 
     printf("[%d]: %s\n", i, tokens[i]); 

    return 0; 
} 

输出:

[0]: this 
[1]: is a 
[2]: very 
[3]: very complex 
[4]: test 
相关问题