added credits to 'marsaglia' rnd
[urasm.git] / src / urasm.c
blobf1b30e92de3270ce3afccfc00d83b13a3504bdcb
1 // URASM Z80 assembler
2 // coded by Ketmar // Vamprile Avalon
3 // GPLv3 or later
4 //
5 #include <ctype.h>
6 #include <getopt.h>
7 #include <setjmp.h>
8 #include <stdarg.h>
9 #include <stdint.h>
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <unistd.h>
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <unistd.h>
19 #ifdef _WIN32
20 # include <windows.h>
21 #endif
23 #include "urasmlib/urasmlib.h"
24 #include "ursna48.h"
27 #define VERSION_HI 0
28 #define VERSION_MID 1
29 #define VERSION_LO 0
32 #define MAYBE_UNUSED __attribute__((unused))
34 ///////////////////////////////////////////////////////////////////////////////
35 // global variables
37 static char *sysIncludeDir = NULL;
39 #define MAX_LINE_SIZE 16384
40 static char curLine[MAX_LINE_SIZE]; /* current processing line; can be freely modified */
43 ///////////////////////////////////////////////////////////////////////////////
44 // init dirs
46 static void initInclideDir (void) {
47 const char *id = getenv("URASM_INCLUDE_DIR");
48 if (id && id[0]) {
49 sysIncludeDir = strdup(id);
50 } else {
51 char buf[128], myDir[4096];
52 #ifndef _WIN32
53 pid_t pid = getpid();
54 sprintf(buf, "/proc/%u/exe", (unsigned int)pid);
55 memset(myDir, 0, sizeof(myDir));
56 if (readlink(buf, myDir, sizeof(myDir)-1) < 0) strcpy(myDir, ".");
57 else {
58 char *p = (char *)strrchr(myDir, '/');
59 if (!p) strcpy(myDir, "."); else *p = '\0';
61 strcat(myDir, "/libs");
62 #else
63 char *p;
64 memset(myDir, 0, sizeof(myDir));
65 GetModuleFileName(GetModuleHandle(NULL), myDir, sizeof(myDir)-1);
66 p = strrchr(myDir, '\\');
67 if (!p) strcpy(myDir, "."); else *p = '\0';
68 strcat(myDir, "\\libs");
69 #endif
70 sysIncludeDir = strdup(myDir);
72 while (sysIncludeDir[0] && sysIncludeDir[strlen(sysIncludeDir)-1] == '/') sysIncludeDir[strlen(sysIncludeDir)-1] = '\0';
73 if (!sysIncludeDir[0]) strcpy(sysIncludeDir, ".");
77 ///////////////////////////////////////////////////////////////////////////////
78 // string utilities
80 /*TODO: expression parser should understant escapes in strings!*/
81 /* trim trailing spaces and comments */
82 static void normalizeStr (char *s) {
83 char *p, inQ = 0;
84 int f;
85 // now skip all shit
86 for (p = s; *p; ++p) {
87 if (inQ) {
88 // inside the string
89 if (*p == '\\') {
90 switch (*(++p)) {
91 case 'x': case 'X': // hex code
92 ++p; // skip 'x'
93 for (f = 0; f < 2; ++f) if (!isxdigit(*p)) break;
94 --p; // last digit will be skiped by 'for'
95 break;
96 case '0': // octal code
97 for (f = 0; f < 4; ++f) if (!isdigit(*p) || *p > '7') break;
98 --p; // last digit will be skiped by 'for'
99 break;
100 case '1' ... '9': // decimal code
101 for (f = 0; f < 3; ++f) if (!isdigit(*p)) break;
102 --p; // last digit will be skiped by 'for'
103 break;
104 default: ; // other escapes, do nothing
106 } else {
107 if (*p == inQ) inQ = 0;
109 continue;
111 // outside the string
112 switch (*p) {
113 case '"': case '\'': // string catched
114 inQ = *p;
115 break;
116 case ';': // end of line, comment follows
117 *p-- = '\0'; // strip it and quit
118 break;
119 default: ; // do nothing
122 // trim right
123 for (p = s+strlen(s)-1; p >= s && isspace(*p); --p) ;
124 p[1] = '\0';
128 /* returns NULL or pointer to args */
129 /* skips spaces after command if any */
130 static char *strIsCommand (const char *command, char *str) {
131 int cnt;
133 for (cnt = 1; cnt > 0; --cnt) {
134 while (*str && isspace(*str)) ++str; // skip spaces
135 for (; *command && *str; ++command, ++str) {
136 if (toupper(*command) != toupper(*str)) return NULL; // alas
138 if (*command) return NULL; // alas
139 if (*str && isalnum(*str)) return NULL; // alas
140 while (*str && isspace(*str)) ++str; // skip spaces
141 if (*str && *str == ':') break; // try again if we have a colon
142 return str; // found
144 return NULL;
148 /* don't free() result */
149 /* skips trailing spaces */
150 static char *parseStr (char **str, char endQ, int *lenp) {
151 static char buf[MAX_LINE_SIZE];
152 int len = 0, n, f, base;
153 char *a = *str;
155 int xDigit (char ch, int base) {
156 if (ch < '0') return -1;
157 if (base <= 10) {
158 if (ch >= '0'+base) return -1;
159 return ch-'0';
161 ch = toupper(ch);
162 if (ch <= '9') return ch-'0';
163 if (ch < 'A' || ch > 'A'+base-10) return -1;
164 ch -= 'A'-10;
165 return ch<base ? ch : -1;
168 memset(buf, 0, sizeof(buf));
169 if (lenp) *lenp = 0;
170 for (; *a; ++a) {
171 if (*a == '\\') {
172 if (!a[1]) break;
173 switch (*(++a)) {
174 case 'a': buf[len++] = '\a'; break;
175 case 'b': buf[len++] = '\b'; break;
176 case 'f': buf[len++] = '\f'; break;
177 case 'n': buf[len++] = '\n'; break;
178 case 'r': buf[len++] = '\r'; break;
179 case 't': buf[len++] = '\t'; break;
180 case 'v': buf[len++] = '\v'; break;
181 case 'z': buf[len++] = '\0'; break;
182 case 'x': case 'X': // hex
183 ++a; // skip 'x'
184 base = 16; f = 2;
185 donum: for (n = 0; f > 0; --f) {
186 char ch = xDigit(*a++, base);
187 if (ch < 0) { --a; break; }
188 n *= base;
189 n += ch;
191 buf[len++] = n;
192 --a; // return to the last digit, 'for' will skip it
193 break;
194 case '0': // octal
195 base = 8; f = 4;
196 goto donum;
197 case '1' ... '9': // decimal
198 base = 10; f = 3;
199 goto donum;
200 default: buf[len++] = a[0]; break; // others
202 } else {
203 if (*a == endQ) { ++a; break; }
204 buf[len++] = *a;
207 while (*a && isspace(*a)) ++a; // skip trailing spaces
208 *str = a;
209 buf[len] = '\0';
210 if (lenp) *lenp = len;
211 return buf;
215 ///////////////////////////////////////////////////////////////////////////////
216 // source file stack, reader, etc
218 typedef struct SourceLine SourceLine;
219 struct SourceLine {
220 SourceLine *next;
221 char *line;
222 char *fname;
223 int lineNo;
226 static SourceLine *asmText = NULL;
227 static SourceLine *asmTextLast = NULL;
228 static SourceLine *curSrcLine = NULL;
231 static void asmTextClear (void) {
232 while (asmText) {
233 SourceLine *l = asmText;
234 asmText = asmText->next;
235 free(l->line);
236 free(l->fname);
237 free(l);
239 asmTextLast = curSrcLine = NULL;
243 static void normIncName (char *dest, const char *fn, int system) {
244 struct stat st;
246 if (system) sprintf(dest, "%s/%s", sysIncludeDir, fn); else sprintf(dest, "%s", fn);
247 if (stat(dest, &st)) return;
248 if (S_ISDIR(st.st_mode)) strcat(dest, "/zzmain.zas");
252 static int asmTextLoad (const char *fname) {
253 FILE *fl;
254 int lineNo = 0;
255 char *args;
256 SourceLine *s;
257 static int includeCount = 0;
259 if (!(fl = fopen(fname, "r"))) {
260 fprintf(stderr, "ERROR: can't open file: %s\n", fname);
261 return -1;
263 printf("loading: %s\n", fname);
264 // read file
265 while (fgets(curLine, sizeof(curLine)-1, fl)) {
266 ++lineNo;
267 curLine[sizeof(curLine)-1] = '\0';
268 normalizeStr(curLine);
269 if (!curLine[0]) continue; // don't store empty lines
270 // find specials, if any
271 if (isspace(curLine[0])) {
272 if ((args = strIsCommand("INCLUDE", curLine)) != NULL) {
273 // process 'INCLUDE'
274 int system = 0;
275 char qCh = 0;
277 if (!args[0]) {
278 fclose(fl);
279 fprintf(stderr, "ERROR: file %s, line %d: invalid INCLUDE!\n", fname, lineNo);
280 return -1;
282 if (args[0] == '"' || args[0] == '\'') qCh = *args++;
283 else if (args[0] == '<') { qCh = '>'; ++args; system = 1; }
284 char *fn = parseStr(&args, qCh, NULL); // i don't care about string length here
285 if (!fn[0]) {
286 fclose(fl);
287 fprintf(stderr, "ERROR: file %s, line %d: can't INCLUDE nothing!\n", fname, lineNo);
288 return -1;
290 if (args[0]) {
291 fclose(fl);
292 fprintf(stderr, "ERROR: file %s, line %d: extra args after INCLUDE filename!\n", fname, lineNo);
293 return -1;
295 if (includeCount > 256) {
296 fclose(fl);
297 fprintf(stderr, "ERROR: file %s, line %d: too many nested INCLUDEs!\n", fname, lineNo);
298 return -1;
300 normIncName(curLine, fn, system);
301 //if (system) sprintf(curLine, "%s/%s", sysIncludeDir, fn); else sprintf(curLine, "%s", fn);
302 fn = strdup(curLine); if (!fn) abort();
303 ++includeCount;
304 system = asmTextLoad(fn);
305 --includeCount;
306 free(fn);
307 if (system) { fclose(fl); return system; }
308 continue;
311 // add current line
312 s = calloc(1, sizeof(SourceLine));
313 if (!s) abort();
314 s->lineNo = lineNo;
315 s->line = strdup(curLine); if (!s->line) abort();
316 s->fname = strdup(fname); if (!s->fname) abort();
317 if (asmTextLast) asmTextLast->next = s; else asmText = s;
318 asmTextLast = s;
320 fclose(fl);
321 return 0;
325 static inline void loadCurSrcLine (void) { if (curSrcLine) strcpy(curLine, curSrcLine?curSrcLine->line:""); }
326 static inline SourceLine *setCurSrcLine (SourceLine *l) { curSrcLine = l; loadCurSrcLine(); return l; }
327 static inline SourceLine *nextSrcLine (void) { return curSrcLine ? setCurSrcLine(curSrcLine->next) : NULL; }
330 ///////////////////////////////////////////////////////////////////////////////
331 // prototypes
333 static void processCurrentLine (void); // only one, will skip to next one
336 ///////////////////////////////////////////////////////////////////////////////
337 // error raisers, etc
339 static jmp_buf errJP;
342 static void errorWriteFile (void) {
343 if (curSrcLine) {
344 fprintf(stderr, "at file %s, line %d\n%s\n*", curSrcLine->fname, curSrcLine->lineNo, curSrcLine->line);
345 } else {
346 fprintf(stderr, "somewhere in time: ");
350 static void errorMsgV (const char *fmt, va_list ap) {
351 errorWriteFile();
352 vfprintf(stderr, fmt, ap);
353 va_end(ap);
354 fputc('\n', stderr);
355 fflush(stderr);
359 static void __attribute__((format(printf, 1, 2))) warningMsg (const char *fmt, ...) {
360 va_list ap;
361 fprintf(stderr, "WARNING ");
362 va_start(ap, fmt);
363 errorMsgV(fmt, ap);
367 static void __attribute__((format(printf, 1, 2))) errorMsg (const char *fmt, ...) {
368 va_list ap;
369 fprintf(stderr, "FATAL ");
370 va_start(ap, fmt);
371 errorMsgV(fmt, ap);
375 static void __attribute__((noreturn)) __attribute__((format(printf, 1, 2))) fatal (const char *fmt, ...) {
376 va_list ap;
377 va_start(ap, fmt);
378 errorMsgV(fmt, ap);
379 longjmp(errJP, 666);
383 static void __attribute__((noreturn)) fatalUrLib (int errcode) {
384 errorMsg("%s", urErrorMessage(errcode));
385 longjmp(errJP, 666);
389 //////////////////////////////////////////////////////////////////////////////
390 // operator management
392 typedef void (*UrAsmOpFn) (void);
394 typedef struct UrAsmOp UrAsmOp;
395 struct UrAsmOp {
396 char *name;
397 UrAsmOpFn fn;
398 UrAsmOp *next;
401 static UrAsmOp *oplist = NULL;
404 static UrAsmOp *urAddOp (const char *name, UrAsmOpFn fn) {
405 UrAsmOp *res = calloc(1, sizeof(UrAsmOp));
406 if (!res) abort();
407 res->name = strdup(name);
408 res->fn = fn;
409 res->next = oplist;
410 oplist = res;
411 return res;
415 static UrAsmOp *urFindOp (const char *name) {
416 UrAsmOp *res;
417 for (res = oplist; res; res = res->next) if (!strcasecmp(name, res->name)) break;
418 return res;
422 static void urClearOps (void) {
423 UrAsmOp *c;
424 while (oplist) {
425 c = oplist; oplist = oplist->next;
426 free(c->name);
427 free(c);
432 ///////////////////////////////////////////////////////////////////////////////
433 // label management
435 typedef struct UrLabelInfo UrLabelInfo;
436 struct UrLabelInfo {
437 char *name;
438 int32_t value;
439 int type; /* -1: uknown (forward reference); 0: =; 1: equ; 2: code */
440 int known; /* !0: label value already known */
441 int refLine; /* first referenced line */
442 char *refFile;
443 UrLabelInfo *next;
446 static UrLabelInfo *labels = NULL;
448 static void urClearLabels (void) {
449 UrLabelInfo *c;
450 while ((c = labels)) {
451 labels = labels->next;
452 if (c->name) free(c->name);
453 if (c->refFile) free(c->refFile);
454 free(c);
459 static UrLabelInfo *urFindLabel (const char *name) {
460 UrLabelInfo *c;
461 for (c = labels; c; c = c->next) if (!strcmp(name, c->name)) break;
462 return c;
466 static UrLabelInfo *urAddLabel (const char *name) {
467 UrLabelInfo *c = urFindLabel(name);
468 if (!c) {
469 UrLabelInfo *p;
470 for (p = NULL, c = labels; c; p = c, c = c->next) ;
471 c = calloc(1, sizeof(UrLabelInfo));
472 if (!c) abort();
473 c->name = strdup(name);
474 c->type = -1;
475 if (p) p->next = c; else labels = c;
476 c->next = NULL;
478 return c;
482 ///////////////////////////////////////////////////////////////////////////////
483 // module list management
485 typedef struct ModuleInfo ModuleInfo;
486 struct ModuleInfo {
487 char *name;
488 char *fname; // opened in this file
489 int seen; // !0: module already seen, skip other definitions from the same file
490 ModuleInfo *next;
492 static ModuleInfo *modules = NULL;
493 static ModuleInfo *curModule = NULL;
496 static void modulesClear (void) {
497 curModule = NULL;
498 while (modules) {
499 ModuleInfo *c = modules;
500 modules = modules->next;
501 free(c->name);
502 free(c->fname);
503 free(c);
508 static void modulesResetSeen (void) {
509 ModuleInfo *c;
510 for (c = modules; c; c = c->next) c->seen = 0;
514 static ModuleInfo *moduleFind (const char *name) {
515 ModuleInfo *c;
516 if (!name || !name[0]) return NULL;
517 for (c = modules; c; c = c->next) if (!strcmp(c->name, name)) break;
518 return c;
522 static ModuleInfo *moduleAdd (const char *name, const char *fname) {
523 ModuleInfo *c;
524 if (!name || !fname || !name[0] || !fname[0]) abort();
525 c = calloc(1, sizeof(ModuleInfo));
526 if (!c) abort();
527 c->name = strdup(name); if (!c->name) abort();
528 c->fname = strdup(fname); if (!c->fname) abort();
529 c->next = modules;
530 modules = c;
531 return c;
535 ///////////////////////////////////////////////////////////////////////////////
536 // destination memory management
538 static uint8_t memory[65536];
539 static char memused[65536];
540 static char memresv[65536];
541 static uint16_t pc = 0; /* current position to write */
542 static uint16_t disp = 0; /* current 'virtual PC' */
543 static uint16_t ent = 0; /* eng */
544 static uint16_t clrAddr = 24999; /* address for CLEAR for cargador */
545 static int pass = 0; /* 0: collect labels; 1: compiling; 2: compiling and fixing labels */
546 static int inTapeBlock = 0;
547 static uint8_t tapeXorB = 0;
550 static uint8_t getByte (uint16_t addr) {
551 return memory[addr];
555 static __attribute__((unused)) uint16_t getWord (uint16_t addr) {
556 return ((uint16_t)memory[addr])|(((uint16_t)memory[addr+1])<<8);
560 static void putByte (uint16_t addr, uint8_t b) {
561 if (inTapeBlock) tapeXorB ^= b;
562 memory[addr] = b;
563 memused[addr] = 1;
567 static MAYBE_UNUSED void putWord (uint16_t addr, uint16_t w) {
568 putByte(addr, w&0xFFU);
569 putByte(addr+1, (w>>8)&0xFFU);
573 static void emitByte (uint8_t b) {
574 putByte(pc, b);
575 ++pc; ++disp;
579 static void emitWord (uint16_t w) {
580 emitByte(w&0xFFU);
581 emitByte((w>>8)&0xFFU);
585 static void emitRWord (uint16_t w) {
586 emitByte((w>>8)&0xFFU);
587 emitByte(w&0xFFU);
591 static void prepareMemory (void) {
592 memset(memory, 0, sizeof(memory));
593 memset(memused, 0, sizeof(memused));
594 memset(memresv, 0, sizeof(memresv));
598 ///////////////////////////////////////////////////////////////////////////////
599 // label getter and utilities
601 static char *lastSeenGlobalLabel = NULL; /* global */
604 static char *fixLocalLabel (const char *name) {
605 static char newname[MAX_LINE_SIZE*2+1024];
607 memset(newname, 0, sizeof(newname));
608 if (!name || !name[0]) newname[0] = '\0';
609 else if (!lastSeenGlobalLabel || name[0] != '.') strcpy(newname, name);
610 else {
611 // this is local label, let's rename it
612 sprintf(newname, "{%s%s}", lastSeenGlobalLabel, name);
614 return newname;
618 static char *fixGlobalLabel (const char *name) {
619 static char newname[MAX_LINE_SIZE*2+1024];
621 memset(newname, 0, sizeof(newname));
622 if (!name || !name[0]) newname[0] = '\0';
623 else if (!curModule || name[0] == '.' || name[0] == '{' || name[0] == '@' || strchr(name, '.')) {
624 if (name[0] == '@' && name[1]) ++name;
625 strcpy(newname, name);
626 } else {
627 // this is global unqualified label and we have a module; let's rename it
628 sprintf(newname, "%s.%s", curModule->name, name);
630 //printf("%s --> %s\n", name, newname);
631 return newname;
635 static int lblOptMakeU2 = 0;
636 static int32_t findLabelCB (const char *name, uint16_t addr, int *defined, int *found) {
637 UrLabelInfo *lbl;
638 char *ln, *nn;
640 nn = fixGlobalLabel((ln = fixLocalLabel(name)));
641 lbl = urFindLabel(nn);
642 if (!lbl) {
643 // try non-module label
644 lbl = urFindLabel(ln);
646 if (!lbl) {
647 if (pass != 0) {
648 errorMsg("using undefined label %s", ln);
649 *found = 0;
650 *defined = 0;
651 return 0;
653 lbl = urAddLabel(nn);
654 lbl->type = lblOptMakeU2 ? -42 : -1;
655 lbl->known = 0;
656 lbl->refLine = curSrcLine->lineNo;
657 lbl->refFile = strdup(curSrcLine->fname);
658 //printf("new label: [%s]\n", lbl->name);
659 } else {
660 //printf("label reference: [%s]\n", lbl->name);
662 if (lbl) {
663 *found = 1;
664 *defined = lbl->known!=0;
665 return lbl->value;
667 *found = 0;
668 *defined = 0;
669 return 0;
673 static int isLabelDefinedOrKnown (const char *name, uint16_t addr, int qtype) {
674 UrLabelInfo *lbl;
675 char *ln, *nn;
677 nn = fixGlobalLabel((ln = fixLocalLabel(name)));
678 lbl = urFindLabel(nn);
679 if (!lbl) {
680 // try non-module label
681 lbl = urFindLabel(ln);
683 switch (qtype) {
684 case UR_QTYPE_DEFINED: return lbl ? lbl->known!=0 : 0;
685 case UR_QTYPE_KNOWN: return lbl!=NULL;
686 default: ;
688 return 0;
692 static int checkLabels (void) {
693 UrLabelInfo *c;
694 int wasError = 0;
696 for (c = labels; c; c = c->next) {
697 if (c->type == -1) {
698 fprintf(stderr, "ERROR at file %s, line %d: referencing undefined label: %s\n", c->refFile, c->refLine, c->name);
699 wasError = 1;
701 if (c->type == 0) c->known = -1;
703 //if (wasError) longjmp(errJP, 667);
704 return wasError;
708 ///////////////////////////////////////////////////////////////////////////////
709 // expression utils
711 static int32_t getExprArg (int *defined) {
712 int error = 0;
713 char *a = curLine;
714 int32_t res;
716 while (*a && isspace(*a)) ++a;
717 if (!a[0]) fatal("expression expected");
718 const char *ee = urExpression(&res, a, disp, defined, &error);
719 if (error) fatalUrLib(error);
720 if (*ee) {
721 if (ee[0] != ',') fatal("bad expression");
722 memmove(curLine, ee, strlen(ee)+1);
723 } else {
724 curLine[0] = '\0';
726 return res;
730 static int32_t getOneExprArg (int *defined) {
731 int32_t res = getExprArg(defined);
732 if (curLine[0]) fatal("too many expressions");
733 return res;
737 static int isStrArg (void) { return curLine[0]=='"' || curLine[0] == '\''; }
740 static char *getStrArg (int *lenp) {
741 char *res, qCh;
742 char *a = curLine;
744 while (*a && isspace(*a)) ++a;
745 qCh = *a++;
746 if (qCh != '"' && qCh != '\'') fatal("string expected");
747 res = parseStr(&a, qCh, lenp);
748 if (*a) {
749 if (a[0] != ',') fatal("bad string expression");
750 memmove(curLine, a, strlen(a)+1);
751 } else {
752 curLine[0] = '\0';
754 return res;
758 static MAYBE_UNUSED char *getOneStrArg (int *lenp) {
759 char *res = getStrArg(lenp);
760 if (curLine[0]) fatal("too many expressions");
761 return res;
765 static char *getLabelArg (void) {
766 static char res[MAX_LINE_SIZE+128], *p;
767 char *a = curLine;
769 memset(res, 0, sizeof(res));
770 while (*a && isspace(*a)) ++a;
771 if (!a[0]) fatal("label expected");
772 for (p = res; *a && *a != ','; ++a, ++p) *p = *a;
773 for (; p > res && isspace(p[-1]); --p) ;
774 *p = '\0';
775 if (p-res > 120) fatal("label name too long: %s", res);
776 while (*a && isspace(*a)) ++a;
777 if (*a) {
778 if (a[0] != ',') fatal("bad string expression");
779 memmove(curLine, a, strlen(a)+1);
780 } else {
781 curLine[0] = '\0';
783 return res;
787 static char *getOneLabelArg (void) {
788 char *res = getLabelArg();
789 if (curLine[0]) fatal("too many expressions");
790 return res;
794 /* res!=0: end of expression */
795 static int skipComma (void) {
796 char *a;
797 for (a = curLine; *a && isspace(*a); ++a) ;
798 if (!a[0]) { curLine[0] = '\0'; return 0; }
799 if (a[0] != ',') fatal("invalid expression: ',' expected");
800 for (++a; *a && isspace(*a); ++a) ;
801 if (!a[0]) { curLine[0] = '\0'; return 0; }
802 memmove(curLine, a, strlen(a)+1);
803 return 1;
807 static void skipInstruction (void) {
808 char *str = curLine;
809 int cnt;
811 for (cnt = 1; cnt > 0; --cnt) {
812 while (*str && isspace(*str)) ++str; // skip spaces
813 while (*str && !isspace(*str)) ++str; // skip non-spaces
814 while (*str && isspace(*str)) ++str; // skip spaces
815 if (!str[0] || *str != ':') break;
816 // try again if we have a colon
818 memmove(curLine, str, strlen(str)+1);
822 ///////////////////////////////////////////////////////////////////////////////
823 // label processor
825 static MAYBE_UNUSED void removeSpacesAndColons (void) {
826 char *ep = curLine;
827 while (*ep && (isspace(*ep) || *ep == ':')) ++ep;
828 memmove(curLine, ep, strlen(ep)+1);
832 /* remove label from curLine */
833 static void removeLabel (void) {
834 char *ep = curLine;
836 if (ep[0] && !isspace(ep[0])) for (ep = curLine; *ep && !isspace(*ep) && *ep != ':'; ++ep) ; // skip text
837 // skip spaces and colons
838 while (*ep && (isspace(*ep) || *ep == ':')) ++ep;
839 memmove(curLine, ep, strlen(ep)+1);
843 static void processLabel (void) {
844 char *argstart;
845 char *ep, *ln, *nn;
846 static char n2[256];
847 UrLabelInfo *lbl;
848 int noLocAff = 0, doEQU = 0;
850 memset(n2, 0, sizeof(n2));
851 if (!curLine[0] || isspace(curLine[0]) || curLine[0] == ':') { removeLabel(); return; } // removeLabel() removes any spaces, etc
852 // collect label
853 for (ep = curLine; *ep && !isspace(*ep) && *ep != ':'; ++ep) ;
854 if (ep-curLine > 120) fatal("label too long");
855 // copy label
856 memset(n2, 0, sizeof(n2));
857 memmove(n2, curLine, ep-curLine);
858 if (!urIsValidLabelName(n2)) return; // this is not a valid label, get out of here
859 // check if this can be instruction
860 while (*ep && isspace(*ep)) ++ep;
861 if (*ep != ':' && urFindOp(n2)) return; // this must be and instruction, process it
862 // ok, we got a good label
863 removeLabel();
864 if (n2[0] == '@' && n2[1]) { noLocAff = 1; memmove(n2, n2+1, strlen(n2)); }
865 nn = fixGlobalLabel((ln = fixLocalLabel(n2)));
866 lbl = urAddLabel(nn);
867 if (!lbl->refFile) {
868 lbl->refLine = curSrcLine->lineNo;
869 lbl->refFile = strdup(curSrcLine->fname);
871 //printf("new: [%s]\n", lbl->name);
872 // get command name
873 if (curLine[0] == '=') {
874 doEQU = 0;
875 argstart = curLine+1;
876 } else {
877 doEQU = 1;
878 argstart = strIsCommand("EQU", curLine);
880 if (!argstart || doEQU) {
881 if (pass == 0 && lbl->type != -1) fatal("duplicate label %s", lbl->name);
883 if (argstart) {
884 // do '=' or 'EQU'
885 memmove(curLine, argstart, strlen(argstart)+1);
886 if (!doEQU) {
887 if (lbl->type != -1 && lbl->type != 0) fatal("duplicate label %s", lbl->name);
889 int defined = 1;
890 int32_t res = getOneExprArg(&defined);
891 lbl->type = doEQU;
892 if (defined) {
893 lbl->value = res;
894 lbl->known = 1;
895 } else {
896 if (pass != 0) fatal("can't calculate label %s", lbl->name);
898 curLine[0] = '\0';
899 return;
901 // code label
902 if (lbl->name[0] != '{' && !noLocAff) {
903 if (lastSeenGlobalLabel) free(lastSeenGlobalLabel);
904 lastSeenGlobalLabel = strdup(lbl->name);
906 lbl->type = 2;
907 lbl->value = disp;
908 lbl->known = 1;
912 ///////////////////////////////////////////////////////////////////////////////
913 // instruction finder (in source)
915 /* array ends with NULL */
916 /* returns line or NULL */
917 /* iidx will be set to found instruction number */
918 static SourceLine *findNextInstructionFromList (int *iidx, ...) {
919 va_list ap;
920 SourceLine *cur = curSrcLine;
922 if (iidx) *iidx = -1;
923 for (; cur; cur = cur->next) {
924 int f;
925 //if (!isspace(cur->line[0])) continue; // fuck labeled strings
926 va_start(ap, iidx);
927 for (f = 0;;++f) {
928 const char *name = va_arg(ap, const char *);
929 if (!name) break;
930 if (strIsCommand(name, cur->line)) {
931 va_end(ap);
932 if (iidx) *iidx = f;
933 return cur;
936 va_end(ap);
938 return NULL;
942 static MAYBE_UNUSED SourceLine *findNextInstruction (const char *name) {
943 return findNextInstructionFromList(NULL, name, NULL);
947 ///////////////////////////////////////////////////////////////////////////////
948 // writers
950 static int optRunSNA = 1;
951 static int optRunTape = 1;
952 static int optTapExt = 0; /* set '.tap' extension (but don't write tape; this for inline tape blocks) */
955 /* return 'found' flag */
956 static int findChunkFrom (int addr, int *start, int *len) {
957 if (addr < 0) addr = 0;
958 for (; addr <= 65535 && !memused[addr]; ++addr) ;
959 if (addr > 65535) return 0;
960 *start = addr;
961 for (; addr <= 65535 && memused[addr]; ++addr) ;
962 *len = addr-(*start);
963 return 1;
967 static void rleUnpack (uint16_t addr, const uint8_t *buf, int buflen) {
968 for (; buflen >= 2; buflen -= 2) {
969 int cnt = *buf++;
970 uint8_t byte = *buf++;
971 //printf("%d %u %u\n", cnt+1, byte, addr);
972 for (; cnt >= 0; --cnt, ++addr) if (!memused[addr]) putByte(addr, byte);
977 static int fWriteByte (FILE *fo, uint8_t b, uint8_t *bxor) {
978 if (fwrite(&b, 1, 1, fo) != 1) return -1;
979 if (bxor) *bxor = (*bxor)^b;
980 return 0;
984 static int fWriteWord (FILE *fo, uint16_t w, uint8_t *bxor) {
985 if (fWriteByte(fo, w&0xFFU, bxor)) return -1;
986 if (fWriteByte(fo, (w>>8)&0xFFU, bxor)) return -1;
987 return 0;
991 ///////////////////////////////////////////////////////////////////////////////
992 // .sna
994 static int saveSna (const char *fname) {
995 char *fn = malloc(strlen(fname)+16);
996 uint8_t regs[27];
997 FILE *fo;
999 sprintf(fn, "%s.sna", fname);
1000 fo = fopen(fn, "wb");
1001 free(fn);
1002 if (!fo) { fprintf(stderr, "ERROR: can't write SNA file: %s.sna\n", fname); return -1; }
1003 printf("out: %s.sna\n", fname);
1004 memmove(regs, ursna48, 27);
1005 if (optRunSNA) {
1006 /* push new address */
1007 uint16_t sp = (uint16_t)regs[23]+(((uint16_t)regs[24])<<8);
1008 sp -= 2;
1009 putByte(sp, ent&0xFFU);
1010 putByte(sp+1, (ent>>8)&0xFFU);
1011 regs[23] = sp&0xFFU;
1012 regs[24] = (sp>>8)&0xFFU;
1014 fwrite(regs, 27, 1, fo);
1015 rleUnpack(16384, ursna48+27, sizeof(ursna48)-27);
1016 fwrite(memory+16384, 49152, 1, fo);
1017 fclose(fo);
1018 return 0;
1022 ///////////////////////////////////////////////////////////////////////////////
1023 // bin chunks
1025 static void saveRaw (const char *basename) {
1026 char *fname = malloc(strlen(basename)+16);
1027 int start = 0, len;
1028 FILE *fo;
1030 while (findChunkFrom(start, &start, &len)) {
1031 sprintf(fname, "%s_%04X.%s", basename, start, optTapExt?"tap":"bin");
1032 fo = fopen(fname, "wb");
1033 if (!fo) {
1034 fprintf(stderr, "ERROR: can't write file %s!\n", fname);
1035 } else {
1036 printf("out: %s\n", fname);
1037 if (fwrite(memory+start, len, 1, fo) != 1) {
1038 fprintf(stderr, "ERROR: error writing file %s!\n", fname);
1039 fclose(fo);
1040 unlink(fname);
1041 } else {
1042 fclose(fo);
1045 start += len;
1047 free(fname);
1051 ///////////////////////////////////////////////////////////////////////////////
1052 // .tap
1054 static void saveTapCargador (FILE *fo) {
1055 // count blocks
1056 static uint8_t cargador[16384]; // should be enough for everyone
1057 int start = 0, len, pos, f;
1058 uint8_t bxor;
1061 void putStr (const char *s) {
1062 for (; *s; ++s) cargador[pos++] = *s;
1065 void putNum (int num) {
1066 char buf[64];
1067 sprintf(buf, "%d", num);
1068 putStr(buf);
1072 pos = 4;
1073 // number
1074 cargador[0] = 0; cargador[1] = 10;
1075 // size (will be fixed later)
1076 putStr("\xfd\xb0\""); putNum(clrAddr); putStr("\""); // CLEAR VAL "xxx"
1077 // load blocks
1078 while (findChunkFrom(start, &start, &len)) {
1079 // :LOAD "" CODE
1080 putStr(":\xef\"\"\xaf");
1081 start += len;
1083 // and run
1084 // :RANDOMIZE USR VAL "xxx"
1085 putStr(":\xf9\xc0\xb0\""); putNum(ent); putStr("\"\r");
1086 // patch len
1087 cargador[2] = (pos-4)&0xff;
1088 cargador[3] = ((pos-4)>>8)&0xff;
1089 // write header
1090 fWriteWord(fo, 19, NULL); // length of header
1091 bxor = 0;
1092 fWriteByte(fo, 0, &bxor); // header block
1093 fWriteByte(fo, 0, &bxor); // 'basic' flag
1094 fWriteByte(fo, 'c', &bxor);
1095 fWriteByte(fo, 'a', &bxor);
1096 fWriteByte(fo, 'r', &bxor);
1097 fWriteByte(fo, 'g', &bxor);
1098 fWriteByte(fo, 'a', &bxor);
1099 fWriteByte(fo, 'd', &bxor);
1100 fWriteByte(fo, 'o', &bxor);
1101 fWriteByte(fo, 'r', &bxor);
1102 fWriteByte(fo, ' ', &bxor);
1103 fWriteByte(fo, ' ', &bxor);
1104 fWriteWord(fo, pos, &bxor); // length
1105 fWriteWord(fo, 10, &bxor); // start
1106 fWriteWord(fo, pos, &bxor); // length2
1107 fWriteByte(fo, bxor, NULL);
1108 // write data
1109 fWriteWord(fo, pos+2, NULL); // plus type and checkbyte
1110 bxor = 0;
1111 fWriteByte(fo, 0xFFU, &bxor); // data block
1112 for (f = 0; f < pos; ++f) fWriteByte(fo, cargador[f], &bxor);
1113 fWriteByte(fo, bxor, NULL);
1117 static void saveTap (const char *basename) {
1118 char *fname = malloc(strlen(basename)+16);
1119 char blkname[128];
1120 int start = 0, len, f;
1121 uint8_t bxor;
1122 FILE *fo;
1124 sprintf(fname, "%s.tap", basename);
1125 fo = fopen(fname, "wb");
1126 free(fname);
1127 if (!fo) { fprintf(stderr, "ERROR: can't write file %s.tap!\n", basename); return; }
1128 printf("out: %s.tap\n", basename);
1129 if (optRunTape) saveTapCargador(fo);
1130 while (findChunkFrom(start, &start, &len)) {
1131 // write header
1132 sprintf(blkname, "c%04X:%04X", start, len);
1133 //printf(" block: %s\n", blkname);
1134 fWriteWord(fo, 19, NULL); // length of header
1135 bxor = 0;
1136 fWriteByte(fo, 0, &bxor); // header block
1137 fWriteByte(fo, 3, &bxor); // 'code' flag
1138 for (f = 0; f < 10; ++f) fWriteByte(fo, blkname[f], &bxor);
1139 fWriteWord(fo, len, &bxor);
1140 fWriteWord(fo, start, &bxor);
1141 fWriteWord(fo, 32768, &bxor);
1142 fWriteByte(fo, bxor, NULL);
1143 // write data
1144 fWriteWord(fo, len+2, NULL); // plus type and checkbyte
1145 bxor = 0;
1146 fWriteByte(fo, 0xFFU, &bxor); // data block
1147 for (f = 0; f < len; ++f) fWriteByte(fo, memory[start+f], &bxor);
1148 fWriteByte(fo, bxor, NULL);
1149 start += len;
1154 ///////////////////////////////////////////////////////////////////////////////
1155 // pseudoinstructions
1157 // note that processCurrentLine() will NOT skip to the next line before
1158 // calling pseudoinstruction handler!
1159 // note that processCurrentLine() will skip current line after calling
1160 // pseudoinstruction handler!
1162 static int wasOrg = 0;
1163 static int wasClr = 0;
1166 ///////////////////////////////////////////////////////////////////////////////
1167 // user warnings
1169 static void piDISPLAYX (int passNo, int asHex) {
1170 for (;;) {
1171 if (isStrArg()) {
1172 int len = 0;
1173 char *res = getStrArg(&len);
1174 if (passNo < 0 || pass == passNo) printf("%s", res);
1175 } else {
1176 int defined = 1;
1177 int32_t v = getExprArg(&defined);
1178 if (passNo < 0 || pass == passNo) {
1179 if (asHex) printf("%04X", (unsigned int)v);
1180 else printf("%d", v);
1183 if (!skipComma()) break;
1188 static void piDISPLAY (void) { piDISPLAYX(1, 0); }
1189 static void piDISPLAY0 (void) { piDISPLAYX(0, 0); }
1190 static void piDISPLAYA (void) { piDISPLAYX(-1, 0); }
1191 static void piDISPHEX (void) { piDISPLAYX(1, 1); }
1192 static void piDISPHEX0 (void) { piDISPLAYX(0, 1); }
1193 static void piDISPHEXA (void) { piDISPLAYX(-1, 1); }
1196 ///////////////////////////////////////////////////////////////////////////////
1197 // ORG, DISP, etc.
1199 static void piORG (void) {
1200 int defined = 1;
1201 int32_t res = getOneExprArg(&defined);
1202 if (!defined) fatal("sorry, ORG operand value must be known here");
1203 if (res < 0 || res > 65535) fatal("invalid ORG operand value: %d", res);
1204 if (inTapeBlock) fatal("sorry, no ORGs in TAPEDATA allowed");
1205 pc = disp = res;
1206 if (!wasOrg) {
1207 wasOrg = 1;
1208 ent = res;
1209 if (!wasClr && res > 0) clrAddr = res-1;
1214 static void piDISP (void) {
1215 int defined = 1;
1216 int32_t res = getOneExprArg(&defined);
1217 if (!defined) fatal("sorry, DISP operand value must be known here");
1218 if (res < 0 || res > 65535) fatal("invalid DISP operand value: %d", res);
1219 //printf("DISP=%d\n", res);
1220 disp = res;
1224 static void piENDDISP (void) {
1225 if (inTapeBlock) fatal("sorry, no ENDDISPs in TAPEDATA allowed");
1226 disp = pc;
1230 static void piENT (void) {
1231 int defined = 1;
1232 int32_t res = getOneExprArg(&defined);
1233 //if (!defined) fatal("sorry, ENT operand value must be known here");
1234 if (res < 0 || res > 65535) fatal("invalid ENT operand value: %d", res);
1235 ent = res;
1239 static void piCLR (void) {
1240 int defined = 1;
1241 int32_t res = getOneExprArg(&defined);
1242 //if (!defined) fatal("sorry, CLR operand value must be known here");
1243 if (res < 0 || res > 65535) fatal("invalid CLR operand value: %d", res);
1244 clrAddr = res;
1245 wasClr = 1;
1249 static void piRESERVE (void) {
1251 int defined = 1, start;
1252 int32_t res = getExprArg(&defined);
1253 if (!defined) fatal("sorry, RESERVE operand values must be known here");
1254 if (res < 0 || res > 65535) fatal("invalid RESERVE operand value: %d", res);
1255 start = res;
1256 if (!skipComma()) fatal("RESERVE needs 2 args");
1257 res = getOneExprArg(&defined);
1258 if (!defined) fatal("sorry, RESERVE operand values must be known here");
1259 if (res < 0 || res > 65535) fatal("invalid RESERVE operand value: %d", res);
1260 for (; len > 0; --len, start = (start+1)&0xffff) memresv[start] = 1;
1262 fatal("RESERVE: not yet!");
1266 static void piALIGN (void) {
1267 int defined = 1;
1268 if (inTapeBlock) fatal("sorry, no ALIGNs in TAPEDATA allowed");
1269 int32_t res = getOneExprArg(&defined);
1270 if (!defined) fatal("sorry, ALIGN operand value must be known here");
1271 if (res < 0 || res > 65535) fatal("invalid ALIGN operand value: %d", res);
1272 if (pc != disp) fatal("sorry, no ALIGNs in desynced blocks allowed");
1273 if (res > 0 && pc%res != 0) {
1274 pc /= res;
1275 pc *= res;
1276 pc += res;
1277 disp = pc;
1282 static void piDISPALIGN (void) {
1283 int defined = 1;
1284 int32_t res = getOneExprArg(&defined);
1285 if (!defined) fatal("sorry, DISPALIGN operand value must be known here");
1286 if (res < 0 || res > 65535) fatal("invalid DISPALIGN operand value: %d", res);
1287 if (res > 0 && disp%res != 0) {
1288 disp /= res;
1289 disp *= res;
1290 disp += res;
1295 ///////////////////////////////////////////////////////////////////////////////
1296 // DEFx
1298 static int defIncr = 0;
1301 static void piDEFINCR (void) {
1302 int defined = 1;
1303 int32_t cnt = getOneExprArg(&defined);
1304 if (!defined) fatal("DEFINCR: increment must be defined");
1305 defIncr = cnt;
1309 static void piDEFBW (int isWord) {
1310 for (;;) {
1311 int defined = 0;
1312 if (isStrArg()) {
1313 int f, len = 0;
1314 char *res = getStrArg(&len);
1315 for (f = 0; f < len; ++f) {
1316 int32_t b = (uint8_t)res[f];
1317 b += defIncr;
1318 if (b < -128 || b > 255) fatal("invalid operand value: %d", b);
1319 if (b < 0) b += 256;
1320 emitByte(b);
1322 } else {
1323 int32_t res = getExprArg(&defined);
1324 if (pass > 0 && !defined) fatal("undefined operand");
1325 res += defIncr;
1326 if (isWord) {
1327 if (res < -32768 || res > 65535) fatal("invalid operand value: %d", res);
1328 if (res < 0) res += 65536;
1329 if (isWord == 1) emitWord(res); else emitRWord(res);
1330 } else {
1331 if (res < -128 || res > 255) fatal("invalid operand value: %d", res);
1332 if (res < 0) res += 256;
1333 emitByte(res);
1336 if (!skipComma()) break;
1340 static void piDEFB (void) { piDEFBW(0); }
1341 static void piDEFW (void) { piDEFBW(1); }
1342 static void piDEFR (void) { piDEFBW(2); }
1345 static void piDEFS (void) {
1346 for (;;) {
1347 int32_t bt, f;
1348 int defined = 0;
1349 int32_t res = getExprArg(&defined);
1350 if (pass > 0 && !defined) fatal("undefined operand");
1351 if (res < 1 || res > 65535) fatal("invalid number of repetitions: %d", res);
1352 if (*curLine && curLine[0] == ',') {
1353 skipComma();
1354 bt = getExprArg(&defined);
1355 if (pass > 0 && !defined) fatal("undefined operand");
1356 bt += defIncr;
1357 if (bt < -128 || bt > 255) fatal("invalid operand value: %d", res);
1358 if (bt < 0) bt += 256;
1359 for (f = 0; f < res; ++f) emitByte(bt);
1360 } else {
1361 pc += res; disp += res;
1363 if (!skipComma()) break;
1368 /* bit 0: put '\0' */
1369 /* bit 1: set bit 7 of last byte */
1370 /* bit 2: put length */
1371 static void piDEFSTR (int type) {
1372 for (;;) {
1373 if (isStrArg()) {
1374 int f, len = 0;
1375 char *res = getStrArg(&len);
1376 if (type&0x04) {
1377 if (len > 255) fatal("string too long");
1378 emitByte(len);
1380 for (f = 0; f < len; ++f) {
1381 uint8_t b = (uint8_t)res[f];
1382 if ((type&0x02) && f == len-1) b |= 0x80;
1383 emitByte(b);
1385 if (type&0x01) emitByte(0);
1386 } else {
1387 int defined = 1;
1388 int32_t v = getExprArg(&defined);
1389 if (pass > 0 && !defined) fatal("undefined expression");
1390 if (!defined) v = 0; else v += defIncr;
1391 if (v < -128 || v > 255) fatal("invalid operand value: %d", v);
1392 if (v < 0) v += 256;
1393 emitByte(v);
1395 if (!skipComma()) break;
1400 static void piDEFM (void) { piDEFSTR(0x00); }
1401 static void piDEFZ (void) { piDEFSTR(0x01); }
1402 static void piDEFX (void) { piDEFSTR(0x02); }
1403 static void piDEFC (void) { piDEFSTR(0x04); }
1406 ///////////////////////////////////////////////////////////////////////////////
1407 // INCBIN
1409 /* INCBIN "name"[,maxlen] */
1410 static void piINCBIN (void) {
1411 int system = 0;
1412 char *fn, qCh;
1413 uint8_t bt;
1414 FILE *fl;
1415 int maxlen = 65536;
1416 char *args = curLine;
1418 if (!curLine[0]) fatal("INCBIN without file name");
1419 if (isStrArg()) {
1420 qCh = *args++;
1421 system = 0;
1422 } else if (curLine[0] == '<') {
1423 qCh = '<'; ++args;
1424 system = 1;
1425 } else {
1426 qCh = 0;
1427 system = 0;
1429 fn = parseStr(&args, qCh, NULL); // i don't care about string length here
1430 if (!fn[0]) fatal("INCBIN: empty file name");
1431 memmove(curLine, args, strlen(args)+1);
1432 // maxlen
1433 if (curLine[0] == ',') {
1434 skipComma();
1435 int defined = 1;
1436 maxlen = getOneExprArg(&defined);
1437 if (!defined) fatal("INCBIN: undefined maxlen");
1438 if (maxlen < 1) return; // nothing to do
1440 // now fix name
1441 if (system) {
1442 sprintf(curLine, "%s/%s", sysIncludeDir, fn);
1443 } else {
1444 sprintf(curLine, "%s", fn);
1447 fl = fopen(curLine, "rb");
1448 if (!fl) fatal("INCBIN: file not found: %s", curLine);
1449 while (maxlen-- > 0) {
1450 int res = fread(&bt, 1, 1, fl);
1451 if (!res) break;
1452 if (res != 1) { fclose(fl); fatal("INCBIN: error reading file: %s", curLine); }
1453 emitByte(bt);
1455 fclose(fl);
1459 ///////////////////////////////////////////////////////////////////////////////
1460 // MODULE, ENDMODULE
1462 static void piENDMODULE (void) {
1463 if (!curModule) fatal("ENDMODULE without MODULE");
1464 if (curLine[0]) {
1465 char *mn = getOneLabelArg();
1466 if (strcmp(mn, curModule->name)) fatal("invalid module name in ENDMODULE: %s", mn);
1468 curModule = NULL;
1472 static void piMODULE (void) {
1473 ModuleInfo *mi;
1474 char *mn;
1475 SourceLine *ol = curSrcLine;
1476 int inum;
1478 if (curModule) fatal("no nested modules allowed");
1479 mn = getOneLabelArg();
1480 if (!urIsValidLabelName(mn)) fatal("invalid module name: %s", mn);
1481 mi = moduleFind(mn);
1482 //printf("[%s] %p\n", mn, mi);
1483 if (mi) {
1484 if (mi->seen) {
1485 if (strcmp(mi->fname, curSrcLine->fname)) fatal("duplicate module definition; previous was in %s", mi->fname);
1486 /* skip module */
1487 nextSrcLine(); // skip ourself
1488 if (!setCurSrcLine(findNextInstructionFromList(&inum, "MODULE", "ENDMODULE", NULL))) {
1489 setCurSrcLine(ol);
1490 fatal("no ENDMODULE");
1492 if (inum == 0) fatal("no nested modules allowed");
1493 curModule = mi;
1494 skipInstruction();
1495 piENDMODULE();
1496 return;
1498 } else {
1499 mi = moduleAdd(mn, curSrcLine->fname);
1501 mi->seen = 1;
1502 curModule = mi;
1506 ///////////////////////////////////////////////////////////////////////////////
1507 // DUP, EDUP
1509 static void piEDUP (void) {
1510 fatal("EDUP without DUP");
1514 static void piDUP (void) {
1515 int defined = 1, dupCnt = 1, inum;
1516 SourceLine *stline, *eline = NULL;
1517 int32_t cnt = getOneExprArg(&defined);
1518 if (!defined) fatal("DUP: counter must be defined");
1519 if (cnt > 65535) fatal("DUP: counter too big: %d", cnt);
1520 if (cnt < 0) warningMsg("DUP: counter too small: %d", cnt);
1521 // now find corresponding EDUP
1522 // note that we should skip nested DUPs
1523 nextSrcLine(); // skip ourself
1524 stline = curSrcLine;
1525 while (curSrcLine) {
1526 if (!setCurSrcLine(findNextInstructionFromList(&inum, "DUP", "EDUP", NULL))) break;
1527 // ok, we found something; what is it?
1528 if (inum == 0) {
1529 // new DUP, skip it
1530 ++dupCnt;
1531 nextSrcLine(); // skip DUP
1532 } else {
1533 // EDUP
1534 if (--dupCnt == 0) {
1535 // gotcha!
1536 eline = curSrcLine;
1537 break;
1539 nextSrcLine(); // skip EDUP
1542 if (!eline) { setCurSrcLine(stline); fatal("no EDUP"); }
1543 // now repeat that lines
1544 while (cnt-- > 0) {
1545 setCurSrcLine(stline);
1546 while (curSrcLine != eline) processCurrentLine();
1551 ///////////////////////////////////////////////////////////////////////////////
1552 // IF, ENDIF
1554 static int ifCount = 0;
1557 static int ifSkipToEndIfOrElse (int wholeBody) {
1558 int inum, wasElse = 0;
1559 SourceLine *oline;
1561 while (curSrcLine) {
1562 if (!setCurSrcLine(findNextInstructionFromList(&inum, "IF", "ELSE", "ENDIF", "ELSEIF", "MACRO", "ENDM", "IFX", "ELSEIFX", NULL))) break;
1563 switch (inum) {
1564 case 0: /* if */
1565 case 6: /* ifx */
1566 nextSrcLine(); // skip IF
1567 ifSkipToEndIfOrElse(1); // and recurse
1568 nextSrcLine(); // skip ENDIF
1569 break;
1570 case 1: /* else */
1571 if (wasElse) fatal("duplicate ELSE");
1572 if (!wholeBody) return 1;
1573 wasElse = 1;
1574 nextSrcLine(); // skip ELSE
1575 break; // and continue
1576 case 2: /* endif */
1577 return 0; // and exit
1578 case 3: /* elif */
1579 case 7: /* elifx */
1580 if (wasElse) fatal("ELSEIF in ELSE");
1581 if (!wholeBody) return 2;
1582 nextSrcLine(); // skip ELSEIF
1583 break; // and continue
1584 case 4: /* macro */
1585 // skip it as a whole
1586 nextSrcLine(); // skip MACRO
1587 for (;;) {
1588 oline = curSrcLine;
1589 if (!setCurSrcLine(findNextInstructionFromList(&inum, "MACRO", "ENDM", NULL))) { setCurSrcLine(oline); fatal("invalid MACRO"); }
1590 if (inum == 1) break;
1591 fatal("invalid nested MACRO");
1593 nextSrcLine(); // skip ENDM
1594 break;
1595 case 5: /* endm */
1596 fatal("unexpected ENDM");
1599 fatal("IF without ENDIF");
1600 return -1;
1604 static void piENDIF (void) {
1605 if (--ifCount < 0) fatal("ENDIF without IF");
1609 static void piELSE (void) {
1610 if (--ifCount < 0) fatal("ELSE without IF");
1611 nextSrcLine(); // skip ELSE
1612 ifSkipToEndIfOrElse(1);
1616 static void piELSEIF (void) {
1617 if (--ifCount < 0) fatal("ELSEIF without IF");
1618 nextSrcLine(); // skip ELSEIF
1619 ifSkipToEndIfOrElse(1);
1623 static void piIFAll (int isIfX) {
1624 int defined = 1;
1625 int ooo = lblOptMakeU2;
1626 lblOptMakeU2 = isIfX ? 1 : 0;
1627 int32_t cond = getOneExprArg(&defined);
1628 lblOptMakeU2 = ooo;
1629 if (!defined) {
1630 if (!isIfX) fatal("IF: condition must be defined");
1631 cond = 0; // for IFX: 0 is there is any undefined label
1633 if (cond) {
1634 // ok, do it until ELSE/ELSEIF/ENDIF
1635 ++ifCount;
1636 return;
1638 for (;;) {
1639 int r;
1640 char *args;
1642 nextSrcLine(); // skip last instruction
1643 // skip until ELSE/ELSEIF/ENDIF
1644 r = ifSkipToEndIfOrElse(0);
1645 if (r == 0) break; // ENDIF
1646 if (r == 1) { ++ifCount; break; } // ELSE
1647 // ELSEIF, do condition
1648 if ((args = strIsCommand("ELSEIF", curLine)) == NULL) fatal("internal error in conditionals"); // the thing that should not be
1649 memmove(curLine, args, strlen(args)+1);
1650 cond = getOneExprArg(&defined);
1651 if (!defined) fatal("ELSEIF: condition must be defined");
1652 if (cond) { ++ifCount; break; } // condition is true
1657 static void piIF (void) { piIFAll(0); }
1658 static void piIFX (void) { piIFAll(1); }
1661 ///////////////////////////////////////////////////////////////////////////////
1662 // macro processor
1664 * what i did with MACRO is the brain-damaged cheating all the way.
1665 * first, i will collect the MACRO body and remember it.
1666 * second, when the macro is used, i will:
1667 * * create unique labels for all supplied macro args, each with
1668 * number (so IFARG/IFNARG can check the existance)
1669 * * insert the whole macro body in place, fixing argument refs
1670 * * let the asm play with it
1671 * another tricky part is 'local labels': i have to change all
1672 * '..lbl' references -- generate new label name for it and
1673 * replace all refs. and be careful to not touch the strings.
1674 * this is not the best scheme, but it is fairly simple and it wokrs.
1676 static void piMACRO (void) {
1677 fatal("sorry, no MACRO yet");
1681 static void piENDM (void) {
1682 fatal("ENDM withoud MACRO");
1686 ///////////////////////////////////////////////////////////////////////////////
1687 // line processor
1688 static int optWriteType = 't';
1689 static int optWTChanged = 0;
1692 static void piDEFFMT (void) {
1693 char *name;
1695 if (isStrArg()) {
1696 int len = 0;
1697 name = getOneStrArg(&len);
1698 } else {
1699 name = getOneLabelArg();
1701 if (optWTChanged) return;
1702 if (!strcasecmp(name, "SNA")) {
1703 optRunTape = 0;
1704 optRunSNA = 0;
1705 optWriteType = 's';
1706 return;
1708 if (!strcasecmp(name, "RUNSNA")) {
1709 optRunTape = 0;
1710 optRunSNA = 1;
1711 optWriteType = 's';
1712 return;
1714 if (!strcasecmp(name, "TAP") || !strcasecmp(name, "TAPE")) {
1715 optRunTape = 0;
1716 optRunSNA = 0;
1717 optWriteType = 't';
1718 return;
1720 if (!strcasecmp(name, "RUNTAP") || !strcasecmp(name, "RUNTAPE")) {
1721 optRunTape = 1;
1722 optRunSNA = 0;
1723 optWriteType = 't';
1724 return;
1726 if (!strcasecmp(name, "BIN") || !strcasecmp(name, "RAW")) {
1727 optRunTape = 0;
1728 optRunSNA = 0;
1729 optWriteType = 'r';
1730 return;
1732 if (!strcasecmp(name, "NONE") || !strcasecmp(name, "NOTHING")) {
1733 optRunTape = 0;
1734 optRunSNA = 0;
1735 optWriteType = 'n';
1736 return;
1738 fatal("invalid default output type: %s", name);
1742 ///////////////////////////////////////////////////////////////////////////////
1743 // line processor
1745 static void processCurrentLine (void) {
1746 char *str, *ee, name[66];
1747 UrAsmOp *op;
1749 if (!curSrcLine) return; // do nothing
1750 loadCurSrcLine();
1751 processLabel();
1752 // try to find and process command
1753 str = curLine; while (*str && isspace(*str)) ++str; // skip spaces
1754 // find command end
1755 for (ee = str; *ee && !isspace(*ee) && *ee != '"' && *ee != '\''; ++ee) ;
1756 // get command, if any
1757 if (ee != str && ee-str <= 64) {
1758 memset(name, 0, sizeof(name));
1759 memmove(name, str, ee-str);
1760 // known command?
1761 op = urFindOp(name);
1762 if (op) {
1763 // ok, do it
1764 str = ee; while (*str && isspace(*str)) ++str; // skip spaces
1765 memmove(curLine, str, strlen(str)+1);
1766 op->fn();
1767 nextSrcLine(); // skip it
1768 return;
1771 // try to compile this
1772 for (;;) {
1773 int len;
1774 const char *errpos;
1775 // skip spaces and ':'
1776 str = curLine; while (*str && (isspace(*str) || *str == ':')) ++str;
1777 memmove(curLine, str, strlen(str)+1);
1778 if (!curLine[0]) break;
1780 len = urAssembleOne(curLine, pc, disp, &errpos);
1781 if (len < 0) fatalUrLib(len);
1782 pc += len; disp += len;
1783 if (len >= 0 && errpos) {
1784 memmove(curLine, errpos+1, strlen(errpos));
1785 } else {
1786 break;
1789 nextSrcLine(); // skip it
1793 ///////////////////////////////////////////////////////////////////////////////
1794 // setup instructions
1796 static void registerInstructions (void) {
1797 urAddOp("DISPLAY", piDISPLAY);
1798 urAddOp("DISPLAY0", piDISPLAY0);
1799 urAddOp("DISPLAYA", piDISPLAYA);
1800 urAddOp("DISPHEX", piDISPHEX);
1801 urAddOp("DISPHEX0", piDISPHEX0);
1802 urAddOp("DISPHEXA", piDISPHEXA);
1804 urAddOp("DEFFMT", piDEFFMT);
1806 urAddOp("MACRO", piMACRO);
1807 urAddOp("ENDM", piENDM);
1809 urAddOp("ORG", piORG);
1810 urAddOp("DISP", piDISP);
1811 urAddOp("ENDDISP", piENDDISP);
1812 urAddOp("PHASE", piDISP);
1813 urAddOp("DEPHASE", piENDDISP);
1814 urAddOp("UNPHASE", piENDDISP);
1815 urAddOp("ALIGN", piALIGN);
1816 urAddOp("DISPALIGN", piDISPALIGN);
1817 urAddOp("PHASEALIGN", piDISPALIGN);
1818 urAddOp("ENT", piENT);
1819 urAddOp("CLR", piCLR);
1820 urAddOp("RESERVE", piRESERVE);
1822 urAddOp("INCBIN", piINCBIN);
1824 urAddOp("MODULE", piMODULE);
1825 urAddOp("ENDMODULE", piENDMODULE);
1827 urAddOp("DUP", piDUP);
1828 urAddOp("EDUP", piEDUP);
1830 urAddOp("IF", piIF);
1831 urAddOp("IFX", piIFX);
1832 urAddOp("ELSE", piELSE);
1833 urAddOp("ELSEIF", piELSEIF);
1834 urAddOp("ELSEIFX", piELSEIF);
1835 urAddOp("ENDIF", piENDIF);
1837 urAddOp("DEFINCR", piDEFINCR);
1838 urAddOp("DEFB", piDEFB); urAddOp("DB", piDEFB);
1839 urAddOp("DEFW", piDEFW); urAddOp("DW", piDEFW);
1840 urAddOp("DEFR", piDEFR); urAddOp("DR", piDEFR);
1841 urAddOp("DEFS", piDEFS); urAddOp("DS", piDEFS);
1842 urAddOp("DEFM", piDEFM); urAddOp("DM", piDEFM);
1843 urAddOp("DEFZ", piDEFZ); urAddOp("DZ", piDEFZ);
1844 urAddOp("DEFX", piDEFX); urAddOp("DX", piDEFX);
1845 urAddOp("DEFC", piDEFC); urAddOp("DC", piDEFC);
1849 ///////////////////////////////////////////////////////////////////////////////
1850 // preparing another pass
1852 static void initPass (void) {
1853 curSrcLine = asmText;
1854 curModule = NULL;
1855 pc = disp = ent = 0x100; // viva CP/M!
1856 inTapeBlock = 0;
1857 tapeXorB = 0;
1858 wasOrg = 0;
1859 wasClr = 0;
1860 ifCount = 0;
1861 defIncr = 0;
1862 lblOptMakeU2 = 0;
1863 lastSeenGlobalLabel = strdup(" [MAIN] ");
1864 modulesResetSeen();
1865 prepareMemory();
1869 static int posstPass (void) {
1870 if (lastSeenGlobalLabel) free(lastSeenGlobalLabel); lastSeenGlobalLabel = NULL;
1871 if (checkLabels()) return -1;
1872 if (ifCount != 0) fatal("unbalanced IFs");
1873 return 0;
1877 ///////////////////////////////////////////////////////////////////////////////
1878 // options
1880 static struct option longOpts[] = {
1881 {"sna", 0, NULL, 's'},
1882 {"autosna", 0, NULL, 'S'},
1883 {"tap", 0, NULL, 't'},
1884 {"autotap", 0, NULL, 'T'},
1885 /*{"rawtap", 0, NULL, 'T'},*/
1886 {"raw", 0, NULL, 'r'},
1887 /*{"dmb", 0, NULL, 'b'},*/
1888 {"none", 0, NULL, 'n'},
1889 {"help", 0, NULL, 'h'},
1890 {NULL, 0, NULL, 0}
1894 static const char *defInFiles[] = {"main.zas", "main.a80", "main.asm", NULL};
1897 static void usage (const char *pname) {
1898 int f;
1899 /*" -T --rawtap write raw file with '.tap' extension\n"*/
1900 /*" -b --dmb write .dmb file\n"*/
1901 printf(
1902 "usage: %s [options] infile\n"
1903 "default infiles:", pname);
1904 for (f = 0; defInFiles[f]; ++f) printf(" %s", defInFiles[f]);
1905 printf("\n"
1906 "options:\n"
1907 " -s --sna write 48K .SNA file\n"
1908 " -S --autosna write 48K .SNA file with autostart\n"
1909 " -t --tap write .tap file\n"
1910 " -T --autotap write .tap file with basic loader (CLR to set CLEAR)\n"
1911 " -r --raw write raw file(s)\n"
1912 " -n --none write nothing\n"
1913 " -h --help this help\n");
1917 ///////////////////////////////////////////////////////////////////////////////
1918 // main
1920 int main (int argc, char *argv[]) {
1921 int res = 0, c;
1922 const char *pname = argv[0];
1923 char *inFile = NULL;
1924 initInclideDir();
1926 urGetByte = getByte;
1927 urPutByte = putByte;
1928 urFindLabelByName = findLabelCB;
1929 urIsLabelDefinedOrKnown = isLabelDefinedOrKnown;
1931 printf("urasm v%d.%d.%d, compile date: %s %s\n", VERSION_HI, VERSION_MID, VERSION_LO, __DATE__, __TIME__);
1932 while ((c = getopt_long(argc, argv, "sStTrnh", longOpts, NULL)) >= 0) {
1933 switch (c) {
1934 case 'S': optRunSNA = 1; optWriteType = 's'; optWTChanged = 1; break;
1935 case 's': optRunSNA = 0; optWriteType = 's'; optWTChanged = 1; break;
1936 case 'T': optRunTape = 1; optWriteType = 't'; optWTChanged = 1; break;
1937 case 't': optRunTape = 0; optWriteType = 't'; optWTChanged = 1; break;
1938 case 'r': case 'n': optWriteType = c; optWTChanged = 1; break;
1939 case 'h': usage(pname); res = 0; goto earlyerrquit;
1940 case '?': return 1;
1943 if (optind >= argc) {
1944 // try to find default input file
1945 int f;
1946 for (f = 0; defInFiles[f]; ++f) {
1947 if (!access(defInFiles[f], R_OK)) {
1948 inFile = strdup(defInFiles[f]);
1949 break;
1952 } else {
1953 inFile = strdup(argv[optind]);
1955 if (!inFile || !inFile[0]) {
1956 res = 1;
1957 fprintf(stderr, "ERROR: no input file!\n");
1958 goto earlyerrquit;
1961 registerInstructions();
1963 res = asmTextLoad(inFile);
1964 if (!res) {
1966 printf("dumping...\n");
1967 FILE *fo = fopen("z000.out", "w");
1968 if (fo) {
1969 SourceLine *c;
1970 for (c = asmText; c; c = c->next) fprintf(fo, "%s ; %s: %d\n", c->line, c->fname, c->lineNo);
1971 fclose(fo);
1974 for (pass = 0; pass <= 1; ++pass) {
1975 initPass();
1976 printf("pass %d\n", pass);
1977 setCurSrcLine(asmText);
1978 if (setjmp(errJP)) { res = 1; break; }
1979 while (curSrcLine) processCurrentLine();
1980 if (posstPass()) { res = 1; break; }
1982 // write result
1983 if (res == 0) {
1984 char *oc = strdup(inFile);
1985 char *pd = strrchr(oc, '.');
1986 if (pd && !strchr(oc, '/')) *pd = '\0';
1987 switch (optWriteType) {
1988 case 's': saveSna(oc); break;
1989 case 't': saveTap(oc); break;
1990 case 'r': saveRaw(oc); break;
1992 free(oc);
1994 } else {
1995 fprintf(stderr, "ERROR: loading error!\n");
1998 if (lastSeenGlobalLabel) free(lastSeenGlobalLabel);
1999 urClearLabels();
2000 modulesClear();
2001 asmTextClear();
2002 urClearOps();
2003 earlyerrquit:
2004 if (inFile) free(inFile);
2005 if (sysIncludeDir) free(sysIncludeDir);
2006 return res?1:0;