Skip to content

Commit

Permalink
More Tifa bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
acbart committed Jan 28, 2018
1 parent 5d389ea commit 4cbfd34
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 37 deletions.
6 changes: 5 additions & 1 deletion blockpy_new.html
Expand Up @@ -96,6 +96,10 @@
<script type="text/javascript" src="test_corgis/blockpy/tate/tate_skulpt.js"></script>
<script type="text/javascript" src="test_corgis/blockpy/tate/tate_dataset.js"></script>-->

<script type="text/javascript" src="test_corgis/blockpy/weather/weather_blockly.js"></script>
<script type="text/javascript" src="test_corgis/blockpy/weather/weather_skulpt.js"></script>
<script type="text/javascript" src="test_corgis/blockpy/weather/weather_dataset.js"></script>

<style>

</style>
Expand Down Expand Up @@ -148,7 +152,7 @@
'initial_view': getQueryParams()["mode"] || 'Split',
'upload': getQueryParams()["upload"]=="true",
'modules': {
'added': ['Functions', 'Classes', 'Data - Parking', 'Tuples'],
'added': ['Functions', 'Classes', 'Data - Parking', 'Tuples', 'Data - Weather'],
'removed': []
}
},
Expand Down
38 changes: 20 additions & 18 deletions build.py
@@ -1,24 +1,25 @@
import os
import re

with open('src/interface.html', 'r') as input, open('src/interface.js', 'w') as output:
contents = input.read()
cleaned_contents = contents.replace('"', '\\"').replace('\n', '')
js_contents = '''
/**
* An automatically generated file, based on interface.html.
* An interesting problem in web development is managing HTML
* code in JS files. Rather than embedding string literals and
* concatenating them, or some other hackish soluion,
* we simply convert an HTML file to a JS string. Therefore,
* relevant edits should be in interface.html instead.
*
* The BlockPyInterface global can be seen as a constant
* representation of the default interface.
*/
BlockPyInterface = "{interface_code}";
'''.format(interface_code=cleaned_contents)
output.write(js_contents)
with open('src/interface.html', 'r') as inp:
with open('src/interface.js', 'w') as output:
contents = inp.read()
cleaned_contents = contents.replace('"', '\\"').replace('\n', '')
js_contents = '''
/**
* An automatically generated file, based on interface.html.
* An interesting problem in web development is managing HTML
* code in JS files. Rather than embedding string literals and
* concatenating them, or some other hackish soluion,
* we simply convert an HTML file to a JS string. Therefore,
* relevant edits should be in interface.html instead.
*
* The BlockPyInterface global can be seen as a constant
* representation of the default interface.
*/
BlockPyInterface = "{interface_code}";
'''.format(interface_code=cleaned_contents)
output.write(js_contents)

INSTRUCTOR_ROOT = 'src/instructor/'
with open('src/sk_mod_instructor_extended.js', 'w') as output:
Expand All @@ -34,6 +35,7 @@
""")
for instructor_file_name in os.listdir(INSTRUCTOR_ROOT):
with open(INSTRUCTOR_ROOT+instructor_file_name, 'r') as input:
print(instructor_file_name)
contents = input.read()
contents = contents.replace('\\','\\\\').replace('"', '\\"').replace('\n', '\\n')
output.write('$INSTRUCTOR_MODULES_EXTENDED["{module}"] = "{code}"\n'.format(
Expand Down
8 changes: 8 additions & 0 deletions src/abstract_interpreter_tests.js
Expand Up @@ -109,6 +109,14 @@
// Incompatible types
['a = 5 + "ERROR"', [], ['Incompatible types']],
['a = "ERROR" * 5', ['Incompatible types'], []],
['-(5)+0', ['Incompatible types'], []],
['a=0\na+=5\na', ['Incompatible types', 'Unread variables', 'Undefined variables', 'Overwritten variables'], []],
['a=""\na+=5\na', ['Unread variables', 'Undefined variables', 'Overwritten variables'], ['Incompatible types']],
['a+=5\na', ['Unread variables', 'Overwritten variables'], ['Undefined variables']],
['a=0\na+=5', ['Undefined variables', 'Overwritten variables'], ['Unread variables']],

// Lambda
['a = lambda: 0\nb=a()\nb+5', ['Incompatible types'], []],

// Handle function definitions
['def named(x):\n\tprint(x)\n', ['Undefined variables'], ['Unread variables']],
Expand Down
2 changes: 1 addition & 1 deletion src/instructor/instructor_histogram.py
Expand Up @@ -48,7 +48,7 @@ def plot_show_missing():
Name: histogram_argument_not_list
Pattern:
plt.hist(<argument>)
Where type(<argument>) is not list
Where type(<argument>) is not "list"
Feedback: Making a histogram requires a list; <argument> is not a list.
Expand Down
25 changes: 13 additions & 12 deletions src/interface.js

Large diffs are not rendered by default.

220 changes: 216 additions & 4 deletions src/pytifa.js
Expand Up @@ -261,7 +261,38 @@ Tifa.prototype.visit_Assign = function(node) {
}
}
}));
}

Tifa.prototype.visit_AugAssign = function(node) {
// Handle value
var right = this.visit(node.value);
// Handle target
var left = this.visit(node.target);
var name = this.identifyCaller(node.target);
// Handle op
var position = Tifa.locate(node);

// Handle operation
this.loadVariable(name, position);
var opLookup = Tifa.VALID_BINOP_TYPES[node.op.name];
if (left.name == "Unknown" || right.name == "Unknown") {
return Tifa._UNKNOWN_TYPE();
} else if (opLookup) {
opLookup = opLookup[left.name];
if (opLookup) {
opLookup = opLookup[right.name];
if (opLookup) {
var resultType = opLookup(left, right);
this.storeVariable(name, resultType, position);
return resultType;
}
}
}
this.reportIssue("Incompatible types",
{"left": left, "right": right,
"operation": node.op.name,
"position": position});
return Tifa._UNKNOWN_TYPE();
}

Tifa.prototype.visit_Import = function(node) {
Expand Down Expand Up @@ -303,7 +334,9 @@ Tifa.prototype.visit_BinOp = function(node) {

// Handle operation
var opLookup = Tifa.VALID_BINOP_TYPES[node.op.name];
if (opLookup) {
if (left.name == "Unknown" || right.name == "Unknown") {
return Tifa._UNKNOWN_TYPE();
} else if (opLookup) {
opLookup = opLookup[left.name];
if (opLookup) {
opLookup = opLookup[right.name];
Expand All @@ -316,7 +349,81 @@ Tifa.prototype.visit_BinOp = function(node) {
{"left": left, "right": right,
"operation": node.op.name,
"position": Tifa.locate(node)});
return Tifa._UNKNOWN_TYPE;
return Tifa._UNKNOWN_TYPE();
}

Tifa.prototype.visit_UnaryOp = function(node) {
// Handle operand
var operand = this.visit(node.operand);

if (node.op.name == "Not") {
return Tifa._BOOL_TYPE();
} else if (operand.name == "Unknown") {
return Tifa._UNKNOWN_TYPE();
} else {
var opLookup = Tifa.VALID_UNARYOP_TYPES[node.op.name];
if (opLookup) {
opLookup = opLookup[operand.name];
if (opLookup) {
return opLookup(operand);
}
}
}
return Tifa._UNKNOWN_TYPE();
}

Tifa.prototype.visit_BoolOp = function(node) {
// Handle left and right
var values = [];
for (var i=0, len=node.values.length; i < len; i+= 1) {
values[i] = this.visit(node.values[i]);
}

// TODO: Truthiness is not supported! Probably need a Union type

// Handle operation
return Tifa._BOOL_TYPE();
}

Tifa.prototype.visit_Compare = function(node) {
// Handle left and right
var left = this.visit(node.left);
var comparators = [];
for (var i=0, len=node.comparators.length; i < len; i+= 1) {
comparators[i] = this.visit(node.comparators[i]);
}

// Handle ops
for (var i=0, len=comparators.length; i < len; i+= 1) {
var op = node.ops[i];
var right = node.op[i];
switch (op.name) {
case "Eq": case "NotEq": case "Is": case "IsNot":
break;
case "Lt": case "LtE": case "GtE": case "Gt":
if (left.name != right.name ||
(left.name != "Num" && left.name != "Bool" &&
left.name != "Str" && left.name != "List" &&
left.name != "Set" && left.name != "Tuple" )) {
this.reportIssue("Incompatible types",
{"left": left, "right": right,
"operation": node.op.name,
"position": Tifa.locate(node)});
}
break;
case "In": case "NotIn":
if (right.name != "Str" && right.name != "List" &&
right.name != "Set" && right.name != "Tuple" &&
right.name != "Dict") {
this.reportIssue("Incompatible types",
{"left": left, "right": right,
"operation": node.op.name,
"position": Tifa.locate(node)});
}
break;
}
}
return Tifa._BOOL_TYPE();
}

Tifa.prototype.visit_Call = function(node) {
Expand All @@ -338,7 +445,25 @@ Tifa.prototype.visit_Call = function(node) {
return result;
}
this.reportIssue("Not a function", {"position": position});
return Tifa._UNKNOWN_TYPE;
return Tifa._UNKNOWN_TYPE();
}

Tifa.prototype.IfExp = function(node) {
// Visit the conditional
this.visit(node.test);

// Visit the body
var body = this.visit(node.body);

// Visit the orelse
var orelse = this.visit(node.orelse);

if (body.name != orelse.name) {
// TODO: Union type?
return Tifa._UNKNOWN_TYPE();
} else {
return body;
}
}

Tifa.prototype.visit_If = function(node) {
Expand Down Expand Up @@ -447,6 +572,37 @@ Tifa.prototype.visit_ListComp = function(node) {
return Tifa._LIST_OF_TYPE(this.visit(elt));
}

Tifa.prototype.visit_SetComp = function(node) {
// TODO: Handle comprehension scope
var generators = node.generators;
for (var i = 0, len = generators.length; i < len; i++) {
this.visit(generators[i]);
}
var elt = node.elt;
return Tifa._SET_OF_TYPE(this.visit(elt));
}

Tifa.prototype.visit_SetComp = function(node) {
// TODO: Handle comprehension scope
var generators = node.generators;
for (var i = 0, len = generators.length; i < len; i++) {
this.visit(generators[i]);
}
var key = node.key;
var value = node.value;
return Tifa._DICT_OF_TYPE(this.visit(key), this.visit(value));
}

Tifa.prototype.visit_GeneratorExp = function(node) {
// TODO: Handle comprehension scope
var generators = node.generators;
for (var i = 0, len = generators.length; i < len; i++) {
this.visit(generators[i]);
}
var elt = node.elt;
return Tifa._GENERATOR_OF_TYPE(this.visit(elt));
}

Tifa.prototype.visit_comprehension = function(node) {
// Handle the iteration list
var position = Tifa.locate(node);
Expand Down Expand Up @@ -600,6 +756,36 @@ Tifa.prototype.visit_FunctionDef = function(node) {
return state.type;
}

Tifa.prototype.visit_Lambda = function(node) {
// Name
var position = Tifa.locate(node);
var definitionsScope = this.scopeChain.slice(0);
var functionType = { "name": "Function"};
functionType.definition = function(analyzer, callType, callName, parameters, callPosition) {
// Manage scope
analyzer.ScopeId += 1;
var oldScope = analyzer.scopeChain.slice(0);
analyzer.scopeChain = definitionsScope.slice(0);
analyzer.scopeChain.unshift(analyzer.ScopeId);
// Process arguments
var args = node.args.args;
for (var i = 0; i < args.length; i++) {
var arg = args[i];
var name = Sk.ffi.remapToJs(arg.id);
var parameter = Tifa.copyType(parameters[i]);
analyzer.storeVariable(name, parameter, position)
}
var returnValue = analyzer.visit(node.body);
// Return scope
analyzer.finishScope();
analyzer.scopeChain.shift();
analyzer.scopeChain = oldScope;
return returnValue;

}
return functionType;
}

Tifa.prototype.visit_Return = function(node) {
if (this.scopeChain.length == 1) {
this.reportIssue("Return outside function", {"position": Tifa.locate(node)});
Expand Down Expand Up @@ -798,7 +984,7 @@ Tifa.prototype.storeVariable = function(name, type, position) {
if (!Tifa.areTypesEqual(type, variable.state.type)) {
this.reportIssue("Type changes",
{'name': name, 'position':position,
'old': state.type, 'new': type})
'old': variable.state.type, 'new': type})
}
newState.type = type;
// Overwritten?
Expand Down Expand Up @@ -901,7 +1087,11 @@ Tifa._FILE_TYPE = function() { return {'name': 'File'} };
Tifa._SET_TYPE = function() { return {'name': 'Str', "empty": false} };
Tifa._LIST_TYPE = function() { return {'name': 'List', "empty": false} };
Tifa._DICT_TYPE = function() { return {'name': 'Dict', "empty": false} };
Tifa._GENERATOR_OF_TYPE = function(subtype) { return {'name': 'Generator', "empty": false, "subtype": subtype} };
Tifa._LIST_OF_TYPE = function(subtype) { return {'name': 'List', "empty": false, "subtype": subtype} };
Tifa._SET_OF_TYPE = function(subtype) { return {'name': 'Set', "empty": false, "subtype": subtype} };
Tifa._DICT_OF_TYPE = function(keytype, valuetype) {
return {'name': 'Set', "empty": false, "keys": keytype, "values": valuetype} };
Tifa._TUPLE_TYPE = function() { return {'name': 'Tuple'} };
Tifa._NONE_TYPE = function() { return {'name': 'None'} };
Tifa._UNKNOWN_TYPE = function() { return {'name': '*Unknown'} };
Expand All @@ -913,6 +1103,7 @@ Tifa.VALID_BINOP_TYPES = {
'Sub': {'Num': {'Num': Tifa._NUM_TYPE},
'Set': {'Set': Tifa.mergeTypes}},
'Div': {'Num': {'Num': Tifa._NUM_TYPE}},
'FloorDiv': {'Num': {'Num': Tifa._NUM_TYPE}},
'Mult': {'Num': {'Num': Tifa._NUM_TYPE,
'Str': Tifa._STR_TYPE,
'List': (l, r) => r,
Expand All @@ -924,6 +1115,25 @@ Tifa.VALID_BINOP_TYPES = {
// Should we allow old-fashioned string interpolation?
// Currently, I vote no because it makes the code harder and is bad form.
'Mod': {'Num': {'Num': Tifa._NUM_TYPE}},
'LShift': {'Num': {'Num': Tifa._NUM_TYPE}},
'RShift': {'Num': {'Num': Tifa._NUM_TYPE}},
'BitOr': {'Num': {'Num': Tifa._NUM_TYPE},
'Bool': {'Num': Tifa._NUM_TYPE,
'Bool': Tifa._BOOL_TYPE},
'Set': {'Set': Tifa.mergeTypes}},
'BitXor': {'Num': {'Num': Tifa._NUM_TYPE},
'Bool': {'Num': Tifa._NUM_TYPE,
'Bool': Tifa._BOOL_TYPE},
'Set': {'Set': Tifa.mergeTypes}},
'BitAnd': {'Num': {'Num': Tifa._NUM_TYPE},
'Bool': {'Num': Tifa._NUM_TYPE,
'Bool': Tifa._BOOL_TYPE},
'Set': {'Set': Tifa.mergeTypes}},
}
Tifa.VALID_UNARYOP_TYPES = {
'UAdd': {'Num': Tifa._NUM_TYPE},
'USub': {'Num': Tifa._NUM_TYPE},
'Invert': {'Num': Tifa._NUM_TYPE}
}

Tifa.locate = function(node) {
Expand All @@ -935,6 +1145,8 @@ Tifa.indexSequenceType= function(type, i) {
return type.subtypes[i];
} else if (type.name == "List") {
return type.subtype;
} else if (type.name == "Generator") {
return type.subtype;
} else if (type.name == "Str") {
return Tifa._STR_TYPE();
} else if (type.name == "File") {
Expand Down

0 comments on commit 4cbfd34

Please sign in to comment.