(* 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;

		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.
	
	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.
