------------------------------------------------------------------------- The NEGA Programming Language v3.22 Copyright (C) Mar. 1997 by Tylisha C. Andersen ------------------------------------------------------------------------- The NEGA language is a block structured substitute for assembly language, and can be classified as a middle level language. NEGA is 99% compatible with the TERSE(*) language by Jim Neil (for more information, see the WWW site http://www.terse.com). To convert TERSE code to use NEGA, you must simply change all data definitions to standard MASM style (var DB ???). If errors appear when compiling the code, try parenthesizing the offending statements, etc. Also, note that unlike TERSE, statements do require semicolons even at the end of a block. The TERSE compiler is over twice as fast and has more options than NEGA, but NEGA supports many more features (such as floating point) that TERSE does not, and TERSE is quite expensive ($49 + $9 S/H). I wrote NEGA so that everyone can use this excellent form of symbolic assembly without paying an exorbitant amount of money. However, if you need the extra speed, or the data statements, etc, then you may want to upgrade to TERSE. The TERSE compiler also comes with a very nice printed manual, and as commercial software it is supported. There is a review of TERSE in Tenie Remmel's Programming Tips & Tricks Magazine, issue 001. You can get it at: ftp://ftp.simtel.net/pub/simtelnet/msdos/pgmtips/ptt001.zip The main reasons for using a symbolic assembly language, are that you write many less lines of code (a NEGA program has 20-50% less lines than the same program in, say, TASM), and that the block statements make the general flow and structure of the program easier to see (although you can get this same advantage by indenting standard ASM code, it is rarely done). NEGA files have the extension .N; to run the compiler, type NEGA file[.N] It will output an assembly language file (file.ASM) that can be compiled with MASM or TASM. It can often be useful to examine this file when debugging. To completely compile a *.N file: NEGA file.N TASM -m file.ASM TLINK -t file The compiler is written in 'C'. The source code compiles under both Borland and Watcom C, and it probably will also compile under MS Visual C and Symantec C. Note that the source code is 120 characters wide. I have provided a viewer that displays 120 line wide files on any EGA compatible graphics system. Source for the viewer is incuded in both ASM and NEGA. NEGA now supports the floating point (FPU) instruction set. Floating point operations start with a '#'. See the last section in this manual for more information. An example of a program written in NEGA (a 5.2K Tetris game) is provided in the directory TETRIS. This game was originally written by Tenie Remmel and I converted it to NEGA so it could be used as an example. (*) TERSE is a trademark of Jim Neil. ------------------------------------------------------------- General considerations ------------------------------------------------------------- Statements must start after the beginning of a line, following some white-space. A single line may contain many statements. Statements are usually terminated with a ';', just like in 'C' or Pascal. In some cases, a '?' can be used (see 'Compares and Tests'). The comment character is '\'. All characters between a '\' character and the end of the line are ignored. For any operation that is not directly supported, simply write the standard assembly code. Example: lidt [bx]; If it would conflict with NEGA syntax, you can either enclose the statement in parentheses, or start it at the very beginning of a line. Example: O equ ; Operands can also be enclosed in parenteses. Examples: mov ax, (-1); \ mov ax,-1 mov (ax, bx); \ mov ax,bx If this does not work, an entire line may be output with no processing at all by starting it with an '!'. Throughout the documentation, if a command is marked with a '*' this means it is an extension that was not available in the original TERSE language. ------------------------------------------------------------- Assignment ------------------------------------------------------------- This category includes all direct assigment operations: Assembly NEGA mov A, B A = B; movzx A, B A =+ B; * movsx A, B A =- B; or A =+- B; xchg A, B A == B; lea A, B A === B; ------------------------------------------------------------- Chained Assignment ------------------------------------------------------------- Chained assignment allows several registers or variables to be set using a single statement. For instance, to copy a segment register, two assembly instructions are needed, but with chained assignment it can be done using one statement: ds = ax = cs; which produces the following code: mov ax, cs mov ds, ax The general syntax of a chained assignment is: X1 = X2 = ... = XN = R = Y; where R must be a general register, X1 through XN must be either registers or variables, and Y can be anything. This produces the following assembly code: mov R, Y mov X1, R mov X2, R . . . mov XN, R For example, you can do this: X = Y = Z = AX = 0; which will set X, Y, Z, and AX to 0. ------------------------------------------------------------- Arithmetic and Logical ------------------------------------------------------------- The syntax for arithmetic and boolean logic instructions is very simple: Assembly NEGA add A, B A + B; adc A, B A ++ B; sub A, B A - B; sbb A, B A -- B; and A, B A & B; or A, B A | B; xor A, B A ^ B; inc A A+; dec A A-; neg A -A; not A ^A; imul A *A; idiv A /A; mul A **A; div A //A; imul A, B A * B; imul A, B, C A, B * C; Also, a sub A,A instruction, which clears a register, may be coded as &A. This has the additional effect of clearing the carry flag. The common code sequences inc A/inc A and dec A/dec A can be coded as A++; and A--; respectively. Longer strings of +'s or -'s are supported, though there is not any size benefit versus an ADD in these cases. Note that the imul A,B and imul A,B,C instructions work with unsigned values as well as signed values. The compiler no longer optimizes the IMUL instruction; use the A, B * C; form instead. This is because there are some circumstances, such as AX = BX * CX; that should not be optimized. ------------------------------------------------------------- Shifting and Rotation ------------------------------------------------------------- These instructions are: shl,shr,sar,rol,ror,rcl,rcr. Assembly NEGA shl A, B A < B; shr A, B A > B; sar A, B A +-> B; rol A, B A << B; ror A, B A >> B; rcl A, B A <<< B; rcr A, B A >>> B; * shld A, B, C A, B < C; * shrd A, B, C B, A > C; In all of these instructions, the last operand, which is the shift count, must be either 1, the CL register, or a constant value. Note that the order of the first two operands in the shrd A,B,C instruction is reversed. This is because a shr B:A,C is what this instruction really does. ------------------------------------------------------------- Stack Operations ------------------------------------------------------------- These include push, pop, pushf, popf, etc. Assembly NEGA push X =X; pop X X=; * pusha =%; * popa %=; pushf =??; popf ??=; pushfd ????=; popfd =????; * enter N,0 =+N; * leave =-; A single statement can include multiple pushes and pops. The syntax for a compound push is: =X1 =X2 ... =XN; which produces the following code: push X1 push X2 . . . push XN The syntax for a compound pop is: X1= X2= ... XN=; which produces the following code: pop XN . . . pop X2 pop X1 Note that the values are popped in REVERSE order. This is intended to eliminate bugs caused by popping values in a different order than they were pushed. Here is an example: =ax =bx =cx =si; . . . ax= bx= cx= si=; This produces the following code: push ax push bx push cx push si . . . pop si pop cx pop bx pop ax ------------------------------------------------------------- Compares and Tests ------------------------------------------------------------- All compares and tests use a '?' terminator instead of a ';' terminator. A cmp A,B can be coded as A - B?. It subtracts B from A, and sets the flags based on the result. A test A,B can be coded as A & B?. It ANDs A with B and sets the flags based on the result. A test A,A can also be coded as A?. Any assignment, chained assignment, arithmetic, logic, shift, rotate, or compound statement can be terminated with a '?'. The compiler will generate a test command on the result. Note that often this may not be needed as the flags are also set by many other instructions. ------------------------------------------------------------- Compound Statements ------------------------------------------------------------- Often, many instructions are needed to compute a result. If they all share a common destination register, they can be combined into one statement. The syntax for a compound statement is: R X1 X2 ... XN or R = Y X1 X2 ... XN where R is a register, X1 through XN are registers or variables, and Y can be anything. This generates the same code as if the following were used: R = Y; \ optional R X1; R X2; . . . R XN; ------------------------------------------------------------- Jumps, Calls and Interrupts ------------------------------------------------------------- Here is the syntax for unconditional jumps, etc: Assembly NEGA jmp X (short) .X; jmp X (near) ..X; call X =.X; * lcall X %.X; int X !X; into !+-; ret .=; ret X .=X; iret !.=; And here is the syntax for conditional jumps: Assembly NEGA ja X >>X; jae/jnb/jnc X >>=X; or =>>X; jb/jc X <X; jge/jnl X >=X; or =>X; jl X X; jno X -+X; jns X ++X; jo X +-X; jpe/jp X &&X; jpo/jnp X ||X; js X --X; These condition codes are used in other commands such as the SETcc, IF, WHILE, conditional break, etc. The conditional jumps will only jump up to 128 bytes in either direction. For a near jump, precede the command with a '.' character, like this: .<> foo; This is not necessary if you use TASM's JUMPS directive, which fixes up near jumps automatically. Use this with care as it wastes 2-3 bytes if you don't need it. 'lcall' is the FREELIB external call. This operation produces the following code: extrn X:proc call X +------------------------------------------------------------+ |You can get FREELIB at the following address: | |ftp://ftp.simtel.net/pub/simtelnet/msdos/asmutl/freeli30.zip| | (The last 2 digits are the version number and may change) | +------------------------------------------------------------+ The syntax for the CX-specific jumps is: Assembly NEGA jcxz X ?==X; * jcxnz X ?<>X; loop X -.X; loope/loopz X -==X; loopne/loopnz X -<>X; jcxnz is not actually an instruction; it is emulated, so it takes 4 bytes instead of the normal 2. ------------------------------------------------------------- Block Structure ------------------------------------------------------------- The most important part of the NEGA language is the block structure. This is what really makes it a middle level language instead of low-level, allowing flow control similar to that used in languages such as 'C' or Pascal. The simplest block structure is the DO: { ... }; A DO simply encloses a block of statements. They may be any valid NEGA statements, including other blocks, or even nothing at all. The DO may not seem useful, but it can be useful if used with a BREAK (see below). This is the syntax for an IF: { ... }; where can be any of the following: > G < L >= GE => GE <= LE =< LE == Z <> NZ >> A << B >>= AE =>> AE <<= BE =<< BE -- S ++ NS +- O -+ NO && P || NP or ?<> which is CXNZ. The condition may be preceded by a '.' to span large blocks. The statements inside the IF are executed only if is true. For example: ax? <>{ ax-; }; \ Decrement AX if it isn't zero. al - 9? >> \ Convert AL to hex. If AL > 9 then { al + 7; }; \ add 7 before adding '0'. al + '0'; The final example produces the following code: cmp al, 9 jbe ?Els0001 ?Top0001: add al, 7 ?Els0001: ?End0001: add al, '0' * You can also use the syntax .{ ... }; or ..{ ... }; * to jump over a block of statements. One dot produces a * short jump, and two produce a near jump. This is useful * for embedding small local subroutines in the code: .{ \ Skip this block. convhex: al & 0Fh - 0Ah? \ Another way to convert to hex. al -- 69h; '-; .=; \ Return... }; This is the syntax for an IF-ELSE: { ... },{ ... }; is as above. The statements in the first (IF) block are executed if is true, and if is false the statements in the second (ELSE) block are executed. If the ELSE block is greater than 127 bytes, you can use a '.': { ... },.{ ... }; Also, an ELSE-IF is possible: { ... }, { ... }, { ... }; The ELSE-IF can be extended as long as you wish. Here is an example of the ELSE: ax - bx? > \ CX = max(AX, BX). { cx = ax; }, { cx = bx; }; which produces the following code: cmp ax, bx jle ?Els0001 ?Top0001: mov cx, ax jmp ?End0001 ?Els0001: ?Top0002: mov cx, bx ?Els0002: ?End0001: The syntax for a WHILE is: { ... } ; The WHILE repeats the statements in the block as long as is true. is as above EXCEPT that instead of the ?<> (CXNZ) there are the following five types: ?== CXZ -. Loop -== Loopz -<> Loopnz . Jmp (loop forever) Examples: cx = 40; \ subtract BX from DI 40 times { di - bx; }-.; { \ wait for a key, and count iterations bx+; \ this could be used to seed a RNG ah = 1; !16h; }==; The final example produces the following code: ?Top0001: inc bx mov ah, 1 int 16h jz ?Top0001 ?Els0001: ?End0001: An IF can be combined with a WHILE, and the loop will be entered only if the IF condition is true: { ... } ; These can even be combined with ELSE's. The general syntax for block structure is: { ... } , { ... } , ... ... { ... } , { ... } ; where any of the 's can be omitted. To exit a block, a BREAK can be used: ; or N; \ N is a digit from 0 to 9 The valid conditions are as above (see IF) EXCEPT that instead of ?<> (CXNZ) there is ?== (CXZ), and there are two unconditional jumps: .; \ short jump ..; \ near jump If there is no operand, the current block is exited. If an operand is supplied, the current block, plus N more levels of blocks are exited (where N is a digit from 0 to 9). Also, the common code sequence .1; }; can be shortened to .}; which is a very useful for selection statements. A conditional break may also be used this way, as in == }; which is the same as == 1; }; For example: ax - bx? > \ sort AX, BX, CX in order { ax -= bx; bx - cx? <; \ sorted already? bx == cx; ax - bx? > { ax == bx; }; }; { \ a CASE statement bx - 0? == { Var0 + ax; .}; bx - 1? == { Var1 - ax; .}; bx - 2? == { Var2 + ax - di; }; }; The final example produces the following code: ?Top0001: cmp bx, 0 jnz ?Els0002 ?Top0002: add Var0, ax jmp ?End0001 ?Els0002: ?End0002: cmp bx, 1 jnz ?Els0003 ?Top0003: sub Var1, ax jmp ?End0001 ?Els0003: ?End0003: cmp bx, 2 jnz ?Els0004 ?Top0004: add Var2, ax sub Var2, di ?Els0004: ?End0004: ?Els0001: ?End0001: ------------------------------------------------------------- Input and Output ------------------------------------------------------------- These instructions read and write from I/O ports. They are represented by the '@' character. The string I/O instructions are described in the String Instructions section. Assembly NEGA in al,X =@X; in ax,X =@@X; in eax,X =@@@@X; out X,al @X=; out X,ax @@X=; out X,eax @@@@X=; in al,dx =@; in ax,dx =@@; in eax,dx =@@@@; out dx,al @=; out dx,ax @@=; out dx,eax @@@@=; ------------------------------------------------------------- String Instructions ------------------------------------------------------------- String instructions manipulate strings or other arrays in memory. If operands are specified, for example: rep movs byte ptr es:[di], ss:[si] the actual operands are ignored; they are only used to figure out the size, and to generate segment overrides (in the example, the source segment was set to SS). Here is the NEGA syntax for the simple string operations: Assembly NEGA lodsb =*; lodsw =**; lodsd =****; stosb *=; stosw **=; stosd ****=; movsb *=*; movsw **=**; movsd ****=****; scasb *-? scasw **-? scasd ****-? cmpsb *-*? cmpsw **-**? cmpsd ****-****? insb *=@; insw **=@@; insd ****=@@@@; * insw **=@; * insd ****=@; outsb @=*; outsw @@=**; outsd @@@@=****; * outsw @=**; * outsd @=****; The syntax for the complex string instructions is: Assembly NEGA lods A =*A; stos A *A=; movs A, B *A=*B; scas A *A-? cmps A, B *A-*B? ins A *A=@; outs A @=*A; Any string instruction may be preceded by a REP prefix. If it is a SCASx or CMPSx, it should be either REPE or REPNE. This is useless for a LODSx instruction. The syntax for a REP prefix or a REPNE prefix is <>. The syntax for a REPE prefix is ==. For example: <> *=*; \ rep movsb == ****-****? \ repe cmpsd <> **-? \ repne scasw Note: SCASW and SCASD can act as a compact way to add 2 or 4 to DI. They save 1 byte relative to the normal method. ------------------------------------------------------------- Pointer Bumping ------------------------------------------------------------- The NEGA language provides a shorthand syntax for the common operation of incrementing or decrementing a pointer before or after using it. For instance, the code sequence mov al, [di] inc di can be coded as al = [di+]; The operator acts only on the nearest operand inside the brackets. The operator must appear either just after the opening bracket or just before the closing bracket. If the operator appears before the operand, the compiler will place the INC or DEC instruction(s) before the command, and vice versa. For example: al = [-di]; \ dec di/mov al,[di] ch = [+si-]; \ inc si/mov ch,[si]/dec si bx = es:[bp+si-]; \ mov bx,es:[bp+si]/dec si cx = [var+]; \ mov cx,[var]/inc var bl = [ebx+edi*4+]; \ Not allowed! This generates: \ mov bl,[ebx+edi*4]/inc 4 \ I don't know of a good way to fix this. ------------------------------------------------------------- Miscellaneous ------------------------------------------------------------- The syntax for a Lx A,B (LDS, LES, etc.) is: x,A = B; The syntax for a SETcc A is: A = ; where can be any of the following: > G < L >= GE => GE <= LE =< LE == Z <> NZ >> A << B >>= AE =>> AE <<= BE =<< BE -- S ++ NS +- O -+ NO && P || NP Other miscellaneous instructions: Assembly NEGA aaa "+; aad "/; * aad A "/A; (unofficial instruction) aam "*; * aam A "*A; (unofficial instruction) aas "-; * cbw *; * cdq ****; clc &; cld +; cli !-; cmc ^; * cwd **; daa '+; das '-; hlt ....; lahf =?; sahf ?=; * setalc =; (unofficial instruction) stc |; std -; sti !+; wait ...; xlatb ><; xlat A >; *; \ AX = 1 if NZ cl - 30? =?; \ test now, but jump later . . . ?=; >>label; ------------------------------------------------------------- Floating Point ------------------------------------------------------------- The floating point instructions do not suggest any obvious division into categories, so here is a list: Arithmetic FPU instructions: Assembly NEGA fadd #+; fadd A #+A; fadd A,B #A+B; faddp #+.; faddp A #+A.; faddp A,B #A+B.; fdiv #/; fdiv A #/A; fdiv A,B #A/B; fdivp #/.; fdivp A #/A.; fdivp A,B #A/B.; fdivr #//; fdivr A #//A; fdivr A,B #A//B; fdivrp #//.; fdivrp A #//A.; fdivrp A,B #A//B.; fmul #*; fmul A #*A; fmul A,B #A*B; fmulp #*.; fmulp A #*A.; fmulp A,B #A*B.; fsub #-; fsub A #-A; fsub A,B #A-B; fsubp #-.; fsubp A #-A.; fsubp A,B #A-B.; fsubr #--; fsubr A #--A; fsubr A,B #A--B; fsubrp #--.; fsubrp A #--A.; fsubrp A,B #A--B.; Other FPU instructions: Assembly NEGA fabs #+-; fchs #-+; fcom #-?; fcom A #-A?; fcomp #-?.; fcomp A #-A?.; fcompp #-?..; fdecstp #=-; ffree A #&A; fiadd A #>+A; ficom A #>-A?; ficomp A #>-A?.; fidiv A #>/A; fidivr A #>//A; fild A #>=A; fimul A #>*A; fincstp #=+; finit #; fist A #>A=; fistp A #>A=.; fisub A #>-A; fisubr A #>--A; fld A #=A; fninit #!; fnstsw A #!A=?; fprem #%; fprem1 #%%; frndint #<>; fscale #**; fst A #A=; fstp A #A=.; fstsw A #A=?; ftst #?; fucom #<>?; fucom A #<>A?; fucomp #<>?.; fucomp A #<>A?.; fucompp #<>?..; fwait #.; fxam #??; fxch #==; fxch A #==A; Symbols: The '.' indicates a FPOP after the operation. The '!' indicates no FWAIT. -------------------------------------------------------------------------- --------------------------------------------------------------------------