MG's Apple ][ EhBASIC Port

I, as others before me, have been working on porting EhBASIC, by the late Lee Davison, to the Apple II. It's actually extremely easy to get it up and running in a minimalist fashion.

What I have wanted (since childhood) is an easy to use/hack BASIC that I have the source code for, that is aware of things like MouseText, the ProDOS MLI, the Pascal 1.1 slot protocol, and other amenities. I wanted I/O to be a first-class citizen in the language, not a somewhat dirty hack that relied on the I/O hooks and literally PRINTing commands to the operating system.

For those of us for whom it was our first programming language, we still love Applesoft. That being said, Apple's biggest updates to Applesoft since 1978 were support for lower-case entry and allowing the delete key on newer keyboards to be used as a backspace. There was pretty much nothing else that changed despite the evolving capabilities of the Apple II machines.


Startup screen.

Some useful commands.

Woz's signature. Credit.

Design Criteria

In short, my design criteria for a “new traditional” BASIC for the Apple II were:

  • It should take advantage of the 65C02, MouseText, ProDOS, and other newer features when it make sense.
  • I/O commands should be first-class citizens. E.g. no more PRINT CHR$(4);“OPEN MYFILE”
    • This includes both slot-based character I/O as well as disk I/O.
  • Modest compatibility with Applesoft programs where it makes sense. When adding graphics commands, they should follow the Applesoft syntax. Where EhBASIC does something different but it is painless to make it behave like Applesoft, do it (e.g. in the INPUT command, don't display ? if the programmer supplied a prompt).
  • Keep a 128K version in mind that can use Aux RAM.
  • It should be as welcoming to new programmers as Applesoft is.

I don't quite have the skills to program a full BASIC interpreter, so EhBASIC was a natural fit for expansion due to its generous non-commercial license, well-commented and easy to understand source code, and ease of expansion.

Implementation Details

What is Done

Loader

The loader code is complete and features the following:

  • Sets up ProDOS memory map.
  • Moves EhBASIC and its Global Page to higher memory.
  • Makes sure a prefix is set to something sensible if not already set.
  • Scans the slots for Pascal-protocol firmware and collects their entry points.
  • Sets up EhBASIC keyboard and text screen I/O.
  • Cold-starts EhBASIC.
  • Loads and runs the startup program, if it was given and it exists. It defaults to EHSTARTUP, but uses the well-defined method of accepting a startup program through a program launcher.

ProDOS Support

  • Dynamic buffer management that can create fixed $400-byte buffers on-the-fly by moving string storage down in memory. Up to 8 buffers may be dynamically allocated.
  • $500 bytes of RAM is reserved for EhBASIC's own purposes. Mainly so that certain operations, such as SAVE continue to work even if all the buffers are used.
  • SAVE “<filename>” saves a program. Uses type $F8 (User 8) with aux type $EBA0.
  • LOAD “<filename>” loads a saved program.
  • RUN “<filename>” loads a saved program and executes it.
  • PREFIX [“<new prefix>”] displays or sets the prefix.
  • ONLINE lists the online volumes.
  • CAT [“<path>”] gives a simple directory catalog.
  • RENAME “<old filename>”,“<new filename>”
  • DELETE “<filename>”
  • BYE quits back to ProDOS.
  • P8CALL <num>,<addr> makes arbitrary ProDOS calls.
  • P2B$() and B2P$() help with P8CALL
    • P2B$(<addr|string>[,<mask>[,<rshift>]) - converts a Pascal string (length followed by bytes) to a BASIC string. Length is determined by the first value at addr, ANDed with <mask> and right-shifted by <rshift> bits.
    • B2P$(<string>[,<lshift>[,<or>]) - converts a BASIC string to a Pascal string embedded within a BASIC string. The value used for the length byte is the length of the source string, left-shifted by <lshift> bits, and ORed with <or>.
  • ProDOS errors are trapped, appropriate messages displayed for the most common of them, and an attempt to close all open files is made (this will change when error handling is introduced).

Text / Graphics Support

  • High-bit-off ASCII is used.
  • Alternate character set (MouseText) is always on.
    • In 40-column mode, MouseText is accessible starting at CHR$(192)
    • In 40- or 80-column mode, MTEXT ON/OFF can be used, which turn on inverse and enable MouseText in place of upper case characters.
  • INVERSE and NORMAL behave as expected. FLASH is the same as INVERSE as flashing text is not supported with the alternate character set.
  • HOME clears the text screen.
  • SCREEN command:
    • Changes between text, lo-res, and hi-res.
    • Does not automatically clear the screen.
    • For HGR modes, can optionally make the drawing commands continue to draw on the previous page.
    • GR/Text page 2 can be displayed, but we always output to page 1 (for now).
  • CLS clears the current active screen.
  • TEXT, GR, HGR, and HGR2 all behave as Applesoft.
    • HGR0 display full screen page 1. HGR1 same as HGR, HGR3 mixed page 2.
  • COLOR=, PLOT, HLIN, and VLIN behave as Applesoft.
    • Additionally, PLOT can accept any amount of pixels in one command. E.g. plot 0,0,0,1,0,2,1,0,2,0 plots 5 pixels.
    • HLIN and VLIN can take a comma instead of AT.
  • HCOLOR= and HPLOT behave as Applesoft.
    • HPLOT can take a comma instead of TO.
  • PDL() and BTN() work for game I/O.
  • ERRNO(n) function where n=0 for line number of error, n=1 for BASIC error #, n=2 for Pascal I/O error #, n=3 for ProDOS error #.
  • GLOBAL(n) returns the Nth address of the EhBASIC global page.

Other New BASIC Commands and Functions

  • BEEP [<pitch>,<duration>] beeps the speaker. If the args are omitted, it produces the system bell.
  • TRY statement:
    • TRY <statement> [THEN <success-clause>] [ELSE <fail-clause>]
    • ERRNO(1) is populated per the below table.
      • If ERRNO(1) is nonzero, ERRNO(0) is valid.
      • If ERRNO(1) is 25, ERRNO(3) is valid.
    • TRYTHEN sometimes doesn't work well with PRINT, INPUT, and statements that don't evaluate expression arguments because they do not expect to be followed by THEN
      • E.g. TRY PRINT “FOO” THEN PRINT “BAZ” ELSE PRINT “BAR” will print “FOOBAR” and ERRNO(1) will reflect a syntax error.
      • For these cases, you can use TRYELSE, TRY with no clauses, or wrap in a subroutine (e.g. TRY GOSUB 1000 THEN …).
    • Be careful nesting IF inside TRY, that may cause unexpected behavior, especially if there is an ELSE clause. If necessary, a solution is to wrap the conditional in a subroutine.
    • TRY GOTO <n> doesn't work. TRY GOSUB <n> does.
ERRNO(1) Values
# Error
0 Success
1 NEXT without FOR
2 Syntax
3 RETURN without GOSUB
4 Out of Data
5 Function Call
6 Overflow
7 Out of Memory
8 Undefined Statement
9 Array Bounds
10 Double Dimensioned Array
11 Divide by Zero
12 Illegal Direct
13 Type Mismatch
14 String Too Long
15 String Too Complex
16 Continue Error
17 Undefined Function
18 LOOP without DO
19 Undefined Variable
20 Undimensioned Array
21 Unimplemented function
22 Illegal Argument
23 I/O Error
24 No Device Connected
25 ProDOS Error (ERRNO(3) contains ProDOS error number)
26 File Type Mismatch

Other features

  • Pressing the tab key when at the beginning of an empty line in immediate mode will generate an automatic line number that is 10 higher than the last one. Note that due to the way EhBASIC flags immediate mode, it won't work right after a program ends, until something else is entered.

What is Planned, but Not Done

  • PR# and IN# to files.
  • OPEN, CLOSE, WRITE (binary file I/O), READ# (read file like a DATA statement), READ() (binary file I/O), SEEK, TELL(), FLUSH, PRINT# (text file I/O), INPUT# (text file I/O), EOF() - Operations on files. Slots will be able to be opened like files for some operations.
  • CREATE, LOCK, UNLOCK, and CHTYPE for files.
  • SAVE and LOAD untokenized files option. E.g. SAVE “MYPROGRAM” AS LIST.
  • POP – dunno why EhBASIC does not have this.
  • ERROR <n> – cause error on purpose.
  • SCRN() and HSCRN() for seeing what pixel is displayed on the graphics screens.
  • PREFIX$() function for returning the current prefix. On GS/OS this should return the numbered prefixes as well.
  • Line editing and renumbering.
    • ESC-editing will never be supported due to the I/O expecting Pascal behavior.
    • That being said, I expect to be able to have a command such as EDIT <n> that will pre-fill the input buffer with that line and let you edit it.

Many optimizations of my initial implementations of functionality are needed!

Differences from standard 6502 EhBASIC

Not covered by the above:

  • INPUT with a supplied prompt does not display ? prompt.
  • Mixed-case keywords supported.
  • Line editing supports forward arrow and the delete key.
  • Values below $20 are now additional command tokens (except $00), so the input routine rejects attempts to embed control characters via the keyboard.

What Will Not Be Implemented

  • ON ERROR GOTO and RESUME
    • See TRY above.

Preview Releases

No source code yet, sorry. I do have a mirror of the repository I started with located here.. Importantly, you should read the README and the EhBASIC manual.

Warning: I will be adding more tokens, and this will cause you problems as certain tokens need to be in certain positions. Don't do anything serious with these preview releases.

Files:

ca. 02/12/2018 12:00 PST

  • Bug fixes in detokenization.
  • Bug fixes in TRY.
  • TRYELSE now supported.

Gzip-compressed .po file Derived from EhBASIC.

ShrinkIt archive Derived from EhBASIC.

Example Programs

Get the ProDOS Prefix into a Variable

In a low-level fashion, until I implement a better way:

10 a$=" "+"  ":REM the + is so EhBASIC won't store the string in program space
20 POKE SADD(a$),1:REM otherwise these pokes do bad things
30 DOKE SADD(a$)+1,$300:REM we'll use $300 as a buffer
40 P8CALL $c7,SADD(a$):REM call ProDOS GET_PREFIX
50 pf$=B2P$($300):REM convert result
60 PRINT "The prefix is ";pf$

Get volume in slot 6, drive 1 into a Variable

10 a$=" "+"   "
20 POKE SADD(a$),2:REM 2 params
30 POKE SADD(a$)+1,$60:REM unit #
40 DOKE SADD(a$)+2,$300:REM buffer (16 bytes)
50 P8CALL $c5,SADD(a$):REM ON_LINE
60 v$=B2P$($300,$f):REM convert string, only need lower 4 bits for length
70 PRINT "Volume in slot 6, drive 1 is ";v$