Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does flex support bison-location exactly?

Tags:

I'm trying to use flex and bison to create a filter, because I want get certain grammar elements from a complex language. My plan is to use flex + bison to recognise the grammar, and dump out the location of elements of interest. (Then use a script to grab text according the locations dumped.)

I found flex can support a bison feature called bison-locations, but how it works in exactly. I tried the example in flex document, it seems the yylloc is not set automatically by flex, I always get (1,0)-(1,0). Could flex calculate each token's location automatically? If not, what interface function is defined for me to implement? Is there any example?

Any better solution regarding to tools?

Best Regards, Kevin

Edit:

Now the interface for yylex turn to:

int yylex(YYSTYPE * yylval_param,YYLTYPE * yylloc_param ); 

bison manual does not specify how lexer should implement to correctly set yylloc_param. For me it is hard to manually trace column number of each token.

like image 592
Kevin Yu Avatar asked Mar 18 '09 01:03

Kevin Yu


People also ask

What are flex and bison used for?

Flex and Bison are tools for building programs that handle structured input. They were originally tools for building compilers, but they have proven to be useful in many other areas.

What is bison program?

Bison is a general-purpose parser generator that converts a grammar description for an LALR(1) context-free grammar into a C program to parse that grammar.


2 Answers

The yylex declaration probably changed because you used a reentrant or pure-parser. Seems like many documents around the web suggest it's required if you want bison locations to work but it's not required.

I needed line numbers too and found the Bison documentation confusing in that regard. The simple solution (using the global var yylloc): In your Bison file just add the %locations directive:

%{ ... %} %locations ... %% ... 

in your lexer:

%{ ... #include "yourprser.tab.h"  /* This is where it gets the definition for yylloc from */ #define YY_USER_ACTION yylloc.first_line = yylloc.last_line = yylineno; %} %option yylineno ... %% ... 

The YY_USER_ACTION macro is "called" before each of your token actions and updates yylloc. Now you can use the @N/@$ rules like this:

statement : error ';'   { fprintf(stderr, "Line %d: Bad statement.\n", @1.first_line); } 

, or use the yylloc global var:

void yyerror(char *s) {   fprintf(stderr, "ERROR line %d: %s\n", yylloc.first_line, s); } 
like image 145
Shlomi Loubaton Avatar answered Oct 20 '22 10:10

Shlomi Loubaton


Neither bison nor flex updates yylloc automatically, but it's actually not difficult to do it yourself—if you know the trick.

The trick to implementing yylloc support is that, even though yyparse() declares yylloc, it never changes it. That means that if you modify yylloc in one call to the lexer, you'll find the same values in it at the next call. Thus, yylloc will contain the position of the last token. Since the last token's end is the same as the current token's start, you can use the old yylloc value to help you determine the new value.

In other words, yylex() should not calculate yylloc; it should update yylloc.

To update yylloc, we must first copy the last_ values to first_, and then update the last_ values to reflect the length of the just-matched token. (This is not the strlen() of the token; it's the lines-and-columns length.) We can do this in the YY_USER_ACTION macro, which is called just before any lexer action is performed; that ensures that if a rule matches but it doesn't return a value (for instance, a rule skipping whitespace or comments), the location of that non-token is skipped, rather than being included at the beginning of the actual token, or lost in a way that makes the location tracking inaccurate.

Here's a version meant for a reentrant parser; you could modify it for a non-reentrant parser by swapping the -> operators for .:

#define YY_USER_ACTION \     yylloc->first_line = yylloc->last_line; \     yylloc->first_column = yylloc->last_column; \     for(int i = 0; yytext[i] != '\0'; i++) { \         if(yytext[i] == '\n') { \             yylloc->last_line++; \             yylloc->last_column = 0; \         } \         else { \             yylloc->last_column++; \         } \     } 

If you'd prefer, you could instead put that code in a function and make the macro call the function, but the two techniques are equivalent.

like image 21
Becca Royal-Gordon Avatar answered Oct 20 '22 12:10

Becca Royal-Gordon