Commits

Martin Haye committed 7936cf3

Making progress on the filter, but a long way to go yet.

Comments (0)

Files changed (2)

+#!/usr/bin/env python
+
+import re, sys
+
+stringTbl = list()
+remTbl = list()
+referencedLines = set()
+firstRefdLine = None
+
+class TransformError(Exception):
+  def __init__(self, message):
+    Exception.__init__(self, message)
+
+def internStrings(line):
+  def doOne(match):
+    stringTbl.append(match.group(0))
+    return '"#%d"' % (len(stringTbl)-1)
+  return re.sub('"[^"]*"', doOne, line)
+  
+def uninternStrings(line):
+  return re.sub('"#(\d+)"', lambda m: stringTbl[int(m.group(1))], line)
+
+def internRems(line):
+  m = re.match("(.*?)(REM.*)$", line, re.IGNORECASE)
+  if not m: return line
+  remTbl.append(m.group(2))
+  return "%sREM#%d" % (m.group(1), len(remTbl)-1)
+
+def uninternRems(line):
+  return re.sub('REM#(\d+)', lambda m: remTbl[int(m.group(1))], line)
+
+##############################################################################
+def removeUnrefdNumber(line):
+  """ Get rid of line numbers that aren't referenced, but retain the indent
+      level of the line. """
+  
+  # It's impossible for us to really know what to do with tabs, so barf on them.
+  if "\t" in line:
+    abortTransform("This script cannot use tabs. De-Tabify your file and turn on the 'Auto-Expand Tabs' option.")
+  
+  # See if there's a line number
+  m = re.match("^(\s*)(\d+\s*)(.*)$", line)
+  if not m: return line
+  lineNum = int(m.group(2))
+  
+  # Account for Slammer at the beginning and any other unusual stuff. 
+  if lineNum < firstRefdLine: return line
+  
+  # Retain lines that are referenced
+  if lineNum in referencedLines: return line
+
+  # Okay, strip the line number but retain the indent level.
+  textIndent = len(m.group(1))
+  return "%-*s%s" % (textIndent, "", m.group(3))
+  
+
+##############################################################################
+def transformForEditing(lines):
+  """ Perform transformations to make the BASIC program easy to edit. This
+      makes it un-pasteable however, and it needs to be transformed back
+      before pasting. """
+      
+  global firstRefdLine
+  
+  # Identify every line number that is referenced
+  for line in lines:
+    for inst in re.finditer("(THEN|GOTO|GOSUB)(\s*\d+(,\s*\d+)*)", line, re.IGNORECASE):
+      for lineNum in re.findall("\d+", inst.group(0)):
+        lineNum = int(lineNum)
+        referencedLines.add(lineNum)
+        if not firstRefdLine or lineNum < firstRefdLine:
+          firstRefdLine = lineNum
+  
+  # Remove line numbers that aren't referenced
+  lines = [removeUnrefdNumber(line) for line in lines]
+  
+  # Add a line so we can tell the transformation needs to be reversed.
+  if re.match("^\s*new\s*$", lines[0], re.IGNORECASE):
+    lines.pop(0)
+  lines.insert(0, "*** Transformed for editing ***")
+  return lines
+
+
+##############################################################################
+def abortTransform(message):
+  """ Raises an exception that aborts the transformation. """
+  
+  raise TransformError(message)
+  
+      
+##############################################################################
+def flushBlock(block, startLine, endLine, out):
+  """ Auto-number a block of lines with the given start and end constraints. """
+  
+  nonBlanks = len([l for l in block if l[2].strip()])
+  if nonBlanks == 0:
+    out.extend([bl[2] for bl in block])
+    return
+    
+  s = startLine if startLine else 0
+  e = endLine if endLine else s + 10*nonBlanks
+    
+  incr = (e - s) / nonBlanks
+  if incr >= 10:
+    incr = 10
+  elif incr >= 5:
+    incr = 5
+  elif incr >= 2:
+    incr = 2
+  elif incr >= 1:
+    incr = 1
+  else:
+    abortTransform("Cannot auto-number block starting at line %d" % startLine)
+    
+  i = 0
+  for (indent1, indent2, text) in block:
+    if text.strip() == "":
+      out.append("")
+    else:
+      lineNum = s + i*incr
+      out.append("%*s%-*d%s" % (indent1, "", indent2, lineNum, text))
+      i += 1
+    
+      
+##############################################################################
+def transformForApple(lines):
+  """ Un-do the editing transformations, so the program can be pasted into
+      an Apple II emulator. """
+      
+  out = ["NEW"] # replaces "Transformed for editing" line
+  
+  # Gather blocks of lines that need to be numbered
+  startLine = 0
+  block = []
+  for line in lines[1:]:
+    m = re.match("^(\s*)(\d+\s*)(.*)$", line)
+    if m:
+      lineNum = int(m.group(2))
+      flushBlock(block, startLine, lineNum, out)
+      block = [(len(m.group(1)), len(m.group(2)), m.group(3))]
+      startLine = lineNum
+    else:
+      m = re.match("^(\s*)(.*)$", line)
+      block.append((0, len(m.group(1)), m.group(2)))
+  flushBlock(block, startLine, None, out)
+  return out
+      
+      
+##############################################################################
+def main():
+
+  # Read in the file.
+  origLines = [line.rstrip() for line in open(sys.argv[1], "r")]
+  
+  # To keep from mis-interpreting what we see, intern REMs and strings.
+  lines = [internStrings(internRems(line.strip())) for line in origLines]
+  
+  # Transform either for editing or for Apple
+  try:
+    if "Transformed for editing" in lines[0]:
+      lines = transformForApple(origLines)
+    else:
+      lines = transformForEditing(origLines)
+      
+    # All done.
+    for line in lines:
+      print uninternRems(uninternStrings(line))
+      
+  except TransformError, e:
+    print "*** Transformation error: " + str(e) + "***"
+    for line in origLines:
+      print line
+      
+
+##############################################################################
+main()
 270 IF SCRN(CX,CY + 1) = 0 THEN CY = CY + 1
 272 IF SCRN(CX,CY + 1) = 0 THEN CY = CY + 1
 275 RETURN
-275 RETURN
 300 REM ROW CLEARED
 310 RC = RC + 1: IF RC < 3 THEN RETURN
 320 RC = 0:HB = HB + 1
 1010 REM 001
 1011 GOTO 1440
 1020 REM 002
-1020 REM 002
 1021 GOTO 1325
 1030 REM 003
 1031 ON RND(1) * 3 + 1 GOTO 1250,1375,1400
 1380 REM -X-
 1385 REM XX-
 1390 CI = 2
-1390 CI = 2
 1395 BL = 1:B0 = 3:BR = 0: RETURN
 1400 REM XX-
 1405 REM XX-
 2060 IF A$ = "N" THEN 20
 2070 IF A$ = "Q" THEN TEXT : HOME : PRINT "BYE NOW.": PRINT : END
 2080 GOTO 2040
-2080 GOTO 2040
 3000 REM FIRST TIME INIT
 3010 DIM H(22)
 3020 CC(1) = 1:CC(2) = 2:CC(3) = 3:CC(4) = 11:CC(5) = 9:CC(6) = 13:CC(7) = 12
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.