• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

z00m128 / sjasmplus / 1451

31 Dec 2024 06:13PM UTC coverage: 96.28% (+0.001%) from 96.279%
1451

Pull #254

cirrus-ci

ped7g
c++17: replacing custom code with std::filesystem[::path] where feasible

Related #240, should fix "check include paths" feature for windows, and
simplify somewhat code base (and get rid of home-made filename patching
functions, they seem robust enough and well tested, but I assume in the
long term having C++ standard library implementation is better, even if
it involves slightly more machine code executed here and there).

This is not thorough refactoring of whole code base, working on it bit
by bit, luckily the std::filesystem API is flexible enough to
connect/disconnect it to rest of code through char[] fields, so I can
replace the code partially and check test results.

Commit v2 (replacing lot more variables and code than first proof of
concept).
Pull Request #254: migrating some of the file stuff to std::filesystem, in the naive hope of getting better cross-platform compatibility

77 of 85 new or added lines in 7 files covered. (90.59%)

38 existing lines in 6 files now uncovered.

9680 of 10054 relevant lines covered (96.28%)

168519.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.57
/sjasm/sjasm.cpp
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 "sjdefs.h"
32
#include <cstdlib>
33
#include <chrono>
34
#include <ctime>
35

36
static void PrintHelpMain() {
4✔
37
        // Please keep help lines at most 79 characters long (cursor at column 88 after last char)
38
        //     |<-- ...8901234567890123456789012345678901234567890123456789012... 80 chars -->|
39
        _COUT "Based on code of SjASM by Sjoerd Mastijn (http://www.xl2s.tk)" _ENDL;
4✔
40
        _COUT "Copyright 2004-2024 by Aprisobal and all other participants" _ENDL;
4✔
41
        //_COUT "Patches by Antipod / boo_boo / PulkoMandy and others" _ENDL;
42
        //_COUT "Tidy up by Tygrys / UB880D / Cizo / mborik / z00m" _ENDL;
43
        _COUT "\nUsage:\nsjasmplus [options] sourcefile(s)" _ENDL;
4✔
44
        _COUT "\nOption flags as follows:" _ENDL;
4✔
45
        _COUT "  -h or --help[=warnings]  Help information (you see it)" _ENDL;
4✔
46
        _COUT "  --zxnext[=cspect]        Enable ZX Spectrum Next Z80 extensions (Z80N)" _ENDL;
4✔
47
        _COUT "  --i8080                  Limit valid instructions to i8080 only (+ no fakes)" _ENDL;
4✔
48
        _COUT "  --lr35902                Sharp LR35902 CPU instructions mode (+ no fakes)" _ENDL;
4✔
49
        _COUT "  --outprefix=<path>       Prefix for save/output/.. filenames in directives" _ENDL;
4✔
50
        _COUT "  -i <path> or -I <path> or --inc= <path> (--inc only to empty the list)" _ENDL;
4✔
51
        _COUT "                           Include path (later defined have higher priority)" _ENDL;
4✔
52
        _COUT "  --lst[=<filename>]       Save listing to <filename> (<source>.lst is default)" _ENDL;
4✔
53
        _COUT "  --lstlab[=sort]          Append [sorted] symbol table to listing" _ENDL;
4✔
54
        _COUT "  --sym=<filename>         Save symbol table to <filename>" _ENDL;
4✔
55
        _COUT "  --exp=<filename>         Save exports to <filename> (see EXPORT pseudo-op)" _ENDL;
4✔
56
        //_COUT "  --autoreloc              Switch to autorelocation mode. See more in docs." _ENDL;
57
        _COUT "  --raw=<filename>         Machine code saved also to <filename> (- is STDOUT)" _ENDL;
4✔
58
        _COUT "  --sld[=<filename>]       Save Source Level Debugging data to <filename>" _ENDL;
4✔
59
        _COUT " Note: use OUTPUT, LUA/ENDLUA and other pseudo-ops to control output" _ENDL;
4✔
60
        _COUT " Logging:" _ENDL;
4✔
61
        _COUT "  --nologo                 Do not show startup message" _ENDL;
4✔
62
        _COUT "  --msg=[all|war|err|none|lst|lstlab]" _ENDL;
4✔
63
        _COUT "                           Stderr messages verbosity (\"all\" is default)" _ENDL;
4✔
64
        _COUT "  --fullpath               Show full path to file in errors" _ENDL;
4✔
65
        _COUT "  --color=[on|off|auto]    Enable or disable ANSI coloring of warnings/errors" _ENDL;
4✔
66
        _COUT " Other:" _ENDL;
4✔
67
        _COUT "  -D<NAME>[=<value>] or --define <NAME>[=<value>]" _ENDL;
4✔
68
        _COUT "                           Define <NAME> as <value>" _ENDL;
4✔
69
        _COUT "  -                        Reads STDIN as source (even in between regular files)" _ENDL;
4✔
70
        _COUT "  --longptr                No device: program counter $ can go beyond 0x10000" _ENDL;
4✔
71
        _COUT "  --reversepop             Enable reverse POP order (as in base SjASM version)" _ENDL;
4✔
72
        _COUT "  --dirbol                 Enable directives from the beginning of line" _ENDL;
4✔
73
        _COUT "  --dos866                 Encode from Windows codepage to DOS 866 (Cyrillic)" _ENDL;
4✔
74
        _COUT "  --syntax=<...>           Adjust parsing syntax, check docs for details." _ENDL;
4✔
75
}
4✔
76

77
namespace Options {
78
        const STerminalColorSequences tcols_ansi = {
79
                /*end*/                "\033[m",
80
                /*display*/        "\033[36m",                // Cyan
81
                /*warning*/        "\033[33m",                // Yellow
82
                /*error*/        "\033[31m",                // Red
83
                /*bold*/        "\033[1m"                // bold
84
        };
85

86
        const STerminalColorSequences tcols_none = {
87
                /*end*/                "",
88
                /*display*/        "",
89
                /*warning*/        "",
90
                /*error*/        "",
91
                /*bold*/        ""
92
        };
93

94
        const STerminalColorSequences* tcols = &tcols_none;
95

96
        void SetTerminalColors(bool enabled) {
1,002✔
97
                tcols = enabled ? &tcols_ansi : &tcols_none;
1,002✔
98
        }
1,002✔
99

100
        std::filesystem::path OutPrefix {""};
101
        std::filesystem::path SymbolListFName {""};
102
        std::filesystem::path ListingFName {""};
103
        std::filesystem::path ExportFName {""};
104
        std::filesystem::path DestinationFName {""};
105
        std::filesystem::path RAWFName {""};
106
        std::filesystem::path UnrealLabelListFName {""};
107
        std::filesystem::path CSpectMapFName {""};
108
        int CSpectMapPageSize = 0x4000;
109
        std::filesystem::path SourceLevelDebugFName {""};
110
        bool IsDefaultSldName = false;
111

112
        EOutputVerbosity OutputVerbosity = OV_ALL;
113
        bool IsLabelTableInListing = 0;
114
        bool IsDefaultListingName = false;
115
        bool IsShowFullPath = 0;
116
        bool AddLabelListing = false;
117
        bool HideLogo = 0;
118
        bool ShowHelp = false;
119
        bool ShowHelpWarnings = false;
120
        bool ShowVersion = false;
121
        bool NoDestinationFile = true;                // no *.out file by default
122
        SSyntax syx, systemSyntax;
123
        bool IsI8080 = false;
124
        bool IsLR35902 = false;
125
        bool IsLongPtr = false;
126
        bool SortSymbols = false;
127
        bool IsBigEndian = false;
128
        bool EmitVirtualLabels = false;
129

130
        // Include directories list is initialized with "." directory
131
        CStringsList* IncludeDirsList = new CStringsList(".");
132

133
        CDefineTable CmdDefineTable;                // is initialized by constructor
134

135
        static const char* fakes_disabled_txt_error = "Fake instructions are not enabled";
136
        static const char* fakes_in_i8080_txt_error = "Fake instructions are not implemented in i8080 mode";
137
        static const char* fakes_in_lr35902_txt_error = "Fake instructions are not implemented in Sharp LR35902 mode";
138

139
        // returns true if fakes are completely disabled, false when they are enabled
140
        // showMessage=true: will also display error/warning (use when fake ins. is emitted)
141
        // showMessage=false: can be used to silently check if fake instructions are even possible
142
        bool noFakes(bool showMessage) {
3,999✔
143
                bool fakesDisabled = Options::IsI8080 || Options::IsLR35902 || (!syx.FakeEnabled);
3,999✔
144
                if (!showMessage) return fakesDisabled;
3,999✔
145
                if (fakesDisabled) {
2,322✔
146
                        const char* errorTxt = fakes_disabled_txt_error;
315✔
147
                        if (Options::IsI8080) errorTxt = fakes_in_i8080_txt_error;
315✔
148
                        if (Options::IsLR35902) errorTxt = fakes_in_lr35902_txt_error;
315✔
149
                        Error(errorTxt, bp, SUPPRESS);
315✔
150
                        return true;
315✔
151
                }
152
                if (syx.FakeWarning) {
2,007✔
153
                        WarningById(W_FAKE, bp);
687✔
154
                }
155
                return false;
2,007✔
156
        }
157

158
        std::stack<SSyntax> SSyntax::syxStack;
159

160
        void SSyntax::resetCurrentSyntax() {
109✔
161
                new (&syx) SSyntax();        // restore defaults in current syntax
109✔
162
        }
109✔
163

164
        void SSyntax::pushCurrentSyntax() {
78✔
165
                syxStack.push(syx);                // store current syntax options into stack
78✔
166
        }
78✔
167

168
        bool SSyntax::popSyntax() {
81✔
169
                if (syxStack.empty()) return false;        // no syntax stored in stack
81✔
170
                syx = syxStack.top();        // copy the syntax values from stack
75✔
171
                syxStack.pop();
75✔
172
                return true;
75✔
173
        }
174

175
        void SSyntax::restoreSystemSyntax() {
1,499✔
176
                while (!syxStack.empty()) syxStack.pop();        // empty the syntax stack first
1,501✔
177
                syx = systemSyntax;                // reset to original system syntax
1,499✔
178
        }
1,499✔
179

180
} // eof namespace Options
181

182
static void PrintHelp(bool forceMainHelp) {
5✔
183
        if (forceMainHelp || Options::ShowHelp) PrintHelpMain();
5✔
184
        if (Options::ShowHelpWarnings) PrintHelpWarnings();
5✔
185
}
5✔
186

187
CDevice *Devices = nullptr;
188
CDevice *Device = nullptr;
189
CDevicePage *Page = nullptr;
190
char* DeviceID = nullptr;
191
TextFilePos globalDeviceSourcePos;
192
aint deviceDirectivesCount = 0;
193
static char* globalDeviceID = nullptr;
194
static aint globalDeviceZxRamTop = 0;
195

196
// extend
197
const char* fileNameFull = nullptr, * fileName = nullptr;        //fileName is either full or basename (--fullpath)
198
char* lp, line[LINEMAX], temp[LINEMAX], * bp;
199
char sline[LINEMAX2], sline2[LINEMAX2], * substitutedLine, * eolComment, ModuleName[LINEMAX];
200

201
SSource::SSource(SSource && src) {        // move constructor, "pick" the stdin pointer
1,004✔
202
        memcpy(fname, src.fname, MAX_PATH);
1,004✔
203
        stdin_log = src.stdin_log;
1,004✔
204
        src.fname[0] = 0;
1,004✔
205
        src.stdin_log = nullptr;
1,004✔
206
}
1,004✔
207

208
SSource::SSource(const char* newfname) : stdin_log(nullptr) {
775✔
209
        STRNCPY(fname, MAX_PATH, newfname, MAX_PATH-1);
775✔
210
        fname[MAX_PATH-1] = 0;
775✔
211
}
775✔
212

213
SSource::SSource(int) {
5✔
214
        fname[0] = 0;
5✔
215
        stdin_log = new stdin_log_t();
5✔
216
        stdin_log->reserve(50*1024);
5✔
217
}
5✔
218

219
SSource::~SSource() {
1,784✔
220
        if (stdin_log) delete stdin_log;
1,784✔
221
}
1,784✔
222

223
std::vector<SSource> sourceFiles;
224

225
int ConvertEncoding = ENCWIN;
226

227
EDispMode PseudoORG = DISP_NONE;
228
bool IsLabelNotFound = false, IsSubstituting = false;
229
int pass = 0, ErrorCount = 0, WarningCount = 0, IncludeLevel = -1;
230
int IsRunning = 0, donotlist = 0, listmacro = 0;
231
int adrdisp = 0, dispPageNum = LABEL_PAGE_UNDEFINED, StartAddress = -1;
232
byte* MemoryPointer=NULL;
233
int macronummer = 0, lijst = 0, reglenwidth = 0;
234
source_positions_t sourcePosStack;
235
source_positions_t smartSmcLines;
236
source_positions_t::size_type smartSmcIndex;
237
uint32_t maxlin = 0;
238
aint CurAddress = 0, CompiledCurrentLine = 0, LastParsedLabelLine = 0, PredefinedCounter = 0;
239
aint destlen = 0, size = -1L, comlin = 0;
240
const char* CurrentDirectory=NULL;
241

242
char* vorlabp=NULL, * macrolabp=NULL, * LastParsedLabel=NULL;
243
std::stack<SRepeatStack> RepeatStack;
244
CStringsList* lijstp = NULL;
245
CLabelTable LabelTable;
246
CTemporaryLabelTable TemporaryLabelTable;
247
CDefineTable DefineTable;
248
CMacroDefineTable MacroDefineTable;
249
CMacroTable MacroTable;
250
CStructureTable StructureTable;
251

252
// reserve keywords in labels table, to detect when user is defining label colliding with keyword
253
static void ReserveLabelKeywords() {
505✔
254
        for (const char* keyword : {
6,565✔
255
                "abs", "and", "exist", "high", "low", "mod", "norel", "not", "or", "shl", "shr", "xor"
256
        }) {
7,070✔
257
                LabelTable.Insert(keyword, -65536, LABEL_IS_UNDEFINED|LABEL_IS_KEYWORD);
6,060✔
258
        }
259
}
505✔
260

261
void InitPass() {
1,499✔
262
        assert(sourcePosStack.empty());                                // there's no source position [left] in the stack
1,499✔
263
        Relocation::InitPass();
1,499✔
264
        Options::SSyntax::restoreSystemSyntax();        // release all stored syntax variants and reset to initial
1,499✔
265
        uint32_t maxpow10 = 1;
1,499✔
266
        reglenwidth = 0;
1,499✔
267
        do {
268
                ++reglenwidth;
2,457✔
269
                maxpow10 *= 10;
2,457✔
270
                if (maxpow10 < 10) ExitASM(1);        // 32b overflow
2,457✔
271
        } while (maxpow10 <= maxlin);
2,457✔
272
        *ModuleName = 0;
1,499✔
273
        SetLastParsedLabel(nullptr);
1,499✔
274
        if (vorlabp) free(vorlabp);
1,499✔
275
        vorlabp = STRDUP("_");
1,499✔
276
        macrolabp = NULL;
1,499✔
277
        listmacro = 0;
1,499✔
278
        CurAddress = 0;
1,499✔
279
        CompiledCurrentLine = 0;
1,499✔
280
        smartSmcIndex = 0;
1,499✔
281
        PseudoORG = DISP_NONE; adrdisp = 0; dispPageNum = LABEL_PAGE_UNDEFINED;
1,499✔
282
        ListAddress = 0; macronummer = 0; lijst = 0; comlin = 0;
1,499✔
283
        lijstp = NULL;
1,499✔
284
        DidEmitByte();                                // reset the emitted flag
1,499✔
285
        StructureTable.ReInit();
1,499✔
286
        MacroTable.ReInit();
1,499✔
287
        MacroDefineTable.ReInit();
1,499✔
288
        DefineTable = Options::CmdDefineTable;
1,499✔
289
        TemporaryLabelTable.InitPass();
1,499✔
290

291
        // reset "device" stuff + detect "global device" directive
292
        if (globalDeviceID) {                // globalDeviceID detector has to trigger before every pass
1,499✔
293
                free(globalDeviceID);
107✔
294
                globalDeviceID = nullptr;
107✔
295
        }
296
        if (1 < pass && 1 == deviceDirectivesCount && Devices) {        // only single DEVICE used
1,499✔
297
                globalDeviceID = STRDUP(Devices->ID);                // make it global for next pass
215✔
298
                globalDeviceZxRamTop = Devices->ZxRamTop;
215✔
299
        }
300
        if (Devices) delete Devices;
1,499✔
301
        Devices = Device = nullptr;
1,499✔
302
        DeviceID = nullptr;
1,499✔
303
        Page = nullptr;
1,499✔
304
        deviceDirectivesCount = 0;
1,499✔
305
        // resurrect "global" device here
306
        if (globalDeviceID) {
1,499✔
307
                sourcePosStack.push_back(globalDeviceSourcePos);
215✔
308
                if (!SetDevice(globalDeviceID, globalDeviceZxRamTop)) {                // manually tested (remove "!")
215✔
309
                        Error("Failed to re-initialize global device", globalDeviceID, FATAL);
×
310
                }
311
                sourcePosStack.pop_back();
215✔
312
        }
313

314
        // predefined defines - (deprecated) classic sjasmplus v1.x (till v1.15.1)
315
        DefineTable.Replace("_SJASMPLUS", "1");
1,499✔
316
        DefineTable.Replace("_RELEASE", "0");
1,499✔
317
        DefineTable.Replace("_VERSION", "__VERSION__");
1,499✔
318
        DefineTable.Replace("_ERRORS", "__ERRORS__");
1,499✔
319
        DefineTable.Replace("_WARNINGS", "__WARNINGS__");
1,499✔
320
        // predefined defines - sjasmplus v2.x-like (since v1.16.0)
321
        // __DATE__ and __TIME__ are defined just once in main(...) (stored in Options::CmdDefineTable)
322
        DefineTable.Replace("__SJASMPLUS__", VERSION_NUM);                // modified from _SJASMPLUS
1,499✔
323
        DefineTable.Replace("__VERSION__", "\"" VERSION "\"");        // migrated from _VERSION
1,499✔
324
        DefineTable.Replace("__ERRORS__", ErrorCount);                        // migrated from _ERRORS (can be already > 0 from earlier pass)
1,499✔
325
        DefineTable.Replace("__WARNINGS__", WarningCount);                // migrated from _WARNINGS (can be already > 0 from earlier pass)
1,499✔
326
        DefineTable.Replace("__PASS__", pass);                                        // current pass of assembler
1,499✔
327
        DefineTable.Replace("__INCLUDE_LEVEL__", "-1");                        // include nesting
1,499✔
328
        DefineTable.Replace("__BASE_FILE__", "<none>");                        // the include-level 0 file
1,499✔
329
        DefineTable.Replace("__FILE__", "<none>");                                // current file
1,499✔
330
        DefineTable.Replace("__LINE__", "<dynamic value>");                // current line in current file
1,499✔
331
        DefineTable.Replace("__COUNTER__", "<dynamic value>");        // gcc-like, incremented upon every use
1,499✔
332
        PredefinedCounter = 0;
1,499✔
333

334
        // open Export file (defined by --exp or by EXPORT directive), even when no EXPORT is in source
335
        if (LASTPASS == pass) OpenExpFile();                                        // will not do anything if filename is empty
1,499✔
336
}
1,499✔
337

338
void FreeRAM() {
508✔
339
        if (Devices) {
508✔
340
                delete Devices;                Devices = nullptr;
159✔
341
        }
342
        if (globalDeviceID) {
508✔
343
                free(globalDeviceID);        globalDeviceID = nullptr;
108✔
344
        }
345
        for (CDeviceDef* deviceDef : DefDevices) delete deviceDef;
516✔
346
        DefDevices.clear();
508✔
347
        lijstp = NULL;                // do not delete this, should be released by owners of DUP/regular macros
508✔
348
        free(vorlabp);                vorlabp = NULL;
508✔
349
        LabelTable.RemoveAll();
508✔
350
        DefineTable.RemoveAll();
508✔
351
        SetLastParsedLabel(nullptr);
508✔
352
        if (PreviousIsLabel) {
508✔
353
                free(PreviousIsLabel);
34✔
354
                PreviousIsLabel = nullptr;
34✔
355
        }
356
        if (Options::IncludeDirsList) delete Options::IncludeDirsList;
508✔
357
        ReleaseArchivedFilenames();
508✔
358
}
508✔
359

360

361
void ExitASM(int p) {
12✔
362
        FreeRAM();
12✔
363
        if (pass == LASTPASS) {
12✔
364
                Close();
1✔
365
        }
366
        exit(p);
12✔
367
}
368

369
namespace Options {
370

371
        class COptionsParser {
372
        private:
373
                const char* arg;
374
                char opt[LINEMAX];
375
                char val[LINEMAX];
376

377
                // returns 1 when argument was processed (keyword detected, value copied into buffer)
378
                // If buffer == NULL, only detection of keyword + check for non-zero "value" is done (no copy)
379
                int CheckAssignmentOption(const char* keyword, char* buffer, const size_t bufferSize) {
2,472✔
380
                        if (strcmp(keyword, opt)) return 0;                // detect "keyword" (return 0 if not)
2,472✔
381
                        if (*val) {
517✔
382
                                if (NULL != buffer) STRCPY(buffer, bufferSize, val);
515✔
383
                        } else {
384
                                Error("no parameters found in", arg, ALL);
2✔
385
                        }
386
                        return 1;        // keyword detected, option was processed
517✔
387
                }
388

389
                // returns 1 when argument was processed (keyword detected, value copied into path var)
390
                int CheckAssignmentOption(const char* keyword, std::filesystem::path & path) {
10,701✔
391
                        if (strcmp(keyword, opt)) return 0;                // detect "keyword" (return 0 if not)
10,701✔
392
                        if (*val) {
341✔
393
                                path = val;
341✔
394
                        } else {
NEW
395
                                Error("no parameters found in", arg, ALL);
×
396
                        }
397
                        return 1;        // keyword detected, option was processed
341✔
398
                }
399

400
                static void splitByChar(const char* s, const int splitter,
3,211✔
401
                                                           char* v1, const size_t v1Size,
402
                                                           char* v2, const size_t v2Size) {
403
                        // only non-zero splitter character is supported
404
                        const char* spos = splitter ? STRCHR(s, splitter) : NULL;
3,211✔
405
                        if (NULL == spos) {
3,211✔
406
                                // splitter character not found, copy whole input string into v1, v2 = empty string
407
                                STRCPY(v1, v1Size, s);
1,191✔
408
                                v2[0] = 0;
1,191✔
409
                        } else {
410
                                // splitter found, copy string ahead splitter to v1, after it to v2
411
                                STRNCPY(v1, v1Size, s, spos - s);
2,020✔
412
                                v1[spos - s] = 0;
2,020✔
413
                                STRCPY(v2, v2Size, spos + 1);
2,020✔
414
                        }
415
                }
3,211✔
416

417
                void parseSyntaxValue() {
146✔
418
                        // Options::syx is expected to be already in default state before entering this
419
                        for (const auto & syntaxOption : val) {
443✔
420
                                switch (syntaxOption) {
443✔
421
                                case 0:   return;
146✔
422
                                // f F - instructions: fake warning, no fakes (default = fake enabled)
423
                                case 'f': syx.FakeEnabled = syx.FakeWarning = true; break;
297✔
424
                                case 'F': syx.FakeEnabled = false; break;
13✔
425
                                // a - multi-argument delimiter ",," (default is ",")
426
                                case 'a': syx.MultiArg = &doubleComma; break;
104✔
427
                                // b - single parentheses enforce mem access (default = relaxed syntax)
428
                                case 'b': syx.MemoryBrackets = 1; break;
73✔
429
                                // B - memory access brackets [] required (default = relaxed syntax)
430
                                case 'B': syx.MemoryBrackets = 2; break;
7✔
431
                                // l L - warn/error about labels using keywords (default = no message)
432
                                case 'l':
5✔
433
                                case 'L':
434
                                {
435
                                        const char this_option_is[2] = { syntaxOption, 0 };
5✔
436
                                        Error("Syntax option not implemented yet", this_option_is, PASS03);
5✔
437
                                        break;
5✔
438
                                }
439
                                // i - case insensitive instructions/directives (default = same case required)
440
                                case 'i': syx.CaseInsensitiveInstructions = true; break;
3✔
441
                                // w - warnings option: report warnings as errors
442
                                case 'w': syx.WarningsAsErrors = true; break;
38✔
443
                                // M - alias "m" and "M" for "(hl)" to cover 8080-like syntax: ADD A,M
444
                                case 'M': syx.Is_M_Memory = true; break;
3✔
445
                                // s - switch off sub-word substitution in DEFINEs (s like "Simple defines" or "Sub word")
446
                                case 's': syx.IsSubwordSubstitution = false; break;
3✔
447
                                // unrecognized option
448
                                default:
1✔
449
                                        const char this_option_is[2] = { syntaxOption, 0 };
1✔
450
                                        Error("Unrecognized syntax option", this_option_is, PASS03);
1✔
451
                                        break;
1✔
452
                                }
453
                        }
454
                }
455

456
        public:
457
                void GetOptions(const char* const * const argv, int& i, bool onlySyntaxOptions = false) {
1,046✔
458
                        while ((arg=argv[i]) && ('-' == arg[0])) {
4,868✔
459
                                bool doubleDash = false;
3,827✔
460
                                // copy "option" (up to '=' char) into `opt`, copy "value" (after '=') into `val`
461
                                if ('-' == arg[1]) {        // double-dash detected, value is expected after "="
3,827✔
462
                                        doubleDash = true;
3,169✔
463
                                        splitByChar(arg + 2, '=', opt, LINEMAX, val, LINEMAX);
3,169✔
464
                                } else {                                // single dash, parse value from second character onward
465
                                        opt[0] = arg[1];        // copy only single letter into `opt`
658✔
466
                                        opt[1] = 0;
658✔
467
                                        if (opt[0]) {                // if it was not empty, try to copy also `val`
658✔
468
                                                STRCPY(val, LINEMAX, arg + 2);
654✔
469
                                        }
470
                                }
471

472
                                // check for particular options and setup option value by it
473
                                // first check all syntax-only options which may be modified by OPT directive
474
                                if (!strcmp(opt, "zxnext")) {
3,827✔
475
                                        if (IsI8080) Error("Can't enable Next extensions while in i8080 mode", nullptr, FATAL);
45✔
476
                                        if (IsLR35902) Error("Can't enable Next extensions while in Sharp LR35902 mode", nullptr, FATAL);
44✔
477
                                        syx.IsNextEnabled = 1;
43✔
478
                                        if (!strcmp(val, "cspect")) syx.IsNextEnabled = 2;        // CSpect emulator extensions
43✔
479
                                } else if (!strcmp(opt, "reversepop")) {
3,782✔
480
                                        syx.IsReversePOP = true;
19✔
481
                                } else if (!strcmp(opt, "dirbol")) {
3,763✔
482
                                        syx.IsPseudoOpBOF = true;
24✔
483
                                } else if (!strcmp(opt, "nofakes")) {
3,739✔
484
                                        syx.FakeEnabled = false;
5✔
485
                                        // was deprecated, as it is provided by `--syntax=F` too, but now I decided to keep also this older option
486
                                        // (it's less cryptic than the --syntax letter soup, one duplicity of functionality will not end the world, right?)
487
                                } else if (!strcmp(opt, "syntax")) {
3,734✔
488
                                        parseSyntaxValue();
146✔
489
                                } else if (!doubleDash && 'W' == opt[0]) {
3,588✔
490
                                        CliWoption(val);
588✔
491
                                } else if (onlySyntaxOptions) {
3,000✔
492
                                        // rest of the options is available only when launching the sjasmplus
493
                                        return;
3✔
494
                                } else if (!strcmp(opt, "lr35902")) {
2,997✔
495
                                        IsLR35902 = true;
16✔
496
                                        // force (silently) other CPU modes OFF
497
                                        IsI8080 = false;
16✔
498
                                        syx.IsNextEnabled = 0;
16✔
499
                                } else if (!strcmp(opt, "i8080")) {
2,981✔
500
                                        IsI8080 = true;
15✔
501
                                        // force (silently) other CPU modes OFF
502
                                        IsLR35902 = false;
15✔
503
                                        syx.IsNextEnabled = 0;
15✔
504
                                } else if ((!doubleDash && 'h' == opt[0] && !val[0]) || (doubleDash && !strcmp(opt, "help"))) {
2,966✔
505
                                        ShowHelp |= strcmp("warnings", val);
4✔
506
                                        ShowHelpWarnings |= !strcmp("warnings", val);
4✔
507
                                } else if (doubleDash && !strcmp(opt, "version")) {
2,962✔
508
                                        ShowVersion = true;
2✔
509
                                } else if (!strcmp(opt, "lstlab")) {
2,960✔
510
                                        AddLabelListing = true;
485✔
511
                                        if (val[0]) SortSymbols = !strcmp("sort", val);
485✔
512
                                } else if (!strcmp(opt, "longptr")) {
2,475✔
513
                                        IsLongPtr = true;
3✔
514
                                } else if (CheckAssignmentOption("msg", NULL, 0)) {
2,472✔
515
                                        if (!*val) {
517✔
516
                                                // nothing to do, CheckAssignmentOption already displayed error
517
                                        } else if (!strcmp("none", val)) {
515✔
518
                                                OutputVerbosity = OV_NONE;
442✔
519
                                                HideLogo = true;
442✔
520
                                        } else if (!strcmp("err", val)) {
73✔
521
                                                OutputVerbosity = OV_ERROR;
1✔
522
                                        } else if (!strcmp("war", val)) {
72✔
523
                                                OutputVerbosity = OV_WARNING;
26✔
524
                                        } else if (!strcmp("all", val)) {
46✔
525
                                                OutputVerbosity = OV_ALL;
2✔
526
                                        } else if (!strcmp("lst", val)) {
44✔
527
                                                OutputVerbosity = OV_LST;
2✔
528
                                                AddLabelListing = false;
2✔
529
                                                HideLogo = true;
2✔
530
                                        } else if (!strcmp("lstlab", val)) {
42✔
531
                                                OutputVerbosity = OV_LST;
41✔
532
                                                AddLabelListing = true;
41✔
533
                                                SortSymbols = true;
41✔
534
                                                HideLogo = true;
41✔
535
                                        } else {
536
                                                Error("unexpected parameter in", arg, ALL);
1✔
537
                                        }
538
                                } else if (!strcmp(opt, "lst") && !val[0]) {
1,955✔
539
                                        IsDefaultListingName = true;
3✔
540
                                } else if (!strcmp(opt, "sld") && !val[0]) {
1,952✔
541
                                        IsDefaultSldName = true;
4✔
542
                                } else if (
1,948✔
543
                                        CheckAssignmentOption("outprefix", OutPrefix) ||
1,948✔
544
                                        CheckAssignmentOption("sym", SymbolListFName) ||
1,947✔
545
                                        CheckAssignmentOption("lst", ListingFName) ||
1,941✔
546
                                        CheckAssignmentOption("exp", ExportFName) ||
1,629✔
547
                                        CheckAssignmentOption("sld", SourceLevelDebugFName) ||
5,506✔
548
                                        CheckAssignmentOption("raw", RAWFName) ) {
1,611✔
549
                                        // was proccessed inside CheckAssignmentOption function
550
                                } else if (!strcmp(opt, "fullpath")) {
1,607✔
551
                                        IsShowFullPath = 1;
507✔
552
                                } else if (!strcmp(opt, "color")) {
1,100✔
553
                                        if (!strcmp("on", val)) {
492✔
554
                                                SetTerminalColors(true);
3✔
555
                                        } else if (!strcmp("off", val)) {
489✔
556
                                                SetTerminalColors(false);
482✔
557
                                        } else if (!strcmp("auto", val)) {
7✔
558
                                                // already heuristically detected, nothing to do
559
                                        } else {
560
                                                Error("invalid --color setting (use: on|off|auto)", val, ALL);
1✔
561
                                        }
562
                                } else if (!strcmp(opt, "nologo")) {
608✔
563
                                        HideLogo = 1;
510✔
564
                                } else if (!strcmp(opt, "dos866")) {
98✔
565
                                        ConvertEncoding = ENCDOS;
1✔
566
                                } else if ((doubleDash && !strcmp(opt, "inc")) ||
97✔
567
                                                        (!doubleDash && 'i' == opt[0]) ||
73✔
568
                                                        (!doubleDash && 'I' == opt[0])) {
68✔
569
                                        if (*val) {
48✔
570
                                                IncludeDirsList = new CStringsList(val, IncludeDirsList);
35✔
571
                                        } else {
572
                                                if (!doubleDash || '=' == arg[5]) {
13✔
573
                                                        if (argv[i+1] && '-' != argv[i+1][0]) {                // include path provided as next argument (after space, like gcc)
8✔
574
                                                                IncludeDirsList = new CStringsList(argv[++i], IncludeDirsList);        // also advance i++
5✔
575
                                                        } else {
576
                                                                Error("no include path found in", arg, ALL);
3✔
577
                                                        }
578
                                                } else {        // individual `--inc` without "=path" will RESET include dirs
579
                                                        if (IncludeDirsList) delete IncludeDirsList;
5✔
580
                                                        IncludeDirsList = nullptr;
5✔
581
                                                }
582
                                        }
583
                                } else if (!strcmp(opt, "define")) {        // for --define name=value the next argv is used (if available)
49✔
584
                                        const char* defarg = argv[i+1] ? argv[++i] : nullptr;
4✔
585
                                        char defN[LINEMAX] {}, defV[LINEMAX] {};
4✔
586
                                        if (defarg) splitByChar(defarg, '=', defN, LINEMAX, defV, LINEMAX);
4✔
587
                                        if (*defN) CmdDefineTable.Add(defN, defV, nullptr);
4✔
588
                                        else Error("missing define value", defarg, ALL);
1✔
589
                                } else if (!doubleDash && 'D' == opt[0]) {
45✔
590
                                        char defN[LINEMAX], defV[LINEMAX];
591
                                        if (*val) {                // for -Dname=value the `val` contains "name=value" string
40✔
592
                                                splitByChar(val, '=', defN, LINEMAX, defV, LINEMAX);
39✔
593
                                                CmdDefineTable.Add(defN, defV, NULL);
39✔
594
                                        } else {
595
                                                Error("no parameters found in", arg, ALL);
1✔
596
                                        }
597
                                } else if (!doubleDash && 0 == opt[0]) {
45✔
598
                                        // only single "-" was on command line = source STDIN
599
                                        sourceFiles.push_back(SSource(1));                // special constructor for stdin input
4✔
600
                                } else {
601
                                        Error("unrecognized option", arg, ALL);
1✔
602
                                }
603

604
                                ++i;                                        // next CLI argument
3,822✔
605
                        } // end of while ((arg=argv[i]) && ('-' == arg[0]))
606
                }
607

608
                void checkIncludePaths(const CStringsList* includes) {
508✔
609
                        while (nullptr != includes) {
1,051✔
610
                                const std::filesystem::path dir(includes->string);
543✔
611
                                if (!std::filesystem::is_directory(dir)) {
543✔
612
                                        const char* errtxt = '~' == includes->string[0] ? "include path starts with ~ (check docs)" : "include path not found";
7✔
613
                                        Error(errtxt, includes->string, ALL);
7✔
614
                                }
615
                                includes = includes->next;
543✔
616
                        }
543✔
617
                        return;
508✔
618
                }
619
        };
620

621
        int parseSyntaxOptions(int n, char** options) {
363✔
622
                if (n <= 0) return 0;
363✔
623
                int i = 0;
259✔
624
                Options::COptionsParser optParser;
625
                optParser.GetOptions(options, i, true);
259✔
626
                return i;
257✔
627
        }
628
}
629

630
// ==============================================================================================
631
// == UnitTest++ part, checking if unit tests are requested and does launch test-runner then   ==
632
// ==============================================================================================
633
#ifdef ADD_UNIT_TESTS
634

635
# include "UnitTest++/UnitTest++.h"
636

637
# define STOP_MAKE_BY_NON_ZERO_EXIT_CODE 0
638

639
//detect "--unittest" switch, prepare UnitTest++, run the test runner, collect results, exit
640
# define CHECK_UNIT_TESTS \
641
        { \
642
                if (2 == argc && !strcmp("--unittest", argv[1])) { \
643
                        _COUT "SjASMPlus \033[96mv" VERSION "\033[0m | \033[95mrunning unit tests:\033[0m" _ENDL _END \
644
                        int exitCode = STOP_MAKE_BY_NON_ZERO_EXIT_CODE + UnitTest::RunAllTests(); \
645
                        if (exitCode) _COUT "\033[91mNon-zero result from test runner!\033[0m" _ENDL _END \
646
                        else _COUT "\033[92mOK: 0 UnitTest++ tests failed.\033[0m" _ENDL _END \
647
                        exit(exitCode); \
648
                } \
649
        }
650
#else
651

652
# define CHECK_UNIT_TESTS { /* no unit tests in this build */ }
653

654
#endif
655

656
// == end of UnitTest++ part ====================================================================
657

658
static char launch_directory[MAX_PATH];
659

660
#ifdef WIN32
661
int main(int argc, char* argv[]) {
662
#else
663
int main(int argc, char **argv) {
517✔
664
#endif
665
        Options::SetTerminalColors(autoColorsDetection());
517✔
666

667
        const char* logo = "SjASMPlus Z80 Cross-Assembler v" VERSION " (https://github.com/z00m128/sjasmplus)";
517✔
668

669
        sourcePosStack.reserve(32);
517✔
670
        smartSmcLines.reserve(64);
517✔
671
        sourceFiles.reserve(32);
517✔
672
        archivedFileNames.reserve(64);
517✔
673

674
        CHECK_UNIT_TESTS                // UnitTest++ extra handling in specially built executable
517✔
675

676
        const word little_endian_test[] = { 0x1234 };
516✔
677
        const byte le_test_byte = *reinterpret_cast<const byte*>(little_endian_test);
516✔
678
        Options::IsBigEndian = (0x12 == le_test_byte);
516✔
679

680
        // start counter
681
        long dwStart = GetTickCount();
516✔
682

683
        // get current directory
684
        SJ_GetCurrentDirectory(MAX_PATH, launch_directory);
516✔
685
        CurrentDirectory = launch_directory;
516✔
686

687
        Options::COptionsParser optParser;
688
        char* envFlags = std::getenv("SJASMPLUSOPTS");
516✔
689
        if (nullptr != envFlags) {
516✔
690
                // split environment arguments into "argc, argv" like variables (by white-space)
691
                char* parsedOptsArray[33] {};        // there must be one more nullptr in the array (32+1)
4✔
692
                int optI = 0, charI = 0;
4✔
693
                while (optI < 32 && !SkipBlanks(envFlags)) {
46✔
694
                        parsedOptsArray[optI++] = temp + charI;
42✔
695
                        while (*envFlags && !White(*envFlags) && charI < LINEMAX-1) temp[charI++] = *envFlags++;
323✔
696
                        temp[charI++] = 0;
42✔
697
                }
698
                if (!SkipBlanks(envFlags)) {
4✔
699
                        Error("SJASMPLUSOPTS environment variable contains too many options (max is 32)", nullptr, ALL);
1✔
700
                }
701
                // process environment variable ahead of command line options (in the same way)
702
                int i = 0;
4✔
703
                while (parsedOptsArray[i]) {
6✔
704
                        optParser.GetOptions(parsedOptsArray, i);
5✔
705
                        if (!parsedOptsArray[i]) break;
5✔
706
                        sourceFiles.push_back(SSource(parsedOptsArray[i++]));
2✔
707
                }
708
        }
709

710
        // setup __DATE__ and __TIME__ macros (setup them just once, not every pass!)
711
        auto now = std::chrono::system_clock::now();
516✔
712
        std::time_t now_c = std::chrono::system_clock::to_time_t(now);
516✔
713
        std::tm now_tm = *std::localtime(&now_c);        // lgtm [cpp/potentially-dangerous-function]
516✔
714
        char dateBuffer[32] = {}, timeBuffer[32] = {};
516✔
715
        SPRINTF3(dateBuffer, 30, "\"%04d-%02d-%02d\"", now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday);
516✔
716
        SPRINTF3(timeBuffer, 30, "\"%02d:%02d:%02d\"", now_tm.tm_hour, now_tm.tm_min, now_tm.tm_sec);
516✔
717
        Options::CmdDefineTable.Add("__DATE__", dateBuffer, nullptr);
516✔
718
        Options::CmdDefineTable.Add("__TIME__", timeBuffer, nullptr);
516✔
719

720
        int i = 1;
516✔
721
        if (argc > 1) {
516✔
722
                while (argv[i]) {
1,286✔
723
                        optParser.GetOptions(argv, i);
782✔
724
                        if (!argv[i]) break;
782✔
725
                        sourceFiles.push_back(SSource(argv[i++]));
771✔
726
                }
727
        }
728
        // warn about BE-host only when there's any CLI argument && after CLI options were parsed
729
        if (2 <= argc && Options::IsBigEndian) WarningById(W_BE_HOST, nullptr, W_EARLY);
516✔
730
        if (Options::IsDefaultListingName && Options::ListingFName.has_filename()) {
516✔
731
                Error("Using both  --lst  and  --lst=<filename>  is not possible.", NULL, FATAL);
1✔
732
        }
733
        if (OV_LST == Options::OutputVerbosity && (Options::IsDefaultListingName || Options::ListingFName.has_filename())) {
515✔
734
                Error("Using  --msg=lst[lab]  and other list options is not possible.", NULL, FATAL);
1✔
735
        }
736
        if (Options::IsDefaultSldName && Options::SourceLevelDebugFName.has_filename()) {
514✔
737
                Error("Using both  --sld  and  --sld=<filename>  is not possible.", NULL, FATAL);
1✔
738
        }
739
        Options::systemSyntax = Options::syx;                // create copy of initial system settings of syntax
513✔
740

741
        if (argc == 1 || Options::ShowHelp || Options::ShowHelpWarnings) {
513✔
742
                _COUT logo _ENDL;
5✔
743
                PrintHelp(argc == 1);
5✔
744
                exit(argc == 1);
5✔
745
        }
746

747
        if (!Options::HideLogo) {
508✔
748
                _CERR logo _ENDL;
3✔
749
        }
750

751
        if (!Options::IsShowFullPath && (Options::IsDefaultSldName || Options::SourceLevelDebugFName.has_filename())) {
508✔
752
                Warning("missing  --fullpath  with  --sld  may produce incomplete file paths.", NULL, W_EARLY);
1✔
753
        }
754

755
        optParser.checkIncludePaths(Options::IncludeDirsList);
508✔
756

757
        if (Options::ShowVersion) {
508✔
758
                if (Options::HideLogo) {        // if "sjasmplus --version --nologo", emit only the raw VERSION
2✔
759
                        _CERR VERSION _ENDL;
1✔
760
                }
761
                // otherwise the full logo was already printed
762
                // now check if there were some sources to assemble, if NOT, exit with "OK"!
763
                if (0 == sourceFiles.size()) exit(0);
2✔
764
        }
765

766
        // exit with error if no input file were specified
767
        if (0 == sourceFiles.size()) {
506✔
768
                Error("no inputfile(s)", nullptr, ALL);
1✔
769
                exit(1);
1✔
770
        }
771

772
        // create default output name, if not specified
773
        ConstructDefaultFilename(Options::DestinationFName, ".out");
505✔
774
        int base_encoding = ConvertEncoding;
505✔
775

776
        // init some vars
777
        InitCPU();
505✔
778

779
        // open lists (if not set to "default" file name, then the OpenFile will handle it)
780
        OpenList();
505✔
781

782
        ReserveLabelKeywords();
505✔
783

784
        do {
785
                ++pass;
1,499✔
786
                if (pass == LASTPASS) OpenSld();        //open source level debugging file (BEFORE InitPass)
1,499✔
787
                InitPass();
1,499✔
788
                if (pass == LASTPASS) OpenDest();
1,499✔
789

790
                for (SSource & src : sourceFiles) {
3,793✔
791
                        IsRunning = 1;
2,303✔
792
                        ConvertEncoding = base_encoding;
2,303✔
793
                        OpenFile(src.fname, false, src.stdin_log);
2,303✔
794
                }
795

796
                while (!RepeatStack.empty()) {
1,493✔
797
                        sourcePosStack.push_back(RepeatStack.top().sourcePos);        // mark DUP line with error
3✔
798
                        Error("[DUP/REPT] missing EDUP/ENDR to end repeat-block");
3✔
799
                        sourcePosStack.pop_back();
3✔
800
                        RepeatStack.pop();
3✔
801
                }
802

803
                if (DISP_NONE != PseudoORG) {
1,490✔
804
                        CurAddress = adrdisp;
3✔
805
                        PseudoORG = DISP_NONE;
3✔
806
                }
807

808
                if (Options::OutputVerbosity <= OV_ALL) {
1,490✔
809
                        if (pass != LASTPASS) {
9✔
810
                                _CERR "Pass " _CMDL pass _CMDL " complete (" _CMDL ErrorCount _CMDL " errors)" _ENDL;
6✔
811
                        } else {
812
                                _CERR "Pass 3 complete" _ENDL;
3✔
813
                        }
814
                }
815
        } while (pass < LASTPASS);
1,490✔
816

817
        pass = 9999; /* added for detect end of compiling */
496✔
818

819
        // dump label table into listing file, the explicit one (Options::IsDefaultListingName == false)
820
        if (Options::AddLabelListing) LabelTable.Dump();
496✔
821

822
        Close();
496✔
823

824
        if (Options::UnrealLabelListFName.has_filename()) {
496✔
825
                LabelTable.DumpForUnreal();
10✔
826
        }
827

828
        if (Options::CSpectMapFName.has_filename()) {
496✔
829
                LabelTable.DumpForCSpect();
12✔
830
        }
831

832
        if (Options::SymbolListFName.has_filename()) {
496✔
833
                LabelTable.DumpSymbols();
6✔
834
        }
835

836
        if (Options::OutputVerbosity <= OV_ALL) {
496✔
837
                _CERR "Errors: " _CMDL ErrorCount _CMDL ", warnings: " _CMDL WarningCount _CMDL ", compiled: " _CMDL CompiledCurrentLine _CMDL " lines" _END;
3✔
838

839
                double dwCount;
840
                dwCount = GetTickCount() - dwStart;
3✔
841
                if (dwCount < 0) dwCount = 0;
3✔
842
                char workTimeTxt[200] = "";
3✔
843
                SPRINTF1(workTimeTxt, 200, ", work time: %.3f seconds", dwCount / 1000);
3✔
844

845
                _CERR workTimeTxt _ENDL;
3✔
846
        }
847

848
        cout << flush;
496✔
849

850
        // free RAM
851
        FreeRAM();
496✔
852

853
        lua_impl_close();
496✔
854

855
        return (ErrorCount != 0);
496✔
856
}
857
//eof sjasm.cpp
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc