dsforth: moved 8x8 printer to separate include (and made it configurable); added...
[urasm.git] / src / urasm.c
bloba46f0cfe942292089fbecbb3e20aa89db4faef64
1 // URASM Z80 assembler
2 // coded by Ketmar // Invisible Vector
3 // GPLv3 or later
4 //
5 #ifndef _GNU_SOURCE
6 # define _GNU_SOURCE
7 #endif
9 #include <getopt.h>
10 #include <setjmp.h>
11 #include <stdarg.h>
12 #include <stddef.h>
13 #include <stdint.h>
14 #include <stdlib.h>
15 #include <stdio.h>
16 #include <string.h>
17 #include <unistd.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <unistd.h>
23 #ifdef WIN32
24 # include <windows.h>
25 #endif
27 #include "liburasm/liburasm.h"
29 #include "ursna48.c"
32 #define VERSION_HI 0
33 #define VERSION_MID 2
34 #define VERSION_LO 2
37 #define MAYBE_UNUSED __attribute__((unused))
39 #define lambda(return_type, body_and_args) ({ \
40 return_type __fn__ body_and_args \
41 __fn__; \
45 ////////////////////////////////////////////////////////////////////////////////
46 static inline int isSpace (char ch) { return (ch && ((unsigned)(ch&0xff) <= 32 || ch == 127)); }
47 static inline int isAlpha (char ch) { return ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')); }
48 static inline int isDigit (char ch) { return (ch >= '0' && ch <= '9'); }
49 static inline int isHexDigit (char ch) { return ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f')); }
50 static inline int isAlphaDigit (char ch) { return (isAlpha(ch) || isDigit(ch)); }
52 static inline char toUpper (char ch) { return (ch >= 'a' && ch <= 'z' ? ch-'a'+'A' : ch); }
53 static inline char toLower (char ch) { return (ch >= 'A' && ch <= 'Z' ? ch-'A'+'a' : ch); }
55 static int digitInBase (char ch, int base) {
56 if (ch < '0') return -1;
57 if (base <= 10) {
58 if (ch >= '0'+base) return -1;
59 return ch-'0';
61 ch = toUpper(ch);
62 if (ch <= '9') return ch-'0';
63 if (ch < 'A' || ch > 'A'+base-10) return -1;
64 ch -= 'A'-10;
65 return (ch < base ? ch : -1);
69 #ifdef WIN32
70 static const char *strcasestr (const char *s, const char *pat) {
71 if (!s || !pat || !pat[0] || !s[0]) return NULL;
72 while (*s) {
73 int ok = 1;
74 for (size_t f = 0; pat[f]; ++f) {
75 if (toLower(s[f]) != toLower(pat[f])) { ok = 0; break; }
77 if (ok) return s;
78 ++s;
80 return NULL;
82 #endif
85 ////////////////////////////////////////////////////////////////////////////////
86 static char *strprintfVA (const char *fmt, va_list vaorig) {
87 char *buf = NULL;
88 int olen, len = 128;
90 buf = malloc(len);
91 if (buf == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
92 for (;;) {
93 char *nb;
94 va_list va;
96 va_copy(va, vaorig);
97 olen = vsnprintf(buf, len, fmt, va);
98 va_end(va);
99 if (olen >= 0 && olen < len) return buf;
100 if (olen < 0) olen = len*2-1;
101 nb = realloc(buf, olen+1);
102 if (nb == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
103 buf = nb;
104 len = olen+1;
109 static __attribute((format(printf,1,2))) char *strprintf (const char *fmt, ...) {
110 char *buf = NULL;
111 va_list va;
113 va_start(va, fmt);
114 buf = strprintfVA(fmt, va);
115 va_end(va);
116 return buf;
120 ///////////////////////////////////////////////////////////////////////////////
121 // global variables
123 enum {
124 AMODE_NORMAL = 0,
125 AMODE_FCODE = 1, // forth assembler code word
126 AMODE_FWORD = 2, // forth threaded code word
129 static int asmMode = 0;
131 static char *sysIncludeDir = NULL;
132 static char *refFileName = NULL;
133 static char *lastIncludePath = NULL;
134 static char *lastSysIncludePath = NULL;
136 #define MAX_LINE_SIZE 16384
137 static char currLine[MAX_LINE_SIZE]; /* current processing line; can be freely modified */
140 ///////////////////////////////////////////////////////////////////////////////
141 // init dirs
143 static void initInclideDir (void) {
144 const char *id = getenv("URASM_INCLUDE_DIR");
145 if (id && id[0]) {
146 sysIncludeDir = strdup(id);
147 } else {
148 char myDir[4096];
149 memset(myDir, 0, sizeof(myDir));
150 #ifndef WIN32
151 if (readlink("/proc/self/exe", myDir, sizeof(myDir)-1) < 0) {
152 strcpy(myDir, ".");
153 } else {
154 char *p = (char *)strrchr(myDir, '/');
155 if (!p) strcpy(myDir, "."); else *p = '\0';
157 #else
158 GetModuleFileName(GetModuleHandle(NULL), myDir, sizeof(myDir)-1);
159 char *p = strrchr(myDir, '\\');
160 if (!p) strcpy(myDir, "."); else *p = '\0';
161 #endif
162 strcat(myDir, "/libs");
163 sysIncludeDir = strdup(myDir);
165 while (sysIncludeDir[0] && sysIncludeDir[strlen(sysIncludeDir)-1] == '/') sysIncludeDir[strlen(sysIncludeDir)-1] = '\0';
166 if (!sysIncludeDir[0]) strcpy(sysIncludeDir, ".");
170 ///////////////////////////////////////////////////////////////////////////////
171 // string utilities
173 /* trim trailing spaces and comments; normalize colons */
174 static void normalizeForthStr (char *s) {
175 char *p = s;
176 /* now skip all shit */
177 while (*p) {
178 const char ch = *p++;
179 /* comment */
180 if (ch == ';' && p[0] == ';') { p[-1] = 0; break; }
182 /* done; trim trailing spaces and colons */
183 size_t slen = strlen(s);
184 while (slen > 0 && isSpace(s[slen-1])) --slen;
185 s[slen] = 0;
189 /* trim trailing spaces and comments; normalize colons */
190 static void normalizeStr (char *s) {
191 if (asmMode == AMODE_FWORD) return normalizeForthStr(s);
192 if (strcasestr(s, "$FORTH")) return normalizeForthStr(s);
193 char *p = s;
194 /* now skip all shit */
195 while (*p) {
196 const char ch = *p++;
197 /* check for "af'" */
198 if (ch == '\'' && p-s >= 2 && toLower(p[-2] == 'a') && toLower(p[-1] == 'f')) continue;
199 /* comment */
200 if (ch == ';') { p[-1] = 0; break; }
201 /* string */
202 if (ch == '"' || ch == '\'') {
203 const char qch = ch;
204 while (*p) {
205 const char c1 = *p++;
206 if (c1 == qch) break;
207 if (c1 == '\\' && *p) ++p;
209 continue;
211 /* reduce and normalise colons */
212 if (ch == ':') {
213 --p; /* back to colon */
214 /* remove spaces before colon */
215 char *t = p;
216 while (t != s && isSpace(t[-1])) --t;
217 if (t != p) memmove(t, p, strlen(p)+1);
218 p = t;
219 if (p[0] != ':') abort(); // assert
220 ++p; /* skip colon */
221 /* remove following spaces and colons */
222 t = p;
223 while (*t == ':' || isSpace(*t)) ++t;
224 if (t != p) memmove(p, t, strlen(t)+1);
225 continue;
228 /* done; trim trailing spaces and colons */
229 size_t slen = strlen(s);
230 while (slen > 0 && (isSpace(s[slen-1]) || s[slen-1] == ':')) --slen;
231 s[slen] = 0;
235 /* check if string starts with the given command (case-insensitive) */
236 /* returns NULL or pointer to args */
237 /* skips spaces after command if any */
238 static char *strIsCommand (const char *command, char *str) {
239 for (int cnt = 1; cnt > 0; --cnt) {
240 while (*str && isSpace(*str)) ++str; // skip spaces
241 for (; *command && *str; ++command, ++str) {
242 if (toUpper(*command) != toUpper(*str)) return NULL; // alas
244 if (*command) return NULL; // alas
245 if (*str && isAlphaDigit(*str)) return NULL; // alas
246 while (*str && isSpace(*str)) ++str; // skip spaces
247 if (*str && *str == ':') break; // try again if we have a colon
248 return str; // found
250 return NULL;
254 /* parse string literal */
255 /* don't free() result */
256 /* skips trailing spaces */
257 static char *parseStr (char **str, char endQ, int *lenp) {
258 static char buf[MAX_LINE_SIZE];
259 int len = 0, n, f, base;
260 char *a = *str;
261 memset(buf, 0, sizeof(buf));
262 if (lenp) *lenp = 0;
263 for (; *a; ++a) {
264 if (*a == '\\') {
265 if (!a[1]) break;
266 switch (*(++a)) {
267 case 'a': buf[len++] = '\a'; break;
268 case 'b': buf[len++] = '\b'; break;
269 case 'e': buf[len++] = '\x1b'; break;
270 case 'f': buf[len++] = '\f'; break;
271 case 'n': buf[len++] = '\n'; break;
272 case 'r': buf[len++] = '\r'; break;
273 case 't': buf[len++] = '\t'; break;
274 case 'v': buf[len++] = '\v'; break;
275 case 'z': buf[len++] = '\0'; break;
276 case 'x': case 'X': // hex
277 ++a; // skip 'x'
278 base = 16; f = 2;
279 donum: for (n = 0; f > 0; --f) {
280 char ch = digitInBase(*a++, base);
282 if (ch < 0) { --a; break; }
283 n *= base;
284 n += ch;
286 buf[len++] = n;
287 --a; // return to the last digit, 'for' will skip it
288 break;
289 case '0': // octal
290 base = 8; f = 4;
291 goto donum;
292 case '1' ... '9': // decimal
293 base = 10; f = 3;
294 goto donum;
295 default: buf[len++] = a[0]; break; // others
297 } else {
298 if (*a == endQ) { ++a; break; }
299 buf[len++] = *a;
302 while (*a && isSpace(*a)) ++a; // skip trailing spaces
303 *str = a;
304 buf[len] = '\0';
305 if (lenp) *lenp = len;
306 return buf;
310 ///////////////////////////////////////////////////////////////////////////////
311 // source file stack, reader, etc
313 typedef struct SourceLine {
314 struct SourceLine *next;
315 char *line;
316 char *fname;
317 int lineNo;
318 int system;
319 } SourceLine;
321 static SourceLine *asmText = NULL;
322 static SourceLine *asmTextLast = NULL;
323 static SourceLine *curSrcLine = NULL;
325 #define MAX_MACRO_ARGS (32)
327 typedef struct MacroDef {
328 struct MacroDef *next;
329 char *name;
330 SourceLine *lines;
331 int argc;
332 char *argdefaults[MAX_MACRO_ARGS]; // default values
333 char *argnames[MAX_MACRO_ARGS]; // argument names
334 } MacroDef;
336 typedef struct {
337 MacroDef *mac;
338 char *argvals[MAX_MACRO_ARGS]; // argument values
339 } CurMacroDef;
341 static MacroDef *maclist = NULL;
342 static CurMacroDef *curmacro = NULL; // !NULL if we are not inserting macro
343 static int curmacronum = 0;
346 static MacroDef *findMacro (const char *name) {
347 for (MacroDef *mc = maclist; mc != NULL; mc = mc->next) if (strcasecmp(name, mc->name) == 0) return mc;
348 return NULL;
352 static void asmTextClear (void) {
353 while (asmText) {
354 SourceLine *l = asmText;
356 asmText = asmText->next;
357 free(l->line);
358 free(l->fname);
359 free(l);
361 asmTextLast = curSrcLine = NULL;
365 static int asmTextLoad (const char *fname, int system) {
366 FILE *fl;
367 int lineNo = 0;
368 SourceLine *s;
370 if (!(fl = fopen(fname, "r"))) {
371 fprintf(stderr, "ERROR: can't open file: %s\n", fname);
372 return -1;
374 printf("loading: %s\n", fname);
375 // read file
376 while (fgets(currLine, sizeof(currLine)-1, fl)) {
377 ++lineNo;
378 currLine[sizeof(currLine)-1] = '\0';
379 //!normalizeStr(currLine);
380 //fprintf(stderr, "*[%s]\n", curLine);
381 if (!currLine[0]) continue; // don't store empty lines
382 // add current line
383 if ((s = calloc(1, sizeof(SourceLine))) == NULL) abort();
384 s->lineNo = lineNo;
385 s->system = system;
386 if ((s->line = strdup(currLine)) == NULL) abort();
387 if ((s->fname = strdup(fname)) == NULL) abort();
388 if (asmTextLast) asmTextLast->next = s; else asmText = s;
389 asmTextLast = s;
391 fclose(fl);
392 return 0;
396 static char *extractFileDir (const char *s) {
397 if (!s || !s[0]) return strdup("");
398 const char *slash;
399 #ifdef WIN32
400 slash = NULL;
401 for (const char *ts = s; *ts; ++ts) {
402 if (*ts == '/' || *ts == '\\') slash = ts;
404 if (slash == s && (s[0] == '/' || s[0] == '\\') && (s[1] == '/' || s[1] == '\\')) slash = NULL;
405 #else
406 slash = strrchr(s, '/');
407 #endif
408 if (!slash) return strdup("");
409 ptrdiff_t len = (ptrdiff_t)(slash-s)+1;
410 char *res = malloc(len+1);
411 memcpy(res, s, len);
412 res[len] = 0;
413 #ifdef WIN32
414 while (len > 0 && (res[len-1] == '\\' || res[len-1] == '/')) --len;
415 #else
416 while (len > 0 && res[len-1] == '/') --len;
417 #endif
418 if (len == 0) { free(res); return strdup(""); }
419 res[len] = 0;
420 return res;
424 static void loadCurSrcLine (void) {
425 if (curSrcLine) {
426 strcpy(currLine, curSrcLine->line);
427 /* macros will not change include dirs */
428 if (!curmacro) {
429 char **incpp = (!curSrcLine->system ? &lastIncludePath : &lastSysIncludePath);
430 if (*incpp) free(*incpp);
431 *incpp = extractFileDir(curSrcLine->fname);
433 normalizeStr(currLine);
434 } else {
435 currLine[0] = 0;
439 static inline SourceLine *setCurSrcLine (SourceLine *l) { curSrcLine = l; loadCurSrcLine(); return l; }
441 static inline SourceLine *nextSrcLine (void) { return (curSrcLine != NULL ? setCurSrcLine(curSrcLine->next) : NULL); }
444 static inline int strHasPathDelim (const char *s) {
445 if (!s || !s[0]) return 0;
446 #ifdef WIN32
447 return (strchr(s, '/') || strchr(s, '\\') ? 1 : 0);
448 #else
449 return (strchr(s, '/') ? 1 : 0);
450 #endif
454 /* returns malloced string */
455 static char *createIncludeName (const char *fname, int assystem, const char *defaultmain) {
456 if (!fname || !fname[0]) return NULL;
457 char *res;
458 if (fname[0] != '/') {
459 const char *incdir;
460 if (!assystem) {
461 incdir = lastIncludePath;
462 } else {
463 incdir = lastSysIncludePath;
464 if (!incdir || !incdir[0]) incdir = sysIncludeDir;
466 res = strprintf("%s/%s", (incdir && incdir[0] ? incdir : "."), fname);
467 } else {
468 res = strprintf("%s", fname);
470 struct stat st;
471 if (defaultmain && defaultmain[0]) {
472 if (stat(res, &st) == 0) {
473 if (S_ISDIR(st.st_mode)) {
474 char *rs = strprintf("%s/%s", res, defaultmain);
475 free(res);
476 res = rs;
480 /* check if there is disk file */
481 if (strHasPathDelim(fname) && stat(res, &st) != 0) {
482 /* no file, try "root include" */
483 const char *incdir = (!assystem ? NULL : sysIncludeDir);
484 char *rs = strprintf("%s/%s", (incdir && incdir[0] ? incdir : "."), fname);
485 free(res);
486 res = rs;
487 /* check for dir again */
488 if (defaultmain && defaultmain[0]) {
489 if (stat(res, &st) == 0) {
490 if (S_ISDIR(st.st_mode)) {
491 char *rs = strprintf("%s/%s", res, defaultmain);
492 free(res);
493 res = rs;
498 //fprintf(stderr, "inc: fname=<%s>; sys=%d; def=<%s>; res=<%s>\n", fname, assystem, defaultmain, res);
499 return res;
503 static int includeCount = 0;
505 // process 'INCLUDE'
506 // include file instead of the current line
507 static int asmTextInclude (const char *fname, int system) {
508 char *fn;
509 FILE *fl;
510 int lineNo = 0;
511 SourceLine *first = NULL, *last = NULL, *s = NULL;
513 if (includeCount > 256) {
514 fprintf(stderr, "ERROR: file %s, line %d: too many nested INCLUDEs!\n", curSrcLine->fname, curSrcLine->lineNo);
515 return -1;
518 fn = createIncludeName(fname, system, "zzmain.zas");
519 ++includeCount;
520 printf("loading: %s\n", fn);
521 if ((fl = fopen(fn, "r")) == NULL) {
522 fprintf(stderr, "ERROR: file %s, line %d: can't open INCLUDE file: '%s'\n", curSrcLine->fname, curSrcLine->lineNo, currLine);
523 free(fn);
524 return -1;
527 while (fgets(currLine, sizeof(currLine)-1, fl)) {
528 ++lineNo;
529 currLine[sizeof(currLine)-1] = '\0';
530 const size_t slen = strlen(currLine);
531 if (slen == 0 || (currLine[slen-1] != '\n' && currLine[slen-1] != '\r')) {
532 fprintf(stderr, "ERROR: file %s, line %d: line too long\n", fn, lineNo);
533 free(fn);
534 return -1;
536 //!normalizeStr(currLine);
537 if (!currLine[0]) continue; // don't store empty lines
538 // add current line
539 if ((s = calloc(1, sizeof(SourceLine))) == NULL) abort();
540 s->system = system;
541 s->lineNo = lineNo;
542 if ((s->line = strdup(currLine)) == NULL) abort();
543 if ((s->fname = strdup(fn)) == NULL) abort();
544 if (last != NULL) last->next = s; else first = s;
545 last = s;
547 fclose(fl);
548 free(fn);
549 --includeCount;
550 curSrcLine->line[0] = 0;
551 if (last) {
552 last->next = curSrcLine->next;
553 curSrcLine->next = first;
555 return 0;
559 ///////////////////////////////////////////////////////////////////////////////
560 // prototypes
562 static void processCurrentLine (void); // only one, will skip to next one
563 static void processForthWordLine (void); // only one, will skip to next one
566 ///////////////////////////////////////////////////////////////////////////////
567 // error raisers, etc
569 static jmp_buf errJP;
572 static void errorWriteFile (FILE *fo) {
573 if (curSrcLine) {
574 fprintf(fo, "at file %s, line %d\n%s\n*", curSrcLine->fname, curSrcLine->lineNo, curSrcLine->line);
575 } else {
576 fprintf(fo, "somewhere in time: ");
580 static void errorMsgV (const char *fmt, va_list ap) {
581 errorWriteFile(stderr);
582 vfprintf(stderr, fmt, ap);
583 va_end(ap);
584 fputc('\n', stderr);
585 fflush(stderr);
589 static void __attribute__((format(printf, 1, 2))) warningMsg (const char *fmt, ...) {
590 va_list ap;
591 fprintf(stderr, "WARNING ");
592 va_start(ap, fmt);
593 errorMsgV(fmt, ap);
597 static void __attribute__((format(printf, 1, 2))) errorMsg (const char *fmt, ...) {
598 va_list ap;
599 fprintf(stderr, "FATAL ");
600 va_start(ap, fmt);
601 errorMsgV(fmt, ap);
605 static void __attribute__((noreturn)) __attribute__((format(printf, 1, 2))) fatal (const char *fmt, ...) {
606 va_list ap;
607 va_start(ap, fmt);
608 errorMsgV(fmt, ap);
609 longjmp(errJP, 666);
613 static void __attribute__((noreturn)) fatalUrLib (int errcode) {
614 errorMsg("%s", urasm_errormsg(errcode));
615 longjmp(errJP, 666);
619 //////////////////////////////////////////////////////////////////////////////
620 // operator management
622 // return !0 to skip current line
623 typedef int (*UrAsmOpFn) (void);
625 enum {
626 PI_CONT_LINE = 0,
627 PI_SKIP_LINE = 1
630 typedef struct UrAsmOp {
631 char *name;
632 UrAsmOpFn fn;
633 struct UrAsmOp *next;
634 } UrAsmOp;
636 static UrAsmOp *oplist = NULL;
639 static UrAsmOp *urAddOp (const char *name, UrAsmOpFn fn) {
640 UrAsmOp *res = calloc(1, sizeof(UrAsmOp));
641 if (!res) abort();
642 res->name = strdup(name);
643 res->fn = fn;
644 res->next = oplist;
645 oplist = res;
646 return res;
650 static UrAsmOp *urFindOp (const char *name) {
651 UrAsmOp *res;
652 for (res = oplist; res; res = res->next) if (!strcasecmp(name, res->name)) break;
653 return res;
657 static void urClearOps (void) {
658 while (oplist) {
659 UrAsmOp *c = oplist;
661 oplist = oplist->next;
662 free(c->name);
663 free(c);
668 ///////////////////////////////////////////////////////////////////////////////
669 // label management
671 typedef struct UrLabelInfo {
672 char *name;
673 int32_t value;
674 int type; /* -1: uknown (forward reference); 0: =; 1: equ; 2: code */
675 int known; /* !0: label value already known */
676 int refLine; /* first referenced line */
677 int fixuptype; /* UR_FIXUP_XXX */
678 char *refFile;
679 struct UrLabelInfo *next;
680 } UrLabelInfo;
682 static UrLabelInfo *labels = NULL;
683 static UrLabelInfo *labelsTail = NULL;
686 static void urClearLabels (void) {
687 UrLabelInfo *c;
688 while ((c = labels) != NULL) {
689 labels = c->next;
690 if (c->name) free(c->name);
691 if (c->refFile) free(c->refFile);
692 free(c);
694 labelsTail = NULL;
698 static UrLabelInfo *urFindLabel (const char *name) {
699 for (UrLabelInfo *c = labels; c; c = c->next) if (strcmp(name, c->name) == 0) return c;
700 return NULL;
704 static UrLabelInfo *urAddLabel (const char *name) {
705 UrLabelInfo *c = urFindLabel(name);
706 if (c == NULL) {
707 c = calloc(1, sizeof(UrLabelInfo));
708 if (!c) abort();
709 c->name = strdup(name);
710 c->type = -1;
711 c->fixuptype = UR_FIXUP_NONE;
712 c->next = NULL;
713 if (labelsTail) labelsTail->next = c; else labels = c;
714 labelsTail = c;
716 return c;
720 ///////////////////////////////////////////////////////////////////////////////
721 // module list management
723 typedef struct ModuleInfo {
724 char *name;
725 char *fname; // opened in this file
726 int seen; // !0: module already seen, skip other definitions from the same file
727 struct ModuleInfo *next;
728 } ModuleInfo;
730 static ModuleInfo *modules = NULL;
731 static ModuleInfo *curModule = NULL;
734 static void modulesClear (void) {
735 curModule = NULL;
736 while (modules) {
737 ModuleInfo *c = modules;
739 modules = modules->next;
740 free(c->name);
741 free(c->fname);
742 free(c);
747 static void modulesResetSeen (void) {
748 for (ModuleInfo *c = modules; c; c = c->next) c->seen = 0;
752 static ModuleInfo *moduleFind (const char *name) {
753 if (!name || !name[0]) return NULL;
754 for (ModuleInfo *c = modules; c; c = c->next) if (!strcmp(c->name, name)) return c;
755 return NULL;
759 static ModuleInfo *moduleAdd (const char *name, const char *fname) {
760 ModuleInfo *c;
762 if (!name || !fname || !name[0] || !fname[0]) abort();
763 if ((c = calloc(1, sizeof(ModuleInfo))) == NULL) abort();
764 if ((c->name = strdup(name)) == NULL) abort();
765 if ((c->fname = strdup(fname)) == NULL) abort();
766 c->next = modules;
767 return (modules = c);
771 ///////////////////////////////////////////////////////////////////////////////
772 // fixup management
773 typedef struct FixupItem {
774 struct FixupItem *next;
775 uint16_t opdestaddr;
776 uint16_t opaddr;
777 int fixuptype;
778 int size;
779 } FixupItem;
780 static FixupItem *fixlisthead = NULL, *fixlisttail = NULL;
783 static void clearFixups (void) {
784 FixupItem *c;
786 while ((c = fixlisthead) != NULL) {
787 fixlisthead = c->next;
788 free(c);
793 static void addFixup (uint16_t opdestaddr, uint16_t opaddr, int fixuptype, int size) {
794 FixupItem *fx = calloc(1, sizeof(FixupItem));
796 fx->opdestaddr = opdestaddr;
797 fx->opaddr = opaddr;
798 fx->fixuptype = fixuptype;
799 fx->size = size;
801 if (fixlisttail != NULL) fixlisttail->next = fx; else fixlisthead = fx;
802 fx->next = NULL;
803 fixlisttail = fx;
807 ///////////////////////////////////////////////////////////////////////////////
808 // destination memory management
810 static uint8_t memory[65536];
811 static char memused[65536];
812 static char memresv[65536];
813 static uint16_t start_pc = 0x100; // viva CP/M!
814 static uint16_t start_disp = 0x100; // viva CP/M!
815 static uint16_t start_ent = 0x100; // viva CP/M!
816 static uint16_t pc = 0; /* current position to write */
817 static uint16_t disp = 0; /* current 'virtual PC' */
818 static uint16_t ent = 0; /* starting address */
819 static uint16_t clrAddr = 24999; /* address for CLEAR for cargador */
820 static int pass = 0; /* 0: collect labels; 1: compiling; 2: compiling and fixing labels */
821 static int inTapeBlock = 0;
822 static uint8_t tapeXorB = 0;
825 static inline uint8_t getByte (uint16_t addr) {
826 return memory[addr];
831 static inline __attribute__((unused)) uint16_t getWord (uint16_t addr) {
832 return ((uint16_t)memory[addr])|(((uint16_t)memory[addr+1])<<8);
837 static inline void putByte (uint16_t addr, uint8_t b) {
838 if (inTapeBlock) tapeXorB ^= b;
839 memory[addr] = b;
840 memused[addr] = 1;
844 static inline MAYBE_UNUSED void putWord (uint16_t addr, uint16_t w) {
845 putByte(addr, w&0xFFU);
846 putByte(addr+1, (w>>8)&0xFFU);
850 static inline void emitByte (uint8_t b) {
851 putByte(pc, b);
852 ++pc;
853 ++disp;
857 static inline void emitWord (uint16_t w) {
858 emitByte(w&0xFFU);
859 emitByte((w>>8)&0xFFU);
863 static inline void emitRWord (uint16_t w) {
864 emitByte((w>>8)&0xFFU);
865 emitByte(w&0xFFU);
869 static void prepareMemory (void) {
870 memset(memory, 0, sizeof(memory));
871 memset(memused, 0, sizeof(memused));
872 memset(memresv, 0, sizeof(memresv));
876 ///////////////////////////////////////////////////////////////////////////////
877 // label getter and utilities
879 static char *lastSeenGlobalLabel = NULL; /* global */
882 static char *fixLocalLabel (const char *name) {
883 static char newname[MAX_LINE_SIZE*2+1024];
885 memset(newname, 0, sizeof(newname));
886 if (!name || !name[0]) {
887 newname[0] = '\0';
888 } else if (!lastSeenGlobalLabel || name[0] != '.') {
889 strcpy(newname, name);
890 } else {
891 if (name[0] == '.' && name[1] == '.') {
892 // this is macro label
893 if (curmacro == NULL) fatal("macro label outside of macro: '%s'", name);
894 sprintf(newname, "{#MAC%d:%s:%s}", curmacronum, curmacro->mac->name, name);
895 } else {
896 // this is local label, let's rename it
897 sprintf(newname, "{%s%s}", lastSeenGlobalLabel, name);
899 /*!fprintf(stderr, "LABEL <%s> renamed to <%s> (%d)\n", name, newname, (curSrcLine ? curSrcLine->lineNo : 0));*/
901 return newname;
905 static char *fixGlobalLabel (const char *name) {
906 static char newname[MAX_LINE_SIZE*2+1024];
908 memset(newname, 0, sizeof(newname));
909 if (!name || !name[0]) {
910 newname[0] = '\0';
911 } else if (!curModule || name[0] == '.' || name[0] == '{' || name[0] == '@' || strchr(name, '.')) {
912 if (name[0] == '@' && name[1]) ++name;
913 strcpy(newname, name);
914 } else {
915 // this is global unqualified label and we have a module; let's rename it
916 sprintf(newname, "%s.%s", curModule->name, name);
918 //printf("%s --> %s\n", name, newname);
919 return newname;
923 static int lblOptMakeU2 = 0;
925 static int32_t findLabelCB (const char *name, uint16_t addr, int *defined, int *found, int *fixuptype) {
926 UrLabelInfo *lbl;
927 char *ln, *nn;
929 nn = fixGlobalLabel((ln = fixLocalLabel(name)));
930 lbl = urFindLabel(nn);
931 if (!lbl) {
932 // try non-module label
933 lbl = urFindLabel(ln);
935 if (!lbl) {
936 if (pass != 0) {
937 errorMsg("using undefined label %s", ln);
938 *found = 0;
939 *defined = 0;
940 return 0;
942 lbl = urAddLabel(nn);
943 lbl->type = (lblOptMakeU2 ? -42 : -1);
944 lbl->known = 0;
945 lbl->refLine = curSrcLine->lineNo;
946 lbl->refFile = strdup(curSrcLine->fname);
947 //fprintf(stderr, "new label: [%s]\n", lbl->name);
948 //fprintf(stderr, "new label: [%s] (%d : #%04X); disp=#%04X; pc=#%04X\n", lbl->name, lbl->value, lbl->value&0xffffU, disp, pc);
949 } else {
950 //fprintf(stderr, "label reference: [%s] (%d : #%04X); disp=#%04X; pc=#%04X\n", lbl->name, lbl->value, lbl->value&0xffffU, disp, pc);
952 if (lbl) {
953 *found = 1;
954 *defined = lbl->known!=0;
955 *fixuptype = lbl->fixuptype;
956 return lbl->value;
958 *found = 0;
959 *defined = 0;
960 return 0;
964 // qtypes
965 enum {
966 UR_QTYPE_DEFINED,
967 UR_QTYPE_KNOWN
970 static int isLabelDefinedOrKnown (const char *name, uint16_t addr, int qtype) {
971 UrLabelInfo *lbl;
972 char *ln, *nn;
974 nn = fixGlobalLabel((ln = fixLocalLabel(name)));
975 lbl = urFindLabel(nn);
976 if (!lbl) {
977 // try non-module label
978 lbl = urFindLabel(ln);
980 switch (qtype) {
981 case UR_QTYPE_DEFINED: return lbl ? lbl->known!=0 : 0;
982 case UR_QTYPE_KNOWN: return lbl!=NULL;
983 default: ;
985 return 0;
989 static void fixupOperandCB (const urasm_operand_t *op, uint16_t opdestaddr, uint16_t opaddr, int fixuptype, int size) {
990 if (pass == 1) {
991 //static const char *n[4] = {"none", "word", "low", "high"};
992 //fprintf(stderr, "%d: fixupOperandCB: destaddr=#%04x; addr=#%04x; pc=#%04X; fixuptype=%s\n", size, opdestaddr, opaddr, pc, n[fixuptype]);
993 addFixup(opdestaddr, opaddr, fixuptype, size);
998 static int checkLabels (void) {
999 int wasError = 0;
1000 for (UrLabelInfo *c = labels; c; c = c->next) {
1001 //fprintf(stderr, ":LBL: <%s> (type=%d)\n", c->name, c->type);
1002 if (c->type == -1) {
1003 fprintf(stderr, "ERROR at file %s, line %d: referencing undefined label: %s\n", c->refFile, c->refLine, c->name);
1004 wasError = 1;
1006 if (c->type == 0) c->known = -1;
1008 //if (wasError) longjmp(errJP, 667);
1009 return wasError;
1013 ///////////////////////////////////////////////////////////////////////////////
1014 // expression utils (aka string parsing)
1017 /* skip leading spaces */
1018 /* returns string with spaces skipped */
1019 static inline char *strSkipSpaces (const char *s) {
1020 while (*s && isSpace(*s)) ++s;
1021 return (char *)s;
1025 /* skip leading spaces */
1026 /* returns string with spaces skipped */
1027 static inline const char *strSkipSpacesConst (const char *s) {
1028 while (*s && isSpace(*s)) ++s;
1029 return s;
1033 /* remove trailing spaces from string */
1034 static void strTrimRight (char *s) {
1035 if (!s || !s[0]) return;
1036 size_t len = strlen(s);
1037 while (len > 0 && isSpace(s[len-1])) --len;
1038 s[len] = 0;
1042 /* skip leading spaces and colons */
1043 /* returns string with spaces skipped */
1044 static inline char *strSkipSpacesColons (char *s) {
1045 while (*s && (isSpace(*s) || *s == ':')) ++s;
1046 return s;
1050 /* remove leading spaces from the current line */
1051 static inline void removeSpaces (void) {
1052 char *e = strSkipSpaces(currLine);
1053 if (e != currLine) memmove(currLine, e, strlen(e)+1);
1057 /* skip leading spaces, and argument (up to, but not including comma or colon) */
1058 /* correctly skip strings */
1059 /* returns string after skipped argument (with trailing spaces skipped) */
1060 static char *skipMacroArg (char *str) {
1061 int parens = 0;
1062 char *strstart = str;
1063 for (;;) {
1064 str = strSkipSpaces(str);
1065 if (!str[0]) return str;
1066 if (parens == 0 && (str[0] == ',' || str[0] == ':')) return str;
1067 /* check for "af'" */
1068 if (str[0] == '\'' && str-strstart >= 2 && toLower(str[-2] == 'a') && toLower(str[-1] == 'f')) {
1069 ++str;
1070 continue;
1072 if (str[0] == '(') { ++parens; continue; }
1073 if (str[0] == ')') { if (--parens < 0) parens = 0; continue; }
1074 /* check for string */
1075 if (str[0] == '"' || str[0] == '\'') {
1076 const char qch = *str++;
1077 while (*str) {
1078 const char ch = *str++;
1079 if (ch == qch) break;
1080 if (ch == '\\' && *str) ++str;
1082 continue;
1084 ++str;
1089 /* evaluate next numeric expression in input string */
1090 /* returns expression value */
1091 static int32_t getExprArg (int *defined, int *addr) {
1092 int error = 0;
1093 char *a = strSkipSpaces(currLine);
1094 int32_t res;
1095 if (!a[0]) fatal("expression expected");
1096 const char *ee = urasm_expr(&res, a, disp, defined, addr, &error);
1097 if (error) fatalUrLib(error);
1098 if (*ee) {
1099 if (ee[0] != ',' && ee[0] != ':') fatal("bad expression");
1100 memmove(currLine, ee, strlen(ee)+1);
1101 } else {
1102 currLine[0] = '\0';
1104 return res;
1108 /* evaluate next string expression in input string */
1109 /* returns expression value */
1110 static char *getStrExprArg (void) {
1111 int error = 0;
1112 int donteval = 0, defined = 0;
1113 static char resbuf[256];
1114 char *a = strSkipSpaces(currLine);
1115 if (!a[0]) fatal("expression expected");
1116 urasm_exprval_t res;
1117 urasm_exprval_init(&res);
1118 const char *ee = urasm_expr_ex(&res, a, disp, &donteval, &defined, &error);
1119 if (error) fatalUrLib(error);
1120 if (*ee) {
1121 if (ee[0] != ',' && ee[0] != ':') fatal("bad expression");
1122 memmove(currLine, ee, strlen(ee)+1);
1123 } else {
1124 currLine[0] = '\0';
1126 if (res.str) {
1127 snprintf(resbuf, sizeof(resbuf), "%s", res.str);
1128 } else {
1129 snprintf(resbuf, sizeof(resbuf), "%d", res.val);
1131 urasm_exprval_clear(&res);
1132 return resbuf;
1136 /* evaluate next numeric expression in input string */
1137 /* there shoild be no other expressions in the string */
1138 /* returns expression value */
1139 static int32_t getOneExprArg (int *defined, int *addr) {
1140 int32_t res = getExprArg(defined, addr);
1141 if (currLine[0] && currLine[0] != ':') fatal("too many expressions");
1142 return res;
1146 /* is next expression a string literal? */
1147 static inline int isStrArg (void) {
1148 const char *s = strSkipSpaces(currLine);
1149 return (s[0] == '"' || s[0] == '\'');
1153 /* check of we reached end of operator */
1154 static __attribute__((unused)) inline int isOperatorEnd (void) {
1155 const char *s = strSkipSpaces(currLine);
1156 return (s[0] == 0 || s[0] == ':');
1160 /* check of we reached end of operator */
1161 static __attribute__((unused)) inline int isLineEnd (void) {
1162 const char *s = strSkipSpaces(currLine);
1163 return (s[0] == 0);
1167 /* parse string argument from input string */
1168 /* returns parsed string */
1169 static char *getStrArg (int *lenp) {
1170 char *res, qCh;
1171 char *a = strSkipSpaces(currLine);
1172 qCh = *a++;
1173 if (qCh != '"' && qCh != '\'') fatal("string expected");
1174 res = parseStr(&a, qCh, lenp);
1175 if (*a) {
1176 if (a[0] != ',' && a[0] != ':') fatal("bad string expression");
1177 memmove(currLine, a, strlen(a)+1);
1178 } else {
1179 currLine[0] = '\0';
1181 return res;
1185 /* get identifier (and lowercase it) */
1186 static char *getOneIdArgLo (void) {
1187 static char res[MAX_LINE_SIZE+128];
1188 char *p;
1189 char *a = strSkipSpaces(currLine);
1190 memset(res, 0, sizeof(res));
1191 if (!a[0]) fatal("identifier expected");
1192 for (p = res; *a && *a != ',' && *a != ':' && *a != '=' && !isSpace(*a); ++a, ++p) *p = *a;
1193 for (; p > res && isSpace(p[-1]); --p) {}
1194 *p = '\0';
1195 if (p-res > 120) fatal("identifier too long: %s", res);
1196 while (*a && isSpace(*a)) ++a;
1197 if (*a) {
1198 memmove(currLine, a, strlen(a)+1);
1199 if (currLine[0] == ';') currLine[0] = 0;
1200 if (currLine[0]) fatal("extra arguments");
1201 } else {
1202 currLine[0] = '\0';
1204 for (char *t = res; *t; ++t) *t = toLower(*t);
1205 return res;
1209 /* get label argument */
1210 static char *getLabelArg (int checkdelim) {
1211 static char res[MAX_LINE_SIZE+128];
1212 char *p;
1213 char *a = strSkipSpaces(currLine);
1214 memset(res, 0, sizeof(res));
1215 if (!a[0]) fatal("label expected");
1216 for (p = res; *a && *a != ',' && *a != ':' && *a != '=' && !isSpace(*a); ++a, ++p) *p = *a;
1217 for (; p > res && isSpace(p[-1]); --p) {}
1218 *p = '\0';
1219 if (p-res > 120) fatal("label name too long: %s", res);
1220 while (*a && isSpace(*a)) ++a;
1221 if (*a) {
1222 if (checkdelim && a[0] != ',' && a[0] != ':') fatal("bad label expression");
1223 memmove(currLine, a, strlen(a)+1);
1224 } else {
1225 currLine[0] = '\0';
1227 return res;
1231 /* get label argument, and ensure that it is the last one */
1232 static char *getOneLabelArg (void) {
1233 char *res = getLabelArg(1);
1234 if (currLine[0] && currLine[0] != ':') fatal("too many expressions");
1235 return res;
1239 /* returns ',' or 0 */
1240 static char eatComma (void) {
1241 char *a = strSkipSpaces(currLine);
1242 if (!a[0]) { currLine[0] = '\0'; return 0; }
1243 if (a[0] == ':') return 0;
1244 if (a[0] != ',') fatal("invalid expression: ',' expected");
1245 for (++a; *a && isSpace(*a); ++a) {}
1246 if (!a[0]) { currLine[0] = '\0'; return 0; }
1247 memmove(currLine, a, strlen(a)+1);
1248 return ',';
1252 ///////////////////////////////////////////////////////////////////////////////
1253 // label processor
1255 static MAYBE_UNUSED void removeSpacesAndColons (void) {
1256 char *ep = strSkipSpacesColons(currLine);
1257 memmove(currLine, ep, strlen(ep)+1);
1261 static void checkExprEnd (void) {
1262 char *ep = strSkipSpaces(currLine);
1263 memmove(currLine, ep, strlen(ep)+1);
1264 if (currLine[0] && currLine[0] != ':') fatal("end of expression expected");
1268 static void checkOperatorEnd (void) {
1269 char *ep = strSkipSpaces(currLine);
1270 memmove(currLine, ep, strlen(ep)+1);
1271 if (currLine[0]) fatal("end of operator expected");
1275 /* remove label from curLine */
1276 static void removeLabel (void) {
1277 char *ep = currLine;
1278 if (ep[0] && !isSpace(ep[0])) for (ep = currLine; *ep && !isSpace(*ep) && *ep != ':'; ++ep) {} // skip text
1279 // skip spaces and colons
1280 ep = strSkipSpacesColons(ep);
1281 memmove(currLine, ep, strlen(ep)+1);
1285 static int labelDoEQU (const char *lblname, const char *value) {
1286 static char n2[256];
1287 UrLabelInfo *lbl;
1289 if (value == NULL || lblname == NULL || !lblname[0] || strlen(lblname) > 255 || strlen(value) >= MAX_LINE_SIZE) return -1;
1290 memset(n2, 0, sizeof(n2));
1291 strcpy(n2, lblname);
1292 if (!urasm_is_valid_name(n2)) return -1; // this is not a valid label, get out of here
1293 // check if this can be an instruction
1294 lbl = urAddLabel(lblname);
1295 if (!lbl->refFile) {
1296 lbl->refLine = 0;
1297 lbl->refFile = strdup("artificially-defined-label");
1299 strcpy(currLine, value);
1301 int defined = 1, addr = UR_FIXUP_NONE;
1302 int32_t res = getOneExprArg(&defined, &addr);
1303 lbl->type = 1; // equ label
1304 if (addr != UR_FIXUP_NONE) lbl->fixuptype = addr;
1305 if (defined) {
1306 lbl->value = res;
1307 lbl->known = 1;
1308 } else {
1309 return -1; //fatal("can't calculate label %s", lbl->name);
1312 return 0;
1316 static void processLabel (void) {
1317 char *argstart;
1318 char *ep, *ln, *nn;
1319 static char n2[256];
1320 UrLabelInfo *lbl;
1321 int noLocAff = 0, doEQU = 0;
1322 /*!fprintf(stderr, "LINE <%s> (%d)\n", curLine, (curSrcLine ? curSrcLine->lineNo : 0));*/
1323 memset(n2, 0, sizeof(n2));
1324 if (!currLine[0] || isSpace(currLine[0]) || currLine[0] == ':') {
1325 // this may be " id = smth" or " id equ smth"
1326 ep = currLine;
1327 // skip spaces
1328 while (isSpace(*ep)) ++ep;
1329 if (ep[0] == ':' || !ep[0] || (!isAlpha(ep[0]) && ep[0] != '_' && ep[0] != '.' && ep[0] != '@')) {
1330 removeLabel(); // removeLabel() removes any spaces, etc
1331 return;
1333 // this looks like a label; check for '=' or 'equ'
1334 while (isAlphaDigit(ep[0]) || ep[0] == '_' || ep[0] == '.' || ep[0] == '@') ++ep;
1335 nn = ep;
1336 // skip trailing spaces
1337 while (isSpace(*ep)) ++ep;
1338 if (ep[0] == '=') {
1339 doEQU = 0;
1340 argstart = ++ep;
1341 } else if (isSpace(*nn)) {
1342 doEQU = 1;
1343 argstart = strIsCommand("EQU", ep);
1344 } else {
1345 argstart = NULL;
1347 if (!argstart) {
1348 removeLabel(); // removeLabel() removes any spaces, etc
1349 return;
1351 // remove leading spaces from name
1352 // copy label
1353 ep = currLine;
1354 // skip spaces
1355 while (isSpace(*ep)) ++ep;
1356 if (ep >= nn) fatal("internal compiler error");
1357 if (nn-ep > 120) fatal("label too long");
1358 memset(n2, 0, sizeof(n2));
1359 memmove(n2, ep, nn-ep);
1360 if (urFindOp(n2) || !urasm_is_valid_name(n2)) {
1361 //fatal("invalid label name");
1362 removeLabel(); // removeLabel() removes any spaces, etc
1363 return;
1365 // remove label name
1366 memmove(currLine, argstart, strlen(argstart)+1);
1367 // append label
1368 if (n2[0] == '@' && n2[1]) { noLocAff = 1; memmove(n2, n2+1, strlen(n2)); }
1369 nn = fixGlobalLabel((ln = fixLocalLabel(n2)));
1370 lbl = urAddLabel(nn);
1371 if (!lbl->refFile) {
1372 lbl->refLine = curSrcLine->lineNo;
1373 lbl->refFile = strdup(curSrcLine->fname);
1375 //fprintf(stderr, "**: LBL: <%s> (doEQU=%d)\n", lbl->name, doEQU);
1376 if (doEQU && pass == 0 && lbl->type != -1) fatal("duplicate label '%s'", lbl->name);
1377 int defined = 1, addr = UR_FIXUP_NONE;
1378 int32_t res = getOneExprArg(&defined, &addr);
1379 lbl->type = doEQU;
1380 if (addr != UR_FIXUP_NONE) lbl->fixuptype = addr;
1381 if (defined) {
1382 lbl->value = res;
1383 lbl->known = 1;
1384 } else {
1385 if (pass != 0) fatal("can't calculate label '%s'", lbl->name);
1387 currLine[0] = '\0';
1388 return;
1390 // collect label
1391 for (ep = currLine; *ep && !isSpace(*ep) && *ep != ':'; ++ep) {}
1392 if (ep-currLine > 120) fatal("label too long");
1393 // copy label
1394 memset(n2, 0, sizeof(n2));
1395 memmove(n2, currLine, ep-currLine);
1396 if (urFindOp(n2)) {
1397 ep = strSkipSpaces(ep);
1398 if (*ep != ':') return; // this must be an instruction, process it
1400 if (!urasm_is_valid_name(n2)) return; // this is not a valid label, get out of here
1401 // check for macro
1402 if (findMacro(n2)) { /*fprintf(stderr, "***M:<%s>\n", n2);*/ return; } // macro call
1403 // check if this can be instruction
1404 //ep = strSkipSpaces(ep);
1405 //if (*ep != ':' && urFindOp(n2)) return; // this must be and instruction, process it
1406 // ok, we got a good label
1407 removeLabel();
1408 if (n2[0] == '@' && n2[1]) { noLocAff = 1; memmove(n2, n2+1, strlen(n2)); }
1409 /*!fprintf(stderr, "GOT LABEL <%s> (%d)\n", n2, (curSrcLine ? curSrcLine->lineNo : 0));*/
1410 nn = fixGlobalLabel((ln = fixLocalLabel(n2)));
1411 /*!fprintf(stderr, " RENAMED(ln) <%s>\n", ln);*/
1412 /*!fprintf(stderr, " RENAMED(nn) <%s>\n", nn);*/
1413 lbl = urAddLabel(nn);
1414 if (!lbl->refFile) {
1415 lbl->refLine = curSrcLine->lineNo;
1416 lbl->refFile = strdup(curSrcLine->fname);
1418 //printf("new: [%s]\n", lbl->name);
1419 // get command name
1420 if (currLine[0] == '=') {
1421 doEQU = 0;
1422 argstart = currLine+1;
1423 } else {
1424 doEQU = 1;
1425 argstart = strIsCommand("EQU", currLine);
1427 if (!argstart || doEQU) {
1428 if (pass == 0 && lbl->type != -1) fatal("duplicate label '%s'", lbl->name);
1430 //fprintf(stderr, "**: LBL: <%s> (doEQU=%d)\n", lbl->name, doEQU);
1431 if (argstart) {
1432 // do '=' or 'EQU'
1433 memmove(currLine, argstart, strlen(argstart)+1);
1434 if (!doEQU) {
1435 if (lbl->type != -1 && lbl->type != 0) fatal("duplicate label '%s'", lbl->name);
1437 int defined = 1, addr = UR_FIXUP_NONE;
1438 int32_t res = getOneExprArg(&defined, &addr);
1439 lbl->type = doEQU;
1440 if (addr != UR_FIXUP_NONE) lbl->fixuptype = addr;
1441 if (defined) {
1442 lbl->value = res;
1443 lbl->known = 1;
1444 } else {
1445 if (pass != 0) fatal("can't calculate label '%s'", lbl->name);
1447 currLine[0] = '\0';
1448 return;
1450 //fprintf(stderr, "**: LBL: <%s> (doEQU=%d); disp=#%04X; pc=#%04X\n", lbl->name, doEQU, disp, pc);
1451 // code label
1452 if (lbl->name[0] != '{' && !noLocAff) {
1453 if (lastSeenGlobalLabel) free(lastSeenGlobalLabel);
1454 lastSeenGlobalLabel = strdup(lbl->name);
1456 lbl->type = 2;
1457 lbl->value = disp;
1458 lbl->known = 1;
1459 lbl->fixuptype = UR_FIXUP_WORD;
1463 ///////////////////////////////////////////////////////////////////////////////
1464 // instruction finder (in source)
1466 /* array ends with NULL */
1467 /* returns line or NULL */
1468 /* iidx will be set to found instruction number */
1469 static __attribute__((sentinel)) SourceLine *findNextInstructionFromList (int *iidx, ...) {
1470 if (iidx) *iidx = -1;
1471 for (SourceLine *cur = curSrcLine; cur; cur = cur->next) {
1472 va_list ap;
1473 //if (!isSpace(cur->line[0])) continue; // fuck labeled strings
1474 va_start(ap, iidx);
1475 for (int f = 0; ;++f) {
1476 const char *name = va_arg(ap, const char *);
1478 if (!name) break;
1479 if (strIsCommand(name, cur->line)) {
1480 va_end(ap);
1481 if (iidx) *iidx = f;
1482 return cur;
1485 va_end(ap);
1487 return NULL;
1491 static MAYBE_UNUSED SourceLine *findNextInstruction (const char *name) {
1492 return findNextInstructionFromList(NULL, name, NULL);
1496 ///////////////////////////////////////////////////////////////////////////////
1497 // writers
1499 static int optWriteFixups = 0;
1500 static int optFixupType = 0; // 0: text file; 1: zas file; 2: difzas; 3: zas with zero endmarker
1501 static int optRunTape = 1;
1502 static int optRunDMB = 1;
1503 static int optRunSCL = -1; /* -1: save cargador, but no boot; 1: boot and cargador */
1504 static int optTapExt = 0; /* set '.tap' extension (but don't write tape; this for inline tape blocks) */
1505 static int optSNA48 = 1;
1506 static char *optOutputDir = NULL;
1509 ///////////////////////////////////////////////////////////////////////////////
1510 static void writeFixups (void) {
1512 void writeFixupList (FILE *fo, int cnt, const char *lbl, int (*chk)(const FixupItem *)) {
1513 if (cnt > 0) {
1514 int prevaddr = 0;
1515 fprintf(fo, "%s:\n", lbl);
1516 if (optFixupType == 1) fprintf(fo, " dw %d ; count\n", cnt);
1517 for (const FixupItem *fx = fixlisthead; fx != NULL; fx = fx->next) {
1518 if (chk(fx)) {
1519 fprintf(fo, " dw #%04X\n", fx->opaddr-prevaddr);
1520 if (optFixupType == 2) {
1521 prevaddr = fx->opaddr;
1525 if (optFixupType == 2 || optFixupType == 3) fprintf(fo, " dw 0 ; end marker\n");
1529 if (optFixupType == 0) {
1530 /* simple text file */
1531 char *fname = strprintf("%s/%s", optOutputDir, "zfixuptable.txt");
1532 printf("writing fixups to '%s'...\n", fname);
1533 FILE *fo = fopen(fname, "w");
1534 free(fname);
1535 if (fo == NULL) fatal("can't write fixup file");
1536 fprintf(fo, "; addr dadr sz\n");
1537 for (const FixupItem *fx = fixlisthead; fx != NULL; fx = fx->next) {
1538 static const char type[4] = "NWLH";
1539 fprintf(fo, "%c #%04x #%04x %d\n", type[fx->fixuptype], fx->opaddr, fx->opdestaddr, fx->size);
1541 fclose(fo);
1542 } else {
1543 /* various asm formats */
1544 char *fname = strprintf("%s/%s", optOutputDir, "zfixuptable.zas");
1545 printf("writing fixups to '%s'...\n", fname);
1546 FILE *fo = fopen(fname, "w");
1547 int cntw = 0, cntwl = 0, cntwh = 0, cntbl = 0, cntbh = 0;
1548 free(fname);
1549 if (fo == NULL) fatal("can't write fixup file");
1550 for (const FixupItem *fx = fixlisthead; fx != NULL; fx = fx->next) {
1551 if (fx->fixuptype == UR_FIXUP_WORD) { ++cntw; continue; }
1552 switch (fx->fixuptype) {
1553 case UR_FIXUP_LOBYTE: if (fx->size == 2) ++cntwl; else ++cntbl; break;
1554 case UR_FIXUP_HIBYTE: if (fx->size == 2) ++cntwh; else ++cntbh; break;
1557 writeFixupList(fo, cntw, "fixup_table_w", lambda(int, (const FixupItem *fx) {
1558 return (fx->fixuptype == UR_FIXUP_WORD);
1559 }));
1560 writeFixupList(fo, cntwl, "fixup_table_wl", lambda(int, (const FixupItem *fx) {
1561 return (fx->fixuptype == UR_FIXUP_LOBYTE && fx->size == 2);
1562 }));
1563 writeFixupList(fo, cntwh, "fixup_table_wh", lambda(int, (const FixupItem *fx) {
1564 return (fx->fixuptype == UR_FIXUP_HIBYTE && fx->size == 2);
1565 }));
1566 writeFixupList(fo, cntbl, "fixup_table_bl", lambda(int, (const FixupItem *fx) {
1567 return (fx->fixuptype == UR_FIXUP_LOBYTE && fx->size == 1);
1568 }));
1569 writeFixupList(fo, cntbh, "fixup_table_bh", lambda(int, (const FixupItem *fx) {
1570 return (fx->fixuptype == UR_FIXUP_HIBYTE && fx->size == 1);
1571 }));
1572 fclose(fo);
1577 ///////////////////////////////////////////////////////////////////////////////
1578 /* return 'found' flag */
1579 static int findChunkFrom (int addr, int *start, int *len) {
1580 if (addr < 0) addr = 0;
1581 for (; addr <= 65535 && !memused[addr]; ++addr) {}
1582 if (addr > 65535) return 0;
1583 *start = addr;
1584 for (; addr <= 65535 && memused[addr]; ++addr) {}
1585 *len = addr-(*start);
1586 return 1;
1590 static void rleUnpack (uint16_t addr, const uint8_t *buf, int buflen) {
1591 for (; buflen >= 2; buflen -= 2) {
1592 int cnt = *buf++;
1593 uint8_t byte = *buf++;
1595 if (cnt == 0) {
1596 // copy
1597 for (cnt = byte; cnt >= 0; --cnt, ++addr, --buflen, ++buf) {
1598 if (!memused[addr]) putByte(addr, *buf);
1599 if (addr == 0xffff) return;
1601 } else {
1602 // fill
1603 for (; cnt > 0; --cnt, ++addr) {
1604 if (!memused[addr]) putByte(addr, byte);
1605 if (addr == 0xffff) return;
1612 static int fWriteByte (FILE *fo, uint8_t b, uint8_t *bxor) {
1613 if (fwrite(&b, 1, 1, fo) != 1) return -1;
1614 if (bxor) *bxor = (*bxor)^b;
1615 return 0;
1619 static int fWriteWord (FILE *fo, uint16_t w, uint8_t *bxor) {
1620 if (fWriteByte(fo, w&0xFFU, bxor)) return -1;
1621 if (fWriteByte(fo, (w>>8)&0xFFU, bxor)) return -1;
1622 return 0;
1626 ///////////////////////////////////////////////////////////////////////////////
1627 // .sna
1629 static int saveSna (const char *fname, int as48) {
1630 char *fn;// = malloc(strlen(fname)+16);
1631 uint8_t regs[27];
1632 FILE *fo;
1633 char abuf[32];
1635 fn = strprintf("%s/%s.sna", optOutputDir, fname);
1636 fo = fopen(fn, "wb");
1637 free(fn);
1638 if (!fo) { fprintf(stderr, "ERROR: can't write SNA file: %s.sna\n", fname); return -1; }
1639 printf("out: %s.sna\n", fname);
1640 memmove(regs, ursna48, 27);
1641 #if 0
1642 if (optRunSNA) {
1643 /* push new address */
1644 uint16_t sp = (uint16_t)regs[23]+(((uint16_t)regs[24])<<8);
1645 sp -= 2;
1646 putByte(sp, ent&0xFFU);
1647 putByte(sp+1, (ent>>8)&0xFFU);
1648 regs[23] = sp&0xFFU;
1649 regs[24] = (sp>>8)&0xFFU;
1651 fwrite(regs, 27, 1, fo);
1652 rleUnpack(16384, ursna48+27, sizeof(ursna48)-27);
1653 fwrite(memory+16384, 49152, 1, fo);
1654 #endif
1656 uint16_t sp = (uint16_t)regs[23]+(((uint16_t)regs[24])<<8);
1658 //fprintf(stderr, "SP: #%04X #%02X%02X\n", sp, memory[sp+1], memory[sp]);
1659 //fprintf(stderr, "%d\n", memused[sp]);
1660 if (memused[sp] || memused[(sp+1)&0xffff]) {
1661 fprintf(stderr, "FATAL: can't save snapshot!\n");
1662 goto error;
1665 if (fwrite(ursna48, 27, 1, fo) != 1) goto error;
1666 rleUnpack(16384, (const uint8_t *)ursna48+27, sizeof(ursna48)-27);
1667 //for (int f = 0; f < 49152; ++f) if (!memused[f+16384]) memory[f+16384] = ursna48[f+27];
1669 sprintf(abuf, "%05d", clrAddr);
1670 memcpy(memory+23762, abuf, 5);
1671 sprintf(abuf, "%05d", ent);
1672 memcpy(memory+23773, abuf, 5);
1674 if (fwrite(memory+16384, 49152, 1, fo) != 1) goto error;
1676 if (!as48) {
1677 int addr = memory[sp]+256*memory[(sp+1)&0xffff];
1679 if (fWriteWord(fo, addr, NULL) != 0) goto error; // PC
1680 if (fWriteByte(fo, 0x10, NULL) != 0) goto error; // 7FFD
1681 if (fWriteByte(fo, 0, NULL) != 0) goto error; // TR-DOS inactive
1682 // write pages
1683 memset(memory, 0, 16384);
1684 for (int f = 1; f < 8; ++f) {
1685 if (f != 2 && f != 5) {
1686 if (fwrite(memory, 16384, 1, fo) != 1) goto error;
1691 fclose(fo);
1692 return 0;
1693 error:
1694 fclose(fo);
1695 unlink(fname);
1696 return -1;
1700 ///////////////////////////////////////////////////////////////////////////////
1701 // bin chunks
1703 static void saveRaw (const char *basename) {
1704 char *fname = NULL;// = malloc(strlen(basename)+16);
1705 int start = 0, len;
1706 FILE *fo;
1708 while (findChunkFrom(start, &start, &len)) {
1709 if (fname != NULL) free(fname);
1710 fname = strprintf("%s/%s_%04X.%s", optOutputDir, basename, start, (optTapExt ? "tap" : "bin"));
1711 fo = fopen(fname, "wb");
1712 if (!fo) {
1713 fprintf(stderr, "ERROR: can't write file %s!\n", fname);
1714 } else {
1715 printf("out: %s\n", fname);
1716 if (fwrite(memory+start, len, 1, fo) != 1) {
1717 fprintf(stderr, "ERROR: error writing file %s!\n", fname);
1718 fclose(fo);
1719 unlink(fname);
1720 } else {
1721 fclose(fo);
1724 start += len;
1726 if (fname != NULL) free(fname);
1730 ///////////////////////////////////////////////////////////////////////////////
1731 // HoBeta files
1733 typedef struct __attribute__((packed)) __attribute__((gcc_struct)) {
1734 char name[8];
1735 char type;
1736 uint16_t start;
1737 uint16_t len;
1738 uint8_t zero; // always zero
1739 uint8_t secLen; // length in sectors
1740 uint16_t checksum;
1741 } HOBHeader;
1744 static uint16_t calcHobSum (const void *hdr) {
1745 const uint8_t *buf = (const uint8_t *)hdr;
1746 uint16_t res = 0;
1748 for (unsigned int f = 0; f < 15; ++f) res += ((uint16_t)buf[f])*257+f;
1749 return res;
1753 static void saveHob (const char *basename) {
1754 char *fname = NULL;//malloc(strlen(basename)+16);
1755 int start = 0, len;
1756 HOBHeader hdr;
1757 FILE *fo;
1759 while (findChunkFrom(start, &start, &len)) {
1760 if (fname != NULL) free(fname);
1761 fname = strprintf("%s/%s_%04X.%s", optOutputDir, basename, start, "$C");
1762 fo = fopen(fname, "wb");
1763 if (!fo) {
1764 fprintf(stderr, "ERROR: can't write file %s!\n", fname);
1765 start += len;
1766 } else {
1767 char tmpbuf[sizeof(hdr.name)*2];
1768 printf("out: %s\n", fname);
1769 memset(&hdr, 0, sizeof(hdr));
1770 memset(&hdr.name, 32, 8);
1771 snprintf(tmpbuf, sizeof(tmpbuf), "%c%04X", basename[0], start);
1772 while (strlen(tmpbuf) < sizeof(hdr.name)) strcat(tmpbuf, " "); /* sorry! */
1773 memcpy(hdr.name, tmpbuf, sizeof(hdr.name));
1774 hdr.type = 'C';
1775 hdr.start = start;
1776 hdr.len = len;
1777 hdr.secLen = (len+255)/256;
1778 hdr.checksum = calcHobSum(&hdr);
1779 if (fwrite(&hdr, sizeof(hdr), 1, fo) != 1) {
1780 fprintf(stderr, "ERROR: error writing file %s!\n", fname);
1781 fclose(fo);
1782 unlink(fname);
1783 goto quit;
1785 if (fwrite(memory+start, len, 1, fo) != 1) {
1786 fprintf(stderr, "ERROR: error writing file %s!\n", fname);
1787 fclose(fo);
1788 unlink(fname);
1789 goto quit;
1791 start += len;
1792 while (len%256) {
1793 if (fwrite(&hdr.zero, 1, 1, fo) != 1) {
1794 fprintf(stderr, "ERROR: error writing file %s!\n", fname);
1795 fclose(fo);
1796 unlink(fname);
1797 goto quit;
1799 ++len;
1800 //fprintf(stderr, ":%d\n", len%256);
1802 fclose(fo);
1805 quit:
1806 if (fname != NULL) free(fname);
1810 // ////////////////////////////////////////////////////////////////////////// //
1811 typedef struct {
1812 uint8_t fcount;
1813 uint8_t dir[14*256];
1814 uint32_t dirpos;
1815 uint32_t currfstartpos;
1816 uint8_t *data;
1817 size_t datapos;
1818 size_t datasize;
1819 } SCLFile;
1822 static void sclInit (SCLFile *scl) {
1823 memset(scl, 0, sizeof(*scl));
1824 scl->datasize = 2*80*16*256; /* maximum disk size */
1825 scl->data = malloc(scl->datasize);
1826 memset(scl->data, 0, scl->datasize);
1827 scl->currfstartpos = 0xffffffffu;
1831 static void sclClear (SCLFile *scl) {
1832 if (!scl) return;
1833 if (scl->data) free(scl->data);
1834 memset(scl, 0, sizeof(*scl));
1835 scl->currfstartpos = 0xffffffffu;
1839 static void sclStartFile (SCLFile *scl, const char *name, char type, uint16_t v0, uint16_t v1) {
1840 if (scl->fcount == 255) {
1841 fprintf(stderr, "FATAL: too many files in SCL!\n");
1842 exit(1);
1844 if (scl->currfstartpos != 0xffffffffu) {
1845 fprintf(stderr, "FATAL: last SCL file is not finished!\n");
1846 exit(1);
1848 int nlen = 0;
1849 while (nlen < 8 && name && name[nlen]) scl->dir[scl->dirpos++] = name[nlen++];
1850 while (nlen < 8) { scl->dir[scl->dirpos++] = ' '; ++nlen; }
1851 scl->dir[scl->dirpos++] = type;
1852 scl->dir[scl->dirpos++] = v0&0xff;
1853 scl->dir[scl->dirpos++] = (v0>>8)&0xff;
1854 scl->dir[scl->dirpos++] = v1&0xff;
1855 scl->dir[scl->dirpos++] = (v1>>8)&0xff;
1856 //scl->dir[scl->dirpos++] = 0; /* size in sectors, unknown yet */
1857 scl->currfstartpos = scl->datapos;
1861 static void sclEndFile (SCLFile *scl) {
1862 if (scl->currfstartpos == 0xffffffffu) {
1863 fprintf(stderr, "FATAL: last SCL file already finished!\n");
1864 exit(1);
1866 /* align to sector */
1867 while (scl->datapos%256) scl->data[scl->datapos++] = 0;
1868 const uint32_t fsz = scl->datapos-scl->currfstartpos;
1869 if (fsz > 255*256) {
1870 fprintf(stderr, "FATAL: SCL file too big!\n");
1871 exit(1);
1873 if (fsz&255) abort();
1874 scl->dir[scl->dirpos++] = fsz/256; /* size in sectors */
1875 if (scl->dirpos%14) abort();
1876 ++scl->fcount;
1877 scl->currfstartpos = 0xffffffffu;
1881 static void sclWriteData (SCLFile *scl, const void *buf, size_t len) {
1882 if (scl->currfstartpos == 0xffffffffu) {
1883 fprintf(stderr, "FATAL: no open SCL file!\n");
1884 exit(1);
1886 if (!len) return;
1887 if (len > 255*256) {
1888 fprintf(stderr, "FATAL: SCL file too big!\n");
1889 exit(1);
1891 memcpy(scl->data+scl->datapos, buf, len);
1892 scl->datapos += len;
1896 static void sclWriteByte (SCLFile *scl, uint8_t b) {
1897 sclWriteData(scl, &b, 1);
1901 static void sclWriteWord (SCLFile *scl, uint16_t w) {
1902 uint8_t b = w&0xff;
1903 sclWriteData(scl, &b, 1);
1904 b = (w>>8)&0xff;
1905 sclWriteData(scl, &b, 1);
1909 static int sclFileWrite (FILE *fo, const void *buf, size_t count, uint32_t *checksum) {
1910 if (!count) return 0;
1911 const uint8_t *p = (const uint8_t *)buf;
1912 if (checksum) for (size_t n = count; n--; ++p) *checksum += (uint32_t)(*p);
1913 if (fwrite(buf, count, 1, fo) != 1) return -1;
1914 return 0;
1918 // <0: error; 0: ok
1919 static int sclSaveToFile (FILE *fo, SCLFile *scl) {
1920 if (scl->currfstartpos != 0xffffffffu) {
1921 fprintf(stderr, "FATAL: last SCL file is not finished!\n");
1922 exit(1);
1924 const char *sign = "SINCLAIR";
1925 uint32_t checksum = 0;
1926 // header
1927 if (sclFileWrite(fo, sign, 8, &checksum) != 0) return -1;
1928 if (sclFileWrite(fo, &scl->fcount, 1, &checksum) != 0) return -1;
1929 // directory
1930 if (sclFileWrite(fo, scl->dir, scl->dirpos, &checksum) != 0) return -1;
1931 // data
1932 if (sclFileWrite(fo, scl->data, scl->datapos, &checksum) != 0) return -1;
1933 // write checksum
1934 for (unsigned f = 0; f < 4; ++f) {
1935 const uint8_t b = (checksum>>(f*8))&0xff;
1936 if (fwrite(&b, 1, 1, fo) != 1) return -1;
1938 // done
1939 return 0;
1943 // ////////////////////////////////////////////////////////////////////////// //
1944 static void saveSCLCargador (SCLFile *scl) {
1945 static uint8_t cargador[16384]; // should be enough for everyone
1946 int linestart = -1;
1947 int linenum = 0;
1948 int start = 0, len, pos;
1950 void putStr (const char *s) {
1951 for (; *s; ++s) cargador[pos++] = *s;
1954 void putNum (int num) {
1955 char buf[64];
1956 sprintf(buf, "%d", num);
1957 putStr(buf);
1960 void startLine () {
1961 ++linenum;
1962 linestart = pos;
1963 // number
1964 cargador[pos++] = (linenum>>8)&0xff;
1965 cargador[pos++] = linenum&0xff;
1966 // size (will be fixed later)
1967 cargador[pos++] = 0;
1968 cargador[pos++] = 0;
1971 void flushLine () {
1972 if (linestart >= 0) {
1973 const int size = pos-linestart-4;
1974 cargador[linestart+2] = size&0xff;
1975 cargador[linestart+3] = (size>>8)&0xff;
1977 linestart = -1;
1980 char cname[16];
1982 pos = 0;
1983 startLine();
1984 // CLEAR VAL "xxx"
1985 putStr("\xfd\xb0\""); putNum(clrAddr); putStr("\"");
1986 // load blocks
1987 int cont = 1;
1988 while (findChunkFrom(start, &start, &len)) {
1989 // :RANDOMIZE USR VAL "15619":REM:LOAD "
1990 if (cont) { putStr(":"); cont = 0; }
1991 putStr("\xf9\xc0\xb0\""); putNum(15619); putStr("\":\xea:\xef\"");
1992 // generate chunk name
1993 snprintf(cname, sizeof(cname), "c%04X", (unsigned)start);
1994 putStr(cname);
1995 //" CODE
1996 putStr("\"\xaf\r");
1997 flushLine();
1998 startLine();
1999 start += len;
2001 // and run
2002 if (cont) { putStr(":"); cont = 0; }
2003 // RANDOMIZE USR VAL "xxx"
2004 putStr("\xf9\xc0\xb0\""); putNum(ent); putStr("\"");
2005 putStr("\r");
2006 flushLine();
2008 //putWord(0xaa80);
2009 //putWord(1); // autostart line
2011 // write to SCL
2012 sclStartFile(scl, "CARGADOR", 'B', (unsigned)pos, (unsigned)pos);
2013 sclWriteData(scl, cargador, (size_t)pos);
2014 sclWriteByte(scl, 0x80);
2015 sclWriteByte(scl, 0xaa);
2016 sclWriteWord(scl, 1);
2017 sclEndFile(scl);
2021 static void saveSCL (const char *basename) {
2022 SCLFile scl;
2023 int fcount = 0;
2024 int start, len;
2025 char *fname = strprintf("%s/%s.scl", optOutputDir, basename);
2026 // count files
2027 start = 0;
2028 while (findChunkFrom(start, &start, &len)) {
2029 ++fcount;
2030 start += len;
2032 if (fcount && optRunSCL) fcount += (optRunSCL > 0 ? 2 : 1); // +loader and boot
2033 if (fcount > 255) {
2034 fprintf(stderr, "ERROR: can't write file '%s' (too many chunks: %d)!\n", fname, fcount);
2035 exit(1);
2037 // create output file
2038 FILE *fo = fopen(fname, "wb");
2039 if (!fo) {
2040 fprintf(stderr, "ERROR: can't write file '%s'!\n", fname);
2041 exit(1);
2043 // initialise SCL writer
2044 sclInit(&scl);
2045 // write loader
2046 if (fcount && optRunSCL) {
2047 // create simple boot
2048 if (optRunSCL > 0) {
2049 const uint8_t dasboot[] = {
2050 0xf9,0xc0,0xb0,'"','1','5','6','1','9','"',':',0xea,':',0xef,'"','C','A','R','G','A','D','O','R','"','\r', //RANDOMIZE USR VAL "15619":REM:LOAD "CARGADOR"
2052 sclStartFile(&scl, "boot", 'B', sizeof(dasboot)+4, sizeof(dasboot)+4);
2053 sclWriteWord(&scl, 1); // line number
2054 sclWriteWord(&scl, (uint16_t)sizeof(dasboot)); // line size
2055 sclWriteData(&scl, dasboot, sizeof(dasboot)); // line data
2056 // TR-DOS signature
2057 sclWriteByte(&scl, 0x80);
2058 sclWriteByte(&scl, 0xaa);
2059 // start line
2060 sclWriteWord(&scl, 0);
2061 sclEndFile(&scl);
2063 saveSCLCargador(&scl);
2065 // write chunks
2066 char cname[16];
2067 start = 0;
2068 while (findChunkFrom(start, &start, &len)) {
2069 snprintf(cname, sizeof(cname), "c%04X", (unsigned)start);
2070 sclStartFile(&scl, cname, 'C', (unsigned)start, (unsigned)len);
2071 sclWriteData(&scl, memory+(unsigned)start, (unsigned)len);
2072 sclEndFile(&scl);
2073 start += len;
2075 if (sclSaveToFile(fo, &scl) < 0) {
2076 fprintf(stderr, "ERROR: error writing file '%s'!\n", fname);
2077 fclose(fo);
2078 unlink(fname);
2079 exit(1);
2081 sclClear(&scl);
2082 fclose(fo);
2083 printf("out: %s\n", fname);
2084 if (fname != NULL) free(fname);
2088 ///////////////////////////////////////////////////////////////////////////////
2089 // .dmb
2091 static int saveDMB (const char *fname) {
2092 char *fn;// = malloc(strlen(fname)+16);
2093 int start = 0, len;
2094 uint16_t pcnt = 0;
2095 FILE *fo;
2097 fn = strprintf("%s/%s.dmb", optOutputDir, fname);
2098 fo = fopen(fn, "wb");
2099 free(fn);
2100 if (!fo) { fprintf(stderr, "ERROR: can't write DMB file: %s.dmb\n", fname); return -1; }
2102 while (findChunkFrom(start, &start, &len)) { ++pcnt; start += len; }
2104 printf("out: %s.dmb\n", fname);
2105 if (fwrite("DMB1", 4, 1, fo) != 1) goto error;
2106 if (fWriteWord(fo, (optRunDMB ? ent : 0), NULL) != 0) goto error;
2107 if (fWriteWord(fo, clrAddr, NULL) != 0) goto error;
2108 if (fWriteWord(fo, pcnt, NULL) != 0) goto error;
2110 start = 0;
2111 while (findChunkFrom(start, &start, &len)) {
2112 if (fWriteWord(fo, len, NULL) != 0) goto error;
2113 if (fWriteWord(fo, start, NULL) != 0) goto error;
2114 if (fwrite(memory+start, len, 1, fo) != 1) goto error;
2115 start += len;
2117 fclose(fo);
2118 return 0;
2119 error:
2120 fclose(fo);
2121 unlink(fname);
2122 fprintf(stderr, "ERROR: error writing file %s.dmb!\n", fname);
2123 return -1;
2127 ///////////////////////////////////////////////////////////////////////////////
2128 // .tap
2130 static char tapeLoaderName[16];
2133 static void saveTapCargador (FILE *fo) {
2134 // count blocks
2135 static uint8_t cargador[16384]; // should be enough for everyone
2136 int start = 0, len, pos, f;
2137 uint8_t bxor;
2140 void putStr (const char *s) {
2141 for (; *s; ++s) cargador[pos++] = *s;
2144 void putNum (int num) {
2145 char buf[64];
2146 sprintf(buf, "%d", num);
2147 putStr(buf);
2151 pos = 4;
2152 // number
2153 cargador[0] = 0; cargador[1] = 10;
2154 // size (will be fixed later)
2155 putStr("\xfd\xb0\""); putNum(clrAddr); putStr("\""); // CLEAR VAL "xxx"
2156 // load blocks
2157 while (findChunkFrom(start, &start, &len)) {
2158 // :LOAD "" CODE
2159 putStr(":\xef\"\"\xaf");
2160 start += len;
2162 // and run
2163 // :RANDOMIZE USR VAL "xxx"
2164 putStr(":\xf9\xc0\xb0\""); putNum(ent); putStr("\"\r");
2165 // patch len
2166 cargador[2] = (pos-4)&0xff;
2167 cargador[3] = ((pos-4)>>8)&0xff;
2168 // write header
2169 fWriteWord(fo, 19, NULL); // length of header
2170 bxor = 0;
2171 fWriteByte(fo, 0, &bxor); // header block
2172 fWriteByte(fo, 0, &bxor); // 'basic' flag
2173 if (tapeLoaderName[0]) {
2174 for (int f = 0; f < 10; ++f) fWriteByte(fo, tapeLoaderName[f], &bxor);
2175 } else {
2176 fWriteByte(fo, 'c', &bxor);
2177 fWriteByte(fo, 'a', &bxor);
2178 fWriteByte(fo, 'r', &bxor);
2179 fWriteByte(fo, 'g', &bxor);
2180 fWriteByte(fo, 'a', &bxor);
2181 fWriteByte(fo, 'd', &bxor);
2182 fWriteByte(fo, 'o', &bxor);
2183 fWriteByte(fo, 'r', &bxor);
2184 fWriteByte(fo, ' ', &bxor);
2185 fWriteByte(fo, ' ', &bxor);
2187 fWriteWord(fo, pos, &bxor); // length
2188 fWriteWord(fo, 10, &bxor); // start
2189 fWriteWord(fo, pos, &bxor); // length2
2190 fWriteByte(fo, bxor, NULL);
2191 // write data
2192 fWriteWord(fo, pos+2, NULL); // plus type and checkbyte
2193 bxor = 0;
2194 fWriteByte(fo, 0xFFU, &bxor); // data block
2195 for (f = 0; f < pos; ++f) fWriteByte(fo, cargador[f], &bxor);
2196 fWriteByte(fo, bxor, NULL);
2200 static void saveTap (const char *basename) {
2201 char *fname;// = malloc(strlen(basename)+16);
2202 char blkname[128];
2203 int start = 0, len, f;
2204 uint8_t bxor;
2205 FILE *fo;
2207 fname = strprintf("%s/%s.tap", optOutputDir, basename);
2208 fo = fopen(fname, "wb");
2209 free(fname);
2210 if (!fo) { fprintf(stderr, "ERROR: can't write file %s.tap!\n", basename); return; }
2211 printf("out: %s.tap\n", basename);
2212 if (optRunTape) saveTapCargador(fo);
2213 while (findChunkFrom(start, &start, &len)) {
2214 // write header
2215 if (tapeLoaderName[0]) {
2216 //for (int f = 0; f < 10; ++f) fWriteByte(fo, tapeLoaderName[f], &bxor);
2217 memcpy(blkname, tapeLoaderName, 10);
2218 } else {
2219 sprintf(blkname, "c%04X:%04X", start, len);
2221 //printf(" block: %s\n", blkname);
2222 fWriteWord(fo, 19, NULL); // length of header
2223 bxor = 0;
2224 fWriteByte(fo, 0, &bxor); // header block
2225 fWriteByte(fo, 3, &bxor); // 'code' flag
2226 for (f = 0; f < 10; ++f) fWriteByte(fo, blkname[f], &bxor);
2227 fWriteWord(fo, len, &bxor);
2228 fWriteWord(fo, start, &bxor);
2229 fWriteWord(fo, 32768, &bxor);
2230 fWriteByte(fo, bxor, NULL);
2231 // write data
2232 fWriteWord(fo, len+2, NULL); // plus type and checkbyte
2233 bxor = 0;
2234 fWriteByte(fo, 0xFFU, &bxor); // data block
2235 for (f = 0; f < len; ++f) fWriteByte(fo, memory[start+f], &bxor);
2236 fWriteByte(fo, bxor, NULL);
2237 start += len;
2239 fclose(fo);
2243 ///////////////////////////////////////////////////////////////////////////////
2244 // pseudoinstructions
2246 // note that processCurrentLine() will NOT skip to the next line before
2247 // calling pseudoinstruction handler!
2248 // note that processCurrentLine() will skip current line after calling
2249 // pseudoinstruction handler!
2251 static int wasOrg = 0;
2252 static int wasClr = 0;
2255 // ////////////////////////////////////////////////////////////////////////// //
2256 // print message using printf-like syntax
2257 // doesn't print new line
2258 static void processPrintf (FILE *fo, const char *fmt) {
2259 if (!fmt || !fmt[0]) return;
2260 /* it may be passed from argument parser, and destroyed by other arguments, so copy it */
2261 char *tempstr = NULL;
2262 size_t tempsize = 0;
2263 char *fmtcopy = malloc(strlen(fmt)+1);
2264 strcpy(fmtcopy, fmt);
2265 char *currfmt = fmtcopy;
2266 char tempbuf[512];
2267 int stlen;
2268 char *strarg;
2269 int defined;
2270 int32_t exprval;
2271 while (*currfmt) {
2272 /* find '%' */
2273 char *prcs = strchr(currfmt, '%');
2274 if (!prcs || !prcs[1]) {
2275 /* no more formatting; print the tail and exit the loop */
2276 fprintf(fo, "%s", currfmt);
2277 break;
2279 /* is this "%%"? */
2280 int docheck = 1;
2281 if (prcs[1] == '%') {
2282 ++prcs;
2283 docheck = 0;
2285 /* print up to `prcs` */
2286 if (prcs > currfmt) {
2287 size_t partlen = (ptrdiff_t)(prcs-currfmt);
2288 if (partlen+1 > tempsize) {
2289 tempsize = ((partlen+8)|0xff)+1;
2290 tempstr = realloc(tempstr, tempsize);
2292 memcpy(tempstr, currfmt, partlen);
2293 tempstr[partlen] = 0;
2294 fprintf(fo, "%s", tempstr);
2296 currfmt = ++prcs; /* skip percent */
2297 if (!docheck) continue;
2298 /* parse format */
2299 char sign = ' ';
2300 int width = 0;
2301 int zerofill = 0;
2302 char ftype = ' ';
2303 if (*currfmt == '+' || *currfmt == '-') sign = *currfmt++;
2304 if (sign != '-' && *currfmt == '0') zerofill = 1;
2305 while (isDigit(*currfmt)) { width = width*10+((*currfmt)-'0'); ++currfmt; }
2306 if (width > 256) width = 256;
2307 ftype = *currfmt++;
2308 if (!ftype) break; /* oops */
2309 if (!eatComma()) fatal("out of arguments for string format");
2310 switch (ftype) {
2311 case 's': /* string */
2312 stlen = 0;
2313 switch (strSkipSpaces(currLine)[0]) {
2314 case '"': case '\'': /* string literal? */
2315 strarg = getStrArg(&stlen);
2316 break;
2317 default: /* expression */
2318 strarg = getStrExprArg();
2319 stlen = (int)strlen(strarg);
2320 break;
2322 /* left pad */
2323 if (sign != '-' && stlen < width) {
2324 int padlen = width-stlen;
2325 memset(tempbuf, ' ', padlen);
2326 tempbuf[padlen+1] = 0;
2327 fprintf(fo, "%s", tempbuf);
2329 fprintf(fo, "%s", strarg);
2330 /* right pad */
2331 if (sign == '-' && stlen < width) {
2332 int padlen = width-stlen;
2333 memset(tempbuf, ' ', padlen);
2334 tempbuf[padlen+1] = 0;
2335 fprintf(fo, "%s", tempbuf);
2337 break;
2338 case 'd': /* decimal */
2339 defined = 1;
2340 exprval = getExprArg(&defined, NULL);
2341 if (width && zerofill) {
2342 fprintf(fo, "%0*d", width, exprval);
2343 } else if (width) {
2344 fprintf(fo, "%*d", (sign != '-' ? width : -width), exprval);
2345 } else {
2346 fprintf(fo, "%d", exprval);
2348 break;
2349 case 'c': /* char */
2350 defined = 1;
2351 exprval = getExprArg(&defined, NULL);
2352 if (exprval <= 0 || exprval == '?' || exprval > 255) exprval = '?';
2353 if (width) {
2354 fprintf(fo, "%*c", (sign != '-' ? width : -width), exprval);
2355 } else {
2356 fprintf(fo, "%c", exprval);
2358 break;
2359 case 'u': /* unsigned */
2360 defined = 1;
2361 exprval = getExprArg(&defined, NULL);
2362 if (width && zerofill) {
2363 fprintf(fo, "%0*u", width, (unsigned)(exprval&0xffff));
2364 } else if (width) {
2365 fprintf(fo, "%*u", (sign != '-' ? width : -width), (unsigned)(exprval&0xffff));
2366 } else {
2367 fprintf(fo, "%u", (unsigned)(exprval&0xffff));
2369 break;
2370 case 'x': case 'X': /* hex */
2371 defined = 1;
2372 exprval = getExprArg(&defined, NULL);
2373 if (width && zerofill) {
2374 fprintf(fo, (ftype == 'x' ? "%0*x" : "%0*X"), width, (unsigned)(exprval&0xffff));
2375 } else if (width) {
2376 fprintf(fo, (ftype == 'x' ? "%*x" : "%*X"), (sign != '-' ? width : -width), (unsigned)(exprval&0xffff));
2377 } else {
2378 fprintf(fo, (ftype == 'x' ? "%x" : "%X"), (unsigned)(exprval&0xffff));
2380 break;
2381 default:
2382 if (ftype <= 0 || ftype == 127) ftype = '?';
2383 fatal("invalid format specifier: '%c'", ftype);
2384 break;
2387 if (tempstr) free(tempstr);
2388 free(fmtcopy);
2392 ///////////////////////////////////////////////////////////////////////////////
2393 // user error
2395 static int piERROR (void) {
2396 int len = 0;
2397 char *res = getStrArg(&len);
2398 fprintf(stdout, "*** USER ERROR: ");
2399 processPrintf(stdout, res);
2400 fputc('\n', stdout);
2401 fatal("user error abort");
2402 return PI_SKIP_LINE;
2406 static int piWARNING (void) {
2407 int len = 0;
2408 char *res = getStrArg(&len);
2409 fprintf(stdout, "*** USER WARNING ");
2410 if (curSrcLine) {
2411 fprintf(stdout, "at file %s, line %d: ", curSrcLine->fname, curSrcLine->lineNo);
2412 } else {
2413 fprintf(stdout, "somewhere in time: ");
2415 processPrintf(stdout, res);
2416 fputc('\n', stdout);
2417 return PI_SKIP_LINE;
2421 ///////////////////////////////////////////////////////////////////////////////
2422 // user warnings
2424 static int piPrintfCommon (int passNo) {
2425 if (passNo < 0 || pass == passNo) {
2426 int len = 0;
2427 char *res = getStrArg(&len);
2428 processPrintf(stdout, res);
2429 fputc('\n', stdout);
2431 return PI_SKIP_LINE;
2435 static int piDISPLAYX (int passNo, int asHex) {
2436 for (;;) {
2437 if (isStrArg()) {
2438 int len = 0;
2439 char *res = getStrArg(&len);
2441 if (passNo < 0 || pass == passNo) printf("%s", res);
2442 } else {
2443 int defined = 1;
2444 int32_t v = getExprArg(&defined, NULL);
2446 if (passNo < 0 || pass == passNo) {
2447 if (asHex) printf("%04X", (unsigned int)v);
2448 else printf("%d", v);
2451 if (!eatComma()) break;
2453 return PI_SKIP_LINE;
2457 static int piDISPLAY (void) { return piDISPLAYX(1, 0); }
2458 static int piDISPLAY0 (void) { return piDISPLAYX(0, 0); }
2459 static int piDISPLAYA (void) { return piDISPLAYX(-1, 0); }
2460 static int piDISPHEX (void) { return piDISPLAYX(1, 1); }
2461 static int piDISPHEX0 (void) { return piDISPLAYX(0, 1); }
2462 static int piDISPHEXA (void) { return piDISPLAYX(-1, 1); }
2464 static int piPRINTF (void) { return piPrintfCommon(1); } /* 2nd pass */
2465 static int piPRINTF0 (void) { return piPrintfCommon(0); } /* 1st pass */
2466 static int piPRINTFA (void) { return piPrintfCommon(-1); } /* all passes */
2469 ///////////////////////////////////////////////////////////////////////////////
2470 // ORG, DISP, etc.
2472 static int piORG (void) {
2473 int defined = 1;
2474 int32_t res = getOneExprArg(&defined, NULL);
2476 if (!defined) fatal("sorry, ORG operand value must be known here");
2477 if (res < 0 || res > 65535) fatal("invalid ORG operand value: %d", res);
2478 if (inTapeBlock) fatal("sorry, no ORGs in TAPEDATA allowed");
2479 pc = disp = res;
2480 if (!wasOrg) {
2481 wasOrg = 1;
2482 ent = res;
2483 if (!wasClr && res > 0) clrAddr = res-1;
2485 return PI_CONT_LINE;
2489 static int piDISP (void) {
2490 int defined = 1;
2491 int32_t res = getOneExprArg(&defined, NULL);
2493 if (!defined) fatal("sorry, DISP operand value must be known here");
2494 if (res < 0 || res > 65535) fatal("invalid DISP operand value: %d", res);
2495 //printf("DISP=%d\n", res);
2496 disp = res;
2497 return PI_CONT_LINE;
2501 static int piENDDISP (void) {
2502 if (inTapeBlock) fatal("sorry, no ENDDISPs in TAPEDATA allowed");
2503 checkExprEnd();
2504 disp = pc;
2505 return PI_CONT_LINE;
2509 static int piENT (void) {
2510 int defined = 1;
2511 int32_t res = getOneExprArg(&defined, NULL);
2513 //if (!defined) fatal("sorry, ENT operand value must be known here");
2514 if (res < 0 || res > 65535) fatal("invalid ENT operand value: %d", res);
2515 ent = res;
2516 return PI_CONT_LINE;
2520 static int piCLR (void) {
2521 int defined = 1;
2522 int32_t res = getOneExprArg(&defined, NULL);
2524 //if (!defined) fatal("sorry, CLR operand value must be known here");
2525 if (res < 0 || res > 65535) fatal("invalid CLR operand value: %d", res);
2526 clrAddr = res;
2527 wasClr = 1;
2528 return PI_CONT_LINE;
2532 static int piRESERVE (void) {
2534 int defined = 1, start;
2535 int32_t res = getExprArg(&defined, NULL);
2536 if (!defined) fatal("sorry, RESERVE operand values must be known here");
2537 if (res < 0 || res > 65535) fatal("invalid RESERVE operand value: %d", res);
2538 start = res;
2539 if (!eatComma()) fatal("RESERVE needs 2 args");
2540 res = getOneExprArg(&defined, NULL);
2541 if (!defined) fatal("sorry, RESERVE operand values must be known here");
2542 if (res < 0 || res > 65535) fatal("invalid RESERVE operand value: %d", res);
2543 for (; len > 0; --len, start = (start+1)&0xffff) memresv[start] = 1;
2545 fatal("RESERVE: not yet!");
2546 return PI_CONT_LINE;
2550 static int piALIGN (void) {
2551 int defined = 1;
2552 int32_t res;
2554 if (inTapeBlock) fatal("sorry, no ALIGNs in TAPEDATA allowed");
2555 res = getOneExprArg(&defined, NULL);
2556 if (!defined) fatal("sorry, ALIGN operand value must be known here");
2557 if (res < 0 || res > 65535) fatal("invalid ALIGN operand value: %d", res);
2558 if (pc != disp) fatal("sorry, no ALIGNs in desynced blocks allowed");
2559 if (res > 0 && pc%res != 0) {
2560 pc /= res;
2561 pc *= res;
2562 pc += res;
2563 disp = pc;
2565 return PI_CONT_LINE;
2569 static int piDISPALIGN (void) {
2570 int defined = 1;
2571 int32_t res = getOneExprArg(&defined, NULL);
2573 if (!defined) fatal("sorry, DISPALIGN operand value must be known here");
2574 if (res < 0 || res > 65535) fatal("invalid DISPALIGN operand value: %d", res);
2575 if (res > 0 && disp%res != 0) {
2576 disp /= res;
2577 disp *= res;
2578 disp += res;
2580 return PI_CONT_LINE;
2584 ///////////////////////////////////////////////////////////////////////////////
2585 // DEFx
2587 static int defIncr = 0;
2590 static int piDEFINCR (void) {
2591 int defined = 1;
2592 int32_t cnt = getOneExprArg(&defined, NULL);
2594 if (!defined) fatal("DEFINCR: increment must be defined");
2595 defIncr = cnt;
2596 return PI_CONT_LINE;
2600 static int piDEFBW (int isWord) {
2601 for (;;) {
2602 int defined = 0, fixuptype = UR_FIXUP_NONE;
2604 if (isStrArg()) {
2605 int f, len = 0;
2606 char *res = getStrArg(&len);
2608 for (f = 0; f < len; ++f) {
2609 int32_t b = (uint8_t)res[f];
2610 b += defIncr;
2611 if (b < -128 || b > 255) fatal("invalid operand value: %d", b);
2612 if (b < 0) b += 256;
2613 emitByte(b);
2615 } else {
2616 int32_t res = getExprArg(&defined, &fixuptype);
2618 if (pass > 0 && !defined) fatal("undefined operand");
2619 res += defIncr;
2620 if (isWord) {
2621 if (res < -32768 || res > 65535) fatal("invalid operand value: %d", res);
2622 if (res < 0) res += 65536;
2623 if (isWord == 1) {
2624 if (fixuptype != UR_FIXUP_NONE) urasm_fixup_operand(NULL, pc, disp, fixuptype, 2);
2625 emitWord(res);
2626 } else {
2627 /* reversed word */
2628 switch (fixuptype) {
2629 case UR_FIXUP_WORD: /* swapped bytes */
2630 urasm_fixup_operand(NULL, pc, disp, UR_FIXUP_HIBYTE, 1);
2631 urasm_fixup_operand(NULL, pc, disp+1, UR_FIXUP_LOBYTE, 1);
2632 break;
2633 case UR_FIXUP_LOBYTE:
2634 case UR_FIXUP_HIBYTE:
2635 warningMsg("non-word fixup for reversed word; wtf?!");
2636 break;
2637 default: break;
2639 emitRWord(res);
2641 } else {
2642 if (res < -128 || res > 255) fatal("invalid operand value: %d", res);
2643 if (fixuptype != UR_FIXUP_NONE) {
2644 if (fixuptype == UR_FIXUP_WORD) warningMsg("invalid fixup for operand");
2645 urasm_fixup_operand(NULL, pc, disp, fixuptype, 1);
2647 if (res < 0) res += 256;
2648 emitByte(res);
2651 if (!eatComma()) break;
2653 return PI_CONT_LINE;
2656 static int piDEFB (void) { return piDEFBW(0); }
2657 static int piDEFW (void) { return piDEFBW(1); }
2658 static int piDEFR (void) { return piDEFBW(2); }
2661 static int piDEFS (void) {
2662 for (;;) {
2663 int32_t bt, f;
2664 int defined = 0, fixuptype = UR_FIXUP_NONE;
2665 int32_t res = getExprArg(&defined, &fixuptype);
2667 if (pass > 0 && !defined) fatal("undefined operand");
2668 if (res < 1 || res > 65535) fatal("invalid number of repetitions: %d", res);
2669 if (*currLine && currLine[0] == ',') {
2670 (void)eatComma();
2671 bt = getExprArg(&defined, NULL);
2672 if (pass > 0 && !defined) fatal("undefined operand");
2673 bt += defIncr;
2674 if (bt < -128 || bt > 255) fatal("invalid operand value: %d", res);
2675 if (bt < 0) bt += 256;
2676 if (fixuptype != UR_FIXUP_NONE) {
2677 if (fixuptype == UR_FIXUP_WORD) warningMsg("invalid fixup for operand");
2679 for (f = 0; f < res; ++f) {
2680 if (fixuptype != UR_FIXUP_NONE) urasm_fixup_operand(NULL, pc, disp, fixuptype, 1);
2681 emitByte(bt);
2683 } else {
2684 pc += res; disp += res;
2686 if (!eatComma()) break;
2688 return PI_CONT_LINE;
2692 /* bit 0: put '\0' */
2693 /* bit 1: set bit 7 of last byte */
2694 /* bit 2: put length */
2695 static int piDEFSTR (int type) {
2696 for (;;) {
2697 if (isStrArg()) {
2698 int f, len = 0;
2699 char *res = getStrArg(&len);
2701 if (type&0x04) {
2702 if (len > 255) fatal("string too long");
2703 emitByte(len);
2705 for (f = 0; f < len; ++f) {
2706 uint8_t b = (uint8_t)res[f];
2708 if ((type&0x02) && f == len-1) b |= 0x80;
2709 emitByte(b);
2711 if (type&0x01) emitByte(0);
2712 } else {
2713 int defined = 1;
2714 int32_t v = getExprArg(&defined, NULL);
2716 if (pass > 0 && !defined) fatal("undefined expression");
2717 if (!defined) v = 0; else v += defIncr;
2718 if (v < -128 || v > 255) fatal("invalid operand value: %d", v);
2719 if (v < 0) v += 256;
2720 emitByte(v);
2722 if (!eatComma()) break;
2724 return PI_CONT_LINE;
2728 static int piDEFM (void) { return piDEFSTR(0x00); }
2729 static int piDEFZ (void) { return piDEFSTR(0x01); }
2730 static int piDEFX (void) { return piDEFSTR(0x02); }
2731 static int piDEFC (void) { return piDEFSTR(0x04); }
2734 ///////////////////////////////////////////////////////////////////////////////
2735 // INCBIN
2737 /* INCBIN "name"[,maxlen] */
2738 static int piINCBIN (void) {
2739 int system = 0;
2740 char *fn, qCh;
2741 uint8_t bt;
2742 FILE *fl;
2743 int maxlen = 65536;
2744 char *args = currLine;
2746 if (!currLine[0]) fatal("INCBIN without file name");
2747 if (isStrArg()) {
2748 qCh = *args++;
2749 system = 0;
2750 } else if (currLine[0] == '<') {
2751 qCh = '>'; ++args;
2752 system = 1;
2753 } else {
2754 qCh = 0;
2755 system = 0;
2757 fn = parseStr(&args, qCh, NULL); // i don't care about string length here
2758 if (!fn[0]) fatal("INCBIN: empty file name");
2759 memmove(currLine, args, strlen(args)+1);
2760 // maxlen
2761 if (currLine[0] == ',') {
2762 int defined = 1;
2763 (void)eatComma();
2764 maxlen = getOneExprArg(&defined, NULL);
2765 if (!defined) fatal("INCBIN: undefined maxlen");
2766 if (maxlen < 1) return 1; // nothing to do
2768 // now fix name
2769 char *fname = createIncludeName(fn, system, NULL);
2770 fl = fopen(fname, "rb");
2771 if (!fl) fatal("INCBIN: file not found: '%s'", fname);
2772 while (maxlen-- > 0) {
2773 int res = fread(&bt, 1, 1, fl);
2774 if (!res) break;
2775 if (res != 1) { fclose(fl); fatal("INCBIN: error reading file: '%s'", fname); }
2776 emitByte(bt);
2778 fclose(fl);
2779 free(fname);
2780 return PI_SKIP_LINE;
2784 ///////////////////////////////////////////////////////////////////////////////
2785 // INCLUDE
2787 /* INCLUDE "name" */
2788 static int piINCLUDE (void) {
2789 int system = 0;
2790 char *fn, qCh;
2791 char *args = currLine;
2793 if (!currLine[0]) fatal("INCLUDE without file name");
2794 if (isStrArg()) {
2795 qCh = *args++;
2796 system = 0;
2797 } else if (currLine[0] == '<') {
2798 qCh = '>'; ++args;
2799 system = 1;
2800 } else {
2801 qCh = 0;
2802 system = 0;
2804 fn = parseStr(&args, qCh, NULL); // i don't care about string length here
2805 if (!fn[0]) fatal("INCLUDE: empty file name");
2807 if (asmTextInclude(fn, system) != 0) fatal("INCLUDE: some shit happens!");
2808 return PI_SKIP_LINE;
2812 ///////////////////////////////////////////////////////////////////////////////
2813 // MODULE, ENDMODULE
2815 static int piENDMODULE (void) {
2816 if (!curModule) fatal("ENDMODULE without MODULE");
2817 if (currLine[0]) {
2818 char *mn = getOneLabelArg();
2819 if (strcmp(mn, curModule->name)) fatal("invalid module name in ENDMODULE: %s", mn);
2821 curModule = NULL;
2822 return PI_SKIP_LINE;
2826 static int piMODULE (void) {
2827 ModuleInfo *mi;
2828 char *mn;
2829 SourceLine *ol = curSrcLine;
2830 int inum;
2832 if (curModule) fatal("no nested modules allowed");
2833 mn = getOneLabelArg();
2834 if (!urasm_is_valid_name(mn)) fatal("invalid module name: %s", mn);
2835 mi = moduleFind(mn);
2836 //printf("[%s] %p\n", mn, mi);
2837 if (mi) {
2838 if (mi->seen) {
2839 if (strcmp(mi->fname, curSrcLine->fname)) fatal("duplicate module definition; previous was in %s", mi->fname);
2840 /* skip module */
2841 nextSrcLine(); /* skip "MODULE" line */
2842 if (!setCurSrcLine(findNextInstructionFromList(&inum, "MODULE", "ENDMODULE", NULL))) {
2843 setCurSrcLine(ol);
2844 fatal("no ENDMODULE");
2846 if (inum == 0) fatal("no nested modules allowed");
2847 curModule = mi;
2848 //skipInstruction(); //k8:wtf?!
2849 return piENDMODULE();
2851 } else {
2852 mi = moduleAdd(mn, curSrcLine->fname);
2854 mi->seen = 1;
2855 curModule = mi;
2856 return PI_SKIP_LINE;
2860 /* Z80, Z80N */
2861 static int piMODEL (void) {
2862 char *mn = getOneIdArgLo();
2863 if (strSkipSpaces(currLine)[0]) fatal("only one model name expected");
2864 if (strcmp(mn, "z80") == 0) urasm_allow_zxnext = 0;
2865 else if (strcmp(mn, "z80a") == 0) urasm_allow_zxnext = 0;
2866 else if (strcmp(mn, "z80n") == 0) urasm_allow_zxnext = 1;
2867 else if (strcmp(mn, "z80next") == 0) urasm_allow_zxnext = 1;
2868 else if (strcmp(mn, "zxnext") == 0) urasm_allow_zxnext = 1;
2869 else fatal("invalid model name: %s", mn);
2870 return PI_SKIP_LINE;
2874 ///////////////////////////////////////////////////////////////////////////////
2875 // DUP, EDUP
2877 static int piEDUP (void) {
2878 fatal("EDUP without DUP");
2879 checkOperatorEnd();
2880 return PI_SKIP_LINE;
2884 static int piDUP (void) {
2885 int defined = 1, dupCnt = 1, inum;
2886 SourceLine *stline, *eline = NULL;
2887 int32_t cnt = getOneExprArg(&defined, NULL);
2889 if (!defined) fatal("DUP: counter must be defined");
2890 if (cnt > 65535) fatal("DUP: counter too big: %d", cnt);
2891 if (cnt < 0) warningMsg("DUP: counter too small: %d", cnt);
2892 // now find corresponding EDUP
2893 // note that we should skip nested DUPs
2894 nextSrcLine(); // skip ourself
2895 stline = curSrcLine;
2896 while (curSrcLine) {
2897 if (!setCurSrcLine(findNextInstructionFromList(&inum, "DUP", "EDUP", NULL))) break;
2898 // ok, we found something; what is it?
2899 if (inum == 0) {
2900 // new DUP, skip it
2901 ++dupCnt;
2902 nextSrcLine(); // skip DUP
2903 } else {
2904 // EDUP
2905 if (--dupCnt == 0) {
2906 // gotcha!
2907 eline = curSrcLine;
2908 break;
2910 nextSrcLine(); // skip EDUP
2913 if (!eline) { setCurSrcLine(stline); fatal("no EDUP"); }
2914 // now repeat that lines
2915 while (cnt-- > 0) {
2916 setCurSrcLine(stline);
2917 while (curSrcLine != eline) processCurrentLine();
2919 return PI_SKIP_LINE;
2923 ///////////////////////////////////////////////////////////////////////////////
2924 // IF, ENDIF
2926 static int ifCount = 0;
2929 // results:
2930 // -1: error (should not happen)
2931 // 0: successfully complete
2932 // 1: stopped *AT* "ELSE"
2933 // 2: stopped *AT* "ELSEIF"
2934 static int ifSkipToEndIfOrElse (int wholeBody) {
2935 int inum, wasElse = 0;
2936 SourceLine *oline;
2938 while (curSrcLine) {
2939 if (!setCurSrcLine(findNextInstructionFromList(&inum, "IF", "ELSE", "ENDIF", "ELSEIF", "MACRO", "ENDM", "IFX", "ELSEIFX", NULL))) break;
2940 switch (inum) {
2941 case 0: /* if */
2942 case 6: /* ifx */
2943 nextSrcLine(); // skip IF
2944 ifSkipToEndIfOrElse(1); // and recurse
2945 nextSrcLine(); // skip ENDIF
2946 break;
2947 case 1: /* else */
2948 if (wasElse) fatal("duplicate ELSE");
2949 if (!wholeBody) return 1;
2950 wasElse = 1;
2951 nextSrcLine(); // skip ELSE
2952 break; // and continue
2953 case 2: /* endif */
2954 return 0; // and exit
2955 case 3: /* elif */
2956 case 7: /* elifx */
2957 if (wasElse) fatal("ELSEIF in ELSE");
2958 if (!wholeBody) return 2;
2959 nextSrcLine(); // skip ELSEIF
2960 break; // and continue
2961 case 4: /* macro */
2962 // skip it as a whole
2963 nextSrcLine(); // skip MACRO
2964 for (;;) {
2965 oline = curSrcLine;
2966 if (!setCurSrcLine(findNextInstructionFromList(&inum, "MACRO", "ENDM", NULL))) { setCurSrcLine(oline); fatal("invalid MACRO"); }
2967 if (inum == 1) break;
2968 fatal("invalid nested MACRO");
2970 nextSrcLine(); // skip ENDM
2971 break;
2972 case 5: /* endm */
2973 fatal("unexpected ENDM");
2976 fatal("IF without ENDIF");
2977 return -1;
2981 static int piENDIF (void) {
2982 /*!fprintf(stderr, "ENDIF(%d): LINE <%s> (%d)\n", ifCount, curLine, (curSrcLine ? curSrcLine->lineNo : 0));*/
2983 if (--ifCount < 0) fatal("ENDIF without IF");
2984 checkOperatorEnd();
2985 return PI_SKIP_LINE;
2989 static int piELSE (void) {
2990 if (--ifCount < 0) fatal("ELSE without IF");
2991 nextSrcLine(); // skip ELSE
2992 ifSkipToEndIfOrElse(1);
2993 return PI_SKIP_LINE;
2996 static int piELSEIF (void) {
2997 if (--ifCount < 0) fatal("ELSEIF without IF");
2998 nextSrcLine(); // skip ELSEIF
2999 ifSkipToEndIfOrElse(1);
3000 return PI_SKIP_LINE;
3004 static int piELSEIFX (void) {
3005 if (--ifCount < 0) fatal("ELSEIFX without IF");
3006 nextSrcLine(); // skip ELSEIFX
3007 ifSkipToEndIfOrElse(1);
3008 return PI_SKIP_LINE;
3012 static int piIFAll (int isIfX) {
3013 int defined = 1;
3014 int ooo = lblOptMakeU2;
3015 lblOptMakeU2 = (isIfX ? 1 : 0);
3016 int32_t cond = getOneExprArg(&defined, NULL);
3017 lblOptMakeU2 = ooo;
3019 if (!defined) {
3020 if (!isIfX) fatal("IF: condition must be defined");
3021 cond = 0; // for IFX: 0 if there is any undefined label
3023 if (cond) {
3024 // ok, do it until ELSE/ELSEIF/ENDIF
3025 ++ifCount;
3026 return PI_SKIP_LINE;
3028 for (;;) {
3029 int r;
3030 char *args;
3032 nextSrcLine(); // skip last instruction
3033 // skip until ELSE/ELSEIF/ENDIF
3034 r = ifSkipToEndIfOrElse(0);
3035 /*!fprintf(stderr, "ELSEIF(%d): 000: LINE <%s> (%d)\n", r, curLine, (curSrcLine ? curSrcLine->lineNo : 0));*/
3036 if (r == 0) break; // ENDIF
3037 if (r == 1) { ++ifCount; break; } // ELSE
3038 // ELSEIF, do condition
3039 args = strIsCommand("ELSEIF", currLine);
3040 if (args) {
3041 isIfX = 0;
3042 } else {
3043 if ((args = strIsCommand("ELSEIFX", currLine)) == NULL) fatal("internal error in conditionals"); // the thing that should not be
3044 isIfX = 1;
3046 memmove(currLine, args, strlen(args)+1);
3047 /*!fprintf(stderr, "ELSEIF(%d): 001: LINE <%s> (%d)\n", r, curLine, (curSrcLine ? curSrcLine->lineNo : 0));*/
3048 cond = getOneExprArg(&defined, NULL);
3049 /*!fprintf(stderr, "ELSEIF(%d): 002: LINE <%s> (%d); cond=%d\n", r, curLine, (curSrcLine ? curSrcLine->lineNo : 0), cond);*/
3050 if (!defined) {
3051 if (!isIfX) fatal("ELSEIF: condition must be defined");
3052 cond = 0; // for IFX: 0 if there is any undefined label
3054 if (cond) { ++ifCount; break; } // condition is true
3056 return PI_SKIP_LINE;
3060 static int piIF (void) { return piIFAll(0); }
3061 static int piIFX (void) { return piIFAll(1); }
3064 ///////////////////////////////////////////////////////////////////////////////
3065 // macro processor
3066 ///////////////////////////////////////////////////////////////////////////////
3068 what i did with MACRO is the brain-damaged cheating all the way.
3070 first, i will collect the MACRO body and remember it (removing it
3071 from the main source code, so second pass will not see it).
3073 second, when the macro is used, i will:
3074 * insert the whole macro body in place (label resolver will
3075 fix "..lbl" labels for us)
3076 * let the asm play with it
3078 this is not the best scheme, but it is fairly simple and it works.
3081 // will be called when parser encounters term starting with '=' or '*'
3082 // first term char will not be skipped
3083 // must return pointer to the first char after expression end
3084 static const char *getValueCB (urasm_exprval_t *res, const char *expr, uint16_t addr, int donteval, int *defined, int *error) {
3085 char name[257];
3086 char *p = name;
3088 if (curmacro == NULL) fatal("'=' outside of macro");
3089 if (*expr++ != '=') fatal("'=' expected!");
3091 expr = strSkipSpaces(expr);
3092 while (*expr) {
3093 if (isAlphaDigit(*expr) || *expr == '_') {
3094 if (p-name > 250) fatal("id too long");
3095 *p++ = *expr++;
3096 continue;
3098 break;
3100 *p = 0;
3102 expr = strSkipSpaces(expr);
3103 for (int f = 0; f < curmacro->mac->argc; ++f) {
3104 if (strcasecmp(name, curmacro->mac->argnames[f]) == 0) {
3105 if (*expr == '[') {
3106 urasm_exprval_t v;
3107 int l = (int)strlen(curmacro->argvals[f]);
3108 ++expr; // skip "["
3109 urasm_exprval_init(&v);
3110 expr = urasm_expr_ex(&v, expr, addr, &donteval, defined, error);
3111 if (*error) return expr;
3112 if (expr == NULL || *expr != ']' || v.str != NULL) { *error = UR_EXPRERR_FUNC; return expr; }
3113 ++expr;
3114 if (v.val < 0) v.val += l;
3115 if (v.val < 0 || v.val >= l) {
3116 res->val = '?';
3117 } else {
3118 res->val = (unsigned char)(curmacro->argvals[f][v.val]);
3120 return expr;
3121 } else {
3122 urasm_expr_ex(res, curmacro->argvals[f], addr, &donteval, defined, error);
3123 return expr;
3128 fatal("unknown macro variable: '%s'", name);
3132 //!0: error; oprlen is opr size (w/o ending 0), it's 255 for now
3133 // opr starts with '=' (invariant)
3134 static int expandCB (char *opr, int oprlen) {
3135 char name[257], *p = name, *op = opr;
3137 if (curmacro == NULL) fatal("'=' outside of macro");
3138 if (*op++ != '=') fatal("'=' expected!"); // just in case
3139 //fprintf(stderr, "expand: [%s]\n", opr);
3141 if (!isAlpha(op[0])) return 0; // nothing to do
3143 // copy argument name
3144 while (*op) {
3145 if (isAlphaDigit(*op) || *op == '_') {
3146 if (p-name > 250) fatal("id too long");
3147 *p++ = *op++;
3148 continue;
3150 break;
3152 *p = 0;
3154 // expand argument? we only need to expand `=arg[n]`
3155 op = strSkipSpaces(op);
3156 if (op[0] != '[') return 0;
3158 for (int f = 0; f < curmacro->mac->argc; ++f) {
3159 if (strcasecmp(name, curmacro->mac->argnames[f]) == 0) {
3160 if (*op != '[') abort(); // assertion, just in case
3161 // replace argument with character, or with literal value
3162 // used for `=regpair[0]` or `=regpair[]`
3163 if (op[1] == ']') {
3164 op += 2;
3165 const int l = (int)strlen(curmacro->argvals[f]);
3166 const int lleft = (int)strlen(op);
3167 char *tmp = malloc(l+lleft+8);
3168 snprintf(tmp, l+lleft+8, "%s%s", curmacro->argvals[f], op);
3169 if ((int)strlen(tmp) > oprlen) {
3170 free(tmp);
3171 return -1;
3173 strcpy(opr, tmp);
3174 free(tmp);
3175 return 0;
3176 } else {
3177 urasm_exprval_t v;
3178 int error = 0, defined = 1, donteval = 0;
3179 ++op; // skip '['
3180 urasm_exprval_init(&v);
3181 op = (char *)urasm_expr_ex(&v, op, pc, &donteval, &defined, &error);
3182 if (error) return -1;
3183 // result should be a number
3184 if (op == NULL || *op != ']' || v.str != NULL) return -1;
3185 ++op; // skip ']'
3186 // it is guaranteed to have more than one char in opr
3187 // so we can simply put char from argument value, and remove other expression chars
3188 const int l = (int)strlen(curmacro->argvals[f]);
3189 if (v.val < 0) v.val += l; // negative: indexing from the end
3190 if (v.val < 0 || v.val >= l) fatal("index %d is out of bounds for macro argument '%s'", v.val, curmacro->mac->argnames[f]);
3191 // copy char
3192 opr[0] = curmacro->argvals[f][v.val];
3193 // remove other chars
3194 memmove(opr+1, op, strlen(op)+1);
3196 return 0;
3200 fatal("unknown macro variable: '%s'", name);
3204 // main macro expander
3205 static void processMacro (MacroDef *mc) {
3206 SourceLine *oldcurline = curSrcLine;
3207 CurMacroDef cm;
3209 //fprintf(stderr, "processMacro: [%s] (%d)\n", mc->name, curmacronum);
3210 //for (SourceLine *l = mc->lines; l != NULL; l = l->next) fprintf(stderr, "*[%s]\n", l->line);
3212 // parse macro arguments
3213 cm.mac = mc;
3214 for (unsigned f = 0; f < MAX_MACRO_ARGS; ++f) cm.argvals[f] = NULL;
3215 int currArg = 0;
3216 for (;;) {
3217 removeSpaces();
3218 // do we have more arguments?
3219 if (!currLine[0]) break;
3220 // check for argument name (this is purely cosmetic thing)
3221 // use like this: "mcall :argname=[value]" (value may be omited for default)
3222 if (currLine[0] == ':' && isAlpha(currLine[1])) {
3223 int pos = 2;
3224 while (isAlphaDigit(currLine[pos]) || currLine[pos] == '_') ++pos;
3225 //hack!
3226 const char svch = currLine[pos];
3227 currLine[pos] = 0;
3228 if (strcasecmp(currLine+1, mc->argnames[currArg]) != 0) {
3229 // out-of-order, find proper argument and rewind to it
3230 currArg = -1;
3231 for (int c = 0; c < mc->argc; ++c) {
3232 if (strcasecmp(currLine+1, mc->argnames[c]) == 0) {
3233 currArg = c;
3234 break;
3237 if (currArg < 0) fatal("macro '%s' has no argument named '%s'", mc->name, currLine+1);
3239 currLine[pos] = svch;
3240 // remove argument name
3241 memmove(currLine, currLine+pos, strlen(currLine+pos)+1);
3242 removeSpaces();
3243 // check for '='
3244 if (currLine[0] != '=') fatal("expected '=' after argument name");
3245 // remove '='
3246 memmove(currLine, currLine+1, strlen(currLine));
3247 removeSpaces();
3249 // too many args?
3250 if (currArg >= mc->argc) fatal("too many arguments to macro '%s'", mc->name);
3251 // check for default value
3252 if (currLine[0] == ',') {
3253 if (mc->argdefaults[currArg] == NULL) fatal("macro '%s' has no default value for argument '%s'", mc->name, mc->argnames[currArg]);
3254 cm.argvals[currArg] = strdup(mc->argdefaults[currArg]);
3255 memmove(currLine, currLine+1, strlen(currLine));
3256 } else {
3257 // skip argument (so we will know its length, and will be able to copy it)
3258 char *e = skipMacroArg(currLine);
3259 //fprintf(stderr, "*[%s]\n*[%s]\n", curLine, e);
3260 const char ech = *e;
3261 if (ech != 0) {
3262 if (ech == ':') fatal("invalid invocation of macro '%s' (only one macro per line allowed)", mc->name);
3263 if (ech != ',') fatal("invalid invocation of macro '%s'", mc->name);
3265 *e = 0;
3266 cm.argvals[currArg] = strdup(currLine);
3267 // strip trailing spaces
3268 strTrimRight(cm.argvals[currArg]);
3269 if (ech) {
3270 *e = ech;
3271 memmove(currLine, e+1, strlen(e));
3272 } else {
3273 currLine[0] = 0;
3276 ++currArg;
3278 // check for line end
3279 removeSpaces();
3280 if (currLine[0]) fatal("invalid macro invocation");
3282 // setup default argument values
3283 for (int f = 0; f < mc->argc; ++f) {
3284 //fprintf(stderr, "MAC: %s; arg #%d (%s): <%s>\n", mc->name, f, mc->argnames[f], cm.argvals[f]);
3285 if (cm.argvals[f]) continue;
3286 if (mc->argdefaults[f] == NULL) fatal("macro '%s' has no default value for argument '%s'", mc->name, mc->argnames[f]);
3287 cm.argvals[f] = strdup(mc->argdefaults[f]);
3288 //fprintf(stderr, " DEF arg #%d (%s): <%s>\n", f, mc->argnames[f], cm.argvals[f]);
3291 //for (int f = 0; f < mc->argc; ++f) fprintf(stderr, "%d: [%s]\n", f, cm.argvals[f]);
3293 // insert macro code into the source
3294 setCurSrcLine(mc->lines);
3295 curmacro = &cm;
3296 ++curmacronum;
3297 while (curSrcLine != NULL) {
3298 //fprintf(stderr, "*[%s] (%d)\n", curSrcLine->line, curSrcLine->lineNo);
3299 processCurrentLine();
3302 setCurSrcLine(oldcurline);
3303 curmacro = NULL;
3304 nextSrcLine();
3308 static int piMACRO (void) {
3309 char *name;
3310 int argc = 0;
3311 char *argdefaults[MAX_MACRO_ARGS];
3312 char *argnames[MAX_MACRO_ARGS];
3313 SourceLine *stline, *eline = NULL;
3314 MacroDef *mc;
3316 name = strdup(getLabelArg(0));
3317 //fprintf(stderr, "[%s]\n", name);
3318 if (findMacro(name) != NULL) fatal("duplicate MACRO definition: '%s'", name);
3319 if (currLine[0] == ',') memmove(currLine, currLine+1, strlen(currLine));
3320 removeSpaces();
3322 while (currLine[0]) {
3323 if (argc >= MAX_MACRO_ARGS) fatal("too many arguments in MACRO");
3324 if (!isAlpha(currLine[0])) fatal("invalid MACRO definition");
3325 argnames[argc] = strdup(getLabelArg(0));
3326 if (currLine[0] == '=') {
3327 // default value
3328 char *e = strchr(currLine, ','), tch;
3330 if (e == NULL) e = currLine+strlen(currLine);
3331 tch = *e;
3332 *e = 0;
3333 argdefaults[argc] = strdup(currLine+1);
3334 *e = tch;
3335 memmove(currLine, e, strlen(e)+1);
3336 } else {
3337 argdefaults[argc] = NULL;
3339 removeSpaces();
3340 if (currLine[0] == ',') {
3341 memmove(currLine, currLine+1, strlen(currLine));
3342 removeSpaces();
3344 //fprintf(stderr, "%d: [%s] [%s]\n", argc, argnames[argc], argdefaults[argc]);
3345 ++argc;
3348 // now find corresponding ENDM
3349 // note that we should skip nested DUPs
3350 stline = curSrcLine;
3351 nextSrcLine(); // skip ourself
3352 while (curSrcLine) {
3353 int inum;
3355 if (!setCurSrcLine(findNextInstructionFromList(&inum, "MACRO", "ENDM", NULL))) break;
3356 // ok, we found something; what is it?
3357 if (inum == 0) {
3358 // new MACRO
3359 fatal("no nested MACROs yet");
3360 } else {
3361 // ENDM, gotcha!
3362 eline = curSrcLine;
3363 // kill ENDM
3364 eline->line[0] = 0;
3365 nextSrcLine(); // skip ENDM
3366 break;
3369 if (!eline) { setCurSrcLine(stline); fatal("no ENDM"); }
3371 if ((mc = calloc(1, sizeof(*mc))) == NULL) fatal("out of memory");
3372 mc->name = name;
3373 mc->argc = argc;
3374 for (int f = 0; f < argc; ++f) { mc->argdefaults[f] = argdefaults[f]; mc->argnames[f] = argnames[f]; }
3376 eline->next = NULL;
3377 mc->lines = stline->next;
3378 stline->next = curSrcLine;
3379 stline->line[0] = 0;
3381 mc->next = maclist;
3382 maclist = mc;
3383 setCurSrcLine(stline);
3384 return PI_SKIP_LINE;
3388 static int piENDM (void) {
3389 fatal("ENDM without MACRO");
3390 return PI_SKIP_LINE;
3394 ///////////////////////////////////////////////////////////////////////////////
3395 // line processor
3396 static int optWriteType = 't';
3397 static int optWTChanged = 0;
3400 static void piTapParseLoaderName (void) {
3401 if (eatComma()) {
3402 int len;
3403 if (!isStrArg()) fatal("loader name expected");
3404 char *fn = getStrArg(&len);
3405 if (len > 10) fatal("loader name too long");
3406 memset(tapeLoaderName, ' ', 10);
3407 for (int f = 0; f < len; ++f) tapeLoaderName[f] = fn[f];
3412 static int piMATHMODE (void) {
3413 char *name = getOneLabelArg();
3414 if (strcasecmp(name, "OLD") == 0) urasm_use_old_priorities = 1;
3415 else if (strcasecmp(name, "NEW") == 0) urasm_use_old_priorities = 0;
3416 else fatal("invalid math mode; NEW or OLD expected");
3417 return PI_SKIP_LINE;
3421 static int piDEFFMT (void) {
3422 char *name;
3424 if (isStrArg()) {
3425 int len = 0;
3426 name = getStrArg(&len);
3427 } else {
3428 name = getLabelArg(1);
3430 if (optWTChanged) return 1;
3432 optRunDMB = optRunTape = optRunSCL = 0;
3433 //optRunSNA = 0;
3435 if (!strcasecmp(name, "SNA") || !strcasecmp(name, "RUNSNA") || !strcasecmp(name, "SNARUN")) {
3436 optWriteType = 's';
3437 if (currLine[0]) fatal("too many expressions");
3438 return PI_SKIP_LINE;
3440 if (!strcasecmp(name, "TAP") || !strcasecmp(name, "TAPE")) {
3441 optWriteType = 't';
3442 piTapParseLoaderName();
3443 return PI_SKIP_LINE;
3445 if (!strcasecmp(name, "RUNTAP") || !strcasecmp(name, "RUNTAPE") || !strcasecmp(name, "TAPERUN")) {
3446 optRunTape = 1;
3447 optWriteType = 't';
3448 piTapParseLoaderName();
3449 return PI_SKIP_LINE;
3451 if (!strcasecmp(name, "BIN") || !strcasecmp(name, "RAW")) {
3452 optWriteType = 'r';
3453 if (currLine[0]) fatal("too many expressions");
3454 return PI_SKIP_LINE;
3456 if (!strcasecmp(name, "DMB") || !strcasecmp(name, "RUNDMB") || !strcasecmp(name, "DMBRUN")) {
3457 optRunDMB = (name[3] != 0);
3458 optWriteType = 'd';
3459 if (currLine[0]) fatal("too many expressions");
3460 return PI_SKIP_LINE;
3462 if (!strcasecmp(name, "NONE") || !strcasecmp(name, "NOTHING")) {
3463 optWriteType = 'n';
3464 if (currLine[0]) fatal("too many expressions");
3465 return PI_SKIP_LINE;
3467 if (!strcasecmp(name, "SCL") || !strcasecmp(name, "RUNSCL") || !strcasecmp(name, "SCLRUN")) {
3468 optWriteType = 'S';
3469 optRunSCL = (name[3] != 0 ? -1 : 0); /* no boot */
3470 piTapParseLoaderName();
3471 return PI_SKIP_LINE;
3473 if (!strcasecmp(name, "SCLBOOT") || !strcasecmp(name, "BOOTSCL")) {
3474 optWriteType = 'S';
3475 optRunSCL = 1; /* with boot */
3476 piTapParseLoaderName();
3477 return PI_SKIP_LINE;
3479 fatal("invalid default output type: %s", name);
3483 ///////////////////////////////////////////////////////////////////////////////
3484 // line processor
3486 static void processCurrentLine (void) {
3487 if (!curSrcLine) return; // do nothing
3488 loadCurSrcLine();
3489 if (asmMode == AMODE_FWORD) {
3490 processForthWordLine();
3491 nextSrcLine(); // skip it
3492 return;
3494 processLabel();
3495 for (;;) {
3496 char *str, *ee, name[66];
3497 UrAsmOp *op;
3498 int len;
3499 const char *errpos;
3501 removeSpacesAndColons();
3502 // skip spaces and ':'
3503 if (!currLine[0]) { nextSrcLine(); break; }
3504 // try to find and process command
3505 str = currLine; //while (*str && isSpace(*str)) ++str; // skip spaces
3506 // find command end
3507 for (ee = str; *ee && !isSpace(*ee) && *ee != '"' && *ee != '\'' && *ee != ',' && *ee != ':'; ++ee) {}
3508 // get command, if any
3509 if (ee != str && ee-str <= 64) {
3510 MacroDef *mc;
3511 memset(name, 0, sizeof(name));
3512 memmove(name, str, ee-str);
3513 /* known command? */
3514 op = urFindOp(name);
3515 if (op) {
3516 // ok, do it
3517 str = ee; while (*str && isSpace(*str)) ++str; // skip spaces
3518 memmove(currLine, str, strlen(str)+1);
3519 if (op->fn()) {
3520 nextSrcLine(); // skip it
3521 break;
3523 continue;
3525 /* known macro? */
3526 if ((mc = findMacro(name)) != NULL) {
3527 // ok, do it
3528 str = ee; while (*str && isSpace(*str)) ++str; // skip spaces
3529 memmove(currLine, str, strlen(str)+1);
3530 processMacro(mc);
3531 /* only one macro per line! */
3532 break;
3536 len = urasm_opasm(currLine, pc, disp, &errpos);
3537 if (len < 0) fatalUrLib(len);
3538 pc += len;
3539 disp += len;
3540 if (len >= 0 && errpos) {
3541 memmove(currLine, errpos+1, strlen(errpos));
3542 } else {
3543 nextSrcLine(); // skip it
3544 break;
3550 ///////////////////////////////////////////////////////////////////////////////
3551 // forth instructions
3553 static uint16_t forthLatest = 0;
3554 static const char *forthCurrWord = NULL;
3557 typedef struct ForthWordT {
3558 char *name; // uppercased
3559 uint16_t nfa;
3560 uint16_t cfa;
3561 int isbranch; // -1: wants relative addr; 1: wants absolute addr; -666: check `USE_REL_BRANCH`
3562 struct ForthWordT *next;
3563 } ForthWord;
3565 static ForthWord *forthWordList = NULL;
3566 static ForthWord *forthWordListTail = NULL;
3568 static ForthWord *findForthWord (const char *name) {
3569 if (!name) name = "";
3570 for (ForthWord *w = forthWordList; w; w = w->next) {
3571 if (strcasecmp(name, w->name) == 0) return w;
3573 return NULL;
3577 static ForthWord *addForthWord (const char *name) {
3578 if (!name) name = "";
3579 ForthWord *nw = findForthWord(name);
3580 if (pass == 0) {
3581 if (nw) fatal("duplicate forth word: '%s'", name);
3582 nw = malloc(sizeof(ForthWord));
3583 nw->name = strdup(name);
3584 for (char *s = nw->name; *s; ++s) {
3585 if (s[0] >= 'a' && s[0] <= 'z') s[0] = s[0]-'a'+'A';
3587 nw->nfa = 0;
3588 nw->cfa = 0;
3589 nw->isbranch = 0;
3590 nw->next = NULL;
3591 if (forthWordListTail) forthWordListTail->next = nw; else forthWordList = nw;
3592 forthWordListTail = nw;
3593 } else {
3594 if (!nw) fatal("internal compiler error");
3596 return nw;
3601 ;; word format:
3602 ;; db len_flags
3603 ;; name
3604 ;; dw lfa
3605 ;; dw cfa
3606 ;; ....
3607 ;; len_flags:
3608 ;; bits 0-4: length
3609 ;; bit 5: SMUDGE flag (=1: word definition isn't finished)
3610 ;; bit 6: IMMEDIATE flag (=1: true)
3611 ;; bit 7: always 1
3612 ;; the last byte of the name always has bit 7 set
3614 static ForthWord *forthWordHead (const char *name, int imm) {
3615 ForthWord *nw = addForthWord(name);
3616 if (!name) name = "";
3617 const uint16_t lt = disp;
3618 //fprintf(stderr, "#%04X: fw=<%s>\n", lt, name);
3619 if (pass == 0) {
3620 nw->nfa = disp;
3621 } else {
3622 if (nw->nfa != disp) fatal("forth word `%s`: orig NFA=#%04X; pass1 NFA=#%04X\n", name, nw->nfa, disp);
3623 //fprintf(stderr, "#%04X: fw=<%s>\n", lt, name);
3625 size_t nlen = strlen(name);
3626 if (nlen == 0) nlen = 1;
3627 if (nlen > 31) fatal("forth word name too long: '%s'", name);
3628 // word length and flags (nfa)
3629 emitByte((nlen&0xff)|0x80u|(imm ? 0x40u : 0x00u));
3630 while (nlen != 0) {
3631 uint8_t b = (uint8_t)(name[0]&0xffu);
3632 if (b >= 'a' && b <= 'z') b = b-'a'+'A';
3633 if (nlen == 1) b |= 0x80u;
3634 emitByte(b);
3635 ++name;
3636 --nlen;
3638 // lfa
3639 emitWord(forthLatest);
3640 forthLatest = lt;
3641 nw->cfa = disp;
3642 //fprintf(stderr, "#%04X: fw=<%s>\n", nw->cfa, nw->name);
3643 return nw;
3647 /* returns static buffer */
3648 static char *forthGetWord (void) {
3649 static char buf[256];
3650 size_t pos = 0;
3651 while (isSpace(currLine[pos])) ++pos;
3652 size_t bp = 0;
3653 while (currLine[pos] && !isSpace(currLine[pos])) {
3654 if (bp >= sizeof(buf)-2) fatal("forth word too long");
3655 buf[bp++] = currLine[pos++];
3657 buf[bp] = 0;
3658 while (isSpace(currLine[pos])) ++pos;
3659 if (pos > 0) memmove(currLine, currLine+pos, strlen(currLine+pos)+1);
3660 return (buf[0] ? buf : NULL);
3664 static void forthEmitLabel (const char *name) {
3665 UrLabelInfo *lbl = urFindLabel(name);
3666 if (!lbl) {
3667 if (pass != 0) fatal("using undefined label '%s'", name);
3668 lbl = urAddLabel(name);
3669 //fprintf(stderr, "****** <%s>\n", lbl->name);
3670 lbl->type = (lblOptMakeU2 ? -42 : -1);
3671 lbl->known = 0;
3672 lbl->refLine = curSrcLine->lineNo;
3673 lbl->refFile = strdup(curSrcLine->fname);
3675 emitWord(lbl->value);
3679 static void forthDoConstVar (const char *lbl) {
3680 const char *wname = forthGetWord();
3681 if (!wname) fatal("forth word name expected");
3682 /*ForthWord *nw =*/ forthWordHead(wname, 0);
3683 // cfa
3684 forthEmitLabel(lbl);
3685 // constant value
3686 int defined = 1, addr = 0;
3687 int32_t res = getOneExprArg(&defined, &addr);
3688 if (strcmp(lbl, "_do2var") != 0) {
3689 if (res < 0) res = 65536+res;
3690 emitWord(res&0xffffU);
3691 } else {
3692 // double var
3693 emitWord(res&0xffffU);
3694 emitWord((res>>16)&0xffffU);
3696 //fprintf(stderr, "FORTH CONST/VAR: '%s' = #%04X\n", nw->name, res);
3697 checkOperatorEnd();
3701 static int piForthConst (void) {
3702 forthDoConstVar("_doconst");
3703 return PI_SKIP_LINE;
3707 static int piForthVar (void) {
3708 forthDoConstVar("_dovar");
3709 return PI_SKIP_LINE;
3713 static int piForthDVar (void) {
3714 forthDoConstVar("_do2var");
3715 return PI_SKIP_LINE;
3719 static int piForthUser (void) {
3720 forthDoConstVar("_douser");
3721 return PI_SKIP_LINE;
3725 static void forthParseBranchFlag (ForthWord *nw) {
3726 const char *wname = forthGetWord();
3727 if (wname) {
3728 if (strcasecmp(wname, "RBRANCH") == 0) nw->isbranch = -1;
3729 else if (strcasecmp(wname, "ABRANCH") == 0) nw->isbranch = 1;
3730 else if (strcasecmp(wname, "XBRANCH") == 0) nw->isbranch = -666;
3731 else fatal("end of line expected");
3736 static int piForthCodeWord (void) {
3737 if (asmMode != AMODE_NORMAL) fatal("invalid forth define");
3738 asmMode = AMODE_FCODE;
3739 const char *wname = forthGetWord();
3740 if (!wname) fatal("forth word name expected");
3741 ForthWord *nw = forthWordHead(wname, 0);
3742 // cfa
3743 emitWord((disp+2)&0xffffU);
3744 forthParseBranchFlag(nw);
3745 checkOperatorEnd();
3746 //fprintf(stderr, "FORTH CODE WORD: '%s'\n", nw->name);
3747 forthCurrWord = nw->name;
3748 return PI_SKIP_LINE;
3752 static int piForthCodeWordEnd (void) {
3753 if (asmMode != AMODE_FCODE || !forthCurrWord) fatal("invalid forth define");
3754 asmMode = AMODE_NORMAL;
3755 const char *wname = forthGetWord();
3756 if (!wname) fatal("forth word name expected");
3757 if (!forthWordListTail) fatal("end of unknown forth code word");
3758 if (strcasecmp(wname, forthCurrWord) != 0) fatal("end of forth code word '%s', but expected '%s'", wname, forthWordListTail->name);
3759 checkOperatorEnd();
3760 forthCurrWord = NULL;
3761 return PI_SKIP_LINE;
3765 static int piForthWord (void) {
3766 if (asmMode != AMODE_NORMAL) fatal("invalid forth define");
3767 asmMode = AMODE_FWORD;
3768 const char *wname = forthGetWord();
3769 if (!wname) fatal("forth word name expected");
3770 if (strcmp(wname, "~") == 0) wname = "";
3771 char *fwn = strdup(wname);
3772 wname = forthGetWord();
3773 int imm = 0;
3774 if (wname && strcasecmp(wname, "IMM") == 0) imm = 1;
3775 ForthWord *nw = forthWordHead(fwn, imm);
3776 free(fwn);
3777 // cfa
3778 forthEmitLabel("_doforth");
3779 forthParseBranchFlag(nw);
3780 checkOperatorEnd();
3781 //fprintf(stderr, "FORTH WORD: '%s'\n", nw->name);
3782 forthCurrWord = nw->name;
3783 return PI_SKIP_LINE;
3787 static int piForthWordDoes (void) {
3788 if (asmMode != AMODE_NORMAL) fatal("invalid forth define");
3789 // get forth word name
3790 const char *wname = forthGetWord();
3791 if (!wname) fatal("forth word name expected");
3792 if (strcmp(wname, "~") == 0) wname = "";
3793 char *fwn = strdup(wname);
3794 // get does label name
3795 wname = forthGetWord();
3796 if (!wname) fatal("does label expected");
3797 char *dlb = strdup(wname);
3798 // get "imm" flag
3799 wname = forthGetWord();
3800 int imm = 0;
3801 if (wname && strcasecmp(wname, "IMM") == 0) imm = 1;
3802 // nfa
3803 /*ForthWord *nw =*/ forthWordHead(fwn, imm);
3804 free(fwn);
3805 // cfa
3806 forthEmitLabel("_dodoes");
3807 // does label
3808 forthEmitLabel(dlb);
3809 free(dlb);
3810 checkOperatorEnd();
3811 //fprintf(stderr, "FORTH DOES WORD: '%s'\n", nw->name);
3812 return PI_SKIP_LINE;
3816 static int piForthWordEnd (void) {
3817 if (asmMode != AMODE_FWORD || !forthCurrWord) fatal("invalid forth define");
3818 asmMode = AMODE_NORMAL;
3819 const char *wname = forthGetWord();
3820 if (!wname) fatal("forth word name expected");
3821 if (!forthWordListTail) fatal("end of unknown forth code word");
3822 if (strcasecmp(wname, forthCurrWord) != 0) fatal("end of forth code word '%s', but expected '%s'", wname, forthWordListTail->name);
3823 checkOperatorEnd();
3824 forthCurrWord = NULL;
3825 return PI_SKIP_LINE;
3829 static int isStartsWithForthCmd (void) {
3830 return
3831 currLine[0] == '$' &&
3832 toUpper(currLine[1]) == 'F' &&
3833 toUpper(currLine[2]) == 'O' &&
3834 toUpper(currLine[3]) == 'R' &&
3835 toUpper(currLine[4]) == 'T' &&
3836 toUpper(currLine[5]) == 'H';
3840 //HACK!
3842 static int forthIsBranchWord (const char *wname) {
3843 if (!wname || !wname[0]) return 0;
3844 if (strcasestr(wname, "BRANCH")) return 1;
3845 if (strcasestr(wname, "LOOP)")) return 1;
3846 return 0;
3851 static void processForthWordLine (void) {
3852 // check for "$FORTH..."
3853 if (isStartsWithForthCmd()) {
3854 const char *w = forthGetWord();
3855 if (!w) fatal("wtf?!");
3856 if (asmMode != AMODE_FWORD) fatal("wtf1?!");
3857 if (strcasecmp(w, "$FORTH_END_WORD") != 0) fatal("invalid forth instruction");
3858 w = forthGetWord();
3859 if (!w) fatal("forth word name expected");
3860 if (strcmp(w, "~") == 0) w = "";
3861 asmMode = AMODE_NORMAL;
3862 if (!forthCurrWord) fatal("end of unknown forth code word");
3863 if (strcasecmp(w, forthCurrWord) != 0) fatal("end of forth code word '%s', but expected '%s'", w, forthWordListTail->name);
3864 checkOperatorEnd();
3865 forthCurrWord = NULL;
3866 return;
3868 // check for a label
3869 if (isAlpha(currLine[0]) || currLine[0] == '_') {
3870 size_t lend = 1;
3871 while (currLine[lend] && currLine[lend] != ':') {
3872 //fprintf(stderr, "lend=%u (%c)\n", lend, currLine[lend]);
3873 if (isSpace(currLine[lend])) fatal("invalid label");
3874 ++lend;
3876 if (lend > 64) fatal("label too long");
3877 //fprintf(stderr, "lend=%u (%c)\n", lend, currLine[lend]);
3878 if (currLine[lend] != ':') fatal("label expected");
3879 char nn[68];
3880 memcpy(nn, currLine, lend);
3881 nn[lend] = 0;
3882 ++lend;
3883 memmove(currLine, currLine+lend, strlen(currLine+lend)+1);
3884 UrLabelInfo *lbl = urAddLabel(nn);
3885 if (!lbl->refFile) {
3886 lbl->refLine = curSrcLine->lineNo;
3887 lbl->refFile = strdup(curSrcLine->fname);
3889 if (pass == 0 && lbl->type != -1) fatal("duplicate label '%s'", lbl->name);
3890 // code label
3891 lbl->type = 2;
3892 lbl->value = disp;
3893 lbl->known = 1;
3894 lbl->fixuptype = UR_FIXUP_NONE;
3895 //fprintf(stderr, "flabel: %s (#%04X)\n", lbl->name, lbl->value);
3898 //fprintf(stderr, "<: %s :>\n", currLine);
3899 for (;;) {
3900 removeSpaces();
3901 const char *wname = forthGetWord();
3902 if (!wname) break;
3903 //fprintf(stderr, " %s: #%04X: <%s>\n", forthCurrWord, disp, wname);
3905 // check for string
3906 if (strcmp(wname, "(.\")") == 0 || strcmp(wname, "(\")") == 0) {
3907 if (pass == 0) {
3908 emitWord(0);
3909 } else {
3910 ForthWord *fw = findForthWord(wname);
3911 if (!fw) fatal("forth word `%s` not found", wname);
3912 emitWord(fw->cfa);
3914 removeSpaces();
3915 char qch = currLine[0];
3916 if (qch != '~' && qch != '"' && qch != '\'') fatal("invalid string quotation");
3917 memmove(currLine, currLine+1, strlen(currLine+1)+1);
3918 size_t pos = 0;
3919 while (currLine[pos] && currLine[pos] != qch) {
3920 if (currLine[pos] != '\\') { ++pos; continue; }
3921 int ch = -1;
3922 if (currLine[pos+1] == qch) {
3923 ch = qch;
3924 } else {
3925 switch (currLine[pos+1]) {
3926 case 'r': ch = 13; break;
3927 case 'n': ch = 256; break;
3928 case 't': ch = 9; break;
3929 case 'b': ch = 8; break;
3930 case 'v': ch = 10; break;
3931 case '\\': ch = '\\'; break;
3932 case 'x':
3933 //fprintf(stderr, "<%s>\n", currLine);
3934 if (!isHexDigit(currLine[pos+2]) || !isHexDigit(currLine[pos+3])) fatal("invalid hex escape");
3935 ch = digitInBase(currLine[pos+2], 16)*16+digitInBase(currLine[pos+3], 16);
3936 currLine[pos++] = ch;
3937 memmove(currLine+pos, currLine+pos+3, strlen(currLine+pos+3)+1);
3938 ch = 666; // special
3939 break;
3942 if (ch < 0) fatal("invalid escape");
3943 if (ch == 666) continue;
3944 if (ch == 256) {
3945 // cr+lf
3946 currLine[pos++] = 13;
3947 currLine[pos++] = 10;
3948 } else {
3949 currLine[pos++] = ch;
3950 memmove(currLine+pos, currLine+pos+1, strlen(currLine+pos+1)+1);
3953 if (pos > 255) fatal("string too long");
3954 if (currLine[pos] != qch) fatal("unterminated string");
3955 emitByte(pos&0xffU);
3956 for (size_t f = 0; f < pos; ++f) emitByte((uint8_t)(currLine[f]&0xffU));
3957 ++pos;
3958 memmove(currLine, currLine+pos, strlen(currLine+pos)+1);
3959 continue;
3962 // check for numeric literal
3963 if (strcasecmp(wname, "LIT") == 0) {
3964 if (pass == 0) {
3965 emitWord(0);
3966 } else {
3967 ForthWord *fw = findForthWord(wname);
3968 if (!fw) fatal("forth word `%s` not found", wname);
3969 emitWord(fw->cfa);
3971 removeSpaces();
3972 wname = forthGetWord();
3973 if (!wname) fatal("unexpected end of line");
3974 //fprintf(stderr, " %s: #%04X: <%s>\n", forthCurrWord, disp, wname);
3975 if (pass == 0) {
3976 emitWord(0);
3977 } else {
3978 // check for label
3979 UrLabelInfo *lbl = urFindLabel(wname);
3980 if (lbl) {
3981 if (!lbl->known) fatal("unknown label '%s'", lbl->name);
3982 emitWord(lbl->value&0xffffU);
3983 continue;
3985 // check for number
3986 char *end;
3987 int base = 10;
3988 if (wname[0] == '#' && isHexDigit(wname[1])) {
3989 ++wname;
3990 base = 16;
3992 long int v = strtol(wname, &end, base);
3993 if (end[0] == 0 && end != wname) {
3994 if (v < -32768 || v > 65535) fatal("forth numeric literal out of range: %d\n", (int)v);
3995 if (v < 0) v = 65536+v;
3996 emitWord(v&0xffffU);
3997 continue;
3999 fatal("number expected, got `%s`", wname);
4001 continue;
4004 // check for `COMPILE`
4005 if (strcasecmp(wname, "COMPILE") == 0) {
4006 if (pass == 0) {
4007 emitWord(0);
4008 } else {
4009 ForthWord *fw = findForthWord(wname);
4010 if (!fw) fatal("forth word `%s` not found", wname);
4011 emitWord(fw->cfa);
4013 removeSpaces();
4014 wname = forthGetWord();
4015 if (!wname) fatal("unexpected end of line");
4016 if (pass == 0) {
4017 emitWord(0);
4018 } else {
4019 ForthWord *fw = findForthWord(wname);
4020 if (!fw) fatal("forth word `%s` not found", wname);
4021 emitWord(fw->cfa);
4023 continue;
4026 // check for `[']`
4027 if (strcasecmp(wname, "[']") == 0) {
4028 if (pass == 0) {
4029 emitWord(0);
4030 } else {
4031 ForthWord *fw = findForthWord("LIT");
4032 if (!fw) fatal("forth word `%s` not found", "LIT");
4033 emitWord(fw->cfa);
4035 removeSpaces();
4036 wname = forthGetWord();
4037 if (!wname) fatal("unexpected end of line");
4038 if (pass == 0) {
4039 emitWord(0);
4040 } else {
4041 ForthWord *fw = findForthWord(wname);
4042 if (!fw) fatal("forth word `%s` not found", wname);
4043 emitWord(fw->cfa+2u);
4045 continue;
4048 // check for `['CFA]`
4049 if (strcasecmp(wname, "['CFA]") == 0) {
4050 if (pass == 0) {
4051 emitWord(0);
4052 } else {
4053 ForthWord *fw = findForthWord("LIT");
4054 if (!fw) fatal("forth word `%s` not found", "LIT");
4055 emitWord(fw->cfa);
4057 removeSpaces();
4058 wname = forthGetWord();
4059 if (!wname) fatal("unexpected end of line");
4060 if (pass == 0) {
4061 emitWord(0);
4062 } else {
4063 ForthWord *fw = findForthWord(wname);
4064 if (!fw) fatal("forth word `%s` not found", wname);
4065 emitWord(fw->cfa);
4067 continue;
4070 // other words
4071 if (pass == 0) {
4072 emitWord(0);
4073 } else {
4074 if (strcmp(wname, "~") == 0) wname = "";
4075 ForthWord *fw = findForthWord(wname);
4076 if (!fw) {
4077 // check for label
4078 UrLabelInfo *lbl = urFindLabel(wname);
4079 if (lbl) {
4080 fatal("label '%s' without LIT", lbl->name);
4081 emitWord(lbl->value&0xffffU);
4083 // check for number
4084 char *end;
4085 int base = 10;
4086 if (wname[0] == '#' && isHexDigit(wname[1])) {
4087 ++wname;
4088 base = 16;
4090 long int v = strtol(wname, &end, base);
4091 if (end[0] == 0 && end != wname) {
4092 fatal("number without LIT! (%d)", (int)v);
4093 continue;
4095 fatal("unknown forth word '%s'", wname);
4097 emitWord(fw->cfa);
4098 if (fw->isbranch) {
4099 wname = forthGetWord();
4100 if (!wname) fatal("label expected");
4101 UrLabelInfo *lbl = urFindLabel(wname);
4102 if (!lbl) fatal("unknown label in forth code: '%s'", wname);
4103 if (!lbl->known) fatal("undefined label '%s'", lbl->name);
4104 if (lbl->value < 0 || lbl->value > 65535) fatal("invalid jump label in forth code: '%s' (%d)", wname, lbl->value);
4105 int brt = fw->isbranch;
4106 if (brt == -666) {
4107 UrLabelInfo *brtl = urFindLabel("USE_REL_BRANCH");
4108 if (!brtl || !brtl->known || brtl->type < 0) fatal("forth XBRANCH requires defined `USE_REL_BRANCH`");
4109 brt = (brtl->value ? -1 : 1);
4111 if (brt < 0) {
4112 // relative
4113 int v = lbl->value-(int)disp;
4114 if (v < -32767 || v > 32767) fatal("forth jump too far");
4115 if (v < 0) v = 65536+v;
4116 emitWord(v&0xffffU);
4117 } else {
4118 // absolute
4119 int v = lbl->value;
4120 if (v < 0 || v > 65535) fatal("forth jump too far");
4121 emitWord(v&0xffffU);
4129 ///////////////////////////////////////////////////////////////////////////////
4130 // setup instructions
4132 static void registerInstructions (void) {
4133 urAddOp("DISPLAY", piDISPLAY);
4134 urAddOp("DISPLAY0", piDISPLAY0);
4135 urAddOp("DISPLAYA", piDISPLAYA);
4136 urAddOp("DISPHEX", piDISPHEX);
4137 urAddOp("DISPHEX0", piDISPHEX0);
4138 urAddOp("DISPHEXA", piDISPHEXA);
4140 urAddOp("DEFFMT", piDEFFMT);
4141 urAddOp("$MODEL", piMODEL);
4143 urAddOp("MACRO", piMACRO);
4144 urAddOp("ENDM", piENDM);
4146 urAddOp("ORG", piORG);
4147 urAddOp("DISP", piDISP);
4148 urAddOp("ENDDISP", piENDDISP);
4149 urAddOp("PHASE", piDISP);
4150 urAddOp("DEPHASE", piENDDISP);
4151 urAddOp("UNPHASE", piENDDISP);
4152 urAddOp("ALIGN", piALIGN);
4153 urAddOp("DISPALIGN", piDISPALIGN);
4154 urAddOp("PHASEALIGN", piDISPALIGN);
4155 urAddOp("ENT", piENT);
4156 urAddOp("CLR", piCLR);
4157 urAddOp("RESERVE", piRESERVE);
4159 urAddOp("INCLUDE", piINCLUDE);
4160 urAddOp("INCBIN", piINCBIN);
4162 urAddOp("MODULE", piMODULE);
4163 urAddOp("ENDMODULE", piENDMODULE);
4165 urAddOp("DUP", piDUP);
4166 urAddOp("EDUP", piEDUP);
4168 urAddOp("IF", piIF);
4169 urAddOp("IFX", piIFX);
4170 urAddOp("ELSE", piELSE);
4171 urAddOp("ELSEIF", piELSEIF);
4172 urAddOp("ELSEIFX", piELSEIFX);
4173 urAddOp("ENDIF", piENDIF);
4175 urAddOp("DEFINCR", piDEFINCR);
4176 urAddOp("DEFB", piDEFB); urAddOp("DB", piDEFB);
4177 urAddOp("DEFW", piDEFW); urAddOp("DW", piDEFW);
4178 urAddOp("DEFR", piDEFR); urAddOp("DR", piDEFR);
4179 urAddOp("DEFS", piDEFS); urAddOp("DS", piDEFS);
4180 urAddOp("DEFM", piDEFM); urAddOp("DM", piDEFM);
4181 urAddOp("DEFZ", piDEFZ); urAddOp("DZ", piDEFZ);
4182 urAddOp("DEFX", piDEFX); urAddOp("DX", piDEFX);
4183 urAddOp("DEFC", piDEFC); urAddOp("DC", piDEFC);
4185 urAddOp("$ERROR", piERROR);
4186 urAddOp("$WARNING", piWARNING);
4188 urAddOp("$PRINTF", piPRINTF);
4189 urAddOp("$PRINTF0", piPRINTF0);
4190 urAddOp("$PRINTFA", piPRINTFA);
4192 urAddOp("$MATHMODE", piMATHMODE);
4194 urAddOp("$FORTH_CONST", piForthConst);
4195 urAddOp("$FORTH_VAR", piForthVar);
4196 urAddOp("$FORTH_DVAR", piForthDVar);
4197 urAddOp("$FORTH_USER", piForthUser);
4199 urAddOp("$FORTH_CODE_WORD", piForthCodeWord);
4200 urAddOp("$FORTH_END_CODE_WORD", piForthCodeWordEnd);
4202 urAddOp("$FORTH_WORD", piForthWord);
4203 urAddOp("$FORTH_END_WORD", piForthWordEnd);
4205 urAddOp("$FORTH_DOES", piForthWordDoes);
4209 ///////////////////////////////////////////////////////////////////////////////
4210 // !0: invalid label
4212 static inline void fnSkipSpaces (const char *expr) {
4213 while (*expr && isSpace(*expr)) ++expr;
4214 return expr;
4219 static inline char fnNextChar (const char *expr) {
4220 while (*expr && isSpace(*expr)) ++expr;
4221 return *expr;
4225 #define FN_SKIP_BLANKS do { \
4226 while (*expr && isSpace(*expr)) ++expr; \
4227 } while (0)
4229 #define FN_CHECK_END do { \
4230 while (*expr && isSpace(*expr)) ++expr; \
4231 if (expr[0] != ')') { *error = UR_EXPRERR_FUNC; return expr; } \
4232 ++expr; \
4233 } while (0)
4236 #define FN_CHECK_COMMA do { \
4237 while (*expr && isSpace(*expr)) ++expr; \
4238 if (expr[0] != ',') { *error = UR_EXPRERR_FUNC; return expr; } \
4239 ++expr; \
4240 } while (0)
4243 static const char *readLabelName (char *buf, const char *expr) {
4244 int pos = 0;
4246 while (*expr && isSpace(*expr)) ++expr;
4247 for (;;) {
4248 char ch = *expr++;
4250 if (pos >= 128) return NULL;
4251 if (ch == ')') { --expr; break; }
4252 if (!ch) break;
4253 if (isAlphaDigit(ch) || ch == '$' || ch == '.' || ch == '_' || ch == '@') {
4254 buf[pos++] = ch;
4255 } else {
4256 break;
4259 if (pos < 1) return NULL;
4260 buf[pos] = '\0';
4261 if (!urasm_is_valid_name(buf)) return NULL;
4262 return expr;
4266 static const char *fnDefKn (urasm_exprval_t *res, const char *expr, uint16_t addr, int donteval, int *defined, int *error, int qtype) {
4267 char lbl[130];
4269 if ((expr = readLabelName(lbl, expr)) == NULL) { *error = UR_EXPRERR_LABEL; return NULL; }
4270 FN_CHECK_END;
4271 if (!donteval) res->val = (isLabelDefinedOrKnown(lbl, addr, qtype) != 0);
4272 return expr;
4275 static const char *fnDefined (urasm_exprval_t *res, const char *expr, uint16_t addr, int donteval, int *defined, int *error) { return fnDefKn(res, expr, addr, donteval, defined, error, UR_QTYPE_DEFINED); }
4276 static const char *fnKnown (urasm_exprval_t *res, const char *expr, uint16_t addr, int donteval, int *defined, int *error) { return fnDefKn(res, expr, addr, donteval, defined, error, UR_QTYPE_KNOWN); }
4279 static const char *fnAligned256 (urasm_exprval_t *res, const char *expr, uint16_t addr, int donteval, int *defined, int *error) {
4280 expr = urasm_expr_ex(res, expr, addr, &donteval, defined, error);
4281 FN_CHECK_END;
4282 if (!donteval) res->val = (res->val%256 ? 0 : 1);
4283 return expr;
4287 static const char *fnSameSeg (urasm_exprval_t *res, const char *expr, uint16_t addr, int donteval, int *defined, int *error) {
4288 urasm_exprval_t v0, v1;
4290 urasm_exprval_init(&v0);
4291 urasm_exprval_init(&v1);
4292 expr = urasm_expr_ex(&v0, expr, addr, &donteval, defined, error);
4293 if (*error) return expr;
4294 FN_CHECK_COMMA;
4295 expr = urasm_expr_ex(&v1, expr, addr, &donteval, defined, error);
4296 if (*error) return expr;
4297 FN_CHECK_END;
4298 if (!donteval) res->val = (v0.val/256 == v1.val/256);
4299 return expr;
4303 static const char *fnAlign (urasm_exprval_t *res, const char *expr, uint16_t addr, int donteval, int *defined, int *error) {
4304 urasm_exprval_t v0, v1;
4305 urasm_exprval_init(&v0);
4306 urasm_exprval_init(&v1);
4307 expr = urasm_expr_ex(&v0, expr, addr, &donteval, defined, error);
4308 if (*error) return expr;
4309 FN_SKIP_BLANKS;
4310 if (fnNextChar(expr) == ',') {
4311 FN_CHECK_COMMA;
4312 expr = urasm_expr_ex(&v1, expr, addr, &donteval, defined, error);
4313 if (*error) return expr;
4314 } else {
4315 v1.val = 256;
4317 FN_CHECK_END;
4318 if (!donteval) {
4319 if (v1.val < 1) { *error = UR_EXPRERR_FUNC; return NULL; }
4320 res->val = (v0.val+v1.val-1)/v1.val*v1.val;
4322 return expr;
4326 static const char *fnLow (urasm_exprval_t *res, const char *expr, uint16_t addr, int donteval, int *defined, int *error) {
4327 const char *ee = expr;
4328 expr = urasm_expr_ex(res, expr, addr, &donteval, defined, error);
4329 FN_CHECK_END;
4330 if (!donteval) {
4331 if (res->fixuptype == UR_FIXUP_HIBYTE) {
4332 *error = UR_EXPRERR_FUNC; return ee;
4334 res->fixuptype = UR_FIXUP_LOBYTE;
4335 res->val &= 0xff;
4337 return expr;
4341 static const char *fnHigh (urasm_exprval_t *res, const char *expr, uint16_t addr, int donteval, int *defined, int *error) {
4342 const char *ee = expr;
4343 expr = urasm_expr_ex(res, expr, addr, &donteval, defined, error);
4344 FN_CHECK_END;
4345 if (!donteval) {
4346 if (res->fixuptype == UR_FIXUP_LOBYTE) {
4347 *error = UR_EXPRERR_FUNC; return ee;
4349 res->fixuptype = UR_FIXUP_HIBYTE;
4350 res->val = (res->val>>8)&0xff;
4352 return expr;
4356 static const char *fnAbs (urasm_exprval_t *res, const char *expr, uint16_t addr, int donteval, int *defined, int *error) {
4357 const char *ee = expr;
4358 expr = urasm_expr_ex(res, expr, addr, &donteval, defined, error);
4359 FN_CHECK_END;
4360 if (!donteval) {
4361 if (res->fixuptype != UR_FIXUP_NONE) {
4362 *error = UR_EXPRERR_FUNC; return ee;
4364 res->val = abs(res->val);
4366 return expr;
4370 static const char *fnBSwap (urasm_exprval_t *res, const char *expr, uint16_t addr, int donteval, int *defined, int *error) {
4371 const char *ee = expr;
4372 expr = urasm_expr_ex(res, expr, addr, &donteval, defined, error);
4373 FN_CHECK_END;
4374 if (!donteval) {
4375 if (res->fixuptype != UR_FIXUP_NONE) {
4376 *error = UR_EXPRERR_FUNC; return ee;
4378 res->val = ((res->val>>8)&0xff)|((res->val&0xff)<<8);
4380 return expr;
4384 static const char *fnScrAddr8xn (int nmul, urasm_exprval_t *res, const char *expr, uint16_t addr, int donteval, int *defined, int *error) {
4385 int32_t scrbase = 0x4000;
4386 int32_t x, y;
4387 const char *ee = expr;
4388 //fprintf(stderr, "fnScrAddr8xn: n=%d; expr=<%s>\n", nmul, expr);
4389 // first argument is `x`
4390 expr = urasm_expr_ex(res, expr, addr, &donteval, defined, error);
4391 x = res->val;
4392 // second argument is `y`
4393 FN_CHECK_COMMA;
4394 expr = urasm_expr_ex(res, expr, addr, &donteval, defined, error);
4395 y = res->val*nmul;
4396 // optional third arg is screen base
4397 FN_SKIP_BLANKS;
4398 if (expr[0] == ',') {
4399 FN_CHECK_COMMA;
4400 expr = urasm_expr_ex(res, expr, addr, &donteval, defined, error);
4401 scrbase = res->val&0xffff;
4403 FN_CHECK_END;
4404 if (!donteval) {
4405 //urasm_exprval_clear(res);
4406 if (res->fixuptype != UR_FIXUP_NONE) { *error = UR_EXPRERR_FUNC; return ee; }
4407 if (x < 0 || x > 31) fatal("invalid x coordinate: %d", x);
4408 if (y < 0 || y > 191) fatal("invalid y coordinate: %d", y/nmul);
4409 res->val = scrbase+
4410 (y/64)*2048+
4411 (y%8)*256+
4412 ((y%64)/8)*32+
4414 //fprintf(stderr, "x=%d; y=%d; addr=#%04X\n", x, y, res->val);
4416 return expr;
4420 static const char *fnScrAddr8x1 (urasm_exprval_t *res, const char *expr, uint16_t addr, int donteval, int *defined, int *error) {
4421 return fnScrAddr8xn(1, res, expr, addr, donteval, defined, error);
4424 static const char *fnScrAddr8x8 (urasm_exprval_t *res, const char *expr, uint16_t addr, int donteval, int *defined, int *error) {
4425 return fnScrAddr8xn(8, res, expr, addr, donteval, defined, error);
4429 static const char *fnScrAttr8x8 (urasm_exprval_t *res, const char *expr, uint16_t addr, int donteval, int *defined, int *error) {
4430 int32_t scrbase = 0x4000;
4431 int32_t x, y;
4432 const char *ee = expr;
4433 // first argument is `x`
4434 expr = urasm_expr_ex(res, expr, addr, &donteval, defined, error);
4435 x = res->val;
4436 // second argument is `y`
4437 FN_CHECK_COMMA;
4438 urasm_exprval_clear(res);
4439 expr = urasm_expr_ex(res, expr, addr, &donteval, defined, error);
4440 y = res->val;
4441 // optional third arg is screen base
4442 FN_SKIP_BLANKS;
4443 if (expr[0] == ',') {
4444 FN_CHECK_COMMA;
4445 urasm_exprval_clear(res);
4446 expr = urasm_expr_ex(res, expr, addr, &donteval, defined, error);
4447 scrbase = res->val&0xffff;
4449 urasm_exprval_clear(res);
4450 FN_CHECK_END;
4451 if (!donteval) {
4452 if (res->fixuptype != UR_FIXUP_NONE) { *error = UR_EXPRERR_FUNC; return ee; }
4453 if (x < 0 || x > 31) fatal("invalid x coordinate: %d", x);
4454 if (y < 0 || y > 23) fatal("invalid y coordinate: %d", y);
4455 res->val = scrbase+6144+y*32+x;
4457 return expr;
4461 static const char *fnMArgToStr (urasm_exprval_t *res, const char *expr, uint16_t addr, int donteval, int *defined, int *error) {
4462 const char *ee = expr;
4463 // argument is macro argument name
4464 expr = strSkipSpacesConst(expr);
4465 if (expr[0] != '=') { *error = UR_EXPRERR_FUNC; return ee; }
4466 ++expr;
4467 if (!isAlpha(expr[0]) && expr[0] != '_') { *error = UR_EXPRERR_FUNC; return ee; }
4468 const char *nend = expr+1;
4469 while (isAlphaDigit(*nend) || *nend == '_') ++nend;
4470 if (nend-expr > 127) { *error = UR_EXPRERR_FUNC; return ee; }
4471 char name[128];
4472 memset(name, 0, sizeof(name));
4473 memcpy(name, expr, nend-expr);
4474 expr = nend;
4475 // parse index
4476 int index = 0;
4477 int has_index = 0;
4478 expr = strSkipSpacesConst(expr);
4479 if (expr[0] == '[') {
4480 expr = strSkipSpacesConst(expr+1);
4481 if (expr[0] != ']') {
4482 has_index = 1;
4483 int doneg = 0;
4484 if (expr[0] == '+') ++expr;
4485 else if (expr[0] == '-') { doneg = 1; ++expr; }
4486 index = 0;
4487 while (isDigit(expr[0])) {
4488 index = index*10+(expr[0]-'0');
4489 if (index >= 0x1fffffff) fatal("`margtostr` index too high");
4490 ++expr;
4492 expr = strSkipSpacesConst(expr);
4493 if (doneg) index = -index;
4495 if (expr[0] != ']') fatal("`margtostr` invalid index syntax");
4496 ++expr;
4498 // done
4499 FN_CHECK_END;
4500 if (curmacro == NULL) fatal("`margtostr` outside of macro");
4501 if (!donteval) {
4502 for (int f = 0; f < curmacro->mac->argc; ++f) {
4503 if (strcasecmp(name, curmacro->mac->argnames[f]) == 0) {
4504 // found argument, convert it to string
4505 if (!has_index) {
4506 res->str = realloc(res->str, strlen(curmacro->argvals[f])+1);
4507 strcpy(res->str, curmacro->argvals[f]);
4508 } else {
4509 const size_t slen = strlen(curmacro->argvals[f]);
4510 if (index < 0) {
4511 index = -index;
4512 if (index > slen) fatal("index out of string");
4513 index = (int)slen-index;
4515 if (index >= slen) fatal("index out of string");
4516 res->str = realloc(res->str, 2);
4517 res->str[0] = curmacro->argvals[f][index];
4518 res->str[1] = 0;
4520 // lowercase it
4521 for (char *s = res->str; *s; ++s) *s = toLower(*s);
4522 //fprintf(stderr, "<%s>\n", res->str);
4523 if (res->str[0]) {
4524 if (res->str[1]) {
4525 res->val = ((unsigned char)res->str[0]);
4526 res->val |= ((unsigned char)res->str[1])<<8;
4527 } else {
4528 res->val = (unsigned char)res->str[0];
4530 } else {
4531 res->val = 0;
4533 return expr;
4536 fatal("unknown macro argument '%s'", name);
4538 return expr;
4542 static const char *fnStrLen (urasm_exprval_t *res, const char *expr, uint16_t addr, int donteval, int *defined, int *error) {
4543 expr = strSkipSpacesConst(expr);
4544 int stlen = 0;
4545 switch (expr[0]) {
4546 case '"': case '\'': /* string literal? */
4548 char *a = (char *)expr+1;
4549 (void)parseStr(&a, expr[0], &stlen);
4550 expr = (const char *)a;
4552 break;
4553 default: /* expression */
4555 urasm_exprval_t r;
4556 urasm_exprval_init(&r);
4557 expr = urasm_expr_ex(&r, expr, disp, &donteval, defined, error);
4558 if (*error) fatalUrLib(*error);
4559 if (!r.str) fatal("string expected for `strlen()`");
4560 stlen = (int)strlen(r.str);
4561 urasm_exprval_clear(&r);
4563 break;
4565 FN_CHECK_END;
4566 if (!donteval) {
4567 urasm_exprval_setint(res, stlen);
4569 return expr;
4573 static void registerFunctions (void) {
4574 urasm_expr_register_func("defined", fnDefined);
4575 urasm_expr_register_func("known", fnKnown);
4576 urasm_expr_register_func("aligned256", fnAligned256);
4577 urasm_expr_register_func("align", fnAlign);
4578 urasm_expr_register_func("sameseg", fnSameSeg);
4579 urasm_expr_register_func("low", fnLow);
4580 urasm_expr_register_func("high", fnHigh);
4581 urasm_expr_register_func("abs", fnAbs);
4582 urasm_expr_register_func("bswap", fnBSwap);
4584 urasm_expr_register_func("scraddr8x8", fnScrAddr8x8);
4585 urasm_expr_register_func("scraddr8x1", fnScrAddr8x1);
4586 urasm_expr_register_func("scrattr8x8", fnScrAttr8x8);
4588 urasm_expr_register_func("marg2str", fnMArgToStr);
4589 urasm_expr_register_func("strlen", fnStrLen);
4593 ///////////////////////////////////////////////////////////////////////////////
4594 // preparing another pass
4596 static void initPass (void) {
4597 curSrcLine = asmText;
4598 curModule = NULL;
4599 pc = start_pc;
4600 disp = start_disp;
4601 ent = start_ent;
4602 inTapeBlock = 0;
4603 tapeXorB = 0;
4604 wasOrg = 0;
4605 wasClr = 0;
4606 ifCount = 0;
4607 defIncr = 0;
4608 lblOptMakeU2 = 0;
4609 curmacronum = 0;
4610 lastSeenGlobalLabel = strdup(" [MAIN] ");
4611 modulesResetSeen();
4612 prepareMemory();
4613 urasm_use_old_priorities = 0;
4614 forthLatest = 0;
4615 forthCurrWord = NULL;
4619 static int posstPass (void) {
4620 if (lastSeenGlobalLabel) free(lastSeenGlobalLabel);
4621 lastSeenGlobalLabel = NULL;
4622 if (checkLabels()) return -1;
4623 if (ifCount != 0) fatal("unbalanced IFs");
4624 if (forthCurrWord) fatal("unfinished forth word `%s`", forthCurrWord);
4625 return 0;
4629 ///////////////////////////////////////////////////////////////////////////////
4630 static int labelCmp (const void *aa, const void *bb) {
4631 if (aa == bb) return 0;
4632 const UrLabelInfo *a = *(const UrLabelInfo **)aa;
4633 const UrLabelInfo *b = *(const UrLabelInfo **)bb;
4634 return
4635 a->value < b->value ? -1 :
4636 a->value > b->value ? 1 :
4641 static void writeLabelsFile (const char *fname) {
4642 if (!fname || !fname[0]) return;
4643 // count labels
4644 int lcount = 0;
4645 for (const UrLabelInfo *ll = labels; ll; ll = ll->next) ++lcount;
4646 UrLabelInfo **larr = NULL;
4647 if (lcount > 0) {
4648 // create labels array
4649 larr = (UrLabelInfo **)malloc(sizeof(UrLabelInfo *)*lcount);
4650 lcount = 0;
4651 for (UrLabelInfo *ll = labels; ll; ll = ll->next, ++lcount) larr[lcount] = ll;
4652 // sort labels
4653 qsort(larr, lcount, sizeof(UrLabelInfo *), &labelCmp);
4655 // write labels
4656 FILE *fo = fopen(fname, "w");
4657 if (lcount > 0) {
4658 for (int f = 0; f < lcount; ++f) {
4659 UrLabelInfo *ll = larr[f];
4660 if (!ll->name || (!isAlpha(ll->name[0]) && ll->name[0] != '@')) continue;
4661 if (ll->value < 0) {
4662 fprintf(fo, "%d %s\n", ll->value, ll->name);
4663 } else {
4664 fprintf(fo, "#%04X %s\n", (unsigned)ll->value, ll->name);
4667 free(larr);
4669 // write forth words
4670 if (forthWordList) {
4671 fprintf(fo, "\n");
4672 for (ForthWord *w = forthWordList; w; w = w->next) {
4673 fprintf(fo, "#%04X %s\n", w->cfa, w->name);
4676 fclose(fo);
4680 ///////////////////////////////////////////////////////////////////////////////
4681 // options
4683 static struct option longOpts[] = {
4684 {"org", required_argument, NULL, 600},
4685 {"define", required_argument, NULL, 601},
4686 {"defzero", required_argument, NULL, 602},
4687 {"outdir", required_argument, NULL, 660},
4688 {"reffile", optional_argument, NULL, 669},
4690 {"sna", 0, NULL, 's'},
4691 {"sna128", 0, NULL, 'S'},
4692 {"tap", 0, NULL, 't'},
4693 {"autotap", 0, NULL, 'T'},
4694 {"raw", 0, NULL, 'r'},
4695 {"autodmb", 0, NULL, 'B'},
4696 {"dmb", 0, NULL, 'b'},
4697 {"none", 0, NULL, 'n'},
4698 {"help", 0, NULL, 'h'},
4699 {"hob", 0, NULL, 'H'},
4700 {"scl", 0, NULL, 'l'},
4701 {"autoscl", 0, NULL, 'L'},
4702 {"fixups", optional_argument, NULL, 'F'},
4704 {NULL, 0, NULL, 0}
4708 static const char *defInFiles[] = {"main.zas", "main.a80", "main.asm", NULL};
4711 static void usage (const char *pname) {
4712 printf(
4713 "usage: %s [options] infile\n"
4714 "default infiles:", pname);
4715 for (int f = 0; defInFiles[f]; ++f) printf(" %s", defInFiles[f]);
4716 printf("\n"
4717 "options:\n"
4718 " -s --sna write 48K .SNA file with autostart\n"
4719 " -S --sna128 write 148K .SNA file with autostart\n"
4720 " -t --tap write .tap file\n"
4721 " -T --autotap write .tap file with basic loader (CLR to set CLEAR)\n"
4722 " -r --raw write raw file(s)\n"
4723 " -b --dmb write DMB file\n"
4724 " -B --autodmb write DMB file with autostart\n"
4725 " -H --hob write HoBeta code file(s)\n"
4726 " -l --scl write SCL TR-DOS archive\n"
4727 " -L --autoscl write autostarting SCL TR-DOS archive\n"
4728 " -n --none write nothing\n"
4729 " -F --fixups write fixup file 'zfixuptable.EXT'\n"
4730 " optional arg: text (default), asm/zas, asmdiff/zasdiff, asmz/zasz\n"
4731 " text: .txt file\n"
4732 " asm: address lists with counters\n"
4733 " asmdiff: 2nd and other addresses are relative, with counters\n"
4734 " asmz: address lists, with 0 end markers\n"
4735 " -h --help this help\n"
4736 "specials:\n"
4737 " --reffile[=name] write labels to reffile, formatted as \"#nnnn NAME\"\n"
4738 " --org xxx set ORG\n"
4739 " --define val perform 'val EQU 1'\n"
4740 " --defzero val perform 'val EQU 0'\n"
4741 " --outdir dir output dir for resulting files (default: current)\n"
4746 ///////////////////////////////////////////////////////////////////////////////
4747 // main
4749 int main (int argc, char *argv[]) {
4750 int res = 0, c;
4751 const char *pname = argv[0];
4752 char *inFile = NULL;
4753 char **defines = NULL, **values = NULL;
4754 int defcount = 0;
4755 int wantref = 0;
4757 initInclideDir();
4759 urasm_getbyte = getByte;
4760 urasm_putbyte = putByte;
4761 urasm_label_by_name = findLabelCB;
4762 urasm_getval = getValueCB;
4763 urasm_expand = expandCB;
4764 urasm_fixup_operand = fixupOperandCB;
4766 //strcpy(tapeLoaderName, "cargador ");
4767 tapeLoaderName[0] = 0;
4769 printf("urasm v%d.%d.%d, compile date: %s %s\n", VERSION_HI, VERSION_MID, VERSION_LO, __DATE__, __TIME__);
4770 while ((c = getopt_long(argc, argv, "sStTrBbnhHFLl", longOpts, NULL)) >= 0) {
4771 switch (c) {
4772 case '?': return 1;
4773 case 'S': /*optRunSNA = 1;*/ optSNA48 = 0; optWriteType = 's'; optWTChanged = 1; break;
4774 case 's': /*optRunSNA = 0;*/ optSNA48 = 1; optWriteType = 's'; optWTChanged = 1; break;
4775 case 'T': optRunTape = 1; optWriteType = 't'; optWTChanged = 1; break;
4776 case 't': optRunTape = 0; optWriteType = 't'; optWTChanged = 1; break;
4777 case 'L': optRunSCL = 1; optWriteType = 'S'; optWTChanged = 1; break; /* with boot */
4778 case 'l': optRunSCL = -1; optWriteType = 'S'; optWTChanged = 1; break; /* no boot */
4779 case 'r': case 'n': optWriteType = c; optWTChanged = 1; break;
4780 case 'b': optRunTape = 0; optRunDMB = 0; optWriteType = 'd'; optWTChanged = 1; break;
4781 case 'B': optRunTape = 0; optRunDMB = 1; optWriteType = 'd'; optWTChanged = 1; break;
4782 case 'h': usage(pname); res = 0; goto earlyerrquit;
4783 case 'H': optWriteType = 'H'; optWTChanged = 1; break;
4784 case 'F':
4785 optWriteFixups = 1;
4786 if (optarg != NULL) {
4787 if (strcmp(optarg, "asm") == 0 || strcmp(optarg, "zas") == 0) {
4788 optFixupType = 1;
4789 } else if (strcmp(optarg, "asmdiff") == 0 || strcmp(optarg, "zasdiff") == 0) {
4790 optFixupType = 2;
4791 } else if (strcmp(optarg, "asmz") == 0 || strcmp(optarg, "zasz") == 0) {
4792 optFixupType = 3;
4793 } else if (strcmp(optarg, "text") == 0) {
4794 optFixupType = 0;
4795 } else {
4796 fprintf(stderr, "FATAL: invalid fixup type: '%s'\n", optarg);
4797 return 1;
4800 break;
4801 case 600: // org
4802 c = atoi(optarg);
4803 //fprintf(stderr, "ORG: %d\n", c);
4804 if (c < 0 || c > 65535) {
4805 fprintf(stderr, "FATAL: invalid ORG: %d\n", c);
4806 return 1;
4808 start_pc = start_disp = start_ent = c;
4809 break;
4810 case 601: // define
4811 //fprintf(stderr, "define: [%s]\n", optarg);
4812 defines = realloc(defines, sizeof(char *)*(defcount+1));
4813 values = realloc(values, sizeof(char *)*(defcount+1));
4814 defines[defcount] = strdup(optarg);
4815 values[defcount] = strdup("1");
4816 ++defcount;
4817 break;
4818 case 602: // defzero
4819 //fprintf(stderr, "defzero: [%s]\n", optarg);
4820 defines = realloc(defines, sizeof(char *)*(defcount+1));
4821 values = realloc(values, sizeof(char *)*(defcount+1));
4822 defines[defcount] = strdup(optarg);
4823 values[defcount] = strdup("0");
4824 ++defcount;
4825 break;
4826 case 660: // outdir
4827 if (optOutputDir != NULL) free(optOutputDir);
4828 optOutputDir = strdup(optarg);
4829 break;
4830 case 669: // reffile
4831 if (refFileName) free(refFileName);
4832 refFileName = (optarg ? strdup(optarg) : NULL);
4833 wantref = 1;
4834 break;
4838 if (optind >= argc) {
4839 // try to find default input file
4840 for (int f = 0; defInFiles[f]; ++f) {
4841 if (!access(defInFiles[f], R_OK)) {
4842 inFile = strdup(defInFiles[f]);
4843 break;
4846 } else {
4847 inFile = strdup(argv[optind]);
4850 if (!inFile || !inFile[0]) {
4851 res = 1;
4852 fprintf(stderr, "ERROR: no input file!\n");
4853 goto earlyerrquit;
4856 if (optOutputDir == NULL) optOutputDir = strdup(".");
4858 registerInstructions();
4859 registerFunctions();
4861 res = asmTextLoad(inFile, 0);
4862 if (!res) {
4863 for (int f = 0; f < defcount; ++f) {
4864 if (labelDoEQU(defines[f], values[f]) != 0) {
4865 fprintf(stderr, "FATAL: can't define label: '%s'\n", defines[f]);
4866 goto errquit;
4870 for (pass = 0; pass <= 1; ++pass) {
4871 initPass();
4872 printf("pass %d\n", pass);
4873 setCurSrcLine(asmText);
4874 if (setjmp(errJP)) { res = 1; break; }
4875 while (curSrcLine) processCurrentLine();
4876 if (posstPass()) { res = 1; break; }
4879 // write result
4880 if (res == 0) {
4881 char *oc = strdup(inFile);
4882 char *pd = strrchr(oc, '.');
4883 if (pd && !strchr(oc, '/')) *pd = '\0';
4884 switch (optWriteType) {
4885 case 's': saveSna(oc, optSNA48); break;
4886 case 't': saveTap(oc); break;
4887 case 'r': saveRaw(oc); break;
4888 case 'd': saveDMB(oc); break;
4889 case 'H': saveHob(oc); break;
4890 case 'S': saveSCL(oc); break;
4892 free(oc);
4893 if (optWriteFixups) writeFixups();
4894 if (wantref) {
4895 /* build ref file name */
4896 if (!refFileName || !refFileName[0]) {
4897 if (refFileName) free(refFileName);
4898 refFileName = malloc(strlen(inFile)+128);
4899 strcpy(refFileName, inFile);
4900 char *ext = strrchr(refFileName, '.');
4901 if (!ext) {
4902 strcat(refFileName, ".ref");
4903 } else {
4904 #ifdef WIN32
4905 char *slash = NULL;
4906 for (char *ts = refFileName; *ts; ++ts) {
4907 if (*ts == '/' || *ts == '\\') slash = ts;
4909 #else
4910 char *slash = strrchr(refFileName, '/');
4911 #endif
4912 if (!slash || slash < ext) {
4913 strcpy(ext, ".ref");
4914 } else {
4915 strcat(refFileName, ".ref");
4919 writeLabelsFile(refFileName);
4920 printf("refs written to '%s'\n", refFileName);
4921 free(refFileName);
4924 } else {
4925 fprintf(stderr, "ERROR: loading error!\n");
4928 errquit:
4929 if (lastSeenGlobalLabel) free(lastSeenGlobalLabel);
4930 clearFixups();
4931 urClearLabels();
4932 modulesClear();
4933 asmTextClear();
4934 urClearOps();
4936 earlyerrquit:
4937 if (inFile) free(inFile);
4938 if (sysIncludeDir) free(sysIncludeDir);
4939 for (int f = defcount-1; f >= 0; --f) {
4940 free(values[f]);
4941 free(defines[f]);
4943 if (defines != NULL) { free(values); free(defines); }
4944 if (optOutputDir != NULL) free(optOutputDir);
4945 return (res ? 1 : 0);