web-dev-qa-db-fra.com

Entrée de chaîne pour flex lexer

Je souhaite créer une boucle read-eval-print à l'aide de l'analyseur flex/bison. Le problème, c'est que le lexer généré par flex veut une entrée de type FILE * et que ce soit char *. Y a-t-il un moyen de faire ça?

Une suggestion a été de créer un canal, lui donner la chaîne et ouvrir le descripteur de fichier et l'envoyer au lexer. C'est assez simple, mais on se sent alambiqué et pas très indépendant de la plate-forme. Y a-t-il un meilleur moyen?

49
bjorns

Les routines suivantes sont disponibles pour configurer des tampons d'entrée pour analyser des chaînes en mémoire au lieu de fichiers (comme le fait yy_create_buffer):

  • YY_BUFFER_STATE yy_scan_string(const char *str): analyse une chaîne terminée par NUL`
  • YY_BUFFER_STATE yy_scan_bytes(const char *bytes, int len): scanne len octets (y compris éventuellement NUL) à partir d'octets de localisation

Notez que ces deux fonctions créent, renvoient un descripteur YY_BUFFER_STATE correspondant (que vous devez supprimer avec yy_delete_buffer () lorsque vous avez terminé), afin que yylex () analyse une copie de la chaîne ou des octets. Ce comportement peut être souhaitable car yylex () modifie le contenu de la mémoire tampon à balayer.

Si vous voulez éviter la copie (et yy_delete_buffer) en utilisant:

  • YY_BUFFER_STATE yy_scan_buffer(char *base, yy_size_t size)

échantillon principal:

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

Reportez-vous à cette section du manuel de Flex pour plus d'informations sur l'analyse des mémoires tampon en mémoire, telles que les chaînes.

19
unwind

flex peut analyser char * en utilisant l'une des trois fonctions suivantes: yy_scan_string(), yy_scan_buffer() et yy_scan_bytes() (voir le documentation ). Voici un exemple du premier:

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

Les instructions équivalentes pour yy_scan_buffer() (qui nécessite une chaîne à double terminaison null):

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

Ma réponse reprend certaines des informations fournies par @dfa et @jlholland, mais aucun des codes de leurs réponses ne semblait fonctionner pour moi.

9
sevko

Voici ce que je devais faire:

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

vous ne pouvez pas externaliser le typedef, ce qui est logique lorsque vous y réfléchissez.

8
jlholland

La réponse acceptée est incorrecte. Cela causera des fuites de mémoire.

En interne, yy_scan_string appelle yy_scan_bytes qui, à son tour, appelle yy_scan_buffer.

yy_scan_bytes alloue de la mémoire pour une copie du tampon d'entrée.

yy_scan_buffer fonctionne directement sur le tampon fourni.

Avec les trois formulaires, vous DEVEZ appeler yy_delete_buffer pour libérer les informations sur l'état du tampon (YY_BUFFER_STATE).

Cependant, avec yy_scan_buffer, vous évitez l’affectation interne/copie/libre du tampon interne.

Le prototype de yy_scan_buffer ne prend PAS de caractère const * et vous ne devez PAS vous attendre à ce que le contenu reste inchangé.

Si vous avez alloué de la mémoire pour conserver votre chaîne, vous devez la libérer APRÈS que vous ayez appelé yy_delete_buffer.

De plus, n'oubliez pas que yywrap renvoie 1 (différent de zéro) lorsque vous analysez JUST cette chaîne.

Vous trouverez ci-dessous un exemple COMPLET.

%%

<<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);
}
3
Tad Carlucci

Sinon, vous pouvez redéfinir la fonction YY_INPUT dans le fichier Lex, puis définir votre chaîne sur l'entrée Lex. Comme ci-dessous:

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

Dans votre fichier main.c, avant de scanner, vous devez d'abord définir le tampon de Lex:

set_lexbuf(your_string);
scanning...
1
Kingcesc

Il y a ce code amusant dans 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); }
0
user3121260

voici un petit exemple d'utilisation de bison/flex en tant qu'analyseur dans votre code cpp pour analyser une chaîne et modifier une valeur de chaîne en fonction. . 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
0
Or Davidi