Some books was written about compiler design and implementation. The following
text, as I think, allow to get an elementary belief about the compilers.
When I has decide to write a compiler, I wanted to realise immediately quite
a few possibilities, noticeably more, than needed for writing the compiler
itself. This manage, but my compiler is kept much details, obstruct its
understanding. Part of details is connected with possibilities of language,
other part - with the processor 8086 (80386 more suitable, but then I did not
know this and 32-bit operating systems are not yet spread). And certainly,
compiler may be written better. At some moment I has reduce a compiler, but
quite small it did not become. Vastly reduce a compiler possible to the account
of simplification of input language, that here and is made. The toy compiler
is useless for practical programming, but it is build in the same way, either
as full compiler...
Program in any high-level language consists of words (reserved words of
language, identifiers, numbers, signs) and for the reading them from the text
function Scan are used. This function are language-dependent. For some
languages such as Pascal it must read only words, numbers and multi-character
operators and single characters (in Context language there are three
two-character operators - <= - less or equal, != - not equal and
>= - greater or equal, all other operators are single-character) and skip
a single-line and multi-line comments. Scanner Fortran'ΰ, for instance, must
read the words .or. (logical or) and .and. (logical and), scanner of Context
language read any of this words as three words. However, tere is not largest
problem of Fortran scanner. Following example more complex:
901 FORMAT (1X,'N = ',I2/)
N=0
10 DO 20 I=1.5
N=N+1
20 CONTINUE
WRITE(6,901) N
STOP
END
Headline of do cycle in the line 10 consist an error, but Fortran does not
find them and assumes that this is assignment of constant 1.5 to variable
DO20I (Fortran does not requires explicit variables declaration). The result
of this program was 1, not 5.
Not only scanner, a compiler as whole depends on input language, so better go
over to private and consider concrete language - Context.
Context program list of followed definitions:
constants
types (structures)
global variables
functions
Their mutual location in the program is limited only by one condition -
each object must be defined before its use, main function (begin) must be
placed at the end of program. For some objects, for instance, functions
sufficiently only declarations, but in the minimum version this will not
be supported.
Definitions of constant, structures and main function are recognized
on the first word (define, struct and begin accordingly),
definitions of functions and global variable begins with the type name and
friend from the friend are distinguish by presence and absence a parentheses
after the name. I.e. after the type name is necessary to read symbols @
(reference signs, if they are present), name itself and following word.
If this word is a parenthesis, must be execute analysis to functions,
otherwise - an analysis global variable. In other languages a function is
defined on the first word. In Pascal, for instance, function begins from
function word and variable list begins from the var word.
Thereby, on the top-level a compiler can consist of the simple cycle:
while TRUE do
Scan(@Buff);
select
case strcmp(@Buff,"begin") =0:
// Main function parse
exit
case strcmp(@Buff,"define")=0:
// Constant parse
case strcmp(@Buff,"struct")=0:
// New type parse
default:
// Global variable and function parse
end
end
Of course, each of select's branches must contain a certain code. Analysis
of constants and structures are relatively simple (more over, in Context
structures are nonrecursive - unlike Pascal in there are no structure
definitions in other structure definition), result of the analysis are new
record in the table of global names, no code will be create. Analysis of
functions more complex. It consists of the analysis of heading with the
parameter list (that much like like the analysis of structure) and analysis
of operators. If we have function Ctrl, which can compile one operator,
compilation of whole functions are reduced to the cycle from three lines:
while strcmp(@Scan(@Buff),"end")!=0 do
Ctrl(@Buff);
end
For further, however, needed to change this cycle and Ctrl function:
//Scan(@Buff);
while strcmp(@Buff,"end")!=0 do
Ctrl(@Buff); // Ctrl must read next word
end
This change will allow execute an analysis of function in extended version
of Context, allow function declarations (Pascal analog - forward):
word F();
word G()
F();
end
After the close parentheses can will be meet semicolon (F) or beginning
of the first operator (G). After reading close parentheses it is necessary to
read a following word and, if this is not a semicolon, remember its word and
go over to compiling the operators, second version of cycle is adapt for this.
By the changing language definition possible to restore capacity to work of
first version of cycle:
word F();
word G() is
F();
end
Here after the close parentheses always present a word (semicolon or is).
All operators, which can will be meet in functions, will be recognize on
the first word, so at the top-level Ctrl can be make so:
void Ctrl(char @Buff)
select
case strcmp(@Buff,"if")=0 | strcmp(@Buff,"select")=0:
// Condition operator parse
case strcmp(@Buff,"while")=0:
// While loop parse
case strcmp(@Buff,"loop")=0:
// loop operator parse
case strcmp(@Buff,"exit")=0:
// exit operator parse
case strcmp(@Buff,"inc")=0:
// inc operator parse
case strcmp(@Buff,"dec")=0:
// dec operator parse
default:
// Local definition and assignment parse
end
Scan(Buff);
end
Many operators contain expressions, their compiling more difficult tasc,
but it can be a solved like calculation of expression. If already written
function Expr that capable to compile any expression and return type and
address of result (this can be an address of memory, register or reference
to the element tables of constants).
By using of functions Expr analysis of cycle while can be executed so:
// Code generation @A: nop; begin of loop
// Expr call,
// loading result in AL
// Code generation or AL,AL
// jnz @B
// jmp ?
// @B: nop
while strcmp(@Buff,"end")!=0 do
Ctrl(@Buff);
end
// Code generation jmp @A
@C: nop
// jmp address correction jmp C (for jmp ?)
This is not all necessary actions, also needes prepare to compilation
of loop/exit operators and to the local variables analysis. But this is
detail, main idea in that cycle, either as a function, includes a sequence of
operators and for their compilation can be used function Ctrl in the same way
as it used for compilation of the whole function.
This is not a better way. Unnecessary commands are generated (or AL,AL...),
but now simplicity more important then efficiency. From this fragment also
unclear, how will be compiled complex condition, contain logical operators
AND/OR, full or short condition evaluation will be made. This question
must be solved in Expr functions. Probably, full evaluation with using
a register for keeping of result is the most simple, in some languages
(for instance in original Pascal) this method are used. This method also
used in DOS version of Context.
Formula compilation ate similar to arithmetic expression calculation.
Differences only in that, that in program expressions can contain not only
numbers and operation signs, but also identifiers and function calls.
And no calculations in compiling time, but computer code are created.
All this can be made in one recursive function Expr. This function must
calculate an result address or value (only if object - a constant).
void Expr(word Prty; char @Buff; OpInfo @Op1)
select
case strcmp(@Buff,"(")=0:
// Expression in parenthesis
case strcmp(@Buff,"'")=0:
// Character constant
case strcmp(@Buff,"#")=0:
// Character constant
case isdigit(Buff)=0:
// Number constant
default:
// Variable, function call
end
while TRUE do
word Sign;
word P2;
select
case strcmp(@Buff,"|")=0:
Sign=OR;
P2 =1;
case strcmp(@Buff,"&")=0:
Sign=AND;
P2 =1;
case strcmp(@Buff,"<")=0:
Sign=LT;
P2 =2;
...
case strcmp(@Buff,"%")=0:
Sign=MOD;
P2 =4;
default:
P2=0;
end
if P2<=P then
exit
end
Expr(P2,@Scan(@Buff),@Op2);
// Type comparison
// Code generation for Sign operator
end
end
Note that Expr function always read next word. This can be a semicolon,
colon, then, do.
All ideas are worded and here is as they are implemented in toy compiler.
Simplification is reach to the account of simplification of language in
the first place. Only one type given - word (but conditions are considered
as boolean), only one-dimensional arrays and no new types, no functions,
and no reference variables. No attempts to optimization. Compiler itself
begins from Read functions:
define @emNOMEMORY "Not enough memory"
define @emSIZE "Identifier too long"
define @emEOF "EOF"
define @emNUMBER "Eroor in number"
define @emNAME "Name expected"
define @emEMPTY "Empty array"
define @emBRACKET "Bracket expected"
define @emCOMMA "Comma expected"
define @emSEMICOLON "Semicolon expected"
define @emTHENEXP "then keyword expected"
define @emDOEXP "do keyword expected"
define @emDUP "Duplicate definition"
define @emLVALUE "Variable expected"
define @emTYPE "Types not match"
define @emUNDEFINED "Undefined variable"
define @emUNDEFOPR "Undefined operation"
define @emASSIGN "Assignment expected"
define tbSIZE 8192
define dbSIZE 8192
define dtSIZE 128
define idSIZE 16
define opOR 1
define opAND 2
define opLT 3
define opLE 4
define opEQ 5
define opNE 6
define opGE 7
define opGT 8
define opADD 9
define opSUB 10
define opMUL 11
define opDIV 12
define opNOT 13
define opNEG 14
define ptZERO 0
define ptBOOL 1
define ptCOMP 2
define ptADD 3
define ptMUL 4
define ptLVALUE 5
define ttWORD 0
define ttBOOL 1
struct DATA
char Name [idSIZE];
word Ofs;
word Index;
end
DATA Data [dtSIZE];
word nData;
word Ofs;
char Text [tbSIZE];
word hText;
word nText;
word pText;
char Name [128];
word Line;
word Label;
char Dest [dbSIZE];
word hDest;
word pDest;
void @Ptr(word Seg, Ofs)
void @P1=@Ofs;
void @@P2=@P1;
return @P2;
end
word isalpha(char Ch)
if ('A'<=Ch & Ch<='Z') | ('a'<=Ch & Ch<='z') | (Ch='_') then
return 0;
end
return 1;
end
word isdigit(char Ch)
if ('0'<=Ch & Ch<='9') then
return 0;
end
return 1;
end
word strlen(char @Buff)
word P=0;
while Buff[P]!=#0 do
inc P;
end
return P;
end
word strcmp(char @St1, @St2)
word P=0;
while St1[P]=St2[P] do
if St1[P]=#0 then
return 0;
end
inc P;
end
return 1;
end
char @strcpy(char @Dst, @Src)
word P=0;
while Src[P]!=#0 do
Dst[P]=Src[P];
inc P;
end
Dst[P]=#0;
return @Dst;
end
char @strcat(char @Dst, @Src)
word P=strlen(@Dst);
word Q=0;
while Src[Q]!=#0 do
Dst[P]=Src[Q];
inc P;
inc Q;
end
Dst[P]=#0;
return @Dst;
end
word GetPSP()
asm mov AH,62H
asm int 21H
asm mov AX,BX
end
word open(char @Name)
asm push DS
asm mov AH,3DH
asm mov AL,00H
asm mov DX,SS:[BP+6]
asm mov DS,DX
asm mov DX,SS:[BP+4]
asm int 21H
asm pop DS
end
word create(char @Name)
asm push DS
asm mov AH,3CH
asm mov CX,00H
asm mov DX,SS:[BP+6]
asm mov DS,DX
asm mov DX,SS:[BP+4]
asm int 21H
asm pop DS
end
word read(word F; void @Buff; word N)
asm push DS
asm mov AH,3FH
asm mov BX,SS:[BP+10]
asm mov CX,SS:[BP+4]
asm mov DX,SS:[BP+8]
asm mov DS,DX
asm mov DX,SS:[BP+6]
asm int 21H
asm pop DS
end
word write(word F; void @Buff; word N)
asm push DS
asm mov AH,40H
asm mov BX,SS:[BP+10]
asm mov CX,SS:[BP+4]
asm mov DX,SS:[BP+8]
asm mov DS,DX
asm mov DX,SS:[BP+6]
asm int 21H
asm pop DS
end
void close(word F)
asm mov AH,3EH
asm mov BX,SS:[BP+4]
asm int 21H
end
void putc(char Ch)
asm mov AH,2
asm mov DL,SS:[BP+4]
asm int 21H
end
void puts(char @St)
word P=0;
while St[P]!=#0 do
putc(St[P]);
inc P;
end
end
word str(word N; word S; char @Buff)
if S>0 then
dec S;
end
word P=0;
if N>=10 | S>0 then
P=str(N/10,S,@Buff);
end
char @D = "0123456789";
Buff [P]=D[N%10];
return P+1;
end
char @Str(word N; word S)
char @P="00000";
P[str(N,S,@P)]=#0;
return @P;
end
void Stop(char @EM)
putc(#13);
puts(@Name);
putc('(');
puts(@Str(Line,0));
putc(')');
if (strlen(@EM)>0) then
puts(": ");
puts(@EM);
end
close (hDest);
close (hText);
asm mov AX,4C00H
asm int 21H
end
word Val(char @Buff)
char @D = "0123456789";
word P = 0;
word L = 0;
word H = 0;
while Buff[P]!=#0 do
word S=0;
while D[S]!=Buff[P] do
inc S;
if S>=10 then
Stop(@emNUMBER);
end
end
S=10*L+S;
L=S%256;
S=10*H+S/256;
H=S%256;
S=S/256;
if S>0 then
Stop(@emNUMBER);
end
inc P;
end
return 256*H+L;
end
char Read()
if pText>=nText then
nText=read(hText,@Text,tbSIZE);
if nText<1 then
Stop(@emEOF);
end
pText=0;
end
return Text[pText];
end
void Next()
inc pText;
end
char @Scan(char @Buff)
while Read()=#09 | Read()=#10 | Read()=#13 | Read()=#32 do
if Read()=#10 then
inc Line;
end
Next();
end
word P=0;
while isalpha(Read())=0 | isdigit(Read())=0 do
Buff[P]=Read();
inc P;
if P>=idSIZE then
Stop(@emSIZE);
end
Next();
end
if P=0 then
Buff[P]=Read();
inc P;
Next();
select
case Buff[0]='<':
if Read()='=' then
Next();
return @strcpy(@Buff,"<=");
end
case Buff[0]='!':
if Read()='=' then
Next();
return @strcpy(@Buff,"!=");
end
case Buff[0]='>':
if Read()='=' then
Next();
return @strcpy(@Buff,">=");
end
end
end
Buff[P]=#0;
return @Buff;
end
void Save(char Ch)
if pDest>=dbSIZE then
Stop(@emNOMEMORY);
end
Dest[pDest]=Ch;
inc pDest;
end
void Decl(char @Inst)
word I=0;
while Inst[I]!=#0 do
Save(Inst[I]);
inc I;
end
Save(#13);
Save(#10);
end
void Code(word L; char @Inst)
if L!=0 then
Save('@');
char @P=@Str(L,5);
word I=0;
while P[I]!=#0 do
Save(P[I]);
inc I;
end
Save(':');
Save(' ');
else
word I=0;
while I<8 do
Save(' ');
inc I;
end
end
Decl(@Inst);
end
word Jump(word L)
char Buff [28];
word P=pDest;
Code(0,@strcat(@strcpy(@Buff,"jmp @"),@Str(L,5)));
return P;
end
word Find(char @Name)
word P=0;
while P=nData then
Stop(@emUNDEFINED);
end
if Data[N].Index>0 then
if strcmp(@Scan(@Buff),"[")!=0 then
Stop(@emBRACKET);
end
if Expr(ptZERO,@Scan(@Buff))!=ttWORD then
Stop(@emTYPE);
end
if strcmp(@Buff,"]")!=0 then
Stop(@emBRACKET);
end
if Prty=ptLVALUE then
if Flag=0 then
Stop(@emLVALUE);
end
return N;
end
while TRUE do
word Op;
word P;
select
case strcmp(@Buff,"|")=0:
Op=opOR;
P =ptBOOL;
case strcmp(@Buff,"&")=0:
Op=opAND;
P =ptBOOL;
case strcmp(@Buff,"<")=0:
Op=opLT;
P =ptCOMP;
case strcmp(@Buff,"<=")=0:
Op=opLE;
P =ptCOMP;
case strcmp(@Buff,"=")=0:
Op=opEQ;
P =ptCOMP;
case strcmp(@Buff,"!=")=0:
Op=opNE;
P =ptCOMP;
case strcmp(@Buff,">=")=0:
Op=opGE;
P =ptCOMP;
case strcmp(@Buff,">")=0:
Op=opGT;
P =ptCOMP;
case strcmp(@Buff,"+")=0:
Op=opADD;
P =ptADD;
case strcmp(@Buff,"-")=0:
Op=opSUB;
P =ptADD;
case strcmp(@Buff,"*")=0:
Op=opMUL;
P =ptMUL;
case strcmp(@Buff,"/")=0:
Op=opDIV;
P =ptMUL;
default:
P =ptZERO;
end
if P<=Prty then
exit
end
Code(0,"push AX");
if Expr(P,@Scan(@Buff))!=Type then
Stop(@emTYPE);
end
Code(0,"pop BX");
select
case Type=ttBOOL:
select
case Op=opOR:
Code(0,"or AL,BL");
case Op=opAND:
Code(0,"and AL,BL");
default:
Stop(@emUNDEFOPR);
end
case Type=ttWORD:
select
case Op=opLT:
Type=Comp("jb ");
case Op=opLE:
Type=Comp("jbe");
case Op=opEQ:
Type=Comp("je ");
case Op=opNE:
Type=Comp("jne");
case Op=opGE:
Type=Comp("jae");
case Op=opGT:
Type=Comp("ja ");
case Op=opADD:
Code(0,"add AX,BX");
case Op=opSUB:
Code(0,"db 93H; xchg AX,BX");
Code(0,"sub AX,BX");
case Op=opMUL:
Code(0,"mul BX");
case Op=opDIV:
Code(0,"db 93H; xchg AX,BX");
Code(0,"xor DX,DX");
Code(0,"div BX");
default:
Stop(@emUNDEFOPR);
end
end
end
return Type;
end
void Ctrl(char @Buff)
select
case strcmp(@Buff,"if")=0:
if Expr(ptZERO,@Scan(@Buff))!=ttBOOL then
Stop(@emTYPE);
end
if strcmp(@Buff,"then")!=0 then
Stop(@emTHENEXP);
end
Code(0, "or AL,AL");
Code(0, @strcat(@strcpy(@Buff,"jne @"),@Str(Label,5)));
word P=Jump(0);
Code(Label,"nop");
inc Label;
while strcmp(@Scan(@Buff),"end")!=0 do
Ctrl(@Buff);
end
Code(Label,"nop");
word pDest1=pDest;
pDest= P;
Jump (Label);
pDest=pDest1;
inc Label;
case strcmp(@Buff,"while")=0:
word While=Label;
Code(Label,"nop");
inc Label;
if Expr(ptZERO,@Scan(@Buff))!=ttBOOL then
Stop(@emTYPE);
end
if strcmp(@Buff,"do")!=0 then
Stop(@emDOEXP);
end
Code(0, "or AL,AL");
Code(0, @strcat(@strcpy(@Buff,"jne @"),@Str(Label,5)));
word P=Jump(0);
Code(Label,"nop");
inc Label;
while strcmp(@Scan(@Buff),"end")!=0 do
Ctrl(@Buff);
end
Code(0, @strcat(@strcpy(@Buff,"jmp @"),@Str(While,5)));
Code(Label,"nop");
word pDest1=pDest;
pDest= P;
Jump (Label);
pDest=pDest1;
inc Label;
case strcmp(@Buff,"write")=0:
while TRUE do
if Expr(ptZERO,@Scan(@Buff))!=ttWORD then
Stop(@emTYPE);
end
Code(0,"mov CX, 8");
Code(0,"call @WRITE");
if strcmp(@Buff,",")!=0 then
exit
end
end
if strcmp(@Buff,";")!=0 then
Stop(@emSEMICOLON);
end
Code(0,"mov AH, 2");
Code(0,"mov DL,13");
Code(0,"int 21H");
Code(0,"mov AH, 2");
Code(0,"mov DL,10");
Code(0,"int 21H");
case strcmp(@Buff,"blank")=0:
Code(0,"mov AH, 2");
Code(0,"mov DL,13");
Code(0,"int 21H");
Code(0,"mov AH, 2");
Code(0,"mov DL,10");
Code(0,"int 21H");
default:
word N=Expr(ptLVALUE,@Buff);
if N>=nData then
Stop(@emUNDEFINED);
end
if strcmp(@Buff,"=")!=0 then
Stop(@emASSIGN);
end
if Data[N].Index>0 then
Code(0,"push AX");
end
if Expr(ptZERO,@Scan(@Buff))!=ttWORD then
Stop(@emTYPE);
end
if Data[N].Index>0 then
Code(0,"pop BX");
Code(0,"shl BX,1");
Code(0,@strcat(@strcat(@strcpy(@Buff,"mov DS:[BX+"),@Str(Data[N].Ofs,0)),"],AX"));
else
Code(0,@strcat(@strcat(@strcpy(@Buff,"mov DS:["),@Str(Data[N].Ofs,0)),"],AX"));
end
end
end
begin
byte @Size=@Ptr(GetPSP(),128);
char @Parm=@Ptr(GetPSP(),129);
char name [128];
word I=0;
while I=dtSIZE then
Stop(@emNOMEMORY);
end
Data[nData].Ofs =2*Ofs; strcpy(@Data[nData].Name,@Buff);
Data[nData].Index=0;
word N=1;
if strcmp(@Scan(@Buff),"[")=0 then
N=Val(@Scan(@Buff));
if N<1 then
Stop(@emEMPTY);
end
Data[nData].Index=N;
if strcmp(@Scan(@Buff),"]")!=0 then
Stop(@emBRACKET);
end
Scan(@Buff);
end
Ofs=Ofs+N;
inc nData;
if strcmp(@Buff,";")=0 then
exit
end
if strcmp(@Buff,",")!=0 then
Stop(@emCOMMA);
end
end
case strcmp(@Buff,"begin")=0:
while strcmp(@Scan(@Buff),"end")!=0 do
Ctrl(@Buff);
end
exit
default:
Stop(@emUNDEFINED);
end
end
Code(0,"mov AX,4C00H");
Code(0,"int 21H");
Decl("@WRITE: dec CX");
Decl(" xor DX,DX");
Decl(" mov BX,10");
Decl(" div BX");
Decl(" push DX");
Decl(" or AX,AX");
Decl(" jz @SPACE");
Decl(" call @WRITE");
Decl(" jmp @DIGIT");
Decl("@SPACE: mov AH, 2");
Decl(" mov DL,32");
Decl(" int 21H");
Decl(" dec CX");
Decl(" jnz @SPACE");
Decl("@DIGIT: mov AH, 2");
Decl(" pop DX");
Decl(" add DL,48");
Decl(" int 21H");
Decl(" retn");
write(hDest,@Dest,pDest);
Stop ("");
end
I/O facilities include only write operator, that can output numbers on console
and blank operator that output blank line. For example you may compile and
execute next simple array sort program:
word Buff[16];
word Temp;
word I;
word J;
word N;
begin
N=10;
I=0;
while I0 do
I=I-1;
J=0;
while JBuff[J+1] then
Temp =Buff[J+1];
Buff[J+1]=Buff[J];
Buff[J] =Temp;
end
J=J+1;
end
end
blank
I=0;
while I
In Full version of compiler some simplifications are made:
all arithmetic and boolean operators are executed only in registers
first operand are placed in AL (byte), AX (word) or DX:AX (double word)
second operand are placed in BL (byte), BX (word) or CX:BX (double word)
operation result are placed in AL (byte), AX (word) or DX:AX (double word)
register DI are used to access to array element
registers ES:DI are used to access to reference variable
if necessary register are busy, it pushes into stack
in function calls parameters are pushed into stack (in order of
definition), parameters are removed from stack by called function,
result are stored in AL (byte), AX (word) θλθ DX:AX (dword)
global variables ara placed in segment, addressed by DS
Compiler structure:
void Stop(char @EM) // Terminate (with error msg. or no)
...
end
void Code(word L; char @S) // Line of asm code
...
end
char Read() // Read character
...
end
void Next() // Next character
...
end
char @Scan(char @Buff) // Read token
...
end
void Init() // Init expr compiler
...
end
void Push(word R) // Store register in stack
...
end
void Pop (word R) // Load register from stack
...
end
void LDAX(OpInfo @Op1) // Load operand into (DX:)AX
...
end
void LDBX(OpInfo @Op1) // Load operand into (CX:)BX
...
end
void LPTR(OpInfo @Op1) // Load pointer into ES:DI
...
end
void STAX(OpInfo @Op1) // Store result in memory
...
end
void MOVS(OpInfo @Op1, @Op2) // Large data assignment
...
end
word Comp(word Size; char @Jump) // Code for comp. operators
...
end
void Test(word pType1; word nPtr1; OpInfo @Op2) // Tect type
... // compatibility
end
void Expr(word P; char @Buff; OpInfo @Op1) // Expression
... // compiler
end
void Ctrl(char @Buff) // Block compiler
...
end
begin
...
while (strcmp(@Scan(@Buff),"begin")!=0) do
if (strcmp(@Buff,"define")=0) then // parse const
...
loop
end
if (strcmp(@Buff,"struct")=0) then // parse struct
...
loop
end
P=FindType(@Buff);
if (P
Some words about compiler modification. New errors may not be shown after
first recompilation. I.e. after compiling compiler A' (changed or extended A)
by initial compiler A, compiler A' will probably be able to compile itself, but
genegated compiler A'' can be wrong.
And last remark. Compiler creates a listings, little different from required for
Turbo Assembler. If you want to use TASM/TLINK, you need to add prolog