Строковый ввод для гибкого лексера

Я хочу создать цикл чтения-оценки-печати с помощью синтаксического анализатора flex/bison. Проблема в том, что лексер, сгенерированный flex, требует ввода типа FILE*, а я бы хотел, чтобы он был char*. Есть какой-либо способ сделать это?

Одно из предложений состояло в том, чтобы создать канал, передать ему строку, открыть файловый дескриптор и отправить в лексер. Это довольно просто, но кажется запутанным и не очень независимым от платформы. Есть ли способ лучше?


person bjorns    schedule 23.04.2009    source источник


Ответы (8)


Следующие подпрограммы доступны для настройки входных буферов для сканирования строк в памяти вместо файлов (как это делает 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();
}
person dfa    schedule 23.04.2009
comment
Эй, dfa (который подходит, учитывая его гибкость), не могли бы вы добавить что-нибудь о требовании двойного нуля? - person Alec Teal; 08.09.2013
comment
Образец main, скорее всего, выйдет из строя, так как yy_scan_buffer требуется доступный для записи буфер (он временно изменяет буфер, вставляя NUL для завершения yytext, а затем восстанавливает исходные символы), И требует ДВА завершающих NUL-байта. - person Chris Dodd; 08.05.2014
comment
Кстати, в моей программе на C++ мне нужно было объявить yy_scan_bytes с параметром size_t len, чтобы избежать ошибок компоновщика. - person coldfix; 05.05.2015
comment
Пример был бы правильным, если бы он содержал 1. yy_scan_string вместо yy_scan_buffer и 2. очистка. scan_string автоматически устанавливает буфер (который нуждается в очистке) и не нуждается в спецификации длины или двойном нулевом конце. - person Andrei Bârsan; 27.08.2015
comment
Пожалуйста, объясните нам, чайникам, как избежать ошибки: ‘yy_scan_buffer’ не был объявлен в этой области видимости. - person Elad Weiss; 22.12.2017

Информацию см. в этом разделе руководства по Flex. о том, как сканировать буферы в памяти, такие как строки.

person unwind    schedule 23.04.2009

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, но ни один из кодов их ответов, похоже, не работает для меня.

person sevko    schedule 08.05.2014
comment
как он узнает, что такое struct yy_buffer_state? - person Ankur Gautam; 20.01.2015
comment
Не будет, но ему все равно. Все, что мы делаем, — это объявляем непрозрачные указатели на некую неизвестную структуру с именем yy_buffer_state, которую компилятор Знает имеет ширину 4 байта (или любой другой размер указателя вашей системы): поскольку мы никогда не обращаемся ни к одной из его переменных-членов, ему не нужно знать о составе этой структуры. Вы можете заменить struct yy_buffer_state в typedef на void или int или что-то еще, так как размер указателя будет одинаковым для каждого. - person sevko; 20.01.2015

Вот что мне нужно было сделать:

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, что имеет смысл, если подумать.

person jlholland    schedule 15.02.2012
comment
вы не можете использовать внешний вид без определенного типа [строка 1]. - person Ankur Gautam; 20.01.2015
comment
Нужно только указать один '\0', так как второй неявно присутствует (из-за строкового литерала). - person a3f; 03.07.2015

Принятый ответ неверен. Это приведет к утечкам памяти.

Внутри yy_scan_string вызывает yy_scan_bytes, который, в свою очередь, вызывает yy_scan_buffer.

yy_scan_bytes выделяет память для КОПИИ входного буфера.

yy_scan_buffer работает непосредственно с предоставленным буфером.

Во всех трех формах вы ДОЛЖНЫ вызывать yy_delete_buffer, чтобы освободить информацию о состоянии гибкого буфера (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);
}
person Tad Carlucci    schedule 27.03.2016
comment
Вы должны каждый раз вызывать yylex/yyparse? - person velocirabbit; 05.12.2016

В противном случае вы можете переопределить функцию YY_INPUT в файле lex, а затем установить свою строку на вход 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...
person Kingcesc    schedule 16.10.2014

вот небольшой пример использования bison/flex в качестве синтаксического анализатора внутри вашего cpp-кода для разбора строки и изменения строкового значения в соответствии с ней (несколько частей кода были удалены, поэтому там могут быть ненужные части). 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
person Or Davidi    schedule 24.06.2016

В 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); }
person user3121260    schedule 08.01.2019