?login_element?

Subversion Repositories NedoOS

Rev

Rev 554 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download

  1. /*
  2.  
  3.   SjASMPlus Z80 Cross Compiler
  4.  
  5.   This is modified sources of SjASM by Aprisobal - aprisobal@tut.by
  6.  
  7.   Copyright (c) 2006 Sjoerd Mastijn
  8.  
  9.   This software is provided 'as-is', without any express or implied warranty.
  10.   In no event will the authors be held liable for any damages arising from the
  11.   use of this software.
  12.  
  13.   Permission is granted to anyone to use this software for any purpose,
  14.   including commercial applications, and to alter it and redistribute it freely,
  15.   subject to the following restrictions:
  16.  
  17.   1. The origin of this software must not be misrepresented; you must not claim
  18.          that you wrote the original software. If you use this software in a product,
  19.          an acknowledgment in the product documentation would be appreciated but is
  20.          not required.
  21.  
  22.   2. Altered source versions must be plainly marked as such, and must not be
  23.          misrepresented as being the original software.
  24.  
  25.   3. This notice may not be removed or altered from any source distribution.
  26.  
  27. */
  28.  
  29. // sjasm.cpp
  30.  
  31. #include "termcolor.hpp"
  32. #include "sjdefs.h"
  33. #include <cstdlib>
  34. #include <chrono>
  35. #include <ctime>
  36.  
  37. #ifdef USE_LUA
  38.  
  39. #include "lua_sjasm.h"
  40.  
  41. #endif //USE_LUA
  42.  
  43. void PrintHelp() {
  44.         // Please keep help lines at most 79 characters long (cursor at column 88 after last char)
  45.         //     |<-- ...8901234567890123456789012345678901234567890123456789012... 80 chars -->|
  46.         _COUT "Based on code of SjASM by Sjoerd Mastijn (http://www.xl2s.tk)" _ENDL;
  47.         _COUT "Copyright 2004-2020 by Aprisobal and all other participants" _ENDL;
  48.         //_COUT "Patches by Antipod / boo_boo / PulkoMandy and others" _ENDL;
  49.         //_COUT "Tidy up by Tygrys / UB880D / Cizo / mborik / z00m" _ENDL;
  50.         _COUT "\nUsage:\nsjasmplus [options] sourcefile(s)" _ENDL;
  51.         _COUT "\nOption flags as follows:" _ENDL;
  52.         _COUT "  -h or --help             Help information (you see it)" _ENDL;
  53.         _COUT "  --zxnext[=cspect]        Enable ZX Spectrum Next Z80 extensions (Z80N)" _ENDL;
  54.         _COUT "  --i8080                  Limit valid instructions to i8080 only (+ no fakes)" _ENDL;
  55.         _COUT "  --lr35902                Sharp LR35902 CPU instructions mode (+ no fakes)" _ENDL;
  56.         _COUT "  --outprefix=<path>       Prefix for save/output/.. filenames in directives" _ENDL;
  57.         _COUT "  -i<path> or -I<path> or --inc=<path> ( --inc without \"=\" to empty the list)" _ENDL;
  58.         _COUT "                           Include path (later defined have higher priority)" _ENDL;
  59.         _COUT "  --lst[=<filename>]       Save listing to <filename> (<source>.lst is default)" _ENDL;
  60.         _COUT "  --lstlab                 Enable label table in listing" _ENDL;
  61.         _COUT "  --sym=<filename>         Save symbols list to <filename>" _ENDL;
  62.         _COUT "  --exp=<filename>         Save exports to <filename> (see EXPORT pseudo-op)" _ENDL;
  63.         //_COUT "  --autoreloc              Switch to autorelocation mode. See more in docs." _ENDL;
  64.         _COUT "  --raw=<filename>         Machine code saved also to <filename> (- is STDOUT)" _ENDL;
  65.         _COUT "  --sld[=<filename>]       Save Source Level Debugging data to <filename>" _ENDL;
  66.         _COUT " Note: use OUTPUT, LUA/ENDLUA and other pseudo-ops to control output" _ENDL;
  67.         _COUT " Logging:" _ENDL;
  68.         _COUT "  --nologo                 Do not show startup message" _ENDL;
  69.         _COUT "  --msg=[all|war|err|none|lst|lstlab]" _ENDL;
  70.         _COUT "                           Stderr messages verbosity (\"all\" is default)" _ENDL;
  71.         _COUT "  --fullpath               Show full path to file in errors" _ENDL;
  72.         _COUT " Other:" _ENDL;
  73.         _COUT "  -D<NAME>[=<value>]       Define <NAME> as <value>" _ENDL;
  74.         _COUT "  -                        Reads STDIN as source (even in between regular files)" _ENDL;
  75.         _COUT "  --longptr                No device: program counter $ can go beyond 0x10000" _ENDL;
  76.         _COUT "  --virtlabels             Emit virtual intead of physical address labels in LABELSLIST" _ENDL;
  77.         _COUT "  --reversepop             Enable reverse POP order (as in base SjASM version)" _ENDL;
  78.         _COUT "  --dirbol                 Enable directives from the beginning of line" _ENDL;
  79.         _COUT "  --nofakes                Disable fake instructions" _ENDL;
  80.         _COUT "  --dos866                 Encode from Windows codepage to DOS 866 (Cyrillic)" _ENDL;
  81.         _COUT "  --syntax=<...>           Adjust parsing syntax, check docs for details." _ENDL;
  82. }
  83.  
  84. namespace Options {
  85.         char OutPrefix[LINEMAX] = {0};
  86.         char SymbolListFName[LINEMAX] = {0};
  87.         char ListingFName[LINEMAX] = {0};
  88.         char ExportFName[LINEMAX] = {0};
  89.         char DestinationFName[LINEMAX] = {0};
  90.         char RAWFName[LINEMAX] = {0};
  91.         char UnrealLabelListFName[LINEMAX] = {0};
  92.         char CSpectMapFName[LINEMAX] = {0};
  93.         int CSpectMapPageSize = 0x4000;
  94.         char SourceLevelDebugFName[LINEMAX] = {0};
  95.         bool IsDefaultSldName = false;
  96.  
  97.         char ZX_SnapshotFName[LINEMAX] = {0};
  98.         char ZX_TapeFName[LINEMAX] = {0};
  99.  
  100.         EOutputVerbosity OutputVerbosity = OV_ALL;
  101.         bool IsLabelTableInListing = 0;
  102.         bool IsDefaultListingName = false;
  103.         bool IsShowFullPath = 0;
  104.         bool AddLabelListing = false;
  105.         bool HideLogo = 0;
  106.         bool ShowHelp = 0;
  107.         bool ShowVersion = false;
  108.         bool NoDestinationFile = true;          // no *.out file by default
  109.         SSyntax syx, systemSyntax;
  110.         bool IsI8080 = false;
  111.         bool IsLR35902 = false;
  112.         bool IsLongPtr = false;
  113.  
  114.         bool EmitVirtualLabels = false;
  115.  
  116.         // Include directories list is initialized with "." directory
  117.         CStringsList* IncludeDirsList = new CStringsList(".");
  118.  
  119.         CDefineTable CmdDefineTable;            // is initialized by constructor
  120.  
  121.         static const char* fakes_disabled_txt_error = "Fake instructions are not enabled";
  122.         static const char* fakes_in_i8080_txt_error = "Fake instructions are not implemented in i8080 mode";
  123.         static const char* fakes_in_lr35902_txt_error = "Fake instructions are not implemented in Sharp LR35902 mode";
  124.  
  125.         // returns true if fakes are completely disabled, false when they are enabled
  126.         // showMessage=true: will also display error/warning (use when fake ins. is emitted)
  127.         // showMessage=false: can be used to silently check if fake instructions are even possible
  128.         bool noFakes(bool showMessage) {
  129.                 bool fakesDisabled = Options::IsI8080 || Options::IsLR35902 || (!syx.FakeEnabled);
  130.                 if (!showMessage) return fakesDisabled;
  131.                 if (fakesDisabled) {
  132.                         const char* errorTxt = fakes_disabled_txt_error;
  133.                         if (Options::IsI8080) errorTxt = fakes_in_i8080_txt_error;
  134.                         if (Options::IsLR35902) errorTxt = fakes_in_lr35902_txt_error;
  135.                         Error(errorTxt, bp, SUPPRESS);
  136.                         return true;
  137.                 }
  138.                 // check end-of-line comment for mentioning "fake" to remove warning, or beginning with "ok"
  139.                 if (syx.FakeWarning && warningNotSuppressed(true)) {
  140.                         Warning("Fake instruction", bp);
  141.                 }
  142.                 return false;
  143.         }
  144.  
  145.         std::stack<SSyntax> SSyntax::syxStack;
  146.  
  147.         void SSyntax::resetCurrentSyntax() {
  148.                 new (&syx) SSyntax();   // restore defaults in current syntax
  149.         }
  150.  
  151.         void SSyntax::pushCurrentSyntax() {
  152.                 syxStack.push(syx);             // store current syntax options into stack
  153.         }
  154.  
  155.         bool SSyntax::popSyntax() {
  156.                 if (syxStack.empty()) return false;     // no syntax stored in stack
  157.                 syx = syxStack.top();   // copy the syntax values from stack
  158.                 syxStack.pop();
  159.                 return true;
  160.         }
  161.  
  162.         void SSyntax::restoreSystemSyntax() {
  163.                 while (!syxStack.empty()) syxStack.pop();       // empty the syntax stack first
  164.                 syx = systemSyntax;             // reset to original system syntax
  165.         }
  166.  
  167. } // eof namespace Options
  168.  
  169. CDevice *Devices = 0;
  170. CDevice *Device = 0;
  171. CDevicePage *Page = 0;
  172. char* DeviceID = 0;
  173.  
  174. // extend
  175. const char* fileNameFull = nullptr, * fileName = nullptr;       //fileName is either full or basename (--fullpath)
  176. char* lp, line[LINEMAX], temp[LINEMAX], ErrorLine[LINEMAX2], ErrorLine2[LINEMAX2], * bp;
  177. char sline[LINEMAX2], sline2[LINEMAX2], * substitutedLine, * eolComment, ModuleName[LINEMAX];
  178.  
  179. SSource::SSource(SSource && src) {      // move constructor, "pick" the stdin pointer
  180.         memcpy(fname, src.fname, MAX_PATH);
  181.         stdin_log = src.stdin_log;
  182.         src.fname[0] = 0;
  183.         src.stdin_log = nullptr;
  184. }
  185.  
  186. SSource::SSource(const char* newfname) : stdin_log(nullptr) {
  187.         STRNCPY(fname, MAX_PATH, newfname, MAX_PATH-1);
  188.         fname[MAX_PATH-1] = 0;
  189. }
  190.  
  191. SSource::SSource(int) {
  192.         fname[0] = 0;
  193.         stdin_log = new stdin_log_t();
  194.         stdin_log->reserve(50*1024);
  195. }
  196.  
  197. SSource::~SSource() {
  198.         if (stdin_log) delete stdin_log;
  199. }
  200.  
  201. std::vector<SSource> sourceFiles;
  202. std::vector<std::string> openedFileNames;
  203.  
  204. int ConvertEncoding = ENCWIN;
  205.  
  206. int pass = 0, IsLabelNotFound = 0, ErrorCount = 0, WarningCount = 0, IncludeLevel = -1;
  207. int IsRunning = 0, donotlist = 0, listmacro = 0;
  208. int adrdisp = 0, PseudoORG = 0, dispPageNum = LABEL_PAGE_UNDEFINED, StartAddress = -1;
  209. byte* MemoryPointer=NULL;
  210. int macronummer = 0, lijst = 0, reglenwidth = 0;
  211. TextFilePos CurSourcePos, DefinitionPos;
  212. uint32_t maxlin = 0;
  213. aint CurAddress = 0, CompiledCurrentLine = 0, LastParsedLabelLine = 0, PredefinedCounter = 0;
  214. aint destlen = 0, size = -1L,PreviousErrorLine = -1L, comlin = 0;
  215. char* CurrentDirectory=NULL;
  216.  
  217. char* vorlabp=NULL, * macrolabp=NULL, * LastParsedLabel=NULL;
  218. std::stack<SRepeatStack> RepeatStack;
  219. CStringsList* lijstp = NULL;
  220. CLabelTable LabelTable;
  221. CLocalLabelTable LocalLabelTable;
  222. CDefineTable DefineTable;
  223. CMacroDefineTable MacroDefineTable;
  224. CMacroTable MacroTable;
  225. CStructureTable StructureTable;
  226.  
  227. #ifdef USE_LUA
  228.  
  229. lua_State *LUA;                 // lgtm[cpp/short-global-name] .. name seems barely ok (especially considering rest of code)
  230. TextFilePos LuaStartPos;
  231.  
  232. #endif //USE_LUA
  233.  
  234. int deviceDirectivesCounter = 0;
  235. static char* globalDeviceID = NULL;
  236. static aint globalDeviceZxRamTop = 0;
  237.  
  238. void InitPass() {
  239.         Options::SSyntax::restoreSystemSyntax();        // release all stored syntax variants and reset to initial
  240.         uint32_t maxpow10 = 1;
  241.         reglenwidth = 0;
  242.         do {
  243.                 ++reglenwidth;
  244.                 maxpow10 *= 10;
  245.                 if (maxpow10 < 10) ExitASM(1);  // 32b overflow
  246.         } while (maxpow10 <= maxlin);
  247.         *ModuleName = 0;
  248.         SetLastParsedLabel(nullptr);
  249.         if (vorlabp) free(vorlabp);
  250.         vorlabp = STRDUP("_");
  251.         macrolabp = NULL;
  252.         listmacro = 0;
  253.         CurAddress = 0;
  254.         CurSourcePos = DefinitionPos = TextFilePos();   // reset current source/definition positions
  255.         CompiledCurrentLine = 0;
  256.         PseudoORG = 0; adrdisp = 0; dispPageNum = LABEL_PAGE_UNDEFINED;
  257.         ListAddress = 0; macronummer = 0; lijst = 0; comlin = 0;
  258.         lijstp = NULL;
  259.         DidEmitByte();                          // reset the emitted flag
  260.         StructureTable.ReInit();
  261.         MacroTable.ReInit();
  262.         MacroDefineTable.ReInit();
  263.         DefineTable = Options::CmdDefineTable;
  264.         LocalLabelTable.InitPass();
  265.         // reset "device" stuff
  266.         if (2 == pass && Devices && 1 == deviceDirectivesCounter) {     // only single device detected
  267.                 globalDeviceID = STRDUP(Devices->ID);           // make it global for remaining passes
  268.                 globalDeviceZxRamTop = Devices->ZxRamTop;
  269.         }
  270.         if (Devices) delete Devices;
  271.         Devices = Device = NULL;
  272.         DeviceID = NULL;
  273.         Page = NULL;
  274.         deviceDirectivesCounter = 0;
  275.  
  276.         // predefined defines - (deprecated) classic sjasmplus v1.x (till v1.15.1)
  277.         DefineTable.Replace("_SJASMPLUS", "1");
  278.         DefineTable.Replace("_RELEASE", "0");
  279.         DefineTable.Replace("_VERSION", "__VERSION__");
  280.         DefineTable.Replace("_ERRORS", "__ERRORS__");
  281.         DefineTable.Replace("_WARNINGS", "__WARNINGS__");
  282.         // predefined defines - sjasmplus v2.x-like (since v1.15.2)
  283.         // __DATE__ and __TIME__ are defined just once in main(...) (stored in Options::CmdDefineTable)
  284.         DefineTable.Replace("__SJASMPLUS__", VERSION_NUM);              // modified from _SJASMPLUS
  285.         DefineTable.Replace("__VERSION__", "\"" VERSION "\"");  // migrated from _VERSION
  286.         DefineTable.Replace("__ERRORS__", "0");                                 // migrated from _ERRORS
  287.         DefineTable.Replace("__WARNINGS__", "0");                               // migrated from _WARNINGS
  288.         DefineTable.Replace("__PASS__", pass);                                  // current pass of assembler
  289.         DefineTable.Replace("__INCLUDE_LEVEL__", "-1");                 // include nesting
  290.         DefineTable.Replace("__BASE_FILE__", "<none>");                 // the include-level 0 file
  291.         DefineTable.Replace("__FILE__", "<none>");                              // current file
  292.         DefineTable.Replace("__LINE__", "<dynamic value>");             // current line in current file
  293.         DefineTable.Replace("__COUNTER__", "<dynamic value>");  // gcc-like, incremented upon every use
  294.         PredefinedCounter = 0;
  295.  
  296.         // resurrect "global" device here
  297.         if (globalDeviceID && !SetDevice(globalDeviceID, globalDeviceZxRamTop)) {
  298.                 Error("Failed to re-initialize global device", globalDeviceID, FATAL);
  299.         }
  300. }
  301.  
  302. void FreeRAM() {
  303.         if (Devices) {
  304.                 delete Devices;         Devices = NULL;
  305.         }
  306.         if (globalDeviceID) {
  307.                 free(globalDeviceID);   globalDeviceID = NULL;
  308.         }
  309.         lijstp = NULL;          // do not delete this, should be released by owners of DUP/regular macros
  310.         free(vorlabp);          vorlabp = NULL;
  311.         LabelTable.RemoveAll();
  312.         DefineTable.RemoveAll();
  313.         SetLastParsedLabel(nullptr);
  314.         if (PreviousIsLabel) {
  315.                 free(PreviousIsLabel);
  316.                 PreviousIsLabel = nullptr;
  317.         }
  318. }
  319.  
  320.  
  321. void ExitASM(int p) {
  322.         FreeRAM();
  323.         if (pass == LASTPASS) {
  324.                 Close();
  325.         }
  326.         exit(p);
  327. }
  328.  
  329. namespace Options {
  330.  
  331.         class COptionsParser {
  332.         private:
  333.                 const char* arg;
  334.                 char opt[LINEMAX];
  335.                 char val[LINEMAX];
  336.  
  337.                 // returns 1 when argument was processed (keyword detected, value copied into buffer)
  338.                 // If buffer == NULL, only detection of keyword + check for non-zero "value" is done (no copy)
  339.                 int CheckAssignmentOption(const char* keyword, char* buffer, const size_t bufferSize) {
  340.                         if (strcmp(keyword, opt)) return 0;             // detect "keyword" (return 0 if not)
  341.                         if (*val) {
  342.                                 if (NULL != buffer) STRCPY(buffer, bufferSize, val);
  343.                         } else {
  344.                                 _CERR "No parameters found in " _CMDL arg _ENDL;
  345.                         }
  346.                         return 1;       // keyword detected, option was processed
  347.                 }
  348.  
  349.                 static void splitByChar(const char* s, const int splitter,
  350.                                                            char* v1, const size_t v1Size,
  351.                                                            char* v2, const size_t v2Size) {
  352.                         // only non-zero splitter character is supported
  353.                         const char* spos = splitter ? STRCHR(s, splitter) : NULL;
  354.                         if (NULL == spos) {
  355.                                 // splitter character not found, copy whole input string into v1, v2 = empty string
  356.                                 STRCPY(v1, v1Size, s);
  357.                                 v2[0] = 0;
  358.                         } else {
  359.                                 // splitter found, copy string ahead splitter to v1, after it to v2
  360.                                 STRNCPY(v1, v1Size, s, spos - s);
  361.                                 v1[spos - s] = 0;
  362.                                 STRCPY(v2, v2Size, spos + 1);
  363.                         }
  364.                 }
  365.  
  366.                 void parseSyntaxValue() {
  367.                         // Options::syx is expected to be already in default state before entering this
  368.                         for (const auto & syntaxOption : val) {
  369.                                 switch (syntaxOption) {
  370.                                 case 0:   return;
  371.                                 // f F - instructions: fake warning, no fakes (default = fake enabled)
  372.                                 case 'f': syx.FakeEnabled = syx.FakeWarning = true; break;
  373.                                 case 'F': syx.FakeEnabled = false; break;
  374.                                 // a - multi-argument delimiter ",," (default is ",")
  375.                                 case 'a': syx.MultiArg = &doubleComma; break;
  376.                                 // b - single parentheses enforce mem access (default = relaxed syntax)
  377.                                 case 'b': syx.MemoryBrackets = 1; break;
  378.                                 // B - memory access brackets [] required (default = relaxed syntax)
  379.                                 case 'B': syx.MemoryBrackets = 2; break;
  380.                                 // l L - warn/error about labels using keywords (default = no message)
  381.                                 case 'l':
  382.                                 case 'L':
  383.                                         if (0 == pass || LASTPASS == pass) {
  384.                                                 _CERR "Syntax option not implemented yet: " _CMDL syntaxOption _ENDL;
  385.                                         }
  386.                                         break;
  387.                                 // i - case insensitive instructions/directives (default = same case required)
  388.                                 case 'i': syx.CaseInsensitiveInstructions = true; break;
  389.                                 // w - warnings option: report warnings as errors
  390.                                 case 'w': syx.WarningsAsErrors = true; break;
  391.                                 // m - switch off "Accessing low memory" warning globally
  392.                                 case 'm': syx.IsLowMemWarningEnabled = false; break;
  393.                                 // M - alias "m" and "M" for "(hl)" to cover 8080-like syntax: ADD A,M
  394.                                 case 'M': syx.Is_M_Memory = true; break;
  395.                                 // unrecognized option
  396.                                 default:
  397.                                         if (0 == pass || LASTPASS == pass) {
  398.                                                 _CERR "Unrecognized syntax option: " _CMDL syntaxOption _ENDL;
  399.                                         }
  400.                                         break;
  401.                                 }
  402.                         }
  403.                 }
  404.  
  405.         public:
  406.                 void GetOptions(const char* const * const argv, int& i, bool onlySyntaxOptions = false) {
  407.                         while ((arg=argv[i]) && ('-' == arg[0])) {
  408.                                 bool doubleDash = false;
  409.                                 // copy "option" (up to '=' char) into `opt`, copy "value" (after '=') into `val`
  410.                                 if ('-' == arg[1]) {    // double-dash detected, value is expected after "="
  411.                                         doubleDash = true;
  412.                                         splitByChar(arg + 2, '=', opt, LINEMAX, val, LINEMAX);
  413.                                 } else {                                // single dash, parse value from second character onward
  414.                                         opt[0] = arg[1];        // copy only single letter into `opt`
  415.                                         opt[1] = 0;
  416.                                         if (opt[0]) {           // if it was not empty, try to copy also `val`
  417.                                                 STRCPY(val, LINEMAX, arg + 2);
  418.                                         }
  419.                                 }
  420.  
  421.                                 // check for particular options and setup option value by it
  422.                                 // first check all syntax-only options which may be modified by OPT directive
  423.                                 if (!strcmp(opt, "zxnext")) {
  424.                                         if (IsI8080) Error("Can't enable Next extensions while in i8080 mode", nullptr, FATAL);
  425.                                         if (IsLR35902) Error("Can't enable Next extensions while in Sharp LR35902 mode", nullptr, FATAL);
  426.                                         syx.IsNextEnabled = 1;
  427.                                         if (!strcmp(val, "cspect")) syx.IsNextEnabled = 2;      // CSpect emulator extensions
  428.                                 } else if (!strcmp(opt, "reversepop")) {
  429.                                         syx.IsReversePOP = true;
  430.                                 } else if (!strcmp(opt, "dirbol")) {
  431.                                         syx.IsPseudoOpBOF = true;
  432.                                 } else if (!strcmp(opt, "nofakes")) {
  433.                                         syx.FakeEnabled = false;
  434.                                 } else if (!strcmp(opt, "syntax")) {
  435.                                         parseSyntaxValue();
  436.                                 } else if (onlySyntaxOptions) {
  437.                                         // rest of the options is available only when launching the sjasmplus
  438.                                         return;
  439.                                 } else if (!strcmp(opt, "lr35902")) {
  440.                                         IsLR35902 = true;
  441.                                         // force (silently) other CPU modes OFF
  442.                                         IsI8080 = false;
  443.                                         syx.IsNextEnabled = 0;
  444.                                 } else if (!strcmp(opt, "i8080")) {
  445.                                         IsI8080 = true;
  446.                                         // force (silently) other CPU modes OFF
  447.                                         IsLR35902 = false;
  448.                                         syx.IsNextEnabled = 0;
  449.                                 } else if ((!doubleDash && !strcmp(opt,"h") && !val[0]) || (doubleDash && !strcmp(opt, "help"))) {
  450.                                         ShowHelp = 1;
  451.                                 } else if (doubleDash && !strcmp(opt, "version")) {
  452.                                         ShowVersion = true;
  453.                                 } else if (!strcmp(opt, "lstlab")) {
  454.                                         AddLabelListing = true;
  455.                                 } else if (!strcmp(opt, "longptr")) {
  456.                                         IsLongPtr = true;
  457.                                 } else if (!strcmp(opt,"virtlabels")) {
  458.                                         EmitVirtualLabels = true;
  459.                                 } else if (CheckAssignmentOption("msg", NULL, 0)) {
  460.                                         if (!strcmp("none", val)) {
  461.                                                 OutputVerbosity = OV_NONE;
  462.                                                 HideLogo = true;
  463.                                         } else if (!strcmp("err", val)) {
  464.                                                 OutputVerbosity = OV_ERROR;
  465.                                         } else if (!strcmp("war", val)) {
  466.                                                 OutputVerbosity = OV_WARNING;
  467.                                         } else if (!strcmp("all", val)) {
  468.                                                 OutputVerbosity = OV_ALL;
  469.                                         } else if (!strcmp("lst", val)) {
  470.                                                 OutputVerbosity = OV_LST;
  471.                                                 AddLabelListing = false;
  472.                                                 HideLogo = true;
  473.                                         } else if (!strcmp("lstlab", val)) {
  474.                                                 OutputVerbosity = OV_LST;
  475.                                                 AddLabelListing = true;
  476.                                                 HideLogo = true;
  477.                                         } else {
  478.                                                 _CERR "Unexpected parameter in " _CMDL arg _ENDL;
  479.                                         }
  480.                                 } else if (!strcmp(opt, "lst") && !val[0]) {
  481.                                         IsDefaultListingName = true;
  482.                                 } else if (!strcmp(opt, "sld") && !val[0]) {
  483.                                         IsDefaultSldName = true;
  484.                                 } else if (
  485.                                         CheckAssignmentOption("outprefix", OutPrefix, LINEMAX) ||
  486.                                         CheckAssignmentOption("sym", SymbolListFName, LINEMAX) ||
  487.                                         CheckAssignmentOption("lst", ListingFName, LINEMAX) ||
  488.                                         CheckAssignmentOption("exp", ExportFName, LINEMAX) ||
  489.                                         CheckAssignmentOption("sld", SourceLevelDebugFName, LINEMAX) ||
  490.                                         CheckAssignmentOption("raw", RAWFName, LINEMAX) ) {
  491.                                         // was proccessed inside CheckAssignmentOption function
  492.                                 } else if (!strcmp(opt, "fullpath")) {
  493.                                         IsShowFullPath = 1;
  494.                                 } else if (!strcmp(opt, "nologo")) {
  495.                                         HideLogo = 1;
  496.                                 } else if (!strcmp(opt, "dos866")) {
  497.                                         ConvertEncoding = ENCDOS;
  498.                                 } else if ((doubleDash && !strcmp(opt, "inc")) ||
  499.                                                         (!doubleDash && !strcmp(opt, "i")) ||
  500.                                                         (!doubleDash && !strcmp(opt, "I"))) {
  501.                                         if (*val) {
  502.                                                 IncludeDirsList = new CStringsList(val, IncludeDirsList);
  503.                                         } else {
  504.                                                 if (!doubleDash || '=' == arg[5]) {
  505.                                                         _CERR "No include path found in " _CMDL arg _ENDL;
  506.                                                 } else {        // individual `--inc` without "=path" will RESET include dirs
  507.                                                         if (IncludeDirsList) delete IncludeDirsList;
  508.                                                         IncludeDirsList = nullptr;
  509.                                                 }
  510.                                         }
  511.                                 } else if (!doubleDash && opt[0] == 'D') {
  512.                                         char defN[LINEMAX], defV[LINEMAX];
  513.                                         if (*val) {             // for -Dname=value the `val` contains "name=value" string
  514.                                                 //TODO the `Error("Duplicate name"..)` is not shown while parsing CLI options
  515.                                                 splitByChar(val, '=', defN, LINEMAX, defV, LINEMAX);
  516.                                                 CmdDefineTable.Add(defN, defV, NULL);
  517.                                         } else {
  518.                                                 _CERR "No parameters found in " _CMDL arg _ENDL;
  519.                                         }
  520.                                 } else if (!doubleDash && 0 == opt[0]) {
  521.                                         // only single "-" was on command line = source STDIN
  522.                                         sourceFiles.push_back(SSource(1));              // special constructor for stdin input
  523.                                 } else {
  524.                                         _CERR "Unrecognized option: " _CMDL arg _ENDL;
  525.                                 }
  526.  
  527.                                 ++i;                                    // next CLI argument
  528.                         } // end of while ((arg=argv[i]) && ('-' == arg[0]))
  529.                 }
  530.         };
  531.  
  532.         int parseSyntaxOptions(int n, char** options) {
  533.                 if (n <= 0) return 0;
  534.                 int i = 0;
  535.                 Options::COptionsParser optParser;
  536.                 optParser.GetOptions(options, i, true);
  537.                 return i;
  538.         }
  539. }
  540.  
  541. #ifdef USE_LUA
  542.  
  543. void LuaFatalError(lua_State *L) {
  544.         Error((char *)lua_tostring(L, -1), NULL, FATAL);
  545. }
  546.  
  547. #endif //USE_LUA
  548.  
  549. // ==============================================================================================
  550. // == UnitTest++ part, checking if unit tests are requested and does launch test-runner then   ==
  551. // ==============================================================================================
  552. #ifdef ADD_UNIT_TESTS
  553.  
  554. # include "UnitTest++/UnitTest++.h"
  555.  
  556. # define STOP_MAKE_BY_NON_ZERO_EXIT_CODE 0
  557.  
  558. //detect "--unittest" switch, prepare UnitTest++, run the test runner, collect results, exit
  559. # define CHECK_UNIT_TESTS \
  560.         { \
  561.                 if (2 == argc && !strcmp("--unittest", argv[1])) { \
  562.                         _COUT "SjASMPlus \033[96mv" VERSION "\033[0m | \033[95mrunning unit tests:\033[0m" _ENDL _END \
  563.                         int exitCode = STOP_MAKE_BY_NON_ZERO_EXIT_CODE + UnitTest::RunAllTests(); \
  564.                         if (exitCode) _COUT "\033[91mNon-zero result from test runner!\033[0m" _ENDL _END \
  565.                         else _COUT "\033[92mOK: 0 UnitTest++ tests failed.\033[0m" _ENDL _END \
  566.                         exit(exitCode); \
  567.                 } \
  568.         }
  569. #else
  570.  
  571. # define CHECK_UNIT_TESTS { /* no unit tests in this build */ }
  572.  
  573. #endif
  574.  
  575. // == end of UnitTest++ part ====================================================================
  576.  
  577. #ifdef WIN32
  578. int main(int argc, char* argv[]) {
  579. #else
  580. int main(int argc, char **argv) {
  581. #endif
  582.         char buf[MAX_PATH];
  583.         int base_encoding;
  584.         const char* logo = "SjASMPlus Z80 Cross-Assembler v" VERSION " (https://github.com/z00m128/sjasmplus)";
  585.  
  586.         sourceFiles.reserve(32);
  587.         openedFileNames.reserve(64);
  588.  
  589.         CHECK_UNIT_TESTS                // UnitTest++ extra handling in specially built executable
  590.  
  591.         // verify that I'm running on little-endian platform (the reinterpret word casts will break on BE platform)
  592.         // The new source is easy to locate through reinterpret_cast keywords, but I have strong
  593.         // suspicion there is also old sjasmplus code which is not endianness-agnostic.
  594.         // To fix it would need major testing with some BE platform and deep code review.
  595.         const word little_endian_test[] = { 0x1234 };
  596.         const byte le_test_byte = *reinterpret_cast<const byte*>(little_endian_test);
  597.         if (0x34 != le_test_byte) {
  598.                 ErrorInt("Big-endian platform detected, unfortunately sjasmplus" \
  599.                 " is currently LE-only, please report the issue.", le_test_byte, FATAL);
  600.         }
  601.  
  602.         // start counter
  603.         long dwStart = GetTickCount();
  604.  
  605.         // get current directory
  606.         SJ_GetCurrentDirectory(MAX_PATH, buf);
  607.         CurrentDirectory = buf;
  608.  
  609.         Options::COptionsParser optParser;
  610.         char* envFlags = std::getenv("SJASMPLUSOPTS");
  611.         if (nullptr != envFlags) {
  612.                 // split environment arguments into "argc, argv" like variables (by white-space)
  613.                 char* parsedOptsArray[33] {};   // there must be one more nullptr in the array (32+1)
  614.                 int optI = 0, charI = 0;
  615.                 while (optI < 32 && !SkipBlanks(envFlags)) {
  616.                         parsedOptsArray[optI++] = temp + charI;
  617.                         while (*envFlags && !White(*envFlags) && charI < LINEMAX-1) temp[charI++] = *envFlags++;
  618.                         temp[charI++] = 0;
  619.                 }
  620.                 if (!SkipBlanks(envFlags)) {
  621.                         _CERR "SJASMPLUSOPTS environment variable contains too many options (max is 32)" _ENDL;
  622.                 }
  623.                 // process environment variable ahead of command line options (in the same way)
  624.                 int i = 0;
  625.                 while (parsedOptsArray[i]) {
  626.                         optParser.GetOptions(parsedOptsArray, i);
  627.                         if (!parsedOptsArray[i]) break;
  628.                         sourceFiles.push_back(SSource(parsedOptsArray[i++]));
  629.                 }
  630.         }
  631.  
  632.         // setup __DATE__ and __TIME__ macros (setup them just once, not every pass!)
  633.         auto now = std::chrono::system_clock::now();
  634.         std::time_t now_c = std::chrono::system_clock::to_time_t(now);
  635.         std::tm now_tm = *std::localtime(&now_c);       // lgtm [cpp/potentially-dangerous-function]
  636.         char dateBuffer[32] = {}, timeBuffer[32] = {};
  637.         SPRINTF3(dateBuffer, 30, "\"%04d-%02d-%02d\"", now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday);
  638.         SPRINTF3(timeBuffer, 30, "\"%02d:%02d:%02d\"", now_tm.tm_hour, now_tm.tm_min, now_tm.tm_sec);
  639.         Options::CmdDefineTable.Add("__DATE__", dateBuffer, nullptr);
  640.         Options::CmdDefineTable.Add("__TIME__", timeBuffer, nullptr);
  641.  
  642.         int i = 1;
  643.         if (argc > 1) {
  644.                 while (argv[i]) {
  645.                         optParser.GetOptions(argv, i);
  646.                         if (!argv[i]) break;
  647.                         sourceFiles.push_back(SSource(argv[i++]));
  648.                 }
  649.                 if (Options::IsDefaultListingName && Options::ListingFName[0]) {
  650.                         Error("Using both  --lst  and  --lst=<filename>  is not possible.", NULL, FATAL);
  651.                 }
  652.                 if (OV_LST == Options::OutputVerbosity && (Options::IsDefaultListingName || Options::ListingFName[0])) {
  653.                         Error("Using  --msg=lst[lab]  and other list options is not possible.", NULL, FATAL);
  654.                 }
  655.                 if (Options::IsDefaultSldName && Options::SourceLevelDebugFName[0]) {
  656.                         Error("Using both  --sld  and  --sld=<filename>  is not possible.", NULL, FATAL);
  657.                 }
  658.         }
  659.         Options::systemSyntax = Options::syx;           // create copy of initial system settings of syntax
  660.  
  661.         if (argc == 1 || Options::ShowHelp) {
  662.                 _COUT logo _ENDL;
  663.                 PrintHelp();
  664.                 exit(argc == 1);
  665.         }
  666.  
  667.         if (!Options::HideLogo) {
  668.                 _COUT logo _ENDL;
  669.         }
  670.  
  671.         if (Options::ShowVersion) {
  672.                 if (Options::HideLogo) {        // if "sjasmplus --version --nologo", emit only the raw VERSION
  673.                         _COUT VERSION _ENDL;
  674.                 }
  675.                 // otherwise the full logo was already printed
  676.                 // now check if there were some sources to assemble, if NOT, exit with "OK"!
  677.                 if (0 == sourceFiles.size()) exit(0);
  678.         }
  679.  
  680. #ifdef USE_LUA
  681.  
  682.         // init LUA
  683.         LUA = lua_open();
  684.         lua_atpanic(LUA, (lua_CFunction)LuaFatalError);
  685.         luaL_openlibs(LUA);
  686.         luaopen_pack(LUA);
  687.  
  688.         tolua_sjasm_open(LUA);
  689.  
  690. #endif //USE_LUA
  691.  
  692.         // exit with error if no input file were specified
  693.         if (0 == sourceFiles.size()) {
  694.                 if (Options::OutputVerbosity <= OV_ERROR) {
  695.                         _CERR "No inputfile(s)" _ENDL;
  696.                 }
  697.                 exit(1);
  698.         }
  699.  
  700.         // create default output name, if not specified
  701.         ConstructDefaultFilename(Options::DestinationFName, LINEMAX, ".out");
  702.         base_encoding = ConvertEncoding;
  703.  
  704.         // init some vars
  705.         InitCPU();
  706.  
  707.         // open lists (if not set to "default" file name, then the OpenFile will handle it)
  708.         OpenList();
  709.  
  710.         do {
  711.                 ++pass;
  712.                 InitPass();
  713.  
  714.                 if (pass == LASTPASS) {
  715.                         OpenDest();
  716.                         //open source level debugging file
  717.                         OpenSld();
  718.                 }
  719.  
  720.                 for (SSource & src : sourceFiles) {
  721.                         IsRunning = 1;
  722.                         ConvertEncoding = base_encoding;
  723.                         OpenFile(src.fname, false, src.stdin_log);
  724.                 }
  725.  
  726.                 if (PseudoORG) {
  727.                         CurAddress = adrdisp; PseudoORG = 0;
  728.                 }
  729.  
  730.                 if (Options::OutputVerbosity <= OV_ALL) {
  731.                         if (pass != LASTPASS) {
  732.                                 _COUT "Pass " _CMDL pass _CMDL " complete (" _CMDL ErrorCount _CMDL " errors)" _ENDL;
  733.                         } else {
  734.                                 _COUT "Pass 3 complete" _ENDL;
  735.                         }
  736.                 }
  737.         } while (pass < LASTPASS);
  738.  
  739.         pass = 9999; /* added for detect end of compiling */
  740.  
  741.         // dump label table into listing file, the explicit one (Options::IsDefaultListingName == false)
  742.         if (Options::AddLabelListing) LabelTable.Dump();
  743.  
  744.         Close();
  745.  
  746.         if (Options::UnrealLabelListFName[0]) {
  747.                 LabelTable.DumpForUnreal();
  748.         }
  749.  
  750.         if (Options::CSpectMapFName[0]) {
  751.                 LabelTable.DumpForCSpect();
  752.         }
  753.  
  754.         if (Options::SymbolListFName[0]) {
  755.                 LabelTable.DumpSymbols();
  756.         }
  757.  
  758.         if (Options::OutputVerbosity <= OV_ALL) {
  759.                 _COUT "Errors: " _CMDL ErrorCount _CMDL ", warnings: " _CMDL WarningCount _CMDL ", compiled: " _CMDL CompiledCurrentLine _CMDL " lines" _END;
  760.  
  761.                 double dwCount;
  762.                 dwCount = GetTickCount() - dwStart;
  763.                 if (dwCount < 0) dwCount = 0;
  764.                 char workTimeTxt[200] = "";
  765.                 SPRINTF1(workTimeTxt, 200, ", work time: %.3f seconds", dwCount / 1000);
  766.  
  767.                 _COUT workTimeTxt _ENDL;
  768.         }
  769.  
  770.         cout << flush;
  771.  
  772.         // free RAM
  773.         FreeRAM();
  774.  
  775. #ifdef USE_LUA
  776.  
  777.         // close Lua
  778.         lua_close(LUA);
  779.  
  780. #endif //USE_LUA
  781.  
  782.         return (ErrorCount != 0);
  783. }
  784. //eof sjasm.cpp
  785.