// BKTurbo8.cpp: определяет точку входа для консольного приложения.
//

#include "pch.h"
#include "Assemble.h"
#include "BKMemory.h"
#include "BKToken.h"
#include "BKTurbo8.h"
#include "ConvW2k.h" // там константы
#include "ErrorManager.h"
#include "getopt.h"
#include "Globals.h"
#include "LabelDefs.h"
#include "LabelManager.h"
#include "LabelTable.h"
#include "LabelTableRef.h"
#include "Listing.h"
#include "Object.h"
#include "Parser.h"
#include "Parser2.h"
#include "RPNChain.h"
#include "ScriptAsm.h"
#include "StringUtil.h"
#include "versionNo.h"
#include <regex>


#pragma warning(disable:4996)

constexpr auto EXT_OBJ = 0;
constexpr auto EXT_LST = 1;
constexpr auto EXT_BIN = 2;
constexpr auto EXT_RAW = 3;
constexpr auto EXT_SAV = 4;
static fs::path m_pathExtArr[] =
{
	_T(".obj"),
	_T(".lst"),
	_T(".bin"),
	_T(".raw"),
	_T(".sav")
};

namespace BKTurbo8
{
	fs::path m_strListingFilename, m_strObjectFileName, m_strTableFileName, m_strResultFile;
	OP_MODE m_operation = OP_MODE::UNKN;

	int m_nStartAddress = -1;   // адрес задаваемый из командной строки
	uint32_t m_flags = BKT_FLAG_NONE;
}

static std::_tstring &ReplaceString(std::_tstring &subject, const std::_tstring &search,
                                    const std::_tstring &replace)
{
	size_t pos = 0;

	while ((pos = subject.find(search, pos)) != std::_tstring::npos)
	{
		subject.replace(pos, search.length(), replace);
		pos += replace.length();
	}

	return subject;
}

constexpr int BKT8_ERROR_OK = 0;            // нет ошибок
constexpr int BKT8_ERROR_CMDLINE = -1;      // ошибка в командной строке
constexpr int BKT8_ERROR_NOMEM = -2;        // недостаточно памяти
constexpr int BKT8_ERROR_UNDEFCODE = -3;    // не определена кодировка исходного файла
constexpr int BKT8_ERROR_OPENSRC = -4;      // не удалось открыть исходный файл
constexpr int BKT8_ERROR_OPENOBJ = -5;      // не удалось открыть объектный файл

int _tmain(int argc, TCHAR *argv[], TCHAR *envp[])
{
#ifdef _DEBUG
	g_RefsTable.SetDumpName(_T("_refs.txt"));
	g_labelGlobalDefs.SetDumpName(_T("_globals.txt"));
	g_labelLocalDefs.SetDumpName(_T("_locals.txt"));
#endif
	setlocale(LC_ALL, "");
	//std::locale::global(std::locale("ru_RU.UTF-8"));
	g_Globals.InitGlobalParameters();
	g_Globals.SetBasePath(fs::current_path());
	static struct option long_options[] =
	{
		{ _T("help"),    no_argument,       nullptr, _T('h') },
		{ _T("version"), no_argument,       nullptr, _T('v') },
		{ _T("details"), no_argument,       nullptr, _T('d') },
		{ _T("raw"),     no_argument,       nullptr, _T('r') },
		{ _T("sav"),     no_argument,       nullptr, _T('x') },
		{ _T("input"),   required_argument, nullptr, _T('i') },
		{ _T("listing"), optional_argument, nullptr, _T('l') },
		{ _T("object"),  optional_argument, nullptr, _T('o') },
		{ _T("table"),   optional_argument, nullptr, _T('t') },
		{ _T("address"), required_argument, nullptr, _T('s') },
		{ _T("InputKOI"), required_argument, nullptr, _T('I') },
		{ _T("OutputKOI"), required_argument, nullptr, _T('O') },
		{ nullptr,       no_argument,       nullptr, 0 }
	};
	static TCHAR optstring[] = _T("hvdrxi:l::o::t::s:I:O:");
	int option_index = 0;
	int c = 0;

	while ((c = getopt_long(argc, argv, optstring, long_options, &option_index)) != -1)
	{
		// Handle options
		switch (c)
		{
			case _T('h'):	// справка
				BKTurbo8::Usage();
				return BKT8_ERROR_OK;

			case _T('v'):	// показать версию
				BKTurbo8::PrintVersion();
				return BKT8_ERROR_OK;

			case _T('d'):	// задать более детальный вывод на экран
				g_Globals.SetVerbosity(true);
				break;

			case _T('i'):	// задать кодировку входного файла
			{
				const auto strTmp = (optarg) ? std::_tstring(optarg) : _T("a");
				g_Globals.SetCharset(CReader::FILE_CHARSET::UNDEFINE);

				const TCHAR wch = std::tolower(strTmp.at(0), std::locale()); // оставим только первую букву

				switch (wch)
				{
					case _T('k'):
						g_Globals.SetCharset(CReader::FILE_CHARSET::KOI8);
						break;

					case _T('o'):
						g_Globals.SetCharset(CReader::FILE_CHARSET::CP866);
						break;

					case _T('w'):
						g_Globals.SetCharset(CReader::FILE_CHARSET::CP1251);
						break;

					case _T('8'):
						g_Globals.SetCharset(CReader::FILE_CHARSET::UTF8);
						break;

					case _T('u'):
						g_Globals.SetCharset(CReader::FILE_CHARSET::UTF16LE);
						break;
				}

			}
			break;

			case _T('r'):	// флаг, формат выходного файла raw
				// если задан флаг делать sav, то этот флаг игнорируется
				if (!(BKTurbo8::m_flags & BKT_FLAG_MAKESAV))
				{
					BKTurbo8::m_flags |= BKT_FLAG_MAKERAW;
				}

				break;

			case _T('x'):	// флаг, формат выходного файла sav
				// отменяет флаг raw, если он был
				BKTurbo8::m_flags |= BKT_FLAG_MAKESAV;
				BKTurbo8::m_flags &= ~BKT_FLAG_MAKERAW;
				break;

			case _T('l'):	// опция - создавать листинг и задать его имя
				BKTurbo8::m_flags |= BKT_FLAG_MAKELISTING;
				// если аргумент не задан, то именем листинга будет имя входного файла
				if (optarg)
				{
					BKTurbo8::m_strListingFilename = strUtil::makePath(std::_tstring(optarg));
				}

				break;

			case _T('o'):	// опция - создавать объектный файл и задать его имя
				BKTurbo8::m_flags |= BKT_FLAG_MAKEOBJECT;
				// если аргумент не задан, то именем объектного файла будет имя входного файла
				if (optarg)
				{
					BKTurbo8::m_strObjectFileName = strUtil::makePath(std::_tstring(optarg));
				}

				break;

			case _T('t'):	// опция - создавать отдельный файл таблицы меток и задать его имя
				BKTurbo8::m_flags |= BKT_FLAG_MAKETABLE;
				// если аргумент не задан, то имя создаётся из имени входного файла
				if (optarg)
				{
					BKTurbo8::m_strTableFileName = strUtil::makePath(std::_tstring(optarg));
				}

				break;

			case _T('s'):	// задать адрес начала компиляции
				if (optarg) // аргумент требуется, но если его нет - то нет
				{
					const auto str = std::_tstring(optarg);
					const int nTmp = g_Globals.StrToOct(str) & 0xffff;

					if (0 <= nTmp && nTmp <= HIGH_BOUND)
					{
						BKTurbo8::m_nStartAddress = nTmp;
					}
				}

				break;

			case _T('I'):	// задать тип таблицы кои8 для входного файла
			{
				const auto strTmp = (optarg) ? std::_tstring(optarg) : _T("");
				if (!strTmp.empty())
				{
					switch (strTmp.at(0))
					{
						case _T('0'):
							g_inputKOI8 = &g_koi8tblBK10;
							break;
						case _T('1'):
							g_inputKOI8 = &g_koi8tblBK11M;
							break;
						case _T('2'):
							g_inputKOI8 = &g_koi8tbl_RFC1489;
							break;
					}
					// неверный аргумент игнорируем
				}
			}
				break;
			case _T('O'):	// задать тип таблицы кои8 для выходного файла
			{
				const auto strTmp = (optarg) ? std::_tstring(optarg) : _T("");
				if (!strTmp.empty())
				{
					switch (strTmp.at(0))
					{
						case _T('0'):
							g_outputKOI8 = &g_koi8tblBK10;
							break;
						case _T('1'):
							g_outputKOI8 = &g_koi8tblBK11M;
							break;
						case _T('2'):
							g_outputKOI8 = &g_koi8tbl_RFC1489;
							break;
					}
					// неверный аргумент игнорируем
				}
			}
				break;
		}
	}

	argc -= optind;
	argv += optind;

	if (argc < 1)
	{
		std::cout << "Ошибка в командной строке: отсутствует команда.\n\n";
		BKTurbo8::Usage();
		return BKT8_ERROR_CMDLINE;
	}

	std::_tstring strCommand = strUtil::strToLower(std::_tstring(*argv).substr(0, 2));

	if (strCommand == _T("li"))
	{
		BKTurbo8::m_operation = OP_MODE::LINK;
	}
	else if (strCommand == _T("co"))
	{
		BKTurbo8::m_operation = OP_MODE::COMP;
	}
	else if (strCommand == _T("cl"))
	{
		BKTurbo8::m_operation = OP_MODE::CL;
	}
	else
	{
		std::cout << "Ошибка в командной строке: неопознанная команда.\n\n";
		BKTurbo8::Usage();
		return BKT8_ERROR_CMDLINE;
	}

	argc--;
	argv++;

	if (argc < 1)
	{
		std::cout << "Ошибка в командной строке: не хватает аргументов после команды.\n\n";
		BKTurbo8::Usage();
		return BKT8_ERROR_CMDLINE;
	}

	switch (BKTurbo8::m_operation)
	{
		case OP_MODE::LINK:
		{
			BKTurbo8::m_strResultFile = fs::path(*argv);
			argc--;
			argv++;

			if (argc < 1)
			{
				std::cout << "Ошибка в командной строке: не хватает аргументов после команды LI.\n\n";
				BKTurbo8::Usage();
				return BKT8_ERROR_CMDLINE;
			}

			std::cout << "Линковка...\nСоздаётся файл: '";
			std::_tcout <<  BKTurbo8::m_strResultFile._tstring();
			std::cout << "'\n";

			if (BKTurbo8::m_nStartAddress != -1) // если в командной строке был задан адрес загрузки
			{
				// то применим его
				g_Globals.SetStartAddress(BKTurbo8::m_nStartAddress);
				g_Globals.SetLinkerAddr(BKTurbo8::m_nStartAddress); // а адреса объектных модулей будем игнорировать
			}
		}
		break;

		case OP_MODE::COMP:
			if (BKTurbo8::m_nStartAddress != -1) // если в командной строке был задан адрес загрузки
			{
				// то применим его
				g_Globals.SetStartAddress(BKTurbo8::m_nStartAddress);
			}

			break;
	}

	bool bRes = true;
	bool bWorked = false;
	int nErrors = 0;

	for (; argc > 0; argc--, argv++) // теперь, пока аргументы не кончатся - обрабатывать
	{
		// достанем из аргумента путь, если он есть
		fs::path filePath = g_Globals.GetBasePath(); // предполагаем базовый путь
		fs::path strArg = strUtil::makePath(std::_tstring(*argv)); // преобразуем в путь аргумент

		if (strArg.is_absolute()) // абсолютный путь задан?
		{
			filePath = strArg.parent_path(); // да - тогда берём его
		}
		else if (strArg.is_relative() && strArg.has_parent_path()) // относительный путь есть?
		{
			filePath /= strArg.parent_path(); // да - тогда присоединяем его к базовому
		}

		std::_tstring mask = strArg.filename()._tstring();
		mask = ReplaceString(mask, _T("."), _T("[.]"));
		mask = ReplaceString(mask, _T("*"), _T(".*"));
		mask = ReplaceString(mask, _T("?"), _T("."));
		std::_tregex fileMask(mask, std::regex_constants::icase);
		std::error_code ec;
		fs::directory_iterator dit(filePath, fs::directory_options::skip_permission_denied | fs::directory_options::follow_directory_symlink, ec);

		for (auto const &it : dit)
		{
			const fs::path &file = it.path(); // имя найденного файла

			// обрабатываем только файлы, директории игнорируем
			if (fs::is_regular_file(file) && std::regex_match(file.filename()._tstring(), fileMask))
			{
				if (BKTurbo8::m_operation == OP_MODE::LINK)
				{
					bWorked = true;
					std::cout << "Линкуется объектный файл: ";
					std::_tcout << file.filename();
					std::cout << " ";
					bRes = ObjManger::LoadObj(file);

					if (bRes)
					{
						std::cout << "- OK\n";
						LabelManager::LabelLinking();
						g_Globals.GrowLinkObjLength(); // это надо делать только после линковки.
					}
					else
					{
						std::cout << "- Ошибка\n";
						nErrors = BKT8_ERROR_OPENOBJ;
						break;
					}
				}
				else if (BKTurbo8::m_operation == OP_MODE::COMP || BKTurbo8::m_operation == OP_MODE::CL)
				{
					bWorked = true;
					nErrors = BKTurbo8::workCycle(file);
					g_Globals.ReInitGlobalParameters();
					// чистим все векторы.
					g_RefsTable.Clear();
					g_labelGlobalDefs.Clear();
					g_labelLocalDefs.Clear();
					g_Listing.clear();
					g_ScriptDefs.clear();

					if (BKTurbo8::m_nStartAddress != -1) // если в командной строке был задан адрес загрузки
					{
						// то применим его
						g_Globals.SetStartAddress(BKTurbo8::m_nStartAddress);
					}
					else
					{
						g_Globals.SetStartAddress(DEFAULT_START_ADDRESS);
					}

					if (nErrors) // если случились ошибки
					{
						bRes = false; // то прервём цикл
					}
				}
			}
		}

		if (!bRes)
		{
			break;
		}
	}

	if (BKTurbo8::m_operation == OP_MODE::LINK && bRes)
	{
		printf("Адрес компоновки = %#6o\n", g_Globals.GetStartAddress());
		printf("Размер исполняемого файла = %#6o\n", g_Globals.GetProgramLength());
		// здесь применяем скрипты
		g_ScriptAsm.RunScript();
		BKTurbo8::SaveFile(BKTurbo8::m_strResultFile);

		if (BKTurbo8::m_flags & BKT_FLAG_MAKETABLE)
		{
			fs::path name = (BKTurbo8::m_strTableFileName.empty() ? BKTurbo8::m_strResultFile : BKTurbo8::m_strTableFileName).stem();
			name += fs::path(_T("_tbl"));
			ObjManger::MakeObj(name, m_pathExtArr[EXT_OBJ], true);
		}

		if (BKTurbo8::m_flags & BKT_FLAG_MAKEOBJECT)
		{
			ObjManger::MakeObj(BKTurbo8::m_strObjectFileName.empty() ? BKTurbo8::m_strResultFile : BKTurbo8::m_strObjectFileName, m_pathExtArr[EXT_OBJ]);
		}

		if (BKTurbo8::m_flags & BKT_FLAG_MAKELISTING)
		{
			ListingManager::MakeListing(BKTurbo8::m_strListingFilename.empty() ? BKTurbo8::m_strResultFile : BKTurbo8::m_strListingFilename, m_pathExtArr[EXT_LST]);
		}

		BKTurbo8::PrintLabelTable(BKTurbo8::m_strListingFilename.empty() ? BKTurbo8::m_strResultFile : BKTurbo8::m_strListingFilename, m_pathExtArr[EXT_LST]);
	}

	if (!bWorked)
	{
		std::cout << "Файлов для обработки не найдено.\n";
	}

	return nErrors;
}

int BKTurbo8::workCycle(const fs::path &strInFileName)
{
	g_Globals.PushReader(nullptr); // условие выхода из цикла
	g_pReader = new CReader(strInFileName, g_Globals.GetCharset());
	g_Globals.SetIncludePath(strInFileName.parent_path());
#if (_DEBUG && DEBUG_LABEL_MANAGER)
	LabelManager::DebugInit();
#endif

	if (!g_pReader)
	{
		std::cout << "Недостаточно памяти.\n";
		return BKT8_ERROR_NOMEM;
	}

	switch (m_operation)
	{
		case OP_MODE::COMP:
			std::cout << "Компиляция исполняемого файла...\n";
			// режим компиляции CO
			g_Globals.SetLinkMode(LINKING_MODE::CO);
			break;

		case OP_MODE::CL:
			std::cout << "Компиляция объектного файла...\n";
			// режим компиляции CL
			g_Globals.SetLinkMode(LINKING_MODE::CL);
			break;
	}

	std::cout << "Файл: '";
	std::_tcout << strInFileName._tstring();
	std::cout << "'\nАвтоопределение кодировки: ";

	switch (g_pReader->GetFileCharset())
	{
		case CReader::FILE_CHARSET::KOI8:
			std::cout << "КОИ8-Р\n";
			break;

		case CReader::FILE_CHARSET::CP866:
			std::cout << "OEM CP-866\n";
			break;

		case CReader::FILE_CHARSET::CP1251:
			std::cout << "ANSI CP-1251\n";
			break;

		case CReader::FILE_CHARSET::UTF8:
			std::cout << "UTF-8\n";
			break;

		case CReader::FILE_CHARSET::UTF16LE:
			std::cout << "UTF-16LE(Unicode)\n";
			break;

		case CReader::FILE_CHARSET::UNDEFINE:
			std::cout << "Не определена. Возможно, это не текстовый файл.\n";
			return BKT8_ERROR_UNDEFCODE;

		default:
			std::cout << "\rОшибка открытия файла.     \n";
			return BKT8_ERROR_OPENSRC;
	}

	do
	{
		// первый проход
		while (!g_Globals.isENDReached() && !g_pReader->isEOF())
		{
			ParseLine();

			if (g_Globals.GetPC() >= HIGH_BOUND)
			{
				std::cout << "Критическая ошибка:: Достигнут предел свободной памяти!\n";
				break;
			}
		}

		if (!g_Globals.isENDReached())
		{
			if (g_Globals.isInInclude())
			{
				g_Globals.SetInInclude(false);
			}
			else
			{
				ErrorManager::OutError(ERRNUM::E_118, false); // Отсутствует .END
			}
		}

		delete g_pReader; // удаляем текущий экземпляр
		g_pReader = g_Globals.PopReader(); // достаём предыдущий, если есть
	}
	while (g_pReader);   // и продолжаем, пока есть что обрабатывать

	const int nErr = ErrorManager::IsError();

	if (nErr)
	{
		// если были ошибки - одно действие
		std::cout << "Количество ошибок: " << nErr << std::endl;
	}
	else
	{
		// если не было ошибок - другое
		if (m_operation == OP_MODE::CL)
		{
			ObjManger::MakeObj(m_strObjectFileName.empty() ? strInFileName : m_strObjectFileName, m_pathExtArr[EXT_OBJ]);
		}
		else
		{
			_tprintf(_T("Адрес компоновки = %#6o\n"), g_Globals.GetStartAddress());
			// здесь применяем скрипты
			g_ScriptAsm.RunScript();

			if (m_flags & BKT_FLAG_MAKEOBJECT)
			{
				ObjManger::MakeObj(m_strObjectFileName.empty() ? strInFileName : m_strObjectFileName, m_pathExtArr[EXT_OBJ]);
			}

			SaveFile(strInFileName);
		}

		_tprintf(_T("Размер исполняемого файла = %#6o\n"), g_Globals.GetProgramLength());
	}

	// листинг делаем в любом случае

	if (m_flags & BKT_FLAG_MAKELISTING)
	{
		ListingManager::MakeListing(m_strListingFilename.empty() ? strInFileName : m_strListingFilename, m_pathExtArr[EXT_LST]);
	}

	PrintLabelTable(m_strListingFilename.empty() ? strInFileName : m_strListingFilename, m_pathExtArr[EXT_LST]);

	// если создаём файл таблицы меток, то нужно вычислить там выражения в определениях
	if (!nErr && (m_flags & BKT_FLAG_MAKETABLE))
	{
		fs::path name = (m_strTableFileName.empty() ? strInFileName : m_strTableFileName).stem();
		name += L"_tbl";

		// если режим объектного файла,
		// здесь делаем полную компоновку, чтобы получить полную таблицу глобальных меток и вычисленных определений
		if (m_operation == OP_MODE::CL)
		{
			// это портит таблицы меток, поэтому этот функционал переехал в самый конец
			g_Globals.SetLinkMode(LINKING_MODE::FINAL);
			LabelManager::LabelLinking();
		}

		ObjManger::MakeObj(name, m_pathExtArr[EXT_OBJ], true);
	}

	return nErr;
}


/*
Пропускаем остаток строки.
Выход: true - встретился конец файла
false - просто конец строки.
*/
bool BKTurbo8::SkipTailString(wchar_t &ch)
{
	while (!g_pReader->isEOF() && g_pReader->getCurrCharType() != CReader::CHAR_TYPE::LN)
	{
		ch = g_pReader->readChar();
	}

	return g_pReader->isEOF();
}

/*
*Парсим строку, в строке может быть неограниченное число ассемблерных команд.
*Перед командой может быть неограниченное число меток.
*Конец файла тоже считается концом строки, затем надо делать конкретную проверку на именно конец файла.
g_pReader->m_nLineNum
*/
void BKTurbo8::ParseLine()
{
	CBKToken token;
	int cp = g_Globals.GetPC();    // текущий временный PC, который приращивается во время построения инструкции или псевдокоманды
	ListingManager::PrepareLine(cp);
	wchar_t ch = g_pReader->readChar(); // читаем первый символ

	for (;;) // крутимся в цикле, пока не достигнем конца строки
	{
		// делаем так, чтобы можно было обрабатывать несколько инструкций в одной строке
		g_Globals.SetInString(false); // мы не внутри текстовой строки
		g_Globals.SetArithmeticType(0); // сбрасываем флаги арифметической операции
		g_Globals.SetPC(cp); // обычно сюда попадаем перед началом очередной команды, поэтому сохраняем счётчик.

		if (g_pReader->SkipWhitespacesLN(ch))  // пропустим возможные пробелы
		{
			// если после чтения получился конец строки,
			break;  // то выход из цикла.
		}

		// обрабатываем многострочный комментарий /*...*/
		// нормально парсится инлайн коммент между инструкциями.
		// комментарий не может быть внутри инструкции, т.к. такой парсинг не реализован
		if (ch == L'/' && g_pReader->getNextChar() == L'*')
		{
			bool bExit = false;
			ListingManager::AddPrepareLine(0, ListType::LT_COMMENTARY);

			do  // цикл, пока не найдём конец комментария или конец файла
			{
				ch = g_pReader->readChar();

				// если внутри комментария конец строки
				if (g_pReader->getCurrCharType() == CReader::CHAR_TYPE::LN)
				{
					ListingManager::PrepareLine(cp);    // в листинге сформируем новую строку
					ListingManager::AddPrepareLine(0, ListType::LT_COMMENTARY);
				}

				if (g_pReader->isEOF()) // если конец файла
				{
					bExit = true; // выход
					break;
				}
			}
			while (!(ch == L'*' && g_pReader->getNextChar() == L'/'));

			if (bExit)
			{
				break; // если конец файла - выход из цикла разбора строки
			}

			// '*' уже прочитали,
			ch = g_pReader->readChar(); // читаем /
			ch = g_pReader->readChar(); // и читаем следующий символ
			continue;
		}

		// теперь обрабатываем строку
		if (ch == L';' || (ch == L'/' && g_pReader->getNextChar() == L'/')) // если комментарий ";" или "// "
		{
			SkipTailString(ch); // пропускаем всё до конца строки
			ListingManager::AddPrepareLine(0, ListType::LT_COMMENTARY);
			break; // выходим из цикла, независимо от результата
		}

		if (ch == L'.') // если псевдокоманда или точка
		{
			// обработка псевдокоманды
			ch = g_pReader->readChar(); // читаем следующий за точкой символ.

			if (g_Globals.isInScript())
			{
				goto l_Script;
			}
			else // тут парсим обычный ассемблер
			{
				// присваивание значения точке (не работает в объектных файлах)
				if (Parser::needChar(L'=', ch))
				{
					ListingManager::AddPrepareLine(cp, ListType::LT_DOTASSIGN);
					ch = g_pReader->readChar(); // читаем следующий символ.

					if (ch == L'=')  // если у нас "=="
					{
						// то для точки это не должно применяться, просто игнорируем
						ch = g_pReader->readChar(); // читаем следующий символ.
					}

					int result = 0;
					ERRNUM errret{};

					if ((errret = g_RPNParser.FullArithmeticParser(ch)) == ERRNUM::E_OK) // если арифметическое выражение распарсилось
					{
						if (g_RPNParser.CalcRpn(result)) // если смогли высчитать результат
						{
							cp = g_Globals.CorrectOffset(result); // то это будет новый PC.
							g_Globals.SetPC(cp); // !!! Вот это вот не работает в режиме CL
						}
						else
						{
							// встретились неопределённые метки.
							// неопределённые метки запрещены.
							ErrorManager::OutError(ERRNUM::E_119);
							SkipTailString(ch);
							break;
						}

						ListingManager::AddPrepareLine2(result);
					}
					else
					{
						ErrorManager::OutError(errret);
						// если ошибка в арифметическом выражении, то
						// всё пропускаем до конца строки. потому что не знаем, где там конец выражения
						// и откуда начинать искать что-то полезное.
						SkipTailString(ch);
						break;
					}
				}
				else
				{
l_Script:

					if (!g_pReader->readToken(&token, ch)) // и читаем лексему
					{
						// если токен не прочёлся, значит сразу за точкой встретился посторонний символ.
						ErrorManager::OutError(ERRNUM::E_103);
						SkipTailString(ch);
						break;
					}

					if (!Assemble::PseudoCommandExec(&token, cp, ch))
					{
						// если встретилась ошибка, пропустим всю строку
						SkipTailString(ch);
					}
					else
					{
						if (g_Globals.isStepInclude())
						{
							g_Globals.SetStepInclude(false);
							break;
						}
					}

					ListingManager::AddPrepareLine2(cp);
				}
			}
		}
		else // дальше что-то, либо метка, либо присваивание, либо команда.
		{
			if (ch == L'%') // если присваивание синонима регистру.
			{
				if (g_pReader->readTokenR(&token, ch)) // Читаем имя регистра
				{
					// тут надо реализовать механизм присваивания
					if (Parser::needChar(L'=', ch)) // если за лексемой, возможно через пробелы идёт '=',
					{
						ListingManager::AddPrepareLine(cp, ListType::LT_REGASSIGN);
						// то это присваивание
						ch = g_pReader->readChar(true); // пропустим =

						if (ch == L'=')  // если у нас "=="
						{
							// то для синонима это не должно применяться, просто игнорируем
							ch = g_pReader->readChar(); // читаем следующий символ.
						}

						// синоним должен начинаться с буквы
						if (g_pReader->getCurrCharType() == CReader::CHAR_TYPE::LETTERS)
						{
							CBKToken synonym;

							if (g_pReader->readToken(&synonym, ch))
							{
								if (Parser::AddRegSynonym(&token, &synonym))
								{
									const int num = token.getName().at(1) - L'0'; // номер регистра
									ListingManager::AddPrepareLine2(num);
									continue; // продолжим обработку.
								}

								ErrorManager::OutError(ERRNUM::E_111); // неверное имя регистра
								SkipTailString(ch);
								break;
							}
						}

						// неверное имя синонима
					}

					// синтаксическая ошибка
				}

				// если токен не прочёлся, значит сразу встретился посторонний символ.
				ErrorManager::OutError(ERRNUM::E_103); // Недопустимый символ в строке.
				SkipTailString(ch);
				break;
			}

			if (!g_pReader->readToken(&token, ch)) // считаем это лексемой
			{
				// если токен не прочёлся, значит сразу встретился посторонний символ.
				ErrorManager::OutError(ERRNUM::E_103); // Недопустимый символ в строке.
				SkipTailString(ch);
				break;
			}

			if (g_Globals.isInScript())
			{
				ListingManager::AddPrepareLine(cp, ListType::LT_COMMENTARY);
				// тут парсим скрипт
				CBKToken scriptLbl, scriptCmd;

				// если сразу за лексемой идёт двоеточие
				if (ch == L':') // значит это метка.
				{
					ch = g_pReader->readChar();         // пропускаем ':'
					// добавим имя метки в строку скрипта
					scriptLbl = token;
					g_pReader->SkipWhitespaces(ch);
				}
				else
				{
					// значит это строка команды
					scriptCmd = token;
				}

				std::wstring scriptCmd2;
				// теперь надо прочитать всё либо до конца строки, либо до комментария.
				// потому как допускаем пустые строки и просто метку в строке без команды
				const auto htype = CBKToken(L"$TYPE").getHash();
				bool bPrevInString = false; // для команды $type надо вернуть регистрозависимость
				// однако тогда и аргумент будет регистрозависимый.

				if (scriptCmd.getHash() == htype)
				{
					bPrevInString = g_Globals.isInString();
					g_Globals.SetInString(true); // отменим преобразование регистра
				}

				for (;;)
				{
					if (g_pReader->getCurrCharType() == CReader::CHAR_TYPE::LN)
					{
						break;
					}

					if (Assemble::CheckComment(ch))  // если комментарий - то выходим, его тут не обработать
					{
						break;
					}

					scriptCmd2.push_back(ch);
					ch = g_pReader->readChar();
				}

				if (scriptCmd.getHash() == htype)
				{
					g_Globals.SetInString(bPrevInString); // отменим преобразование регистра
				}

				g_ScriptAsm.AddScriptLine(ScriptLine_t{ CBKToken(scriptLbl), CBKToken(scriptCmd.getName() + scriptCmd2) });
				ListingManager::AddPrepareLine2(cp);
			}
			else // тут парсим обычный ассемблер
			{
				// если сразу за лексемой идёт двоеточие
				if (ch == L':') // значит это метка.
				{
					ch = g_pReader->readChar(true);         // пропускаем ':' и пустоту за ним
					const wchar_t wch = token.getName().at(0);    // определяем тип метки.
					// если метка начинается с буквы - это глобальная метка
					// если с цифры - то это локальная метка.
					// остальные символы - любые допустимые.
					bool bl = false;

					if (g_pReader->AnalyzeChar(wch) == CReader::CHAR_TYPE::DIGITS)
					{
						// локальная метка
						bl = LabelManager::AddLocalLabel(&token, g_Globals.GetPC(), LBL_LOCAL);
					}
					else
					{
						// глобальная метка
						bl = LabelManager::AddGlobalLabel(&token, g_Globals.GetPC(), LBL_GLOBAL);
					}

					ListingManager::AddPrepareLine(cp, ListType::LT_LABEL);

					if (!bl) // если метка не добавилась в таблицу
					{
						ErrorManager::OutError(ERRNUM::E_113); // то ошибка - повторное определение метки
					}
				}
				else if (Parser::needChar(L'=', ch)) // если за лексемой, возможно через пробелы идёт '=',
				{
					// то это присваивание
					ch = g_pReader->readChar(); // пропустим =
					bool bAbsolute = false;

					if (ch == L'=')         // если у нас конструкция "=="
					{
						// то, это абсолютное присваивание, которое не надо корректировать
						ch = g_pReader->readChar(); // пропустим второе =
						bAbsolute = true;
					}

					g_pReader->SkipWhitespaces(ch);
					int result = 0;
					ListingManager::AddPrepareLine(cp, ListType::LT_ASSIGN);
					g_Globals.SetArithmeticType(ARITHM_NOLOCLABEL);
					ERRNUM errret{};

					if ((errret = g_RPNParser.FullArithmeticParser(ch)) == ERRNUM::E_OK) // если арифметическое выражение распарсилось
					{
						if (g_RPNParser.GetNamesNum()) // если там были имена меток или других определений
						{
							// добавляем метку без значения, обозначим, что оно ещё не вычислилось
							// без удаления локальных меток
							uint32_t flg = LBL_WEAKDEFINE;

							if (bAbsolute)
							{
								flg |= LBL_ABSVALUE;
							}

							if (!g_labelGlobalDefs.AddLabel(&token, 0, flg))
							{
								ErrorManager::OutError(ERRNUM::E_113); // если не добавилось - ошибка. уже есть такая метка
							}

							// а в таблицу ссылок добавим невычисленную цепочку выражения
							flg = ARL_DEFINE;

							if (bAbsolute)
							{
								flg |= ARL_ABS;
							}

							LabelManager::AddLabelReference(&token, 0, flg);
						}
						else if (g_RPNParser.CalcRpn(result)) // были только числа - вычисляем.
						{
							// добавляем метку со значением, без удаления локальных меток
							uint32_t flg = LBL_DEFINE;

							if (bAbsolute)
							{
								flg |= LBL_ABSVALUE;
							}

							if (!g_labelGlobalDefs.AddLabel(&token, result, flg))
							{
								ErrorManager::OutError(ERRNUM::E_113); // если не добавилось - ошибка. уже есть такая метка
							}
						}
						else
						{
							assert(false); // такого быть не должно, надо разбираться.
						}

						const int n = g_labelGlobalDefs.SearchLabel(&token);
						// для определения передаём номер элемента в таблице,
						// потом его оттуда прочитаем, если он добавился, если нет - будет 0
						ListingManager::AddPrepareLine2(n);
					}
					else
					{
						ErrorManager::OutError(errret);
						SkipTailString(ch);
						break;
					}
				}
				else
				{
					g_pReader->SkipWhitespaces(ch); // пропускаем пробелы - на всякий случай

					// обработка ассемблерной команды.
					if (!Assemble::AssembleCPUInstruction(&token, cp, ch))
					{
						// assert(false);
						// если встретилась ошибка, пропустим всю строку
						SkipTailString(ch);
					}

					ListingManager::AddPrepareLine2(cp);
				}
			}
		}
	}
}

// Вход: strName - имя создаваемого файла
void BKTurbo8::SaveFile(const fs::path &pathName)
{
	fs::path pathExt = pathName.extension(); // выделим расширение у входного файла

	// если получается очень большой файл
	if (g_Globals.GetProgramLength() >= HIGH_BOUND)
	{
		m_flags |= BKT_FLAG_MAKERAW; // то бин делать никак нельзя
	}

	// оставим имя файла без расширения
	fs::path pathOutName = pathName;
	// если у файла стандартное расширение, то его убираем, остальные - оставляем
	auto strTmpExt = strUtil::strToLower(pathExt._tstring());

	if (strTmpExt == _T(".mac") || strTmpExt == _T(".asm") || strTmpExt == _T(".obj"))
	{
		pathOutName.replace_extension();
	}

	// сделаем новое имя файла - добавим расширение бин или рав
	pathOutName += m_pathExtArr[m_flags & BKT_FLAG_MAKESAV ? EXT_SAV : m_flags & BKT_FLAG_MAKERAW ? EXT_RAW : EXT_BIN];
	FILE *of = _tfopen(pathOutName.c_str(), _T("wb"));

	if (of)
	{
		// если sav файл
		if (m_flags & BKT_FLAG_MAKESAV)
		{
			constexpr auto BLOCK_SIZE = 512;
			// запишем sav
			std::array<uint8_t, BLOCK_SIZE> sav{};
			CreateSAVHdr(sav); // генерируем заголовок
			fwrite(sav.data(), 1, sav.size(), of); // записываем заголовок
			// теперь заполним пустое место перед адресом запуска и адресом 01000,
			// если такое есть.
			sav.fill(0);
			const uint16_t nStartAddr = g_Globals.GetStartAddress();
			uint16_t a = BLOCK_SIZE * 2;

			while (a < nStartAddr)
			{
				fwrite(sav.data(), 1, sav.size(), of); // записываем пустой блок
				a += BLOCK_SIZE;
			}

			// а теперь, если адрес запуска не кратен BLOCK_SIZE
			// сделаем кусок блока, а за ним запишем основное тело программы
			a -= BLOCK_SIZE;
			const uint16_t tail = nStartAddr - a;

			if (tail)
			{
				fwrite(sav.data(), 1, tail, of);
			}
		}
		else
		{
			// если не sav, то может быть raw или bin
			if (!(m_flags & BKT_FLAG_MAKERAW)) // если bin, то запишем заголовок
			{
				std::array<uint16_t, 2> bin
				{
					static_cast<uint16_t>(g_Globals.GetStartAddress()),
					static_cast<uint16_t>(g_Globals.GetProgramLength())
				};
				fwrite(bin.data(), sizeof(uint16_t), bin.size(), of);
			}
		}

		fwrite(&g_Memory.b[BASE_ADDRESS], 1, g_Globals.GetProgramLength(), of);
		fclose(of);
	}
	else
	{
		std::cout << "Ошибка создания файла '";
		std::_tcout << pathOutName._tstring();
		std::cout << "'\n";
	}
}


constexpr auto DEFAULT_LABEL_MAXLEN = 16; // максимальная длина метки по умолчанию
constexpr auto STRADDR_LEN = 12;        // 12 - это 10 символов на метку + 2 пробела между столбцами
constexpr auto SCREENLINE_LEN = 82; // длина строки экрана


// вывод на экран таблицы меток.
void BKTurbo8::PrintLabelTable(const fs::path &pathName, const fs::path &pathExt)
{
	// сделаем новое имя файла
	fs::path pathOutName = pathName;
	pathOutName.replace_extension(pathExt);
	FILE *of = nullptr;

	if (m_flags & BKT_FLAG_MAKELISTING)
	{
		of = _tfopen(pathOutName.c_str(), _T("a+t"));
	}

	// выведем таблицу глобальных меток.
	std::wstring wstrOut = L"\nГлобальные метки\n\n";

	if (g_Globals.isVerbosity())
	{
		std::_tcout << strUtil::_t_ws2s(wstrOut);
	}

	if (of)
	{
		ListingManager::OutString(of, &wstrOut);
	}

	size_t sz = g_labelGlobalDefs.getSize();
	size_t maxlen = 0;

	// сперва найдём самую длинную метку
	for (size_t i = 0; i < sz; ++i)
	{
		const auto &name = g_labelGlobalDefs.GetLabel(i)->getName();

		if (name.length() > maxlen)
		{
			maxlen = name.length();
		}
	}

	// теперь сформируем строку формата
	if (maxlen > DEFAULT_LABEL_MAXLEN)
	{
		maxlen = DEFAULT_LABEL_MAXLEN;
	}

	int n = SCREENLINE_LEN / (int(maxlen) + STRADDR_LEN); // вот столько влазит в экран
	int j = n;

	for (size_t i = 0; i < sz; ++i)
	{
		auto name = g_labelGlobalDefs.GetLabel(i)->getName();
		int value = g_labelGlobalDefs.GetValue(i);

		if ((g_labelGlobalDefs.GetType(i) & LBL_DEFINITE_MASK) == LBL_GLOBAL)
		{
			value = g_Globals.GetRealAddress(value);
		}

		if ((g_labelGlobalDefs.GetType(i) & LBL_DEFINITE_MASK) == LBL_WEAKDEFINE)
		{
			name = L"*" + name;
		}

		std::wstring strEq = L"=";

		if ((g_labelGlobalDefs.GetType(i) & LBL_FLAGS_MASK) == LBL_ABSVALUE)
		{
			strEq.push_back(L'='); // это обозначение абсолютного присваивания
		}

		if (name.length() > maxlen)
		{
			// если метка очень длинная, то её только одну в строке выведем.
			wstrOut = std::format(L"{0:} {1:} {2:07o}\n", name, strEq, (value & 0xffff));

			if (g_Globals.isVerbosity())
			{
				std::_tcout << strUtil::_t_ws2s(wstrOut);
			}

			if (of)
			{
				ListingManager::OutString(of, &wstrOut);
			}

			j = n;
		}
		else
		{
			wstrOut = std::format(L"{:<{}s} {:} {:07o}", name, int(maxlen), strEq, (value & 0xffff));

			if (g_Globals.isVerbosity())
			{
				std::_tcout << strUtil::_t_ws2s(wstrOut);
			}

			if (of)
			{
				ListingManager::OutString(of, &wstrOut);
			}

			if (--j <= 0)
			{
				j = n;

				if (g_Globals.isVerbosity())
				{
					std::cout << std::endl;
				}

				if (of)
				{
					fprintf(of, "\n");
				}
			}
			else
			{
				if (g_Globals.isVerbosity())
				{
					std::cout << "  ";
				}

				if (of)
				{
					fprintf(of, "  ");
				}
			}
		}
	}

	// а теперь выведем оставшиеся ссылки на не найденные метки
	sz = g_RefsTable.getSize();

	if (sz)
	{
		wstrOut = L"\n\nСсылки на неопределённые метки!\n\n";
		std::_tcout << strUtil::_t_ws2s(wstrOut);

		if (of)
		{
			ListingManager::OutString(of, &wstrOut);
		}

		maxlen = 0;

		// сперва найдём самую длинную метку
		for (size_t i = 0; i < sz; ++i)
		{
			RPNChain_t chain = g_RefsTable.GetElement(i).getRPN();

			for (auto &cn : chain)
			{
				if ((cn.type == A_TYPE::LABEL) || ((cn.type == A_TYPE::LOC_LABEL) && (int(cn.number) < 0)))
				{
					const auto &name = cn.token.getName();

					if (name.length() > maxlen)
					{
						maxlen = name.length();
					}
				}
			}
		}

		// теперь сформируем строку формата
		if (maxlen > DEFAULT_LABEL_MAXLEN)
		{
			maxlen = DEFAULT_LABEL_MAXLEN;
		}

		j = n = SCREENLINE_LEN / (int(maxlen) + STRADDR_LEN); // вот столько влазит в экран

		for (size_t i = 0; i < sz; ++i)
		{
			RPNChain_t chain = g_RefsTable.GetElement(i).getRPN();

			for (auto &cn : chain)
			{
				if (cn.type == A_TYPE::LABEL || ((cn.type == A_TYPE::LOC_LABEL) && (int(cn.number) < 0)))
				{
					const auto &name = cn.token.getName();
					int value = g_RefsTable.GetElement(i).getAddress();
					value = g_Globals.GetRealAddress(value);

					if (name.length() > maxlen)
					{
						// если метка очень длинная, то её только одну в строке выведем.
						wstrOut = std::format(L"{0:07o} : {1:}\n", (value & 0xffff), name);
						std::_tcout << strUtil::_t_ws2s(wstrOut);

						if (of)
						{
							ListingManager::OutString(of, &wstrOut);
						}

						j = n;
					}
					else
					{
						wstrOut = std::format(L"{:07o} : {:<{}s}", (value & 0xffff), name, int(maxlen));
						std::_tcout << strUtil::_t_ws2s(wstrOut);

						if (of)
						{
							ListingManager::OutString(of, &wstrOut);
						}

						if (--j <= 0) // если вывели всё в строку, то перевод строки
						{
							j = n;
							std::cout << std::endl;

							if (of)
							{
								fprintf(of, "\n");
							}
						}
						else if (j >= 1) // иначе, 2 пробела между столбцами, кроме последнего столбца
						{
							std::cout << "  ";

							if (of)
							{
								fprintf(of, "  ");
							}
						}
					}
				}
			}
		}
	}

	std::cout << std::endl;

	if (of)
	{
		fprintf(of, "\n"); fclose(of);
	}
}

// сгенерируем заголовок SAV
void BKTurbo8::CreateSAVHdr(std::span<uint8_t> sav)
{
	constexpr int START_ADDR = 040;
	constexpr int TOP_STACK = 042;
	constexpr int PSW = 044;
	constexpr int TOP_ADDR = 050;

	// обнуляем массив
	for (auto &i : sav)
	{
		i = 0;
	}

	auto sav_w = reinterpret_cast<uint16_t *>(sav.data());
	const uint16_t nStartAddr = g_Globals.GetStartAddress();
	const uint16_t nProgramLength = g_Globals.GetProgramLength();
	const uint16_t nEndAddr = nStartAddr + nProgramLength;
	sav_w[START_ADDR / 2] = nStartAddr;
	sav_w[TOP_STACK / 2] = 01000; // Вершина стека
	sav_w[PSW / 2] = 0; // PSW
	sav_w[TOP_ADDR / 2] = (nEndAddr + 1) & 0177776; //выравниваем до слова
	// теперь заполним битовую карту.
	int p = 0360; // начальный байт
	uint8_t m = 0200; // начальная маска
	uint16_t a = 0; // граничный адрес

	// участок от 0 до стартового адреса
	while (a < nStartAddr)
	{
		a += 512;
		m >>= 1;

		if (m == 0)
		{
			m = 0200; p++;
		}
	}

	// если адрес запуска не кратен 512, шаг назад
	if (a > nStartAddr)
	{
		a -= 512;
		m <<= 1;

		if (m == 0)
		{
			m = 1; p--;
		}
	}

	sav[0360] |= 0200; // заголовок sav всегда загружается
	// теперь участок программы
	const uint16_t nEvenEndAddr = (nEndAddr + 0777) & 0177000; // выровняем по границе 512 байтов

	while (a < nEvenEndAddr)
	{
		sav[p] |= m;
		a += 512;
		m >>= 1;

		if (m == 0)
		{
			m = 0200; p++;
		}
	}

	g_Globals.SetProgramLength(nEvenEndAddr - nStartAddr);
}

void BKTurbo8::Usage()
{
	std::_tcout << (_T(FILE_PRODUCT_STR"\n") \
	                _T("(с) 2016-2026 gid\n\n") \
	                _T("Использование:\n") \
	                _T("BKTurbo8 -h (--help)\n") \
	                _T("  Вывод этой справки.\n\n") \
	                _T("BKTurbo8 -v (--version)\n") \
	                _T("  Показать информацию о версии.\n\n") \
	                _T("Имеется два режима работы. Режим компиляции, с созданием объектных модулей\n") \
	                _T("и исполняемого файла. И режим линковки объектных модулей.\n\n") \
	                _T("1. Режим компиляции.\n") \
	                _T("BKTurbo8 [-i<c>][-I<n>][-O<n>][-d][-r|-x][-l[name]][-o[name]][-s<0addr>] <cmd> <file_1 *[ file_n]>\n") \
	                _T("  -i<c> (--input <c>) - задать кодировку исходного файла.\n") \
	                _T("      Возможные кодировки:\n") \
	                _T("        a - автоопределение (по умолчанию)\n") \
	                _T("        k - KOI8-R\n") \
	                _T("        o - OEM CP866\n") \
	                _T("        w - ANSI CP1251\n") \
	                _T("        8 - UTF8\n") \
	                _T("        u - UNICODE UTF16LE\n") \
	                _T("  Если автоопределение определило кодировку некорректно, необходимо задать\n") \
	                _T("верную кодировку данным ключом.\n\n") \
					_T("  -I<n> (--InputKOI <n>) - задать таблицу КОИ-8 для входного файла (ключ имеет\n") \
					_T("      смысл только когда задана или автоопределена кодировка входного исходного\n") \
					_T("      файла KOI8-R)\n") \
					_T("  -O<n> (--OutputKOI <n>) - задать таблицу КОИ-8 для выходных файлов - листинга\n") \
					_T("      объектного и бинарного.\n") \
					_T("      Возможные значения:\n") \
					_T("        0 - кодовая таблица КОИ8 БК0010  (по умолчанию для выходных файлов)\n") \
					_T("        1 - кодовая таблица КОИ8 БК0011М\n") \
					_T("        2 - кодовая таблица КОИ8 RFC1489 (по умолчанию для входных файлов)\n\n") \
	                _T("  -d (--details) - вывод большего количества информации на экран.\n") \
	                _T("      На данный момент дополнительно выводится таблица меток программы.\n\n") \
	                _T("  -r (--raw) - создавать просто бинарный массив, не использовать формат .bin.\n\n") \
	                _T("  -x (--sav) - создавать файл в формате sav. Этот ключ отменяет действие\n") \
	                _T("      ключа -r и имеет более высокий приоритет. Создаётся простой sav файл\n") \
	                _T("      без заголовка bin.\n\n") \
	                _T("  -l[name] (--listing[=name]) - генерировать lst Файл.\n") \
	                _T("      Если имя файла задано, то используется оно для генерации листинга, если\n") \
	                _T("      нет - то берётся имя файла исходного текста.\n\n") \
	                _T("  -o[name] (--object[=name]) - генерировать объектный файл.\n") \
	                _T("      Если имя файла задано, то используется оно для генерации листинга, если\n") \
	                _T("      нет - то берётся имя файла исходного текста.\n\n") \
	                _T("  -t[name] (--table[=name]) - создавать особый объектный файл, в котором\n") \
	                _T("      содержатся только глобальные метки. (См. документацию)\n\n") \
	                _T("  -s<0addr> (--address <0addr>) - задать начальный адрес компиляции.\n") \
	                _T("      Адрес задаётся в восьмеричном виде.\n\n") \
	                _T("  <cmd> - команда компиляции:\n") \
	                _T("      CO - полная компиляция. В результате при отсутствии ошибок создаётся\n") \
	                _T("           бинарный исполняемый файл и опционально создаются объектные файлы,\n") \
	                _T("           заданные соответствующими ключами.\n") \
	                _T("      CL - компиляция в объектный файл для дальнейшей линковки с другими\n") \
	                _T("           объектными файлами. В результате при отсутствии ошибок всегда\n") \
	                _T("           создаётся объектный файл. Бинарный файл не создаётся.\n") \
	                _T("      Файл листинга создаётся в любом случае. При наличии ошибок код ошибки\n") \
	                _T("      и его текстовое пояснение помещаются перед строкой листинга, вызвавшей\n") \
	                _T("      ошибку.\n") \
	                _T("      В конец файла листинга записывается таблица глобальных меток, а также\n") \
	                _T("      список ссылок на неопределённые метки, если они есть.\n\n") \
	                _T("  <file_1 *[ file_n]> - список исходных файлов, перечисленных через пробел.\n") \
	                _T("      Допускаются маски файлов.\n\n") \
	                _T("2. Режим линковки.\n") \
	                _T("BKTurbo8 [-d][-r|x][-l[name]][-o[name]][-s<0addr>] LI <outfile> <file_1 *[ file_n]>\n") \
	                _T("  Ключи -i, -I, -O не используются.\n\n") \
	                _T("  Ключи -d, -r, -x, -l, -o, -t и -s имеют тот же смысл, что и в режиме компиляции.\n\n") \
	                _T("  Команда линковки - LI, за командой следует обязательное имя выходного файла\n") \
	                _T("  <outfile>, маска файла не допускается. А затем список файлов объектных\n") \
	                _T("  модулей.\n") \
	                _T("  Листинг при этом не создаётся, потому что не из чего, но если задан ключ -l,\n") \
	                _T("  в файл листинга сохраняется список меток, а так же список ссылок на \n") \
	                _T("  неопределённые метки, если они есть.\n\n") \
	                _T("  <file_1 *[ file_n]> - список файлов объектных модулей, перечисленных через\n") \
	                _T("      пробел. Допускаются маски файлов.\n\n"));
}

void BKTurbo8::PrintVersion()
{
	std::_tcout << _T(FILE_PRODUCT_STR"\n");
	const auto strVers = std::format(_T("Версия: {}.{}.{}.{}\n"), _T(VERSION_MAJ_STR), _T(VERSION_MIN_STR), _T(VERSION_REV_STR), _T(VERSION_BLD_STR));
	std::_tcout << strVers;
	// ревизия и билд могут начинаться с нуля, но они тем не менее десятичные числа
	// поэтому слегка поизвращаемся.
	std::_tstring str = _T(VERSION_MIN_STR);
	const int vmin = std::stol(str, nullptr, 10);
	str = _T(VERSION_REV_STR);
	const int rev = std::stol(str, nullptr, 10);
	str = _T(VERSION_BLD_STR);
	const int bld = std::stol(str, nullptr, 10);
	const auto strDate = std::format(_T(" {:02d}-{:02d}-{:04d}"), (rev / 100), vmin, (2000 + VERSION_MAJ));
	const auto strTime = std::format(_T(" {:02d}:{:02d}:{:02d}"), (rev % 100), (bld / 100), (bld % 100));
	std::_tcout << _T("Дата сборки:") << strDate << strTime << std::endl;
}

/*
    Коды ошибок:
    0 - нет ошибок
    -1 - ошибка в командной строке
    -2 - недостаточно памяти
    -3 - не определена кодировка исходного файла
    -4 - не удалось открыть исходный файл
    -5 - не удалось открыть объектный файл

    >0 - количество ошибок компиляции в исходном файле.
*/
