The Locale Explorer: Template-controlled code generation

Home
Back To Tips Page

Back to LocaleExplorer

I chose a relatively simplistic methodology for generating code: external templates provide most of the structure and the code itself.  This allows the program to be relatively independent of the actual code; it merely sets up a number of parameters, and invokes a template substitution.

The idea was that anyone who wants to use this program to generate Visual Basic, Delphi, or (shudder) FORTRAN or COBOL (yes, there is a COBOL.NET!). or whatever language the user wanted, could supply templates.  There is a mechanism for adding a user-defined template file.

The mechanism is not very sophisticated, and this leads to a lot of redundancy and a bit of gratuitous duplication of effort.  What started out as a rather simple technique for GetLocaleInfo did not scale up as smoothly as I would have liked, but a few tweaks and it seems to work.

Option Intended target code
C.tpl Win32 code intended to compiled with the Microsoft C compiler (not C++)
CPP.tpl Win32 code intended to be compiled with the Microsoft C++ compiler, but which does not use either STL (The Standard C++ Library) or MFC.
MFC.tpl Code intended to be compiled with the Microsoft C++ compiler and used in the context of an MFC application.
STL.tpl Code intended to be compiled with the Microsoft C++ compiler and which does not use MFC, but which is intended to utilize the Standard C++ Library (STL)
HELP.tpl A simple enumeration of each parameter code and its expansion in the context of the current set of options.  This is primarily intended to support those who want to write their own template files for other languages
username.tpl A user-defined file containing any language code of the user's choice.

The templates are stored in a series of "template files" indicated below.  These are included as resources in the executable.  But this leads to the question of how to make changes if a bug is found.  The algorithm I added is that whenever a template is needed, the program first checks in the executable directory to see if there is an appropriate template file; if one is found, that is used.  Otherwise, the built-in template is used.  When the "user template" option is specified, a user-defined file, set by using the open-file button ( ) in the "Code Generation" section, will be used.  The selected file will be remembered across sessions.

The "Copy" button will copy the entire contents of the generated code template to the clipboard.  To copy only part of the text to the clipboard, select the text and use Ctrl+C to copy it.

Most templates require only one variable; when this is required, the Variable edit control specifies the desired variable name.  Should this be empty, a name will be synthesized.  The name is unique to each page of the tabbed dialog and will be remembered with that page, but will not be retained in the Registry and therefore will not persist across program executions.  Some pages allow for additional variables to be named, but many variable names are hardwired into the templates.

FormatMessage: a simple macro expander

The simplest mechanism is to do simple string substitutions.  There is no sophistication in the macro expander (it is as trivial as the C/C++ "define" facility), so I use the FormatMessage API, which is actually conveniently accessed by the CString::FormatMessage call.  For consistency, I tended to keep similar assignments across all templates.  Each template has a predefined set of values defined by %n values.  For example, %1 is always the LCID value expressed as a C/C++ macro.  However, not all languages have the necessary symbolic constants defined, or have a convention for hexadecimal numbers that is compatible with C.  Therefore, any time a symbolic name is used, I also provide a "decimal" equivalent, which is the macro values expanded and formatted as a simple integer. In the case of the LCID, the decimal value of the LCID is found as %8.  The way to build a bit mask is also highly variable.  However, I do presume there is an infix operator that can do this (I do not provide for functions that can take only two parameters, for example, and require cascading operations).  The template %7 is the infix "or" operator for bitwise or, and is provided in the template file as the template [OR].

A canoncical set of of macro values is shown in the table below.

Macro APIs Purpose Typical example
%1 All LCID MAKELCID(MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN_LUXEMBOURG), SORT_DEFAULT)
%2 EnumCalendarInfo
EnumCalendarInfoEx
GetCalendarInfo
CALTYPE CAL_ITWODIGITYEARMAX
GetCPInfo, GetCPInfoEx Codepage designator selected by radio buttons GetACP(), CP_ACP
GetGeoInfo GEOTYPE GEO_LATITUDE
%3 All various flag options CAL_RETURN_NUMBER | CAL_ITWODIGITYEARMAX
%4 GetGeoInfo GEOID 14
%5 All variable name Value supplied in "Variable" box of "Code Generation".  Generally, if this is empty, a suitable variable will be concocted.
%6 All Decimal value of %2 or %3 536870960
%7 All Bitwise "or" operator |
%8 All %1 expressed as a decimal integer 4103
%9 EnumCalendarInfo
EnumCalendarInfoEx
Desired enumeration operation ENUM_ALL_CALENDARS
GetGeoInfo Language ID LANGID(LOCALE_SYSTEM_DEFAULT)
GetCurrencyFormat value variable to format A name of your choice specified in the "Value name" control
GetDateFormat
GetTimeFormat
SYSTEMTIME variable A value as specified in the SYSTEMTIME control
%10 EnumCalendarInfo
EnumCalendarInfoEx
%9 expressed as a decimal integer -1
GetCPInfo, GetCPInfoEx Code page designator GetACP(), 1 Note that when a function is called that returns an integer, rather than using a manifest constant, the function call itself is used again.
GetGeoInfo Decimal language ID %9 2048
%11 EnumCalendarInfo
EnumCalendarInfoEx
callback function name Value supplied in the "Callback name" box which appears for those APIs that perform callbacks.  Generally if this name is blank, a suitable callback function name based on the Microsoft documentation will be provided.
GetGeoInfo GEO_FRIENDLYNAME of %2 Austria
GetCurrencyFormat Name of CURRENCYFMT variable Specified in the "CURRENCYFMT name" control
%12 GetCurrencyFormat CurrencyFmt.NumDigits 2
%13 GetCurrencyFormat CurrencyFmt.LeadingZero 1
%14 GetCurrencyFormat CurrencyFmt.Grouping 3
%15 GetCurrencyFormat CurrencyFmt.lpDecimalSep .
%16 GetCurrencyFormat CurrencyFmt.lpThousandSep ,
%17 GetCurrencyFormat CurrencyFmt.NegativeOrder 0
%18 GetCurrencyFormat CurrencyFmt.PositiveOrder 0
%19 GetCurrencyFormat CurrencyFmt.lpCurrencySymbol $

Template names

Because there is no "conditional", each template which requires unique values must be a unique template.  This is a bit of a bother because it means there are duplicate templates to be maintained; when I had only six templates, it was less of a problem than it is now, with nearly 70 templates. However, although it does not appear on most of the screen shots in this documentation, (having been added late in the development), each set of options selected will now display the template name of the template it is trying to locate.

Template Syntax

The template file is segmented into groups of templates by a line that starts with the template name.  The template name has up to three components: an API name, and two modifiers.  Part of the reason that there are so many templates is that the types in other languages do not correspond to the types in C, so having a type parameter that said "DWORD", or "double" or "BYTE" would not be useful, since these could not be used if templates for other languages were created.  Consequently, the template name may contain a key phrase like DWORD or DOUBLE, but it is up to the writer of the template to represent these using the appropriate keyword in the language.

Template files are made more readable by adding comments.  A comment is any line that starts with "**".  Major groupings are delimited by one kind of comment, and variants are delimited by another, as shown below.

*****************************************************************************
** GetLocaleInfo
***************************************************************************** 
[GetLocaleInfo:FIXED]
... template here
**---------------------------------------------------------------------------
[GetLocaleInfo:VARIABLE]
...template here
**---------------------------------------------------------------------------
[GetLocaleInfo:DWORD]
...template here
*****************************************************************************
** WideCharToMultiByte
*****************************************************************************
**---------------------------------------------------------------------------
** UsedDefault = NULL, DefaultChar = NULL (NN) VS6
**---------------------------------------------------------------------------
[WideCharToMultiByte:6NN]
...template
**---------------------------------------------------------------------------
** UsedDefault = NULL, DefaultChar = 'c' (NS) VS6
**---------------------------------------------------------------------------
[WideCharToMultiByte:6NS]
...template
**---------------------------------------------------------------------------
** UsedDefault = NULL, DefaultChar = GetCpInfo (NG) VS6
**---------------------------------------------------------------------------
[WideCharToMultiByte:6NG]
...template
**---------------------------------------------------------------------------
** UsedDefault = ~NULL, DefaultChar = NULL (UN) VS6
**---------------------------------------------------------------------------
[WideCharToMultiByte:6UN]
...template

Checking templates

Eventually I got too many templates.  I wasn't sure if every file had every template.  So I wrote a little program called checktempl which is part of the project.  This reads template files, and makes sure that every template appears in every file.

It also detects if there are duplicate templates in a file.

This is an example that illustrates that most of the templates for STL have not been written.  Anyone who wants to complete this by writing and testing STL-based templates and send me the result will get a credit line.  You can also use the ** comment convention to add your own credit line(s) in the file.

If anyone wants to do VB or any other language, I will consider adding additional languages to the set of radio buttons displayed.  Same offer for a credit line applies.

Now, given that I cannot predict which templates will exist, how could I write a program that checks that the files are consistent?  The answer is that I assume that a template in any file is a defining instance.  Therefore, it must be in every file.  (Of course, a mis-spelling would create such an error; but then the mis-spelling would be spotted and could be corrected).

Essentially, I create a "matrix" of files and templates; imagine a set of rows indexed by filename and a set of columns indexed by template name.  At each intersection there is a Boolean "true" or "false" indicating if that file has that template.  Upon completion of reading all the files, every array element should be "true".  If one is "false", this represents a missing template in that file.  And if, during the addition, it is found that a <file, template> pair is already set "true", there is a duplicate definition.

Now, the problem arises because it is hard to do 2D dynamic arrays in MFC, and I didn't want to learn STL (The Standard C++ Library) to use std::vector, so I was left with a bit of a problem as to how to accomplish this. I solved this by creating a "file set" and a "template set", where there was a unique 16-bit integer that could represent each element of the set.  This was done trivially by having an array, where I first did a lookup in the array of the names; if the name already existed, I returned its unique ID (its array index).  If it did not exist, I called CArray::Add to add the element to the array, and returned the new index.

Then I created a CMap which was indexed by the pair of <file, template>, which I conveniently represented as a MAKELONG of the two indices.  Values in this CMap were a DWORD which was the line number in the file where it was defined. This allowed me to report a duplicate definition by giving both the line where it was originally defined and the line where I found the duplicate.  So what I did was do a CMap::Lookup of the <file, template> pair.  If the lookup failed, the template was not previously defined, and therefore I would add it to the set.  If the lookup succeeded, I had found a duplicate definition.

Upon completion of the file read, which is based on *.tpl files found in the specified directory (which is retained across executions), I do a nested for-loop, for each file, and each template.  Given that the array indices are the unique IDs, this then spans the entire array space.  I do a CMap::Lookup of the <file, template> pair, and if it fails, then I know for that file index and that template index that there is a missing template, which I can then report.

Note the Debug checkbox.  This will enable detailed tracig in the CListBox which helped me debug.  This control is hidden in the release version.  The Save... button will save the contents of the CListBox in a file.

[Dividing Line Image]

The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.

Send mail to newcomer@flounder.com with questions or comments about this web site.
Copyright 2005-2006 Joseph M. Newcomer/FlounderCraft Ltd.  All Rights Reserved.
Last modified: May 14, 2011