【编译原理课程设计二】 --- Java实现 语法分析:分析算术表达式的语法结构+对语句进行语法分析+递归下降的语法分析

【任务介绍】根据给定的上下文无关文法,分析任意一个算术表达式的语法结构。

【输入】任意的算术表达式。

【输出】与输入对应的一颗语法树或者错误。

【题目】设计一个程序,根据给定的上下文无关文法,构造一颗语法树来表达任意一个算术表达式的语法结构。要求:

1. 基础文法:

 <Expr>-><Term><Expr1>

<Expr1>-><AddOp><Term><Expr1>|empty

<Term>-><Factor><Term1>

<Term1>-><MulOp><Factor><Term1>|empty

<Factor>->id|number|(<Expr>)

<AddOp>->+|-

<MulOp>->*|/

2. 语法分析方法采用递归子程序法。

3. 输入:形如  a+b*2/4-(b+c)*3 的算术表达式,有+、-、*、/ 四种运算符,运算符的优先级、结合规则和括号的用法遵循惯例,有变量、整数两种运算对象。为简化问题,变量和整数均为只含有1个字符的单词,忽略空格等非必要的字符。

4. 输出:输入正确时,输出其对应的语法树,树根标记为<Expr>;输入错误时,输出error。

【任务介绍】根据给定的上下文无关文法,对高级程序设计语言中常见的几种执行语句进行语法分析。

【输入】一串执行语句,其中包括:赋值语句、选择语句和循环语句。

【输出】与输入对应的一颗完整的语法树或者错误。

【题目】设计一个程序,根据给定的上下文无关文法,对于输入的一串源程序语句,构造其对应的语法树或者报告错误。要求:

   1) 基础文法以<Block>为开始符号:

        <Block> → { <Decls> <Statements> }

        <Decls> → <Decls> <Decl> | empty

        <Decl> → <Type> <NameList> ;

        <NameList> → <NameList> , <Name> | <Name>

        <Type> → int

        <Name> → id

        <Statements>的定义参照附录A中的文法定义;

   2)语法分析方法采用递归子程序法。

   3)输入:一串(3∽5句)执行语句,其中包括:赋值语句、选择语句和循环语句。

   4)输出:输入正确时,输出其对应的语法树,树根标记为< Block>;输入错误时,输出error。

   5)赋值语句:左部为1个简单变量(假设都定义为整型),右部为1个算术表达式;可以调用之前的程序来完成对这个算术表达式的分析。

   6)选择语句:包含if-then单分支和if-then-else双分支两种结构,可以只考虑分支判定条件为1个简单的关系运算表达式的情况,暂不处理逻辑运算。

   7)循环语句:包含while-do、do-while 和 for-each 三种结构中的任一种。

【任务介绍】递归下降的语法分析。

【输入】一个完整的源程序。

【输出】语法树或者错误。

【题目】设计一个程序,输入字符串形式的源程序,输出该程序的语法分析树,有错误时报告错误。要求:

     1)源语言及其语法规则:可以参照附录A,也可以自定义。

     2)输入为字符串形式的源程序,因此,需要调用前面实验做过的词法分析器,为语法分析器提供单词符号。

     3)应该指出错误的具体位置,如:在xx单词之后/之前发现错误,分析中止。

实验思路:本实验的目标是实现一个语法分析器。通过递归下降解析。按照layer类的数据结构,按照文法规则递归解析

 layer类:树结构类,包含构造函数, addNext-添加子节点,removeLayer-移除子节点,toString-树形输出

package Grammatical;

import java.util.ArrayList;
import java.util.List;

// 语法树类
public class layer {
    final String content; // 节点内容
    private final String content_length; // 控制节点内容缩进的空格字符串
    final List<layer> next; // 子节点列表动态索引来访问和操作列表中的元素

    // 构造函数
    public layer(String content,int depth) {
        this.content = content;
        //深度
        this.content_length = " ".repeat(depth*2); // 根据内容长度生成相应数量的空格字符串
        this.next = new ArrayList<>(); // 初始化子节点列表
    }

    // 添加子节点
    public void addNext(layer layer) {
        this.next.add(layer);
    }

    // 移除子节点
    public void removeLayer(layer layer) {
        this.next.remove(layer);
    }

    // 内部方法,用于生成树结构字符串
    private void buildString(StringBuilder builder, String prefix, String childrenPrefix) {
        builder.append(prefix);
        builder.append(content);
        builder.append('\n');
        for (int i = 0; i < next.size(); i++) {
            layer nextLayer = next.get(i);
            if (i < next.size() - 1) {
                nextLayer.buildString(builder, childrenPrefix + "├── ", childrenPrefix + "│   ");
            } else {
                nextLayer.buildString(builder, childrenPrefix + "└── ", childrenPrefix + "    ");
            }
        }
    }

    // 输出
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        buildString(builder, "", "");
        return builder.toString();
    }
}

 ArithmeticParser类:解析文法。包含各种工具类(isDigit-检查字符是否为数字, isVariable-检查字符是否为变量,match-匹配字符,skipBeforeEqual,skipAfterEqual-更新 line 为等号前、后的内容) 。对算术表达式的递归下降分析。

package Grammatical;

import java.util.Scanner;

public class ArithmeticParser {
    static String line; // 存储用户输入的表达式
    static int index; // 用于追踪当前解析的位置
    static int depth; // 树的深度

    // 工具类
    // ********************************************************************************************************
    // 检查字符是否为数字
    public static boolean isDigit(char c) {
        return Character.isDigit(c);
    }

    // 检查字符是否为变量
    public static boolean isVariable(char c) {
        return Character.isLetter(c);
    }

    // 匹配字符
    public static boolean match(char expected) {
        if (index < line.length() && line.charAt(index) == expected) {
            index++; // 移动解析位置到下一个字符
            return true; // 返回匹配成功
        }
        return false; // 返回匹配失败
    }

    // 跳过op之前的内容并将索引移动到op处,然后更新 line 为op后的内容
    public static String skipBeforeEqual(String op, String line) {
        if (line != null && line.contains(op)) {
            index = line.indexOf(op) + 1; // 将索引移动到op后的位置
            line = line.substring(index).trim(); // 更新 line 为op后的内容
            index = 0; // 重置索引为 0,因为 line 已经被更新为op后的内容
        }
        return line;
    }

    // 跳过op之前的内容并将索引移动到op处,然后更新 line 为op前的内容
    public static String skipAfterEqual(String op, String line) {
        if (line != null && line.contains(op)) {
            index = line.indexOf(op) + 1; // 将索引移动到op后的位置
            line = line.substring(0, index-1).trim(); // 更新 line 为op前的内容
            index = 0; // 重置索引为 0,因为 line 已经被更新为op前的内容
        }
        return line;
    }
    // *************************************************************************************************************

    // <Expr> -> <Term> <Expr1>
    public static boolean expr(layer parent, int depth) {
        layer exprNode = new layer("<Expr>", depth + 1); // 创建<Expr>节点
        parent.addNext(exprNode); // 将<Expr>节点添加到父节点
        line = skipAfterEqual(";",line);
        line = skipBeforeEqual("=", line);
        if (term(exprNode, depth + 2) && expr1(exprNode, depth + 2)) { // 解析<Expr>的子节点<Term>和<Expr1>
            return true; // 返回解析成功
        } else {
            parent.removeLayer(exprNode); // 移除<Expr>节点
            return false; // 返回解析失败
        }
    }

    // <Expr1> -> <AddOp> <Term> <Expr1> | empty
    public static boolean expr1(layer parent, int depth) {
        layer expr1Node = new layer("<Expr1>", depth + 1); // 创建<Expr1>节点
        parent.addNext(expr1Node); // 将<Expr1>节点添加到父节点
        if (index >= line.length()) {
            return true; // 到达末尾,返回空
        }
        if (match('+') || match('-')) { // 匹配加号或减号
            expr1Node.addNext(new layer(String.valueOf(line.charAt(index - 1)), depth + 2)); // 添加操作符节点
            if (term(expr1Node, depth + 2) && expr1(expr1Node, depth + 2)) { // 解析<Expr1>的子节点<Term>和<Expr1>
                return true; // 返回解析成功
            } else {
                parent.removeLayer(expr1Node); // 移除<Expr1>节点
                return false; // 返回解析失败
            }
        } else {
            parent.removeLayer(expr1Node); // 移除<Expr1>节点
            return true; // 返回空
        }
    }

    // <Term> -> <Factor> <Term1>
    public static boolean term(layer parent, int depth) {
        layer termNode = new layer("<Term>", depth + 1); // 创建<Term>节点
        parent.addNext(termNode); // 将<Term>节点添加到父节点
        if (factor(termNode, depth + 2) && term1(termNode, depth + 2)) { // 解析<Term>的子节点<Factor>和<Term1>
            return true; // 返回解析成功
        } else {
            parent.removeLayer(termNode); // 移除<Term>节点
            return false; // 返回解析失败
        }
    }

    // <Term1> -> <MulOp> <Factor> <Term1> | empty
    public static boolean term1(layer parent, int depth) {
        layer term1Node = new layer("<Term1>", depth + 1); // 创建<Term1>节点
        parent.addNext(term1Node); // 将<Term1>节点添加到父节点
        if (index >= line.length()) {
            return true; // 到达末尾,返回空
        }
        if (match('*') || match('/')) { // 匹配乘号或除号
            term1Node.addNext(new layer(String.valueOf(line.charAt(index - 1)), depth + 2)); // 添加操作符节点
            if (factor(term1Node, depth + 2) && term1(term1Node, depth + 2)) { // 解析<Term1>的子节点<Factor>和<Term1>
                return true; // 返回解析成功
            } else {
                parent.removeLayer(term1Node); // 移除<Term1>节点
                return false; // 返回解析失败
            }
        } else {
            parent.removeLayer(term1Node); // 移除<Term1>节点
            return true; // 返回空
        }
    }

    // <Factor> -> id | number | '(' <Expr> ')'
    public static boolean factor(layer parent, int depth) {
        layer factorNode = new layer("<Factor>", depth + 1); // 创建<Factor>节点
        parent.addNext(factorNode); // 将<Factor>节点添加到父节点
        if (index < line.length()) {
            char currentChar = line.charAt(index);
            if (isVariable(currentChar) || isDigit(currentChar)) { // 匹配变量或数字
                factorNode.addNext(new layer(String.valueOf(currentChar), depth + 1)); // 添加变量或数字节点
                index++; // 移动解析位置到下一个字符
                return true; // 返回解析成功
            } else if (match('(')) { // 匹配左括号
                factorNode.addNext(new layer("(", depth + 2)); // 添加左括号节点
                if (expr(factorNode, depth + 2) && match(')')) { // 解析括号内的表达式
                    factorNode.addNext(new layer(")", depth + 2)); // 添加右括号节点
                    return true; // 返回解析成功
                }
            }
        }
        parent.removeLayer(factorNode); // 移除<Factor>节点
        return false; // 返回解析失败
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入算术表达式:");
        line = scanner.nextLine().trim();
        scanner.close();
        index = 0; // 初始化解析位置
        depth = 0;

        if (!line.isEmpty()) {
            layer root = new layer(line, depth); // 创建根节点
            if (expr(root, depth + 1) && index == line.length()) { // 如果整个表达式被成功解析
                System.out.println("输入正确,语法树如下:");
                System.out.println(root); // 输出构造的语法树
            } else {
                // 输入错误,输出错误信息
                System.out.println("输入错误,无法构造语法树。");
                if (index < line.length()) {
                    System.out.println("错误位置前的内容:" + line.substring(0, index));
                    System.out.println("错误位置:" + line.charAt(index));
                    System.out.println("错误原因:不符合语法规则。");
                } else {
                    System.out.println("错误原因:输入不完整或存在未匹配的语法结构。");
                }
            }
        } else {
            System.out.println("语法错误:无内容。");
        }
    }
}

 SyntaxTreeGenerator:解析文法。生成树。

package Grammatical;

import Lexer.Lexer;
import java.io.*;
import java.util.*;

import static Grammatical.ArithmeticParser.*;
import static Lexer.Identifier.isKeyword_short;

public class SyntaxTreeGenerator {
    private static final List<String> lines = new ArrayList<>(); // 创建程序对象
    private static String line; // 存储当前的表达式
    private static List<String> words;//存储当前的表达式分割单词
    private static int index; // 用于追踪当前解析的位置

    // 提取括号中的条件
    private static String extractCondition(char op1,char op2) {
        int start = line.indexOf(op1);
        int end = line.indexOf(op2, start);
        if (start != -1 && end != -1) {
            return line.substring(start + 1, end);
        }
        return "";
    }

    //<Block> → { <Decls> <STMTS> }
    private static boolean block(layer parent,int depth){
        if (!isKeyword_short(words.get(index))) {
            return decls(parent, depth+2); // 返回解析成功
        }else {
            return stmts(parent, depth+2);
        }
    }

    //<Decls> →  <Decls> <Decl> | empty // <Decl> → <Type> <NameList> ; -->  <Decls> → <Type> <NameList> ; <Decls> | empty
    private static boolean decls(layer parent, int depth) {
        layer declsNode = new layer("<Decls>", depth+1);
        parent.addNext(declsNode); // 将节点添加到父节点
        // 尝试解析声明
        if (type(declsNode, depth + 2) && namelist(declsNode, depth + 2) && words.get(index).equals(" ;")) {
            // 如果成功解析了声明,尝试解析下一个声明
            if (decls(declsNode, depth + 2)) {
                return true; // 返回解析成功
            } else {
                parent.removeLayer(declsNode); // 移除节点
                return false; // 返回解析失败
            }
        } else {
            return true;
        }
    }

    //<Type> → int
    private static boolean type(layer parent,int depth) {
        layer typeNode = new layer("<Type>",depth+1);
        parent.addNext(typeNode); // 将节点添加到父节点
        if (!isKeyword_short(words.get(index))) {
            layer intnode = new layer(words.get(index),depth+2);
            typeNode.addNext(intnode);
            index++;
            return true; // 返回解析成功
        } else {
            parent.removeLayer(typeNode); // 移除节点
            return false; // 返回解析失败
        }
    }

    // <NameList> → <Name> <NameList1>
    private static boolean namelist(layer parent, int depth) {
        layer nameListNode = new layer("<NameList>", depth + 1);
        parent.addNext(nameListNode); // 将节点添加到父节点

        if (!name(nameListNode, depth + 1)) {
            return false; // 如果无法解析第一个变量名,返回解析失败
        }
        // 尝试解析<NameList1>
        return namelist1(nameListNode, depth + 1); // 返回解析成功
    }

    // <NameList1> → , <Name> <NameList1> | empty
    private static boolean namelist1(layer parent, int depth) {
        // 终止条件:索引超出范围或没有逗号,不再继续解析
        if (index >= words.size() || !words.get(index).equals(",")) {
            return true; // 返回解析成功
        }
        layer nameListNode1 = new layer("<NameList1>", depth + 1);
        parent.addNext(nameListNode1); // 将节点添加到父节点
        index++;
        // 如果成功匹配逗号,则继续解析下一个变量名
        if (!name(nameListNode1, depth + 2)) {
            return false; // 如果无法解析变量名,返回解析失败
        }
        // 继续解析下一个<NameList1>
        return namelist1(nameListNode1, depth + 2);
    }

    // <Name> → id
    private static boolean name(layer parent, int depth) {
        if (index >= words.size() || words.get(index).equals(",") || words.get(index).equals("=")) {
            return false; // 如果索引超出范围或者当前字符是逗号,返回解析失败
        }
        layer nameNode = new layer("<Name>", depth+1);
        parent.addNext(nameNode); // 将节点添加到父节点
        layer idNode = new layer(words.get(index), depth +2);
        nameNode.addNext(idNode); // 添加变量名节点
        index++; // 移动索引到下一个位置
        return true; // 返回解析成功
    }

    private static boolean stmts(layer parent, int depth) {
        // 终止条件:当前索引已经超出了
        if (index >= words.size()) {
            return true; // 返回解析成功
        }

        // 如果 parent 节点没有子节点或最后一个子节点不是 <STMTS>,创建新的 <STMTS> 节点
        if (parent.next.isEmpty() || !parent.next.get(parent.next.size() - 1).content.equals("<STMTS>")) {
            layer stmtsNode = new layer("<STMTS>", depth + 1);
            parent.addNext(stmtsNode); // 将节点添加到父节点
        }

        // 获取 parent 的最后一个子节点(应该是 <STMTS> 节点)
        layer stmtsNode = parent.next.get(parent.next.size() - 1);

        // 解析当前行的语句
        if (stmt(stmtsNode, depth + 2)) {
            // 如果成功解析了当前行的语句,尝试解析下一行的语句
            return stmts(stmtsNode, depth + 2);
        } else {
            parent.removeLayer(stmtsNode);
            return false; // 如果无法解析当前行的语句,返回解析失败
        }
    }


    // <STMT> → other ;
    private static boolean stmt(layer parent, int depth) {
        // 检查当前行是否为空或已解析完毕
        if (index >= words.size() || words.get(index).equals(";")) {
            return false; // 返回解析失败
        }
        // 创建 <STMT> 节点
        layer stmtNode = new layer("<STMT>", depth + 1);
        parent.addNext(stmtNode);
        // 解析 if 语句
        if (words.get(index).startsWith("if") || words.get(index).startsWith("else")) {
            return ifStatement(stmtNode, depth + 2); // 解析 if 语句
        }
        // 解析 while 语句
        else if (words.get(index).startsWith("while")) {
            return whileStatement(stmtNode, depth + 2); // 解析 while 语句
        }
        // 解析赋值语句
        else if (line.contains("=")) {
            return assignment(stmtNode, depth + 2); // 解析赋值语句
        }
        // 如果不匹配任何语句形式,返回解析失败
        else {
            parent.removeLayer(stmtNode);
            return false;
        }
    }

    // <STMT> → <Name> = <Expr> ;
    private static boolean assignment(layer parent, int depth) {
        // 解析 <Name>
        if (!name(parent, depth+1)) {
            // 如果无法解析 <Name>,直接返回 false
            return false;
        }
        // 确认赋值符号为 =
        String equalSign = words.get(index);
        if (!equalSign.equals("=")) {
            // 如果不是等号,返回 false
            return false;
        }
        // 创建赋值符号节点
        layer equalNode = new layer("=", depth+1);
        parent.addNext(equalNode);

        ArithmeticParser.line = line;
        ArithmeticParser.index = index;
        // 解析 <Expr>
        if (!expr(parent, depth+1)) {
            // 如果无法解析 <Expr>,返回 false
            return false;
        }
        index = index + ArithmeticParser.index+2;
        // 解析成功,返回 true
        return true;
    }

    //<STMT> → if ( BOOL ) <STMT>
    //<STMT> → if ( BOOL ) <STMT> else <STMT>
    private static boolean ifStatement(layer parent, int depth) {
        layer ifNode = new layer("<IF>", depth + 1); // 创建 if 节点
        parent.addNext(ifNode); // 将 if 节点添加到父节点
        if (line.contains("if")) {
            String bool_line = extractCondition('(',')'); // 提取条件
            layer boolsNode = new layer("<Bool>", depth + 2); // 创建 bool 节点
            ifNode.addNext(boolsNode); // 将 bool 节点添加到 if 节点
            layer boolNode = new layer(bool_line, depth + 3); // 创建 bool 节点
            boolsNode.addNext(boolNode); // 将 bool 节点添加到 if 节点
            index = words.indexOf(")") + 1;
            if(words.get(index).equals("{")) {
                // 查找 if 语句的主体部分,即 if 语句的代码块
                int start = line.indexOf('{');
                if (start != -1) {
                    String ifBody = extractCondition('{','}'); // 获取代码块内容
                    // 创建 if 语句代码块节点
                    layer ifBodyNode = new layer("<IF_BODY>", depth + 2);
                    ifNode.addNext(ifBodyNode); // 将 if 语句代码块节点添加到 if 节点
                    String[] bodyLines = ifBody.split(";");
                    for (String bodyLine : bodyLines) {
                        index = 0; // 初始化
                        line = bodyLine;
                        words = Lexer.splitIntoWords(line);//分割字符串为单词
                        line = line.replace(" ", "");
                        stmt(ifBodyNode,depth+3);
                    }
                }
            }
            return true; // 解析成功
        } else if (line.contains("else")) {
            layer elseNode = new layer("else", depth + 1); // 创建 else 节点
            ifNode.addNext(elseNode); // 将 else 节点添加到父节点
            // 查找 else 语句的主体部分,即 else 语句的代码块
            int start = line.indexOf('{');
            if (start != -1) {
                String elseBody = extractCondition('{','}'); // 获取代码块内容
                // 创建 if 语句代码块节点
                layer elseBodyNode = new layer("<ELSE_BODY>", depth + 2);
                elseNode.addNext(elseBodyNode); // 将 else语句代码块节点添加到 if 节点
                // 添加 if 语句代码块内容到节点中
                String[] bodyLines = elseBody.split(";");
                for (String bodyLine : bodyLines) {
                    index = 0; // 初始化
                    line = bodyLine;
                    words = Lexer.splitIntoWords(line);//分割字符串为单词
                    line = line.replace(" ", "");
                    stmt(elseBodyNode,depth+3);
                }
            }
            index++; // 移动到下一个单词
            return true; // 解析成功
        } else {
            parent.removeLayer(ifNode);
            return false;
        }
    }

    //<STMT> → while ( BOOL ) <STMT>
    private static boolean whileStatement(layer parent, int depth) {
        layer whileNode = new layer("<WHILE>", depth + 1); // 创建 while 节点
        parent.addNext(whileNode); // 将 while 节点添加到父节点
        if ((line.contains("while"))) {
            String while_line = extractCondition('(',')'); // 提取条件
            layer boolsNode = new layer("<Bool>", depth + 2); // 创建 bool 节点
            whileNode.addNext(boolsNode); // 将 bool 节点添加到 if 节点
            layer boolNode = new layer(while_line, depth + 3); // 创建 bool 节点
            boolsNode.addNext(boolNode); // 将 bool 节点添加到 if 节点
            // 查找 while 语句的主体部分,即 while 语句的代码块
            int start = line.indexOf('{');
            if (start != -1) {
                // 创建 while 语句代码块节点
                String whileBody = extractCondition('{','}'); // 获取代码块内容
                layer whileBodyNode = new layer("<WHILE_BODY>", depth + 2);
                whileNode.addNext(whileBodyNode);  // 将 while 语句代码块节点添加到 while 节点
                // 添加 while 语句代码块内容到节点中
                String[] bodyLines = whileBody.split(";");
                for (String bodyLine : bodyLines) {
                    index = 0; // 初始化
                    line = bodyLine;
                    words = Lexer.splitIntoWords(line);//分割字符串为单词
                    stmt(whileBodyNode,depth+3);
                }
            }
            return true;
        } else {
            parent.removeLayer(whileNode);
            return false;
        }
    }

    public static void main(String[] args) throws Exception {
        BufferedReader reader = new BufferedReader(new FileReader("Output_Lexer.txt"));
        BufferedWriter writer = new BufferedWriter(new FileWriter("Output_SystanTree.txt"));
        //树的深度
        int depth = 0;
        // 逐行读取文件内容
        while ((line = reader.readLine()) != null){
            lines.add(line);
        }
        if(!lines.isEmpty()) {
            layer root = new layer("<Block>", depth); // 创建根节点
            if (!lines.get(0).startsWith("{") && !lines.get(lines.size() - 1).endsWith("}")) {
                System.out.println("Block_语法错误:无{}");
            }
            reader.close();
            for (int i = 1; i < lines.size() - 1; i++) {
                index = 0; // 初始化
                line = null;
                words = null;
                line = lines.get(i);
                words = Lexer.splitIntoWords(line);//分割字符串为单词
                if (!block(root, depth)) {
                    // 输入错误,输出错误信息
                    System.out.println("输入错误,无法构造语法树。");
                    int errorIndex = index; // 记录出错位置
                    // 输出出错位置前的内容
                    if (errorIndex > 0) {
                        System.out.println("错误位置前的内容:" + line.substring(0, errorIndex));
                    }
                    // 输出出错位置及错误信息
                    System.out.println("错误位置:line ="+ i +" "+line.charAt(errorIndex));
                    System.out.println("错误原因:不符合语法规则。");
                }
            }
            writer.write(root.toString()); // 将构造的语法树写入文件
            writer.close(); // 关闭写入流
        }else {
            System.out.println("语法错误:无内容");
        }
    }
}

 结构:                                                     结果截图:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值