text: 
  # This is where all the built in functions live
  # Edit with care lol
  # © Copyrights reserved 2021
  
  from .runtime import *
  from .datatypes import *
  from .errors import *
  import os
  from .value import Value
  from .import interprete
  from .functions import *
  
  # Context class
  class Context:
    def __init__(self, display_name, parent=None, parent_entry_pos=None):
      self.display_name = display_name
      self.parent = parent
      self.parent_entry_pos = parent_entry_pos
      self.symbol_table = None
  
  # This is the symbol table where the variables are stored.
  class SymbolTable:
    def __init__(self, parent=None):
      self.symbols = {}
      self.parent = parent
  
    def get(self, name):
      value = self.symbols.get(name, None)
      if value == None and self.parent:
        return self.parent.get(name)
      return value
  
    def set(self, name, value):
      self.symbols[name] = value
  
    def remove(self, name):
      del self.symbols[name]
  
  class BaseFunction(Value):
  
    # __init__ function 
    # arguments: name
    def __init__(self, name):
      super().__init__()
      self.name = name or "<anonymous>"
  
    # Generates a new context class
    def generate_new_context(self):
      new_context = Context(self.name, self.context, self.pos_start)
      new_context.symbol_table = SymbolTable(new_context.parent.symbol_table)
      return new_context
  
    # This checks the list of args below the builtin function  
    def check_args(self, arg_names, args):
      res = RTResult()
  
      if len(args) > len(arg_names):
        return res.failure(RTError(
          self.pos_start, self.pos_end,
          f"{len(args) - len(arg_names)} too many args passed into {self}",
          self.context
        ))
      
      if len(args) < len(arg_names):
        return res.failure(RTError(
          self.pos_start, self.pos_end,
          f"{len(arg_names) - len(args)} too few args passed into {self}",
          self.context
        ))
  
      return res.success(None)
  
    # Populates args
    def populate_args(self, arg_names, args, exec_ctx):
      for i in range(len(args)):
        arg_name = arg_names[i]
        arg_value = args[i]
        arg_value.set_context(exec_ctx)
        exec_ctx.symbol_table.set(arg_name, arg_value)
  
    # Checks args then populate args
    def check_and_populate_args(self, arg_names, args, exec_ctx):
      res = RTResult()
      res.register(self.check_args(arg_names, args))
      if res.should_return(): return res
      self.populate_args(arg_names, args, exec_ctx)
      return res.success(None)
  
  
  
  # Function class inheriting from BaseFunction 
  class Function(BaseFunction):
    def __init__(self, name, body_node, arg_names, should_auto_return):
      super().__init__(name)
      self.body_node = body_node
      self.arg_names = arg_names
      self.should_auto_return = should_auto_return
  
    def execute(self, args):
      res = RTResult()
      interpreter = interprete.Interpreter()
      exec_ctx = self.generate_new_context()
  
      res.register(self.check_and_populate_args(self.arg_names, args, exec_ctx))
      if res.should_return(): return res
  
      value = res.register(interpreter.visit(self.body_node, exec_ctx))
      if res.should_return() and res.func_return_value == None: return res
  
      ret_value = (value if self.should_auto_return else None) or res.func_return_value or Number.null
      return res.success(ret_value)
  
    def copy(self):
      copy = Function(self.name, self.body_node, self.arg_names, self.should_auto_return)
      copy.set_context(self.context)
      copy.set_pos(self.pos_start, self.pos_end)
      return copy
  
    def __repr__(self):
      return f"<function {self.name}>"
  
  
  # Where all the functions live together!
  class BuiltInFunction(BaseFunction):
    def __init__(self, name):
      super().__init__(name)
  
    def execute(self, args):
      res = RTResult()
      exec_ctx = self.generate_new_context()
  
      method_name = f'execute_{self.name}'
      method = getattr(self, method_name, self.no_visit_method)
  
      res.register(self.check_and_populate_args(method.arg_names, args, exec_ctx))
      if res.should_return(): return res
  
      return_value = res.register(method(exec_ctx))
      if res.should_return(): return res
      return res.success(return_value)
    
    def no_visit_method(self, node, context):
      raise Exception(f'No execute_{self.name} method defined')
  
    def copy(self):
      copy = BuiltInFunction(self.name)
      copy.set_context(self.context)
      copy.set_pos(self.pos_start, self.pos_end)
      return copy
  
    def __repr__(self):
      return f"<built-in function {self.name}>"
  
    # A Muchcript special function
    
    def execute_crypto_price(self, exec_ctx):
        crypto = str(exec_ctx.symbol_table.get('crypto'))
        realcurrency = str(exec_ctx.symbol_table.get('realcurrency'))
        price = get_crypto(crypto, realcurrency)
        return RTResult().success(Number(price))
    execute_crypto_price.arg_names = ['crypto', 'realcurrency']
    
    # Other basic builtin functions needed for coding... :)
    
    # Print function 
    # This is used to print a string, number, list, function, etc to the terminal
    # Similar to the print() function in python
    def execute_print(self, exec_ctx):
      print(str(exec_ctx.symbol_table.get('value')))
      return RTResult().success(Number.null)
    execute_print.arg_names = ['value']
    
    # Same as the print function
    # But is more prefered for printing string
    def execute_print_ret(self, exec_ctx):
      return RTResult().success(String(str(exec_ctx.symbol_table.get('value'))))
    execute_print_ret.arg_names = ['value']
    
    # Input function
    # This is the input function, used to get input from the user from the console
    def execute_input(self, exec_ctx):
      text = input(str(exec_ctx.symbol_table.get('text')))
      return RTResult().success(String(text))
    execute_input.arg_names = ['text']
    
    # Same as the input function but used to get numbers only.
    # Does not quit until you input a number
    def execute_input_int(self, exec_ctx):
      while True:
        text = input()
        try:
          number = int(exec_ctx.symbol_table.get('text'))
          break
        except ValueError:
          print(f"'{text}' must be an integer. Try again!")
      return RTResult().success(Number(number))
    execute_input_int.arg_names = ['text']
  
    # Used to clear the console window
    def execute_clear(self, exec_ctx):
      os.system('cls' if os.name == 'nt' else 'clear') 
      return RTResult().success(Number.null)
    execute_clear.arg_names = []
  
    # Checks if the object is number
    def execute_is_number(self, exec_ctx):
      is_number = isinstance(exec_ctx.symbol_table.get("value"), Number)
      return RTResult().success(Number.true if is_number else Number.false)
    execute_is_number.arg_names = ["value"]
  
    # Checks if the object is string
    def execute_is_string(self, exec_ctx):
      is_number = isinstance(exec_ctx.symbol_table.get("value"), String)
      return RTResult().success(Number.true if is_number else Number.false)
    execute_is_string.arg_names = ["value"]
  
    # Checks if the object is list
    def execute_is_list(self, exec_ctx):
      is_number = isinstance(exec_ctx.symbol_table.get("value"), List)
      return RTResult().success(Number.true if is_number else Number.false)
    execute_is_list.arg_names = ["value"]
  
    # Checks if the object is function
    def execute_is_function(self, exec_ctx):
      is_number = isinstance(exec_ctx.symbol_table.get("value"), BaseFunction)
      return RTResult().success(Number.true if is_number else Number.false)
    execute_is_function.arg_names = ["value"]
  
    # Used to append objects to lists
    def execute_append(self, exec_ctx):
      list_ = exec_ctx.symbol_table.get("list")
      value = exec_ctx.symbol_table.get("value")
  
      if not isinstance(list_, List):
        return RTResult().failure(RTError(
          self.pos_start, self.pos_end,
          "First argument must be list",
          exec_ctx
        ))
  
      list_.elements.append(value)
      return RTResult().success(Number.null)
    execute_append.arg_names = ["list", "value"]
  
    # Used to remove objects from list
    def execute_pop(self, exec_ctx):
      list_ = exec_ctx.symbol_table.get("list")
      index = exec_ctx.symbol_table.get("index")
  
      if not isinstance(list_, List):
        return RTResult().failure(RTError(
          self.pos_start, self.pos_end,
          "First argument must be list",
          exec_ctx
        ))
  
      if not isinstance(index, Number):
        return RTResult().failure(RTError(
          self.pos_start, self.pos_end,
          "Second argument must be number",
          exec_ctx
        ))
  
      try:
        element = list_.elements.pop(index.value)
      except:
        return RTResult().failure(RTError(
          self.pos_start, self.pos_end,
          'Element at this index could not be removed from list because index is out of bounds',
          exec_ctx
        ))
      return RTResult().success(element)
    execute_pop.arg_names = ["list", "index"]
  
    # Used to append two lists together.
    def execute_extend(self, exec_ctx):
      listA = exec_ctx.symbol_table.get("listA")
      listB = exec_ctx.symbol_table.get("listB")
  
      if not isinstance(listA, List):
        return RTResult().failure(RTError(
          self.pos_start, self.pos_end,
          "First argument must be list",
          exec_ctx
        ))
  
      if not isinstance(listB, List):
        return RTResult().failure(RTError(
          self.pos_start, self.pos_end,
          "Second argument must be list",
          exec_ctx
        ))
  
      listA.elements.extend(listB.elements)
      return RTResult().success(Number.null)
    execute_extend.arg_names = ["listA", "listB"]
  
    # It is used to get the number of objects in the list. Currently only works for lists.
    def execute_len(self, exec_ctx):
      list_ = exec_ctx.symbol_table.get("list")
  
      if not isinstance(list_, List):
        return RTResult().failure(RTError(
          self.pos_start, self.pos_end,
          "Argument must be list",
          exec_ctx
        ))
  
      return RTResult().success(Number(len(list_.elements)))
    execute_len.arg_names = ["list"]
  
    # Used to run files from within the code.
    def execute_run(self, exec_ctx):
      fn = exec_ctx.symbol_table.get("fn")
  
      if not isinstance(fn, String):
        return RTResult().failure(RTError(
          self.pos_start, self.pos_end,
          "Second argument must be string",
          exec_ctx
        ))
  
      fn = fn.value
  
      try:
        with open(fn, "r") as f:
          script = f.read()
      except Exception as e:
        return RTResult().failure(RTError(
          self.pos_start, self.pos_end,
          f"Failed to load script \"{fn}\"\n" + str(e),
          exec_ctx
        ))
  
      _, error = run(fn, script)
      
      if error:
        return RTResult().failure(RTError(
          self.pos_start, self.pos_end,
          f"Failed to finish executing script \"{fn}\"\n" +
          error.as_string(),
          exec_ctx
        ))
  
      return RTResult().success(Number.null)
    execute_run.arg_names = ["fn"]
  
  '''
  YOU HAVE REACHED THE END OF THIS FILE!
  HAVE A GOOD DAY!
  THANKS FOR CHECKING OUT THIS CODE!
  THANKS AGAIN!
  PEACE! 
  '''
  