def find_next_end_else(line_mgr, start, end_only=False, allow_catch=False): """ Returns the corresponding end-statement or else-statement or catch-statement for the given clause-opener (which can be an if-statement, while-statement, etc.) Skips over end and else statements that are part of nested clauses. If end_only is set to True, all else statements are passed over. """ i = start open_clauses = 0 while i < len(line_mgr): line = line_mgr[i] if line == 'end': if open_clauses == 0: return ['end', i] else: open_clauses -= 1 elif (not end_only) and line == 'else' and open_clauses == 0: return ['else', i] elif (not end_only) and allow_catch and \ line.startswith('catch ') and open_clauses == 0: return ['catch', i] else: for opener in openers: if line.startswith(opener) or line == 'try': open_clauses += 1 break i += 1 throw_exception('NoEndStatement', 'Corresponding end statement does not exist.')
def build_type_table(tree): """ Creates a dictionary where all the nodes of a tree are directly accessible by name. e.g. A / \ B C \ D will become: { 'A': (tree A containing B,C), 'B': (tree B), 'C': (tree C containing D), 'D': (tree D) } """ entries = {} for name in tree.names: entries[name] = tree for subclass in tree.subclasses: child_entries = build_type_table(subclass) for key, val in child_entries.items(): if key in entries: throw_exception('CannotBuildTypeTable', 'Duplicate node name {0}'.format(key)) else: entries[key] = val return entries
def validate_string_index(string, i): length = len(string) # Indices can be negative, with -1 signifying the last char if i < -length or i >= length: exception.throw_exception( 'StringIndexOutOfBounds', 'Index {0} is out of bounds for {1}'.format(i, string))
def split_args(args): """ Given a comma-separated argument list, returns the individual arguments as a list. e.g. '(x+1)*8, y, 25' -> ['(x+1)*8', ' y', ' 25'] """ i = 0 buffer = '' results = [] while i < len(args): char = args[i] if char == '(': offset = find_matching(args[i + 1:]) if offset == -1: throw_exception('UnmatchedOpeningParenthesis', args) buffer += args[i:i + offset] i += offset elif char == '[': offset = find_matching(args[i + 1:], '[', ']') if offset == -1: throw_exception('UnmatchedOpeningSquareBracket', args) buffer += args[i:i + offset] i += offset elif char == ',': results.append(buffer) buffer = '' i += 1 else: buffer += char i += 1 if len(buffer) > 0: results.append(buffer) return results
def execute(self, arg_values, env): """ Executes this function's code, given argument values and an environment. Returns the result of the function. """ if len(self.args) != len(arg_values): throw_exception('ArgValueException', 'Number of arguments does not match function definition') if self.supplied_env is not None: env = self.supplied_env env.new_frame() env.merge_latest(self.defined_funcs) i = 0 # Put function arguments into environment frame: while i < len(self.args): current_arg = self.args[i] current_val = arg_values[i] env.assign(current_arg, current_val) i += 1 result = execute_lines(self.lines, env) # Remove the stack frame created by this function: env.pop() # Remove the last this pointer: if self.is_method: env.pop_this() if env.value_is_a(result, self.return_type): return result else: throw_exception('IncorrectReturnType', '{0} is not of type {1}'.format(result, self.return_type))
def eval_parentheses(expr, env): """ Recursively evaluates expressions enclosed in parentheses, which change the order-of-operations for the expression. """ ast = AST(expr) tokens = ast.parse() tokens = call_functions(tokens, env) if tokens.__class__ is Trigger: return tokens # For function values, do not transform the value to a string: if len(tokens) == 1 and (str(tokens[0]).startswith('<function') or \ type(tokens[0]) is list or type(tokens[0]) is dict): return tokens[0] expr = ''.join(str(token) for token in tokens) paren_open = expr.find('(') if paren_open == -1: return evaluate_expression(expr, env) else: paren_close = find_matching(expr[paren_open + 1:]) + paren_open if paren_close == -1: throw_exception('UnmatchedOpeningParenthesis', expr) left = expr[0:paren_open] center = expr[paren_open + 1:paren_close] right = expr[paren_close + 1:] return eval_parentheses(left + str(eval_parentheses(center, env)) + right, env)
def split_args(args): """ Given a comma-separated argument list, returns the individual arguments as a list. e.g. '(x+1)*8, y, 25' -> ['(x+1)*8', ' y', ' 25'] """ i = 0 buffer = '' results = [] while i < len(args): char = args[i] if char == '(': offset = find_matching(args[i + 1:]) if offset == -1: throw_exception('UnmatchedOpeningParenthesis', args) buffer += args[i : i+offset] i += offset elif char == '[': offset = find_matching(args[i + 1:], '[', ']') if offset == -1: throw_exception('UnmatchedOpeningSquareBracket', args) buffer += args[i : i+offset] i += offset elif char == ',': results.append(buffer) buffer = '' i += 1 else: buffer += char i += 1 if len(buffer) > 0: results.append(buffer) return results
def find_next_end_else(lines, start, end_only=False, allow_catch=False): """ Returns the corresponding end-statement or else-statement or catch-statement for the given clause-opener (which can be an if-statement, while-statement, etc.) Skips over end and else statements that are part of nested clauses. If end_only is set to True, all else statements are passed over. """ i = start open_clauses = 0 openers = ['if ', 'while ', 'for ', 'repeat ', 'switch ', 'sub '] while i < len(lines): line = lines[i] if line == 'end': if open_clauses == 0: return ['end', i] else: open_clauses -= 1 elif (not end_only) and line == 'else' and open_clauses == 0: return ['else', i] elif (not end_only) and allow_catch and \ line.startswith('catch ') and open_clauses == 0: return ['catch', i] else: for opener in openers: if line.startswith(opener) or line == 'try': open_clauses += 1 break i += 1 throw_exception('NoEndStatement', 'Corresponding end statement does not exist.')
def perform_import(library, env): library = library.strip() obj = None if ' into ' in library: pieces = library.split() library = pieces[0] obj = pieces[2] env.new_frame() if library == 'math': import_math(env) elif library == 'functional': import_functional(env) elif library == 'dataStructures': import_data_structures(env) elif library == 'debugging': import_debugging(env) elif library == 'prettyPrint': import_pretty_print(env) elif library == 'random': import_random(env) elif library == 'file': import_file(env) elif library.startswith('"') and library.endswith('"'): # Import a program from the file system capacita.execute_file(library[1:-1], env) else: throw_exception('NoExistingLibrary', 'No library named {0}'.format(library)) if obj is not None: frame = env.pop() env.assign(obj, frame)
def char_num(string): if len(string) == 0: throw_exception( 'StringEmpty', 'Cannot convert first character to integer when string is empty' ) return ord(string[0])
def get_type(value): kind = type(value) if kind is int: return 'Int' elif kind is float: return 'Double' elif kind is bool: return 'Boolean' elif kind is str: if value.startswith('#'): return 'Tag' else: return 'String' elif kind is list: return 'List' if value.__class__ is Ratio: return 'Ratio' elif value.__class__ is Table: return 'Table' elif value is None: return 'Null' elif kind is dict: # This is a user-defined object if '$type' in value: if value['$type'].startswith('"'): return value['$type'][1:-1] else: return value['$type'] else: return 'Instance' elif str(value).startswith('<function'): return 'Function' elif value.__class__ is Trigger: return 'Trigger' throw_exception('UnknownType', str(value) + ' has an unknown type.')
def get(self, key): if hashable(key) and key in self.hashable_pairs: return self.hashable_pairs[key] else: for k, v in self.unhashable_pairs: if k == key: return v throw_exception('KeyNotFound', 'Table has no key ' + str(key))
def modify_existing_unhashable(self, key, value): for i in xrange(len(self.unhashable_pairs)): k, v = self.unhashable_pairs[i] if k == key: self.unhashable_pairs[i] = (key, value) return throw_exception('ModifyingKeyNotFound', 'Trying to modify key ' + str(key) + ' that does not exist')
def type_restrict(val, type_tree, env): kind = type_tree.get_default_name() if type(val) is list: return RestrictedList(val, kind, env) else: throw_exception( 'CannotTypeRestrictValue', 'Value {0} cannot be restricted with type {1}'.format(val, kind) )
def modify_existing_unhashable(self, key, value): for i in xrange(len(self.unhashable_pairs)): k, v = self.unhashable_pairs[i] if k == key: self.unhashable_pairs[i] = (key, value) return throw_exception( 'ModifyingKeyNotFound', 'Trying to modify key ' + str(key) + ' that does not exist')
def update(self, var_name, value): # TODO : env.update(...) should be removed in favor # of transforms such as (age += 1) -> (age = age + 1) # Therefore the added functionality of env.assign(...) # will apply to these statements. if var_name in self.frames[-1]: self.frames[-1][var_name] = value else: throw_exception('UndefinedVariable', var_name + ' is not defined.')
def execute(self, arg_values, env=None): if not self.check_args_within_bounds(arg_values): throw_exception( 'IncorrectNumberOfArguments', 'Wrong number of arguments passed to built-in function\n' \ 'Expected between {0} and {1}, but received {2}'.format( self.min_args, self.max_args, len(arg_values) ) ) return self.action(*arg_values)
def set_digit(n, index, radix, new_digit): check_radix(radix) sign = -1 if n < 0 else 1 n = abs(n) if (not is_int(new_digit)) or new_digit < 0 or new_digit >= radix: throw_exception('UnsupportedDigit', 'Digit must be between 0 and {0} inclusive'.format(radix - 1)) prev_digit = get_digit(n, index, radix) scale = radix ** index prev_value = prev_digit * scale n -= prev_value n += new_digit * scale return sign * n
def get(self, var_name): last_this = self.last_this() if var_name in last_this: return last_this[var_name] i = -1 while i >= -len(self.frames): if var_name in self.frames[i]: value = self.frames[i][var_name] if type(value) is tuple: return value[1] return value else: i -= 1 throw_exception('UndefinedVariable', var_name + ' is not defined.')
def insert_skips(lines): """ Insert :skiptoelse statements into try-catch blocks """ i = 0 while i < len(lines): line = lines[i] if line == 'try': kind, j = find_next_end_else(lines, i + 1, False, True) if kind != 'catch': throw_exception('CannotFindCatch', 'Line found that was not catch statement: ' + str(lines[j])) catch_statement = lines[j] lines[j : j+1] = [':skiptoelse', catch_statement] i += 1 return lines
def partial(f, fixed_args): num_original_args = f.get_num_args() num_fixed_args = len(fixed_args) num_args_needed = num_original_args - num_fixed_args if num_args_needed < 0: throw_exception( 'TooManyFixedArguments', 'Function {0} requires {1} arguments, but passing in {2}'. format(f.name, num_original_args, num_fixed_args)) arg_names = ['x' + str(i) for i in xrange(num_args_needed)] def partially_applied_f(*args): return f.execute(fixed_args + list(args), env) return builtin_function.BuiltinFunction('fPrime', arg_names, partially_applied_f)
def assign(self, var_name, value): self.last_assigned = value # Check for a type restriction match_obj = re.match(r'([A-Za-z_][A-Za-z_0-9]*) (\$?[A-Za-z_][A-Za-z_0-9]*)', var_name) if match_obj: kind = match_obj.group(1) if not self.value_is_a(value, kind): throw_exception('MismatchedType', str(value) + ' is not of type ' + kind + '\nType tree is: ' + str(self.all_types)) var = match_obj.group(2) frame_or_this = self.get_frame_or_this(var) if var in frame_or_this: type_tuple = frame_or_this[var] if type(type_tuple) is tuple: existing_kind = type_tuple[0] if kind != existing_kind: throw_exception('AlreadyRestrictedType', var + ' already has type ' + existing_kind) else: throw_exception('AlreadyAnyType', var + ' already has type Any and cannot be further restricted.') frame_or_this[var] = (kind, value) else: # Is this an assignment to an index? match_obj = re.match(r'(\$?[A-Za-z_][A-Za-z_0-9]*)\[(.+)\]', var_name) if match_obj: indexable = match_obj.group(1) index = eval_parentheses(match_obj.group(2), self) frame_or_this = self.get_frame_or_this(indexable) container = frame_or_this[indexable] if type(container) is tuple: # Modify the iterable contained within the tuple iterable = container[1] iterable[index] = value else: # This iterable has no type restriction frame_or_this[indexable][index] = value else: # Is this an attribute of an object? match_obj = re.match(r'(\$?[A-Za-z_][A-Za-z_0-9]*)\.(\$?[A-Za-z_][A-Za-z_0-9]*)', var_name) if match_obj: obj = match_obj.group(1) attr = match_obj.group(2) frame_or_this = self.get_frame_or_this(obj) frame_or_this[obj][attr] = value else: frame_or_this = self.get_frame_or_this(var_name) if var_name in frame_or_this and type(frame_or_this[var_name]) is tuple: # There is an existing type restriction kind = frame_or_this[var_name][0] if not self.value_is_a(value, kind): throw_exception('MismatchedType', str(value) + ' is not of type ' + kind + '\nType tree is: ' + str(self.all_types)) frame_or_this[var_name] = (kind, value) else: # There is no type restriction given frame_or_this[var_name] = value
def insert_skips(line_mgr): """ Insert :skiptoelse statements into try-catch blocks """ i = 0 while i < len(line_mgr): line = line_mgr[i] if line == 'try': kind, j = find_next_end_else(line_mgr, i + 1, False, True) if kind != 'catch': throw_exception( 'CannotFindCatch', 'Line found that was not catch statement: ' + str(line_mgr[j])) catch_statement = line_mgr[j] line_mgr[j:j + 1] = [':skiptoelse', catch_statement] i += 1
def parse_elem(self, expr): """ Splits an expression based on its operators. e.g. '2 + 3*4' -> ['2', '+', '3', '*', '4'] """ buffer = '' tokens = [] expr_length = len(expr) i = 0 in_quotes = False while i < expr_length: next_char = expr[i] if next_char == '(': paren_close = find_matching(expr[i + 1:]) if paren_close == -1: throw_exception('UnmatchedOpeningParenthesis', expr) buffer += '(' + expr[i+1 : i+paren_close] i += paren_close else: op_detected = False if not in_quotes: for op in self.ordered_ops: op_length = len(op) if not(op == '.' and is_digit(expr[i+op_length : i+op_length+1])) and \ expr[i : i+op_length] == op: buffer_contents = buffer.strip() if len(buffer_contents) > 0: tokens.append(buffer_contents) tokens.append(op) buffer = '' i += op_length op_detected = True break if not op_detected: if next_char == '"': if in_quotes: in_quotes = False else: in_quotes = True buffer += next_char i += 1 buffer_contents = buffer.strip() if len(buffer_contents) > 0: tokens.append(buffer_contents) return self.merge_negatives(tokens)
def test_all(path, has_delay=True, use_repl=False): files = [f for f in os.listdir(path) if f.endswith('.cap')] print '===== Starting tests...' is_first_file = True for file in files: if has_delay and not is_first_file: time.sleep(3) is_first_file = False full_path = path + '/' + file print '===== Testing ' + full_path contents = fileio.file_to_str(full_path) try: test_program(contents, use_repl) except NameError as ex: exception.throw_exception('UnexpectedNameError', str(ex)) except BaseException as ex: print '===== Exception: ' + str(ex.__class__) print '===== Done.'
def parse_elem(self, expr, prev_elem): """ Splits an expression based on its operators. e.g. '2 + 3*4' -> ['2', '+', '3', '*', '4'] """ buffer = '' tokens = [] expr_length = len(expr) i = 0 in_quotes = False while i < expr_length: next_char = expr[i] if next_char == '(': paren_close = find_matching(expr[i + 1:]) if paren_close == -1: throw_exception('UnmatchedOpeningParenthesis', expr) buffer += '(' + expr[i + 1:i + paren_close] i += paren_close else: op_detected = False if (not in_quotes) and next_char in self.starters: for op in self.ordered_ops: op_length = len(op) if expr[i : i+op_length] == op and \ not(op == '.' and is_digit(expr[i+1 : i+2])): buffer_contents = buffer.strip() if len(buffer_contents) > 0: tokens.append(buffer_contents) tokens.append(op) buffer = '' i += op_length op_detected = True break if not op_detected: if next_char == '"': in_quotes = not in_quotes buffer += next_char i += 1 buffer_contents = buffer.strip() if len(buffer_contents) > 0: tokens.append(buffer_contents) return self.merge_negatives(self.merge_exponent_notation(tokens), prev_elem)
def dot_operator(obj, name, env): if type(obj) is str and re.match(r'\$?[A-Za-z_][A-Za-z_0-9]*', obj): obj = env.get(obj) obj = execution.convert_value(obj, env) method = None if type_restrict.is_considered_list(obj): method = list_methods(obj, name, env) elif obj.__class__ is strtools.CapString: method = str_methods(obj, name, env) elif type(obj) is dict: method = obj_methods(obj, name, env) elif type(obj) in [int, long, float]: method = number_methods(obj, name, env) elif obj.__class__ is Table: method = table_methods(obj, name, env) elif obj.__class__ is type_tree.TypeTree: method = type_tree_methods(obj, name, env) if method is None: throw_exception('NoSuchAttribute', str(obj) + ' object has no attribute ' + name) else: return method
def merge_trees(tree, other): """ Combines two type trees into a single tree. The new tree will contain all the children of both original trees. Children that are shared will be recursively merged. e.g. Take the two trees: A A / \ / \ B C D B | / \ G E F The result should be: A /|\ B C D /|\ G E F """ if tree.names != other.names: throw_exception( 'CannotMergeTrees', 'Root value {0} is not the same as {1}'.format( tree.names, other.names)) combined_subclasses = {} for subclass in tree.subclasses: combined_subclasses[subclass.names] = subclass for subclass in other.subclasses: if subclass.names in combined_subclasses: old_tree = combined_subclasses[subclass.names] new_tree = merge_trees(old_tree, subclass) combined_subclasses[subclass.names] = new_tree else: combined_subclasses[subclass.names] = subclass result = TypeTree(*tree.names) for val in combined_subclasses.values(): result.add_subclass(val) return result
def dot_operator(obj, name, env): if type(obj) is str and re.match(r'\$?[A-Za-z_][A-Za-z_0-9]*', obj): obj = env.get(obj) obj = convert_value(obj, env) if type(obj) is list: if name == 'length' or name == 'size': length = len(obj) return BuiltinFunction('constant', [], lambda: length) elif name == 'pop': last_elem = obj.pop() return BuiltinFunction('constant', [], lambda: last_elem) elif name == 'push': def func_push(new_elem): obj.append(new_elem) return obj return BuiltinFunction('list', ['new_elem'], func_push) elif type(obj) is dict: # This is a method call if obj[name].__class__ is BuiltinFunction: # Built in functions don't need a this pointer return obj[name] else: env.new_this(obj) method = obj[name] method.activate_method() return method elif type(obj) in [int, float]: if name == 'next': return BuiltinFunction('constant', [], lambda: obj + 1) elif name == 'previous': return BuiltinFunction('constant', [], lambda: obj - 1) elif obj.__class__ is Table: if name == 'length' or name == 'size': return BuiltinFunction('constant', [], lambda: len(obj)) elif name == 'keys': return BuiltinFunction('constant', [], lambda: obj.keys()) elif name == 'hasKey': return BuiltinFunction('hasKey', ['key'], lambda key: obj.has_key(key)) throw_exception('NoSuchAttribute', str(obj) + ' object has no attribute ' + name)
def split_list_elems(self): """ Splits apart list and table literals into brackets, commas, and elements. e.g. '[1, 2+3, 4*5]' -> ['[', '1', ',', '2+3', ',', '4*5', ']'] '{1, 2 | 3, 4}' -> ['{', '1', ',', '2', '|', '3', ',', '4', '}'] """ def append_if_not_empty(lst, elem): if len(elem) > 0: lst.append(elem) results = [] buffer = '' delimiters = '[],{}|' i = 0 while i < len(self.expr): char = self.expr[i] if char in delimiters: append_if_not_empty(results, buffer) results.append(char) buffer = '' elif char == '(': j = find_matching(self.expr[i + 1:]) if j == -1: throw_exception('UnmatchedOpeningParenthesis', self.expr) buffer += self.expr[i:i + j] i += j continue elif char == '"': j = find_matching_quote(self.expr[i + 1:]) if j == -1: throw_exception('UnmatchedQuote', self.expr) buffer += self.expr[i:i + j + 1] + '"' i += j + 1 else: buffer += char i += 1 append_if_not_empty(results, buffer) return results
def get_type(value): kind = type(value) if kind is int or kind is long: return 'Int' elif kind is float: return 'Double' elif kind is bool: return 'Boolean' elif kind is str: if value.startswith('#'): return 'Tag' throw_exception('UnknownValueType', 'The type of {0} cannot be determined'.format(value)) elif kind is list: return 'List' val_class = value.__class__ if val_class is strtools.CapString: return 'String' if val_class is Ratio: return 'Ratio' elif val_class is Table: return 'Table' elif value is None: return 'Null' elif kind is dict: # This is a user-defined object if '$type' in value: if value['$type'].__class__ is strtools.CapString: return value['$type'].contents else: return value['$type'] else: return 'Instance' elif function.is_function(value): return 'Function' elif val_class is Trigger: return 'Trigger' throw_exception('UnknownType', str(value) + ' has an unknown type.')
def split_list_elems(self): """ Splits apart list and table literals into brackets, commas, and elements. e.g. '[1, 2+3, 4*5]' -> ['[', '1', ',', '2+3', ',', '4*5', ']'] '{1, 2 | 3, 4}' -> ['{', '1', ',', '2', '|', '3', ',', '4', '}'] """ def append_if_not_empty(lst, elem): if len(elem) > 0: lst.append(elem) results = [] buffer = '' delimiters = ['[', ']', ',', '{', '}', '|'] i = 0 while i < len(self.expr): char = self.expr[i] if char in delimiters: append_if_not_empty(results, buffer) results.append(char) buffer = '' elif char == '(': j = find_matching(self.expr[i + 1:]) if j == -1: throw_exception('UnmatchedOpeningParenthesis', self.expr) buffer += self.expr[i : i+j] i += j continue elif char == '"': j = find_matching_quote(self.expr[i + 1:]) if j == -1: throw_exception('UnmatchedQuote', self.expr) buffer += self.expr[i : i+j+1] + '"' i += j + 1 else: buffer += char i += 1 append_if_not_empty(results, buffer) return results
def feed(*args): if len(args) == 0: throw_exception('NotEnoughArguments', 'Function requires at least 1 argument') result = args[0] for elem in args[1:]: if function.is_function(elem): result = elem.execute([result], env) elif type(elem) is list: if len(elem) == 0 or not function.is_function(elem[0]): throw_exception( 'InvalidArgument', 'List must contain a function as first element') f = elem[0] rest = list(elem[1:]) result = f.execute(rest + [result], env) else: throw_exception('InvalidArgument', 'Argument must be a list or function') return result
def evaluate_operators(tokens, indices, env): """ Evaluates an expression based on a list of tokens, indices where the operators are located, and environment env. """ brackets = ['[', ']', '{', '}'] transform_string_literals(tokens) for idx in indices: # TODO : checking bounds should no longer be # necessary after fixing the xrange issue. # (passing in an xrange as operator indices) if idx >= len(tokens): break op = tokens[idx] is_dot = op == '.' if idx > 0: left = convert_value(tokens[idx - 1], env) else: left = None if idx + 1 >= len(tokens): # This index is not valid: break if not is_dot: right = convert_value(tokens[idx + 1], env) else: right = tokens[idx + 1] if left in brackets or right in brackets: break left, right = promote_values(left, right) # TODO : support user-defined methods such as $eq, $notEq when evaluating operators if is_dot: tokens[idx - 1:idx + 2] = [environment.get_typed_value(left[right])] elif op != ' of ' and operator_overload.ready_for_overload( op, left, right): # Perform operator overloading result = operator_overload.operator_overload( op, left, right, idx, tokens, env) return_val = result['return_val'] if result['is_unary']: tokens[idx:idx + 2] = [return_val] else: tokens[idx - 1:idx + 2] = [return_val] elif op == '+': tokens[idx - 1:idx + 2] = [left + right] elif op == '-': tokens[idx - 1:idx + 2] = [left - right] elif op == '~': tokens[idx:idx + 2] = [-right] elif op == '*': tokens[idx - 1:idx + 2] = [left * right] elif op == '/': tokens[idx - 1:idx + 2] = [divide(left, right)] elif op == '%': tokens[idx - 1:idx + 2] = [left % right] elif op == '==': tokens[idx - 1:idx + 2] = [left == right] elif op == '!=': tokens[idx - 1:idx + 2] = [left != right] elif op == '>=': tokens[idx - 1:idx + 2] = [left >= right] elif op == '<=': tokens[idx - 1:idx + 2] = [left <= right] elif op == '>': tokens[idx - 1:idx + 2] = [left > right] elif op == '<': tokens[idx - 1:idx + 2] = [left < right] elif op == ' and ': tokens[idx - 1:idx + 2] = [left and right] elif op == ' or ': tokens[idx - 1:idx + 2] = [left or right] elif op == 'not ': tokens[idx:idx + 2] = [not right] elif op == '^': tokens[idx - 1:idx + 2] = [left**right] elif op == ':': tokens[idx - 1:idx + 2] = [Ratio(left, right)] elif op == ' of ': tokens[idx - 1:idx + 2] = [type_restrict.type_restrict(left, right, env)] elif op == ' xor ': tokens[idx - 1:idx + 2] = [(left and not right) or ((not left) and right)] stage = 0 while len(tokens) != 1: if stage == 0: tokens = evaluate_list(tokens, env) stage = 1 elif stage == 1: tokens = index_lists(tokens, env) stage = 2 elif stage == 2: input_tokens = tokens # TODO : avoid having to pass in an xrange of possible operator indices tokens = evaluate_operators(tokens, xrange(len(tokens) - 1), env) # If the value returned is no longer the outer list, it means # an inner list is the result: if tokens is not input_tokens: return tokens stage = 3 elif stage == 3: throw_exception( 'ExprEval', str(tokens) + ' cannot be converted into a single value') # Return the resulting (single) value without the outer list. return convert_value(tokens[0], env)
def prepare_control_flow(line_mgr): """ Converts if-statements, while-statements, for-statements, etc. into :cond and jump directives (:j for unconditional jump, :jt for jump true, and :jf for jump false). e.g. x = 100 if x > 0 // do something else // do other thing end 0: x = 100 1: :cond x > 0 2: :jf 5 3: // do something 4: :j 6 5: // do other thing 6: """ def replace_labels(line_mgr): """ Replaces labels in jump directives with the actual addresses being jumped to. """ label_table = {} i = 0 for line in line_mgr.get_lines(): if line.startswith(':label'): label_table[line[6:]] = i # Erase the label at this line: line_mgr[i] = '' i += 1 i = 0 jumps = [':j ', ':jt ', ':jf '] for line in line_mgr.get_lines(): for jump in jumps: if line.startswith(jump + 'label'): label_num = line[len(jump) + 5:] if label_num in label_table: line_mgr[i] = jump + str(label_table[label_num]) break i += 1 def prepare_else_ifs(line_mgr): """ Splits else-if statements into two separate lines: else followed by an if. Adds additional end statements for padding. This allows else-if statements to be evaluated using only basic if-else logic. For example, the following structures are equivalent: x = 2 if x == 1 A else if x == 2 B else C end x = 2 if x == 1 A else if x == 2 B else C end end """ i = 0 while i < len(line_mgr): line = line_mgr[i] if line.startswith('if '): # Find the end statement: _, end = find_next_end_else(line_mgr, i + 1, True) j = i + 1 # Keep track of how many else-ifs were split: splits = 0 while j < end: other_line = line_mgr[j] if other_line.startswith('else if '): line_mgr[j:j + 1] = ['else', other_line[5:]] end += 1 splits += 1 j += 1 # Add extra end statements: line_mgr[end:end + 1] = ['end' for _ in xrange(splits + 1)] i += 1 def prepare_breaks_continues(line): """ Replace 'break' with 'break 1' and 'continue' with 'continue 1'. Thus, all breaks and continues will be supplied with a depth parameter. """ if line == 'break': return 'break 1' elif line == 'continue': return 'continue 1' else: return line def replace_for(line_mgr): """ Replaces for loops with for directives, eventually translated to the equivalent of a while loop. for i = 0; i < 10; i++ print i end i = 0 while i < 10 print i i++ end """ i = 0 while i < len(line_mgr): line = line_mgr[i] if line.startswith('for '): initialization = line[4:] condition = line_mgr[i + 1] increment = line_mgr[i + 2] _, j = find_next_end_else(line_mgr, i + 1, True) line_mgr[j:j + 1] = [increment, 'end'] line_mgr[i:i + 3] = [initialization, ':for ' + condition, increment] i += 1 def replace_for_each(line_mgr): """ Replaces for-each loops with while loops. for each x of xs print x end $v0 = 0 while $v0 < xs.length() x = xs[$v0] print x $v0++ end """ i = 0 index_var_num = 0 while i < len(line_mgr): line = line_mgr[i] match_obj = re.match(r'for each ([A-Za-z_][A-Za-z_0-9]*) of (.*)', line) if match_obj: elem_var = match_obj.group(1) iterable = match_obj.group(2) index_var = '$i' + str(index_var_num) initialization = index_var + '=0' condition = index_var + '<' + iterable + '.length()' increment = index_var + '+=1' assignment = elem_var + '=' + iterable + '[' + index_var + ']' _, j = find_next_end_else(line_mgr, i + 1, True) line_mgr[j:j + 1] = [increment, 'end'] # Temporarily place the increment statement after the :for directive, # so it can be used when processing continue statements. line_mgr[i:i + 1] = [ initialization, ':for ' + condition, increment, assignment ] index_var_num += 1 i += 1 def insert_skips(line_mgr): """ Insert :skiptoelse statements into try-catch blocks """ i = 0 while i < len(line_mgr): line = line_mgr[i] if line == 'try': kind, j = find_next_end_else(line_mgr, i + 1, False, True) if kind != 'catch': throw_exception( 'CannotFindCatch', 'Line found that was not catch statement: ' + str(line_mgr[j])) catch_statement = line_mgr[j] line_mgr[j:j + 1] = [':skiptoelse', catch_statement] i += 1 def replace_when_continue(line_mgr): """ Transforms a when statement containing a continue in its clause into an if statement. when CONDITION continue 2 if CONDITION continue 2 end This is so that continue statements can be replaced by an increment statement followed by a continue, which is needed in for loops and for-each loops. """ i = 0 while i + 1 < len(line_mgr): current_line = line_mgr[i] next_line = line_mgr[i + 1] if current_line.startswith('when ') and next_line.startswith( 'continue '): line_mgr[i:i + 2] = ['if ' + current_line[5:], next_line, 'end'] i += 1 prepare_else_ifs(line_mgr) line_mgr.for_each_line(prepare_breaks_continues) replace_when_continue(line_mgr) replace_for_each(line_mgr) replace_for(line_mgr) insert_skips(line_mgr) i = 0 label_counter = 0 while i < len(line_mgr): line = line_mgr[i] if line.startswith('when '): line_mgr[i:i + 1] = [':cond ' + line[5:], ':jf ' + str(i + 3)] elif line.startswith('if '): line_mgr[i:i + 1] = [ ':cond ' + line[3:], ':jf label' + str(label_counter) ] # Find the next else or end statement: kind, j = find_next_end_else(line_mgr, i + 2) if kind == 'end': line_mgr[j] = ':label' + str(label_counter) else: # kind must be an else statement. # replace the else statement with a jump: else_label = ':label' + str(label_counter) label_counter += 1 line_mgr[j:j + 1] = [':j label' + str(label_counter), else_label] kind, end = find_next_end_else(line_mgr, j + 1) if kind == 'else': throw_exception( 'MultipleElseStatement', 'if statements cannot have multiple else clauses' ' (aside from else-if statements).') line_mgr[end] = ':label' + str(label_counter) label_counter += 1 elif is_loop(line): if line.startswith(':for '): # Remove the increment statement, and save it for later use increment_statement = line_mgr.pop(i + 1) condition_start = 5 else: increment_statement = None condition_start = 6 # label_leave is used to exit the loop: label_leave = str(label_counter) label_counter += 1 # label_loop is used to repeat the loop: label_loop = str(label_counter) label_counter += 1 # Replace the existing while statement or :for directive with a :cond directive line_mgr[i:i + 1] = [ ':cond ' + line[condition_start:], ':jf label' + label_leave, ':label' + label_loop ] kind, j = find_next_end_else(line_mgr, i + 3) if kind == 'end': line_mgr[j:j + 1] = [ ':cond ' + line[condition_start:], ':jt label' + label_loop, ':label' + label_leave ] k = i + 3 depth = 1 # stack for keeping track of end statements: stack = [] # Handle 'continue' and 'break' statements: while k < j: current = line_mgr[k] if is_loop(current): _, end = find_next_end_else(line_mgr, k + 1, True) depth += 1 stack.append(end) elif len( stack) > 0 and current == 'end' and stack[-1] == k: # This is the end to a nested while loop: stack.pop() depth -= 1 elif current == 'break ' + str(depth): line_mgr[k] = ':j label' + label_leave elif current == 'continue ' + str(depth): jump_to_loop_start = ':j label' + label_loop if increment_statement is not None: line_mgr[k:k + 1] = [ increment_statement, jump_to_loop_start ] else: line_mgr[k] = jump_to_loop_start k += 1 else: throw_exception('InvalidElseStatement', 'While loops cannot have else statements.') i += 1 replace_labels(line_mgr)
def enforce_type(val, kind, env, msg=''): if not env.value_is_a(val, kind): throw_exception( 'IncorrectTypeGiven', '{0} is not of type {1} {2}'.format(val, kind, msg) )
def prepare_control_flow(lines): """ Converts if-statements, while-statements, for-statements, etc. into :cond and jump directives (:j for unconditional jump, :jt for jump true, and :jf for jump false). e.g. x = 100 if x > 0 // do something else // do other thing end 0: x = 100 1: :cond x > 0 2: :jf 5 3: // do something 4: :j 6 5: // do other thing 6: """ def replace_labels(lines): """ Replaces labels in jump directives with the actual addresses being jumped to. """ label_table = {} i = 0 for line in lines: if line.startswith(':label'): label_table[line[6:]] = i # Erase the label at this line: lines[i] = '' i += 1 i = 0 jumps = [':j ', ':jt ', ':jf '] for line in lines: for jump in jumps: if line.startswith(jump + 'label'): label_num = line[len(jump) + 5:] if label_num in label_table: lines[i] = jump + str(label_table[label_num]) break i += 1 return lines def prepare_else_ifs(lines): """ Splits else-if statements into two separate lines: else followed by an if. Adds additional end statements for padding. This allows else-if statements to be evaluated using only basic if-else logic. For example, the following structures are equivalent: x = 2 if x == 1 A else if x == 2 B else C end x = 2 if x == 1 A else if x == 2 B else C end end """ i = 0 while i < len(lines): line = lines[i] if line.startswith('if '): # Find the end statement: _, end = find_next_end_else(lines, i + 1, True) j = i + 1 # Keep track of how many else-ifs were split: splits = 0 while j < end: other_line = lines[j] if other_line.startswith('else if '): lines[j : j+1] = ['else', other_line[5:]] end += 1 splits += 1 j += 1 # Add extra end statements: lines[end : end+1] = ['end' for _ in xrange(splits + 1)] i += 1 return lines def prepare_breaks_continues(lines): """ Replace 'break' with 'break 1' and 'continue' with 'continue 1'. Thus, all breaks and continues will be supplied with a depth parameter. """ i = 0 while i < len(lines): line = lines[i] if line == 'break': lines[i] = 'break 1' elif line == 'continue': lines[i] = 'continue 1' i += 1 return lines def replace_for(lines): """ Replaces for loops with while loops. for i = 0; i < 10; i++ print i end i = 0 while i < 10 print i i++ end """ i = 0 while i < len(lines): line = lines[i] if line.startswith('for '): initialization = line[4:] condition = lines[i + 1] increment = lines[i + 2] _, j = find_next_end_else(lines, i + 1, True) lines[j : j+1] = [increment, 'end'] lines[i : i+3] = [initialization, 'while ' + condition] i += 1 return lines def replace_for_each(lines): """ Replaces for-each loops with while loops. for each x of xs print x end $v0 = 0 while $v0 < xs.length() x = xs[$v0] print x $v0++ end """ i = 0 index_var_num = 0 while i < len(lines): line = lines[i] match_obj = re.match(r'for each ([A-Za-z_][A-Za-z_0-9]*) of (.*)', line) if match_obj: elem_var = match_obj.group(1) iterable = match_obj.group(2) index_var = '$i' + str(index_var_num) initialization = index_var + '=0' condition = index_var + '<' + iterable + '.length()' increment = index_var + '+=1' assignment = elem_var + '=' + iterable + '[' + index_var + ']' _, j = find_next_end_else(lines, i + 1, True) lines[j : j+1] = [increment, 'end'] lines[i : i+1] = [initialization, 'while ' + condition, assignment] index_var_num += 1 i += 1 return lines def insert_skips(lines): """ Insert :skiptoelse statements into try-catch blocks """ i = 0 while i < len(lines): line = lines[i] if line == 'try': kind, j = find_next_end_else(lines, i + 1, False, True) if kind != 'catch': throw_exception('CannotFindCatch', 'Line found that was not catch statement: ' + str(lines[j])) catch_statement = lines[j] lines[j : j+1] = [':skiptoelse', catch_statement] i += 1 return lines lines = prepare_else_ifs(lines) lines = prepare_breaks_continues(lines) lines = replace_for_each(lines) lines = replace_for(lines) lines = insert_skips(lines) i = 0 label_counter = 0 while i < len(lines): line = lines[i] if line.startswith('when '): lines[i : i+1] = [':cond ' + line[5:], ':jf ' + str(i + 3)] elif line.startswith('if '): lines[i : i+1] = [':cond ' + line[3:], ':jf label' + str(label_counter)] # Find the next else or end statement: kind, j = find_next_end_else(lines, i + 2) if kind == 'end': lines[j] = ':label' + str(label_counter) else: # kind must be an else statement. # replace the else statement with a jump: else_label = ':label' + str(label_counter) label_counter += 1 lines[j : j+1] = [':j label' + str(label_counter), else_label] kind, end = find_next_end_else(lines, j + 1) if kind == 'else': throw_exception('MultipleElseStatement', 'if statements cannot have multiple else clauses' ' (aside from else-if statements).') lines[end] = ':label' + str(label_counter) label_counter += 1 elif line.startswith('while '): # label_leave is used to exit the loop: label_leave = str(label_counter) label_counter += 1 # label_loop is used to repeat the loop: label_loop = str(label_counter) label_counter += 1 lines[i : i+1] = [':cond ' + line[6:], ':jf label' + label_leave, ':label' + label_loop] kind, j = find_next_end_else(lines, i + 3) if kind == 'end': lines[j : j+1] = [':cond ' + line[6:], ':jt label' + label_loop, ':label' + label_leave] k = i + 3 depth = 1 # stack for keeping track of end statements: stack = [] # Handle 'continue' and 'break' statements: while k < j: current = lines[k] if current.startswith('while '): _, end = find_next_end_else(lines, k + 1, True) depth += 1 stack.append(end) elif len(stack) > 0 and current == 'end' and stack[-1] == k: # This is the end to a nested while loop: stack.pop() depth -= 1 elif current == 'break ' + str(depth): lines[k] = ':j label' + label_leave elif current == 'continue ' + str(depth): lines[k] = ':j label' + label_loop k += 1 else: throw_exception('InvalidElseStatement', 'While loops cannot have else statements.') i += 1 lines = replace_labels(lines) return lines
def extract_string(cap_string): if cap_string.__class__ is not strtools.CapString: throw_exception('ExpectedString', '{0} is not a string'.format(cap_string)) return cap_string.contents
def throw_select_on_empty_list(): throw_exception('SelectOnEmptyList', "Can't select item from empty list")
def check_radix(radix): if (not is_int(radix)) or radix < 2 or radix > 36: throw_exception('UnsupportedBase', 'Base must be between 2 and 36 inclusive')
def execute(self, arg_values, env=None): if self.args is not None and len(arg_values) != len(self.args): throw_exception('IncorrectNumberOfArguments', 'Wrong number of arguments passed to built-in function') return self.action(*arg_values)
def execute_lines(lines, env): """ Executes lines of code in a given environment. """ prgm_counter = 0 cond_flag = False while prgm_counter < len(lines): line = lines[prgm_counter] directive = execute_statement(line, env) if directive is not None: if directive[0] == ':cond': cond_flag = eval_parentheses(directive[1], env) prgm_counter += 1 elif directive[0] == ':j': prgm_counter = int(directive[1]) elif (cond_flag and directive[0] == ':jt') or \ ((not cond_flag) and directive[0] == ':jf'): prgm_counter = int(directive[1]) elif directive[0] == ':skiptoelse': # Nothing was thrown in this try clause _, j = find_next_end_else(lines, prgm_counter + 1, False) prgm_counter = j + 1 elif directive[0] == ':hook': env.activate_hook(directive[1]) prgm_counter += 1 elif directive[0] == 'return': if directive[1] == 'this': # Create a user-defined object. # Do not use env.pop() because the function # will automatically remove the stack frame # upon completion. obj = env.last() # Allow the object to refer to itself obj['this'] = obj return obj else: value = eval_parentheses(directive[1], env) if str(value).startswith('<function'): value = value.supply(env) return value elif directive[0] == 'try': env.exception_push(prgm_counter) prgm_counter += 1 elif directive[0] == 'throw': prgm_counter = env.exception_pop() if prgm_counter is None: throw_exception('UncaughtException', 'Thrown value ' + str(directive[1:])) else: original_counter = prgm_counter # Look for a matching catch statement prgm_counter = find_catch(directive, lines, prgm_counter, env) if prgm_counter is None: # We can't find a catch statement. # Let the exception bubble up from its current scope. env.exception_push(original_counter) return Trigger(directive[1]) elif directive[0] in ['catch', 'else']: kind, j = find_next_end_else(lines, prgm_counter + 1, True) # Travel to the next end prgm_counter = j + 1 elif directive[0] == 'end': # This is an isolated end statement. Ignore it. prgm_counter += 1 else: prgm_counter += 1 else: prgm_counter += 1
def assign_arguments(arg_names, arg_values, env): num_grouping_args = count_grouping_arguments(arg_names) if num_grouping_args == 0: if len(arg_values) > len(arg_names): throw_exception( 'TooManyArgumentsException', 'Number of arguments exceeds number expected in function definition' ) for i in xrange(len(arg_names)): current_name = arg_names[i] if '=' in current_name: # This argument name has a default value given # TODO : the following line does not account for operators # such as ==, !=, <=, etc. pieces = [piece.strip() for piece in current_name.split('=')] if len(pieces) != 2: throw_exception( 'DefaultArgumentException', 'Incorrect default argument syntax in {0}'.format( current_name)) var_name, var_expr = pieces if is_out_of_bounds(i, arg_values): env.assign(var_name, execution.eval_parentheses(var_expr, env)) else: # Instead of the default value, use the value already given env.assign(var_name, arg_values[i]) else: if is_out_of_bounds(i, arg_values): throw_exception( 'ArgValueException', 'Number of arguments does not match function definition' ) else: env.assign(current_name, arg_values[i]) elif num_grouping_args == 1: for i in xrange(len(arg_names)): if is_grouping_argument(arg_names[i]): break # Remove brackets around group name group_name = arg_names[i][1:-1].strip() num_left = i num_right = len(arg_names) - num_left - 1 # TODO : account for default arguments # (num_left + num_right) might be greater than the actual number of needed arguments if len(arg_values) < num_left + num_right: throw_exception( 'TooFewArgumentsException', 'Number of arguments is less than number expected in function definition' ) else: values_for_left = arg_values[0:i] if num_right == 0: values_for_right = [] values_for_middle = arg_values[i:] else: values_for_right = arg_values[-num_right:] values_for_middle = arg_values[i:-num_right] env.assign(group_name, values_for_middle) other_arg_names = arg_names[0:i] + arg_names[i + 1:] assign_arguments(other_arg_names, values_for_left + values_for_right, env) else: throw_exception( 'GroupingArgumentException', 'Cannot have more than 1 grouping argument in function definition')
def evaluate_operators(tokens, indices, env): """ Evaluates an expression based on a list of tokens, indices where the operators are located, and environment env. """ brackets = ['[', ']', '{', '}'] for idx in indices: # TODO : checking bounds should no longer be # necessary after fixing the xrange issue. # (passing in an xrange as operator indices) if idx >= len(tokens): break op = tokens[idx] is_dot = op == '.' if idx > 0: left = convert_value(tokens[idx-1], env) else: left = None if idx + 1 >= len(tokens): # This index is not valid: break if not is_dot: right = convert_value(tokens[idx+1], env) else: right = tokens[idx+1] if left in brackets or right in brackets: break left, right = promote_values(left, right) if op == '+': tokens[idx-1 : idx+2] = [plus(left, right)] elif op == '-': if idx == 0: tokens[idx : idx+2] = [-right] else: tokens[idx-1 : idx+2] = [left - right] elif op == '*': tokens[idx-1 : idx+2] = [left * right] elif op == '/': # Todo allow for better ratio and int division tokens[idx-1 : idx+2] = [float(left) / float(right)] elif op == '%': tokens[idx-1 : idx+2] = [left % right] elif op == '^': tokens[idx-1 : idx+2] = [left ** right] elif op == ':': tokens[idx-1 : idx+2] = [Ratio(left, right)] elif op == '==': tokens[idx-1 : idx+2] = [left == right] elif op == '!=': tokens[idx-1 : idx+2] = [left != right] elif op == '>=': tokens[idx-1 : idx+2] = [left >= right] elif op == '<=': tokens[idx-1 : idx+2] = [left <= right] elif op == '>': tokens[idx-1 : idx+2] = [left > right] elif op == '<': tokens[idx-1 : idx+2] = [left < right] elif op == ' and ': tokens[idx-1 : idx+2] = [left and right] elif op == ' or ': tokens[idx-1 : idx+2] = [left or right] elif op == ' xor ': tokens[idx-1 : idx+2] = [(left and not right) or ((not left) and right)] elif op == 'not ': tokens[idx : idx+2] = [not right] elif op == '.': tokens[idx-1 : idx+2] = [left[right]] stage = 0 while len(tokens) != 1: if stage == 0: tokens = evaluate_list(tokens, env) stage = 1 elif stage == 1: tokens = index_lists(tokens, env) stage = 2 elif stage == 2: input_tokens = tokens # TODO : avoid having to pass in an xrange of possible operator indices tokens = evaluate_operators(tokens, xrange(len(tokens) - 1), env) # If the value returned is no longer the outer list, it means # an inner list is the result: if tokens is not input_tokens: return tokens stage = 3 elif stage == 3: throw_exception('ExprEval', str(tokens) + ' cannot be converted into a single value') # Return the resulting (single) value without the outer list. return convert_value(tokens[0], env)