/**
* 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,
levelIndex = this.levelIndex; // The bottom of the current peer
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,
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);
// Otherwise, always embed it in there.
} else {
nestChild(newChild);
}
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.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");
mutationNode.setAttribute("name", mutation);
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) {
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("text_print_multiple", 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", 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;
throw new Error("Sets are not implemented");
}
/*
* 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"]
}
};
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 {
console.log(func, args, keywords, starargs, kwargs);
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": "false"
}, 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("text_print_multiple", node.lineno, {},
this.convertElements("PRINT", args),
{"inline": "true"
}, { "@items": args.length})];
}
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 "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": "false"
}, 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.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),
});
}
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) {
var output = {};
for (var i = 0; i < values.length; i++) {
output[key+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;
throw new Error("Tuples not implemented");
}
/*
*
*
*/
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
*/