After some encouragement by Ksilyan, I have been learning about lex - a program for doing lexical analysis.
After some preliminary research, it seemed to be an ideal tool for doing something I wanted to do whilst fixing up SMAUG source code. In particular I was thinking of the problem of converting it to C++ where it had the word "class" sprinkled liberally through it (ie. player class) however class is a C++ keyword, and gave heaps of compile errors.
Now I know you can edit the files or use Perl to do a "change all" of "class" to (say) "mudclass" but the problem is:
The program lex which seems to be standard under Red Hat Linux, and can no doubt be obtained from the Cygwin download, lets this be done in a simple way. I post the method below, as it took a bit of work to get it perfect. :)
You can copy the text between the lines below, and paste them into a file called "fixup.l" (that's a trailing L for Lexer) and then run the lex program as suggested in the comments below.
You could then run something like this:
Then do a "diff" to check the changes were made OK. eg.
The slightly tricky part of the code is the provision of three "states" - INITIAL, quote and comment.
The INITIAL state is the default state for the lexer.
The "quote" state is entered when a quoted string is detected. Inside the quote state the target word is not changed. Also a quote-within-a-quote (namely \") does not terminate the quoted string.
The "comment" state is entered for a multi-line comment, and is only terminated when the closing comment is found.
After some preliminary research, it seemed to be an ideal tool for doing something I wanted to do whilst fixing up SMAUG source code. In particular I was thinking of the problem of converting it to C++ where it had the word "class" sprinkled liberally through it (ie. player class) however class is a C++ keyword, and gave heaps of compile errors.
Now I know you can edit the files or use Perl to do a "change all" of "class" to (say) "mudclass" but the problem is:
- You only want to change whole words, eg. class_table should stay the same
- You don't want to change literals, eg. "Choose your class" should stay the same
- You don't want to change multi-line comments, eg.
/* Here we choose a player's class */
should stay the same.
- You don't want to change single-line comments, eg. // choose class, should stay the same.
The program lex which seems to be standard under Red Hat Linux, and can no doubt be obtained from the Cygwin download, lets this be done in a simple way. I post the method below, as it took a bit of work to get it perfect. :)
You can copy the text between the lines below, and paste them into a file called "fixup.l" (that's a trailing L for Lexer) and then run the lex program as suggested in the comments below.
You could then run something like this:
./fixup class mudclass < update.c > update.c.new
Then do a "diff" to check the changes were made OK. eg.
diff update.c update.c.new
The slightly tricky part of the code is the provision of three "states" - INITIAL, quote and comment.
The INITIAL state is the default state for the lexer.
The "quote" state is entered when a quoted string is detected. Inside the quote state the target word is not changed. Also a quote-within-a-quote (namely \") does not terminate the quoted string.
The "comment" state is entered for a multi-line comment, and is only terminated when the closing comment is found.
%x comment
%x quote
%{
/*
To compile, save this file as fixup.l and run this:
lex -ofixup.c fixup.l && gcc fixup.c -lfl -o fixup
To run (to change "foo" to "bar" in a C program):
./fixup foo bar < input.c > output.c
*/
char * sFrom; /* word to search for */
char * sTo; /* word to replace it with */
%}
%%
"/*" ECHO; BEGIN (comment); /* begin multi-line comment */
<comment>"*/" ECHO; BEGIN (INITIAL); /* end multi-line comment */
"\"" ECHO; BEGIN (quote); /* begin quotes */
<quote>{ /* inside quote state */
["\n] ECHO; BEGIN (INITIAL); /* end quotes */
"\\\"" ECHO; /* escaped quote inside quotes */
} /* end quote state */
"//".* ECHO; /* single-line comment */
/* identifier */
[a-zA-Z0-9_]+ { if (strcmp (yytext, sFrom) == 0)
printf ("%s", sTo);
else
ECHO;
}
%%
int main ( int argc, char ** argv)
{
char * sProgram = argv [0];
if (argc != 3)
{
fprintf (stderr, "Usage: %s target_word replacement_word\n", sProgram);
return 1;
}
sFrom = argv [1];
sTo = argv [2];
yylex();
return 0;
}