You are not logged in.

#1 2012-06-05 04:39:46

/dev/zero
Member
From: Melbourne, Australia
Registered: 2011-10-20
Posts: 1,247

Can lex/yacc return values to other C code? [Solved]

Dear Archers,

TLDR: I would like a function that internally uses lex/yacc to fill a struct with data from a file.


Longer:
I've been playing around with the development of a simple roguelike. The concept is a zombie game, in my own mind a little like The Walking Dead. It uses ncurses and I currently have the basic game mechanics working nicely. I can fill the console with zombies that shuffle around randomly unless I get too close, and then they start chasing.

Screenshot:
JVYqj.png

I would now like to implement game loading and saving. Well, saving should be fairly easy with a little fprintf, but for loading I didn't want to write my own parser, not when I already know enough about lex and yacc to think they would be good for this.

However, it isn't really clear how to use them without doing a total rewrite and recasting all the game mechanics in terms of what lex and yacc can understand.

To show what I'm talking about, let's keep things as absolutely simple as possible, and suppose I have a save file that always just has one line representing the character's health, like so:

pc health=3

The following shows how I would like to load this information into the game,

typedef struct {
   int health;
} character;
typedef character *chr;

chr load_game() {
    FILE* sf;
    sf = fopen( "game.save", "r" );
    chr c = custom_yyparse( sf );
    fclose( sf );
    return c;
}

This way, lex and yacc would just interface nicely with what I already have.

I've tried googling lots of things and looking through the manuals. It doesn't look hopeful. Still, writing a parser manually would be a bit of a PITA, and what if I decide to change the format later, for example when I start using more complicated maps and permitting character to carry weapons? The PITA factor would just keep climbing.

Does anyone have any tips?


Edit: added an "fclose" to the example code, otherwise the whole forum could have been destroyed.

Last edited by /dev/zero (2012-06-05 08:36:54)

Offline

#2 2012-06-05 04:50:01

Trilby
Inspector Parrot
Registered: 2011-11-29
Posts: 30,330
Website

Re: Can lex/yacc return values to other C code? [Solved]

Short answer: Yes, definitely.  But I suspect you want more than that.

I believe there are (at least) two good ways, once I read the rest of your post I might be able to suggest one over the other, but in a nutshell, to help you start googling, you'll want to look at "reentrant" scanners, and/or redefining the output function.

In a very short simpler lexer I made I redefined the input macro YY_INPUT, I'm sure there is an analogous output.  This would be far simpler than a reentrant scanner, but the latter would provide more flexibility (and multithreading to not interfere with other program processes).

Edit: after reading, wouldn't it be far easier to simply embed all that work in the lexer actions?  Basically

%{
chr tchr;
%}

%%

<*>pc health=        BEGIN health;
<health>[0-9]*        tchr->health=yytext;

/* snip, other stuff here */

chr load_game(){
    // open a file for YY_INPUT, or some such here.
    yylex();
    return tchr;
}

Edit: the tchr->health=yytext probably needs more - it shouldn't work as is.  But the point is, you can do all the work of setting the variable(s) in the actions of the lexer.  Just set them to a temporary chr (tchr here) then return that temporary chr (or a copy of it as needed).

Edit2: yes, I know my regexes are all wrong.  Let's just call this pseudocode - it was just scribbled down to get the above point across, it's not remotely functional.

Last edited by Trilby (2012-06-05 05:11:02)


"UNIX is simple and coherent" - Dennis Ritchie; "GNU's Not Unix" - Richard Stallman

Offline

#3 2012-06-05 05:12:01

/dev/zero
Member
From: Melbourne, Australia
Registered: 2011-10-20
Posts: 1,247

Re: Can lex/yacc return values to other C code? [Solved]

Thanks heaps Trilby. Good ideas. I'll see where they take me smile.

Offline

#4 2012-06-05 08:36:28

/dev/zero
Member
From: Melbourne, Australia
Registered: 2011-10-20
Posts: 1,247

Re: Can lex/yacc return values to other C code? [Solved]

Okay, looks like we've solved it. I got it to work with bison/yacc as well; I considered this important to permit a more complicated syntax as the game evolves. It would already be complicated with needing to save the locations of large numbers of zombies, and distinguish them from the player character.

There were two tricks required that work together:

  1. Predeclare the pointer to the struct in the yacc definition section

  2. The load function must malloc space for same.


Example files for a minimal solution:

game.save (as before):

pc health=3

main.c

#include <stdlib.h>
#include <stdio.h>
#include "main.h"

int main( int argc, char *argv[] ) {	
    chr c = load_game();
    printf( "%d\n", c->health );
    free( c );
    return 0;
}

main.h

#ifndef _MAIN_HEADER_
#define _MAIN_HEADER_
extern FILE* yyin;

typedef struct {
    int health;
} creature;
typedef creature *chr;

chr load_game();
void yyerror();
#endif

save.l

%{
#include "main.h"
#include "save.tab.h"
%}

%%
pc|npc  return CH;
[0-9]+  yylval.num=atoi(yytext); return NUM;
health  return HEALTH;
[=]     return *yytext;
\n  
.   

%%

int yywrap() {
    return 1;
}

save.y

%{
#include <stdio.h>
#include "main.h"
chr c;
%}
%union{
    int num;
};
%token CH HEALTH
%token <num> NUM 

%%

save:
    | save character    
    | character        
    ;

character:
    character CH stat 
    | CH stat    
    ;

stat:
    HEALTH '=' NUM  { c->health = $3; }
    ;

%%
void yyerror() {}

chr load_game() {
    c = (chr)malloc(sizeof(creature));
    yyin = fopen( "game.save", "r" );

    yyparse();
    fclose( yyin );
    return c;
}

Makefile

NAME = savetest

.PHONY: all clean

all: save.tab.c lex.yy.c
	gcc -o $(NAME) save.tab.c lex.yy.c main.c

save.tab.c save.tab.h: save.y
	bison -d save.y

lex.yy.c: save.tab.h
	flex save.l

clean:
	rm -f save.tab.c save.tab.h lex.yy.c $(NAME)

Thanks a million Trilby big_smile.

Offline

#5 2012-06-05 18:47:17

Trilby
Inspector Parrot
Registered: 2011-11-29
Posts: 30,330
Website

Re: Can lex/yacc return values to other C code? [Solved]

Glad it's working.  I've only dabbled with (f)lex a bit.  This is inspiring me to learn more about yacc/bison.


"UNIX is simple and coherent" - Dennis Ritchie; "GNU's Not Unix" - Richard Stallman

Offline

#6 2012-06-05 21:35:24

Trilby
Inspector Parrot
Registered: 2011-11-29
Posts: 30,330
Website

Re: Can lex/yacc return values to other C code? [Solved]

Bit of a side question: you do know that fscanf can take some regexs right?

If you are writing the files with fprintf, why not read with fscanf?


"UNIX is simple and coherent" - Dennis Ritchie; "GNU's Not Unix" - Richard Stallman

Offline

#7 2012-06-05 22:28:02

/dev/zero
Member
From: Melbourne, Australia
Registered: 2011-10-20
Posts: 1,247

Re: Can lex/yacc return values to other C code? [Solved]

Trilby wrote:

Bit of a side question: you do know that fscanf can take some regexs right?

If you are writing the files with fprintf, why not read with fscanf?

It would just get too complicated. It might work on my toy example, but for the "real thing" the data will start off looking something like this:

pc { pos=(5, 20), fatigue = 3, exhausted = 0, health = 5 }
npcs [ 5 ] { { pos=(48, 52), alertness = 0, alert=0 }, { pos = (4, 18), alertness = 5, alert = 1 }, { pos=(8, 12), alertness = 4, alert = 0 }, { pos=(6,11), alertness = 3, alert = 1}, {pos=(1,20), alertness = 6, alert= 0} }

The npcs[5] says there were 5 zombies in the game at the time of saving. The difference between alertness and alert is that if the zombie is alert it is actively chasing the pc, whereas alertness represents how aware the zombie is that "something is up" - interpreted as maybe it heard the pc moving around, but isn't yet sure fresh brains are available. A character can run for brief periods and build up fatigue; an exhausted character simply cannot run any more.

I think this is already too complicated for scanning with regexes, and I haven't even got real maps or findable/usable objects or a character inventory yet.

Offline

Board footer

Powered by FluxBB