listzx81.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. #!/usr/bin/env python
  2. # -*- coding: UTF-8 -*-
  3. # List a ZX81 program.
  4. # Written by Pedro Gimeno Fortea. Donated to the public domain.
  5. import sys
  6. chars = [
  7. ' ',
  8. '▘', # Top Left
  9. '▝', # Top Right
  10. '▀', # Top
  11. '▖', # Bottom Left
  12. '▌', # Left
  13. '▞', # Top Right & Bottom Left
  14. '▛', # Inverse Bottom Right
  15. '▒', # Checkerboard 50% grey
  16. '🮎', # Half Checkerboard Up
  17. '🮏', # Half Checkerboard Down
  18. '"', '£', '$', ':', '?', '(', ')', '>', '<', '=', '+', '-', '*', '/', ';',
  19. ',', '.',
  20. '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
  21. 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
  22. 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
  23. 'RND', 'INKEY$', 'PI',
  24. '{x43}', '{x44}', '{x45}', '{x46}', '{x47}', '{x48}', '{x49}', '{x4A}',
  25. '{x4B}', '{x4C}', '{x4D}', '{x4E}', '{x4F}', '{x50}', '{x51}', '{x52}',
  26. '{x53}', '{x54}', '{x55}', '{x56}', '{x57}', '{x58}', '{x59}', '{x5A}',
  27. '{x5B}', '{x5C}', '{x5D}', '{x5E}', '{x5F}', '{x60}', '{x61}', '{x62}',
  28. '{x63}', '{x64}', '{x65}', '{x66}', '{x67}', '{x68}', '{x69}', '{x6A}',
  29. '{x6B}', '{x6C}', '{x6D}', '{x6E}', '{x6F}',
  30. '⇧', '⇩', '⇦', '⇨', '{GRAPH}', '{EDIT}',
  31. '↲',
  32. '⌫', '{K/L MODE}', '{FUNCTION}',
  33. '{x7A}', '{x7B}', '{x7C}', '{x7D}', '{#}', '{CURSOR}',
  34. '█', '▟', '▙', '▄', '▜', '▐', '▚', '▗', '🮐','🮒','🮑',
  35. '{inv"}', '{inv£}', '{inv$}', '{inv:}', '{inv?}', '{inv(}', '{inv)}',
  36. '{inv>}', '{inv<}', '{inv=}', '{inv+}', '{inv-}', '{inv*}', '{inv/}',
  37. '{inv;}', '{inv,}', '{inv.}', '{inv0}', '{inv1}', '{inv2}', '{inv3}',
  38. '{inv4}', '{inv5}', '{inv6}', '{inv7}', '{inv8}', '{inv9}', '{invA}',
  39. '{invB}', '{invC}', '{invD}', '{invE}', '{invF}', '{invG}', '{invH}',
  40. '{invI}', '{invJ}', '{invK}', '{invL}', '{invM}', '{invN}', '{invO}',
  41. '{invP}', '{invQ}', '{invR}', '{invS}', '{invT}', '{invU}', '{invV}',
  42. '{invW}', '{invX}', '{invY}', '{invZ}',
  43. '""', 'AT', 'TAB', '{xC3}', 'CODE', 'VAL', 'LEN', 'SIN', 'COS', 'TAN',
  44. 'ASN', 'ACS', 'ATN', 'LN', 'EXP', 'INT', 'SQR', 'SGN', 'ABS', 'PEEK',
  45. 'USR', 'STR$', 'CHR$', 'NOT', '**', 'OR', 'AND', '<=', '>=', '<>', 'THEN',
  46. 'TO', 'STEP', 'LPRINT', 'LLIST', 'STOP', 'SLOW', 'FAST', 'NEW', 'SCROLL',
  47. 'CONT', 'DIM', 'REM', 'FOR', 'GOTO', 'GOSUB', 'INPUT', 'LOAD', 'LIST',
  48. 'LET', 'PAUSE', 'NEXT', 'POKE', 'PRINT', 'PLOT', 'RUN', 'SAVE', 'RAND',
  49. 'IF', 'CLS', 'UNPLOT', 'CLEAR', 'RETURN', 'COPY'
  50. ]
  51. # Without bytearray, this is needed:
  52. #if sys.hexversion < 0x03000000:
  53. # print("NOTE: Python 3 required")
  54. # Probably because Python3 treats bytes[i] as int, while Python2 treats
  55. # it as bytes, while both treat bytearray[i] as int.
  56. if len(sys.argv) < 2:
  57. print("Usage: %s <filename> [hilite]" % sys.argv[0])
  58. sys.exit(0)
  59. f = open(sys.argv[1], 'rb')
  60. outf = sys.stdout
  61. hilite = False
  62. inv = ''
  63. bold = ''
  64. norm = ''
  65. if len(sys.argv) > 2:
  66. # Bold tokens, reverse video for inverse chars.
  67. # Requires terminal supporting ISO-6429 subset.
  68. # See console_codes(4)
  69. hilite = True
  70. inv = '\x1B[7m'
  71. bold = '\x1B[1m'
  72. norm = '\x1B[0m'
  73. for i in range(0x40):
  74. chars[i+0x80] = inv + chars[i] + norm
  75. try:
  76. # Both work in Python 3. Only the second lets the rest of the
  77. # program work in Python 2.
  78. # buf = f.read(65556)
  79. buf = bytearray(f.read(65556))
  80. lineptr = 0x74
  81. LeadingSpc = False
  82. while lineptr < len(buf):
  83. if buf[lineptr] >= 64:
  84. break
  85. linenum = buf[lineptr]*256 + buf[lineptr+1]
  86. linelen = buf[lineptr+2] + 256*buf[lineptr+3]
  87. lineptr += 4
  88. if linenum > 9999:
  89. outf.write(chars[linenum//1000+28] + str(linenum)[-3:] + ' ')
  90. else:
  91. outf.write('%4d ' % linenum)
  92. charptr = lineptr
  93. lineptr += linelen
  94. LeadingSpc = False # Represents the opposite of bit 0 of FLAGS
  95. while True:
  96. c = buf[charptr]
  97. if charptr+1 == lineptr:
  98. if c != 118:
  99. outf.write(chars[c])
  100. outf.write('{NO EOL}')
  101. break
  102. if c == 126: # FP number
  103. charptr += 6
  104. continue
  105. if c & 64: # a token?
  106. # Carry from the TOKEN-ADD (L0975) subroutine is the key to
  107. # knowing whether to obey LeadingSpc (see TOKENS, L094B).
  108. # Returns C if bit 0 of FLAGS should be obeyed.
  109. # TOKEN-ADD maps C0-FF to 0-3F, then:
  110. # if result >= 0x43: no leading space (097F)
  111. # else if result >= 0x40: no leading space (0985, C set)
  112. # else if result < 0x18: no leading space (0990)
  113. # else if first token char < 0x1C: no leading space (0998)
  114. # else obey the leading space flag
  115. # If result < 0x18 then it originally was < 0xD8, so the
  116. # above rules translate to: obey only if c >= 0xD8 and the
  117. # token begins with alphanumeric characters.
  118. if c >= 0xD8 and ('0' <= chars[c] <= '9'
  119. or 'A' <= chars[c] <= 'Z'
  120. ) and LeadingSpc:
  121. outf.write(' ') # Leading space
  122. LeadingSpc = False
  123. outf.write(bold + chars[c] + norm)
  124. else:
  125. outf.write(chars[c])
  126. if c:
  127. # Unless we just printed a space, next token should have a
  128. # leading one
  129. LeadingSpc = True
  130. # Trailing space
  131. if buf[charptr] >= 192: # RND, INKEY$, PI have no trailing space
  132. c = chars[buf[charptr]][0]
  133. # Check if alphanum or $ (this skips ** "" >= etc)
  134. if c == '$' or '0' <= c <= '9' or 'A' <= c <= 'Z':
  135. outf.write(' ')
  136. LeadingSpc = False
  137. charptr += 1
  138. outf.write('\n')
  139. finally:
  140. f.close()