2019-08-24 07:46:29 +00:00
|
|
|
// xasm 3.1.0 by Piotr Fusik <fox@scene.pl>
|
|
|
|
// http://xasm.atari.org
|
|
|
|
// Can be compiled with DMD v2.087.1.
|
|
|
|
|
|
|
|
// Poetic License:
|
|
|
|
//
|
|
|
|
// This work 'as-is' we provide.
|
|
|
|
// No warranty express or implied.
|
|
|
|
// We've done our best,
|
|
|
|
// to debug and test.
|
|
|
|
// Liability for damages denied.
|
|
|
|
//
|
|
|
|
// Permission is granted hereby,
|
|
|
|
// to copy, share, and modify.
|
|
|
|
// Use as is fit,
|
|
|
|
// free or for profit.
|
|
|
|
// These rights, on this notice, rely.
|
|
|
|
|
|
|
|
import std.algorithm;
|
|
|
|
import std.array;
|
|
|
|
import std.conv;
|
|
|
|
import std.math;
|
|
|
|
import std.path;
|
|
|
|
import std.stdio;
|
|
|
|
|
|
|
|
version (Windows) {
|
|
|
|
import core.sys.windows.windows;
|
|
|
|
}
|
|
|
|
|
|
|
|
int readByte(File *file) {
|
|
|
|
char c;
|
|
|
|
if (file.readf("%c", &c) != 1)
|
|
|
|
return -1;
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
|
|
|
const string TITLE = "xasm 3.1.0";
|
|
|
|
|
|
|
|
string sourceFilename = null;
|
|
|
|
bool[26] options;
|
|
|
|
string[26] optionParameters;
|
|
|
|
string[] commandLineDefinitions = null;
|
|
|
|
string makeTarget;
|
|
|
|
string[] makeSources = null;
|
|
|
|
|
|
|
|
int exitCode = 0;
|
|
|
|
|
|
|
|
int totalLines;
|
|
|
|
|
|
|
|
bool pass2 = false;
|
|
|
|
|
|
|
|
bool optionFill; // opt f
|
|
|
|
bool option5200; // opt g
|
|
|
|
bool optionHeaders; // opt h
|
|
|
|
bool optionListing; // opt l
|
|
|
|
bool optionObject; // opt o
|
|
|
|
bool optionUnusedLabels; // opt u
|
|
|
|
|
|
|
|
string currentFilename;
|
|
|
|
int lineNo;
|
|
|
|
int includeLevel = 0;
|
|
|
|
string line;
|
|
|
|
int column;
|
|
|
|
|
|
|
|
bool foundEnd;
|
|
|
|
|
|
|
|
class AssemblyError : Exception {
|
|
|
|
this(string msg) {
|
|
|
|
super(msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class Label {
|
|
|
|
int value;
|
|
|
|
bool unused = true;
|
|
|
|
bool unknownInPass1 = false;
|
|
|
|
bool passed = false;
|
|
|
|
|
|
|
|
this(int value) {
|
|
|
|
this.value = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Label[string] labelTable;
|
|
|
|
Label currentLabel;
|
|
|
|
|
|
|
|
alias int function(int a, int b) OperatorFunction;
|
|
|
|
|
|
|
|
bool inOpcode = false;
|
|
|
|
|
|
|
|
struct ValOp {
|
|
|
|
int value;
|
|
|
|
OperatorFunction func;
|
|
|
|
int priority;
|
|
|
|
}
|
|
|
|
|
|
|
|
ValOp[] valOpStack;
|
|
|
|
|
|
|
|
int value;
|
|
|
|
bool unknownInPass1;
|
|
|
|
|
|
|
|
enum AddrMode {
|
|
|
|
ACCUMULATOR = 0,
|
|
|
|
IMMEDIATE = 1,
|
|
|
|
ABSOLUTE = 2,
|
|
|
|
ZEROPAGE = 3,
|
|
|
|
ABSOLUTE_X = 4,
|
|
|
|
ZEROPAGE_X = 5,
|
|
|
|
ABSOLUTE_Y = 6,
|
|
|
|
ZEROPAGE_Y = 7,
|
|
|
|
INDIRECT_X = 8,
|
|
|
|
INDIRECT_Y = 9,
|
|
|
|
INDIRECT = 10,
|
|
|
|
ABS_OR_ZP = 11, // temporarily used in readAddrMode
|
|
|
|
STANDARD_MASK = 15,
|
|
|
|
INCREMENT = 0x20,
|
|
|
|
DECREMENT = 0x30,
|
|
|
|
ZERO = 0x40
|
|
|
|
}
|
|
|
|
|
|
|
|
AddrMode addrMode;
|
|
|
|
|
|
|
|
enum OrgModifier {
|
|
|
|
NONE,
|
|
|
|
FORCE_HEADER,
|
|
|
|
FORCE_FFFF,
|
|
|
|
RELOCATE
|
|
|
|
}
|
|
|
|
|
|
|
|
int origin = -1;
|
|
|
|
int loadOrigin;
|
|
|
|
int loadingOrigin;
|
|
|
|
ushort[] blockEnds;
|
|
|
|
int blockIndex;
|
|
|
|
|
|
|
|
bool repeating; // line
|
|
|
|
int repeatCounter; // line
|
|
|
|
|
|
|
|
bool instructionBegin;
|
|
|
|
bool pairing;
|
|
|
|
|
|
|
|
bool willSkip;
|
|
|
|
bool skipping;
|
|
|
|
ushort[] skipOffsets;
|
|
|
|
int skipOffsetsIndex = 0;
|
|
|
|
|
|
|
|
int repeatOffset; // instruction repeat
|
|
|
|
|
|
|
|
bool wereManyInstructions;
|
|
|
|
|
|
|
|
alias void function(int move) MoveFunction;
|
|
|
|
|
|
|
|
int value1;
|
|
|
|
AddrMode addrMode1;
|
|
|
|
int value2;
|
|
|
|
AddrMode addrMode2;
|
|
|
|
|
|
|
|
struct IfContext {
|
|
|
|
bool condition;
|
|
|
|
bool wasElse;
|
|
|
|
bool aConditionMatched;
|
|
|
|
}
|
|
|
|
|
|
|
|
IfContext[] ifContexts;
|
|
|
|
|
|
|
|
File listingStream;
|
|
|
|
char[32] listingLine;
|
|
|
|
int listingColumn;
|
|
|
|
string lastListedFilename = null;
|
|
|
|
|
|
|
|
File objectStream;
|
|
|
|
|
|
|
|
int objectBytes = 0;
|
|
|
|
|
|
|
|
bool getOption(char letter) {
|
|
|
|
assert(letter >= 'a' && letter <= 'z');
|
|
|
|
return options[letter - 'a'];
|
|
|
|
}
|
|
|
|
|
|
|
|
void warning(string msg, bool error = false) {
|
|
|
|
stdout.flush();
|
|
|
|
version (Windows) {
|
|
|
|
HANDLE stderrHandle = GetStdHandle(STD_ERROR_HANDLE);
|
|
|
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
|
|
|
GetConsoleScreenBufferInfo(stderrHandle, &csbi);
|
|
|
|
SetConsoleTextAttribute(stderrHandle, (csbi.wAttributes & ~0xf) | (error ? 12 : 14));
|
|
|
|
scope (exit) SetConsoleTextAttribute(stderrHandle, csbi.wAttributes);
|
|
|
|
}
|
|
|
|
if (line !is null)
|
|
|
|
stderr.writeln(line);
|
|
|
|
stderr.writefln("%s (%d) %s: %s", currentFilename, lineNo, error ? "ERROR" : "WARNING", msg);
|
|
|
|
exitCode = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void illegalCharacter() {
|
|
|
|
throw new AssemblyError("Illegal character");
|
|
|
|
}
|
|
|
|
|
|
|
|
bool eol() {
|
|
|
|
return column >= line.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
char readChar() {
|
|
|
|
if (eol())
|
|
|
|
throw new AssemblyError("Unexpected end of line");
|
|
|
|
return line[column++];
|
|
|
|
}
|
|
|
|
|
|
|
|
int readDigit(int base) {
|
|
|
|
if (eol()) return -1;
|
|
|
|
int r = line[column];
|
|
|
|
if (r >= '0' && r <= '9') {
|
|
|
|
r -= '0';
|
|
|
|
} else {
|
|
|
|
r &= 0xdf;
|
|
|
|
if (r >= 'A' && r <= 'Z')
|
|
|
|
r -= 'A' - 10;
|
|
|
|
else
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (r < base) {
|
|
|
|
column++;
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int readNumber(int base) {
|
|
|
|
long r = readDigit(base);
|
|
|
|
if (r < 0)
|
|
|
|
illegalCharacter();
|
|
|
|
do {
|
|
|
|
int d = readDigit(base);
|
|
|
|
if (d < 0)
|
|
|
|
return cast(int) r;
|
|
|
|
r = r * base + d;
|
|
|
|
} while (r <= 0x7fffffff);
|
|
|
|
throw new AssemblyError("Number too big");
|
|
|
|
}
|
|
|
|
|
|
|
|
void readSpaces() {
|
|
|
|
switch (readChar()) {
|
|
|
|
case '\t':
|
|
|
|
case ' ':
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new AssemblyError("Space expected");
|
|
|
|
}
|
|
|
|
while (!eol()) {
|
|
|
|
switch (line[column]) {
|
|
|
|
case '\t':
|
|
|
|
case ' ':
|
|
|
|
column++;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
string readLabel() {
|
|
|
|
string label;
|
|
|
|
while (!eol()) {
|
|
|
|
char c = line[column++];
|
|
|
|
if (c >= '0' && c <= '9' || c == '_') {
|
|
|
|
label ~= c;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
c &= 0xdf;
|
|
|
|
if (c >= 'A' && c <= 'Z') {
|
|
|
|
label ~= c;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
column--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return label >= "A" ? label : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
void readComma() {
|
|
|
|
if (readChar() != ',')
|
|
|
|
throw new AssemblyError("Bad or missing function parameter");
|
|
|
|
}
|
|
|
|
|
|
|
|
string readInstruction() {
|
|
|
|
string r;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
char c = readChar() & 0xdf;
|
|
|
|
if (c < 'A' || c > 'Z')
|
|
|
|
throw new AssemblyError("Illegal instruction");
|
|
|
|
r ~= c;
|
|
|
|
}
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
string readFunction() {
|
|
|
|
if (column + 5 >= line.length) return null;
|
|
|
|
if (line[column + 3] != '(') return null;
|
|
|
|
string r;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
char c = line[column + i] & 0xdf;
|
|
|
|
if (c < 'A' || c > 'Z') return null;
|
|
|
|
r ~= c;
|
|
|
|
}
|
|
|
|
column += 4;
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
string readFilename() {
|
|
|
|
readSpaces();
|
|
|
|
char delimiter = readChar();
|
|
|
|
switch (delimiter) {
|
|
|
|
case '"':
|
|
|
|
case '\'':
|
|
|
|
string filename;
|
|
|
|
char c;
|
|
|
|
while ((c = readChar()) != delimiter)
|
|
|
|
filename ~= c;
|
|
|
|
return filename;
|
|
|
|
default:
|
|
|
|
illegalCharacter();
|
|
|
|
}
|
|
|
|
assert(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void readStringChar(char c) {
|
|
|
|
if (readChar() != c)
|
|
|
|
throw new AssemblyError("String error");
|
|
|
|
}
|
|
|
|
|
|
|
|
ubyte[] readString() {
|
|
|
|
if (eol()) return null;
|
|
|
|
char delimiter = readChar();
|
|
|
|
switch (delimiter) {
|
|
|
|
case '"':
|
|
|
|
case '\'':
|
|
|
|
ubyte[] r;
|
|
|
|
for (;;) {
|
|
|
|
char c = readChar();
|
|
|
|
if (c == delimiter) {
|
|
|
|
if (eol()) return r;
|
|
|
|
if (line[column] != delimiter) {
|
|
|
|
if (line[column] == '*') {
|
|
|
|
column++;
|
|
|
|
foreach (ref ubyte b; r)
|
|
|
|
b ^= 0x80;
|
|
|
|
}
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
column++;
|
|
|
|
}
|
|
|
|
r ~= cast(ubyte) c;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
column--;
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void checkNoExtraCharacters() {
|
|
|
|
if (eol()) return;
|
|
|
|
switch (line[column]) {
|
|
|
|
case '\t':
|
|
|
|
case ' ':
|
|
|
|
return;
|
|
|
|
default:
|
|
|
|
throw new AssemblyError("Extra characters on line");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void checkOriginDefined() {
|
|
|
|
if (origin < 0)
|
|
|
|
throw new AssemblyError("No ORG specified");
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorPlus(int a, int b) {
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorMinus(int a, int b) {
|
|
|
|
return -b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorLow(int a, int b) {
|
|
|
|
return b & 0xff;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorHigh(int a, int b) {
|
|
|
|
return (b >> 8) & 0xff;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorLogicalNot(int a, int b) {
|
|
|
|
return !b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorBitwiseNot(int a, int b) {
|
|
|
|
return ~b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorAdd(int a, int b) {
|
|
|
|
long r = cast(long) a + b;
|
|
|
|
if (r < -0x80000000L || r > 0x7fffffffL)
|
|
|
|
throw new AssemblyError("Arithmetic overflow");
|
|
|
|
return a + b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorSubtract(int a, int b) {
|
|
|
|
long r = cast(long) a - b;
|
|
|
|
if (r < -0x80000000L || r > 0x7fffffffL)
|
|
|
|
throw new AssemblyError("Arithmetic overflow");
|
|
|
|
return a - b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorMultiply(int a, int b) {
|
|
|
|
long r = cast(long) a * b;
|
|
|
|
if (r < -0x80000000L || r > 0x7fffffffL)
|
|
|
|
throw new AssemblyError("Arithmetic overflow");
|
|
|
|
return a * b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorDivide(int a, int b) {
|
|
|
|
if (b == 0)
|
|
|
|
throw new AssemblyError("Divide by zero");
|
|
|
|
return a / b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorModulus(int a, int b) {
|
|
|
|
if (b == 0)
|
|
|
|
throw new AssemblyError("Divide by zero");
|
|
|
|
return a % b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorAnd(int a, int b) {
|
|
|
|
return a & b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorOr(int a, int b) {
|
|
|
|
return a | b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorXor(int a, int b) {
|
|
|
|
return a ^ b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorEqual(int a, int b) {
|
|
|
|
return a == b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorNotEqual(int a, int b) {
|
|
|
|
return a != b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorLess(int a, int b) {
|
|
|
|
return a < b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorGreater(int a, int b) {
|
|
|
|
return a > b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorLessEqual(int a, int b) {
|
|
|
|
return a <= b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorGreaterEqual(int a, int b) {
|
|
|
|
return a >= b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorShiftLeft(int a, int b) {
|
|
|
|
if (b < 0)
|
|
|
|
return operatorShiftRight(a, -b);
|
|
|
|
if (a != 0 && b >= 32)
|
|
|
|
throw new AssemblyError("Arithmetic overflow");
|
|
|
|
long r = cast(long) a << b;
|
|
|
|
if (r & 0xffffffff00000000L)
|
|
|
|
throw new AssemblyError("Arithmetic overflow");
|
|
|
|
return a << b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorShiftRight(int a, int b) {
|
|
|
|
if (b < 0)
|
|
|
|
return operatorShiftLeft(a, -b);
|
|
|
|
if (b >= 32)
|
|
|
|
b = 31;
|
|
|
|
return a >> b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorLogicalAnd(int a, int b) {
|
|
|
|
return a && b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int operatorLogicalOr(int a, int b) {
|
|
|
|
return a || b;
|
|
|
|
}
|
|
|
|
|
|
|
|
void pushValOp(int value, OperatorFunction func, int priority) {
|
|
|
|
ValOp valOp;
|
|
|
|
valOp.value = value;
|
|
|
|
valOp.func = func;
|
|
|
|
valOp.priority = priority;
|
|
|
|
valOpStack ~= valOp;
|
|
|
|
}
|
|
|
|
|
|
|
|
void readValue() {
|
|
|
|
assert(valOpStack.length == 0);
|
|
|
|
unknownInPass1 = false;
|
|
|
|
int priority = 0;
|
|
|
|
do {
|
|
|
|
int operand;
|
|
|
|
char c = readChar();
|
|
|
|
switch (c) {
|
|
|
|
case '[':
|
|
|
|
priority += 10;
|
|
|
|
continue;
|
|
|
|
case '+':
|
|
|
|
pushValOp(0, &operatorPlus, priority + 8);
|
|
|
|
continue;
|
|
|
|
case '-':
|
|
|
|
pushValOp(0, &operatorMinus, priority + 8);
|
|
|
|
continue;
|
|
|
|
case '<':
|
|
|
|
pushValOp(0, &operatorLow, priority + 8);
|
|
|
|
continue;
|
|
|
|
case '>':
|
|
|
|
pushValOp(0, &operatorHigh, priority + 8);
|
|
|
|
continue;
|
|
|
|
case '!':
|
|
|
|
pushValOp(0, &operatorLogicalNot, priority + 4);
|
|
|
|
continue;
|
|
|
|
case '~':
|
|
|
|
pushValOp(0, &operatorBitwiseNot, priority + 8);
|
|
|
|
continue;
|
|
|
|
case '(':
|
|
|
|
throw new AssemblyError("Use square brackets instead");
|
|
|
|
case '*':
|
|
|
|
checkOriginDefined();
|
|
|
|
operand = origin;
|
|
|
|
break;
|
|
|
|
case '#':
|
|
|
|
if (!repeating)
|
|
|
|
throw new AssemblyError("'#' is allowed only in repeated lines");
|
|
|
|
operand = repeatCounter;
|
|
|
|
break;
|
|
|
|
case '\'':
|
|
|
|
case '"':
|
|
|
|
operand = readChar();
|
|
|
|
if (operand == c)
|
|
|
|
readStringChar(c);
|
|
|
|
readStringChar(c);
|
|
|
|
if (!eol() && line[column] == '*') {
|
|
|
|
column++;
|
|
|
|
operand ^= 0x80;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case '^':
|
|
|
|
switch (readChar()) {
|
|
|
|
case '0':
|
|
|
|
operand = option5200 ? 0xc000 : 0xd000;
|
|
|
|
break;
|
|
|
|
case '1':
|
|
|
|
operand = option5200 ? 0xc010 : 0xd010;
|
|
|
|
break;
|
|
|
|
case '2':
|
|
|
|
operand = option5200 ? 0xe800 : 0xd200;
|
|
|
|
break;
|
|
|
|
case '3':
|
|
|
|
if (option5200)
|
|
|
|
throw new AssemblyError("There's no PIA chip in Atari 5200");
|
|
|
|
operand = 0xd300;
|
|
|
|
break;
|
|
|
|
case '4':
|
|
|
|
operand = 0xd400;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
illegalCharacter();
|
|
|
|
}
|
|
|
|
int d = readDigit(16);
|
|
|
|
if (d < 0)
|
|
|
|
illegalCharacter();
|
|
|
|
operand += d;
|
|
|
|
break;
|
|
|
|
case '{':
|
|
|
|
if (inOpcode)
|
|
|
|
throw new AssemblyError("Nested opcodes not supported");
|
|
|
|
ValOp[] savedValOpStack = valOpStack;
|
|
|
|
AddrMode savedAddrMode = addrMode;
|
|
|
|
bool savedUnknownInPass1 = unknownInPass1;
|
|
|
|
bool savedInstructionBegin = instructionBegin;
|
|
|
|
valOpStack.length = 0;
|
|
|
|
inOpcode = true;
|
|
|
|
assemblyInstruction(readInstruction());
|
|
|
|
if (readChar() != '}')
|
|
|
|
throw new AssemblyError("Missing '}'");
|
|
|
|
assert(!instructionBegin);
|
|
|
|
inOpcode = false;
|
|
|
|
valOpStack = savedValOpStack;
|
|
|
|
addrMode = savedAddrMode;
|
|
|
|
unknownInPass1 = savedUnknownInPass1;
|
|
|
|
instructionBegin = savedInstructionBegin;
|
|
|
|
operand = value;
|
|
|
|
break;
|
|
|
|
case '$':
|
|
|
|
operand = readNumber(16);
|
|
|
|
break;
|
|
|
|
case '%':
|
|
|
|
operand = readNumber(2);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
column--;
|
|
|
|
if (c >= '0' && c <= '9') {
|
|
|
|
operand = readNumber(10);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
string label = readLabel();
|
|
|
|
if (label is null)
|
|
|
|
illegalCharacter();
|
|
|
|
if (label in labelTable) {
|
|
|
|
Label l = labelTable[label];
|
|
|
|
operand = l.value;
|
|
|
|
l.unused = false;
|
|
|
|
if (pass2) {
|
|
|
|
if (l.passed) {
|
|
|
|
if (l.unknownInPass1)
|
|
|
|
unknownInPass1 = true;
|
|
|
|
} else {
|
|
|
|
if (l.unknownInPass1)
|
|
|
|
throw new AssemblyError("Illegal forward reference");
|
|
|
|
unknownInPass1 = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (l.unknownInPass1)
|
|
|
|
unknownInPass1 = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (pass2)
|
|
|
|
throw new AssemblyError("Undeclared label: " ~ label);
|
|
|
|
unknownInPass1 = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
while (!eol() && line[column] == ']') {
|
|
|
|
column++;
|
|
|
|
priority -= 10;
|
|
|
|
if (priority < 0)
|
|
|
|
throw new AssemblyError("Unmatched bracket");
|
|
|
|
}
|
|
|
|
if (eol()) {
|
|
|
|
if (priority != 0)
|
|
|
|
throw new AssemblyError("Unmatched bracket");
|
|
|
|
pushValOp(operand, &operatorPlus, 1);
|
|
|
|
} else {
|
|
|
|
switch (line[column++]) {
|
|
|
|
case '+':
|
|
|
|
pushValOp(operand, &operatorAdd, priority + 6);
|
|
|
|
break;
|
|
|
|
case '-':
|
|
|
|
pushValOp(operand, &operatorSubtract, priority + 6);
|
|
|
|
break;
|
|
|
|
case '*':
|
|
|
|
pushValOp(operand, &operatorMultiply, priority + 7);
|
|
|
|
break;
|
|
|
|
case '/':
|
|
|
|
pushValOp(operand, &operatorDivide, priority + 7);
|
|
|
|
break;
|
|
|
|
case '%':
|
|
|
|
pushValOp(operand, &operatorModulus, priority + 7);
|
|
|
|
break;
|
|
|
|
case '<':
|
|
|
|
switch (readChar()) {
|
|
|
|
case '<':
|
|
|
|
pushValOp(operand, &operatorShiftLeft, priority + 7);
|
|
|
|
break;
|
|
|
|
case '=':
|
|
|
|
pushValOp(operand, &operatorLessEqual, priority + 5);
|
|
|
|
break;
|
|
|
|
case '>':
|
|
|
|
pushValOp(operand, &operatorNotEqual, priority + 5);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
column--;
|
|
|
|
pushValOp(operand, &operatorLess, priority + 5);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case '=':
|
|
|
|
switch (readChar()) {
|
|
|
|
default:
|
|
|
|
column--;
|
|
|
|
goto case '=';
|
|
|
|
case '=':
|
|
|
|
pushValOp(operand, &operatorEqual, priority + 5);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case '>':
|
|
|
|
switch (readChar()) {
|
|
|
|
case '>':
|
|
|
|
pushValOp(operand, &operatorShiftRight, priority + 7);
|
|
|
|
break;
|
|
|
|
case '=':
|
|
|
|
pushValOp(operand, &operatorGreaterEqual, priority + 5);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
column--;
|
|
|
|
pushValOp(operand, &operatorGreater, priority + 5);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case '!':
|
|
|
|
switch (readChar()) {
|
|
|
|
case '=':
|
|
|
|
pushValOp(operand, &operatorNotEqual, priority + 5);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
illegalCharacter();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case '&':
|
|
|
|
switch (readChar()) {
|
|
|
|
case '&':
|
|
|
|
pushValOp(operand, &operatorLogicalAnd, priority + 3);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
column--;
|
|
|
|
pushValOp(operand, &operatorAnd, priority + 7);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case '|':
|
|
|
|
switch (readChar()) {
|
|
|
|
case '|':
|
|
|
|
pushValOp(operand, &operatorLogicalOr, priority + 2);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
column--;
|
|
|
|
pushValOp(operand, &operatorOr, priority + 6);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case '^':
|
|
|
|
pushValOp(operand, &operatorXor, priority + 6);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
column--;
|
|
|
|
if (priority != 0)
|
|
|
|
throw new AssemblyError("Unmatched bracket");
|
|
|
|
pushValOp(operand, &operatorPlus, 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (;;) {
|
|
|
|
immutable sp = valOpStack.length - 1;
|
|
|
|
if (sp <= 0 || valOpStack[sp].priority > valOpStack[sp - 1].priority)
|
|
|
|
break;
|
|
|
|
int operand1 = valOpStack[sp - 1].value;
|
|
|
|
OperatorFunction func1 = valOpStack[sp - 1].func;
|
|
|
|
valOpStack[sp - 1] = valOpStack[sp];
|
|
|
|
valOpStack.length = sp;
|
|
|
|
if (pass2 || !unknownInPass1) { // skip operations if unknown operands
|
|
|
|
valOpStack[sp - 1].value = func1(operand1, valOpStack[sp - 1].value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (valOpStack.length != 1 || valOpStack[0].priority != 1);
|
|
|
|
value = valOpStack[0].value;
|
|
|
|
valOpStack.length = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
debug int testValue(string l) {
|
|
|
|
line = l;
|
|
|
|
column = 0;
|
|
|
|
readValue();
|
|
|
|
writefln("Value of %s is %x", l, value);
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
unittest {
|
|
|
|
assert(testValue("123") == 123);
|
|
|
|
assert(testValue("$1234abCd") == 0x1234abcd);
|
|
|
|
assert(testValue("%101") == 5);
|
|
|
|
assert(testValue("^07") == 0xd007);
|
|
|
|
assert(testValue("^1f") == 0xd01f);
|
|
|
|
assert(testValue("^23") == 0xd203);
|
|
|
|
assert(testValue("^31") == 0xd301);
|
|
|
|
assert(testValue("^49") == 0xd409);
|
|
|
|
assert(testValue("!0") == 1);
|
|
|
|
assert(testValue("<$1234") == 0x34);
|
|
|
|
assert(testValue(">$1234567") == 0x45);
|
|
|
|
assert(testValue("1+2") == 3);
|
|
|
|
assert(testValue("1+2*3") == 7);
|
|
|
|
assert(testValue("[1+2]*3") == 9);
|
|
|
|
assert(testValue("{nop}") == 0xea);
|
|
|
|
assert(testValue("{CLC}+{sec}") == 0x50);
|
|
|
|
assert(testValue("{Jsr}") == 0x20);
|
|
|
|
assert(testValue("{bit a:}") == 0x2c);
|
|
|
|
assert(testValue("{bIt $7d}") == 0x24);
|
|
|
|
}
|
|
|
|
|
|
|
|
void mustBeKnownInPass1() {
|
|
|
|
if (unknownInPass1)
|
|
|
|
throw new AssemblyError("Label not defined before");
|
|
|
|
}
|
|
|
|
|
|
|
|
void readWord() {
|
|
|
|
readValue();
|
|
|
|
if ((!unknownInPass1 || pass2) && (value < -0xffff || value > 0xffff))
|
|
|
|
throw new AssemblyError("Value out of range");
|
|
|
|
}
|
|
|
|
|
|
|
|
void readUnsignedWord() {
|
|
|
|
readWord();
|
|
|
|
if ((!unknownInPass1 || pass2) && value < 0)
|
|
|
|
throw new AssemblyError("Value out of range");
|
|
|
|
}
|
|
|
|
|
|
|
|
void readKnownPositive() {
|
|
|
|
readValue();
|
|
|
|
mustBeKnownInPass1();
|
|
|
|
if (value <= 0)
|
|
|
|
throw new AssemblyError("Value out of range");
|
|
|
|
}
|
|
|
|
|
|
|
|
void optionalIncDec() {
|
|
|
|
if (eol()) return;
|
|
|
|
switch (line[column]) {
|
|
|
|
case '+':
|
|
|
|
column++;
|
|
|
|
addrMode += AddrMode.INCREMENT;
|
|
|
|
return;
|
|
|
|
case '-':
|
|
|
|
column++;
|
|
|
|
addrMode += AddrMode.DECREMENT;
|
|
|
|
return;
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void readAddrMode() {
|
|
|
|
readSpaces();
|
|
|
|
char c = readChar();
|
|
|
|
switch (c) {
|
|
|
|
case '@':
|
|
|
|
addrMode = AddrMode.ACCUMULATOR;
|
|
|
|
return;
|
|
|
|
case '#':
|
|
|
|
case '<':
|
|
|
|
case '>':
|
|
|
|
addrMode = AddrMode.IMMEDIATE;
|
|
|
|
if (inOpcode && line[column] == '}')
|
|
|
|
return;
|
|
|
|
readWord();
|
|
|
|
final switch (c) {
|
|
|
|
case '#':
|
|
|
|
break;
|
|
|
|
case '<':
|
|
|
|
value &= 0xff;
|
|
|
|
break;
|
|
|
|
case '>':
|
|
|
|
value = (value >> 8) & 0xff;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
case '(':
|
|
|
|
if (inOpcode) {
|
|
|
|
switch (readChar()) {
|
|
|
|
case ',':
|
|
|
|
switch (readChar()) {
|
|
|
|
case 'X':
|
|
|
|
case 'x':
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
illegalCharacter();
|
|
|
|
}
|
|
|
|
if (readChar() != ')')
|
|
|
|
throw new AssemblyError("Need parenthesis");
|
|
|
|
addrMode = AddrMode.INDIRECT_X;
|
|
|
|
return;
|
|
|
|
case ')':
|
|
|
|
if (readChar() == ',') {
|
|
|
|
switch (readChar()) {
|
|
|
|
case 'Y':
|
|
|
|
case 'y':
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
illegalCharacter();
|
|
|
|
}
|
|
|
|
addrMode = AddrMode.INDIRECT_Y;
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
column--;
|
|
|
|
addrMode = AddrMode.INDIRECT;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
column--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
readUnsignedWord();
|
|
|
|
switch (readChar()) {
|
|
|
|
case ',':
|
|
|
|
switch (readChar()) {
|
|
|
|
case 'X':
|
|
|
|
case 'x':
|
|
|
|
addrMode = AddrMode.INDIRECT_X;
|
|
|
|
break;
|
|
|
|
case '0':
|
|
|
|
addrMode = cast(AddrMode) (AddrMode.INDIRECT_X + AddrMode.ZERO);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
illegalCharacter();
|
|
|
|
}
|
|
|
|
if (readChar() != ')')
|
|
|
|
throw new AssemblyError("Need parenthesis");
|
|
|
|
return;
|
|
|
|
case ')':
|
|
|
|
if (eol()) {
|
|
|
|
addrMode = AddrMode.INDIRECT;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (line[column] == ',') {
|
|
|
|
column++;
|
|
|
|
switch (readChar()) {
|
|
|
|
case 'Y':
|
|
|
|
case 'y':
|
|
|
|
addrMode = AddrMode.INDIRECT_Y;
|
|
|
|
break;
|
|
|
|
case '0':
|
|
|
|
addrMode = cast(AddrMode) (AddrMode.INDIRECT_Y + AddrMode.ZERO);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
illegalCharacter();
|
|
|
|
}
|
|
|
|
optionalIncDec();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
addrMode = AddrMode.INDIRECT;
|
|
|
|
return;
|
|
|
|
default:
|
|
|
|
illegalCharacter();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'A':
|
|
|
|
case 'a':
|
|
|
|
if (!eol() && line[column] == ':') {
|
|
|
|
column++;
|
|
|
|
addrMode = AddrMode.ABSOLUTE;
|
|
|
|
} else {
|
|
|
|
addrMode = AddrMode.ABS_OR_ZP;
|
|
|
|
column--;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'Z':
|
|
|
|
case 'z':
|
|
|
|
if (!eol() && line[column] == ':') {
|
|
|
|
column++;
|
|
|
|
addrMode = AddrMode.ZEROPAGE;
|
|
|
|
} else {
|
|
|
|
addrMode = AddrMode.ABS_OR_ZP;
|
|
|
|
column--;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
addrMode = AddrMode.ABS_OR_ZP;
|
|
|
|
column--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// absolute or zeropage addressing, optionally indexed
|
|
|
|
if (inOpcode && (addrMode == AddrMode.ABSOLUTE || addrMode == AddrMode.ZEROPAGE)) {
|
|
|
|
switch (readChar()) {
|
|
|
|
case '}':
|
|
|
|
column--;
|
|
|
|
return;
|
|
|
|
case ',':
|
|
|
|
switch (readChar()) {
|
|
|
|
case 'X':
|
|
|
|
case 'x':
|
|
|
|
addrMode += cast(AddrMode) (AddrMode.ABSOLUTE_X - AddrMode.ABSOLUTE);
|
|
|
|
return;
|
|
|
|
case 'Y':
|
|
|
|
case 'y':
|
|
|
|
addrMode += cast(AddrMode) (AddrMode.ABSOLUTE_Y - AddrMode.ABSOLUTE);
|
|
|
|
return;
|
|
|
|
default:
|
|
|
|
illegalCharacter();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
default:
|
|
|
|
column--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
readUnsignedWord();
|
|
|
|
if (addrMode == AddrMode.ABS_OR_ZP) {
|
|
|
|
if (unknownInPass1 || value > 0xff)
|
|
|
|
addrMode = AddrMode.ABSOLUTE;
|
|
|
|
else
|
|
|
|
addrMode = AddrMode.ZEROPAGE;
|
|
|
|
}
|
|
|
|
if (eol()) return;
|
|
|
|
if (line[column] == ',') {
|
|
|
|
column++;
|
|
|
|
switch (readChar()) {
|
|
|
|
case 'X':
|
|
|
|
case 'x':
|
|
|
|
addrMode += cast(AddrMode) (AddrMode.ABSOLUTE_X - AddrMode.ABSOLUTE);
|
|
|
|
optionalIncDec();
|
|
|
|
return;
|
|
|
|
case 'Y':
|
|
|
|
case 'y':
|
|
|
|
addrMode += cast(AddrMode) (AddrMode.ABSOLUTE_Y - AddrMode.ABSOLUTE);
|
|
|
|
optionalIncDec();
|
|
|
|
return;
|
|
|
|
default:
|
|
|
|
illegalCharacter();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void readAbsoluteAddrMode() {
|
|
|
|
if (inOpcode && readChar() == '}') {
|
|
|
|
column--;
|
|
|
|
} else {
|
|
|
|
readAddrMode();
|
|
|
|
switch (addrMode) {
|
|
|
|
case AddrMode.ABSOLUTE:
|
|
|
|
case AddrMode.ZEROPAGE:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
illegalAddrMode();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
addrMode = AddrMode.ABSOLUTE;
|
|
|
|
}
|
|
|
|
|
|
|
|
debug AddrMode testAddrMode(string l) {
|
|
|
|
line = l;
|
|
|
|
column = 0;
|
|
|
|
readAddrMode();
|
|
|
|
writefln("Addressing mode of \"%s\" is %x", l, addrMode);
|
|
|
|
return addrMode;
|
|
|
|
}
|
|
|
|
|
|
|
|
unittest {
|
|
|
|
assert(testAddrMode(" @") == AddrMode.ACCUMULATOR);
|
|
|
|
assert(testAddrMode(" #0") == AddrMode.IMMEDIATE);
|
|
|
|
assert(testAddrMode(" $abc,x-") == cast(AddrMode) (AddrMode.ABSOLUTE_X + AddrMode.DECREMENT));
|
|
|
|
assert(testAddrMode(" $ab,Y+") == cast(AddrMode) (AddrMode.ZEROPAGE_Y + AddrMode.INCREMENT));
|
|
|
|
assert(testAddrMode(" (0,x)") == AddrMode.INDIRECT_X);
|
|
|
|
assert(testAddrMode(" ($ff),Y+") == cast(AddrMode) (AddrMode.INDIRECT_Y + AddrMode.INCREMENT));
|
|
|
|
assert(testAddrMode(" ($abcd)") == AddrMode.INDIRECT);
|
|
|
|
inOpcode = true;
|
|
|
|
assert(testAddrMode(" a:}") == AddrMode.ABSOLUTE);
|
|
|
|
assert(testAddrMode(" z:}") == AddrMode.ZEROPAGE);
|
|
|
|
assert(testAddrMode(" a:,x}") == AddrMode.ABSOLUTE_X);
|
|
|
|
assert(testAddrMode(" z:,y}") == AddrMode.ZEROPAGE_Y);
|
|
|
|
assert(testAddrMode(" (,X)}") == AddrMode.INDIRECT_X);
|
|
|
|
assert(testAddrMode(" (),y}") == AddrMode.INDIRECT_Y);
|
|
|
|
assert(testAddrMode(" ()}") == AddrMode.INDIRECT);
|
|
|
|
inOpcode = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool inFalseCondition() {
|
|
|
|
foreach (IfContext ic; ifContexts) {
|
|
|
|
if (!ic.condition) return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
string makeEscape(string s) {
|
|
|
|
return s.replace("$", "$$");
|
|
|
|
}
|
|
|
|
|
|
|
|
File openInputFile(string filename) {
|
|
|
|
if (find(makeSources, filename).empty)
|
|
|
|
makeSources ~= filename;
|
|
|
|
try {
|
|
|
|
return File(filename);
|
|
|
|
} catch (Exception e) {
|
|
|
|
throw new AssemblyError(e.msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
File openOutputFile(char letter, string defaultExt) {
|
|
|
|
string filename = optionParameters[letter - 'a'];
|
|
|
|
if (filename is null)
|
|
|
|
filename = sourceFilename.setExtension(defaultExt);
|
|
|
|
if (letter == 'o')
|
|
|
|
makeTarget = makeEscape(filename);
|
|
|
|
try {
|
|
|
|
return File(filename, "wb");
|
|
|
|
} catch (Exception e) {
|
|
|
|
throw new AssemblyError(e.msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ensureListingFileOpen(char letter, string msg) {
|
|
|
|
if (!listingStream.isOpen) {
|
|
|
|
listingStream = openOutputFile(letter, "lst");
|
|
|
|
if (!getOption('q'))
|
|
|
|
write(msg);
|
|
|
|
listingStream.writeln(TITLE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void listNibble(int x) {
|
|
|
|
listingLine[listingColumn++] = cast(char) (x <= 9 ? x + '0' : x + ('A' - 10));
|
|
|
|
}
|
|
|
|
|
|
|
|
void listByte(ubyte x) {
|
|
|
|
listNibble(x >> 4);
|
|
|
|
listNibble(x & 0xf);
|
|
|
|
}
|
|
|
|
|
|
|
|
void listWord(ushort x) {
|
|
|
|
listByte(cast(ubyte) (x >> 8));
|
|
|
|
listByte(cast(ubyte) x);
|
|
|
|
}
|
|
|
|
|
|
|
|
void listLine() {
|
|
|
|
if (!optionListing || !getOption('l') || line is null)
|
|
|
|
return;
|
|
|
|
assert(pass2);
|
|
|
|
if (inFalseCondition() && !getOption('c'))
|
|
|
|
return;
|
|
|
|
if (getOption('i') && includeLevel > 0)
|
|
|
|
return;
|
|
|
|
ensureListingFileOpen('l', "Writing listing file...\n");
|
|
|
|
if (currentFilename != lastListedFilename) {
|
|
|
|
listingStream.writeln("Source: ", currentFilename);
|
|
|
|
lastListedFilename = currentFilename;
|
|
|
|
}
|
|
|
|
int i = 4;
|
|
|
|
int x = lineNo;
|
|
|
|
while (x > 0 && i >= 0) {
|
|
|
|
listingLine[i--] = '0' + x % 10;
|
|
|
|
x /= 10;
|
|
|
|
}
|
|
|
|
while (i >= 0)
|
|
|
|
listingLine[i--] = ' ';
|
|
|
|
listingLine[5] = ' ';
|
|
|
|
while (listingColumn < 32)
|
|
|
|
listingLine[listingColumn++] = ' ';
|
|
|
|
listingStream.writefln("%.32s%s", listingLine, line);
|
|
|
|
}
|
|
|
|
|
|
|
|
void listCommentLine() {
|
|
|
|
if (currentLabel is null)
|
|
|
|
listingColumn = 6;
|
|
|
|
else {
|
|
|
|
assert(!inFalseCondition());
|
|
|
|
checkOriginDefined();
|
|
|
|
}
|
|
|
|
listLine();
|
|
|
|
}
|
|
|
|
|
|
|
|
void listLabelTable() {
|
|