- The LBRANCH and LCBRANCH directives create different instruction sequences depending on the jump distance. So the code size can shrink during the first pass when the jump distance can be determined (when the label that is the jump destination is parsed). In the long form, LOADREL/JUMP is used, which might or might not need 2 bytes of padding. With this bugfix, the padding is always added, either before or after the LOADREL indirect operand, so that the code size does not change depending on the padding required. Otherwise the code might shrink further on the second pass because a LBRANCH/LCBRANCH instruction no longer needs padding due to an earlier code size change.
2710 lines
64 KiB
ObjectPascal
2710 lines
64 KiB
ObjectPascal
(* Copyright 2021-2024 Sebastian Lederer. See the file LICENSE.md for details *)
|
|
{$MODE objfpc}
|
|
{$H600}
|
|
{$S4}
|
|
program sasm;
|
|
{$!}{$ifdef FPC}uses math,crt;{$endif}
|
|
{$R+}
|
|
type TokenType = (
|
|
PlusToken, MinusToken, AsteriskToken, SlashToken,
|
|
SemicolonToken, EOFToken, EOLToken,
|
|
NumberToken, KeywordToken, LabelToken, DirectiveToken,
|
|
StringLitToken, CharLitToken, MetaKeywordToken,
|
|
CommaToken, DotToken, ColonToken, PercentToken, TildeToken,
|
|
AndToken, OrToken, XorToken,
|
|
AtToken, UnknownToken
|
|
);
|
|
|
|
IdentString = string[120];
|
|
KeywordString = string[255];
|
|
InsString = string[24];
|
|
AddrString = string[8];
|
|
|
|
Token = record
|
|
tokenText: string[255];
|
|
tokenKind: TokenType;
|
|
end;
|
|
|
|
OperandType = (
|
|
NoOprnd,
|
|
U13WOprnd, (* unsigned 13 bit word-aligned operand e.g. LOAD and STORE *)
|
|
S13Oprnd, (* signed 13 bit operand e.g. LOADC *)
|
|
RelWOprnd, (* signed 13 bit word-aligned operand, PC-relative, e.g. BRANCH/CBRANCH *)
|
|
S10Oprnd, (* signed 10 bit operand, e.g. FPADJ *)
|
|
U4Oprnd, (* unsigned 4 bit operand, would used for modifiers but is actually unused *)
|
|
CmpOprnd, (* comparison operand, unsigned 4 bit, actually unused *)
|
|
RegOprnd, (* register id operand for LOADREG, 4 bit unsigned *)
|
|
RelU10Oprnd, (* PC-relative 10 bit unsigned, for LOADREL *)
|
|
OptOprnd (* optional 4 bit operand for ALU *)
|
|
);
|
|
|
|
EncodingEntry = record
|
|
mask: integer;
|
|
value: integer;
|
|
end;
|
|
|
|
InstructionWord = integer;
|
|
MachineWord = integer;
|
|
OutputWord = record
|
|
intvalue:integer;
|
|
end;
|
|
|
|
ModifierEntry = record
|
|
keyword: string[24];
|
|
encoding: EncodingEntry;
|
|
next: ^ModifierEntry;
|
|
end;
|
|
|
|
OpcodeData = record (* the key is stored in the tree as metadata *)
|
|
encoding: EncodingEntry;
|
|
modifiers: ^ModifierEntry;
|
|
operand: OperandType;
|
|
id: integer; (* unique id for this opcode or alias *)
|
|
end;
|
|
|
|
SymbolType = (ConstSymbol, LabelSymbol, SpecialSymbol, SkippedSymbol);
|
|
|
|
Symbol = record
|
|
value:integer;
|
|
typ:SymbolType;
|
|
aligned:boolean;
|
|
padded:boolean;
|
|
exported:boolean;
|
|
end;
|
|
|
|
SymbolRef = ^Symbol;
|
|
|
|
CPoolEntry = record
|
|
labelname: string[40];
|
|
value: KeywordString;
|
|
offset: integer;
|
|
symbolic: boolean;
|
|
sym: SymbolRef;
|
|
next: ^CPoolEntry;
|
|
prev: ^CPoolEntry;
|
|
end;
|
|
|
|
CPoolRef = ^CPoolEntry;
|
|
|
|
LabelList = record
|
|
name: IdentString;
|
|
next: ^LabelList;
|
|
prev: ^LabelList;
|
|
end;
|
|
|
|
LabelListRef = ^LabelList;
|
|
|
|
UnresolvedBranch = record
|
|
target:IdentString;
|
|
origin:integer;
|
|
maxDistance:integer;
|
|
shrinkage:integer;
|
|
labels:LabelListRef;
|
|
next:^UnresolvedBranch;
|
|
end;
|
|
|
|
UnresBranchRef = ^UnresolvedBranch;
|
|
|
|
{$I 'platform-types+.pas'} (* defines OutputFileType, InputFileType, SymFileType *)
|
|
|
|
InputFileState = record
|
|
name: string;
|
|
filevar: InputFileType;
|
|
line: integer;
|
|
end;
|
|
|
|
TreeDataType = (TDString, TDInteger, TDSymbol, TDOpcode);
|
|
Treedata = record
|
|
case typ:Treedatatype of
|
|
TDString:(stringdata:string);
|
|
TDInteger:(intdata:integer);
|
|
TDSymbol:(symboldata:Symbol);
|
|
TDOpcode:(opcodedata:OpcodeData);
|
|
end;
|
|
|
|
{$I 'treetypes.pas'}
|
|
|
|
const insSize = 2;
|
|
wordSize = 4;
|
|
wordSizeMask = 3;
|
|
MaxUShortOffset = 8191;
|
|
MaxShortOffset = 4095;
|
|
MaxShorterOffset = 511;
|
|
MaxTinyOffset = 15;
|
|
Unresolved = 2147483647; (* max integer - 1 *)
|
|
MaxIntegerDigits = 24;
|
|
wordBits = 32;
|
|
MaxIncludes = 4;
|
|
FilenameSuffix = '.s';
|
|
OutfileSuffix = '.prog';
|
|
SymFileSuffix = '.sym';
|
|
AsciifileSuffix = '.mem';
|
|
|
|
progressSteps = 511;
|
|
|
|
shortcutChar = '`';
|
|
firstShCChar = 'A';
|
|
lastShCChar = 'i';
|
|
|
|
var
|
|
curToken, nextToken, lastToken: Token;
|
|
|
|
infileOpened: boolean;
|
|
outputEnabled: boolean;
|
|
asciiOutput: boolean;
|
|
lastOpcode: TreeDataRef;
|
|
|
|
bufferedChar: char;
|
|
buffered: boolean;
|
|
infile: InputFileType;
|
|
outfile: OutputFileType;
|
|
filename: string;
|
|
outfilename: string;
|
|
editOnError, runOnSuccess: boolean;
|
|
lineno: integer;
|
|
prevFiles: array[1..MaxIncludes] of InputFileState;
|
|
includeLevel: integer;
|
|
paramPos: integer;
|
|
|
|
pc: integer;
|
|
pass:integer;
|
|
|
|
bytesCount:integer;
|
|
|
|
symbolTable: TreeRef;
|
|
opcodeTable: TreeRef;
|
|
nextOpcodeId: integer;
|
|
constantPool: CPoolRef;
|
|
cPoolCount: integer;
|
|
nextConstId: integer;
|
|
|
|
firstUnresBranch: ^UnresolvedBranch;
|
|
|
|
LOADCPId: integer;
|
|
|
|
outputPrefix: string;
|
|
includePrefix: string;
|
|
|
|
shortcuts: array[firstShCChar..lastShCChar] of OpcodeData;
|
|
|
|
procedure errorExit2(message1, message2: string); forward;
|
|
|
|
{$I 'platform+.pas'}
|
|
{$I 'treeimpl.pas'}
|
|
|
|
|
|
procedure verifyTree(node:TreeRef); forward;
|
|
|
|
procedure dumpSymbolTable; forward;
|
|
|
|
|
|
procedure cleanup;
|
|
begin
|
|
if infileOpened then
|
|
close(infile);
|
|
if outputEnabled then
|
|
close(outfile);
|
|
end;
|
|
|
|
|
|
procedure errorExit;
|
|
begin
|
|
cleanup;
|
|
(* dumpSymbolTable; *)
|
|
halt;
|
|
end;
|
|
|
|
procedure errorLine(line:integer);
|
|
begin
|
|
if curToken.tokenKind = EOLToken then
|
|
lineno := lineno - 1;
|
|
writeln('at line ',lineno, ' in ', filename);
|
|
end;
|
|
|
|
procedure errorExit2(message1, message2: string);
|
|
var errormsg:string[128];
|
|
begin
|
|
errormsg := message1 + ' ' + message2;
|
|
writeln;
|
|
writeln('Error: ', errormsg);
|
|
errorLine(lineno);
|
|
cleanup;
|
|
if editOnError then
|
|
ExecEditor(filename, lineno, errormsg)
|
|
else
|
|
halt;
|
|
end;
|
|
|
|
function descToken(kind:tokenType):string;
|
|
begin
|
|
case kind of
|
|
PlusToken, MinusToken, AsteriskToken, SlashToken, AndToken, OrToken, XorToken:
|
|
descToken := 'one of + - * / & | ^';
|
|
SemicolonToken:
|
|
descToken := '";"';
|
|
EOFToken:
|
|
descToken := 'end-of-file';
|
|
EOLToken:
|
|
descToken := 'end-of-line';
|
|
NumberToken:
|
|
descToken := 'number';
|
|
KeywordToken:
|
|
descToken := 'keyword';
|
|
LabelToken:
|
|
descToken := 'label';
|
|
DirectiveToken:
|
|
descToken := 'directive';
|
|
StringLitToken:
|
|
descToken := 'string literal';
|
|
CharLitToken:
|
|
descToken := 'char literal';
|
|
MetaKeywordToken:
|
|
descToken := 'meta directive';
|
|
CommaToken:
|
|
descToken := '","';
|
|
DotToken:
|
|
descToken := '"."';
|
|
ColonToken:
|
|
descToken := '":"';
|
|
PercentToken:
|
|
descToken := '"."';
|
|
AtToken:
|
|
descToken := '"@"'
|
|
else
|
|
descToken := '<unknown>';
|
|
end;
|
|
end;
|
|
|
|
procedure makeCPoolLabel(var labelname:IdentString);
|
|
var digits:string[16];
|
|
begin
|
|
str(nextConstId, digits);
|
|
labelname := '_CP_' + digits;
|
|
end;
|
|
|
|
procedure hexstr(value:integer;var output:string); forward;
|
|
|
|
procedure putCPoolEntry(var constant:KeywordString; offset:integer; symbolic:boolean;var labelname:IdentString);
|
|
var newEntry:CPoolRef;
|
|
begin
|
|
makeCPoolLabel(labelname);
|
|
|
|
new(newEntry);
|
|
|
|
newEntry^.labelname := labelname;
|
|
newEntry^.value := constant;
|
|
newEntry^.offset := offset;
|
|
newEntry^.symbolic := symbolic;
|
|
newEntry^.next := constantPool;
|
|
newEntry^.prev := nil;
|
|
|
|
if constantPool <> nil then
|
|
constantPool^.prev := newEntry;
|
|
constantPool := newEntry;
|
|
|
|
nextConstId := nextConstId + 1;
|
|
end;
|
|
|
|
function isNumber(var s:string):boolean;
|
|
begin
|
|
if isdigit(s[1]) then
|
|
isNumber := true
|
|
else
|
|
isNumber := s[1] in [ '-', '$', '%' ];
|
|
end;
|
|
|
|
function convertNumber(digits:KeywordString):integer; forward;
|
|
function findSymbol(var keyword:KeywordString):TreeDataRef; forward;
|
|
|
|
procedure addCPoolEntry(var constant:KeywordString; offset:integer; var labelname:IdentString);
|
|
begin
|
|
putCPoolEntry(constant, offset, not isNumber(constant), labelname);
|
|
cPoolCount := cPoolCount + 1;
|
|
end;
|
|
|
|
function getSymbolValue(var keyword:KeywordString):integer; forward;
|
|
|
|
(*
|
|
Get the address of a cpool entry for a constant value.
|
|
If the pool does not contain the constant, create
|
|
a new entry.
|
|
The constant needs to be a string because it might
|
|
be a symbol. In pass 1, the value of the symbol is not
|
|
known, so to reuse a value we need to use the symbolic name.
|
|
|
|
The offset is an optional numerical value or symbol that gets
|
|
added to the constant before it is put into the pool.
|
|
This is used by the compiler for global variables
|
|
(arrays, record fields). Can be zero.
|
|
*)
|
|
|
|
function getCPoolAddr(var constant:KeywordString;offset:integer):integer;
|
|
var labelname:IdentString;
|
|
current:CPoolRef;
|
|
found:CPoolRef;
|
|
begin
|
|
getCPoolAddr := pc;
|
|
|
|
found := nil;
|
|
current := constantPool;
|
|
while (current <> nil) and (found = nil) do
|
|
begin
|
|
if (constant = current^.value) and (offset = current^.offset) then
|
|
found := current;
|
|
current := current^.next;
|
|
end;
|
|
|
|
if found <> nil then
|
|
begin
|
|
(* value already exists in pool *)
|
|
labelname := found^.labelname;
|
|
end
|
|
else
|
|
(* value not found, add it to the pool and
|
|
set the label name for the new entry *)
|
|
addCPoolEntry(constant,offset,labelname);
|
|
|
|
(* writeln(' [P', pass, ' cpool ', constant, ' -> ', labelname, '] '); *)
|
|
getCPoolAddr := getSymbolValue(labelname);
|
|
end;
|
|
|
|
procedure printPassNo;
|
|
begin
|
|
write('P', pass, ' ');
|
|
end;
|
|
procedure printFilename(var f:string);
|
|
begin
|
|
write(#13);
|
|
printPassNo;
|
|
write(f);
|
|
end;
|
|
|
|
procedure printCurrentLineno;
|
|
begin
|
|
printFilename(filename);
|
|
write(' ', lineno);
|
|
ClrEol;
|
|
end;
|
|
|
|
procedure printLastLineno;
|
|
begin
|
|
printCurrentLineno;
|
|
end;
|
|
|
|
procedure beginInclude(var newname: string);
|
|
var newfile: InputFileType;
|
|
begin
|
|
if includeLevel = MaxIncludes then
|
|
errorExit2('Too many nested includes', '');
|
|
|
|
includeLevel := includeLevel + 1;
|
|
|
|
prevFiles[includeLevel].filevar := infile;
|
|
prevFiles[includeLevel].name := filename;
|
|
prevFiles[includeLevel].line := lineno;
|
|
|
|
openFileWithDefault(newfile, newname);
|
|
|
|
infile := newfile;
|
|
filename := newname;
|
|
lineno := 1;
|
|
buffered := false;
|
|
end;
|
|
|
|
procedure endInclude;
|
|
begin
|
|
if includeLevel = 0 then
|
|
errorExit2('Internal error in', 'endInclude');
|
|
|
|
close(infile);
|
|
|
|
infile := prevFiles[includeLevel].filevar;
|
|
filename := prevFiles[includeLevel].name;
|
|
lineno := prevFiles[includeLevel].line;
|
|
|
|
buffered := false;
|
|
|
|
includeLevel := includeLevel - 1;
|
|
end;
|
|
|
|
function includeIsActive:boolean;
|
|
begin
|
|
includeIsActive := includeLevel > 0;
|
|
end;
|
|
|
|
function nextChar: char;
|
|
var ch: char;
|
|
begin
|
|
if buffered then
|
|
begin
|
|
ch := bufferedChar;
|
|
buffered := false;
|
|
end
|
|
else
|
|
begin
|
|
if not eof(infile) then
|
|
begin
|
|
read(infile, ch);
|
|
end
|
|
else
|
|
begin
|
|
(* we reached end-of-file, was this
|
|
the end of an include file? *)
|
|
if includeIsActive then
|
|
begin
|
|
(* if yes, switch back to previous file *)
|
|
endInclude;
|
|
ch := ' '; (* return a space which will get skipped *)
|
|
end
|
|
else
|
|
(* no, return null character which becomes an EOFToken *)
|
|
ch := #0;
|
|
end
|
|
end;
|
|
if ch = #10 then lineno := lineno + 1;
|
|
nextChar := ch;
|
|
end;
|
|
|
|
function peekChar: char;
|
|
var tmpChar: char;
|
|
begin
|
|
if buffered then
|
|
begin
|
|
peekChar := bufferedChar;
|
|
end
|
|
else
|
|
begin
|
|
if not eof(infile) then
|
|
begin
|
|
read(infile, tmpChar);
|
|
peekChar := tmpChar;
|
|
bufferedChar := tmpChar;
|
|
buffered := true;
|
|
end
|
|
else
|
|
begin
|
|
(* at the eof of an include,
|
|
just return an extra space and let nextChar
|
|
do the work *)
|
|
if includeIsActive then
|
|
begin
|
|
peekChar := ' ';
|
|
buffered := false; (* force nextChar to do real I/O *)
|
|
end
|
|
else
|
|
peekChar := #0;
|
|
end
|
|
end
|
|
end;
|
|
|
|
procedure skipWhitespace;
|
|
var c:char;
|
|
begin
|
|
while peekChar() in [ #13, #32, #9 ] do
|
|
c := nextChar;
|
|
end;
|
|
|
|
function integerFromString(digits:KeywordString):integer;
|
|
var value,error:integer;
|
|
begin
|
|
val(digits, value, error);
|
|
if error <> 0 then
|
|
errorExit2('Invalid integer value', digits);
|
|
integerFromString := value;
|
|
end;
|
|
|
|
function convertHex(var digits:KeywordString):integer;
|
|
var i,v,len:integer;
|
|
c:char;
|
|
begin
|
|
len := length(digits);
|
|
|
|
i := 2;
|
|
convertHex := 0;
|
|
|
|
while i <= len do
|
|
begin
|
|
convertHex := convertHex shl 4;
|
|
c := digits[i];
|
|
if (c >= 'A') and (c <= 'F') then
|
|
v := ord(c) - ord('A') + 10
|
|
else
|
|
if (c >= '0') and (c <= '9') then
|
|
v := ord(c) - ord('0')
|
|
else
|
|
errorExit2('Invalid number',digits);
|
|
convertHex := convertHex + v;
|
|
i := i + 1;
|
|
end;
|
|
end;
|
|
|
|
function convertBin(var digits:KeywordString):integer;
|
|
var i,v,len:integer;
|
|
c:char;
|
|
begin
|
|
len := length(digits);
|
|
|
|
i := 2;
|
|
convertBin := 0;
|
|
|
|
while i <= len do
|
|
begin
|
|
c := digits[i];
|
|
if c <> '_' then (* ignore '_' for a syntax like 0000_0001 *)
|
|
begin
|
|
convertBin := convertBin shl 1;
|
|
|
|
if (c >= '0') and (c <= '1') then
|
|
v := ord(c) - ord('0')
|
|
else
|
|
errorExit2('Invalid number',digits);
|
|
|
|
convertBin := convertBin + v;
|
|
end;
|
|
i := i + 1;
|
|
end;
|
|
end;
|
|
|
|
function convertChar(digits:KeywordString):integer;
|
|
begin
|
|
convertChar := ord(digits[1]);
|
|
end;
|
|
|
|
function convertNumber(digits:KeywordString):integer;
|
|
var negate:boolean;
|
|
begin
|
|
negate := digits[1] = '-';
|
|
(* we need to keep the sign for decimals
|
|
because we cannot represent abs(-maxint)
|
|
as a signed 32-bit integer and integerFromString
|
|
uses val() *)
|
|
if negate then
|
|
if (digits[2] in [ '$', '%' ]) then
|
|
delete(digits,1,1);
|
|
|
|
if digits[1] = '$' then
|
|
convertNumber := convertHex(digits)
|
|
else
|
|
if digits[1] = '%' then
|
|
convertNumber := convertBin(digits)
|
|
else
|
|
begin
|
|
negate := false;
|
|
convertNumber := integerFromString(digits);
|
|
end;
|
|
if negate then
|
|
convertNumber := -convertNumber;
|
|
end;
|
|
|
|
function getCharLitValue(tokenText:string):integer;
|
|
begin
|
|
(* is is a one-character-string-literal like 'A' ? *)
|
|
if length(tokenText) = 1 then
|
|
getCharLitValue := ord(tokenText[1])
|
|
else
|
|
errorExit2('Cannot use string as char here', tokenText);
|
|
end;
|
|
|
|
(* scan for an integer number. the first digit is already in curChar.
|
|
digits are written to keyword. *)
|
|
procedure getDigits(curChar: char; var keyword: KeywordString);
|
|
begin
|
|
keyword := keyword + curChar;
|
|
while peekChar in [ '0'..'9' ] do
|
|
begin
|
|
keyword := keyword + nextChar;
|
|
end;
|
|
end;
|
|
|
|
procedure hexstr(value:integer;var output:string);
|
|
var i:integer;
|
|
nibble:integer;
|
|
c:char;
|
|
begin
|
|
output := '00000000';
|
|
|
|
for i := 8 downto 1 do
|
|
begin
|
|
nibble := value and $F;
|
|
if nibble > 9 then
|
|
c := chr( ord('A') + nibble - 10)
|
|
else
|
|
c := chr( ord('0') + nibble);
|
|
|
|
output[i] := c;
|
|
value := value shr 4;
|
|
if value = 0 then break;
|
|
end;
|
|
end;
|
|
|
|
(* Scan for an integer number in hexadecimal format.
|
|
The hex marker '$' is already in curChar.
|
|
Digits are written to keyword. *)
|
|
procedure getHexDigits(curChar: char; var keyword: KeywordString);
|
|
begin
|
|
keyword := keyword + curChar;
|
|
while peekChar in [ '0'..'9', 'A'..'F' ] do
|
|
begin
|
|
keyword := keyword + nextChar;
|
|
end;
|
|
end;
|
|
|
|
procedure getToken(var tokenReturn:Token;stringTokens:boolean);
|
|
var curChar,pkChar: char;
|
|
keyword: KeywordString;
|
|
startLine: string[12];
|
|
|
|
function isKeywordChar(ch:char):boolean;
|
|
begin
|
|
isKeywordChar := (ch >= 'A') and (ch <= 'Z') or
|
|
(ch >= 'a') and (ch <= 'z') or
|
|
(ch >= '0') and (ch <= '9') or
|
|
(ch = '_') or (ch = '.');
|
|
end;
|
|
|
|
function isAlpha(ch:char):boolean;
|
|
begin
|
|
isAlpha := ((ch >= 'A') and (ch <= 'Z')) or
|
|
((ch >= 'a') and (ch <= 'z'));
|
|
end;
|
|
|
|
function isKeywordStart(ch:char):boolean;
|
|
begin
|
|
isKeywordStart := ((ch >= 'A') and (ch <= 'Z')) or
|
|
((ch >= 'a') and (ch <= 'z')) or
|
|
(ch = '_');
|
|
end;
|
|
|
|
begin
|
|
curChar := nextChar;
|
|
|
|
tokenReturn.tokenText := curChar;
|
|
|
|
if curChar = shortcutChar then (* two character instruction shortcut *)
|
|
begin
|
|
keyword := curChar + nextChar;
|
|
|
|
(* shortcuts can have modifiers *)
|
|
while isKeywordChar(peekChar) do
|
|
begin
|
|
curChar := Upcase(nextChar);
|
|
if (length(keyword) < 80) and (curChar <> #0) then keyword := keyword + curChar;
|
|
end;
|
|
|
|
tokenReturn.tokenKind := KeywordToken;
|
|
tokenReturn.tokenText := keyword;
|
|
end
|
|
else
|
|
if curChar = #0 then
|
|
tokenReturn.tokenKind := EOFToken
|
|
else
|
|
if curChar = #10 then
|
|
tokenReturn.tokenKind := EOLToken
|
|
else
|
|
if curChar = '+' then
|
|
tokenReturn.tokenKind := PlusToken
|
|
else
|
|
if curChar = '-' then
|
|
tokenReturn.tokenKind := MinusToken
|
|
else
|
|
if curChar = '*' then
|
|
tokenReturn.tokenKind := AsteriskToken
|
|
else
|
|
if curChar = '/' then
|
|
tokenReturn.tokenKind := SlashToken
|
|
else
|
|
if curChar = '~' then
|
|
tokenReturn.tokenKind := TildeToken
|
|
else
|
|
if curChar = '@' then
|
|
tokenReturn.tokenKind := AtToken
|
|
else
|
|
if curChar = ',' then
|
|
tokenReturn.tokenKind := CommaToken
|
|
else
|
|
if curChar = '&' then
|
|
tokenReturn.tokenKind := AndToken
|
|
else
|
|
if curChar = '|' then
|
|
tokenReturn.tokenKind := OrToken
|
|
else
|
|
if curChar = '^' then
|
|
tokenReturn.tokenKind := XorToken
|
|
else
|
|
if curChar = '.' then
|
|
begin
|
|
pkChar := peekChar;
|
|
if isAlpha(pkChar) then
|
|
begin
|
|
keyword := Upcase(curChar);
|
|
while isKeywordChar(peekChar) do
|
|
begin
|
|
curChar := Upcase(nextChar);
|
|
if (length(keyword) < 80) and (curChar <> #0) then keyword := keyword + curChar;
|
|
end;
|
|
tokenReturn.tokenText := keyword;
|
|
tokenReturn.tokenKind := DirectiveToken;
|
|
end
|
|
else
|
|
tokenReturn.tokenKind := DotToken;
|
|
end
|
|
else
|
|
if curChar = '%' then
|
|
(* percent sign can be the start of a binary number or
|
|
an include directive *)
|
|
begin
|
|
pkChar := peekChar;
|
|
if pkChar in ['A'..'Z', 'a'..'z' ] then (* is it a meta directive? *)
|
|
begin
|
|
keyword := Upcase(curChar);
|
|
while peekChar in ['A'..'Z', 'a'..'z', '0'..'9', '_'] do
|
|
begin
|
|
curChar := Upcase(nextChar);
|
|
if (length(keyword) < 80) and (curChar <> #0) then keyword := keyword + curChar;
|
|
end;
|
|
tokenReturn.tokenText := keyword;
|
|
tokenReturn.tokenKind := MetaKeywordToken;
|
|
end
|
|
else
|
|
if pkChar in ['0'..'1' ] then (* is it a binary number? *)
|
|
begin
|
|
keyword := curChar;
|
|
while peekChar in ['0','1','_' ] do
|
|
begin
|
|
curChar := nextChar;
|
|
if (length(keyword) < 80) and (curChar <> #0) then keyword := keyword + curChar;
|
|
end;
|
|
tokenReturn.tokenText := keyword;
|
|
tokenReturn.tokenKind := NumberToken;
|
|
end
|
|
else
|
|
tokenReturn.tokenKind := PercentToken; (* this is most likely unusable *)
|
|
end
|
|
else
|
|
if curChar = ';' then
|
|
tokenReturn.tokenKind := SemicolonToken
|
|
else
|
|
if isKeywordStart(curChar) then
|
|
begin
|
|
keyword := Upcase(curChar);
|
|
while isKeywordChar(peekChar) do
|
|
begin
|
|
curChar := Upcase(nextChar);
|
|
if (length(keyword) < 80) and (curChar <> #0) then keyword := keyword + curChar;
|
|
end;
|
|
|
|
tokenReturn.tokenText := keyword;
|
|
if peekChar = ':' then
|
|
begin
|
|
tokenReturn.tokenKind := LabelToken;
|
|
curChar := nextChar;
|
|
end
|
|
else
|
|
tokenReturn.tokenKind := KeywordToken;
|
|
end
|
|
else
|
|
if isdigit(curChar) then
|
|
begin
|
|
keyword := '';
|
|
getDigits(curChar, keyword);
|
|
tokenReturn.tokenText := keyword;
|
|
tokenReturn.tokenKind := NumberToken;
|
|
end
|
|
else
|
|
if curChar = '$' then
|
|
begin
|
|
keyword := '';
|
|
getHexDigits(curChar, keyword);
|
|
tokenReturn.tokenText := keyword;
|
|
tokenReturn.tokenKind := NumberToken;
|
|
end
|
|
else
|
|
if (curChar = '''') and stringTokens then
|
|
begin
|
|
keyword := nextChar;
|
|
curChar := nextChar;
|
|
tokenReturn.tokenKind := CharLitToken;
|
|
tokenReturn.tokenText := keyword;
|
|
|
|
if curChar <> '''' then
|
|
errorExit2('Invalid character literal, missing ''', keyword);
|
|
end
|
|
else
|
|
if (curChar = '"') and stringTokens then
|
|
begin
|
|
str(lineno, startLine);
|
|
keyword := '';
|
|
curChar := nextChar;
|
|
(* add characters as long as the current char is not '
|
|
(or if it is a double ') and not EOF *)
|
|
while (not ((curChar = '"') and (peekChar <> '"'))) and (curChar <> #0 ) do
|
|
begin
|
|
if (curChar = '"') and (peekChar = '"') then
|
|
begin
|
|
keyword := keyword + curChar;
|
|
curChar := nextChar;
|
|
end
|
|
else
|
|
keyword := keyword + curChar;
|
|
curChar := nextChar;
|
|
end;
|
|
if curChar = #0 then
|
|
errorExit2('Unterminated string constant starting at line', startLine);
|
|
tokenReturn.tokenText := keyword;
|
|
(* string literals with a length of 1 are char literals
|
|
which may be converted into string constants later *)
|
|
if length(keyword) = 1 then
|
|
tokenReturn.tokenKind := CharLitToken
|
|
else
|
|
tokenReturn.tokenKind := StringLitToken;
|
|
end
|
|
else
|
|
tokenReturn.tokenKind := UnknownToken;
|
|
end;
|
|
|
|
(* check for (and do not consume) a specific token, returns true on match *)
|
|
function checkToken(kind: TokenType): boolean;
|
|
begin
|
|
checkToken := curToken.tokenKind = kind;
|
|
end;
|
|
|
|
(* move to next token without any processing.
|
|
sets curToken global variable. *)
|
|
procedure skipToNextToken;
|
|
begin
|
|
getToken(nextToken, true);
|
|
curToken := nextToken;
|
|
end;
|
|
|
|
(* read the next token into the global variable curToken.
|
|
skips whitespace and comments.
|
|
*)
|
|
procedure readNextToken;
|
|
var c:char;
|
|
begin
|
|
skipWhitespace;
|
|
|
|
lastToken := curToken;
|
|
getToken(nextToken, true);
|
|
|
|
curToken := nextToken;
|
|
|
|
if curToken.tokenKind = SemicolonToken then
|
|
begin
|
|
repeat
|
|
c := nextChar;
|
|
until c = #10;
|
|
curToken.tokenKind := EOLToken;
|
|
end;
|
|
end;
|
|
|
|
(* match (and consume) a token or exit with error *)
|
|
procedure matchToken(kind: TokenType);
|
|
begin
|
|
if curToken.tokenKind <> kind then
|
|
errorExit2('Expected ' + descToken(kind) + ', found', curToken.tokenText);
|
|
readNextToken;
|
|
end;
|
|
|
|
(* match (and consume) a token, returning true, or if no match, do not
|
|
consume token and return false *)
|
|
function matchTokenOrNot(wantedToken: TokenType): boolean;
|
|
begin
|
|
if checkToken(wantedToken) then
|
|
begin
|
|
matchTokenOrNot := true;
|
|
readNextToken;
|
|
end
|
|
else
|
|
matchTokenOrNot := false;
|
|
end;
|
|
|
|
procedure emitInstructionWord(value:InstructionWord); forward;
|
|
procedure fixupLabel(oldPc, newPc:integer); forward;
|
|
|
|
procedure alignOutput(amount:integer);
|
|
var mask,o:integer;
|
|
oldPc:integer;
|
|
begin
|
|
oldPc := pc;
|
|
mask := amount - 1;
|
|
|
|
o := pc and mask;
|
|
if o = 2 then
|
|
begin
|
|
emitInstructionWord(0);
|
|
(* if there was a label, we need to fix the address *)
|
|
fixupLabel(oldPc, pc);
|
|
end
|
|
else
|
|
if o = 0 then
|
|
begin (* do nothing *) end
|
|
else
|
|
errorExit2('internal error: bad alignment', '');
|
|
end;
|
|
|
|
procedure emitBinByte(value:integer);
|
|
var c:char;
|
|
begin
|
|
c := chr(value and $FF);
|
|
write(outfile,c);
|
|
end;
|
|
|
|
procedure emitBin16(value:InstructionWord);
|
|
var hi,lo:integer;
|
|
begin
|
|
hi := value and $FF00 shr 8;
|
|
lo := value and $00FF;
|
|
emitBinByte(hi);
|
|
emitBinByte(lo);
|
|
end;
|
|
|
|
procedure emitBin32(value:MachineWord);
|
|
var b3,b2,b1,b0:integer;
|
|
begin
|
|
b0 := value and $FF;
|
|
value := value shr 8;
|
|
b1 := value and $FF;
|
|
value := value shr 8;
|
|
b2 := value and $FF;
|
|
value := value shr 8;
|
|
b3 := value and $FF;
|
|
emitBinByte(b3);
|
|
emitBinByte(b2);
|
|
emitBinByte(b1);
|
|
emitBinByte(b0);
|
|
end;
|
|
|
|
procedure emitAsciiBin(encoded:InstructionWord);
|
|
var i:integer;
|
|
digit:char;
|
|
begin
|
|
for i := 1 to 16 do
|
|
begin
|
|
if (encoded and $8000) <> 0 then
|
|
digit := '1'
|
|
else
|
|
digit := '0';
|
|
|
|
write(outfile, digit);
|
|
encoded := encoded shl 1;
|
|
end;
|
|
if((pc and 3) = 2) then
|
|
writeln(outfile);
|
|
end;
|
|
|
|
(* assumes 32-bit alignment *)
|
|
procedure emitAsciiBin32(value:MachineWord);
|
|
var i:integer;
|
|
digit:char;
|
|
begin
|
|
for i := 1 to 32 do
|
|
begin
|
|
if (value and $80000000) <> 0 then
|
|
digit := '1'
|
|
else
|
|
digit := '0';
|
|
|
|
write(outfile, digit);
|
|
value := value shl 1;
|
|
end;
|
|
writeln(outfile);
|
|
end;
|
|
|
|
procedure emitAsciiByte(value:integer);
|
|
var i:integer;
|
|
digit:char;
|
|
begin
|
|
for i := 1 to 8 do
|
|
begin
|
|
if (value and $80) <> 0 then
|
|
digit := '1'
|
|
else
|
|
digit := '0';
|
|
|
|
write(outfile, digit);
|
|
value := value shl 1;
|
|
end;
|
|
|
|
if (pc and wordSizeMask) = 3 then
|
|
writeln(outfile);
|
|
end;
|
|
|
|
(*
|
|
Emit a single byte.
|
|
Make sure to do proper alignment afterwards.
|
|
Mainly used for .BYTE directive.
|
|
*)
|
|
procedure emitByte(value:integer);
|
|
begin
|
|
if outputEnabled then
|
|
begin
|
|
if asciiOutput then
|
|
emitAsciiByte(value)
|
|
else
|
|
emitBinByte(value);
|
|
end;
|
|
bytesCount := bytesCount + 1;
|
|
pc := pc + 1;
|
|
end;
|
|
|
|
(* assumes aligned output *)
|
|
procedure emitWord(value:MachineWord);
|
|
begin
|
|
if outputEnabled then
|
|
begin
|
|
if asciiOutput then
|
|
emitAsciiBin32(value)
|
|
else
|
|
emitBin32(value);
|
|
end;
|
|
bytesCount := bytesCount + 4;
|
|
pc := pc + 4;
|
|
end;
|
|
|
|
procedure emitInstructionWord(value:InstructionWord);
|
|
begin
|
|
if outputEnabled then
|
|
begin
|
|
if asciiOutput then
|
|
emitAsciiBin(value)
|
|
else
|
|
emitBin16(value);
|
|
end;
|
|
bytesCount := bytesCount + 2;
|
|
pc := pc + 2;
|
|
end;
|
|
|
|
procedure emitInstruction(var opcode:OpcodeData; encoded:InstructionWord);
|
|
begin
|
|
emitInstructionWord(encoded);
|
|
end;
|
|
|
|
procedure emitBlock(count:integer; value:integer);
|
|
var i:integer;
|
|
begin
|
|
alignOutput(wordSize);
|
|
for i := 1 to count do
|
|
emitWord(value);
|
|
end;
|
|
|
|
procedure encodeOperand(value:integer; var op:OpcodeData; var encoded:InstructionWord); forward;
|
|
procedure getBaseAndModifiers(var ins:KeywordString; var opcode:OpcodeData; var encoded:InstructionWord); forward;
|
|
|
|
procedure encodeInstruction(ins:string; operand:integer; var encoded:InstructionWord);
|
|
var opcode:OpcodeData;
|
|
begin
|
|
getBaseAndModifiers(ins, opcode, encoded);
|
|
if opcode.id = LOADCPId then
|
|
errorExit2('internal error in encodeInstruction', curToken.tokenText);
|
|
encodeOperand(operand, opcode, encoded);
|
|
end;
|
|
|
|
procedure ClearTree(var root:TreeRef);
|
|
begin
|
|
while root <> nil do
|
|
begin
|
|
(* delete subtrees first to reduce reshuffling of nodes *)
|
|
if root^.left <> nil then TreeDelete(root^.left, root^.left^.key^);
|
|
if root^.right <> nil then TreeDelete(root^.right, root^.right^.key^);
|
|
TreeDelete(root, root^.key^);
|
|
end;
|
|
verifyTree(root);
|
|
end;
|
|
|
|
procedure ClearCPool;
|
|
var current:CPoolRef;
|
|
next:CPoolRef;
|
|
begin
|
|
current := constantPool;
|
|
|
|
while current <> nil do
|
|
begin
|
|
next := current^.next;
|
|
dispose(current);
|
|
current := next;
|
|
end;
|
|
constantPool := nil;
|
|
end;
|
|
|
|
procedure createSymbol(var keyword:IdentString; typ:SymbolType; value:integer; aligned,padded:boolean);
|
|
forward;
|
|
procedure dumpCPool; forward;
|
|
|
|
procedure emitConstantPool(branch:boolean);
|
|
var current:CPoolRef;
|
|
labelname:IdentString;
|
|
value:KeywordString;
|
|
intValue:integer;
|
|
size:integer;
|
|
encoded:InstructionWord;
|
|
padded:boolean;
|
|
begin
|
|
(* writeln('*** emitConstantPool at ', pc, ' count ', cPoolCount); *)
|
|
if branch then
|
|
begin
|
|
(* calculate size of all constants in bytes *)
|
|
size := cPoolCount * wordSize;
|
|
|
|
(*
|
|
Add padding if alignment is needed.
|
|
This happens when the pc is at a word boundary (pc and wordSize = 0).
|
|
Then after the branch instruction, the pc is at a half-word boundary
|
|
and we need a padding half-word to get the correct alignment for the
|
|
word-sized constants.
|
|
*)
|
|
if (pc and wordSizeMask) = 0 then size := size + insSize;
|
|
|
|
(* encode the instruction, adjust operand for the size of the branch instruction*)
|
|
encodeInstruction('BRANCH', pc + size + insSize, encoded);
|
|
emitInstructionWord(encoded);
|
|
end;
|
|
|
|
|
|
current := constantPool;
|
|
padded := false;
|
|
|
|
(* if the constant pool is empty, do no alignment *)
|
|
if current <> nil then
|
|
begin
|
|
(* remember if we needed alignment padding *)
|
|
padded := (pc and wordSizeMask) = insSize;
|
|
alignOutput(wordSize);
|
|
end;
|
|
|
|
(* the cpool list has the latest entries at the front,
|
|
so go to the tail first and then go backward *)
|
|
if current <> nil then
|
|
while current^.next <> nil do
|
|
current := current^.next;
|
|
|
|
while current <> nil do
|
|
begin
|
|
labelname := current^.labelname;
|
|
value := current^.value;
|
|
|
|
createSymbol(labelname, LabelSymbol, pc, true, padded);
|
|
(* only the first entry is marked as padded *)
|
|
if padded then padded := false;
|
|
if current^.symbolic then
|
|
intValue := getSymbolValue(value)
|
|
else
|
|
intValue := convertNumber(value);
|
|
|
|
if intValue <> Unresolved then
|
|
intValue := intValue + current^.offset;
|
|
emitWord(intValue);
|
|
|
|
current := current^.prev;
|
|
end;
|
|
|
|
(* writeln('*** emitConstantPool new pc ', pc); *)
|
|
(* dumpCPool; *)
|
|
ClearCPool;
|
|
cPoolCount := 0;
|
|
end;
|
|
|
|
procedure encodeOperand(value:integer; var op:OpcodeData; var encoded:InstructionWord);
|
|
var mask, negativeMask:integer;
|
|
valueStr:string;
|
|
isSigned:boolean;
|
|
min, max: integer;
|
|
begin
|
|
case op.operand of
|
|
NoOprnd: begin mask := 0; negativeMask := 0; end;
|
|
U13WOprnd: begin mask := $1FFE; negativeMask := $0000; end;
|
|
S13Oprnd: begin mask := $1FFF; negativeMask := $1000; end;
|
|
RelWOprnd: begin mask := $1FFE; negativeMask := $1000; value := value - pc; end;
|
|
S10Oprnd: begin mask := $03FF; negativeMask := $0200; end;
|
|
U4Oprnd: begin mask := $000F; negativeMask := $0000; end;
|
|
CmpOprnd: begin mask := $000F; negativeMask := $0000; end;
|
|
RegOprnd: begin mask := $000F; negativeMask := $0000; end;
|
|
RelU10Oprnd: begin mask := $03FF; negativeMask := $0000; value := value - pc; end;
|
|
OptOprnd: begin mask := $000F; negativeMask := $0000; end;
|
|
end;
|
|
|
|
isSigned := negativeMask <> 0;
|
|
|
|
if isSigned then
|
|
begin
|
|
min := -negativeMask;
|
|
max := -min + 1;
|
|
end
|
|
else
|
|
begin
|
|
min := 0;
|
|
max := mask;
|
|
end;
|
|
|
|
if (value < min) or (value > max) then
|
|
begin
|
|
(* if not on the last pass (generating output),
|
|
we ignore values which are out of range,
|
|
because they might change after the 1st pass *)
|
|
|
|
if not outputEnabled then
|
|
value := 0
|
|
else
|
|
begin
|
|
if op.operand = RelU10Oprnd then
|
|
begin
|
|
writeln;
|
|
writeln('bad RelU10Oprnd:', value, ' pc:', pc, ' pass:', pass);
|
|
dumpCPool;
|
|
end;
|
|
str(value, valueStr);
|
|
errorExit2('Invalid operand value', valueStr);
|
|
end;
|
|
end;
|
|
|
|
encoded := (encoded and not mask) or (value and mask);
|
|
end;
|
|
|
|
function getSymbol(var keyword:KeywordString):TreeDataRef;
|
|
begin
|
|
getSymbol := TreeSearch(symbolTable, keyword);
|
|
if (getSymbol = nil) and (pass > 1) then
|
|
errorExit2('Undeclared symbol', keyword);
|
|
end;
|
|
|
|
function getLabelAddr(var keyword:KeywordString):integer;
|
|
var sym:TreeDataRef;
|
|
begin
|
|
sym := getSymbol(curToken.tokenText);
|
|
|
|
if sym = nil then
|
|
getLabelAddr := Unresolved
|
|
else
|
|
getLabelAddr := sym^.symboldata.value;
|
|
end;
|
|
|
|
function findSymbol(var keyword:KeywordString):TreeDataRef;
|
|
begin
|
|
findSymbol := TreeSearch(symbolTable, keyword);
|
|
end;
|
|
|
|
function getSymbolValue(var keyword:KeywordString):integer;
|
|
var data:TreeDataRef;
|
|
begin
|
|
data := TreeSearch(symbolTable, keyword);
|
|
|
|
if (data = nil) then
|
|
begin
|
|
if pass = 1 then
|
|
(* in pass 1, we do not care about undefined symbols *)
|
|
getSymbolValue := Unresolved
|
|
else
|
|
errorExit2('Undeclared symbol', keyword);
|
|
end
|
|
else
|
|
getSymbolValue := data^.symboldata.value;
|
|
end;
|
|
|
|
procedure addUncertainLabel(var name:IdentString);
|
|
var current:UnresBranchRef;
|
|
newLListEntry:LabelListRef;
|
|
begin
|
|
current := firstUnresBranch;
|
|
|
|
while (current <> nil) do
|
|
begin
|
|
(* writeln('**addUncertainLabel ',name, ' for unresBranch ', current^.target); *)
|
|
(* put new label list entry at head of the list *)
|
|
new(newLListEntry);
|
|
newLListEntry^.next := current^.labels;
|
|
newLListEntry^.prev := nil;
|
|
newLListEntry^.name := name;
|
|
if current^.labels <> nil then
|
|
current^.labels^.prev := newLListEntry;
|
|
current^.labels := newLListEntry;
|
|
|
|
current := current^.next;
|
|
end;
|
|
end;
|
|
|
|
procedure addUnresBranch(var name:IdentString; origin:integer; maxDistance,shrink:integer);
|
|
var newUnresBranch:^UnresolvedBranch;
|
|
begin
|
|
new(newUnresBranch);
|
|
newUnresBranch^.target := name;
|
|
newUnresBranch^.origin := origin;
|
|
newUnresBranch^.maxDistance := maxDistance;
|
|
newUnresBranch^.shrinkage := shrink;
|
|
newUnresBranch^.labels := nil;
|
|
newUnresBranch^.next := firstUnresBranch;
|
|
firstUnresBranch := newUnresBranch;
|
|
|
|
(* writeln('** addUnresBranch ', name, ' at ', origin); *)
|
|
end;
|
|
|
|
(* Delete unresolved branch entry with target *name*.
|
|
Will delete multiple occurrences of the same name. *)
|
|
procedure deleteUnresBranch(var name:IdentString);
|
|
var last,current,temp:^UnresolvedBranch;
|
|
begin
|
|
current := firstUnresBranch;
|
|
last := nil;
|
|
|
|
while (current <> nil) do
|
|
begin
|
|
if current^.target = name then
|
|
begin
|
|
if last = nil then
|
|
firstUnresBranch := current^.next
|
|
else
|
|
last^.next := current^.next;
|
|
temp := current^.next;
|
|
dispose(current);
|
|
current := temp;
|
|
end
|
|
else
|
|
begin
|
|
last := current;
|
|
current := current^.next;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure addUnresBranch2(var name:IdentString; o:integer; max,shrink:integer);
|
|
var newUnresBranch:^UnresolvedBranch;
|
|
begin
|
|
new(newUnresBranch);
|
|
with newUnresBranch^ do
|
|
begin
|
|
target := name;
|
|
origin := o;
|
|
maxDistance := max;
|
|
shrinkage := shrink;
|
|
labels := nil;
|
|
next := firstUnresBranch;
|
|
end;
|
|
firstUnresBranch := newUnresBranch;
|
|
end;
|
|
|
|
(* Check if a label declaration resolves an unresolved branch
|
|
(.LBRANCH/.LCBRANCH). If it does, apply the code size correction
|
|
to all labels we encountered since the branch. *)
|
|
procedure checkUnresBranches(var name:IdentString);
|
|
var r:UnresBranchRef;
|
|
current,last,next:LabelListRef;
|
|
sym:TreeDataRef;
|
|
distance:integer;
|
|
shrink:boolean;
|
|
adjustment:integer;
|
|
begin
|
|
r := firstUnresBranch;
|
|
|
|
while r <> nil do
|
|
begin
|
|
if r^.target = name then
|
|
begin
|
|
distance := pc - r^.origin;
|
|
shrink := distance <= r^.maxDistance;
|
|
{ writeln('** checkUnresBranches found ', name, ' at ', pc, ' distance ', pc - r^.origin, ' line ', lineno);
|
|
writeln(' ', r^.origin, ' ', r^.maxDistance, ' ', r^.shrinkage); }
|
|
current := r^.labels;
|
|
last := nil;
|
|
|
|
if shrink then
|
|
pc := pc - r^.shrinkage;
|
|
(* writeln(' short:', shrink); *)
|
|
|
|
while current <> nil do
|
|
begin
|
|
(* go through all labels we encountered since the LBRANCH/LCBRANCH,
|
|
process and dispose of the list entries *)
|
|
|
|
(* writeln(' ', current^.name); *)
|
|
|
|
if shrink then
|
|
begin
|
|
sym := findSymbol(current^.name);
|
|
with sym^.symboldata do
|
|
begin
|
|
(* writeln(' ', value, ' -', r^.shrinkage, ' ', aligned, ' ', padded); *)
|
|
value := value - r^.shrinkage;
|
|
(* writeln(' ', value); *)
|
|
end;
|
|
end;
|
|
last := current;
|
|
current := current^.next;
|
|
end;
|
|
|
|
(* walk through list backwards to check alignment,
|
|
that is in order of occurrence *)
|
|
adjustment := 0;
|
|
current := last;
|
|
while current <> nil do
|
|
begin
|
|
if shrink then
|
|
begin
|
|
sym := findSymbol(current^.name);
|
|
with sym^.symboldata do
|
|
begin
|
|
if aligned then
|
|
begin
|
|
(* if this label needs alignment, shift all labels
|
|
after this one by one instruction word (2 bytes) *)
|
|
if ((value + adjustment) and wordSizeMask) = insSize then
|
|
begin
|
|
(* if a constant pool entry was already padded,
|
|
do not add second padding but remove first
|
|
padding instead *)
|
|
if not padded then
|
|
begin
|
|
padded := true;
|
|
pc := pc + insSize;
|
|
adjustment := adjustment + insSize;
|
|
end
|
|
else
|
|
begin
|
|
padded := false;
|
|
pc := pc - insSize;
|
|
adjustment := adjustment - insSize;
|
|
end;
|
|
end;
|
|
end;
|
|
value := value + adjustment;
|
|
(* if adjustment <> 0 then
|
|
writeln(' ', current^.name, ' adjusted to ', value, ' by ', adjustment); *)
|
|
end;
|
|
end;
|
|
{$ifndef FPC}
|
|
{writeln(' disposing ', current^.name, ' ', current);}
|
|
{$endif}
|
|
next := current^.prev;
|
|
dispose(current);
|
|
current := next;
|
|
end;
|
|
r^.labels := nil;
|
|
end;
|
|
r := r^.next;
|
|
end;
|
|
deleteUnresBranch(name);
|
|
end;
|
|
|
|
(* Create a symbol table entry.
|
|
On passes other than 1, symbol must exist and the value is updated. *)
|
|
procedure createSymbol(var keyword:IdentString; typ:SymbolType; value:integer; aligned, padded:boolean);
|
|
var d:TreeData;
|
|
dref:TreeDataRef;
|
|
begin
|
|
dref := TreeSearch(symbolTable, keyword);
|
|
|
|
if pass = 1 then
|
|
begin
|
|
if dref <> nil then
|
|
errorExit2('Duplicate label', keyword);
|
|
d.typ := TDSymbol;
|
|
d.symboldata.typ := typ;
|
|
d.symboldata.value := value;
|
|
d.symboldata.aligned := aligned;
|
|
d.symboldata.padded := padded;
|
|
d.symboldata.exported := false;
|
|
TreeInsert(symbolTable, keyword, d);
|
|
|
|
addUncertainLabel(keyword);
|
|
checkUnresBranches(keyword);
|
|
end
|
|
else
|
|
begin
|
|
if dref = nil then
|
|
begin
|
|
dumpCPool;
|
|
ErrorExit2('internal error in createSymbol', keyword);
|
|
end;
|
|
|
|
if dref^.symboldata.value <> value then
|
|
dref^.symboldata.value := value;
|
|
end;
|
|
end;
|
|
|
|
(* Change the address of a label after it has been
|
|
created. This happens if an alignment is required
|
|
e.g. for a .WORD directive which has a label *)
|
|
procedure fixupLabel(oldPc, newPc:integer);
|
|
var current:TreeRef;
|
|
walkState:TreeWalkState;
|
|
begin
|
|
TreeWalkFirst(symbolTable, walkState, current);
|
|
while current <> nil do
|
|
begin
|
|
if current^.data^.symboldata.value = oldPc then
|
|
begin
|
|
current^.data^.symboldata.aligned := true;
|
|
current^.data^.symboldata.value := newPc;
|
|
current^.data^.symboldata.padded := (newPc <> oldPc);
|
|
|
|
(* if newPc <> oldPc then
|
|
writeln(' aligning ', current^.key^, ' ', oldPc, ' -> ', newPc); *)
|
|
end;
|
|
TreeWalkNext(walkState, current);
|
|
end;
|
|
end;
|
|
|
|
function parsePrimary:integer;
|
|
var applyNot:boolean;
|
|
negate:boolean;
|
|
value:integer;
|
|
begin
|
|
if checkToken(TildeToken) then
|
|
begin
|
|
readNextToken;
|
|
applyNot := true;
|
|
end
|
|
else
|
|
applyNot := false;
|
|
|
|
if checkToken(MinusToken) then
|
|
begin
|
|
readNextToken;
|
|
negate := true;
|
|
end
|
|
else
|
|
negate := false;
|
|
|
|
if checkToken(NumberToken) then
|
|
begin
|
|
(* let convertNumber handle negative numbers
|
|
because the statement
|
|
"value := -value" below would
|
|
not work for -maxint.
|
|
abs(-maxint) cannot be represented
|
|
as a signed 32-bit integer *)
|
|
if negate then
|
|
begin
|
|
value := convertNumber('-' + curToken.tokenText);
|
|
negate := false;
|
|
end
|
|
else
|
|
value := convertNumber(curToken.tokenText);
|
|
end
|
|
else
|
|
if checkToken(KeywordToken) then
|
|
value := getSymbolValue(curToken.tokenText)
|
|
else
|
|
if checkToken(CharLitToken) then
|
|
value := convertChar(curToken.tokenText)
|
|
else
|
|
if checkToken(AtToken) then
|
|
value := pc
|
|
else
|
|
errorExit2('number or symbol expected, got', descToken(curToken.tokenKind));
|
|
|
|
if applyNot then
|
|
value := not value;
|
|
|
|
if negate then
|
|
value := - value;
|
|
|
|
readNextToken;
|
|
|
|
parsePrimary := value;
|
|
end;
|
|
|
|
function parseExpression:integer;
|
|
var value:integer;
|
|
|
|
function parseNextPrimary:integer;
|
|
begin
|
|
readNextToken;
|
|
parseNextPrimary := parsePrimary();
|
|
end;
|
|
|
|
begin
|
|
value := parsePrimary;
|
|
|
|
while not (curToken.tokenKind in [ EOLToken, CommaToken ]) do
|
|
begin
|
|
if checkToken(PlusToken) then
|
|
value := value + parseNextPrimary
|
|
else
|
|
if checkToken(MinusToken) then
|
|
value := value - parseNextPrimary
|
|
else
|
|
if checkToken(AsteriskToken) then
|
|
value := value * parseNextPrimary
|
|
else
|
|
if checkToken(AndToken) then
|
|
value := value and parseNextPrimary
|
|
else
|
|
if checkToken(OrToken) then
|
|
value := value or parseNextPrimary
|
|
else
|
|
if checkToken(XorToken) then
|
|
value := value xor parseNextPrimary
|
|
else
|
|
if checkToken(SlashToken) then
|
|
value := value div parseNextPrimary
|
|
else
|
|
errorExit2('Expected one of + - * / & | ^ but got', curToken.tokenText);
|
|
end;
|
|
|
|
parseExpression := value;
|
|
end;
|
|
|
|
procedure parseLabel;
|
|
begin
|
|
(* writeln(':::: parseLabel ', curToken.tokenText, ' at ', pc); *)
|
|
createSymbol(curToken.tokenText, LabelSymbol, pc, false, false);
|
|
|
|
readNextToken;
|
|
end;
|
|
|
|
procedure parseOneWordArg;
|
|
var operandValue:integer;
|
|
begin
|
|
operandValue := parseExpression;
|
|
emitWord(operandValue);
|
|
end;
|
|
|
|
procedure parseWordArgs;
|
|
begin
|
|
alignOutput(wordSize);
|
|
parseOneWordArg;
|
|
while checkToken(CommaToken) do
|
|
begin
|
|
readNextToken;
|
|
(* if there is a comma at the end of the line,
|
|
continue to the next line *)
|
|
if checkToken(EOLToken) then
|
|
readNextToken;
|
|
|
|
parseOneWordArg;
|
|
end;
|
|
end;
|
|
|
|
procedure parseOneByteArg;
|
|
var bytevalue:integer;
|
|
c:char;
|
|
begin
|
|
if checkToken(StringLitToken) then
|
|
begin
|
|
for c in curToken.tokenText do
|
|
emitByte(ord(c));
|
|
readNextToken;
|
|
end
|
|
else
|
|
begin
|
|
bytevalue := parseExpression;
|
|
emitByte(byteValue);
|
|
end;
|
|
end;
|
|
|
|
procedure parseByteArgs;
|
|
begin
|
|
alignOutput(wordSize);
|
|
parseOneByteArg;
|
|
while checkToken(CommaToken) do
|
|
begin
|
|
readNextToken;
|
|
(* if there is a comma at the end of the line,
|
|
continue to the next line *)
|
|
if checkToken(EOLToken) then
|
|
readNextToken;
|
|
|
|
parseOneByteArg;
|
|
end;
|
|
|
|
(* align to word *)
|
|
while (pc and wordsizeMask) <> 0 do
|
|
emitByte(0);
|
|
end;
|
|
|
|
procedure dumpOpcodeTable;
|
|
var walkState:TreeWalkState;
|
|
walkRes:TreeRef;
|
|
begin
|
|
writeln('Opcode Table:');
|
|
TreeWalkStart(opcodeTable, walkState);
|
|
repeat
|
|
TreeWalkNext(walkState, walkRes);
|
|
if walkRes <> nil then
|
|
writeln(walkRes^.key^);
|
|
until walkRes = nil;
|
|
end;
|
|
|
|
function changeFileSuffix(filename: string; suffix:string): string; forward;
|
|
|
|
procedure writeSymbolTable;
|
|
var walkState:TreeWalkState;
|
|
walkRes:TreeRef;
|
|
h:string;
|
|
f:SymFileType;
|
|
fname:string;
|
|
c:char;
|
|
begin
|
|
fname := changeFileSuffix(filename, SymFileSuffix);
|
|
overwriteFile(f, fname);
|
|
|
|
TreeWalkStart(symbolTable, walkState);
|
|
repeat
|
|
TreeWalkNext(walkState, walkRes);
|
|
if walkRes <> nil then
|
|
begin
|
|
if walkRes^.data^.symboldata.typ in [ LabelSymbol, ConstSymbol ] then
|
|
begin
|
|
hexstr(walkRes^.data^.symboldata.value, h);
|
|
if walkRes^.data^.symboldata.typ = ConstSymbol then
|
|
c := '='
|
|
else
|
|
if walkRes^.data^.symboldata.exported then
|
|
c := '!'
|
|
else
|
|
c := ' ';
|
|
writeln(f, h, ' ', c, walkRes^.key^);
|
|
end;
|
|
end;
|
|
until walkRes = nil;
|
|
close(f);
|
|
end;
|
|
|
|
procedure dumpSymbolTable;
|
|
var walkState:TreeWalkState;
|
|
walkRes:TreeRef;
|
|
h:string;
|
|
begin
|
|
writeln('Symbol Table:');
|
|
TreeWalkStart(symbolTable, walkState);
|
|
repeat
|
|
TreeWalkNext(walkState, walkRes);
|
|
if walkRes <> nil then
|
|
begin
|
|
if walkRes^.data^.symboldata.typ = LabelSymbol then
|
|
begin
|
|
hexstr(walkRes^.data^.symboldata.value, h);
|
|
writeln(h, ' ', walkRes^.key^);
|
|
end;
|
|
end;
|
|
until walkRes = nil;
|
|
end;
|
|
|
|
procedure dumpCPool;
|
|
var current:CPoolRef;
|
|
begin
|
|
writeln('dump constant pool ', cPoolCount, ' ', nextConstId);
|
|
|
|
current := constantPool;
|
|
|
|
while current <> nil do
|
|
begin
|
|
writeln(current^.labelname, ': ', current^.value, ' ',
|
|
getSymbolValue(current^.labelname), ' ');
|
|
current := current^.next;
|
|
end;
|
|
end;
|
|
|
|
procedure emitLoadcp(var operand:KeywordString; numOffset:integer);
|
|
var cpooladdr:integer;
|
|
encoded:InstructionWord;
|
|
begin
|
|
cpooladdr := getCPoolAddr(operand, numOffset);
|
|
(* writeln('** emitLoadcp for ',operand, '/', numOffset, ' ', cpooladdr); *)
|
|
encodeInstruction('LOADREL', cpooladdr, encoded);
|
|
emitInstructionWord(encoded);
|
|
end;
|
|
|
|
procedure parseLoadcp;
|
|
var opstr:KeywordString;
|
|
data:TreeDataRef;
|
|
numOffset:integer;
|
|
begin
|
|
if not (curToken.tokenKind in [ KeywordToken, NumberToken, MinusToken ]) then
|
|
errorExit2('Identifier or number expected, got', curToken.tokenText);
|
|
|
|
if checkToken(MinusToken) then
|
|
begin
|
|
opstr := '-';
|
|
readNextToken;
|
|
if not checkToken(NumberToken) then
|
|
errorExit2('Invalid number', curToken.tokenText);
|
|
opstr := opstr + curToken.tokenText;
|
|
end
|
|
else
|
|
opstr := curToken.tokenText;
|
|
|
|
readNextToken;
|
|
|
|
(* check for optional offset *)
|
|
if checkToken(CommaToken) then
|
|
begin
|
|
readNextToken;
|
|
(* offset can be either a number literal *)
|
|
if checkToken(NumberToken) then
|
|
numOffset := convertNumber(curToken.tokenText)
|
|
else
|
|
(* or a symbol *)
|
|
if checkToken(KeywordToken) then
|
|
begin
|
|
data := findSymbol(curToken.tokenText);
|
|
if data = nil then
|
|
errorExit2('Cannot use unresolved symbol for LOADCP offset:',
|
|
curToken.tokenText);
|
|
numOffset := data^.symboldata.value;
|
|
end
|
|
else
|
|
errorExit2('Number or symbol required, got', curToken.tokenText);
|
|
readNextToken;
|
|
end
|
|
else
|
|
numOffset := 0;
|
|
|
|
emitLoadcp(opstr, numOffset);
|
|
end;
|
|
|
|
procedure parseLbranch;
|
|
var value:integer;
|
|
distance:integer;
|
|
offset,shrinkage:integer;
|
|
pad:boolean;
|
|
encoded:InstructionWord;
|
|
begin
|
|
if not checkToken(KeywordToken) then
|
|
errorExit2('identifier expected, got', curToken.tokenText);
|
|
|
|
value := getLabelAddr(curToken.tokenText);
|
|
|
|
distance := value - pc;
|
|
if (value = Unresolved) or (distance > 4095) or (distance < -4096) then
|
|
begin
|
|
if (pc and 3) = 0 then (* no padding *)
|
|
begin
|
|
pad := false;
|
|
(* total size 10 bytes *)
|
|
offset := 4; (* offset for LOADREL *)
|
|
shrinkage := 8; (* difference to short form size *)
|
|
end
|
|
else
|
|
begin
|
|
pad := true;
|
|
(* total size 10 bytes *)
|
|
offset := 6; (* offset for LOADREL with padding *)
|
|
shrinkage := 8; (* difference to short form size *)
|
|
end;
|
|
if value = Unresolved then
|
|
addUnresBranch(curToken.tokenText, pc, 4095, shrinkage);
|
|
|
|
encodeInstruction('LOADREL', pc + offset, encoded);
|
|
emitInstructionWord(encoded);
|
|
|
|
encodeInstruction('JUMP', 0, encoded);
|
|
emitInstructionWord(encoded);
|
|
|
|
(* Padding is always done, so the length of the sequence does
|
|
not change depending on the position. The pad is either
|
|
placed before the address to align it on a word address,
|
|
or after, if it is already aligned. *)
|
|
if pad then
|
|
emitInstructionWord(0);
|
|
|
|
emitWord(value);
|
|
|
|
if not pad then
|
|
emitInstructionWord(0);
|
|
end
|
|
else
|
|
begin
|
|
encodeInstruction('BRANCH', value, encoded);
|
|
emitInstructionWord(encoded);
|
|
end;
|
|
|
|
(*
|
|
if value = Unresolved then
|
|
writeln('** parseLbranch ', curToken.tokenText, ' unresolved')
|
|
else
|
|
if (distance > 4095) then
|
|
writeln('** parseLbranch ', curToken.tokenText, ' long ', distance)
|
|
else
|
|
writeln('** parseLbranch ', curToken.tokenText, ' short ', distance);
|
|
*)
|
|
|
|
readNextToken;
|
|
end;
|
|
|
|
procedure parseLcbranch(negate:boolean);
|
|
var value:integer;
|
|
relValue:integer;
|
|
offset,shrinkage:integer;
|
|
encoded:InstructionWord;
|
|
modifier:string[4];
|
|
pad:boolean;
|
|
begin
|
|
if not checkToken(KeywordToken) then
|
|
errorExit2('identifier expected, got', curToken.tokenText);
|
|
|
|
value := getLabelAddr(curToken.tokenText);
|
|
|
|
relValue := value - pc;
|
|
|
|
modifier := '';
|
|
|
|
if (value = Unresolved) or (relValue > 4095) or (relValue < -4096) then
|
|
begin
|
|
if (pc and 3) = 2 then (* no padding *)
|
|
begin
|
|
pad := false;
|
|
(* total size 12 bytes *)
|
|
offset := 4; (* offset for LOADREL *)
|
|
shrinkage := 10; (* difference to short form size *)
|
|
end
|
|
else
|
|
begin
|
|
pad := true;
|
|
(* total size 12 bytes *)
|
|
offset := 6; (* offset for LOADREL with padding *)
|
|
shrinkage := 10; (* difference to short form size *)
|
|
end;
|
|
|
|
if value = Unresolved then
|
|
addUnresBranch(curToken.tokenText, pc, 4095, shrinkage);
|
|
|
|
(* writeln('*** long cbranch triggered:', value, ' ', relValue, ' pass:',pass);
|
|
writeln('*** pc ', pc, ' offset ', offset, ' insSize ', insSize);
|
|
writeln('*** ', pc + offset + insSize); *)
|
|
if not negate then
|
|
modifier := '.Z';
|
|
(* branch over CBRANCH, LOADREL, padding and literal value *)
|
|
encodeInstruction('CBRANCH' + modifier, pc + insSize + offset + wordSize, encoded);
|
|
emitInstructionWord(encoded);
|
|
|
|
encodeInstruction('LOADREL', pc + offset, encoded);
|
|
emitInstructionWord(encoded);
|
|
|
|
encodeInstruction('JUMP', 0, encoded);
|
|
emitInstructionWord(encoded);
|
|
|
|
(* Padding is always done, so the length of the sequence does
|
|
not change depending on the position. The pad is either
|
|
placed before the address to align it on a word address,
|
|
or after, if it is already aligned. *)
|
|
if pad then
|
|
emitInstructionWord(0);
|
|
|
|
emitWord(value);
|
|
|
|
if not pad then
|
|
emitInstructionWord(0);
|
|
end
|
|
else
|
|
begin
|
|
if negate then
|
|
modifier := '.Z';
|
|
encodeInstruction('CBRANCH' + modifier, value, encoded);
|
|
emitInstructionWord(encoded);
|
|
end;
|
|
|
|
readNextToken;
|
|
end;
|
|
|
|
procedure parseDirective;
|
|
var operandValue:integer;
|
|
count:integer;
|
|
name:IdentString;
|
|
oldsym:TreeDataRef;
|
|
begin
|
|
readNextToken;
|
|
|
|
if lastToken.tokenText = '.LBRANCH' then
|
|
parseLbranch
|
|
else
|
|
if lastToken.tokenText = '.LCBRANCH' then
|
|
parseLcbranch(false)
|
|
else
|
|
if lastToken.tokenText = '.LCBRANCHZ' then
|
|
parseLcbranch(true)
|
|
else
|
|
if lastToken.tokenText = '.ORG' then
|
|
begin
|
|
operandValue := parseExpression;
|
|
pc := operandValue;
|
|
end
|
|
else
|
|
if lastToken.tokenText = '.EQU' then
|
|
begin
|
|
matchToken(KeywordToken);
|
|
name := lastToken.tokenText;
|
|
operandValue := parseExpression;
|
|
|
|
oldsym := findSymbol(name);
|
|
if oldsym <> nil then
|
|
begin
|
|
if oldsym^.symboldata.value <> operandValue then
|
|
errorExit2('Symbol already declared:', name);
|
|
end
|
|
else
|
|
createSymbol(name, ConstSymbol, operandValue, false, false);
|
|
end
|
|
else
|
|
if lastToken.tokenText = '.WORD' then
|
|
parseWordArgs
|
|
else
|
|
if lastToken.tokenText = '.BYTE' then
|
|
parseByteArgs
|
|
else
|
|
if lastToken.tokenText = '.CPOOL' then
|
|
emitConstantPool(false)
|
|
else
|
|
if lastToken.tokenText = '.CPOOLNOP' then
|
|
emitConstantPool(true)
|
|
else
|
|
if lastToken.tokenText = '.BLOCK' then
|
|
begin
|
|
count := parseExpression;
|
|
if matchTokenOrNot(CommaToken) then
|
|
operandValue := parseExpression
|
|
else
|
|
operandValue := 0;
|
|
emitBlock(count, operandValue);
|
|
end
|
|
else
|
|
errorExit2('Unrecognized directive', lastToken.tokenText);
|
|
end;
|
|
|
|
procedure readImage(filename:string);
|
|
var f:InputFileType;
|
|
c:char;
|
|
i:integer;
|
|
size:integer;
|
|
begin
|
|
printFilename(filename);
|
|
ClrEol;
|
|
|
|
openFileWithDefault(f, filename);
|
|
size := filesize(f);
|
|
|
|
pc := pc + size;
|
|
bytesCount := bytesCount + size;
|
|
|
|
if pass = 2 then
|
|
begin
|
|
|
|
for i := 1 to size do
|
|
begin
|
|
read(f,c);
|
|
write(outfile,c);
|
|
end;
|
|
|
|
end;
|
|
|
|
close(f);
|
|
|
|
printCurrentLineno;
|
|
end;
|
|
|
|
procedure parseMetaDirective;
|
|
var filename:string;
|
|
sym:TreeDataRef;
|
|
begin
|
|
readNextToken;
|
|
if lastToken.tokenText = '%INCLUDE' then
|
|
begin
|
|
if curToken.tokenKind in [StringLitToken, KeywordToken] then
|
|
begin
|
|
filename := curToken.tokenText;
|
|
readNextToken;
|
|
beginInclude(filename);
|
|
end
|
|
else
|
|
errorExit2('Filename expected', '');
|
|
end
|
|
else
|
|
if lastToken.tokenText = '%EXPORT' then
|
|
begin
|
|
if curToken.tokenKind = KeywordToken then
|
|
begin
|
|
sym := findSymbol(curToken.tokenText);
|
|
if sym = nil then
|
|
errorExit2('Undeclared symbol', curToken.tokenText);
|
|
sym^.symboldata.exported := true;
|
|
end
|
|
else
|
|
errorExit2('Symbol expected', '');
|
|
readNextToken;
|
|
end
|
|
else
|
|
if lastToken.tokenText = '%INCBIN' then
|
|
begin
|
|
filename := curToken.tokenText;
|
|
readNextToken;
|
|
readImage(filename);
|
|
end
|
|
else
|
|
errorExit2('Invalid meta directive', lastToken.tokenText);
|
|
end;
|
|
|
|
procedure encode(var entry:EncodingEntry; var value:InstructionWord);
|
|
begin
|
|
value := (value and not entry.mask) or entry.value;
|
|
end;
|
|
|
|
procedure getModifier(var m:KeywordString; var opcode:OpcodeData; var encoded:InstructionWord);
|
|
var cur:^ModifierEntry;
|
|
begin
|
|
if opcode.id <> 0 then
|
|
begin
|
|
cur := opcode.modifiers;
|
|
while cur <> nil do
|
|
begin
|
|
if m = cur^.keyword then
|
|
begin
|
|
encode(cur^.encoding, encoded);
|
|
break;
|
|
end;
|
|
cur := cur^.next;
|
|
end;
|
|
if cur = nil then
|
|
errorExit2('Invalid modifier', m);
|
|
end;
|
|
end;
|
|
|
|
procedure getMnemonic(var m:KeywordString; var opcode:OpcodeData; var encoded:InstructionWord);
|
|
var data:TreeDataRef;
|
|
ch:char;
|
|
begin
|
|
if m[1] = shortcutChar then
|
|
begin
|
|
ch := m[2];
|
|
if (ch < firstShCChar) or (ch > lastShCChar) then
|
|
errorExit2('invalid shortcut', m);
|
|
opcode := shortcuts[ch];
|
|
if opcode.id = -1 then
|
|
errorExit2('invalid shortcut', m);
|
|
encode(opcode.encoding, encoded);
|
|
end
|
|
else
|
|
begin
|
|
data := TreeSearch(opcodeTable, m);
|
|
if data = nil then
|
|
begin
|
|
errorExit2('Unrecognized instruction', m);
|
|
opcode.id := 0;
|
|
end
|
|
else
|
|
begin
|
|
opcode := data^.opcodedata;
|
|
encode(opcode.encoding, encoded);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure getBaseAndModifiers(var ins:KeywordString; var opcode:OpcodeData; var encoded:InstructionWord);
|
|
var i:integer;
|
|
insLength:integer;
|
|
slice:string;
|
|
startPos:integer;
|
|
|
|
function scanchar(c:char; curPos:integer):integer;
|
|
begin
|
|
scanchar := 0;
|
|
|
|
while curPos <= insLength do
|
|
begin
|
|
if ins[curPos] = c then
|
|
begin
|
|
scanchar := curPos;
|
|
break;
|
|
end
|
|
else
|
|
curPos := curPos + 1;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
encoded := 0;
|
|
insLength := length(ins);
|
|
|
|
i := pos('.',ins);
|
|
|
|
{ writeln('** getModifiers ',i); }
|
|
|
|
if i > 1 then
|
|
begin
|
|
slice := copy(ins,1,i-1);
|
|
getMnemonic(slice, opcode, encoded);
|
|
repeat
|
|
startPos := i + 1;
|
|
i := scanchar('.', startPos);
|
|
if i > 0 then
|
|
begin
|
|
slice := copy(ins,startPos,i-startPos);
|
|
getModifier(slice, opcode, encoded);
|
|
end;
|
|
until i < 1;
|
|
|
|
(* last slice *)
|
|
slice := copy(ins, startPos, insLength-startPos+1);
|
|
getModifier(slice, opcode, encoded);
|
|
end
|
|
else
|
|
getMnemonic(ins, opcode, encoded);
|
|
end;
|
|
|
|
procedure parseInstruction;
|
|
var operandValue:integer;
|
|
opcode:OpcodeData;
|
|
encodedIns:InstructionWord;
|
|
begin
|
|
getBaseAndModifiers(curToken.tokenText, opcode, encodedIns);
|
|
|
|
readNextToken;
|
|
|
|
if opcode.id = LOADCPId then
|
|
parseLoadcp
|
|
else
|
|
begin
|
|
if opcode.operand <> NoOprnd then
|
|
begin
|
|
if not checkToken(EOLToken) then
|
|
operandValue := parseExpression
|
|
else
|
|
begin
|
|
if opcode.operand = OptOprnd then
|
|
operandValue := 0
|
|
else
|
|
errorExit2('Missing operand', lastToken.tokenText)
|
|
end
|
|
end
|
|
else
|
|
operandValue := 0;
|
|
|
|
encodeOperand(operandValue, opcode, encodedIns);
|
|
emitInstruction(opcode, encodedIns);
|
|
end;
|
|
end;
|
|
|
|
procedure parseLine;
|
|
begin
|
|
(* writeln('## P', pass, ' line ', lineno:4, ' pc ', pc:8); *)
|
|
|
|
if checkToken(LabelToken) then
|
|
parseLabel;
|
|
|
|
if checkToken(DirectiveToken) then
|
|
parseDirective
|
|
else
|
|
if checkToken(KeywordToken) then
|
|
parseInstruction
|
|
else
|
|
if checkToken(MetaKeywordToken) then
|
|
parseMetaDirective
|
|
else
|
|
if checkToken(EOLToken) then
|
|
begin end (* empty line *)
|
|
else
|
|
begin
|
|
(* writeln(curToken.tokenKind); *)
|
|
errorExit2('Invalid syntax', curToken.tokenText);
|
|
end;
|
|
|
|
matchToken(EOLToken);
|
|
end;
|
|
|
|
procedure parseFile;
|
|
begin
|
|
readNextToken;
|
|
repeat
|
|
parseLine;
|
|
if (lineno and progressSteps) = 0 then
|
|
printCurrentLineno;
|
|
until checkToken(EOFToken);
|
|
emitConstantPool(false);
|
|
end;
|
|
|
|
(* Add an instruction to the opcodeTable (which is a tree).
|
|
mask specifies the bits which are used by the opcode.
|
|
*)
|
|
procedure addOpcode(mnemonic:InsString; value, mask:integer; oprnd: OperandType);
|
|
var data:TreeData;
|
|
|
|
begin
|
|
data.typ := TDOpcode;
|
|
data.opcodedata.encoding.value := value;
|
|
data.opcodedata.encoding.mask := mask;
|
|
data.opcodedata.modifiers := nil;
|
|
data.opcodedata.operand := oprnd;
|
|
data.opcodedata.id := nextOpcodeId;
|
|
|
|
nextOpcodeId := nextOpcodeId + 1;
|
|
|
|
TreeInsert(opcodeTable, mnemonic, data);
|
|
|
|
lastOpcode := TreeSearch(opcodeTable, mnemonic);
|
|
end;
|
|
|
|
(* Add a modifier to the instruction that was last added by addOpcode.
|
|
mask specifies the bits used by the modifier.
|
|
*)
|
|
procedure addModifier(key:InsString; value, mask:integer);
|
|
var newModifier:^ModifierEntry;
|
|
cur:^ModifierEntry;
|
|
begin
|
|
if lastOpcode = nil then
|
|
errorExit2('internal error in addModifier', key);
|
|
|
|
new(newModifier);
|
|
newModifier^.keyword := key;
|
|
newModifier^.encoding.value := value;
|
|
newModifier^.encoding.mask := mask;
|
|
newModifier^.next := nil;
|
|
|
|
|
|
cur := lastOpcode^.opcodedata.modifiers;
|
|
if cur = nil then
|
|
begin
|
|
lastOpcode^.opcodedata.modifiers := newModifier
|
|
end
|
|
else
|
|
begin
|
|
while cur^.next <> nil do cur := cur^.next;
|
|
cur^.next := newModifier;
|
|
end;
|
|
end;
|
|
|
|
procedure addAlias(key:InsString; dest:InsString);
|
|
var opcode:OpcodeData;
|
|
encoded:InstructionWord;
|
|
begin
|
|
(* encode the instruction and modifiers *)
|
|
getBaseAndModifiers(dest, opcode, encoded);
|
|
(* add a new opcode entry with the alias name and
|
|
the values we just calculated *)
|
|
addOpcode(key, encoded, opcode.encoding.mask, opcode.operand);
|
|
(* copy the modifier list (list of valid modifiers)
|
|
from the original instruction *)
|
|
lastOpcode^.opcodedata.modifiers := opcode.modifiers;
|
|
end;
|
|
|
|
procedure addShortcut(ch:char; dest:InsString);
|
|
var opcode:OpcodeData;
|
|
encoded:InstructionWord;
|
|
begin
|
|
if shortcuts[ch].id <> -1 then
|
|
errorExit2('internal error in addShortcut for', dest);
|
|
|
|
getBaseAndModifiers(dest, opcode, encoded);
|
|
shortcuts[ch] := opcode;
|
|
end;
|
|
|
|
procedure addSpecialOperand(key:IdentString; value:integer);
|
|
begin
|
|
createSymbol(key, SpecialSymbol, value, false, false)
|
|
end;
|
|
|
|
procedure initSpecialOperands;
|
|
begin
|
|
pass := 1; (* createSymbol only creates symbols on pass 1 *)
|
|
|
|
(* create special operand symbols *)
|
|
addSpecialOperand('FP', 0);
|
|
addSpecialOperand('BP', 1);
|
|
addSpecialOperand('RP', 2);
|
|
addSpecialOperand('IV', 3);
|
|
addSpecialOperand('IR', 4);
|
|
addSpecialOperand('ESP', 5);
|
|
addSpecialOperand('EQ', 2);
|
|
addSpecialOperand('LT', 1);
|
|
addSpecialOperand('NE', 6);
|
|
addSpecialOperand('LE', 3);
|
|
addSpecialOperand('GE', 5);
|
|
addSpecialOperand('GT', 7);
|
|
end;
|
|
|
|
procedure initOpcodes;
|
|
begin
|
|
addOpcode('BRANCH', $0000, $E000, RelWOprnd);
|
|
|
|
addOpcode('LOADC', $C000, $E000, S13Oprnd);
|
|
|
|
addOpcode('LOAD', $8000, $E000, U13WOprnd);
|
|
addModifier('B', $0001, $0001);
|
|
|
|
addOpcode('STORE', $4000, $E000, U13WOprnd);
|
|
addModifier('B', $0001, $0001);
|
|
|
|
addOpcode('CBRANCH',$A001, $E001, RelWOprnd);
|
|
addModifier('N', $0001, $0001);
|
|
addModifier('NZ', $0001, $0001);
|
|
addModifier('Z', $0000, $0001);
|
|
|
|
addOpcode('XFER', $6000, $E000, NoOprnd);
|
|
addModifier('RSM1', $0300, $0300);
|
|
addModifier('RS0', $0000, $0300);
|
|
addModifier('RS1', $0100, $0300);
|
|
addModifier('R2P', $0080, $0080);
|
|
addModifier('P2R', $0040, $0040);
|
|
addModifier('SM1', $0030, $0030);
|
|
addModifier('S0', $0000, $0030);
|
|
addModifier('S1', $0010, $0030);
|
|
addModifier('X2P', $0001, $0001);
|
|
|
|
addOpcode('ALU', $2000, $E000, OptOprnd);
|
|
addModifier('SM1', $0030, $0030);
|
|
addModifier('S0', $0000, $0030);
|
|
addModifier('S1', $0010, $0030);
|
|
addModifier('X2Y', $0040, $0040);
|
|
addModifier('NX2Y', $0000, $0040);
|
|
addModifier('XT', $0080, $0080);
|
|
addModifier('ADD', $0000, $1e00);
|
|
addModifier('SUB', $0200, $1e00);
|
|
addModifier('NOT', $0400, $1e00);
|
|
addModifier('AND', $0600, $1e00);
|
|
addModifier('OR', $0800, $1e00);
|
|
addModifier('XOR', $0a00, $1e00);
|
|
addModifier('CMP', $0c00, $1e00);
|
|
addModifier('Y', $0e00, $1e00);
|
|
addModifier('SHR', $1000, $1e00);
|
|
addModifier('SHL', $1200, $1e00);
|
|
addModifier('INC', $1400, $1e00);
|
|
addModifier('DEC', $1600, $1e00);
|
|
addModifier('BPLC', $1a00, $1e00);
|
|
addModifier('BROT', $1c00, $1e00);
|
|
addModifier('BSEL', $1e00, $1e00);
|
|
addModifier('CMPU', $1800, $1e00);
|
|
|
|
(* addOpcode('EXT', $E000, $E000); *)
|
|
|
|
addOpcode('MEM', $E400, $FFF0, OptOprnd);
|
|
addModifier('W', $0200, $0200);
|
|
addModifier('SM1', $0030, $0030);
|
|
addModifier('S0', $0000, $0030);
|
|
addModifier('S1', $0010, $0030);
|
|
addModifier('X2Y', $0040, $0040);
|
|
addModifier('NX2Y', $0000, $0040);
|
|
|
|
addOpcode('LOADREL',$F400, $FC00, RelU10Oprnd);
|
|
|
|
LOADCPId := nextOpcodeId;
|
|
addOpcode('LOADCP', $F400, $FC00, RelU10Oprnd);
|
|
|
|
addOpcode('REG', $E000, $FFF0, RegOprnd);
|
|
addModifier('W', $0200, $0200);
|
|
|
|
addOpcode('FPADJ', $EC00, $FC00, S10Oprnd);
|
|
|
|
addAlias('JUMP', 'XFER.SM1.X2P');
|
|
addAlias('CALL', 'XFER.RS1.SM1.P2R.X2P');
|
|
addAlias('RET', 'XFER.RSM1.R2P');
|
|
addAlias('ADD', 'ALU.ADD.SM1');
|
|
addAlias('SUB', 'ALU.SUB.SM1');
|
|
addAlias('NOT', 'ALU.NOT.S0');
|
|
addAlias('AND', 'ALU.AND.SM1');
|
|
addAlias('OR', 'ALU.OR.SM1');
|
|
addAlias('XOR', 'ALU.XOR.SM1');
|
|
addAlias('CMP', 'ALU.CMP.SM1');
|
|
addAlias('SHR', 'ALU.SHR.S0');
|
|
addAlias('SHL', 'ALU.SHL.S0');
|
|
addAlias('DUP', 'ALU.INC.S1.X2Y');
|
|
addAlias('NIP', 'ALU.INC.SM1');
|
|
addAlias('INC', 'ALU.INC.S0');
|
|
addAlias('DEC', 'ALU.DEC.S0');
|
|
addAlias('CMPU', 'ALU.CMPU.SM1');
|
|
addAlias('BPLC', 'ALU.BPLC.SM1');
|
|
addAlias('BROT', 'ALU.BROT.S0');
|
|
addAlias('BSEL', 'ALU.BSEL.SM1');
|
|
addAlias('Y', 'ALU.Y.S1.X2Y');
|
|
addAlias('DROP', 'ALU.Y.SM1');
|
|
addAlias('SWAP', 'ALU.Y.S0.X2Y');
|
|
addAlias('OVER', 'ALU.Y.S1.X2Y');
|
|
addAlias('LOADI', 'MEM');
|
|
addAlias('STOREI', 'MEM.W.SM1');
|
|
addAlias('LOADREG', 'REG');
|
|
addAlias('STOREREG', 'REG.W');
|
|
end;
|
|
|
|
procedure initShortcuts;
|
|
var ch:char;
|
|
begin
|
|
for ch := firstShCChar to lastShCChar do
|
|
shortcuts[ch].id := -1;
|
|
|
|
addShortcut('A', 'ADD');
|
|
addShortcut('B', 'BRANCH');
|
|
addShortcut('C', 'CALL');
|
|
addShortcut('D', 'DUP');
|
|
addShortcut('E', 'LOADREL');
|
|
addShortcut('F', 'LOAD');
|
|
addShortcut('G', 'LOADREG');
|
|
addShortcut('H', 'SHL');
|
|
addShortcut('I', 'LOADI');
|
|
addShortcut('J', 'JUMP');
|
|
addShortcut('K', 'LOADC');
|
|
addShortcut('L', 'LOADCP');
|
|
addShortcut('M', 'STORE');
|
|
addShortcut('N', 'NIP');
|
|
addShortcut('O', 'OR');
|
|
addShortcut('P', 'DROP');
|
|
(* Q is unused *)
|
|
addShortcut('R', 'RET');
|
|
addShortcut('S', 'STOREI');
|
|
addShortcut('T', 'NOT');
|
|
addShortcut('U', 'CMPU');
|
|
addShortcut('V', 'OVER');
|
|
addShortcut('W', 'SWAP');
|
|
addShortcut('X', 'XOR');
|
|
(* Y is ununsed *)
|
|
addShortcut('Z', 'SUB');
|
|
addShortcut('a', 'AND');
|
|
addShortcut('b', 'CBRANCH');
|
|
addShortcut('c', 'CMP');
|
|
addShortcut('d', 'DEC');
|
|
(* e is unused *)
|
|
addShortcut('f', 'FPADJ');
|
|
addShortcut('g', 'STOREREG');
|
|
addShortcut('h', 'SHR');
|
|
addShortcut('i', 'INC');
|
|
end;
|
|
|
|
function changeFileSuffix(filename: string; suffix:string): string;
|
|
var suffixPos:integer;
|
|
begin
|
|
suffixPos := pos(filenameSuffix, filename);
|
|
if suffixPos > 0 then
|
|
setlength(filename, suffixPos-1);
|
|
filename := filename + suffix;
|
|
changeFileSuffix := filename;
|
|
end;
|
|
|
|
procedure performPass(passNo:integer);
|
|
begin
|
|
lineno := 1;
|
|
nextConstId := 0;
|
|
cPoolCount := 0;
|
|
pc := 0;
|
|
bytesCount := 0;
|
|
pass := passNo;
|
|
|
|
outputEnabled := pass = 2;
|
|
|
|
openFileWithDefault(infile, filename);
|
|
infileOpened := true;
|
|
|
|
if outputEnabled then
|
|
overwriteFile(outfile, outfilename);
|
|
|
|
parseFile;
|
|
printLastLineno;
|
|
|
|
close(infile);
|
|
|
|
if outputEnabled then
|
|
close(outfile);
|
|
|
|
(* dumpSymbolTable; *)
|
|
end;
|
|
|
|
procedure verifyNodeKey(node:TreeRef);
|
|
var c:integer;
|
|
begin
|
|
if node = nil then
|
|
errorExit2('verifyNodeKey FAIL node is nil', '');
|
|
if node^.key = nil then
|
|
errorExit2('verifyNodeKey FAIL key is nil', '');
|
|
|
|
if length(node^.key^) < 1 then
|
|
errorExit2('verifyNodeKey FAIL key has zero length', '');
|
|
|
|
c := ord(node^.key^[1]);
|
|
|
|
if not (
|
|
((c >= ord('0')) and (c <= ord('9')))
|
|
or
|
|
((c >= ord('A')) and (c <= ord('F')))
|
|
) then
|
|
begin
|
|
writeln('verifyNodeKey FAIL at ', node^.key^, ' ', c);
|
|
if node^.parent <> nil then
|
|
writeln(' parent:', node^.parent^.key^);
|
|
errorExit;
|
|
end;
|
|
end;
|
|
|
|
procedure verifyTree(node:TreeRef);
|
|
begin
|
|
if node <> nil then
|
|
begin
|
|
verifyNodeKey(node);
|
|
|
|
if node^.right <> nil then
|
|
begin
|
|
if node^.right^.parent <> node then
|
|
errorExit2('verifyTree FAIL parent check right at', node^.key^);
|
|
verifyTree(node^.right);
|
|
end;
|
|
|
|
if node^.left <> nil then
|
|
begin
|
|
if node^.left^.parent <> node then
|
|
errorExit2('verifyTree FAIL parent check left at', node^.key^);
|
|
verifyTree(node^.left);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
infileOpened := false;
|
|
outputEnabled := false;
|
|
asciiOutput := false;
|
|
editOnError := false;
|
|
runOnSuccess := false;
|
|
buffered := false;
|
|
includeLevel := 0;
|
|
symbolTable := nil;
|
|
opcodeTable := nil;
|
|
lastOpcode := nil;
|
|
firstUnresBranch := nil;
|
|
|
|
nextOpcodeId := 1;
|
|
|
|
if ParamCount < 1 then halt;
|
|
|
|
paramPos := 1;
|
|
filename := '';
|
|
outfilename := '';
|
|
|
|
while paramPos <= ParamCount do
|
|
begin
|
|
if paramStr(paramPos) = '-e' then
|
|
editOnError := true
|
|
else
|
|
if paramStr(paramPos) = '-R' then
|
|
runOnSuccess := true
|
|
else
|
|
if paramStr(paramPos) = '-A' then
|
|
asciiOutput := true
|
|
else
|
|
begin
|
|
if length(filename) = 0 then
|
|
filename := ParamStr(paramPos)
|
|
else
|
|
outfilename := ParamStr(paramPos);
|
|
end;
|
|
paramPos := paramPos + 1;
|
|
end;
|
|
|
|
initPlatform;
|
|
initOpcodes;
|
|
initSpecialOperands;
|
|
initShortcuts;
|
|
|
|
if length(outfilename) = 0 then
|
|
begin
|
|
if asciiOutput then
|
|
outfilename := changeFileSuffix(filename, asciifileSuffix)
|
|
else
|
|
outfilename := changeFileSuffix(filename, outfileSuffix)
|
|
end;
|
|
|
|
writeln('Assembling ', filename, ' to ', outfilename);
|
|
|
|
performPass(1);
|
|
performPass(2);
|
|
|
|
writeln(#13, lineno - 1, ' lines, program size ', bytesCount, ' bytes.');
|
|
|
|
(* dumpOpcodeTable; *)
|
|
(* dumpSymbolTable; *)
|
|
writeSymbolTable;
|
|
|
|
if runOnSuccess then
|
|
ExecProgram(outfilename);
|
|
end.
|