703 lines
14 KiB
C++
Executable File
703 lines
14 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "platform/platform.h"
|
|
#include "platform/event.h"
|
|
#include "platform/platformAssert.h"
|
|
#include "console/console.h"
|
|
#include "console/consoleTypes.h"
|
|
#include "math/mathTypes.h"
|
|
#include "langc/langc.h"
|
|
|
|
#include "core/fileStream.h"
|
|
|
|
#include "i18n/lang.h"
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include "langcomp.h"
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Simple hash function for ID lookups in translations
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
static U32 hashString(const UTF8 *str)
|
|
{
|
|
S32 i;
|
|
U32 h = 0;
|
|
|
|
for(i = 0;str[i];i++)
|
|
{
|
|
h += (str[i] * i);
|
|
}
|
|
return h;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// LFileWriter Class
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
LFileWriter::LFileWriter(LangFile *langFile) : mLangFile(langFile)
|
|
{
|
|
}
|
|
|
|
bool LFileWriter::Open(const UTF8 *basename)
|
|
{
|
|
UTF8 filename[256];
|
|
|
|
GetFilename(basename, filename, sizeof(filename));
|
|
return mStream.open((const char*)filename, FileStream::Write);
|
|
}
|
|
|
|
void LFileWriter::Close()
|
|
{
|
|
mStream.close();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Writer Classes
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
class LHeaderWriter : public LFileWriter
|
|
{
|
|
public:
|
|
LHeaderWriter(LangFile *l) : LFileWriter(l)
|
|
{
|
|
}
|
|
|
|
virtual void GetFilename(const UTF8 *basename, UTF8 *buf, U32 bufsize)
|
|
{
|
|
dSprintf(buf, bufsize, "%s.h", basename);
|
|
}
|
|
|
|
virtual void WriteHeader()
|
|
{
|
|
mStream.writeLine((U8 *)"// Automatically generated. DO NOT EDIT\n");
|
|
mStream.writeLine((U8 *)"#ifndef _TGE_I18N_AUTOGEN_H_");
|
|
mStream.writeLine((U8 *)"#define _TGE_I18N_AUTOGEN_H_\n");
|
|
}
|
|
|
|
virtual void WriteFooter()
|
|
{
|
|
UTF8 buf[LCL_MAXSTRINGLENGTH + 4];
|
|
|
|
dSprintf(buf, sizeof(buf), "\n#define I18N_NUM_STRINGS\t%d", mLangFile->getNumStrings());
|
|
mStream.writeLine((U8 *)&buf);
|
|
|
|
mStream.writeLine((U8 *)"\n#endif // _TGE_I18N_AUTOGEN_H_");
|
|
}
|
|
|
|
virtual void WriteString(UTF8 *idstr, U32 idnum, UTF8 *str)
|
|
{
|
|
UTF8 buf[LCL_MAXSTRINGLENGTH + 4];
|
|
|
|
dSprintf(buf, sizeof(buf), "// %s", str);
|
|
mStream.writeLine((U8 *)&buf);
|
|
dSprintf(buf, sizeof(buf), "#define %s\t%d", idstr, idnum);
|
|
mStream.writeLine((U8 *)&buf);
|
|
}
|
|
};
|
|
|
|
class LScriptWriter : public LFileWriter
|
|
{
|
|
public:
|
|
LScriptWriter(LangFile *l) : LFileWriter(l)
|
|
{
|
|
}
|
|
|
|
virtual void GetFilename(const UTF8 *basename, UTF8 *buf, U32 bufsize)
|
|
{
|
|
dSprintf(buf, bufsize, "%s.cs", basename);
|
|
}
|
|
|
|
virtual void WriteHeader()
|
|
{
|
|
mStream.writeLine((U8 *)"// Automatically generated. DO NOT EDIT\n");
|
|
}
|
|
|
|
virtual void WriteFooter()
|
|
{
|
|
}
|
|
|
|
virtual void WriteString(UTF8 *idstr, U32 idnum, UTF8 *str)
|
|
{
|
|
UTF8 buf[LCL_MAXSTRINGLENGTH + 4];
|
|
|
|
dSprintf(buf, sizeof(buf), "// %s", str);
|
|
mStream.writeLine((U8 *)&buf);
|
|
dSprintf(buf, sizeof(buf), "$%s = %d;", idstr, idnum);
|
|
mStream.writeLine((U8 *)&buf);
|
|
}
|
|
};
|
|
|
|
class LDefaultsWriter : public LFileWriter
|
|
{
|
|
public:
|
|
LDefaultsWriter(LangFile *l) : LFileWriter(l)
|
|
{
|
|
}
|
|
|
|
virtual void GetFilename(const UTF8 *basename, UTF8 *buf, U32 bufsize)
|
|
{
|
|
dSprintf(buf, bufsize, "%sDefaults.cc", basename);
|
|
}
|
|
|
|
virtual void WriteHeader()
|
|
{
|
|
mStream.writeLine((U8 *)"// Automatically generated. DO NOT EDIT\n");
|
|
mStream.writeLine((U8 *)"const UTF8 *gI18NDefaultStrings[] =\n{");
|
|
}
|
|
|
|
virtual void WriteFooter()
|
|
{
|
|
mStream.writeLine((U8 *)"};");
|
|
}
|
|
|
|
virtual void WriteString(UTF8 *idstr, U32 idnum, UTF8 *str)
|
|
{
|
|
UTF8 buf[LCL_MAXSTRINGLENGTH + 4];
|
|
|
|
dSprintf(buf, sizeof(buf), "\t// %s = %d", idstr, idnum);
|
|
mStream.writeLine((U8 *)&buf);
|
|
dSprintf(buf, sizeof(buf), "\t(const UTF8*)\"%s\",", str);
|
|
mStream.writeLine((U8 *)&buf);
|
|
}
|
|
};
|
|
|
|
class LTransWriter : public LFileWriter
|
|
{
|
|
public:
|
|
LTransWriter(LangFile *l) : LFileWriter(l)
|
|
{
|
|
}
|
|
|
|
virtual void GetFilename(const UTF8 *basename, UTF8 *buf, U32 bufsize)
|
|
{
|
|
dSprintf(buf, bufsize, "%s.tran", basename);
|
|
}
|
|
|
|
virtual void WriteHeader()
|
|
{
|
|
mStream.writeLine((U8 *)"# Automatically generated.\n");
|
|
}
|
|
|
|
virtual void WriteFooter()
|
|
{
|
|
}
|
|
|
|
virtual void WriteString(UTF8 *idstr, U32 idnum, UTF8 *str)
|
|
{
|
|
UTF8 buf[LCL_MAXSTRINGLENGTH + 4];
|
|
|
|
dSprintf(buf, sizeof(buf), "# [%s:%d] %s", idstr, idnum, str);
|
|
mStream.writeLine((U8 *)&buf);
|
|
dSprintf(buf, sizeof(buf), "%s=\n", idstr);
|
|
mStream.writeLine((U8 *)&buf);
|
|
}
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// LString Class
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
LString::LString(UTF8 *i /* = NULL */, UTF8 *s /* = NULL */, UTF8 *sclean /* = NULL */) : str(NULL), strclean(NULL), id(NULL)
|
|
{
|
|
if(s)
|
|
{
|
|
str = new UTF8 [dStrlen(s) + 1];
|
|
dStrcpy(str, s);
|
|
}
|
|
|
|
if(sclean)
|
|
{
|
|
strclean = new UTF8 [dStrlen(sclean) + 1];
|
|
dStrcpy(strclean, sclean);
|
|
}
|
|
|
|
if(i)
|
|
{
|
|
id = new UTF8 [dStrlen(i) + 1];
|
|
dStrcpy(id, i);
|
|
}
|
|
}
|
|
|
|
LString::~LString()
|
|
{
|
|
if(str)
|
|
delete [] str;
|
|
if(id)
|
|
delete [] id;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// LangComp Class
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
LangComp::LangComp(U32 options /* = 0L */) : mOptions(options), mLangFile(NULL),
|
|
mCurrentFilename(NULL), mCurrentLine(0), mNumErrors(0), mNumWarn(0),
|
|
mTransLookup(LC_ID_LOOKUP_SIZE)
|
|
{
|
|
}
|
|
|
|
LangComp::~LangComp()
|
|
{
|
|
Free();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Protected Methods
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
void LangComp::Free()
|
|
{
|
|
if(mLangFile)
|
|
{
|
|
delete mLangFile;
|
|
mLangFile = NULL;
|
|
}
|
|
|
|
if(mCurrentFilename)
|
|
{
|
|
delete [] mCurrentFilename;
|
|
mCurrentFilename = NULL;
|
|
}
|
|
mCurrentLine = 0;
|
|
|
|
S32 i;
|
|
for(i = 0;i < mFileWriters.size();i++)
|
|
{
|
|
delete mFileWriters[i];
|
|
}
|
|
mFileWriters.empty();
|
|
}
|
|
|
|
void LangComp::Error(const UTF8 *msg, ...)
|
|
{
|
|
UTF8 buf[512];
|
|
va_list va;
|
|
|
|
va_start(va, msg);
|
|
dVsprintf(buf, sizeof(buf), msg, va);
|
|
va_end(va);
|
|
|
|
if(mCurrentFilename)
|
|
dPrintf("error at %s:%d: %s\n", mCurrentFilename, mCurrentLine, buf);
|
|
else
|
|
dPrintf("error: %s\n", buf);
|
|
|
|
mNumErrors++;
|
|
}
|
|
|
|
void LangComp::Warn(const UTF8 *msg, ...)
|
|
{
|
|
UTF8 buf[512];
|
|
va_list va;
|
|
|
|
if(mOptions & LCO_NOWARNINGS)
|
|
return;
|
|
|
|
va_start(va, msg);
|
|
dVsprintf(buf, sizeof(buf), msg, va);
|
|
va_end(va);
|
|
|
|
if(mCurrentFilename)
|
|
dPrintf("warning at %s:%d: %s\n", mCurrentFilename, mCurrentLine, buf);
|
|
else
|
|
dPrintf("warning: %s\n", buf);
|
|
|
|
mNumWarn++;
|
|
}
|
|
|
|
LString * LangComp::ParseLine(UTF8 *line)
|
|
{
|
|
UTF8 *p, id[LCL_MAXIDLENGTH], str[LCL_MAXSTRINGLENGTH];
|
|
S32 i, warnCount;
|
|
|
|
i = dStrlen(line)-1;
|
|
if(line[i] == '\n') line[i] = 0;
|
|
i = dStrlen(line)-1;
|
|
if(line[i] == '\r') line[i] = 0;
|
|
|
|
p = line;
|
|
|
|
// Allowable comment delimiters: # ; //
|
|
if(*p == '#' || *p == ';' || (*p == '/' && *(p+1) == '/') || *p == 0)
|
|
return new LString();
|
|
|
|
i = 0;
|
|
while(*p)
|
|
{
|
|
if(*p == '=')
|
|
break;
|
|
|
|
if(i < (sizeof(id)-1))
|
|
{
|
|
if(dIsalnum(*p) || *p == '_')
|
|
{
|
|
id[i] = *p;
|
|
i++;
|
|
}
|
|
else if(! dIsspace(*p) && ! (mOptions & LCO_DONTWARNINVALIDCHAR))
|
|
Warn("invalid character ('%c') in identifier ignored", *p);
|
|
}
|
|
|
|
p++;
|
|
}
|
|
id[i] = 0;
|
|
p++;
|
|
|
|
// Identifiers cannot start with a number
|
|
if(dIsdigit(id[0]))
|
|
{
|
|
Error("identifiers cannot start with a number");
|
|
return NULL;
|
|
}
|
|
|
|
// Identifiers must be there
|
|
if(id[0] == 0)
|
|
{
|
|
Error("no identifier");
|
|
return NULL;
|
|
}
|
|
|
|
i = 0;
|
|
bool foundStart = (mOptions & LCO_DONTSTRIPSPACES);
|
|
while(*p)
|
|
{
|
|
if(i < (sizeof(str)-1))
|
|
{
|
|
if(!foundStart && ! dIsspace(*p))
|
|
foundStart = true;
|
|
|
|
if(foundStart && i < (sizeof(str)-1))
|
|
{
|
|
str[i] = *p;
|
|
i++;
|
|
}
|
|
}
|
|
|
|
p++;
|
|
}
|
|
str[i] = 0;
|
|
|
|
if(mOptions & LCO_STRIPTRAILINGSPACE)
|
|
{
|
|
p = dStrchr(str, 0);
|
|
while(dIsspace(*(--p)))
|
|
*p = 0;
|
|
}
|
|
|
|
if(mOptions & LCO_WARNNOSTRING && str[0] == 0)
|
|
{
|
|
Warn("identifier '%s' is empty", id);
|
|
}
|
|
|
|
UTF8 strbuf[LCL_MAXSTRINGLENGTH];
|
|
processSlashes(str, strbuf);
|
|
|
|
return new LString(id, strbuf, str);
|
|
}
|
|
|
|
void LangComp::WriteString(UTF8 *idstr, U32 idnum, UTF8 *str)
|
|
{
|
|
S32 i;
|
|
|
|
for(i = 0;i < mFileWriters.size();i++)
|
|
{
|
|
mFileWriters[i]->WriteString(idstr, idnum, str);
|
|
}
|
|
}
|
|
|
|
void LangComp::WriteFileHeader()
|
|
{
|
|
S32 i;
|
|
|
|
for(i = 0;i < mFileWriters.size();i++)
|
|
{
|
|
mFileWriters[i]->WriteHeader();
|
|
}
|
|
}
|
|
|
|
void LangComp::WriteFileFooter()
|
|
{
|
|
S32 i;
|
|
|
|
for(i = 0;i < mFileWriters.size();i++)
|
|
{
|
|
mFileWriters[i]->WriteFooter();
|
|
}
|
|
}
|
|
|
|
|
|
// [tom, 3/7/2005] There is no buffer size limit here as the size of the buffer
|
|
// in ParseLine() is the same size as the buffer the string is in. If this function
|
|
// is ripped for use elsewhere, I suggest adding a buffer size check.
|
|
UTF8 *LangComp::processSlashes(const UTF8 *string, UTF8 *buffer)
|
|
{
|
|
const UTF8 *s = string;
|
|
UTF8 *d = buffer;
|
|
|
|
while(*s)
|
|
{
|
|
if(*s == '\\')
|
|
{
|
|
s++;
|
|
switch(*s)
|
|
{
|
|
case 'n':
|
|
*d++ = '\n';
|
|
s++;
|
|
break;
|
|
|
|
case 'r':
|
|
*d++ = '\r';
|
|
s++;
|
|
break;
|
|
|
|
case 't':
|
|
*d++ = '\t';
|
|
s++;
|
|
break;
|
|
|
|
default:
|
|
*d++ = *s++;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*d++ = *s++;
|
|
}
|
|
}
|
|
*d = 0;
|
|
|
|
return buffer;
|
|
}
|
|
|
|
// [tom, 3/17/2005] No buffer check here either, this time out of lazyness.
|
|
// [tom, 3/17/2005] This function isnt actually used anymore. I'm keeping it
|
|
// here in case its useful in the future.
|
|
UTF8 *LangComp::quoteString(const UTF8 *string, UTF8 *buffer)
|
|
{
|
|
static struct {
|
|
unsigned char c;
|
|
unsigned char q;
|
|
} quoteTab[]=
|
|
{
|
|
{ '\n', 'n' },
|
|
{ '\r', 'r' },
|
|
{ '\t', 't' },
|
|
{ '"', '"' },
|
|
{ '\'', '\'' },
|
|
{ '\\', '\\' },
|
|
|
|
{ 0, 0 }
|
|
};
|
|
const UTF8 *s = string;
|
|
UTF8 *d = buffer;
|
|
|
|
while(*s)
|
|
{
|
|
int i;
|
|
bool rep = false;
|
|
|
|
for(i = 0;quoteTab[i].c;i++)
|
|
{
|
|
if(*s == quoteTab[i].c)
|
|
{
|
|
*d++ = '\\';
|
|
*d++ = quoteTab[i].q;
|
|
rep = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!rep)
|
|
*d++ = *s;
|
|
|
|
s++;
|
|
}
|
|
*d = 0;
|
|
|
|
return buffer;
|
|
}
|
|
|
|
bool LangComp::AddFileWriter(LFileWriter *lfw, const UTF8 *basename)
|
|
{
|
|
if(lfw->Open(basename))
|
|
{
|
|
mFileWriters.push_back(lfw);
|
|
return true;
|
|
}
|
|
|
|
UTF8 buf[256];
|
|
lfw->GetFilename(basename, buf, sizeof(buf));
|
|
Warn("Could not open file \"%s\"", buf);
|
|
|
|
delete lfw;
|
|
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Public Methods
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
bool LangComp::Compile(const UTF8 *filename, const UTF8 *outbasename, const UTF8 *englishTable /* = NULL */)
|
|
{
|
|
bool ret = true;
|
|
UTF8 lsoname[256];
|
|
|
|
Free();
|
|
|
|
if((mOptions & LCO_COMPILETRANSLATION) && englishTable == NULL)
|
|
{
|
|
Error("you must specify the english language file when compiling translations.");
|
|
return false;
|
|
}
|
|
|
|
if(mOptions & LCO_COMPILETRANSLATION)
|
|
{
|
|
if(! LoadLangForTranslation(englishTable))
|
|
{
|
|
Error("could not load %s", englishTable);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
dSprintf(lsoname, sizeof(lsoname), "%s.lso", outbasename);
|
|
|
|
mCurrentFilename = new UTF8 [dStrlen(filename) + 1];
|
|
dStrcpy(mCurrentFilename, filename);
|
|
|
|
mLangFile = new LangFile;
|
|
|
|
FileStream fs;
|
|
if(fs.open(filename, FileStream::Read))
|
|
{
|
|
if(mOptions & LCO_WRITEHEADER)
|
|
AddFileWriter(new LHeaderWriter(mLangFile), outbasename);
|
|
|
|
if(mOptions & LCO_WRITESCRIPT)
|
|
AddFileWriter(new LScriptWriter(mLangFile), outbasename);
|
|
|
|
if(mOptions & LCO_WRITECDEFAULTS)
|
|
AddFileWriter(new LDefaultsWriter(mLangFile), outbasename);
|
|
|
|
if(mOptions & LCO_WRITETRANSLATION)
|
|
AddFileWriter(new LTransWriter(mLangFile), outbasename);
|
|
|
|
WriteFileHeader();
|
|
|
|
// This buffer must be able to hold the max lengths, the equals, end of line chars and a bit of leeway for spaces
|
|
UTF8 buf[LCL_MAXIDLENGTH + LCL_MAXSTRINGLENGTH + 28];
|
|
|
|
while(fs.getStatus() != Stream::EOS)
|
|
{
|
|
mCurrentLine++;
|
|
fs.readLine((U8 *)&buf[0], sizeof(buf));
|
|
|
|
LString *ls;
|
|
if((ls = ParseLine(buf)) == NULL)
|
|
break;
|
|
|
|
if(ls->id)
|
|
{
|
|
U32 idnum;
|
|
if(mOptions & LCO_COMPILETRANSLATION)
|
|
{
|
|
idnum = (U32)mTransLookup.retreive(hashString(ls->id));
|
|
if(idnum == 0)
|
|
Warn("id %s does not exist in the english table", ls->id);
|
|
else
|
|
idnum--;
|
|
mLangFile->setString(idnum, ls->str);
|
|
}
|
|
else
|
|
idnum = mLangFile->addString(ls->str);
|
|
|
|
WriteString(ls->id, idnum, ls->strclean);
|
|
}
|
|
|
|
delete ls;
|
|
}
|
|
|
|
WriteFileFooter();
|
|
|
|
S32 i;
|
|
for(i = 0;i < mFileWriters.size();i++)
|
|
{
|
|
mFileWriters[i]->Close();
|
|
}
|
|
|
|
fs.close();
|
|
}
|
|
|
|
if(mOptions & LCO_WRITELANGTABLE)
|
|
{
|
|
if(ret && fs.open(lsoname, FileStream::Write))
|
|
{
|
|
mLangFile->save(&fs);
|
|
fs.close();
|
|
}
|
|
}
|
|
|
|
|
|
dPrintf("%s - ", lsoname);
|
|
if(ret)
|
|
dPrintf("%d string(s), ", mLangFile->getNumStrings());
|
|
dPrintf("%d error(s), %d warning(s)\n", mNumErrors, mNumWarn);
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool LangComp::LoadLangForTranslation(const UTF8 *filename)
|
|
{
|
|
bool ret = true;
|
|
FileStream fs;
|
|
|
|
UTF8 *fnBak = mCurrentFilename;
|
|
U32 lineBak = mCurrentLine;
|
|
|
|
mCurrentFilename = new UTF8 [dStrlen(filename) + 1];
|
|
dStrcpy(mCurrentFilename, filename);
|
|
mCurrentLine = 0;
|
|
|
|
if(fs.open(filename, FileStream::Read))
|
|
{
|
|
// This buffer must be able to hold the max lengths, the equals, end of line chars and a bit of leeway for spaces
|
|
UTF8 buf[LCL_MAXIDLENGTH + LCL_MAXSTRINGLENGTH + 28];
|
|
U32 lastID = 1; // [tom, 4/25/2005] 0 is used as an error indicator
|
|
|
|
while(fs.getStatus() != Stream::EOS)
|
|
{
|
|
mCurrentLine++;
|
|
fs.readLine((U8 *)&buf[0], sizeof(buf));
|
|
|
|
LString *ls;
|
|
if((ls = ParseLine(buf)) == NULL)
|
|
break;
|
|
|
|
if(ls->id)
|
|
{
|
|
U32 h = hashString(ls->id);
|
|
mTransLookup.insert((U32 *)lastID, h);
|
|
lastID++;
|
|
}
|
|
|
|
delete ls;
|
|
}
|
|
|
|
fs.close();
|
|
}
|
|
|
|
delete [] mCurrentFilename;
|
|
mCurrentFilename = fnBak;
|
|
mCurrentLine = lineBak;
|
|
|
|
return ret;
|
|
}
|