将字符串输入转换为Flex词法分析器

61

我想使用flex/bison解析器创建一个read-eval-print循环。问题是,由flex生成的词法分析器需要FILE*类型的输入,而我想要传入char*类型的输入。有没有什么方法可以实现这一点?

有人建议创建一个管道,将字符串输入进去,然后打开文件描述符并发送给词法分析器。这个方法相当简单,但感觉有些绕弯子,也不太平台独立。有没有更好的方法呢?

8个回答

61
以下程序用于设置输入缓冲区以扫描内存中的字符串,而不是文件(就像yy_create_buffer一样):

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

请注意,这两个函数都创建并返回相应的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),你能否请添加一些关于双null要求的内容? - Alec Teal
4
样例的主要部分可能会失败,因为yy_scan_buffer需要一个可写的缓冲区(它在临时修改缓冲区,插入NUL以终止yytext,然后恢复原始字符),并且需要两个终止NUL字节。 - Chris Dodd
顺便说一下,在我的C++程序中,我需要声明yy_scan_bytes带有一个size_t len参数,以避免链接器错误。 - coldfix
2
如果示例包含1.yy_scan_string而不是yy_scan_buffer,并且进行了清理,则示例将是正确的。 scan_string会自动设置一个缓冲区(需要清理),并且不需要长度规定或双重空终止符。 - Andrei Bârsan
请为我们这些小白解释如何避免出现错误:'yy_scan_buffer'未在此范围内声明。 - Elad Weiss

21

请参阅Flex手册的此部分,了解如何扫描内存缓冲区(例如字符串)的信息。


16

flex可以使用以下三个函数之一解析char *字符串:yy_scan_string()yy_scan_buffer()yy_scan_bytes()(请参阅文档)。下面是第一个函数的示例:

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提供的一些信息,但是他们的回答代码似乎对我不起作用。


1
它将如何识别struct yy_buffer_state是什么? - Ankur Gautam
它不会,但它并不关心。我们所做的一切都是声明对名为 yy_buffer_state 的某个未知结构体的不透明指针,编译器知道这些指针的长度为4个字节(或者你的系统指针大小),因为我们从未访问过该结构体的任何成员变量,所以它不需要了解该结构体的组合方式。您可以在typedef中将struct yy_buffer_state替换为voidint或其他类型,因为每个类型的指针大小都相同。 - sevko

8
以下是需要完成的任务:

这是我需要完成的任务:

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
你不能使用未定义的类型进行外部声明[line 1]。 - Ankur Gautam
2
只需要指定一个单一的 '\0',因为字符串字面值中隐含了第二个。 - a3f

5

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

在内部,yy_scan_string调用yy_scan_bytes,而yy_scan_bytes又调用yy_scan_buffer。

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

yy_scan_buffer直接处理提供的缓冲区。

使用这三种形式时,必须调用yy_delete_buffer以释放flex缓冲区状态信息(YY_BUFFER_STATE)。

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

yy_scan_buffer的原型不接受const char*,您不能指望其内容保持不变。

如果您分配了内存来保存字符串,则在调用yy_delete_buffer之后,您负责释放该内存。

还要记得当您仅解析此字符串时,让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吗? - velocirabbit

1

这是一个使用Bison / Flex作为解析器的小例子,可以在您的C++代码中用于解析字符串并根据其更改字符串值。 (由于删除了部分代码,因此可能存在不相关的部分。) 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

1
另一种方法是,在词法分析器文件中重新定义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 中,在扫描之前,需要先设置词法分析器的缓冲区:

set_lexbuf(your_string);
scanning...

0

在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); }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接