duskos

dusk os fork
git clone git://git.alexwennerberg.com/duskos
Log | Files | Refs | README | LICENSE

commit abd884945d66755c239d54ea258a4f366c0e23ae
parent 69600129406d3e2a7d2e06d6567325c158f22ced
Author: Virgil Dupras <hsoft@hardcoded.net>
Date:   Sun,  4 Dec 2022 08:46:20 -0500

asm/uxntal: preparing the battleground

Diffstat:
Afs/asm/uxntal.c | 474+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afs/asm/uxntal.fs | 2++
Mfs/comp/c/lib.fs | 2+-
Mfs/doc/cc/lib.txt | 2+-
Mfs/doc/cc/usage.txt | 3++-
Mfs/tests/asm/all.fs | 1+
Afs/tests/asm/uxntal.fs | 7+++++++
Alicense/dll-uxn.txt | 8++++++++
8 files changed, 496 insertions(+), 3 deletions(-)

diff --git a/fs/asm/uxntal.c b/fs/asm/uxntal.c @@ -0,0 +1,474 @@ +/* Source: https://git.sr.ht/~rabbits/uxn + * License: /license/dll-uxn.txt + */ +// TODO: Doesn't compile yet. See TODOs below + +#define TRIM $100 +#define LENGTH $10000 + +typedef unsigned char Uint8; +typedef char Sint8; +typedef unsigned short Uint16; + +typedef struct { + // TODO: allow multiple decls per line + char name[$40]; + char items[$40*$40]; + Uint8 len; +} Macro; + +typedef struct { + char name[$40]; + Uint16 addr; + Uint16 refs; +} Label; + +typedef struct { + char name[$40]; + char rune; + Uint16 addr; +} Reference; + +typedef struct { + Uint8 data[LENGTH]; + unsigned int ptr; + unsigned int length; + Uint16 llen; + Uint16 mlen; + Uint16 rlen; + Label labels[$400]; + Macro macros[$100]; + Reference refs[$800]; + char scope[$40]; +} Program; + +Program p; + +// TODO: compile error +static char* ops[$20] = { + "LIT"0, "INC"0, "POP"0, "NIP"0, "SWP"0, "ROT"0, "DUP"0, "OVR"0, + "EQU"0, "NEQ"0, "GTH"0, "LTH"0, "JMP"0, "JCN"0, "JSR"0, "STH"0, + "LDZ"0, "STZ"0, "LDR"0, "STR"0, "LDA"0, "STA"0, "DEI"0, "DEO"0, + "ADD"0, "SUB"0, "MUL"0, "DIV"0, "AND"0, "ORA"0, "EOR"0, "SFT"0 +}; + +static int scmp(char *a, char *b, int len) { int i = 0; while(a[i] == b[i]) if(!a[i] || ++i >= len) return 1; return 0; } /* string compare */ +static int sihx(char *s) { int i = 0; char c; while((c = s[i++])) if(!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f')) return 0; return i > 1; } /* string is hexadecimal */ +static int shex(char *s) { int n = 0, i = 0; char c; while((c = s[i++])) if(c >= '0' && c <= '9') n = n * 16 + (c - '0'); else if(c >= 'a' && c <= 'f') n = n * 16 + 10 + (c - 'a'); return n; } /* string to num */ +static int slen(char *s) { int i = 0; while(s[i]) i++; return i; } /* string length */ +static int spos(char *s, char c) { Uint8 i = 0, j; while((j = s[i++])) if(j == c) return i; return -1; } /* character position */ +static char *scpy(char *src, char *dst, int len) { int i = 0; while((dst[i] = src[i]) && i < len - 2) i++; dst[i + 1] = 0; return dst; } /* string copy */ +static char *scat(char *dst, const char *src) { char *ptr = dst + slen(dst); while(*src) *ptr++ = *src++; *ptr = 0; return dst; } /* string cat */ + +static int parse(char *w, int hdl); + +static int +error(const char *name, const char *msg) +{ + fprintf(name, msg, "%s: %s\n", ConsoleOut()); + return 0; +} + +static char * +sublabel(char *src, char *scope, char *name) +{ + return scat(scat(scpy(scope, src, $40), "/"), name); +} + +static Macro * +findmacro(char *name) +{ + int i; + for(i = 0; i < p.mlen; i++) + if(scmp(p.macros[i].name, name, $40)) + return &p.macros[i]; + return NULL; +} + +static Label * +findlabel(char *name) +{ + int i; + for(i = 0; i < p.llen; i++) + if(scmp(p.labels[i].name, name, $40)) + return &p.labels[i]; + return NULL; +} + +static Uint8 +findopcode(char *s) +{ + int i; + for(i = 0; i < $20; i++) { + int m = 0; + if(!scmp(ops[i], s, 3)) + // TODO: implement continue + // continue; + {} + if(!i) i |= (1 << 7); /* force keep for LIT */ + while(s[3 + m]) { + if(s[3 + m] == '2') + i |= (1 << 5); /* mode: short */ + else if(s[3 + m] == 'r') + i |= (1 << 6); /* mode: return */ + else if(s[3 + m] == 'k') + i |= (1 << 7); /* mode: keep */ + else + return 0; /* failed to match */ + m++; + } + return i; + } + return 0; +} + +static int +freadword(char *buf, int hdl) +{ + int i, c = 0; + while ((c > 0) && (c <= ' ')) c = fgetc(hdl); + if (c < 0) /* EOF */ return 0; + for (i=0; i < $3f; i++) { + // TODO: add CC test for this, I'm really not sure it's going to work. + *buf++ = c; + c = fgetc(hdl) + if ((c > 0) && (c <= ' ')) { + *buf = 0; + return 1; + } + } + return 0; // too long +} + +static int +makemacro(char *name, int hdl) +{ + Macro *m; + char word[$40]; + if(findmacro(name)) + return error("Macro duplicate", name); + if(sihx(name) && slen(name) % 2 == 0) + return error("Macro name is hex number", name); + if(findopcode(name) || scmp(name, "BRK", 4) || !slen(name)) + return error("Macro name is invalid", name); + if(p.mlen == $100) + return error("Macros limit exceeded", name); + m = &p.macros[p.mlen++]; + scpy(name, m->name, $40); + while(freadword(word, hdl) == 1) { + // TODO: implement continue + if(word[0] == '{') /* continue; */ {} + if(word[0] == '}') break; + if(word[0] == '%') + return error("Macro error", name); + if(m->len >= $40) + return error("Macro size exceeded", name); + scpy(word, m->items[m->len++], $40); + } + return 1; +} + +static int +makelabel(char *name) +{ + Label *l; + if(findlabel(name)) + return error("Label duplicate", name); + if(sihx(name) && (slen(name) == 2 || slen(name) == 4)) + return error("Label name is hex number", name); + if(findopcode(name) || scmp(name, "BRK", 4) || !slen(name)) + return error("Label name is invalid", name); + if(p.llen == $400) + return error("Labels limit exceeded", name); + l = &p.labels[p.llen++]; + l->addr = p.ptr; + l->refs = 0; + scpy(name, l->name, $40); + return 1; +} + +static int +makereference(char *scope, char *label, Uint16 addr) +{ + char subw[$40], parent[$40]; + int pos; + Reference *r; + if(p.rlen == $800) + return error("References limit exceeded", label); + r = &p.refs[p.rlen++]; + if(label[1] == '&') + scpy(sublabel(subw, scope, label + 2), r->name, $40); + else { + pos = spos(label + 1, '/'); + if(pos > 0) { + Label *l; + if((l = findlabel(scpy(label + 1, parent, pos)))) + l->refs++; + } + scpy(label + 1, r->name, $40); + } + r->rune = label[0]; + r->addr = addr; + return 1; +} + +static int +writebyte(Uint8 b) +{ + if(p.ptr < TRIM) + return error("Writing in zero-page", ""); + else if(p.ptr > $ffff) + return error("Writing after the end of RAM", ""); + else if(p.ptr < p.length) + return error("Memory overwrite", ""); + p.data[p.ptr++] = b; + p.length = p.ptr; + return 1; +} + +static int +writeopcode(char *w) +{ + Uint8 res; + res = writebyte(findopcode(w)); + return res; +} + +static int +writeshort(Uint16 s, int lit) +{ + if(lit) + if(!writebyte(findopcode("LIT2"))) return 0; + return writebyte(s >> 8) && writebyte(s & $ff); +} + +static int +writelitbyte(Uint8 b) +{ + if(!writebyte(findopcode("LIT"))) return 0; + if(!writebyte(b)) return 0; + return 1; +} + +static int +doinclude(const char *filename) +{ + int hdl; + char w[$40]; + // TODO: add API to detect if file exists + hdl = fopen(filename); + while(freadword(w, hdl) == 1) + if(!parse(w, hdl)) + return error("Unknown token", w); + fclose(hdl); + return 1; +} + +static int +parse(char *w, int hdl) +{ + int i; + char word[$40], subw[$40], c; + Macro *m; + if(slen(w) >= 63) + return error("Invalid token", w); + switch(w[0]) { + case '(': /* comment */ + if(slen(w) != 1) fprintf(w, "-- Malformed comment: %s\n", ConsoleOut()); + i = 1; /* track nested comment depth */ + while(freadword(word, hdl) == 1) { + if(slen(word) != 1) + continue; + else if(word[0] == '(') + i++; + else if(word[0] == ')' && --i < 1) + break; + } + break; + case '~': /* include */ + if(!doinclude(w + 1)) + return error("Invalid include", w); + break; + case '%': /* macro */ + if(!makemacro(w + 1, hdl)) + return error("Invalid macro", w); + break; + case '|': /* pad-absolute */ + if(!sihx(w + 1)) + return error("Invalid padding", w); + p.ptr = shex(w + 1); + break; + case '$': /* pad-relative */ + if(!sihx(w + 1)) + return error("Invalid padding", w); + p.ptr += shex(w + 1); + break; + case '@': /* label */ + if(!makelabel(w + 1)) + return error("Invalid label", w); + scpy(w + 1, p.scope, $40); + break; + case '&': /* sublabel */ + if(!makelabel(sublabel(subw, p.scope, w + 1))) + return error("Invalid sublabel", w); + break; + case '#': /* literals hex */ + if(!sihx(w + 1) || (slen(w) != 3 && slen(w) != 5)) + return error("Invalid hex literal", w); + if(slen(w) == 3) { + if(!writelitbyte(shex(w + 1))) return 0; + } else if(slen(w) == 5) { + if(!writeshort(shex(w + 1), 1)) return 0; + } + break; + case '_': /* raw byte relative */ + makereference(p.scope, w, p.ptr); + if(!writebyte($ff)) return 0; + break; + case ',': /* literal byte relative */ + makereference(p.scope, w, p.ptr); + if(!writelitbyte($ff)) return 0; + break; + case '-': /* raw byte absolute */ + makereference(p.scope, w, p.ptr); + if(!writebyte($ff)) return 0; + break; + case '.': /* literal byte zero-page */ + makereference(p.scope, w, p.ptr); + if(!writelitbyte($ff)) return 0; + break; + case ':': /* raw short absolute */ + case '=': + makereference(p.scope, w, p.ptr); + if(!writeshort($ffff, 0)) return 0; + break; + case ';': /* literal short absolute */ + makereference(p.scope, w, p.ptr); + if(!writeshort($ffff, 1)) return 0; + break; + case '"': /* raw string */ + i = 0; + while((c = w[++i])) + if(!writebyte(c)) return 0; + break; + case '[': + case ']': + if(slen(w) == 1) break; /* else fallthrough */ + default: + /* opcode */ + if(findopcode(w) || scmp(w, "BRK", 4)) { + if(!writeopcode(w)) return 0; + } + /* raw byte */ + else if(sihx(w) && slen(w) == 2) { + if(!writebyte(shex(w))) return 0; + } + /* raw short */ + else if(sihx(w) && slen(w) == 4) { + if(!writeshort(shex(w), 0)) return 0; + } + /* macro */ + else if((m = findmacro(w))) { + for(i = 0; i < m->len; i++) + if(!parse(m->items[i], hdl)) + return 0; + return 1; + } else + return error("Unknown token", w); + } + return 1; +} + +static int +resolve(void) +{ + Label *l; + int i; + for(i = 0; i < p.rlen; i++) { + Reference *r = &p.refs[i]; + switch(r->rune) { + case '_': + if(!(l = findlabel(r->name))) + return error("Unknown relative reference", r->name); + p.data[r->addr] = (Sint8)(l->addr - r->addr - 2); + if((Sint8)p.data[r->addr] != (l->addr - r->addr - 2)) + return error("Relative reference is too far", r->name); + l->refs++; + break; + case ',': + if(!(l = findlabel(r->name))) + return error("Unknown relative reference", r->name); + p.data[r->addr + 1] = (Sint8)(l->addr - r->addr - 3); + if((Sint8)p.data[r->addr + 1] != (l->addr - r->addr - 3)) + return error("Relative reference is too far", r->name); + l->refs++; + break; + case '-': + if(!(l = findlabel(r->name))) + return error("Unknown absolute reference", r->name); + p.data[r->addr] = l->addr & $ff; + l->refs++; + break; + case '.': + if(!(l = findlabel(r->name))) + return error("Unknown zero-page reference", r->name); + p.data[r->addr + 1] = l->addr & $ff; + l->refs++; + break; + case ':': + case '=': + if(!(l = findlabel(r->name))) + return error("Unknown absolute reference", r->name); + p.data[r->addr] = l->addr >> $8; + p.data[r->addr + 1] = l->addr & $ff; + l->refs++; + break; + case ';': + if(!(l = findlabel(r->name))) + return error("Unknown absolute reference", r->name); + p.data[r->addr + 1] = l->addr >> $8; + p.data[r->addr + 2] = l->addr & $ff; + l->refs++; + break; + default: + return error("Unknown reference", r->name); + } + } + return 1; +} + +static int +assemble(int hdl) +{ + char w[$40]; + scpy("on-reset", p.scope, $40); + while(freadword(w, hdl) == 1) + if(!parse(w, hdl)) + return error("Unknown token", w); + return resolve(); +} + +static void +review() +{ + int i; + for(i = 0; i < p.llen; i++) + if(p.labels[i].name[0] >= 'A' && p.labels[i].name[0] <= 'Z') + continue; /* Ignore capitalized labels(devices) */ + else if(!p.labels[i].refs) + fprintf(p.labels[i].name, "-- Unused label: %s\n", ConsoleOut()); + fprintf( + p.mlen, p.llen, p.length-TRIM + "Assembled %d bytes, %d labels, %d macros.\n", ConsoleOut()); +} + +int +uxntal(int srchdl) +{ + if(!assemble(srchdl)) + return !error("Assembly", "Failed to assemble rom."); + if(p.length <= TRIM) + return !error("Assembly", "Output rom is empty."); + fwrite(p.data + TRIM, p.length - TRIM, 1, StdOut()); + return 0; +} diff --git a/fs/asm/uxntal.fs b/fs/asm/uxntal.fs @@ -0,0 +1,2 @@ +?f<< /comp/c/lib.fs +cc<< /asm/uxntal.c diff --git a/fs/comp/c/lib.fs b/fs/comp/c/lib.fs @@ -23,7 +23,7 @@ :c void memset(char *a, char c, unsigned int n); : fgetc IO :getc ; -:c char fgetc(int hdl); +:c int fgetc(int hdl); : fputc IO :putc ; :c void fputc(char c, int hdl); : fputback IO :putback ; diff --git a/fs/doc/cc/lib.txt b/fs/doc/cc/lib.txt @@ -44,7 +44,7 @@ int isdigit(char c) The I/O API is a simple layer over the system I/O described at doc/io. `hdl` is a regular I/O handle. -char fgetc(int hdl) --> IO :getc +int fgetc(int hdl) --> IO :getc void fputc(char c, int hdl) --> IO :putc void fputback(char c, int hdl) --> IO :putback char* freadline(int hdl) --> IO :putback diff --git a/fs/doc/cc/usage.txt b/fs/doc/cc/usage.txt @@ -35,7 +35,7 @@ are a few differences: yields 0. This is also true of the "x ? y : z" operator. Both y and z are evaluated regardless of x. * Number literals are the same as Dusk OS, so 12345, $1234 and 'X'. No 0x1234 - or 0o777. + or 0o777. No '\n', '\r', '\t', '\0' char literals. * string literals are not null-terminated, but "counted strings". The exact same format as system strings. You can create a null-terminated string literal by appending a "0" next to the closing quote, for example "hello"0. This 0 shifts @@ -57,6 +57,7 @@ are a few differences: "a = (b = (c = 42))" and "a ? b ? c : d : e ? f : g" (seriously, you wanted to write that?!?) must be written as "a ? (b ? c : d) : (e ? f : g)" * The keyword "static" has a slightly different meaning. See below. +* No multi-dimensional array. ## Pre-processor diff --git a/fs/tests/asm/all.fs b/fs/tests/asm/all.fs @@ -1,3 +1,4 @@ \ Run all asm test suites f<< /tests/asm/i386.fs f<< /tests/asm/arm.fs +f<< /tests/asm/uxntal.fs diff --git a/fs/tests/asm/uxntal.fs b/fs/tests/asm/uxntal.fs @@ -0,0 +1,7 @@ +?f<< /tests/harness.fs +\ Doesn't compile yet +\ ?f<< /asm/uxntal.fs +testbegin +\ Tests for asm/uxntal.fs +testend + diff --git a/license/dll-uxn.txt b/license/dll-uxn.txt @@ -0,0 +1,8 @@ +Copyright (c) 2021 Devine Lu Linvega + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE.