def __translate_define_expr(self, expr, end=True): ''' Get a single parsed DEFINE expression and return the equivalent C++ and CUDA code. If 'end' is false, then the final characters of the expression, like semi-colons and newlines, are not added. ''' cpp = '' cuda = '' return_type = Type.enum_to_c_type(expr.loc, expr.type) cpp = f'{return_type} {expr.name}(' # Add the parameters. for (arg_type, arg_name) in expr.args[:-1]: cpp += f'{Type.enum_to_c_type(expr.loc, arg_type)} {arg_name}, ' # The last parameter is a little different. if len(expr.args) > 0: (arg_type, arg_name) = expr.args[-1] cpp += f'{Type.enum_to_c_type(expr.loc, arg_type)} {arg_name}' cpp += ')' # Add this prototype for use in a header file. self.cpp_prototypes.append(cpp + ';\n') cpp += ' {\n' # Add the body of the function. body_cpp = '' for e in expr.body[:-1]: (c, cu) = self.__translate_expr(e, True) body_cpp += c cuda += cu # The last expression should be returned. if len(expr.body) == 0: error_str = 'expected one or more body expressions' raise error.Syntax(expr.loc, error_str) e = expr.body[-1] if e.exprClass != ExprEnum.LITERAL and \ e.exprClass != ExprEnum.GET_VAR and \ e.exprClass != ExprEnum.CALL: error_str = 'expected literal, get, or call as last body expression' raise error.Syntax(expr.loc, error_str) (c, cu) = self.__translate_expr(e, False) body_cpp += f'return {c};\n' cuda += cu self._increase_indent() body_cpp = self._make_indented(body_cpp) self._decrease_indent() cpp = self._make_indented(cpp) cpp = cpp + body_cpp + self._make_indented('}\n\n') return (cpp, cuda)
def __parse_expr(self, parsing_exprs_list=False, got_paren=False): ''' Private function to parse and return a single expression. If parsing_exprs_list is true, then reading the next non-whitespace character as ')' causes this function to return None, indicating that there are no more expressions in the list. If parsing_exprs_list is false and a ')' is read, an error occurs indicating invalid syntax. If got_paren is false, then the expression must start with '('. If got_paren is true, then the opening parenthesis was already read so the expression should begin with a keyword like lit, val, call, .... ''' if not got_paren: # Make sure the expression starts with an open paranthesis. c = self.__eat_whitespace() if c == '': # The end of file was reached return if parsing_exprs_list and c == ')': # The end of the expression list has been reached. return None if c != '(': loc = self.__get_point_loc(True).span(self.__get_point_loc()) raise error.Syntax(loc, f'expected "(" but found "{c}"') start_point_loc = self.__get_point_loc(prev_col=True) (loc, word) = self.__parse_word_with_loc() if word == 'lit': return self.__parse_literal(start_point_loc) elif word == 'get': return self.__parse_get_var(start_point_loc) elif word == 'val': return self.__parse_create_var(start_point_loc) elif word == 'set': return self.__parse_set_var(start_point_loc) elif word == 'define': return self.__parse_define(start_point_loc) elif word == 'call': return self.__parse_call(start_point_loc) elif word == 'if': return self.__parse_if(start_point_loc) elif word == 'loop' or word == 'seq_loop': return self.__parse_loop(start_point_loc, word == 'seq_loop') elif word == 'list': return self.__parse_list(start_point_loc) elif word == 'list_at': return self.__parse_list_at(start_point_loc) elif word == 'list_set': return self.__parse_list_set(start_point_loc) else: raise error.Syntax(loc, f'unknown expression type: {word}')
def __parse_if(self, start_point_loc): ''' Private function to parse a single IF expression. The _file variable should be in a state starting with (without quotes): '<cond> then <e1> <e2> ... else <e1> <e2> ...)' That is, the '(if ' section has alread been read. Returns an instance of If() ''' # Parse the condition expression. if_cond = self.__parse_expr() # After the condition there should be the 'then' keyword. (l, then_word) = self.__parse_word_with_loc() if then_word != 'then': raise error.Syntax(l, f'expected "then" but got {then_word}') # Parse the list of 'then' expressions. if_then = [] while True: e = self.__parse_expr_or_keyword() # Check if a keyword was read. if e in keywords: if e == 'else': break else: point_l = self.__get_point_loc() point_l.col -= len(e) loc = point_l.span(self.__get_point_loc()) raise error.Syntax(loc, f'expected "else" but got {e}') # An expression was parsed. if_then.append(e) # Parse the list of 'else' expressions. if_else = [] while True: e = self.__parse_expr(parsing_exprs_list=True) if e is None: break if_else.append(e) # Now the entire if expression has been parsed. loc = start_point_loc.span(self.__get_point_loc()) return If(loc, if_cond, if_then, if_else)
def __translate_call_prim_binary_expr(self, expr, end=True): ''' Get a single parsed CALL expression for a primitive binary function and return the equivalent C++ and CUDA code. If 'end' is false, then the final characters of the expression, like semi-colons and newlines, are not added. ''' cpp = '' cuda = '' if len(expr.params) != 2: error_str = f'expected 2 arguments but got {len(expr.params)}' raise error.Syntax(expr.loc, error_str) cpp += '(' cpp += f'{self.__translate_expr(expr.params[0], False)[0]}' cpp += f' {prim_binary_funcs[expr.name]} ' cpp += f'{self.__translate_expr(expr.params[1], False)[0]}' cpp += ')' cpp += ';\n' if end else '' cpp = self._make_indented(cpp) return (cpp, cuda)
def convert_type(loc, type_enum, val): ''' type_enum: Type.INT, Type.FLOAT, ... val: string that will be converted to type 'type_enum' Return 'val' as a value of the type corresponding to 'type_enum'. For example: convert_type(loc, Type.INT, "67") -> 67 convert_type(loc, Type.FLOAT, "0.43") -> 0.43 convert_type(loc, Type.STRING, "hello") -> "hello" ''' try: if type_enum == Type.INT: return int(val) elif type_enum == Type.FLOAT: return float(val) elif type_enum == Type.STRING: if val[0] != '\'' or val[-1] != '\'': raise error.Syntax(loc, f'invalid string: {val}') return str(val[1:-1]) else: error_str = f'convert_type: unknown type {type_enum}' raise error.InternalError(loc, error_str) except ValueError: type_str = Type.type_to_str(loc, type_enum) raise error.IncompatibleType(loc, type_str, val)
def __eat_close_paren(self): ''' Advance the file one character and varify that it reads ')'. ''' c = self._file.read(1) self._file_col += 1 loc = self.__get_point_loc(True).span(self.__get_point_loc()) if c != ')': raise error.Syntax(loc, f'expected ")" but got "{c}"')
def __parse_call(self, start_point_loc): ''' Private function to parse a single CALL expression. The _file variable should be in a state starting with (without quotes): '<name> <arg1_expr> <arg2_expr> ...)' That is, the '(call ' section has alread been read. Returns an instance of Call() ''' call_name = self.__parse_word() call_params = [] # Parse the ':' between function name and start of arguments. c = self.__eat_whitespace() if c == '': # The end of file was reached. loc = self.__get_point_loc(True).span(self.__get_point_loc()) raise error.Syntax(loc, f'unexpected end of file') if c != ':': loc = self.__get_point_loc(True).span(self.__get_point_loc()) raise error.Syntax(loc, f'expected ":" but found "{c}"') # Parse the argumnts, if any. while True: e = self.__parse_expr(parsing_exprs_list=True) if e is None: break call_params.append(e) # Now the entire call has been parsed. loc = start_point_loc.span(self.__get_point_loc()) return Call(loc, call_name, call_params)
def get_type_from_string_val(loc, v): ''' Return the intended type of 'v', which is of type string. Some examples of expected behavior are: v = "1245" -> Type.INT v = "12.4" -> Type.FLOAT v = "'hi'" -> Type.STRING ''' if v[0] == '\'': if v[-1] != '\'': raise error.Syntax(loc, f'invalid string: {v}') return Type.STRING elif '.' in v: return Type.FLOAT else: return Type.INT
def __validate_define_expr_type(self, expr): ''' Typecheck and add environment data for a single Define Expr. ''' # Make sure the name is not in the current scope. if self._env.name_in_scope(expr.name): error_str = f'function {expr.name} already defined in this scope' raise error.Name(expr.loc, error_str) # Update the environment before validating the body expressions so that # recursive functions are allowed (i.e. the body expression can find # the return type of this function). func_type = [expr.type] # This is the return type. for arg in expr.args: # arg[0] is the type and arg[1] is the name. func_type.append(arg[0]) self._env.add(expr.name, func_type, False) # The function body is in a new scope. self._env.push_scope() # Add the function arguments to the environment so that the body can # use them. for arg in expr.args: # arg[0] is the type and arg[1] is the name. self._env.add(arg[1], [arg[0]], True) # Validate all of body expressions. for e in expr.body: self.__validate_single_expr(e) # The last body expression is returned from the function, so it must # have the correct type. if len(expr.body) == 0: error_str = f'expected body to have an expression to return' raise error.Syntax(expr.loc, error_str) ret_expr = expr.body[-1] self.__validate_type_of_expr(ret_expr.loc, func_type[0], ret_expr) # The function body scope ended. expr.env = self._env.copy() self._env.pop_scope(expr.loc)
def lookup_variable(self, loc, expr_name): ''' Find a variable in the environment and return its type. ''' # Make sure the variable already exists. (name, type_lst, is_var) = self.get_entry_for_name(expr_name) if name is None: error_str = f'variable {expr_name} does not exist' raise error.Name(loc, error_str) if len(type_lst) != 1 or not is_var: error_str = f'{expr_name} is a function; expected a variable' raise error.Syntax(loc, error_str) # Make sure the variable has a type. if type_lst == [Type.UNDETERMINED] or type_lst == [Type.NONE]: error_str = f'variable {expr_name} has bad type: {type_lst[0]}' raise error.Type(loc, error_str) return type_lst[0]
def __translate_create_var_expr(self, expr, end=True): ''' Get a single parsed CREATE_VAR expression and return the equivalent C++ and CUDA code. If 'end' is false, then the final characters of the expression, like semi-colons and newlines, are not added. ''' cpp = '' cuda = '' if expr.type == Type.INT: (c, cu) = self.__translate_expr(expr.val, False) cpp = f'int {expr.name} = {c}' cpp += ';\n' if end else '' cuda += cu elif expr.type == Type.FLOAT: (c, cu) = self.__translate_expr(expr.val, False) cpp = f'float {expr.name} = {c}' cpp += ';\n' if end else '' cuda += cu elif expr.type == Type.STRING: if expr.val.exprClass == ExprEnum.LITERAL: size = len(expr.val.val) + 1 # Add 1 for the null terminator. cpp = f'\nchar *{expr.name} = malloc({size});\n' + \ f'if ({expr.name} == NULL) {"{"}\n' + \ f' printf("failed to allocate {size} bytes\\n");\n' + \ f' exit(1);\n' + \ f'{"}"}\n' + \ f'*{expr.name} = "{expr.val.val}"' cpp += ';\n' if end else '' elif expr.val.exprClass == ExprEnum.GET_VAR or \ expr.val.exprClass == ExprEnum.CALL: (c, cu) = self.__translate_expr(expr.val, False) cpp = f'char *{expr.name} = {c}' cpp += ';\n' if end else '' cuda += cu else: raise error.Syntax(expr.loc, 'invalid string initialization') cpp = self._make_indented(cpp) return (cpp, cuda)
def __parse_loop(self, start_point_loc, no_parallelization): ''' Private function to parse a single LOOP expression. The _file variable should be in a state starting with (without quotes): '<init> <test> <update> <e1> <e2> ...)' That is, the '(loop ' section has alread been read. If 'no_parallelization' is true, then the loop will not be parallelized, even if it is possible to do so. Returns an instance of Loop() ''' # Parse the beginning expressions. loop_init = self.__parse_expr() loop_test = self.__parse_expr() loop_update = self.__parse_expr() # Just before the body expressions there should be the 'do' keyword. (l, do_word) = self.__parse_word_with_loc() if do_word != 'do': raise error.Syntax(l, f'expected "do" but got {do_word}') # Parse the list of body expressions. loop_body = [] while True: e = self.__parse_expr(parsing_exprs_list=True) if e is None: break loop_body.append(e) # Now the entire loop expression has been parsed. loc = start_point_loc.span(self.__get_point_loc()) return Loop(loc, loop_init, loop_test, loop_update, loop_body, no_parallelization)
def lookup_function(self, loc, expr_name): ''' Find a function in the environment. Return a tuple with two elements. The first is the return type of the function and the second is a list of types for the arguments. ''' # Make sure the function already exists. (name, type_lst, is_var) = self.get_entry_for_name(expr_name) if name is None: error_str = f'function {expr_name} does not exist' raise error.Name(loc, error_str) if is_var: error_str = f'{expr_name} is a variable; expected a function' raise error.Syntax(loc, error_str) if len(type_lst) == 0: error_str = f'{expr_name} has no return type' raise error.InternalError(loc, error_str) # Make sure the function has a return type. ret_type = type_lst[0] if ret_type == Type.UNDETERMINED or ret_type == Type.NONE: error_str = f'function {expr_name} has bad return type: {ret_type}' raise error.Type(loc, error_str) # Make sure the parameter types are valid. for param_type in type_lst[1:]: if param_type == Type.UNDETERMINED or param_type == Type.NONE: error_str = f'function {expr_name} has bad ' error_str += f'paramter type: {param_type}' raise error.Type(expr.loc, error_str) return (ret_type, type_lst[1:])
def __parse_expr_or_keyword(self): ''' Private function to parse and return a single expression or a keyword. Returns either a parsed expression or a string keyword. ''' c = self.__eat_whitespace() if c == '': # The end of file was reached return if c == '(': # Get the rest of the expression. return self.__parse_expr(got_paren=True) else: # All expressions start with '(', so this must be a keyword. (loc, word) = self.__parse_word_with_loc() word = c + word if word in keywords: return word else: error_str = f'expected expression or keyword but found "{word}"' raise error.Syntax(loc, error_str)
def __parse_define(self, start_point_loc): ''' Private function to parse a single DEFINE expression. The _file variable should be in a state starting with (without quotes): '<name> (<arg1> <arg2> ...) <body1> <body2> ...)' That is, the '(define ' section has alread been read. Returns an instance of Define() ''' # Get the return type and function name. (l, def_return_type) = self.__parse_word_with_loc() if def_return_type == 'list': list_type = self.__parse_type() if list_type == Type.INT: def_return_type = Type.LIST_INT elif list_type == Type.FLOAT: def_return_type = Type.LIST_FLOAT elif list_type == Type.STRING: def_return_type = Type.LIST_STRING else: error_str = '__parse_define cannot get here' raise error.InternalError(l, error_str) else: def_return_type = Type.str_to_type(l, def_return_type) def_name = self.__parse_word() # Parse the ':' between function name and start of arguments. c = self.__eat_whitespace() if c == '': # The end of file was reached. loc = self.__get_point_loc(True).span(self.__get_point_loc()) raise error.Syntax(loc, f'unexpected end of file') if c != ':': loc = self.__get_point_loc(True).span(self.__get_point_loc()) raise error.Syntax(loc, f'expected ":" but found "{c}"') # Parse the arguments. def_args = [] while True: (l, maybe_type) = self.__parse_word_with_loc() # Check if the end of the arguments has been reached. if maybe_type == ':': break # There is another argument to parse. arg_type = None if maybe_type == 'list': list_type = self.__parse_type() if list_type == Type.INT: arg_type = Type.LIST_INT elif list_type == Type.FLOAT: arg_type = Type.LIST_FLOAT elif list_type == Type.STRING: arg_type = Type.LIST_STRING else: error_str = '__parse_define cannot get here' raise error.InternalError(l, error_str) else: arg_type = Type.str_to_type(l, maybe_type) arg_name = self.__parse_word() def_args.append((arg_type, arg_name)) # Parse the body expressions. def_body = [] while True: e = self.__parse_expr(parsing_exprs_list=True) if e is None: break def_body.append(e) # Now the entire function has been parsed. loc = start_point_loc.span(self.__get_point_loc()) return Define(loc, def_return_type, def_name, def_args, def_body)