SwordScript - 使用C#开发脚本语言(五)表达式解析

1,387 阅读7分钟

本章节对应仓库

3.表达式解析 Github

巴科斯范式(BNF)

在本章之前,需要先科普一个知识点:BNF

如果已经有此类知识的读者可以跳过该段内容。

BNF

什么是BNF

BNF是一种形式化的语法表示方法,其有几个特点:

  • 所描述的语法是上下文无关的
  • 在双引号中的字("word")代表着这些字符本身。而double_quote用来代表双引号。
  • 在双引号外的字(有可能有下划线)代表着语法部分。
  • 尖括号( < > )内包含的为必选项。
  • 方括号( [ ] )内包含的为可选项。
  • 大括号( { } )内包含的为可重复0至无数次的项。
  • 竖线( | )表示在其左右两边任选一项,相当于"OR"的意思。
  • ::= 是“被定义为”的意思
  • 终结符:语言中的基本元素,无法再分解
  • 非终结符:可以再分解的元素

语法定义

描述简单的数字相加时,语法规则为:

add ::= <number> <"+"> <number>

这种表达式可以解析 1 + 2,解析后的结果为1 "+" 2

但这显然是不够用的,如果需要连续的数字相加时,如 1 + 2 + 3 时,上面的表达式便有了局限性。

因此需要对其进行改造,不过这并不难,BNF支持递归语法。

改造后的加法:

add ::= <add> <"+"> <number> | <number>

在解析 1 + 2 + 3 时,会解析为:(1 "+" 2) "+" 3

此时,简单的加法已经没有了难度。所以可以对其进行进一步改造,使其支持减法与括号

  • 减法支持
expr0 ::= <expr0> <"+"|"-"> <number> | <number>

由于减法与加法的优先级相同,可以把加减法合并到一个表达式当中。

  • 括号支持 相较减法,支持括号显然在难度上更大一些。由于括号会改变运算优先级,因此我们把带括号的表达式单独定义为一个非终结符。
primary ::= <"("> <expr0> <")"> | <number>
expr0   ::= <expr0> <"+"|"-"> <primary> | <primary>

如解析1 + (3 - 2 + 5)时,会按照如下方式进行解析:

expr0--primary--1
        |
       "+"
        |
       primary--"("
                 |
                expr0--primary--3
                 |        |
                ")"      "-"
                          |
                         expr0--primary--2
                                   |
                                  "+"
                                   |
                                primary--5
  • 右结合的操作符 上面列举的加法是左结合的运算符,所谓左结合是指解析时按照从左往右进行结合,如1 + 2 + 3,如果+号是左结合,那么最后得到的表达式是(1 + 2) + 3

而如果是右结合运算符,例如使用^符号表示乘方时,2 ^ 3 为2的3次方,2 ^ 3 ^ 2 为 2的(3的2次方)次方(即2的9次方),解析出来的表达式则是2 ^ (3 ^ 2)

要表达右结合运算符时,便要对语法定义进行一定的变换。上述例子中,左结合的加法定义为:

primary ::= <"("> <expr0> <")"> | <number>
expr0   ::= <expr0> <"+"> <primary> | <primary>

则右结合的乘方定义为

primary ::= <"("> <expr0> <")"> | <number>
expr0   ::= <primary> <"^"> <expr0> | <primary>

SwordScript的算术表达式定义

在开始写语法解析代码前,我们需要先写出算术表达式的定义。

按照优先级,可以将操作符的优先级表格列出:

操作符名称优先级类型结合性
.成员获取1成员获取左结合
[ ]数组取值1访问器调用左结合
( )函数调用1函数调用左结合
not取否2单目运算符右结合
-取负2单目运算符右结合
乘方3双目运算符右结合
*4双目运算符左结合
/4双目运算符左结合
%取余4双目运算符左结合
+5双目运算符左结合
-5双目运算符左结合
大于6双目运算符左结合
>=大于等于6双目运算符左结合
<小于6双目运算符左结合
<=小于等于6双目运算符左结合
==等于7双目运算符左结合
!=不等于7双目运算符左结合
and逻辑与8双目运算符左结合
or逻辑或9双目运算符左结合

在本章中,暂不考虑成员、数组与函数调用

将以上操作符化作BNF如下:

primary ::= <"("> <expr> <")"> | <literal|identifier>
expr2 ::= <"not"|"-"> <primary> | <primary>
expr3 ::= <expr2> <"^"> <expr3> | <expr2>
expr4 ::= <expr4> <"*"|"/"|"%"> <expr3> | <expr3>
expr5 ::= <expr5> <"+"|"-"> <expr4> | <expr4>
expr6 ::= <expr6> <">"|">="|"<"|"<="> <expr5> | <expr5>
expr7 ::= <expr7> <"=="|"!="> <expr6> | <expr6>
expr8 ::= <expr8> <"and"> <expr7> | <expr7>
expr9 ::= <expr9> <"or"> <expr8> | <expr8>
expr  ::= <expr9>

将BNF转换为解析器

有了以上定义后,便可以开始写解析器了。

新建一个类,命名为ScriptParser

ScriptParser.cs

using Sprache;

namespace SwordScript;

public static class ScriptParser
{
    
}

在定义表达式之前,我们需要定义终结符。

在上面的BNF中,终结符有各种符号以及literalidentifier

literal是字面量,指写在代码里的数值。因此可以得到定义:

public static readonly Parser<ASTLiteral> Literal =
    (from nullValue in Lexer.Null select new ASTNullLiteral())
    .Or<ASTLiteral>(from booleanValue in Lexer.Boolean select new ASTBooleanLiteral(booleanValue))
    .Or(from doubleValue in Lexer.DoubleFloat select new ASTDoubleFloatLiteral(doubleValue))
    .Or(from longValue in Lexer.LongInteger select new ASTLongIntegerLiteral(longValue))
    .Or(from stringValue in Lexer.String select new ASTStringLiteral(stringValue));

同理可得identifier

public static readonly Parser<ASTIdentifier> Identifier =
    from id in Lexer.Identifier select new ASTIdentifier(id);

primary

而在定义primary前,我们需要先定义一个指代表达式的非终结符expr

public static Parser<ASTNode> Expr;

expr的定义会在全部表达式定义完毕后定义。

primary的定义:

public static readonly Parser<ASTNode> Primary = (
        from left in Parse.Char('(').SuperToken()
        from expr in Parse.Ref(() => Expr)
        from right in Parse.Char(')').SuperToken()
        select expr)
    .Or(Literal)
    .Or(Identifier)
    .SuperToken();

单元测试

Tests工程新建类ExprTest

namespace Tests;

public class ExprTest
{

    
}

添加如下内容:

[Test]
public void PrimaryTest()
{
    Assert.AreEqual("123", ScriptParser.Primary.Parse(" 123 ").ToString());
    Assert.AreEqual("0.5", ScriptParser.Primary.Parse(" 0.5 ").ToString());
    Assert.AreEqual("abc", ScriptParser.Primary.Parse(" abc ").ToString());
    Assert.AreEqual("true", ScriptParser.Primary.Parse(" true ").ToString());
    Assert.AreEqual("null", ScriptParser.Primary.Parse(" null ").ToString());
}

运行单元测试,全部通过

expr2 - 取非、取反

在定义expr2之前,首先需要定义对应的语法树节点。

新建类ASTUnaryExpr

namespace SwordScript;

public abstract class ASTUnaryExpr : ASTList
{
    public ASTUnaryExpr(ASTNode expr) : base(new ASTNode[] { expr })
    {
        
    }
    
    public ASTNode Expr => this[0];
}

该类将作为所有一元表达式的基类

新建类ASTUnaryExprNegative表示取负

public class ASTUnaryExprNegative : ASTUnaryExpr
{
    public ASTUnaryExprNegative(ASTNode expr) : base(expr)
    {
    }
    
    public override string ToString()
    {
        return $"-{Expr}";
    }
}

新建类ASTUnaryExprNot表示取反

public class ASTUnaryExprNot : ASTUnaryExpr
{
    public ASTUnaryExprNot(ASTNode expr) : base(expr)
    {
    }
    
    public override string ToString()
    {
        return $"not {Expr}";
    }
}

随后在Lexer类中定义如下静态方法:

/// <summary>
/// 返回下个字符不为Unicode字符的字符符号解析器
/// </summary>
/// <param name="symbol"></param>
/// <returns></returns>
public static Parser<string> LetterSymbol(string symbol)
{
    return (from symbolStr in Parse.String(symbol).Text()
        from nextChar in Parse.Regex(@"[0-9_\p{L}]").Preview()
        where nextChar.IsEmpty
        select symbolStr).SuperToken();
}

/// <summary>
/// 返回普通的标点符号解析器
/// </summary>
/// <param name="symbol"></param>
/// <returns></returns>
public static Parser<string> PunctuationSymbol(string symbol)
{
    return (from symbolStr in Parse.String(symbol).Text() select symbolStr).SuperToken();
}

LetterSymbol用于解析如 and or not 这类由字母组成的符号,防止与标识符黏连在一起时也被识别

PunctuationSymbol用于解析普通的标点符号

添加负号与取非的词法解析:

public static readonly Parser<string> Negate = PunctuationSymbol("-");
public static readonly Parser<string> Not = LetterSymbol("not");

ScriptParser类中定义expr2

public static readonly Parser<ASTNode> Expr2 =(
        from symbol in Lexer.Negate.Or(Lexer.Not)
        from expr in Primary
        select (ASTNode)(symbol == "-" ? new ASTUnaryExprNegative(expr) : new ASTUnaryExprNot(expr)))
    .Or(Primary)
    .SuperToken();

单元测试

添加单元测试:

[Test]
public void Expr2Test()
{
    Assert.AreEqual("123", ScriptParser.Expr2.Parse(" 123 ").ToString());
    Assert.AreEqual("-123", ScriptParser.Expr2.Parse(" -123 ").ToString());
    Assert.AreEqual("not false", ScriptParser.Expr2.Parse(" not false ").ToString());
    Assert.AreEqual("nottrue", ScriptParser.Expr2.Parse(" nottrue ").ToString());
}

运行测试,全部通过

expr3 - 乘方

先定义乘方对应类:

ASTBinaryExprPower

public class ASTBinaryExprPower : ASTBinaryExpr
{
    public ASTBinaryExprPower(ASTNode left, ASTNode right) : base(left, right)
    {
        
    }

    public override string ToString()
    {
        return $"({Left} ^ {Right})";
    }
}

定义Expr3

public static readonly Parser<ASTNode> Expr3 =(
        from left in Expr2
        from symbol in Lexer.Power
        from right in Expr3
        select new ASTBinaryExprPower(left, right))
    .Or(Expr2)
    .SuperToken();

单元测试

[Test]
public void Expr3Test()
{
    Assert.AreEqual("(1 ^ 4)", ScriptParser.Expr3.Parse(" 1 ^ 4 ").ToString());
    Assert.AreEqual("(5 ^ 0.7)", ScriptParser.Expr3.Parse(" 5 ^ .7 ").ToString());
    Assert.AreEqual("(0.1 ^ 0.1)", ScriptParser.Expr3.Parse(" 0.1 ^ .1 ").ToString());
    Assert.AreEqual("(2 ^ (2 ^ 2))", ScriptParser.Expr3.Parse(" 2 ^ 2 ^ 2 ").ToString());
}

由于目前Expr还没有定义,所以暂时无法测试嵌套公式

运行测试,全部通过

expr4 - 乘、除、取余

与前面的表达式不同,从expr4开始,在文法上是无法直接输入解析器的。

左递归

先来看看expr4的文法:

expr4 ::= <expr4> <"*"|"/"|"%"> <expr3> | <expr3>

可以看到,expr4构成了一个左递归,有左递归的文法,在解析时可能会陷入无限左展开,如:

A ::= Aa | a
A = Aa
A = Aaa
A = Aaaa
A = Aaaaa.......

因此,在将BNF转换为语法解析前,需要先进行左递归消除。

消除左递归

首先需要分析,左递归的语句可以如何拆分。 网上常用的拆分方法如下:

expr4 ::= <expr3> <expr4'>
expr4' ::= <"*"|"/"|"%"> <expr3> <expr4'> | ε

注:ε代表空语法

而从方便语法解析的角度分析,其实也可以将expr4'视为一个重复N次的语法项

于是有了以下拆解:

expr4 ::= <expr3> {<"*"|"/"|"%"> <expr3>}

由于我们后面的表达式几乎都是左递归的文法,因此可以将左递归的表达式进行封装,封装后如下:

public delegate ASTNode CreateNode(ASTNode left,string op,ASTNode right);

public static Parser<ASTNode> LeftOperator(Parser<ASTNode> leftExpr, Parser<string> symbol,
    CreateNode apply)
{
    ASTNode CreateNode(ASTNode left, IEnumerable<Tuple<string,ASTNode>> rights)
    {
        foreach(var right in rights)
        {
            left = apply(left, right.Item1, right.Item2);
        }

        return left;
    }
    
    Parser<Tuple<string,ASTNode>> innerOperatorExpr = (
        from getSymbol in symbol
        from right in leftExpr
        select new Tuple<string, ASTNode>(getSymbol, right)
    ).SuperToken();
    
    Parser<ASTNode> operatorExpr = (
            from left in leftExpr
            from rights in innerOperatorExpr.Many()
            select CreateNode(left, rights)
        ).SuperToken();
    
    return operatorExpr;
}

之后的表达式只需要输入子项、符号与构造语法树的委托,即可直接构造并拆分左递归表达式文法。

Expr4定义

定义对应类:

public class ASTBinaryExprMultiply : ASTBinaryExpr
{
    public ASTBinaryExprMultiply(ASTNode left, ASTNode right) : base(left, right)
    {
        
    }

    public override string ToString()
    {
        return $"({Left} * {Right})";
    }
}

public class ASTBinaryExprDivide : ASTBinaryExpr
{
    public ASTBinaryExprDivide(ASTNode left, ASTNode right) : base(left, right)
    {
        
    }

    public override string ToString()
    {
        return $"({Left} / {Right})";
    }
}

public class ASTBinaryExprModulo : ASTBinaryExpr
{
    public ASTBinaryExprModulo(ASTNode left, ASTNode right) : base(left, right)
    {
        
    }

    public override string ToString()
    {
        return $"({Left} % {Right})";
    }
}

Lexer类中定义操作符

public static readonly Parser<string> Multiply = PunctuationSymbol("*");
public static readonly Parser<string> Divide = PunctuationSymbol("/");
public static readonly Parser<string> Modulo = PunctuationSymbol("%");

定义expr4

public static readonly Parser<ASTNode> Expr4 = LeftOperator(Expr3, Lexer.Multiply.Or(Lexer.Divide).Or(Lexer.Modulo),
    (left, op, right) =>
    {
        switch (op)
        {
            case "*":
                return new ASTBinaryExprMultiply(left, right);
            case "/":
                return new ASTBinaryExprDivide(left, right);
            case "%":
                return new ASTBinaryExprModulo(left, right);
            default:
                throw new ArgumentException($"Unknown operator '{op}'");
        }
    });

单元测试

[Test]
public void Expr4Test()
{
    Assert.AreEqual("(1 * 2)", ScriptParser.Expr4.Parse(" 1 * 2 ").ToString());
    Assert.AreEqual("(1 / 2)", ScriptParser.Expr4.Parse(" 1 / 2 ").ToString());
    Assert.AreEqual("(1 % 2)", ScriptParser.Expr4.Parse(" 1 % 2 ").ToString());
    Assert.AreEqual("((((1 * 2) / 3) % 4) * 5)", ScriptParser.Expr4.Parse(" 1 * 2 / 3 % 4 * 5 ").ToString());
}

运行测试,全部通过

Expr5 - 加、减

定义对应类:

public class ASTBinaryExprPlus : ASTBinaryExpr
{
    public ASTBinaryExprPlus(ASTNode left, ASTNode right) : base(left, right)
    {
        
    }

    public override string ToString()
    {
        return $"({Left} + {Right})";
    }
}

public class ASTBinaryExprMinus : ASTBinaryExpr
{
    public ASTBinaryExprMinus(ASTNode left, ASTNode right) : base(left, right)
    {
        
    }

    public override string ToString()
    {
        return $"({Left} - {Right})";
    }
}

定义操作符:

public static readonly Parser<string> Plus = PunctuationSymbol("+");
public static readonly Parser<string> Minus = PunctuationSymbol("-");

定义expr5

public static readonly Parser<ASTNode> Expr5 = LeftOperator(Expr4, Lexer.Plus.Or(Lexer.Minus),
    (left, op, right) =>
    {
        switch (op)
        {
            case "+":
                return new ASTBinaryExprPlus(left, right);
            case "-":
                return new ASTBinaryExprMinus(left, right);
            default:
                throw new ArgumentException($"Unknown operator '{op}'");
        }
    });

单元测试

[Test]
public void Expr5Test()
{
    Assert.AreEqual("(1 + 2)", ScriptParser.Expr5.Parse(" 1 + 2 ").ToString());
    Assert.AreEqual("(1 - 2)", ScriptParser.Expr5.Parse(" 1 - 2 ").ToString());
    Assert.AreEqual("(((1 + 2) - 3) + 4)", ScriptParser.Expr5.Parse(" 1 + 2 - 3 + 4 ").ToString());
}

运行单元测试,全部通过

Expr6 大于、大于等于、小于、小于等于

定义对应类:

public class ASTBinaryExprGreaterThan : ASTBinaryExpr
{
    public ASTBinaryExprGreaterThan(ASTNode left, ASTNode right) : base(left, right)
    {
        
    }

    public override string ToString()
    {
        return $"({Left} > {Right})";
    }
}

public class ASTBinaryExprGreaterThanOrEqual : ASTBinaryExpr
{
    public ASTBinaryExprGreaterThanOrEqual(ASTNode left, ASTNode right) : base(left, right)
    {
        
    }

    public override string ToString()
    {
        return $"({Left} >= {Right})";
    }
}

public class ASTBinaryExprLessThan : ASTBinaryExpr
{
    public ASTBinaryExprLessThan(ASTNode left, ASTNode right) : base(left, right)
    {
        
    }

    public override string ToString()
    {
        return $"({Left} < {Right})";
    }
}

public class ASTBinaryExprLessThanOrEqual : ASTBinaryExpr
{
    public ASTBinaryExprLessThanOrEqual(ASTNode left, ASTNode right) : base(left, right)
    {
        
    }

    public override string ToString()
    {
        return $"({Left} <= {Right})";
    }
}

定义操作符

public static readonly Parser<string> GreaterThan = PunctuationSymbol(">");
public static readonly Parser<string> GreaterThanOrEqual = PunctuationSymbol(">=");
public static readonly Parser<string> LessThan = PunctuationSymbol("<");
public static readonly Parser<string> LessThanOrEqual = PunctuationSymbol("<=");

定义Expr6

public static readonly Parser<ASTNode> Expr6 = LeftOperator(Expr5, 
    Lexer.GreaterThanOrEqual.Or(Lexer.GreaterThan)
    .Or(Lexer.LessThanOrEqual).Or(Lexer.LessThan),
    (left, op, right) =>
    {
        switch (op)
        {
            case ">":
                return new ASTBinaryExprGreaterThan(left, right);
            case ">=":
                return new ASTBinaryExprGreaterThanOrEqual(left, right);
            case "<":
                return new ASTBinaryExprLessThan(left, right);
            case "<=":
                return new ASTBinaryExprLessThanOrEqual(left, right);
            default:
                throw new ArgumentException($"Unknown operator '{op}'");
        }
    });

需要注意的是,大于等于需要放在大于前,小于等于需要放在小于前,否则会因为先匹配了大于或小于符号,导致大于等于和小于等于无法匹配。

单元测试

[Test]
public void Expr6Test()
{
    Assert.AreEqual("(1 < 2)", ScriptParser.Expr6.Parse(" 1 < 2 ").ToString());
    Assert.AreEqual("(1 > 2)", ScriptParser.Expr6.Parse(" 1 > 2 ").ToString());
    Assert.AreEqual("(1 <= 2)", ScriptParser.Expr6.Parse(" 1 <= 2 ").ToString());
    Assert.AreEqual("(1 >= 2)", ScriptParser.Expr6.Parse(" 1 >= 2 ").ToString());
    Assert.AreEqual("(((1 < 2) < 3) < 4)", ScriptParser.Expr6.Parse(" 1 < 2 < 3 < 4 ").ToString());
}

运行测试,全部通过

Expr7 等于、不等于

定义对应类:

public class ASTBinaryExprEqual : ASTBinaryExpr
{
    public ASTBinaryExprEqual(ASTNode left, ASTNode right) : base(left, right)
    {
        
    }

    public override string ToString()
    {
        return $"({Left} == {Right})";
    }
}

public class ASTBinaryExprNotEqual : ASTBinaryExpr
{
    public ASTBinaryExprNotEqual(ASTNode left, ASTNode right) : base(left, right)
    {
        
    }

    public override string ToString()
    {
        return $"({Left} != {Right})";
    }
}

定义操作符:

public static readonly Parser<string> Equal = PunctuationSymbol("==");
public static readonly Parser<string> NotEqual = PunctuationSymbol("!=");

定义expr7

public static readonly Parser<ASTNode> Expr7 = LeftOperator(Expr6, Lexer.Equal.Or(Lexer.NotEqual),
    (left, op, right) =>
    {
        switch (op)
        {
            case "==":
                return new ASTBinaryExprEqual(left, right);
            case "!=":
                return new ASTBinaryExprNotEqual(left, right);
            default:
                throw new ArgumentException($"Unknown operator '{op}'");
        }
    });

单元测试

[Test]
public void Expr7Test()
{
    Assert.AreEqual("(1 == 2)", ScriptParser.Expr7.Parse(" 1 == 2 ").ToString());
    Assert.AreEqual("(1 != 2)", ScriptParser.Expr7.Parse(" 1 != 2 ").ToString());
    Assert.AreEqual("(((1 == 2) == 3) == 4)", ScriptParser.Expr7.Parse(" 1 == 2 == 3 == 4 ").ToString());
}

运行单元测试,全部通过

Expr8 逻辑与

定义对应类:

public class ASTBinaryExprAnd : ASTBinaryExpr
{
    public ASTBinaryExprAnd(ASTNode left, ASTNode right) : base(left, right)
    {
        
    }

    public override string ToString()
    {
        return $"({Left} and {Right})";
    }
}

定义操作符:

public static readonly Parser<string> And = LetterSymbol("and");

定义expr8

public static readonly Parser<ASTNode> Expr8 = LeftOperator(Expr7, Lexer.And,
    (left, op, right) => new ASTBinaryExprAnd(left, right));

单元测试

[Test]
public void Expr8Test()
{
    Assert.AreEqual("(a and b)", ScriptParser.Expr8.Parse(" a and b ").ToString());
    Assert.AreEqual("((a and b) and c)", ScriptParser.Expr8.Parse(" a and b and c ").ToString());
}

运行单元测试,全部通过

Expr9 逻辑或

定义对应类:

public class ASTBinaryExprOr : ASTBinaryExpr
{
    public ASTBinaryExprOr(ASTNode left, ASTNode right) : base(left, right)
    {
        
    }

    public override string ToString()
    {
        return $"({Left} or {Right})";
    }
}

定义操作符:

public static readonly Parser<string> Or = LetterSymbol("or");

定义Expr9

public static readonly Parser<ASTNode> Expr9 = LeftOperator(Expr8, Lexer.Or,
    (left, op, right) => new ASTBinaryExprOr(left, right));

单元测试

[Test]
public void Expr9Test()
{
    Assert.AreEqual("(a or b)", ScriptParser.Expr9.Parse(" a or b ").ToString());
    Assert.AreEqual("((a or b) or c)", ScriptParser.Expr9.Parse(" a or b or c ").ToString());
}

运行单元测试,全部通过

表达式闭环

在定义完所有表达式后,回到Expr上,将其补充为如下定义:

public static readonly Parser<ASTNode> Expr = Parse.Ref(() => Expr9);

此时,表达式解析已经完成了一个闭环,所有的表达式相互嵌套应当没有了问题。

单元测试

[Test]
public void Expression()
{
    Assert.AreEqual("((1 + 2) * (3 + 4))", ScriptParser.Expr.Parse(" (1 + 2) * (3 + 4)  ").ToString());
    Assert.AreEqual("(a >= ((3 * (b ^ 5)) - 8))", ScriptParser.Expr.Parse(" a >= 3 * b ^ 5 - 8 ").ToString());
    Assert.AreEqual("((a and (b1 == b2)) or (((9 * 9) == c) and d))", ScriptParser.Expr.Parse(" a and b1 == b2 or 9*9 == c and d ").ToString());
    Assert.AreEqual("((a and (b1 == (b2 or ((9 * 9) == c)))) and d)", ScriptParser.Expr.Parse(" a and b1 == (b2 or 9*9 == c) and d ").ToString());
}

在测试中,各级表达式嵌套及优先级匹配都运行通过

结语

本章内容较多,主要涉及了完整的算术表达式解析。在下章中,我们将以表达式解析出的抽象语法树为基础,进行表达式的计算。