That looks fine to me. I don't see any real pitfalls myself, except that it won't match a multiline repeat, e.g.
repeat (2 *
len(data)):
I don't think that's a significant issue, though - just tell students that they're restricted to single line repeats (which are much nicer stylistically, anyway). Maybe they're already restricted in tigerjython?
Of course, the students will probably find other pitfalls - they usually do :)
I might be missing something, but I think you can simplify the template by just using dynamically generated identifiers of the form loop_control_variable_1, loop_control_variable_2 etc rather than picking them from a list. That way you won't need to insert
extra code before the student code, which will ensure that Python error message line numbers match the students' own code. Also, if you use the version of re.sub that takes a function parameter you can avoid having an explicit loop. Here's my quick
hack at it, which works on the one and only test I've tried. I'm not sure it's much of an improvement, though.
import re
loopCounter = 0
def replacer(match):
"""Return the replacement for a matched repeat n statement"""
global loopCounter
loopCounter += 1
return "{}for __TigerJythonLoopControlVariable_{} in range ({}):".format(match.group(1), loopCounter, match.group(2))
answer = """{{ STUDENT_ANSWER | e('py') }}"""
# replace 'repeat :' by 'while True :'
answer = re.sub( r"(^[ \t]*)repeat[ \t]*:", r"\1while True:", answer, flags=re.M)
# replace 'repeat <expr> :' by 'for <var> in range( <expr> )'
answer = re.sub( r"(^[ \t]*)repeat[ \t]+(.*):", replacer, answer, flags=re.M)
{% for TEST in TESTCASES %}
answer += "\n" + """{{ TEST.testcode | e('py')}}\n"""
{% if not loop.last %}
answer += 'print("#<ab@17943918#@>#")\n'
{% endif %}
{% endfor %}
exec(answer)
Another possibility to consider: the error messages emerging from an exec can be a bit confusing. An alternative which gives simpler error messages is to replace exec(answer) with
with open("student_prog.py", "w") as outfile:
outfile.write(answer)
subprocess.run(["python3", "student_prog.py"], encoding="utf-8")