java开发C语言解释器:根据执行树执行代码

具体的代码演示和讲解请参数视频:
用java开发编译器

前一节,我们建立起了代码执行树,本节,我们看看,如何根据执行树直接执行C语言代码,以下是上一节我们构建的执行树:
这里写图片描述

这里写图片描述

这些执行树,本质上是图论里面的有向图,我们执行代码的过程,实际上是根据有向图进行深度优先遍历的过程,我们先遍历到底层节点,根据底层节点的信息执行相应动作,然后把执行的结果传递给上一层节点,然后再根据传递上来的信息做相应的操作,以此类推,知道返回到根节点时,执行结束。

我们先看看与解释器相关的代码实现:

public interface Executor {
    public Object Execute(ICodeNode root);
}

Executor 是一个接口,执行树中每个节点都对应相应类型的Executor,每个具体类型的Executor都实现Execute接口,例如类型为NO_COMMA_EXPR类型的节点就会对应一个NoCommaExprExecutor类。在上面接口的基础上,我们先实现一个解释器的基类叫BaseExecutor:

package backend;

import java.util.Collections;

public abstract class BaseExecutor implements Executor{
    protected void executeChildren(ICodeNode root) {
        ExecutorFactory factory = ExecutorFactory.getExecutorFactory();
        Collections.reverse(root.getChildren());
        int i = 0;
        while (i < root.getChildren().size()) {
            ICodeNode child = root.getChildren().get(i);
            Executor executor = factory.getExecutor(child);
            executor.Execute(child);
            i++;
        }
    }


    protected void copyChild(ICodeNode root, ICodeNode child) {
        root.setAttribute(ICodeKey.SYMBOL, child.getAttribute(ICodeKey.SYMBOL));
        root.setAttribute(ICodeKey.VALUE, child.getAttribute(ICodeKey.VALUE));
        root.setAttribute(ICodeKey.TEXT, child.getAttribute(ICodeKey.TEXT));
    }
}

BaseExecutor 实现两个调用,一个是executeChildren, 这个接口的作用是,当我们遍历到执行树上的某个节点时,要根据该节点进行相应的代码执行操作,在此之前,如果该节点有孩子的话,我们需要先执行他的孩子,它收到所以孩子的执行结果后,再根据这些结果进行相应的操作。while循环里就是从孩子队列里取出每个孩子对象,然后从Executor的工厂类中获得对应的执行对象去进行相应的执行动作。

copyChild的目的是,把孩子节点的所有相关信息拷贝到父节点。

接下来我们看看Executor的工厂类:

package backend;

import frontend.CTokenType;

public class ExecutorFactory {
    private static ExecutorFactory executorFactory = null;
    private ExecutorFactory() {

    }

    public static ExecutorFactory getExecutorFactory() {
        if (executorFactory == null) {
            executorFactory = new ExecutorFactory();
        }

        return executorFactory;
    }

    public Executor getExecutor(ICodeNode node) {
        CTokenType type = (CTokenType)node.getAttribute(ICodeKey.TokenType);
        switch (type) {
        case UNARY:
            return new UnaryNodeExecutor();
        case BINARY:
            return new BinaryExecutor();
        case NO_COMMA_EXPR:
            return new NoCommaExprExecutor();
        case EXPR:
            return new ExprExecutor();
        case STATEMENT:
            return new StatementExecutor();
        case STMT_LIST:
            return new StatementListExecutor();
        }

        return null;
    }
}

通过getExecutor 可以看到,工程类会根据传入的节点类型生成对应的执行对象,当前节点类型有几种,我们就可以生成几种不同类型的节点执行器。我们先看看某个具体节点执行器的实现,例如BinaryExecutor:

package backend;

import java.util.Collections;

import frontend.CGrammarInitializer;
import frontend.Symbol;

public class BinaryExecutor extends BaseExecutor{
    @Override
    public Object Execute(ICodeNode root) {
        executeChildren(root);
        ICodeNode child;
        int production = (int)root.getAttribute(ICodeKey.PRODUCTION);
        switch (production) {
        case CGrammarInitializer.Uanry_TO_Binary:
            child = root.getChildren().get(0);
            copyChild(root, child);
            break;

        case CGrammarInitializer.Binary_Plus_Binary_TO_Binary:
            Collections.reverse(root.getChildren());
            //先假设是整形数相加
            int val1 = (Integer)root.getChildren().get(0).getAttribute(ICodeKey.VALUE);
            int val2 = (Integer)root.getChildren().get(1).getAttribute(ICodeKey.VALUE);
            root.setAttribute(ICodeKey.VALUE, val1 + val2);

            System.out.println("Assign sum of " + root.getChildren().get(0).getAttribute(ICodeKey.TEXT) + " and "
                    + root.getChildren().get(1).getAttribute(ICodeKey.TEXT) + " to variable " + root.getAttribute(ICodeKey.TEXT));
            break;
        }

        return root;
    }
}

在Execute函数中,先调用execueChildren对所以孩子节点进行执行操作,然后根据执行节点所对应的表达式进行相应的操作,例如,如果当前Binary节点对应的表达式是:
BINARY -> BINARY + BINARY
那么,我们知道,当前要执行的就是加法操作,于是执行器从当前节点的两个子节点中,获得要做加法操作的两个加数,把它们相加后的结果存入当前节点,这样,当前节点的父节点就可以拿到当前节点的执行结果,以便进行相应的执行操作。

其他执行器的逻辑类似,具体请参看视频演示,下面我们看看语句:
a = a + b;
是如何被执行的。

1: 从第二个图开始,执行树的根节点是STMT_LIST, 然后一直往下遍历,直到倒数第二个节点:NO_COMMA_EXPR.

2: NO_COMMA_EXPR 有两个子节点,于是先执行这两个子节点,因此先往下走,遍历到最下方的NO_COMMA_EXPR节点。

3:当前节点有一个孩子节点,BINARY, 于是向右走,遍历到子节点BINARY.

4: 当前BINARY有两个子节点,因此可以先往右走,一直遍历到最底层节点UNARY.

5: UNARY是最底层节点,因此可以直接执行,执行的过程是,根据UNARY节点的SYMBOL对象,获得变量a对应的数值,把该数值存储到节点里,然后返回到父节点BINARY。

6: 在父节点BINARY中,根据前面显示的代码,该节点要做的只是把子节点所有信息拷贝过来,当然也就把变量a的值以及对应的Symbol也拷贝过来,然后再返回父节点,也是BINARY节点。

7:当前BINARY节点还有一个子节点,对应的是变量b,因此继续遍历第二个子节点,执行过程跟步骤4,5,6一样,这样当前BINARY节点的两个子节点就分别含有变量a 和 b 对应的数值,根据前面代码,BINARY对应的执行器会把两个子节点的数值相加,然后存储到当前节点。

8:然后继续返回到父节点NO_COMMA_EXPR, 该节点执行是所做的操作就是把子节点的值拷贝过来,于是当前节点就包含了a + b的结果,然后继续返回到父节点NO_COMMA_EXPR.

9: 此时节点NO_COMMA_EXPR 正右边还有一个子节点,于是执行该子节点,该子节点执行后的作用是得到变量a对应的Symbol对象。

10:由于所有子节点都执行完毕,当前节点NO_COMMA_EXPR可以执行相应操作,它做的动作是将下方子节点NO_COMMA_EXPR的数值赋值到右边节点所得到的Symbol对象里,也就是把 a+b的值设置到变量a所对应的Symbol对象中,这样,语句a = a + b; 就执行结束了。

具体的代码演示和讲解请参数视频:
用java开发编译器

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值