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

z00m128 / sjasmplus / 1470

27 Jan 2025 01:40AM UTC coverage: 96.394% (-0.01%) from 96.404%
1470

Pull #255

cirrus-ci

ped7g
sjio: extended --fullpath - inner implementation

Unify filename string used for output (errors, listing, sld, ...) and
make it depend on the --fullpath option.

Add three levels internally: 1) base filename (default), 2) relative to
launch dir, 3) absolute full path

The only exception is opening some.lua file where the c-string path to
open is processed by lua itself and also preserved for source-positon
debug info, which becomes visible in error messages - here the
relative-to-launch-dir normalization is enforced (to make error strings
reasonable).

The `--fullpath` option itself is not modified in this commit yet, still
works as before (allowing only options 1) and 2))
Pull Request #255: more --fullpath options

33 of 37 new or added lines in 7 files covered. (89.19%)

2 existing lines in 1 file now uncovered.

9544 of 9901 relevant lines covered (96.39%)

171031.41 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 = false;
114
        bool IsDefaultListingName = false;
115
        EFileVerbosity FileVerbosity = FNAME_BASE;
116
        bool AddLabelListing = false;
117
        bool HideLogo = false;
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

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

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

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

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

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

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

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

365

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

374
namespace Options {
375

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

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

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

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

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

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

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

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

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

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

626
# include "UnitTest++/UnitTest++.h"
627

628
# define STOP_MAKE_BY_NON_ZERO_EXIT_CODE 0
629

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

643
# define CHECK_UNIT_TESTS { /* no unit tests in this build */ }
644

645
#endif
646

647
// == end of UnitTest++ part ====================================================================
648

649
#ifdef WIN32
650
int main(int argc, char* argv[]) {
651
#else
652
int main(int argc, char **argv) {
517✔
653
#endif
654
        Options::SetTerminalColors(autoColorsDetection());
517✔
655

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

658
        sourcePosStack.reserve(32);
517✔
659
        smartSmcLines.reserve(64);
517✔
660
        sourceFiles.reserve(32);
517✔
661

662
        CHECK_UNIT_TESTS                // UnitTest++ extra handling in specially built executable
517✔
663

664
        const word little_endian_test[] = { 0x1234 };
516✔
665
        const byte le_test_byte = *reinterpret_cast<const byte*>(little_endian_test);
516✔
666
        Options::IsBigEndian = (0x12 == le_test_byte);
516✔
667

668
        // start counter
669
        long dwStart = GetTickCount();
516✔
670

671
        // get current directory
672
        LaunchDirectory = std::filesystem::current_path();
516✔
673
        CurrentDirectory = LaunchDirectory;
516✔
674

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

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

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

729
        if (argc == 1 || Options::ShowHelp || Options::ShowHelpWarnings) {
513✔
730
                _COUT logo _ENDL;
5✔
731
                PrintHelp(argc == 1);
5✔
732
                exit(argc == 1);
5✔
733
        }
734

735
        if (!Options::HideLogo) {
508✔
736
                _CERR logo _ENDL;
3✔
737
        }
738

739
        if ((Options::FNAME_BASE == Options::FileVerbosity) &&
511✔
740
                (Options::IsDefaultSldName || Options::SourceLevelDebugFName.has_filename())) {
3✔
741
                Warning("missing  --fullpath  with  --sld  may produce incomplete file paths.", NULL, W_EARLY);
1✔
742
        }
743

744
        optParser.checkIncludePaths(Options::IncludeDirsList);
508✔
745

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

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

761
        // create default output name, if not specified
762
        ConstructDefaultFilename(Options::DestinationFName, "out");
505✔
763
        int base_encoding = ConvertEncoding;
505✔
764

765
        // init some vars
766
        InitCPU();
505✔
767

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

771
        ReserveLabelKeywords();
505✔
772

773
        do {
774
                ++pass;
1,499✔
775
                if (pass == LASTPASS) OpenSld();        //open source level debugging file (BEFORE InitPass)
1,499✔
776
                InitPass();
1,499✔
777
                if (pass == LASTPASS) OpenDest();
1,499✔
778

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

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

800
                if (DISP_NONE != PseudoORG) {
1,490✔
801
                        CurAddress = adrdisp;
3✔
802
                        PseudoORG = DISP_NONE;
3✔
803
                }
804

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

814
        pass = 9999; /* added for detect end of compiling */
496✔
815

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

819
        Close();
496✔
820

821
        if (Options::UnrealLabelListFName.has_filename()) {
496✔
822
                LabelTable.DumpForUnreal();
10✔
823
        }
824

825
        if (Options::CSpectMapFName.has_filename()) {
496✔
826
                LabelTable.DumpForCSpect();
12✔
827
        }
828

829
        if (Options::SymbolListFName.has_filename()) {
496✔
830
                LabelTable.DumpSymbols();
6✔
831
        }
832

833
        // sync files, release everything
834
        cout << flush;
496✔
835
        FreeRAM();
496✔
836
        lua_impl_close();
496✔
837

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

846
        return (ErrorCount != 0);
496✔
847
}
848
//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