Commits

catseye  committed 1febd24

Check that line numbers in source are strictly ascending.

  • Participants
  • Parent commits 10e48e7

Comments (0)

Files changed (2)

File README.markdown

 yucca
 =====
 
-_Version 1.0, June 10 2012_
+_Version 1.1-pre_
 
 IT SO HAPPENS in the course of human events that one may find oneself 
 cross-developing an 8-bit BASIC program from a modern development 
 Python's `fileinput` module is used, so the BASIC source can also be piped
 into `yucca`, and so forth.
 
+By default, `yucca` checks that each line number in the program source is
+given in strictly ascending order.  Some tokenizers (e.g. `petcat`) will
+happily tokenize a program source into a tokenized program with
+out-of-sequence and/or duplicate line numbers, and this option prevents
+that from happening.  To suppress this check, give the `-A` option on the
+command line.
+
 By default, `yucca` checks that the target of each jump in the program is an
 existing line number.  This includes any jumps that may occur in immediate
 mode commands (i.e. commands with no line number) given in the text
 #!/usr/bin/env python
 
 # yucca -- dialect-agnostic static analyzer for 8-bit BASIC programs
-# Version 1.0, June 10 2012
+# Version 1.1-pre
 
 # Copyright (c)2012 Chris Pressey, Cat's Eye Technologies
 #
             str(self.line_number).strip(), self.line.description)
 
 
+class OutOfSequence(Error):
+    def __str__(self):
+        return '?OUT OF SEQUENCE LINE "%s" IN: %s' % (
+            str(self.line_number).strip(), self.line.description)
+
+
 class LineNumber(object):
     """An object representing the line number of a line in a
     BASIC program.
     >>> for e in b.check_line_numbers(): print e
     ?UNDEFINED STATEMENT "30" IN: 20 GOTO 30
 
+    Proper (sequential) ordering of line numbers can be checked for.
+
+    >>> b = BasicProgram('10 PRINT "HI":REM HELLO\n'
+    ...                  'PRINT "IMMEDIATE MODE"\n'
+    ...                  '20 GOTO 30\n'
+    ...                  '30 PRINT "HI"\n')
+    >>> b.check_ascending()
+    []
+    >>> b = BasicProgram('10 PRINT\n'
+    ...                  '20 PRINT\n'
+    ...                  '20 GOTO 30\n'
+    ...                  '30 PRINT\n'
+    ...                  '40 GOTO 30\n'
+    ...                  '60 GOTO 30\n'
+    ...                  '50 GOTO 30\n')
+    >>> for e in b.check_ascending(): print e
+    ?OUT OF SEQUENCE LINE "20" IN: 20 GOTO 30
+    ?OUT OF SEQUENCE LINE "50" IN: 50 GOTO 30
+
     Computed GOTOs/GOSUBs can be detected.  Note that a line number
     computation cannot appear after the THEN in an IF...THEN because
     it cannot readily be distinguished from a command.
     def add_line(self, line, text_file_line):
         self.lines.append(BasicLine(line, text_file_line))
 
+    def check_ascending(self):
+        errors = []
+        last_line_number = None
+        text_file_line = 1
+        for line in self.lines:
+            if line.line_number is not None and last_line_number is not None:
+                if line.line_number.number <= last_line_number.number:
+                    errors.append(OutOfSequence(line, line.line_number))
+            last_line_number = line.line_number
+        return errors
+
     def check_line_numbers(self):
         referenced = {}
         defined = {}
 
 if __name__ == '__main__':
     parser = OptionParser()
+    parser.add_option("-A", "--no-check-ascending",
+                      dest="check_ascending",
+                      default=True,
+                      action="store_false",
+                      help="do not check that line numbers are given in "
+                           "strictly ascending order")
     parser.add_option("-C", "--allow-computed-jumps",
                       dest="allow_computed_jumps",
                       default=False,
         p.strip_remarks(program_lines_only=options.program_lines_only)
 
     errors = []
+    if options.check_ascending:
+        errors += p.check_ascending()
     if not options.allow_computed_jumps:
         errors += p.check_computed_jumps()
     if options.check_line_numbers: