def visitMethod_decl(self, ctx: DecafParser.Method_declContext):
        #1. get method return type
        method_type = ctx.return_type().getText()
        #2. get method name
        method_name = ctx.ID(0).getText()
        #3. get list of method parameter types and names
        param_types = []
        param_names = []
        for i in range(len(ctx.data_type())):
            param_types.append(ctx.data_type(i).getText())
            param_names.append(ctx.ID(i + 1).getText())
        #4. create a new method symbol
        method_symbol = SymbolTable.MethodSymbol(method_name, method_type,
                                                 ctx.start.line, param_types)
        #5. add method symbol to current scope
        self.st.addSymbol(method_symbol)
        #6. enter the method scope
        self.st.enterScope()
        #7. add method symbol to method scope (method names hsould exist in two scopes)

        #8. add the method parameters as individual symbols
        for i in range(len(param_types)):
            self.st.addSymbol(
                SymbolTable.VarSymbol(param_names[i], param_types[i],
                                      ctx.start.line))
        #9. visit child nodes (all sub trees and nodes)
        result = self.visitChildren(ctx)
        #10. exis the method scope (after visiting all sub-trees!)
        self.st.exitScope()
        return result
    def visitMethod_decl(self, ctx: DecafParser.Method_declContext):       
        # add the method_decl params to the symbol table one by one
        i = 1
        paramvalues = []
        if ctx.ID() and ctx.data_type(1):
            while i < len(ctx.ID()):  
                val = ctx.ID(i).getText()
                t = ctx.data_type(1).getText()
                i += 1     
                paramvalues.append(val)
                
                var_symbol = SymbolTable.VarSymbol(
                id=val,
                type=t,
                line=ctx.start.line,
                size=8,
                mem=SymbolTable.STACK
            )
            self.st.addSymbol(var_symbol)             
        
        if ctx.ID() and ctx.data_type():
            #add the method_decl and its type to the symbol table, with also the list of params 
            return_type = ctx.data_type(0).getText()
            method_name = ctx.ID(0).getText()
            line_number = ctx.start.line
                
            #sets values to be inserted into the symbol table
            method_symbol = SymbolTable.MethodSymbol(
                id=method_name,
                type=return_type,
                line=line_number,
                params = paramvalues
            )
            
            #add var symbol to the stack, you need to add method symbol to global scope and method symbol include param types, enter new scope,    
            #then loop through the param list and add to stack so theyre added to the symbol table

            #adds the above method to the symbol table for later use!            
            self.st.addSymbol(method_symbol)  
     
            #Rule 3 check if main has params, which it should not, we also check that method currently being checked is main, otherwise they are allowed to have params
            if len(self.st.lookup(method_name).params) > 0 and method_name == 'main':
                print("[Rule 3] error found parameters in main declaration on line", ctx.start.line)
        # enter the scope for the method scope
        self.st.enterScope()          
        #add params to table one by one
        self.visitChildren(ctx)

        #Rule 7 void method check if contains return in body! as the method we're checking should not have a return since it is VOID
        if ctx.VOID():
            if ctx.block().getText().find('return') != -1:
                print('[Rule 7] found void method, but return found on line', ctx.start.line , 'for method:', method_name)
                #catch the method_name if this method is type void
                self.voidMethods.append(method_name)
      
        self.st.exitScope()