编译器理论简介:使用C#对Pascal语言进行词法分析

介绍



最近,大多数编程新手都从高级语言开始,例如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或您所在领域的语言-所有这些都是可能的总体上理解工作的所有细节和编译器的设备。



该项目可以在这里找到



All Articles