text: """pipText text driver for a Pip CPU simulation.
  See actions list at the end of the file.
  
  This is an alternative to the graphical interface of pipGUI.py.
  
  The main loop reads input, notes cmd name and remembers remaining tokens,
  and then finds the entry in the actions list corresponding to the cmd and calls
  the associated function.  The function then generally accesses the tokens
  for details of what to do.
  
  Note that the doc string for each such function is printed out if requested
  in the help command.
  
  The functions associated with actions are shown before main().
  The remaining functions are shown after main.
  The actions list that refers to the cmd methods is defined last.
  """
  
  import sys, time
  from graphics import *
  from pip import Pip
  #import pip
  
  showAll = True  # if true print whole cpu state instead of log entry
  cpu = None      # Pip acted on
  fileName = None # last filename used
  
  def textMakeErr(err):
      if err:
          print(err)
          return True
      return False
  
  makeErr = textMakeErr
  
  def setMakeErr(errPrinter):
      global makeErr
      makeErr = errPrinter
  
  def errOrShow(err):
      isErr = makeErr(err) 
      if not isErr:
          show()
      return isErr
  
  def step(tokens=None):
      """Single step the CPU and show the result.
  -step     -- an empty line is sufficient.
  """
  
      return errOrShow(cpu.step())
  
  def runCPU(tokens=None):
      """Run multiple steps of the simulator with a possible delay.  Versions:
  r-un
  r-un <steps total (100 max)>
  r-un <steps> <seconds delay between steps>
  
  The number of steps is reduced if it is over 100 or the delays will add up to
  more than 25 seconds.
  """
      steps = 100
      secDelay = 0
      if tokens:
          if not tokens[0]:
              tokens[0] = steps
          if len(tokens) > 1:
              secDelay = abs(float(tokens[1]))
          if steps * secDelay > 25:
              steps = min(25.0/secDelay, steps)
          steps = int(max(1, min(int(tokens[0]), steps)))
      runText(cpu, steps, secDelay, not showAll)
  
  def help(tokens=None):
      """Prints this help message or help on a particular command.
  h-elp
  """
      if tokens:
          if match(tokens[0], "q-uit"):
              print("Quit the simulation.")
          else:
              for (s, f) in actions:
                  if match(tokens[0], s):
                      print(f.__doc__)
                      break
              else:
                  print("No command matched.  Enter h for a list of commands.")
  
      else:
          print("The possible commands are below,\n"+\
                "where the part before any '-' is required,\n"+\
                "omit any '-' itself, and\n"+\
                "include as much of the part after any '-' as you like.\n"+\
                "For more information on a legal command, enter help cmd,\n"+\
                "for instance 'help run'.   Commands:\n")
          for i, (s, f) in enumerate(actions):
              print(s,"", end=' ')
              if i % 6 == 5:
                  print()
          print()
  
  def setShowAll(doShowCPU):
      global showAll
      showAll = doShowCPU
      
  def viewEntry(tokens=None):  # particularly for text version
      """See the latest log entry, and switch to showing brief log entries
  after changes rather than the whole CPU state.
  en-tryInLog
  """
      setShowAll(False)
      print(cpu.logEntry("", True, False))
      return False
  
  def viewCPU(tokens=None):  # assumed for GUI
      """See the whole CPU state, and switch to showing this whole state
  after changes rather than a brief log entry.
  c-pu
  """
      setShowAll(True)
      show()
  
  def viewLog(tokens=None):
      """View the entire history retained in the log.
  hi-storyInLog
  Use wipe to clear this and start over.
  
      """
      print(cpu.getLogLines(0))
  
  def save(tokens=None, showNumLabels = False):
      """Save the CPU state to a file.  Options:
  sa-ve             -- save to the last used filename
  sa-ve fileName    -- save the specified file
  
  File format given by explicit extension:
     .dat file like those generated by the Pippin applet,
     .bin file text of bytes in binary
     .asm assember - using current label display format
  If a name is given with out one of these extensions, an extension is added:
     .dat if the labels match the display in the Pippin applet, otherwise
     .bin if the current display is binary, or otherwise
     .asm assember - using current label display format
  """
      
      global fileName
      file = fileName
      if tokens:
          file = tokens[0]
      if not file:
          return makeErr("A filename or filebase must be given.")
      try:
          fileName = cpu.saveFile(file, True, showNumLabels)
          print("Saved to " + fileName)  #?GUI
      except Exception as e:
          return makeErr(str(e))
      return False
  
  def load(tokens=None):
      """Load the CPU state from a file.  Options:
  lo-ad             -- load the previous file if there was one, or an empty CPU
  lo-ad fileName    -- load the specified file if it is legal.
  
  Legal file are
     .dat file generated by the Pippin applet,
     files of binary bytes (groups of up to eight 0's and 1's, separated by white)
     assembler files with or without symbolic instruction labels or data labels
  """
      
      global fileName
      file = fileName
      if tokens:
          file = tokens[0]
      if not file:
          return makeErr("A filename or filebase must be given.")
      try:
          cpu.loadFile(file)
          fileName = file
      except Exception as e:
           return makeErr(str(e))           
      show()
      return False
      
  
  def ip(tokens=None):
      """
  i-pSet <val>  -- choose a value for the IP 
  
  """
     
      if not tokens:
          return makeErr("New value required.")
      return errOrShow(cpu.ipEdit(tokens[0]))
  
  def accum(tokens=None):
      """
  ac-cum <val>  -- choose a value for the accumulator 
  
  """
      
      if not tokens:
          return makeErr("New value required.")
      return errOrShow(cpu.accEdit(tokens[0]))
  
  def wipe(tokens=None):
      """
  w-ipeLog  -- clear the history log
  """
      cpu.clearLog()
      return False
  
  def whichLabels(tokens=None):
      """Specify which labels are displayed in assembler listings:
  l-abels                 -- show all symbolic labels available
  l-abels n-one           -- show no symbolic labels -- all numeric
  l-abels c-ode           -- show symbolic code labels, numeric data labels
  l-abels d-ata           -- show numeric code labels, symbolic data labels,
                             like in the applet.
  
  The notation with the '-' is as in the help message -- indicating only the
  first letter of none, code or data are needed.
  """
  
      if not tokens or matchToken(tokens, "a-ll"):
          code = True
          data = True
      elif matchToken(tokens, "n-one"):
          code = False
          data = False
      elif matchToken(tokens, "c-ode"):
          code = True
          data = False
      elif matchToken(tokens, "d-ata"):
          code = False
          data = True
      else:
          return makeErr("Unrecognized label option: {}".format(tokens[0]))
      setLabels(data, code)
      return False
  
  def maybeShow(oldHeading):
      """Called on formatting change, may trigger display."""
      if showAll:
          show()
      else:
          heading = cpu.logHeading(False)
          if oldHeading != heading: 
              print(cpu.logEntry(heading, False, False))
  
  def setLabels(data, code):
      oldHeading = cpu.logHeading(False)
      cpu.useAlphaDataLabels(data) 
      cpu.useAlphaCodeLabels(code)
      cpu.useBinary(False)
      maybeShow(oldHeading)
  
  def binary(tokens=None):
      """Show everything in binary.  Reverse with a-sm.
  b-inary
  """
      oldHeading = cpu.logHeading(False)
      cpu.useBinary(True)
      maybeShow(oldHeading)
      return False
  
  def assembler(tokens=None):
      """Show everything in assember. Switch to binary with b-inary.
  a-sm
  """
      cpu.useBinary(False)
      show()  # only used in text version
      return False
  
  def editMem(tokens=None):
      """Edit Pip instrution or data
      e-dit <numerical or symbolic code or data address label> <newVal>
      """
      #  For GUI version see Pip's codeEdit and dataEdit
      if not len(tokens) > 1:
          return makeErr("Requires both a memory address and new value.")
      return errOrShow(cpu.memEdit(tokens[0], " ".join(tokens[1:])))
  
  
  def init(tokens=None):
      """
  in-it -- initialize the CPU for a program restart
  """
      cpu.initCPU()
      show()
  
  
  
  def cd(tokens=None):
      """Not inplemented.
  """
      print("Not inplemented")
  
  def ls(tokens=None):
      """Not inplemented.
  """
      print("Not inplemented")
  
  def createCPU(args):
      global cpu
      if args:
          if len(args) > 1:
              print("Too many arguments -- ignoring them!")
              cpu = Pip()
          else:
           cpu = Pip(args[0])
      else:
          cpu = Pip()
      return cpu    
      
  def mainText(args = None):
      global cpu
      cpu = createCPU(args)
      
      print("Type h for help.")
      cmd, tokens = getInput()  
      while not match(cmd, "q-uit"):
          for (s, f) in actions:
              if match(cmd, s):
                  f(tokens)
                  break
          else:
              print("No command matched.  Enter h for help.")
          cmd, tokens = getInput()
  
      
  def match(cmd, s):
      """ Check if cmd has the required parts of s and any of the optional part.
      """
      parts = s.split('-')
      start = parts[0]
      if len(parts) > 1:
          end = parts[1]
      else:
          end = ""
      return cmd.startswith(start) and (start+end).startswith(cmd)
  
  def matchToken(tokens, s):
      """ helper to parse match in whichLabels."""
      for token in tokens:
          if match(token, s):
              return True
      return False
          
  def getInput():
      """ return cmd, list of remaining tokens."""
      tokens = input("?: ").split() # tokens object never changes
      if not tokens:
          return "", []
      return tokens[0].lower(), tokens[1:]
  
  lastShownLine = None
  def show(logLines = 1):
      """This is generally called after each command that changes the state.
      """
      global lastShownLine
      if showAll:
          showStateText(cpu)
      else:
          line = cpu.getLogLines(logLines)
          if line != lastShownLine:
              print(line)
              lastShownLine = line
  
  def showStateText(cpu):
      """
      Show all instructions through halt, with
      * after last instruction executed,
      > after next instruction executed
      show data range from first to last referenced in code
      = after last if written (or omit if none written last)
      > after next data to be referenced (or omit if none to be referenced)
      """
      print()        
      print("======CODE======")
      for i in range(0, cpu.codeLim, 2):
          marker = ""
          if i == cpu.ip:
              marker +=" >"
          if i == cpu.lastIP:
              marker +=" *"
          print("{:<18}{}".format(cpu.lineStr(i), marker))
      if cpu.dataLims:
          dataAddr = cpu.getDataRef(cpu.ip)
          if cpu.wroteLast():
              writtenDataAddr = cpu.getDataRef(cpu.lastIP)
          else:
              writtenDataAddr = None
          print("------DATA------")
          for i in range(*cpu.dataLims):
              marker = ""
              if i == writtenDataAddr:
                  marker =" ="
              if i == dataAddr:
                  marker +=" >"
              print("{:8}: {:<8}{}"
                    .format(cpu.labelStr(i), cpu.dataStr(i), marker))
      print("------CPU-------")
      print("      IP: " + cpu.ipStr())        
      print("     Acc: " + cpu.accStr()) 
  
  def runText(cpu, steps = 100, secDelay = 0, seeLog = False, 
              seeLogHeading = False):
      """Run specified number of steps or until halted.
      If seeLog is true
         see the log lines appear, but no whole Pip displays
      Otherwise
         If secDelay is 0, see no Pip display only at the end
         If secDelay > 0 see Pip display after each step.
      """
      
      if cpu.halted:
        return makeErr('Halted -- reset the IP to continue.')
      if seeLogHeading:
          print(cpu.logHeading(False))
      for n in range(steps):
          err = cpu.step()
          if makeErr(err):
              return
          if seeLog:
              print(cpu.getLogLines())
          if secDelay or n == steps - 1 or cpu.halted:
              if not seeLog:
                  showStateText(cpu)
              if n < steps - 1 and not cpu.halted:
                  time.sleep(secDelay)
          if cpu.halted:
              return
  
  
  actions = [("-stepOnce", step),   #single step
             ("h-elp", help),   #view format
             ("en-tryInLog", viewEntry),   #view format
             ("c-puView", viewCPU),   #view format
             ("hi-storyOfLog", viewLog),   #view format
             ("sa-veFile", save),   #view format
             ("lo-adFile", load),   # file=fileName - load file
             ("i-pSet", ip),   #view format
             ("ac-cumSet", accum),   # # - set accum
             ("r-un", runCPU),   #
             ("l-abelsShown", whichLabels),   #
             ("b-inaryShown", binary),   #
             ("a-smShown", assembler),   #
             ("e-ditMemory", editMem),   #
             ("in-itializeCPU", init),   #
             ("w-ipeLog", wipe),# - wipe log
             ("cd", cd),   # cd not implemented
             ("ls", ls) ]   # ls not implemented
  
  
      
  if __name__ == "__main__":
      mainText(sys.argv[1:])
  