From 83656e9fc7d129f2250dfd3f307494fbac7b59de Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Fri, 19 Jul 2019 11:25:12 -0400 Subject: [PATCH] Basic syntax support complete for all AST nodes! --- .idea/dictionaries/acbart.xml | 1 + .idea/workspace.xml | 508 ++++++++++++++++++++-------------- src/ast/ast_Break.js | 18 ++ src/ast/ast_Call.js | 6 +- src/ast/ast_ClassDef.js | 156 +++++++++++ src/ast/ast_Continue.js | 18 ++ src/ast/ast_FunctionDef.js | 161 +++++++++-- src/ast/ast_Global.js | 85 ++++++ src/ast/ast_If.js | 137 +++++++++ src/ast/ast_Import.js | 143 ++++++++++ src/ast/ast_Lambda.js | 58 ++++ src/ast/ast_Nonlocal.js | 85 ++++++ src/ast/ast_Return.js | 45 +++ src/ast/ast_Try.js | 167 +++++++++++ src/ast/ast_While.js | 83 ++++++ src/ast/ast_With.js | 141 ++++++++++ src/ast/ast_Yield.js | 43 +++ src/ast/ast_YieldFrom.js | 25 ++ src/ast/todo.md | 38 +-- test/simple_dev.html | 37 ++- 20 files changed, 1704 insertions(+), 251 deletions(-) create mode 100644 src/ast/ast_Break.js create mode 100644 src/ast/ast_ClassDef.js create mode 100644 src/ast/ast_Continue.js create mode 100644 src/ast/ast_Global.js create mode 100644 src/ast/ast_If.js create mode 100644 src/ast/ast_Import.js create mode 100644 src/ast/ast_Nonlocal.js create mode 100644 src/ast/ast_Return.js create mode 100644 src/ast/ast_Try.js create mode 100644 src/ast/ast_While.js create mode 100644 src/ast/ast_With.js create mode 100644 src/ast/ast_Yield.js create mode 100644 src/ast/ast_YieldFrom.js diff --git a/.idea/dictionaries/acbart.xml b/.idea/dictionaries/acbart.xml index d4d2d89..90ff7bc 100644 --- a/.idea/dictionaries/acbart.xml +++ b/.idea/dictionaries/acbart.xml @@ -3,6 +3,7 @@ binops blockly + finalbody orelse diff --git a/.idea/workspace.xml b/.idea/workspace.xml index ad65c33..2732632 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,40 +2,25 @@ - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - + + + - - - - @@ -50,33 +35,49 @@ - - + + + + + + - + - - + + + + + + + + + + + + + + - + - - + + - + - - + + @@ -84,35 +85,44 @@ - - + + - - + + - - + + - + - - + + - + - - + + + + + + + + + + + @@ -128,36 +138,36 @@ - previousDisabledState_ - getProcedureCall - "func - "funct - "NAME - 'NAME - arguments_ - this.convert - showParameterNames_ - renameP - renameProcedure - setProcedureParameters_ - convert - conso - saveConnections - ast_ListComp_create_with_item - ast_Dict_create_with_item - decompose: - DictPair - Pair - todo - fromJson - extension - contextMenu updateShape setMov movable_ - console consle + itemCount + hasReturn_ + hasReturnV + hasReturns + setReturn + setReturnAnnotation + setVisible + hasReturnValue + hasReturnValue_ + ast_Assert + ast_Return + ast_Yield + ast_Delete + Sk.ffi + hasReturns_ + ast_Global + GLOBAL + ast_Break + consol + console + appendStatementInput console.log + ast_If + asname + getInput + ast_WithDef UN @@ -168,7 +178,16 @@ Set Dict ast_IfExpr + ast_Raise + ast_Yield + ast_YieldFrom + ast_Global ast_IfExp + ast_Nonlocal + NONLOCAL + ast_Continue + ast_While + ast_With @@ -190,14 +209,12 @@ @@ -302,12 +336,12 @@ - @@ -337,309 +371,361 @@ - + - - + + - + + + + + + + + + + + + + + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + + + + + + + + - - + + - + - - + + - + + + + + + + + + - - + + - + - - + + - + - - + + - - + + + + + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + + + + + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - - + + + + + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + + + + - - + + - + + + + + + + + - - + + - - + + diff --git a/src/ast/ast_Break.js b/src/ast/ast_Break.js new file mode 100644 index 0000000..f5737e4 --- /dev/null +++ b/src/ast/ast_Break.js @@ -0,0 +1,18 @@ +BlockMirrorTextToBlocks.BLOCKS.push({ + "type": "ast_Break", + "message0": "break", + "inputsInline": false, + "previousStatement": null, + "nextStatement": null, + "colour": 60, + "tooltip": "", + "helpUrl": "" +}); + +Blockly.Python['ast_Break'] = function (block) { + return "break\n"; +}; + +BlockMirrorTextToBlocks.prototype['ast_Break'] = function (node) { + return BlockMirrorTextToBlocks.create_block("ast_Break", node.lineno); +}; \ No newline at end of file diff --git a/src/ast/ast_Call.js b/src/ast/ast_Call.js index a5668d2..153f4fd 100644 --- a/src/ast/ast_Call.js +++ b/src/ast/ast_Call.js @@ -250,8 +250,7 @@ Blockly.Blocks['ast_Call'] = { container.appendChild(parameter); } return container; - } - , + }, /** * Parse XML to restore the (non-editable) name and parameters. * @param {!Element} xmlElement XML storage element. @@ -275,8 +274,7 @@ Blockly.Blocks['ast_Call'] = { if (this.simpleName_ !== null) { this.renameProcedure(this.getProcedureCall(), this.simpleName_); } - } - , + }, /** * Return all variables referenced by this block. * @return {!Array.} List of variable models. diff --git a/src/ast/ast_ClassDef.js b/src/ast/ast_ClassDef.js new file mode 100644 index 0000000..a59fecb --- /dev/null +++ b/src/ast/ast_ClassDef.js @@ -0,0 +1,156 @@ +Blockly.Blocks['ast_ClassDef'] = { + init: function () { + this.decorators_ = 0; + this.bases_ = 0; + this.keywords_ = 0; + this.appendDummyInput('HEADER') + .appendField("Class") + .appendField(new Blockly.FieldVariable("item"), "NAME"); + this.appendStatementInput("BODY") + .setCheck(null); + this.setInputsInline(false); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(230); + this.setTooltip(""); + this.setHelpUrl(""); + this.updateShape_(); + }, + // TODO: Not mutable currently + updateShape_: function () { + for (let i = 0; i < this.decorators_; i++) { + let input = this.appendValueInput("DECORATOR" + i) + .setCheck(null) + .setAlign(Blockly.ALIGN_RIGHT); + if (i === 0) { + input.appendField("decorated by"); + } + this.moveInputBefore('DECORATOR' + i, 'BODY'); + } + for (let i = 0; i < this.bases_; i++) { + let input = this.appendValueInput("BASE" + i) + .setCheck(null) + .setAlign(Blockly.ALIGN_RIGHT); + if (i === 0) { + input.appendField("inherits from"); + } + this.moveInputBefore('BASE' + i, 'BODY'); + } + + for (let i = 0; i < this.keywords_; i++) { + this.appendValueInput("KEYWORDVALUE" + i) + .setCheck(null) + .setAlign(Blockly.ALIGN_RIGHT) + .appendField(new Blockly.FieldTextInput("metaclass"), "KEYWORDNAME" + i) + .appendField("="); + this.moveInputBefore('KEYWORDVALUE' + i, 'BODY'); + } + }, + /** + * Create XML to represent the (non-editable) name and arguments. + * @return {!Element} XML storage element. + * @this Blockly.Block + */ + mutationToDom: function () { + let container = document.createElement('mutation'); + container.setAttribute('decorators', this.decorators_); + container.setAttribute('bases', this.bases_); + container.setAttribute('keywords', this.keywords_); + return container; + }, + /** + * Parse XML to restore the (non-editable) name and parameters. + * @param {!Element} xmlElement XML storage element. + * @this Blockly.Block + */ + domToMutation: function (xmlElement) { + this.decorators_ = parseInt(xmlElement.getAttribute('decorators'), 10); + this.bases_ = parseInt(xmlElement.getAttribute('bases'), 10); + this.keywords_ = parseInt(xmlElement.getAttribute('keywords'), 10); + this.updateShape_(); + }, +}; + +Blockly.Python['ast_ClassDef'] = function (block) { + // Name + let name = Blockly.Python.variableDB_.getName(block.getFieldValue('NAME'), Blockly.Variables.NAME_TYPE); + // Decorators + let decorators = new Array(block.decorators_); + for (let i = 0; i < block.decorators_; i++) { + let decorator = (Blockly.Python.valueToCode(block, 'DECORATOR' + i, Blockly.Python.ORDER_NONE) || + Blockly.Python.blank); + decorators[i] = "@" + decorator + "\n"; + } + // Bases + let bases = new Array(block.bases_); + for (let i = 0; i < block.bases_; i++) { + bases[i] = (Blockly.Python.valueToCode(block, 'BASE' + i, Blockly.Python.ORDER_NONE) || + Blockly.Python.blank); + } + // Keywords + let keywords = new Array(block.keywords_); + for (let i = 0; i < block.keywords_; i++) { + let name = block.getFieldValue('KEYWORDNAME' + i); + let value = (Blockly.Python.valueToCode(block, 'KEYWORDVALUE' + i, Blockly.Python.ORDER_NONE) || + Blockly.Python.blank); + if (name == '**') { + keywords[i] = '**' + value; + } else { + keywords[i] = name + '=' + value; + } + } + // Body: + let body = Blockly.Python.statementToCode(block, 'BODY') || Blockly.Python.PASS; + // Put it together + let arguments = (bases.concat(keywords)); + arguments = (arguments.length === 0) ? "" : "(" + arguments.join(', ') + ")"; + return decorators.join('') + "class " + name + arguments + ":\n" + body; +} +; + +BlockMirrorTextToBlocks.prototype['ast_ClassDef'] = function (node) { + let name = node.name; + let bases = node.bases; + let keywords = node.keywords; + let body = node.body; + let decorator_list = node.decorator_list; + + let values = {}; + let fields = {'NAME': Sk.ffi.remapToJs(name)}; + + if (decorator_list !== null) { + for (let i = 0; i < decorator_list.length; i++) { + values['DECORATOR' + i] = this.convert(decorator_list[i]); + } + } + + if (bases !== null) { + for (let i = 0; i < bases.length; i++) { + values['BASE' + i] = this.convert(bases[i]); + } + } + + if (keywords !== null) { + for (let i = 0; i < keywords.length; i++) { + values['KEYWORDVALUE' + i] = this.convert(keywords[i].value); + let arg = keywords[i].arg; + if (arg === null) { + fields['KEYWORDNAME' + i] = "**"; + } else { + fields['KEYWORDNAME' + i] = Sk.ffi.remapToJs(arg); + } + } + } + + return BlockMirrorTextToBlocks.create_block("ast_ClassDef", node.lineno, fields, + values, + { + "inline": "false" + }, { + "@decorators": (decorator_list === null ? 0 : decorator_list.length), + "@bases": (bases === null ? 0 : bases.length), + "@keywords": (keywords === null ? 0 : keywords.length), + }, { + 'BODY': this.convertBody(body) + }); +}; diff --git a/src/ast/ast_Continue.js b/src/ast/ast_Continue.js new file mode 100644 index 0000000..c159b88 --- /dev/null +++ b/src/ast/ast_Continue.js @@ -0,0 +1,18 @@ +BlockMirrorTextToBlocks.BLOCKS.push({ + "type": "ast_Continue", + "message0": "continue", + "inputsInline": false, + "previousStatement": null, + "nextStatement": null, + "colour": 60, + "tooltip": "", + "helpUrl": "" +}); + +Blockly.Python['ast_Continue'] = function (block) { + return "continue\n"; +}; + +BlockMirrorTextToBlocks.prototype['ast_Continue'] = function (node) { + return BlockMirrorTextToBlocks.create_block("ast_Continue", node.lineno); +}; \ No newline at end of file diff --git a/src/ast/ast_FunctionDef.js b/src/ast/ast_FunctionDef.js index 0382f05..d304e18 100644 --- a/src/ast/ast_FunctionDef.js +++ b/src/ast/ast_FunctionDef.js @@ -7,7 +7,7 @@ BlockMirrorTextToBlocks.BLOCKS.push({ "args0": [ {"type": "input_dummy"}, {"type": "input_statement", "name": "STACK", "align": "RIGHT"}, - {"type": "input_value", "name": "RETURNS", "align": "RIGHT"} + {"type": "field_checkbox", "name": "RETURNS", "checked": true, "align": "RIGHT"} ], "style": "procedure_blocks", "enableContextMenu": false @@ -73,6 +73,8 @@ BlockMirrorTextToBlocks.BLOCKS.push({ } }); +// TODO: Figure out an elegant "complexity" flag feature to allow different levels of Mutators + Blockly.Blocks['ast_FunctionDef'] = { init: function () { this.appendDummyInput() @@ -81,6 +83,7 @@ Blockly.Blocks['ast_FunctionDef'] = { this.decoratorsCount_ = 0; this.parametersCount_ = 0; this.hasReturn_ = false; + this.mutatorComplexity_ = 0; this.appendStatementInput("BODY") .setCheck(null); this.setInputsInline(false); @@ -90,6 +93,8 @@ Blockly.Blocks['ast_FunctionDef'] = { this.setTooltip(""); this.setHelpUrl(""); this.updateShape_(); + this.setMutator(new Blockly.Mutator(['ast_FunctionMutantParameter', + 'ast_FunctionMutantParameterType'])); }, /** * Create XML to represent list inputs. @@ -114,6 +119,21 @@ Blockly.Blocks['ast_FunctionDef'] = { this.hasReturn_ = "true" === xmlElement.getAttribute('returns'); this.updateShape_(); }, + setReturnAnnotation_: function(status) { + let currentReturn = this.getInput('RETURNS'); + if (status) { + if (!currentReturn) { + this.appendValueInput("RETURNS") + .setCheck(null) + .setAlign(Blockly.ALIGN_RIGHT) + .appendField("returns"); + } + this.moveInputBefore('RETURNS', 'BODY'); + } else if (!status && currentReturn) { + this.removeInput('RETURNS'); + } + this.hasReturn_ = status; + }, updateShape_: function () { // Set up decorators and parameters let block = this; @@ -144,19 +164,128 @@ Blockly.Blocks['ast_FunctionDef'] = { } }); // Set up optional Returns annotation - let currentReturn = this.getInput('RETURNS'); - if (this.hasReturn_) { - if (!currentReturn) { - this.appendValueInput("RETURNS") - .setCheck(null) - .setAlign(Blockly.ALIGN_RIGHT) - .appendField("returns"); + this.setReturnAnnotation_(this.hasReturn_); + }, + /** + * Populate the mutator's dialog with this block's components. + * @param {!Blockly.Workspace} workspace Mutator's workspace. + * @return {!Blockly.Block} Root block in mutator. + * @this Blockly.Block + */ + decompose: function (workspace) { + var containerBlock = workspace.newBlock('ast_FunctionHeaderMutator'); + containerBlock.initSvg(); + + // Check/uncheck the allow statement box. + if (this.getInput('RETURNS')) { + containerBlock.setFieldValue( + this.hasReturn_ ? 'TRUE' : 'FALSE', 'RETURNS'); + } else { + // TODO: set up "canReturns" for lambda mode + //containerBlock.getField('RETURNS').setVisible(false); + } + + // Set up parameters + var connection = containerBlock.getInput('STACK').connection; + let parameters = []; + for (var i = 0; i < this.parametersCount_; i++) { + let parameter = this.getInput('PARAMETER' + i).connection; + let sourceType = parameter.targetConnection.getSourceBlock().type; + let createName = 'ast_FunctionMutant' + sourceType.substring('ast_Function'.length); + var itemBlock = workspace.newBlock(createName); + itemBlock.initSvg(); + connection.connect(itemBlock.previousConnection); + connection = itemBlock.nextConnection; + parameters.push(itemBlock); + } + return containerBlock; + }, + /** + * Reconfigure this block based on the mutator dialog's components. + * @param {!Blockly.Block} containerBlock Root block in mutator. + * @this Blockly.Block + */ + compose: function (containerBlock) { + var itemBlock = containerBlock.getInputTargetBlock('STACK'); + // Count number of inputs. + var connections = []; + let blockTypes = []; + while (itemBlock) { + connections.push(itemBlock.valueConnection_); + blockTypes.push(itemBlock.type); + itemBlock = itemBlock.nextConnection && + itemBlock.nextConnection.targetBlock(); + } + // Disconnect any children that don't belong. + for (let i = 0; i < this.parametersCount_; i++) { + var connection = this.getInput('PARAMETER' + i).connection.targetConnection; + if (connection && connections.indexOf(connection) === -1) { + // Disconnect all children of this block + let connectedBlock = connection.getSourceBlock(); + for (let j = 0; j < connectedBlock.inputList.length; j++) { + let field = connectedBlock.inputList[j].connection; + if (field && field.targetConnection) { + field.targetConnection.getSourceBlock().unplug(true); + } + } + connection.disconnect(); + connection.getSourceBlock().dispose(); } - this.moveInputBefore('RETURNS', 'BODY'); - } else if (!this.hasReturn_ && currentReturn) { - this.removeInput('RETURNS'); } - } + this.parametersCount_ = connections.length; + this.updateShape_(); + // Reconnect any child blocks. + for (let i = 0; i < this.parametersCount_; i++) { + Blockly.Mutator.reconnect(connections[i], this, 'PARAMETER' + i); + if (!connections[i]) { + let createName = 'ast_Function' + blockTypes[i].substring('ast_FunctionMutant'.length); + let itemBlock = this.workspace.newBlock(createName); + itemBlock.setDeletable(false); + itemBlock.setMovable(false); + itemBlock.initSvg(); + this.getInput('PARAMETER' + i).connection.connect(itemBlock.outputConnection); + itemBlock.render(); + //this.get(itemBlock, 'ADD'+i) + } + } + // Show/hide the returns annotation + let hasReturns = containerBlock.getFieldValue('RETURNS'); + if (hasReturns !== null) { + hasReturns = hasReturns === 'TRUE'; + if (this.hasReturn_ != hasReturns) { + if (hasReturns) { + this.setReturnAnnotation_(true); + Blockly.Mutator.reconnect(this.returnConnection_, this, 'RETURNS'); + this.returnConnection_ = null; + } else { + let returnConnection = this.getInput('RETURNS').connection + this.returnConnection_ = returnConnection.targetConnection; + if (this.returnConnection_) { + let returnBlock = returnConnection.targetBlock(); + returnBlock.unplug(); + returnBlock.bumpNeighbours_(); + } + this.setReturnAnnotation_(false); + } + } + } + }, + /** + * Store pointers to any connected child blocks. + * @param {!Blockly.Block} containerBlock Root block in mutator. + * @this Blockly.Block + */ + saveConnections: function (containerBlock) { + var itemBlock = containerBlock.getInputTargetBlock('STACK'); + var i = 0; + while (itemBlock) { + var input = this.getInput('PARAMETER' + i); + itemBlock.valueConnection_ = input && input.connection.targetConnection; + i++; + itemBlock = itemBlock.nextConnection && + itemBlock.nextConnection.targetBlock(); + } + }, }; Blockly.Python['ast_FunctionDef'] = function (block) { @@ -183,7 +312,7 @@ Blockly.Python['ast_FunctionDef'] = function (block) { } // Body let body = Blockly.Python.statementToCode(block, 'BODY') || Blockly.Python.PASS; - return decorators.join('') + "def " + name + "(" + parameters.join(', ') + ")" + returns + ":\n" + body + "\n"; + return decorators.join('') + "def " + name + "(" + parameters.join(', ') + ")" + returns + ":\n" + body; }; BlockMirrorTextToBlocks.prototype.parseArg = function (arg, type, lineno, values) { @@ -216,8 +345,8 @@ BlockMirrorTextToBlocks.prototype.parseArgs = function (args, values, lineno) { for (let i = 0; i < positional.length; i++) { let childValues = {}; let type = 'ast_FunctionParameter'; - if (defaults[defaultOffset+i]) { - childValues['DEFAULT'] = this.convert(defaults[defaultOffset+i]); + if (defaults[defaultOffset + i]) { + childValues['DEFAULT'] = this.convert(defaults[defaultOffset + i]); type += "Default"; } values['PARAMETER' + totalArgs] = this.parseArg(positional[i], type, lineno, childValues); @@ -249,7 +378,7 @@ BlockMirrorTextToBlocks.prototype.parseArgs = function (args, values, lineno) { } return totalArgs; -} +}; BlockMirrorTextToBlocks.prototype['ast_FunctionDef'] = function (node) { let name = node.name; diff --git a/src/ast/ast_Global.js b/src/ast/ast_Global.js new file mode 100644 index 0000000..1253fe0 --- /dev/null +++ b/src/ast/ast_Global.js @@ -0,0 +1,85 @@ +Blockly.Blocks['ast_Global'] = { + init: function () { + this.setInputsInline(true); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(60); + this.setTooltip(""); + this.setHelpUrl(""); + this.nameCount_ = 1; + this.appendDummyInput('GLOBAL') + .appendField("make global", "START_GLOBALS"); + this.updateShape_(); + }, + updateShape_: function () { + let input = this.getInput("GLOBAL"); + // Update pluralization + if (this.getField('START_GLOBALS')) { + this.setFieldValue(this.nameCount_ > 1 ? "make globals" : "make global", "START_GLOBALS"); + } + // Update fields + for (var i = 0; i < this.nameCount_; i++) { + if (!this.getField('NAME' + i)) { + if (i !== 0) { + input.appendField(',').setAlign(Blockly.ALIGN_RIGHT); + } + input.appendField(new Blockly.FieldVariable("variable"), 'NAME' + i); + } + } + // Remove deleted fields. + while (this.getField('NAME' + i)) { + input.removeField('NAME' + i); + i++; + } + // Delete and re-add ending field + if (this.getField("END_GLOBALS")) { + input.removeField("END_GLOBALS"); + } + input.appendField("available", "END_GLOBALS"); + }, + /** + * Create XML to represent list inputs. + * @return {!Element} XML storage element. + * @this Blockly.Block + */ + mutationToDom: function () { + var container = document.createElement('mutation'); + container.setAttribute('names', this.nameCount_); + return container; + }, + /** + * Parse XML to restore the list inputs. + * @param {!Element} xmlElement XML storage element. + * @this Blockly.Block + */ + domToMutation: function (xmlElement) { + this.nameCount_ = parseInt(xmlElement.getAttribute('names'), 10); + this.updateShape_(); + }, +}; + +Blockly.Python['ast_Global'] = function (block) { + // Create a list with any number of elements of any type. + let elements = new Array(block.nameCount_); + for (let i = 0; i < block.nameCount_; i++) { + elements[i] = Blockly.Python.variableDB_.getName(block.getFieldValue('NAME' + i), Blockly.Variables.NAME_TYPE); + } + return 'global ' + elements.join(', ') + "\n"; +}; + +BlockMirrorTextToBlocks.prototype['ast_Global'] = function (node) { + let names = node.names; + + let fields = {}; + for (var i = 0; i < names.length; i++) { + fields["NAME" + i] = Sk.ffi.remapToJs(names[i]); + } + + return BlockMirrorTextToBlocks.create_block("ast_Global", node.lineno, + fields, + {}, { + "inline": "true", + }, { + "@names": names.length + }); +}; \ No newline at end of file diff --git a/src/ast/ast_If.js b/src/ast/ast_If.js new file mode 100644 index 0000000..6bba90e --- /dev/null +++ b/src/ast/ast_If.js @@ -0,0 +1,137 @@ +Blockly.Blocks['ast_If'] = { + init: function () { + this.orelse_ = 0; + this.elifs_ = 0; + this.appendValueInput('TEST') + .appendField("if"); + this.appendStatementInput("BODY") + .setCheck(null) + .setAlign(Blockly.ALIGN_RIGHT); + this.setInputsInline(false); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setStyle("logic_blocks"); + this.setTooltip(""); + this.setHelpUrl(""); + this.updateShape_(); + }, + // TODO: Not mutable currently + updateShape_: function () { + let latestInput = "BODY"; + for (let i = 0; i < this.elifs_; i++) { + if (!this.getInput('ELIF' + i)) { + this.appendValueInput('ELIFTEST' + i) + .appendField('elif'); + this.appendStatementInput("ELIFBODY" + i) + .setCheck(null); + } + } + // Remove deleted inputs. + while (this.getInput('ELIFTEST' + i)) { + this.removeInput('ELIFTEST' + i); + this.removeInput('ELIFBODY' + i); + i++; + } + + if (this.orelse_ && !this.getInput('ELSE')) { + this.appendDummyInput('ORELSETEST') + .appendField("else:"); + this.appendStatementInput("ORELSEBODY") + .setCheck(null); + } else if (!this.orelse_ && this.getInput('ELSE')) { + block.removeInput('ORELSETEST'); + block.removeInput('ORELSEBODY'); + } + + for (let i = 0; i < this.elifs_; i++) { + if (this.orelse_) { + this.moveInputBefore('ELIFTEST' + i, 'ORELSETEST'); + this.moveInputBefore('ELIFBODY' + i, 'ORELSETEST'); + } else if (i+1 < this.elifs_) { + this.moveInputBefore('ELIFTEST' + i, 'ELIFTEST' + (i+1)); + this.moveInputBefore('ELIFBODY' + i, 'ELIFBODY' + (i+1)); + } + } + }, + /** + * Create XML to represent the (non-editable) name and arguments. + * @return {!Element} XML storage element. + * @this Blockly.Block + */ + mutationToDom: function () { + let container = document.createElement('mutation'); + container.setAttribute('orelse', this.orelse_); + container.setAttribute('elifs', this.elifs_); + }, + /** + * Parse XML to restore the (non-editable) name and parameters. + * @param {!Element} xmlElement XML storage element. + * @this Blockly.Block + */ + domToMutation: function (xmlElement) { + this.orelse_ = "true" === xmlElement.getAttribute('orelse'); + this.elifs_ = parseInt(xmlElement.getAttribute('elifs'), 10); + this.updateShape_(); + }, +}; + +Blockly.Python['ast_If'] = function (block) { + // Test + let test = "if " + (Blockly.Python.valueToCode(block, 'TEST', + Blockly.Python.ORDER_NONE) || Blockly.Python.blank) + ":\n"; + // Body: + let body = Blockly.Python.statementToCode(block, 'BODY') || Blockly.Python.PASS; + // Elifs + + let elifs = new Array(block.elifs_); + for (let i = 0; i < block.elifs_; i++) { + let elif = block.elifs_[i]; + let clause = "elif " + (Blockly.Python.valueToCode(block, 'ELIFTEST' + i, + Blockly.Python.ORDER_NONE) || Blockly.Python.blank); + clause += ":\n" + (Blockly.Python.statementToCode(block, 'ELIFBODY' + i) || Blockly.Python.PASS); + elifs[i] = clause; + } + // Orelse: + let orelse = ""; + if (this.orelse_) { + orelse = "else:\n" + (Blockly.Python.statementToCode(block, 'ORELSEBODY') || Blockly.Python.PASS); + } + return test + body + elifs.join("") + orelse; +}; + +BlockMirrorTextToBlocks.prototype['ast_If'] = function (node) { + let test = node.test; + let body = node.body; + let orelse = node.orelse; + + let hasOrelse = false; + let elifCount = 0; + + let values = {"TEST": this.convert(test)}; + let statements = {"BODY": this.convertBody(body)}; + + while (orelse !== undefined && orelse.length > 0) { + if (orelse.length === 1) { + if (orelse[0]._astname === "If") { + // This is an ELIF + this.heights.shift(); + values['ELIFTEST' + elifCount] = this.convert(orelse[0].test); + statements['ELIFBODY' + elifCount] = this.convertBody(orelse[0].body); + elifCount++; + } else { + hasOrelse = true; + statements['ORELSEBODY'] = this.convertBody(orelse, false); + } + } else { + hasOrelse = true; + statements['ORELSEBODY'] = this.convertBody(orelse, false); + } + orelse = orelse[0].orelse; + } + + return BlockMirrorTextToBlocks.create_block("ast_If", node.lineno, {}, + values, {}, { + "@orelse": hasOrelse, + "@elifs": elifCount + }, statements); +}; \ No newline at end of file diff --git a/src/ast/ast_Import.js b/src/ast/ast_Import.js new file mode 100644 index 0000000..65eb5bc --- /dev/null +++ b/src/ast/ast_Import.js @@ -0,0 +1,143 @@ +// TODO: direct imports are not variables, because you can do stuff like: +// import os.path +// What should the variable be? Blockly will mangle it, but we should really be +// doing something more complicated here with namespaces (probably make `os` the +// variable and have some kind of list of attributes. But that's in the fading zone. +Blockly.Blocks['ast_Import'] = { + init: function () { + this.nameCount_ = 1; + this.from_ = false; + this.regulars_ = [true]; + this.setInputsInline(false); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(230); + this.setTooltip(""); + this.setHelpUrl(""); + this.updateShape_(); + }, + // TODO: Not mutable currently + updateShape_: function () { + // Possible FROM part + if (this.from_ && !this.getInput('FROM')) { + this.appendDummyInput('FROM') + .setAlign(Blockly.ALIGN_RIGHT) + .appendField('from') + .appendField(new Blockly.FieldTextInput("module"), "MODULE"); + } else if (!this.from_ && this.getInput('FROM')) { + this.removeInput('FROM'); + } + // Import clauses + for (let i = 0; i < this.nameCount_; i++) { + let input = this.getInput('CLAUSE' + i); + if (!input) { + input = this.appendDummyInput('CLAUSE' + i) + .setAlign(Blockly.ALIGN_RIGHT); + if (i === 0) { + input.appendField("import"); + } + input.appendField(new Blockly.FieldTextInput("default"), "NAME" + i) + } + if (this.regulars_[i] && this.getField('AS' + i)) { + input.removeField('AS' + i); + input.removeField('ASNAME' + i); + } else if (!this.regulars_[i] && !this.getField('AS' + i)) { + input.appendField("as", 'AS' + i) + .appendField(new Blockly.FieldVariable("alias"), "ASNAME" + i); + } + } + // Remove deleted inputs. + while (this.getInput('CLAUSE' + i)) { + this.removeInput('CLAUSE' + i); + i++; + } + // Reposition everything + if (this.from_ && this.nameCount_ > 0) { + this.moveInputBefore('FROM', 'CLAUSE0'); + } + for (let i = 0; i + 1 < this.nameCount_; i++) { + this.moveInputBefore('CLAUSE' + i, 'CLAUSE' + (i + 1)); + } + }, + /** + * Create XML to represent the (non-editable) name and arguments. + * @return {!Element} XML storage element. + * @this Blockly.Block + */ + mutationToDom: function () { + let container = document.createElement('mutation'); + container.setAttribute('names', this.nameCount_); + container.setAttribute('from', this.from_); + for (let i = 0; i < this.nameCount_; i++) { + let parameter = document.createElement('regular'); + parameter.setAttribute('name', this.regulars_[i]); + container.appendChild(parameter); + } + return container; + }, + /** + * Parse XML to restore the (non-editable) name and parameters. + * @param {!Element} xmlElement XML storage element. + * @this Blockly.Block + */ + domToMutation: function (xmlElement) { + this.nameCount_ = parseInt(xmlElement.getAttribute('names'), 10); + this.from_ = "true" === xmlElement.getAttribute('from'); + this.regulars_ = []; + for (let i = 0, childNode; childNode = xmlElement.childNodes[i]; i++) { + if (childNode.nodeName.toLowerCase() === 'regular') { + this.regulars_.push("true" === childNode.getAttribute('name')); + } + } + this.updateShape_(); + }, +}; + +Blockly.Python['ast_Import'] = function (block) { + // Optional from part + let from = ""; + if (this.from_) { + from = "from "+block.getFieldValue('MODULE') + " "; + } + // Create a list with any number of elements of any type. + let elements = new Array(block.nameCount_); + for (let i = 0; i < block.nameCount_; i++) { + elements[i] = block.getFieldValue('NAME' + i); + if (!this.regulars_[i]) { + elements[i] += " as " + Blockly.Python.variableDB_.getName(block.getFieldValue('ASNAME' + i), Blockly.Variables.NAME_TYPE); + } + } + return from + 'import ' + elements.join(', ') + "\n"; +}; + +BlockMirrorTextToBlocks.prototype['ast_Import'] = function (node) { + let names = node.names; + + let fields = {}; + let mutations = {'@names': names.length}; + + let regulars = []; + for (let i = 0; i < names.length; i++) { + fields["NAME" + i] = Sk.ffi.remapToJs(names[i].name); + let isRegular = (names[i].asname === null); + if (!isRegular) { + fields["ASNAME" + i] = Sk.ffi.remapToJs(names[i].asname); + } + regulars.push(isRegular); + } + mutations['regular'] = regulars; + + if (node._astname === 'ImportFrom') { + // acbart: GTS suggests module can be None for '.' but it's an empty string in Skulpt + mutations['@from'] = true; + fields['MODULE'] = ('.'.repeat(node.level)) + Sk.ffi.remapToJs(node.module); + } else { + mutations['@from'] = false; + } + + return BlockMirrorTextToBlocks.create_block("ast_Import", node.lineno, fields, + {}, {}, mutations); +}; + +// Alias ImportFrom because of big overlap +BlockMirrorTextToBlocks.prototype['ast_ImportFrom'] = BlockMirrorTextToBlocks.prototype['ast_Import']; \ No newline at end of file diff --git a/src/ast/ast_Lambda.js b/src/ast/ast_Lambda.js index e69de29..02bdeb0 100644 --- a/src/ast/ast_Lambda.js +++ b/src/ast/ast_Lambda.js @@ -0,0 +1,58 @@ +Blockly.Blocks['ast_Lambda'] = { + init: function () { + this.appendDummyInput() + .appendField("lambda") + .setAlign(Blockly.ALIGN_RIGHT); + this.decoratorsCount_ = 0; + this.parametersCount_ = 0; + this.hasReturn_ = false; + this.appendValueInput("BODY") + .appendField("body") + .setAlign(Blockly.ALIGN_RIGHT) + .setCheck(null); + this.setInputsInline(false); + this.setOutput(true); + this.setStyle("procedure_blocks"); + this.setTooltip(""); + this.setHelpUrl(""); + this.updateShape_(); + }, + mutationToDom: Blockly.Blocks['ast_FunctionDef'].mutationToDom, + domToMutation: Blockly.Blocks['ast_FunctionDef'].domToMutation, + updateShape_: Blockly.Blocks['ast_FunctionDef'].updateShape_, + setReturnAnnotation_: Blockly.Blocks['ast_FunctionDef'].setReturnAnnotation_, +}; + +Blockly.Python['ast_Lambda'] = function (block) { + // Parameters + let parameters = new Array(block.parametersCount_); + for (let i = 0; i < block.parametersCount_; i++) { + parameters[i] = (Blockly.Python.valueToCode(block, 'PARAMETER' + i, Blockly.Python.ORDER_NONE) || + Blockly.Python.blank); + } + // Body + let body = Blockly.Python.valueToCode(block, 'BODY', Blockly.Python.ORDER_LAMBDA) || Blockly.Python.PASS; + return ["lambda " + parameters.join(', ') + ": " + body, Blockly.Python.ORDER_LAMBDA]; +}; + +BlockMirrorTextToBlocks.prototype['ast_Lambda'] = function (node) { + let args = node.args; + let body = node.body; + + let values = {'BODY': this.convert(body)}; + + let parsedArgs = 0; + if (args !== null) { + parsedArgs = this.parseArgs(args, values, node.lineno); + } + + return BlockMirrorTextToBlocks.create_block("ast_Lambda", node.lineno, {}, + values, + { + "inline": "false" + }, { + "@decorators": 0, + "@parameters": parsedArgs, + "@returns": false, + }); +}; \ No newline at end of file diff --git a/src/ast/ast_Nonlocal.js b/src/ast/ast_Nonlocal.js new file mode 100644 index 0000000..b72dd4f --- /dev/null +++ b/src/ast/ast_Nonlocal.js @@ -0,0 +1,85 @@ +Blockly.Blocks['ast_Nonlocal'] = { + init: function () { + this.setInputsInline(true); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(60); + this.setTooltip(""); + this.setHelpUrl(""); + this.nameCount_ = 1; + this.appendDummyInput('NONLOCAL') + .appendField("make nonlocal", "START_NONLOCALS"); + this.updateShape_(); + }, + updateShape_: function () { + let input = this.getInput("NONLOCAL"); + // Update pluralization + if (this.getField('START_NONLOCALS')) { + this.setFieldValue(this.nameCount_ > 1 ? "make nonlocals" : "make nonlocal", "START_NONLOCALS"); + } + // Update fields + for (var i = 0; i < this.nameCount_; i++) { + if (!this.getField('NAME' + i)) { + if (i !== 0) { + input.appendField(',').setAlign(Blockly.ALIGN_RIGHT); + } + input.appendField(new Blockly.FieldVariable("variable"), 'NAME' + i); + } + } + // Remove deleted fields. + while (this.getField('NAME' + i)) { + input.removeField('NAME' + i); + i++; + } + // Delete and re-add ending field + if (this.getField("END_NONLOCALS")) { + input.removeField("END_NONLOCALS"); + } + input.appendField("available", "END_NONLOCALS"); + }, + /** + * Create XML to represent list inputs. + * @return {!Element} XML storage element. + * @this Blockly.Block + */ + mutationToDom: function () { + var container = document.createElement('mutation'); + container.setAttribute('names', this.nameCount_); + return container; + }, + /** + * Parse XML to restore the list inputs. + * @param {!Element} xmlElement XML storage element. + * @this Blockly.Block + */ + domToMutation: function (xmlElement) { + this.nameCount_ = parseInt(xmlElement.getAttribute('names'), 10); + this.updateShape_(); + }, +}; + +Blockly.Python['ast_Nonlocal'] = function (block) { + // Create a list with any number of elements of any type. + let elements = new Array(block.nameCount_); + for (let i = 0; i < block.nameCount_; i++) { + elements[i] = Blockly.Python.variableDB_.getName(block.getFieldValue('NAME' + i), Blockly.Variables.NAME_TYPE); + } + return 'nonlocal ' + elements.join(', ') + "\n"; +}; + +BlockMirrorTextToBlocks.prototype['ast_Nonlocal'] = function (node) { + let names = node.names; + + let fields = {}; + for (var i = 0; i < names.length; i++) { + fields["NAME" + i] = Sk.ffi.remapToJs(names[i]); + } + + return BlockMirrorTextToBlocks.create_block("ast_Nonlocal", node.lineno, + fields, + {}, { + "inline": "true", + }, { + "@names": names.length + }); +}; \ No newline at end of file diff --git a/src/ast/ast_Return.js b/src/ast/ast_Return.js new file mode 100644 index 0000000..7f5686a --- /dev/null +++ b/src/ast/ast_Return.js @@ -0,0 +1,45 @@ +BlockMirrorTextToBlocks.BLOCKS.push({ + "type": "ast_ReturnFull", + "message0": "return %1", + "args0": [ + {"type": "input_value", "name": "VALUE"} + ], + "inputsInline": true, + "previousStatement": null, + "nextStatement": null, + "colour": 60, + "tooltip": "", + "helpUrl": "" +}); + +BlockMirrorTextToBlocks.BLOCKS.push({ + "type": "ast_Return", + "message0": "return", + "inputsInline": true, + "previousStatement": null, + "nextStatement": null, + "colour": 60, + "tooltip": "", + "helpUrl": "" +}); + +Blockly.Python['ast_Return'] = function (block) { + return "return\n"; +}; + +Blockly.Python['ast_ReturnFull'] = function (block) { + var value = Blockly.Python.valueToCode(block, 'VALUE', Blockly.Python.ORDER_ATOMIC) || Blockly.Python.blank; + return "return " + value + "\n"; +}; + +BlockMirrorTextToBlocks.prototype['ast_Return'] = function (node) { + let value = node.value; + + if (value == null) { + return BlockMirrorTextToBlocks.create_block("ast_Return", node.lineno); + } else { + return BlockMirrorTextToBlocks.create_block("ast_ReturnFull", node.lineno, {}, { + "VALUE": this.convert(value) + }); + } +}; \ No newline at end of file diff --git a/src/ast/ast_Try.js b/src/ast/ast_Try.js new file mode 100644 index 0000000..88c03ec --- /dev/null +++ b/src/ast/ast_Try.js @@ -0,0 +1,167 @@ +BlockMirrorTextToBlocks.HANDLERS_CATCH_ALL = 0; +BlockMirrorTextToBlocks.HANDLERS_NO_AS = 1; +BlockMirrorTextToBlocks.HANDLERS_COMPLETE = 3; + +Blockly.Blocks['ast_Try'] = { + init: function () { + this.handlersCount_ = 0; + this.handlers_ = []; + this.hasElse_ = false; + this.hasFinally_ = false; + this.appendDummyInput() + .appendField("try:"); + this.appendStatementInput("BODY") + .setCheck(null) + .setAlign(Blockly.ALIGN_RIGHT); + this.setInputsInline(true); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(230); + this.setTooltip(""); + this.setHelpUrl(""); + this.updateShape_(); + }, + // TODO: Not mutable currently + updateShape_: function () { + for (let i = 0; i < this.handlersCount_; i++) { + if (this.handlers_[i] === BlockMirrorTextToBlocks.HANDLERS_CATCH_ALL) { + this.appendDummyInput() + .appendField('except'); + } else { + this.appendValueInput("TYPE"+i) + .setCheck(null) + .appendField("except"); + if (this.handlers_[i] === BlockMirrorTextToBlocks.HANDLERS_COMPLETE) { + this.appendDummyInput() + .appendField("as") + .appendField(new Blockly.FieldVariable("item"), "NAME"+i); + } + } + this.appendStatementInput("HANDLER"+i) + .setCheck(null); + } + if (this.hasElse_) { + this.appendDummyInput() + .appendField("else:"); + this.appendStatementInput("ORELSE") + .setCheck(null); + } + if (this.hasFinally_) { + this.appendDummyInput() + .appendField("finally:"); + this.appendStatementInput("FINALBODY") + .setCheck(null); + } + }, + /** + * Create XML to represent the (non-editable) name and arguments. + * @return {!Element} XML storage element. + * @this Blockly.Block + */ + mutationToDom: function () { + let container = document.createElement('mutation'); + container.setAttribute('orelse', this.hasElse_); + container.setAttribute('finalbody', this.hasFinally_); + container.setAttribute('handlers', this.handlersCount_); + for (let i = 0; i < this.handlersCount_; i++) { + let parameter = document.createElement('arg'); + parameter.setAttribute('name', this.handlers_[i]); + container.appendChild(parameter); + } + return container; + }, + /** + * Parse XML to restore the (non-editable) name and parameters. + * @param {!Element} xmlElement XML storage element. + * @this Blockly.Block + */ + domToMutation: function (xmlElement) { + this.hasElse_ = "true" === xmlElement.getAttribute('orelse'); + this.hasFinally_ = "true" === xmlElement.getAttribute('finalbody'); + this.handlersCount_ = parseInt(xmlElement.getAttribute('handlers'), 10); + this.handlers_ = []; + for (let i = 0, childNode; childNode = xmlElement.childNodes[i]; i++) { + if (childNode.nodeName.toLowerCase() === 'arg') { + this.handlers_.push(parseInt(childNode.getAttribute('name'), 10)); + } + } + this.updateShape_(); + }, +}; + +Blockly.Python['ast_Try'] = function (block) { + // Try: + let body = Blockly.Python.statementToCode(block, 'BODY') || Blockly.Python.PASS; + // Except clauses + var handlers = new Array(block.handlersCount_); + for (let i = 0; i < block.handlersCount_; i++) { + let level = block.handlers_[i]; + let clause = "except"; + if (level !== BlockMirrorTextToBlocks.HANDLERS_CATCH_ALL) { + clause += " " + Blockly.Python.valueToCode(block, 'TYPE' + i, + Blockly.Python.ORDER_NONE) || Blockly.Python.blank; + if (level === BlockMirrorTextToBlocks.HANDLERS_COMPLETE) { + clause += " as " + Blockly.Python.variableDB_.getName(block.getFieldValue('NAME' + i), + Blockly.Variables.NAME_TYPE); + } + } + clause += ":\n" + (Blockly.Python.statementToCode(block, 'HANDLER' + i) || Blockly.Python.PASS); + handlers[i] = clause; + } + // Orelse: + let orelse = ""; + if (this.hasElse_) { + orelse = "else:\n" + (Blockly.Python.statementToCode(block, 'ORELSE') || Blockly.Python.PASS); + } + // Finally: + let finalbody = ""; + if (this.hasFinally_) { + finalbody = "finally:\n" + (Blockly.Python.statementToCode(block, 'FINALBODY') || Blockly.Python.PASS); + } + return "try:\n" + body + handlers.join("") + orelse + finalbody; +}; + +BlockMirrorTextToBlocks.prototype['ast_Try'] = function (node) { + let body = node.body; + let handlers = node.handlers; + let orelse = node.orelse; + let finalbody = node.finalbody; + + let fields = {}; + let values = {}; + let mutations = { + "@ORELSE": orelse !== null && orelse.length > 0, + "@FINALBODY": finalbody !== null && finalbody.length > 0, + "@HANDLERS": handlers.length + }; + + let statements = {"BODY": this.convertBody(body)}; + if (orelse !== null) { + statements['ORELSE'] = this.convertBody(orelse); + } + if (finalbody !== null && finalbody.length) { + statements['FINALBODY'] = this.convertBody(finalbody); + } + + let handledLevels = []; + for (let i = 0; i < handlers.length; i++) { + let handler = handlers[i]; + statements["HANDLER" + i] = this.convertBody(handler.body); + if (handler.type === null) { + handledLevels.push(BlockMirrorTextToBlocks.HANDLERS_CATCH_ALL); + } else { + values["TYPE" + i] = this.convert(handler.type); + if (handler.name === null) { + handledLevels.push(BlockMirrorTextToBlocks.HANDLERS_NO_AS); + } else { + handledLevels.push(BlockMirrorTextToBlocks.HANDLERS_COMPLETE); + fields["NAME" + i] = Sk.ffi.remapToJs(handler.name.id); + } + } + } + + mutations["ARG"] = handledLevels; + + return BlockMirrorTextToBlocks.create_block("ast_Try", node.lineno, fields, + values, {}, mutations, statements); +}; \ No newline at end of file diff --git a/src/ast/ast_While.js b/src/ast/ast_While.js new file mode 100644 index 0000000..ee636f7 --- /dev/null +++ b/src/ast/ast_While.js @@ -0,0 +1,83 @@ +Blockly.Blocks['ast_While'] = { + init: function () { + this.orelse_ = 0; + this.appendValueInput('TEST') + .appendField("while"); + this.appendStatementInput("BODY") + .setCheck(null) + .setAlign(Blockly.ALIGN_RIGHT); + this.setInputsInline(false); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setStyle("loop_blocks"); + this.setTooltip(""); + this.setHelpUrl(""); + this.updateShape_(); + }, + // TODO: Not mutable currently + updateShape_: function () { + let latestInput = "BODY"; + + if (this.orelse_ && !this.getInput('ELSE')) { + this.appendDummyInput('ORELSETEST') + .appendField("else:"); + this.appendStatementInput("ORELSEBODY") + .setCheck(null); + } else if (!this.orelse_ && this.getInput('ELSE')) { + block.removeInput('ORELSETEST'); + block.removeInput('ORELSEBODY'); + } + }, + /** + * Create XML to represent the (non-editable) name and arguments. + * @return {!Element} XML storage element. + * @this Blockly.Block + */ + mutationToDom: function () { + let container = document.createElement('mutation'); + container.setAttribute('orelse', this.orelse_); + }, + /** + * Parse XML to restore the (non-editable) name and parameters. + * @param {!Element} xmlElement XML storage element. + * @this Blockly.Block + */ + domToMutation: function (xmlElement) { + this.orelse_ = "true" === xmlElement.getAttribute('orelse'); + this.updateShape_(); + }, +}; + +Blockly.Python['ast_While'] = function (block) { + // Test + let test = "while " + (Blockly.Python.valueToCode(block, 'TEST', + Blockly.Python.ORDER_NONE) || Blockly.Python.blank) + ":\n"; + // Body: + let body = Blockly.Python.statementToCode(block, 'BODY') || Blockly.Python.PASS; + // Orelse: + let orelse = ""; + if (this.orelse_) { + orelse = "else:\n" + (Blockly.Python.statementToCode(block, 'ORELSEBODY') || Blockly.Python.PASS); + } + return test + body + orelse; +}; + +BlockMirrorTextToBlocks.prototype['ast_While'] = function (node) { + let test = node.test; + let body = node.body; + let orelse = node.orelse; + + let values = {"TEST": this.convert(test)}; + let statements = {"BODY": this.convertBody(body)}; + + let hasOrelse = false; + if (orelse !== null && orelse.length > 0) { + statements['ORELSEBODY'] = this.convertBody(orelse, false); + hasOrelse = true; + } + + return BlockMirrorTextToBlocks.create_block("ast_While", node.lineno, {}, + values, {}, { + "@orelse": hasOrelse + }, statements); +}; \ No newline at end of file diff --git a/src/ast/ast_With.js b/src/ast/ast_With.js new file mode 100644 index 0000000..9d9fd47 --- /dev/null +++ b/src/ast/ast_With.js @@ -0,0 +1,141 @@ +BlockMirrorTextToBlocks.BLOCKS.push({ + "type": "ast_WithItem", + "output": "WithItem", + "message0": "context %1", + "args0": [{"type": "input_value", "name": "CONTEXT"}], + "enableContextMenu": false, + "colour": 60, + "inputsInline": false, +}); +Blockly.Python["ast_WithItem"] = function (block) { + let context = Blockly.Python.valueToCode(block, 'CONTEXT', + Blockly.Python.ORDER_NONE) || Blockly.Python.blank; + return [context, Blockly.Python.ORDER_NONE]; +}; +BlockMirrorTextToBlocks.BLOCKS.push({ + "type": "ast_WithItemAs", + "output": "WithItem", + "message0": "context %1 as %2", + "args0": [{"type": "input_value", "name": "CONTEXT"}, + {"type": "input_value", "name": "AS"}], + "enableContextMenu": false, + "colour": 60, + "inputsInline": true, +}); +Blockly.Python["ast_WithItemAs"] = function (block) { + let context = Blockly.Python.valueToCode(block, 'CONTEXT', + Blockly.Python.ORDER_NONE) || Blockly.Python.blank; + let as = Blockly.Python.valueToCode(block, 'AS', + Blockly.Python.ORDER_NONE) || Blockly.Python.blank; + return [context + " as " + as, Blockly.Python.ORDER_NONE]; +}; + +Blockly.Blocks['ast_With'] = { + init: function () { + this.appendValueInput('ITEM0') + .appendField("with"); + this.appendStatementInput("BODY") + .setCheck(null); + this.itemCount_ = 1; + this.renames_ = [false]; + this.setInputsInline(false); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setTooltip(""); + this.setHelpUrl(""); + this.setColour(60); + this.updateShape_(); + }, + /** + * Create XML to represent list inputs. + * @return {!Element} XML storage element. + * @this Blockly.Block + */ + mutationToDom: function () { + var container = document.createElement('mutation'); + container.setAttribute('items', this.itemCount_); + for (let i = 0; i < this.itemCount_; i++) { + let parameter = document.createElement('as'); + parameter.setAttribute('name', this.renames_[i]); + container.appendChild(parameter); + } + return container; + }, + /** + * Parse XML to restore the list inputs. + * @param {!Element} xmlElement XML storage element. + * @this Blockly.Block + */ + domToMutation: function (xmlElement) { + this.itemCount_ = parseInt(xmlElement.getAttribute('items'), 10); + this.renames_ = []; + for (let i = 0, childNode; childNode = xmlElement.childNodes[i]; i++) { + if (childNode.nodeName.toLowerCase() === 'as') { + this.renames_.push("true" === childNode.getAttribute('name')); + } + } + this.updateShape_(); + }, + updateShape_: function () { + // With clauses + for (let i = 1; i < this.itemCount_; i++) { + let input = this.getInput('ITEM' + i); + if (!input) { + input = this.appendValueInput('ITEM' + i); + } + } + // Remove deleted inputs. + while (this.getInput('ITEM' + i)) { + this.removeInput('ITEM' + i); + i++; + } + // Reposition everything + for (let i = 0; i < this.itemCount_; i++) { + this.moveInputBefore('ITEM' + i, 'BODY'); + } + }, +}; + +Blockly.Python['ast_With'] = function (block) { + // Contexts + let items = new Array(block.itemCount_); + for (let i = 0; i < block.itemCount_; i++) { + items[i] = (Blockly.Python.valueToCode(block, 'ITEM' + i, Blockly.Python.ORDER_NONE) || + Blockly.Python.blank); + } + // Body + let body = Blockly.Python.statementToCode(block, 'BODY') || Blockly.Python.PASS; + return "with " + items.join(', ') + ":\n" + body; +}; + +BlockMirrorTextToBlocks.prototype['ast_With'] = function (node) { + let items = node.items; + let body = node.body; + + let values = {}; + let mutations = {"@items": items.length}; + + let renamedItems = []; + for (let i = 0; i < items.length; i++) { + let hasRename = items[i].optional_vars; + renamedItems.push(hasRename); + let innerValues = {'CONTEXT':this.convert(items[i].context_expr)}; + if (hasRename) { + innerValues['AS'] = this.convert(items[i].optional_vars); + values['ITEM'+i] = BlockMirrorTextToBlocks.create_block("ast_WithItemAs", node.lineno, + {}, innerValues);; + } else { + values['ITEM'+i] = BlockMirrorTextToBlocks.create_block("ast_WithItem", node.lineno, + {}, innerValues);; + } + } + mutations['as'] = renamedItems; + + return BlockMirrorTextToBlocks.create_block("ast_With", node.lineno, {}, + values, + { + "inline": "false" + }, mutations, { + 'BODY': this.convertBody(body) + }); +}; diff --git a/src/ast/ast_Yield.js b/src/ast/ast_Yield.js new file mode 100644 index 0000000..c3d2b2c --- /dev/null +++ b/src/ast/ast_Yield.js @@ -0,0 +1,43 @@ +BlockMirrorTextToBlocks.BLOCKS.push({ + "type": "ast_YieldFull", + "message0": "yield %1", + "args0": [ + {"type": "input_value", "name": "VALUE"} + ], + "inputsInline": false, + "output": null, + "colour": 60, + "tooltip": "", + "helpUrl": "" +}); + +BlockMirrorTextToBlocks.BLOCKS.push({ + "type": "ast_Yield", + "message0": "yield", + "inputsInline": false, + "output": null, + "colour": 60, + "tooltip": "", + "helpUrl": "" +}); + +Blockly.Python['ast_Yield'] = function (block) { + return ["yield", Blockly.Python.ORDER_LAMBDA]; +}; + +Blockly.Python['ast_YieldFull'] = function (block) { + var value = Blockly.Python.valueToCode(block, 'VALUE', Blockly.Python.ORDER_LAMBDA) || Blockly.Python.blank; + return ["yield " + value, Blockly.Python.ORDER_LAMBDA]; +}; + +BlockMirrorTextToBlocks.prototype['ast_Yield'] = function (node) { + let value = node.value; + + if (value == null) { + return BlockMirrorTextToBlocks.create_block("ast_Yield", node.lineno); + } else { + return BlockMirrorTextToBlocks.create_block("ast_YieldFull", node.lineno, {}, { + "VALUE": this.convert(value) + }); + } +}; \ No newline at end of file diff --git a/src/ast/ast_YieldFrom.js b/src/ast/ast_YieldFrom.js new file mode 100644 index 0000000..d70470b --- /dev/null +++ b/src/ast/ast_YieldFrom.js @@ -0,0 +1,25 @@ +BlockMirrorTextToBlocks.BLOCKS.push({ + "type": "ast_YieldFrom", + "message0": "yield from %1", + "args0": [ + {"type": "input_value", "name": "VALUE"} + ], + "inputsInline": false, + "output": null, + "colour": 60, + "tooltip": "", + "helpUrl": "" +}); + +Blockly.Python['ast_YieldFrom'] = function (block) { + var value = Blockly.Python.valueToCode(block, 'VALUE', Blockly.Python.ORDER_LAMBDA) || Blockly.Python.blank; + return ["yield from " + value, Blockly.Python.ORDER_LAMBDA]; +}; + +BlockMirrorTextToBlocks.prototype['ast_YieldFrom'] = function (node) { + let value = node.value; + + return BlockMirrorTextToBlocks.create_block("ast_YieldFrom", node.lineno, {}, { + "VALUE": this.convert(value) + }); +}; \ No newline at end of file diff --git a/src/ast/todo.md b/src/ast/todo.md index 81c61f4..7029471 100644 --- a/src/ast/todo.md +++ b/src/ast/todo.md @@ -1,21 +1,8 @@ -[ ] FunctionDef -[ ] Param -[ ] ClassDef -[ ] With -[ ] Lambda -[ ] Return -[ ] Yield -[ ] YieldFrom -[ ] If -[ ] Import -[ ] ImportFrom -[ ] Global -[ ] Nonlocal -[ ] Try -[ ] ExceptHandler -[ ] While -[ ] Break -[ ] Continue +[X] If +[X] Import +[X] ImportFrom +[X] While +[X] With [-] AsyncFor [-] AsyncFunctionDef [-] AsyncWith @@ -25,6 +12,8 @@ [-] Bytes [-] Ellipsis [-] Expression +[-] Nonlocal +[-] Param [X] Add [X] And [X] Assert @@ -36,19 +25,25 @@ [X] BitOr [X] BitXor [X] BoolOp +[X] Break [X] Call +[X] ClassDef [X] Compare +[X] Continue [X] Del [X] Delete [X] Dict [X] DictComp [X] Div [X] Eq +[X] ExceptHandler [X] Expr [X] ExtSlice [X] FloorDiv [X] For +[X] FunctionDef [X] GeneratorExp +[X] Global [X] Gt [X] GtE [X] IfExp @@ -59,6 +54,7 @@ [X] Is [X] IsNot [X] LShift +[X] Lambda [X] List [X] ListComp [X] Load @@ -79,6 +75,7 @@ [X] Pow [X] RShift [X] Raise +[X] Return [X] Set [X] SetComp [X] Slice @@ -88,7 +85,10 @@ [X] Sub [X] Subscript [X] Suite +[X] Try [X] Tuple [X] UAdd [X] USub -[X] UnaryOp \ No newline at end of file +[X] UnaryOp +[X] Yield +[X] YieldFrom \ No newline at end of file diff --git a/test/simple_dev.html b/test/simple_dev.html index f0252af..7fecde0 100644 --- a/test/simple_dev.html +++ b/test/simple_dev.html @@ -32,6 +32,8 @@ + + @@ -58,6 +60,18 @@ + + + + + + + + + + + + @@ -172,7 +186,28 @@ `def alpha(beta: str, gamma=True, delta: int=0):\n pass`, `@route\n@open('test')\ndef alpha(beta: str, gamma=True, delta: int=0):\n pass`, `@route\n@open('test')\ndef alpha(beta: str, gamma=True, delta: int=0, *args, k=4, num: int=3, **kwargs):\n a = 0\n b = 7`, - "def do_something(a:int) -> str:\n assert (4 == 3)" + "def do_something(a: int) -> str:\n assert (4 == 3)", + 'lambda x, y=0: x + y', + "def simple(a, b, c) -> int:\n return 'Hello world!'\n return", + "def simple(a, b, c) -> int:\n (yield 'Hello world!')\n (yield)\n dog = yield b + 4", + "def simple(a, b, c) -> int:\n (yield from 'Hello world!')\n dog = yield from b + 4", + "def simple(a, b, c) -> int:\n global alpha\ndef another(e, f):\n global alpha, beta, gamma", + //"def simple(a, b, c) -> int:\n def inner():\n nonlocal alpha\n nonlocal alpha, beta, gamma", + "for x in y:\n break\n continue", + ("try:\n pass\nexcept NameError:\n pass" + + "\nexcept Something() as other:\n pass\n" + + "except None as some:\n pass\nexcept:\n " + + "pass\nelse:\n pass\nfinally:\n pass"), + "try:\n a = 0\nexcept:\n return", + "@whatever\nclass a(b, *d, c=0, **e):\n a = 0\nclass Dog:\n age = 1\n name = 'Ada'", + "if x:\n pass\nif y:\n pass\nelse:\n pass", + "if a:\n if j:\n pass\n elif k:\n pass\n else:\n pass\nelif b:\n pass", + "while x == 0:\n pass\nwhile y < z:\n a = 0\nelse:\n b = a", + "import x as y, b as c, d\nimport os", + "from . import x\nfrom .os import y\nfrom ..path import z\nfrom dog.house import a\nfrom cat import b, c as d, e", + "import matplotlib.pyplot as plt", + "with open('filename') as outfile:\n pass", + "with open('filename') as outfile, open('file2') as infile, other_context:\n pass", ]; for (var i = 0; i < TESTS.length; i += 1) {