catch mismatched parens
This commit is contained in:
@@ -100,6 +100,13 @@ class UndefinedVariable(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnmatchedParenthesis(Exception):
|
||||
"""
|
||||
Indicate when a student inputs a formula with mismatched parentheses.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def lower_dict(input_dict):
|
||||
"""
|
||||
Convert all keys in a dictionary to lowercase; keep their original values.
|
||||
@@ -249,6 +256,7 @@ def evaluator(variables, functions, math_expr, case_sensitive=False):
|
||||
return float('nan')
|
||||
|
||||
# Parse the tree.
|
||||
check_parens(math_expr)
|
||||
math_interpreter = ParseAugmenter(math_expr, case_sensitive)
|
||||
math_interpreter.parse_algebra()
|
||||
|
||||
@@ -278,6 +286,30 @@ def evaluator(variables, functions, math_expr, case_sensitive=False):
|
||||
return math_interpreter.reduce_tree(evaluate_actions)
|
||||
|
||||
|
||||
def check_parens(formula):
|
||||
"""
|
||||
Check that any open parentheses are closed
|
||||
|
||||
Otherwise, raise an UnmatchedParenthesis exception
|
||||
"""
|
||||
count = 0
|
||||
delta = {
|
||||
'(': +1,
|
||||
')': -1
|
||||
}
|
||||
for index, char in enumerate(formula):
|
||||
if char in delta:
|
||||
count += delta[char]
|
||||
if count < 0:
|
||||
msg = "Invalid Input: A closing parenthesis was found after segment " + \
|
||||
"{}, but there is no matching opening parenthesis before it."
|
||||
raise UnmatchedParenthesis(msg.format(formula[0:index]))
|
||||
if count > 0:
|
||||
msg = "Invalid Input: Parentheses are unmatched. " + \
|
||||
"{} parentheses were opened but never closed."
|
||||
raise UnmatchedParenthesis(msg.format(count))
|
||||
|
||||
|
||||
class ParseAugmenter(object):
|
||||
"""
|
||||
Holds the data for a particular parse.
|
||||
|
||||
@@ -554,3 +554,12 @@ class EvaluatorTest(unittest.TestCase):
|
||||
calc.evaluator({'r1': 5}, {}, "r1+r2")
|
||||
with self.assertRaisesRegexp(calc.UndefinedVariable, 'r1 r3'):
|
||||
calc.evaluator(variables, {}, "r1*r3", case_sensitive=True)
|
||||
|
||||
def test_mismatched_parens(self):
|
||||
"""
|
||||
Check to see if the evaluator catches mismatched parens
|
||||
"""
|
||||
with self.assertRaisesRegexp(calc.UnmatchedParenthesis, 'opened but never closed'):
|
||||
calc.evaluator({}, {}, "(1+2")
|
||||
with self.assertRaisesRegexp(calc.UnmatchedParenthesis, 'no matching opening parenthesis'):
|
||||
calc.evaluator({}, {}, "(1+2))")
|
||||
|
||||
@@ -40,7 +40,7 @@ import capa.safe_exec as safe_exec
|
||||
import capa.xqueue_interface as xqueue_interface
|
||||
import dogstats_wrapper as dog_stats_api
|
||||
# specific library imports
|
||||
from calc import UndefinedVariable, evaluator
|
||||
from calc import UndefinedVariable, UnmatchedParenthesis, evaluator
|
||||
from cmath import isnan
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
|
||||
@@ -1604,6 +1604,10 @@ class NumericalResponse(LoncapaResponse):
|
||||
bad_variables=text_type(undef_var),
|
||||
)
|
||||
)
|
||||
except UnmatchedParenthesis as err:
|
||||
raise StudentInputError(
|
||||
err.args[0]
|
||||
)
|
||||
except ValueError as val_err:
|
||||
if 'factorial' in text_type(val_err):
|
||||
# This is thrown when fact() or factorial() is used in an answer
|
||||
@@ -1770,7 +1774,7 @@ class NumericalResponse(LoncapaResponse):
|
||||
try:
|
||||
evaluator(dict(), dict(), answer)
|
||||
return True
|
||||
except (StudentInputError, UndefinedVariable):
|
||||
except (StudentInputError, UndefinedVariable, UnmatchedParenthesis):
|
||||
return False
|
||||
|
||||
def get_answers(self):
|
||||
@@ -3108,6 +3112,14 @@ class FormulaResponse(LoncapaResponse):
|
||||
raise StudentInputError(
|
||||
_("Invalid input: {bad_input} not permitted in answer.").format(bad_input=text_type(err))
|
||||
)
|
||||
except UnmatchedParenthesis as err:
|
||||
log.debug(
|
||||
'formularesponse: unmatched parenthesis in formula=%s',
|
||||
cgi.escape(answer)
|
||||
)
|
||||
raise StudentInputError(
|
||||
err.args[0]
|
||||
)
|
||||
except ValueError as err:
|
||||
if 'factorial' in text_type(err):
|
||||
# This is thrown when fact() or factorial() is used in a formularesponse answer
|
||||
|
||||
Reference in New Issue
Block a user