介绍
最近,大多数编程新手都从高级语言开始,例如Java,Python,C#或任何其他包含垃圾收集器,现成数据结构等形式的``绅士集''的语言。当然,这种方法有其优势,但是通常来说,使用现成语言功能的新手开发人员会错过最重要的事情-它的结构以及操作和实现的机制。
我不会详细介绍内存分配和代码解释的方法,相反,我想谈谈编译器本身,即词法分析器,并尝试在C#中实现它。绝大多数人都知道我们将分析的语言-它是Pascal。
词法分析器是编译器的第一个“层”,负责突出显示标记以进行进一步处理。
词素是字典中代表我们语言的最小单位。服务字,运算符,标识符等可以用作令牌。
实作
结构说明
语言的形式描述将存储在两个数组中:第一个-服务词,第二个-分隔符和带有找到标记的列表
private string[] Words = { "program", "var", "integer", "real", "bool", "begin", "end", "if", "then", "else", "while", "do", "read", "write", "true", "false" };
private string[] Delimiter = { ".", ";", ",", "(", ")", "+", "-", "*", "/", "=", ">", "<" };
public List<Lex> Lexemes = new List<Lex>();
lexeme本身将存储密钥,该密钥将用于确定类型(服务字,运算符,标识符,数字),令牌的ID和值本身。
class Lex
{
public int id;
public int lex;
public string val;
public Lex(int _id, int _lex, string _val)
{
id = _id;
lex = _lex;
val = _val;
}
}
处理令牌的最佳解决方案是状态机。这将消除不必要的ifs,并使更改循环变得容易。S是初始状态,NUM,DLM,ASGN,ID是相应类型的令牌的状态,ER将用于错误,FIN将用于最终状态。
private string buf = ""; //
private char[] sm = new char[1];
private int dt = 0;
private enum States { S, NUM, DLM, FIN, ID, ER, ASGN, COM } // state-
private States state; //
private StringReader sr; //
主要方法是SearchLex,它在我们的数组中查找一个令牌,并在一个元组中返回其ID和值(是的,元组也可能有用); PushLex,它在字典中添加一个新的令牌。
private (int, string) SerchLex(string[] lexes)
{
var srh = Array.FindIndex(lexes, s => s.Equals(buf));
if (srh != -1)
return (srh, buf);
else return (-1, "");
}
private (int, string) PushLex(string[] lexes, string buf)
{
var srh = Array.FindIndex(lexes, s => s.Equals(buf));
if (srh != -1)
return (-1, "");
else
{
Array.Resize(ref lexes, lexes.Length + 1);
lexes[lexes.Length - 1] = buf;
return (lexes.Length - 1, buf);
}
}
算法实现
第一步是确定周期的结束-FIN状态,并实施初始状态,即
sr = new StringReader(text); //
while (state != States.FIN)
{
switch (state)
{
case States.S:
if (sm[0] == ' ' || sm[0] == '\n' || sm[0] == '\t' || sm[0] == '\0' || sm[0] == '\r' )
GetNext();
else if (Char.IsLetter(sm[0]))
{
ClearBuf();
AddBuf(sm[0]);
state = States.ID;
GetNext();
}
else if (char.IsDigit(sm[0]))
{
dt = (int)(sm[0]-'0');
GetNext();
state = States.NUM;
}
else if (sm[0] == '{')
{
state = States.COM;
GetNext();
}
else if (sm[0] == ':')
{
state = States.ASGN;
ClearBuf();
AddBuf(sm[0]);
GetNext();
}
else if (sm[0] == '.')
{
AddLex(Lexemes, 2, 0, sm[0].ToString());
state = States.FIN;
}
else
{
state = States.DLM;
}
break;
}
}
GetNext方法允许您分别获取字符串ClearBuf中的下一个字符,以清除存储令牌的缓冲区
private void GetNext()
{
sr.Read(sm, 0, 1);
}
应特别注意“:=”赋值运算符,它由两个单独的运算符组成。定义此运算符的最简单方法是添加条件并将中间值写入缓冲区。为此,实现了一个单独的ASGN状态(在翻译中,为assign-assignment)。如果将缓冲区定义为“:”,则算法将仅添加一个新令牌,如果下一个符号为“ =”,则将添加一个赋值运算符。
case States.ASGN:
if (sm[0] == '=')
{
AddBuf(sm[0]);
AddLex(Lexemes, 2, 4, buf);
ClearBuf();
GetNext();
}
else
AddLex(Lexemes, 2, 3, buf);
state = States.S;
break;
最终状态和有错误的状态仅通过服务消息来实现。您可以改进此选项并同样检查错误,但是也许可以将此功能转移到解析器。
case States.ER:
MessageBox.Show(" ");
state = States.FIN;
break;
case States.FIN:
MessageBox.Show(" ");
break;
测验
您可以通过不同的方式测试算法:直接指定.pas文件的路径,以编程方式创建字符串或任何其他方便的选项。由于我们使用C#编写程序,因此向应用程序添加表单并不困难,该表单将包含2个textBox,第一个用于输入程序代码,第二个用于显示算法的结果。
通过按下按钮,我们将开始文本分析,并将使用开关构造来处理结果:此外,我们将显示找到的令牌的类型。
private void button1_Click(object sender, EventArgs e)
{
textBox2.Clear();
TplMain tpl = new TplMain();
tpl.Analysis(textBox1.Text);
foreach(var lex in tpl.Lexemes)
{
switch (lex.id)
{
case 1:
textBox2.Text += "id: " + lex.id + " lex: " + lex.lex + " val: " + lex.val + " |" + " "+ Environment.NewLine;
break;
case 2:
textBox2.Text += "id: " + lex.id + " lex: " + lex.lex + " val: " + lex.val + " |" + " " + Environment.NewLine;
break;
case 3:
textBox2.Text += "id: " + lex.id + " lex: " + lex.lex + " val: " + lex.val + " |" + " " + Environment.NewLine;
break;
case 4:
textBox2.Text += "id: " + lex.id + " lex: " + lex.lex + " val: " + lex.val + " |" + " " + Environment.NewLine;
break;
}
}
}
输入数据
program hellohabr;
var a, b, c : integer;
begin
c := a - b + 15;
end.
输出量
id: 1 lex: 0 val: program |
id: 4 lex: 1 val: hellohabr |
id: 2 lex: 1 val: ; |
id: 1 lex: 1 val: var |
id: 4 lex: 1 val: a |
id: 2 lex: 2 val: , |
id: 4 lex: 1 val: b |
id: 2 lex: 2 val: , |
id: 4 lex: 1 val: c |
id: 2 lex: 3 val: : |
id: 1 lex: 2 val: integer |
id: 2 lex: 1 val: ; |
id: 1 lex: 5 val: begin |
id: 4 lex: 1 val: c |
id: 2 lex: 4 val: := |
id: 4 lex: 1 val: a |
id: 2 lex: 6 val: - |
id: 4 lex: 1 val: b |
id: 2 lex: 5 val: + |
id: 3 lex: 1 val: 15 |
id: 2 lex: 1 val: ; |
id: 1 lex: 6 val: end |
id: 2 lex: 0 val: . |
完整算法
public void Analysis(string text)
{
sr = new StringReader(text);
while (state != States.FIN)
{
switch (state)
{
case States.S:
if (sm[0] == ' ' || sm[0] == '\n' || sm[0] == '\t' || sm[0] == '\0' || sm[0] == '\r')
GetNext();
else if (Char.IsLetter(sm[0]))
{
ClearBuf();
AddBuf(sm[0]);
state = States.ID;
GetNext();
}
else if (char.IsDigit(sm[0]))
{
dt = (int)(sm[0] - '0');
GetNext();
state = States.NUM;
}
else if (sm[0] == '{')
{
state = States.COM;
GetNext();
}
else if (sm[0] == ':')
{
state = States.ASGN;
ClearBuf();
AddBuf(sm[0]);
GetNext();
}
else if (sm[0] == '.')
{
AddLex(Lexemes, 2, 0, sm[0].ToString());
state = States.FIN;
}
else
{
state = States.DLM;
}
break;
case States.ID:
if (Char.IsLetterOrDigit(sm[0]))
{
AddBuf(sm[0]);
GetNext();
}
else
{
var srch = SerchLex(Words);
if (srch.Item1 != -1)
AddLex(Lexemes, 1, srch.Item1, srch.Item2);
else
{
var j = PushLex(TID, buf);
AddLex(Lexemes, 4, j.Item1, j.Item2);
}
state = States.S;
}
break;
case States.NUM:
if (Char.IsDigit(sm[0]))
{
dt = dt * 10 + (int)(sm[0] - '0');
GetNext();
}
else
{
var j = PushLex(TNUM, dt.ToString());
AddLex(Lexemes, 3, j.Item1, j.Item2);
state = States.S;
}
break;
case States.DLM:
ClearBuf();
AddBuf(sm[0]);
var r = SerchLex(Delimiter);
if (r.Item1 != -1)
{
AddLex(Lexemes, 2, r.Item1, r.Item2);
state = States.S;
GetNext();
}
else
state = States.ER;
break;
case States.ASGN:
if (sm[0] == '=')
{
AddBuf(sm[0]);
AddLex(Lexemes, 2, 4, buf);
ClearBuf();
GetNext();
}
else
AddLex(Lexemes, 2, 3, buf);
state = States.S;
break;
case States.ER:
MessageBox.Show(" ");
state = States.FIN;
break;
case States.FIN:
MessageBox.Show(" ");
break;
}
}
}
结论
词法分析器似乎不是一件很清楚的事情,实际上也不是很重要。为什么不能将所有这些都放在解析器中?如何处理复杂的结构?是的,实现词法分析器的方法因编译器而异,但是当您分析所有这些原理时,您不仅会了解X编程语言的工作原理,而且还将为开发自己的编程语言奠定基础:第二个Python或您所在领域的语言-所有这些都是可能的总体上理解工作的所有细节和编译器的设备。
该项目可以在这里找到