37. 脚本系统之令牌流
文件是由分开的不同文本块组成的。这些文本可用作命令、属性名称、字符串、数字等。所使用的这些内容有一个共同点,即它们是用分隔符分开的。分开的每一组文本称为一个令牌。文件可以由许多令牌组成,每个令牌都有自己的用途和含义。在不考虑出现令牌原因的情况下,令牌只是一些文本。如果愿意的话,有时可以将文件分隔成一系列令牌——令牌流——这是一种很好的做法。如果读者曾经研究过更高级类型的脚本,实际上就会发现这只是为了将脚本编译成一种更低级形式而要做的一些工作。也许在创建命令和属性脚本系统时,已经使用过令牌流。虽然有一种获取信息的方法,但如果本该使用令牌流而没有使用,那么这个方法就不是很清晰。例如,对属性脚本而言,使用令牌流,所要做的全部工作就是调用某类函数,如GetNextToken(),将其赋给属性名称,然后再次调用GetNextToken()函数获取属性值。对命令脚本,可以使用GetNextToken()函数获取命令名,再次调用该函数可以获取跟在命令后的单个值或一组值,所有工作只需一个函数即可完成。
虽然本章并未对开发的脚本演示程序使用令牌流,但稍后在模型加载一章将使用它。在此,对它做个介绍,因为即使本书已经介绍了书中使用的两个系统,但令牌流确实与脚本紧密相关。将要创建的类似文件作为参数,并在每次调用一个名为GetNextToken()函数时返回该文件中的下一个令牌。对该令牌所做的处理取决于正在使用它的程序。
演示程序将加载文件,显示文件中的令牌流数目,文件的字节数以及整个文件的令牌流。这里将使用std::cout向屏幕上显示内容。控制台程序也会让人生厌,但它对验证这个主题却非常合适。
演示程序中要介绍的第一个文件是main.cpp源文件。该演示程序由4个文件构成:main.cpp、tokensFile.txt、token.h和token.cpp。在演示程序的main函数中,将整个文件加载到内存中,将其设置为令牌流,并输出一些与令牌流相关的信息。该信息是指明文件大小的字节数、令牌数目以及文件名。完成该工作后,用空格分开显示每个令牌。如果查看将要加载的tokensFile.txt文件,会发现它包含了随机单词和一些用空格分开的无规则的数。由于空格是分隔符,因此可以忽律令牌之间的其他行和空格。空格和新行只是为了让人看着方便,对机器却没有任何意义。一旦显示完令牌,就可以关闭程序。
main.cpp
#include<iostream>
#include"Token.h"
using namespace std;
int main(int arg, char **argc)
{
// Open file for input.
FILE *file = fopen("tokenFile.txt", "r");
// Display errors if any.
if(!file)
{
cout << "Error reading file." << endl << endl;
return 0;
}
// Get the length of the file.
fseek(file, 0, SEEK_END);
int length = ftell(file);
fseek(file, 0, SEEK_SET);
// Read in all data from the file.
char *data = new char[(length + 1) * sizeof(char)];
if(!data) return NULL;
fread(data, length, 1, file);
data[length] = '\0';
// Close the file when we are done.
fclose(file);
// Set our file to our lexer.
CToken token;
token.SetTokenStream(data);
// No longer need.
delete[] data;
data = NULL;
// Get total tokens.
int totalTokens = 0;
char buff[256] = { 0 };
while(token.GetNextToken(buff))
if(buff[0] != '\0')
totalTokens++;
// Print statistics.
cout << " File name: " << "Tokens.txt." << endl;
cout << " File size in bytes: " << length << "." << endl;
cout << "Total number of tokens: " << totalTokens << "." << endl;
cout << endl << endl;
cout << "Token stream:" << endl << endl;
// Reset.
token.Reset();
// Print all tokens.
while(token.GetNextToken(buff))
if(buff[0] != '\0')
cout << buff << " ";
// Release all memory.
token.Shutdown();
cout << endl << endl << endl;
return 0;
}
token.h
#ifndef _UGP_TOKEN_H_
#define _UGP_TOKEN_H_
class CToken
{
public:
CToken() : m_length(0), m_startIndex(0), m_endIndex(0), m_data(0) {}
~CToken() { Shutdown(); }
// 返回到文件的头部
void Reset(){ m_startIndex = m_endIndex = 0; }
void SetTokenStream(char *data);
// @buffer 保存令牌的缓存
bool GetNextToken(char *buffer);
// @token 令牌
// @buffer 保存令牌的缓存
bool GetNextToken(char *token, char *buffer);
// 移动到文件的下一行
bool MoveToNextLine(char *buffer);
void Shutdown();
private:
// 文件长度
int m_length;
//跟踪文件的起始索引和终止索引
int m_startIndex, m_endIndex;
// 文件数据流
char *m_data;
};
#endif
token.cpp
#include<string.h>
#include"Token.h"
bool isValidIdentifier(char c)
{
// It is valid if it falls within one of these ranges.
if((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') || c == '_' || c =='"' || c =='/' ||
c =='(' || c ==')' || c =='-' || c=='.')
return true;
return false;
}
void CToken::SetTokenStream(char *data)
{
Shutdown();
m_length = strlen(data);
m_data = new char[(m_length + 1) * sizeof(char)];
strcpy(m_data, data);
m_data[m_length] = '\0';
}
bool CToken::GetNextToken(char *buffer)
{
bool inString = false;
// 本次开的起始索引赋值为上次的终止索引
m_startIndex = m_endIndex;
if(buffer)
buffer[0] = '\0';
// 只要是没有inString或者当前字符是空格或者'\t',则向前移动起始索引
while( m_startIndex < m_length
&& (m_data[m_startIndex] == ' ' || m_data[m_startIndex] == '\t')
&& !inString)
{
if(m_data[m_startIndex] == '"')
inString = !inString;
m_startIndex++;
}
// 将终止索引赋值为起始索引+1
m_endIndex = m_startIndex + 1;
if(m_startIndex < m_length)
{
bool valid = true;
if(isValidIdentifier(m_data[m_startIndex]))
// 不停地向前移动终止索引直到该元素结束
while(isValidIdentifier(m_data[m_endIndex]) || m_data[m_endIndex] == '.')
m_endIndex++;
else
valid = false;
if(buffer != NULL)
{
if(valid)
{
strncpy(buffer, m_data + m_startIndex, m_endIndex - m_startIndex);
buffer[m_endIndex - m_startIndex] = '\0';
if(strcmp(buffer, "\n") == 0)
buffer[0] = '\0';
}
else
buffer[0] = '\0';
}
return true;
}
return false;
}
bool CToken::GetNextToken(char *token, char *buffer)
{
char tok[256];
while(GetNextToken(tok))
if(stricmp(tok, token) == 0)
return GetNextToken(buffer);
return false;
}
bool CToken::MoveToNextLine(char *buffer)
{
if(m_startIndex < m_length && m_endIndex < m_length)
{
m_startIndex = m_endIndex;
while(m_endIndex < m_length && (m_data[m_endIndex] != '\n' &&
m_data[m_endIndex] != '\r' && m_data[m_endIndex] != '\0'))
m_endIndex++;
if(m_endIndex - m_startIndex >= 511)
return false;
if(buffer != NULL)
{
strncpy(buffer, m_data + m_startIndex, m_endIndex - m_startIndex);
buffer[m_endIndex - m_startIndex] = '\0';
}
}
else
return false;
return true;
}
void CToken::Shutdown()
{
if(m_data) delete[] m_data;
m_data = NULL;
m_length = m_startIndex = m_endIndex = 0;
}