Tridora-CPU/pcomp/pcomp.pas
2024-10-16 02:01:12 +02:00

6456 lines
167 KiB
ObjectPascal

(* Copyright 2021-2024 Sebastian Lederer. See the file LICENSE.md for details *)
program PascalCompiler;
{$R+}
{$!}{$ifdef FPC}uses math,crt;{$endif}
type TokenType = (
AssignmentToken, PlusToken, MinusToken, AsteriskToken, SlashToken,
SemicolonToken, EOFToken, PointerToken,
CommentStartToken, CommentEndToken, CommentAltStartToken, CommentAltEndToken,
NumberToken,
IdentToken, StringLitToken, CharLitToken,
StringToken, IntegerToken,
BooleanToken, RealToken, CharToken, TrueToken, FalseToken,
LabelToken, GotoToken,
IfToken, ThenToken, ElseToken, WhileToken,
RepeatToken, DoToken, UntilToken, ForToken, ToToken, InToken,
DowntoToken, BreakToken, ContinueToken,
BeginToken, EndToken, WithToken,
VarToken, TypeToken, CaseToken, ConstToken, RecordToken,
CommaToken, EqToken, EqEqToken, NotEqToken, LtToken,
LtEqToken, GtToken, GtEqToken, LParenToken, RParenToken,
LBracketToken, RBracketToken, ColonToken,
NotToken, AndToken, OrToken, XorToken, DivToken, ModToken,
ShlToken, ShrToken, NilToken,
ProcedureToken, FunctionToken,
ArrayToken, OfToken, DotToken, SetToken,
ForwardToken, ExternalToken, ProgramToken, PackedToken,
UnitToken, ImplementationToken, InterfaceToken, UsesToken,
UnknownToken
);
IdentString = string[120];
CompOpString = string[4];
KeywordString = string[255];
TypeTagString = string[8];
StringRef = ^string;
SymbolScope = ( GlobalSymbol, LocalSymbol, ParameterSymbol, WithStmntSymbol );
SymbolType = ( NoType, IntegerType, StringType, RealType, BooleanType, CharType,
ArrayType, RecordType, PointerType, StringCharType, EnumType,
SetType, UnresolvedType );
SpecialProc = ( NoSP, NewSP, DisposeSP, ReadSP, WriteSP, ReadlnSP, WritelnSP,
SetlengthSP, ValSP, StrSP, ExitSP );
SpecialFunc = ( NoSF, TruncSF, FracSF, IntSF, SqrSF, SuccSF, PredSF,
OddSF, ChrSF, OrdSF, AbsSF);
Token = record
tokenText: string[255];
tokenKind: TokenType;
end;
StringList = record
head: ^StringListItem;
tail: ^StringListItem;
current: ^StringListItem;
end;
StringListItem = record
value: IdentString;
next: ^StringListItem;
end;
IntList = record
head: ^IntListItem;
tail: ^IntListItem;
current: ^IntListItem;
end;
IntListItem = record
value: integer;
next: ^IntListItem;
end;
TypeSpec = record
size: integer; (* size in bytes *)
subStart: integer;
subEnd: integer;
hasSubrange: boolean;
case baseType: SymbolType of
IntegerType,RealType,BooleanType,CharType: ();
EnumType: (enumId, enumLength: integer; enumList: StringList);
StringType: (stringLength: integer); (* max length *)
ArrayType: (arrayLength, arrayStart, arrayEnd: integer;
elementType: ^TypeSpec; indexEnumId: integer);
RecordType: (fields: ^FieldListItem);
PointerType: (pointedType: ^TypeSpec);
SetType: (memberBaseType: SymbolType; memberEnumId: integer);
StringCharType: (); (* used internally when getting a char from a string *)
UnresolvedType:(sourceLine:integer; typeName: ^IdentString);
end;
TypeSpecPtr = ^TypeSpec;
TypeRef = ^TypeItem;
TypeItem = record
typePtr: ^TypeSpec;
name: IdentString;
next: TypeRef;
end;
FieldListItem = record
fieldType: TypeSpec;
name: IdentString;
offset: integer;
isVariant: boolean;
tagField: ^FieldListItem;
tagValues: IntList;
next: ^FieldListItem;
end;
FieldRef = ^FieldListItem;
SymblRef = ^Symbl;
Symbl = record
name: IdentString;
symType: TypeSpec;
scope: SymbolScope;
level: integer;
size: integer;
offset: integer;
isParam: boolean;
isVarParam: boolean;
isConst: boolean;
isExternal: boolean;
initialized: boolean;
initialValue: integer;
hasInitialValue: boolean;
withStmntSlot: integer;
next: SymblRef;
end;
MemLocType = (NoMem, GlobalMem, LocalMem, NestedMem, Indirect, TemporaryMem, OnStack);
MemLocation = record
memLoc: MemLocType;
offset: integer;
scopeDistance: integer;
name: IdentString;
typ: TypeSpec;
initialized: boolean;
origSym: SymblRef;
end;
SymbolTable = record
first: SymblRef;
offset: integer;
scope: SymbolScope;
level: integer;
end;
LablRef = ^Labl;
Labl = record
name: IdentString;
id: integer;
next: LablRef;
end;
OpaqueDataElement = record
(* TODO: need optional string values here
if we want to have readable record fields
or arrays of string type *)
next: ^OpaqueDataElement;
case isStringValue: boolean of
false: (intValue: integer);
true: (strValue: ^string; maxLength:integer);
end;
OpaqueDataRef = ^OpaqueDataElement;
ArrayConstList = record
id: integer;
count: integer;
firstElement: ^OpaqueDataElement;
next: ^ArrayConstList;
extraLabel: ^IdentString;
end;
ArrayConstRef = ^ArrayConstList;
ConstStrRef = ^ConstStr;
ConstStr = record
no: integer;
value: string[255];
length: integer;
extraLabel: ^IdentString;
next: ConstStrRef;
end;
ConstListItem = record
next: ^ConstListItem;
name: IdentString;
typ: TypeSpec;
realValue: real;
intValue: integer;
arrayValue: ArrayConstRef; (* FIXME: rename to opaqueValue or similar *)
strValue: ConstStrRef;
enumRef: TypeRef;
end;
ConstRef = ^ConstListItem;
ProcRef = ^Proc;
Proc = record
name: IdentString;
id: integer;
parent: ProcRef;
level: integer;
isForward: boolean;
isNested: boolean;
hasNested: boolean;
parameters: SymbolTable;
vars: SymbolTable;
returnType: TypeSpec;
returnsAggregate: boolean;
next: ProcRef;
procedures: ProcRef;
labels: LablRef;
constants: ConstRef;
types: TypeRef;
unresolved: TypeRef;
tempsSize: integer;
estackCleanup: integer;
hasExit: boolean;
end;
WithStmntAnchor = record
recordLoc: MemLocation;
tempLoc: MemLocation;
tmpSymbol: SymblRef;
end;
{$I 'platform-types+.pas' }
InputFileState = record
name: string;
filevar: InputFileType;
line: integer;
end;
const insSize = 2;
wordSize = 4;
lowCpoolMark = 240;
highCpoolMark = 400;
StringHeaderSize = 8;
MaxUShortOffset = 8191;
MaxShortOffset = 4095;
MaxShorterOffset = 511;
MaxTinyOffset = 15;
WithStackDepth = 8;
DefaultStringLength = 80;
MaxIntegerDigits = 24;
Float32ExpBits = 8;
Float32FractBits = 23;
Float32ExpBias = 127;
Float32ExpMax = 255;
wordBits = 32;
startAddress = 24576;
MaxIncludes = 4;
StdLibName = 'stdlib';
UnitSuffix1 = '.inc';
UnitSuffix2 = '.lib';
FilenameSuffix = '.pas';
OutfileSuffix = '.s';
InputFileName = 'INPUT';
OutputFileName = 'OUTPUT';
FileTypeName = 'FILE';
PlatformTag = 'tdr';
PlatformMagic = '+';
ProgressSteps = 255;
var
keywords: array [TokenType] of string[32] = (
':=', '+', '-', '*', '/', ';' , '<end-of-file>', '^', '{', '}', '(*', '*)',
'number', 'identifier', '$', 'c',
'STRING', 'INTEGER', 'BOOLEAN', 'REAL', 'CHAR', 'TRUE', 'FALSE',
'LABEL', 'GOTO', 'IF', 'THEN', 'ELSE',
'WHILE', 'REPEAT', 'DO', 'UNTIL', 'FOR', 'TO', 'IN',
'DOWNTO', 'BREAK', 'CONTINUE',
'BEGIN', 'END', 'WITH',
'VAR', 'TYPE', 'CASE', 'CONST', 'RECORD',
',', '=', '==', '!=', '<', '<=', '>', '>=',
'(', ')', '[', ']', ':', 'NOT', 'AND', 'OR', 'XOR', 'DIV', 'MOD',
'SHL', 'SHR', 'NIL',
'PROCEDURE', 'FUNCTION',
'ARRAY', 'OF', '.', 'SET',
'FORWARD', 'EXTERNAL', 'PROGRAM', 'PACKED',
'UNIT', 'IMPLEMENTATION', 'INTERFACE', 'USES',
'_' );
specialprocnames: array [SpecialProc] of string[12] = (
'_', 'NEW', 'DISPOSE', 'READ', 'WRITE', 'READLN', 'WRITELN', 'SETLENGTH',
'VAL','STR', 'EXIT');
specialfuncnames: array [SpecialFunc] of string[8] = (
'_', 'TRUNC', 'FRAC', 'INT', 'SQR', 'SUCC', 'PRED', 'ODD',
'CHR', 'ORD', 'ABS' );
typenames: array[SymbolType] of string[8] = (
'NONE?', 'INTEGER', 'STRING', 'REAL', 'BOOLEAN', 'CHAR', 'ARRAY', 'RECORD',
'POINTER', 'STRCHR?', 'ENUM', 'SET', 'UNRES?'
);
curToken, nextToken, lastToken: Token;
bufferedChar: char;
buffered: boolean;
infile: InputFileType;
outfile: text;
filename: string;
outfilename: string;
lineno: integer;
ifCount: integer;
whileCount: integer;
forCount: integer;
repeatCount: integer;
caseCount: integer;
nestedProcsCount: integer;
enumCount: integer;
anonTypeCount: integer;
curBreakLabel: IdentString;
firstConstStr, lastConstStr: ConstStrRef;
firstArrayConst, lastArrayConst: ArrayConstRef;
constStrNo: integer;
arrayConstNo: integer;
curProcedure: ProcRef;
mainProcedure: ProcRef;
defaultHeapSize: integer;
defaultStackSize: integer;
insCount: integer;
emptyIntList: IntList;
withStmntStack: array [1..WithStackDepth] of WithStmntAnchor;
withStmntCount: integer;
globalSuffix: IdentString;
fileTyp: TypeSpec;
useStdlib, useStandalone: boolean;
editOnError, runAsm, runProg: boolean;
paramPos: integer;
prevFiles: array[1..MaxIncludes] of InputFileState;
includeLevel: integer;
usedUnits: StringList;
outputPrefix: string[16];
includePrefix: string[16];
procedure errorExit2(message1, message2: string); forward;
procedure errorExit1(message1: string); forward;
procedure checkDuplicateSymbol(var name:IdentString); forward;
function getStringWordCount(maxLength: integer): integer; forward;
procedure readNextToken; forward;
procedure matchToken(kind: TokenType); forward;
function checkToken(kind: TokenType): boolean; forward;
procedure parseExpression(var typeReturn: TypeSpec); forward;
procedure errorLine(line:integer); forward;
procedure errorExit; forward;
function isScalar(var typ: TypeSpec): boolean; forward;
function isFunction(aProc: ProcRef): boolean; forward;
procedure getRangePart(var value:integer; var typeReturn: TypeSpec); forward;
function parseInteger: Integer; forward;
procedure parseLvalue(var memLocReturn: MemLocation); forward;
procedure parseSpecialFunction(sf: SpecialFunc; var returnType: TypeSpec); forward;
procedure parseArrayIndex(var arrayTyp: TypeSpec; var name:IdentString;
var elType:TypeSpec); forward;
procedure parseStringIndex; forward;
procedure parseTypeSpec(var typSpec: TypeSpec; allowUnresolved:boolean); forward;
procedure parseEnumDecl(var name:IdentString;var typeReturn: TypeSpec); forward;
procedure parseConstValue(constData: ArrayConstRef; var expectedType: TypeSpec); forward;
procedure parseProgramBlock; forward;
procedure parseStatement; forward;
function findProcedure(var name: IdentString; aProc:ProcRef): ProcRef; forward;
procedure parseProcedure; forward;
procedure parseFunction; forward;
procedure parseCharExprTail(var typeA: TypeSpec); forward;
procedure parseStringExprTail(dstType: TypeSpec); forward;
procedure parseSetExprTail(var typeA: TypeSpec); forward;
procedure loadVarParamRef(var loc: MemLocation); forward;
procedure loadAddr(var loc: MemLocation); forward;
procedure allocTemporary(aProc: ProcRef;
var typ: TypeSpec; var memLocReturn: MemLocation); forward;
procedure disposeWithStmntTmp; forward;
procedure convertToIndirect(var mem: MemLocation); forward;
function matchTokenOrNot(wantedToken: TokenType): boolean; forward;
{$I 'platform+.pas'}
{$I 'float32+.pas'}
{$I 'emit.pas'}
procedure initStringList(var list:StringList);
begin
with list do
begin
head := nil;
tail := nil;
current := nil;
end;
end;
procedure addToStringList(var list:StringList; var name: IdentString);
var itemRef: ^StringListItem;
begin
new(itemRef);
itemRef^.value := name;
itemRef^.next := nil;
with list do
begin
if head = nil then
begin
head := itemRef;
tail := itemRef;
current := itemRef;
end
else
begin
head^.next := itemRef;
head := itemRef;
end;
end;
end;
function nextStringListItem(var list:StringList; var returnStr: IdentString): boolean;
begin
if list.current = nil then
nextStringListItem := false
else
begin
returnStr := list.current^.value;
list.current := list.current^.next;
nextStringListItem := true;
end;
end;
procedure rewindStringList(var list:StringList);
begin
list.current := list.tail;
end;
procedure disposeStringList(var list:StringList);
var itemRef, next: ^StringListItem;
begin
itemRef := list.tail;
while itemRef <> nil do
begin
next := itemRef^.next;
dispose(itemRef);
itemRef := next;
end;
end;
procedure initIntList(var list:IntList);
begin
with list do
begin
head := nil;
tail := nil;
current := nil;
end;
end;
procedure addToIntList(var list:IntList; var anInteger: integer);
var itemRef: ^IntListItem;
begin
new(itemRef);
itemRef^.value := anInteger;
itemRef^.next := nil;
with list do
begin
if head = nil then
begin
head := itemRef;
tail := itemRef;
current := itemRef;
end
else
begin
head^.next := itemRef;
head := itemRef;
end;
end;
end;
function nextIntListItem(var list:IntList; var returnValue: integer): boolean;
begin
if list.current = nil then
nextIntListItem := false
else
begin
returnValue := list.current^.value;
list.current := list.current^.next;
nextIntListItem := true;
end;
end;
procedure rewindIntList(var list:IntList);
begin
list.current := list.tail;
end;
procedure disposeIntList(var list:IntList);
var itemRef, next: ^IntListItem;
begin
itemRef := list.tail;
while itemRef <> nil do
begin
next := itemRef^.next;
dispose(itemRef);
itemRef := next;
end;
end;
function findSymbol(var table: SymbolTable; var name: IdentString): SymblRef;
var current: SymblRef;
begin
current := table.first;
while (current <> nil) do
if (current^.name <> name) then
current := current^.next
else
break;
findSymbol := current;
end;
function addSymbol(var table: SymbolTable; var name: IdentString; var typ: TypeSpec;
isParam, isVarParam: boolean): SymblRef;
var current: SymblRef;
newSymbol: SymblRef;
begin
checkDuplicateSymbol(name);
new(newSymbol);
newSymbol^.name := name;
newSymbol^.offset := table.offset;
newSymbol^.next := nil;
newSymbol^.scope := table.scope;
newSymbol^.level := table.level;
newSymbol^.size := typ.size;
newSymbol^.symType := typ; (* TODO: needs a deep copy for aggregate types *)
newSymbol^.isParam := isParam;
newSymbol^.isVarParam := isVarParam;
newSymbol^.isExternal := false;
newSymbol^.initialized := (table.scope = GlobalSymbol) or isVarParam or isParam;
if isVarParam then
table.offset := table.offset + wordSize
else
table.offset := table.offset + typ.size;
emitNewSymbol(table.scope, name, newSymbol^.offset);
if table.first = nil then
table.first := newSymbol
else
begin
current := table.first;
while current^.next <> nil do current := current^.next;
current^.next := newSymbol;
end;
addSymbol := newSymbol;
end;
(* Create a pseudo symbol for a record field that is accessed inside
a with statement.
Because we need to return a pointer to a symbol in findHieraSymbol,
we have to allocate a TypeSpec record which needs to be freed later
(in parseWithStatement)
*)
function createPseudoSym(var name: string; field: FieldRef;
withSlot: integer): SymblRef;
var sym: SymblRef;
typ: TypeSpec;
begin
typ := field^.fieldType;
new(sym);
sym^.name := name;
sym^.symType := typ;
sym^.scope := WithStmntSymbol;
sym^.level := 0;
sym^.size := typ.size;
sym^.offset := field^.offset;
sym^.isParam := false;
sym^.isVarParam := false;
sym^.isConst := false;
sym^.isExternal := false;
sym^.initialized := false;
sym^.initialValue := 0;
sym^.hasInitialValue := false;
sym^.withStmntSlot := withSlot;
sym^.next := nil;
createPseudoSym := sym;
end;
function findWithStmntSym(var name: string): SymblRef;
var sym: SymblRef;
i: integer;
w: WithStmntAnchor;
field: ^FieldListItem;
begin
sym := nil;
for i := withStmntCount downto 1 do
begin
w := withStmntStack[i];
field := w.recordLoc.typ.fields;
while field <> nil do
begin
if field^.name = name then
begin
sym := createPseudoSym(name, field, i);
(* If there was a pseudo-symbol allocated earlier,
free it. The last one is freed in parseWithStatement.
We can overwrite the previous pointer because it is only
used from findHieraSymbol until initMemLocation/parseMemlocation.
The call sequence looks like this:
- findHieraSymbol -> pseudo-sym allocated
- initMemLocation -> used
- read/writeVariable -> ignored
If we have multiple accesses to the same record within a
statement, the call sequence is as follows:
- findHieraSymbol a -> allocate psym for a
- initMemLocation a -> psym-a is used
- findHieraSymbol b -> allocate for b, free psym-a
- initMemLocation b -> psym-b is used
- readVariable b -> memloc-b is used
- writeVariable a -> memloc-a is used
*)
disposeWithStmntTmp;
withStmntStack[i].tmpSymbol := sym;
break;
end;
field := field^.next;
end;
if sym <> nil then
break;
end;
findWithStmntSym := sym;
end;
function findHieraSymbol(var name: string): SymblRef;
var sym: SymblRef;
aProc: ProcRef;
begin
(* TODO: check the with-stack, use WithScope in that case.
Also add this to initMemLocation/parseMemLocation *)
sym := nil;
aProc := curProcedure;
sym := findWithStmntSym(name);
(* if not a record field from a with statement, check
for variable names recursively *)
if (sym = nil) and (aProc <> nil) then
(* aProc can be nil during the initialization of the main procedure *)
repeat
sym := findSymbol(aProc^.vars, name);
if sym = nil then
aProc := aProc^.parent;
until (sym <> nil) or (aProc = nil);
findHieraSymbol := sym;
end;
function findConstant(aProc:ProcRef; var name:IdentString): ConstRef;
var current: ConstRef;
begin
current := aProc^.constants;
while current <> nil do
begin
if current^.name = name then
break
else
current := current^.next;
end;
findConstant := current;
end;
function findConstantHiera(var name:IdentString): ConstRef;
var aProc:ProcRef;
begin
findConstantHiera := nil;
aProc := curProcedure;
while aProc <> nil do
begin
findConstantHiera := findConstant(aProc, name);
if findConstantHiera <> nil then
break
else
aProc := aProc^.parent;
end;
end;
function addConstant(var name:IdentString): ConstRef;
var current,newConst: ConstRef;
begin
checkDuplicateSymbol(name);
new(newConst);
newConst^.name := name;
newConst^.next := nil;
newConst^.typ.baseType := NoType;
if curProcedure^.constants = nil then
curProcedure^.constants := newConst
else
begin
current := curProcedure^.constants;
while current^.next <> nil do current := current^.next;
current^.next := newConst;
end;
addConstant := newConst;
end;
function findLabel(var aProc: ProcRef; var name: IdentString): LablRef;
var current: LablRef;
begin
current := aProc^.labels;
while (current <> nil) do
begin
if current^.name = name then
break
else
current := current^.next;
end;
findLabel := current;
end;
procedure addLabel(var aProc: ProcRef; var name: IdentString);
var current, newLabl: ^Labl;
begin
checkDuplicateSymbol(name);
new(newLabl);
newLabl^.name := name;
newLabl^.id := aProc^.id;
newLabl^.next := nil;
if aProc^.labels = nil then
aProc^.labels := newLabl
else
begin
current := aProc^.labels;
while current^.next <> nil do current := current^.next;
current^.next := newLabl;
end;
end;
function addArrayConst: ArrayConstRef;
var newArrayConst: ArrayConstRef;
begin
arrayConstNo := arrayConstNo + 1;
new(newArrayConst);
newArrayConst^.firstElement := nil;
newArrayConst^.next := nil;
newArrayConst^.id := arrayConstNo;
newArrayConst^.extraLabel := nil;
if firstArrayConst = nil then
firstArrayConst := newArrayConst
else
lastArrayConst^.next := newArrayConst;
lastArrayConst := newArrayConst;
addArrayConst := newArrayConst;
end;
function addNamedArrayConst(var name:IdentString; var first:boolean): ArrayConstRef;
var constData: ArrayConstRef;
begin
constData := addArrayConst;
if first then
begin
new(constData^.extraLabel);
constData^.extraLabel^ := name;
first := false;
end;
addNamedArrayConst := constData;
end;
function addConstElem(arrayConst: ArrayConstRef): OpaqueDataRef;
var newElem,current: ^OpaqueDataElement;
begin
new(newElem);
newElem^.next := nil;
current := arrayConst^.firstElement;
if current = nil then
arrayConst^.firstElement := newElem
else
begin
while current^.next <> nil do current := current^.next;
current^.next := newElem;
end;
addConstElem := newElem;
end;
procedure addArrayConstElem(arrayConst: ArrayConstRef;value:integer);
var newElem: ^OpaqueDataElement;
begin
newElem := addConstElem(arrayConst);
newElem^.isStringValue := false;
newElem^.intValue := value;
end;
procedure addStrConstElem(arrayConst: ArrayConstRef; var aString:KeywordString;
maxLength:integer);
var newElem: ^OpaqueDataElement;
begin
if length(aString) > maxLength then
errorExit2('String constant length exceeds declared length','');
newElem := addConstElem(arrayConst);
newElem^.isStringValue := true;
new(newElem^.strValue);
newElem^.strValue^ := aString;
newElem^.maxLength := maxLength;
end;
function findConstStr(var value: string):ConstStrRef;
var current: ConstStrRef;
begin
current := firstConstStr;
while (current <> nil) and (current^.value <> value) do current := current^.next;
findConstStr := current;
end;
function addConstStrRaw(var value: string): ConstStrRef;
var newstring: ConstStrRef;
begin
new(newstring);
newstring^.value := value;
newstring^.next := nil;
newstring^.no := constStrNo;
newstring^.length := 0;
newstring^.extraLabel := nil;
constStrNo := constStrNo + 1;
if lastConstStr = nil then
begin
firstConstStr := newstring;
lastConstStr := newstring;
end
else
begin
lastConstStr^.next := newstring;
lastConstStr := newstring;
end;
addConstStrRaw := newstring;
end;
(* create a string constant/literal and return a pointer to it.
if a constant with the same value already exists, it is reused.
*)
function addConstStr(var value: string): ConstStrRef;
var newstring: ConstStrRef;
begin
newstring := findConstStr(value);
if newstring = nil then
begin
newstring := addConstStrRaw(value);
end;
addConstStr := newstring;
end;
procedure nextAnonTypeName(var name:IdentString);
var buf:string[16];
begin
str(anonTypeCount, buf);
name := '_anon' + buf;
anonTypeCount := anonTypeCount + 1;
end;
procedure dumpTypes; forward;
procedure addType(var newType: TypeSpec; var name:IdentString);
var curItem: TypeRef;
newTypeSpec: ^TypeSpec;
newItem: TypeRef;
begin
checkDuplicateSymbol(name);
curItem := curProcedure^.types;
new(newTypeSpec);
newTypeSpec^ := newType;
new(newItem);
newItem^.name := name;
newItem^.typePtr := newTypeSpec;
newItem^.next := nil;
(* if list is empty, set first item *)
if curItem = nil then
begin
curProcedure^.types := newItem;
end
else
begin
(* find the end of the list *)
while curItem^.next <> nil do
curItem := curItem^.next;
curItem^.next := newItem;
end;
{ dumpTypes; }
end;
function findTypeRef(aProc: ProcRef; var name: IdentString): TypeRef;
var curItem: TypeRef;
begin
findTypeRef := nil;
curItem := aProc^.types;
while curItem <> nil do
begin
if curItem^.name = name then
begin
findTypeRef := curItem;
break;
end;
curItem := curItem^.next;
end;
end;
function findTypeRefHiera(var name: IdentString): TypeRef;
var aProc:ProcRef;
begin
findTypeRefHiera := nil;
aProc := curProcedure;
while aProc <> nil do
begin
findTypeRefHiera := findTypeRef(aProc, name);
if findTypeRefHiera <> nil then
break
else
aProc := aProc^.parent;
end;
end;
(* TODO: use findTypeRef *)
function findType(aProc:ProcRef; var name: IdentString): TypeSpec;
var curItem: TypeRef;
begin
findType.baseType := NoType;
curItem := aProc^.types;
while curItem <> nil do
begin
{ writeln('***** findType searching ', name, ' ', curItem^.name,
' ', curItem^.typePtr^.baseType); }
if curItem^.name = name then
begin
findType := curItem^.typePtr^;
{ writeln('***** findType found ', curItem^.name); }
break;
end;
curItem := curItem^.next;
end;
end;
(* TODO: use findTypeRefHiera *)
function findTypeHiera(var name: IdentString): TypeSpec;
var aProc: ProcRef;
begin
findTypeHiera.baseType := NoType;
aProc := curProcedure;
while aProc <> nil do
begin
findTypeHiera := findType(aProc, name);
if findTypeHiera.baseType <> NoType then
break
else
aProc := aProc^.parent;
end;
end;
function findEnumById(enumId: integer):TypeRef;
var aProc:ProcRef;
curItem: TypeRef;
begin
findEnumById := nil;
aProc := curProcedure;
while (aProc <> nil) and (findEnumById = nil) do
begin
curItem := aProc^.types;
while curItem <> nil do
begin
if (curItem^.typePtr^.baseType = EnumType)
and (curItem^.typePtr^.enumId = enumId) then
begin
findEnumById := curItem;
break;
end;
curItem := curItem^.next;
end;
aProc := aProc^.parent;
end;
end;
procedure dumpTypes;
var curType: TypeRef;
begin
curType := curProcedure^.types;
while curType <> nil do
begin
writeln(curType^.name:20,' ', curType^.typePtr^.baseType, ' ',
curType^.typePtr^.size);
curType := curType^.next;
end;
end;
procedure checkDuplicateVar(var name:IdentString);
var typ:TypeSpec;
begin
if curProcedure <> nil then
if findSymbol(curProcedure^.vars, name) <> nil then
errorExit2('duplicate identifier', name);
typ := findTypeHiera(name);
if typ.baseType <> NoType then
errorExit2('duplicate identifier (type)', name);
if findConstantHiera(name) <> nil then
errorExit2('duplicate identifier (constant)', name);
end;
procedure checkDuplicateSymbol(var name:IdentString);
begin
checkDuplicateVar(name);
(* FIXME: this should most likely be searchProcedure, not findProcedure*)
if findProcedure(name, curProcedure) <> nil then
errorExit2('duplicate identifier (procedure/function)', name);
end;
procedure setBaseType(var typ: TypeSpec; baseTyp: SymbolType);
begin
typ.size := wordSize;
typ.baseType := baseTyp;
typ.hasSubrange := false;
end;
procedure setSubrange(var typ: TypeSpec; rStart,rEnd: integer);
begin
typ.hasSubrange := true;
typ.subStart := rStart;
typ.subEnd := rEnd;
end;
(* TODO: wrong name, Pointer is not a scalar *)
function isScalar(var typ: TypeSpec): boolean;
begin
isScalar := typ.baseType in [ IntegerType, BooleanType, RealType, CharType, PointerType,
EnumType ];
end;
function isAggregate(var typ:TypeSpec): boolean;
begin
isAggregate := typ.baseType in [ ArrayType, RecordType, StringType ];
end;
(* check if type is a single value (used only for constant declaration?)*)
function isSimpleType(var typ: TypeSpec): boolean;
begin
isSimpleType := typ.baseType in [ IntegerType, BooleanType, RealType, CharType, EnumType ];
end;
function isDirectType(var typ: TypeSpec): boolean;
begin
isDirectType := typ.baseType in [ IntegerType, BooleanType, RealType, CharType, EnumType,
SetType, PointerType ];
end;
(* check if valid type for array indexing *)
function isIndexType(var typ: TypeSpec): boolean;
begin
isIndexType := typ.baseType in [ IntegerType, BooleanType, CharType, EnumType ];
end;
procedure setStringTypeSize(var typeReturn:TypeSpec; length:integer);
begin
setBaseType(typeReturn, StringType);
typeReturn.size := StringHeaderSize + ((length div wordSize) + 1) * wordSize;
typeReturn.stringLength := length;
end;
procedure convertStringToChar(var typeReturn:TypeSpec);
begin
setBaseType(typeReturn, CharType);
emitConvStringToChar;
end;
procedure convertCharToString(var typeReturn:TypeSpec);
var temp: MemLocation;
begin
setBaseType(typeReturn, StringType);
setStringTypeSize(typeReturn,1);
allocTemporary(curProcedure, typeReturn, temp);
loadAddr(temp);
emitConvCharToString;
end;
procedure convertIntToReal(var typeReturn:TypeSpec);
begin
setBaseType(typeReturn, RealType);
emitIntToFloat();
end;
procedure convertPrevIntToReal(var typeReturn:TypeSpec);
begin
setBaseType(typeReturn, RealType);
emitSwap;
emitIntToFloat();
emitSwap;
end;
function getTypeName(t: SymbolType):TypeTagString;
begin
getTypeName := typeNames[t];
end;
procedure matchBaseType(var typ: TypeSpec; wantedBaseType: SymbolType);
begin
if typ.baseType <> wantedBaseType then
errorExit1('Error: Expected type ' + getTypeName(wantedBaseType) +
', got ' + getTypeName(typ.baseType));
end;
procedure matchBaseTypes(var typeA, typeB: TypeSpec; wantedBaseType: SymbolType);
begin
matchBaseType(typeA, wantedBaseType);
matchBaseType(typeB, wantedBaseType);
end;
procedure matchLogicOpTypes(var typeA, typeB:TypeSpec);
begin
if (typeA.baseType <> typeB.baseType) or
((typeA.baseType <> IntegerType) and
(typeA.baseType <> BooleanType)) then
errorExit2('Either two boolean or two integer operands expected', '');
end;
procedure matchSymbolType(actualType: TypeSpec; sym: SymblRef);
begin
(* TODO: match complex types *)
if sym^.symType.baseType <> actualType.baseType then
errorExit1('Error: expected type ' + getTypeName(sym^.symType.baseType) +
' for ' + sym^.name + ', got ' + getTypeName(actualType.baseType));
end;
(* check if two types are the same.
Arrays must have same length and element type.
Records must be the same type alias (that is,
having the same field types is not enough)
This is like matchTypes but it returns a value
instead of throwing errors.
*)
function isSameType(var typeA, typeB: TypeSpec):boolean;
begin
isSameType := false;
if typeA.baseType = typeB.baseType then
begin
isSameType := true; (* if the base types match, we set the return
value to true here, with more checks below *)
if typeA.baseType = EnumType then
isSameType := typeA.enumId = typeB.enumId
else
if typeA.baseType = ArrayType then
isSameType := isSameType(typeA.elementType^, typeB.elementType^) and
(typeA.arrayLength = typeB.arrayLength)
else if typeA.baseType = RecordType then
(* the pointer to the first record field works as an unique
identifier, because the field items are allocated
exactly once when compiling and are never deallocated.*)
isSameType := typeA.fields = typeB.fields
else if typeA.baseType = PointerType then
begin
(* pointedType is nil for the nil pointer value
which is compatible with all pointer types *)
if (typeB.pointedType <> nil) and (typeA.pointedType <> nil) then
isSameType := isSameType(typeA.pointedType^, typeB.pointedType^);
end;
end;
end;
procedure matchTypes(var typeA, typeB: TypeSpec);
begin
matchBaseType(typeA, typeB.baseType);
if typeA.baseType = EnumType then
begin
if typeA.enumId <> typeB.enumId then
errorExit2('Incompatible enum types for', lastToken.tokenText);
end
else
if typeA.baseType = ArrayType then
begin
matchTypes(typeA.elementType^, typeB.elementType^);
if typeA.arrayLength <> typeB.arrayLength then
errorExit2('Incompatible arrays', '');
end
else if typeA.baseType = RecordType then
begin
(* the pointer to the first record field works as an unique
identifier, because the field items are allocated
exactly once when compiling and are never deallocated.*)
if typeA.fields <> typeB.fields then
errorExit2('Incompatible record types', '');
end
else if typeA.baseType = PointerType then
begin
(* pointedType is nil for the nil pointer value
which is compatible with all pointer types *)
if (typeB.pointedType <> nil) and (typeA.pointedType <> nil) then
matchTypes(typeA.pointedType^, typeB.pointedType^);
end;
end;
(* checks if a type is compatible with real.
accepts real or integer. integer will be converted
to real. value must be already on stack. *)
procedure matchRealType(var typeB: TypeSpec);
begin
if typeB.baseType = IntegerType then
convertIntToReal(typeB)
else
matchBaseType(typeB, RealType);
end;
(* Match argument types for an arithmetic operation of reals.
One of both args can be an integer and will be converted.
Both args must already be on the stack.
*)
procedure matchRealCompatibleArgs(var typeA, typeB: TypeSpec);
begin
if typeA.baseType = IntegerType then
convertPrevIntToReal(typeA);
if typeB.baseType = IntegerType then
convertIntToReal(typeB);
matchBaseType(typeA, RealType);
matchBaseType(typeB, RealType);
end;
procedure matchComparableTypes(var typeA, typeB: TypeSpec);
begin
if (typeA.baseType = RealType) and (typeB.baseType = IntegerType) then
begin
(* writeln('**** real/integer comparison'); *)
end
else if (typeA.baseType = IntegerType) and (typeB.baseType = RealType) then
begin
(* writeln('**** integer/real comparison'); *)
end
else if (typeA.baseType <> typeB.baseType) then
begin
if (typeA.baseType = CharType) and (typeB.baseType = StringType) then
convertStringToChar(typeB)
else
if (typeA.baseType = StringType) and (typeB.baseType = CharType) then
convertCharToString(typeB)
else
begin
errorExit1('types ' + getTypeName(typeA.baseType) + ' and '+
getTypeName(typeB.baseType) + ' are not comparable');
end;
end
else
begin
if not (isScalar(typeA) or (typeA.baseType = StringType)) then
matchTypes(typeA, typeB);
(* FIXME: what happens when the if condition is not met? *)
end;
end;
(* match types and in some cases, try to convert non-matching types.
typeB is converted to typeA, if needed. A conversion is performed
on the topmost stack element (if any).
currently implemented: string -> char, char -> string, integer -> real
*)
procedure matchAndConvertTypes(var typeA, typeB: TypeSpec);
begin
if (typeA.baseType = CharType) and (typeB.baseType = StringType) then
convertStringToChar(typeB)
else
if (typeA.baseType = StringType) and (typeB.baseType = CharType) then
convertCharToString(typeB)
else
if (typeA.baseType = StringCharType) and (typeB.baseType = CharType) then
(* StringCharType and CharType are compatible and conversion will be
handled by parseMemLocation and readVariable/writeVariable*)
else
if (typeA.baseType = RealType) and (typeB.baseType = IntegerType) then
convertIntToReal(typeB)
else
matchTypes(typeB, typeA); (* reverse order to get correct error message *)
end;
procedure setIndirect(var mem: MemLocation);
begin
if mem.memLoc <> Indirect then
begin
mem.memLoc := Indirect;
(* mem.name := mem.name + '<i>'; *)
mem.offset := -1;
end;
end;
procedure initNoMemLocation(var loc: MemLocation);
begin
loc.memLoc := NoMem;
loc.offset := -1;
loc.name := 'NoMem';
loc.typ.baseType := NoType;
end;
(* initialize a MemLocation object from a Symbol. *)
procedure initMemLocation(sym: SymblRef; var loc: MemLocation);
begin
loc.memLoc := NoMem;
loc.name := sym^.name;
loc.offset := 0;
loc.typ := sym^.symType;
loc.initialized := sym^.initialized;
loc.origSym := sym;
if sym^.isVarParam then
begin
(* is it a var parameter from an outer scope? *)
if sym^.level < curProcedure^.level then
begin
loc.memLoc := NestedMem;
loc.offset := sym^.offset;
loc.scopeDistance := curProcedure^.level - sym^.level;
end
else
begin
loc.memLoc := LocalMem;
loc.offset := sym^.offset;
end;
loadVarParamRef(loc);
setIndirect(loc);
(* for var parameters the local variable slot contains
the address of the value
FIXME: loadVarParamRef should not be done here
- why not?
- because it might be possible we want to initialize
the MemLocation record without emitting code
- but we do emit code to start the address calculation
for other cases, too, see below *)
end
else
if sym^.scope = GlobalSymbol then
begin
loc.memLoc := GlobalMem;
(* nothing to do, name and offset are already set *)
end
else if sym^.scope in [ LocalSymbol, ParameterSymbol ] then
begin
(* is it a variable from an outer scope? *)
if sym^.level < curProcedure^.level then
begin
loc.memLoc := NestedMem;
loc.offset := sym^.offset;
loc.scopeDistance := curProcedure^.level - sym^.level;
emitNestedMemLoc(loc);
if isNestedIndirect(loc) then
setIndirect(loc);
end
else
begin
loc.memLoc := LocalMem;
loc.offset := sym^.offset;
emitLocalMemLoc(loc);
if isLocalIndirect(loc) then
setIndirect(loc);
end;
end
else if sym^.scope = WithStmntSymbol then
begin
loc.memLoc := Indirect;
loc.offset := sym^.offset;
emitWithStmntMemLoc(loc, sym^.withStmntSlot);
end
else
errorExit2('Internal error in initMemLocation', sym^.name);
end;
function findField(var typ:TypeSpec; var name:IdentString): FieldRef;
var curField: FieldRef;
begin
if typ.baseType <> RecordType then
errorExit2('Invalid record field access:', name);
findField := nil;
curField := typ.fields;
while curField <> nil do
begin
if curField^.name = name then
begin
findField := curField;
break;
end;
curField := curField^.next;
end;
end;
function findSpecialProcedure(var name: IdentString): SpecialProc;
var i: SpecialProc;
begin
findSpecialProcedure := NoSP;
for i := NoSP to ExitSP do
if name = specialprocnames[i] then
begin
findSpecialProcedure := i;
break;
end;
end;
function findSpecialFunction(var name: IdentString): SpecialFunc;
var i: SpecialFunc;
begin
findSpecialFunction := NoSF;
for i := NoSF to AbsSF do
if name = specialfuncnames[i] then
begin
findSpecialFunction := i;
break;
end;
end;
function findProcedure(var name: IdentString; aProc:ProcRef): ProcRef;
var current: ProcRef;
begin
if aProc <> nil then
begin
current := aProc^.procedures;
while (current <> nil) do
if (current^.name <> name) then
current := current^.next
else
break;
findProcedure := current;
end
else
findProcedure := nil;
end;
(* do a nested search for a procedure, i.e. search in procedures
at the current scope, then continue search at all outer scopes if not found. *)
function searchProcedure(var name: IdentString): ProcRef;
var parent: ProcRef;
begin
parent := curProcedure;
repeat
searchProcedure := findProcedure(name, parent);
if searchProcedure = nil then parent := parent^.parent;
until (parent = nil) or (searchProcedure <> nil);
end;
function createProcedure(var name:IdentString; parent:ProcRef):ProcRef;
var newProc, current:ProcRef;
begin
new(newProc);
newProc^.name := name;
newProc^.next := nil;
newProc^.tempsSize := 0;
newProc^.returnsAggregate := false;
newProc^.parent := parent;
newProc^.level := 0;
newProc^.isForward := false;
newProc^.isNested := false;
newProc^.hasNested := false;
newProc^.procedures := nil;
newProc^.labels := nil;
newProc^.types := nil;
newProc^.unresolved := nil;
newProc^.constants := nil;
newProc^.hasExit := false;
newProc^.estackCleanup := 0;
nestedProcsCount := nestedProcsCount + 1;
newProc^.id := nestedProcsCount;
setBaseType(newProc^.returnType, NoType);
if parent <> nil then
begin
newProc^.level := parent^.level + 1;
if newProc^.level > 0 then newProc^.isNested := true;
if (parent^.procedures) = nil then
parent^.procedures := newProc
else
begin
current := parent^.procedures;
while current^.next <> nil do current := current^.next;
current^.next := newProc;
end;
end;
newProc^.vars.first := nil;
newProc^.vars.offset := 0;
newProc^.vars.scope := LocalSymbol;
newProc^.vars.level := newProc^.level;
newProc^.parameters.first := nil;
newProc^.parameters.offset := 0;
newProc^.parameters.level := newProc^.level;
{
if parent <> nil then
writeln('***** createProcedure ', newProc^.name, ' parent:', newProc^.parent^.name,
' level:', newProc^.level);
}
(* do some preparations for the stack frame,
e.g. allocate space for the link to
outer stack frames for nested procedures *)
cpuAllocStackFrame(newProc);
createProcedure := newProc;
end;
function addProcedure(name: IdentString; hasReturnValue: boolean; parent:ProcRef): ProcRef;
var fwdDecl:ProcRef;
begin
checkDuplicateVar(name);
fwdDecl := findProcedure(name, parent);
if fwdDecl <> nil then
begin
if not fwdDecl^.isForward then
errorExit2('duplicate identifier (procedure/function)', name);
addProcedure := fwdDecl;
end
else
addProcedure := createProcedure(name, parent);
end;
procedure addParam(aProc: ProcRef; name: IdentString; typSpec: TypeSpec; isVarParam: boolean);
var pSym, vSym: SymblRef;
begin
(* parameters are added to both the parameter list and the
list of local variables *)
pSym := addSymbol(aProc^.parameters, name, typSpec, true, isVarParam);
vSym := addSymbol(aProc^.vars, name, typSpec, true, isVarParam);
pSym^.scope := ParameterSymbol;
vSym^.scope := ParameterSymbol;
pSym^.isParam := true;
vSym^.isParam := true;
end;
procedure printLineStats;
begin
write(#13);
ClrEol;
writeln(filename:16, lineno - 1:8, ' lines.');
end;
procedure beginInclude(var newname: string);
var newfile: InputFileType;
p:integer;
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;
p := pos(PlatformMagic, newname);
if p > 0 then
insert(PlatformTag, newname, p + 1);
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);
printLineStats;
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
begin
lineno := lineno + 1;
if (lineno and ProgressSteps) = 0 then
begin
write(#13, filename, ' ', lineno);
ClrEol;
end;
end;
nextChar := ch;
end;
procedure skipChar;
var ch:char;
begin
ch := nextChar;
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;
begin
while peekChar() in [ #10, #13, #32, #9, #12 ] do
skipChar;
end;
function findToken(var keyword: string): TokenType;
var i: TokenType;
begin
findToken := UnknownToken;
for i := StringToken to UnknownToken do
begin
if keywords[i] = keyword then
begin
findToken := i;
break;
end
end
end;
(* Convert hexadecimal digits to integer like val().
digits may or may not start with a '$' character. *)
procedure hexVal(var digits:string; var retval:integer; var error:integer);
var i,v,len:integer;
c:char;
begin
len := length(digits);
if (len > 0) and (digits[1] = '$') then
i := 2
else
i := 1;
retval := 0;
error := 0;
while (i <= len) and (error = 0) do
begin
retval := retval 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
error := i;
retval := retval + v;
i := i + 1;
end;
end;
function integerFromString(digits:KeywordString):integer;
var value,error:integer;
begin
if (length(digits) > 0) and (digits[1] = '$') then
hexVal(digits, value, error)
else
val(digits, value, error);
if error <> 0 then
errorExit2('invalid integer value', digits);
integerFromString := value;
end;
function getInteger:integer;
var curChar: char;
digits: string[24];
begin
if peekChar = '-' then
begin
curChar := nextChar;
digits := '-';
end
else
digits := '';
while peekChar in ['0'..'9'] do
begin
curChar := nextChar;
if (length(digits) < MaxIntegerDigits) and (curChar <> #0) then
digits := digits + curChar;
end;
getInteger := integerFromString(digits);
end;
function realFromString(var digits:KeywordString): real;
var code: Integer;
v: real;
begin
val(digits, v, code);
if code <> 0 then errorExit2('invalid real value', digits);
realFromString := v;
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;
(* 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 upcase(peekChar) in [ '0'..'9', 'A'..'F' ] do
begin
keyword := keyword + upcase(nextChar);
end;
end;
(* Scan for an integer or real number. All digits up to the first non-digit are
already in curToken.tokenText.
Returns all digits/characters in digits and either
IntegerType or RealType in typeReturn *)
procedure getNumber(var digits:IdentString; var typeReturn:TypeSpec);
begin
digits := curToken.tokenText;
if checkToken(MinusToken) then
begin
readNextToken;
digits := digits + curToken.tokenText;
end;
if not (peekChar in [ '.', 'E', 'e' ]) then
begin
setBaseType(typeReturn, IntegerType);
end
else
begin
if peekChar = '.' then (* is there a decimal point? *)
begin
digits := digits + nextChar;
if peekChar in [ '0'..'9'] then (* is there a fraction after the decimal point ? *)
begin
getDigits(nextChar, digits);
end;
end;
if peekChar in ['E','e'] then (* is there an exponent? *)
begin
digits := digits + nextChar;
if peekChar in ['+', '-'] then (* exponent can have a sign *)
begin
digits := digits + nextChar;
end;
if peekChar in ['0'..'9'] then (* now we require some exponent digits *)
begin
getDigits(nextChar, digits);
end
else
errorExit2('invalid number format', digits);
end;
setBaseType(typeReturn, RealType);
end;
readNextToken;
end;
(* parse an integer or real number. all digits up to the first non-digit are
already in curToken.tokenText. leaves the number on the stack. *)
(* FIXME: use getNumber *)
procedure parseNumber(last:TokenType;var typeReturn: TypeSpec);
var digits: KeywordString;
value: integer;
r: real;
begin
if last = MinusToken then
digits := '-' + curToken.tokenText
else
digits := curToken.tokenText;
if not (peekChar in [ '.', 'E', 'e' ]) then
begin
value := integerFromString(digits);
emitLoadConstantInt(value);
setBaseType(typeReturn, IntegerType);
end
else
begin
if peekChar = '.' then (* is there a decimal point?*)
begin
digits := digits + nextChar;
if peekChar in [ '0'..'9'] then (* is there a fraction after the decimal point ? *)
begin
getDigits(nextChar, digits);
end;
end;
if peekChar in ['E','e'] then (* is there an exponent? *)
begin
digits := digits + nextChar;
if peekChar in ['+', '-'] then (* exponent can have a sign *)
begin
digits := digits + nextChar;
end;
if peekChar in ['0'..'9'] then (* now we require some exponent digits *)
begin
getDigits(nextChar, digits);
end
else
errorExit2('invalid number format', digits);
end;
r := realFromString(digits);
emitLoadConstantReal(r);
setBaseType(typeReturn, RealType);
end;
readNextToken;
end;
procedure getToken(var tokenReturn:Token;stringTokens:boolean);
var curChar,pkChar: char;
keyword: KeywordString;
startLine: string[12];
begin
curChar := nextChar;
tokenReturn.tokenText := curChar;
if curChar = #0 then
tokenReturn.tokenKind := EOFToken
else
if curChar = '+' then
tokenReturn.tokenKind := PlusToken
else
if curChar = '-' then
tokenReturn.tokenKind := MinusToken
else
if curChar = '*' then
begin
pkChar := peekChar;
if pkChar = ')' then
begin
skipChar;
tokenReturn.tokenText := tokenReturn.tokenText + pkChar;
tokenReturn.tokenKind := CommentAltEndToken;
end
else
tokenReturn.tokenKind := AsteriskToken;
end
else
if curChar = '/' then
tokenReturn.tokenKind := SlashToken
else
if curChar = '(' then
begin
pkChar := peekChar;
if pkChar = '*' then
begin
skipChar;
tokenReturn.tokenText := tokenReturn.tokenText + pkChar;
tokenReturn.tokenKind := CommentAltStartToken;
end
else
tokenReturn.tokenKind := LParenToken;
end
else
if curChar = ')' then
tokenReturn.tokenKind := RParenToken
else
if curChar = '{' then
tokenReturn.tokenKind := CommentStartToken
else
if curChar = '}' then
tokenReturn.tokenKind := CommentEndToken
else
if curChar = '[' then
tokenReturn.tokenKind := LBracketToken
else
if curChar = ']' then
tokenReturn.tokenKind := RBracketToken
else
if curChar = ',' then
tokenReturn.tokenKind := CommaToken
else
if curChar = '=' then
begin
pkChar := peekChar;
if pkChar = '=' then
begin
skipChar;
tokenReturn.tokenText := tokenReturn.tokenText + pkChar;
tokenReturn.tokenKind := EqEqToken;
end
else
tokenReturn.tokenKind := EqToken;
end
else
if curChar = '>' then
begin
pkChar := peekChar;
if pkChar = '=' then
begin
skipChar;
tokenReturn.tokenText := tokenReturn.tokenText + pkChar;
tokenReturn.tokenKind := GtEqToken;
end
else
tokenReturn.tokenKind := GtToken;
end
else
if curChar = '<' then
begin
pkChar := peekChar;
if pkChar = '=' then
begin
skipChar;
tokenReturn.tokenText := tokenReturn.tokenText + pkChar;
tokenReturn.tokenKind := LtEqToken;
end
else
if pkChar = '>' then
begin
skipChar;
tokenReturn.tokenText := tokenReturn.tokenText + pkChar;
tokenReturn.tokenKind := NotEqToken;
end
else
tokenReturn.tokenKind := LtToken;
end
else
if curChar = '.' then
begin
tokenReturn.tokenKind := DotToken;
end
else
if curChar = '^' then
begin
tokenReturn.tokenKind := PointerToken;
end
else
if curChar = ';' then
begin
tokenReturn.tokenKind := SemicolonToken;
end
else
if curChar = ':' then
begin
pkChar := peekChar;
if pkChar = '=' then
begin
skipChar;
tokenReturn.tokenText := tokenReturn.tokenText + pkChar;
tokenReturn.tokenKind := AssignmentToken;
end
else
tokenReturn.tokenKind := ColonToken;
end
else
if curChar in ['A'..'Z', 'a'..'z' ] then
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 := findToken(keyword);
if tokenReturn.tokenKind = UnknownToken then tokenReturn.tokenKind := IdentToken;
end
else
if curChar in ['0'..'9' ] 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
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
if curChar = '#' then
begin
tokenReturn.tokenText := chr(getInteger);
tokenReturn.tokenKind := CharLitToken;
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;
(* Parse a compiler directive which is inside a comment.
The start token of the comment has already been parsed. *)
procedure parseDirective(closingToken:TokenType);
var ch:char;
filename:string;
begin
ch := nextChar; (* skip $ character *)
ch := nextChar; (* this is our directive *)
if ch = 'I' then
begin
if peekChar = ' ' then
begin
readNextToken;
(* we require the include filename to be enclosed
in single quotes for simplicity *)
if curToken.tokenKind <> StringLitToken then
errorExit2('Include filename must be enclosed in single quotes','');
filename := curToken.tokenText;
readNextToken;
if curToken.tokenKind <> closingToken then
errorExit2('Invalid directive', '');
beginInclude(filename);
matchToken(closingToken);
end
end
else
if ch = 'H' then
begin
if (peekChar = ' ') or isDigit(peekChar) then
begin
readNextToken;
defaultHeapSize := integerFromString(curToken.tokenText) * 1024;
readNextToken;
end;
matchToken(closingToken);
end
else
if ch = 'S' then
begin
if (peekChar = ' ') or isDigit(peekChar) then
begin
readNextToken;
defaultStackSize := integerFromString(curToken.tokenText) * 1024;
readNextToken;
end;
matchToken(closingToken);
end
else
if ch = '!' then
(* special comment till end of line *)
begin
while not (nextChar = #13) do (* nothing *);
readNextToken;
end
else
begin
(* no directive recognized, treat as comment *)
while not matchTokenOrNot(closingToken) do
begin
skipWhitespace;
skipToNextToken;
end;
end;
end;
(* This will skip a comment, works with both comment styles depending
on closingToken. Also processes compiler directives. *)
procedure skipComment(closingToken: TokenType);
var startLine:string[8];
done:boolean;
begin
if peekChar = '$' then
parseDirective(closingToken)
else
begin
str(lineno,startLine);
if closingToken = CommentEndToken then
while nextChar <> '}' do
begin
if eof(infile) then
errorExit2('runaway comment starting at line', startLine);
end
else
if closingToken = CommentAltEndToken then
begin
done := false;
repeat
if eof(infile) then
errorExit2('runaway comment starting at line', startLine);
(* we cannot use getToken because it would not work with
string literals or numbers inside comments *)
if nextChar = '*' then
if peekChar = ')' then
done := nextChar = ')';
until done;
end;
skipWhitespace;
skipToNextToken;
end;
end;
(* read the next token into the global variable curToken.
skips whitespace and comments.
*)
procedure readNextToken;
begin
skipWhitespace;
lastToken := curToken;
getToken(nextToken, true);
curToken := nextToken;
while curToken.tokenKind in [ CommentStartToken, CommentAltStartToken ] do
begin
if checkToken(CommentAltStartToken) then
skipComment(CommentAltEndToken)
else
if checkToken(CommentStartToken) then
skipComment(CommentEndToken)
end;
end;
function checkComparisonOperator(aTokenType: TokenType): boolean;
begin
checkComparisonOperator := aTokenType in
[ LtToken, LtEqToken, EqToken, NotEqToken, GtEqToken, GtToken ];
end;
function getCompareOpFromToken(tok: TokenType): CompOpString;
begin
if not checkComparisonOperator(tok) then
errorExit2('invalid comparison operator token', '');
case tok of
LtToken: getCompareOpFromToken := 'LT';
LtEqToken: getCompareOpFromToken := 'LE';
EqToken: getCompareOpFromToken := 'EQ';
NotEqToken: getCompareOpFromToken := 'NE';
GtEqToken: getCompareOpFromToken := 'GE';
GtToken: getCompareOpFromToken := 'GT';
end;
end;
procedure cleanup;
begin
close(infile);
close(outfile);
end;
procedure errorExit;
begin
cleanup;
halt;
end;
procedure errorLine(line:integer);
begin
writeln('at line ',lineno, ' in ', filename);
end;
procedure errorExit2(message1, message2: string);
var errormsg:string[128];
begin
errormsg := message1 + ' ' + message2;
write(#13); ClrEol;
writeln('Error: ', errormsg);
errorLine(lineno);
if editOnError then
begin
cleanup;
ExecEditor(filename, lineno, errormsg)
end;
errorExit;
end;
procedure errorExit1(message1:string);
begin
errorExit2(message1, '');
end;
function quoteToken(var s:string):string;
begin
if length(s) = 1 then
quoteToken := '''' + s + ''''
else
quoteToken := s;
end;
(* match (and consume) a token or exit with error *)
procedure matchToken(kind: TokenType);
var errormsg:string[128];
begin
if curToken.tokenKind <> kind then
begin
errormsg := 'Expected ' + quoteToken(keywords[kind]) +
', got ' + quoteToken(curToken.tokenText);
errorExit1(errormsg);
end;
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;
(* like matchTokenOrNot, but does not return a value *)
procedure optionalToken(wantedToken: TokenType);
begin
if checkToken(wantedToken) then
readNextToken;
end;
(* match a token. if the token is matched, consume it and return true.
if the current token is SemicolonToken, consume it and try to match the
next token then return true.
otherwise, return false and do not consume token.
Multiple consecutive semicolons are also matched and consumed.
Example with tok1 = EndToken:
"; END" returns true
"; BEGIN" returns false
"END" returns true
"BEGIN" returns false
*)
(* FIXME: the line reported in errors is now sometimes off by one,
because this function scans after a possible semicolon and into
the next line *)
function matchEndOf(tok1: TokenType): boolean;
begin
matchEndOf := false;
if (curToken.tokenKind = tok1) then
begin
matchEndOf := true;
readNextToken;
end
else
begin
while checkToken(SemicolonToken) do
readNextToken;
if matchTokenOrNot(tok1) then
matchEndOf := true
else
if curToken.tokenKind in [ EndToken, UntilToken, EOFToken ] then
errorExit2('Missing', quoteToken(keywords[tok1]))
end;
end;
function getStringWordCount(maxLength:integer): integer;
begin
getStringWordCount := (maxLength + (wordSize - 1)) div wordSize;
end;
function getStringMemSize(maxLength: integer): integer;
var size: integer;
begin
size := 2 * wordSize;
size := size + getStringWordCount(maxLength) * wordSize;
getStringMemSize := size;
end;
procedure compareStrings(operatr: TokenType);
var compOp: CompOpString;
begin
compOp := getCompareOpFromToken(operatr);
if operatr = EqToken then
emitStringComparison
else if operatr = NotEqToken then
begin
emitStringComparison;
emitBooleanNot;
end
else
emitStringLexiComparison(compOp);
end;
procedure compareAggregate(operatr: TokenType; var typ: TypeSpec);
begin
emitMemComparison(typ);
if operatr <> EqToken then
begin
if operatr = NotEqToken then
emitBooleanNot
else
errorExit2('Invalid comparison operator for aggregate type','');
end;
end;
(* Scan for an integer and return its value. nothing is placed on the stack.
Handles constant identifiers.
should be called getInteger then for consistency.
*)
function parseInteger: Integer;
var cnst: ConstRef;
digits: string[12];
begin
(* handle possible constant *)
if checkToken(IdentToken) then
begin
cnst := findConstantHiera(curToken.tokenText);
if cnst = nil then
errorExit2('Number or constant identifier expected, got',
curToken.tokenText);
if cnst^.typ.baseType <> IntegerType then
errorExit2('Not an integer constant:', curToken.tokenText);
parseInteger := cnst^.intValue;
end
else
begin
digits := curToken.tokenText;
if matchTokenOrNot(MinusToken) then
begin
readNextToken;
digits := digits + curToken.tokenText;
end;
parseInteger := integerFromString(digits);
end;
readNextToken;
end;
procedure parseConstant(var typeReturn: TypeSpec);
var intValue:integer;
begin
getRangePart(intValue, typeReturn);
emitLoadConstantInt(intValue);
end;
(*
write a value to a variable.
accessScalar or parseMemLocation must have been called
before, with the same symbol reference.
Subrange checks can be switched off, which is only used
in for-loop iterations.
*)
procedure writeVariable2(var mem: MemLocation; checkSubranges:boolean);
begin
if mem.typ.baseType in [ IntegerType, BooleanType, RealType, CharType, PointerType,
EnumType, SetType ]
then
begin
if mem.typ.hasSubrange and checkSubranges then
emitSubrangeCheck(mem.typ.subStart,mem.typ.subEnd);
case mem.memLoc of
Indirect: emitStoreIndirect;
GlobalMem: errorExit2('internal error: accessing GlobalMem', mem.name);
LocalMem: emitStoreLocal(mem.offset, mem.name);
NestedMem: emitStoreNested(mem.offset, mem.scopeDistance, mem.name);
end;
end
else if mem.typ.baseType = StringType then
emitCopyString
else if mem.typ.baseType in [ ArrayType, RecordType ] then
emitCopy(mem.typ.size)
else if mem.typ.baseType = StringCharType then
emitSetStringChar (* store char to byte ptr *)
else
errorExit2('internal error: writeVariable baseType not handled for', mem.name);
end;
(*
Write a value to a variable.
accessScalar or parseMemLocation must have been called
before, with the same symbol reference.
Subrange checks are enabled.
See writeVariable2.
*)
procedure writeVariable(var mem:MemLocation);
begin
writeVariable2(mem, true);
end;
(*
read value from a memory location.
see parseMemLocation which parses an identifier into a
memory location.
if it is an indirect access, the address has to be on the stack already.
this can be done with parseMemLocation or accessScalar.
*)
procedure readVariable(var mem: MemLocation);
begin
if mem.typ.baseType in [ IntegerType, BooleanType, RealType, CharType, PointerType,
EnumType, SetType ] then
begin
case mem.memLoc of
Indirect: emitLoadIndirect;
GlobalMem: errorExit2('internal error: accessing GlobalMem for', mem.name);
LocalMem: emitLoadLocal(mem.offset, mem.name);
NestedMem: emitLoadNested(mem.offset, mem.scopeDistance, mem.name);
end;
end
else if mem.typ.baseType = ArrayType then
(* nothing to do to access a whole array, its address
is already on the stack *)
else if mem.typ.baseType = StringType then
begin end
(* nothing to do to read a string variable, its
address is already on the stack *)
else if mem.typ.baseType = RecordType then
(* nothing to do to read a record variable, its
address is already on the stack *)
else if mem.typ.baseType = StringCharType then
begin
emitLoadStringChar; (* load char from byte ptr *)
setBaseType(mem.typ, CharType); (* we now have a char on stack *)
end
else
errorExit2('internal error: reading memloc', mem.name);
end;
procedure convertToIndirect(var mem: MemLocation);
begin
if mem.memLoc <> Indirect then
begin
if mem.memLoc = GlobalMem then
emitLoadGlobalAddr(mem.name, mem.offset)
else if mem.memLoc = LocalMem then
emitLoadLocalAddr(mem.name, mem.offset)
else if mem.memLoc = NestedMem then
emitLoadNestedAddr(mem.name, mem.scopeDistance, mem.offset);
setIndirect(mem);
end;
end;
procedure addMemOffset(var mem: MemLocation; delta: integer);
begin
(* if the location is indirect, i.e. the address is already on stack,
we need to emit code for increasing the address.
otherwise, we just increase the offset *)
if mem.MemLoc in [ Indirect, OnStack ] then
emitInc(delta);
mem.offset := mem.offset + delta;
end;
(* Parse possible qualifiers of a symbol down to a memory location.
This can be a scalar variable, an array element, or a record field.
Places the address on the stack if it is not a local variable.
This is called after the identifier has been parsed and the
symbol (SymblRef) has already been determined.
"Qualifiers" are: Array indices(brackets), record field qualifiers,
pointer dereferencing (^ operator).
ForceIndirect is (only?) used when passing var parameters.
The sym parameter can be nil if memReturn has already been
initialized.
Scalar variables should resolve to GlobalMem with a symbol name
or to LocalMem with an offset.
Record fields should resolve to GlobalMem with symbol + offset
or LocalMem with cumulative offset.
If the MemLocType is already Indirect (e.g. array of record),
they address is already on stack.
Arrays of arrays or arrays as record fields should
calculate an offset as usual and add to address on stack.
*)
(* TODO: rename to something like parseQualifiers *)
procedure parseSymMemLoc(sym:SymblRef;forceIndirect: boolean; var memReturn: MemLocation);
var aFieldRef: ^FieldListItem;
elementType: TypeSpec;
pointerMemLoc: MemLocation;
begin
if sym <> nil then
initMemLocation(sym, memReturn);
while curToken.tokenKind in [ LBracketToken, DotToken, PointerToken ] do
begin
if checkToken(LBracketToken) then
begin
(* indexing an array *)
convertToIndirect(memReturn);
if memReturn.typ.baseType = ArrayType then
begin
parseArrayIndex(memReturn.typ, memReturn.name, elementType);
memReturn.typ := elementType;
(* strings contained in arrays need to be initialized *)
if memReturn.typ.baseType = StringType then
memReturn.initialized := false;
end
else
if memReturn.typ.baseType = StringType then
begin
parseStringIndex;
memReturn.typ.baseType := StringCharType;
end
else
errorExit2('invalid subscript for', memReturn.name);
end
else if checkToken(DotToken) then
begin
(* accessing a record field *)
readNextToken;
aFieldRef := findField(memReturn.typ, curToken.tokenText);
if aFieldRef = nil then
errorExit2('invalid field name', curToken.tokenText);
readNextToken;
addMemOffset(memReturn, aFieldRef^.offset);
memReturn.typ := aFieldRef^.fieldType;
(* strings contained in records need to be initialized *)
if memReturn.typ.baseType = StringType then
memReturn.initialized := false;
{
if aFieldRef^.isVariant then
writeln('******* variant:', aFieldRef^.isVariant,
' first case value:', aFieldRef^.tagValues.tail^.value);
}
end
else if checkToken(PointerToken) then
begin
(* dereferencing a pointer *)
readNextToken;
if memReturn.typ.baseType <> PointerType then
if sym <> nil then
errorExit2('not a pointer:', sym^.name)
else
errorExit2('not a pointer', '');
pointerMemLoc := memReturn;
convertToIndirect(memReturn);
memReturn.typ := memReturn.typ.pointedType^;
memReturn.initialized := true;
(* assume that the variable the pointer points to
is initialized. otherwise, passing a pointer to
a string would overwrite the string header with
wrong values, if the string lengths
of the argument and the parameter differ *)
(* Function return values are encoded as NoMem,
which means the value (not the address of the pointer)
is already on stack. In this case,
we need to skip the emitLoadIndirect. *)
if pointerMemLoc.memLoc <> OnStack then
emitLoadIndirect; (* the pointer variable contains the address *)
end;
end;
if (memReturn.typ.baseType in [StringType, RecordType, ArrayType ])
or forceIndirect or
(memReturn.memLoc = GlobalMem) then
convertToIndirect(memReturn);
end;
(* parse an identifier and possible qualifiers, see parseSymMemLoc() *)
procedure parseMemLocation(forceIndirect: boolean; var memReturn: MemLocation);
var sym: SymblRef;
begin
if curToken.tokenKind <> IdentToken then
errorExit2('Expected identifier, got', curToken.tokenText);
sym := findHieraSymbol(curToken.tokenText);
if sym = nil then
errorExit2('Undeclared variable', curToken.tokenText);
readNextToken;
parseSymMemLoc(sym, forceIndirect, memReturn);
end;
procedure loadAddr(var loc: MemLocation);
begin
case loc.memLoc of
GlobalMem: emitLoadGlobalAddr(loc.name, loc.offset);
LocalMem: emitLoadLocalAddr(loc.name, loc.offset);
NestedMem: emitLoadNestedAddr(loc.name, loc.scopeDistance, loc.offset);
Indirect: errorExit2('internal error: loadAddr with Indirect', loc.name);
TemporaryMem: emitLoadTempAddr(loc.name, loc.offset);
end;
end;
(* load the pointer/reference of a local or nested var parameter *)
procedure loadVarParamRef(var loc: MemLocation);
begin
if loc.memLoc = LocalMem then
emitLoadLocal(loc.offset, loc.name)
else
if loc.memLoc = NestedMem then
emitLoadNested(loc.offset, loc.scopeDistance,
loc.name)
else
errorExit2('internal error in loadVarParamRef','');
end;
(* calculate the address for a variable that fits into one word.
code is emitted to put the address on the stack, if it is not
a short access. a MemLocation record is returned which can
be passed to readVariable/writeVariable for the actual access.
*)
procedure accessScalar(sym: SymblRef; var memLocReturn: MemLocation);
begin
initMemLocation(sym, memLocReturn);
if sym^.scope = GlobalSymbol then
begin
convertToIndirect(memLocReturn);
end
else if (sym^.scope in [ LocalSymbol, ParameterSymbol ]) and sym^.isVarParam then
(* for var parameters the local variable slot contains
the address of the value, so we do a emitLoad... not a
emitLoad...Addr *)
loadVarParamRef(memLocReturn)
else
begin
if memLocReturn.memLoc = LocalMem then
emitLocalMemLoc(memLocReturn)
else if memLocReturn.memLoc = NestedMem then
emitNestedMemLoc(memLocReturn);
end;
end;
procedure accessVariable(sym: SymblRef; var memLocReturn: MemLocation);
begin
accessScalar(sym, memLocReturn);
if not isScalar(sym^.symType) then
convertToIndirect(memLocReturn);
end;
procedure dumpVars(var table: SymbolTable);
var sym: SymblRef;
begin
writeln('dumpVars ', table.scope);
sym := table.first;
while sym <> nil do
begin
writeln(' ', sym^.name, ' ', sym^.symType.baseType);
end;
end;
(* the pointer to the string is already on the stack here *)
procedure parseStringIndex;
var typeReturn: TypeSpec;
begin
matchToken(LBracketToken);
parseExpression(typeReturn); (* now we have the string ptr and index value on stack*)
matchBaseType(typeReturn, IntegerType);
emitStringIndexToAddr;
matchToken(RBracketToken);
end;
procedure parseArrayIndex(var arrayTyp: TypeSpec; var name: IdentString;var elType:TypeSpec);
var typeReturn: TypeSpec;
begin
elType := arrayTyp;
matchToken(LBracketToken);
repeat
parseExpression(typeReturn);
if typeReturn.baseType = EnumType then
begin
if arrayTyp.indexEnumId = 0 then
errorExit2('invalid array subscript type for', name);
if typeReturn.enumId <> arrayTyp.indexEnumId then
errorExit2('invalid array subscript type for', name)
end
else
if not isIndexType(typeReturn) then
errorExit2('invalid array subscript type for', name);
emitIndexToAddr(elType);
elType := elType.elementType^;
if checkToken(CommaToken) then
begin
if elType.baseType <> ArrayType then
errorExit2('invalid array subscript for', name)
end;
until not matchTokenOrNot(CommaToken);
matchToken(RBracketToken);
end;
(* parse accessing an array constant.
merge this with parseMemLocation? *)
procedure parseArrayConstAccess(cnst:ConstRef; var returnType:TypeSpec);
begin
emitLoadArrayConst(cnst^.arrayValue);
if checkToken(LBracketToken) then
begin
parseArrayIndex(cnst^.typ, cnst^.name, returnType);
emitLoadIndirect;
end
end;
procedure parseVarBlock; forward;
procedure parseCall(aProc: ProcRef; var optionalDest: MemLocation); forward;
(* Parse an identifier as part of an expression. also parses array indices,
record fields and pointer dereferencing.
See also parseLvalue which parses an identifier as the left hand side
of an assignment.
Emits code to place the result value on the eval stack.
*)
procedure parseIdentifier(var returnType: TypeSpec);
var sym: SymblRef;
cnst: ConstRef;
func: ProcRef;
memLoc: MemLocation;
sf: SpecialFunc;
name:IdentString;
isCall:boolean;
begin
name := curToken.tokenText;
readNextToken;
cnst := findConstantHiera(name);
if cnst <> nil then
begin
returnType := cnst^.typ;
case cnst^.typ.baseType of
IntegerType, CharType, BooleanType:
emitLoadConstantInt(cnst^.intValue);
RealType:
emitLoadConstantReal(cnst^.realValue);
ArrayType:
parseArrayConstAccess(cnst, returnType);
StringType:
emitLoadConstStr(cnst^.strValue);
EnumType:
begin
emitLoadConstantInt(cnst^.intValue);
returnType := cnst^.enumRef^.typePtr^;
end
else
errorExit2('internal error in parseIdentifier constant',cnst^.name);
end;
end
else
begin
(* function call syntax? *)
isCall := checkToken(LParenToken);
(* is it a variable?*)
sym := findHieraSymbol(name);
if isCall or (sym = nil) then
begin
(* if no symbol found, or if we have parentheses,
it must be a function *)
func := searchProcedure(name);
if func = nil then
begin
sf := findSpecialFunction(name);
if sf <> NoSF then
parseSpecialFunction(sf, returnType)
else
errorExit2('Undeclared identifier', name)
end
else
begin
if not isFunction(func) then
errorExit2('procedure cannot be called as a function:', name)
else
begin
initNoMemLocation(memLoc);
parseCall(func, memLoc);
returnType := func^.returnType;
(* a function return value can have qualifiers like array
indexing or pointer dereferencing, so try to parse them *)
(* TODO: redundant code with parseSymMemLoc, at least
make a checkQualifierToken() function *)
if curToken.tokenKind in [ LBracketToken, DotToken, PointerToken] then
begin
memLoc.typ := func^.returnType;
(* address is already on stack, so the memloc type is OnStack *)
memLoc.memLoc := OnStack;
parseSymMemLoc(nil, false, memLoc);
readVariable(memLoc);
returnType := memLoc.typ;
end;
end
end
end
else
begin
(* we found a sym, so it is a variable *)
parseSymMemLoc(sym, false, memLoc);
readVariable(memLoc);
returnType := memLoc.typ;
{
if returnType.baseType = SetType then
writeln('********** parseIdentifier Set var enum id ', returnType.memberEnumId);
}
end;
end;
end;
procedure parseLvalue(var memLocReturn: MemLocation);
begin
parseMemLocation(false, memLocReturn);
end;
procedure parsePrimary(var typeReturn: TypeSpec);
var c:ConstStrRef;
begin
if checkToken(LParenToken) then
begin
readNextToken;
parseExpression(typeReturn);
matchToken(RParenToken);
end
else
if checkToken(NumberToken) then
begin
(* parse integer and real *)
parseNumber(PlusToken, typeReturn);
end
else
if checkToken(IdentToken) then
begin
parseIdentifier(typeReturn);
end
else
if checkToken(StringLitToken) then
begin
c := addConstStr(curToken.tokenText);
emitLoadConstStr(c);
setStringTypeSize(typeReturn, c^.length);
readNextToken;
end
else
if checkToken(TrueToken) then
begin
emitConstBoolean(true);
setBaseType(typeReturn, BooleanType);
readNextToken;
end
else
if checkToken(FalseToken) then
begin
emitConstBoolean(false);
setBaseType(typeReturn, BooleanType);
readNextToken;
end
else
if checkToken(NilToken) then
begin
emitLoadConstantInt(0);
setBaseType(typeReturn, PointerType);
typeReturn.pointedType := nil;
readNextToken;
end
else
if checkToken(CharLitToken) then
begin
(* TODO: convert to string constant if destination is string *)
emitLoadConstantInt(getCharLitValue(curToken.tokenText));
setBaseType(typeReturn, CharType);
readNextToken;
end
else
errorExit2('Unexpected token ', quoteToken(curToken.tokenText));
end;
procedure parseUnary(var typeReturn: TypeSpec);
begin
if checkToken(PlusToken) then
begin
readNextToken;
parsePrimary(typeReturn);
if not (typeReturn.baseType in [IntegerType, RealType]) then
errorExit2('Expected INTEGER or REAL type for unary','+')
end
else
if checkToken(MinusToken) then
begin
readNextToken;
if checkToken(NumberToken) then
parseNumber(MinusToken, typeReturn)
else
begin
parsePrimary(typeReturn);
if typeReturn.baseType = IntegerType then
emitNegate
else if typeReturn.baseType = RealType then
emitNegFloat32;
end;
if not (typeReturn.baseType in [ IntegerType, RealType]) then
errorExit2('Expected INTEGER or REAL type for unary','-');
end
else
if checkToken(NotToken) then
begin
readNextToken;
parsePrimary(typeReturn);
if typeReturn.baseType = BooleanType then
emitBooleanNot
else
if typeReturn.baseType = IntegerType then
emitNot
else
errorExit2('Boolean or integer operand expected', '');
end
else
parsePrimary(typeReturn);
end;
procedure shiftIntegerOp(var typeA, typeB: TypeSpec; op: string);
begin
matchBaseTypes(typeA, typeB, IntegerType);
emitShiftMultiple(op);
end;
procedure integerOp(var typeA, typeB: TypeSpec; op: string);
begin
matchBaseTypes(typeA, typeB, IntegerType);
emitOperator(op);
end;
procedure realOp(var typeA, typeB: TypeSpec; op: string);
begin
matchRealCompatibleArgs(typeA, typeB);
emitFloatOperator(op);
end;
procedure arithmeticOp(var typeA, typeB: TypeSpec; op: string);
begin
if (typeA.baseType = RealType) or (typeB.baseType = RealType) then
realOp(typeA, typeB, op)
else
integerOp(typeA, typeB, op);
end;
procedure logicOp(var typeA, typeB: TypeSpec; op: string);
begin
matchLogicOpTypes(typeA, typeB);
emitOperator(op);
end;
procedure parseTerm(var typeReturn: TypeSpec);
var operatr: TokenType;
typeA, typeB: TypeSpec;
begin
typeA := typeReturn;
typeB := typeReturn;
parseUnary(typeA); (* parse first operand *)
(* ugly hack for set expressions *)
if typeA.baseType = SetType then
parseSetExprTail(typeA)
else
begin
while curToken.tokenKind in [AsteriskToken, DivToken, ModToken, SlashToken,
AndToken, ShrToken, ShlToken ] do
begin
operatr := curToken.tokenKind;
readNextToken;
(* / check first operand for real type, realOp checks second operand *)
if operatr = SlashToken then matchRealType(typeA);
parseUnary(typeB); (* parse second operand *)
if operatr = AsteriskToken then arithmeticOp(typeA, typeB, 'MUL')
else if operatr = DivToken then arithmeticOp(typeA, typeB, 'DIV')
else if operatr = ModToken then arithmeticOp(typeA, typeB, 'MOD')
else if operatr = ShlToken then shiftIntegerOp(typeA, typeB, 'SHLM')
else if operatr = ShrToken then shiftIntegerOp(typeA, typeB, 'SHRM')
else if operatr = SlashToken then realOp(typeA, typeB, 'DIV')
else if operatr = AndToken then logicOp(typeA, typeB, 'AND');
end;
end;
typeReturn := typeA;
end;
procedure dumpArrayConst(a:ArrayConstRef);
var curElem: ^OpaqueDataElement;
begin
curElem := a^.firstElement;
writeln('**** dumpArrayConst id ', a^.id);
write('**** dumpArrayConst [ ');
while curElem <> nil do
begin
if curElem^.isStringValue then
write(curElem^.strValue^)
else
write(curElem^.intValue,' ');
curElem := curElem^.next;
end;
writeln(']');
end;
function getCharValue:integer;
var cons: ConstRef;
begin
if checkToken(IdentToken) then
begin
cons := findConstantHiera(curToken.tokenText);
if cons = nil then
errorExit2('invalid character constant', curToken.tokenText);
matchBaseType(cons^.typ, CharType);
getCharValue := cons^.intValue;
readNextToken;
end
else
begin
matchToken(CharLitToken);
getCharValue := ord(lastToken.tokenText[1]);
end;
end;
procedure parseCharConstArray(arrayConst:ArrayConstRef);
var count: integer;
value: integer;
startValue,endValue: integer;
begin
count := 0;
while curToken.tokenKind in [CharLitToken, IdentToken] do
begin
startValue := getCharValue;
endValue := startValue;
(* process a subrange specification *)
if checkToken(DotToken) then
begin
readNextToken;
if not matchTokenOrNot(DotToken) then
errorExit2('invalid subrange spec after', lastToken.tokenText);
endValue := getCharValue;
end;
for value := startValue to endValue do
begin
count := count + 1;
addArrayConstElem(arrayConst, value);
end;
if checkToken(CommaToken) then
begin
readNextToken;
if not (curToken.tokenKind in [CharLitToken, IdentToken]) then
errorExit2('char literal or constant expected, got', curToken.tokenText);
end;
end;
arrayConst^.count := count;
end;
(* TODO: merge with parseCharConstArray? *)
(* TODO: reuse parseArrayLitValue, parseConstValue, getConstvalue? *)
procedure parseIntConstArray(arrayConst:ArrayConstRef);
var count: integer;
value: integer;
startValue, endValue: integer;
begin
count := 0;
while curToken.tokenKind in [NumberToken, IdentToken] do
begin
startValue := parseInteger;
endValue := startValue;
(* process a subrange specification *)
if checkToken(DotToken) then
begin
readNextToken;
if not matchTokenOrNot(DotToken) then
errorExit2('invalid subrange spec after', lastToken.tokenText);
endValue := parseInteger;
end;
for value := startValue to endValue do
begin
count := count + 1;
addArrayConstElem(arrayConst, value);
end;
if checkToken(CommaToken) then
begin
readNextToken;
if not (curToken.tokenKind in [NumberToken, IdentToken]) then
errorExit2('integer literal or constant expected, got', curToken.tokenText);
end
end;
arrayConst^.count := count;
end;
function getBooleanValue:boolean;
begin
if checkToken(TrueToken) then
getBooleanValue := true
else if checkToken(FalseToken) then
getBooleanValue := false
else
errorExit2('Expected TRUE or FALSE, got', curToken.tokenText);
readNextToken;
end;
procedure getRangePart(var value:integer; var typeReturn: TypeSpec);
var cnst: ConstRef;
begin
setBaseType(typeReturn, NoType);
if checkToken(IdentToken) then
begin
(* is it a constant ? *)
cnst := findConstantHiera(curToken.tokenText);
if cnst <> nil then
begin
typeReturn := cnst^.typ;
if cnst^.typ.baseType in [IntegerType, EnumType, CharType ] then
value := cnst^.intValue
else
errorExit2('scalar value or constant identifier expected, got',
curToken.tokenText);
end
else
errorExit2('scalar value or constant identifier expected, got',
curToken.tokenText);
readNextToken;
end
else if checkToken(CharLitToken) then
begin
setBaseType(typeReturn, CharType);
value := ord(curToken.tokenText[1]);
readNextToken;
end
else if checkToken(TrueToken) or checkToken(FalseToken) then
begin
setBaseType(typeReturn, BooleanType);
value := ord(getBooleanValue);
end
else
begin
setBaseType(typeReturn, IntegerType);
value := parseInteger;
end;
end;
(* TODO: merge with parseCharConstArray? *)
procedure parseEnumConstArray(arrayConst:ArrayConstRef);
var count: integer;
value: integer;
startValue, endValue: integer;
typeA, typeB: TypeSpec;
begin
count := 0;
while checkToken(IdentToken) do
begin
getRangePart(startValue, typeA);
endValue := startValue;
(* process a subrange specification *)
if checkToken(DotToken) then
begin
readNextToken;
if not matchTokenOrNot(DotToken) then
errorExit2('invalid subrange spec after', lastToken.tokenText);
getRangePart(endValue, typeB)
end;
for value := startValue to endValue do
begin
count := count + 1;
addArrayConstElem(arrayConst, value);
end;
if checkToken(CommaToken) then
begin
readNextToken;
if not checkToken(IdentToken) then
errorExit2('integer literal or constant expected, got', curToken.tokenText);
end
end;
arrayConst^.count := count;
end;
(* parse and convert an array literal without the brackets *)
function getArrayConstRaw(var typeReturn: TypeSpec): ArrayConstRef;
var newArrayConst: ArrayConstRef;
newArrayType:^TypeSpec;
cons: ConstRef;
baseType: SymbolType;
begin
setBaseType(typeReturn, ArrayType);
newArrayConst := addArrayConst;
new(newArrayType);
(* check for symbolic constant to determine type *)
if checkToken(IdentToken) then
begin
cons := findConstantHiera(curToken.tokenText);
if cons = nil then
errorExit2('invalid element identifier', curToken.tokenText);
baseType := cons^.typ.baseType;
case baseType of
IntegerType: parseIntConstArray(newArrayConst);
CharType: parseCharConstArray(newArrayConst);
EnumType: parseEnumConstArray(newArrayConst);
else
errorExit2('element must be of integer, char or enum type:', curToken.tokenText);
end;
newArrayType^ := cons^.typ;
end
else if checkToken(CharLitToken) then
begin
setBaseType(newArrayType^, CharType);
parseCharConstArray(newArrayConst);
end
else if checkToken(NumberToken) then
begin
setBaseType(newArrayType^, IntegerType); (* TODO: handle real numbers *)
parseIntConstArray(newArrayConst);
end
else
errorExit2('invalid set/array literal at', curToken.tokenText);
typeReturn.arrayLength := newArrayConst^.count;
typeReturn.arrayStart := 1;
typeReturn.arrayEnd := typeReturn.arrayLength;
typeReturn.elementType := newArrayType;
typeReturn.size := typeReturn.elementType^.size * typeReturn.arrayLength;
{ dumpArrayConst(newArrayConst); }
getArrayConstRaw := newArrayConst;
end;
(* parse and convert an array literal including the brackets *)
function getArrayConst(var typeReturn: TypeSpec): ArrayConstRef;
begin
matchToken(LBracketToken);
getArrayConst := getArrayConstRaw(typeReturn);
matchToken(RBracketToken);
end;
procedure dumpEnumById(enumId:integer);
var typRef: TypeRef;
names: StringList;
name: IdentString;
begin
typRef := findEnumById(enumId);
if typRef = nil then
writeln('enum id ', enumId, ' not found')
else
begin
names := typRef^.typePtr^.enumList;
rewindStringList(names);
while(nextStringListItem(names, name)) do write(name, ' ');
writeln;
end;
end;
(* we handle that what is syntactically a set literal in
standard pascal as an array literal. <- Should be called
"parseArrayLiteral" then.
Real set literals are parsed in parseSetTerm/parseSetExpression *)
procedure parseSetLiteral(var typeReturn: TypeSpec);
var newArrayConst: ArrayConstRef;
begin
newArrayConst := getArrayConst(typeReturn);
emitLoadArrayConst(newArrayConst);
end;
(* parse some comma-separated set items without the brackets *)
procedure parseSetValue(var elementType: TypeSpec);
var sym:SymblRef;
cnst: ConstRef;
savedType: TypeSpec;
begin
(* we don't know the type at first *)
setBaseType(savedType, NoType);
(* start with an empty set value on the stack,
then set bits below *)
emitLoadConstantInt(0);
repeat
if checkToken(IdentToken)
then
begin
(* is it a variable? *)
sym := findHieraSymbol(curToken.tokenText);
if sym <> nil then
begin
parseIdentifier(elementType);
if savedType.baseType = NoType then
savedType := elementType;
end
else
begin
(* if not, it should be a constant *)
cnst := findConstantHiera(curToken.tokenText);
if cnst = nil then
errorExit2('Integer constant or variable expected, got',
curToken.tokenText);
elementType := cnst^.enumRef^.typePtr^;
if savedType.baseType = NoType then
savedType := elementType;
emitLoadConstantInt(cnst^.intValue);
end;
emitAddToSet;
readNextToken;
end
else if checkToken(NumberToken) then
begin
errorExit2('Integers in sets not implemented yet',
curToken.tokenText);
end
else if checkToken(RBracketToken) then
begin
(* empty set is permissible *)
end
else
errorExit2('Integer constant or variable expected, got',
curToken.tokenText);
if savedType.baseType <> NoType then
matchTypes(savedType, elementType);
until not matchTokenOrNot(CommaToken);
end;
procedure parseSetTerm(var setTypeReturn: TypeSpec);
var typ:TypeSpec;
elementType:TypeSpec;
begin
if checkToken(LBracketToken) then
begin
(* handle a set literal *)
readNextToken;
parseSetValue(elementType);
matchToken(RBracketToken);
setBaseType(setTypeReturn, SetType);
setTypeReturn.memberBaseType := elementType.baseType;
if elementType.baseType = EnumType then
setTypeReturn.memberEnumId := elementType.enumId;
setTypeReturn.hasSubrange := elementType.hasSubrange;
setTypeReturn.subStart := elementType.subStart;
setTypeReturn.subEnd := elementType.subEnd;
end
else
if checkToken(IdentToken) then
begin
(* handle a set variable *)
parseIdentifier(typ);
matchBaseType(typ, SetType);
setTypeReturn := typ;
end
else
errorExit2('rest of parseSetTerm not implemented yet',
curToken.tokenText);
end;
(* Parse second part of a set expression.
typeA needs to be set to the set type (not the member type).
Valid operators are: +, -, *, =, <>, <=
*)
procedure parseSetExprTail(var typeA: TypeSpec);
var tok:TokenType;
typeB: TypeSpec;
begin
tok := curToken.tokenKind;
while tok in [ PlusToken, MinusToken, AsteriskToken,
EqToken, NotEqToken, LtEqToken, GtEqToken] do
begin
readNextToken;
parseSetTerm(typeB);
{
dumpEnumById(typeA.memberEnumId);
dumpEnumById(typeB.memberEnumId);
}
matchTypes(typeA, typeB);
case tok of
PlusToken: emitSetAdd;
MinusToken: emitSetSubtract;
AsteriskToken: emitSetIntersect;
EqToken: emitSetCompare;
NotEqToken: emitSetCompareNE;
LtEqToken: emitSetIsSubset;
GtEqToken: begin emitSwap; emitSetIsSubset; end;
end;
if tok in [ EqToken, NotEqToken, LtEqToken, GtEqToken ] then
setBaseType(typeA, BooleanType);
tok := curToken.tokenKind;
end;
end;
(* Parse a set expression (which may be a single term), return
the type in typeReturn *)
procedure parseSetExpression(var typeReturn: TypeSpec);
var elementType:TypeSpec;
begin
parseSetTerm(elementType);
setBaseType(typeReturn, SetType);
typeReturn.memberBaseType := elementType.baseType;
if typeReturn.memberBaseType = EnumType then
typeReturn.memberEnumId := elementType.enumId;
typeReturn.hasSubrange := elementType.hasSubrange;
typeReturn.subStart := elementType.subStart;
typeReturn.subEnd := elementType.subEnd;
parseSetExprTail(typeReturn);
end;
procedure parseInOperator(var typeA: TypeSpec);
var typeB: TypeSpec;
begin
if not isSimpleType(typeA) then
errorExit2('invalid IN operand*', lastToken.tokenText);
matchToken(InToken);
parseExpression(typeB);
if (typeB.baseType = ArrayType) then
begin
{
writeln('**** parseInOperator types ',
typeA.baseType, ' -> ', typeB.elementType^.baseType);
if typeA.baseType = EnumType then
writeln(' enum types ', typeA.enumId, ' ', typeB.elementType^.enumId);
}
matchTypes(typeA, typeB.elementType^);
emitIsInArray(typeB.arrayLength);
end
else
if typeB.baseType = SetType then
begin
matchBaseType(typeA, typeB.memberBaseType);
if (typeB.memberBaseType = EnumType) and
(typeA.enumId <> typeB.memberEnumId) then
errorExit2('Invalid IN operand', lastToken.tokenText);
emitIsInSet;
end
else
if typeB.baseType = StringType then
begin
if not (typeA.baseType in [ CharType, StringCharType ]) then
errorExit2('Invalid IN operand before', lastToken.tokenText);
emitIsInString;
end
else
errorExit2('invalid IN operand', lastToken.tokenText);
end;
procedure parseSimpleExpression(var typeReturn: TypeSpec);
var operatr: TokenType;
typeA, typeB: TypeSpec;
begin
if checkToken(LBracketToken) then
parseSetLiteral(typeReturn)
else
begin
parseTerm(typeA);
(* special cases for char and string expressions *)
if typeA.baseType = CharType then
parseCharExprTail(typeA)
else
if typeA.baseType = StringType then
parseStringExprTail(typeA)
else
begin
while curToken.tokenKind in [PlusToken, MinusToken, OrToken, XorToken ] do
begin
operatr := curToken.tokenKind;
readNextToken;
parseTerm(typeB);
if operatr = PlusToken then arithmeticOp(typeA, typeB, 'ADD')
else if operatr = MinusToken then arithmeticOp(typeA, typeB, 'SUB')
else if operatr = OrToken then logicOp(typeA, typeB, 'OR')
else if operatr = XorToken then logicOp(typeA, typeB, 'XOR');
end;
end;
typeReturn := typeA;
end;
end;
(* Parse an expression. The value of the expression is placed on the stack.
The type is returned in typeReturn.
In case of an aggregate type, a temporary is allocated and the address
is placed on the stack *)
procedure parseExpression(var typeReturn: TypeSpec);
var operatr: TokenType;
typeA, typeB: TypeSpec;
compOp: CompOpString;
begin
parseSimpleExpression(typeA);
typeReturn := typeA;
if checkToken(InToken) then
begin
parseInOperator(typeA);
setBaseType(typeReturn, BooleanType);
end
else
if checkComparisonOperator(curToken.tokenKind) then
begin
operatr := curToken.tokenKind;
compOp := getCompareOpFromToken(operatr);
readNextToken;
parseSimpleExpression(typeB);
matchComparableTypes(typeA, typeB);
setBaseType(typeReturn, BooleanType);
if typeA.baseType = RealType then
begin
matchRealType(typeB); (* converts b from int to real if necessary *)
emitFloatComparison(compOp);
end
else if (typeA.baseType = IntegerType) and (typeB.baseType = RealType) then
begin
(* special case for comparing integer to real *)
emitIntFloatComparison(compOp);
end
else if isScalar(typeA) then
begin
emitComparison(compOp);
end
else
begin
if typeA.baseType = StringType then
compareStrings(operatr)
else
compareAggregate(operatr, typeA);
end;
end;
end;
procedure parseStringPrimary;
var aConstStr: ConstStrRef;
typeReturn: TypeSpec;
begin
if checkToken(StringLitToken) or checkToken(CharLitToken) then
begin
aConstStr := addConstStr(curToken.tokenText);
emitLoadConstStr(aConstStr);
readNextToken;
end
else if checkToken(IdentToken) then
begin
parseIdentifier(typeReturn);
if typeReturn.baseType = CharType then
convertCharToString(typeReturn);
matchBaseType(typeReturn, StringType);
end
else
errorExit2('Expected string, got', curToken.tokenText);
end;
(* Emit a copy or initfrom call, depending on the
initialization flag of the MemLocation.
We cannot always initialize strings on assignment,
as this would corrupt var parameters or
dereferenced string pointers.
We use the initialized field of Symbl and MemLocation
to track if a string has already been initialized.
*)
procedure initOrCopyString(var dstMem: MemLocation);
begin
if dstMem.initialized then
emitCopyString
else
emitInitStringFrom(dstMem.typ.stringLength);
end;
(* allocate a temporary and initialize it from the
string pointer on the stack, which is then removed.*)
procedure getTempFromString(srcType:TypeSpec; var tempReturn: MemLocation);
var typ:TypeSpec;
begin
typ := srcType;
if typ.stringLength < DefaultStringLength then
setStringTypeSize(typ, DefaultStringLength);
allocTemporary(curProcedure, typ, tempReturn);
loadAddr(tempReturn); (* put address of temporary on stack *)
emitSwap; (* and swap it with src for COPYSTRING *)
initOrCopyString(tempReturn); (* copy src to temp *)
end;
(* convert a string to a temporary.
allocate temp space and copy source to temp.
requires src string address on stack,
which is then replaced by the address of the temporary *)
procedure convertStringToTemp(srcType:TypeSpec; var tempReturn: MemLocation);
begin
getTempFromString(srcType, tempReturn);
emitLoadTempAddr(tempReturn.name, tempReturn.offset);
end;
(* parse the tail of a string expression, that is
everything after a plus operator if there is one
(including the plus operator) *)
procedure parseStringExprTail(dstType: TypeSpec);
var temp: MemLocation;
begin
if checkToken(PlusToken) then
begin
(* if there is a plus operator, we need to allocate a
temporary to build the concatenated string which is
then copied to the destination. this is required
so that it is possible to have the same string variable
on the left and the right side of the assignment.
example: s := '/' + s
*)
getTempFromString(dstType, temp);
while checkToken(PlusToken) do
begin
readNextToken;
emitLoadTempAddr(temp.name, temp.offset);
parseStringPrimary;
emitAppendString;
end;
(* put temporary address on stack as src for final COPYSTRING call *)
emitLoadTempAddr(temp.name, temp.offset);
end;
end;
(* parse a string expression, which can be a single string identifier/literal or
a concatenation with a plus operator *)
procedure parseStringExpression(var dstMem: MemLocation);
begin
parseStringPrimary; (* parse first primary, addr is placed on stack *)
parseStringExprTail(dstMem.typ); (* parse the rest, if any *)
initOrCopyString(dstMem); (* copy to destination *)
end;
(* parse the tail of a char expression, which can be a "+" operator,
making it a string expression, or an IN operator *)
procedure parseCharExprTail(var typeA: TypeSpec);
begin
if checkToken(PlusToken) then
begin
convertCharToString(typeA);
parseStringExprTail(typeA);
end
else
if checkToken(InToken) then
begin
parseInOperator(typeA);
setBaseType(typeA, BooleanType);
end;
end;
procedure parseCompoundStatement;
begin
if checkToken(BeginToken) then
begin
readNextToken;
while not checkToken(EndToken) do
begin
parseStatement;
if not checkToken(EndToken) then
matchToken(SemicolonToken);
end;
matchToken(EndToken);
end
else
parseStatement;
end;
(* Parse a range specification in the form 1..10.
Handles constants, enums, subrange types.
In case of an enum, the type is returned in typeReturn.
Otherwise, typeReturn is set to NoType.
For enum and subrange types, a single type identifier stands
for the start and the end value, like so:
type aSubrangeType = 1..10;
type anEnum = (one, two, three);
var anArray: array [aSubrangeType] of boolean;
var array2: array [anEnum] of boolean;
Cases to cover:
1..10 -> returns integer type with subrange
1..c -> "
c..10 -> "
c1..c2 -> "
enum-type -> returns enum type
enumval1..enumval2 -> returns enum type
subrange-type -> returns enum type
*)
procedure getRange(var typeReturn:TypeSpec);
var typ,typ2: TypeSpec;
need2nd: boolean;
rStart,rEnd: integer;
begin
need2nd := true;
setBaseType(typeReturn, NoType);
setBaseType(typ, NoType);
setBaseType(typ2, NoType);
if checkToken(IdentToken) then
begin
(* is it a enum or subrange type identifier? *)
typ := findTypeHiera(curToken.tokenText);
if typ.baseType <> NoType then
begin
readNextToken;
need2nd := false;
if typ.baseType = EnumType then
begin
setSubrange(typ, 0, typ.enumLength - 1);
typeReturn := typ;
end
else
if (typ.baseType = IntegerType) and (typ.hasSubrange) then
begin
typeReturn := typ;
end
(* TODO: can also be a set type identifier *)
else
errorExit2('invalid range specification', curToken.tokenText);
end
else
(* should be a constant now, maybe with an enum type *)
getRangePart(rStart, typ);
end
else if curToken.tokenKind in [ NumberToken, MinusToken, CharLitToken ] then
begin
(* integer and char can also be handled by getRangePart *)
getRangePart(rStart, typ);
end
else
errorExit2('invalid range specification', curToken.tokenText);
if need2nd then
begin
matchToken(DotToken);
matchToken(DotToken);
getRangePart(rEnd,typ2);
if typ.baseType <> typ2.baseType then
errorExit2('invalid range specification', lastToken.tokenText);
if rStart > rEnd then
errorExit2('range start must be less than end', lastToken.tokenText);
typeReturn := typ;
setSubrange(typeReturn, rStart, rEnd);
end;
end;
procedure parseRangeSpec(var typeReturn:TypeSpec);
begin
getRange(typeReturn);
end;
(* Parse the range part of an array declaration and try to determine
the element type.
When handling multiple array dimensions,
parseArraySpecPart is called recusively for each part
(like "1..10,1..10").
Returns the resulting type in the typSpec var parameter,
also creates a complete type chain for the element type
or types for multidimensional arrays.
Parses the end of the array spec including the right bracket
and the "OF" type declaration.
*)
procedure parseArraySpecPart(var typSpec: TypeSpec);
var rangeStart,rangeEnd: integer;
range: TypeSpec;
newType: ^TypeSpec;
begin
getRange(range);
rangeStart := range.subStart;
rangeEnd := range.subEnd;
typSpec.baseType := ArrayType;
typSpec.arrayStart := rangeStart;
typSpec.arrayEnd := rangeEnd;
typSpec.arrayLength := rangeEnd - rangeStart + 1;
if range.baseType = EnumType then
typSpec.indexEnumId := range.enumId
else
typSpec.indexEnumId := 0;
new(newType);
if checkToken(CommaToken) then
begin
readNextToken;
parseArraySpecPart(newType^);
(* need to call recursively to calculate the element sizes
from right to left *)
end
else
begin
matchToken(RBracketToken);
matchToken(OfToken);
new(newType);
parseTypeSpec(newType^, false);
end;
typSpec.elementType := newType;
typSpec.size := newType^.size * typSpec.arrayLength;
end;
procedure addUnresolvedType(typePtr:TypeSpecPtr);
var t:TypeRef;
newItem: TypeRef;
begin
new(newItem);
newItem^.typePtr := typePtr;
newItem^.name := '';
newItem^.next := nil;
t := curProcedure^.unresolved;
if t = nil then
curProcedure^.unresolved := newItem
else
begin
(* get to end of list *)
while t^.next <> nil do t := t^.next;
t^.next := newItem;
end;
end;
procedure parseAnonRecordType(var typeReturn: TypeSpec); forward;
procedure parseTypeSpec(var typSpec: TypeSpec; allowUnresolved:boolean);
var length: integer;
pointedType: ^TypeSpec;
cnst: ConstRef;
nameStr: ^IdentString;
elementType: TypeSpec;
namebuf: IdentString;
begin
if not (curToken.tokenKind in
[ IntegerToken, RealToken, StringToken, BooleanToken, CharToken,
ArrayToken, LParenToken, IdentToken, PointerToken, NumberToken,
MinusToken, CharLitToken, SetToken, RecordToken, PackedToken ]) then
errorExit2('invalid type', curToken.tokenText);
typSpec.size := wordSize; (* use a sensible default *)
typSpec.hasSubrange := false;
if checkToken(LParenToken) then
begin
nextAnonTypeName(namebuf);
parseEnumDecl(namebuf, typSpec);
end
else
if checkToken(PointerToken) then
begin
readNextToken;
typSpec.baseType := PointerType;
new(pointedType);
parseTypeSpec(pointedType^, true);
typSpec.pointedType := pointedType;
(* pointers can point to a yet not declared type *)
if pointedType^.baseType = UnresolvedType then
addUnresolvedType(pointedType);
end
else
if checkToken(StringToken) then
begin
typSpec.baseType := StringType;
length := DefaultStringLength;
readNextToken;
if checkToken(LBracketToken) then
begin
readNextToken;
length := parseInteger;
matchToken(RBracketToken);
end;
typSpec.size := getStringMemSize(length);
typSpec.baseType := StringType;
typSpec.stringLength := length;
end
else if checkToken(IntegerToken) then
begin
typSpec.baseType := IntegerType;
readNextToken;
end
else if checkToken(RealToken) then
begin
typSpec.baseType := RealType;
readNextToken;
end
else if checkToken(BooleanToken) then
begin
typSpec.baseType := BooleanType;
readNextToken;
end
else if checkToken(CharToken) then
begin
typSpec.baseType := CharType;
readNextToken;
end
else if checkToken(NumberToken) or checkToken(MinusToken) then
begin
parseRangeSpec(typSpec)
end
else if checkToken(CharLitToken) then
begin
parseRangeSpec(typSpec)
end
else if checkToken(IdentToken) then
begin
(* if it is a constant it must be part of a range *)
cnst := findConstantHiera(curToken.tokenText);
if cnst <> nil then
begin
parseRangeSpec(typSpec);
end
else
begin
(* if it is not a constant, it must be a type identifier *)
typSpec := findTypeHiera(curToken.tokenText);
if typSpec.baseType = NoType then
begin
if not allowUnresolved then
errorExit2('invalid type', curToken.tokenText);
setBaseType(typSpec, UnresolvedType);
new(nameStr);
nameStr^ := curToken.tokenText;
typSpec.typeName := nameStr;
typSpec.sourceLine := lineno;
end;
readNextToken;
end;
end
else if checkToken(SetToken) then
begin
readNextToken;
matchToken(OfToken);
parseTypeSpec(elementType,false);
if not (elementType.baseType in [IntegerType, BooleanType, CharType, EnumType])
then errorExit2('invalid set member type', lastToken.tokenText);
if elementType.baseType in [IntegerType, CharType] then
begin
if not (elementType.hasSubrange and
(elementType.subStart >=0) and (elementType.subEnd < wordBits)) then
errorExit2('Unsupported set size', '');
end
else if elementType.baseType = EnumType then
begin
if elementType.hasSubrange then
begin
if not ((elementType.subStart >= 0) and
(elementType.subEnd < wordBits)) then
errorExit2('Unsupported set size', '');
end
else
if elementType.enumLength > wordBits then
errorExit2('Unsupported set size', '');
typSpec.memberEnumId := elementType.enumId;
end;
(* if it is not integer, char or enum, it is boolean, which will
most certainly within a word *)
setBaseType(typSpec, SetType);
typSpec.memberBaseType := elementType.baseType;
typSpec.hasSubrange := elementType.hasSubrange;
typSpec.subStart := elementType.subStart;
typSpec.subEnd := elementType.subStart;
end
else
begin
optionalToken(PackedToken);
if checkToken(ArrayToken) then
begin
readNextToken;
matchToken(LBracketToken);
parseArraySpecPart(typSpec);
end
else
if checkToken(RecordToken) then
parseAnonRecordType(typSpec)
else
(* TODO: test if it really cannot happen and remove *)
(* happens at the moment with something like "packed char" *)
errorExit2('invalid type (should not happen)', curToken.tokenText);
end
end;
procedure parseRecordDecl(var newTypeName:IdentString); forward;
procedure parseAnonRecordType(var typeReturn: TypeSpec);
var typeNam:IdentString;
recTypeRef: TypeRef;
begin
nextAnonTypeName(typeNam);
parseRecordDecl(typeNam);
recTypeRef := findTypeRef(curProcedure, typeNam);
typeReturn := recTypeRef^.typePtr^;
end;
procedure validateParam(var forwardParam:SymblRef; aProc:ProcRef;
var name:IdentString; var typ:TypeSpec; isVarParam:boolean);
var valid:boolean;
begin
valid := true;
if forwardParam = nil then
valid := false
else
if forwardParam^.name <> name then
valid := false
else
if not isSameType(forwardParam^.symType, typ) then
valid := false
else
valid := forwardParam^.isVarParam = isVarParam;
if not valid then
errorExit2('Parameters do not match forward declaration for', aProc^.name);
forwardParam := forwardParam^.next;
end;
procedure parseParameter(aProc: ProcRef; var forwardParam:SymblRef);
var name: IdentString;
names: StringList;
typSpec: TypeSpec;
isVarParam: boolean;
begin
initStringList(names);
if checkToken(VarToken) then
begin
isVarParam := true;
readNextToken;
end
else
isVarParam := false;
repeat
addToStringList(names, curToken.tokenText);
matchToken(IdentToken);
until not matchTokenOrNot(CommaToken);
matchToken(ColonToken);
parseTypeSpec(typSpec, false);
(* create parameters with the declared type from the list of names *)
while(nextStringListItem(names, name)) do
begin
if aProc^.isForward then
validateParam(forwardParam, aProc, name, typSpec, isVarParam)
else
addParam(aProc, name, typSpec, isVarParam);
end;
disposeStringList(names);
end;
procedure storeArg(sym: SymblRef);
begin
if not sym^.isVarParam then
begin
(* aggregates which are not var params need to be copied *)
if sym^.symType.baseType in [ RecordType, ArrayType ] then
begin
emitLoadLocalAddr(sym^.name, sym^.offset);
emitSwap; (* COPYWORDS wants src on ToS then dest *)
emitCopy(sym^.size);
end
else if sym^.symType.baseType = StringType then
begin
emitLoadLocalAddr(sym^.name, sym^.offset);
emitSwap; (* INITSTRINGFROM wants src on ToS then dest *)
emitInitStringFrom(sym^.symType.stringLength);
end
else
emitStoreArg(sym);
end
else
emitStoreArg(sym);
end;
procedure initLocalString(sym: SymblRef);
begin
if (not sym^.isVarParam) and (not sym^.initialized) then
begin
emitForceInitString(sym^.name, sym^.offset, sym^.symType.stringLength);
sym^.initialized := true;
end;
end;
procedure initTemporaryString(loc: MemLocation);
begin
emitInitTempString(loc.name, loc.offset, loc.typ.stringLength);
end;
function typeContainsString(typ: TypeSpec): boolean;
var field: FieldRef;
begin
typeContainsString := false;
if typ.baseType = StringType then
typeContainsString := true
else
if typ.baseType = RecordType then
begin
field := typ.fields;
while (not typeContainsString) and (field <> nil) do
begin
typeContainsString := typeContainsString(field^.fieldType);
field := field^.next;
end
end
else
if typ.baseType = ArrayType then
typeContainsString := typeContainsString(typ.elementType^)
end;
(* this procedure should initialize local variables,
but since we do not guarantee variable initialization,
we don't do anything here, for now, except for strings.
strings are normally initialized on assignment, but when
we pass them as var parameters before they have been assigned,
they need to be initialized.
If a record or an array contains a string, we call CLEARMEM
for that variable, so it can be recognized as uninitialized. *)
procedure initLocalVars(aProc: ProcRef);
var sym: SymblRef;
begin
sym := aProc^.vars.first;
while sym <> nil do
begin
if sym^.symType.baseType = StringType then
initLocalString(sym)
else if typeContainsString(sym^.symType)
and not (sym^.isVarParam or sym^.isParam) then
clearLocalVar(sym);
sym := sym^.next;
end;
end;
function getReturnVar(aProc: ProcRef): SymblRef;
var sym: SymblRef;
begin
sym := findSymbol(aProc^.vars, aProc^.name);
if sym = nil then
errorExit2('internal error: returnVar not found', aProc^.name)
else
getReturnVar := sym;
end;
(* call storeArg for each entry in the parameter list
in reverse order, using recursion *)
procedure reverseArgs(sym: SymblRef);
begin
if sym^.next <> nil then
reverseArgs(sym^.next);
storeArg(sym);
end;
procedure fetchProcedureArgs(aProc: ProcRef);
var sym: SymblRef;
begin
if aProc^.returnsAggregate then
storeArg(getReturnVar(aProc));
sym := aProc^.parameters.first;
if sym <> nil then reverseArgs(sym)
end;
procedure parseParameterList(var aProc: ProcRef);
var forwardParam:SymblRef;
begin
forwardParam := aProc^.parameters.first;
while not checkToken(RParenToken) do
begin
if not (curToken.tokenKind in [ IdentToken, VarToken ]) then
errorExit2('Expected identifier, got', curToken.tokenText);
parseParameter(aProc, forwardParam);
if checkToken(SemicolonToken) then
begin
readNextToken;
if not (curToken.tokenKind in [ IdentToken, VarToken ]) then
errorExit2('Expected identifier, got', curToken.tokenText)
end
else
if not checkToken(RParenToken) then
errorExit2('Expected ; or ), got', curToken.tokenText);
end;
end;
procedure parseLabelBlock;
begin
repeat
readNextToken;
matchToken(IdentToken);
addLabel(curProcedure, lastToken.tokenText);
until not checkToken(CommaToken);
matchToken(SemicolonToken);
end;
procedure parseProcOrFunc;
begin
if checkToken(ProcedureToken) then
parseProcedure
else if checkToken(FunctionToken) then
parseFunction
else
errorExit2('Expected PROCEDURE or FUNCTION, got', curToken.tokenText);
end;
procedure parseProcOrFuncBody(aProc: ProcRef; returnVar: SymblRef);
begin
if checkToken(ExternalToken) then
begin
(* for an externally declared function, no
code is emitted *)
readNextToken;
end
else
if checkToken(ForwardToken) then
begin
(* for a forward declaration, just set the isForward flag *)
aProc^.isForward := true;
readNextToken;
end
else
begin
aProc^.isForward := false; (* If there was a forward declaration,
we are using its aProc record. Set the isForward field to false
for that case, to prevent multiple procedure statements. *)
(* parse var, type, const, label statements,
also nested procedures *)
parseProgramBlock;
emitProcedurePrologue(aProc);
fetchProcedureArgs(aProc);
initLocalVars(aProc);
parseCompoundStatement;
if aProc^.hasExit then
emitExitLabel(aProc);
if returnVar <> nil then
begin
(* if return value is an aggregate,
returnVar is a var parameter. in this case,
nothing needs to be done here, because
the value has already been set at the
destination.
we still return the pointer to the destination
so it can be evaluated without any special handling. *)
emitFunctionValueReturn(returnVar);
end;
emitProcedureEpilogue(aProc);
end;
end;
procedure parseFunction;
var aProc, previousProc: ProcRef;
name: IdentString;
sym: SymblRef;
returnType: TypeSpec;
returnsAggregate: boolean;
begin
readNextToken;
name := curToken.tokenText;
previousProc := curProcedure;
aProc := addProcedure(name, true, previousProc);
curProcedure := aProc;
readNextToken;
if checkToken(LParenToken) then
begin
readNextToken;
parseParameterList(aProc);
matchToken(RParenToken);
end;
(* parse return type declaration *)
matchToken(ColonToken);
parseTypeSpec(returnType, false);
matchToken(SemicolonToken);
(* If we parse the function the second time after
a forward declaration, we must not add
the result variable a second time.*)
if not aProc^.isForward then
begin
(* add function name as local variable for return value *)
(* if the return value is an aggregate,
make the return value a var parameter *)
returnsAggregate := isAggregate(returnType);
sym := addSymbol(aProc^.vars, name, returnType, false, returnsAggregate);
aProc^.returnsAggregate := returnsAggregate;
aProc^.returnType := sym^.symType;
end
else
(* take the return var from forward declaration *)
sym := findSymbol(aProc^.vars, name);
parseProcOrFuncBody(aProc, sym);
curProcedure := previousProc;
end;
procedure parseProcedure;
var aProc, previousProc: ProcRef;
begin
readNextToken;
previousProc := curProcedure;
aProc := addProcedure(curToken.tokenText, false, previousProc);
curProcedure := aProc;
readNextToken;
if checkToken(LParenToken) then
begin
readNextToken;
parseParameterList(aProc);
matchToken(RParenToken);
end;
matchToken(SemicolonToken);
parseProcOrFuncBody(aProc, nil);
curProcedure := previousProc;
end;
procedure parseVarParam(var typeReturn: TypeSpec);
var mem: MemLocation;
begin
parseMemLocation(true, mem); (* put memory loc of variable on stack *)
typeReturn := mem.typ;
if (mem.typ.baseType = StringType) and (not mem.initialized) then
begin
emitInitStringShort(mem.typ.stringLength);
mem.initialized := true;
{ FIXME: the following causes a bug with string
initialization if the string is passed
as a var parameter in a nested procedure.
why was this needed in the first place? }
{ mem.origSym^.initialized := true; }
end;
end;
function isFunction(aProc: ProcRef): boolean;
begin
isFunction := aProc^.returnType.baseType <> NoType;
end;
procedure parseNew;
var memLoc: MemLocation;
typeReturn: TypeSpec;
begin
matchToken(LParenToken);
parseLvalue(memLoc);
matchBaseType(memLoc.typ,PointerType);
if memLoc.typ.pointedType^.baseType = StringType then
begin
if checkToken(CommaToken) then
begin
readNextToken;
parseExpression(typeReturn);
matchBaseType(typeReturn, IntegerType);
end
else
emitLoadConstantInt(memLoc.typ.pointedType^.stringLength);
emitStringAlloc;
end
else
begin
emitLoadConstantInt(memLoc.typ.pointedType^.size);
emitMemAlloc;
if typeContainsString(memLoc.typ.pointedType^) then
emitClearAlloc(memLoc.typ.pointedType);
end;
emitCheckAlloc;
(*We need to call CLEARMEM when the allocated type
contains strings.
INITSTRING checks if the header is non-zero to see if
the string is already initialized, and the allocated
chunk might contain random data so it would look
like an initialized string. *)
writeVariable(memLoc);
matchToken(RParenToken);
end;
procedure parseDispose;
var memLoc: MemLocation;
begin
matchToken(LParenToken);
parseMemLocation(false, memLoc);
matchBaseType(memLoc.typ, PointerType);
readVariable(memLoc);
emitMemFree;
matchToken(RParenToken);
end;
function isFileVariable(var name:IdentString):boolean;
var sym:SymblRef;
begin
sym := findHieraSymbol(name);
if sym = nil then
errorExit2('Undeclared variable', name);
isFileVariable := isSameType(sym^.symType, fileTyp);
end;
(* Parse optional width and precision specifications
for str and write.
Count specifies the possible number of specs (1 or 2).
If a spec is not there,
a zero is put onto the stack for each missing
spec.
*)
procedure parseFieldSpecs(var argType:TypeSpec);
var specType:TypeSpec;
max, i:integer;
begin
if argType.baseType in
[StringType, IntegerType, BooleanType, PointerType, EnumType ] then
max := 1
else
if argType.baseType = RealType then
max := 2
else
if argType.baseType = CharType then
max := 0
else
max := 0;
(* Chars should also have a field width by
the standard, but that's not very useful
and it slows things down.
We could call a different routine if a field
width is specified. We could do that for all
types of course. *)
if max > 0 then
begin
if checkToken(ColonToken) then
begin
readNextToken;
parseExpression(specType);
matchBaseType(specType, IntegerType);
if max = 2 then
begin
if checkToken(ColonToken) then
begin
readNextToken;
parseExpression(specType);
matchBaseType(specType, IntegerType);
end
else
emitLoadConstantInt(0);
end
else
if checkToken(ColonToken) then
errorExit2('Fraction length not allowed' , '');
end
else
for i := 1 to max do
emitLoadConstantInt(0);
end;
end;
procedure writeByType(var typ:TypeSpec);
begin
emitWriteFileArg;
parseFieldSpecs(typ);
if typ.baseType = StringType then
emitWrite('STRING')
else
if typ.baseType = CharType then
emitWrite('CHAR')
else
if typ.baseType = RealType then
emitWrite('REAL')
else
if typ.baseType in [ IntegerType, BooleanType, PointerType, EnumType ] then
emitWrite('INT')
else (* everything else is raw binary*)
emitWriteWords(typ.size);
end;
procedure parseWrite(newline:boolean);
var typeReturn: TypeSpec;
isFirst:boolean;
hasFileArg:boolean;
count:integer;
begin
count := 0;
if matchTokenOrNot(LParenToken) then (* can be empty and have no parentheses *)
begin
if not checkToken(RParenToken) then (* can be empty inside parentheses *)
begin
isFirst := true;
hasFileArg := false;
repeat
parseExpression(typeReturn);
if isFirst then
begin
if isSameType(typeReturn, fileTyp) then
begin
(* File var address is on stack now
from parseExpression.
*)
hasFileArg := true;
emitCheckError;
end
else
begin
(* the first arg is already on stack and it needs to be written *)
emitDefaultOutput;
end;
end;
(* ignore the first arg if it is a file arg *)
if not (isFirst and hasFileArg) then
writeByType(typeReturn);
isFirst := false;
count := count + 1;
until not matchTokenOrNot(CommaToken);
if newline then
emitWriteNewline;
emitWriteEnd;
end;
matchToken(RParenToken);
end;
if (count = 0) and newline then
emitDefaultNewline;
end;
procedure readByType(var mem:MemLocation);
begin
emitReadFileArg;
if mem.typ.baseType = CharType then
begin
(* freadchar is a special case, it returns
a char value on the estack to make it a
bit faster. The address of the destination variable
has already been put on the stack by parseMemLocation,
so we do a writeVariable to store the result. *)
emitRead('CHAR');
writeVariable(mem);
end
else
begin
(* For all other types, the address of the variable
is passed as a var parameter. *)
if mem.typ.baseType = StringType then
begin
if not mem.initialized then
emitInitStringSwapped(mem.typ.stringLength);
emitRead('STRING');
end
else
if mem.typ.baseType = RealType then
emitRead('REAL')
else
if mem.typ.baseType in [IntegerType, BooleanType, PointerType] then
emitRead('INT')
else
emitReadWords(mem.typ.size);
end;
end;
procedure parseRead(newline:boolean);
var mem: MemLocation;
isFirst:boolean;
hasFileArg:boolean;
count:integer;
begin
count := 0;
if matchTokenOrNot(LParenToken) then (* can be empty and have no parentheses *)
begin
if not checkToken(RParenToken) then (* can be empty inside parentheses *)
begin
isFirst := true;
hasFileArg := false;
repeat
parseMemLocation(true, mem); (* get destination memLoc, force indirect *)
if isFirst then
begin
if isSameType(mem.typ, fileTyp) then
begin
(* File var address is on stack now
from parseLvalue.
*)
hasFileArg := true;
(* for read/write, we generate a call to checkerror,
because otherwise when reading/writing multiple
variables, we get a runtime error if the first
read/write gets an error and a second variable
is being read/written.
But we want to be able to check with
IOResult.
For other file operations (e.g. seek),
the code in stdlib does the checkerror call.
*)
emitCheckError;
end
else
begin
emitDefaultInput;
end;
end;
(* ignore the first arg if it is a file arg *)
if not (isFirst and hasFileArg) then
readByType(mem);
isFirst := false;
count := count + 1;
until not matchTokenOrNot(CommaToken);
if newline then
emitReadNewline
else
emitReadEnd;
matchToken(RParenToken);
end;
end;
if (count = 0) and newline then
emitReadDefaultNewline;
end;
procedure parseSimpleSP(var typeReturn: TypeSpec);
begin
readNextToken;
matchToken(LParenToken);
parseExpression(typeReturn);
matchToken(RParenToken);
end;
procedure parseSetLength;
var argType: TypeSpec;
begin
matchToken(LParenToken);
parseExpression(argType);
matchBaseType(argType, StringType);
matchToken(CommaToken);
parseExpression(argType);
matchBaseType(argType, IntegerType);
matchToken(RParenToken);
emitSetStringLength;
end;
procedure parseSimpleSF(var typeReturn: TypeSpec);
begin
matchToken(LParenToken);
parseExpression(typeReturn);
matchToken(RParenToken);
end;
procedure parseChr(var typeReturn: TypeSpec);
begin
parseSimpleSF(typeReturn);
matchBaseType(typeReturn, IntegerType);
setBaseType(typeReturn, CharType);
end;
procedure parseOrd(var typeReturn: TypeSpec);
begin
parseSimpleSF(typeReturn);
if not (typeReturn.baseType in
[ CharType, EnumType, BooleanType, IntegerType ]) then
errorExit2('invalid argument type for ORD', '');
(* no code is required, just the type conversion *)
setBaseType(typeReturn, IntegerType);
end;
procedure parseOdd(var typeReturn: TypeSpec);
begin
parseSimpleSF(typeReturn);
matchBaseType(typeReturn, IntegerType);
setBaseType(typeReturn, BooleanType);
emitOdd;
end;
procedure parseAbs(var typeReturn: TypeSpec);
begin
parseSimpleSF(typeReturn);
if typeReturn.baseType = IntegerType then
emitAbsInt
else
if typeReturn.baseType = RealType then
emitAbsFloat32
else
errorExit2('Integer or real type required for ABS', '');
end;
procedure parseTrunc(var typeReturn: TypeSpec);
begin
parseSimpleSF(typeReturn);
matchBaseType(typeReturn, RealType);
emitTruncFloat;
setBaseType(typeReturn, IntegerType);
end;
procedure parseFrac(var typeReturn: TypeSpec);
begin
parseSimpleSF(typeReturn);
matchBaseType(typeReturn, RealType);
emitFractFloat;
end;
procedure parseInt(var typeReturn: TypeSpec);
begin
parseSimpleSF(typeReturn);
matchBaseType(typeReturn, RealType);
emitIntFloat;
end;
procedure parseSucc(var typeReturn: TypeSpec);
begin
parseSimpleSF(typeReturn);
if typeReturn.baseType in [ IntegerType, CharType ] then
emitInc(1)
else
if typeReturn.baseType = EnumType then
begin
emitInc(1);
emitEnumCheck(typeReturn.enumLength - 1);
end
else
errorExit2('Integer, char or enum type expected', '');
end;
procedure parsePred(var typeReturn: TypeSpec);
begin
parseSimpleSF(typeReturn);
if typeReturn.baseType in [ IntegerType, CharType ] then
emitDec(1)
else
if typeReturn.baseType = EnumType then
begin
emitDec(1);
emitEnumCheck(typeReturn.enumLength - 1);
end
else
errorExit2('integer, char or enum type expected', '');
end;
procedure parseSqr(var typeReturn: TypeSpec);
begin
parseSimpleSF(typeReturn);
if typeReturn.baseType = IntegerType then
emitSqrInt
else
if typeReturn.baseType = RealType then
emitSqrFloat
else
errorExit2('integer or real argument expected for sqr', '');
end;
procedure parseValSP;
var valType:TypeSpec;
codeType:TypeSpec;
strType:TypeSpec;
begin
matchToken(LParenToken);
parseExpression(strType); (* first arg must be a string *)
matchBaseType(strType, StringType);
matchToken(CommaToken);
parseVarParam(valType); (* this can be integer or real *)
matchToken(CommaToken);
parseVarParam(codeType); (* the return code must be integer *)
if valType.baseType = IntegerType then
emitValCall('INT')
else
if valType.baseType = RealType then
emitValCall('REAL')
else
errorExit2('Expected INTEGER or REAL variable','');
matchBaseType(codeType, IntegerType);
matchToken(RParenToken);
end;
procedure parseStrSP;
var numType:TypeSpec;
argType:TypeSpec;
begin
matchToken(LParenToken);
parseExpression(numType);
parseFieldSpecs(numType);
matchToken(CommaToken);
parseExpression(argType); (* FIXME: use parseVarParam *)
matchBaseType(argType, StringType);
if numType.baseType = IntegerType then
emitStrCall('INT')
else
if numType.baseType = RealType then
emitStrCall('REAL')
else
errorExit2('Invalid argument type close to', lastToken.tokenText);
matchToken(RParenToken);
end;
procedure parseExitSP;
begin
(* check for optional empty parentheses *)
if matchTokenOrNot(LParenToken) then
(* we do not support parameters for exit() *)
matchToken(RParenToken);
if not curProcedure^.hasExit then
curProcedure^.hasExit := true;
emitExit(curProcedure);
end;
procedure spNotImplemented;
begin
errorExit2('special procedure/function not implemented:', lastToken.tokenText);
end;
procedure parseSpecialFunction(sf: SpecialFunc; var returnType: TypeSpec);
begin
case sf of
NoSF:
errorExit2('internal error in parseSpecialFunction:', curToken.tokenText);
TruncSF:
parseTrunc(returnType);
FracSF:
parseFrac(returnType);
IntSF:
parseInt(returnType);
SqrSF:
parseSqr(returnType);
SuccSF:
parseSucc(returnType);
PredSF:
parsePred(returnType);
OddSF:
parseOdd(returnType);
ChrSF:
parseChr(returnType);
OrdSF:
parseOrd(returnType);
AbsSF:
parseAbs(returnType);
end;
end;
procedure parseSpecialProcCall(sp: SpecialProc);
begin
case sp of
NoSP:
errorExit2('internal error in parseSpecialProcCall', lastToken.tokenText);
NewSP:
parseNew;
DisposeSP:
parseDispose;
ReadSP:
parseRead(false);
WriteSP:
parseWrite(false);
ReadlnSP:
parseRead(true);
WritelnSP:
parseWrite(true);
SetlengthSP:
parseSetLength;
ValSP:
parseValSP;
StrSP:
parseStrSP;
ExitSP:
parseExitSP;
(* TODO: inc() and dec() *)
end;
end;
procedure parseProcedureCall(var name: IdentString);
var aProc: ProcRef;
noMemLocation: MemLocation;
sp: SpecialProc;
begin
sp := NoSP;
readNextToken;
aProc := searchProcedure(name);
if aProc = nil then (* no procedure found, try special procedures *)
sp := findSpecialProcedure(name);
if (aProc = nil) and (sp = NoSP) then
(* neither regular nor special procedure, error *)
errorExit2('Undeclared identifier', name);
if sp <> NoSP then
parseSpecialProcCall(sp)
else
begin
if isFunction(aProc) then
errorExit2('function cannot be called as a procedure:', name);
initNoMemLocation(noMemLocation);
parseCall(aProc, noMemLocation);
end;
end;
function markTemporaries(aProc: ProcRef): integer;
begin
markTemporaries := aProc^.tempsSize;
end;
procedure allocTemporary(aProc: ProcRef;
var typ: TypeSpec; var memLocReturn: MemLocation);
begin
aProc^.tempsSize := aProc^.tempsSize + typ.size;
memLocReturn.memLoc := TemporaryMem;
memLocReturn.offset := aProc^.tempsSize;
memLocReturn.name := '<tmp>';
memLocReturn.typ := typ;
memLocReturn.initialized := false;
end;
procedure releaseTemporaries(aProc: ProcRef; offset: integer);
begin
aProc^.tempsSize := offset;
end;
(*
If the procedure has an aggregate return value,
you can pass a MemLocation for the return variable
so it can be used in an aggregate assignment without using
a temporary.
Otherwise, you should pass a MemLocation instance where
the memLoc field is set to NoMem. A temporary is then created
and its MemLocation passed back in optionalDest.
FIXME: this is not implemented? A temporary is always created.
Argument passing: Args are passed on the eval stack.
If a nested procedure is called, a pointer to the parent's
stack frame is passed as an invisible first arg
(needs to be stored at offset 0).
For aggregate returns, a temporary is allocated and
passed as a invisible var parameter at the last arg
position. This is used by the called function as the return
variable.
*)
procedure parseCall(aProc: ProcRef; var optionalDest: MemLocation);
var arg: SymblRef;
typeReturn: TypeSpec;
tempRetval: MemLocation;
retvalVar: SymblRef;
begin
initNoMemLocation(tempRetval);
arg := aProc^.parameters.first;
if arg = nil
then
begin
if checkToken(LParenToken) then
begin
readNextToken;
matchToken(RParenToken);
end
end
else
begin
matchToken(LParenToken);
repeat
(* FIXME: dont convert for var params - why?*)
if arg^.isVarParam then
parseVarParam(typeReturn)
else
if arg^.symType.baseType = SetType then
(* special handling of sets for set literals *)
parseSetExpression(typeReturn)
else
parseExpression(typeReturn);
(* TODO: release temporaries after each parameter *)
matchAndConvertTypes(arg^.symType, typeReturn);
if arg^.symType.hasSubrange then
emitSubrangeCheck(arg^.symType.subStart, arg^.symType.subEnd);
arg := arg^.next;
if arg <> nil then
matchToken(CommaToken);
until arg = nil;
matchToken(RParenToken);
end;
(* if the called function returns an aggregate, allocate a temporary
and pass it as an invisible arg.
this arg is passed last and becomes the return value local variable. *)
if aProc^.returnsAggregate then
begin
retvalVar := getReturnVar(aProc);
(* allocate space on program stack *)
allocTemporary(curProcedure, retvalVar^.symType, tempRetval);
optionalDest := tempRetVal;
(* string temporaries need to be initialized *)
if retvalVar^.symType.baseType = StringType then
initTemporaryString(tempRetval);
(* put the address of the temporary on the stack *)
emitLoadTempAddr(tempRetval.name, tempRetval.offset);
end;
emitProcedureCall(aProc);
{
if aProc^.returnsAggregate then
writeln('***** parseCall returnsAggregate ', aProc^.returnsAggregate);
}
end;
(* parse the right hand side of an assignment and generate code *)
procedure parseAssignmentPart(sym: SymblRef; var mem: memLocation);
var typeReturn: TypeSpec;
begin
if mem.typ.baseType = StringType
then
(* we need to pass the memLoc here because the
result is directly written to the destination
in the optimized case *)
parseStringExpression(mem)
else if mem.typ.baseType = SetType
then
begin
(* parsing a set expression leaves a word on the stack
so we don't need a memLoc and explicitly call
writeVariable here *)
parseSetExpression(typeReturn);
writeVariable(mem);
end
else
begin
parseExpression(typeReturn);
matchAndConvertTypes(mem.typ, typeReturn);
writeVariable(mem);
end;
end;
(* parse a complete assignment statement and generate code *)
procedure parseAssignment(sym: SymblRef);
var mem: memLocation;
begin
parseLvalue(mem);
matchToken(AssignmentToken);
parseAssignmentPart(sym, mem);
end;
procedure initConstListItem(var value:ConstListItem);
begin
value.next := nil;
value.name := '';
value.arrayValue := nil;
value.strValue := nil;
value.enumRef := nil;
end;
procedure getConstValue(var value:ConstListItem);
var digits:string[24];
typ:TypeSpec;
cnst:ConstRef;
newStr: ConstStrRef;
begin
if checkToken(NumberToken) or checkToken(MinusToken) then
begin
getNumber(digits, typ);
if typ.baseType = IntegerType then
value.intValue := integerFromString(digits)
else if typ.baseType = RealType then
value.realValue := realFromString(digits)
else
errorExit2('internal error getConstValue', digits);
end
else if checkToken(StringLitToken) then
begin
setBaseType(typ, StringType);
newStr := addConstStr(curToken.tokenText);
value.strValue := newStr;
readNextToken;
end
else if checkToken(CharLitToken) then
begin
setBaseType(typ, CharType);
(* char constants are stored as integer *)
value.intValue := getCharValue;
end
else if checkToken(TrueToken) or checkToken(FalseToken) then
begin
setBaseType(typ, BooleanType);
(* boolean constants are stored as integer *)
value.intValue := ord(getBooleanValue);
end
else if checkToken(IdentToken) then
begin
cnst := findConstantHiera(curToken.tokenText);
if cnst = nil then
errorExit2('Constant expected, got', curToken.tokenText);
(* copy all relevant fields *)
value.typ := cnst^.typ;
value.realValue := cnst^.realValue;
value.intValue := cnst^.intValue;
value.arrayValue := cnst^.arrayValue;
value.strValue := cnst^.strValue;
value.enumRef := cnst^.enumRef;
typ := value.typ;
readNextToken;
end
else
errorExit2('Constant value expected, got', curToken.tokenText);
value.typ := typ;
end;
procedure getStringValue(var dest:KeywordString);
var cnst:ConstRef;
begin
if checkToken(IdentToken) then
begin
cnst := findConstantHiera(curToken.tokenText);
if cnst = nil then
errorExit2('String constant expected, got', curToken.tokenText);
if cnst^.typ.baseType <> StringType then
errorExit2('String constant expected, got', curToken.tokenText);
dest := cnst^.strValue^.value;
readNextToken;
end
else
begin
if not matchTokenOrNot(CharLitToken) then
matchToken(StringLitToken);
dest := lastToken.tokenText;
end;
end;
(* encode a constant value of a simple type in our most basic type (integer)
which is used to store constant data for variable initializations *)
function encodeConstValue(var constValue:ConstListItem): integer;
begin
if constValue.typ.baseType = RealType then
(* this makes the assumption that a real fits into an integer *)
encodeConstValue := encodefloat32(constValue.realValue)
else
encodeConstValue := constValue.intValue;
end;
procedure parseArrayLitValue(constData: ArrayConstRef; var typ: TypeSpec);
var count, endCount: integer;
begin
matchToken(LParenToken);
endCount := typ.arrayLength;
for count := 1 to endCount do
begin
parseConstValue(constData, typ.elementType^);
if count < endCount then
matchToken(CommaToken);
end;
matchToken(RParenToken);
end;
procedure parseRecordLitValue(constData: ArrayConstRef; var typ: TypeSpec);
var curField:FieldRef;
begin
matchToken(LParenToken);
curField := typ.fields;
while curField <> nil do
begin
if curField^.isVariant then
errorExit2('variant records cannot be initialized','');
if isSimpleType(curField^.fieldType)
or (curField^.fieldType.baseType = StringType) then
begin
parseConstValue(constData, curField^.fieldType);
end
else
if curField^.fieldType.baseType = ArrayType then
parseArrayLitValue(constData, curField^.fieldType)
else
errorExit2('invalid record field initialization for',
curField^.name);
curField := curField^.next;
if curField <> nil then
matchToken(CommaToken);
end;
matchToken(RParenToken);
end;
procedure parseConstValue(constData: ArrayConstRef; var expectedType: TypeSpec);
var constValue:ConstListItem;
strConst: KeywordString;
begin
if expectedType.baseType = StringType then
begin
getStringValue(strConst);
addStrConstElem(constData, strConst, expectedType.stringLength);
end
else
if expectedType.baseType = ArrayType then
parseArrayLitValue(constData, expectedType)
else
if expectedType.baseType = RecordType then
parseRecordLitValue(constData, expectedType)
else
begin
initConstListItem(constValue);
getConstValue(constValue);
matchTypes(constValue.typ, expectedType);
addArrayConstElem(constData, encodeConstValue(constValue));
end;
end;
procedure parseVarInitialization(sym:SymblRef);
var baseType: SymbolType;
constValue:ConstListItem;
constData:ArrayConstRef;
first: boolean;
begin
first := true;
if curProcedure <> mainProcedure then
errorExit2('Only global variables can be initialized:', sym^.name);
baseType := sym^.symType.baseType;
if baseType = StringType then
begin
(* strings with initialization data are handled like arrays
with opaque data *)
constData := addNamedArrayConst(sym^.name, first);
parseConstValue(constData, sym^.symType);
end
else if isSimpleType(sym^.symType) then
begin
initConstListItem(constValue);
getConstValue(constValue);
sym^.initialValue := constValue.intValue;
end
else if baseType = ArrayType then
begin
constData := addNamedArrayConst(sym^.name, first);
parseArrayLitValue(constData, sym^.symType);
end
else if baseType = RecordType then
begin
constData := addNamedArrayConst(sym^.name, first);
parseRecordLitValue(constData, sym^.symType);
end
else
errorExit2('internal error in parseVarInitialization: invalid baseType for',
sym^.name);
sym^.hasInitialValue := true;
end;
procedure parseSingleVarStatement;
var name: IdentString;
sym: SymblRef;
names: StringList;
typSpec: TypeSpec;
hasNext: boolean;
isExternal: boolean;
begin
(* first, gather list of variable names *)
initStringList(names);
repeat
matchToken(IdentToken);
addToStringList(names, lastToken.tokenText);
until not matchTokenOrNot(CommaToken);
matchToken(ColonToken);
parseTypeSpec(typSpec, false);
(* handle initialization *)
if checkToken(EqToken) then
begin
readNextToken;
hasNext := nextStringListItem(names, name);
if not hasNext then
errorExit2('internal error when parsing var statement','');
sym := addSymbol(curProcedure^.vars, name, typSpec, false, false);
parseVarInitialization(sym);
(* check if there is more than one variable *)
hasNext := nextStringListItem(names, name);
if hasNext then
errorExit2('Cannot initialize multiple variables:',name);
end
else
begin
(* if external keyword follows after the type spec, it is
an external variable *)
isExternal := matchTokenOrNot(ExternalToken);
(* create variables with the declared type from the list of names *)
while(nextStringListItem(names, name)) do
begin
sym := addSymbol(curProcedure^.vars, name, typSpec, false, false);
if isExternal then
if curProcedure = mainProcedure
then
sym^.isExternal := true
else
errorExit2('Local variable cannot be declared external', name);
end;
end;
disposeStringList(names);
end;
procedure parseVarBlock;
begin
matchToken(VarToken);
while checkToken(IdentToken) do
begin
parseSingleVarStatement;
matchToken(SemicolonToken);
end;
end;
procedure parseForInStatement(var sym:SymblRef;forNo:integer);
var containerType:TypeSpec;
mem: MemLocation;
elementMem: MemLocation;
begin
matchToken(InToken);
(* parseMemLocation(true, container); containerType := container^.typ; *)
parseExpression(containerType);
(* TODO: would be nice if for-in worked with enum types *)
if containerType.baseType = ArrayType then
begin
matchTypes(containerType.elementType^, sym^.symType);
matchToken(DoToken);
emitForInHeader(containerType.arrayLength);
emitForInStart(forNo);
accessVariable(sym, mem);
elementMem.memLoc := Indirect;
elementMem.typ := containerType.elementType^;
elementMem.origSym := nil;
elementMem.name := '<in-loop-element>';
elementMem.offset := 0;
elementMem.scopeDistance := 0;
elementMem.initialized := false;
emitForInMid(sym, mem);
readVariable(elementMem);
writeVariable(mem);
parseCompoundStatement;
emitForInIter(forNo, containerType);
emitForInEnd(forNo);
end
else
if containerType.baseType = StringType then
begin
matchBaseType(sym^.symType, CharType);
matchToken(DoToken);
emitForInStrHeader;
emitForInStart(forNo);
accessScalar(sym, mem);
emitForInStrMid(sym, mem);
writeVariable(mem);
parseCompoundStatement;
emitForInStrIter(forNo);
emitForInEnd(forNo);
end
else
errorExit2('Array or string expected', lastToken.tokenText);
end;
procedure parseForStatement;
var sym: SymblRef;
name: IdentString;
typeReturn: TypeSpec;
mem: MemLocation;
tmpCount: integer;
down: boolean;
prevBreakLabel:IdentString;
begin
readNextToken;
tmpCount := forCount;
forCount := forCount + 1;
prevBreakLabel := curBreakLabel;
curBreakLabel := getForEndLabel(tmpCount);
name := curToken.tokenText;
sym := findHieraSymbol(name);
if sym = nil then
errorExit2('Undeclared variable', name);
readNextToken;
if checkToken(InToken) then
parseForInStatement(sym, tmpCount)
else
begin
matchToken(AssignmentToken);
if not (sym^.symType.baseType in [ IntegerType, CharType, BooleanType,
EnumType ]) then
errorExit2('Invalid type for loop variable', sym^.name);
accessScalar(sym, mem); (* FOR initializer *)
parseAssignmentPart(sym, mem);
if not (curToken.tokenKind in [ ToToken, DowntoToken ]) then
errorExit2('Expected TO or DOWNTO, got', curToken.tokenText);
down := checkToken(DowntoToken);
readNextToken;
parseExpression(typeReturn); (* FOR end condition is kept on stack *)
matchTypes(typeReturn, sym^.symType);
emitForStart(tmpCount);
(* read and check loop variable *)
accessScalar(sym, mem);
readVariable(mem);
if down then
emitForDowntoBranch(tmpCount)
else
emitForBranch(tmpCount);
(* We need to check for a subrange at the start of the loop, not
at the end. After the last iteration the control
variable will be out of range, so we cannot do the subrange
check there. *)
if (sym^.symType.baseType = IntegerType) and
(sym^.symType.hasSubrange) then
begin
(* need to read the variable again *)
accessScalar(sym, mem);
readVariable(mem);
emitSubrangeCheckRaw(sym^.symType.subStart, sym^.symType.subEnd);
end;
matchToken(DoToken);
parseCompoundStatement; (* FOR body *)
accessScalar(sym, mem); (* increment counter variable *)
accessScalar(sym, mem); (* load mem loc twice for write and read *)
readVariable(mem);
if down then
emitDec(1)
else
emitInc(1);
writeVariable2(mem, false);
emitForEnd(tmpCount); (* branch to beginning of loop *)
end;
curBreakLabel := prevBreakLabel;
end;
procedure parseIfStatement;
var tmpCount: integer;
typeReturn: TypeSpec;
begin
readNextToken;
tmpCount := ifCount; (* local copy of the if counter to allow for nested ifs *)
ifCount := ifCount + 1;
parseExpression(typeReturn);
matchBaseType(typeReturn, BooleanType);
matchToken(ThenToken);
emitIfBranch(tmpCount);
parseCompoundStatement;
if matchTokenOrNot(ElseToken) then
begin
emitElseBranch(tmpCount);
emitElseLabel(tmpCount);
parseCompoundStatement;
end
else
emitElseLabel(tmpCount);
emitIfLabel(tmpCount);
end;
procedure parseWhileStatement;
var tmpCount: integer;
typeReturn: TypeSpec;
prevBreakLabel: IdentString;
begin
readNextToken;
tmpCount := whileCount;
whileCount := whileCount + 1;
prevBreakLabel := curBreakLabel;
curBreakLabel := getWhileEndLabel(tmpCount);
emitWhileStart(tmpCount);
parseExpression(typeReturn);
matchBaseType(typeReturn, BooleanType);
emitWhileBranch(tmpCount);
matchToken(DoToken);
parseCompoundStatement;
emitWhileEnd(tmpCount);
curBreakLabel := prevBreakLabel;
end;
procedure parseRepeatStatement;
var tmpCount: integer;
typeReturn: TypeSpec;
prevBreakLabel: IdentString;
begin
readNextToken;
tmpCount := repeatCount;
repeatCount := repeatCount + 1;
prevBreakLabel := curBreakLabel;
curBreakLabel := getRepeatEndLabel(tmpCount);
emitRepeatStart(tmpCount);
repeat
parseStatement;
until matchEndOf(UntilToken);
parseExpression(typeReturn);
matchBaseType(typeReturn, BooleanType);
emitRepeatBranch(tmpCount);
emitRepeatEnd(tmpCount);
curBreakLabel := prevBreakLabel;
end;
procedure parseCaseStatement;
var tmpCount, caseLabelCount, caseSubValCount: integer;
selectorType, caseType: TypeSpec;
begin
readNextToken;
tmpCount := caseCount;
caseCount := caseCount + 1;
caseLabelCount := 0;
parseExpression(selectorType); (* parse case selector *)
matchToken(OfToken);
emitCaseStart(tmpCount);
repeat
caseSubValCount := 0;
repeat
(* emit the label which is used by the previous case clause if
it does not match *)
emitCaseLabelStart(tmpCount, caseLabelCount, caseSubValCount);
parseConstant(caseType);
if matchTokenOrNot(DotToken) then
begin
if matchTokenOrNot(DotToken) then
begin
(* handle ranges which use two comparisons *)
emitCaseRangeLoBranch(tmpCount, caseLabelCount, caseSubValCount, true);
parseConstant(caseType);
emitCaseRangeHiBranch(tmpCount, caseLabelCount, caseSubValCount,
not checkToken(CommaToken));
end
end
else
emitCaseLabelBranch(tmpCount, caseLabelCount, caseSubValCount,
not checkToken(CommaToken));
matchTypes(selectorType, caseType);
caseSubValCount := caseSubValCount + 1;
until not matchTokenOrNot(CommaToken);
matchToken(ColonToken);
(* this label is used for clauses with multiple values to jump to on a match *)
emitCaseLabelMatch(tmpCount, caseLabelCount);
(* parse the (compound) statement which is executed on a match *)
parseCompoundStatement;
emitCaseLabelEnd(tmpCount);
(* last normal clause may omit the semicolon, otherwise it is required *)
if not (curToken.tokenKind in [ EndToken, ElseToken]) then
matchToken(SemicolonToken);
(* emit label to catch the last conditional branch of a multi-value clause *)
emitCaseLabelLabel(tmpCount, caseLabelCount, caseSubValCount);
caseLabelCount := caseLabelCount + 1;
(* check for a final ELSE clause *)
if checkToken(ElseToken) then
begin
readNextToken;
(* just generate the code, which will be put after
the last no-match-label and therefore will be
executed if the last clause does not match *)
parseCompoundStatement;
if checkToken(SemicolonToken) then readNextToken;
if not checkToken(EndToken) then
errorExit2('ELSE must be last case clause', curToken.tokenText);
end;
until matchTokenOrNot(EndToken);
emitCaseEnd(tmpCount, caseLabelCount);
end;
procedure parseBreakStatement;
begin
if length(curBreakLabel) = 0 then
errorExit2('BREAK not within loop', '');
emitBreak(curBreakLabel);
readNextToken;
end;
procedure disposeWithStmntTmp;
begin
with withStmntStack[withStmntCount] do
begin
if tmpSymbol <> nil then
begin
dispose(tmpSymbol);
tmpSymbol := nil;
end;
end;
end;
procedure parseWithStmntPart;
var withLoc, tLoc: MemLocation;
tempType: TypeSpec;
begin
parseMemLocation(true,withLoc); (* parse the memory location of the record to be opened *)
(* allocate a temporary for the address of the record *)
setBaseType(tempType, PointerType);
allocTemporary(curProcedure, tempType, tLoc);
(* add it to the with-stack *)
withStmntCount := withStmntCount + 1;
if withStmntCount > WithStackDepth then
errorExit2('Too many nested WITH statements','');
withStmntStack[withStmntCount].tmpSymbol := nil;
with withStmntStack[withStmntCount] do
begin
recordLoc := withLoc; (* the memloc of the opened record *)
tempLoc := tLoc; (* the memloc of the temporary which stores the
address of the opened record *)
end;
(* store the record address to the temporary *)
emitLoadTempAddr(withLoc.name, tLoc.offset);
emitSwap;
emitStoreIndirect;
end;
procedure parseWithStatement;
var tempMark: integer;
oldWithStmntCount: integer;
begin
tempMark := markTemporaries(curProcedure);
oldWithStmntCount := withStmntCount;
readNextToken;
(* the with clause can contain multiple comma separated records *)
repeat
parseWithStmntPart;
until not matchTokenOrNot(CommaToken);
matchToken(DoToken);
parseCompoundStatement;
(* remove entries from with-stack *)
while withStmntCount > oldWithStmntCount do
begin
disposeWithStmntTmp;
withStmntCount := withStmntCount -1;
end;
releaseTemporaries(curProcedure,tempMark);
end;
procedure parseLabel(var aLabl:LablRef);
begin
emitLabel(aLabl);
readNextToken;
matchToken(ColonToken);
end;
procedure parseStatement;
var sym: SymblRef;
cnst: ConstRef;
aLabl: LablRef;
name: IdentString;
tempMark: integer;
begin
(* temporaries used during the statement
can be released afterwards, so mark
the temp space now and release later *)
tempMark := markTemporaries(curProcedure);
(* try to parse a label before every statement.
if we succeed, continue to parse (because there is no
semicolon after a label, so it is not a complete statement)
*)
if checkToken(IdentToken) then
begin
name := curToken.tokenText;
aLabl := findLabel(curProcedure, name);
if aLabl <> nil then
parseLabel(aLabl);
end;
if checkToken(GotoToken) then
begin
readNextToken;
matchToken(IdentToken);
aLabl := findLabel(curProcedure, lastToken.tokenText);
if aLabl = nil then errorExit2('GOTO to undefined label', lastToken.tokenText);
emitLabelJump(aLabl);
end
else
if checkToken(IfToken) then
parseIfStatement
else
if checkToken(WhileToken) then
parseWhileStatement
else
if checkToken(RepeatToken) then
parseRepeatStatement
else
if checkToken(ForToken) then
parseForStatement
else
if checkToken(BreakToken) then
parseBreakStatement
else
if checkToken(CaseToken) then
parseCaseStatement
else
if checkToken(WithToken) then
parseWithStatement
else
if checkToken(IdentToken) then
begin
(* this can be either a procedure call or an assignment *)
name := curToken.tokenText;
sym := findHieraSymbol(name);
if sym <> nil
then
parseAssignment(sym)
else
begin
(* check if it is a constant *)
cnst := findConstantHiera(name);
if cnst <> nil then
errorExit2('variable identifier expected, got constant', name);
(* now it can only be a procedure *)
parseProcedureCall(name);
end;
end
else
begin
if (curToken.tokenKind = ElseToken) then
begin
(* two consecutive else tokens mean an empty else clause followed by another*)
if not (lastToken.tokenKind in [ElseToken, ThenToken]) then
errorExit2('Unexpected ELSE, check for erroneous ; after previous statement',
'')
end
else if not (curToken.tokenKind in [SemicolonToken, EndToken, UntilToken ]) then
begin
(* For an empty statement, the semicolon or end token is not consumed.
If not an empty statement, it is an error. *)
errorExit2('Unexpected token', quoteToken(keywords[curToken.tokenKind]));
end;
end;
releaseTemporaries(curProcedure, tempMark);
end;
procedure parseRecordField(var recordTyp: TypeSpec; var offset:integer;
isVariant:boolean; tagField:FieldRef; var tagValues:IntList);
var fieldType: TypeSpec;
fieldName: IdentString;
curField: ^FieldListItem;
newField: ^FieldListItem;
names: StringList;
begin
initStringList(names);
repeat
addToStringList(names, curToken.tokenText);
matchToken(IdentToken);
until not matchTokenOrNot(CommaToken);
matchToken(ColonToken);
parseTypeSpec(fieldType, false);
curField := recordTyp.fields;
(* go to last field in list *)
if curField <> nil then
while curField^.next <> nil do curField := curField^.next;
while(nextStringListItem(names, fieldName)) do
begin
new(newField);
newField^.name := fieldName;
newField^.offset := offset;
newField^.fieldType := fieldType;
newField^.isVariant := isVariant;
newField^.tagField := tagField;
newField^.tagValues := tagValues;
newField^.next := nil;
if curField = nil then
recordTyp.fields := newField
else
curField^.next := newField;
curField := newField;
offset := offset + fieldType.size;
end;
disposeStringList(names);
end;
procedure parseRecordFields(var recordTyp: TypeSpec; var offset:integer;
isVariant:boolean; tagField:FieldRef; var tagValues:IntList);
begin
while checkToken(IdentToken) do
begin
parseRecordField(recordTyp, offset, isVariant, tagField, tagValues);
if checkToken(SemicolonToken) then
readNextToken;
end;
end;
procedure parseVariantRecord(var recordTyp: TypeSpec; var offset:integer);
var tagField:FieldRef;
tagValue:integer;
tagValueType:TypeSpec;
variantOffset:integer;
maxSize:integer;
caseValues: IntList;
begin
matchToken(CaseToken);
parseRecordField(recordTyp, offset, false, nil, emptyIntList);
(* get the tag field which was just added at then end of the list *)
tagField := recordTyp.fields;
while tagField^.next <> nil do tagField := tagField^.next;
matchToken(OfToken);
maxSize := 0;
repeat
variantOffset := offset;
initIntList(caseValues);
(* there can be a comma-separated list of case values *)
repeat
getRangePart(tagValue, tagValueType);
addToIntList(caseValues, tagValue);
until not matchTokenOrNot(CommaToken);
matchToken(ColonToken);
matchToken(LParenToken);
parseRecordFields(recordTyp, variantOffset, true, tagField, caseValues);
if variantOffset > maxSize then
maxSize := variantOffset;
matchToken(RParenToken);
matchToken(SemicolonToken);
{
while nextIntListItem(caseValues, tagValue) do
writeln('******* parseVariantRecord case values:', tagValue);
}
rewindIntList(caseValues);
(* the caseValues list is not disposed, it stays attached to
the field list of the record type *)
until checkToken(EndToken);
offset := maxSize;
end;
procedure parseRecordDecl(var newTypeName:IdentString);
var offset: integer;
recordTyp: TypeSpec;
begin
offset := 0;
setBaseType(recordTyp, RecordType);
recordTyp.fields := nil;
matchToken(RecordToken);
repeat
if checkToken(CaseToken) then
parseVariantRecord(recordTyp, offset)
else
parseRecordField(recordTyp, offset, false, nil, emptyIntList);
if checkToken(SemicolonToken) then
readNextToken
else if not checkToken(EndToken) then
errorExit2('Expected ; or END, got', curToken.tokenText);
until checkToken(EndToken);
readNextToken;
recordTyp.size := offset;
addType(recordTyp, newTypeName);
end;
procedure parseEnumDecl(var name:IdentString;var typeReturn: TypeSpec);
var ident: IdentString;
value: integer;
cnst: ConstRef;
enumTyp: TypeSpec;
enumRef: TypeRef;
identList: StringList;
begin
value := 0;
initStringList(identList);
setBaseType(enumTyp, EnumType);
addType(enumTyp, name);
enumRef := findTypeRef(curProcedure, name);
matchToken(LParenToken);
repeat
ident := curToken.tokenText;
matchToken(IdentToken);
addToStringList(identList, ident);
value := value + 1;
until not matchTokenOrNot(CommaToken);
matchToken(RParenToken);
enumCount := enumCount + 1;
value := 0;
while nextStringListItem(identList, ident) do
begin
cnst := addConstant(ident);
cnst^.typ.baseType := EnumType;
cnst^.typ.enumId := enumCount;
cnst^.intValue := value;
cnst^.enumRef := enumRef;
value := value + 1;
end;
enumRef^.typePtr^.enumLength := value;
enumRef^.typePtr^.enumList := identList;
enumRef^.typePtr^.enumId := enumCount;
typeReturn := enumRef^.typePtr^;
end;
procedure parseTypeStatement;
var newTypeName: IdentString;
newType: TypeSpec;
begin
(* newType.baseType := NoType; *)
newTypeName := curToken.tokenText;
matchToken(IdentToken);
matchToken(EqToken);
optionalToken(PackedToken);
if checkToken(RecordToken) then (* TODO: move to parseTypeSpec*)
parseRecordDecl(newTypeName)
else
if checkToken(LParenToken) then (* TODO: move to parseTypeSpec*)
parseEnumDecl(newTypeName, newType)
else
begin
parseTypeSpec(newType, false);
addType(newType, newTypeName);
end;
end;
procedure parseTypeBlock;
begin
matchToken(TypeToken);
while checkToken(IdentToken) do
begin
parseTypeStatement;
matchToken(SemicolonToken);
end;
end;
procedure parseConstBlock;
var name:IdentString;
typeReturn:TypeSpec;
newConst: ConstRef;
begin
matchToken(ConstToken);
repeat
matchToken(IdentToken);
name := lastToken.tokenText;
matchToken(EqToken);
newConst := addConstant(name);
if checkToken(LBracketToken) then
begin
setBaseType(typeReturn, NoType);
newConst^.arrayValue := getArrayConst(typeReturn);
newConst^.typ := typeReturn;
end
else
getConstValue(newConst^);
matchToken(SemicolonToken);
until not checkToken(IdentToken);
end;
procedure processUnresolvedTypes(aProc:ProcRef);
var typeListItem, t:TypeRef;
typePtr: ^TypeSpec;
begin
typeListItem := aProc^.unresolved;
while typeListItem <> nil do
begin
typePtr := typeListItem^.typePtr;
if typePtr^.baseType = UnresolvedType then
begin
t := findTypeRef(aProc, typePtr^.typeName^);
if t = nil then
begin
errorExit2('unresolved type', typePtr^.typeName^);
end
else
begin
(* overwrite the unresolved type spec with the one we just found *)
typeListItem^.typePtr^ := t^.typePtr^;
end;
end;
typeListItem := typeListItem^.next;
end;
end;
procedure parseProgramBlock;
begin
(* parse var, type and const statements *)
while curToken.tokenKind in [ VarToken, TypeToken, ConstToken, LabelToken ] do
begin
if checkToken(VarToken) then parseVarBlock
else if checkToken(TypeToken) then parseTypeBlock
else if checkToken(ConstToken) then parseConstBlock
else if checkToken(LabelToken) then parseLabelBlock
end;
processUnresolvedTypes(curProcedure);
(* parse functions and procedures *)
while checkToken(ProcedureToken) or checkToken(FunctionToken) do
begin
parseProcOrFunc;
matchToken(SemicolonToken);
end;
end;
procedure parseLib(n:IdentString);
var libFile: InputFileType;
prevFile: InputFileType;
prevLineno: integer;
prevFilename: string[255];
newFilename: string[255];
begin
prevFile := infile;
prevLineno := lineno;
prevFilename := filename;
newFilename := n + UnitSuffix1;
openFileWithDefault(libFile, newFilename);
filename := newFilename;
infile := libFile;
lineno := 1;
buffered := false;
readNextToken;
parseProgramBlock;
if not checkToken(EOFToken) then
errorExit2('Expected <end-of-file>','');
close(libFile);
printLineStats;
infile := prevFile;
lineno := prevLineno;
filename := prevFilename;
buffered := false;
end;
procedure parseStdLib;
var name:IdentString;
begin
parseLib(StdlibName);
(* the file type is declared in stdlib, so
we can look it up now *)
name := 'FILE';
fileTyp := findType(mainProcedure, name);
end;
procedure setGlobalSuffix;
begin
globalSuffix := '_' + mainProcedure^.name;
end;
procedure parseUnit;
begin
matchToken(UnitToken);
matchToken(IdentToken);
mainProcedure^.name := lastToken.tokenText;
setGlobalSuffix;
matchToken(SemicolonToken);
matchToken(ImplementationToken);
parseProgramBlock;
matchToken(EndToken);
matchToken(DotToken);
matchToken(EOFToken);
end;
function lower(c:char):char;
begin
if (ord(c) >= ord('A')) and
(ord(c) <= ord('Z')) then
lower := chr(ord(c) + 32) (* assumes ASCII*)
else
lower := c;
end;
procedure parseUsesStatement;
var unitName:IdentString;
c:char;
begin
repeat
matchToken(IdentToken);
unitName := '';
for c in lastToken.tokenText do
unitName := unitName + lower(c);
addToStringList(usedUnits, unitName);
until not matchTokenOrNot(CommaToken);
if not checkToken(SemicolonToken) then
matchToken(SemicolonToken);
while nextStringListItem(usedUnits, unitName) do
parseLib(unitName);
readNextToken; (* read token from main input file *)
end;
procedure parseProgram;
begin
(* require Program statement *)
matchToken(ProgramToken);
matchToken(IdentToken);
mainProcedure^.name := lastToken.tokenText;
(* we don't do anything with the program name *)
(* and we parse but otherwise ignore file declarations *)
if matchTokenOrNot(LParenToken) then
begin
repeat
matchToken(IdentToken);
until not matchTokenOrNot(CommaToken);
matchToken(RParenToken);
end;
matchToken(SemicolonToken);
if matchTokenOrNot(UsesToken) then
parseUsesStatement;
(* parse var, type and const statements and procedures/functions *)
parseProgramBlock;
(* parse main program *)
emitMainStart();
parseCompoundStatement;
matchToken(DotToken);
(* nothing should be after the main program *)
matchToken(EOFToken);
end;
procedure parseProgramOrUnit(useStdlib:boolean);
begin
if useStdlib then
parseStdlib;
readNextToken;
if checkToken(ProgramToken) then
begin
emitPrologue;
parseProgram;
emitEpilogue;
end
else
if checkToken(UnitToken) then
begin
parseUnit;
emitUnitEpilogue;
end
else
errorExit2('PROGRAM or UNIT expected, got', curToken.tokenText);
end;
function changeFileSuffix(filename: string): string;
var suffixPos:integer;
begin
suffixPos := pos(filenameSuffix, filename);
if suffixPos > 0 then
setlength(filename, suffixPos-1);
filename := filename + outfileSuffix;
changeFileSuffix := filename;
end;
procedure initMainProcedure;
begin
mainProcedure := addProcedure('_MAIN', false, nil);
mainProcedure^.vars.scope := GlobalSymbol;
mainProcedure^.procedures := nil;
mainProcedure^.next := nil;
mainProcedure^.types := nil;
mainProcedure^.unresolved := nil;
mainProcedure^.constants := nil;
mainProcedure^.level := -1;
curProcedure := mainProcedure;
end;
begin
initPlatform;
buffered := false;
firstConstStr := nil;
firstArrayConst := nil;
constStrNo := 0;
arrayConstNo := 0;
ifCount := 0;
whileCount := 0;
forCount := 0;
repeatCount := 0;
caseCount := 0;
nestedProcsCount := 0;
enumCount := 0;
anonTypeCount := 0;
curBreakLabel := '';
lineno := 1;
includeLevel := 0;
defaultHeapSize := 262144;
defaultStackSize := 16384;
withStmntCount := 0;
insCount := 0;
initStringList(usedUnits);
initIntList(emptyIntList);
initMainProcedure;
globalSuffix := '';
useStdlib := true;
useStandalone := false;
editOnError := false;
runProg := false;
runAsm := true;
paramPos := 1;
filename := '';
outfilename := '';
while paramPos <= paramCount do
begin
if paramStr(paramPos) = '-n' then (* do not include stdlib.inc *)
useStdlib := false
else
if paramStr(paramPos) = '-s' then (* use standalone corelib *)
useStandalone := true
else
if paramStr(paramPos) = '-e' then (* call editor on error *)
editOnError := true
else
if paramStr(paramPos) = '-R' then (* run compiled/assembled program *)
runProg := true
else
if paramStr(paramPos) = '-S' then (* do not run assembler *)
runAsm := false
else
if paramStr(paramPos) = '-H' then (* set heap size *)
begin
paramPos := paramPos + 1;
DefaultHeapSize := integerFromString(ParamStr(paramPos)) * 1024;
end
else
begin
if length(filename) = 0 then
filename := paramStr(paramPos)
else
outfilename := paramStr(paramPos);
end;
paramPos := paramPos + 1;
end;
if length(outfilename) = 0 then
outfilename := changeFileSuffix(filename);
if length(filename) = 0 then
begin
writeln('No file name given.');
halt;
end;
writeln('Compiling ', filename, ' to ', outfilename);
openFileWithDefault(infile, filename);
overwriteFile(outfile, outfilename);
parseProgramOrUnit(useStdlib);
printLineStats;
cleanup;
if runAsm then
ExecAssembler(outfilename, runProg, editOnError);
end.