Implement PRINT TAB() and very basic cursor position tracking
authorNick Downing <nick@ndcode.org>
Mon, 16 May 2022 17:43:00 +0000 (03:43 +1000)
committerNick Downing <nick@ndcode.org>
Mon, 16 May 2022 17:43:00 +0000 (03:43 +1000)
apple_io.py
applesoft_basic.t
applesoft_basic.y
test_io.bas

index c0e95a9..f6e3b6d 100755 (executable)
@@ -7,13 +7,150 @@ import sys
 import termios
 import tty
 
+# see https://www.tinaja.com/ebooks/tearing_rework.pdf
+ZP_WNDLFT = 0x20 # Left side of scroll window
+ZP_WNDWTH = 0x21 # Width of scroll window
+ZP_WNDTOP = 0x22 # Top of scroll window
+ZP_WNDBTM = 0x23 # Bottom of scroll window
+ZP_CH = 0x24 # Cursor horizontal position
+ZP_CV = 0x25 # Cursor vertical position
+ZP_GBASL = 0x21 # LORES graphics base low
+ZP_GBASH = 0x27 # LORES graphics base high
+ZP_BASL = 0x28 # TEXT base address low
+ZP_BASH = 0x29 # TEXT base address high
+ZP_BAS2L = 0x2A # Scroll temporary base low
+ZP_BAS2H = 0x2B # Scroll temporary base high
+ZP_COLOR = 0x30 # Holds the LORES color value
+ZP_INVFLG = 0x32 # Normal/Inverse/Flash mask
+ZP_PROMPT = 0x33 # Holds prompt symbol
+ZP_YSAV = 0x34 # Temporary Y register hold
+ZP_CSWL = 0x36 # Output character hook low
+ZP_CSWH = 0x37 # Output character hook high
+ZP_KSWL = 0x38 # Input character hook low
+ZP_KSWH = 0x39 # Input character hook high
+ZP_ACC = 0x45 # Accumulator save
+ZP_XREG = 0x46 # X register save
+ZP_YREG = 0x47 # Y register save
+ZP_STATUS = 0x48 # Flag register save
+ZP_SPNT = 0x49 # Stack pointer save
+ZP_RNDL = 0x4E # Keybounce random number low
+ZP_RNDH = 0x4F # Keybounce random number high
+VP_REENTER = 0x03D0 # Re-enter DOS
+VP_RECONNECT = 0x03EA # Reconnect DOS I/O hooks
+VP_BRKV = 0x03F0 # Break vector address
+VP_SOFTEV = 0x03F2 # Warm start vector address
+VP_PWRDUP = 0x03F4 # Warm start EOR A5 checksum
+VP_AMPERV = 0x03F6 # Applesoft "&" Jump Code
+VP_USRADR = 0x03F8 # Control Y vector Jump Code
+VP_NMI = 0x03FB # NMI vector Jump Code
+VP_IRQLOC = 0x03FE # Interrupt vector address
+HW_IOADR = 0xC000 # Keyboard input location
+HW_KBDSTRB = 0xC010 # Keyboard strobe reset
+HW_TAPEOUT = 0xC020 # Cassette data output
+HW_SPKR = 0xC030 # Speaker click output
+HW_STROBE = 0xC040 # Game I/O connector strobe
+HW_TXTCLR = 0xC050 # Graphics ON soft switch
+HW_TXTSET = 0xC051 # Text ON soft switch
+HW_MIXCLR = 0xC052 # Full screen ON soft switch
+HW_MIXSET = 0xC053 # Split screen ON soft switch
+HW_LOWSCR = 0xC054 # Page ONE display soft switch
+HW_HISCR = 0xC055 # Page TWO display soft switch
+HW_LORES = 0xC056 # LORES ON soft switch
+HW_HIRES = 0xC057 # HIRES ON soft switch
+HW_AN0CLR = 0xC058 # Annunciator 0 OFF soft switch
+HW_AN0SET = 0xC059 # Annunciator 0 ON soft switch
+HW_AN1CLR = 0xC05A # Annunciator 1 OFF soft switch
+HW_AN1SET = 0xC05B # Annunciator 1 ON soft switch
+HW_AN2CLR = 0xC05C # Annunciator 2 OFF soft switch
+HW_AN2SET = 0xC05D # Annunciator 2 ON soft switch
+HW_AN3CLR = 0xC05E # Annunciator 3 OFF soft switch
+HW_AN3SET = 0xC05F # Annunciator 3 ON soft switch
+HW_TAPEIN = 0xC060 # Cassette tape read input
+HW_PB0 = 0xC061 # Push button 0 input
+HW_PB1 = 0xC062 # Push button 1 input
+HW_PB2 = 0xC063 # Push button 2 input
+HW_PDL0 = 0xC064 # Game Paddle 0 analog input
+HW_PDL1 = 0xC065 # Game Paddle 1 analog input
+HW_PDL2 = 0xC066 # Game Paddle 2 analog input
+HW_PDL3 = 0xC067 # Game Paddle 3 analog input
+ROM_PLOT = 0xF800 # Plot a block on LORES screen
+ROM_HLINE = 0xF819 # Draw a horizontal LORES line
+ROM_VLINE = 0xF828 # Draw a vertical LORES line
+ROM_CLRSCR = 0xF832 # Clear full LORES screen
+ROM_CLRTOP = 0xF836 # Clear top of LORES screen
+ROM_GBASCALC = 0xF847 # Calculate LORES base address
+ROM_NEXTCOL = 0xF85F # Increase LORES color by three
+ROM_SETCOL = 0xF864 # Set color for LORES plotting
+ROM_SCRN = 0xF871 # Read color of LORES screen
+ROM_PRNTAX = 0xF941 # Output A then X as hex
+ROM_PRBLNK = 0xF948 # Output three spaces via hooks
+ROM_PRBL2 = 0xF94A # Output X spaces via hooks
+ROM_STEP = 0xFA43 # Single step (old ROM only!)
+ROM_REGDSP = 0xFAD7 # Display working registers
+ROM_PREAD = 0xFB1E # Read a game paddle
+ROM_INIT = 0xFB2F # Initial i ze text screen
+ROM_SETTXT = 0xFB39 # Set up text screen
+ROM_SETGR = 0xFB40 # Setup LORES screen
+ROM_SETWND = 0xFB4B # Set text window to normal
+ROM_BASCALC = 0xFBC1 # Calculate text base address
+ROM_BELL = 0xFBD9 # 1 Beep speaker if ctrl G
+ROM_BELL2 = 0xFBE4 # Beep speaker once
+ROM_ADVANCE = 0xFBF4 # Move text cursor right by one
+ROM_VIDOUT = 0xFBFD # Output ASCII to screen only
+ROM_BS = 0xFC10 # Backspace screen
+ROM_UP = 0xFC1A # Move screen cursor up one
+ROM_VTAB = 0xFC22 # Vertical screen tab using CV
+ROM_VTABZ = 0xFC24 # Vertical screen tab using A
+ROM_ESCl = 0xFC2C # Process escape movements A-G
+ROM_CLREOP = 0xFC42 # Clear text to end of screen
+ROM_HOME = 0xFC58 # Clear screen and home cursor
+ROM_CR = 0xFC62 # Carriage return to screen
+ROM_LF = 0xFC66 # Line feed to screen onIy
+ROM_SCROLL = 0xFC70 # Scroll text screen up one
+ROM_CLEOL = 0xFC9C # Clear text to end of line
+ROM_WAIT = 0xFCA8 # Time delay set by accumulator
+ROM_RDKEY = 0xFD0C # Get input character via hooks
+ROM_KEYIN = 0xFD1B # Read the Apple keyboard
+ROM_RDCHAR = 0xFD35 # Get key and process ESC A-F
+ROM_CANCEL = 0xFD62 # Cancel keyboard line entry
+ROM_GETLNZ = 0xFD67 # CR, then get kbrl input line
+ROM_GETLN = 0xFD6A # Get input line from keyboard
+ROM_GETLN1 = 0xFD6F # Get kbd input, no prompt
+ROM_CROUT1 = 0xFD8B # Clear EOL then CR via hooks
+ROM_CROUT = 0xFD8E # Output return via hooks
+ROM_PRBYTE = 0xFDDA # Output full A in hpxto hooks
+ROM_PRHEX = 0xFDE3 # Output low A in hex to hooks
+ROM_COUT = 0xFDED # Output character via hooks
+ROM_COUT1 = 0xFDF0 # Output character to screen
+ROM_MOVE = 0xFE2C # Move block of memory
+ROM_VERIFY = 0xFE36 # Verify block of memory
+ROM_LIST = 0xFE5E # Disassemble 20 instructions
+ROM_L1ST2 = 0xFE63 # Disassemble A instructions
+ROM_SETINV = 0xFE80 # Print inverse text on screen
+ROM_SETNORM = 0xFE84 # Print normal text on screen
+ROM_SETVID = 0xFE93 # Grab output hooks for screen
+ROM_XBASIC = 0xFEB0 # Goto BASIC, destroying old
+ROM_BASCON = 0xFEB3 # Goto BASIC continuing old
+ROM_TRACE = 0xFEC2 # Start tracing (old ROM only!)
+ROM_WRITE = 0xFECD # Save to cassette tape
+ROM_READ = 0xFEFD # Read from cassette tape
+ROM_PRERR = 0xFF2D # Print "ERR" to output hook
+ROM_BELL = 0xFF3A # Output bell via hooks
+ROM_IORESR = 0xFF3F # Restore all working register
+ROM_IOSAVE = 0xFF4A # Save all working registers
+ROM_OLDRST = 0xFF59 # Old reset entry, no autostart
+ROM_MON = 0xFF65 # Enter monitor and beep spkr
+
 # global state
 attr = None
 fd_in = sys.stdin.fileno()
 fd_out = sys.stdout.fileno()
 poll_in = select.poll()
 poll_in.register(fd_in, select.POLLIN)
-mem = {}
+mem = {
+  ZP_CH: 0,
+  ZP_CV: 0
+}
 
 def init():
   global attr
@@ -40,61 +177,75 @@ def read(n):
 def write(data):
   os.write(fd_out, bytes(data, 'ascii'))
 
-def cr():
-  # apple treats \r as \r\n, so if you write e.g. \r\n you'll get \r\n\n
-  write('\r\n')
+def _print(data):
+  for ch in data:
+    if ch == '\r':
+      # apple treats \r as \r\n, so if you write e.g. \r\n you'll get \r\n\n
+      write('\r\n')
+      if mem[ZP_CV] < 23:
+        mem[ZP_CV] += 1
+      mem[ZP_CH] = 0
+    else:
+      write(ch)
+      if ch == '\a':
+         pass
+      elif ch == '\b':
+        if mem[ZP_CH] > 0:
+          mem[ZP_CH] -= 1
+      elif mem[ZP_CH] < 39:
+        mem[ZP_CH] += 1
+      else:
+        if mem[ZP_CV] < 23:
+          mem[ZP_CV] += 1
+        mem[ZP_CH] = 0
 
 def get():
-  data = read(1) 
-  if len(data) == 0:
+  ch = read(1) 
+  if len(ch) == 0:
     raise Exception('end of input') # due to piping or input redirection
-  if data == '\x7f':
-    data = '\b'
-  return data
+  if ch == '\x7f':
+    ch = '\b'
+  return ch
 
 def input():
   line = ''
   while True:
-    key = get()
-    _print(key)
-    if key == '\r':
+    ch = get()
+    _print(ch)
+    if ch == '\r':
       return line
-    if key == '\b':
+    if ch == '\b':
       if len(line):
-        write(' \b')
+        _prnt(' \b')
         line = line[:-1]
       else:
         # strictly, apple only does this if cursor not at left, maybe
         # because it knows the echoed backspace has no effect at left
-        cr()
+        _print('\r')
     else:
       if len(line) >= 247:
-        write('\a')
+        _print('\a')
         if len(line) >= 255:
-          cr()
+          _print('\r')
           line = ''
           continue
-      line += key
-
-def _print(data):
-  # basically write(data.replace('\r', '\r\n')) but may be more responsive
-  lines = data.split('\r')
-  for line in lines[:-1]:
-    write(line)
-    cr()
-  write(lines[-1])
+      line += ch
 
 def htab(x):
   write(f'\x1b[{x:d}G')
+  mem[ZP_CH] = x - 1
 
 def vtab(y):
   write(f'\x1b[{y:d}d')
+  mem[ZP_CV] = y - 1
 
 def clreop():
   write('\x1b[J')
 
 def home():
   write('\x1b[H\x1b[2J')
+  mem[ZP_CH] = 0
+  mem[ZP_CV] = 0
 
 def normal():
   write('\x1b[0m')
@@ -110,16 +261,10 @@ def tone(freq, dur): # Hz, ms
   write(f'\x1b7\x1b[10;{freq:d}]\x1b[11;{dur:d}]\x07\x1b8')
 
 def peek(addr):
-  addr &= 0xffff
-  return mem.get(addr, 0)
+  return mem.get(addr & 0xffff, 0)
 
 def poke(addr, data):
-  addr &= 0xffff
-  data &= 0xff
-  if data:
-    mem[addr] = data
-  elif addr in mem:
-    del mem[addr]
+  mem[addr & 0xffff] = data & 0xff
 
 def call(addr):
   addr &= 0xffff
@@ -143,6 +288,10 @@ def hlin(x0, x1, y):
 def vlin(y0, y1, x):
   pass
 
+def tab(x):
+  x -= 1 + mem[ZP_CH]
+  return ' ' * x if x >= 1 else ''
+
 if __name__ == '__main__':
   init()
   while True:
index 362f070..b6e2063 100644 (file)
@@ -105,6 +105,7 @@ class RValueStrLiteral: RValue;
 class RValueStrDollar: RValue;
 class RValueVal: RValue;
 class RValuePeek: RValue;
+class RValueTabLParen: RValue;
 class LValue: RValue;
 class LValueVariable: LValue;
 class LValueArray: LValue;
@@ -268,7 +269,7 @@ def execute(self, context):
       value = data_types.str_dollar(value)
     apple_io._print(value)
   if not self.semicolon:
-    apple_io.cr() # really writes \r\n
+    apple_io._print('\r')
 @method(StatementGoto)
 def execute(self, context):
   context.i = context.find_line(self.children[0].int_value)
@@ -615,6 +616,10 @@ def get(self, context):
 def get(self, context):
   value = self.children[0].get_str(context)
   return float(apple_io.peek(data_types.cint(value)))
+@method(RValueTabLParen)
+def get(self, context):
+  value = self.children[0].get_float(context)
+  return apple_io.tab(data_types.cint(value))
 @method(LValue)
 def get(self, context):
   value = self.find_variable(context).value
index 11cdc88..9b0a793 100644 (file)
@@ -155,8 +155,8 @@ statement_opt
   :
   | %space (?E{t_def.StatementLet}lvalue '=' rvalue)
   | %space (?E{t_def.StatementLet}KEYWORD_LET lvalue '=' rvalue)
-  | %space (?E{t_def.StatementPrint, semicolon = False}KEYWORD_PRINT print_rvalue_list0)
-  | %space (?E{t_def.StatementPrint, semicolon = True}KEYWORD_PRINT print_rvalue_list1)
+  | %space (?E{t_def.StatementPrint, semicolon = False}KEYWORD_PRINT print_item_list0)
+  | %space (?E{t_def.StatementPrint, semicolon = True}KEYWORD_PRINT print_item_list1)
   | %space (?E{t_def.StatementGoto}KEYWORD_GOTO INT_LITERAL)
   | %space (?E{t_def.StatementIf}KEYWORD_IF rvalue KEYWORD_THEN) statement_opt
   | %space (?E{t_def.StatementIf}KEYWORD_IF rvalue KEYWORD_THEN) %space (?E{t_def.StatementGoto}INT_LITERAL)
@@ -190,15 +190,20 @@ statement_opt
   | %space (?E{t_def.StatementVLin}KEYWORD_VLIN rvalue ',' rvalue KEYWORD_AT rvalue)
   ;
 
-print_rvalue_list0
+print_item_list0
   :
-  | print_rvalue_list0 rvalue
-  | print_rvalue_list1 rvalue
+  | print_item_list0 print_item
+  | print_item_list1 print_item
   ;
 
-print_rvalue_list1
-  : print_rvalue_list0 ';'
-  | print_rvalue_list1 ';'
+print_item_list1
+  : print_item_list0 ';'
+  | print_item_list1 ';'
+  ;
+
+print_item
+  : rvalue
+  | %space (?E{t_def.RValueTabLParen}KEYWORD_TAB_LPAREN rvalue ')')
   ;
 
 data_item_list
@@ -256,7 +261,7 @@ rvalue
 lvalue
   : %space (?E{t_def.LValueVariable}VARIABLE_NAME)
   | %space (?E{t_def.LValueArray}VARIABLE_NAME '(' rvalue_list ')')
-  ; 
+  ;
 %%
 
 def yyerror(s):
index b5806aa..bba54ea 100644 (file)
@@ -9,6 +9,9 @@
 90 PRINT "HELLO, "J$
 100 INPUT K$
 110 PRINT "YOU TYPED: "K$
-120 FLASH
-130 PRINT "GOODBYE"
-140 NORMAL
+120 FOR I=1 TO 10
+130 PRINT "HELLO"TAB(I)"GOODBYE"
+140 NEXT
+150 FLASH
+160 PRINT "GOODBYE"
+170 NORMAL