/** * An object for converting Python source code to the * Blockly XML representation. * * @constructor * @this {PythonToBlocks} */ function PythonToBlocks() { } function xmlToString(xml) { return new XMLSerializer().serializeToString(xml); } PythonToBlocks.prototype.convertSourceToCodeBlock = function(python_source) { var xml = document.createElement("xml"); xml.appendChild(raw_block(python_source)); return xmlToString(xml); } /** * The main function for converting a string representation of Python * code to the Blockly XML representation. * * @param {string} python_source - The string representation of Python * code (e.g., "a = 0"). * @returns {Object} An object which will either have the converted * source code or an error message and the code as a code-block. */ PythonToBlocks.prototype.convertSource = function(python_source) { var xml = document.createElement("xml"); if (python_source.trim() === "") { return {"xml": xmlToString(xml), "error": null}; } this.source = python_source.split("\n"); var filename = 'user_code.py'; // Attempt parsing - might fail! var parse, ast, symbol_table, error; try { parse = Sk.parse(filename, python_source); ast = Sk.astFromParse(parse.cst, filename, parse.flags); //symbol_table = Sk.symboltable(ast, filename, python_source, filename, parse.flags); } catch (e) { error = e; xml.appendChild(raw_block(python_source)) return {"xml": xmlToString(xml), "error": error}; } this.comments = {}; for (var commentLocation in parse.comments) { var lineColumn = commentLocation.split(","); var yLocation = parseInt(lineColumn[0], 10); this.comments[yLocation] = parse.comments[commentLocation]; } this.highestLineSeen = 0; this.levelIndex = 0; this.nextExpectedLine = 0; this.measureNode(ast); var converted = this.convert(ast); if (converted !== null) { for (var block = 0; block < converted.length; block+= 1) { xml.appendChild(converted[block]); } } return {"xml": xmlToString(xml), "error": null, "lineMap": this.lineMap, 'comment': this.comments}; } PythonToBlocks.prototype.identifier = function(node) { return Sk.ffi.remapToJs(node); } PythonToBlocks.prototype.recursiveMeasure = function(node, nextBlockLine) { if (node === undefined) { return; } var myNext = nextBlockLine; if ("orelse" in node && node.orelse.length > 0) { if (node.orelse.length == 1 && node.orelse[0]._astname == "If") { myNext = node.orelse[0].lineno-1; } else { myNext = node.orelse[0].lineno-1-1; } } this.heights.push(nextBlockLine); if ("body" in node) { for (var i = 0; i < node.body.length; i++) { var next; if (i+1 == node.body.length) { next = myNext; } else { next = node.body[i+1].lineno-1; } this.recursiveMeasure(node.body[i], next); } } if ("orelse" in node) { for (var i = 0; i < node.orelse.length; i++) { var next; if (i == node.orelse.length) { next = nextBlockLine; } else { next = 1+(node.orelse[i].lineno-1); } this.recursiveMeasure(node.orelse[i], next); } } } PythonToBlocks.prototype.measureNode = function(node) { this.heights = []; this.recursiveMeasure(node, this.source.length-1); this.heights.shift(); } PythonToBlocks.prototype.getSourceCode = function(frm, to) { var lines = this.source.slice(frm-1, to); // Strip out any starting indentation. if (lines.length > 0) { var indentation = lines[0].search(/\S/); for (var i = 0; i < lines.length; i++) { lines[i] = lines[i].substring(indentation); } } return lines.join("\n"); } PythonToBlocks.prototype.convertBody = function(node, is_top_level) { this.levelIndex += 1; // Empty body, return nothing if (node.length == 0) { return null; } // Final result list var children = [], // The complete set of peers root = null, // The top of the current peer current = null, // The bottom of the current peer levelIndex = this.levelIndex; function addPeer(peer) { if (root == null) { children.push(peer); } else { children.push(root); } root = peer; current = peer; } function finalizePeers() { if (root != null) { children.push(root); } } function nestChild(child) { if (root == null) { root = child; current = child; } else if (current == null) { root = current; } else { var nextElement = document.createElement("next"); nextElement.appendChild(child); current.appendChild(nextElement); current = child; } } var lineNumberInBody = 0, lineNumberInProgram, previousLineInProgram=null, distance, skipped_line, commentCount, previousHeight = null, previousWasStatement = false; visitedFirstLine = false; // Iterate through each node for (var i = 0; i < node.length; i++) { lineNumberInBody += 1; lineNumberInProgram = node[i].lineno; distance = 0, wasFirstLine = true; if (previousLineInProgram != null) { distance = lineNumberInProgram - previousLineInProgram-1; wasFirstLine = false; } lineNumberInBody += distance; // Handle earlier comments commentCount = 0; for (var commentLineInProgram in this.comments) { if (commentLineInProgram < lineNumberInProgram) { commentChild = this.Comment(this.comments[commentLineInProgram], commentLineInProgram); if (previousLineInProgram == null) { nestChild(commentChild); } else { skipped_previous_line = Math.abs(previousLineInProgram-commentLineInProgram) > 1; if (is_top_level && skipped_previous_line) { addPeer(commentChild); } else { nestChild(commentChild); } } previousLineInProgram = commentLineInProgram; this.highestLineSeen = Math.max(this.highestLineSeen, parseInt(commentLineInProgram, 10)); distance = lineNumberInProgram - previousLineInProgram; delete this.comments[commentLineInProgram]; commentCount += 1; } } distance = lineNumberInProgram - this.highestLineSeen; this.highestLineSeen = Math.max(lineNumberInProgram, this.highestLineSeen); // Now convert the actual node var height = this.heights.shift(); var originalSourceCode = this.getSourceCode(lineNumberInProgram, height); var newChild = this.convertStatement(node[i], originalSourceCode, is_top_level); // Skip null blocks (e.g., imports) if (newChild == null) { continue; } skipped_line = distance > 1; previousLineInProgram = lineNumberInProgram; previousHeight = height; // Handle top-level expression blocks if (is_top_level && newChild.constructor == Array) { addPeer(newChild[0]); // Handle skipped line } else if (is_top_level && skipped_line && visitedFirstLine) { addPeer(newChild); // The previous line was not a Peer } else if (is_top_level && !previousWasStatement) { addPeer(newChild); // Otherwise, always embed it in there. } else { nestChild(newChild); } previousWasStatement = newChild.constructor !== Array; visitedFirstLine = true; } // Handle comments that are on the very last line var lastLineNumber = lineNumberInProgram+1 if (lastLineNumber in this.comments) { commentChild = this.Comment(this.comments[lastLineNumber], lastLineNumber); nestChild(commentChild); delete this.comments[lastLineNumber]; } // Handle any extra comments that stuck around if (is_top_level) { for (var commentLineInProgram in this.comments) { commentChild = this.Comment(this.comments[commentLineInProgram], commentLineInProgram); distance = commentLineInProgram - previousLineInProgram; if (previousLineInProgram == null) { addPeer(commentChild); } else if (distance > 1) { addPeer(commentChild); } else { nestChild(commentChild); } previousLineInProgram = commentLineInProgram; delete this.comments[lastLineNumber]; } } finalizePeers(); this.levelIndex -= 1; return children; } function block(type, lineNumber, fields, values, settings, mutations, statements) { var newBlock = document.createElement("block"); // Settings newBlock.setAttribute("type", type); newBlock.setAttribute("line_number", lineNumber); for (var setting in settings) { var settingValue = settings[setting]; newBlock.setAttribute(setting, settingValue); } // Mutations if (mutations !== undefined && Object.keys(mutations).length > 0) { var newMutation = document.createElement("mutation"); for (var mutation in mutations) { var mutationValue = mutations[mutation]; if (mutation.charAt(0) == '@') { newMutation.setAttribute(mutation.substr(1), mutationValue); } else if (mutationValue != null && mutationValue.constructor === Array) { for (var i = 0; i < mutationValue.length; i++) { var mutationNode = document.createElement(mutation); mutationNode.setAttribute("name", mutationValue[i]); newMutation.appendChild(mutationNode); } } else { var mutationNode = document.createElement("arg"); if (mutation.charAt(0) == '*') { mutationNode.setAttribute("name", ""); } else { mutationNode.setAttribute("name", mutation); } if (mutationValue !== null) { mutationNode.appendChild(mutationValue); } newMutation.appendChild(mutationNode); } } newBlock.appendChild(newMutation); } // Fields for (var field in fields) { var fieldValue = fields[field]; var newField = document.createElement("field"); newField.setAttribute("name", field); newField.appendChild(document.createTextNode(fieldValue)); newBlock.appendChild(newField); } // Values for (var value in values) { var valueValue = values[value]; var newValue = document.createElement("value"); if (valueValue !== null) { newValue.setAttribute("name", value); newValue.appendChild(valueValue); newBlock.appendChild(newValue); } } // Statements if (statements !== undefined && Object.keys(statements).length > 0) { for (var statement in statements) { var statementValue = statements[statement]; if (statementValue == null) { continue; } else { for (var i = 0; i < statementValue.length; i += 1) { // In most cases, you really shouldn't ever have more than // one statement in this list. I'm not sure Blockly likes // that. var newStatement = document.createElement("statement"); newStatement.setAttribute("name", statement); newStatement.appendChild(statementValue[i]); newBlock.appendChild(newStatement); } } } } return newBlock; } raw_block = function(txt) { // TODO: lineno as second parameter! return block("raw_block", 0, { "TEXT": txt }); } raw_expression = function(txt, lineno) { return block("raw_expression", lineno, {"TEXT": txt}); } PythonToBlocks.prototype.convert = function(node, is_top_level) { return this[node._astname](node, is_top_level); } function arrayMax(array) { return array.reduce(function(a, b) { return Math.max(a, b); }); } function arrayMin(array) { return array.reduce(function(a, b) { return Math.min(a, b); }); } PythonToBlocks.prototype.convertStatement = function(node, full_source, is_top_level) { try { return this.convert(node, is_top_level); } catch (e) { heights = this.getChunkHeights(node); extractedSource = this.getSourceCode(arrayMin(heights), arrayMax(heights)); console.error(e); return raw_block(extractedSource); } } PythonToBlocks.prototype.getChunkHeights = function(node) { var lineNumbers = []; if (node.hasOwnProperty("lineno")) { lineNumbers.push(node.lineno); } if (node.hasOwnProperty("body")) { for (var i = 0; i < node.body.length; i += 1) { var subnode = node.body[i]; lineNumbers = lineNumbers.concat(this.getChunkHeights(subnode)); } } if (node.hasOwnProperty("orelse")) { for (var i = 0; i < node.orelse.length; i += 1) { var subnode = node.orelse[i]; lineNumbers = lineNumbers.concat(this.getChunkHeights(subnode)); } } return lineNumbers; } /* ----- Nodes ---- */ /* * NO LINE OR COLUMN NUMBERS * Module * body: asdl_seq */ PythonToBlocks.prototype.Module = function(node) { return this.convertBody(node.body, true); } PythonToBlocks.prototype.Comment = function(txt, lineno) { return block("comment_single", lineno, { "BODY": txt.slice(1) }, {}, {}, {}, {}) } /* * NO LINE OR COLUMN NUMBERS * Interactive * body: asdl_seq */ PythonToBlocks.prototype.Interactive = function(body) { return this.convertBody(node.body); } /* * NO LINE OR COLUMN NUMBERS * TODO * body: expr_ty */ PythonToBlocks.prototype.Expression = function(body) { this.body = body; } /* * NO LINE OR COLUMN NUMBERS * * body: asdl_seq */ PythonToBlocks.prototype.Suite = function(body) { this.asdl_seq(node.body); } /* * * name: identifier * args: arguments__ty * body: asdl_seq * decorator_list: asdl_seq */ PythonToBlocks.prototype.FunctionDef = function(node) { var name = node.name; var args = node.args; var body = node.body; var decorator_list = node.decorator_list; if (decorator_list.length > 0) { throw new Error("Decorators are not implemented."); } return block("procedures_defnoreturn", node.lineno, { "NAME": this.identifier(name) }, { }, { "inline": "false" }, { "arg": this.arguments_(args) }, { "STACK": this.convertBody(body) }); } /* * name: identifier * args: arguments__ty * bases: asdl_seq * body: asdl_seq * decorator_list: asdl_seq */ PythonToBlocks.prototype.ClassDef = function(node) { var name = node.name; var bases = node.bases; var body = node.body; var decorator_list = node.decorator_list; if (decorator_list.length > 0) { throw new Error("Decorators are not implemented."); } return block("class_creation", node.lineno, { "CLASS": this.identifier(name) }, { }, { "inline": "false" }, { //"arg": this.arguments_(args) }, { "BODY": this.convertBody(body) }); } /* * value: expr_ty * */ PythonToBlocks.prototype.Return = function(node) { var value = node.value; // No field, one title, one setting return block("procedures_return", node.lineno, {}, { "VALUE": this.convert(value) }, { "inline": "false" }); } /* * targets: asdl_seq * */ PythonToBlocks.prototype.Delete = function(/* {asdl_seq *} */ targets) { this.targets = targets; // TODO throw new Error("Delete is not implemented"); } /* * targets: asdl_seq * value: expr_ty */ PythonToBlocks.prototype.Assign = function(node) { var targets = node.targets; var value = node.value; if (targets.length == 0) { throw new Error("Nothing to assign to!"); } else if (targets.length == 1) { //alert(node.targets[0].id.v); //alert(JSON.stringify(node.value.func));//&&node.value.id.v == "turtle.Turtle()" =="turtle" if(value.func!=null && targets[0].id!= null ){ if(value.func.value.id.v == "turtle" && value.func.attr.v=="Turtle" && value.args.length==0) return block("turtle_create", node.lineno, {}, {}); }else { return block("variables_set", node.lineno, { "VAR": this.Name_str(targets[0]) //targets }, { "VALUE": this.convert(value) }); } } else { //TODO throw new Error("Multiple Assigment Targets Not implemented"); } } /* * target: expr_ty * op: operator_ty * value: expr_ty */ PythonToBlocks.prototype.AugAssign = function(node) { var target = node.target; var op = node.op; var value = node.value; if (op.name != "Add") { //TODO throw new Error("Only addition is currently supported for augmented assignment!"); } else { return block("math_change", node.lineno, { "VAR": this.Name_str(target) }, { "DELTA": this.convert(value) }); } } /* * dest: expr_ty * values: asdl_seq * nl: bool * */ PythonToBlocks.prototype.Print = function(node) { var dest = node.dest; var values = node.values; var nl = node.nl; if (values.length == 1) { return block("text_print", node.lineno, {}, { "TEXT": this.convert(values[0]) }); } else { return block("esp32_main_controller_text_print", node.lineno, {}, this.convertElements("PRINT", values), { "inline": "true" }, { "@items": values.length }); } } /* * target: expr_ty * iter: expr_ty * body: asdl_seq * orelse: asdl_seq * */ PythonToBlocks.prototype.For = function(node) { var target = node.target; var iter = node.iter; var body = node.body; var orelse = node.orelse; if (orelse.length > 0) { // TODO throw new Error("Or-else block of For is not implemented."); } return block("controls_forEach", node.lineno, { }, { "LIST": this.convert(iter), "VAR": this.convert(target) }, { "inline": "true" }, {}, { "DO": this.convertBody(body) }); } /* * test: expr_ty * body: asdl_seq * orelse: asdl_seq */ PythonToBlocks.prototype.While = function(node) { var test = node.test; var body = node.body; var orelse = node.orelse; if (orelse.length > 0) { // TODO throw new Error("Or-else block of While is not implemented."); } return block("controls_while", node.lineno, {}, { "BOOL": this.convert(test) }, {}, {}, { "DO": this.convertBody(body) }); } /* * test: expr_ty * body: asdl_seq * orelse: asdl_seq * */ PythonToBlocks.prototype.If = function(node) { var test = node.test; var body = node.body; var orelse = node.orelse; var IF_values = {"IF0": this.convert(test)}; var DO_values = {"DO0": this.convertBody(body)}; var elseifCount = 0; var elseCount = 0; var potentialElseBody = null; // Handle weird orelse stuff if (orelse !== undefined) { if (orelse.length == 1 && orelse[0]._astname == "If") { // This is an 'ELIF' while (orelse.length == 1 && orelse[0]._astname == "If") { this.heights.shift(); elseifCount += 1; body = orelse[0].body; test = orelse[0].test; orelse = orelse[0].orelse; DO_values["DO"+elseifCount] = this.convertBody(body, false); if (test !== undefined) { IF_values["IF"+elseifCount] = this.convert(test); } } } if (orelse !== undefined && orelse.length > 0) { // Or just the body of an Else statement elseCount += 1; DO_values["ELSE"] = this.convertBody(orelse); } } return block("controls_if_better", node.lineno, { }, IF_values, { "inline": "false" }, { "@elseif": elseifCount, "@else": elseCount }, DO_values); } /* * context_expr: expr_ty * optional_vars: expr_ty * body: asdl_seq */ PythonToBlocks.prototype.With = function(node) { var context_expr = node.context_expr; var optional_vars = node.optional_vars; var body = node.body; throw new Error("With_ not implemented"); } /* * type: expr_ty * inst: expr_ty * tback: expr_ty */ PythonToBlocks.prototype.Raise = function(node) { var type = node.type; var inst = node.inst; var tback = node.tback; throw new Error("Raise not implemented"); } /* * body: asdl_seq * handlers: asdl_seq * orelse: asdl_seq * */ PythonToBlocks.prototype.TryExcept = function(node) { var body = node.body; var handlers = node.handlers; var orelse = node.orelse; throw new Error("TryExcept not implemented"); } /* * body: asdl_seq * finalbody: asdl_seq * */ PythonToBlocks.prototype.TryFinally = function(node) { var body = node.body; var finalbody = node.finalbody; throw new Error("TryExcept not implemented"); } /* * test: expr_ty * msg: expr_ty */ PythonToBlocks.prototype.Assert = function(node) { var test = node.test; var msg = node.msg; throw new Error("Assert not implemented"); } /* * names: asdl_seq * */ PythonToBlocks.prototype.Import = function(node) { var names = node.names; // The import statement isn't used in blockly because it happens implicitly return null; } /* * module: identifier * names: asdl_seq * level: int * */ PythonToBlocks.prototype.ImportFrom = function(node) { var module = node.module; var names = node.names; var level = node.level; // The import statement isn't used in blockly because it happens implicitly return null; } /* * body: expr_ty * globals: expr_ty * locals: expr_ty * */ PythonToBlocks.prototype.Exec = function(node) { var body = node.body; var globals = node.globals; var locals = node.locals; throw new Error("Exec not implemented"); } /* * names: asdl_seq * */ PythonToBlocks.prototype.Global = function(node) { var names = node.names; throw new Error("Globals not implemented"); } /* * value: expr_ty * */ PythonToBlocks.prototype.Expr = function(node, is_top_level) { var value = node.value; var converted = this.convert(value); if (converted.constructor == Array) { return converted[0]; } else if (is_top_level === true) { return [this.convert(value)]; } else { return block("raw_empty", node.lineno, {}, { "VALUE": this.convert(value) }); } } /* * * */ PythonToBlocks.prototype.Pass = function() { return null; //block("controls_pass"); } /* * * */ PythonToBlocks.prototype.Break = function(node) { return block("controls_flow_statements", node.lineno, { "FLOW": "BREAK" }); } /* * * */ PythonToBlocks.prototype.Continue = function(node) { return block("controls_flow_statements", node.lineno, { "FLOW": "CONTINUE" }); } /* * TODO: what does this do? * */ PythonToBlocks.prototype.Debugger = function() { return null; } PythonToBlocks.prototype.booleanOperator = function(op) { switch (op.name) { case "And": return "AND"; case "Or": return "OR"; default: throw new Error("Operator not supported:"+op.name); } } /* * op: boolop_ty * values: asdl_seq */ PythonToBlocks.prototype.BoolOp = function(node) { var op = node.op; var values = node.values; // TODO: is there ever a case where it's < 1 values? var result_block = this.convert(values[0]); for (var i = 1; i < values.length; i+= 1) { result_block = block("logic_operation", node.lineno, { "OP": this.booleanOperator(op) }, { "A": result_block, "B": this.convert(values[i]) }, { "inline": "true" }); } return result_block; } PythonToBlocks.prototype.binaryOperator = function(op) { switch (op.name) { case "Add": return "ADD"; case "Sub": return "MINUS"; case "Div": case "FloorDiv": return "DIVIDE"; case "Mult": return "MULTIPLY"; case "Pow": return "POWER"; case "Mod": return "MODULO"; default: throw new Error("Operator not supported:"+op.name); } } /* * left: expr_ty * op: operator_ty * right: expr_ty */ PythonToBlocks.prototype.BinOp = function(node) { var left = node.left; var op = node.op; var right = node.right; return block("math_arithmetic", node.lineno, { "OP": this.binaryOperator(op) // TODO }, { "A": this.convert(left), "B": this.convert(right) }, { "inline": true }); } /* * op: unaryop_ty * operand: expr_ty */ PythonToBlocks.prototype.UnaryOp = function(node) { var op = node.op; var operand = node.operand; if (op.name == "Not") { return block("logic_negate", node.lineno, {}, { "BOOL": this.convert(operand) }, { "inline": "false" }); } else { throw new Error("Other unary operators are not implemented yet."); } } /* * args: arguments__ty * body: expr_ty */ PythonToBlocks.prototype.Lambda = function(node) { var args = node.args; var body = node.body; throw new Error("Lambda functions are not implemented yet."); } /* * test: expr_ty * body: expr_ty * orelse: expr_ty */ PythonToBlocks.prototype.IfExp = function(node) { var test = node.test; var body = node.body; var orelse = node.orelse; throw new Error("Inline IF expressions are not implemented yet."); } /* * keys: asdl_seq * values: asdl_seq */ PythonToBlocks.prototype.Dict = function(node) { var keys = node.keys; var values = node.values; var keyList = []; var valueList = []; for (var i = 0; i < keys.length; i+= 1) { if (keys[i]._astname != "Str") { throw new Error("Dictionary Keys should be Strings."); } keyList["KEY"+i] = this.Str_value(keys[i]); valueList["VALUE"+i] = this.convert(values[i]); } return block("dicts_create_with", node.lineno, keyList, valueList, { "inline": "false" }, { "@items": keys.length }); } /* * elts: asdl_seq * */ PythonToBlocks.prototype.Set = function(node) { var elts = node.elts; var ctx = node.ctx; return block("set_create", node.lineno, {}, this.convertElements("ADD", elts) , { "inline": elts.length > 3 ? "false" : "true", }, { "@items": elts.length }); } /* * elt: expr_ty * generators: asdl_seq */ PythonToBlocks.prototype.ListComp = function(node) { var elt = node.elt; var generators = node.generators; // TODO } /* * elt: expr_ty * generators: asdl_seq */ PythonToBlocks.prototype.SetComp = function(node) { var elt = node.elt; var generators = node.generators; throw new Error("Set Comprehensions are not implemented"); } /* * key: expr_ty * value: expr_ty * generators: asdl_seq */ PythonToBlocks.prototype.DictComp = function(node) { var key = node.key; var value = node.value; var generators = node.generators; throw new Error("Dictionary Comprehensions are not implemented"); } /* * elt: expr_ty * generators: asdl_seq */ PythonToBlocks.prototype.GeneratorExp = function(node) { var elt = node.elt; var generators = node.generators; throw new Error("Generator Expresions are not implemented"); } /* * value: expr_ty * */ PythonToBlocks.prototype.Yield = function(node) { var value = value; throw new Error("Yield expression is not implemented"); } PythonToBlocks.prototype.compareOperator = function(op) { switch (op.name) { case "Eq": return "EQ"; case "NotEq": return "NEQ"; case "Lt": return "LT"; case "Gt": return "GT"; case "LtE": return "LTE"; case "GtE": return "GTE"; case "In_": return "IN"; case "NotIn": return "NOTIN"; // Is, IsNot, In, NotIn default: throw new Error("Operator not supported:"+op.name); } } /* * left: expr_ty * ops: asdl_int_seq * asdl_seq: comparators */ PythonToBlocks.prototype.Compare = function(node) { var left = node.left; var ops = node.ops; var comparators = node.comparators; if (ops.length != 1) { throw new Error("Only one comparison operator is supported"); } else if (ops[0].name == "In_" || ops[0].name == "NotIn") { return block("logic_isIn", node.lineno, { "OP": this.compareOperator(ops[0]) }, { "ITEM": this.convert(left), "LIST": this.convert(comparators[0]) }, { "inline": "true" }); } else { return block("logic_compare", node.lineno, { "OP": this.compareOperator(ops[0]) }, { "A": this.convert(left), "B": this.convert(comparators[0]) }, { "inline": "true" }); } } convertStockSymbols = function(symbol) { switch (symbol) { case 'FB': case "Facebook": return "Facebook"; case "AAPL": case "Apple": return "Apple"; case "MSFT": case "Microsoft": return "Microsoft"; case "GOOG": case "Google": return "Google"; default: throw new Error("Unknown Stock Symbol."); } } toTitleCase = function(str) { return str.replace(/\w\S*/g, function(txt){ return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); }); } PythonToBlocks.KNOWN_MODULES = { "weather": { "get_temperature": ["weather_temperature", "CITY"], "get_report": ["weather_report", "CITY"], "get_forecasts": ["weather_forecasts", "CITY"], "get_highs_lows": ["weather_highs_lows", "CITY"], "get_all_forecasted_temperatures": ["weather_all_forecasts"], "get_forecasted_reports": ["weather_report_forecasts", "CITY"] }, "earthquakes": { "get": ["earthquake_get", "PROPERTY"], "get_both": ["earthquake_both"], "get_all": ["earthquake_all"] }, "stocks": { "get_current": ["stocks_current", ["TICKER", convertStockSymbols]], "get_past": ["stocks_past", ["TICKER", convertStockSymbols]] }, "crime": { // STATE = toTitleCase "get_property_crimes": ["crime_state", ["STATE", toTitleCase], ["TYPE", "property"]], "get_violent_crimes": ["crime_state", ["STATE", toTitleCase], ["TYPE", "violent"]], "get_by_year": ["crime_year", "YEAR"], "get_all": ["crime_all"] }, "books": { "get_all": ["books_get"] }, "plt": { "title": ["*plot_title", "TEXT"], "xlabel": ["*plot_xlabel", "TEXT"], "ylabel": ["*plot_ylabel", "TEXT"], "hist": ["*plot_hist", {"type": "variable", "mode": "value", "name": "values"}], "scatter": ["*plot_scatter", {"type": "variable", "mode": "value", "name": "x_values"}, {"type": "variable", "mode": "value", "name": "y_values"}], "show": ["*plot_show"], "legend": ["*plot_legend"] } }; PythonToBlocks.prototype.KNOWN_FUNCTIONS = ["append", "strip", "rstrip", "lstrip"]; PythonToBlocks.KNOWN_ATTR_FUNCTIONS = {}; PythonToBlocks.prototype.CallAttribute = function(func, args, keywords, starargs, kwargs, node) { var name = this.identifier(func.attr); if (func.value._astname == "Name") { var module = this.identifier(func.value.id); if (module == "plt" && name == "plot") { if (args.length == 1) { return [block("plot_line", func.lineno, {}, { "y_values": this.convert(args[0]) }, {"inline": "false"})]; } else if (args.length == 2) { return [block("plot_lineXY", func.lineno, {}, { "x_values": this.convert(args[0]), "y_values": this.convert(args[1]) }, {"inline": "false"})]; } else { throw new Error("Incorrect number of arguments to plt.plot"); } } else if (module in PythonToBlocks.KNOWN_MODULES && name in PythonToBlocks.KNOWN_MODULES[module]) { var definition = PythonToBlocks.KNOWN_MODULES[module][name]; var blockName = definition[0]; var isExpression = true; if (blockName.charAt(0) == "*") { blockName = blockName.slice(1); isExpression = false; } var fields = {}; var mutations = {}; var values = {}; for (var i = 0; i < args.length; i++) { var argument = definition[1+i]; var destination = fields; if (typeof argument == "string") { fields[argument] = this.Str_value(args[i]); } else if (typeof argument == "object") { if (argument.mode == "value") { destination = values; } if (argument.add_mutation !== undefined) { mutations[argument.add_mutation.name] = argument.add_mutation.value; } if (argument.type == 'mutation') { if (argument.index == undefined) { mutations[argument.name] = this.Str_value(args[i]); } else { mutations[argument.name] = this.Str_value(args[argument.index+1]); } } else if (argument.type == "integer") { destination[argument.name] = this.Num_value(args[i]); } else if (argument.type == 'variable') { destination[argument.name] = this.convert(args[i]); } else if (argument.type == "integer_mapper") { // Okay we jumped the shark here var argumentName = argument.name; var argumentMapper = argument.method; destination[argumentName] = argumentMapper(this.Num_value(args[i])); } else if (argument.type == 'mapper') { var argumentName = argument.name; var argumentMapper = argument.method; destination[argumentName] = argumentMapper(this.Str_value(args[i])); } } else { var argumentName = argument[0]; var argumentMapper = argument[1]; fields[argumentName] = argumentMapper(this.Str_value(args[i])); } } for (var i = 1+args.length; i < definition.length; i++) { var first = definition[i][0]; var second = definition[i][1]; fields[first] = second; } if (isExpression) { var k = block(blockName, func.lineno, fields, values, [], mutations); return k; } else { return [block(blockName, func.lineno, fields, values, [], mutations)]; } } } if (this.KNOWN_FUNCTIONS.indexOf(name) > -1) { switch (name) { case "append": if (args.length !== 1) { throw new Error("Incorrect number of arguments to .append"); } // Return as statement return [block("lists_append", func.lineno, {}, { "ITEM": this.convert(args[0]), "LIST": this.convert(func.value) }, { "inline": "true" })]; case "strip": return block("text_trim", func.lineno, { "MODE": "BOTH" }, { "TEXT": this.convert(func.value) }); case "lstrip": return block("text_trim", func.lineno, { "MODE": "LEFT" }, { "TEXT": this.convert(func.value) }); case "rstrip": return block("text_trim", func.lineno, { "MODE": "RIGHT" }, { "TEXT": this.convert(func.value) }); default: throw new Error("Unknown function call!"); } } else if (name in PythonToBlocks.KNOWN_ATTR_FUNCTIONS) { return PythonToBlocks.KNOWN_ATTR_FUNCTIONS[name].bind(this)(func, args, keywords, starargs, kwargs, node) } else { heights = this.getChunkHeights(node); extractedSource = this.getSourceCode(arrayMin(heights), arrayMax(heights)); var col_endoffset = node.col_endoffset; if (args.length > 0) { for (var i = 0; i < args.length; i+= 1) { col_endoffset = args[i].col_endoffset; } } else { col_endoffset += 2; expressionCall += "()"; } var expressionCall = extractedSource.slice(node.col_offset, 1+col_endoffset); //console.log(node, extractedSource, node.col_offset, node.col_endoffset); var lineno = node.lineno; //console.error(e); //return raw_expression(expressionCall, lineno); var argumentsNormal = {}; var argumentsMutation = {"@name": name}; for (var i = 0; i < args.length; i+= 1) { argumentsNormal["ARG"+i] = this.convert(args[i]); argumentsMutation["*"+i] = this.convert(args[i]); } var methodCall = block("procedures_callreturn", node.lineno, { }, argumentsNormal, { "inline": "true" }, argumentsMutation); return block("attribute_access", node.lineno, {}, { "MODULE": this.convert(func.value), "NAME": methodCall }, { "inline": "true"}, {}); } } /* * func: expr_ty * args: asdl_seq * keywords: asdl_seq * starargs: expr_ty * kwargs: expr_ty * */ PythonToBlocks.prototype.Call = function(node) { var func = node.func; var args = node.args; var keywords = node.keywords; var starargs = node.starargs; var kwargs = node.kwargs; switch (func._astname) { case "Name": switch (this.identifier(func.id)) { case "print": if (args.length == 1) { return [block("text_print", node.lineno, {}, { "TEXT": this.convert(args[0])})]; } else { return [block("esp32_main_controller_text_print", node.lineno, {}, this.convertElements("PRINT", args), {"inline": "true" }, { "@items": args.length})]; } case "input": return block("text_input", node.lineno, {"MESSAGE": args.length ? this.Str_value(args[0]) :""}); case "abs": return block("math_single", node.lineno, {"OP": "ABS"}, {"NUM": this.convert(args[0])}) case "round": return block("math_round", node.lineno, {"OP": "ROUND"}, {"NUM": this.convert(args[0])}) case "ceil": return block("math_round", node.lineno, {"OP": "ROUNDUP"}, {"NUM": this.convert(args[0])}) case "floor": return block("math_round", node.lineno, {"OP": "ROUNDDOWN"}, {"NUM": this.convert(args[0])}) case "sum": return block("math_on_list", node.lineno, {"OP": "SUM"}, {"LIST": this.convert(args[0])}) case "min": return block("math_on_list", node.lineno, {"OP": "MIN"}, {"LIST": this.convert(args[0])}) case "max": return block("math_on_list", node.lineno, {"OP": "MAX"}, {"LIST": this.convert(args[0])}) case "len": return block("lists_length", node.lineno, {}, {"VALUE": this.convert(args[0])}) case "xrange": return block("procedures_callreturn", node.lineno, {}, {"ARG0": this.convert(args[0])}, {"inline": "true"}, {"@name": "xrange", "": this.convert(args[0])}) default: if (starargs !== null && starargs.length > 0) { throw new Error("*args (variable arguments) are not implemented yet."); } else if (kwargs !== null && kwargs.length > 0) { throw new Error("**args (keyword arguments) are not implemented yet."); } var argumentsNormal = {}; var argumentsMutation = {"@name": this.identifier(func.id)}; for (var i = 0; i < args.length; i+= 1) { argumentsNormal["ARG"+i] = this.convert(args[i]); argumentsMutation["*"+i] = this.convert(args[i]); } return block("procedures_callreturn", node.lineno, {}, argumentsNormal, { "inline": "true" }, argumentsMutation); } // Direct function call case "Attribute": // Module function call return this.CallAttribute(func, args, keywords, starargs, kwargs, node); } } /* * value: expr_ty * */ PythonToBlocks.prototype.Repr = function(node) { var value = node.value; throw new Error("Repr is not yet implemented"); } /* * n: object * */ PythonToBlocks.prototype.Num = function(node) { var n = node.n; return block("math_number", node.lineno, {"NUM": Sk.ffi.remapToJs(n)}); } PythonToBlocks.prototype.Num_value = function(node) { var n = node.n; return Sk.ffi.remapToJs(n); } /* * s: string * */ PythonToBlocks.prototype.Str = function(node) { var s = node.s; var strValue = Sk.ffi.remapToJs(s); if (strValue.split("\n").length > 1) { return block("string_multiline", node.lineno, {"TEXT": strValue}); } else { return block("text", node.lineno, {"TEXT": strValue}); } } PythonToBlocks.prototype.Str_value = function(node) { var s = node.s; return Sk.ffi.remapToJs(s); } /* * value: expr_ty * attr: identifier * ctx: expr_context_ty * */ PythonToBlocks.prototype.Attribute = function(node) { var value = node.value; var attr = node.attr; var ctx = node.ctx; console.log(node); return block("attribute_access", node.lineno, { "MODULE": this.convert(value), "NAME": this.convert(attr) }); //throw new Error("Attribute access not implemented"); } /* * value: expr_ty * slice: slice_ty * ctx: expr_context_ty * */ PythonToBlocks.prototype.Subscript = function(node) { var value = node.value; var slice = node.slice; var ctx = node.ctx; if (slice._astname == "Index") { if (slice.value._astname == "Str") { return block("dict_get_literal", node.lineno, { "ITEM": this.Str_value(slice.value) }, { "DICT": this.convert(value) }); } else if (slice.value._astname == "Num") { return block("lists_index", node.lineno, {}, { "ITEM": this.convert(slice.value), "LIST": this.convert(value), }); } } else if (slice._astname == "Slice") { return block("lists_getSublist", node.lineno, { "WHERE1": "FROM_START", "WHERE2": "FROM_START" }, { "LIST": this.convert(value), "AT1": (slice.lower == null) ? null : this.convert(slice.lower), "AT2": (slice.upper == null) ? null : this.convert(slice.upper), }, {}, { "@at1": "true", "@at2": "true", }); } throw new Error("This kind of subscript is not supported."); } /* * id: identifier * ctx: expr_context_ty */ PythonToBlocks.prototype.Name = function(node) { var id = node.id; var ctx = node.ctx; switch (this.Name_str(node)) { case "True": return block("logic_boolean", node.lineno, {"BOOL": "TRUE"}); case "False": return block("logic_boolean", node.lineno, {"BOOL": "FALSE"}); case "None": return block("logic_null", node.lineno); case "___": return null; default: return block('variables_get', node.lineno, { "VAR": this.identifier(id) }); } } /* * id: identifier * ctx: expr_context_ty */ PythonToBlocks.prototype.Name_str = function(node) { var id = node.id; var ctx = node.ctx; return this.identifier(id); } PythonToBlocks.prototype.convertElements = function(key, values, plusser) { if (plusser === undefined) { plusser = 0; } var output = {}; for (var i = 0; i < values.length; i++) { output[key+(plusser+i)] = this.convert(values[i]); } return output; } /* * elts: asdl_seq * ctx: expr_context_ty * */ PythonToBlocks.prototype.List = function(node) { var elts = node.elts; var ctx = node.ctx; return block("lists_create_with", node.lineno, {}, this.convertElements("ADD", elts) , { "inline": elts.length > 3 ? "false" : "true", }, { "@items": elts.length }); } /* * elts: asdl_seq * ctx: expr_context_ty */ PythonToBlocks.prototype.Tuple = function(node) { var elts = node.elts; var ctx = node.ctx; return block("tuple_create", node.lineno, {}, this.convertElements("ADD", elts) , { "inline": elts.length > 3 ? "false" : "true", }, { "@items": elts.length }); } /* * * */ PythonToBlocks.prototype.Ellipsis = function() { throw new Error("Ellipsis not implemented"); } /* * lower: expr_ty * upper: expr_ty * step: expr_ty * */ PythonToBlocks.prototype.Slice = function(node) { var lower = node.lower; var upper = node.upper; var step = node.step; throw new Error("Slices not implemented"); } /* * dims: asdl_seq * */ PythonToBlocks.prototype.ExtSlice = function(node) { var dims = node.dims; throw new Error("ExtSlice is not implemented."); } /* * value: expr_ty * */ PythonToBlocks.prototype.Index = function(value) { var value = node.value; throw new Error("Index is not implemented"); } /* * target: expr_ty * iter: expr_ty * ifs: asdl_seq * */ PythonToBlocks.prototype.comprehension = function(node) { var target = node.target; var iter = node.iter; var ifs = node.ifs; throw new Error("Comprehensions not implemented."); } /* * type: expr_ty * name: expr_ty * body: asdl_seq * */ PythonToBlocks.prototype.ExceptHandler = function(node) { var type = node.type; var name = node.name; var body = node.boy; throw new Error("Except handlers are not implemented"); } PythonToBlocks.prototype.argument_ = function(node) { var id = node.id; return this.identifier(id); } /* * args: asdl_seq * vararg: identifier * kwarg: identifier * defaults: asdl_seq * */ PythonToBlocks.prototype.arguments_ = function(node) { var args = node.args; var vararg = node.vararg; var kwarg = node.kwarg; var defaults = node.defaults; var allArgs = []; for (var i = 0; i < args.length; i++) { var arg = args[i]; allArgs.push(this.argument_(arg)); } return allArgs; } /* * arg: identifier * value: expr_ty * */ PythonToBlocks.prototype.keyword = function(node) { var arg = node.arg; var value = node.value; throw new Error("Keywords are not implemented"); } /* * name: identifier * asname: identifier * */ PythonToBlocks.prototype.alias = function(node) { var name = node.name; var asname = node.asname; throw new Error("Aliases are not implemented"); } /* ----- expr_context ----- */ /* Load Store Del AugLoad AugStore Param */ /* ----- operator ----- */ /* Add Sub Mult Div Mod Pow LShift RShift BitOr BitXor BitAnd FloorDiv */ /* ----- unaryop ----- */ /* Invert Not UAdd USub */