You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
619 lines
19 KiB
619 lines
19 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Linq;
|
|
|
|
namespace ProtoCSStruct
|
|
{
|
|
public class ProtoFileParser
|
|
{
|
|
private ProtoDescriptor pd;
|
|
|
|
private ProtoEnum currentEnum;
|
|
private ProtoMessage currentMessage;
|
|
|
|
public ProtoFileParser()
|
|
{
|
|
pd = new ProtoDescriptor();
|
|
}
|
|
|
|
public ProtoDescriptor Parse(string file, string nameSpace, string outpath,
|
|
string outfile, string enumOutFile, string classOutFile, string genClassCfg)
|
|
{
|
|
Console.WriteLine("begin parse proto file {0}", file);
|
|
pd.NameSpace = nameSpace;
|
|
pd.OutPutPath = outpath;
|
|
pd.OutPutFile = outfile;
|
|
pd.EnumOutPutFile = enumOutFile;
|
|
pd.ClassOutPutFile = classOutFile;
|
|
pd.ProtoFile = file;
|
|
|
|
string[] allline = File.ReadAllLines(file);
|
|
bool nameNeedUpper = true;
|
|
for(int i=0; i< allline.Length; i++)
|
|
{
|
|
if (string.IsNullOrEmpty(genClassCfg) && string.Equals(allline[i], "//**battle begin**//"))
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (string.Equals(allline[i], "//**battle begin**//"))
|
|
{
|
|
nameNeedUpper = false; //不改变结构,战斗相关首字母不大写
|
|
}
|
|
ParseLine(i + 1, allline[i], nameNeedUpper);
|
|
}
|
|
|
|
ProtoDescriptorAll.Instance.AllProtoDescriptor.Add(pd);
|
|
|
|
ProccessOnReadEnd();
|
|
|
|
// 是否需要生成message的纯数据结构的class描述
|
|
ParseGenClassDescCfg(genClassCfg);
|
|
|
|
return pd;
|
|
}
|
|
|
|
private void ParseLine(int lineNum, string lineContent,bool nameNeedUpper)
|
|
{
|
|
lineContent = DeleteComment(lineContent);
|
|
lineContent = Escape(lineContent);
|
|
|
|
if (string.IsNullOrEmpty(lineContent))
|
|
{
|
|
return;
|
|
}
|
|
|
|
List<ProtoFileLine> linelist = GetComplexLineInfo(lineNum, lineContent);
|
|
|
|
foreach(var lineInfo in linelist)
|
|
{
|
|
ProcessOneLineInfo(lineInfo, nameNeedUpper);
|
|
}
|
|
}
|
|
|
|
private void ProcessOneLineInfo(ProtoFileLine lineInfo, bool nameNeedUpper)
|
|
{
|
|
if(lineInfo.LineType == LineType.Syntax)
|
|
{
|
|
if(lineInfo.Fields[1].Contains("proto3") == false)
|
|
{
|
|
ExceptionUtils.Throw("protocsstruct onlysupport proto3 syntax, at line {0}"
|
|
, lineInfo.LineNum);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if(lineInfo.LineType == LineType.Enum)
|
|
{
|
|
if(currentEnum != null)
|
|
{
|
|
ExceptionUtils.Throw("start enum {0} at line {1}, but last enum not end"
|
|
, lineInfo.Fields[1], lineInfo.LineNum);
|
|
}
|
|
|
|
if (currentMessage != null)
|
|
{
|
|
ExceptionUtils.Throw("start enum {0} at line {1}, but last message not end"
|
|
, lineInfo.Fields[1], lineInfo.LineNum);
|
|
}
|
|
|
|
currentEnum = new ProtoEnum();
|
|
currentEnum.Name = lineInfo.Fields[1];
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
if (lineInfo.LineType == LineType.Message)
|
|
{
|
|
if (currentEnum != null)
|
|
{
|
|
ExceptionUtils.Throw("start message {0} at line {1}, but last enum not end"
|
|
, lineInfo.Fields[1], lineInfo.LineNum);
|
|
}
|
|
|
|
if (currentMessage != null)
|
|
{
|
|
ExceptionUtils.Throw("start message {0} at line {1}, but last message not end"
|
|
, lineInfo.Fields[1], lineInfo.LineNum);
|
|
}
|
|
|
|
currentMessage = new ProtoMessage();
|
|
currentMessage.Name = lineInfo.Fields[1];
|
|
return;
|
|
}
|
|
|
|
if(lineInfo.LineType == LineType.FiledContent)
|
|
{
|
|
if (currentEnum != null)
|
|
{
|
|
ProtoEnumValue enumValue = new ProtoEnumValue();
|
|
enumValue.OriginalName = lineInfo.Fields[0];
|
|
enumValue.Number = int.Parse(lineInfo.Fields[1]);
|
|
|
|
FormatEnumValueName(currentEnum.Name, enumValue);
|
|
|
|
currentEnum.Values.Add(enumValue);
|
|
|
|
return;
|
|
}
|
|
|
|
if (currentMessage != null)
|
|
{
|
|
ProtoMessageField field = new ProtoMessageField();
|
|
|
|
field.IsRepeated = lineInfo.Fields[0] == "repeated";
|
|
|
|
int contentIndex = 0;
|
|
if(field.IsRepeated)
|
|
{
|
|
contentIndex++;
|
|
}
|
|
|
|
field.TypeName = lineInfo.Fields[contentIndex];
|
|
FieldType type = FieldTypeUtils.GetFieldType(field.TypeName);
|
|
field.FieldType = type;
|
|
|
|
contentIndex++;
|
|
field.Name = FormatFieldName(lineInfo.Fields[contentIndex], nameNeedUpper);
|
|
|
|
contentIndex++;
|
|
field.Number = int.Parse(lineInfo.Fields[contentIndex]);
|
|
|
|
while (lineInfo.Fields.Length > contentIndex + 2)
|
|
{
|
|
contentIndex++;
|
|
string key = lineInfo.Fields[contentIndex];
|
|
|
|
contentIndex++;
|
|
string value = lineInfo.Fields[contentIndex];
|
|
|
|
|
|
if(key == "default")
|
|
{
|
|
ExceptionUtils.Throw("protocsstruct not support default option in message filed at line {0}", lineInfo.LineNum);
|
|
}
|
|
|
|
if(key == "stringlength")
|
|
{
|
|
if(type != FieldType.String)
|
|
{
|
|
ExceptionUtils.Throw("protocsstruct option stringlength need string type filed at line {0}", lineInfo.LineNum);
|
|
}
|
|
|
|
field.StringLength = int.Parse(value);
|
|
|
|
//string 有长度限制,太长了不支持算了
|
|
if(field.StringLength < 8
|
|
|| field.StringLength > Define.MaxStringLength)
|
|
{
|
|
ExceptionUtils.Throw("protocsstruct option stringlength need in range 8 ~ {0} at line {1}"
|
|
, Define.MaxStringLength, lineInfo.LineNum);
|
|
}
|
|
|
|
if (FieldTypeUtils.IsValidStringLength(field.StringLength) == false)
|
|
{
|
|
ExceptionUtils.Throw("protocsstruct option stringlength need times with 8 at line {0}", lineInfo.LineNum);
|
|
}
|
|
}
|
|
|
|
if(key == "repeatedcount")
|
|
{
|
|
if(field.IsRepeated == false && field.FieldType != FieldType.Bytes)
|
|
{
|
|
ExceptionUtils.Throw("protocsstruct option repeatedcount need repeated at line {0}", lineInfo.LineNum);
|
|
}
|
|
|
|
field.RepeatedCount = int.Parse(value);
|
|
|
|
if (FieldTypeUtils.IsValidRepeatedCount(field.RepeatedCount) == false)
|
|
{
|
|
ExceptionUtils.Throw("protocsstruct option repeatedcount need times with 2 at line {0}", lineInfo.LineNum);
|
|
}
|
|
}
|
|
}
|
|
|
|
//这种情况最后统一处理
|
|
if(type == FieldType.String)
|
|
{
|
|
if(field.StringLength == 0)
|
|
{
|
|
ExceptionUtils.Throw("protocsstruct option stringlength need by string type at line {0}", lineInfo.LineNum);
|
|
}
|
|
}
|
|
|
|
if(field.IsRepeated && field.RepeatedCount == 0)
|
|
{
|
|
ExceptionUtils.Throw("protocsstruct option repeatedcount need by repeated field type at line {0}", lineInfo.LineNum);
|
|
}
|
|
|
|
|
|
currentMessage.Fields.Add(field);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//结束
|
|
if(lineInfo.LineType == LineType.End)
|
|
{
|
|
if (currentEnum != null)
|
|
{
|
|
//排序
|
|
currentEnum.Values = currentEnum.Values.OrderBy(o => o.Number).ToList();
|
|
|
|
pd.Enums.Add(currentEnum);
|
|
currentEnum = null;
|
|
return;
|
|
}
|
|
|
|
if(currentMessage != null)
|
|
{
|
|
//排序
|
|
currentMessage.Fields = currentMessage.Fields.OrderBy(o => o.Number).ToList();
|
|
|
|
//map
|
|
foreach(var field in currentMessage.Fields)
|
|
{
|
|
currentMessage.FieldsMap.Add(field.Name, field);
|
|
}
|
|
|
|
pd.Messages.Add(currentMessage);
|
|
currentMessage = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
//首字母大写
|
|
private string FormatFieldName(string name,bool nameNeedUpper)
|
|
{
|
|
if (!nameNeedUpper)
|
|
{
|
|
return name;
|
|
}
|
|
if(Char.IsUpper(name[0]))
|
|
{
|
|
return name;
|
|
}
|
|
|
|
StringBuilder sb = new StringBuilder(32);
|
|
|
|
for (int i = 0; i < name.Length; i++)
|
|
{
|
|
char c = name[i];
|
|
if(i == 0)
|
|
{
|
|
c = Char.ToUpper(c);
|
|
}
|
|
|
|
sb.Append(c);
|
|
}
|
|
|
|
return sb.ToString();
|
|
|
|
}
|
|
|
|
private void FormatEnumValueName(string enumName, ProtoEnumValue enumValue)
|
|
{
|
|
string name = enumValue.OriginalName;
|
|
|
|
int indexInEnumName = 0;
|
|
|
|
bool NextCharNeedUp = true;
|
|
bool needCmpNameChar = true;
|
|
bool allCharUp = true;
|
|
|
|
for (int i = 0; i < name.Length; i++)
|
|
{
|
|
char c = name[i];
|
|
if (c == '_')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(Char.IsLower(c))
|
|
{
|
|
allCharUp = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool isPrefixClear = false;
|
|
StringBuilder sb = new StringBuilder(32);
|
|
|
|
for(int i=0; i< name.Length; i++)
|
|
{
|
|
char c = name[i];
|
|
|
|
if(c == '_')
|
|
{
|
|
NextCharNeedUp = true;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (indexInEnumName < enumName.Length && needCmpNameChar)
|
|
{
|
|
char c2 = enumName[indexInEnumName];
|
|
indexInEnumName++;
|
|
|
|
if (Char.ToLower(c) != Char.ToLower(c2))
|
|
{
|
|
needCmpNameChar = false;
|
|
}
|
|
}
|
|
else if(! isPrefixClear && indexInEnumName == enumName.Length && needCmpNameChar)
|
|
{
|
|
// 如果枚举变量开头部分和枚举名一致时去掉前缀
|
|
// enum CSMsgID
|
|
// {
|
|
// CSMsgID_Login = 1;
|
|
// CSMsgID_Logout = 2;
|
|
// }
|
|
// 被转换成
|
|
// public enum CSMsgID
|
|
// {
|
|
// Login = 1,
|
|
// Logout = 2,
|
|
// }
|
|
sb.Clear();
|
|
isPrefixClear = true;
|
|
}
|
|
|
|
if(NextCharNeedUp)
|
|
{
|
|
c = Char.ToUpper(c);
|
|
NextCharNeedUp = false;
|
|
}
|
|
else if(allCharUp == true)
|
|
{
|
|
c = Char.ToLower(c);
|
|
}
|
|
|
|
sb.Append(c);
|
|
}
|
|
|
|
enumValue.Name = sb.ToString();
|
|
}
|
|
|
|
//删除注释
|
|
private string DeleteComment(string lineContent)
|
|
{
|
|
int commentIndex = lineContent.IndexOf("//");
|
|
if(commentIndex != -1)
|
|
{
|
|
lineContent = lineContent.Substring(0, commentIndex);
|
|
|
|
if(string.IsNullOrEmpty(lineContent))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
lineContent = lineContent.Trim();
|
|
|
|
//去掉最后的;
|
|
if (lineContent.Length > 0 && lineContent.IndexOf(';') == lineContent.Length - 1)
|
|
{
|
|
lineContent = lineContent.Substring(0, lineContent.Length - 1);
|
|
}
|
|
|
|
return lineContent;
|
|
}
|
|
|
|
private string Escape(string lineContent)
|
|
{
|
|
if(lineContent.Contains('\t'))
|
|
{
|
|
lineContent = lineContent.Replace('\t', ' ');
|
|
}
|
|
|
|
return lineContent;
|
|
}
|
|
|
|
private List<ProtoFileLine> GetComplexLineInfo(int lineNum, string line)
|
|
{
|
|
List<ProtoFileLine> lineInfoList = new List<ProtoFileLine>();
|
|
|
|
if(line.Contains('{'))
|
|
{
|
|
int index = line.IndexOf('{');
|
|
string line1 = line.Substring(0, index);
|
|
|
|
ProtoFileLine lineinfo = GetSimpleLineInfo(lineNum, line1);
|
|
if(lineinfo != null)
|
|
{
|
|
lineInfoList.Add(lineinfo);
|
|
}
|
|
|
|
ProtoFileLine linebegin = new ProtoFileLine();
|
|
linebegin.LineNum = lineNum;
|
|
linebegin.LineType = LineType.Begin;
|
|
lineInfoList.Add(linebegin);
|
|
|
|
return lineInfoList;
|
|
}
|
|
|
|
if (line.Contains('}'))
|
|
{
|
|
int index = line.IndexOf('}');
|
|
string line1 = line.Substring(0, index);
|
|
|
|
ProtoFileLine lineinfo = GetSimpleLineInfo(lineNum, line1);
|
|
if (lineinfo != null)
|
|
{
|
|
lineInfoList.Add(lineinfo);
|
|
}
|
|
|
|
ProtoFileLine lineend = new ProtoFileLine();
|
|
lineend.LineNum = lineNum;
|
|
lineend.LineType = LineType.End;
|
|
lineInfoList.Add(lineend);
|
|
|
|
return lineInfoList;
|
|
}
|
|
|
|
{
|
|
ProtoFileLine lineinfo = GetSimpleLineInfo(lineNum, line);
|
|
if (lineinfo != null)
|
|
{
|
|
lineInfoList.Add(lineinfo);
|
|
}
|
|
}
|
|
|
|
|
|
return lineInfoList;
|
|
}
|
|
|
|
private ProtoFileLine GetSimpleLineInfo(int lineNum, string line)
|
|
{
|
|
line = line.Trim();
|
|
|
|
if (string.IsNullOrEmpty(line))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
ProtoFileLine lineInfo = new ProtoFileLine();
|
|
|
|
lineInfo.LineNum = lineNum;
|
|
|
|
line = line.Replace('=',' ');
|
|
line = line.Replace('[', ' ');
|
|
line = line.Replace(']', ' ');
|
|
line = line.Replace(',', ' ');
|
|
|
|
while (line.Contains(" "))
|
|
{
|
|
line = line.Replace(" ", " ");
|
|
}
|
|
|
|
line = line.Trim();
|
|
|
|
string[] splits = line.Split(' ');
|
|
lineInfo.Fields = splits;
|
|
|
|
if (splits[0] == "syntax")
|
|
{
|
|
lineInfo.LineType = LineType.Syntax;
|
|
return lineInfo;
|
|
}
|
|
|
|
if (splits[0] == "package")
|
|
{
|
|
lineInfo.LineType = LineType.Package;
|
|
return lineInfo;
|
|
}
|
|
|
|
if (splits[0] == "option")
|
|
{
|
|
lineInfo.LineType = LineType.Option;
|
|
return lineInfo;
|
|
}
|
|
|
|
if (splits[0] == "import")
|
|
{
|
|
lineInfo.LineType = LineType.Import;
|
|
return lineInfo;
|
|
}
|
|
|
|
if (splits[0] == "enum")
|
|
{
|
|
lineInfo.LineType = LineType.Enum;
|
|
return lineInfo;
|
|
}
|
|
|
|
if (splits[0] == "message")
|
|
{
|
|
lineInfo.LineType = LineType.Message;
|
|
return lineInfo;
|
|
}
|
|
|
|
//剩下的就是内容
|
|
lineInfo.LineType = LineType.FiledContent;
|
|
return lineInfo;
|
|
}
|
|
|
|
private void ProccessOnReadEnd()
|
|
{
|
|
//所有enum加入map
|
|
foreach (var oneenum in pd.Enums)
|
|
{
|
|
pd.EnumsMap.Add(oneenum.Name, oneenum);
|
|
}
|
|
|
|
//所有message加入map
|
|
foreach (var onemessage in pd.Messages)
|
|
{
|
|
pd.MessagesMap.Add(onemessage.Name, onemessage);
|
|
}
|
|
|
|
//遍历所有message,里面有MessageOrEnum类型处理一下
|
|
foreach (var onemessage in pd.Messages)
|
|
{
|
|
foreach (var field in onemessage.Fields)
|
|
{
|
|
if (field.FieldType == FieldType.MessageOrEnum)
|
|
{
|
|
ProtoEnum pe = ProtoDescriptorAll.Instance.GetEnumByName(field.TypeName);
|
|
if (pe != null)
|
|
{
|
|
field.FieldType = FieldType.Enum;
|
|
field.Enum = pe;
|
|
continue;
|
|
}
|
|
|
|
ProtoMessage pm = ProtoDescriptorAll.Instance.GetMessageByName(field.TypeName);
|
|
if (pm != null)
|
|
{
|
|
field.FieldType = FieldType.Message;
|
|
field.Message = pm;
|
|
continue;
|
|
}
|
|
|
|
ExceptionUtils.Throw("parse error, unknow type {0}"
|
|
, field.TypeName);
|
|
}
|
|
}
|
|
}
|
|
|
|
//遍历所有message,计算一下大小什么的
|
|
foreach (var onemessage in pd.Messages)
|
|
{
|
|
onemessage.Calc();
|
|
}
|
|
}
|
|
|
|
private void ParseGenClassDescCfg(string file)
|
|
{
|
|
if (string.IsNullOrEmpty(file))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (! File.Exists(file))
|
|
{
|
|
Console.WriteLine("genclasscfg file {0} not exist", file);
|
|
return;
|
|
}
|
|
|
|
string[] allline = File.ReadAllLines(file);
|
|
for (int i = 0; i < allline.Length; i++)
|
|
{
|
|
// 跳过注释
|
|
if (allline[i].Contains("//"))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (pd.MessagesMap.TryGetValue(allline[i], out var message))
|
|
{
|
|
pd.MessagesNeedClassDesc.Add(message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|