Skip to content

Code generator #10

Open
Open
@XenonLab-Studio

Description

@XenonLab-Studio

So far, the adventure has 10 objects. Each object consists of 5 attributes (the fields in the struct OBJECT). It is likely that a real-life adventure has hundreds, even thousands of objects, and the number of attributes per object increases. Keeping such a large list of objects and attributes in its current form would be difficult.

For example, when I added the wallField and wallCave objects previously, I had to do it in three different places: once in object.h (like #define) and twice in object.c (an element in the array objs, and a separate matrix for the tags). This is clumsy and error-prone.

Instead of keeping object.h and object.c by hand, I will start generating the files from a single source that best suits my needs. This new source file could be in any language I prefer (usually a domain-specific language), as long as I have the tools to convert it back to C. The following is a simple example. Consider the following layout to organize objects:

+----------------------------------------------------+
|                                                    |
|   +--------------------------------------------+   |
|   |                                            |   |
|   |    Raw C code (declarations)               |   |
|   |                                            |   |
|   +--------------------------------------------+   |
|   +--------------------------------------------+   |
|   |                                            |   |
|   |                                            |   |
|   |    - ObjectName                            |   |
|   |          AttributeName AttributeValue      |   |
|   |          AttributeName AttributeValue      |   |
|   |          ...                               |   |
|   |    - ObjectName                            |   |
|   |          AttributeName AttributeValue      |   |
|   |          AttributeName AttributeValue      |   |
|   |          ...                               |   |
|   |    - ...                                   |   |
|   |                                            |   |
|   +--------------------------------------------+   |
|                                                    |
+----------------------------------------------------+

Based on the objects I've collected so far, I can build the following source file. The file name does not matter much; I simply called object.txt, to make it clear that it is related to object.h and object.c.

+------------+
| object.txt |
+------------+
#include <stdio.h>
#include "object.h"

typedef struct object {
   const char    *description;
   const char   **tags;
   struct object *location;
   struct object *destination;
} OBJECT;

extern OBJECT objs[];

- field
     description "an open field"
     tags        "field"

- cave
     description "a little cave"
     tags        "cave"

- silver
     description "a silver coin"
     tags        "silver", "coin", "silver coin"
     location    field

- gold
     description "a gold coin"
     tags        "gold", "coin", "gold coin"
     location    cave

- guard
     description "a burly guard"
     tags        "guard", "burly guard"
     location    field

- player
     description "yourself"
     tags        "yourself"
     location    field

- intoCave
     description "a cave entrance to the east"
     tags        "east", "entrance"
     location    field
     destination cave

- exitCave
     description "a way out to the west"
     tags        "west", "out"
     location    cave
     destination field

- wallField
     description "dense forest all around"
     tags        "west", "north", "south", "forest"
     location    field

- wallCave
     description "solid rock all around"
     tags        "east", "north", "south", "rock"
     location    cave

I made up the syntax myself, so it is safe to assume there are no standard tools to translate it to C. We will have to write our own code generator! Since this code generator will be a separate program, completely independent of our adventure program, we can write it in any language we like - not necessarily C. Here is one possible implementation, written in AWK:

+------------+
| object.awk |
+------------+
BEGIN {
   count = 0;
   obj = "";
   if (pass == "c2")
   {
      print "\nOBJECT objs[] = {";
   }
}

/^- / {
   outputRecord(",");
   obj = $2;
   prop["description"] = "NULL";
   prop["tags"]        = "";
   prop["location"]    = "NULL";
   prop["destination"] = "NULL";
}

obj && /^[ \t]+[a-z]/ {
   name = $1;
   $1 = "";
   if (name in prop)
   {
      prop[name] = $0;
   }
   else if (pass == "c2")
   {
      print "#error \"" FILENAME " line " NR ": unknown attribute '" name "'\"";
   }
}

!obj && pass == (/^#include/ ? "c1" : "h") {
   print;
}

END {
   outputRecord("\n};");
   if (pass == "h")
   {
      print "\n#define endOfObjs\t(objs + " count ")";
   }
}

function outputRecord(separator)
{
   if (obj)
   {
      if (pass == "h")
      {
         print "#define " obj "\t(objs + " count ")";
      }
      else if (pass == "c1")
      {
         print "static const char *tags" count "[] = {" prop["tags"] ", NULL};";
      }
      else if (pass == "c2")
      {
         print "\t{\t/* " count " = " obj " */";
         print "\t\t" prop["description"] ",";
         print "\t\ttags" count ",";
         print "\t\t" prop["location"] ",";
         print "\t\t" prop["destination"];
         print "\t}" separator;
         delete prop;
      }
      count++;
   }
}

We actually need to call this AWK script three times to generate the C sources:

awk -v pass=h  -f object.awk object.txt >  ../include/object.h
awk -v pass=c1 -f object.awk object.txt >  ../src/object.c
awk -v pass=c2 -f object.awk object.txt >> ../src/object.c

This will generate a new object.h and object.c, which should be identical (save for layout).

object.c is generated in two steps; for object.h, a single pass is sufficient. I could have created three separate AWK scripts, one for each pass, but instead I created one big script that combined all three, which seemed like the right thing to do considering the many similarities.

The code generation script is very simple; does not check the syntax on attribute values. Most typos made in object.txt pass through the generator without errors. This is not a problem: the syntax checks made later by the C compiler are sufficient. When compiling fails, the trick is to recognize errors in the C code, then find and correct the original source in object.txt. To make this task a little easier, the least I could do was to let the code generator add some comments in the generated C code (see object.awk).

The AWK script can also pass errors to the C compiler, issuing a #error directive as part of the generated code.

Notes:

  • Important: at the time, it did not make any manual changes to object.h and object.c; these would only be
    discarded by the code generation process.

  • This combination of languages ​​(C, AWK and a domain-specific language) may seem initially confusing. Moreover,
    this is still relatively simple compared to the combination of server-side and client-side techniques with
    which the average web developer is confronted.

  • Because object.txt is converted to simple C code, compiled and linked with the other modules, all of its data
    will be part of the final executable. Like any source file, object.txt should not be present when the
    executable is running (for example, when someone is playing). This, of course, is just a matter of choice. It
    is very well possible to keep object.txt regardless of the executable and make the executable import data from
    object.txt at runtime. This is particularly interesting when you build an adventures development system. Keep
    in mind that it will make the code slightly more complicated; requires more effort in analyzing object.txt,
    since there will not be any C compiler to support me.

Visualization

When it comes to choosing a domain-specific language, keep in mind that code generation is not its only advantage. A simple AWK script, similar to the one above, can be used to display a virtual world map by drawing a chart.

Currently under development...

Metadata

Metadata

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions