#!/usr/bin/python # CodeQ: an online programming tutor. # Copyright (C) 2015 UL FRI # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU Affero General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more # details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . import io import re from tokenize import tokenize, TokenError import ast def get_tokens(code): """ Gets a list of tokens. """ try: stream = io.BytesIO(code.encode('utf-8')) return [t.string for t in tokenize(stream.readline) if t.string] except TokenError: return [] def all_tokens(code): try: stream = io.BytesIO(code.encode('utf-8')) return [t for t in tokenize(stream.readline)] except TokenError: return [] # Check if tokens contain a sequence of tokens (given as a list of strings). def has_token_sequence(tokens, sequence): for i in range(len(tokens)-len(sequence)+1): if tokens[i:i+len(sequence)] == sequence: return True return False def almost_equal(a, b, prec): """ Compares values a and b using prec if value < 1 or prec most significant numbers otherwise. """ return abs(a-b) <= max(abs(a), abs(b), 1) * 10**(-prec) def get_numbers(s): """ Extracts numbers from string s. """ str_vals = re.findall(r''' [-+]? # optional sign (?: (?: \d* \. \d+ ) # .1 .12 .123 etc 9.1 etc 98.1 etc | (?: \d+ \.? ) # 1. 12. 123. etc 1 12 123 etc ) # followed by optional exponent part if desired (?: [Ee] [+-]? \d+ ) ? ''', s, re.VERBOSE) return [float(v) for v in str_vals] def string_almost_equal(s, a, prec=3): """ Searches string s for a value that is almost equal to a. """ for v in get_numbers(s): if almost_equal(v, a, prec): return True return False def string_contains_number(s, a): """ Searches string s for a value that is equal to a. """ return a in get_numbers(s) def get_exception_desc(exc): # if have an exception! if exc: if 'EOFError' in exc: return [{'id':'eof_error'}] if 'timed out' in exc: return [{'id':'timed_out'}] if 'sandbox violation' in exc: return [{'id': 'sandbox_violation'}] if 'NameError' in exc: return [{'id':'name_error', 'args': {'message': exc}}] elif 'TypeError' in exc: return [{'id':'type_error', 'args': {'message': exc}}] else: return [{'id':'error', 'args': {'message': exc}}] return None def get_ast(code): """ Turn code into ast; use it when regular expressions on strings are not enjoyable enough. """ return ast.parse(code) def has_comprehension(tree): """ Searches code for comprehensions and generators. """ for n in ast.walk(tree): if isinstance(n, ast.comprehension): return True return False def has_loop(tree): """ Searches abstract syntax tree for loops (for and while). """ for n in ast.walk(tree): if isinstance(n, ast.For) or isinstance(n, ast.While): return True return False if __name__ == '__main__': print(has_token_sequence(get_tokens('x + y >= 0'), ['>=', '0'])) print(has_token_sequence(get_tokens('x + y > 0'), ['>=', '0']))