编译原理LL1文法分析
LL(1)文法是一个自顶向下语法分析算法
基本流程
1.读取文法
这里的终结符、非终结符是可以用字符串来表示,而不是单个字符,同时也要求了我们不同了的符号需要用一个空格来隔开,在输入文法时也要求间隔开来。例如:A -> A a | b
对象声明:
/**
* 终结符
*/
private Vector<String> T = new Vector<>();
/**
* 非终结符
*/
private Vector<String> NT = new Vector<>();
/**
* 产生式
*/
private Map<String, Vector<String>> production = new HashMap<>();
文法读取txt文件,具体txt文件以行读取,对每一行的"->“分割,”->“左边就是非终结符,若NT中不存则加入NT中,右边部分再以”|"分割,分割成多个产生式,将所有所有产生式加入到对应的NT的产生式production中;在所有文法读取完毕之后,对已经建立起来的产生式集进行扫描,若不在NT中字符串则将其加入到N中作为终结符。
/**
* 读取文法
* 终结符非终结符
*
* @param fileName 路径
*/
public void readGrammar(String fileName) {
File file = new File(fileName);
try {
BufferedReader reader = new BufferedReader(new FileReader(file));
String line = null;
while ((line = reader.readLine()) != null) {
System.out.println("line: " + line);
//添加文法
String[] lr = line.split("->");
String[] rs = lr[1].split("\\|");
Vector<String> rights = new Vector<>();
for (String right : rs) {
rights.add(right.trim());
}
production.put(lr[0].trim(), rights);
addNt(lr[0].trim());
}
System.out.println("文法: " + production);
System.out.println("非终结符:" + NT);
addN();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 添加非终结符
*/
private void addNt(String left) {
if (!NT.contains(left)) {
NT.add(left);
}
}
/**
* 添加终结符
*/
private void addN() {
for (String left : production.keySet()) {
for (String right : production.get(left)) {
String[] strs = right.split(" ");
for (String str : strs) {
if (NT.contains(str) || T.contains(str)) {
continue;
} else {
T.add(str);
}
}
}
}
System.out.println("终结符:" + T);
}
2.消除左递归
消除左递归有立即消除和非立即消除,立即消除就是非终结符自己的产生式之间来进行替换,非立即消除则就是利用前(i-1)个非终极符的产生式来替换第 i 个中的产生式,然后再对于第 i 个产生式自己进行立即消除。因此下面实现了两个函数来作为这两种消除方式,同时包括以及辅助函数。
函数名 | 参数 | 描述 |
---|---|---|
eliminateImmediateLeftRecursion(String Ai) | Ai:非终结符 | 消除直接左递归 |
eliminateLeftRecursion() | 消除左递归 | |
isFirst(String right, String Ai) | right:产生式右部;Ai:非终结符 | 判读右部的第一个字符串是否为Ai非终结符 |
replaceLast(String right, String Ai, String newAi) | right:右部;Ai非终结符;newAi:新非终结符 | 非终结符替换,删除原来的非终结符,将新的非终结符加入末尾 |
replace(String right, String Ai, String newAi) | right:右部;Ai非终结符;newAi:新非终结符 | 非终结符替换,将非终结符替换为新终结符 |
辅助函数
/**
* 判断右部第一个字符是否为Ai
*
* @param right 右部
* @param Ai 字符
* @return True包含 False不包含
*/
private boolean isFirst(String right, String Ai) {
String[] strs = right.trim().split(" ");
return Ai.trim().equals(strs[0].trim());
}
/**
* 字符替换
*
* @param right 右部
* @param Ai 旧字符
* @param newAi 新字符
* @return 新字符串
*/
private String replaceLast(String right, String Ai, String newAi) {
String newRight = "";
String[] strs = right.trim().split(" ");
for (String str : strs) {
if (str.equals(Ai) || " ".equals(str)) {
continue;
}
newRight += str + " ";
}
newRight += newAi;
return newRight.trim();
}
/**
* 字符替换
*
* @param right 右部
* @param Ai 旧字符
* @param newAi 新字符
* @return 新字符串
*/
private String replace(String right, String Ai, String newAi) {
String newRight = "";
String[] strs = right.trim().split(" ");
for (String str : strs) {
if (str.equals(Ai)) {
newRight += newAi + " ";
continue;
} else if (" ".equals(str)) {
continue;
}
newRight += str + " ";
}
return newRight.trim();
}
消除立即左递归,传入一个非终结符Ai,要知道能调用这个函数则证明会产生一个新的非终结符来存储一个新的产生式,然后扫描 Ai的所有产生式,若产生式中第一个字符串就是Ai的话,那么需要产生式中原来的Ai删除,将新的非终结符加入到末尾,这里用到的也就是replaceLast函数;若不包含就在原来的产生式后面加上新的非终结符来调用;最后需要在新的非终结符中加入$的产生来结束递归,更新到产生式集合中即可。
非立即消除左递归,在第i个非终结符时,扫描所有产生式,若产生式第一个字符串就是前i-1个的非终结符中的一个,就需要将这个终结符替换成这个非终结符的所有产生式,若不是则保证原来的产生式即可,最后判断自己的产生式中第一个字符串是否为自身,若是则执行消除立即左递归函数,注意执行完之后需要跳过一个i ,将i增大一个数,因为消除立即左递归加入了一个新的非终结符到NT中,而这个非终结符就这个下一个i中,新的非终结符也不存在左递归的情况,所以需要直接跳过。
/**
* 消除立即左递归
*
* @param Ai 字符
*/
private void eliminateImmediateLeftRecursion(String Ai) {
String newAi = Ai + "'";
NT.insertElementAt(newAi, NT.indexOf(Ai));
Vector<String> rights1 = new Vector<>();
Vector<String> rights2 = new Vector<>();
for (String right : production.get(Ai)) {
if (isFirst(right, Ai)) {
String newRight = replaceLast(right, Ai, newAi);
rights2.add(newRight);
} else {
String newRight = right.trim() + " " + newAi;
rights1.add(newRight);
}
}
rights2.add("$");
production.replace(Ai, rights1);
production.put(newAi, rights2);
}
/**
* 消除左递归
* 产生式 无环 和 $
*/
public void eliminateLeftRecursion() {
for (int i = 0; i < NT.size(); i++) {
String Ai = NT.get(i);
//新的右部
Vector<String> newIrights = new Vector<>();
for (String iRight : production.get(Ai)) {
//iRight是否包含aj
boolean flag = false;
for (int j = 0; j < i; j++) {
String Aj = NT.get(j);
if (isFirst(iRight, Aj)) {
flag = true;
for (String jRight : production.get(Aj)) {
String newIright = replace(iRight, Aj, jRight);
newIrights.add(newIright);
}
}
}
//未替换 加入原来的右部
if (!flag) {
newIrights.add(iRight);
}
}
//i==0时,newIrights未维护
if (i != 0) {
production.replace(Ai, newIrights);
}
//消除直接左递归
for (String iRight : production.get(Ai)) {
if (isFirst(iRight, Ai)) {
eliminateImmediateLeftRecursion(Ai);
i++;
break;
}
}
}
System.out.println("新文法:" + production);
}
执行示例:
我们输入:
A -> a B | B b
B -> A c | d
执行结果如下:
3.提取左公共因子
提取左公因子我们用一个新的对象 产生式树,也就是将我们现在的产生式建立一颗树,因为这样可用很好的找到我们公共最大前缀,这颗树其实就相当于我们熟悉的字典树, 找到公共最大前缀之后对其进行提取,提取之后同时也会产生新的非终结符加入到NT中即可。
首先我们来建立这颗 产生式树,加入我们的新对象,对于每一个非终结符建立起一颗树,树根就是 我们的非终结符,productionTree利用HashMap存储可快速定义到我们需要的树。
/**
* 产生式树
*/
private Map<String, TreeNode> productionTree = new HashMap<>();
定义我们树的节点,树节点包含节点、很多孩子节点,同时每个树节点存在一个标识isLast,表示此节点是否为一个产生式的末尾,这对于后面的递归就很大的作用,因为有了这样的标识我们递归到此节点时就可以判断是否进行提取。
/**
* @author zzj
* @date 2021-12-6 03:42:55
*/
public class TreeNode {
/**
* 孩子节点
*/
public Vector<TreeNode>childNodes =new Vector<>();
/**
* 是否为结束标志
*/
public boolean isLast = false;
/**
* 节点的值
*/
public String nodeVal;
@Override
public String toString() {
return "TreeNode{" +
"childNodes=" + childNodes +
", isLast=" + isLast +
", nodeVal='" + nodeVal + '\'' +
'}';
}
}
首先我们利用递归来建立这些以非终结符为根的树。用到两个函数,buildProductionTree()建立产生式树,对于每一个非终结符的所有产生式进行addProductionToTree()函数。
buildProductionTree()对于每一个非终结符建立起一个数根,然后这个非终结符所有的产生式,将产生以空格分割成字符串数组,从0层开始,进行addProductionToTree递归。
addProductionToTree(),参数为根节点,字符串数组,树的层数。字符串数组的大小也就表明了该产生式能走到树的层数,这也就是递归出口,出口对于产生式的字符串数组也就是走到的最后一层的节点,这里将isLast表识为true,证明此节点为一个产生式末尾。对于产生式就是一个$的情况,我们不需要对其建立节点,直接返回即可。结点建立的过程中,一些值的节点已经建立我们直接跳过进行下一步节点,若该节点不存在则建立节点加入到根节点之后再进行下一步递归。
/**
* 建立产生式树
*/
public void buildProductionTree() {
for (String left : production.keySet()) {
TreeNode root = new TreeNode();
for (String right : production.get(left)) {
String[] strs = right.split(" ");
addProductionToTree(root, strs, 0);
}
productionTree.put(left, root);
}
System.out.println("产生式树:" + productionTree);
}
/**
* 添加产生式到树结点
*
* @param root 树结点
* @param right 产生式
* @param i 产生式位置
*/
private void addProductionToTree(TreeNode root, String[] right, int i) {
if (right[0].equals("$")) {
return;
}
if (i == right.length) {
root.isLast = true;
return;
}
for (TreeNode childNode : root.childNodes) {
if (childNode.nodeVal.equals(right[i])) {
addProductionToTree(childNode, right, i + 1);
return;
}
}
TreeNode newChildNode = new TreeNode();
newChildNode.nodeVal = right[i];
root.childNodes.add(newChildNode);
addProductionToTree(newChildNode, right, i + 1);
}
输入示例建立树:
A -> a a a a | a a b c | a a b d | a a
非终结符A的产生式如下图:
图示中标红的节点则表示为该节点为一个产生式的结尾
有了上面这个图我们就能很清楚的两个产生式之间的最大公共前缀。下面我们根据这个树来提取我们最大公共前缀。
辅助函数isPrefix()来判断产生式中前缀是否为prefix
/**
* 判断是否包含前缀
*
* @param right
* @param prefix
* @return
*/
private boolean isPrefix(String right, String prefix) {
String[] str1 = right.trim().split(" ");
String[] str2 = prefix.trim().split(" ");
if(str2.length>str1.length)
{
return false;
}
for (int i = 0; i < str2.length; i++) {
if (!str1[i].equals(str2[i])) {
return false;
}
}
return true;
}
提取左公共因子,下面对树节点采用后序遍历,也就是自底向上,自底向上能够很好的确定我们的最大公共因子,深入底部在此以上的父节点就是我们的公共因子。
leftFactoring后序遍历入口,对每个非终结符进行getLeftFactorProduction()。
getLeftFactorProduction()函数需要维护我们的前缀prefix,每到一个节点将其值加入到prefix中,返回值int也很重要,这决定我们是否提取。每到一个结点,递归其子节点,对其返回值用total相加,若total值大于2,则证明子节点下面有两个文法经过于此,调用updateProduction()函数进行在产生式集中提取,为何这个节点能成为最大的公共前缀,因为每提取一次公共因子我们的total值都会变为1,想上传的最大值就为1,自底向上所以就能保证在下面的时候就进行提取公共因子。
updateProduction()获取到最大公共前缀,我们就能在产生中进行提取合并,产生新的非终结符。因为这一步产生的新的非终结符可能很多,我们保证新产生的非终结符在NT中不存在,利用循环决定加入“ ‘ ” 的个数。得到的非终结符加入道NT中,然后遍历原来的非终结符的所有产生式,匹配我们的最大前缀成功匹配的就提取替换,不匹配保证原来的产生式即可。
/**
* 提取左公因子 获得新文法
*/
public void leftFactoring() {
for (int i = 0; i < NT.size(); i++) {
String left = NT.elementAt(i);
if (productionTree.containsKey(left)) {
getLeftFactorProduction(left, productionTree.get(left), "");
}
}
System.out.println("新文法:" + production);
}
/**
* 获取产生式最大公共前缀
*
* @param left 非终结符
* @param root 树节点
* @param prefix 前缀
* @return 占有公共前缀结点数量
*/
private int getLeftFactorProduction(String left, TreeNode root, String prefix) {
int total = 0;
for (TreeNode childNode : root.childNodes) {
total += getLeftFactorProduction(left, childNode, prefix.trim() + " " + childNode.nodeVal);
}
if (root.isLast) {
total++;
}
if (total >= 2) {
total = 1;
if (!prefix.equals("")) {
updateProduction(left, prefix);
}
}
return total;
}
/**
* 更新产生式
*
* @param left 非终结符
* @param prefix 前缀
*/
private void updateProduction(String left, String prefix) {
String newLeft = left + "'";
while (NT.contains(newLeft)) {
newLeft += "'";
}
NT.insertElementAt(newLeft, NT.indexOf(left) + 1);
Vector<String> rights1 = new Vector<>();
Vector<String> rights2 = new Vector<>();
for (String right : production.get(left)) {
//匹配到前缀
if (isPrefix(right, prefix)) {
String newRight = prefix.trim() + " " + newLeft;
if (!rights1.contains(newRight)) {
rights1.add(newRight.trim());
}
String tmp = right.replaceFirst(prefix.trim(), "").trim();
rights2.add("".equals(tmp) ?"$":tmp);
} else {
rights1.add(right.trim());
}
}
production.replace(left, rights1);
production.put(newLeft, rights2);
}
输入示例执行结果:
A -> a a a a | a a b c | a a b d | a a
第二个新文法即为我们执行提取左公因子的结果
4.求FIRST集
我们创建新的对象来存储我们FIRST集合
/**
* first集
*/
private Map<String, HashSet<String>> FIRST = new HashMap<>();
参照龙书上的描述来实现我们求FIRST程序。首先将所有非终结符以First(x)=x的形式存入FIRST对象中;依照描述我们的 字 符 也 将 加 入 到 F I R S T 集 合 中 。 然 后 遍 历 所 有 的 非 终 结 符 , 对 非 终 结 符 的 所 有 文 法 进 行 扫 描 , 获 取 文 法 的 第 一 个 字 符 , 若 该 字 符 在 F I R S T 集 合 中 , 则 该 字 符 的 F I R S T 集 合 将 加 入 到 第 一 个 字 符 的 F I R S T 集 合 中 ; 对 于 文 法 中 第 一 个 字 符 为 字符也将加入到FIRST集合中。然后遍历所有的非终结符,对非终结符的所有文法进行扫描,获取文法的第一个字符,若该字符在FIRST集合中,则该字符的FIRST集合将加入到第一个字符的FIRST集合中;对于文法中第一个字符为 字符也将加入到FIRST集合中。然后遍历所有的非终结符,对非终结符的所有文法进行扫描,获取文法的第一个字符,若该字符在FIRST集合中,则该字符的FIRST集合将加入到第一个字符的FIRST集合中;对于文法中第一个字符为的情况,因为 的 F I R S T 集 合 为 的FIRST集合为 的FIRST集合为,则对于这个非终结符的FIRST集合中加入$。我们将上述描述多执行几遍来达到最终效果,因为第一次执行时,需要的非终结符FIRST集合还不存在,需要执行生成了再第二遍时使用。
辅助函数:
/**
* 获取右部的第一个字符
*
* @param right
* @return
*/
public String getFirstStr(String right) {
String[] strs = right.trim().split(" ");
return strs[0];
}
/**
* 获取FIRST集合
*/
public void getFirst() {
FIRST.clear();
//将所有终结符加入到FIRST集合
for (String t : T) {
HashSet<String> firsts = new HashSet<String>();
firsts.add(t);
FIRST.put(t, firsts);
}
HashSet<String> tmp = new HashSet<String>();
tmp.add("$");
FIRST.put("$", tmp);
//非终结符
int k = 0;
while (k < 5) {
for (String Ai:NT) {
//所有文法
for (String right : production.get(Ai)) {
//是否需要加入$
boolean flag = true;
//第一个token
String first = getFirstStr(right);
//终结符 非终结符就是自己 $ //&& !FIRST.get(first).isEmpty()
if (FIRST.containsKey(first) ) {
for (String firstNext : FIRST.get(first)) {
if (firstNext.equals("$")) {
continue;
} else {
flag = false;
if (!FIRST.containsKey(Ai)) {
HashSet<String> firsts = new HashSet<>();
firsts.add(firstNext);
FIRST.put(Ai, firsts);
} else {
FIRST.get(Ai).add(firstNext);
}
}
}
if (flag) {
if (!FIRST.containsKey(Ai)) {
HashSet<String> firsts = new HashSet<>();
firsts.add("$");
FIRST.put(Ai, firsts);
} else {
FIRST.get(Ai).add("$");
}
}
}
}
}
k++;
}
System.out.println("First集:" + FIRST);
}
5.求FOLLOW集
创建我们的FOLLOW集合对象
/**
* follow
*/
private Map<String, HashSet<String>> FOLLOW = new HashMap<>();
同样根据龙书描述,但我们的程序中字符KaTeX parse error: Expected 'EOF', got '#' at position 12: 表示为空,这里我们使用#̲表示结束标记。首先在FOLLO…,包含则执行我们3)规则。上述过程也执行多次来达到最终状态,与求FIRST集合同理。
辅助函数:
/**
* 获取当前字符的下一个字符
* @param right 右部
* @param B 当前字符
* @return 返回字符串
*/
public String getNextStr(String right,String B)
{
String strs[] = right.trim().split(" ");
for (int i =0;i<strs.length;i++)
{
if(strs[i].equals(B)&&i+1<strs.length)
{
return strs[i+1];
}
}
return " ";
}
/**
* 判断右部是否包含 B
* @param right 右部
* @param B 字符
* @return true包含
*/
public boolean isContain(String right,String B)
{
String strs[] = right.trim().split(" ");
for (String str:strs)
{
if(str.equals(B))
{
return true;
}
}
return false;
}
/**
* 添加follow
* @param left
* @param hs
*/
public void addFollow(String left,HashSet<String> hs)
{
if(FOLLOW.containsKey(left))
{
FOLLOW.get(left).addAll(hs);
}
else{
FOLLOW.put(left,hs);
}
}
/**
* 获取Follow集
*/
public void getFollow() {
FOLLOW.clear();
HashSet<String> follows = new HashSet<String>();
follows.add("#");
FOLLOW.put(NT.elementAt(0), follows);
//若A->αBβ是一个产生式,则把First(β)-{ε}加入到Follow(B)中
//若A->αB是一个产生式,或者A->αBβ是一个产生式且β->ε,则把Follow(A)加入到Follow(B)中
int k = 0;
while (k < 5) {
for (String A:NT)
{
for (String right:production.get(A))
{
if(right.equals("$"))
{
continue;
}
for (String B:NT)
{
if(A.equals(B))
{
continue;
}
if(isContain(right,B))
{
//B后面的字符
String b = getNextStr(right,B);
if(!" ".equals(b))
{
addFollow(B, (HashSet<String>) FIRST.get(b).clone());
FOLLOW.get(B).remove("$");
if(FIRST.get(b).contains("$")&&FOLLOW.containsKey(A))
{
addFollow(B, FOLLOW.get(A));
}
}
else
{
//B后无字符
if(FOLLOW.containsKey(A))
{
addFollow(B, FOLLOW.get(A));
}
}
}
}
}
}
k++;
}
System.out.println("Follow集:"+FOLLOW);
}
6.获取分析表
在实现了FIRST和FOLLOW集后我们的分析表就能很快实现,根据龙书上提出的方法完成我们的分析表,创建分析表对象
/**
* 分析表
*/
private Map<Pair<String, String>, String> Table = new HashMap<>();
遍历所有的非终结符的产生式,提取产生式的第一个第一字符,判断其FIRST集合中是否包含 , 若 包 含 则 在 分 析 表 中 加 入 该 非 终 结 符 的 所 有 F O L L O W 集 合 ; 反 之 不 包 含 ,若包含则在分析表中加入该非终结符的所有FOLLOW集合;反之不包含 ,若包含则在分析表中加入该非终结符的所有FOLLOW集合;反之不包含,则将第一个字符的FIRST集合加入值分析表中。
/**
* 获得分析表
*/
public void getTable()
{
for (String A:NT)
{
for (String right:production.get(A))
{
String first=getFirstStr(right);
right = A +"->"+ right;
if(FIRST.get(first).contains("$"))
{
for (String a:FOLLOW.get(A))
{
Pair<String,String> symbol =new Pair<>(A,a);
Table.put(symbol,right);
}
}
else
{
for (String a:FIRST.get(first))
{
Pair<String,String> symbol =new Pair<>(A,a);
Table.put(symbol,right);
}
}
}
}
System.out.println("分析表:"+Table);
}
7.建立语法树
首先输入字符串进行语法分析,根据龙书步骤,首先在输入字符后面加上结束标志#,同时在栈里面最先也加入结束标志#,两个结束表示用于匹配时结束程序。
循环栈,查看栈顶元素,维护输入字符串下标,若栈顶元素与当前字符串下标的字符相等,弹出栈顶,字符串下标右移;当栈顶为终结符或在分析表中查询不到有值的情况跳过;最后一种情况就在分析表中查找的有值,我们取出分析表的值,将栈顶推出,然后将分析表的值倒置后加入栈中,同时要去除掉产生式是$字符。我们的语法树结点嵌套在最后一个情况,当从分析表中取出产生式时,我们利用栈将产生式记录下来,在后面用来建立我们的语法树。
接下来建立语法书,根据我们存储的产生式的栈,循环栈直到空,每次取出栈顶的产生式,将其建立节点,符号->左边的为父节点,右边的全是其子节点,但子节点中只需要加入终结符的子节点,因为非终结符节点将以此创建;栈的好处是因为能模仿低估具有树结构的顺序,我们将每次建立其节点存储到新的节点栈中,每次加入这个节点栈时判断,当前要加入的节点的产生式中是否包含 已经在栈中的节点的非终结符,如果包含,取出节点栈栈顶节点,将其作为要加入节点的子节点,直到栈顶节点不包含为止 ,再将要加入的节点压入栈中。最后我们的节点栈中存储的一个节点就是我们的语法树的树根。
/**
* 语法分析
* @param input 输入字符串
*/
public void parser(String input)
{
//加入结束标志
input +=" #";
String[] inputs =input.trim().split(" ");
int k = 0;
Stack<String> analysis =new Stack<>();
//加入结束标志
analysis.add("#");
analysis.add(S);
while (!analysis.isEmpty())
{
//查看栈顶
String top = analysis.peek();
if(top.equals(inputs[k]))
{
//栈顶与输入匹配
String tmp = analysis.pop();
System.out.println("匹配:"+tmp);
k++;
}
else if(T.contains(top))
{
//终结符包含top
break;
}
else if(!Table.containsKey(new Pair<>(top,inputs[k])))
{
//分析表中不存在
break;
}
else
{
//分析表中取出产生式
String str = Table.get(new Pair<>(top,inputs[k]));
analysis.pop();
System.out.println("输出:"+str);
//加入语法栈
stack.add(str);
String []strs =str.split("->");
String []strss = strs[1].split(" ");
Collections.swap(Arrays.asList(strss),0,strss.length-1);
analysis.addAll(Arrays.asList(strss));
analysis.remove("$");
}
}
}
/**
* 根据产生式创建节点
* @param str 文法
* @return 节点
*/
public TreeNode createNode(String str)
{
String[] strs = str.trim().split("->");
TreeNode root =new TreeNode(strs[0]);
String[] strss = strs[1].trim().split(" ");
for (String tmp:strss)
{
if(T.contains(tmp)||tmp.equals("$"))
{
TreeNode tmpNode=new TreeNode(tmp);
root.childNodes.add(tmpNode);
}
}
return root;
}
/**
* 建立语法树
*/
public void buildTree()
{
//节点栈
Stack<TreeNode> stackNode =new Stack<>();
while (!stack.isEmpty())
{
//文法
String str = stack.pop();
TreeNode newNode =createNode(str);
//在栈中寻找子节点 栈顶是否子节点
while (!stackNode.isEmpty()&&isContain(str.trim().split("->")[1],stackNode.peek().nodeVal))
{
TreeNode childNode = stackNode.pop();
newNode.childNodes.add(childNode);
}
stackNode.add(newNode);
}
if(stackNode.size()!=1)
{
System.out.println("错误!!!!!!!!!!!!!!!");
}
root=stackNode.pop();
System.out.println(root);
}