About
RSS

Bit Focus


移花接木 - 编译同步代码为异步代码 [补述篇]

Posted at 2013-03-13 02:05:29 | Updated at 2024-03-29 09:19:34

上篇

编译步骤解释

多个正规异步调用出现在同一个语句中的编译顺序

    RegularAsyncCall 对象的 compile 代码如下
def compile(self, context):
    # 0
    compl_callee = self.callee.compile(context)

    # 1
    compl_args = [ arg.compile(context) for arg in self.arguments ]

    # 2
    callback_body_context = ContextFlow()
    cb_body_flow = callback_body_context.block
    compl_args.append(output.Lambda([ 'error', 'result' ], cb_body_flow))

    # 3
    context.block.add(output.Arithmetics(output.Call(compl_callee, compl_args)))

    # 4
    context.block = cb_body_flow

    return output.Reference('result')
    步骤依次是
    它们的顺序不可随意颠倒, 不仅仅因为有些步骤依赖于前面的步骤 (比如步骤 3 构造 Call 对象须 0 与 1 中得到的结果), 更多地是因为在编译过程中上下文语句流随时可能变化. 比如下面这个例子
async_call('xx', %%)('oo', %%)
这应当生成下面这段 Javascript 代码
async_call('xx', function(error, result) {
    result('oo', function(error, result) {
        result;
    });
});
    async_call('xx', %%) 编译时第一次修改了上下文语句流, 而之后对前一次异步调用结果的调用再一次修改了上下文语句流. 用语法树来表示即

+-------------+
| origin flow |
+-------------+
       |         +-------------+
       +---------| arithmetics |
                 +-------------+
                        |
            [A]====================+
              | regular async call |
              +====================+
                  /               \
    [B]====================+    +------+
      | regular async call |    | 'oo' |
      +====================+    +------+
           /           \
    +------------+  +------+
    | async_call |  | 'xx' |
    +------------+  +------+

    按照上述顺序, 实际上编译过程采用的是深度优先遍历. 首先进入 Arithmetics 节点的编译, 它直接调用其成员节点, 也就是上面标记有 A 的正规异步调用; 接着, 在它的编译过程中执行 0 编译被调用对象, 于是进入了上面标记有 B 的正规异步调用的编译方法.
    B 的被调用对象与参数分别是 async_call 引用和字符串常量, 均不会改变上下文语句流. 经过了 0-4 步编译, 得到的语法树看起来类似

+-------------+
| origin flow |
+-------------+
       |         +-------------+
       +---------| arithmetics |
                 +-------------+
                        |
                    +------+
                    | call |   (arguments)
                    +------+\_____________
                   /            \         \
            +------------+  +------+   +--------+
            | async_call |  | 'xx' |---| lambda +---------+
            +------------+  +------+   |  ` param: error  |
                                       |  ` param: result |
                                       +------------------+
              ________________________________|
             |            (function body)
     +--------------+
     | current flow |
     +--------------+
           |
           |     +-------------+
           +-----| arithmetics |
                 +-------------+
                        |
            [A]====================+
              | regular async call |
              +====================+
                  /               \
             +--------+        +------+
             | result |        | 'oo' |
             +--------+        +------+

    B 节点编译后返回 result 引用, 所以第一次变换后原先对 B 节点的函数调用变成了对 result 的函数调用.
    然后 A 节点再继续后面 1-4 步骤, 最终的语法树于是变成了

+-------------+
| origin flow |
+-------------+
       |         +-------------+
       +---------| arithmetics |
                 +-------------+
                        |
                    +------+
                    | call |   (arguments)
                    +------+\_____________
                   /            \         \
            +------------+  +------+   +--------+
            | async_call |  | 'xx' |---| lambda +---------+
            +------------+  +------+   |  ` param: error  |
                                       |  ` param: result |
                                       +------------------+
              ________________________________|
             |            (function body)
    +---------------+
    | previous flow |
    +---------------+
            |         +-------------+
            +---------| arithmetics |
                      +-------------+
                             |
                         +------+
                         | call |   (arguments)
                         +------+\_____________
                        /            \         \
                   +--------+    +------+   +--------+
                   | result |    | 'oo' |---| lambda +---------+
                   +--------+    +------+   |  ` param: error  |
                                            |  ` param: result |
                                            +------------------+
                   ________________________________|
                  |            (function body)
          +==============+
          | current flow |
          +==============+
                 |
                 |     +-------------+
                 +-----| arithmetics |
                       +-------------+
                              |
                          +--------+
                          | result |
                          +--------+

    并且此后再有新的语句节点则挂在等号框起来的当前语句流中, 直到当前语句流再次被替换.

    此外正规异步调用还可以作为参数传递. 如
console.log(fs.readFile(
                fs.readFile('fileList', %%).toString().split('\n')[0],
                %%))
    它应被转换为如下形式
fs.readFile('fileList', function(error, result) {
    fs.readFile(result.toString().split('\n')[0], function(error, result) {
        console.log(result);
    });
});
    转换的方式仍然是在 0) 在原语句流中加入一个算术节点, 算术节点包含一个调用; 1) 将上下文语句流替换为正规异步回调的函数体.
    只不过在编译时, 一定要深度优先递归地编译节点. 如对于一个函数调用, 要先编译被调用者再编译参数. 对于 Python, Javascript 等语言, 情况要好得多, 这些语言都有规定, 参数的求值顺序从左至右, 一般二元运算的求值顺序也是从左至右, 即如下代码
class Binary(Expression):
    def compile(self, context):
        return output.Binary(self.op,
                             self.left.compile(context),
                             self.right.compile(context))
等价于
class Binary(Expression):
    def compile(self, context):
        compl_left = self.left.compile(context)
        compl_right = self.right.compile(context)
        return output.Binary(self.op, compl_left, compl_right)
    但有时候这又不是理所当然的, 比如用 C/C++ 实现这东西的话, 就得写得更明显一些, 必须写成后一种的形式.

保存当前上下文语句流

    在匿名函数编译时它对上下文语句流进行了一次保存, 实现为
class Lambda(Expression):
    def compile(self, context):
        body_context = ContextFlow()
        body_flow = body_context.block                   # keep body flow
        self.body.compile(body_context)
        return output.Lambda(self.parameters, body_flow) # use body flow
    而不能这样实现
class Lambda(Expression):
    def compile(self, context):
        body_context = ContextFlow()
        self.body.compile(body_context)
        return output.Lambda(self.parameters, body_context.block)
    因为在编译过程中 body_context.block 遇到正规异步调用就会发生变化, 如果直接用之, 其实也就是最后一个正规异步回调的函数体了. 当然还有一种方法, 就是在 ContextFlow 中再保存一个根语句流, 类似这样
class ContextFlow:
    def __init__(self):
        self.block = output.Block([])
        self.root_flow = self.block
    那么实现匿名函数编译便可以这样
class Lambda(Expression):
    def compile(self, context):
        body_context = ContextFlow()
        self.body.compile(body_context)
        return output.Lambda(self.parameters, body_context.root_flow)
    除了匿名函数之外, 分支语句的编译亦须以类似方式保存上下文语句流.

    另一方面, 算术节点须先行编译其中的表达式, 然后再生成一个输出的算术节点到上下文语句流中
class Arithmetics(Statement):
    def compile(self, context):
        compl_arith = output.Arithmetics(self.expression.compile(context))
        context.block.add(compl_arith)
    而不能写作
class Arithmetics(Statement):
    def compile(self, context):
        context.block.add(output.Arithmetics(self.expression.compile(context)))
    否则, context.block 必定是表达式编译前的上下文语句流.

并列的正规异步调用

    当多个正规异步调用并列出现时, 如果不对名字进行区分, 生成的代码会出现两个对 result 的引用实际上是同一个对象, 如
x: f(0, %%) + g(1, %%)
被编译成
f(0, function(error, result) {
    g(1, function(error, result) {
        x = result + result;
    });
});
    两个 result 都是 g 调用回调时得到的结果. 虽然从一开始就有这种可能, 定义一个名为 result 的变量, 结果被回调参数名字给覆盖了, 但假定现在已经有一套名字捣碎 (name mangle) 机制避免这种情况, 现在要解决的就是区分不同的回调结果.
    通用的方式是对每个异步回调的结果设置一个序列号来区分. 对于 Python 而言可以使用对象 id 作为这个唯一的序列号 (C++ 可以使用对象地址作为唯一标识, 如果某个语言不支持取对象 id 或地址, 那么得造轮子弄个序列号生成器).
    因为最终生成代码的是 output 模块中的节点, 因此最好使用 output 对象的 id. 如这样实现正规异步回调的编译函数
def compile(self, context):
    compl_callee = self.callee.compile(context)

    compl_args = [ arg.compile(context) for arg in self.arguments ]

    callback_body_context = ContextFlow()
    cb_body_flow = callback_body_context.block

    result_id = id(cb_body_flow)
    result_ref = 'result$' + str(result_id)
    compl_args.append(output.Lambda([ 'error', result_ref ], cb_body_flow))

    context.block.add(output.Arithmetics(output.Call(compl_callee, compl_args)))

    context.block = cb_body_flow

    return output.Reference(result_ref)
    然后再构造这样一段节点来测试一下
Block([
        Arithmetics(
            Binary(
                '=',
                Reference('x'),
                Binary(
                    '+',
                    RegularAsyncCall(
                        Reference('f'),
                        [ NumericLiteral(0) ]),
                    RegularAsyncCall(
                        Reference('g'),
                        [ NumericLiteral(1) ]))))
    ])
    得到的代码可能如
{(f(0,function (error,result$12345678) {(g(1,function (error,result$23456789) {(x = (result$12345678+ result$23456789));}));}));}
格式化之后为
{
    (f(0, function (error, result$12345678) {
        (g(1, function (error, result$23456789) {
            (x = (result$12345678 + result$23456789));
        }));
    }));
}

猛击这里查看本篇相关代码完整版
下篇: 编译条件表达式

Post tags:   Python  Javascript  Asynchronous Expression  Compiler Construction

Leave a comment:




Creative Commons License Your comment will be licensed under
CC-NC-ND 3.0


. Back to Bit Focus
NijiPress - Copyright (C) Neuron Teckid @ Bit Focus
About this site