def generate_parameter(decl_params): # holds working parameters param = {} for item in decl_params.content: if isinstance(item, ASTNode): # handle parameter prefix if item.name == 'func_param_prefix': # add passing by reference if item.content[0].type == 'AMP': param['reference'] = True # add constant parameters elif item.content[0].type == '@': param['const'] = True # specify type elif item.name == 'extension': param['data_type'] = generate_type(item.content[-1]) # handle initializer elif item.name == 'func_initializer': param['default_value'] = generate_expr(item.content[-1]) param['data_type'] = param['default_value'].data_type param['optional'] = True else: # handle indefinite params if item.type == '...': param['indefinite'] = True # add name elif item.type == 'IDENTIFIER': param['name'] = item.value # generate parameter object return type('Object', (), param)
def generate_statement(stmt, context: Context): return { # handle return 'return_stmt': generate_return, # handle yield # yield uses same function, b/c it differentiates 'yield_stmt': generate_return, # generate break statement and check context 'break_stmt': lambda s, a: StatementNode('Break') if context.break_context else errormodule.throw('semantic_error', 'Invalid context for break statement', stmt), # generate continue statement and check context 'continue_stmt': lambda s, a: StatementNode('Continue') if context.continue_context else errormodule.throw('semantic_error', 'Invalid context for continue statement', stmt), # generate throw statement 'throw_stmt': lambda s: StatementNode('Throw', generate_expr(s.content[1])), # generate variable with no modifiers 'variable_declaration': lambda s: generate_variable_declaration(s, []), # generate variable with external modifier 'external_stmt': lambda s: generate_variable_declaration(s.content[1].content[0], [Modifiers.EXTERNAL]), # generate variable with volatile and possibly external modifiers 'lock_stmt': lambda s: generate_variable_declaration(s.content[-1], [Modifiers.LOCK] if s.content[1].name != 'extern' else [Modifiers.LOCK, Modifiers.EXTERNAL]), # generate assignment / function call statement 'assignment': generate_assignment, # generate delete statement 'delete_stmt': generate_delete # subscript to get statement name and then call function, pass in context if necessary }[stmt.name](*([stmt, context] if stmt.name in {'yield_stmt', 'return_stmt', 'break_stmt', 'continue_stmt'} else [stmt]))
def compile_parameters(param_ast): params = [] expr = '' if not isinstance(param_ast, ASTNode): return params for item in param_ast.content: if isinstance(item, ASTNode): if item.name == 'expr': expr = item elif item.name == 'named_param': ue = unparse(expr) if len(ue) > 1 or isinstance( ue[0], ASTNode) or ue[0].type != 'IDENTIFIER': errormodule.throw( 'semantic_error', 'Invalid parameter name', param_ast. content[0 if isinstance(param_ast.content[0], ASTNode ) else 1]) expr = (ue[0].value, generate_expr(item.content[1])) elif item.name == 'n_param': params += compile_parameters(item) return [generate_expr(expr) if isinstance(expr, ASTNode) else expr ] + params
def generate_returns(rt_expr): # hold return types rt_types = [] for elem in rt_expr.content: if isinstance(elem, ASTNode): # if it is an expr, assume it is part of return if elem.name == 'expr': # add to return types rt_types.append(generate_expr(elem)) # if there are more/multiple return expression, add those to the return type list as well elif elem.name == 'n_rt_expr': # get a set of return types and decide how to add them to the list n_types = generate_returns(elem) if isinstance(n_types, list): rt_types += n_types else: rt_types.append(n_types) # if there are no returns, return None if len(rt_types) == 0: return # return a list if there are multiple or just one if there is only 1 return rt_types if len(rt_types) > 1 else rt_types[0]
def generate_variable_dict(ast): for item in ast.content: # if is an AST if isinstance(item, ASTNode): # add extension if item.name == 'extension': variable['extension'] = generate_type(item.content[-1]) # add initializer and constexpr elif item.name == 'initializer': variable['initializer'] = generate_expr(item.content[-1]) # check for constexpr from initializer operator variable['constexpr'] = item.content[0].type == ':=' # perform constexpr check if is constexpr if variable['constexpr']: if not check_constexpr(variable['initializer']): errormodule.throw('semantic_error', 'Expected constexpr', item.content[-1]) # recur and continue building variable dictionary elif item.name == 'multi_var': add_final_variable() generate_variable_dict(item) # otherwise assume token and check if it identifier elif item.type == 'IDENTIFIER': variable['name'] = item
def generate_type(ext): pointers = 0 # handle std types (from extension) if ext.name == 'types': if ext.content[0].name == 'deref_op': # extract data type pointers pointers = len(unparse(ext.content[0])) # update ext to simple types ext = ext.content[-1] # selects last element (always simple types) # if it is token, assume array, list or dict if isinstance(ext.content[0], Token): # assume array if ext.content[0].type == 'ARRAY_TYPE': # ext.content[1].content[1] == pure_types -> array_modifier -> types et = generate_type(ext.content[1].content[1]) # extract count value # ext.content[1].content[1] == pure_types -> array_modifiers -> expr error_ast = ext.content[1].content[3] try: count = get_array_bound( generate_expr(ext.content[1].content[3])) except IndexError: errormodule.throw('semantic_error', 'Index out of range', error_ast) return if not count and count != 0: errormodule.throw('semantic_error', 'Non-constexpr array bound', error_ast) elif type(count) == bool: errormodule.throw('semantic_error', 'Invalid value for array bound', error_ast) try: _ = count < 0 count = float(count) except (ValueError, TypeError): errormodule.throw('semantic_error', 'Invalid value for array bound', error_ast) if not count.is_integer(): errormodule.throw('semantic_error', 'Invalid value for array bound', error_ast) elif count < 1: errormodule.throw('semantic_error', 'Invalid value for array bound', error_ast) return types.ArrayType(et, int(count), pointers) # assume list elif ext.content[0].type == 'LIST_TYPE': # ext.content[1].content[1] == pure_types -> list_modifier -> types return types.ListType(generate_type(ext.content[1].content[1]), pointers) # assume function elif ext.content[0].type in {'FUNC', 'ASYNC'}: params, return_types = None, None for item in ext.content[1].content: if not isinstance(item, Token): if item.name == 'func_params_decl': params = generate_parameter_list(item) elif item.name == 'rt_type': return_types = get_return_from_type(item) return types.Function(params, return_types, 0, ext.content[0].value == 'ASYNC', False) # assume dict else: # ext.content[1].content[1] == pure_types -> dict_modifier -> types kt, vt = generate_type(ext.content[1].content[1]), generate_type( ext.content[1].content[3]) # check mutability if types.mutable(kt): errormodule.throw('semantic_error', 'Invalid key type for dictionary', ext.content[1].content[1]) # compile dictionary type return types.MapType(kt, vt, pointers) else: if ext.content[0].name == 'pure_types': # data type literal if ext.content[0].content[0].type == 'DATA_TYPE': return types.DataTypeLiteral(types.DataTypes.DATA_TYPE) # return matched pure types return types.DataType({ 'INT_TYPE': types.DataTypes.INT, 'BOOL_TYPE': types.DataTypes.BOOL, 'BYTE_TYPE': types.DataTypes.BYTE, 'FLOAT_TYPE': types.DataTypes.FLOAT, 'LONG_TYPE': types.DataTypes.LONG, 'COMPLEX_TYPE': types.DataTypes.COMPLEX, 'STRING_TYPE': types.DataTypes.STRING, 'CHAR_TYPE': types.DataTypes.CHAR, 'OBJECT_TYPE': types.OBJECT_TEMPLATE, }[ext.content[0].content[0].type], 0) else: # get root symbol name = ext.content[0].content[0].value if name.type == 'THIS': sym = modules.get_instance() else: sym = util.symbol_table.look_up(name) # hold previous symbol ASTNode for error messages prev_sym = ext.content[0].content[0] # extract outer symbols if necessary if len(ext.content[0].content) > 1: content = ext.content[0].content[1:] for item in content: if isinstance(item, Token): if item.type == 'IDENTIFIER': # make sure symbol is a custom type if not isinstance(sym, types.CustomType): errormodule.throw( 'semantic_error', 'Object is not a valid is a data type', prev_sym) identifier = item.value # get member for modules if sym.data_type.data_type == types.DataTypes.MODULE: # get and check property prop = modules.get_property( sym.data_type, identifier) if not prop: errormodule.throw( 'semantic_error', 'Object has no member \'%s\'' % identifier, item) # update previous symbol prev_sym = item # update symbol sym = prop # invalid get member on interfaces elif sym.data_type.data_type == types.DataTypes.INTERFACE: errormodule.throw( 'semantic_error', '\'.\' is not valid for this object', item) # assume struct or enum else: for member in sym.data_type.members: # if there is a match, extract value if member.name == identifier: prev_sym = item sym = member # break to prevent else condition break # if there is no match, throw error else: errormodule.throw( 'semantic_error', 'Object has no member \'%s\'' % identifier, item) # continue recursive for loop elif item.name == 'dot_id': content.extend(item.content) # final check for invalid data types if not isinstance(sym, types.CustomType): errormodule.throw('semantic_error', 'Object is not a valid is a data type', prev_sym) # add instance marker if necessary if sym.data_type.data_type != types.DataTypes.INTERFACE: sym.data_type.instance = True return sym.data_type
def generate_variable_declaration(stmt, modifiers): # is constant constant = False # for multideclaration: holds set of variables variables = {} # main type extension overall_type = None # main variable initializer initializer = None # is marked constexpr constexpr = False # iterate to generate statement components for item in stmt.content: if isinstance(item, ASTNode): # generate variable if name given if item.name == 'var': variables = generate_var(item) # set overall type elif item.name == 'extension': overall_type = generate_type(item.content[1]) # add initializer elif item.name == 'initializer': initializer = generate_expr(item.content[1]) # set constexpr if := operator used constexpr = item.content[0].type == ':=' # set constant if @ operator used instead of $ elif item.type == '@': constant = True # add constant to modifiers if variable is marked constant if constant: modifiers.append(Modifiers.CONSTANT) # all constexpr variables must also be constant if constexpr and not constant: errormodule.throw('semantic_error', 'Declaration of constexpr on non-constant', stmt) # if multi-declaration was used if isinstance(variables, dict): # position in variable set pos = 0 # iterate through variable dictionary # k = identifier token, v = generated variable object for k, v in variables.items(): # handle variables marked constexpr in non constant environment if v.constexpr and not constant: errormodule.throw('semantic_error', 'Declaration of constexpr on non-constant', stmt) # if there is a global initializer if initializer: # if it is tuple based initializer if isinstance(initializer.data_type, types.Tuple): # if the variable is still within the domain of the global initializer if pos < len(initializer.data_type.values): # if is doesn't have a data type, add the one provided by the initializer if not hasattr(v, 'data_type'): setattr(v, 'data_type', initializer.data_type.values[pos].data_type) # handle invalid double initializers, ie $(x = 2, y) = tupleFunc(); elif hasattr(v, 'initializer'): errormodule.throw('semantic_error', '%s cannot have two initializers' % ('constant' if constant else 'variable'), k) # if there is a type mismatch between the type extension and the given initializer value elif not types.coerce(v.data_type, initializer.data_type.values[pos].data_type): errormodule.throw('semantic_error', 'Variable type extension and initializer data types do not match', k) # otherwise, it is invalid else: errormodule.throw('semantic_error', 'Multi-%s declaration cannot have single global initializer' % ('constant' if constant else 'variable'), stmt) # constexpr value (constexpr(s) store value so they can be evaluated at compile-time) val = None if v.constexpr: val = v.initializer # generate symbol object # identifier name, data type, modifiers, value (if constexpr) sym = Symbol(k.value, v.data_type if hasattr(v, 'data_type') else overall_type, modifiers + [Modifiers.CONSTEXPR] if v.constexpr else modifiers, value=val) # if the symbol lacks a data type if not sym.data_type: errormodule.throw('semantic_error', 'Unable to infer data type of variable', k) # if there is a null declared variable (x = null) elif not overall_type and isinstance(sym.data_type, types.DataType) and sym.data_type.data_type == types.DataTypes.NULL: errormodule.throw('semantic_error', 'Unable to infer data type of variable', k) # add variable to symbol table util.symbol_table.add_variable(sym, k) pos += 1 # statement name name = 'DeclareConstants' if constant else 'DeclareVariables' # if there is an initializer, add it to final statement if initializer: return StatementNode(name, overall_type, dict(zip([k.value for k in variables.keys()], variables.values())), modifiers, initializer) return StatementNode(name, overall_type, dict(zip([k.value for k in variables.keys()], variables.values())), modifiers) # if only normal declaration was used else: # handle null declarations (no type extension or initializer) if not overall_type and not initializer: errormodule.throw('semantic_error', 'Unable to infer data type of variable', stmt) # handle null declarations (no type extension and null initializer) if not overall_type and isinstance(initializer.data_type, types.DataType) and initializer.data_type.data_type == types.DataTypes.NULL: errormodule.throw('semantic_error', 'Unable to infer data type of variable', stmt) # check for type extension and initializer mismatch if overall_type and initializer and not types.coerce(overall_type, initializer.data_type): errormodule.throw('semantic_error', 'Variable type extension and initializer data types do not match', stmt) # add constexpr if marked as such if constexpr: modifiers.append(Modifiers.CONSTEXPR) # assume initializer exists if not check_constexpr(initializer): errormodule.throw('semantic_error', 'Expected constexpr', stmt) # add to symbol table util.symbol_table.add_variable(Symbol(variables.value, overall_type if overall_type else initializer.data_type, modifiers, None if not constexpr else initializer), stmt) # return generated statement node return StatementNode('DeclareConstant' if constant else 'DeclareVariable', overall_type, variables.value, initializer, modifiers)
def generate_return(stmt, context): # generate return or yield if possible if context.return_context: return StatementNode('Return' if stmt.name == 'return_stmt' else 'Yield', generate_expr(stmt.content[1])) else: errormodule.throw('semantic_error', 'Invalid context for ' + ('yield statement' if stmt.name == 'yield_stmt' else 'return statement'), stmt)
def generate_assignment_expr(root, assign_expr): # check for traditional assignment if isinstance(assign_expr.content[0], ASTNode): # NOTE all upper level values are ASTNodes # value is used to determine where to begin generating assignment expr is_n_assign = int(assign_expr.content[0].name != 'n_assignment') # variables is the collection of variables used in assignment (assign vars) # initializers is the matching initializer set variables, initializers = [root], [generate_expr(assign_expr.content[2 - is_n_assign])] # if there are multiple variables if assign_expr.content[0].name == 'n_assignment': # holding content used in recursive for loop assign_content = assign_expr.content[0].content for item in assign_content: # ignore commas if isinstance(item, ASTNode): # check for the assignment variable if item.name == 'assign_var': # add generated assignment variable variables.append(generate_assign_var(item)) elif item.name == 'n_assignment': # recur assign_content = item.content # if there are multiple expressions if assign_expr.content[-1].name == 'n_list': # holding content used in recursive for loop expressions = assign_expr.content[-1].content for item in expressions: # ignore commas if isinstance(item, ASTNode): # check for expression (initializer) if item.name == 'expr': # generate initializer expression expr = generate_expr(item) # if it is a tuple (multiple values stored in a single expression) if isinstance(expr.data_type, types.Tuple): # add each value to expression set (de-tuple) for elem in expr.data_type.values: initializers.append(elem) # else add raw expr to list else: initializers.append(expr) elif item.name == 'n_list': # recur expressions = item.content # check for matching assignment properties (unmodified variables) if len(variables) != len(initializers): errormodule.throw('semantic_error', 'Assignment value counts don\'t match', assign_expr) # get the assignment operator used # use offset to calculate it op = assign_expr.content[1 - is_n_assign].content[0] # iterate through variables and initializers together for var, expr in zip(variables, initializers): # if the variable is not modifiable if not modifiable(var): errormodule.throw('semantic_error', 'Unable to modify unmodifiable l-value', assign_expr) # if there is a type mismatch if not types.coerce(var.data_type, expr.data_type): errormodule.throw('semantic_error', 'Variable type and reassignment type do not match', assign_expr) # if there is a compound operator if op.type != '=' and not types.numeric(var.data_type): # all compound operators only work on numeric types errormodule.throw('semantic_error', 'Compound assignment operator invalid for non-numeric type', op) # return generate statement return StatementNode('Assign', op.type, dict(zip(variables, initializers))) # else assume increment and decrement else: # holds whether or not it is increment of decrement increment = assign_expr.content[0].type == '+' # check if the root is modifiable if not modifiable(root): errormodule.throw('semantic_error', 'Unable to modify unmodifiable l-value', assign_expr) # check if the root is numeric (as only numeric types accept these operators) if not types.numeric(root.data_type): errormodule.throw('semantic_error', 'Unable to %s non numeric value' % 'increment' if increment else 'decrement', assign_expr) # generate statement return StatementNode('Increment' if increment else 'Decrement', root)