/*

  SjASMPlus Z80 Cross Compiler

  This is modified sources of SjASM by Aprisobal - aprisobal@tut.by

  Copyright (c) 2006 Sjoerd Mastijn

  This software is provided 'as-is', without any express or implied warranty.
  In no event will the authors be held liable for any damages arising from the
  use of this software.

  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it freely,
  subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not claim
	 that you wrote the original software. If you use this software in a product,
	 an acknowledgment in the product documentation would be appreciated but is
	 not required.

  2. Altered source versions must be plainly marked as such, and must not be
	 misrepresented as being the original software.

  3. This notice may not be removed or altered from any source distribution.

*/

// tables.cpp

#include "sjdefs.h"

TextFilePos::TextFilePos(const char* fileNamePtr, uint32_t line) : filename(fileNamePtr), line(line), colBegin(0), colEnd(0) {
}

void TextFilePos::newFile(const char* fileNamePtr) {
	filename = fileNamePtr;
	line = colBegin = colEnd = 0;
}

// advanceColumns are valid only when true == endsWithColon (else advanceColumns == 0)
// default arguments are basically "next line"
void TextFilePos::nextSegment(bool endsWithColon, size_t advanceColumns) {
	if (endsWithColon && 0 == colEnd) colEnd = 1;	// first segment of "colonized" line (do +1,+1)
	colBegin = colEnd;
	if (colBegin <= 1) ++line;		// first segment of any line, increment also line number
	if (endsWithColon)	colEnd += advanceColumns;
	else				colEnd = 0;
}

std::string vorlab;

void InitVorlab() {
	if (ModuleName[0]) {
		vorlab = ModuleName; vorlab += "._";
	} else {
		vorlab = "_";
	}
}

char* PreviousIsLabel = nullptr;

// since v1.18.0:
// The ignore invalid char after feature disconnected from "setNameSpace" to "ignoreCharAfter"
// (it's for evaluating labels straight from the expressions, without copying them out first)
// The prefix "!" is now recognized as "do not set main label" for following local labels
// since v1.18.3:
// Inside macro prefix "@." will create non-macro local label instead of macro's instance
char* ValidateLabel(bool & local, const char* naam, bool setNameSpace, bool ignoreCharAfter) {
	local = false;
	if (nullptr == naam || 0 == *naam || White(naam[0])) {
		Error("Invalid (blank) labelname");
		return nullptr;
	}
	if ('!' == *naam) {
		setNameSpace = false;
		++naam;
	}
	// check if local label defined inside macro wants to become non-macro local label
	const bool escMacro = setNameSpace && macrolabp && ('@' == naam[0]) && ('.' == naam[1]);
	if (escMacro) ++naam;			// such extra "@" is consumed right here and only '.' is left
	// regular single prefix case in other use cases
	const bool global = ('@' == *naam);
	local = ('.' == *naam);
	if (!isLabelStart(naam)) {		// isLabelStart assures that only single modifier exist
		if (global || local) ++naam;// single modifier is parsed (even when invalid name)
		Error("Invalid character at start of labelname", naam, SUPPRESS);
		return nullptr;
	}
	if (global || local) ++naam;	// single modifier is parsed
	const bool inMacro = !escMacro && local && macrolabp;
	const bool inModule = !inMacro && !global && !local && ModuleName[0];
	// check all chars of label
	const char* np = naam;
	while (islabchar(*np)) ++np;
	if ('[' == *np) return nullptr;	// this is DEFARRAY name, do not process it as label (silent exit)
	if (*np && !ignoreCharAfter) {
		// if this is supposed to be new label, there shoulnd't be anything else after it
		Error("Invalid character after labelname", naam, SUPPRESS);
		return nullptr;
	}
	// calculate expected length of fully qualified label name
	int labelLen = (np - naam), truncateAt = LABMAX;
	if (LABMAX < labelLen) Error("Label too long", naam, IF_FIRST);	// non-fatal error, will truncate it
	if (inMacro) labelLen += 1 + strlen(macrolabp);
	else if (local) labelLen += 1 + vorlab.size();
	if (inModule) labelLen += 1 + strlen(ModuleName);
	// build fully qualified label name (in newly allocated memory buffer, with precise length)
	char* const label = new char[1+labelLen];
	if (nullptr == label) ErrorOOM();
	label[0] = 0;
	if (inModule) {		// false when `local` so doesn't double with vorlab content
		STRCAT(label, labelLen, ModuleName);		STRCAT(label, 2, ".");
	}
	if (inMacro) {
		STRCAT(label, labelLen, macrolabp);			STRCAT(label, 2, ">");
	} else if (local) {
		STRCAT(label, labelLen, vorlab.c_str());	STRCAT(label, 2, ".");
	}
	char* lp = label + strlen(label);
	while (truncateAt-- && islabchar(*naam)) *lp++ = *naam++;	// add the new label (truncated if needed)
	*lp = 0;
	if (labelLen < lp - label) Error("internal error", nullptr, FATAL);		// should never happen :)
	if (setNameSpace && !local) vorlab = label;
	return label;
}

char* ValidateLabel(const char* naam, bool setNameSpace, bool ignoreCharAfter) {
	bool discardIsLocal;
	return ValidateLabel(discardIsLocal, naam, setNameSpace, ignoreCharAfter);
}

static char sldLabelExport[2*LINEMAX];

char* ExportLabelToSld(const char* naam, const SLabelTableEntry* label) {
	// does re-parse the original source line again similarly to ValidateLabel
	// but prepares SLD 'L'-type line, with module/main/local comma separated + usage traits info
	assert(nullptr != label);
	// check if this is setting namespace
	bool setNameSpace = ('!' != *naam);
	if (!setNameSpace) ++naam;
	// check if local label defined inside macro wants to become non-macro local label
	const bool escMacro = setNameSpace && macrolabp && ('@' == naam[0]) && ('.' == naam[1]);
	if (escMacro) ++naam;			// such extra "@" is consumed right here and only '.' is left
	assert(isLabelStart(naam));		// this should be called only when ValidateLabel did succeed
	const bool global = '@' == *naam;
	const bool local = '.' == *naam;
	if (global || local) ++naam;	// single modifier is parsed
	const auto modNameSz = strlen(ModuleName);
	const bool inMacro = !escMacro && local && macrolabp;
	// struct def full name "@" global marker is not part of "naam" here, deduct it from matching against ModuleName (if any)
	const bool globalStructDef = (label->traits & LABEL_IS_STRUCT_D) && (strncmp(ModuleName, naam, modNameSz) || ('.' != naam[modNameSz]));
	const bool inModule = !inMacro && !global && modNameSz && !globalStructDef;
	const bool isStructLabel = (label->traits & (LABEL_IS_STRUCT_D|LABEL_IS_STRUCT_E));
	// build fully qualified SLD info
	sldLabelExport[0] = 0;
	// module part
	if (inModule) STRCAT(sldLabelExport, LINEMAX, ModuleName);
	STRCAT(sldLabelExport, 2, ",");
	// main label part (the `vorlab` is already the current label, if it was main label)
	// except for structure labels: the inner ones don't "set namespace" == vorlab, use "naam" then
	// (but only if the main label of structure itself is not local, if it's local, use vorlab)
	const char* mainLabel = isStructLabel && !local ? naam : inMacro ? macrolabp : vorlab.data();
	if (!setNameSpace) mainLabel = naam;
	if ((vorlab.data() == mainLabel || isStructLabel) && modNameSz) {	// vorlab may contain module part, skip it then
		if (0 == strncmp(ModuleName, mainLabel, modNameSz)) mainLabel += modNameSz + 1;
	}
	STRCAT(sldLabelExport, LABMAX, mainLabel);
	STRCAT(sldLabelExport, 2, ",");
	// local part
	if (local) STRCAT(sldLabelExport, LABMAX, naam);
	assert(local == !!(label->traits&LABEL_IS_LOCAL));
	// usage traits
	if (label->traits&LABEL_IS_LOCAL) STRCAT(sldLabelExport, 20, ",+local");
	if (label->traits&LABEL_IS_EQU) STRCAT(sldLabelExport, 20, ",+equ");
	if (inMacro) STRCAT(sldLabelExport, 20, ",+macro");
	if (label->traits&LABEL_IS_SMC) STRCAT(sldLabelExport, 20, ",+smc");
	if (Relocation::REGULAR == label->isRelocatable) STRCAT(sldLabelExport, 20, ",+reloc");
	if (Relocation::HIGH == label->isRelocatable) STRCAT(sldLabelExport, 20, ",+reloc_high");
	if (label->used) STRCAT(sldLabelExport, 20, ",+used");
	if (label->traits&LABEL_IS_STRUCT_D) STRCAT(sldLabelExport, 20, ",+struct_def");
	if (label->traits&LABEL_IS_STRUCT_E) STRCAT(sldLabelExport, 20, ",+struct_data");
	if (label->traits&LABEL_HAS_SIZE) STRCAT(sldLabelExport, 20, ",+sizeof");
	return sldLabelExport;
}

char* ExportModuleToSld(bool endModule) {
	assert(ModuleName[0]);
	STRNCPY(sldLabelExport, 2*LINEMAX, ModuleName, LINEMAX);
	STRCAT(sldLabelExport, LINEMAX-1, endModule ? ",,,+endmod" : ",,,+module");
	return sldLabelExport;
}

static bool getLabel_invalidName = false;

// does parse + consume input source at "p" (and stores result into "fullName")
//  ^^^ may report invalid label name error
// does search (and only search) LabelTable for various variants of label based on "fullName"
// No refresh of "used", no inserting into table when not found, no other errrors reported
// Leaves canonical name in "temp" global variable, if this is inside macro
// returns table entry, preferring the one with "page defined", if multiple entries are found
static SLabelTableEntry* SearchLabel(char*& p, bool setUsed, /*out*/ std::unique_ptr<char[]>& fullName) {
	getLabel_invalidName = true;
	fullName.reset(ValidateLabel(p, false, true));
	if (!fullName) return nullptr;
	getLabel_invalidName = false;
	const bool global = '@' == *p;
	const bool local = '.' == *p;
	while (islabchar(*p)) ++p;		// advance pointer beyond the parsed label
	// find the label entry in the label table (for local macro labels it has to try all sub-parts!)
	// then regular full label has to be tried
	// and if it's regular non-local in module, then variant w/o current module has to be tried
	bool inMacro = local && macrolabp;		// not just inside macro, but should be prefixed
	const int modNameLen = strlen(ModuleName);
	const char *findName = fullName.get();
	SLabelTableEntry* undefinedLabelEntry = nullptr;
	SLabelTableEntry* labelEntry = nullptr;
	temp[0] = 0;
	do {
		labelEntry = LabelTable.Find(findName);
		if (labelEntry) {
			if (setUsed && pass < LASTPASS) labelEntry->used = true;
			if (LABEL_PAGE_UNDEFINED != labelEntry->page) return labelEntry;	// found
			// if found, but "undefined" one, remember it as fall-back result
			undefinedLabelEntry = labelEntry;
			labelEntry = nullptr;
		}
		// not found (the defined one, try more variants)
		if (inMacro) {				// try outer macro (if there is one)
			while ('>' != *findName && '.' != *findName) ++findName;
			// if no more outer macros, try module+non-local prefix with the original local label
			if ('>' == *findName++) {
				inMacro = false;
				STRCAT(temp, LABMAX-1, vorlab.c_str()); STRCAT(temp, 2, ".");
				STRCAT(temp, LABMAX-1, findName);
				findName = temp;
			}
		} else {
			if (!global && !local && fullName.get() == findName && modNameLen) {
				// this still may be global label without current module (but author didn't use "@")
				findName = fullName.get() + modNameLen + 1;
			} else {
				findName = nullptr;	// all options exhausted
			}
		}
	} while (findName);
	return undefinedLabelEntry;
}

static SLabelTableEntry* GetLabel(char*& p, uint16_t setExtraTraits = 0) {
	std::unique_ptr<char[]> fullName;
	SLabelTableEntry* labelEntry = SearchLabel(p, true, fullName);
	if (getLabel_invalidName) return nullptr;
	if (labelEntry) labelEntry->traits |= setExtraTraits;
	if (!labelEntry || LABEL_PAGE_UNDEFINED == labelEntry->page) {
		IsLabelNotFound = true;
		// don't insert labels or report errors during substitution phase
		if (IsSubstituting) return nullptr;
		// regular parsing/assembling, track new labels and report "not found" error
		char* findName = temp[0] ? temp : fullName.get();
		if (!labelEntry) {
			LabelTable.Insert(findName, 0, (LABEL_IS_UNDEFINED | setExtraTraits));
		}
		Error("Label not found", findName, IF_FIRST);
		return nullptr;
	} else {
		return labelEntry;
	}
}

bool LabelExist(char*& p, aint& val) {
	std::unique_ptr<char[]> fullName;
	SLabelTableEntry* labelEntry = SearchLabel(p, false, fullName);
	val = (labelEntry && LABEL_PAGE_UNDEFINED != labelEntry->page) ? -1 : 0;
	return !getLabel_invalidName;
}

bool GetLabelPage(char*& p, aint& val) {
	SLabelTableEntry* labelEntry = GetLabel(p);
	val = labelEntry ? labelEntry->page : LABEL_PAGE_UNDEFINED;
	// true even when not found, but valid label name (neeed for expression-eval logic)
	return !getLabel_invalidName;
}

bool GetLabelValue(char*& p, aint& val) {
	SLabelTableEntry* labelEntry = GetLabel(p);
	if (labelEntry) {
		val = labelEntry->value;
		if (Relocation::areLabelsOffset) {
			switch (labelEntry->isRelocatable) {
				case Relocation::REGULAR:	val += Relocation::alternative_offset;			break;
				case Relocation::HIGH:		val += Relocation::alternative_offset >> 8;		break;
				default:					;
			}
		}
	} else {
		val = 0;
	}
	// true even when not found, but valid label name (needed for expression-eval logic)
	return !getLabel_invalidName;
}

bool GetLabelPhPage(char*& p, aint& val) {
	SLabelTableEntry* labelEntry = GetLabel(p);
	val = labelEntry ? labelEntry->ph_page : LABEL_PAGE_UNDEFINED;
	return !getLabel_invalidName;		// true even when not found, but valid label name
}

bool GetLabelPhValue(char*& p, aint& val) {
	SLabelTableEntry* labelEntry = GetLabel(p);
	val = labelEntry ? labelEntry->ph_value : 0;	// no relocation logic for ph_value
	return !getLabel_invalidName;		// true even when not found, but valid label name
}

bool GetLabelSize(char*& p, aint& val) {
	// if symbol is found, add "sizeof" trait, if not found, insert placeholder with "sizeof" trait
	SLabelTableEntry* labelEntry = GetLabel(p, LABEL_HAS_SIZE);
	val = labelEntry ? labelEntry->size : 0;		// get symbol's size as return value
	// true even when not found, but valid label name (needed for expression-eval logic)
	return !getLabel_invalidName;
}

int GetTemporaryLabelValue(char*& op, aint& val, bool requireUnderscore) {
	char* p = op;
	if (SkipBlanks(p) || !isdigit((byte)*p)) return 0;
	char* const numberB = p;
	while (isdigit((byte)*p)) ++p;
	const bool hasUnderscore = ('_' == *p);
	if (requireUnderscore && !hasUnderscore) return 0;
	// convert suffix [bB] => 'b', [fF] => 'f' and ignore underscore
	const char type = (hasUnderscore ? p[1] : p[0]) | 0x20;	// should be 'b' or 'f'
	const char following = hasUnderscore ? p[2] : p[1];		// should be non-label char
	if ('b' != type && 'f' != type) return 0;	// local label must have "b" or "f" after number
	if (islabchar(following)) return 0;			// that suffix didn't end correctly
	// numberB -> p are digits to be parsed as integer
	if (!GetNumericValue_IntBased(op = numberB, p, val, 10)) return 0;
	if ('_' == *op) ++op;
	++op;
	// ^^ advance main parsing pointer op beyond the local label (here it *is* local label)
	auto label = ('b' == type) ? TemporaryLabelTable.seekBack(val) : TemporaryLabelTable.seekForward(val);
	if (label) {
		val = label->value;
		if (requireUnderscore) {				// part of full expression, do relocation by +offset
			if (label->isRelocatable && Relocation::areLabelsOffset) {
				val += Relocation::alternative_offset;
			}
		} else {								// single-label-only in jump/call instructions
			Relocation::isResultAffected = label->isRelocatable;
			Relocation::deltaType = label->isRelocatable ? Relocation::REGULAR : Relocation::OFF;
		}
	} else {
		if (LASTPASS == pass) Error("Temporary label not found", numberB, SUPPRESS);
		val = 0L;
	}
	return 1;
}

static page_t getAddressPageNumber(const aint address, bool forceRecalculateByAddress) {
	// everything is "ROM" based when device is NONE
	if (!DeviceID) return LABEL_PAGE_ROM;
	// fast-shortcut for regular labels in current slot (if they fit into it)
	auto slot = Device->GetCurrentSlot();
	assert(Page && slot);
	if (!forceRecalculateByAddress && DISP_NONE == PseudoORG) {
		if (slot->Address <= address && address < slot->Address + slot->Size) {
			return Page->Number;
		}
	}
	// enforce explicit request of fake DISP page
	if (DISP_NONE != PseudoORG && LABEL_PAGE_UNDEFINED != dispPageNum) {
		return dispPageNum;
	}
	// in other case (implicit DISP, out-of-slot-bounds or forceRecalculateByAddress)
	// track down the page num from current memory mapping
	const page_t page = Device->GetPageOfA16(address);
	if (LABEL_PAGE_UNDEFINED == page) return LABEL_PAGE_OUT_OF_BOUNDS;
	return page;
}

// requires label.page and label.ph_value to be already up-to-date
static void updatePhAddressPageNumber(SLabelTableEntry & label) {
	page_t page = LABEL_PAGE_OUT_OF_BOUNDS;
	if (LABEL_PAGE_UNDEFINED != label.page) {
		page = DeviceID ? Device->GetPageOfA16(label.ph_value) : LABEL_PAGE_ROM;
	}
	label.ph_page = page;
}

int CLabelTable::Insert(const char* nname, aint nvalue, uint16_t traits, page_t equPageNum) {
	const bool IsUndefined = !!(traits & LABEL_IS_UNDEFINED);

	// the EQU/DEFL is relocatable when the expression itself is relocatable
	// the regular label is relocatable when relocation is active
	const Relocation::EType deltaType = \
			(traits&LABEL_HAS_RELOC_TRAIT) ? \
				(traits & LABEL_IS_RELOC ? Relocation::REGULAR : Relocation::OFF) : \
				(traits & (LABEL_IS_DEFL|LABEL_IS_EQU)) ? \
					Relocation::deltaType : \
					Relocation::type && DISP_INSIDE_RELOCATE != PseudoORG ? \
						Relocation::REGULAR : Relocation::OFF;
	// Find label in label table
	symbol_map_t::iterator labelIt = symbols.find(nname);
	if (symbols.end() != labelIt) {
		//if label already added (as used, or in previous pass), just refresh values
		auto& label = labelIt->second;
		if (label.traits&LABEL_IS_KEYWORD) WarningById(W_OPKEYWORD, nname, W_EARLY);
		bool needsUpdate = label.traits&LABEL_IS_DEFL || label.page == LABEL_PAGE_UNDEFINED || label.updatePass < pass;
		if (needsUpdate) {
			label.value = nvalue;
			label.ph_value = (DISP_NONE != PseudoORG) ? adrdisp : CurAddress;
			if ((traits & LABEL_IS_EQU) && LABEL_PAGE_UNDEFINED != equPageNum) {
				label.page = equPageNum;
			} else {
				label.page = getAddressPageNumber(nvalue, traits & (LABEL_IS_DEFL|LABEL_IS_EQU));
			}
			updatePhAddressPageNumber(label);
			label.traits &= LABEL_HAS_SIZE;			// preserve some
			label.traits |= traits;
			label.isRelocatable = deltaType;
			label.updatePass = pass;
		}
		return needsUpdate;
	}
	auto& label = symbols[nname];
	label.traits = traits;
	label.updatePass = pass;
	label.value = nvalue;
	label.ph_value = (DISP_NONE != PseudoORG) ? adrdisp : CurAddress;	// in case of expression using undefined label, ph_value is "address" ahead of expression
	label.used = IsUndefined;
	if ((traits & LABEL_IS_EQU) && LABEL_PAGE_UNDEFINED != equPageNum) {
		label.page = equPageNum;
	} else {
		label.page = IsUndefined ? LABEL_PAGE_UNDEFINED : getAddressPageNumber(nvalue, traits & (LABEL_IS_DEFL|LABEL_IS_EQU));
	}
	updatePhAddressPageNumber(label);
	label.isRelocatable = IsUndefined ? Relocation::OFF : deltaType;	// ignore "relocatable" for "undefined"
	return 1;
}

int CLabelTable::Update(char* name, aint value) {
	// this may happen in early passes with duplicate labels (ie. the result is wrong anyway and source needs fixing)
	// or in third pass even when single label drifts from second pass, in such case full update to have correct values
	// at least for rest of the pass 3 is needed
	auto labelIt = symbols.find(name);
	if (symbols.end() == labelIt) return 0;
	auto& label = labelIt->second;
	label.value = value;
	label.ph_value = (DISP_NONE != PseudoORG) ? adrdisp : CurAddress;
	if (0 == (label.traits & (LABEL_IS_EQU|LABEL_IS_DEFL|LABEL_IS_STRUCT_D))) {
		if (LABEL_PAGE_UNDEFINED != label.page) {	// regular label with some page, refresh it
			label.page = getAddressPageNumber(value, false);
		}
		updatePhAddressPageNumber(label);
	}
	return 1;
}

SLabelTableEntry* CLabelTable::Find(const char* name, bool onlyDefined) {
	symbol_map_t::iterator labelIt = symbols.find(name);
	if (symbols.end() == labelIt) return nullptr;
	return (onlyDefined && LABEL_PAGE_UNDEFINED == labelIt->second.page) ? nullptr : &labelIt->second;
}

bool CLabelTable::IsUsed(const char* name) {
	auto labelIt = symbols.find(name);
	return (symbols.end() != labelIt) ? labelIt->second.used : false;
}

bool CLabelTable::Remove(const char* name) {
	return symbols.erase(name);
}

void CLabelTable::RemoveAll() {
	symbols.clear();
}

static const std::vector<symbol_map_t::key_type> getDumpOrder(const symbol_map_t& table) {
	std::vector<symbol_map_t::key_type> order;
	order.reserve(table.size());
	for (const auto& it : table) order.emplace_back(it.first);
	if (Options::SortSymbols) {
		std::sort(
			order.begin(), order.end(),
			[&](const symbol_map_t::key_type& a, const symbol_map_t::key_type& b) {
				// if case insenstive are same, do case sensitive too!
				int caseres = strcasecmp(a.c_str(), b.c_str());
				if (0 == caseres) return a < b;
				return caseres < 0;
			}
		);
	}
	return order;
}

void CLabelTable::Dump() {
	FILE* listFile = GetListingFile();
	if (NULL == listFile) return;		// listing file must be already opened here

	const auto order = getDumpOrder(symbols);

	char line[LINEMAX], *ep;
	fputs("\nValue    Label\n", listFile);
	fputs("------ - -----------------------------------------------------------\n", listFile);
	for (const symbol_map_t::key_type& name: order) {
		const symbol_map_t::mapped_type& symbol = symbols.at(name);
		if (LABEL_PAGE_UNDEFINED == symbol.page) continue;
		ep = line;
		*(ep) = 0;
		*(ep++) = '0';
		*(ep++) = 'x';
		PrintHexAlt(ep, symbol.value);
		*(ep++) = ' ';
		*(ep++) = symbol.used ? ' ' : 'X';
		*(ep++) = ' ';
		STRNCPY(ep, LINEMAX, name.c_str(), LINEMAX - (ep - line) - 2);
		STRNCAT(ep, LINEMAX, "\n", 2);
		fputs(line, listFile);
	}
}

void CLabelTable::DumpForUnreal() {
	char ln[LINEMAX], * ep;
	FILE* FP_UnrealList;
	if (!FOPEN_ISOK(FP_UnrealList, Options::UnrealLabelListFName, "w")) {
		Error("opening file for write", Options::UnrealLabelListFName.string().c_str(), FATAL);
	}
	// not adding to --cleanonerror, this is not binary output file
	const int PAGE_MASK = DeviceID ? Device->GetPage(0)->Size - 1 : 0x3FFF;
	const int ADR_MASK = Options::EmitVirtualLabels ? 0xFFFF : PAGE_MASK;
	const auto order = getDumpOrder(symbols);
	for (const symbol_map_t::key_type& name: order) {
		const symbol_map_t::mapped_type& symbol = symbols.at(name);
		if (LABEL_PAGE_UNDEFINED == symbol.page) continue;
		int page = Options::EmitVirtualLabels ? LABEL_PAGE_OUT_OF_BOUNDS : symbol.page;
		if (!strcmp(DeviceID, "ZXSPECTRUM48") && page < 4) {	//TODO fix this properly?
			// convert pages {0, 1, 2, 3} of ZX48 into ZX128-like {ROM, 5, 2, 0}
			// this can be fooled when there were multiple devices used, Label doesn't know into
			// which device it does belong, so even ZX128 labels will be converted.
			const int fakeZx128Pages[] = {LABEL_PAGE_ROM, 5, 2, 0};
			page = fakeZx128Pages[page];
		}
		int lvalue = symbol.value & ADR_MASK;
		ep = ln;

		if (page < LABEL_PAGE_ROM) ep += SPRINTF1(ep, LINEMAX, "%02d", page&255);
		*(ep++) = ':';
		PrintHexAlt(ep, lvalue);

		*(ep++) = ' ';
		STRCPY(ep, LINEMAX-(ep-ln), name.c_str());
		STRCAT(ep, LINEMAX, "\n");
		fputs(ln, FP_UnrealList);
	}
	fclose(FP_UnrealList);
}

void CLabelTable::DumpForCSpect() {
	FILE* file;
	if (!FOPEN_ISOK(file, Options::CSpectMapFName, "w")) {
		Error("opening file for write", Options::CSpectMapFName.string().c_str(), FATAL);
	}
	// not adding to --cleanonerror, this is not binary output file
	const int CSD_PAGE_SIZE = Options::CSpectMapPageSize;
	const int CSD_PAGE_MASK = CSD_PAGE_SIZE - 1;
	const auto order = getDumpOrder(symbols);
	for (const symbol_map_t::key_type& name: order) {
		const symbol_map_t::mapped_type& symbol = symbols.at(name);
		if (LABEL_PAGE_UNDEFINED == symbol.page) continue;
		const int labelType =
			(symbol.traits&LABEL_IS_STRUCT_E) ? 0 :
			(symbol.traits&LABEL_IS_STRUCT_D) ? 4 :
			(symbol.traits&LABEL_IS_EQU) ? 1 :
			(symbol.traits&LABEL_IS_DEFL) ? 2 :
			(LABEL_PAGE_ROM <= symbol.page) ? 3 : 0;
		const short page = labelType ? 0 : symbol.page;
			// TODO:
			// page == -1 will put regular EQU like "BLUE" out of reach for disassembly window
			// (otherwise BLUE becomes label for address $C001 with default mapping)
			// BUT then it would be nice to provide real page data for equ which have them explicit
			// BUT I can't distinguish explicit/implicit page number, as there's heuristic to use current mapping
			// instead of using the LABEL_PAGE_OUT_OF_BOUNDS page number...
			// TODO: figure out when/why the implicit page number heuristic happenned and if you can detect
			// only explicit page numbers used in EQU, and export only those

		const aint longAddress = (CSD_PAGE_MASK & symbol.value) + page * CSD_PAGE_SIZE;
		fprintf(file, "%08X %08X %02X ", 0xFFFF & symbol.value, longAddress, labelType);
		// convert primary+local label to be "@" delimited (not "." delimited)
		STRCPY(temp, LINEMAX, name.c_str());
		// look for "primary" label (where the local label starts)
		char* localLabelStart = strrchr(temp, '.');
		while (temp < localLabelStart) {	// the dot must be at least second character
			*localLabelStart = 0;			// terminate the possible "primary" part
			if (Find(temp, true)) {
				*localLabelStart = '@';
				break;
			}
			*localLabelStart = '.';			// "primary" label didn't work, restore dot
			do {
				--localLabelStart;			// and look for next dot
			} while (temp < localLabelStart && '.' != *localLabelStart);
		}
		// convert whole label to upper-case, as CSpect search is malfunctioning otherwise.
		char* strToUpper = temp;
		while ((*strToUpper = (char) toupper((byte)*strToUpper))) { ++strToUpper; }
		fprintf(file, "%s\n", temp);
	}
	fclose(file);
}

void CLabelTable::DumpSymbols() {
	FILE* symfp;
	if (!FOPEN_ISOK(symfp, Options::SymbolListFName, "w")) {
		Error("opening file for write", Options::SymbolListFName.string().c_str(), FATAL);
	}
	// not adding to --cleanonerror, this is not binary output file
	const auto order = getDumpOrder(symbols);
	for (const symbol_map_t::key_type& name: order) {
		const symbol_map_t::mapped_type& symbol = symbols.at(name);
		if (isdigit((byte)name[0])) continue;
		if (symbol.traits&LABEL_IS_KEYWORD) continue;
		WriteLabelEquValue(name.c_str(), symbol.value, symfp);
	}
	fclose(symfp);
}

void CLabelTable::TrackForSize(SLabelTableEntry *label) {
	assert(label);
	if ((LABEL_IS_EQU|LABEL_IS_DEFL|LABEL_IS_SMC) & label->traits) {				// skip size counting for certain types of labels
		Warning("SIZEOF(...) this type of label is invalid, size is zero");
		return;
	}
	const aint myLevel = sourceBlockLevel + (!!(LABEL_IS_LOCAL & label->traits));	// local labels are +1 level
	sizeofStack.emplace_back(label, myLevel);
}

void CLabelTable::SizeBoundary(BoundaryLevel boundaryLevel) {
	if (sizeofStack.empty()) return;
	if (BOUNDARY_FLOW == boundaryLevel) {
		Warning("SIZEOF-tracking does not work across wrap-around. Stopping count here.");
	}
	// soft boundary, process all stacked labels with current sourceBlockLevel (nesting of source text)
	aint myLevel = sourceBlockLevel + (BOUNDARY_LOCAL == boundaryLevel);	// local labels are +1 level
	while (myLevel <= sizeofStack.back().srcLevel) {
		auto & label = *sizeofStack.back().label;
		label.size = ((DISP_NONE != PseudoORG) ? adrdisp : CurAddress) - label.ph_value;
		// printf("boundary: %p, updatePass %d, ph_value %X, size %X, adrdisp %X, CurAddress %X\n", &label, label.updatePass, label.ph_value, label.size, adrdisp, CurAddress);
		sizeofStack.pop_back();
		if (sizeofStack.empty()) return;
	}
	if (BOUNDARY_FLOW != boundaryLevel && BOUNDARY_ALL != boundaryLevel) return;
	// process *all* stacked labels, even with lower boundary level, but warn about them
	// but I don't have names of them! So general warning about "Labels" ... :shrug:
	assert(!sizeofStack.empty());
	if (BOUNDARY_ALL == boundaryLevel) {
		Warning("There are labels in parent scope being SIZEOF-tracked at this point. Stopping their count here.");
	}
	do {
		auto & label = *sizeofStack.back().label;
		label.size = ((DISP_NONE != PseudoORG) ? adrdisp : CurAddress) - label.ph_value;
		// printf("boundary: %p, updatePass %d, ph_value %X, size %X, adrdisp %X, CurAddress %X\n", &label, label.updatePass, label.ph_value, label.size, adrdisp, CurAddress);
		sizeofStack.pop_back();
	} while (!sizeofStack.empty());
}

int CFunctionTable::Insert(const char* name_cstr, function_fn_t nfunp) {
	std::string name(name_cstr);
	if (!std::get<1>(functions.emplace(name, nfunp))) return 0;
	for (auto& c : name) c = toupper(c);
	return std::get<1>(functions.emplace(name, nfunp));
}

int CFunctionTable::insertd(const char* name, function_fn_t nfunp) {
	if ('.' != name[0]) Error("Directive string must start with dot", NULL, FATAL);
	// insert the non-dot variant first, then dot variant
	return Insert(name+1, nfunp) && Insert(name, nfunp);
}

int CFunctionTable::zoek(const char* name) {
	auto it = functions.find(name);
	if (functions.end() == it) return 0;
	(*it->second)();
	return 1;
}

TemporaryLabel::TemporaryLabel(aint number, aint address)
	: nummer(number), value(address), isRelocatable(bool(Relocation::type)) {}

void CTemporaryLabelTable::InitPass() {
	labels = std::move(nextPassLabels);				// use labels collected in previous pass
	nextPassLabels.reserve(128U + labels.size());	// reserve similar memory for next pass
}

bool CTemporaryLabelTable::InsertRefresh(const aint labelNumber) {
	temporary_labels_t::size_type refresh = nextPassLabels.size();
	nextPassLabels.emplace_back(labelNumber, CurAddress);
	if (labels.size() <= refresh || labels.at(refresh).nummer != labelNumber) return false;
	TemporaryLabel & to_r = labels.at(refresh);
	if (to_r.value != CurAddress) Warning("Temporary label has different address");
	return true;
}

const TemporaryLabel* CTemporaryLabelTable::seekForward(const aint labelNumber) const {
	// has to search through previous pass "labels", so data may be outdated a bit (last pass should match penultimate pass!)
	temporary_labels_t::size_type i = nextPassLabels.size();	// points at first "forward" temporary label
	while (i < labels.size() && labelNumber != labels[i].nummer) ++i;
	return (i < labels.size()) ? &labels[i] : nullptr;
}

const TemporaryLabel* CTemporaryLabelTable::seekBack(const aint labelNumber) const {
	// will search through nextPassLabels, so data are "fresh" from current pass
	temporary_labels_t::size_type i = nextPassLabels.size();	// points after last "backward" temporary label
	while (i-- && labelNumber != nextPassLabels[i].nummer) ;	// empty while statement, condition does all work
	return (i < nextPassLabels.size()) ? &nextPassLabels[i] : nullptr;
}

CStringsList::CStringsList(const char* stringSource, CStringsList* nnext) {
	string = STRDUP(stringSource);
	next = nnext;
	if (!sourcePosStack.empty()) source = sourcePosStack.back();
}

CStringsList::~CStringsList() {
	if (string) free(string);
	if (next) delete next;
}

bool CStringsList::contains(const CStringsList* strlist, const char* searchString) {
	while (nullptr != strlist) {
		if (!strcmp(searchString, strlist->string)) return true;
		strlist = strlist->next;
	}
	return false;
}

CDefineTableEntry::CDefineTableEntry(const char* nname, const char* nvalue, CStringsList* nnss, CDefineTableEntry* nnext)
		: name(NULL), value(NULL) {
	name = STRDUP(nname);
	value = new char[strlen(nvalue) + 1];
	if (NULL == name || NULL == value) ErrorOOM();
	char* s1 = value;
	while (*nvalue && *nvalue != '\n' && *nvalue != '\r') *s1++ = *nvalue++;
	*s1 = 0;
	next = nnext;
	nss = nnss;
}

CDefineTableEntry::~CDefineTableEntry() {
	if (name) free(name);
	if (value) delete[] value;
	if (nss) delete nss;
	if (next) delete next;
}

void CDefineTableEntry::Replace(const char* nvalue) {
	if (value) delete[] value;
	value = new char[strlen(nvalue) + 1];
	strcpy(value, nvalue);
}

CDefineTable::~CDefineTable() {
	for (auto def : defs) if (def) delete def;
}

CDefineTable& CDefineTable::operator=(CDefineTable const & defTable) {
	RemoveAll();
	for (CDefineTableEntry* srcDef : defTable.defs) {
		CDefineTableEntry* srcD = srcDef;
		while (srcD) {
			Add(srcD->name, srcD->value, srcD->nss);
			srcD = srcD->next;
		}
	}
	return *this;
}

void CDefineTable::Init() {
	DefArrayList = NULL;
	for (auto & def : defs) def = NULL;
}

void CDefineTable::Add(const char* name, const char* value, CStringsList* nss) {
	if (FindDuplicate(name)) {
		Error("Duplicate define (replacing old value)", name, PASS03);
	}
	defs[(*name)&127] = new CDefineTableEntry(name, value, nss, defs[(*name)&127]);
}

static char defineGet__Counter__Buffer[32] = {};
static char defineGet__Line__Buffer[32] = {};

const char* CDefineTable::Get(const char* name) {
	DefArrayList = nullptr;
	if (nullptr == name || 0 == name[0]) return nullptr;
	// the __COUNTER__ and __LINE__ have fully dynamic custom implementation here
	if ('_' == name[1]) {
		if (!strcmp(name, "__COUNTER__")) {
			SPRINTF1(defineGet__Counter__Buffer, 30, "%d", PredefinedCounter);
			++PredefinedCounter;
			return defineGet__Counter__Buffer;
		}
		if (!strcmp(name, "__LINE__")) {
			SPRINTF1(defineGet__Line__Buffer, 30, "%d", sourcePosStack.empty() ? 0 : sourcePosStack.back().line);
			return defineGet__Line__Buffer;
		}
	}
	CDefineTableEntry* p = defs[(*name)&127];
	while (p && strcmp(name, p->name)) p = p->next;
	if (nullptr == p) return nullptr;
	DefArrayList = p->nss;
	return p->value;
}

int CDefineTable::FindDuplicate(const char* name) {
	CDefineTableEntry* p = defs[(*name)&127];
	while (p) {
		if (!strcmp(name, p->name)) {
			return 1;
		}
		p = p->next;
	}
	return 0;
}

int CDefineTable::Replace(const char* name, const char* value) {
	CDefineTableEntry* p = defs[(*name)&127];
	while (p) {
		if (!strcmp(name, p->name)) {
			p->Replace(value);
			return 0;
		}
		p = p->next;
	}
	defs[(*name)&127] = new CDefineTableEntry(name, value, 0, defs[(*name)&127]);
	return 1;
}

int CDefineTable::Replace(const char* name, const int value) {
	char newIntValue[24];
	SPRINTF1(newIntValue, sizeof(newIntValue), "%d", value);
	return Replace(name, newIntValue);
}

int CDefineTable::Remove(const char* name) {
	CDefineTableEntry* p = defs[(*name)&127];
	CDefineTableEntry* p2 = NULL;
	while (p) {
		if (!strcmp(name, p->name)) {
			// unchain the particular item
			if (NULL == p2) defs[(*name)&127] = p->next;
			else			p2->next = p->next;
			p->next = NULL;
			// delete it
			delete p;
			DefArrayList = NULL;		// may be invalid here, so just reset it
			return 1;
		}
		p2 = p;
		p = p->next;
	}
	return 0;
}

void CDefineTable::RemoveAll() {
	DefArrayList = NULL;
	for (auto & def : defs) {
		if (!def) continue;
		delete def;
		def = NULL;
	}
}

CMacroDefineTable::CMacroDefineTable() : defs(nullptr) {
	for (auto & usedX : used) usedX = false;
}

CMacroDefineTable::~CMacroDefineTable() {
	if (defs) delete defs;
}

void CMacroDefineTable::ReInit() {
	if (defs) delete defs;
	defs = nullptr;
	for (auto & usedX : used) usedX = false;
}

void CMacroDefineTable::AddMacro(char* naam, char* vervanger) {
	CDefineTableEntry* tmpdefs = new CDefineTableEntry(naam, vervanger, 0, defs);
	defs = tmpdefs;
	used[(*naam)&127] = true;
}

CDefineTableEntry* CMacroDefineTable::getdefs() {
	return defs;
}

void CMacroDefineTable::setdefs(CDefineTableEntry* const ndefs) {
	if (ndefs == defs) return;			// the current HEAD of defines is already same as requested one
	// traverse through current HEAD until the requested chain is found, unchain the HEAD from it
	CDefineTableEntry* entry = defs;
	while (entry && ndefs != entry->next) entry = entry->next;
	if (entry) entry->next = nullptr;	// if "ndefs" is chained to current HEAD, unchain
	if (defs) delete defs;				// release front part of current chain from memory
	defs = ndefs;						// the requested chain is new current HEAD
}

const char* CMacroDefineTable::getverv(const char* name) const {
	if (nullptr == name) return nullptr;
	if (!used[(*name)&127]) return nullptr;
	const CDefineTableEntry* p = defs;
	while (p && strcmp(name, p->name)) p = p->next;
	return p ? p->value : nullptr;
}

void CMacroTable::ReInit() {
	used.assign(128, false);
	macs.clear();
}

void CMacroTable::Add(const char* nnaam, char*& p) {
	auto [add_it, is_new] = macs.try_emplace(nnaam, CMacroTableEntry());
	if (!is_new) {
		Error("Duplicate macroname", nnaam);
		return;
	}
	used[nnaam[0] & 127] = true;
	CStringsList* last = nullptr;
	do {
		char* n = GetID(p);
		if (!n) {
			// either EOL when no previous argument, or valid name is required after comma (2nd+ loop)
			if ((1 == pass) && (last || *p)) Error("Illegal argument name", p, EARLY);
			SkipToEol(p);
			break;
		}
		if ((1 == pass) && CStringsList::contains(add_it->second.args, n)) {
			Error("Duplicate argument name", n, EARLY);
		}
		CStringsList* argname = new CStringsList(n);
		if (!add_it->second.args) {
			add_it->second.args = argname;	// first argument name, make it head of list
		} else {
			last->next = argname;
		}
		last = argname;
	} while (anyComma(p));
	if ((1 == pass) && *p) {
		Error("Unexpected", p, EARLY);
	}
	ListFile();
	if (!ReadFileToCStringsList(add_it->second.body, "endm")) {
		Error("Unexpected end of macro", NULL, EARLY);
	}
}

int CMacroTable::Emit(char* naam, char*& p) {
	// search for the desired macro
	if (!used[naam[0] & 127]) return 0;
	auto mac_it = macs.find(naam);
	if (macs.end() == mac_it) return 0;
	// macro found, emit it, prepare temporary instance label base
	char* omacrolabp = macrolabp;
	char labnr[LINEMAX], ml[LINEMAX];
	SPRINTF1(labnr, LINEMAX, "%d", macronummer++);
	macrolabp = labnr;
	if (omacrolabp) {
		STRCAT(macrolabp, LINEMAX-1, "."); STRCAT(macrolabp, LINEMAX-1, omacrolabp);
	} else {
		MacroDefineTable.ReInit();
	}
	// parse argument values
	CDefineTableEntry* odefs = MacroDefineTable.getdefs();
	CStringsList* a = mac_it->second.args;
	while (a) {
		char* n = ml;
		const bool lastArg = NULL == a->next;
		if (!GetMacroArgumentValue(p, n) || (!lastArg && !comma(p))) {
			Error("Not enough arguments for macro", naam, SUPPRESS);
			macrolabp = omacrolabp;
			return 1;
		}
		MacroDefineTable.AddMacro(a->string, ml);
		a = a->next;
	}
	SkipBlanks(p);
	if (*p) {
		Error("Too many arguments for macro", naam, SUPPRESS);
		macrolabp = omacrolabp;
		return 1;
	}
	// arguments parsed, emit the macro lines and parse them
	lp = p;
	ListFile();
	sourceBlockLevel += 2;
	++listmacro;
	CStringsList* olijstp = lijstp;
	lijstp = mac_it->second.body;
	++lijst;
	STRCPY(ml, LINEMAX, line);
	sourcePosStack.push_back(TextFilePos());
	const aint osldSwapSrcPos = sldSwapSrcPos;
	while (lijstp) {
		sourcePosStack.back() = lijstp->source;
		STRCPY(line, LINEMAX, lijstp->string);
		substitutedLine = line;		// reset substituted listing
		eolComment = NULL;			// reset end of line comment
		lijstp = lijstp->next;
		ParseLineSafe();
	}
	LabelTable.SizeBoundary(CLabelTable::BOUNDARY_MAIN);
	sourceBlockLevel -= 2;
	if (osldSwapSrcPos != sldSwapSrcPos) WarningById(W_SLD_SWAP, mac_it->first.c_str());
	sourcePosStack.pop_back();
	++CompiledCurrentLine;
	STRCPY(line, LINEMAX, ml);
	lijstp = olijstp;
	--lijst;
	MacroDefineTable.setdefs(odefs);
	macrolabp = omacrolabp;
	--listmacro; donotlist = 1;
	return 2;
}

CStructureEntry1::CStructureEntry1(char* nnaam, aint noffset) {
	next = 0;
	naam = STRDUP(nnaam);
	if (naam == NULL) ErrorOOM();
	offset = noffset;
}

CStructureEntry1::~CStructureEntry1() {
	free(naam);
	if (next) delete next;
}

CStructureEntry2::CStructureEntry2(aint noffset, aint nlen, aint ndef, Relocation::EType ndeltatype, EStructureMembers ntype) :
	next(nullptr), text(nullptr), offset(noffset), len(nlen), def(ndef), defDeltaType(ndeltatype), type(ntype)
{
}

CStructureEntry2::CStructureEntry2(aint noffset, aint nlen, byte* textData) :
	next(nullptr), text(textData), offset(noffset), len(nlen), def(0), defDeltaType(Relocation::OFF), type(SMEMBTEXT)
{
	assert(1 <= len && len <= TEXT_MAX_SIZE && nullptr != text);
}

CStructureEntry2::~CStructureEntry2() {
	if (next) delete next;
	if (text) delete[] text;
}

// Parses source input for types: BYTE, WORD, DWORD, D24
aint CStructureEntry2::ParseValue(char* & p) {
	if (SMEMBBYTE != type && SMEMBWORD != type && SMEMBDWORD != type && SMEMBD24 != type) return def;
	SkipBlanks(p);
	aint val = def;
	bool keepRelocatableFlags = false;	// keep flags from the ParseExpressionNoSyntaxError?
	// check for unexpected {
	if ('{' != *p) {
		if (!(keepRelocatableFlags = ParseExpressionNoSyntaxError(p, val))) {
			val = def;
		}
		switch (type) {
			case SMEMBBYTE:
				check8(val);
				val &= 0xFF;
				break;
			case SMEMBWORD:
				check16(val);
				val &= 0xFFFF;
				break;
			case SMEMBD24:
				check24(val);
				val &= 0xFFFFFF;
				break;
			case SMEMBDWORD:
				break;
			default:
				break;
		}
	}
	if (!Relocation::type) return val;
	if (SMEMBBYTE == type && Relocation::HIGH == Relocation::type) {
		if (!keepRelocatableFlags) {	// override flags, if parse expression was not successful
			Relocation::isResultAffected |= bool(defDeltaType);
			Relocation::deltaType = defDeltaType;
		}
		Relocation::resolveRelocationAffected(0, Relocation::HIGH);
	} else if (SMEMBWORD == type) {
		if (!keepRelocatableFlags) {	// override flags, if parse expression was not successful
			Relocation::isResultAffected |= bool(defDeltaType);
			Relocation::deltaType = defDeltaType;
		}
		Relocation::resolveRelocationAffected(0);
	}
	Relocation::checkAndWarn();
	return val;
}

CStructure::CStructure(const char* nnaam, int no, CStructure* p) {
	mnf = mnl = NULL; mbf = mbl = NULL;
	naam = STRDUP(nnaam);
	if (naam == NULL) ErrorOOM();
	next = p; noffset = no;
	maxAlignment = 0;
}

CStructure::~CStructure() {
	free(naam);
	if (mnf) delete mnf;
	if (mbf) delete mbf;
	if (next) delete next;
}

void CStructure::AddLabel(char* nnaam) {
	CopyLabel(nnaam, 0);
}

void CStructure::AddMember(CStructureEntry2* n) {
	if (!mbf)	mbf = n;
	else 		mbl->next = n;
	mbl = n;
	noffset += n->len;
}

void CStructure::CopyLabel(char* nnaam, aint offset) {
	CStructureEntry1* n = new CStructureEntry1(nnaam, noffset + offset);
	if (!mnf)	mnf = n;
	else		mnl->next = n;
	mnl = n;
}

void CStructure::CopyLabels(CStructure* st) {
	CStructureEntry1* np = st->mnf;
	if (!np || !PreviousIsLabel) return;
	char str[LINEMAX];
	STRCPY(str, LINEMAX-1, PreviousIsLabel);
	STRCAT(str, LINEMAX-1, ".");
	char * const stw = str + strlen(str);
	while (np) {
		STRCPY(stw, LINEMAX, np->naam);	// overwrite the second part of label
		CopyLabel(str, np->offset);
		np = np->next;
	}
}

void CStructure::CopyMember(CStructureEntry2* item, aint newDefault, Relocation::EType newDeltaType) {
	AddMember(new CStructureEntry2(noffset, item->len, newDefault, newDeltaType, item->type));
}

void CStructure::CopyMembers(CStructure* st, char*& lp) {
	aint val;
	int haakjes = 0;
	AddMember(new CStructureEntry2(noffset, 0, 0, Relocation::OFF, SMEMBPARENOPEN));
	SkipBlanks(lp);
	if (*lp == '{') {
		++haakjes; ++lp;
	}
	CStructureEntry2* ip = st->mbf;
	while (ip || 0 < haakjes) {
		Relocation::isResultAffected = false;
		// check if inside curly braces block, and input seems to be empty -> fetch next line
		if (0 < haakjes && !PrepareNonBlankMultiLine(lp)) break;
		if (nullptr == ip) {	// no more struct members expected, looking for closing '}'
			assert(0 < haakjes);
			if (!need(lp, '}')) break;
			--haakjes;
			continue;
		}
		assert(ip);
		switch (ip->type) {
		case SMEMBBLOCK:
			CopyMember(ip, ip->def, Relocation::OFF);
			break;
		case SMEMBBYTE:
		case SMEMBWORD:
		case SMEMBD24:
		case SMEMBDWORD:
			{
				Relocation::EType isRelocatable = Relocation::OFF;
				if (ParseExpressionNoSyntaxError(lp, val)) {
					isRelocatable = (Relocation::isResultAffected && (SMEMBWORD == ip->type || SMEMBBYTE == ip->type))
										? Relocation::deltaType : Relocation::OFF;
				} else {
					val = ip->def;
					isRelocatable = ip->defDeltaType;
				}
				CopyMember(ip, val, isRelocatable);
				if (SMEMBWORD == ip->type) {
					Relocation::resolveRelocationAffected(INT_MAX);	// clear flags + warn when can't be relocated
				} else if (SMEMBBYTE == ip->type) {
					Relocation::resolveRelocationAffected(INT_MAX, Relocation::HIGH);	// clear flags + warn when can't be relocated
				}
				if (ip->next && SMEMBPARENCLOSE != ip->next->type) anyComma(lp);
			}
			break;
		case SMEMBTEXT:
			{
				byte* textData = new byte[ip->len]();	// zero initialized for stable binary results
				if (nullptr == textData) ErrorOOM();
				GetStructText(lp, ip->len, textData, ip->text);
				AddMember(new CStructureEntry2(noffset, ip->len, textData));
				if (ip->next && SMEMBPARENCLOSE != ip->next->type) anyComma(lp);
			}
			break;
		case SMEMBPARENOPEN:
			SkipBlanks(lp);
			if (*lp == '{') {
				++haakjes; ++lp;
			}
			CopyMember(ip, 0, Relocation::OFF);
			break;
		case SMEMBPARENCLOSE:
			SkipBlanks(lp);
			if (haakjes && *lp == '}') {
				--haakjes; ++lp;
				if (ip->next && SMEMBPARENCLOSE != ip->next->type) anyComma(lp);
			}
			CopyMember(ip, 0, Relocation::OFF);
			break;
		default:
			Error("internalerror CStructure::CopyMembers", NULL, FATAL);
		}
		Relocation::checkAndWarn();
		ip = ip->next;
	}
	if (haakjes) {
		Error("closing } missing");
	}
	AddMember(new CStructureEntry2(noffset, 0, 0, Relocation::OFF, SMEMBPARENCLOSE));
}

static void InsertSingleStructLabel(const bool setNameSpace, char *name, const bool isRelocatable, const aint value, const bool isDefine = true) {
	// "isDefine" case already has namespaced-full-name, don't add namespace twice by ValidateLabel
	bool isLocal = false;
	std::unique_ptr<char[]> tempFullName(isDefine ? nullptr : ValidateLabel(isLocal, name, setNameSpace));
	if (!isDefine && !tempFullName) return;		// invalid labelname reported by ValidateLabel
	char *fullName = isDefine ? name : tempFullName.get();
	if (pass == LASTPASS) {
		aint oval;
		char *lp = name;	// not full name, full name fails in GetLabelValue for local macro labels (trying to full-name it second time)
		if (!GetLabelValue(lp, oval)) {
			Error("Internal error. InsertSingleStructLabel()", fullName, FATAL);
		}
		if (value != oval) {
			Error("Label has different value in pass 2", fullName);
		}
		if (IsSldExportActive()) {		// SLD (Source Level Debugging) tracing-data logging
			SLabelTableEntry* symbol = LabelTable.Find(fullName, true);
			assert(symbol);	// should have been already defined before last pass
			if (symbol) {
				WriteToSldFile(isDefine ? -1 : symbol->page, value, 'L', ExportLabelToSld(name, symbol));
			}
		}
	} else {
		Relocation::isResultAffected = isRelocatable;
		Relocation::deltaType = isRelocatable ? Relocation::REGULAR : Relocation::OFF;
		assert(!isDefine || Relocation::OFF == Relocation::deltaType);	// definition labels are always nonrel
		unsigned traits = LABEL_HAS_RELOC_TRAIT \
						| (isLocal ? LABEL_IS_LOCAL : 0) \
						| (isDefine ? LABEL_IS_STRUCT_D : LABEL_IS_STRUCT_E) \
						| (isRelocatable ? LABEL_IS_RELOC : 0);
		if (!LabelTable.Insert(fullName, value, traits)) {
			// while main struct name shouldn't be possible to dupe, item names may compound into clash, caught as late as here
			Error("Duplicate label", fullName, EARLY);
		} else if (isDefine && setNameSpace) {	// this is name of structure defined, make it's SIZEOF also set
			SLabelTableEntry* structLabel = LabelTable.Find(fullName, true);
			assert(structLabel);
			structLabel->size = value;
		}
	}
}

static void InsertStructSubLabels(const char* mainName, const bool isRelocatable, const CStructureEntry1* members, const aint address = 0, const bool isDefine = true) {
	char ln[LINEMAX+1];
	size_t mainNameSz = std::min(strlen(mainName), size_t(LINEMAX-2));
	memcpy(ln, mainName, mainNameSz);
	ln[mainNameSz++] = '.';
	ln[mainNameSz] = 0;
	char * const lnsubw = ln + mainNameSz;
	while (members) {
		STRCPY(lnsubw, LINEMAX-strlen(ln), members->naam);		// overwrite sub-label part
		InsertSingleStructLabel(false, ln, isRelocatable, members->offset + address, isDefine);
		members = members->next;
	}
}

void CStructure::deflab() {
	InsertSingleStructLabel(true, naam, false, noffset);
	InsertStructSubLabels(naam, false, mnf);
}

void CStructure::emitlab(char* iid, aint address, const bool isRelocatable) {
	const aint misalignment = maxAlignment ? ((-address) & (maxAlignment - 1)) : 0;
	if (misalignment) {
		// emitting in misaligned position (considering the ALIGN used to define this struct)
		char warnTxt[LINEMAX];
		SPRINTF3(warnTxt, LINEMAX,
					"Struct %s did use ALIGN %d in definition, but here it is misaligned by %d bytes",
					naam, maxAlignment, misalignment);
		Warning(warnTxt);
	}
	char sn[LINEMAX] { 0 };
	STRCPY(sn, LINEMAX-1, iid);
	InsertSingleStructLabel(true, sn, isRelocatable, address, false);
	InsertStructSubLabels(sn, isRelocatable, mnf, address, false);
	SLabelTableEntry* structLabel = LabelTable.Find(sn, true);		// set SIZEOF of emitted structure label
	if (structLabel) structLabel->size = noffset;
}

void CStructure::emitmembs(char*& p) {
	byte* emitTextBuffer = nullptr;
	aint val;
	int haakjes = 0;
	SkipBlanks(p);
	if (*p == '{') {
		++haakjes; ++p;
	}
	CStructureEntry2* ip = mbf;
	Relocation::isResultAffected = false;
	while (ip || 0 < haakjes) {
		// check if inside curly braces block, and input seems to be empty -> fetch next line
		if (0 < haakjes && !PrepareNonBlankMultiLine(p)) break;
		if (nullptr == ip) {	// no more struct members expected, looking for closing '}'
			assert(0 < haakjes);
			if (!need(p, '}')) break;
			--haakjes;
			continue;
		}
		assert(ip);
		switch (ip->type) {
		case SMEMBBLOCK:
			EmitBlock(ip->def != -1 ? ip->def : 0, ip->len, ip->def == -1, 8);
			if (8 < ip->len) ListFile();	// "..." elipsis happened in listing, force listing
			break;
		case SMEMBBYTE:
			EmitByte(ip->ParseValue(p));
			if (ip->next && SMEMBPARENCLOSE != ip->next->type) anyComma(p);
			break;
		case SMEMBWORD:
			// ParseValue will also add relocation data if needed (so the "ParseValue" name is misleading)
			EmitWord(ip->ParseValue(p));
			if (ip->next && SMEMBPARENCLOSE != ip->next->type) anyComma(p);
			break;
		case SMEMBD24:
			val = ip->ParseValue(p);
			EmitByte(val & 0xFF);
			EmitWord((val>>8) & 0xFFFF);
			if (ip->next && SMEMBPARENCLOSE != ip->next->type) anyComma(p);
			break;
		case SMEMBDWORD:
			val = ip->ParseValue(p);
			EmitWord(val & 0xFFFF);
			EmitWord((val>>16) & 0xFFFF);
			if (ip->next && SMEMBPARENCLOSE != ip->next->type) anyComma(p);
			break;
		case SMEMBTEXT:
			{
				if (nullptr == emitTextBuffer) {
					emitTextBuffer = new byte[CStructureEntry2::TEXT_MAX_SIZE+2];
					if (nullptr == emitTextBuffer) ErrorOOM();
				}
				memset(emitTextBuffer, 0, ip->len);
				GetStructText(p, ip->len, emitTextBuffer, ip->text);
				for (aint ii = 0; ii < ip->len; ++ii) EmitByte(emitTextBuffer[ii]);
			}
			if (ip->next && SMEMBPARENCLOSE != ip->next->type) anyComma(p);
			break;
		case SMEMBPARENOPEN:
			SkipBlanks(p);
			if (*p == '{') { ++haakjes; ++p; }
			break;
		case SMEMBPARENCLOSE:
			SkipBlanks(p);
			if (haakjes && *p == '}') {
				--haakjes; ++p;
			}
			if (ip->next && SMEMBPARENCLOSE != ip->next->type) anyComma(p);
			break;
		default:
			ErrorInt("Internal Error CStructure::emitmembs", ip->type, FATAL);
		}
		ip = ip->next;
	}
	if (haakjes) {
		Error("closing } missing");
	}
	if (!SkipBlanks(p)) Error("[STRUCT] Syntax error - too many arguments?");
	Relocation::checkAndWarn();
	if (nullptr != emitTextBuffer) delete[] emitTextBuffer;
}

CStructureTable::CStructureTable() {
	for (auto & structPtr : strs) structPtr = nullptr;
}

CStructureTable::~CStructureTable() {
	for (auto structPtr : strs) if (structPtr) delete structPtr;
}

void CStructureTable::ReInit() {
	for (auto & structPtr : strs) {
		if (structPtr) delete structPtr;
		structPtr = nullptr;
	}
}

CStructure* CStructureTable::Add(const char* naam, int offset) {
	if (FindDuplicate(naam)) {
		Error("[STRUCT] Structure already exist", naam);
		return nullptr;
	}
	CStructure** strsN = strs + (naam[0] & 127);
	*strsN = new CStructure(naam, 0, *strsN);
	if (offset) {
		(*strsN)->AddMember(new CStructureEntry2(0, offset, -1, Relocation::OFF, SMEMBBLOCK));
	}
	return *strsN;
}

CStructure* CStructureTable::zoek(const char* naam, int gl) {
	char sn[LINEMAX], * sp;
	sn[0] = 0;
	const char* prependNS = gl ? nullptr : ('.' == naam[0]) ? vorlab.c_str() : ModuleName[0] ? ModuleName : nullptr;
	if (prependNS) {
		STRCPY(sn, LINEMAX-2, prependNS);
		if ('.' != naam[0]) STRCAT(sn, 2, ".");
	}
	STRCAT(sn, LINEMAX-1, naam);
	sp = sn;
	CStructure* p = strs[(*sp)&127];
	while (p) {
		if (!strcmp(sp, p->naam)) return p;
		p = p->next;
	}
	if (gl || ! *ModuleName) return NULL;
	sp += 1 + strlen(ModuleName); p = strs[(*sp)&127];
	while (p) {
		if (!strcmp(sp, p->naam)) return p;
		p = p->next;
	}
	return NULL;
}

int CStructureTable::FindDuplicate(const char* naam) {
	CStructure* p = strs[(*naam)&127];
	while (p) {
		if (!strcmp(naam, p->naam)) return 1;
		p = p->next;
	}
	return 0;
}

aint CStructureTable::ParseDesignedAddress(char* &p) {
	if (!SkipBlanks(p) && ('=' == *p)) {
		char* adrP = ++p;
		aint resultAdr;
		if (ParseExpressionNoSyntaxError(p, resultAdr)) return resultAdr;
		Error("[STRUCT] Syntax error in designed address", adrP, SUPPRESS);
		return 0;
	}
	return INT_MAX;		// no "designed address" provided, emit structure bytes
}

int CStructureTable::Emit(char* naam, char* l, char*& p, int gl) {
	CStructure* st = zoek(naam, gl);
	if (!st) return 0;
	// create new labels corresponding to current/designed address
	aint address = CStructureTable::ParseDesignedAddress(p);
	if (l) {
		const Relocation::EType relocatable =
			(INT_MAX == address) ?
				(Relocation::type ? Relocation::REGULAR : Relocation::OFF)
				: Relocation::isResultAffected ? Relocation::deltaType : Relocation::OFF;
		st->emitlab(l, (INT_MAX == address) ? CurAddress : address, relocatable == Relocation::REGULAR);
	}
	if (INT_MAX == address) st->emitmembs(p);	// address was not designed, emit also bytes
	else if (!l) Warning("[STRUCT] designed address without label = no effect");
	return 1;
}

SRepeatStack::SRepeatStack(aint count, CStringsList* condition, CStringsList* firstLine)
	: RepeatCount(count), RepeatCondition(condition), Lines(firstLine), Pointer(firstLine), IsInWork(false), Level(0)
{
	assert(!sourcePosStack.empty());
	sourcePos = sourcePosStack.back();
}

SRepeatStack::~SRepeatStack() {
	if (RepeatCondition) delete RepeatCondition;
	if (Lines) delete Lines;
}

//eof tables.cpp
