XPLPX.TXT       31-Mar-2020

                       OPTIMIZING 32-BIT COMPILER
                              Version 3.5

XPLPX combines the features of XPLX (the optimized 16-bit version) with
XPLP (the non-optimized 32-bit protected-mode version) to provide: 32-bit
integers that run as fast as the fastest 16-bit code, much faster
floating point, megabytes of array space, large .exe files up to 600K,
protected mode with and without Windows, and extended graphic capability.

VESA graphic modes are supported in a straightforward way. For instance,
SetVid($112) sets the display for 640x480x24 - that's 16.8 million
colors. The Point intrinsic can completely fill this screen 20 times per
second (on a Duron 850, and with an nVidia card using a linear frame
buffer the screen can be filled 60 times per second.)

32-bit integer operations are about twice as fast as with XPLP. Floating
point operations (reals) are up to ten times faster than with XPLX (even
when linking with NATIVE7X). Point plotting is a remarkable one hundred
times faster than with XPLP.


COMPATIBILITY

The most common compatibility issues when converting programs from 16 to
32 bits are: 1) integer arrays set up with the Reserve intrinsic need to
double the number of bytes; 2) segment arrays must be converted to
regular arrays, as explained below; 3) the HexOut intrinsic displays
eight digits instead of four; 4) short reals need to be converted to
regular (8-byte) reals.

These intrinsics are no longer supported: POut (64), PIn (65), IntRet
(66), ExtJmp (67), ExtCal (68), Equip (77), and Irq (80). The POut and
PIn intrinsics can be replaced with the more intuitive 'port' command (at
least for single-byte ports). The IntRet, ExtJmp, ExtCal, and Irq
intrinsics can be replaced with in-line assembly code, as explained
below. The Equip intrinsic is obsolete for any modern computer.

The Line intrinsic (42) is significantly slower in 32-bit XPL0 for planar
graphic modes (such as mode $12) than it is in 16-bit XPL0. The built-in
high-speed routines in 16-bit XPL0 required too much code to support the
myriad graphic modes, many of which are now obsolete. Non-planar modes
(such as $101) are not only faster, but they provide at least 256 colors.

External assembly language routines must not alter the EDI and ESI
registers. Under 16-bit XPL0 these registers (DI and SI) were
automatically saved and restored across any external assembly language
call. Now, for speed's sake (and for other reasons), it's the
programmer's responsibility to make sure that these registers are
preserved.

Note that segment registers cannot be used in protected mode like they
are used in real mode. In protected mode they're selectors into
descriptor tables. XPLPX sets up the FS register to select a descriptor
with a base address of 0 so it can be used to access absolute locations
such as BIOS variables and video RAM.

XPX.BAT uses short-circuit boolean evaluation (the /B switch is used).
This is faster than the traditional evaluation, but it can change the way
a program runs in some rare situations.

The Reserve intrinsic now rounds the reserved space up to the next
double-word boundary (i.e. the next size evenly divisible by 4). This
guarantees that subsequent (local) variables will be aligned on
double-word boundaries, which can double the speed that they are
accessed. However, this also means that sequential calls of the Reserve
intrinsic might not allocate contiguous memory locations. It's very
unlikely that this will affect any existing code, but it could.

Since the heap is located in extended memory, normal arrays cannot be
accessed by BIOS or DOS routines. Use the MAlloc intrinsic to set up an
array in conventional memory.

Array space under Windows 98 can end up in virtualized memory (i.e. on
the hard drive). This can slow a program tremendously the first time this
array space is accessed.

The 32-bit version of the HexIn intrinsic (26) works slightly differently
than the 16-bit version. The 32-bit version returns after 8 hex digits
whether or not the string of digits is terminated with a non-hex
character (such as a space). The 16-bit version only returns when the
digits are terminated. Thus the value returned for 16-bit XPL0 is the
last 4 digits if more than 4 digits are input, and the value returned for
32-bit XPL0 is the first 8 digits if more than 8 digits are input.

The Read (31) and Write (30) intrinsics use the large I/O buffers, which
can interfere with device 3. The Read intrinsic uses the input buffer,
and the Write intrinsic uses the output buffer. These buffers are used
because the DOS routines used to do the actual disk reads and writes
cannot access memory above 1MB. A further consequence of this is that
each sector is buffered, which makes reading and writing multiple sectors
somewhat slower than with 16-bit XPL0. Windows XP only allows read or
write access to floppy drives. (It should go without saying that writing
directly to the hard drive using the Write intrinsic is a VERY dangerous
operation.)


GRAPHICS

VESA (Video Electronics Standards Association) provides dramatic new
graphic capabilities. On some computers DOS and BIOS routines don't
display characters when a VESA graphic mode is enabled. To provide this
essential capability, XPL0's device 6 displays them when the video mode
is greater than $0FF. Devices 0 and 1 still call DOS and BIOS routines to
display a character like they have always done, but be aware that this
does not always work for VESA modes.

Device 6 enables characters to be positioned with the Move intrinsic in
addition to the Cursor intrinsic. When the Move intrinsic is used, text
is not limited to being on character cell boundaries. An 8x16 font is
used. On a 640x480 pixel screen this displays 30 lines of 80 characters.
Device $106 displays an 8x8 font, and device $206 displays an 8x14 font.

The Attrib intrinsic supports the increased number of colors available
with VESA. The location of the bits that set the background and
foreground colors depend on the number of colors provided by the video
mode. For modes with 16 or fewer colors the high nibble sets the
background color, and the low nibble sets the foreground color. For 8-bit
color modes (such as $13 and $101) the high byte sets the background
color, and the low byte sets the foreground color. For 15- and 16-bit
color modes the high word sets the background color, and the low word
sets the foreground color. For 24-bit color modes the background color is
black, and the foreground color is the value of the argument.

If the foreground and background colors are the same then the character
is drawn using only the foreground color. This makes the character's
background transparent, which enables overstrike cabability as well as
other useful features.

VESA uses a windowing technique to fit the its larger graphic displays
into the 64K page (at $A0000) provided by the PC. Beware of thrashing
this window. For instance, a Mandelbrot program using mode $101 was made
many times faster by NOT mirroring the symmetric top and bottom halves of
the image. For animations, it's best to use an image buffer in memory,
and then copy it to display memory using the Paint intrinsic (explained
below).

The (interrupt $33) BIOS routines for displaying a mouse pointer in VGA
modes don't work for VESA modes. New intrinsics provide the capability.
VMouse.xpl shows how to use these new intrinsics.

When using graphic modes, it's no longer necessary to execute SetVid(3)
to return to text mode before exiting a program. This is because text
mode is now set automatically, if necessary, after waiting for a final
keystroke.


NEW INTRINSICS

Perhaps the most obvious new feature is that intrinsic routines are
automatically declared. It's no longer necessary to type "include
c:\cxpl\codes;" at the beginning of every program. These 'code'
declarations are made at level -2 so they can be superseded by any
declarations written in the traditional manner.

Some intrinsics can be replaced by new command words that perform their
operations significantly faster, since they use in-line code instead of
calling a routine. They are: 'fix', 'float', 'sqrt', 'sq', 'abs', and
'port'. Also, unlike intrinsic calls, they work in 'define' declarations;
and 'abs' 'sqrt' and 'sq' work with both real and integer arguments.

Here are the intrinsics added to make this DOS-based version more
consitent with other versions of XPL0:

64. Floor(real): This intrinsic rounds down to a (real) integer value.
For example, Floor(3.9) returns 3.0.

65. Ceil(real): This intrinsic rounds up to a (real) integer value.
For example, Ceil(3.2) returns 4.0, and Ceil(-3.9) returns -3.0.

66. Pow(realX, realY): This intrinsic returns X raised to the Y power. The
first argument (realX) must be greater than 0.0. For example, Pow(2.0,
10.0) returns 1024.0, Pow(2.0, -3.0) returns 0.125, and Pow(2.0, 0.5)
returns 1.414....

77: ShowMouse(boolean): This intrinsic turns the display of the mouse
pointer on and off. The mouse pointer defaults to being off. This works
for all graphic modes including VESA.

78: MoveMouse: This intrinsic moves the displayed mouse pointer to track
the position of the actual mouse. (Automatic tracking using an interrupt
is not available.)

80. rgb:= GetPalette(register): This intrinsic returns the red, green,
and blue intensities for the specified color register. The palette
determines the colors displayed for video modes with a depth of eight
bits or fewer. There are 256 color registers, thus "register" varies from
0 to 255. The returned "rgb" value contains the blue intensity in the low
byte, the green in the next higher byte, and the red in the byte above
that. Intensities range from 0 to 255 in this format: $00RRGGBB. The low
two bits of each intensity are not used by the hardware.

81. Paint(X, Y, W, H, Image, W2): This intrinsic quickly copies the array
Image to video memory. A detailed explanation is below.

82. time:= GetTime: This intrinsic returns the current time in
microseconds. The unsigned 32-bit integer rolls over about every hour and
11 minutes. GetTime is often used to measure the duration between two
events. If this duration is less than half an hour, you needn't be
concerned about the rollover. The duration is simply the time of the
second event minus the time of the first event. Any rollovers are
automatically handled.

	int T0, I;
	begin
	T0:= GetTime;          \How long for a billion?
	for I:= 1 to 1_000_000_000 do \nothing\;
	RlOut(0, float(GetTime-T0) / 1e6);
	Text(0, " seconds^J");
	end;

83. BackUp: This intrinsic enables the last byte read from any input
device to be reread (like C's ungetc function). It's handy, for instance,
in the situation where a user can step through a series of numbers with
the Enter (or Tab) key and change a number merely by typing its new
value. In the example below BackUp enables IntIn to be used if the user
typed a digit.

        int Number, Digit;
        begin
        Number:= 123;                  \default value
        IntOut(0, Number);  CrLf(0);
        Digit:= ChIn(0);
        if Digit>=^0 & Digit<=^9 then   \change it
                [BackUp;  Number:= IntIn(0)];
        IntOut(0, Number);  CrLf(0);    \confirm result
        end;

84: SetHexDigits(digits): This intrinsic sets the number of digits
displayed by the HexOut intrinsic (27). The default is eight digits, but
it can be changed to fewer. If a number being output is too big for the
number of digits specified, all digits are still output. For example:

	HexOut(0, $12);		\displays 00000012
	SetHexDigits(3);
	HexOut(0, $12);		\displays 012
	HexOut(0, $1234);	\displays 1234

85: boolean:= OpenMouse: This intrinsic initializes the mouse and sets
its position to the center of the screen, although its pointer remains
hidden. This intrinsic returns 'false' if a mouse driver is not detected.
This intrinsic is called by the SetVid intrinsic (45).

86: address:= GetMouse: This intrinsic returns the address of an integer
array that contains the state of the mouse. The integers are:

	0: X position (from left edge of screen, in pixels)
	1: Y position (down from top of screen, in pixels)
	2: buttons: bit 0 set = left button down
                    bit 1 set = right button down
                    bit 2 set = middle button down (if supported)

For example, if Address(2) = 3, it means that both the left and right
buttons are currently being pressed.

87: address:= GetMouseMove: This intrinsic returns the address of an
integer array that contains the distance that the mouse moved since the
previous call to this intrinsic. The integers are:

        0: change in X position (in pixels)
        1: change in Y position (in pixels)

88: ShowCursor(boolean): This intrinsic turns the flashing cursor off or
on. The flashing cursor defaults to being on for text display modes and
off for graphic display modes, which do not support a flashing cursor.
The flashing cursor is automatically turned back on when a program exits.

90: SetPalette(register, red, green, blue): This intrinsic changes a
color in the 256-color palette that's used for 8-bit (or less) depth
graphics. The red, green and blue values range from 0 through 255, but
only the high six bits are actually used by the hardware. An 8-bit (or
less) graphic mode must already be set up with SetVid (45).

91. address:= GetFont(set): This intrinsic returns the address of a
256-character font table. If set = 0 then "address" points to the 8x16
table, which has 16 bytes per character. If set = 1 then the address of
the 8x8 table is returned, and if set = 2 the address of the 8x14 table
is returned. One use for accessing these font tables is to make banner
programs with giant letters.

92. SetFont(height, address): This changes the character font table used
by device 6. "Height" is the number of bytes per character, which is also
the height of a character cell in pixels. Character cells are always 8
pixels wide. "Address" is the location of the replacement table (which
must be in the first megabyte of memory, as with a constant array, not in
upper memory, as with a normal data array). This intrinsic should be
called immediately after calling SetVid. There is no way to change the
font used by devices $106 and $206.

93: bits:= GetShiftKeys: This intrinsic returns the current state of some
keyboard keys. If bits = 1 then the right Shift key is held down. If bits
= 2 then the left Shift key is down. If bits = $03 then both Shift keys
are down. These are the bit assignments:

	0  right Shift is down
	1  left Shift key is down
	2  Ctrl key is down
	3  Alt key is down
	4  Scroll Lock is on
	5  Num Lock is on
	6  Caps Lock is on
	7  Insert is on

94: DelayUS(duration): This intrinsic delays "duration" microseconds. For
example, DelayUS (54945) delays about 1/18 of a second, which is the
duration of a IBM-PC system clock tick.

95. address:= GetDateTime: This returns the address of a byte (char)
array containing the current system date and time. The bytes are:

        0: year since 1900
        1: month (1..12)
        2: day (1..31)
        3: hour (0..23)
        4: minute (0..59)
        5: second (0..59)
        6: hundredths of seconds (0..99)
        7: day of week (0=Sun, 1=Mon, ... 6=Sat)

96. InsertKey(char): This inserts a character into the keyboard's input
buffer. The next key read from ChIn(1) will be the inserted character.
Several characters can be inserted before being read out in sequence by
ChIn(1). Keys can also be read out with ChIn(0), but there may already be
other characters ahead of the inserted keys in device 0's buffer. This
intrinsic is useful for GUI buttons and menu items that use shortcut
keys.

97. address:= GetFB: This returns the address of an integer array that
contains information about the video mode $13 frame buffer. (Other video
modes are not available.) The integers are:

        0: 320 (width in pixels)
        1: 200 (height in pixels)
        2: 8 (depth in bits per pixel)
        3: frame buffer's memory address (minus the data segment value)
        4: graphic pen horizontal position (start for Line intrinsic)
        5: graphic pen vertical position

The frame buffer address provides direct access to the display memory.
For example, this code will draw a very fast diagonal yellow line:

	int I;
	char FB;
	begin
	SetVid($13);
	I:= GetFB;
	FB:= I(3);
	for I:= 0 to 200-1 do
		FB(I*321):= $0E;
	end;

98: WaitForVSync: This intrinsic waits for the vertical sync signal,
which ususally occurs every 1/60th of a second at the beginning of each
video frame. This is useful for preventing flicker in animations.

100. CopyMem(dst, src, size): This copies an array of bytes from the
address in "src" to the address in "dst". It's equivalent to, but as much
as 20 times faster than: for I:= 0 to Size-1 do Dst(I):= Src(I); It also
works if the source and destination areas overlap.

101. FillMem(addr, value, size): This fills an array of bytes. It's
equivalent to, but as much as 20 times faster than this equivalent code:
for I:= 0 to Size-1 do Addr(I):= Value;


PAINT INTRINSIC

The Paint intrinsic (81) quickly copies an image to video memory - useful
for animations. Paint works for all video modes, except modes with 16
colors or less (planar modes: $0D, $12, $102, $104, and $106).

Paint passes six integer arguments in this format:

        Paint(X, Y, W, H, Image, W2);

X,Y are the coordinates where the upper-left corner of the Image data
will be displayed on the screen. W,H are the width and height (in pixels)
of the portion of the Image data that gets displayed. "Image" is the
address of the Image data array. W2 is the actual width (in pixels) of
the Image data array.

Normally the image data array is equal to the width and height defined by
W and H, and thus W2 and W are set to the same value. However, the
apparently redundant argument W2, provides some flexibility. X, Y, W and
H define a window on the screen. But a second window into the Image data
array can also be defined.

For example, if an offset (X2,Y2) is added to Image (such as: Image + X2
+ Y2*W2) then the Image data will appear to move left and up (by X2,Y2)
inside the major window defined by X,Y,W,H. In this case W2 would be
greater than W. Also the Image data array would have additional data
extending its actual height beyond the height defined by H.

For the 8-bit color modes ($13, $100, $101, $103, $105, $107) the Image
array is reserved as a byte array, for example: char Image(640*480).

For the 15 and 16-bit color modes ($10D, $10E, $110, $111, $113, $114,
$116, $117, $119, $11A) the size of the Image array must be doubled, for
example: char Image(640*480*2). You (the programmer) must combine a pair
of bytes for each pixel, and deal with the way the hardware packs the
colors into the resulting 16-bit word. For instance, mode $111 packs the
bits like this:

        bit:    F E D C B A 9 8  7 6 5 4 3 2 1 0
        color:  R R R R R G G G  G G G B B B B B

While the 15-bit color mode $110 packs the bits like this:

        bit:    F E D C B A 9 8  7 6 5 4 3 2 1 0
        color:  - R R R R R G G  G G G B B B B B

In 24-bit color modes ($10F, $112, $115, $118, $11B) the image is
reserved as an integer array, for example: int Image(640*480). The order
of the colors in the 4-byte integer is: $xxRRGGBB. The high byte is
currently unused.


SEGMENTS

Segment addressing is supported by the 16-bit versions of XPL0. However,
since 32-bit XPL can directly access up to 4GB of memory, and since
segment addressing is a confusing kludge, there is little incentive to
continue supporting it, and it's not done in this 32-bit version.

In protected mode the CPU does not do segment addressing. Instead it
fakes it by using "descriptor tables". XPLPX could have faked it too, for
compatibility's sake, by automatically converting segment addresses to
linear addresses. But since there are so few users of XPL0, the luxury
was taken to drop the burden of supporting this obsolete feature.

Fortunately it's not difficult to modify existing 16-bit XPL0 code to
eliminate segment arrays, and the resulting code is usually simpler. For
example:

   seg char Buffer(1);
   begin
   Buffer(0):= MAlloc(1024);    \1024 paragraphs of 16 bytes each
   for I:= 0, 16383 do Buffer(0, I):= 0;
   . . .

can be simplified using a normal one-dimensional array:

   char Buffer(16384);
   begin
   for I:= 0, 16383 do Buffer(I):= 0;
   . . .

When using physical addresses, the situation is a little uglier. For
example:

   seg char Screen(1);
   begin
   Screen(0):= $A000;
   for I:= 0, 64000-1 do Screen(0, I):= 0;
   . . .

must be handled something like this:

   char Screen;
   begin
   CpuReg:= GetReg;
   DSeg:= CpuReg(12);
   Screen:= ($A000 - DSeg) << 4;
   for I:= 0, 64000-1 do Screen(I):= 0;
   . . .

The CPU automatically adds the base address of its data (DSeg) to every
address it accesses. Thus to access a particular physical location, DSeg
must be subtracted to compensate. Since DSeg is a segment (or paragraph)
address, it must be multiplied by 16 to convert it to an actual (byte)
address. In the example above, $A000 is the segment address of the start
of the graphic memory for the video display.

The Peek, Poke, and Blit intrinsics, which use segment addresses in their
arguments, still work the way they have always worked. Thus:

   char Memory;
   begin
   CpuReg:= GetReg;
   DSeg:= CpuReg(12);
   Memory:= $400 - DSeg;
   Byte:= Memory(0);
   . . .

which reads the first BIOS variable at location 0040:0000, can simply be
replaced with:

   Byte:= Peek($40, 0);


Even though segment addressing has been dropped, the MAlloc and Release
intrinsics are still used. They are needed in some instances to provide
chunks of memory when DOS or BIOS are called with the SoftInt intrinsic.
DOS and BIOS run in real mode and can only access memory in the first
megabyte. XPLPX's array space is normally above this, in extended memory.
(Exceptions are constant arrays and strings in quote marks, which reside
in the first megabyte - conventional memory.)


IN-LINE ASSEMBLY CODE

XPL0 can handle assembly code that is inserted directly into an XPL
program file. The command word 'asm' designates that the following
characters on the line are assembly code, and they are to be copied
directly to the output (.ASM) file. For example:

        asm     cli
        asm     mov     eax, 102         ;comment
        asm     mov     ebx, Frog        ;Comment

Assembly code must be written in lowercase characters except when an XPL0
variable or constant name is used. These are written the usual way with
at least the first letter capitalized. This enables the compiler to
distinguish them from the rest of the assembly code and to substitute
them with their assembly code representation. For instance, in the above
example, "Frog" might be replaced with something like "[ESI+4]". Capital
letters may be used in a comment set off with a semicolon because
comments are ignored by the compiler.

If several lines of assembly code are used, they can be written this way:

        asm {   cli
                mov     eax, 102         ;comment
                mov     ebx, Frog        ;Comment
            }

The ability to insert assembly code into a high-level language program
is a two-edged sword. In general it should be avoided, but there are
instances when it's very useful.

The most obvious application is to replace compiled code with more
efficient assembly code. For instance "Irq(false)" can be replaced with
"asm cli", which is at least ten times faster (except under Windows XP,
which simulates the cli). Similarly, "POut(Time, $40, 1)" could be
replaced with:

        asm     mov eax, Time
        asm     out 40h, ax


Assembly language provides low-level control that a high-level language
can't. Consider this expression: Frog * 777777 / 1000000. If Frog is
greater than 2761, the calculation will overflow. However the following
code will not overflow even when Frog is 1600000000:

        asm {   mov     eax, 777777
                imul    Frog            ;eax:edx := eax * Frog
                mov     ecx, 1000000
                idiv    ecx             ;eax := eax:edx / ecx
            }

Here is an example of a double-precision accumulator:

        TimeLo:= TimeLo + 143;
        asm     {jnc    tm10
                 inc    TimeHi
                tm10:};


With the power of assembly language, it's easy to shoot yourself in the
foot. When using XPLPX, the ESI and EDI registers must not be altered,
and of course altering the descriptor selectors ES, DS, SS or CS is not
allowed.

The compiler generates the correct code for named constants such as: "def
Frog=123; asm mov eax, Frog". It also generates the correct code for
variables except if the variable is at an intermediate level (neither
local nor global). In that case the inserted assembly code must make sure
that the correct BASEn is loaded into the EBP register.


ASSEMBLY LANGUAGE EXTERNALS

Long sections of code are best handled by using a separate file. Here's
an example of how to write an external assembly language routine in
32-bit protected mode:

		.386p			;enable 386 CPU/FPU instructions
	cseg	segment para public use32 'code' ;matches compiled code
		assume	cs:cseg, ds:dseg
		public	Add3		;to be linked to external module
	
	Add3:	mov	eax, three	;Ans:= Add3(N);
		add	[esp+4], eax	;add 3 to N
		ret			;arguments and result passed on stack
	
	dseg	segment	para public use32 'data' ;match compiled code
		align	4		;aligned data are faster
	three	dd	3
	dseg	ends
	
	cseg	ends
		end

XPL code that calls the external assembly routine:

	ext Add3;	\declare Add3 as external assembly code
	int I, N;
	[I:= 4;
	I:= Add3(I);	\use like any other procedure or function
	IntOut(0, I);	\displays 7
	];


PMODE

PMODE is the DOS extender that makes XPLPX possible. It's a free piece of
software available on the Internet and written by Tran (aka: Thomas
Pytel). Thanks Tran! Wherever you are.

PMODE is basically a DOS Protected Mode Interface (DPMI) like used in
Windows 98. PMODE 3.07 uses a multilevel design that provides whatever
is lacking in a system. If an XPLPX program is running under Windows then
PMODE does very little: It switches into 32-bit protected mode and hands
control over to the DPMI in Windows. If however the system only has
HIMEM.SYS then PMODE uses it to allocate extended memory and provides the
other functions necessary to complete the DPMI.

These are the levels that PMODE supports. Each level includes the
functions of the levels below it:

        DPMI    WINDOWS
        VCPI    EMM386
        XMS     HIMEM
        Raw     DOS

This design makes the DOS extender compatible with any system.

A DPMI provides a set of routines that are called by software interrupt
31h, similar to the DOS interrupt 21h routines. These routines manage
memory, manipulate interrupt vectors, set up protected-mode descriptors,
and provide access to DOS and BIOS routines that pass segment registers.

Switching to protected mode provides 32-bit operations and up to 4GB of
address space, but it also cuts a program off from the outside world. I/O
is often done through DOS and BIOS, which call device handlers that run
in 16-bit real mode. The DPMI provides access to these routines by
switching back and forth between real and protected mode.

The DPMI also deals with a problem that occurs within an 1/18th of a
second after switching into protected mode: the system timer hardware
interrupt. Since the real-mode interrupt vectors are no longer used,
protected-mode vectors are provided. These point to routines that switch
to real mode, call the appropriate interrupt service routine, switch back
to protected mode, and then return.


MEMORY MAP

0000:   Vectors, BIOS variables, DOS, TSRs, etc.

(Location that the .exe loader decides to load your XPL0 program)
????:   PSP (256 bytes) (Contains file name typed on command line, if used)
        PMODE code (PMODE_TEXT) DOS Extender
        NATIVEPX code (CSEG) Runtime support code for XPL0 (intrinsics etc.)
        Compiled .XPL code (up to about 600K maximum)
        NATIVEPX data (DSEG)            <- base address of all XPL0 data
        Compiled data (XPL0 strings and constant arrays)
        Miscellaneous buffer space
        Heap space for global variables (HEAPLO up to 1600 reals = 12800 bytes)
        Stack (stacklen = 16K bytes)

        Unused/available conventional memory, which can be MAlloc'ed for use
         with DOS and BIOS routines that require buffer space

A0000:  Video graphic memory
B8000:  Video text memory
F0000:  BIOS
100000: Start of extended memory
        DOS (if it's loaded high)

(Location that DPMI returns when NATIVEPX allocates extended memory)
??????: Heap space for everything except global variables
        (Size is set to whatever the operating system will provide)
        (The pointer to a global array is in conventional memory while the
        actual array space is here in extended memory)


SWITCHES

Typing "XPLPX /?" displays these optional command-line switches:

	Usage: XPLXP [options] source
	   /A: Display Assembly code
	   /B: Short circuit Boolean evaluations
	   /C: Make identifer names Case-sensitive
	   /D: Include XPL source in output (Debug)
	   /I: Include I2L comments in output
	   /L: Display source code Listing
	   /W: Display Warning messages
	   /2: Align loops on word boundaries
	   /4: Align loops on dword boundaries

The following are new for the XPLPX compiler:

/C makes identifier names (variables, procedure names, etc.) case-
sensitive. For example "Frog" would not be the same name as "FROG". This
is intended for making sure that names have consistent capitalizing.

/I is used to output intermediate code (I2L), which can be used for
debugging. The 16-bit versions of the compilers output these codes using
the /C switch.

/W displays warning messages. These are handled like error messages, but
they don't discard the compiled output file (.asm).


KNOWN PROBLEMS

The GetTime intrinsic (82) returns erratic values under Windows XP, but
works fine under pure DOS. (Under Windows the high and low words of the
returned value are not synchronized.)

Device 6 in VESA graphic modes does not scroll up lines of text.

The SetWind intrinsic (70) does not work for VESA graphic modes.

A maximum of eight real arguments can be passed to a procedure.

Some floating point trap error numbers are wrong as demonstrated by
FTEST.XPL.

The Read intrinsic (31) blows up under Windows 98. (DOS interrupts $25
and $26 are an unbelievable mess!)

The Read intrinsic (31) doesn't detect an error if a floppy disk is not
inserted in the drive.

The 'sqrt' command does not detect an error if its argument is negative.
(The Sqrt intrinsic (53) does detect negative arguments.)


Please report any other problems you find. Thanks!

-Loren Blaney
loren.blaney@gmail.com
