编译步骤解释
多个正规异步调用出现在同一个语句中的编译顺序
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')
- 0 编译被调用对象
- 1 依次编译调用实参
- 2 向实参列表中追加一个匿名函数, 函数体语句流是新建的一个
ContextFlow
中的语句流 - 3 向当前上下文语句流中插入一个算术节点, 节点的内容是编译后的异步调用
- 4 修改上下文语句流
Call
对象须 0 与 1 中得到的结果), 更多地是因为在编译过程中上下文语句流随时可能变化. 比如下面这个例子async_call('xx', %%)('oo', %%)
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' |
+--------+ +------+
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);
});
});
只不过在编译时, 一定要深度优先递归地编译节点. 如对于一个函数调用, 要先编译被调用者再编译参数. 对于 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)
保存当前上下文语句流
在匿名函数编译时它对上下文语句流进行了一次保存, 实现为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));
}));
}));
}
猛击这里查看本篇相关代码完整版
下篇: 编译条件表达式