LLX > Neil Parker > Apple II > Pascal Secrets

Undocumented Secrets of Apple Pascal

Here are some secrets of Apple Pascal which Apple never documented. Most of this was discovered by studying source code—the source code for Apple Pascal has never been publicly available, but that of its predecessor, UCSD Pascal II.0, was released in 2006, and can be downloaded from Bitsavers.org. UCSD Pascal II.0 is definitely not the same as Apple Pascal (most importantly, II.0 had no concept of intrinsic units), but there are enough points in common that quite a lot about one can be learned by studying the other.

Warning: This is definitely not beginner-level information. Much of what follows assumes familiarity with just about everything in the Apple Pascal manuals, especially the P-machine architecture and codefile format. The most complete manual is the Apple II Pascal 1.3 manual. More important information is found in the Device and Interrupt Support Tools Manual, and its prececessor the ATTACH-BIOS Document for Apple II Pascal 1.1 (the latter is obsolete, but contains some information omitted from its replacement). Another good source of information is Randy Hyde's p-Source, which includes lots of information about the innards of Apple Pascal 1.1.

Reserved Words

In Apple Pascal 1.0 and 1.1, SEPARATE is a reserved word. It was taken out in 1.2.

In UCSD Pascal II.0, a unit declaration could have the word SEPARATE before the word UNIT. This caused the unit to be given a segment type of SEPRTSEG (rather than the usual UNITSEG), which is the same segment type that the assembler gives to its output. Thus, when a SEPARATE UNIT was linked, it behaved like assembly-language routines instead of a regular unit—that is, any referenced routines were extracted and added to the caller's segment, instead of the whole unit going into a segment all its own.

This feature might still have been functional in Apple Pascal 1.0. It was removed in 1.1, but the orphan reserved word was still reserved until 1.2.

Despite what the manual (and the Pascal standard) says, NIL is not a reserved word in Apple Pascal. It's implemented as a pre-defined identifier, which means that the compiler won't complain if declare your own identifier with that name (which is, of course, not recommended).

Builtins

Not all of Apple Pascal's built-in routines are described in the manual. Here are the omitted routines:

OPENNEW(VAR f: FILE OF whatever; name: STRING)
This is an alternate name for REWRITE(f, name).
OPENOLD(VAR f: FILE OF whatever; name: STRING)
This is an alternate name for RESET(f, name), except that the name argument may not be omitted.
TIME(VAR hi, lo: INTEGER)
This procedure reads the system clock. The clock is assumed to return a 32-bit value in units of 1/60th of a second, representing the time elapsed since some arbitrary point in the past (often, but not necessarily, the most recent power-on or reboot)—the high 16 bits are returned in the first argument and the low 16 bits in the second. Since most Apple IIs have no clock, Apple Pascal normally returns zero in both arguments, but if a clock card is available, a SYSTEM.ATTACH driver might patch the system to return meaningful numbers (the above-mentioned p-Source includes an example of such a driver, for a Mountain Computer clock card...alas, it only works with Apple Pascal 1.1).

Compiler Options

Not all of the Apple Pascal compiler options are documented in the manual. The USCD Pascal II.0 source code reveals these additional (admittedly not very useful) options, which are still supported through Apple Pascal 1.3:

Debug, {$D+}
Turning this option on causes the compiler to emit a P-code BPT (breakpoint) instruction before every statement. The BPT instruction is intended to pass control to a debugger, but even though a D(ebug command persisted on the command menu through Apple Pascal 1.1, a debugger has never been available for Apple Pascal (nor for UCSD Pascal II.0), and the BPT instruction does nothing. {$D+} may be placed before any statement, and its effect lasts until a {$D-} occurs. The default state is {$D-}.
Flip, {$F+}
If this option appears at the beginning of the source code, the compiler will issue P-code with the opposite byte order from that normally used by the host computer...on the Apple II, that means to issue big-endian P-code instead of little-endian P-code. Apple Pascal cannot run such code. The default state is {$F-}.
Tiny, {$T+}
If this option appears at the beginning of the source code, the compiler will omit some of its built-in identifiers from the symbol table. This makes more memory available for compiling, but it means the missing built-ins can't be used by the source code being compiled. In UCSD Pascal II.0, the omitted built-ins were READLN, PRED, SUCC, SQR, UNITREAD, INSERT, DELETE, COPY, POS, SEEK, GET, PUT, PAGE, STR, and GOTOXY; the list in Apple Pascal is probably similar. The default state is {$T-}.

Assembler Directives

The assembler recogizes two directives that aren't documented in the manual:

Absolute section, .ASECT
This has an effect similar to the DSECT directive of Apple's DOS Toolkit assembler, or the DUM directive of later versions of Merlin. It starts a special source code section within which no code is generated and no space is reserved, but any labels defined within the section are assigned (absolute) values as if code were being generated normally. After .ASECT, the next directive is usually .ORG, followed by .BYTE, .WORD, .BLOCK, and/or .EQU. .ASECT must occur within a .PROC or .FUNC. The special section ends with a .PSECT directive, after which normal code generation resumes at the point where it left off.
Program section, .PSECT
This directive ends a .ASECT section. Every .ASECT must end with a .PSECT.

Example usage:

        .PROC   SILLY
        .ASECT
        .ORG    300
FOO     .BLOCK  8
BAR     .WORD   0
BAZ     .BYTE   0
        .PSECT
        LDA     FOO     ;Becomes "LDA 300"
        LDA     BAR     ;Becomes "LDA 308"
        LDA     BAZ     ;Becomes "LDA 30A"
        RTS
        .END

Machine Types

The Apple Pascal manual shows how each code segment has a machine type, of which ten values are possible, numbered 0 through 9. But it documents only four of these types. So what are the other six?

Digging around a bit in Apple's LIBMAP.CODE reveals the full list:

Type Processor
0 Unknown
1 P-code (most sig. 1st)
2 P-code (least sig. 1st)
3 PDP-11
4 8080
5 Z-80
6 GA 440
7 6502
8 6800
9 TI 9900

Of course only types 2 and 7 are meaningful on Apple Pascal. The others were used by other versions of UCSD Pascal. Version II.0 and earlier compilers usually didn't set the machine type at all, so their code appears to be of type 0 (Unknown).

(So what's "GA 440"? It was the General Automation 440, a mini-mainframe of the 1970's.)

The Truth About {$U-}

The Apple Pascal manual documents the {$U-} compiler option, but only with a brief, cryptic note about how code compiled using it behaves significantly differently than normal, and it should only be used by people familiar with how it works. Thus "What the heck does {$U-} really do?" has been one of Apple Pascal's enduring mysteries.

In brief, it switches the compiler into system mode, which is used to compile Apple Pascal itself (SYSTEM.PASCAL, and also SYSTEM.FILER, SYSTEM.EDITOR, SYSTEM.COMPILER, SYSTEM.ASSMBLER, and SYSTEM.LINKER, and probably parts of SYSTEM.LIBRARY).

Normally, the compiler produces code with these characteristics:

Turning on {$U-} does this instead:

These may seem like minor behind-the-scenes bureaucratic changes, but they (especially the segment number assignments) have profound and far-reaching effects on how the program behaves. To understand these, we need to look at how Pascal code files are loaded into memory.

The P-code interpreter includes, as part of its SYSCOM area, an array called SEGTABLE, which has 32 entries (or 64 in the 128K system), one for each possible segment number. Each entry describes one segment of the currently-active program or library file, and consists of three pieces of information: the segment's disk unit number, the block number of its first disk block, and its length in bytes. At boot time, entries 0 through 6 of SEGTABLE are filled (by SYSTEM.APPLE) with the information for SYSTEM.PASCAL's segments. Within SYSTEM.APPLE is a routine that uses this information to read a segment from disk into the next available space on the program stack, which runs whenever a call is made to a routine that's not already in memory.

SYSTEM.PASCAL's segment 0 is its main program, and the basic routines that Pascal code calls upon to handle I/O and strings. It always remains in memory. Segments 2 through 6 of SYSTEM.PASCAL contain routines that can be swapped out when not needed, such as its startup initialization, main menu handling, error message printing, etc.

SYSTEM.PASCAL's segment 1, called USERPROGRAM, is just a stub that does nothing but print the message "No user program". As we'll see in a moment, you'll hardly ever see this message.

Whenever SYSTEM.PASCAL is asked to run a program, it opens the code file and reads its first block, which contains the segment dictionary. It uses the segment dictionary contents to fill the SEGTABLE array with the information for each of the program's segments, and each of the intrinsic units that it uses. But as it does this, it ignores any segments with segment number 0 or 2 through 6—thus all of SYSTEM.PASCAL's segments (except USERPROGRAM) are preserved in SEGTABLE, regardless of what's in the code file.

Then it closes the code file, and calls the USERPROGRAM procedure. But by this time USERPROGRAM's segment information is no longer in SEGTABLE—it's been replaced with segment 1 of the new program. So instead of SYSTEM.PASCAL's USERPROGRAM stub, the new program's segment 1 starts running.

Note how this meshes with standard compilation conventions described above. The compiler has put the program's main code in segment 1, and any additional segments are numbered starting with 7, so they won't get ignored.

Note also that this makes the main menu's U(ser restart command especially simple—it just calls USERPROGRAM again.

Now consider what all of this implies for {$U-} mode. The compiler puts the main code in segment 0, which is ignored, so there's no point in having your main code be anything beyond BEGIN END, unless you're writing a replacement for SYSTEM.PASCAL itself (not recommended, unless you're exceptionally ambitious). For the same reason there's no point in declaring any normal PROCEDUREs or FUNCTIONs at the top level.

Segment 1 will be loaded and run as the main program, so it had better actually be your main program. It must be declared as your first SEGMENT PROCEDURE. SYSTEM.PASCAL passes two word-sized arguments to it, which it must accept, but they're useless and can be ignored by the rest of your code (the declaration commonly looks like SEGMENT PROCEDURE MYPROGRAM(XXX, YYY: INTEGER);). This procedure's local variables are in effect your global variables, and you should nest all your PROCEDUREs and FUNCTIONs inside it.

if you want any additional SEGMENT PROCEDUREs or SEGMENT FUNCTIONs beyond the first not to be ignored, you'll need to skip ahead to at least segment 7. In the old days this could only be done by declaring enough empty dummy procedures to fill the gap (SEGMENT PROCEDURE DUMMY2; BEGIN END; SEGMENT PROCEDURE DUMMY3; BEGIN END;, etc.), but ever since Apple Pascal 1.1, we can write {$NS 7} instead.

If you use any intrinsic units, they will not be automatically preloaded—instead, they will be loaded as needed just like a SEGMENT PROCEDURE, resulting in chaos if the boot disk (with SYSTEM.LIBARY on it) isn't in the boot drive at the time. If you want to preload an intrinsic unit, use the {$R unitname} option at the beginning of your main SEGMENT PROCEDURE. It's probably best to avoid units with data segments (like TURTLEGRAPHICS), because {$R} only preloads the code segment, not the data segment.

Don't try to use a non-intrinsic unit with a {$U-} program—they don't play well together. (The compiler mistakenly allocates segment 1 for the unit, pushing your main SEGMENT PROCEDURE to segment 2, which ruins everything. And the then linker chokes on the mistaken segment, so the program can't be linked.)

So, given the inconvenience of all those rules, what's the benefit of compiling anything other than SYSTEM.PASCAL in {$U-} mode? Simple: Access to SYSTEM.PASCAL's global variables. Segment 0 is ignored when loading a program, but the compiler doesn't know that, and any references that you compile to top-level global variables will still try to access segment 0's data space, and since your segment 0 never got loaded, that means SYSTEM.PASCAL's segment 0's data space.

Of course you must be very careful if you do this. You can't just define whatever {$U-} global variables you feel like - you can only use variables that SYSTEM.PASCAL has already defined. And since Apple has never published anything about these variables, doing this has traditionally not been easy.

But now that we have the UCSD Pascal II.0 source code, it's gotten a lot easier—a little experimentation shows that the the II.0 global variables are still laid out the same way in Apple Pascal. This is the topic of the next section.

SYSTEM.PASCAL's Global Variables

First, a warning: THIS SECTION APPLIES TO THE FULL PASCAL DEVELOPMENT ENVIRONMENT ONLY. THE RUNTIME SYSTEM'S GLOBAL VARIABLES ARE DIFFERENT! If you write code intended to run under the runtime environment, it's probably best to assume that the global variables aren't available at all.

If you're still with me, the listing below shows the constant, type, and variable definitions from UCSD Pascal II.0's GLOBALS.TEXT. Comments beginning with "NP:" are my own additions, as are the numbers at the right side of data structures, which indicate the offsets from the beginning of the data structure to the field.

CONST
     MMAXINT = 32767;   (*MAXIMUM INTEGER VALUE*)
     MAXUNIT = 12;      (*MAXIMUM PHYSICAL UNIT # FOR UREAD*)
{NP:           ^^ 12 for Apple Pascal 1.0 & 1.1, 20 for 1.2 & 1.3 }
     MAXDIR = 77;       (*MAX NUMBER OF ENTRIES IN A DIRECTORY*)
     VIDLENG = 7;       (*NUMBER OF CHARS IN A VOLUME ID*)
     TIDLENG = 15;      (*NUMBER OF CHARS IN TITLE ID*)
     MAX_SEG = 31;      (*MAX CODE SEGMENT NUMBER*)
{NP:           ^^ 31 for the 64K system, 63 for the 128K system }
     FBLKSIZE = 512;    (*STANDARD DISK BLOCK LENGTH*)
     DIRBLK = 2;        (*DISK ADDR OF DIRECTORY*)
     AGELIMIT = 300;    (*MAX AGE FOR GDIRP...IN TICKS*)
     EOL = 13;          (*END-OF-LINE...ASCII CR*)
     DLE = 16;          (*BLANK COMPRESSION CODE*)
     NAME_LEN = 23;     {Length of CONCAT(VIDLENG,':',TIDLENG)}
     FILL_LEN = 11;     {Maximum # of nulls in FILLER}

TYPE

     IORSLTWD = (INOERROR,IBADBLOCK,IBADUNIT,IBADMODE,ITIMEOUT,
	         ILOSTUNIT,ILOSTFILE,IBADTITLE,INOROOM,INOUNIT,
	         INOFILE,IDUPFILE,INOTCLOSED,INOTOPEN,IBADFORMAT,
	         ISTRGOVFL);

	                                (*COMMAND STATES...SEE GETCMD*)

     CMDSTATE = (HALTINIT,DEBUGCALL,
	         UPROGNOU,UPROGUOK,SYSPROG,
	         COMPONLY,COMPANDGO,COMPDEBUG,
                 LINKANDGO,LINKDEBUG);
     
                                        (*CODE FILES USED IN GETCMD*)
                                        
     SYSFILE = (ASSMBLER,COMPILER,EDITOR,FILER,LINKER);

	                                (*ARCHIVAL INFO...THE DATE*)

     DATEREC = PACKED RECORD
	         MONTH: 0..12;          (*0 IMPLIES DATE NOT MEANINGFUL*)
	         DAY: 0..31;            (*DAY OF MONTH*)
	         YEAR: 0..100           (*100 IS TEMP DISK FLAG*)
	       END (*DATEREC*) ;

	                                (*VOLUME TABLES*)
     UNITNUM = 0..MAXUNIT;
     VID = STRING[VIDLENG];

	                                (*DISK DIRECTORIES*)
     DIRRANGE = 0..MAXDIR;
     TID = STRING[TIDLENG];
     FULL_ID = STRING[NAME_LEN];

     FILE_TABLE = ARRAY [SYSFILE] OF FULL_ID;

     FILEKIND = (UNTYPEDFILE,XDSKFILE,CODEFILE,TEXTFILE,
	         INFOFILE,DATAFILE,GRAFFILE,FOTOFILE,SECUREDIR);

     DIRENTRY = PACKED RECORD
	          DFIRSTBLK: INTEGER;   (*FIRST PHYSICAL DISK ADDR*)           {0}
	          DLASTBLK: INTEGER;    (*POINTS AT BLOCK FOLLOWING*)          {2}
	          CASE DFKIND: FILEKIND OF                                     {4}
                    SECUREDIR,
	            UNTYPEDFILE: (*ONLY IN DIR[0]...VOLUME INFO*)
	               (FILLER1 : 0..2048; {for downward compatibility,13 bits}
			DVID: VID;              (*NAME OF DISK VOLUME*)        {6}
	                DEOVBLK: INTEGER;       (*LASTBLK OF VOLUME*)          {14}
	                DNUMFILES: DIRRANGE;    (*NUM FILES IN DIR*)           {16}
	                DLOADTIME: INTEGER;     (*TIME OF LAST ACCESS*)        {18}
	                DLASTBOOT: DATEREC);    (*MOST RECENT DATE SETTING*)   {20}
	            XDSKFILE,CODEFILE,TEXTFILE,INFOFILE,
	            DATAFILE,GRAFFILE,FOTOFILE:
	               (FILLER2 : 0..1024; {for downward compatibility}
			STATUS : BOOLEAN;        {for FILER wildcards}
			DTID: TID;              (*TITLE OF FILE*)              {6}
			DLASTBYTE: 1..FBLKSIZE; (*NUM BYTES IN LAST BLOCK*)    {22}
	                DACCESS: DATEREC)       (*LAST MODIFICATION DATE*)     {24}
	        END (*DIRENTRY*) ;                                             {66 total}

     DIRP = ^DIRECTORY;

     DIRECTORY = ARRAY [DIRRANGE] OF DIRENTRY;

	                                (*FILE INFORMATION*)

     CLOSETYPE = (CNORMAL,CLOCK,CPURGE,CCRUNCH);
     WINDOWP = ^WINDOW;
     WINDOW = PACKED ARRAY [0..0] OF CHAR;
     FIBP = ^FIB;

{NP: FIB is the structure underlying the FILE types.  If by variant record
 trickery you can get a FIB record to overly a FILE variable, you can use
 this record to see inside the FILE variable.

 Pascal always allocates 600 bytes for a typed FILE, which is 22 bytes more than
 needed (the extra bytes are wasted).  FILEs also need however much extra
 memory is required for the GET/PUT buffer.}
     FIB = RECORD
	     FWINDOW: WINDOWP;  (*USER WINDOW...F^, USED BY GET-PUT*)          {0}
	     FEOF,FEOLN: BOOLEAN;                                              {4,2}
	     FSTATE: (FJANDW,FNEEDCHAR,FGOTCHAR);                              {6}
	     FRECSIZE: INTEGER; (*IN BYTES...0=>BLOCKFILE, 1=>CHARFILE*) {8}
	     CASE FISOPEN: BOOLEAN OF                                          {10}
	       TRUE: (FISBLKD: BOOLEAN; (*FILE IS ON BLOCK DEVICE*)            {12}
	              FUNIT: UNITNUM;   (*PHYSICAL UNIT #*)                    {14}
	              FVID: VID;        (*VOLUME NAME*)                        {16}
	              FREPTCNT,         (* # TIMES F^ VALID W/O GET*)          {28}
	              FNXTBLK,          (*NEXT REL BLOCK TO IO*)               {26}
	              FMAXBLK: INTEGER; (*MAX REL BLOCK ACCESSED*)             {24}
	              FMODIFIED:BOOLEAN;(*PLEASE SET NEW DATE IN CLOSE*)       {30}
	              FHEADER: DIRENTRY;(*COPY OF DISK DIR ENTRY*)             {32}
	              CASE FSOFTBUF: BOOLEAN OF (*DISK GET-PUT STUFF*)         {58}
	                TRUE: (FNXTBYTE,FMAXBYTE: INTEGER;                     {62,60}
	                       FBUFCHNGD: BOOLEAN;                             {64}
	                       FBUFFER: PACKED ARRAY [0..FBLKSIZE] OF CHAR))   {66}
	   END (*FIB*) ;                                                       {578 total}

	                                (*USER WORKFILE STUFF*)

     INFOREC = RECORD
	         SYMFIBP,CODEFIBP: FIBP;        (*WORKFILES FOR SCRATCH*)      {2,0}
	         ERRSYM,ERRBLK,ERRNUM: INTEGER; (*ERROR STUFF IN EDIT*)        {8,6,4}
	         SLOWTERM,STUPID: BOOLEAN;      (*STUDENT PROGRAMMER ID!!*)    {12,10}
	         ALTMODE: CHAR;                 (*WASHOUT CHAR FOR COMPILER*)  {14}
	         GOTSYM,GOTCODE: BOOLEAN;       (*TITLES ARE MEANINGFUL*)      {18,16}
	         WORKVID,SYMVID,CODEVID: VID;   (*PERM&CUR WORKFILE VOLUMES*)  {36,28,20}
	         WORKTID,SYMTID,CODETID: TID    (*PERM&CUR WORKFILES TITLE*)   {76,60,44}
	       END (*INFOREC*) ;                                               {92 total}

	                                (*CODE SEGMENT LAYOUTS*)

     SEG_RANGE = 0..MAX_SEG;
     SEG_DESC = RECORD
	         DISKADDR: INTEGER;     (*REL BLK IN CODE...ABS IN SYSCOM^*)   {0}
	         CODELENG: INTEGER      (*# BYTES TO READ IN*)                 {2}
	       END (*SEGDESC*) ;                                               {4 total}

					(*DEBUGGER STUFF*)

     BYTERANGE = 0..255;
     TRICKARRAY = RECORD        {Memory diddling for execerror}
		    CASE BOOLEAN OF
		      TRUE : (WORD : ARRAY [0..0] OF INTEGER); 
		      FALSE : (BYTE : PACKED ARRAY [0..0] OF BYTERANGE)
		    END;
     MSCWP = ^ MSCW;            (*MARK STACK RECORD POINTER*)
     MSCW = RECORD
	      STATLINK: MSCWP;  (*POINTER TO PARENT MSCW*)                     {0}
	      DYNLINK: MSCWP;   (*POINTER TO CALLER'S MSCW*)                   {2}
	      MSSEG,MSJTAB: ^TRICKARRAY;                                       {6,4}
	      MSIPC: INTEGER;                                                  {8}
	      {LOCALDATA: TRICKARRAY}
{NP: In Apple Pascal, LOCALDATA is replaced by MSSP:}
              MSSP: INTEGER                                                    {10}
	    END (*MSCW*) ;                                                     {12 total}

	                                (*SYSTEM COMMUNICATION AREA*)
	                                (*SEE INTERPRETERS...NOTE  *)
	                                (*THAT WE ASSUME BACKWARD  *)
	                                (*FIELD ALLOCATION IS DONE *)
     SEG_ENTRY = RECORD
		   CODEUNIT: UNITNUM;                                          {0}
		   CODEDESC: SEGDESC                                           {2}
		 END;                                                          {6 total}
     SYSCOMREC = RECORD
	           IORSLT: IORSLTWD;    (*RESULT OF LAST IO CALL*)             {0}
	           XEQERR: INTEGER;     (*REASON FOR EXECERROR CALL*)          {2}
	           SYSUNIT: UNITNUM;    (*PHYSICAL UNIT OF BOOTLOAD*)          {4}
	           BUGSTATE: INTEGER;   (*DEBUGGER INFO*)                      {6}
	           GDIRP: DIRP;         (*GLOBAL DIR POINTER,SEE VOLSEARCH*)   {8}
	           LASTMP,STKBASE,BOMBP: MSCWP;                                {14,12,10}
	           MEMTOP,SEG,JTAB: INTEGER;                                   {20,18,16}
	           BOMBIPC: INTEGER;    (*WHERE XEQERR BLOWUP WAS*)            {22}
	           HLTLINE: INTEGER;    (*MORE DEBUGGER STUFF*)                {24}
	           BRKPTS: ARRAY [0..3] OF INTEGER;                            {26}
	           RETRIES: INTEGER;    (*DRIVERS PUT RETRY COUNTS*)           {34}
	           EXPANSION: ARRAY [0..8] OF INTEGER;                         {36}
	           HIGHTIME,LOWTIME: INTEGER;                                  {56,54}
	           MISCINFO: PACKED RECORD                                     {58}
	                       NOBREAK,STUPID,SLOWTERM,
	                       HASXYCRT,HASLCCRT,HAS8510A,HASCLOCK: BOOLEAN;
	                       USERKIND:(NORMAL, AQUIZ, BOOKER, PQUIZ);
{NP: USERKIND implements a simple disk security mechanism.  If it's NORMAL,
 the user can access disks that have DIRECTORY[0].DFKIND = UNTYPEDFILE, which
 is the normal case.  If it's AQUIZ or PQUIZ, the user can only access disks
 with DIRECTORY[0].DFKIND = SECUREDIR.  If it's BOOKER, both kinds of disks
 can be accessed.

 This supported a computerized quiz system.  Students could access their
 regular student disks, quiz takers could access the special quiz disks, and
 the quiz bookkeeper (BOOKER) could access both kinds. }
	                       WORD_MACH, IS_FLIPT : BOOLEAN
			     END;
	           CRTTYPE: INTEGER;                                           {60}
{NP: CRTTYPE is loaded from SYSTEM.MISCINFO at boot, but nothing ever looks at
 it after that. }
	           CRTCTRL: PACKED RECORD
	                      RLF,NDFS,ERASEEOL,ERASEEOS,HOME,ESCAPE: CHAR;    {62..67}
	                      BACKSPACE: CHAR;                                 {68}
	                      FILLCOUNT: 0..255;                               {69}
                              CLEARSCREEN, CLEARLINE: CHAR;                    {71,70}
                              PREFIXED: PACKED ARRAY [0..15] OF BOOLEAN        {72}
	                    END;
	           CRTINFO: PACKED RECORD
	                      WIDTH,HEIGHT: INTEGER;                           {76,74}
	                      RIGHT,LEFT,DOWN,UP: CHAR;                        {78..81}
	                      BADCH,CHARDEL,STOP,BREAK,FLUSH,EOF: CHAR;        {82..87}
	                      ALTMODE,LINEDEL: CHAR;                           {89,88}
                              ALPHA_LOCK,ETX,PREFIX: CHAR;                     {90..92}
                              PREFIXED: PACKED ARRAY [0..15] OF BOOLEAN        {94}
	                    END;
	           SEGTABLE: ARRAY [SEG_RANGE] OF SEG_ENTRY;                   {96}
	         END (*SYSCOM*);

     MISCINFOREC = RECORD
	             MSYSCOM: SYSCOMREC
	           END;

VAR
    SYSCOM: ^SYSCOMREC;                 (*MAGIC PARAM...SET UP IN BOOT*)       {0}
    GFILES: ARRAY [0..5] OF FIBP;       (*GLOBAL FILES, 0=INPUT, 1=OUTPUT*)    {2}
    USERINFO: INFOREC;                  (*WORK STUFF FOR COMPILER ETC*)        {14}
    EMPTYHEAP: ^INTEGER;                (*HEAP MARK FOR MEM MANAGING*)         {106}
    INPUTFIB,OUTPUTFIB,                 (*CONSOLE FILES...GFILES ARE COPIES*)  {114,112}
    SYSTERM,SWAPFIB: FIBP;              (*CONTROL AND SWAPSPACE FILES*)        {110,108}
    SYVID,DKVID: VID;                   (*SYSUNIT VOLID & DEFAULT VOLID*)      {124,116}
    THEDATE: DATEREC;                   (*TODAY...SET IN FILER OR SIGN ON*)    {132}
    DEBUGINFO: ^INTEGER;                (*DEBUGGERS GLOBAL INFO WHILE RUNIN*)  {134}
    STATE: CMDSTATE;                    (*FOR GETCOMMAND*)                     {136}
    PL: STRING;                         (*PROMPTLINE STRING...SEE PROMPT*)     {138}
    IPOT: ARRAY [0..4] OF INTEGER;      (*INTEGER POWERS OF TEN*)              {220}
    FILLER: STRING[FILL_LEN];           (*NULLS FOR CARRIAGE DELAY*)           {230}
    DIGITS: SET OF '0'..'9';                                                   {242}
    UNITABLE: ARRAY [UNITNUM] OF (*0 NOT USED*)                                {250}
	        RECORD
	          UVID: VID;    (*VOLUME ID FOR UNIT*)
	          CASE UISBLKD: BOOLEAN OF
	            TRUE: (UEOVBLK: INTEGER)
	        END (*UNITABLE*) ;
{NP: Because Apple Pascal 1.2 changed MAXUNIT from 12 to 20, everything after
 this point is 96 bytes farther up in memory starting in 1.2:  The next field
 is at offset 406 in 1.0 and 1.1, and offset 502 in 1.2 and 1.3.  Rather than
 maintain two offset lists, I'll restart the count at 0.}
    FILENAME : FILE_TABLE;                                                     {0}
{NP: THis is the end of the II.0 globals.  The variables listed below were
 added in Apple Pascal.  The functions of these were worked out by
 Dave Tribby, and are described in files on Tribby's PSYS disk, from which
 the following was copied.}
   config_char: SET OF CHAR;    { Configuration chars from MISCINFO }          {120}
   
   chain_name:  STRING[23];     { "Next File" from SETCHAIN }                  {136}
   chain_msg:   STRING;         { "Message" from SETCVAL    }                  {160}
   
   what_f:   ARRAY [1..2] OF INTEGER;  { both elements are the same  }         {242}
                                { 0 until EXEC file opened, then 3370 }
                                { or 4394, depending upon bottom of heap }
   
   exec_ch_num: INTEGER;        { Num of next EXEC char in disk buffer }       {246}
   
   what_g:   ARRAY [1..2] OF INTEGER;                                          {248}
                                { 0 until EXEC file opened, then 511,2 }
   
   r_exec_flg:  BOOLEAN;        { Reading an EXEC file flag }                  {252}
   w_exec_flg:  BOOLEAN;        { Writing an EXEC file flag }                  {254}
   
   swap_on:     BOOLEAN;        { System swapping on?  }                       {256}
   swap_1_on:   BOOLEAN;        { Level 1 swapping on? }                       {258}
   swap_2_on:   BOOLEAN;        { Level 2 swapping on? }                       {260}
   
   just_boot:   BOOLEAN;        { Just booted flag }                           {262}
   
   what_h:   ARRAY [1..2] OF INTEGER;   { 0,0 }                                {264}
                                
   exec_in_ch:  CHAR;           { Last char read from EXEC file }              {268}
   exec_term:   CHAR;           { EXEC file termination character }            {270}
   
   what_i:   ARRAY [1..7] OF INTEGER;                                          {272}
                                { #2: 1 when EXEC file closed, 0 when open }
                                { #3: 1 when EXEC file closed, 0 when open }
                                { #6: 0 when EXEC file closed, 1 when open }
                                { #7: 1 after EXEC file opened }
   
   exec_unit:   INTEGER;        { EXEC volume unit number }                    {286}
   exec_vol:    VID;            { EXEC file volume name }                      {288}
   exec_size:   INTEGER;        { # blocks in EXEC file }                      {296}
   
   what_j:   ARRAY [1..3] OF INTEGER;                                          {298}
                                { #1: 0 when EXEC file closed, 3 when reading, 
                                      2 when writing.                        }
   
   exec_fentry: DIRENTRY;       { Directory entry for EXEC file }              {304}
   
   what_k:   ARRAY [1..11] OF INTEGER;                                         {330}
   
   dline_str:   STRING[7];      { Printed for each char to delete line }       {352}
                                { (usually single char backspace) }
   bspace_str:  STRING[7];      { Printed to backspace and blank out a char }  {360}
                                { (usually bspace, blank, bspace) }
   
   run_vol:     VID;            { Run (%) volume }                             {368}
{NP: run_vol did not exist prior to Apple Pascal 1.2.}
   
   what_l:   ARRAY [1..4] OF INTEGER;                                          {376}
{NP: End of Apple Pascal global variables.}

A {$U-} program can use these directly...just include them in your program's top-level declaration section. Even though SYSTEM.PASCAL won't load the top-level segment, the compiler will see the declarations and compile the correct code to access them.

A normal (non-{$U-}) program cannot directly access these variables—they live at nesting level -1, which a normal program, whose nesting level starts at 0, can't reach. But there are ways that a normal program can get to these indirectly, via some trickery which will be discussed later.

But first some remarks about the variables themselves. These are not all equally interesting or useful...here are some of the highlights.

SYSCOM is probably the most important global variable. It points to the system communcation area, a set of variables shared between SYSTEM.PASCAL and SYSTEM.APPLE. This includes the setup values read in from SYSTEM.MISCINFO, and the vitally-important SEGTABLE, discussed above.

Note that all of the file variables are declared as FIBP. If you want to call normal file operations on them, you can trick Pascal into doing so by replacing the FIBP definition with something like this:

TYPE
  XFILE = FILE {for disk files, or TEXT for character devices};
  FIBP = ^XFILE;

and then, if X is one of the FIBP variables, you can call BLOCKREAD and BLOCKWRITE on X^ if it's a disk file, or READ[LN] and WRITE[LN] if it's a character device.

The USERINFO record contains useful information about the current workfile. If USERINFO.GOTSYM is true, then a text workfile exists and is named CONCAT(USERINFO.SYMVID, ':', USERINFO.SYMTID), and likewise, if USERINFO.GOTCODE is true, then a code workfile exists and is named CONCAT(USERINFO.CODEVID, ':', USERINFO.CODETID). If the workfile has a permanent name (something other than SYSTEM.WRK.TEXT / SYSTEM.WRK.CODE), the permanent name is CONCAT(USERINFO.WORKVID, ':', USERINFO.WORKTID).

The C(ompile and A(seemble commands open the source file as USERINFO.SYMFIBP and the object file as USERINFO.CODEFIBP before running the compiler or assembler. After the compiler or editor exits, if an error occurred, USERINFO.ERRNO is the error number, and its position in the source code is given by USERINFO.ERRBLK and USERINFO.ERRSYM.

The bottom of the system heap is in the pointer EMPTYHEAP. SYSTEM.PASCAL does a MARK(EMPTYHEAP) after it's finished initializing itself, and RELEASE(EMPTYHEAP) whenever a program exits. You can permanently reserve space at the bottom of the heap by increasing EMPTYHEAP (I suspect this is how SYSTEM.ATTACH protects the memory that it loads device drivers into). Of course this must be done before allocating any ordinary (non-permanent) dynamic variables with NEW, and before manipulating EMPTYHEAP, it's a good idea to first call MARK on some pointer variable—this causes the system to discard the disk directory buffer from the heap, if there is one. After changing EMPTYHEAP, calling RELEASE(EMPTYHEAP) sets the heap pointer to EMPTYHEAP, protecting everything underneath it.

Setting EMPTYHEAP := NIL signals SYSTEM.PASCAL to exit from its main loop (after your program exits), rebooting the system.

INPUTFIB, OUTPUTFIB, and SYSTERM are the FIBP versions of the standard files INPUT, OUTPUT, and KEYBOARD. If the file SYSTEM.SWAPDISK exists on the boot disk, it will be opened as SWAPFIBP whenver SYSTEM.PACAL needs to swap out memory to make room for a disk directory.

SYVID contains the name of the boot disk. You can inspect this name, but don't try to change it—this isn't the only place where the system remembers the boot disk, so changing this name can only lead to chaos and misery.

DKVID contains the name of the current prefix disk. You can change the prefix by changing this variable—something normally only possible by exiting and using the Filer's P(refix command.

THEDATE contains the current system date. You can change it, but doing so won't write the new date to the boot disk—normally only the Filer does that.

UNITABLE gives, for each unit, the unit's name (or the disk's name if it's a disk), whether or not it's a block device, and if it's a block device, the number of blocks on it.

FILENAME is an array of strings, giving the full name ("DISK:FILENAME") of the assembler, filer, editor, compiler, and linker, in that order.

If you only care about a few of these variables, you don't have to declare all of them. You can completely omit all the variables after the last you're accessing, and fill the space before the first variable you're looking at with dummy arrays. Of course this means you have to count the sizes of the skipped variables carefully to make the declarations you're interested in land in the right place. For example, if you just want to access the current prefix and nothing else,

VAR
  DUMMY: ARRAY[1..58] OF INTEGER; {Skip over 116 bytes}
  DKVID: STRING[7];

If you want to access the FILENAME array or anything after it, and you don't want your program to depend on a specific version of Apple Pascal, then static declarations won't work for you - you'll need to search for the variables at run time, using techniques like those in the next section.

Accessing SYSTEM.PASCAL's Globals Without {$U-}

If you're not a {$U-} program, you can still access SYSTEM.PASCAL's global variables, but you have to do it indirectly.

First, declare a record to hold the global varlabies. If you want access to the FILENAME array or anything after it, you'll need to delcare two records, due to the fact that the UNITABLE array sits right in the middle, and its length changed in Apple Pascal 1.2.

CONST
  MAXUNIT = 20; { Assume the longer case }
  MAX_SEG = 63; { Assume the longer case }

TYPE
  TFILE = TEXT;    { For treating FIBPs as ordinary files: }
  PTFILE = ^TFILE; { Replace character device FIBPs with this }
  BFILE = FILE;
  PBFILE = ^BFILE; { Replace disk file FIBPs with this }

  GREC1 = RECORD
    SYSCOM: ^SYSCOMREC;
    GFILES: ARRAY[0..5] OF PTFILE;
    USERINFO: INFOREC; { Don't forget this has disk FIBPs in it }
    { etc., but no farther than UNITABLE }
  END;
  GREC2 = RECORD
    FILENAME: FILE_TABLE;
    config_char: SET OF CHAR;
    { etc. }
  END;

VAR
  VERSION: INTEGER; {Apple Pascal version: 0=1.0, 2=1.1, 3=1.2, 4=1.3}
  UNITMAX, SEGMAX: INTEGER; { Actual number of units and segments }
  GBL1P: ^GREC1;
  GBL2P: ^GREC2;

Now we need to find the global variables. This can be done by starting at a convenient markstack record and chasing the static links until you reach the topmost, which can be recognized by the fact that its static link points to itself. The globals start twelve bytes after the topmost markstack record (a markstack record is twelve bytes long).

A good place to start chasing links is your own global markstack record, which is pointed to by the BASE pointer, which lives at memory location 80.

PROCEDURE FINDGLOBALS;
VAR
  TRIX: RECORD CASE INTEGER OF
    0: (I: INTEGER);
    1: (P: ^INTEGER);
    2: (B: ^PACKED ARRAY[0..1] OF 0..255);
    3: (G1: ^GREC1);
    4: (G2: ^GREC2)
  END;
BEGIN
  TRIX.I := -16607; { Version byte }
  VERSION := TRIX.B^[0];
  TRIX.I := 80; { Start at BASE }
  WHILE TRIX.I <> TRIX.P^ DO
    TRIX.I := TRIX.P^; { Chase MSSTAT pointers to top }
  TRIX.I := TRIX.I + 12; { Skip past markstack }
  GBL1P := TRIX.G1;
  TRIX.I := TRIX.I + 406; { 1st part 406 bytes long in 1.0 and 1.1 }
  IF (VERSION = 3) OR (VERSION = 4) THEN
    TRIX.I := TRIX.I + 96; { 96 bytes longer in 1.2 and 1.3 }
  GBL2P := TRIX.G2
  UNITMAX := 12;
  SEGMAX := 31;
  IF (VERSION = 3) OR (VERSION = 4) THEN
    BEGIN
      UNITMAX := 20;
      TRIX.I := -16606;
      IF ORD(ODD(TRIX.B^[0]) AND 96) = 64 THEN {128K}
        SEGMAX := 63;
    END
END;

If you only want to access things in the SYSCOM area, you don't need to go through all the trouble of finding SYSTEM.PASCAL's globals. A pointer to SYSCOM is kept in memory location -16609, so you can just follow that:

VAR
  SYSCOM: ^SYSCOMREC;

PROCEDURE FINDSYSCOM;
VAR
  TRIX: RECORD CASE BOOLEAN OF
    FALSE: (I: INTEGER);
    TRUE: (P: ^SYSCOMREC)
  END;
BEGIN
  TRIX.I := -16609;
  SYSCOM; := TRIX.P
END;

Where's My Variable?

Here's an assembly-language routine that can be used to find the address of any variable of any type. Using this and an appropriate variant record, you can point a pointer variable at any other variable.

; FUNCTION ADDRESSOF(VAR X): INTEGER; EXTERNAL;
; Returns the address of any variable.
; (This really just returns its argument unchanged, and is about the
; closest you can get to a generic identity function.)
        .FUNC   ADDRESSOF,1
        PLA             ;Save return address in X, Y
        TAX
        PLA
        TAY
        PLA             ;Discard passed result space
        PLA
        PLA
        PLA
        TYA             ;Restore return address
        PHA             ;(leaving argument as result)
        TXA
        PHA
        RTS
        .END

Then, in your main program, declare (as the comment indicates) PUNCTION ADDRESSOF(VAR X): INTEGER; EXTERNAL;, and link the assembled routine to your main code.

(The Apple Pascal 1.3 manual lists almost the same routine, but this version, which uses the X and Y registers to save the return address instead of a pair of zero-page addresses, is slightly shorter and faster. Honestly, looking at the manual's assembly-language examples, I sometimes think the people who wrote them forgot that the 6502 has registers.)

Example usage:

{Assuming FIB and FIBP are declared as shown above}
VAR
  TRIX: RECORD CASE BOOLEAN OF
    FALSE: (I: INTEGER);
    TRUE: (F: FIBP)
  END;
  F: FIBP;

{...}

  TRIX.I := ADDRESSOF(INPUT);
  F := TRIX.F;

Then you can use F^ to see what's going on inside the standard file INPUT.

All About MISCINFO

The MISCINFO, CRTTYPE, CRTCTRL, and CRTINFO fields of SYSCOM are filled at boot time from the file SYSTEM.MISCINFO. This file is large enough to contain the entire SYSCOM area (on the 64K system with MAX_SEG = 31), but only the four fields just named are used—the rest of the file is ignored.

Here are all the values read from SYSTEM.MISCINFO, their byte positions in the file (and offsets from the start of SYSCOM), and for fields that have them, their names in the SETUP.CODE utility.

Byte[:bit] Identifier SETUP Name Normal value Meaning
58:0 MISCINFO.HASCLOCK HAS CLOCK FALSE TRUE if a clock is available. If FALSE, SYSTEM.PASCAL always checks whether the right disk is still in the right drive before writing a directory entry. If TRUE, it skips this check if less that 5 seconds have passed since the last check (the DLOADTIME field of the disk's directory is used to remember the last check time).
58:1 MISCINFO.HAS8510A HAS 8510A FALSE TRUE if Terak 8510/a-compatible video hardware is available. (The Terak 8510/a was a PDP-11-based computer of the late 1970's and early 1980's.)
58:2 MISCINFO.HASLCCRT HAS LOWER CASE TRUE if lowercase is available.
58:3 MISCINFO.HASXYCRT HAS RANDOM CURSOR ADDRESSING TRUE TRUE if a working GOTOXY is available.
58:4 MISCINFO.SLOWTERM HAS SLOW TERMINAL FALSE Should be TRUE if the display operates at 600 baud or slower. Causes system prompts to be abbreviated. No longer functional starting in Apple Pascal 1.2.
58:5 MISCINFO.STUPID STUDENT FALSE If TRUE, always enter the editor after the compiler detects an error in the source code, without offering the user any choice. In UCSD Pascal II.0, it also caused the compiler to insert USES TURTLEGRAPHICS into every program.
58:6 MISCINFO.NOBREAK (none) FALSE If TRUE, prevent the BREAK key (ctrl-@) from working. Doesn't seem to be functional on the Apple II. This bit was very version-dependent even in UCSD Pascal II.0—it worked in the PDP-11 version, but not the 8080 version.
58:7 (lo) & 59:0 (hi) MISCINFO.USERKIND (none) 0 (USER) 0 (USER) for normal users, 1 (AQUIZ) or 3 (PQUIZ) for quiz system users, or 2 (BOOKER) for quiz system bookkeepers. Once supported an electronic quiz system, where normal users and quiz users couldn't access each other's disks, but the quiz bookkeeper could access any disk. If 1 (AQUIZ), the system reboots after the startup program terminates.
59:1 MISCINFO.IS_FLIPT (none) FALSE If TRUE, the system error message routine assumes the machine has big-endian byte order.
59:2 MISCINFO.WORD_MACH (none) FALSE Apparently unused.
60 (lo) & 61 (hi) CRTTYPE (none) Reserved (but not actually used) for a number identifying the type of terminal in use.
62 CRTCTRL.ESCAPE LEAD-IN TO SCREEN CHR(0), ^@ If there are two-character screen control codes, the first character of the sequence.
63 CRTCTRL.HOME MOVE CURSOR HOME CHR(25), ^Y Control code to move the cursor to the upper left corner of the screen.
64 CRTCTRL.ERASEEOS ERASE TO END OF SCREEN CHR(11), ^K Control code to clear from the cursor position to the end of the screen.
65 CRTCTRL.ERASEEOL ERASE TO END OF LINE CHR(29), ^] Control code to clear from the cursor position to the end of the current line.
66 CRTCTRL.NDFS MOVE CURSOR RIGHT CHR(28), ^\ Control code to move the cursor one character to the right, non-destructively.
67 CRTCTRL.RLF MOVE CURSOR UP CHR(31), ^_ Control code to move the cursor one line up.
68 CRTCTRL.BACKSPACE BACKSPACE CHR(8), ^H Control code to move the cursor one character to the left, non-destructively.
69 CRTCTRL.FILLCOUNT VERTICAL MOVE DELAY 0 Number of nulls to send for delay purposes after a vertical move.
70 CRTCTRL.CLEARLINE ERASE LINE CHR(0), ^@ Control code to clear the entire line the cursor is on.
71 CRTCTRL.CLEARSCREEN ERASE SCREEN CHR(12), ^L Control code to clear the entire screen.
72:0 CRTCTRL.PREFIXED[0] PREFIXED[MOVE CURSOR UP] FALSE If TRUE, send CRTCTRL.ESCAPE before CRTCTRL.RLF.
72:1 CRTCTRL.PREFIXED[1] PREFIXED[MOVE CURSOR RIGHT] FALSE If TRUE, send CRTCTRL.ESCAPE before CRTCTRL.NDFS.
72:2 CRTCTRL.PREFIXED[2] PREFIXED[ERASE TO END OF LINE] FALSE If TRUE, send CRTCTRL.ESCAPE before CRTCTRL.ERASEEOL.
72:3 CRTCTRL.PREFIXED[3] PREFIXED[ERASE TO END OF SCREEN] FALSE If TRUE, send CRTCTRL.ESCAPE before CRTCTRL.ERASEEOS.
72:4 CRTCTRL.PREFIXED[4] PREFIXED[MOVE CURSOR HOME] FALSE If TRUE, send CRTCTRL.ESCAPE before CRTCTRL.HOME.
72:5 CRTCTRL.PREFIXED[5] PREFIXED[DELETE CHARACTER] FALSE If TRUE, send CRTCTRL.ESCAPE before deleting a character. But no "delete a character" code is defined in SYSCOM, and this bit appears not to be used anywhere.
72:6 CRTCTRL.PREFIXED[6] PREFIXED[ERASE SCREEN] FALSE If TRUE, send CRTCTRL.ESCAPE before CRTCTRL.CLEARSCREEN.
72:7 CRTCTRL.PREFIXED[7] PREFIXED[ERASE LINE] FALSE If TRUE, send CRTCTRL.ESCAPE before CRTCTRL.CLEARLINE.
74 (lo) & 75 (hi) CRTINFO.HEIGHT SCREEN HEIGHT 24 The height of the screen.
76 (lo) & 77 (hi) CRTINFO.WIDTH SCREEN WIDTH 79 or 80 The width of the screen.
78 CRTINFO.UP KEY TO MOVE CURSOR UP CHR(15), ^O; or CHR(11), ^K Key to move up a line in the editor.
79 CRTINFO.DOWN KEY TO MOVE CURSOR DOWN CHR(12), ^L; or CHR(10), ^J Key to move down a line in the editor.
80 CRTINFO.LEFT KEY TO MOVE CURSOR LEFT CHR(8), ^H Key to move one space left in the editor.
81 CRTINFO.RIGHT KEY TO MOVE CURSOR RIGHT CHR(21), ^U Key to move one space right in the editor.
82 CRTINFO.EOF KEY TO END FILE CHR(3), ^C Key to indicate end-of-file on the console.
83 CRTINFO.FLUSH KEY FOR FLUSH CHR(6), ^F Key to cancel console output.
84 CRTINFO.BREAK KEY FOR BREAK CHR(0), ^@ Key to terminate the current program.
85 CRTINFO.STOP KEY FOR STOP CHR(19), ^S Key to suspend/resume console output.
86 CRTINFO.CHARDEL KEY TO DELETE CHARACTER CHR(8), ^H Key to backspace.
87 CRTINFO.BADCH NON-PRINTING CHARACTER ? Character to print instead of non-printing characters. Appears to be unused.
88 CRTINFO.LINEDEL KEY TO DELETE LINE CHR(24), ^X Key to erase current input line.
89 CRTINFO.ALTMODE EDITOR "ESCAPE" KEY CHR(27), ESC Key to cancel actions in editor.
90 CRTINFO.PREFIX LEAD-IN FROM KEYBOARD CHR(0), ^@ If keys send two-byte sequences, lead-in character.
91 CRTINFO.ETX EDITOR "ACCEPT" KEY CHR(3), ^C Key to accept actions in editor.
92 CRTINFO.ALPHA_LOCK (none) Apparently unused.
94:0 CRTINFO.PREFIXED[0] PREFIXED[KEY FOR MOVING CURSOR RIGHT] FALSE If TRUE, CRTINFO.RIGHT key sends CRTINFO.PREFIX code first.
94:1 CRTINFO.PREFIXED[1] PREFIXED[KEY FOR MOVING CURSOR LEFT] FALSE If TRUE, CRTINFO.LEFT key sends CRTINFO.PREFIX code first.
94:2 CRTINFO.PREFIXED[2] PREFIXED[KEY FOR MOVING CURSOR DOWN] FALSE If TRUE, CRTINFO.DOWN key sends CRTINFO.PREFIX code first.
94:3 CRTINFO.PREFIXED[3] PREFIXED[KEY FOR MOVING CURSOR UP] FALSE If TRUE, CRTINFO.UP key sends CRTINFO.PREFIX code first.
94:4 CRTINFO.PREFIXED[4] PREFIXED[NON-PRINTING CHARACTER] FALSE If TRUE, CRTINFO.BADCH sends CRTINFO.PREFIX code first.
94:6 CRTINFO.PREFIXED[6] PREFIXED[KEY FOR STOP] FALSE If TRUE, CRTINFO.STOP key sends CRTINFO.PREFIX code first.
94:7 CRTINFO.PREFIXED[7] PREFIXED[KEY FOR BREAK] FALSE If TRUE, CRTINFO.BREAK key sends CRTINFO.PREFIX code first.
95:0 CRTINFO.PREFIXED[8] PREFIXED[KEY FOR FLUSH] FALSE If TRUE, CRTINFO.FLUSH key sends CRTINFO.PREFIX code first.
95:1 CRTINFO.PREFIXED[9] PREFIXED[KEY TO END FILE] FALSE If TRUE, CRTINFO.EOF key sends CRTINFO.PREFIX code first.
95:2 CRTINFO.PREFIXED[10] PREFIXED[EDITOR 'ESCAPE' KEY] FALSE If TRUE, CRTINFO.ALTMODE key sends CRTINFO.PREFIX code first.
95:3 CRTINFO.PREFIXED[11] PREFIXED[KEY TO DELETE LINE] FALSE If TRUE, CRTINFO.LINEDEL key sends CRTINFO.PREFIX code first.
95:4 CRTINFO.PREFIXED[12] PREFIXED[KEY TO DELETE CHARACTER] FALSE If TRUE, CRTINFO.CHARDEL key sends CRTINFO.PREFIX code first.
95:5 CRTINFO.PREFIXED[13] PREFIXED[EDITOR "ACCEPT" KEY] FALSE If TRUE, CRTINFO.ETX key sends CRTINFO.PREFIX code first.

Downloads

LLX > Neil Parker > Apple II > Pascal Secrets

Original: September 13, 2021