Skip to content

Instantly share code, notes, and snippets.

@ungarst
Created November 19, 2012 00:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ungarst/4108297 to your computer and use it in GitHub Desktop.
Save ungarst/4108297 to your computer and use it in GitHub Desktop.
You said you wanted it rfw
#!/usr/bin/python
import sys
import re
class Submission(object):
def __init__ (self, lines):
self.info = lines[0]
start = 2
while lines[start] != '----------------':
start += 1
self.code = '\n'.join(lines[2:start])
self.report = '\n'.join(lines[start+1:])
self.errors = []
def __str__ (self):
""" Defines a way of printing the submission for easy reading"""
return '==================\n' + \
'\n----------------\n'.join([self.info, self.code, self.report]) + \
'\n'
def is_matched (self):
""" Returns true if at least on of the errors has been matched. """
return self.errors
def diagnose (self):
""" Diagnoses the submission's error messages.
Goes through all diagnostic methods attempting to match
the error message with a diagnostic.
More diagnostics can be added by writing a diagnostic method
which appends the problem to the objects errors list and
inserting the method into the possible_errors list.
"""
possible_errors = [
self.missing_semicolon_diagnose,
self.illegal_predicate_operation,
self.missing_closing_curly_brace_diagnose,
self.missing_opening_curly_brace_diagnose,
self.variable_cannot_be_resolved,
self.undefined_library_method,
self.undefined_user_method,
self.incorrect_arguments,
self.not_double_equals,
self.type_mismatch,
self.duplicate_variable,
self.illegal_modifier,
self.assignment_not_using_equals,
self.incorrect_return_type,
self.unreachable_code,
self.too_many_curly_braces,
self.accessing_non_array,
self.empty_square_braces,
self.variable_not_initialized,
self.using_length_as_method,
self.including_main_header,
self.including_method_header,
self.new_as_a_var,
self.single_quotes_string,
self.invalid_assignment_operator,
self.predicate_combination_operators_undefined,
self.predicate_operators_undefined,
self.else_without_leading_curly_brace,
self.variable_type_on_right_hand_side_of_equals,
self.variable_type_in_brackets,
self.missing_closing_bracket,
self.casting_error,
self.incorrect_assignment_in_conditional,
self.incorrect_operator_usage,
self.invalid_invocation
]
[x() for x in possible_errors]
def missing_semicolon_diagnose (self):
""" Diagnoses a missing semicolon error. """
if 'Syntax error, insert ";"' in self.report:
self.errors.append('Missing Semicolon')
def illegal_predicate_operation (self):
""" Diagnoses the use of illegal predicate operations.
Predicate operations counted as illegal:
* =!
* =>
* =<
* <<
* >>
"""
match = re.search(r'.*if\s*\(.*(\=\!|\=\>|\=\<|\>\>|\<\<).*\)',
self.report)
if match:
self.errors.append('Illegal predicate operation')
def missing_closing_curly_brace_diagnose (self):
""" Diagnoses a missing curly brace error. """
if 'Syntax error, insert "}"' in self.report:
self.errors.append('Missing closing curly brace')
def missing_opening_curly_brace_diagnose (self):
""" Diagnoses a missing opening curly brace error. """
diagnostic = "{ expected"
if diagnostic in self.report:
self.errors.append('Missing opening curly brace')
def variable_cannot_be_resolved (self):
""" Diagnoses a variable not resolve error. """
if 'cannot be resolved' in self.report:
self.errors.append('Cannot be resolved')
def undefined_library_method (self):
""" Diagnoses a undefined library method error.
It will only diagnose an error if the code is trying to invoke a
method on an object that it doesn't have, or is trying to call
a static method that doesn't exist from a library.
It doesn't diagnose an undefined method that should have been implemented
by the user.
"""
match = re.search(r'The method [\w\.\(\),\s]+ is undefined for the type (?!user)\w*',
self.report)
if match:
self.errors.append('Undefined library method')
def undefined_user_method (self):
""" Diagnoses a undefined library method error.
It will only diagnose missing methods that should be implemented by the
programmer, not ones that should be defined in the programming language.
Relies on the fact that the name of the class that the method is placed into
begins with 'user'.
"""
match = re.search(r'The method [\w\.\(\)]+ is undefined for the type user',
self.report)
if match:
self.errors.append('Undefined user method')
def incorrect_arguments (self):
""" Diagnoses the error of incorrect arguments to a method. """
match = re.search(r'The method .* in the type \w* is not applicable for the arguments', self.report)
if match:
self.errors.append('Incorrect arguments')
def not_double_equals (self):
""" Diagnoses the error of attempting to compare two
things using a single equals.
"""
if 'The left-hand side of an assignment must be a variable' in self.report:
self.errors.append('No double equals')
def type_mismatch (self):
""" Diagnoses a type mismatch error. """
match = re.search(r'Type mismatch: cannot convert from \w* to \w*',
self.report)
if match:
self.errors.append('Type mismatch')
def duplicate_variable (self):
""" Diagnoses a duplicate variable error.
These tend to be a result of the programmer redefining the
parameters or redefining a variable, most likely 'i'
used in a for loop.
"""
if 'Duplicate local variable' in self.report:
self.errors.append('Duplicate variable')
def illegal_modifier (self):
""" Diagnoses the use of an illegal modifier.
Often occurs when the programmer tries to make a local
variable public..
"""
if 'Illegal modifier' in self.report:
self.errors.append('Illegal modifiers')
def assignment_not_using_equals (self):
""" Diagnoses the error of attempting to assign not using equals.
Usually occurs when the programmer tries to assign to a boolean
using a predicate e.g. boolean x == 4.
"""
match = re.search(r'Syntax error on token ".*", = expected',
self.report)
if match:
self.errors.append('None equals assignment')
def incorrect_return_type (self):
""" Diagnoses an incorrect return type.
Tends to be a result of either:
* Missing a return statement completely.
* Returning a variable that is of the wrong type.
* Having return statements inside an if statement and
no return at the end and so code is not guaranteed to
reach return.
"""
if 'This method must return a result of type' in self.report:
self.errors.append('Wrong or no return type')
def unreachable_code (self):
""" Diagnoses unreachable code errors.
Tends to be a result of having code in the
sam branch following a return statement.
"""
if 'Unreachable code' in self.report:
self.errors.append('Unreachable code')
def too_many_curly_braces (self):
""" Diagnoses too many curly braces. """
if 'Syntax error on token "}", delete this token' in self.report:
self.errors.append('Too many curly braces')
def accessing_non_array (self):
""" Diagnoses attempts to access non-arrays using square braces.
Tends to be due to accidentally using the wrong variable or using
python style string indexing rather than using the charAt(int)
string method.
"""
if 'The type of the expression must be an array type but it resolved to' in self.report:
self.errors.append('Non array access')
def empty_square_braces (self):
""" Diagnoses empty square braces. """
diagnostic = 'Syntax error on token "[", Expresssion expected ' \
'after this token'
if diagnostic in self.report:
self.errors.append('Empty square braces')
def variable_not_initialized (self):
""" Diagnoses variables that are not initialized but are declared.
Tends to be the use of summing or iterative
varibles that are not set to zero.
"""
match = re.search(r'The local variable \w* may not have been initialized',
self.report)
if match:
self.errors.append('Variable not initialized')
def using_length_as_method (self):
""" Diagnoses the use of length as a method on an array. """
diagnostic = 'Cannot invoke length() on the array type'
if diagnostic in self.report:
self.errors.append('Used length as a method')
def including_main_header (self):
""" Diagnoses the student putting in unnessasary code.
Code snippet is wrapped in method header and placed in file
by compiler backend so students don't have to include it.
When they do, it causes compiler errors.
"""
if 'public static void main(String args[])' in self.code:
self.errors.append('Including main method header')
def including_method_header (self):
""" Diagnoses including method header which is done by the compiler. """
match = re.search(r'public \w*(\[\])? .+\(.+\).*\n?{?', self.code)
if match:
self.errors.append('Including method header')
def new_as_a_var (self):
""" Diagnoses the use of new as a variable. """
if 'new =' in self.code:
self.errors.append('Uses new as a variable')
def single_quotes_string (self):
""" Diagnoses the use of single quote strings. """
diagnostic = 'Invalid character constant'
if diagnostic in self.report:
self.errors.append('Single quote string')
def invalid_assignment_operator (self):
""" Diagnoses an incorrect assignment.
Ususally occurs when the programmer accidentally assigns using an
operator that is not the equals operator or when they attempt
to change a variable without assigning it back to its name.
e.g. var + 1 instead of var += 1
"""
match = re.search(r'Syntax error on token ".+", invalid AssignmentOperator',
self.report)
if match:
self.errors.append('Invalid assignment operator')
def predicate_combination_operators_undefined (self):
""" Diagnoses the incorrect usage of the && and || operators.
Usually as a result of one of the predicates not actually being a
conditional.
"""
match = re.search(r'The operator \&\&|\|\| is undefined for the argument',
self.report)
if match:
self.errors.append('Incorrect use of && or ||')
def predicate_operators_undefined (self):
""" Diagnoses the improper use of predicate operators.
Ususally the result of people putting the equals sign in the wrong place
e.g. =! rather than != and => rather than >=
"""
match = re.search(r'The operator (\=\=|\!\=|\<\=|\>\=) is undefined for the argument type\(s\)',
self.report)
if match:
self.errors.append('Trying to compare two things that cannot be compared')
def else_without_leading_curly_brace (self):
""" Diagnoses not closing an if statement with a curly brace
before leading on with an else or if else statement.
"""
match = re.search(r'else\s?(if)?(\(.*\))?\s?{', self.report)
diagnostic = 'Syntax error on token "else", delete this token'
if match and diagnostic in self.report:
self.errors.append("Doesn't have a leading curly brace infront of else")
def variable_type_on_right_hand_side_of_equals (self):
""" Diagnoses including the variable type on the right at assingment. """
match = re.search(r'=\s*(int|double|String)', self.report)
if match:
self.errors.append('Variable type on right hand side of assignment')
def variable_type_in_brackets (self):
""" Diagnoses wrapping the variable type in brackets at declaration time.
e.g. (int) i = 50;
(String) s = "Hello";
"""
match = re.search(r'\((int|double|String)\)\s*\w+\s*=', self.report)
if match:
self.errors.append('Varible type wrapped in brackets at declaration')
def missing_closing_bracket (self):
""" Diagnoses a missing closing brace. """
diagnostic = 'Syntax error, insert ")"'
if diagnostic in self.report:
self.errors.append('Requires a closing bracket to be completed')
def casting_error (self):
""" Diagnoses improper casting e.g. int x = (int) "Dave"; """
match = re.search(r'Cannot cast from .* to .*', self.report)
if match:
self.errors.append('Casting error')
def incorrect_assignment_in_conditional (self):
""" Diagnoses incorrect assignments in conditionals.
This is generally a result of programmers trying to assign in
an if statement but not using their braces correctly.
e.g. if ((x = Integer.parseInt(word)) < 3)) is correct but often people use
if ((x = Integer.parseInt)(word < 3))
"""
diagnostic = 'Syntax error, insert "AssignmentOperator Expression" ' + \
'to complete Expression'
if diagnostic in self.report:
self.errors.append('Incorrect assignmet in conditional')
def incorrect_operator_usage (self):
""" Diagnoses the incorrect usage of a basic operator.
An example of this is attempting to subtract two strings from one another, or
any other type of operator overloading that java does not support.
"""
match = re.search(r'The operator [\+\-\\\*\%] is undefined for the argument type\(s\)',
self.report)
if match:
self.errors.append('Incorrect operator usage')
def invalid_invocation (self):
""" Diagnoses an attempt to invoke a method on an object
for which the method is not defined.
e.g. Cannot invoke substring(int, int) on the primitive type int
"""
match = re.search(r'Cannot invoke (?!length\(\)).* on the .* type .*',
self.report)
if match:
self.errors.append('Invalid invocation of method')
files = [
'Activity1_Data/question1',
'Activity1_Data/question2',
'Activity1_Data/question3',
'Activity1_Data/question4',
'Activity1_Data/question5'
]
for filename in files:
# Read all the submisssions in to a list from the Data file
# Keep only the ones which are NO COMPILE
with open (filename, 'r') as handle:
data = handle.read()
no_compiles = []
# Switch means that we can split them as a whole submission
switch = False
lines = data.split('\n')
for i, line in enumerate(lines):
if line == '========================' and not switch:
start_point = i+1
switch = True
elif line == '========================' and switch:
submission = lines[start_point:i]
if submission[0][-9:] == 'NOCOMPILE':
no_compiles.append(Submission(submission))
switch = False
# Diagnose NOCOMPILES and write all that are not matched to a file
with open(filename + '_notdiagnosed', 'w') as handle:
count = 0
for sub in no_compiles:
sub.diagnose()
if sub.is_matched():
count += 1
else:
handle.write(str(sub))
handle.write('\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n')
handle.write("{:.2f}% of {} NOCOMPILES matched\n".format(float(count)/len(no_compiles), len(no_compiles)))
handle.write("{} NOCOMPILES remaining".format(len(no_compiles)-count))
print ("{:.2f}% of NOCOMPILES identified in {}".format(float(count)*100/len(no_compiles), filename))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment