Commits

Anonymous committed 6ef7d2c

Finally have two-way transformation working.

Comments (0)

Files changed (1)

 
 import re, sys
 
+allLines = set()
 referencedLines = set()
 firstRefdLine = None
 lineSubMap = {}
 class BasicLine:
   """ Holds information about one line of the BASIC program. """
   
-  def __init__(self, lineCount, origText):
-    (self.lineCount, self.origText) = (lineCount, origText)
+  def __init__(self, lineCount, origText, prevIsCall):
+    (self.lineCount, self.origText, self.prevIsCall) = (lineCount, origText, prevIsCall)
     
     # Figure out the indent level, line number, and text of the statements
-    m = re.match("^(\s*)(\d*)(.*)$", origText)
+    m = re.match("^(\s*)(\+?\d*)(.*)$", origText)
     self.indent = m.group(1)
-    self.lineNum = int(m.group(2)) if m.group(2) else ""
+    if m.group(2):
+      if m.group(2).startswith("+"):
+        self.lineNum = m.group(2)
+      else:
+        self.lineNum = int(m.group(2));
+    else:
+      self.lineNum = ""
     stmtsText = m.group(3)
     self.pieces = []
     
         
     # Finish up.
     self.pieces.append(stmtsText[s:i])
-   
+  
+  def isCall(self):
+    return [p for p in self.pieces if p.upper().startswith("CALL")]
+  
+  def isRem(self):
+    return [p for p in self.pieces if p.upper().startswith("REM")]
+  
   def isNoOp(self):
-    for piece in self.pieces:
-        piece = piece.strip()
-        if piece != "" and not piece.upper().startswith("REM"):
-            return False
+    if [p for p in self.pieces if p.strip() != "" and not re.match("REM|NEW", p, re.I)]:
+      return False
+    # This is a blank line or REM statement. Preserve numbers for REMs right
+    # after a call in case they're Lammer data.
+    if self.prevIsCall and [p for p in self.pieces if p.strip() != ""]:
+      return False
     return True
-  
+    
   def __str__(self):
     return self.indent + str(self.lineNum) + "".join(self.pieces)
 
         if not firstRefdLine or lineNum < firstRefdLine:
           firstRefdLine = lineNum
           
-  # Determine where the subroutine boundaries are, and assign each line
-  # to a sub when possible
-  curSub = None
-  for line in lines:
-    found = False
-    for piece in line.pieces:
-      if re.match("REM\W+SUB", piece, re.IGNORECASE):
-        found = True
-      elif len(piece.strip()) > 0:
-        break
-    if found:
-      curSub = line.lineNum
-    elif curSub >= 0 and line.lineNum and line.lineNum > curSub:
-      lineSubMap[line.lineNum] = curSub
-  
   # Replace line number references within a sub
   for line in lines:
     newPieces = []
     else:
       line.lineNum = calcLineMark(None, line.lineNum)
       
-  # Add a line so we can tell the transformation needs to be reversed.
-  if lines[0].origText.strip().upper() == "NEW":
-    lines.pop(0)
-  lines.insert(0, BasicLine(-1, "*** Transformed for editing ***"))
   return lines
 
 
 ##############################################################################
+def undoLineMark(lineNum, mark):
+  """ Translate a "+123" mark to a real line number in the current sub """
+
+  assert lineNum != ""  
+  subsBefore = [int(ln) for ln in sorted(lineSubMap.values()) if int(ln) <= int(lineNum)]
+  if not subsBefore:
+    abortTransform("Cannot find subroutine for '%s' at line %d" % (mark, lineNum))
+  target = subsBefore[-1] + int(mark[1:])
+  if not target in allLines:
+    abortTransform("Unknown target %d for '%s' at line %d" % (target, mark, lineNum))
+  return str(target)
+  
+  
+##############################################################################
 def flushBlock(block, startLine, endLine, out):
   """ Auto-number a block of lines with the given start and end constraints. """
   
-  numLinesNeeded = len([l for l in block if not l.isNoOp()])
+  numLinesNeeded = len([l for l in block if l.lineNum or not l.isNoOp()])
   if numLinesNeeded == 0:
     out.extend(block)
     return
     
   s = startLine if startLine else 0
   e = endLine if endLine else s + 10*numLinesNeeded
-    
+  
   incr = (e - s) / numLinesNeeded
   if incr >= 10:
     incr = 10
   elif incr >= 1:
     incr = 1
   else:
-    abortTransform("Cannot auto-number block starting at line %d" % startLine)
+    abortTransform("Cannot auto-number block %d-%d" % (s, e))
+    
+  #print "flush(s=%d, e=%d, i=%d)" % (s, e, incr)
     
   cur = s
   for line in block:
-    if not line.isNoOp():
-      line.lineNum = str(cur) + " "
+    #print "  Number line '%s': %d" % (str(line.lineNum) + " " + "".join(line.pieces), cur)
+    if line.lineNum:
+      cur = line.lineNum + incr
+    elif not line.isNoOp():
+      
+      assert cur != endLine
+      assert cur not in allLines
+      allLines.add(cur)
+      line.lineNum = cur
       cur += incr
+      
+    # Translate "+123" marks to real line numbers
+    newPieces = []
+    for piece in line.pieces:
+      if not re.match("REM", piece, re.I):      
+        m = re.match("(.*)(THEN|GOTO|GOSUB)(.*)", piece, re.IGNORECASE)
+        if m:
+          piece = m.group(1) + m.group(2) + \
+                  re.sub("\+\d+", lambda m: undoLineMark(line.lineNum, m.group(0)), m.group(3))
+      newPieces.append(piece)
+      
+    # If necessary, add a space between line num and statement(s)
+    if newPieces[0].strip() != "":
+      newPieces.insert(0, " ");
+    
+    # Moving on
+    line.pieces = newPieces      
+      
     out.append(line)
     
       
   startLine = 0
   block = []
   for line in lines:
-    if "Transformed for editing" in line.origText:
-      continue
-    elif line.lineNum != "":
+    if line.lineNum != "":
       flushBlock(block, startLine, line.lineNum, out)
       block = [line]
       startLine = line.lineNum
   
   # Parse each line
   lines = []
+  prevIsCall = False
+  nNumbered = 0
   for i in range(len(origLines)):
-    lines.append(BasicLine(i, origLines[i]))
+    nl = BasicLine(i, origLines[i], prevIsCall)
+    lines.append(nl)
+    if nl.lineNum != "":
+      nNumbered += 1
+    prevIsCall = nl.isCall() or (prevIsCall and nl.isRem())
+  
+  # Determine where the subroutine boundaries are, and assign each line
+  # to a sub when possible
+  curSub = None
+  for line in lines:
+    found = False
+    for piece in line.pieces:
+      if re.match("REM\W+SUB", piece, re.IGNORECASE):
+        found = True
+      elif len(piece.strip()) > 0:
+        break
+    if found and line.lineNum != "":
+      curSub = line.lineNum
+    elif curSub >= 0 and line.lineNum:
+      if str(line.lineNum).startswith("+"):
+        line.lineNum = curSub + int(line.lineNum[1:])
+      if line.lineNum > curSub:
+        lineSubMap[line.lineNum] = curSub
+        
+  # Figure out the set of all line numbers
+  global allLines
+  allLines = set([ln.lineNum for ln in lines]) 
   
   # Transform either for editing or for Apple
   try:
-    if [ln for ln in lines if "Transformed for editing" in ln.origText]:
+    if nNumbered < len(lines)/2:
       lines = transformForApple(lines)
     else:
       lines = transformForEditing(lines)