对于想要二次开发scratch3.0 的同学来说,多语言(Python、Javascript、Arduino等等)代码生成或许是很多人面临的一个难题,因为scratch3.0在参照谷歌的blockly进行scratch-blocks模块研发的时候摒弃了代码生成这一环节,导致现在很多scratch二次开发者在代码生成这一环节犯难。那么我们今天就要讲一讲,Blockly是如何进行代码生成的,这对Scratch的代码生成又将有着什么样的意义。
大多数的Blockly应用程序都需要将用户的程序翻译成JavaScript、Python、PHP、Lua、Dart或其他一些语言。这都是通过Blockly的客户端执行去完成的。例如:
第一步是去包裹你想要的语言生成器,Blockly自带以下几种代码生成器:
javascript_compressed.js
python_compressed.js
php_compressed.js
lua_compressed.js
dart_compressed.js
代码生成器的js文件应该放置在blockly_compressed.js
之后,例如:
在你的应用中,想要拖拽出的积木随时转换成代码,需要这样调用:
var code = Blockly.JavaScript.workspaceToCode(workspace);
上面这行代码中,只需要将JavaScript
字段 修改成 Python, PHP, Lua, Dart
中的任意一个,就可转换代码生成器的语言。’
生成代码是一个非常快的操作,因此系统会频繁地调用这个功能,且不能产生异常。通常的做法是,在Blockly的状态变更期间去添加一个监听器,从而做到代码的生成和展示实时进行。代码如下:
function myUpdateFunction(event) {
var code = Blockly.JavaScript.workspaceToCode(workspace);
document.getElementById('textarea').value = code;
}
workspace.addChangeListener(myUpdateFunction);
大部分Blockly应用需要将前台的积木转换成后台的代码去执行,本章节讲述的是如何给一个定制的积木添加一个代码生成器。
首先到generators/
目录下,选择你想生成的语言(JavaScript, Python, PHP, Lua, Dart,等等)的子目录,如果你的积木运行不是这几类的语言,那么需要你创建一个新的js文件。这个新的js文件在HTML编辑器中需要被包裹在标签中。
下面是一个典型的代码生成器结构:
Blockly.JavaScript['text_indexOf'] = function(block) {
// 搜索文本中的子字符串
var operator = block.getFieldValue('END') == 'FIRST' ? 'indexOf' : 'lastIndexOf';
var subString = Blockly.JavaScript.valueToCode(block, 'FIND',
Blockly.JavaScript.ORDER_NONE) || '\'\'';
var text = Blockly.JavaScript.valueToCode(block, 'VALUE',
Blockly.JavaScript.ORDER_MEMBER) || '\'\'';
var code = text + '.' + operator + '(' + subString + ')';
return [code, Blockly.JavaScript.ORDER_MEMBER];
};
绝大多数的代码生成器的第一项任务就是收集所有参数和字段的数据。其中最常用的是以下介个方法:
getFieldValue
valueToCode
statementToCode
block.getFieldValue('END')
这个方法从一个指定的名称字段返回一个值:
getFieldValue
函数会返回一个在下拉框被创建时指定的中性语言文本(Blockly的核心积木通常使用大写的英文单词,例如‘FIRST’)。Blockly.JavaScript.variableDB_.getName(block.getFieldValue('VAR'), Blockly.Variables.NAME_TYPE);
Blockly.JavaScript.valueToCode(block, 'FROM', Blockly.JavaScript.ORDER_ADDITION) || '0'
此函数查找连接到命名值输入(‘FROM’)的积木,生成该积木的代码,并将代码作为字符串返回。如果未连接输入,则此函数返回null,这就是为什么通常使用布尔’或’和默认值来跟随函数。因此,在上面的示例中,如果没有附加到名为’FROM’的输入的积木,则此输入的默认代码将是字符串’0’。
第三个参数指定嵌入所需的操作信息的顺序。每个语言生成器都有一个有序的优先级列表。valueToCode
函数需要通过顺序值去给返回的代码添加优先顺序。valueToCode如果需要,这允许 将代码包装在括号中。有关详细信息,请参阅**运算符优先级**页面。
请注意,JavaScript应改为相应的语言(Python,PHP,Lua,Dart,等)。
Blockly.JavaScript.statementToCode(block, 'DO')
此函数查找连接到指定语句输入的嵌套块堆栈,生成该堆栈的代码,缩进代码,并将代码作为字符串返回。如果未连接输入,则此函数返回空字符串。
请注意,JavaScript应改为相应的语言(Python,PHP,Lua,Dart,等)。
一旦收集了所有参数,就可以组装最终代码。这对于大多数积木来说都是很简单的。下面是while循环的一个例子:
var code = 'while (' + argument0 + ') {\n' + branch0 + '}\n';
那些没有返回值的状态积木,可以立即返回代码:
return code;
那些返回一个值的值积木会更难一点,下面是一个基本算术运算符(加减等)的例子:
var code = argument0 + ' ' + operator + ' ' + argument1;
这个例子表明了一个操作顺序的问题,从表达式(2*(3+4))看,有2种积木的连接方式。使用上面的代码片段,加法积木将返回字符串“3 + 4”,而乘法积木将使用这个作为输入,返回“2 * 3 + 4”。这个结果是不正确的,因为3在执行时被绑定到乘法上了。
为了解决这个问题,值积木必须返回一个包含2个值(代码和优先级顺序值)的列表。
return [code, Blockly.JavaScript.ORDER_ADDITION];
每种生成器语言都有一个优先级顺序列表。返回的顺序值将以最小的优先级跟代码绑在一起。有关详细信息,请参阅**运算符优先级**页面。
如果生成的代码要求包含子块的代码两次,则应该缓存参数以提高效率并防止副作用。