第一步:字符串到Token
要执行一段代码,首先需要能够识别代码中的语法元素,所以我们需要对代码进行解析。
青语言代码的解析定义在 Lang/Parser.cs 文件中,首先定义了青语言中使用到的符号和关键字:
/*这里定义一般的符号*/
public static List<string> Marks = new List<string>() {
";;",";", ",", ",", "“", "”", "‘", "\"", "、"
};
/*定义语言使用的中文关键字*/
public static List<string> KeyMarks = new List<string>() {
"元", "空", "真", "假", "如果", "再则", "否则", "当",
"执行", "直到", "返回", "跳出", "继续", "取反",
"异步", "等待", "尝试", "排查", "例行", "抛出", "遍历",
};
/*
* 下面定义中缀运算符
* 由于中缀运算符存在优先级差异
* 分开定义
*/
public static List<string> AddMarks = new List<string>() {
"+=", "-=", "+", "-",
"加等", "减等", "加", "减",
};
public static List<string> MulMarks = new List<string>() {
"*=", "/=", "%=", "*", "/", "%",
"乘等", "除等", "模等", "乘", "除以", "模",
};
public static List<string> CompareMarks = new List<string>() {
">=", "<=", "==", "!=", "<>", ">", "<",
"大于等于", "小于等于", "等于", "不等于", "大于", "小于",
};
public static List<string> LogicMarks = new List<string>() {
"&&", "||",
"且", "或",
};
public static List<string> SetMarks = new List<string>() {
"=", ":", ":",
"设为", "为",
};
/*定义集合类型的标记符号*/
public static List<string> CollMarks = new List<string>() {
"{", "}", "【", "】", "(", ")", "《", "》",
};
由于我们使用中文进行编程,所以中文关键字和符号如果出现在变量名或函数名中,会出现歧义。从语法上我们要求,如果需要使用中文关键字和符号,那么其前后必须有空白字符或者其他符号作为分隔标记。
所以,中文关键字和符号我们不作为拆分代码的标记,而是使用上面定义的符号(包括中英文符号)外加任意空白字符,作为拆分代码的依据。
拆分代码字符串,讲其转化为Token序列,使用到的函数:
public List SliceText(string text, string src) {
List<Token> ret = new List<Token>(); //定义返回的Token列表
int startIdx = 0; //当前片段的起点
int idx = 0; // 当前遍历的序号
//遍历
while(idx < text.Length) {
……
}
这里我们遍历整个代码字符串,设置idx表示当前遍历的位置。
遍历的过程中,每次向后移动1个位置,然后判断当前位置是否以符号开头。如果当前位置以符号开头,那么就作为分隔标志,将符号之前的部分切割后放入新建的Token。
遍历的过程中,如果遇到字符串起始标记,由于字符串是特殊的语法元素,在切割字符串时,就直接作为一个整体,放到Token中,使用到的函数是:
public string TakeStr(string text, int idx) {
……
}
另外,整个遍历过程中,一旦发现换行,那么就对Parser对象中的line自增1,表示当前行号。每次生成新的Token时都记录其所处的行号,作为之后运行期间的异常提示信息。
最后,得到的Token序列还需要进行一个-号歧义的处理,因为-号既有可能表示减法,也有可能是负数的标记,所以在返回前我们进行一次处理,包括一些清理工作
for(int i=0; i<ret.Count; i++) {
if (ret[i].Str == "-" && ((i == 0 && ret.Count > 1) || (i>0 && Marks.Contains(ret[i-1].Str) && i<ret.Count-1))) {
ret[i].Str = ret[i].Str + ret[i+1].Str;
ret.Remove(ret[i+1]);
}else if (ret[i].Str == "," || ret[i].Str == ",") {
ret.Remove(ret[i]);
if (i < ret.Count-1 && ret[i].Str == "-") {
ret[i].Str = ret[i].Str + ret[i+1].Str;
ret.Remove(ret[i+1]);
} else {
i--;
}
}
}
这里如果-号的前面也是符号或关键字,那么就认为应该是负数的标记,把它后一个Token合并。
另外,逗号在青语言中仅作为视觉分隔,本身不表示任何意义,所以这里把逗号都去除了。
到这里,我们就得到了字符串分割后的Token序列,每个Token中都保存对应的代码片段、所处行号和来源的文件。