【问题标题】:String input to flex lexer弹性词法分析器的字符串输入
【发布时间】:2010-10-21 07:24:15
【问题描述】:

我想使用 flex/bison 解析器创建一个 read-eval-print 循环。麻烦的是,flex 生成的词法分析器需要 FILE* 类型的输入,我希望它是 char*。有没有办法做到这一点?

一个建议是创建一个管道,向它提供字符串并打开文件描述符并发送到词法分析器。这相当简单,但感觉很复杂,而且不是很独立于平台。有没有更好的办法?

【问题讨论】:

    标签: c bison yacc lex flex-lexer


    【解决方案1】:

    以下例程可用于设置输入缓冲区以扫描内存中的字符串而不是文件(如 yy_create_buffer 所做的那样):

    • YY_BUFFER_STATE yy_scan_string(const char *str): 扫描一个以 NUL 结尾的字符串`
    • YY_BUFFER_STATE yy_scan_bytes(const char *bytes, int len):从位置字节开始扫描 len 个字节(可能包括 NUL)

    请注意,这两个函数都会创建、返回相应的 YY_BUFFER_STATE 句柄(完成后必须使用 yy_delete_buffer() 将其删除),因此 yylex() 会扫描字符串或字节的副本。这种行为可能是可取的,因为 yylex() 修改了它正在扫描的缓冲区的内容)。

    如果您想避免使用复制(和 yy_delete_buffer):

    • YY_BUFFER_STATE yy_scan_buffer(char *base, yy_size_t size)

    示例主要:

    int main() {
        yy_scan_buffer("a test string");
        yylex();
    }
    

    【讨论】:

    • 嘿 dfa(考虑到它是 flex 是合适的)您能补充一下关于双空要求的内容吗?
    • 示例 main 可能会失败,因为 yy_scan_buffer 需要一个可写缓冲区(它会临时修改缓冲区,插入 NUL 以终止 yytext 然后恢复原始字符),并且它需要两个终止NUL 字节。
    • 顺便说一下,在我的 C++ 程序中,我需要用 size_t len 参数声明 yy_scan_bytes 以避免链接器错误。
    • 如果该示例包含 1.yy_scan_string 而不是 yy_scan_buffer 和 2.cleanup,则该示例是正确的。 scan_string 自动设置缓冲区(需要清理),不需要长度规范或双空终止符。
    • 请为我们解释一下如何避免错误:'yy_scan_buffer' 没有在这个范围内声明。
    【解决方案2】:

    有关如何扫描内存缓冲区(例如字符串)的信息,请参阅 Flex 手册的 this section

    【讨论】:

      【解决方案3】:

      flex 可以使用以下三个函数之一解析char *yy_scan_string()yy_scan_buffer()yy_scan_bytes()(参见 documentation)。这是第一个的示例:

      typedef struct yy_buffer_state * YY_BUFFER_STATE;
      extern int yyparse();
      extern YY_BUFFER_STATE yy_scan_string(char * str);
      extern void yy_delete_buffer(YY_BUFFER_STATE buffer);
      
      int main(){
          char string[] = "String to be parsed.";
          YY_BUFFER_STATE buffer = yy_scan_string(string);
          yyparse();
          yy_delete_buffer(buffer);
          return 0;
      }
      

      yy_scan_buffer() 的等效语句(需要双空终止字符串):

      char string[] = "String to be parsed.\0";
      YY_BUFFER_STATE buffer = yy_scan_buffer(string, sizeof(string));
      

      我的回答重申了@dfa 和@jlholland 提供的一些信息,但他们的回答代码似乎都不适合我。

      【讨论】:

      • 它将如何识别 struct yy_buffer_state 是什么?
      • 它不会,但它不在乎。我们所做的只是将opaque pointers 声明给一个名为yy_buffer_state 的未知结构,编译器知道它是4 字节宽(或者你的系统的指针大小):因为我们从不访问它的任何成员变量,那么它不需要知道该结构的组成。您可以将typedef 中的struct yy_buffer_state 替换为voidint 或其他任何内容,因为每个指针的大小都是相同的。
      【解决方案4】:

      这是我需要做的:

      extern yy_buffer_state;
      typedef yy_buffer_state *YY_BUFFER_STATE;
      extern int yyparse();
      extern YY_BUFFER_STATE yy_scan_buffer(char *, size_t);
      
      int main(int argc, char** argv) {
      
        char tstr[] = "line i want to parse\n\0\0";
        // note yy_scan_buffer is is looking for a double null string
        yy_scan_buffer(tstr, sizeof(tstr));
        yy_parse();
        return 0;
      }
      

      你不能将 typedef 外部化,这在你考虑时是有道理的。

      【讨论】:

      • 你不能在没有定义类型的情况下 extern [第 1 行]。
      • 一个只需要指定一个'\0',因为第二个是隐式存在的(由于字符串文字)。
      【解决方案5】:

      接受的答案不正确。会导致内存泄漏。

      在内部,yy_scan_string 调用 yy_scan_bytes,后者又调用 yy_scan_buffer。

      yy_scan_bytes 为输入缓冲区的 COPY 分配内存。

      yy_scan_buffer 直接作用于提供的缓冲区。

      对于所有三种形式,您必须调用 yy_delete_buffer 来释放弹性缓冲区状态信息 (YY_BUFFER_STATE)。

      但是,使用 yy_scan_buffer,您可以避免内部缓冲区的内部分配/复制/释放。

      yy_scan_buffer 的原型不采用 const char* 并且您不能期望内容保持不变。

      如果你分配内存来保存你的字符串,你有责任在调用 yy_delete_buffer 之后释放它。

      另外,当您解析 JUST 这个字符串时,不要忘记让 yywrap 返回 1(非零)。

      下面是一个完整的例子。

      %%
      
      <<EOF>> return 0;
      
      .   return 1;
      
      %%
      
      int yywrap()
      {
          return (1);
      }
      
      int main(int argc, const char* const argv[])
      {
          FILE* fileHandle = fopen(argv[1], "rb");
          if (fileHandle == NULL) {
              perror("fopen");
              return (EXIT_FAILURE);
          }
      
          fseek(fileHandle, 0, SEEK_END);
          long fileSize = ftell(fileHandle);
          fseek(fileHandle, 0, SEEK_SET);
      
          // When using yy_scan_bytes, do not add 2 here ...
          char *string = malloc(fileSize + 2);
      
          fread(string, fileSize, sizeof(char), fileHandle);
      
          fclose(fileHandle);
      
          // Add the two NUL terminators, required by flex.
          // Omit this for yy_scan_bytes(), which allocates, copies and
          // apends these for us.   
          string[fileSize] = '\0';
          string[fileSize + 1] = '\0';
      
          // Our input file may contain NULs ('\0') so we MUST use
          // yy_scan_buffer() or yy_scan_bytes(). For a normal C (NUL-
          // terminated) string, we are better off using yy_scan_string() and
          // letting flex manage making a copy of it so the original may be a
          // const char (i.e., literal) string.
          YY_BUFFER_STATE buffer = yy_scan_buffer(string, fileSize + 2);
      
          // This is a flex source file, for yacc/bison call yyparse()
          // here instead ...
          int token;
          do {
              token = yylex(); // MAY modify the contents of the 'string'.
          } while (token != 0);
      
          // After flex is done, tell it to release the memory it allocated.    
          yy_delete_buffer(buffer);
      
          // And now we can release our (now dirty) buffer.
          free(string);
      
          return (EXIT_SUCCESS);
      }
      

      【讨论】:

      • 你应该每次都自己调用 yylex/yyparse 吗?
      【解决方案6】:

      另外,您可以在 lex 文件中重新定义函数 YY_INPUT,然后将您的字符串设置为 LEX 的输入。如下:

      #undef YY_INPUT
      #define YY_INPUT(buf) (my_yyinput(buf))
      
      char my_buf[20];
      
      void set_lexbuf(char *org_str)
      {  strcpy(my_buf, org_str);  }
      
      void my_yyinput (char *buf)
      {  strcpy(buf, my_buf);      } 
      

      在你的main.c中,在扫描之前,你需要先设置lex的缓冲区:

      set_lexbuf(your_string);
      scanning...
      

      【讨论】:

        【解决方案7】:

        这里是一个小例子,在你的 cpp 代码中使用 bison / flex 作为解析器来解析字符串并根据它更改字符串值 (代码的少数部分已被删除,因此可能存在不相关的部分。) parser.y:

        %{
        #include "parser.h"
        #include "lex.h"
        #include <math.h> 
        #include <fstream>
        #include <iostream> 
        #include <string>
        #include <vector>
        using namespace std;
         int yyerror(yyscan_t scanner, string result, const char *s){  
            (void)scanner;
            std::cout << "yyerror : " << *s << " - " << s << std::endl;
            return 1;
          }
            %}
        
        %code requires{
        #define YY_TYPEDEF_YY_SCANNER_T 
        typedef void * yyscan_t;
        #define YYERROR_VERBOSE 0
        #define YYMAXDEPTH 65536*1024 
        #include <math.h> 
        #include <fstream>
        #include <iostream> 
        #include <string>
        #include <vector>
        }
        %output "parser.cpp"
        %defines "parser.h"
        %define api.pure full
        %lex-param{ yyscan_t scanner }
        %parse-param{ yyscan_t scanner } {std::string & result}
        
        %union {
          std::string *  sval;
        }
        
        %token TOKEN_ID TOKEN_ERROR TOKEN_OB TOKEN_CB TOKEN_AND TOKEN_XOR TOKEN_OR TOKEN_NOT
        %type <sval>  TOKEN_ID expression unary_expression binary_expression
        %left BINARY_PRIO
        %left UNARY_PRIO
        %%
        
        top:
        expression {result = *$1;}
        ;
        expression:
        TOKEN_ID  {$$=$1; }
        | TOKEN_OB expression TOKEN_CB  {$$=$2;}
        | binary_expression  {$$=$1;}
        | unary_expression  {$$=$1;}
        ;
        
        unary_expression:
         TOKEN_NOT expression %prec UNARY_PRIO {result =  " (NOT " + *$2 + " ) " ; $$ = &result;}
        ;
        binary_expression:
        expression expression  %prec BINARY_PRIO {result = " ( " + *$1+ " AND " + *$2 + " ) "; $$ = &result;}
        | expression TOKEN_AND expression %prec BINARY_PRIO {result = " ( " + *$1+ " AND " + *$3 + " ) "; $$ = &result;} 
        | expression TOKEN_OR expression %prec BINARY_PRIO {result = " ( " + *$1 + " OR " + *$3 + " ) "; $$ = &result;} 
        | expression TOKEN_XOR expression %prec BINARY_PRIO {result = " ( " + *$1 + " XOR " + *$3 + " ) "; $$ = &result;} 
        ;
        
        %%
        
        lexer.l : 
        
        %{
        #include <string>
        #include "parser.h"
        
        %}
        %option outfile="lex.cpp" header-file="lex.h"
        %option noyywrap never-interactive
        %option reentrant
        %option bison-bridge
        
        %top{
        /* This code goes at the "top" of the generated file. */
        #include <stdint.h>
        }
        
        id        ([a-zA-Z][a-zA-Z0-9]*)+
        white     [ \t\r]
        newline   [\n]
        
        %%
        {id}                    {    
            yylval->sval = new std::string(yytext);
            return TOKEN_ID;
        }
        "(" {return TOKEN_OB;}
        ")" {return TOKEN_CB;}
        "*" {return TOKEN_AND;}
        "^" {return TOKEN_XOR;}
        "+" {return TOKEN_OR;}
        "!" {return TOKEN_NOT;}
        
        {white};  // ignore white spaces
        {newline};
        . {
        return TOKEN_ERROR;
        }
        
        %%
        
        usage : 
        void parse(std::string& function) {
          string result = "";
          yyscan_t scanner;
          yylex_init_extra(NULL, &scanner);
          YY_BUFFER_STATE state = yy_scan_string(function.c_str() , scanner);
          yyparse(scanner,result);
          yy_delete_buffer(state, scanner);
          yylex_destroy(scanner);
          function = " " + result + " ";  
        }
        
        makefile:
        parser.h parser.cpp: parser.y
            @ /usr/local/bison/2.7.91/bin/bison -y -d parser.y
        
        
        lex.h lex.cpp: lexer.l
            @ /usr/local/flex/2.5.39/bin/flex lexer.l
        
        clean:
            - \rm -f *.o parser.h parser.cpp lex.h lex.cpp
        

        【讨论】:

          【解决方案8】:

          在 libmatheval 中有这个有趣的代码:

          /* Redefine macro to redirect scanner input from string instead of
           * standard input.  */
          #define YY_INPUT( buffer, result, max_size ) \
          { result = input_from_string (buffer, max_size); }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2019-05-07
            • 1970-01-01
            • 2011-02-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多