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

z00m128 / sjasmplus / 1460

21 Jan 2025 03:16AM UTC coverage: 96.3% (-0.2%) from 96.454%
1460

push

cirrus-ci

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

Adding GetInputFileName using std::filesystem::path and making it work
for main OpenFile and IncludeFile.

TODO:
- convert other input file situations like INCBIN, etc..
- get rid of obsolete functions later

Committing this as intermittent step just in case I will be unable
to develop this further soon or if I need rollback to working version.
(and to verify all CI platforms to pass tests)

100 of 105 new or added lines in 6 files covered. (95.24%)

12 existing lines in 2 files now uncovered.

9631 of 10001 relevant lines covered (96.3%)

169323.02 hits per line

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

99.34
/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
        // CStringsList* IncludeDirsList = nullptr;        // wrong?
133
        //FIXME ^ explain this to me like I'm 5yo, seems like empty list does same job after changing to fs::path
134
        // ... but that's weird, wasn't there something about includes vs "anchor" dir? But tests *almost* do pass (just extra "./").
135
        // Look into it harder! ... I think there should be difference, because CurrentDirectory / fname
136
        // is tested and then all include path, so "." is not <empty list>
137
        // I guess "." could become at least empty path "", but include paths must contain it/something
138
        //FIXME and this list itself could become vector<fs::path> now?
139

140
        CDefineTable CmdDefineTable;                // is initialized by constructor
141

142
        static const char* fakes_disabled_txt_error = "Fake instructions are not enabled";
143
        static const char* fakes_in_i8080_txt_error = "Fake instructions are not implemented in i8080 mode";
144
        static const char* fakes_in_lr35902_txt_error = "Fake instructions are not implemented in Sharp LR35902 mode";
145

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

165
        std::stack<SSyntax> SSyntax::syxStack;
166

167
        void SSyntax::resetCurrentSyntax() {
109✔
168
                new (&syx) SSyntax();        // restore defaults in current syntax
109✔
169
        }
109✔
170

171
        void SSyntax::pushCurrentSyntax() {
78✔
172
                syxStack.push(syx);                // store current syntax options into stack
78✔
173
        }
78✔
174

175
        bool SSyntax::popSyntax() {
81✔
176
                if (syxStack.empty()) return false;        // no syntax stored in stack
81✔
177
                syx = syxStack.top();        // copy the syntax values from stack
75✔
178
                syxStack.pop();
75✔
179
                return true;
75✔
180
        }
181

182
        void SSyntax::restoreSystemSyntax() {
1,499✔
183
                while (!syxStack.empty()) syxStack.pop();        // empty the syntax stack first
1,501✔
184
                syx = systemSyntax;                // reset to original system syntax
1,499✔
185
        }
1,499✔
186

187
} // eof namespace Options
188

189
static void PrintHelp(bool forceMainHelp) {
5✔
190
        if (forceMainHelp || Options::ShowHelp) PrintHelpMain();
5✔
191
        if (Options::ShowHelpWarnings) PrintHelpWarnings();
5✔
192
}
5✔
193

194
CDevice *Devices = nullptr;
195
CDevice *Device = nullptr;
196
CDevicePage *Page = nullptr;
197
char* DeviceID = nullptr;
198
TextFilePos globalDeviceSourcePos;
199
aint deviceDirectivesCount = 0;
200
static char* globalDeviceID = nullptr;
201
static aint globalDeviceZxRamTop = 0;
202

203
// extend
204
fullpath_p_t fileNameFull = nullptr;
205
char* lp, line[LINEMAX], temp[LINEMAX], * bp;
206
char sline[LINEMAX2], sline2[LINEMAX2], * substitutedLine, * eolComment, ModuleName[LINEMAX];
207

208
SSource::SSource(SSource && src) {        // move constructor, "pick" the stdin pointer
1,004✔
209
        memcpy(fname, src.fname, MAX_PATH);
1,004✔
210
        stdin_log = src.stdin_log;
1,004✔
211
        src.fname[0] = 0;
1,004✔
212
        src.stdin_log = nullptr;
1,004✔
213
}
1,004✔
214

215
SSource::SSource(const char* newfname) : stdin_log(nullptr) {
775✔
216
        STRNCPY(fname, MAX_PATH, newfname, MAX_PATH-1);
775✔
217
        fname[MAX_PATH-1] = 0;
775✔
218
}
775✔
219

220
SSource::SSource(int) {
5✔
221
        fname[0] = 0;
5✔
222
        stdin_log = new stdin_log_t();
5✔
223
        stdin_log->reserve(50*1024);
5✔
224
}
5✔
225

226
SSource::~SSource() {
1,784✔
227
        if (stdin_log) delete stdin_log;
1,784✔
228
}
1,784✔
229

230
std::vector<SSource> sourceFiles;
231

232
int ConvertEncoding = ENCWIN;
233

234
EDispMode PseudoORG = DISP_NONE;
235
bool IsLabelNotFound = false, IsSubstituting = false;
236
int pass = 0, ErrorCount = 0, WarningCount = 0, IncludeLevel = -1;
237
int IsRunning = 0, donotlist = 0, listmacro = 0;
238
int adrdisp = 0, dispPageNum = LABEL_PAGE_UNDEFINED, StartAddress = -1;
239
byte* MemoryPointer=NULL;
240
int macronummer = 0, lijst = 0, reglenwidth = 0;
241
source_positions_t sourcePosStack;
242
source_positions_t smartSmcLines;
243
source_positions_t::size_type smartSmcIndex;
244
uint32_t maxlin = 0;
245
aint CurAddress = 0, CompiledCurrentLine = 0, LastParsedLabelLine = 0, PredefinedCounter = 0;
246
aint destlen = 0, size = -1L, comlin = 0;
247
std::filesystem::path CurrentDirectory {};
248

249
char* vorlabp=NULL, * macrolabp=NULL, * LastParsedLabel=NULL;
250
std::stack<SRepeatStack> RepeatStack;
251
CStringsList* lijstp = NULL;
252
CLabelTable LabelTable;
253
CTemporaryLabelTable TemporaryLabelTable;
254
CDefineTable DefineTable;
255
CMacroDefineTable MacroDefineTable;
256
CMacroTable MacroTable;
257
CStructureTable StructureTable;
258

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

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

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

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

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

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

367

368
void ExitASM(int p) {
12✔
369
        FreeRAM();
12✔
370
        if (pass == LASTPASS) {
12✔
371
                Close();
1✔
372
        }
373
        exit(p);
12✔
374
}
375

376
namespace Options {
377

378
        class COptionsParser {
379
        private:
380
                const char* arg;
381
                char opt[LINEMAX];
382
                char val[LINEMAX];
383

384
                // returns 1 when argument was processed (keyword detected, value copied into path var)
385
                int CheckAssignmentOption(const char* keyword, std::filesystem::path & path) {
10,705✔
386
                        if (strcmp(keyword, opt)) return 0;                // detect "keyword" (return 0 if not)
10,705✔
387
                        if (*val) {
342✔
388
                                path = val;
341✔
389
                        } else {
390
                                Error("no parameters found in", arg, ALL);
1✔
391
                        }
392
                        return 1;        // keyword detected, option was processed
342✔
393
                }
394

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

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

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

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

597
                                ++i;                                        // next CLI argument
3,823✔
598
                        } // end of while ((arg=argv[i]) && ('-' == arg[0]))
599
                }
600

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

614
        int parseSyntaxOptions(int n, char** options) {
363✔
615
                if (n <= 0) return 0;
363✔
616
                int i = 0;
259✔
617
                Options::COptionsParser optParser;
618
                optParser.GetOptions(options, i, true);
259✔
619
                return i;
257✔
620
        }
621
}
622

623
// ==============================================================================================
624
// == UnitTest++ part, checking if unit tests are requested and does launch test-runner then   ==
625
// ==============================================================================================
626
#ifdef ADD_UNIT_TESTS
627

628
# include "UnitTest++/UnitTest++.h"
629

630
# define STOP_MAKE_BY_NON_ZERO_EXIT_CODE 0
631

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

645
# define CHECK_UNIT_TESTS { /* no unit tests in this build */ }
646

647
#endif
648

649
// == end of UnitTest++ part ====================================================================
650

651
static char launch_directory[MAX_PATH];
652

653
#ifdef WIN32
654
int main(int argc, char* argv[]) {
655
#else
656
int main(int argc, char **argv) {
517✔
657
#endif
658
        Options::SetTerminalColors(autoColorsDetection());
517✔
659

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

662
        sourcePosStack.reserve(32);
517✔
663
        smartSmcLines.reserve(64);
517✔
664
        sourceFiles.reserve(32);
517✔
665
        archivedFileNames.reserve(64);
517✔
666

667
        CHECK_UNIT_TESTS                // UnitTest++ extra handling in specially built executable
517✔
668

669
        const word little_endian_test[] = { 0x1234 };
516✔
670
        const byte le_test_byte = *reinterpret_cast<const byte*>(little_endian_test);
516✔
671
        Options::IsBigEndian = (0x12 == le_test_byte);
516✔
672

673
        // start counter
674
        long dwStart = GetTickCount();
516✔
675

676
        // get current directory
677
        SJ_GetCurrentDirectory(MAX_PATH, launch_directory);
516✔
678
        CurrentDirectory = launch_directory;
516✔
679

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

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

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

734
        if (argc == 1 || Options::ShowHelp || Options::ShowHelpWarnings) {
513✔
735
                _COUT logo _ENDL;
5✔
736
                PrintHelp(argc == 1);
5✔
737
                exit(argc == 1);
5✔
738
        }
739

740
        if (!Options::HideLogo) {
508✔
741
                _CERR logo _ENDL;
3✔
742
        }
743

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

748
        optParser.checkIncludePaths(Options::IncludeDirsList);
508✔
749

750
        if (Options::ShowVersion) {
508✔
751
                if (Options::HideLogo) {        // if "sjasmplus --version --nologo", emit only the raw VERSION
2✔
752
                        _CERR VERSION _ENDL;
1✔
753
                }
754
                // otherwise the full logo was already printed
755
                // now check if there were some sources to assemble, if NOT, exit with "OK"!
756
                if (0 == sourceFiles.size()) exit(0);
2✔
757
        }
758

759
        // exit with error if no input file were specified
760
        if (0 == sourceFiles.size()) {
506✔
761
                Error("no inputfile(s)", nullptr, ALL);
1✔
762
                exit(1);
1✔
763
        }
764

765
        // create default output name, if not specified
766
        ConstructDefaultFilename(Options::DestinationFName, ".out");
505✔
767
        int base_encoding = ConvertEncoding;
505✔
768

769
        // init some vars
770
        InitCPU();
505✔
771

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

775
        ReserveLabelKeywords();
505✔
776

777
        do {
778
                ++pass;
1,499✔
779
                if (pass == LASTPASS) OpenSld();        //open source level debugging file (BEFORE InitPass)
1,499✔
780
                InitPass();
1,499✔
781
                if (pass == LASTPASS) OpenDest();
1,499✔
782

783
                static bool warn_stdin_default_lst = true;
784
                for (SSource & src : sourceFiles) {
3,793✔
785
                        if (src.stdin_log && warn_stdin_default_lst && 1 == pass && Options::IsDefaultListingName) {
2,303✔
NEW
786
                                Warning("use explicit --lst=<filename> for <stdin> source", nullptr, W_EARLY);
×
NEW
787
                                warn_stdin_default_lst = false;
×
788
                                //FIXME add test ^^
789
                        }
790
                        IsRunning = 1;
2,303✔
791
                        ConvertEncoding = base_encoding;
2,303✔
792
                        // don't search include paths, but use GetInputFile to get "archived" fullpath_ref_t for c_str() lifetime
793
                        fullpath_ref_t src_fname = GetInputFile(delim_string_t(src.fname, DT_COUNT));
2,303✔
794
                        OpenFile(src_fname, src.stdin_log);
2,303✔
795
                }
796

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

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

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

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

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

823
        Close();
496✔
824

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

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

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

837
        // sync files, release everything
838
        cout << flush;
496✔
839
        FreeRAM();
496✔
840
        lua_impl_close();
496✔
841

842
        if (Options::OutputVerbosity <= OV_ALL) {
496✔
843
                double dwCount;
844
                dwCount = GetTickCount() - dwStart;
3✔
845
                if (dwCount < 0) dwCount = 0;
3✔
846
                fprintf(stderr, "Errors: %d, warnings: %d, compiled: %d lines, work time: %.3f seconds\n",
3✔
847
                                ErrorCount, WarningCount, CompiledCurrentLine, dwCount / 1000);
848
        }
849

850
        return (ErrorCount != 0);
496✔
851
}
852
//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