Repository: asteroid-lang/asteroid (language: python)

Issue:
Refined features of tab completion in repl.py
This pull request covers the milestones for 3/18/2024, 4/1/2024 and 4/15/2024.
 
The changes in `repl.py` are largely found in the `completion_function` (formerly `completion_func`). A tab press will now behave as follows:
1. Any tab press will cycle through completions instead of only modifying the input_buffer when there is only one possible completion.
2. If the completion is after an @ and that @ is after a valid object or struct name, the pool of completions will correspond to the members of the struct
3. If the completion is after an @ and that @ is after a valid module, the pool of completions will correspond to the variables inside of that module.

The change in lex.py is the addition of the `get_member_identifiers()`, which will give you the identifiers of either the members of a struct/object, or the variables of a module if the identifier is valid, or `None` otherwise.
MAD: printing variables in stack frames other than the current one gives incorrect results
Printing variables in stack frames other than the current one gives incorrect results.  Consider the program,
```
load system io.

function fact
   with 0 do
      1
   with n do
      n*fact(n-1)
end

io @println (fact 3)
```
when using MAD ,
```
lutz$ asteroid -d fact.ast
Minimal Asteroid Debugger 0.0.1
(c) University of Rhode Island
type "help" for additional information
mad> next
>  1 load system io.
   2 
   3 function fact
   4    with 0 do
   5       1
mad> next
   1 load system io.
   2 
>  3 function fact
   4    with 0 do
   5       1
   6    with n do
   7       n*fact(n-1)
mad> set 7
mad> cont
reached breakpoint (fact.ast:7)
   4    with 0 do
   5       1
   6    with n do
>  7       n*fact(n-1)
   8 end
   9 
   10 io @println (fact 3)
   11 
mad> cont
reached breakpoint (fact.ast:7)
   4    with 0 do
   5       1
   6    with n do
>  7       n*fact(n-1)
   8 end
   9 
   10 io @println (fact 3)
   11 
mad> stack
Runtime stack (most recent call first):
frame #0: fact.ast @fact
frame #1: fact.ast @fact
frame #2: fact.ast @<toplevel>
mad> print n
n: 2
mad> up
you are looking at frame #1
mad> print n
n: 2                        <<<< this should be 3
mad> 
```
Notice that we keep returning the value of n associated with the current frame.


Discussion / hints:

This seems like something that happens with how the debugger handles `print *` and `print $SYM`. Calling `print *` tells the debugger to load the current stack frame from the SymTab with MAD's `self.frame_ix`. However, `print $SYM` is handled by calling the SymTab method `.lookup_sym()`, which then calls `.find_sym_dict()` to return the first dictionary that contains a specific symbol. This is why calling `print *` correctly displays the value of `n` in different scopes.
```
Minimal Asteroid Debugger 0.0.1
(c) University of Rhode Island
type "help" for additional information
mad> next
>  1 load system io.
   2
   3 function fact
   4    with 0 do
   5       1
mad> next
   1 load system io.
   2
>  3 function fact
   4    with 0 do
   5       1
   6    with n do
   7       n*fact(n-1)
mad> set 7
mad> cont
reached breakpoint (.\fact.ast:7)
   4    with 0 do
   5       1
   6    with n do
>  7       n*fact(n-1)
   8 end
   9
   10 io @println (fact 3
   11 [EOF]
mad> cont
reached breakpoint (.\fact.ast:7)
   4    with 0 do
   5       1
   6    with n do
>  7       n*fact(n-1)
   8 end
   9
   10 io @println (fact 3
   11 [EOF]
mad> stack
Runtime stack (most recent call first):
frame #0: .\fact.ast @fact
frame #1: .\fact.ast @fact
frame #2: .\fact.ast @<toplevel>
mad> print n
n: 2
mad> up
you are looking at frame #1
mad> print n
n: 2
mad> print *
n: 3
mad> down
you are looking at frame #0
mad> print *
n: 2
mad>
```

This else branch in `._handle_print()`may need to get the current scope instead of looking up the symbol from the table directly.
```python
# mad.py
def _handle_print(self,args):
      # ...
      if args[0] == '*':
         var_list = self.interp_state.symbol_table.get_curr_scope(scope=self.frame_ix, option="items")
         for (name,val) in var_list:
            print("{}: {}".format(name,term2string(val)))
      else:
         val = self.interp_state.symbol_table.lookup_sym(args[0],strict=False)
         if not val:
            print("error: variable {} not found".format(args[0]))
         else:
            print("{}: {}".format(args[0],term2string(val)))
      return START_DEBUGGER
```

Fix patch:
diff --git a/asteroid/__init__.py b/asteroid/__init__.py
index 3810bd63..af102fff 100755
--- a/asteroid/__init__.py
+++ b/asteroid/__init__.py
@@ -111,7 +111,7 @@ def main():
 
     if debug_flag:
         # Create a new debugger
-        db = MAD()
+        db = MAD(functional_mode=flags['-F'])
     else:
         db = None
 
diff --git a/asteroid/lex.py b/asteroid/lex.py
index ff53931e..da16a82d 100644
--- a/asteroid/lex.py
+++ b/asteroid/lex.py
@@ -7,7 +7,7 @@
 import re
 
 from asteroid.state import state, warning
-from asteroid.globals import ExpectationError
+from asteroid.globals import ExpectationError, builtins
 
 # table that specifies the token value and type for keywords
 keywords = {
@@ -126,6 +126,115 @@ def init_token_values():
 def token_lookup(type):
     return token_values[type]
 
+#This returns all of the token identifiers currently defined.
+#The pool of identifiers includes both user-defined data
+#and keywords defined by the language. The list of identifiers
+#does not contain duplicates and is sorted alphanumerically.
+def get_indentifiers():
+    #A set is used to only have unique elements
+    ids = builtins
+    for name in keywords:
+        ids.add(name)
+    
+    for table in state.symbol_table.scoped_symtab:
+        for name in table:
+            ids.add(name)
+    
+    #We want a list so we can index into it
+    ids = list(ids)
+    ids.sort()
+    
+    return ids
+
+#This returns all of the token identifiers currently defined which
+#are prefixed by the input string (i.e. it begins with the string).
+#The pool of identifiers includes both user-defined data
+#and keywords defined by the language. The list of identifiers
+#does not contain duplicates and is sorted alphanumerically.
+def get_indentifiers_by_prefix(prefix):
+    #A set is used to only have unique elements
+    ids = set()
+    
+    for name in builtins:
+        if name.startswith(prefix):
+            ids.add(name)
+            
+    for name in keywords:
+        if name.startswith(prefix):
+            ids.add(name)
+    
+    for table in state.symbol_table.scoped_symtab:
+        for name in table:
+            if name.startswith(prefix):
+                ids.add(name)
+    
+    #We want a list so we can index into it
+    ids = list(ids)
+    ids.sort()
+    
+    return ids
+
+#Given an identifier, it will return a list of identifiers indentifying members of
+#the associated data type, which can be either an object, module or struct. If
+#the identifier given is not for one of the mentioned data types, it will return
+#None
+def get_member_identifiers(identifier):
+    if identifier in state.symbol_table.global_scope.keys():
+        id_type = state.symbol_table.global_scope[identifier][0]
+        if id_type == 'object':
+            token = state.symbol_table.global_scope[identifier]
+            (
+                TOKEN_TYPE,
+                STRUCT_ID,
+                ( #Member names
+                    MEMBER_NAMES_LABEL,
+                    ( #List token
+                        LIST_LABEL,
+                        member_names,
+                    )
+                ),
+                STRUCT_MEMORY,
+            ) = token
+            return member_names
+        
+        elif id_type == 'module':
+            token = state.symbol_table.global_scope[identifier]
+            (
+                TOKEN_TYPE,
+                IDENTIFIER_TUPLE,
+                ( #Scope outer layer
+                    SCOPE_LABEL,
+                    ( #Scope inner layer
+                        (#Local scope symbol tables
+                            local_symtab,
+                            BUILTINS,
+                        ),
+                        _, #?
+                        GLOBAL_SCOPE,
+                    )
+                )
+            ) = token
+            return list(local_symtab.keys())
+        
+        elif id_type == 'struct':
+            token = state.symbol_table.global_scope[identifier]
+            (
+                TOKEN_TYPE,
+                ( #Member names
+                    MEMBER_NAMES_LABEL,
+                    ( #List token
+                        LIST_LABEL,
+                        member_names,
+                    )
+                ),
+                STRUCT_MEMORY,
+            ) = token
+            return member_names
+        else:
+            return None
+    else:
+        return None
+
 class Token:
     def __init__(self,type,value,module,lineno):
         self.type = type
diff --git a/asteroid/mad.py b/asteroid/mad.py
index ea58261f..45624ee6 100644
--- a/asteroid/mad.py
+++ b/asteroid/mad.py
@@ -48,7 +48,8 @@
 ###########################################################################################
 
 from os.path import exists, split, basename
-from asteroid.support import term2string
+from asteroid.support import term2string, get_tail_term, term2verbose, find_function
+from asteroid.frontend import Parser
 from asteroid.version import MAD_VERSION
 import copy 
 
@@ -58,9 +59,11 @@
 class MAD:
 
    ###########################################################################################
-   def __init__(self):
+   def __init__(self, functional_mode=False):
       # a reference to the interpreter state object
       self.interp_state = None
+      # the command line argument indicating functional mode
+      self.functional_mode = functional_mode
       # a lookup table for the program texts used during debugging
       self.program_text = {}
       # continue mode is used in the implementation of the continue cmd
@@ -86,7 +89,7 @@ def __init__(self):
                   'set'     : self._handle_set,
                   'sta'     : self._handle_stack,
                   'ste'     : self._handle_step,
-                  'tra'     : self._handle_stack,
+                  'tra'     : self._handle_trace,
                   'up '     : self._handle_up,
                   'whe'     : self._handle_where,
                }
@@ -279,25 +282,37 @@ def _handle_help(self,_):
       print("down\t\t\t- move down one stack frame")
       print("frame\t\t\t- display current stack frame number")
       print("help\t\t\t- display help")
-      print("list\t\t\t- display source code")
+      print("list [<num>|*]\t\t\t- display <num> (default 4) lines of source code, * displays all lines in file")
       print("next\t\t\t- step execution across a nested scope")
-      print("print <name>|*\t\t- print contents of <name>, * lists all vars in scope")
+      print("print <name>[@<num>|<name>]+|* [-v]\t\t- print contents of <name>, * lists all vars in scope, recursively access (nested) objects with @, '-v' enables verbose printing of nested data")
       print("quit\t\t\t- quit debugger")
-      print("set [<func>|<line#> [<file>]]\n\t\t\t- set a breakpoint")
-      print("stack\t\t\t- display runtime stack")
+      print("set [<func>|<line#> [<file>]]\n\t\t\t- set a breakpoint, breakpoints may only be set on valid statements on already loaded files")
+      print("stack [<num>|* [-v]]\t\t\t- display runtime stack, list all items in specific frame with an index or all frames with '*', '-v' toggles verbose printing")
       print("step\t\t\t- step to next executable statement")
-      print("trace\t\t\t- display runtime stack")
+      print("trace [<num> [<num>]]\t\t\t- display runtime stack trace, display runtime stack trace, can specify either the first n frames or all of the frames between the start and end")
       print("up\t\t\t- move up one stack frame")
       print("where\t\t\t- print current program line")
       print()
       return START_DEBUGGER
 
-   def _handle_list(self, _):
+   def _handle_list(self, args):
       (file,lineno) = self.interp_state.lineinfo
       self._load_program_text(file)
       pt = self.program_text[file]
-      # Length around the current line to display
-      length = 4
+      
+      if len(args) > 1:                                  # Too many arguments, reject with an error message
+         print("error: too many arguments")
+         return START_DEBUGGER
+      elif len(args) == 0:                               # No arguments, assume default length and fall through
+         length = 4
+      elif args[0] == '*':                               # '*' argument, set length to length of the file and lineno to 0
+         lineno, length = 0, len(pt)
+      elif args[0].isnumeric() and int(args[0]) > 0: # Number greater than 0, cast it and set length to it
+         length = int(args[0])
+      else:                                              # Any other input should be rejected with an error message
+         print("error: expected a number greater than 0 or '*', found '{}'".format(args[0]))
+         return START_DEBUGGER
+      
       # Compute the start and end of listing
       start = (lineno - length) if lineno >= length else 0
       end = lineno + length if lineno < len(pt) - 2 else len(pt)
@@ -326,49 +341,117 @@ def _handle_next(self,_):
       return RETURN_TO_INTERP
 
    def _handle_print(self,args):
-      if len(args) > 1:
+      if len(args) > 2:
          print("error: too many arguments")
          return False
       elif len(args) == 0:
          print("error: no argument given")
          return False
-      if args[0] == '*':
-         var_list = self.interp_state.symbol_table.get_curr_scope(scope=self.frame_ix, option="items")
+      
+      if len(args) == 2 and args[1] == '-v':
+         verbose = True
+      elif len(args) == 1:
+         verbose = False
+      else:
+         print("error: unknown option '{}'".format(args[1]))
+         return False
+      
+      # Split any arguments by the '@' character when necessary
+      syms = args[0].split('@')
+      # '@' occurs at beginning or end of argument, or multiple `@`s occur next to each other is rejected with an error message
+      if '' in syms:
+         print("error: any @s must exist between keywords or integers, not adjacent or next to each other")
+         return START_DEBUGGER
+      
+      var_list = self.interp_state.symbol_table.get_curr_scope(scope=self.frame_ix, option="items")
+      # If '*' is the only argument, handle output as normal
+      if syms[0] == '*' and len(syms) == 1:
          for (name,val) in var_list:
-            print("{}: {}".format(name,term2string(val)))
+            print("{}: {}".format(name, term2verbose(val) if verbose else term2string(val)))
       else:
-         val = self.interp_state.symbol_table.lookup_sym(args[0],strict=False)
-         if not val:
-            print("error: variable {} not found".format(args[0]))
-         else:
-            print("{}: {}".format(args[0],term2string(val)))
+         # Loop through scope and check if any symbols in the scope are the first symbol in the list
+         term = None
+         for (name, val) in var_list:
+            if name == syms[0]:
+               term = val
+               break
+         # Iterate over remaining terms to find the final symbol
+         val = get_tail_term(syms[0], term, syms[1:])
+         # Print the entire argument along with its current symbol if it is found
+         if val:
+            print("{}: {}".format(args[0], term2verbose(val) if verbose else term2string(val)))
       return START_DEBUGGER
 
    def _handle_quit(self,_):
       raise SystemExit()
    
    def _handle_set(self,args):
+      (file,lineno) = self.interp_state.lineinfo
       if len(args) == 0:
          # set a breakpoint at the current line
-         (file,lineno) = self.interp_state.lineinfo
          self.line_breakpoints.append((lineno,file))
          return START_DEBUGGER
       elif len(args) == 1:
-         (file,_) = self.interp_state.lineinfo
-         if args[0].isnumeric(): 
-            self.line_breakpoints.append((int(args[0]),file))
+         self._load_program_text(file)
+         if args[0].isnumeric():
+            if self._validate_breakpoint_line(file, int(args[0])):
+               self.line_breakpoints.append((int(args[0]),file))
          else:
-            self.function_breakpoints.append((args[0],file))
+            if self._validate_breakpoint_function(file, args[0]):
+               self.function_breakpoints.append((args[0],file))
          return START_DEBUGGER
       elif len(args) == 2:
-         if args[0].isnumeric(): 
-            self.line_breakpoints.append((int(args[0]),args[1]))
+         self._load_program_text(args[1])
+         if args[0].isnumeric():
+            if self._validate_breakpoint_line(args[1], int(args[0])):
+               self.line_breakpoints.append((int(args[0]),args[1]))
          else:
-            self.function_breakpoints.append((args[0],args[1]))
+            if self._validate_breakpoint_function(args[1], args[0]):
+               self.function_breakpoints.append((args[0],args[1]))
          return START_DEBUGGER
       else:
          print("error: too many arguments to set")
          return START_DEBUGGER
+   
+   def _validate_breakpoint_line(self, fname, lineno):
+      # Create a temporary Parser and reset the lineinfo
+      (module, line) = self.interp_state.lineinfo
+      temp_parser = Parser(functional_mode=self.functional_mode)
+      self.interp_state.lineinfo = (module, line)
+      # Read the file contents and get the current line
+      curr_file = self.program_text[fname]
+      if lineno <= 0 or lineno >= len(curr_file):
+         print("error: cannot place breakpoints outside of file")
+         return False
+      line_data = curr_file[lineno-1]
+      # Reject blank lines and '[EOF]'
+      if line_data.strip() == '':
+         print("error: cannot place breakpoints on blank lines")
+         return False
+      elif line_data == '[EOF]':
+         print("error: cannot place breakpoints at end of file")
+         return False
+      # Incrementally add lines to line_data until either a valid statement is generated or every following line has been checked
+      for l in range(lineno, len(curr_file)):
+         try:
+            stmts = temp_parser.parse(line_data)
+            self.interp_state.lineinfo = (module, line)
+            return True
+         except:
+            line_data += ("\n" + curr_file[l])
+      print("error: line {} in file '{}' cannot accept a breakpoint".format(lineno, fname))
+      self.interp_state.lineinfo = (module, line)
+      return False
+      
+   
+   def _validate_breakpoint_function(self, fname, func_name):
+      # Loop through every scope and check if the function was found
+      loaded_syms = self.interp_state.symbol_table.scoped_symtab
+      for scope in loaded_syms:
+         for (sym, val) in scope.items():
+            if find_function(sym, val, fname, func_name): return True
+      print("error: unable to find function '{}' in file '{}'".format(func_name, fname))
+      return False
 
    def _handle_frame(self,_):
       print("you are looking at frame #{}".format(self.frame_ix))
@@ -378,13 +461,79 @@ def _handle_step(self,_):
       self.frame_ix = 0
       return RETURN_TO_INTERP
 
-   def _handle_stack(self,_):
+   def _handle_stack(self, args):
       trace = copy.copy(self.interp_state.trace_stack)
       trace.reverse()
-      print("Runtime stack (most recent call first):")
-      for i in range(self.frame_ix,len(trace)):
-         (module,lineno,fname) = trace[i]
-         print("frame #{}: {} @{}".format(i,module,fname))
+      
+      # Determine if the verbose flag has been provided
+      verbose = len(args) == 2 and args[1] == '-v'
+      if len(args) > 2:
+         print("error: too many arguments")
+      elif len(args) == 2 and args[1] != '-v':
+         print("error: unknown argument '{}'".format(args[1]))
+      # Stack may only recieve either a positive integer or '*'
+      elif len(args) >= 1 and not (args[0].isnumeric() or args[0] == '*'):
+         print("error: invalid argument '{}', must be either an integer or '*'".format(args[0]))
+      # Print all symbols in a specific stack frame
+      elif len(args) >= 1 and args[0].isnumeric():
+         prev_frame = self.frame_ix
+         self.frame_ix = int(args[0])
+         if len(trace) <= self.frame_ix:
+            print("error: invalid index {}".format(self.frame_ix))
+         else:
+            (module, lineno, fname) = trace[self.frame_ix]
+            print("frame #{}: {} @{}".format(self.frame_ix, module, fname))
+            self._handle_print(['*', '-v'] if verbose else ['*'])
+         self.frame_ix = prev_frame
+      # Print all symbols in all stack frames
+      elif len(args) >= 1 and args[0] == '*':
+         prev_frame = self.frame_ix
+         print("Runtime stack (most recent call first):")
+         for i in range(prev_frame, len(trace)):
+            self.frame_ix = i
+            (module,lineno,fname) = trace[i]
+            print("frame #{}: {} @{}".format(i,module,fname))
+            self._handle_print(['*', '-v'] if verbose else ['*'])
+         self.frame_ix = prev_frame
+      else:
+         print("Runtime stack (most recent call first):")
+         for i in range(self.frame_ix,len(trace)):
+            (module,lineno,fname) = trace[i]
+            print("frame #{}: {} @{}".format(i,module,fname))
+      
+      return START_DEBUGGER
+
+   def _handle_trace(self, args):
+      trace = copy.copy(self.interp_state.trace_stack)
+      trace.reverse()
+      
+      if len(args) > 2:
+         print("error: too many arguments")
+         return START_DEBUGGER
+      elif len(args) == 2:
+         if not args[0].isnumeric():
+            print("error: invalid first argument '{}', must be some positive integer".format(args[0]))
+            return START_DEBUGGER
+         elif not args[1].isnumeric():
+            print("error: invalid second argument '{}', must be some positive integer".format(args[1]))
+            return START_DEBUGGER
+         start, end = int(args[0]), int(args[1])
+      elif len(args) == 1:
+         if not args[0].isnumeric():
+            print("error: invalid argument '{}', must be some positive integer".format(args[0]))
+            return START_DEBUGGER
+         start, end = self.frame_ix, self.frame_ix + int(args[0])
+      else:
+         start, end = self.frame_ix, len(trace)
+      
+      if end > len(trace):
+         print("error: range of stack frames ({}, {}) must not exceed number of existing frames ({})".format(start, end, len(trace)))
+         return START_DEBUGGER
+      
+      print("Runtime stack trace (most recent call first):")
+      for i in range(start, end):
+         (module, lineno, fname) = trace[i]
+         print("frame #{}: {} @{}".format(i, module, fname))
       return START_DEBUGGER
 
    def _handle_up(self, args):
diff --git a/asteroid/modules/os.ast b/asteroid/modules/os.ast
index 74a4edc3..9148b2b1 100644
--- a/asteroid/modules/os.ast
+++ b/asteroid/modules/os.ast
@@ -369,7 +369,7 @@ with cmd:%string do return escape
 global __retval__
 from os import system
 (STRING, cmd_val) = state.symbol_table.lookup_sym('cmd')
-status = sytem(cmd_val)
+status = system(cmd_val)
 __retval__ = ('integer', status)
 "
 end -- syscmd
diff --git a/asteroid/modules/prologue.ast b/asteroid/modules/prologue.ast
index a1bf737f..554fd6e6 100644
--- a/asteroid/modules/prologue.ast
+++ b/asteroid/modules/prologue.ast
@@ -972,4 +972,4 @@ with none do
     return this @explode()
                 @reverse()
                 @join("").
-end
+end
\ No newline at end of file
diff --git a/asteroid/repl.py b/asteroid/repl.py
index ef96941a..6741e1db 100644
--- a/asteroid/repl.py
+++ b/asteroid/repl.py
@@ -10,17 +10,20 @@
 from asteroid.globals import ExpectationError
 from asteroid.walk import function_return_value
 from asteroid.support import term2string
-
+from asteroid.lex import get_indentifiers, get_member_identifiers
 from sys import stdin,exit
-import platform
+import readline
+import re
 
-if platform.system() == 'Windows':
-    import pyreadline3
-else:
-    import readline
+#Variables needed for autocomplete cycling
+prefix = ""
+last_completion = ""
+last_index = 0
 
-def repl(new=True, redundancy=False, prologue=False, functional_mode=False):
+#Change this flag to False to disable autocompletion
+state.repl_use_autocompletion = True
 
+def repl(new=True, redundancy=False, prologue=False, functional_mode=False):
     if new:
         state.initialize()
         if prologue:
@@ -37,8 +40,122 @@ def print_repl_menu():
     print("(c) University of Rhode Island")
     print("Type \"help\" for additional information")
 
-def run_repl(redundancy, functional_mode):
+#This is the function that will be passed into readline to
+#allow for autocompletion. Text is the token currently highlighted
+#by the user, while state is the number of times the function was called before
+#a valid completion was returned. The function will be called again if a string is
+#returned, and will stop being called if a non string object is returned.
+def completion_function(text, state):
+    #Here we "hijack" the iteration with readline's calling of the function by
+    #iterating on our own only on the first call, returning None on
+    #all other calls of the function
+    if state == 0:
+        #identifiers is the default pool of completions
+        identifiers = get_indentifiers()
+        
+        #If nothing has been initialized yet, we need to interpret
+        #something to initialize the asteroid state.
+        #We interpret nothing so that it has no impact on the lines
+        #interpreted later.
+        if identifiers == []:
+            interp("",
+                    initialize_state=False,
+                    redundancy=False,
+                    prologue=True,
+                    functional_mode=False,
+                    exceptions=True)
+            identifiers = get_indentifiers()
+        
+        #Here we start checking if we are doing a completion for accessing a member
+        user_in = readline.get_line_buffer()
+        #The index of the beginning of the substring that the user has highlighted
+        i = readline.get_begidx()
+        
+        get_members = False
+        #Check if there's an @ before or at the autocompletion index
+        if i < len(user_in) and user_in[i] == '@':
+            get_members = True
+        else:
+            i -= 1
+            while i > 0:
+                if user_in[i] == ' ':
+                    pass
+                elif user_in[i] == '@':
+                    get_members = True
+                    i -= 1
+                    break
+                else:
+                    break
+                i -= 1
+            
+        #If there was a preceding @, try to use members as the completion pool
+        if get_members:
+            parent_id = ""
+            
+            #Find where the parent begins
+            while i >= 0 and user_in[i] == " ":
+                i -= 1
+            
+            #Get the parent
+            while i >= 0:
+                if re.match(r'[a-zA-Z_0-9]',user_in[i]):
+                    parent_id = user_in[i] + parent_id
+                else:
+                    break
+                i -= 1
+
+            #Only try to set identifiers to member_list if the
+            #parent identifier is valid
+            if len(parent_id) > 0 and re.match(r'[a-zA-Z_]',parent_id[0]):
+                member_list = get_member_identifiers(parent_id)
+                #Make sure the parent is actually a parent
+                if member_list:
+                    identifiers = list(member_list)
+        
+        global last_completion
+        global last_index
+        global prefix
+        
+        start = 0 #Start at the first identifier
+        #This flag allows for the autocompleter to start over
+        #if no autcompletions are available
+        allow_cycle = False
+        
+        if text == "": #This otherwise causes weird behavior
+            start = 0
+            last_index = 0
+            prefix = ""
+            last_completion = ""
+        elif text == last_completion: #Check if this is a repeat tab hit
+            allow_cycle = True
+            start = last_index + 1 #Jump ahead to avoid redundancy
+            if start == len(identifiers):
+                start = 0
+            text = prefix
+        else:
+            prefix = text #Set a new prefix for future cycles
+        
+        i = start
+        while i < len(identifiers):
+            token = identifiers[i]
+            if token.startswith(text) and token != last_completion:
+                last_index = i
+                last_completion = token
+                return token
+            i += 1
+            
+            if i == len(identifiers) and allow_cycle:
+                allow_cycle = False
+                i = 0
+    else:
+        return None
 
+def run_repl(redundancy, functional_mode):
+    #Setup the autocompleter
+    if state.repl_use_autocompletion:
+        readline.parse_and_bind("Tab: complete")
+        readline.set_completer(completion_function)
+    
     # The two different prompt types either > for a new statement
     # or . for continuing one
     # lhh: changed the prompt since replit.com uses the > as their console prompt
@@ -126,3 +243,6 @@ def run_repl(redundancy, functional_mode):
             current_prompt = arrow_prompt
         else:
             current_prompt = arrow_prompt
+
+if __name__ == "__main__":
+    repl(True, redundancy=False, prologue=True, functional_mode=False)
diff --git a/asteroid/state.py b/asteroid/state.py
index 7b89e664..79886ee7 100644
--- a/asteroid/state.py
+++ b/asteroid/state.py
@@ -25,6 +25,7 @@ def initialize(self,module="<input>"):
         # this.
         self.error_trace = None
         self.debugger = None
+        self.repl_use_autocompletion = True
 
 state = State()
 
diff --git a/asteroid/support.py b/asteroid/support.py
index 7401c6c3..606f03f8 100644
--- a/asteroid/support.py
+++ b/asteroid/support.py
@@ -297,3 +297,137 @@ def term2string(term):
     else:
         return '('+TYPE+'...)'
 
+def get_tail_term(sym, term, attrs):
+    # sym was None: print error message and return None
+    if not sym:
+        print("error: variable {} not found".format('@'.join(sym + attrs)))
+        return None
+    
+    # Loop over all of the attributes, get the type and check if the attribute is an integer or number
+    for a in attrs:
+        TYPE = term[0]
+        is_index = a.lstrip('-+').isnumeric()
+        is_member = not a.isnumeric()
+        
+        # Indexing a list or tuple: if the index is at least 0, set the term to the value of that index.
+        # Otherwise, print an error message and return None
+        if TYPE in ['list', 'tuple'] and is_index:
+            val = term[1]
+            if 0 <= int(a) < len(val):
+                term = val[int(a)]
+            else:
+                print("error: invalid index value {}".format(a))
+                return None
+        # Indexing a string: if the index is at least 0, set the term to a string equal to a single character.
+        # Otherwise, error and return None
+        elif TYPE in ['string'] and is_index:
+            val = term[1]
+            if 0 <= int(a) < len(val):
+                term = ('string', val[int(a)])
+            else:
+                print("error: invalid index value {}".format(a))
+                return None
+        # Accessing structure member: if the member exists, set the term to the object data of that member
+        # Otherwise, error and return None
+        elif TYPE in ['object'] and is_member:
+            (OBJECT,
+            (STRUCT_ID, (ID, struct_id)),
+            (MEMBER_NAMES, (LIST, member_names)),
+            (OBJECT_MEMORY, (LIST, object_memory))) = term
+            if a in member_names:
+                member_ix = member_names.index(a)
+                term = object_memory[member_ix]
+            else:
+                print("error: instance of struct {} has no member '{}'".format(struct_id, a))
+                return None
+        
+        # Accessing member on list, tuple, or string, or indexing a structure, or attempting to access attributes on any type that
+        # does not include those types: error and return None.
+        elif TYPE in ['list', 'tuple', 'string'] and is_member:
+            print("error: instance of {} has no data members".format(TYPE))
+            return None
+        elif TYPE in ['object'] and is_index:
+            print("error: structures are not indexable")
+            return None
+        elif TYPE not in ['list', 'tuple', 'string', 'object']:
+            print("error: instance of {} has no accessible attributes".format(TYPE))
+            return None
+        
+        # Used for error handling: attach the next attribute to the existing symbol
+        sym += "@{}".format(a)
+    return term
+
+def term2verbose(term, indent_size=0, initial_indent=True):
+    TYPE = term[0]
+    # Type does not contain nested data: call term2string and return the value
+    if TYPE not in ['list', 'tuple', 'object']:
+        return term2string(term)
+    
+    curr_indent, next_indent = ' '*4*indent_size, ' '*4*(indent_size+1)
+    term_string = curr_indent if initial_indent else ''
+    
+    # Type is an object: build string with struct name, and data members and corresponding types on separate lines
+    # Make sure indent is not applied twice
+    if TYPE == 'object':
+        (OBJECT,
+        (STRUCT_ID, (ID, struct_id)),
+        (MEMBER_NAMES, (LIST, member_names)),
+        (OBJECT_MEMORY, (LIST, object_memory))) = term
+        
+        term_string += (struct_id + '(\n')
+        l = len(member_names)
+        for ix in range(l):
+            term_string += "{}{}: {}".format(next_indent,
+                                           member_names[ix],
+                                           term2verbose(object_memory[ix], indent_size=indent_size+1, initial_indent=False))
+            term_string += ',\n' if ix < l-1 else '\n'
+        term_string += curr_indent + ')'
+    # Type is list or tuple: build string with brackets, and stored data on each new line, making sure to indent new data when not nested.
+    else:
+        val = term[1]
+        term_string += '[\n' if TYPE == 'list' else '(\n'
+        l = len(val)
+        for i in range(l):
+            if val[i][0] not in ['list', 'tuple', 'object']:
+                term_string += next_indent
+            term_string += term2verbose(val[i], indent_size=indent_size+1)
+            if i != l-1:
+                term_string += ','
+            if l > 1 or TYPE == 'list':
+                term_string += '\n'
+        if l == 1 and TYPE == 'tuple':
+            term_string += ',\n'
+        term_string += curr_indent + (']' if TYPE == 'list' else ')')
+    return term_string
+
+def find_function(sym, val, fname, func_name):
+    # If the function has already been found, check if it is in the correct file
+    if sym == func_name and val[0] == 'function-val':
+        if _check_lineinfo_function(val, fname): return True
+    # If we are looking at a struct, check if the function is one of its members and if it lies in the correct file
+    if val[0] == 'struct':
+        (STRUCT,
+        (MEMBER_NAMES, (LIST, member_names)),
+        (STRUCT_MEMORY, (LIST, struct_memory))) = val
+        if func_name in member_names and struct_memory[member_names.index(func_name)][0] == 'function-val':
+            if _check_lineinfo_function(struct_memory[member_names.index(func_name)], fname): return True
+    # If we are looking at a loaded module, recurse on everything that is not another module and return True if the function was found
+    if val[0] == 'module':
+        (MODULE,
+        (ID, id),
+        (SCOPE, scope)) = val
+        for s in scope[0]:
+            for (new_sym, new_val) in s.items():
+                if new_val[0] != 'module' and find_function(new_sym, new_val, fname, func_name):
+                    return True
+    return False
+    
+def _check_lineinfo_function(val, fname):
+    # Extract the lineinfo field and compare it to the provided filename
+    (FUNCTION_VAL,
+    (BODY_LIST,
+    (LIST, body_list)),
+    (scopes, globals, global_scope)) = val
+    (LINEINFO,
+    (filename, lineno)) = body_list[0]
+    return filename == fname
diff --git a/docs/Asteroid in Action.rst b/docs/Asteroid in Action.rst
index caaa390b..eee3202c 100644
--- a/docs/Asteroid in Action.rst
+++ b/docs/Asteroid in Action.rst
@@ -1,11 +1,11 @@
 ..
     /******************************************************************
     This is the source file from which the action doc is generated.
-    We use cpp to insert live code snippets into the document.
+    We use pcpp to insert live code snippets into the document.
     In order to generate the action doc run the following command
     on a Unix-like system:
 
-    cpp -w -P "Asteroid in Action.txt" > "Asteroid in Action.rst"
+    python generate_docs.py
 
     ******************************************************************/
 ..
diff --git a/docs/Asteroid in Action.txt b/docs/Asteroid in Action.txt
index 266082e9..5bfa3b3f 100644
--- a/docs/Asteroid in Action.txt
+++ b/docs/Asteroid in Action.txt
@@ -1,11 +1,11 @@
 ..
     /******************************************************************
     This is the source file from which the action doc is generated.
-    We use cpp to insert live code snippets into the document.
+    We use pcpp to insert live code snippets into the document.
     In order to generate the action doc run the following command
     on a Unix-like system:
 
-    cpp -w -P "Asteroid in Action.txt" > "Asteroid in Action.rst"
+    python generate_docs.py
 
     ******************************************************************/
 #include "header.txt"
diff --git a/docs/MAD.rst b/docs/MAD.rst
index 7c8de580..10bdd113 100644
--- a/docs/MAD.rst
+++ b/docs/MAD.rst
@@ -1,11 +1,11 @@
 ..
     /******************************************************************
     This is the source file from which the action doc is generated.
-    We use cpp to insert live code snippets into the document.
+    We use pcpp to insert live code snippets into the document.
     In order to generate the action doc run the following command
     on a Unix-like system:
 
-    cpp -w -P "MAD.txt" > "MAD.rst"
+    python generate_docs.py
 
     ******************************************************************/
 ..
@@ -36,111 +36,497 @@ This will start a debugging session of the file ``<FILENAME>``.
 Here is an example where ``<FILENAME>`` is ``list.ast``,
 ::
     $ asteroid -d list.ast
-    Minimal Asteroid Debugger -- Version 0.1
+    Minimal Asteroid Debugger 0.0.1
     (c) University of Rhode Island
     type 'help' for additional information
     mad>
 
-Debugging sessions
-==================
-During a debugging session the user is able to step through a program and
-examine its state.  The following is debugging session of a program that prints
-out a sequence of integer values,
+The ``help`` command will display all available commands with a short description of each.
+Here is a table of the available commands in the the debugger,
 ::
-    load system io.
+    breakpoints ........................ show all breakpoints
+    clear .............................. clear all breakpoints
+    continue ........................... continue execution to next breakpoint
+    down ............................... move down one stack frame
+    frame .............................. display current stack frame number
+    help ............................... display help
+    list [<num>|*]...................... display <num> (default 4) lines of source code, * displays all lines in file
+    next ............................... step execution across a nested scope
+    print <name>[@<num>|<name>]+|* [-v]. print contents of <name>, * lists all vars in scope, recursively access (nested) objects with @, '-v' enables verbose printing of nested data
+    quit ............................... quit debugger
+    stack [<num>|* [-v]]................ display runtime stack, list all items in specific frame with an index or all frames with '*', '-v' toggles verbose printing
+    set [<func>|<line#> [<file>]] ...... set a breakpoint, breakpoints may only be set on valid statements on already loaded files
+    step ............................... step to next executable statement
+    trace [<num> [<num>]]............... display runtime stack trace, can specify either the first n frames or all of the frames between the start and end
+    up ................................. move up one stack frame
+    where .............................. print current program line
 
-    for i in 1 to 5 do
-        let x = i.
-        io @println x.
-    end
+The ``|`` means that either the item to the left or to the right of it may be used. Any items enclosed in square brackets
+``[]`` are considered optional, meaning that they are not required to be provided in order to the command to be valid.
+The ``+`` means that any number of the item to its left may be supplied to the command. Items enclosed in angle brackets
+``<>`` represent placeholders, indicating that an actual value must be used when executing the command. The text between
+the brackets indicates what should be used.
+
+All debugging sessions can be stopped at any time by using the ``quit`` command.
+
+Source Code
+===========
+When running a debugging session, the ``list`` command will display some of the source
+code for the current file. In the output, the ``>`` character at the beginning of a line
+indicates the current line to be executed. Any lines beginning with a ``*`` are lines with
+a breakpoint.
+
+The ``list`` command may also be executed with an optional parameter, either some positive integer
+or ``*``. When called with a positive integer, ``list`` will output the current line and that many
+lines above and below that line, or until the beginning or the end of the file. By default,
+``list`` will display 4 lines above and below the current line. To display every line in the current file,
+use the ``*`` argument.
 
-We start the debugger as shown above. We then set a breakpoint at the first statement in the
-``for`` loop then ``continue``
-our computation until it hits the break point. We can then look at the contents of
-memory.  Finally we clear all breakpoints and let the computation finish.  Here we go,
+To get only the current line, the ``where`` command will display the current file and line number, alongside
+the contents of that line. Here are those commands in action,
 ::
     $ asteroid -d list.ast
-    Minimal Asteroid Debugger -- Version 0.1
+    Minimal Asteroid Debugger 0.0.1
     (c) University of Rhode Island
-    type 'help' for additional information
+    type "help" for additional information
+    mad> list
+    >  1 load system io.
+       2
+       3 for i in 1 to 5 do
+       4     let x = i.
+       5     io @println x.
+    mad> list 2
+    >  1 load system io.
+       2
+       3 for i in 1 to 5 do
+    mad> list *
+       1 load system io.
+       2
+       3 for i in 1 to 5 do
+       4     let x = i.
+       5     io @println x.
+       6 end
+       7 [EOF]
+    mad> where
+    list.ast:1:load system io.
+    mad> quit
+    $
+
+Breakpoints
+===========
+For breakpoints, we can use the ``set`` command to set a breakpoint on either a line or
+a function definition. When setting breakpoints, only lines containing valid statements or
+already loaded functions are allowed to be set. Blank lines and incomplete statements will
+be rejected. In addition, breakpoints may only be set on lines and functions defined in files
+that have already been loaded. By default, breakpoints will be set in their current file.
+If ``set`` is called without an argument, a breakpoint will be placed on the current line.
+
+To examine all of the set breakpoints, the command ``breakpoints`` will display every breakpoint
+in the order that it was set in. The ``clear`` command will remove all of the breakpoints. Here is
+an example of this behavior,
+::
+    $ asteroid -d list.ast
+    Minimal Asteroid Debugger 0.0.1
+    (c) University of Rhode Island
+    type "help" for additional information
     mad> list
-    >  1 load system io.             <<<< current line
+    >  1 load system io.
        2
        3 for i in 1 to 5 do
-       4   let x = i.
-       5   io @println x.
-    mad> set 4                       <<<< setting a breakpoint
+       4     let x = i.
+       5     io @println x.
+    mad> set 4
+    mad> set 2
+    error: cannot place breakpoints on blank lines
     mad> breakpoints
     breakpoints:
     list.ast:4
-    mad> continue                    <<<< continue execution
-    reached breakpoint (list.ast:4)
-       1 load system io.
+    mad> list
+    >  1 load system io.
+       2
+       3 for i in 1 to 5 do
+    *  4     let x = i.
+       5     io @println x.
+    mad> clear
+    mad> breakpoints
+    breakpoints:
+    mad> list
+    >  1 load system io.
        2
        3 for i in 1 to 5 do
-    >  4   let x = i.
-       5   io @println x.
+       4     let x = i.
+       5     io @println x.
+    mad> quit
+    $
+
+Execution
+=========
+The Minimal Asteroid Debugger provides multiple commands to allow for a developer to resume and gradually
+execute their debugging sessions. The ``continue`` command will resume the session until either a breakpoint
+is encountered or the session executes the final line of the program. To gradually step through a session,
+the ``next`` and ``step`` commands allow a developer to walk through their program one statement at a time.
+When executing ``step``, the debugging session with enter a nested scope and set the next statement to be
+executed as the first statement in the new scope. By contrast, ``next`` will step over nested scopes and will
+set the next statement to be after that scope ends. The following example illustratea the difference between
+each of the three commands,
+::
+    load system io.
+
+    function some_func with () do
+         io @println "Inside a function".
+         io @println "Leaving the function..."
+    end
+
+    io @println "Outside a function".
+    some_func ().
+    io @println "Left the function".
+
+When calling ``continue``, the debugging session will stop on line 4 followed by line 10,
+::
+    $ asteroid -d nested_scopes.ast
+    Minimal Asteroid Debugger 0.0.1
+    (c) University of Rhode Island
+    type "help" for additional information
+    mad> list
+    >  1 load system io.
+       2
+       3 function some_func with () do
+       4     io @println "Inside a function".
+       5     io @println "Leaving the function..."
+    mad> set 4
+    mad> set 10
+    mad> continue
+    Outside a function
+    reached breakpoint (nested_scopes.ast:4)
+       1 load system io.
+       2
+       3 function some_func with () do
+    >  4     io @println "Inside a function".
+       5     io @println "Leaving the function..."
        6 end
        7
-       8 [EOF]
-    mad> print i                     <<<< examining the value of i
-    i: 1
+       8 io @println "Outside a function".
     mad> continue
-    1
-    reached breakpoint (list.ast:4)
+    Inside a function
+    Leaving the function...
+    reached breakpoint (nested_scopes.ast:10)
+       7
+       8 io @println "Outside a function".
+       9 some_func ().
+    >  10 io @println "Left the function".
+       11 [EOF]
+    mad> continue
+    Left the function
+    stopping MAD
+    mad> quit
+    $
+
+When calling ``step``, the debugging session will enter any scope, including load statements and function calls,
+::
+    $ asteroid -d nested_scopes.ast
+    Minimal Asteroid Debugger 0.0.1
+    (c) University of Rhode Island
+    type "help" for additional information
+    mad> list
+    >  1 load system io.
+       2
+       3 function some_func with () do
+       4     io @println "Inside a function".
+       5     io @println "Leaving the function..."
+    mad> step
+    >  1 load system io.
+       2
+       3 function some_func with () do
+       4     io @println "Inside a function".
+       5     io @println "Leaving the function..."
+    mad> step
+    entering module io
+    >  1 load system io.
+       2
+       3 function some_func with () do
+       4     io @println "Inside a function".
+       5     io @println "Leaving the function..."
+    mad> step
+       10 ------------------------------------------------------------------
+       11
+       12 ------------------------------------------------------------------
+    >  13 structure "MAD.txt" with
+       14 ------------------------------------------------------------------
+       15 -- Basic file i/o
+       16
+       17 data fd.
+    mad> continue
+    Outside a function
+    Inside a function
+    Leaving the function...
+    Left the function
+    stopping MAD
+    mad> quit
+    $
+
+Calling ``next`` only stops on valid statements within the current scope, never entering a lower scope.
+::
+    $ asteroid -d nested_scopes.ast
+    Minimal Asteroid Debugger 0.0.1
+    (c) University of Rhode Island
+    type "help" for additional information
+    mad> next
+    >  1 load system io.
+       2
+       3 function some_func with () do
+       4     io @println "Inside a function".
+       5     io @println "Leaving the function..."
+    mad> next
        1 load system io.
        2
-       3 for i in 1 to 5 do
-    >  4   let x = i.
-       5   io @println x.
+    >  3 function some_func with () do
+       4     io @println "Inside a function".
+       5     io @println "Leaving the function..."
        6 end
        7
-       8 [EOF]
-    mad> print i
-    i: 2
-    mad> clear                       <<<< delete breakpoints
-    mad> continue
-    2
-    3
-    4
-    5
+    mad> next
+       5     io @println "Leaving the function..."
+       6 end
+       7
+    >  8 io @println "Outside a function".
+       9 some_func ().
+       10 io @println "Left the function".
+       11 [EOF]
+    mad> next
+    Outside a function
+       6 end
+       7
+       8 io @println "Outside a function".
+    >  9 some_func ().
+       10 io @println "Left the function".
+       11 [EOF]
+    mad> next
+    Inside a function
+    Leaving the function...
+       7
+       8 io @println "Outside a function".
+       9 some_func ().
+    >  10 io @println "Left the function".
+       11 [EOF]
+    mad> next
+    Left the function
     stopping MAD
-    mad> print i
-    i: 5
-    mad> print x
-    x: 5
     mad> quit
     $
 
-Commands
-========
-Here is a table of the available commands in the the debugger,
+Data
+====
+During the execution of an Asteroid program, all defined data within a given scope may be displayed using
+the ``print`` command. This command accepts either the name of a structure, a function, a variable, or ``*``,
+which will output every defined item within the currently viewed scope.
+
+When displaying a variable that holds a list, a tuple, a string, or an instance of some structure, ``print`` will
+allow the display of specific data at a given index or data member. Like in Asteroid, this would be accomplished
+by adding ``@`` after the name, followed by either an integer of at least 0 (for lists, tuples, and strings) or some
+other name (for instances of structures). For highly nested data, these access patterns may be infinitely added,
+provided that the pattern is both valid and accesses a list, tuple, string, or structure instance.
+
+Additionally, the ``-v`` option may be added to ``print`` commands. When called on lists, tuples, or instances of structures,
+every data member will be printed on an individual line with the depth of a specific item indicated by the amount of whitespace
+to the left of the line. Structure instances will display every unique data member and method on a new line, with the corresponding
+value separated by a colon on the same line. Here is a demonstration with the following example,
 ::
-    breakpoints .................... show all breakpoints
-    clear .......................... clear all breakpoints
-    continue ....................... continue execution to next breakpoint
-    down ........................... move down one stack frame
-    frame .......................... display current stack frame number
-    help ........................... display help
-    list ........................... display source code
-    next ........................... step execution across a nested scope
-    print <name>|* ................. print contents of <name>, * lists all vars in scope
-    quit ........................... quit debugger
-    stack .......................... display runtime stack
-    set [<func>|<line#> [<file>]] .. set a breakpoint
-    step ........................... step to next executable statement
-    trace .......................... display runtime stack
-    up ............................. move up one stack frame
-    where .......................... print current program line
-
-The or bar ``|`` means different options as arguments to the commands. Anything between
-square brackets is optional.  Anything appearing in angle brackets are actual values.
-For example, ``print <name>`` means we want to examine the value of an actual variable, e.g.
+    load system io.
+
+    structure Foo with
+        data bar.
+        data baz.
+
+        function quux with () do
+            io @println "Calling quux".
+        end
+    end
+
+    let lst = [1, [2, 3], (4, 5, "678"), Foo(9, ["ten", "eleven"])].
+    io @println lst.
+
+This is the debugging session,
 ::
-    print n
+    $ asteroid -d nested_data.ast
+    Minimal Asteroid Debugger 0.0.1
+    (c) University of Rhode Island
+    type "help" for additional information
+    mad> set 13
+    mad> continue
+    reached breakpoint (nested_data.ast:13)
+       10 end
+       11
+       12 let lst = [1, [2, 3], (4, 5, "678"), Foo(9, ["ten", "eleven"])].
+    >  13 io @println lst.
+       14 [EOF]
+    mad> print *
+    Error: (struct...)
+    Exception: (struct...)
+    len: (function ...)
+    -- ...
+    io: (module...)
+    Foo: (struct...)
+    lst: [1,[2,3],(4,5,678),Foo(9,[ten,eleven])]
+    mad> print lst
+    lst: [1,[2,3],(4,5,678),Foo(9,[ten,eleven])]
+    mad> print lst@0
+    lst@0: 1
+    mad> print lst@1
+    lst@1: [2,3]
+    mad> print lst@1@0
+    lst@1@0: 2
+    mad> print lst@3
+    lst@3: Foo(9,[ten,eleven])
+    mad> print lst@3@baz
+    lst@3@baz: [ten,eleven]
+    mad> print lst@3@baz@1
+    lst@3@baz@1: eleven
+    mad> print lst@3@baz@1@3
+    lst@3@baz@1@3: v
+    mad> print lst -v
+    lst: [
+       1,
+       [
+          2,
+          3
+       ],
+       (
+          4,
+          5,
+          678
+       ),
+       Foo(
+          bar: 9,
+          baz: [
+                ten,
+                eleven
+          ],
+          quux: (function ...)
+      )
+   ]
+   mad> quit
+   $
+
+
+Stack Traces
+============
+The Minimal Asteroid Debugger allows developers to switch between any currently loaded scope. To determine
+which scope is currently viewed, the ``frame`` command will display the number of the current scope.
+The ``up`` and ``down`` commands allow developers to move to a higher or lower scope respectively, or until
+hitting either the toplevel scope or the most recently defined scope respectively.
+
+To get a more detailed view of the stack, the ``trace`` command will display every scope from the currently
+viewed scope until the toplevel scope. ``trace`` may also accept some positive integer, which will display that
+number of scopes beginning with the currently viewed scope. To display a specific range of scope, ``trace`` may
+accept two positive integers indicating the lowest and highest scope numbers to be displayed.
 
-where ``n`` is a variable name.
+Since Asteroid is a statically scoped language, the data in a particular scope may be different between scopes.
+To examine this difference, the ``stack`` command may be used to examine the data of a particular scope. By default,
+``stack`` will behave identically to ``trace`` when called with no arguments. However, ``stack`` can accept an optional
+argument either representing a specific scope number or ``*``. When called with a specific scope number, ``stack``
+will display every defined item in that scope. The argument ``*`` will display every defined item in every defined scope.
+Similar to ``print``, the ``-v`` argument allows for the verbose printing of any nested data and can be used with any of
+the previously defined arguments.
 
-Most commands are very much self-explanatory with the exception of perhaps ``next``.  The ``next``
-command works just like the ``step`` command except that it will skip stepping into nested scopes
-such as function calls or the execution of module statements.
+The following is an demonstration using the example program,
+::
+   load system io.
+
+   function factorial
+      with 0 do
+         1.
+      with n:%integer do
+         n * factorial (n - 1).
+   end
+
+   io @println (factorial 4).
+
+This is the demonstration,
+::
+   $ asteroid -d factorial.ast
+   Minimal Asteroid Debugger 0.0.1
+   (c) University of Rhode Island
+   type "help" for additional information
+   mad> set 5
+   mad> continue
+   reached breakpoint (factorial.ast:5)
+      2
+      3 function factorial
+      4     with 0 do
+   >  5         1.
+      6     with n:%integer do
+      7         n * factorial (n - 1).
+      8 end
+      9
+   mad> frame
+   you are looking at frame #0
+   mad> trace
+   Runtime stack trace (most recent call first):
+   frame #0: factorial.ast @factorial
+   frame #1: factorial.ast @factorial
+   frame #2: factorial.ast @factorial
+   frame #3: factorial.ast @factorial
+   frame #4: factorial.ast @factorial
+   frame #5: factorial.ast @<toplevel>
+   mad> stack
+   Runtime stack (most recent call first):
+   frame #0: factorial.ast @factorial
+   frame #1: factorial.ast @factorial
+   frame #2: factorial.ast @factorial
+   frame #3: factorial.ast @factorial
+   frame #4: factorial.ast @factorial
+   frame #5: factorial.ast @<toplevel>
+   mad> down
+   error: no such frame
+   mad> up
+   you are looking at frame #1
+   mad> up
+   you are looking at frame #2
+   mad> trace
+   Runtime stack trace (most recent call first):
+   frame #2: factorial.ast @factorial
+   frame #3: factorial.ast @factorial
+   frame #4: factorial.ast @factorial
+   frame #5: factorial.ast @<toplevel>
+   mad> trace 3
+   Runtime stack trace (most recent call first):
+   frame #2: factorial.ast @factorial
+   frame #3: factorial.ast @factorial
+   frame #4: factorial.ast @factorial
+   mad> trace 0 3
+   Runtime stack trace (most recent call first):
+   frame #0: factorial.ast @factorial
+   frame #1: factorial.ast @factorial
+   frame #2: factorial.ast @factorial
+   mad> stack 0
+   frame #0: factorial.ast @factorial
+   mad> stack 3
+   frame #3: factorial.ast @factorial
+   __AST__TEMP81__: 3
+   n: 3
+   mad> stack 4
+   frame #4: factorial.ast @factorial
+   __AST__TEMP81__: 4
+   n: 4
+   mad> stack *
+   Runtime stack (most recent call first):
+   frame #2: factorial.ast @factorial
+   __AST__TEMP81__: 2
+   n: 2
+   frame #3: factorial.ast @factorial
+   __AST__TEMP81__: 3
+   n: 3
+   frame #4: factorial.ast @factorial
+   __AST__TEMP81__: 4
+   n: 4
+   frame #5: factorial.ast @<toplevel>
+   Error: (struct...)
+   Exception: (struct...)
+   -- ...
+   io: (module...)
+   factorial: (function ...)
+   mad> continue
+   24
+   stopping MAD
+   mad> quit
+   $
diff --git a/docs/MAD.txt b/docs/MAD.txt
index 11da0f58..55c9e0c0 100644
--- a/docs/MAD.txt
+++ b/docs/MAD.txt
@@ -1,11 +1,11 @@
 ..
     /******************************************************************
     This is the source file from which the action doc is generated.
-    We use cpp to insert live code snippets into the document.
+    We use pcpp to insert live code snippets into the document.
     In order to generate the action doc run the following command
     on a Unix-like system:
 
-    cpp -w -P "MAD.txt" > "MAD.rst"
+    python generate_docs.py
 
     ******************************************************************/
 
@@ -34,111 +34,497 @@ This will start a debugging session of the file ``<FILENAME>``.
 Here is an example where ``<FILENAME>`` is ``list.ast``,
 ::
     $ asteroid -d list.ast   
-    Minimal Asteroid Debugger -- Version 0.1
+    Minimal Asteroid Debugger 0.0.1
     (c) University of Rhode Island
     type 'help' for additional information
     mad>
 
-Debugging sessions
-==================
-During a debugging session the user is able to step through a program and 
-examine its state.  The following is debugging session of a program that prints
-out a sequence of integer values,
+The ``help`` command will display all available commands with a short description of each.
+Here is a table of the available commands in the the debugger,
 ::
-    load system io.
+    breakpoints ........................ show all breakpoints
+    clear .............................. clear all breakpoints
+    continue ........................... continue execution to next breakpoint
+    down ............................... move down one stack frame
+    frame .............................. display current stack frame number
+    help ............................... display help
+    list [<num>|*]...................... display <num> (default 4) lines of source code, * displays all lines in file
+    next ............................... step execution across a nested scope
+    print <name>[@<num>|<name>]+|* [-v]. print contents of <name>, * lists all vars in scope, recursively access (nested) objects with @, '-v' enables verbose printing of nested data
+    quit ............................... quit debugger
+    stack [<num>|* [-v]]................ display runtime stack, list all items in specific frame with an index or all frames with '*', '-v' toggles verbose printing
+    set [<func>|<line#> [<file>]] ...... set a breakpoint, breakpoints may only be set on valid statements on already loaded files
+    step ............................... step to next executable statement
+    trace [<num> [<num>]]............... display runtime stack trace, can specify either the first n frames or all of the frames between the start and end
+    up ................................. move up one stack frame
+    where .............................. print current program line
 
-    for i in 1 to 5 do
-        let x = i.
-        io @println x.
-    end
+The ``|`` means that either the item to the left or to the right of it may be used. Any items enclosed in square brackets
+``[]`` are considered optional, meaning that they are not required to be provided in order to the command to be valid.
+The ``+`` means that any number of the item to its left may be supplied to the command. Items enclosed in angle brackets
+``<>`` represent placeholders, indicating that an actual value must be used when executing the command. The text between
+the brackets indicates what should be used.
+
+All debugging sessions can be stopped at any time by using the ``quit`` command.
+
+Source Code
+===========
+When running a debugging session, the ``list`` command will display some of the source
+code for the current file. In the output, the ``>`` character at the beginning of a line
+indicates the current line to be executed. Any lines beginning with a ``*`` are lines with
+a breakpoint.
 
-We start the debugger as shown above. We then set a breakpoint at the first statement in the
-``for`` loop then ``continue``
-our computation until it hits the break point. We can then look at the contents of 
-memory.  Finally we clear all breakpoints and let the computation finish.  Here we go,
+The ``list`` command may also be executed with an optional parameter, either some positive integer
+or ``*``. When called with a positive integer, ``list`` will output the current line and that many
+lines above and below that line, or until the beginning or the end of the file. By default,
+``list`` will display 4 lines above and below the current line. To display every line in the current file,
+use the ``*`` argument.
+
+To get only the current line, the ``where`` command will display the current file and line number, alongside
+the contents of that line. Here are those commands in action,
 ::
     $ asteroid -d list.ast
-    Minimal Asteroid Debugger -- Version 0.1
+    Minimal Asteroid Debugger 0.0.1
     (c) University of Rhode Island
-    type 'help' for additional information
+    type "help" for additional information
+    mad> list
+    >  1 load system io.
+       2
+       3 for i in 1 to 5 do
+       4     let x = i.
+       5     io @println x.
+    mad> list 2
+    >  1 load system io.
+       2
+       3 for i in 1 to 5 do
+    mad> list *
+       1 load system io.
+       2
+       3 for i in 1 to 5 do
+       4     let x = i.
+       5     io @println x.
+       6 end
+       7 [EOF]
+    mad> where
+    list.ast:1:load system io.
+    mad> quit
+    $
+
+Breakpoints
+===========
+For breakpoints, we can use the ``set`` command to set a breakpoint on either a line or
+a function definition. When setting breakpoints, only lines containing valid statements or
+already loaded functions are allowed to be set. Blank lines and incomplete statements will
+be rejected. In addition, breakpoints may only be set on lines and functions defined in files
+that have already been loaded. By default, breakpoints will be set in their current file.
+If ``set`` is called without an argument, a breakpoint will be placed on the current line.
+
+To examine all of the set breakpoints, the command ``breakpoints`` will display every breakpoint
+in the order that it was set in. The ``clear`` command will remove all of the breakpoints. Here is
+an example of this behavior,
+::
+    $ asteroid -d list.ast
+    Minimal Asteroid Debugger 0.0.1
+    (c) University of Rhode Island
+    type "help" for additional information
     mad> list
-    >  1 load system io.             <<<< current line
-       2 
+    >  1 load system io.
+       2
        3 for i in 1 to 5 do
-       4   let x = i.
-       5   io @println x.
-    mad> set 4                       <<<< setting a breakpoint
+       4     let x = i.
+       5     io @println x.
+    mad> set 4
+    mad> set 2
+    error: cannot place breakpoints on blank lines
     mad> breakpoints
     breakpoints:
     list.ast:4
-    mad> continue                    <<<< continue execution
-    reached breakpoint (list.ast:4)
-       1 load system io.
-       2 
+    mad> list
+    >  1 load system io.
+       2
        3 for i in 1 to 5 do
-    >  4   let x = i.
-       5   io @println x.
-       6 end
-       7 
-       8 [EOF]
-    mad> print i                     <<<< examining the value of i
-    i: 1
+    *  4     let x = i.
+       5     io @println x.
+    mad> clear
+    mad> breakpoints
+    breakpoints:
+    mad> list
+    >  1 load system io.
+       2
+       3 for i in 1 to 5 do
+       4     let x = i.
+       5     io @println x.
+    mad> quit
+    $
+
+Execution
+=========
+The Minimal Asteroid Debugger provides multiple commands to allow for a developer to resume and gradually
+execute their debugging sessions. The ``continue`` command will resume the session until either a breakpoint
+is encountered or the session executes the final line of the program. To gradually step through a session,
+the ``next`` and ``step`` commands allow a developer to walk through their program one statement at a time.
+When executing ``step``, the debugging session with enter a nested scope and set the next statement to be
+executed as the first statement in the new scope. By contrast, ``next`` will step over nested scopes and will
+set the next statement to be after that scope ends. The following example illustratea the difference between
+each of the three commands,
+::
+    load system io.
+
+    function some_func with () do
+         io @println "Inside a function".
+         io @println "Leaving the function..."
+    end
+
+    io @println "Outside a function".
+    some_func ().
+    io @println "Left the function".
+
+When calling ``continue``, the debugging session will stop on line 4 followed by line 10,
+::
+    $ asteroid -d nested_scopes.ast
+    Minimal Asteroid Debugger 0.0.1
+    (c) University of Rhode Island
+    type "help" for additional information
+    mad> list
+    >  1 load system io.
+       2
+       3 function some_func with () do
+       4     io @println "Inside a function".
+       5     io @println "Leaving the function..."
+    mad> set 4
+    mad> set 10
     mad> continue
-    1
-    reached breakpoint (list.ast:4)
+    Outside a function
+    reached breakpoint (nested_scopes.ast:4)
        1 load system io.
-       2 
-       3 for i in 1 to 5 do
-    >  4   let x = i.
-       5   io @println x.
+       2
+       3 function some_func with () do
+    >  4     io @println "Inside a function".
+       5     io @println "Leaving the function..."
        6 end
-       7 
-       8 [EOF]
-    mad> print i
-    i: 2
-    mad> clear                       <<<< delete breakpoints
+       7
+       8 io @println "Outside a function".
+    mad> continue
+    Inside a function
+    Leaving the function...
+    reached breakpoint (nested_scopes.ast:10)
+       7
+       8 io @println "Outside a function".
+       9 some_func ().
+    >  10 io @println "Left the function".
+       11 [EOF]
     mad> continue
-    2
-    3
-    4
-    5
+    Left the function
     stopping MAD
-    mad> print i
-    i: 5
-    mad> print x
-    x: 5
     mad> quit
     $
 
-Commands
-========
-Here is a table of the available commands in the the debugger,
+When calling ``step``, the debugging session will enter any scope, including load statements and function calls,
+::
+    $ asteroid -d nested_scopes.ast
+    Minimal Asteroid Debugger 0.0.1
+    (c) University of Rhode Island
+    type "help" for additional information
+    mad> list
+    >  1 load system io.
+       2
+       3 function some_func with () do
+       4     io @println "Inside a function".
+       5     io @println "Leaving the function..."
+    mad> step
+    >  1 load system io.
+       2
+       3 function some_func with () do
+       4     io @println "Inside a function".
+       5     io @println "Leaving the function..."
+    mad> step
+    entering module io
+    >  1 load system io.
+       2
+       3 function some_func with () do
+       4     io @println "Inside a function".
+       5     io @println "Leaving the function..."
+    mad> step
+       10 ------------------------------------------------------------------
+       11
+       12 ------------------------------------------------------------------
+    >  13 structure __FILE__ with
+       14 ------------------------------------------------------------------
+       15 -- Basic file i/o
+       16
+       17 data fd.
+    mad> continue
+    Outside a function
+    Inside a function
+    Leaving the function...
+    Left the function
+    stopping MAD
+    mad> quit
+    $
+ 
+Calling ``next`` only stops on valid statements within the current scope, never entering a lower scope.
+::
+    $ asteroid -d nested_scopes.ast
+    Minimal Asteroid Debugger 0.0.1
+    (c) University of Rhode Island
+    type "help" for additional information
+    mad> next
+    >  1 load system io.
+       2
+       3 function some_func with () do
+       4     io @println "Inside a function".
+       5     io @println "Leaving the function..."
+    mad> next
+       1 load system io.
+       2
+    >  3 function some_func with () do
+       4     io @println "Inside a function".
+       5     io @println "Leaving the function..."
+       6 end
+       7
+    mad> next
+       5     io @println "Leaving the function..."
+       6 end
+       7
+    >  8 io @println "Outside a function".
+       9 some_func ().
+       10 io @println "Left the function".
+       11 [EOF]
+    mad> next
+    Outside a function
+       6 end
+       7
+       8 io @println "Outside a function".
+    >  9 some_func ().
+       10 io @println "Left the function".
+       11 [EOF]
+    mad> next
+    Inside a function
+    Leaving the function...
+       7
+       8 io @println "Outside a function".
+       9 some_func ().
+    >  10 io @println "Left the function".
+       11 [EOF]
+    mad> next
+    Left the function
+    stopping MAD
+    mad> quit
+    $
+
+Data
+====
+During the execution of an Asteroid program, all defined data within a given scope may be displayed using
+the ``print`` command. This command accepts either the name of a structure, a function, a variable, or ``*``,
+which will output every defined item within the currently viewed scope.
+
+When displaying a variable that holds a list, a tuple, a string, or an instance of some structure, ``print`` will
+allow the display of specific data at a given index or data member. Like in Asteroid, this would be accomplished
+by adding ``@`` after the name, followed by either an integer of at least 0 (for lists, tuples, and strings) or some
+other name (for instances of structures). For highly nested data, these access patterns may be infinitely added,
+provided that the pattern is both valid and accesses a list, tuple, string, or structure instance.
+
+Additionally, the ``-v`` option may be added to ``print`` commands. When called on lists, tuples, or instances of structures,
+every data member will be printed on an individual line with the depth of a specific item indicated by the amount of whitespace
+to the left of the line. Structure instances will display every unique data member and method on a new line, with the corresponding
+value separated by a colon on the same line. Here is a demonstration with the following example,
 ::
-    breakpoints .................... show all breakpoints
-    clear .......................... clear all breakpoints
-    continue ....................... continue execution to next breakpoint
-    down ........................... move down one stack frame
-    frame .......................... display current stack frame number
-    help ........................... display help
-    list ........................... display source code
-    next ........................... step execution across a nested scope
-    print <name>|* ................. print contents of <name>, * lists all vars in scope
-    quit ........................... quit debugger
-    stack .......................... display runtime stack
-    set [<func>|<line#> [<file>]] .. set a breakpoint
-    step ........................... step to next executable statement
-    trace .......................... display runtime stack
-    up ............................. move up one stack frame
-    where .......................... print current program line
-
-The or bar ``|`` means different options as arguments to the commands. Anything between
-square brackets is optional.  Anything appearing in angle brackets are actual values. 
-For example, ``print <name>`` means we want to examine the value of an actual variable, e.g.
+    load system io.
+
+    structure Foo with
+        data bar.
+        data baz.
+
+        function quux with () do
+            io @println "Calling quux".
+        end
+    end
+
+    let lst = [1, [2, 3], (4, 5, "678"), Foo(9, ["ten", "eleven"])].
+    io @println lst.
+
+This is the debugging session,
 ::
-    print n
+    $ asteroid -d nested_data.ast
+    Minimal Asteroid Debugger 0.0.1
+    (c) University of Rhode Island
+    type "help" for additional information
+    mad> set 13
+    mad> continue
+    reached breakpoint (nested_data.ast:13)
+       10 end
+       11
+       12 let lst = [1, [2, 3], (4, 5, "678"), Foo(9, ["ten", "eleven"])].
+    >  13 io @println lst.
+       14 [EOF]
+    mad> print *
+    Error: (struct...)
+    Exception: (struct...)
+    len: (function ...)
+    -- ...
+    io: (module...)
+    Foo: (struct...)
+    lst: [1,[2,3],(4,5,678),Foo(9,[ten,eleven])]
+    mad> print lst
+    lst: [1,[2,3],(4,5,678),Foo(9,[ten,eleven])]
+    mad> print lst@0
+    lst@0: 1
+    mad> print lst@1
+    lst@1: [2,3]
+    mad> print lst@1@0
+    lst@1@0: 2
+    mad> print lst@3
+    lst@3: Foo(9,[ten,eleven])
+    mad> print lst@3@baz
+    lst@3@baz: [ten,eleven]
+    mad> print lst@3@baz@1
+    lst@3@baz@1: eleven
+    mad> print lst@3@baz@1@3
+    lst@3@baz@1@3: v
+    mad> print lst -v
+    lst: [
+       1,
+       [
+          2,
+          3
+       ],
+       (
+          4,
+          5,
+          678
+       ),
+       Foo(
+          bar: 9,
+          baz: [
+                ten,
+                eleven
+          ],
+          quux: (function ...)
+      )
+   ]
+   mad> quit
+   $
+
+
+Stack Traces
+============
+The Minimal Asteroid Debugger allows developers to switch between any currently loaded scope. To determine
+which scope is currently viewed, the ``frame`` command will display the number of the current scope.
+The ``up`` and ``down`` commands allow developers to move to a higher or lower scope respectively, or until
+hitting either the toplevel scope or the most recently defined scope respectively.
 
-where ``n`` is a variable name.
+To get a more detailed view of the stack, the ``trace`` command will display every scope from the currently
+viewed scope until the toplevel scope. ``trace`` may also accept some positive integer, which will display that
+number of scopes beginning with the currently viewed scope. To display a specific range of scope, ``trace`` may
+accept two positive integers indicating the lowest and highest scope numbers to be displayed.
 
-Most commands are very much self-explanatory with the exception of perhaps ``next``.  The ``next``
-command works just like the ``step`` command except that it will skip stepping into nested scopes
-such as function calls or the execution of module statements.
\ No newline at end of file
+Since Asteroid is a statically scoped language, the data in a particular scope may be different between scopes.
+To examine this difference, the ``stack`` command may be used to examine the data of a particular scope. By default,
+``stack`` will behave identically to ``trace`` when called with no arguments. However, ``stack`` can accept an optional
+argument either representing a specific scope number or ``*``. When called with a specific scope number, ``stack``
+will display every defined item in that scope. The argument ``*`` will display every defined item in every defined scope.
+Similar to ``print``, the ``-v`` argument allows for the verbose printing of any nested data and can be used with any of
+the previously defined arguments.
+
+The following is an demonstration using the example program,
+::
+   load system io.
+
+   function factorial
+      with 0 do
+         1.
+      with n:%integer do
+         n * factorial (n - 1).
+   end
+
+   io @println (factorial 4).
+
+This is the demonstration,
+::
+   $ asteroid -d factorial.ast
+   Minimal Asteroid Debugger 0.0.1
+   (c) University of Rhode Island
+   type "help" for additional information
+   mad> set 5
+   mad> continue
+   reached breakpoint (factorial.ast:5)
+      2
+      3 function factorial
+      4     with 0 do
+   >  5         1.
+      6     with n:%integer do
+      7         n * factorial (n - 1).
+      8 end
+      9
+   mad> frame
+   you are looking at frame #0
+   mad> trace
+   Runtime stack trace (most recent call first):
+   frame #0: factorial.ast @factorial
+   frame #1: factorial.ast @factorial
+   frame #2: factorial.ast @factorial
+   frame #3: factorial.ast @factorial
+   frame #4: factorial.ast @factorial
+   frame #5: factorial.ast @<toplevel>
+   mad> stack
+   Runtime stack (most recent call first):
+   frame #0: factorial.ast @factorial
+   frame #1: factorial.ast @factorial
+   frame #2: factorial.ast @factorial
+   frame #3: factorial.ast @factorial
+   frame #4: factorial.ast @factorial
+   frame #5: factorial.ast @<toplevel>
+   mad> down
+   error: no such frame
+   mad> up
+   you are looking at frame #1
+   mad> up
+   you are looking at frame #2
+   mad> trace
+   Runtime stack trace (most recent call first):
+   frame #2: factorial.ast @factorial
+   frame #3: factorial.ast @factorial
+   frame #4: factorial.ast @factorial
+   frame #5: factorial.ast @<toplevel>
+   mad> trace 3
+   Runtime stack trace (most recent call first):
+   frame #2: factorial.ast @factorial
+   frame #3: factorial.ast @factorial
+   frame #4: factorial.ast @factorial
+   mad> trace 0 3
+   Runtime stack trace (most recent call first):
+   frame #0: factorial.ast @factorial
+   frame #1: factorial.ast @factorial
+   frame #2: factorial.ast @factorial
+   mad> stack 0
+   frame #0: factorial.ast @factorial
+   mad> stack 3
+   frame #3: factorial.ast @factorial
+   __AST__TEMP81__: 3
+   n: 3
+   mad> stack 4
+   frame #4: factorial.ast @factorial
+   __AST__TEMP81__: 4
+   n: 4
+   mad> stack *
+   Runtime stack (most recent call first):
+   frame #2: factorial.ast @factorial
+   __AST__TEMP81__: 2
+   n: 2
+   frame #3: factorial.ast @factorial
+   __AST__TEMP81__: 3
+   n: 3
+   frame #4: factorial.ast @factorial
+   __AST__TEMP81__: 4
+   n: 4
+   frame #5: factorial.ast @<toplevel>
+   Error: (struct...)
+   Exception: (struct...)
+   -- ...
+   io: (module...)
+   factorial: (function ...)
+   mad> continue
+   24
+   stopping MAD
+   mad> quit
+   $
\ No newline at end of file
diff --git a/docs/Quickstart Tutorial.rst b/docs/Quickstart Tutorial.rst
index b0e17dbe..c0c4035c 100644
--- a/docs/Quickstart Tutorial.rst
+++ b/docs/Quickstart Tutorial.rst
@@ -1,12 +1,12 @@
 ..
    /******************************************************************
    This is the source file from which the reference guide is
-   generated.  We use cpp to insert live code snippets into the
+   generated.  We use pcpp to insert live code snippets into the
    document. In order to generate the reference guide run the
    following command on a Unix-like system in the directory of
    this doc:
 
-   bash generate_docs
+   python generate_docs.py
 
    ******************************************************************/
 ..
diff --git a/docs/Quickstart Tutorial.txt b/docs/Quickstart Tutorial.txt
index 7a653250..67cda7fa 100644
--- a/docs/Quickstart Tutorial.txt
+++ b/docs/Quickstart Tutorial.txt
@@ -1,12 +1,12 @@
 ..
    /******************************************************************
    This is the source file from which the reference guide is
-   generated.  We use cpp to insert live code snippets into the
+   generated.  We use pcpp to insert live code snippets into the
    document. In order to generate the reference guide run the
    following command on a Unix-like system in the directory of
    this doc:
 
-   bash generate_docs
+   python generate_docs.py
 
    ******************************************************************/
 
diff --git a/docs/Reference Guide.rst b/docs/Reference Guide.rst
index 72a8b2c2..d873442b 100644
--- a/docs/Reference Guide.rst
+++ b/docs/Reference Guide.rst
@@ -938,7 +938,7 @@ list **@length** ()
       Returns the number of elements within the list.
 
 list **@map** f:%function
-      Applies the function f to each element of the list in place. The modified list is returned.
+      Returns a new list formed by applying the function f to each element of the list. The original list is unchanged.
 
 list **@member** item
       Returns true only if item exists on the list.


Test patch:
diff --git a/asteroid/test-suites/action-tests/test068.ast b/asteroid/test-suites/action-tests/test068.ast
index 350cd555..ec59aeaa 100644
--- a/asteroid/test-suites/action-tests/test068.ast
+++ b/asteroid/test-suites/action-tests/test068.ast
@@ -10,7 +10,6 @@ function plot with (dt,avg) do escape
 "
 import pandas as pd
 import matplotlib.pyplot as plt
-plt.style.use('seaborn')
 
 # import parameters from Asteroid into Python
 dt_val = state.symbol_table.lookup_sym('dt')
diff --git a/asteroid/test-suites/python-tests/README.md b/asteroid/test-suites/python-tests/README.md
new file mode 100644
index 00000000..b1df700c
--- /dev/null
+++ b/asteroid/test-suites/python-tests/README.md
@@ -0,0 +1,1 @@
+The tests in this suite are not run when run-tests.py is run. Currently, each file must be run individually.
\ No newline at end of file
diff --git a/asteroid/test-suites/python-tests/repl_test.py b/asteroid/test-suites/python-tests/repl_test.py
new file mode 100644
index 00000000..1b2d942e
--- /dev/null
+++ b/asteroid/test-suites/python-tests/repl_test.py
@@ -0,0 +1,62 @@
+import sys
+import os
+
+#Code from run-tests.py
+file_path = os.path.dirname(os.path.abspath( __file__ ))
+os.chdir(file_path)
+(parent_dir,_) = os.path.split(file_path)
+#We need the granparent dir, since we're 2 layers down from asteroid
+(grandparent_dir,_) = os.path.split(parent_dir)
+sys.path.append(grandparent_dir)
+
+from asteroid.interp import interp
+import asteroid.lex as lex
+from asteroid.state import state
+
+def test_get_identifiers():
+    print(lex.get_indentifiers())
+    interp("load io.", "test")
+    print(lex.get_indentifiers())
+    interp("let aaa = 1.", "test")
+    interp("let aab = 2.", "test", initialize_state=False, prologue=False)
+    interp("let abc = 3.", "test", initialize_state=False, prologue=False)
+    interp("let bc = 4.", "test", initialize_state=False, prologue=False)
+    interp("let baa = 5.", "test", initialize_state=False, prologue=False)
+    print(lex.get_indentifiers())
+    
+def test_get_identifiers_by_prefix():
+    print(lex.get_indentifiers())
+    interp("load io.", "test")
+    print(lex.get_indentifiers())
+    interp("let aaa = 1.", "test")
+    interp("let aab = 2.", "test", initialize_state=False, prologue=False)
+    interp("let abc = 3.", "test", initialize_state=False, prologue=False)
+    interp("let bc = 4.", "test", initialize_state=False, prologue=False)
+    interp("let baa = 5.", "test", initialize_state=False, prologue=False)
+    print(lex.get_indentifiers())
+    print(lex.get_indentifiers_by_prefix("a"))
+    print(lex.get_indentifiers_by_prefix("aa"))
+    print(lex.get_indentifiers_by_prefix("__"))
+    
+def test_get_member_identifiers():
+    interp("load io.")
+    interp('structure A with\n\tdata a.\n\tdata b.\nend.', initialize_state=False, prologue=False)
+    interp('let foo = A(1,2).', initialize_state=False, prologue=False)
+    print(lex.get_member_identifiers("io"))
+    print(lex.get_member_identifiers("A"))
+    print(lex.get_member_identifiers("foo"))
+    return None
+
+if __name__ == "__main__":
+    test_get_member_identifiers()
+    print("Testing get_identifiers:\n")
+    test_get_identifiers()
+    print("\n\n\n")
+    print("Testing get_identifiers_by_prefix:\n")
+    test_get_identifiers_by_prefix()
+    print("\n\n\n")
+    print("Testing get_member_identifiers:\n")
+    test_get_member_identifiers()
+    print("\n\nTesting complete.")
+    
+    
\ No newline at end of file