SDL Usage, Part 4: LEX and YACC Construction Syntax Analyzer for Script and GUI Design
Sam Lantinga and Lauren McDonellLoki Entertainment Softwareskillednursing.com 2000 May
Content: Another BISON starts from the basics to use LEX and YACC and C to use the string analysis from string analysis using multiple syntax analyzer scripting language GUI example Concluding Reference Information About the author
In this section, we will discuss two utilities in all Linux programmers tools: Lex and Yacc. These tools let us easily build the scripting language and GUI framework used in our SDL-based Linux game Pirates Ho!
When designing Pirates Ho!, We need a simple way to describe the interface and dialog options to the player. We need simple, consistent and flexible language to describe, so we look for tools that can help us build scripting languages.
Who wants another Bison? When I am in school, I have been full of fear of "Yacc". It reminds me of those hair messy, pale students, low, lie, compilers and symbols. So I am very careful, try to avoid using the compiler class. But when developing games, I took the courage to use YACC, I hope it can make the script make it easy. Finally, YACC not only makes the script become easier, but also makes this process interesting.
Starting from the foundation YACC is actually very easy to use. As long as it is provided to a set of rules that describe syntax, it can analyze the tags and operate according to the taken operation. For the scripting language we use, we hope to be deep, originally only specified some numbers and its logic: evAl.y
% {
/ * This first section, contains c code Which Will Be include in the output
File.
* /
#include
#include
/ * Since WE Are Using C , We need to specify the protoypes for som
INTERNAL YACC Functions So That They Can Be Found At Link Time.
* /
Extern Int Yylex (Void);
Extern void Yyerror (Char * MSG);
%}
/ * This is a union of the difference type of values That a token can
Take on. in Our Case We'll Just Handle "Numbers", Which Areral
C Int Type.
* /
% union {
Int number;
}
/ * THESE ARE UNYPED TOKENS WHICH Are Recognized as part of the grammar * /
% Token and OR Equals
/ * Here, Any Number token is stored in the number member of the
Union Above.
* /
% token Number
/ * THESE Rules All Return A Numeric Value * /
% TYPE Expression
% Type Logical_Expression and OR Equals
%%
/ * Our Language Consists Either of a Single Statement Or Off The Recursivity of The Rule, this allows us to have any
Number of Statements in a statement list.
* /
Statement_list: Statement | Statement_List Statement
;
/ * A statement is simply an expression .hen the Parser SEES AN Expression
We print out its value for debugging purposes. Later on We'll
Have More Than Just Expressions IN Our statements.
* /
Statement: Expression
{Printf ("Expression =% D / N", $ 1);
;
/ * An Expression Can Be a Number OR a logical expression. * /
Expression: Number
| logical_expression
;
/ * WE HAVE A FEW DIFFERENT TYPES OF Logical Expressions * /
Logical_Expression: and
| OR
| Equals
;
/ * WHEN The Parser SEES Two Expressions Surrounded by Parenthesis and
Connected by the and token, IT Will Actually Perform A C Logical
Expression and Store The Result Into
This Statement.
* /
AND: '(' Expression and Expression ')'
{IF ($ 2 && $ 4) {$$ = 1;} else {$$ = 0;}}
;
Al: '(' Expression or Expression ')'
{IF ($ 2 || $ 4) {$$ = 1;} else {$$ = 0;}}
;
Equals: '(' Expression Equals Expression ')'
{IF ($ 2 == $ 4) {$$ = 1;} else {$$ = 0;}}
;
%%
/ * This is a sample main () Function That Just Parses Standard Input
Using ouacc grammar. It allows us to feed sample scripts in
And See eti the is pased Correctly.
* /
Int main (int Argc, char * argv [])
{
Yyparse ();
}
/ * This is an error function use by yacc, and must be defined * / -
Void Yyerror (Char * Message)
{
FPRINTF (stderr, "% s / n", message;
}
How to provide input? Since we already have a simple syntax that identifies tag sequences, we will need to seek a way to provide these tags to the syntax analyzer. Lex This tool can be entered, convert it into a tag, and then pass these tags to YACC. Below, we will describe Lex to convert it to a marker expression: Eval.l% {
/ * Again, this is c code That IS INSERTED INTO The Beginning of the Output * /
#include
#include "y.tab.h" / * include the token definitions generated by yacc * /
%}
/ * Prevent the need for lineing with -lfl * /
% OPTION NOYYWRAP
/ * This next section is a set of regular expressions That Describe INPUT
Tokens That Are Passed Back to Yacc. The tokens area defined in y.tab.h,
Which is generated by yacc.
* /
%%
* / * ignore comments * /
- [0-9] | [0-9] {Yylval.Number = ATOI (YYTEXT); RETURN NUMBER;}
[/ t / n] / * ignore whitespace * /
&& {return and;
/ | / | {return {}}
== {Return Equals;}
Return Yytext [0];
%%
Now, there is already an analysis source code in the current directory, we need a makefile to build them: makefile
All: EVAL
Y. Tab.c: evAl.y
Yacc -d $
Lex.yy.c: evAl.l
Lex $ <
Eval: y.tab.o lex.yy.o
$ (Cc) -o $ @ $ ^
By default, YACC outputs to Y.Tab.c, LEX outputs to lex.yy.c, so we use those names as a source file. Makefile contains rules that build the source code based on the analysis description file. After everything is ready, you can enter "make" to build a syntax analyzer. Then we can run the syntax analyzer and enter the script to check the logic.
Transfer / Compressed Conflict When YACC must continue to analyze a set of tags or make it parsing it to make a selection between rules, a transfer / compression conflict occurs. For example, if you create a syntax consisting of the following: Expression: Number | Plus
;
Plus: Expression ' ' Expression
;
When the YACC Syntax Analyzer sees a number, it doesn't know if it is immediately parsing the number into an expression or waits for "x y". In this case, YACC will warn the transfer / compressed conflict, which will wait for the complete "x y" expression. The GNU Bison specifies the '-V' command line option creates a * .output file that contains the rules and conflicts that have occurred. This file can know what happens when using GNU Bison. As long as you add parentheses on both sides of the PLUS expression, you can easily solve this polymity problem: Expression: Number | Plus
;
Plus: '(' Expression ' ' Expression ')'
;
Use LEX and YACC with C for use with Lex and Yacc with C , there are some advice. LEX and YACC output to C file, so we use GNU equivalents Flex and Bison for C . These tools allow you to specify the name of the output file. We also add common rules to makefile, so GNU Make will automatically build C source files based on LEX and YACC source code. This requires that we rename the Lex and Yacc source, respectively, "Lex_eval.l" and "YACC_EVAL.Y", so Make will generate different C source files for them. You also need to change LEX to store files defined by YACC tag. Bison output header file uses the output file name of the .h suffix, and in our example is "Yacc_EVAL.cpp.h". The following is a new Makefile: makefile
All: EVAL
% .CPP:% .y
Bison -d -o $ @ $ <
% .CPP:% .L
Flex -o $ @ $ <
YACC_EVAL.O: YACC_EVAL.CPP
Lex_eval.o: lex_eval.cpp
Eval: yacc_eval.o lex_eval.o
$ (Cxx) -o $ @ $ ^
From the string analysis default Lex code reads its input from the standard input, but we want the game to analyze the strings in the memory. Using flex is easy to do, just redefine the macro YY_INPUT at the top of the LEX source file:
Extern Int Eval_getinput (char * buf, int maxlen);
#undef yy_input
#define yy_input (buf, retval, maxlen) (Retval = evAl_getinput (buf, maxlen))
We write the actual code of eval_getinput () to a separate file so that it can get an input from a file pointer or a string in memory. In order to use the actual code, we first build a global data source variable, then call YACC functions YYPARSE (), this function calls the input function and analyzes it.
Using multiple syntax analyzers We want to use different syntax analyzers for scripting languages and GUI descriptions in the game because they use different syntax rules. This is feasible, but we must use some tips to Flex and Bison. First, you need to change the prefix of the grammar analyzer by "YY" into a unique name to avoid name conflicts. As long as Flex and Bison use the command line option, you can rename the syntax analyzer, use -p to Flex, use -p to Bison. Then, you must change the use of the "yy" prefix in the code to the prefix we choose. This includes a reference to Yylval in the LEX source code, as well as the definition of YYError () because we put it in a separate file of the last game. The final makefile is as follows: makefile
All: EVAL
YY_PREFIX = EVAL_
% .CPP:% .y
Bison -P $ (YY_PREFIX) -d -o $ @ $ <
% .CPP:% .L
Flex -P $ (YY_PREFIX) -O $ @ $ <
YACC_EVAL.O: YACC_EVAL.CPPLEX_EVAL.O: Lex_eval.cpp
Eval: yacc_eval.o lex_eval.o
$ (Cxx) -o $ @ $ ^
Script language We start from the code displayed above (available in the reference), continue to add support for functions, variables, and simple traffic control, and finally get a fairly complete interpretation of the game. Here is a possible script sample:
EXAMPLE.TXT
Function Whitewash
{
IF ($ 1 == "BlackBeard") {
Print ("Pouring Whitewash On BlackBeard!")
IF ($ rum> = 3) {
Print ("Pouring Whitewash On BlackBeard!")
Mood = "happy"
} else {
Print ($ 1, "Says Grr ....")
Mood = "angry"
Print ("Have Some More Rum?")
rum
}
}
}
Pirate = "BlackBeard"
Rum = 0
Mood = "angry"
Print ($ PIRATE, "IS WALKING BY ...")
While ($ mood == "angry") {
WhiteWash ($ PIRATE)
}
Return "There Was Much Rejoicing"
Use YACC to build GUI We build GUI into a set of widgets, which inherit attributes from the base class. This is very good to tell YACC to analyze the method of its input. We define a set of rules corresponding to the base class properties, and then define the rules for each widget, and the rules of the base class are also defined. When the syntax analyzer matches the rule of the widget, we can use the widget pointer into an appropriate class and set the desired properties. The following is a simple button component: yacc_gui.y
% {
#include
#include
#include "widget.h"
#include "widget_button.h"
#define parse_debug (x) (Printf x)
#define max_widget_depth 32
Static int widget_depth = -1;
Static widget * widget_stack [max_widget_depth];
Static widget * widget;
Static void startwidget (widget * the_widget)
{
Widget_stack [widget_depth ] = widget = the_widget;
}
Static void finishwidget (void)
{
Widget * child;
--widget_depth;
IF (widget_depth> = 0) {
Child = widget;
Widget = widget_stack [widget_depth];
Widget-> addChild (child);
}
}
%}
[Tokens and Types Skipped for BREVITY]
%%
Widget: Button
{FinishWidget (); parse_debug (("Completed Widget / N"));}
;
Widget_attribute:
Widget_area
;
/ * Widget Area: X, Y, Width, Height * /
Widget_Area:
Area '{' Number ',' Number ',' Number ',' Number '}'
{Widget-> Setarea ($ 3, $ 5, $ 7, $ 9);
PARSE_DEBUG (("Area:% DX% D at (% D,% D) / N", $ 7, $ 9, $ 3, $ 5);
;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * The button widget * /
Button:
Button_tag '{' Button_Attributes '}'
{PARSE_DEBUG (("Completed Button / N");}
;
Button_tag:
Button name
{StartWidget (New WidgetButton);
PARSE_DEBUG (("Starting A Button:% S / N", $ 2));
Free ($ 2);
;
/ * The Button Widget Attributes * /
Button_attributes:
Button_attribute
| Button_Attributes Button_Attribute, BUTTON_ATTRIBUTE
;
Button_attribute:
widget
Widget_attribute
| Button_NORMAL_IMAGE
| Button_Hover_Image
;
Button_normal_image:
Image file
{(WidgetButton *) Widget) -> LoadNormalImage ($ 2);
PARSE_DEBUG (("Button Normal Image:% S / N", $ 2));
Free ($ 2);
;
Button_hover_image:
HoverImage File
{(WidgetButton *) Widget) -> LoadHoverImage ($ 2);
PARSE_DEBUG (("Button Hover Image: S / N", $ 2));
Free ($ 2);
;
GUI Example The following is our main menu, with a GUI built as this technology: main_menu.gui
Background "main_menu" {
Image "main_menu"
Button "new_game" {
Area {32, 80, 370, 64}
Image "main_menu-new"
Hover_Image "main_menu-new_hi"
#onclick [new_gui ("new_game")]]
Onclick [new_gui ("character_screen")]]
}
Button "load_game" {
Area {32, 152, 370, 64}
Image "main_menu-load"
Hover_Image "main_menu-load_hi"
Onclick [new_gui ("limited_game")]}
Button "save_game" {
Area {32, 224, 370, 64}
Image "main_menu-save"
Hover_Image "main_menu-save_hi"
Onclick [new_gui ("save_game")]]
}
Button "preferences" {
Area {32, 296, 370, 64}
Image "main_menu-prefs"
Hover_Image "main_menu-prefs_hi"
Onclick [new_gui ("preferences")]]]]
}
Button "quit_game" {
Area {32, 472, 370, 64}
Image "main_menu-quit"
Hover_Image "main_menu-quit_hi"
Onclick [quit_game ()]
}
}
In this screen description, widgets and attributes are analyzed by the syntax analyzer, and the button callback is explained by the script syntax. New_GUI () and quit_game () are internal functions exported to the script mechanism. main menu
Conclusion During our script and GUI design language, Lex and Yacc are very important tools. They seem to be timid, but after a period of time, you will find that they are both convenient to have fun. Please visit our column next month, and we will start to combine them together and lead you into the world of Pirates Ho!
Reference
Please visit the Pirates HO! Website can download the source code of the sample:
Eval.tar.gz (Script Example Source Code) Snapshot-043000.tar.gz (Game Source Snapshot) "Pirates Ho!" Used Library
SDL_IMAGE SDL_MIXER SDL_TTF LEX & YACC, 2nd Edition, O'Reilly, 1992 DeveloperWorks Pirates Ho! Series:
"SDL:" SDL: "Pirates Ho!" Is used "SDL Usage, Part 2:" Pirates Ho! "SDL Usage, Part 3: Graphic Design" Introduction SDL API SDL Usage Part 2: "pirates ho!" Encoding
About the author sam lantinga is the author of the Simple DirectMedia Layer (SDL) library, is now the Chief Programmer of Loki Entertainment Software, which is committed to producing the best-selling Linux game. He started with Linux and games to start in 1995, engaged in various DOM! Tools, and porting Macintosh game Maelstrom to Linux.
Lauren MacDonell is a technical writer of skillednursing.com and a cooperative developer of "Pirates Ho!". In the work, write books or dancing, she carested with tropical fish.