def runBenchmark(self, cmd): """ Run one benchmark. This includes creating ssh connection to the remote machine and running one benchmark. It will remove the benchmark from the benchmark list. """ from dispatcher import RunningTask # nothing to run if not self._benchmarks: return None self._count -= 1 name, cat = self._benchmarks.pop() ecmd = self.expandSpecialVariables(cmd, name, cat) ecmd = expandVariables(ecmd) dbg('running: {0}'.format(ecmd)) p = subprocess.Popen(ecmd, Task.BUFSIZE, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) return RunningTask(cmd, p, self, name, cat)
def resolve_variable(self, track, field, i, depth): if field == '': if self.debug: dbg('output of single percent at char %s' % i, depth) return EvaluatorAtom('%', False) local_field = field if not self.case_sensitive: local_field = field.upper() if self.debug: dbg('parsed variable %s at char %s' % (local_field, i), depth) resolved = None if not self.magic: resolved = track.get(local_field) else: resolved = self.magic_resolve_variable(track, local_field, depth) if resolved is None or resolved is False: return None if self.for_filename: resolved = re.sub('[\\\\/:|]', '-', resolved) return EvaluatorAtom(resolved, True)
def rsync_tool_runner(tasks): satt_log('Synchronizing...') if not configs.has_key('sync-cmd'): dbg('No sync command') return add_tasks(tasks) d = SyncDispatcher(tasks) d.run()
def parse_fn_arg(self, track, current_fn, current, current_argv, c, i, depth=0, offset=0, memory={}): next_current = '' if self.debug: dbg('finished argument %s for function "%s" at char %s' % ( len(current_argv), current_fn, i), depth) # The lazy expression will parse the current buffer if it's ever needed. lazy = LazyExpression( self, track, current, False, depth + 1, offset, memory) return (next_current, lazy)
def __init__(self, tasks): Dispatcher.__init__(self, tasks, SyncReporter(tasks)) # create remote directory if it does not exists # this create remote-dir and remote-dir/satt for t in tasks: m = '{0}@{1}'.format(configs['ssh-user'], t.getMachine()) dbg('Creating remote directory on {0}'.format(t.getMachine())) subprocess.call(['ssh', m, 'mkdir', '-p', '{0}/satt'.format(configs['remote-dir'])])
def __init__(self, tasks): Dispatcher.__init__(self, tasks, SyncReporter(tasks)) # create remote directory if it does not exists # this create remote-dir and remote-dir/satt for t in tasks: m = '{0}@{1}'.format(configs['ssh-user'], t.getMachine()) dbg('Creating remote directory on {0}'.format(t.getMachine())) subprocess.call([ 'ssh', m, 'mkdir', '-p', '{0}/satt'.format(configs['remote-dir']) ])
def assign_set(dirpath, path, tasks, should_skip): old_dir = os.getcwd() epath = expand(dirpath) os.chdir(epath) # there is not so much of .set files, so there won't be # any significant time penalty if we parse the configs # here every time if configs.configs.has_key('exclude')\ and configs.configs['exclude'].strip() != '': exclude = configs.configs['exclude'].split(',') bname = os.path.basename(path) for e in exclude: e = e.strip() if re.search(e, bname): print('Skiping {0} benchmarks'.format(bname)) os.chdir(old_dir) return False try: f = open(path, 'r') except OSError as e: err("Failed opening set of benchmarks ({0}): {1}".format( path, e.strerror)) num = len(tasks) assert num > 0 cat = path[:-4] for line in f: line = line.strip() if not line: continue n = 0 for it in glob.iglob(line): bench = ('benchmarks/c/{0}'.format(it), cat) if should_skip(bench): dbg('Skipping benchmark {0}'.format(it)) else: tasks[n % num].add(bench) n += 1 f.close() os.chdir(old_dir) return n != 0
def get_benchmarks(files, tasks): items = files.split(',') skip_known_id = configs.configs['skip-known-benchmarks'] if skip_known_id != 'no': from database_proxy import DatabaseProxy dbproxy = DatabaseProxy() year_id = dbproxy.getYearID(configs.configs['year']) if year_id is None: err('Wrong year: {0}'.format(configs.configs['year'])) try: toolid = int(skip_known_id) except ValueError: err('Invalid tool id for skip-known-benchmarks') dbg('Will skip benchmarks from tool {0}'.format(toolid)) should_skip = lambda x: _should_skip_with_db(dbproxy, toolid, year_id, x) else: should_skip = lambda x: False for it in items: paths = glob.glob(expand(it)) if not paths: sys.stderr.write('Directory does not exist: {0}\n'.format(it)) return for path in paths: # get folder or .set file if os.path.isdir(path): path = '{0}/c'.format(path) assign_set_dir(path, tasks, should_skip) elif os.path.isfile(path): dirpath = os.path.dirname(path) basename = os.path.basename(path) assign_set(dirpath, basename, tasks, should_skip) # return number of found benchmarks num = 0 for t in tasks: num += t.getCount() return num
def run(self): """ Dispatch tasks over network and wait for outcomes """ dbg('[local] Started dispatching benchmarks') # take every task and call as many of benchmarks as # you are allowed. Later, when a task ends, # we will spawn only one new (one new for one done) try: for task in self._tasks: for n in range(0, task.getParallel()): if self._runBenchmark(task) is None: break # monitor the tasks self._monitorTasks() except KeyboardInterrupt: self._dontSendResults = True self._killTasks() satt_log('Stopping...')
def parse_literal(self, c, i, lookbehind, literal_count, include_quote, depth=0, offset=0): next_output = '' next_literal_state = True literal_chars_parsed = 0 if c == "'": if lookbehind == "'" and literal_count == 0: if self.debug: dbg('output of single quote due to lookbehind at char %s' % i, depth) next_output += c elif include_quote: next_output += c if self.debug: dbg('leaving literal mode at char %s' % i, depth) next_literal_state = False else: next_output += c literal_chars_parsed += 1 return (next_output, next_literal_state, literal_chars_parsed)
def assign_set_dir(dirpath, tasks, should_skip): edirpath = os.path.expanduser(dirpath) dbg('Looking for benchmarks in: {0}'.format(edirpath)) try: files = os.listdir(edirpath) except OSError as e: err('Failed opening dir with benchmarks ({0}): {1}'.format( edirpath, e.strerror)) gotany = False for f in files: if f[-4:] != '.set': continue # this path needs to be relative, since it can appear # on remote computer gotany |= assign_set(dirpath, f, tasks, should_skip) if not gotany: sys.stderr.write('Warning: Haven\'t found any .set file\n')
def sendResults(self): """ Send results by e-mail to given people """ if self._dontSendResults: return emails = [] try: f = open('emails.txt') for email in f: emails.append(email.strip()) f.close() except IOError as e: dbg('Not sending email with results, because' ' error occured while processing file with emails') # if we don't have any e-mail, we don't have to send anything if not emails: return self._report.sendEmail('localhost', '*****@*****.**', emails)
def points(self, ok, res, witness = None, categ=None): if res is None: return 0 res = res.lower() if res == 'unknown' or res == 'error' or res == 'timeout': return self.unknown elif res == 'false': if ok: if witness == 'confirmed' or no_witness_categ(categ): return self.false_correct else: return self.unknown else: return self.false_incorrect elif res == 'true': if ok: return self.true_correct else: return self.true_incorrect else: dbg('Unknown result, skipping points') return 0
def points(self, ok, res, witness=None, categ=None): if res is None: return 0 res = res.lower() if res == 'unknown' or res == 'error' or res == 'timeout': return self.unknown elif res == 'false': if ok: if witness == 'confirmed' or no_witness_categ(categ): return self.false_correct else: return self.unknown else: return self.false_incorrect elif res == 'true': if ok: return self.true_correct else: return self.true_incorrect else: dbg('Unknown result, skipping points') return 0
def sendEmail(self, server, from_addr, to_addrs): import smtplib from email.mime.text import MIMEText time_format = '%Y-%m-%d-%H-%S' raw_started_at = strptime(configs.configs['started_at'], time_format) started_at = strftime('%a %b %d %H:%M:%S %Y', raw_started_at) finished_at = strftime('%a %b %d %H:%M:%S %Y') text = """ This is automatically generated message. Do not answer. ======================================================= Satt on tool {0} started at {1}, finished {2} with parameters: {3} on benchmarks from year {4} Note: {5} Results: """.format(configs.configs['tool'], started_at, finished_at, configs.configs['params'], configs.configs['year'], configs.configs['note']) q = """ SELECT result, is_correct, witness, count(*) FROM task_results WHERE run_id = {0} GROUP BY result, is_correct, witness""".format(self.run_id) res = self._db.query(q) if not res: err('No results stored to db after this run?') total = 0 for row in res: result = row[0] if result == 'true' or result == 'false': if row[1] == 0: result += ' incorrect' else: result += ' correct' if not row[2] is None: text += '{0:<15} (witness {1}): {2}\n'.format(result, row[2], row[3]) else: text += '{0:<15}: {1}\n'.format(result, row[3]) total += row[3] text += '\nTotal number of benchmarks: {0}'.format(total) q = """SELECT tool_id FROM task_results WHERE run_id = {0}""".format(self.run_id) res = self._db.query(q) if not res: err('Failed querying db for tool\'s id') tool_id = res[0][0] text += '\n\nYou can check the results here:\n' text += 'http://macdui.fi.muni.cz:3000/tools/{0}'.format(tool_id) text += '\n\nHave a nice day!\n' msg = MIMEText(text) msg['Subject'] = 'Satt results from {0}'.format(started_at) msg['From'] = from_addr msg['To'] = '*****@*****.**' s = smtplib.SMTP(server) ret = s.sendmail(from_addr, to_addrs, msg.as_string()) s.quit() for r in ret: dbg('Failed sending e-mail to {0},' 'err: {1}'.format(r[0], r[1]))
def parse_command_line(): from common import err, dbg try: opts, args = getopt.getopt(sys.argv[1:], '', ['help', 'machines=', 'benchmarks=', 'no-sync', 'no-db', 'sync=', 'debug', 'year=', 'exclude=', 'params=', 'note=', 'save-new-tasks', 'ignore-duplicates', 'no-email', 'tool-tag=', 'skip-known-benchmarks=']) except getopt.GetoptError as e: err('{0}'.format(str(e))) for opt, arg in opts: if opt == '--help': usage() sys.exit(1) elif opt == '--machines': configs['machines'] = arg elif opt == '--benchmarks': configs['benchmarks'] = arg elif opt == '--no-sync': configs['sync'] = 'no' elif opt == '--sync': configs['sync'] = arg elif opt == '--no-db': configs['no-db'] = 'yes' elif opt == '--save-new-tasks': configs['save-new-tasks'] = 'yes' elif opt == '--ignore-duplicates': configs['ignore-duplicates'] = 'yes' elif opt == '--debug': configs['debug'] = 'yes' elif opt == '--year': configs['year'] = arg elif opt == '--exclude': configs['exclude'] = arg elif opt == '--note': configs['note'] = arg elif opt == '--tool-tag': configs['tool-tag'] = arg # this switch takes int argument just due # to technicall constrains. The tool id we can infer after # we got the result, but we don't want to run it at all elif opt == '--skip-known-benchmarks': configs['skip-known-benchmarks'] = arg elif opt == '--no-email': configs['send-email'] = 'no' elif opt == '--params': if configs.has_key('params'): # if we have some params, just update configs['params'] = params_from_string(arg, configs['params']) else: configs['params'] = params_from_string(arg) else: err('Unknown switch {0}'.format(opt)) if len(args) > 1: usage() sys.exit(1) elif len(args) == 1: configs['tool'] = args[0] # print debug for l, r in configs.items(): dbg('{0} = {1}'.format(l, r)) return configs['tool']
def parse_command_line(): from common import err, dbg try: opts, args = getopt.getopt(sys.argv[1:], '', [ 'help', 'machines=', 'benchmarks=', 'no-sync', 'no-db', 'sync=', 'debug', 'year=', 'exclude=', 'params=', 'note=', 'save-new-tasks', 'ignore-duplicates', 'no-email', 'tool-tag=', 'skip-known-benchmarks=' ]) except getopt.GetoptError as e: err('{0}'.format(str(e))) for opt, arg in opts: if opt == '--help': usage() sys.exit(1) elif opt == '--machines': configs['machines'] = arg elif opt == '--benchmarks': configs['benchmarks'] = arg elif opt == '--no-sync': configs['sync'] = 'no' elif opt == '--sync': configs['sync'] = arg elif opt == '--no-db': configs['no-db'] = 'yes' elif opt == '--save-new-tasks': configs['save-new-tasks'] = 'yes' elif opt == '--ignore-duplicates': configs['ignore-duplicates'] = 'yes' elif opt == '--debug': configs['debug'] = 'yes' elif opt == '--year': configs['year'] = arg elif opt == '--exclude': configs['exclude'] = arg elif opt == '--note': configs['note'] = arg elif opt == '--tool-tag': configs['tool-tag'] = arg # this switch takes int argument just due # to technicall constrains. The tool id we can infer after # we got the result, but we don't want to run it at all elif opt == '--skip-known-benchmarks': configs['skip-known-benchmarks'] = arg elif opt == '--no-email': configs['send-email'] = 'no' elif opt == '--params': if configs.has_key('params'): # if we have some params, just update configs['params'] = params_from_string(arg, configs['params']) else: configs['params'] = params_from_string(arg) else: err('Unknown switch {0}'.format(opt)) if len(args) > 1: usage() sys.exit(1) elif len(args) == 1: configs['tool'] = args[0] # print debug for l, r in configs.items(): dbg('{0} = {1}'.format(l, r)) return configs['tool']
def sendEmail(self, server, from_addr, to_addrs): import smtplib from email.mime.text import MIMEText time_format = '%Y-%m-%d-%H-%S' raw_started_at = strptime(configs.configs['started_at'], time_format) started_at = strftime('%a %b %d %H:%M:%S %Y', raw_started_at) finished_at = strftime('%a %b %d %H:%M:%S %Y') text = """ This is automatically generated message. Do not answer. ======================================================= Satt on tool {0} started at {1}, finished {2} with parameters: {3} on benchmarks from year {4} Note: {5} Results: """.format(configs.configs['tool'], started_at, finished_at, configs.configs['params'], configs.configs['year'], configs.configs['note']) q = """ SELECT result, is_correct, witness, count(*) FROM task_results WHERE run_id = {0} GROUP BY result, is_correct, witness""".format(self.run_id) res = self._db.query(q) if not res: err('No results stored to db after this run?') total = 0 for row in res: result = row[0] if result == 'true' or result == 'false': if row[1] == 0: result += ' incorrect' else: result += ' correct' if not row[2] is None: text += '{0:<15} (witness {1}): {2}\n'.format( result, row[2], row[3]) else: text += '{0:<15}: {1}\n'.format(result, row[3]) total += row[3] text += '\nTotal number of benchmarks: {0}'.format(total) q = """SELECT tool_id FROM task_results WHERE run_id = {0}""".format(self.run_id) res = self._db.query(q) if not res: err('Failed querying db for tool\'s id') tool_id = res[0][0] text += '\n\nYou can check the results here:\n' text += 'http://macdui.fi.muni.cz:3000/tools/{0}'.format(tool_id) text += '\n\nHave a nice day!\n' msg = MIMEText(text) msg['Subject'] = 'Satt results from {0}'.format(started_at) msg['From'] = from_addr msg['To'] = '*****@*****.**' s = smtplib.SMTP(server) ret = s.sendmail(from_addr, to_addrs, msg.as_string()) s.quit() for r in ret: dbg('Failed sending e-mail to {0},' 'err: {1}'.format(r[0], r[1]))
def invoke_function( self, track, function_name, function_argv, depth=0, offset=0, memory={}): if self.debug: dbg('invoking function %s, args %s' % ( function_name, function_argv), depth) return vinvoke(track, function_name, function_argv, memory)
def magic_resolve_variable(self, track, field, depth): field_lower = field.lower() if self.debug: dbg('checking %s for magic mappings' % field_lower, depth) if field_lower in magic_mappings: mapping = magic_mappings[field_lower] if not mapping: dbg('mapping "%s" is not valid' % field_lower, depth) return track.get(field) else: # First try to call it -- the mapping can be a function. try: magically_resolved = mapping(self, track) if self.debug: dbg('mapped %s via function mapping' % field_lower, depth) return magically_resolved except TypeError: # That didn't work. It's a list. if self.debug: dbg('mapping "%s" is not a function' % field_lower, depth) for each in mapping: if self.debug: dbg('attempting to map "%s"' % each, depth) if each in track: return track.get(each) if self.case_sensitive: each_lower = each.lower() if self.debug: dbg('attempting to map "%s"' % each_lower, depth) if each_lower in track: return track.get(each_lower) # Still couldn't find it. if self.debug: dbg('mapping %s failed to map magic variable' % field_lower, depth) return track.get(field) if self.debug: dbg('mapping %s not found in magic variables' % field_lower, depth) return track.get(field)
def eval(self, track, title_format, conditional=False, depth=0, offset=0, memory={}): lookbehind = None outputting = True literal = False literal_count = None parsing_variable = False parsing_function = False parsing_function_args = False parsing_function_recursive = False parsing_conditional = False offset_start = 0 fn_offset_start = 0 bad_var_char = None conditional_parse_count = 0 evaluation_count = 0 recursive_lparen_count = 0 recursive_rparen_count = 0 output = '' current = '' current_fn = '' current_argv = [] if self.debug: dbg('fresh call to eval(); format="%s" offset=%s' % ( title_format, offset), depth) for i, c in enumerate(title_format): if outputting: if literal: next_output, literal, chars_parsed = self.parse_literal( c, i, lookbehind, literal_count, False, depth, offset + i) output += next_output literal_count += chars_parsed else: if c == "'": if self.debug: dbg('entering literal mode at char %s' % i, depth) literal = True literal_count = 0 elif c == '%': if self.debug: dbg('begin parsing variable at char %s' % i, depth) if parsing_variable or parsing_function or parsing_conditional: raise TitleFormatParseException( "Something went horribly wrong while parsing token '%'") outputting = False parsing_variable = True elif c == '$': if self.debug: dbg('begin parsing function at char %s' % i, depth) if parsing_variable or parsing_function or parsing_conditional: raise TitleFormatParseException( "Something went horribly wrong while parsing token '$'") outputting = False parsing_function = True fn_offset_start = i + 1 elif c == '[': if self.debug: dbg('begin parsing conditional at char %s' % i, depth) if parsing_variable or parsing_function or parsing_conditional: raise TitleFormatParseException( "Something went horribly wrong while parsing token '['") outputting = False parsing_conditional = True offset_start = i + 1 elif c == ']': message = self.make_backwards_error(']', '[', offset, i) raise TitleFormatParseException(message) else: output += c else: if parsing_variable: if literal: raise TitleFormatParseException( 'Invalid parse state: Cannot parse names while in literal mode') if c == '%': evaluated_value = self.resolve_variable(track, current, i, depth) if self.debug: dbg('value is: %s' % evaluated_value, depth) evaluated_value_str = unistr(evaluated_value) if evaluated_value or evaluated_value == '': if evaluated_value is not True: output += evaluated_value_str evaluation_count += 1 elif evaluated_value is not None and evaluated_value is not False: # This is the case where no evaluation happened but there is still # a string value (that won't output conditionally). output += evaluated_value_str else: output += '?' if self.debug: dbg('evaluation count is now %s' % evaluation_count, depth) current = '' outputting = True parsing_variable = False elif not self.is_valid_var_identifier(c): dbg('probably an invalid character: %s at char %i' % (c, i), depth) # Only record the first instance. if bad_var_char is None: bad_var_char = (c, offset + i) current += c else: current += c elif parsing_function: if literal: raise TitleFormatParseException( 'Invalid parse state: Cannot parse names while in literal mode') if c == '(': if current == '': raise TitleFormatParseException( "Can't call function with no name at char %s" % i) if self.debug: dbg('parsed function %s at char %s' % (current, i), depth) current_fn = current current = '' parsing_function = False parsing_function_args = True offset_start = i + 1 elif c == ')': message = self.make_backwards_error(')', '(', offset, i) raise TitleFormatParseException(message) elif not (c == '_' or c.isalnum()): raise TitleFormatParseException( "Illegal token '%s' encountered at char %s" % (c, i)) else: current += c elif parsing_function_args: if not parsing_function_recursive: if literal: next_current, literal, chars_parsed = self.parse_literal( c, i, lookbehind, literal_count, True, depth, offset + i) current += next_current literal_count += chars_parsed else: if c == ')': if current != '' or len(current_argv) > 0: current, arg = self.parse_fn_arg(track, current_fn, current, current_argv, c, i, depth, offset + offset_start, memory) current_argv.append(arg) if self.debug: dbg('finished parsing function arglist at char %s' % i, depth) fn_result = self.invoke_function( track, current_fn, current_argv, depth, offset + fn_offset_start) if self.debug: dbg('finished invoking function %s, value: %s' % ( current_fn, repr(fn_result)), depth) if fn_result is not None and fn_result is not False: str_fn_result = unistr(fn_result) if str_fn_result: output += str_fn_result if fn_result: evaluation_count += 1 if self.debug: dbg('evaluation count is now %s' % evaluation_count, depth) current_argv = [] outputting = True parsing_function_args = False elif c == "'": if self.debug: dbg('entering arglist literal mode at char %s' % i, depth) literal = True literal_count = 0 # Include the quotes because we reparse function arguments. current += c elif c == ',': current, arg = self.parse_fn_arg(track, current_fn, current, current_argv, c, i, depth, offset + offset_start, memory) current_argv.append(arg) offset_start = i + 1 elif c == '$': if self.debug: dbg('stopped evaluation for function in arg at char %s' % i, depth) current += c parsing_function_recursive = True recursive_lparen_count = 0 recursive_rparen_count = 0 else: current += c else: # parsing_function_recursive current += c if c == '(': recursive_lparen_count += 1 elif c == ')': recursive_rparen_count += 1 if recursive_lparen_count == recursive_rparen_count: # Stop skipping evaluation. if self.debug: dbg('resumed evaluation at char %s' % i, depth) parsing_function_recursive = False elif recursive_lparen_count < recursive_rparen_count: message = self.make_backwards_error(')', '(', offset, i) raise TitleFormatParseException(message) elif parsing_conditional: if literal: current += c if c == "'": if self.debug: dbg('leaving conditional literal mode at char %s' % i, depth) literal = False else: if c == '[': if self.debug: dbg('found a pending conditional at char %s' % i, depth) conditional_parse_count += 1 if self.debug: dbg('conditional parse count now %s' % conditional_parse_count, depth) current += c elif c == ']': if conditional_parse_count > 0: if self.debug: dbg('found a terminating conditional at char %s' % i, depth) conditional_parse_count -= 1 if self.debug: dbg('conditional parse count now %s at char %s' % ( conditional_parse_count, i), depth) current += c else: if self.debug: dbg('finished parsing conditional at char %s' % i, depth) evaluated_value = self.eval( track, current, True, depth + 1, offset + offset_start, memory) if self.debug: dbg('value is: %s' % evaluated_value, depth) if evaluated_value: output += unistr(evaluated_value) evaluation_count += 1 if self.debug: dbg('evaluation count is now %s' % evaluation_count, depth) current = '' conditional_parse_count = 0 outputting = True parsing_conditional = False elif c == "'": if self.debug: dbg('entering conditional literal mode at char %s' % i, depth) current += c literal = True else: current += c else: # Whatever is happening is invalid. raise TitleFormatParseException( "Invalid title format parse state: Can't handle character " + c) lookbehind = c # At this point, we have reached the end of the input. if outputting: if literal: message = self.make_unterminated_error('literal', "'", offset, i) raise TitleFormatParseException(message) else: message = None if parsing_variable: message = self.make_unterminated_error('variable', '%', offset, i) if bad_var_char is not None: message += " (probably caused by char '%s' in position %s)" % ( bad_var_char[0], bad_var_char[1]) elif parsing_function: message = self.make_unterminated_error('function', '(', offset, i) elif parsing_function_args: message = self.make_unterminated_error('function call', ')', offset, i) elif parsing_conditional: message = self.make_unterminated_error('conditional', ']', offset, i) else: message = "Invalid title format parse state: Unknown error" raise TitleFormatParseException(message) if conditional and evaluation_count == 0: if self.debug: dbg('about to return nothing for output: %s' % output, depth) return None if depth == 0 and self.for_filename: system = platform.system() if system == 'Windows' and re.match('^[A-Z]:', output, flags=re.I): disk_id = output[0:2] output = disk_id + re.sub('[\\\\/:|]', re.escape(os.sep), output[2:]) else: output = re.sub('[\\\\/:|]', re.escape(os.sep), output) output = re.sub('[*]', 'x', output) output = re.sub('"', "''", output) output = re.sub('[?<>]', '_', output) result = EvaluatorAtom(output, False if evaluation_count == 0 else True) if self.debug: dbg('eval() is returning: ' + repr(result), depth) return result