def hard_coding(val_list): """ Args: val_list: Returns: """ message = ("Please show code that makes the computer extract " "the value from the dictionary.") code = "hard_code" tldr = "Printing raw value" # Pattern 1 possibility matches = find_matches("print(__exp__)") for match in matches: __exp__ = match["__exp__"] value = __exp__.value if value in val_list: return explain(message, label=code, title=tldr) # Pattern 2 possibility matches = find_matches("__exp__\n" "print(_var_)") for match in matches: __exp__ = match["__exp__"] _var_ = match["_var_"] submatches = __exp__.find_matches("_var_ = __exp2__") for submatch in submatches: __exp2__ = submatch["__exp2__"] value = __exp2__.value if value in val_list: return explain(message, label=code, title=tldr) return False
def test_explain(self): # Tifa < Explain with Execution('1+""') as e: explain("You cannot add those.") self.assertEqual(e.final.message, "You cannot add those.") # Tifa > Gently with Execution('1+""') as e: gently("You cannot add those.") self.assertEqual(e.final.title, "Incompatible types")
def wrong_average_numerator(): """ Returns: """ message = "The average is not calculated correctly." code = "avg_numer" tldr = "Incorrect Average Calculation" matches = find_matches( "for _item_ in ___:\n" " __expr__\n" # where expr contains _total_ = _total_ + 1 "__expr2__") # where expr2 contains _value_/___ if matches: for match in matches: __expr__ = match["__expr__"] __expr2__ = match["__expr2__"] _item_ = match["_item_"][0] # TODO: In theory, we could merge these matches to match variables... submatches = __expr__.find_matches("_total_ = _total_ + _item_") submatches02 = __expr2__.find_matches("_value_/___") if submatches and submatches02: for submatch in submatches: for submatch02 in submatches02: _value_ = submatch02["_value_"][0] _total_ = submatch["_total_"][0] if _total_.id != _value_.id: return explain(message, label=code, title=tldr) return False
def wrong_average_denominator(): """ Returns: """ message = "The average is not calculated correctly." code = "avg_denom" tldr = "Incorrect Average Calculation" matches = find_matches( "for ___ in ___:\n" " __expr__\n" # where expr contains _count_ = _count_ + 1 "__expr2__") # where expr2 contains ___/_value_ # where _value_.id != _count_.id for match in matches: __expr__ = match["__expr__"] __expr2__ = match["__expr2__"] # _value_ = match["_value_"][0] submatches = __expr__.find_matches("_count_ = _count_ + 1", ) submatches02 = __expr2__.find_matches("___/_value_") if submatches and submatches02: for submatch in submatches: for submatch02 in submatches02: _count_ = submatch["_count_"] _value_ = submatch02["_value_"] if _count_.id != _value_.id: return explain(message, label=code, title=tldr) return False
def warning_average_in_iteration(): """ Returns: """ message = ( 'An average value is best computed after the properties name <code>{0!s}</code>(total) and ' '<code>{1!s}</code> are completely known rather than recomputing the average on each iteration.' ) code = "avg_in_iter" tldr = "Redundant Average Calculation" matches = find_matches("for ___ in ___:\n" " __expr__\n") if matches: for match in matches: __expr__ = match["__expr__"] submatches = __expr__.find_matches("_average_ = _total_/_count_", ) if submatches: for submatch in submatches: _total_ = submatch["_total_"][0] _count_ = submatch["_count_"][0] _average_ = submatch["_average_"][0] if _total_.id != _count_.id != _average_.id and _total_.id != _average_.id: return explain(message.format(_total_.id, _count_.id), label=code, title=tldr) return False
def missing_summing_list(): """ std_ast = parse_program() has_total = False for_loops = std_ast.find_all('For') if len(for_loops) > 0: for loop in for_loops: assignments = loop.find_all('Assign') if len(assignments) < 1: continue iter_prop = loop.target for assignment in assignments: binops = assignment.find_all('BinOp') if len(binops) < 1: continue lhs = assignment.target for binop in binops: if binop.has(lhs) and binop.has(iter_prop) and binop.op == 'Add': has_total = True if not has_total: explain('Sum the total of all list elements using iteration.<br><br><i>(miss_sum_list)<i></br>') Returns: """ message = 'Sum the total of all list elements using iteration.' code = "miss_sum_list" tldr = "Missing Sum in Iteration" matches = find_matches("for _item_ in ___:\n" " __expr__") if matches: for match in matches: _item_ = match["_item_"][0] __expr__ = match["__expr__"] submatches = __expr__.find_matches("_sum_ = _sum_ + _item_") if submatches: return False return explain(message, label=code, title=tldr)
def missing_counting_list(): """ std_ast = parse_program() has_count = False for_loops = std_ast.find_all('For') if len(for_loops) > 0: for loop in for_loops: assignments = loop.find_all('Assign') if len(assignments) < 1: continue for assignment in assignments: binops = assignment.find_all('BinOp') if len(binops) < 1: continue lhs = assignment.target for binop in binops: if binop.has(lhs) and binop.has(1) and binop.op == 'Add': has_count = True if not has_count: explain('Count the total number of items in the list using iteration.<br><br><i>(miss_count_list)<i></br>') Returns: """ message = 'Count the total number of items in the list using iteration.' code = "miss_count_list" tldr = "Missing Count in Iteration" matches = find_matches("for _item_ in ___:\n" " __expr__") if matches: for match in matches: __expr__ = match["__expr__"] submatches = __expr__.find_matches("_sum_ = _sum_ + 1", ) if submatches: return False return explain(message, label=code, title=tldr)
def wrong_cannot_sum_list(): """ std_ast = parse_program() for_loops = std_ast.find_all('For') for loop in for_loops: list_prop = loop.iter assignments = loop.find_all('Assign') for assignment in assignments: binops = assignment.find_all('BinOp') for binop in binops: if binop.has(list_prop) and binop.op == 'Add': explain('Addition can only be done with a single value at a time, not with an entire list at one' ' time.<br><br><i>(sum_list)<i></br>') Returns: """ message = 'Addition can only be done with a single value at a time, not with an entire list at once' code = "sum_list" tldr = "Cannot Sum a List" matches = find_matches("for ___ in _list_ :\n" " __expr__") if matches: for match in matches: _list_ = match["_list_"][0] __expr__ = match["__expr__"] # submatches = __expr__.find_matches("___ = ___ + {}".format(_list_.id), ) submatches = __expr__.find_matches("___ = ___ + _list_") if submatches: return explain(message, label=code, title=tldr) return False
def missing_addition_slot_empty(): """ std_ast = parse_program() assignments = std_ast.find_all('Assign') for assignment in assignments: # left = assignment.target right = assignment.value binOp = right.find_all('BinOp') if len(binOp) == 1: binOp = binOp[0] if binOp.op == 'Add': if binOp.left.ast_name == 'Name' and binOp.right.ast_name == 'Name': if binOp.left.id == '___' or binOp.right.id == '___': explain('You must fill in the empty slot in the addition.<br><br><i>(add_empty)<i></br>') return True return False Returns: """ message = "You must fill in the empty slot in the addition." code = "add_empty" tldr = "Addition Blank" matches = find_matches("___ + _item_") if matches: for match in matches: _item_ = match["_item_"][0] if _item_.id == "___": return explain(message, label=code, title=tldr) return False
def wrong_should_be_counting(): """ std_ast = parse_program() for_loops = std_ast.find_all('For') for loop in for_loops: iter_prop = loop.target assignments = loop.find_all('Assign') for assignment in assignments: binops = assignment.find_all('BinOp') for binop in binops: if binop.has(iter_prop) and binop.op == 'Add': explain('This problem asks for the number of items in the list not the total of all the values in ' 'the list.<br><br><i>(not_count)<i></br>') Returns: """ message = "This problem asks for the number of items in the list not the total of all the values in the list." code = "not_count" tldr = "Summing instead of counting" matches = find_matches("for _item_ in ___:\n" " __expr__") if matches: for match in matches: _item_ = match["_item_"][0] __expr__ = match["__expr__"] submatches = __expr__.find_matches("___ = ___ + _item_") if submatches: return explain(message, label=code, title=tldr) return False
def wrong_initialization_in_iteration(): """ Returns: """ message = ( "You only need to initialize <code>{0!s}</code> once. " "Remember that statements in an iteration block happens multiple times" ) code = "wrong_init_in_iter" tldr = "Initialization in Iteration" matches = find_matches("for ___ in ___:\n" " __expr__") if matches: for match in matches: __expr__ = match["__expr__"] submatches = __expr__.find_matches("_assign_ = __expr__", ) if submatches: for submatch in submatches: __expr__sub = submatch["__expr__"] _assign_ = submatch["_assign_"][0].astNode if len(__expr__sub.find_all("Name")) == 0: return explain(message.format(_assign_.id), label=code, title=tldr) return False
def wrong_debug_10_6(): """ Should be on change feedback as opposed to on-run Returns: """ message = "This is not one of the two changes needed. Undo the change and try again." code = "debug_10.6" tldr = "At least one unnecessary change" matches = find_matches( 'quakes = earthquakes.get("location.depth","(None)","")\n' 'quakes_in_miles = []\n' 'for quake in _list1_:\n' ' _list2_.append(quake * 0.62)\n' 'plt.hist(quakes_in_miles)\n' 'plt.xlabel("Depth in Miles")\n' 'plt.ylabel("Number of Earthquakes")\n' 'plt.title("Distribution of Depth in Miles of Earthquakes")\n' 'plt.show()') for match in matches: name1 = match["_list1_"][0].ast_node.id name2 = match["_list2_"][0].ast_node.id master_list = ["quake", "quakes", "quakes_in_miles"] if (name1 in master_list and name2 in master_list and name1 != "quakes_in_miles" and name2 != "quakes" and (name1 != "quake" or name2 != "quake")): return False return explain(message, label=code, title=tldr)
def wrong_append_problem_atl1_10_5(): """ Returns: """ message = "You are not appending the correct values.<br><br><i>(app_alt1_10.5" code = "app_alt1_10.5" tldr = "Incorrect Value Appended" matches = find_matches("for _item_ in ___:\n" " if __cond__:\n" " _list_.append(__expr__)") if matches: for match in matches: _item_ = match["_item_"][0].astNode __cond__ = match["__cond__"] __expr__ = match["__expr__"] if (__cond__.numeric_logic_check(0.1, "item > 16.1290322580645") and __cond__.has(_item_)): # new_code = "{}*0.62".format(_item_.id) new_code = "_item_*0.62" matches02 = __expr__.find_matches(new_code) if not matches02: return explain(message, label=code, title=tldr) return False
def wrong_filter_problem_atl2_10_5(): """ Returns: """ message = "You are not correctly filtering out values from the list." code = "filt_alt2_10.5" tldr = "Incorrect Filter Statement" matches = find_matches("for _item_ in ___:\n" " _miles_ = __expr__\n" " if __cond__:\n" " _list_.append(_miles_)") if matches: for match in matches: __expr__ = match["__expr__"] __cond__ = match["__cond__"] _item_ = match["_item_"][0].astNode _miles_ = match["_miles_"][0].astNode matches02 = __expr__.find_matches("_item_*0.62") for _ in matches02: if not (__cond__.has(_miles_) and __cond__.numeric_logic_check(1, "_item_ > 10")): return explain(message, label=code, title=tldr) return False
def wrong_filter_problem_atl1_10_5(): """ find pattern where expression is equal to _item_*0.62 and where the condition is not equivalent to _expr_ > 10 Returns: """ message = "You are not correctly filtering out values from the list." code = "filt_alt1_10.5" tldr = "Incorrect Filter Statement" matches = find_matches("for _item_ in ___:\n" " if __cond__:\n" " _list_.append(__expr__)") if matches: for match in matches: _item_ = match["_item_"][0].astNode __cond__ = match["__cond__"] __expr__ = match["__expr__"] # matches02 = __expr__.find_matches("{0!s}*0.62".format(_item_.id)) matches02 = __expr__.find_matches("_item_*0.62") if matches02: for match02 in matches02: if (__cond__.has(_item_) and not __cond__.numeric_logic_check( 0.1, "item > 16.1290322580645")): return explain(message, label=code, title=tldr) return False
def fetch_acc_dict(values): """ Args: values: Returns: """ message = ( "The code to fetch the list of dictionaries, <code>{}.{}</code>, cannot be used to select data. " "Selection of data should be done with an if statement") code = "fetch_acc" tldr = "Malformed Dictionary List Fetch" matches = find_matches("_var_._func_[__str__]") for match in matches: _var_ = match["_var_"].id _func_ = match["_func_"].id __str__ = match["__str__"] if __str__.is_ast("Str") and __str__.value in values: return explain(message.format(_var_, _func_), label=code, title=tldr) return False
def wrong_nested_filter_condition_10_4(): """ Returns: """ message = ("The decisions used to filter the temperatures into " "the specified range of temperatures is not correct.") code = "nest_filt_10.4" tldr = "Incorrect Set of Decisions" matches = find_matches("for _temp_ in _list_:\n" " if __cond1__:\n" " if __cond2__:\n" " pass") if matches: for match in matches: _temp_ = match["_temp_"][0].astNode __cond1__ = match["__cond1__"] __cond2__ = match["__cond2__"] if not (__cond1__.has(_temp_) and __cond2__.has(_temp_) and (__cond1__.numeric_logic_check(1, "32 <= temp") and __cond2__.numeric_logic_check(1, "temp <= 50") or __cond2__.numeric_logic_check(1, "32 <= temp") and __cond1__.numeric_logic_check(1, "temp <= 50"))): return explain(message, label=code, title=tldr) return False
def histogram_wrong_list(): """ Name: histogram_wrong_list Pattern: for ___ in ___: <target>.append(___) plt.hist(<list>) where name(<target>) != name(<list>) Feedback: The list created in the iteration is not the list being used to create the histogram. Returns: """ message = "The list created in the iteration is not the list being used to create the histogram." code = "histo_wrong_list" tldr = "Plotting Wrong List" matches = find_matches("for ___ in ___:\n" " __expr__\n" "plt.hist(_list_)") if matches: for match in matches: _list_ = match["_list_"].astNode __expr__ = match["__expr__"] submatches = __expr__.find_matches("_list_.append(___)") if submatches: return False return explain(message, label=code, title=tldr) return False
def filt_key(c_value, num_slices): """ Args: c_value: num_slices: Returns: """ message = ( 'It looks like you\'re using <code>"{c_value}"</code> as a dictionary key to filter data. ' "Dictionary keys don't filter data, they only access data that's already there. " "You should be comparing data retrieved from the dictionary to <code>'{c_value}'</code>" ) code = "filt_key" tldr = "Attempting filter as Key" construct = "_var_" for a_slice in range(num_slices): construct += "[__str{}__]".format(a_slice) matches = find_matches(construct) for match in matches: for num in range(a_slice + 1): value = match["__str{}__".format(num)] if value.is_ast("Str") and value.value == c_value: return explain(message.format(c_value=value), label=code, title=tldr) return False
def histogram_argument_not_list(): """ Name: histogram_argument_not_list Pattern: plt.hist(<argument>) Where type(<argument>) is not "list" Feedback: Making a histogram requires a list; <argument> is not a list. Returns: """ message = "Making a histogram requires a list; <code>{0!s}</code> is not a list." code = "hist_arg_not_list" tldr = "Making Histogram from Non-list" matches = find_matches("plt.hist(_argument_)") if matches: for match in matches: _argument_ = match["_argument_"] if not _argument_.was_type('list'): return explain(message.format(_argument_.id), label=code, title=tldr) return False
def key_order(keys): """ Args: keys: Returns: """ # TODO: Is it possible to run this test after confirming (through other tests) that there are no unused keys and # that all keys used are the correct keys, such that the feedback message can explicitly address JUST the case of # wrong order? message = "It looks like you aren't using the correct keys, or the correct key order. Double check your data map." code = "key_order_c" tldr = "Wrong key order" construct = None # Assemble chain of dictionary slicing find_chain = "_var_" for a_slice in range(len(keys)): find_chain += "[__str{}__]".format(a_slice) # If we find a chain of dictionary accesses if find_match(find_chain): # Assemble a new match pattern using the provided key order construct = "_var_" for key in keys: construct += "['{}']".format(key) if construct: # check if we have a set of keys of the proper order matches = find_matches(construct) if not matches: return explain(message, label=code, title=tldr) return False
def key_order_unchained(keys): """ Args: keys: Returns: """ message = "It looks like you aren't using the correct keys, or the correct key order. Double check your data map." code = "key_order_u" tldr = "Wrong key order" construct = None find_chain = "" for a_slice in range(len(keys)): find_chain += "_var{a2}_ = _var{a1}_[__str{a1}__]\n".format( a2=a_slice + 1, a1=a_slice) if find_match(find_chain): construct = "" count = 0 for key in keys: construct += "_var{a2}_ = _var{a1}_['{key}']\n".format(a2=count + 1, a1=count, key=key) count += 1 if construct: matches = find_matches(construct) if not matches: return explain(message, label=code, title=tldr) return False
def missing_key(keys): """ Checks if student is missing a key TODO: Should be good if run AFTER the var_instead_of_key check, although it doesn't appear to catch a key that's been assigned as the value of an unused variable. :param keys: list of keys :type keys: list of Str :return: Feedback String :rtype: Str """ message = "You seem to be missing the following dictionary key(s):<ul>{}</ul>" code = "miss_key" tldr = "Missing necessary keys" key_list = "" first = False for key in keys: matches = find_matches("\"{}\"".format(key)) if not matches: if not first: key_list += ", " key_list += '<li><code>"' + key + '"</code></li>' if key_list != "": return explain(message.format(key_list), label=code, title=tldr) return False
def dict_parens_brack(): """ Returns: """ message = ( "It looks like you are trying to dictionary access <code>{}</code>. " "The dictionary access syntax does not require parenthesis.") code = "dict_parbrack" tldr = "Improper dictionary access" matches = find_matches("_var_([__str1__][__str2__])") matches += find_matches("_var_([__str1__])") for match in matches: _var_ = match['_var_'] __str1__ = match["__str1__"] __str2__ = __str1__ try: __str2__ = match["__str2__"] except KeyError: pass if __str1__.is_ast("Str") and __str2__.is_ast("Str") and data_state( _var_.id): return explain(message.format(_var_.id), label=code, title=tldr) return False
def iter_as_key(keys): """ Args: keys: Returns: """ message = ("It looks like you are using the iteration variable <code>{}" "</code> to access a value of a specific key in a dictionary. " "To access a key-value from a list of dictionaries, use <code>") code = "iter_key" tldr = "Iteration variable is not key" matches = find_matches("for _var_ in ___:\n" " pass") for match in matches: _var_ = match['_var_'] submatches = find_matches("_var2_[__str__]") missing = True for submatch in submatches: __str__ = submatch["__str__"] if __str__.is_ast("Str") and __str__.value == _var_.id: missing = False break if missing and _var_.id in keys: return explain(message.format(_var_.id), label=code, title=tldr) return False
def hard_coded_list(val_list): """ Args: val_list: Returns: """ message = ( "In later abstractions, it's not possible to view the values of a specific key in a list." "You should use a dictionary key-value pair to access values in the list of dictionaries." ) code = "hard_list" tldr = "Don't use raw list" matches = find_matches("[__exp__]") for match in matches: __exp__ = match['__exp__'].parent if __exp__.ast_name == "List": try: vals = sum([x.value for x in __exp__.elts]) if sum(val_list) == vals: return explain(message, label=code, title=tldr) except TypeError: pass # This should be the only error return False
def dict_out_of_loop(keys): """ Args: keys: Returns: """ message = ( "Remember that a list of dictionaries, like <code>{}</code>, " "is still a list of individual items. Each dictionary needs to be accessed with " "the appropriate key-value pair one at a time.") code = "dict_out_loop" tldr = "Dictionary Access Outside of Loop" matches = find_matches("__exp__\n" "for ___ in _var_:\n" " pass") matches += find_matches("for ___ in _var_:\n" " pass\n" "__exp__\n") for match in matches: __exp__ = match['__exp__'] _var_ = match['_var_'] submatches = __exp__.find_matches( "{var}[__str__]".format(var=_var_.id)) for submatch in submatches: __str__ = submatch['__str__'] if __str__.is_ast("Str") and __str__.value in keys: return explain(message.format(_var_.id), label=code, title=tldr) return False
def var_instead_of_key(keys): """ Args: keys: Returns: """ message = ( "It looks like you are trying to use (<code>{}</code>) as a dictionary key. " "Use the dictionary access syntax to get values from a dictionary") code = "var_as_k" tldr = "Using Variable instead of key" matches = find_matches("_var_") matches += find_matches("[_var_]") for match in matches: _var_ = match["_var_"] if _var_.id in keys: submatch = find_match("_dict_['{}']".format(_var_.id)) submatch2 = find_match("{} = ___".format(_var_.id)) if submatch is None and submatch2 is None: # If we don't find a dictionary access using this key and # we don't see that this variable is assigned to a value... return explain(message.format(_var_.id), label=code, title=tldr) return False
def missing_zero_initialization(): """ std_ast = parse_program() for_loops = std_ast.find_all('For') accumulator = None loop_acu = None for loop in for_loops: assignments = loop.find_all('Assign') for assignment in assignments: binops = assignment.find_all('BinOp') if len(binops) > 0: lhs = assignment.target for binop in binops: if binop.has(lhs) and binop.op == 'Add': accumulator = lhs loop_acu = loop accu_init = False if accumulator is not None: assignments = std_ast.find_all('Assign') for assignment in assignments: if loop_acu.lineno > assignment.lineno: lhs = assignment.target if lhs.id == accumulator.id and assignment.has(0): accu_init = True break if not accu_init and accumulator is not None: explain('The addition on the first iteration step is not correct because either the variable ' '<code>{0!s}</code> has not been initialized to an appropriate initial value or it has not been placed' ' in an appropriate location<br><br><i>(miss_zero_init)<i></br>'.format(accumulator.id)) return False return True Returns: """ message = ( 'The addition on the first iteration step is not correct because either the variable <code>{0!s}</code> ' 'has not been initialized to an appropriate initial value ' 'or it has not been placed in an appropriate location') code = "miss_zero_init" tldr = "Missing Initialization for Accumulator" matches01 = find_matches("for ___ in ___:\n" " __expr__") if matches01: for match01 in matches01: __expr__ = match01["__expr__"] submatches01 = __expr__.find_matches("_sum_ = _sum_ + ___", ) if submatches01: for submatch01 in submatches01: _sum_ = submatch01["_sum_"][0] matches02 = find_matches(("{} = 0\n" "for ___ in ___:\n" " __expr__").format(_sum_.id)) if not matches02: return explain(message.format(_sum_.id), label=code, title=tldr) return False
def test_gently_vs_runtime(self): # Runtime > Gently clear_report() contextualize_report('import json\njson.loads("0")+"1"') verify() tifa_analysis() commands.run() gently("I have a gentle opinion, but you don't want to hear it.") final = simple.resolve() print(final.label) self.assertEqual(Feedback.CATEGORIES.RUNTIME, final.category) # Runtime < Explain clear_report() contextualize_report('import json\njson.loads("0")+"1"') verify() tifa_analysis() commands.run() explain("LISTEN TO ME") final = simple.resolve() self.assertEqual(Feedback.CATEGORIES.INSTRUCTOR, final.category)