mirror of
https://github.com/pfusik/xasm.git
synced 2024-12-22 00:29:15 +00:00
d7c22cec84
Close #1.
3022 lines
62 KiB
D
3022 lines
62 KiB
D
// xasm 3.1.0 by Piotr Fusik <fox@scene.pl>
|
|
// http://xasm.atari.org
|
|
// Can be compiled with DMD v2.061.
|
|
|
|
// 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 std.c.windows.windows;
|
|
extern (Windows) HANDLE GetStdHandle(DWORD nStdHandle);
|
|
}
|
|
|
|
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() {
|
|
if (optionParameters['t' - 'a'] !is null && listingStream.isOpen)
|
|
listingStream.close();
|
|
ensureListingFileOpen('t', "Writing label table...\n");
|
|
listingStream.writeln("Label table:");
|
|
foreach (string name; labelTable.keys.sort) {
|
|
Label l = labelTable[name];
|
|
listingStream.write(l.unused ? 'n' : ' ');
|
|
listingStream.write(l.unknownInPass1 ? '2' : ' ');
|
|
int value = l.value;
|
|
char sign = ' ';
|
|
if (value < 0) {
|
|
sign = '-';
|
|
value = -value;
|
|
}
|
|
listingStream.writefln(
|
|
(l.value & 0xffff0000) != 0 ? " %s%08X %s" : " %s%04X %s",
|
|
sign, value, name);
|
|
}
|
|
}
|
|
|
|
debug ubyte[] objectBuffer;
|
|
|
|
void objectByte(ubyte b) {
|
|
debug {
|
|
objectBuffer ~= b;
|
|
} else {
|
|
assert(pass2);
|
|
if (!optionObject) return;
|
|
if (!objectStream.isOpen) {
|
|
objectStream = openOutputFile('o', "obx");
|
|
if (!getOption('q'))
|
|
writeln("Writing object file...");
|
|
}
|
|
try {
|
|
objectStream.write(cast(char) b);
|
|
} catch (Exception e) {
|
|
throw new AssemblyError("Error writing object file");
|
|
}
|
|
}
|
|
objectBytes++;
|
|
}
|
|
|
|
void objectWord(ushort w) {
|
|
objectByte(cast(ubyte) w);
|
|
objectByte(cast(ubyte) (w >> 8));
|
|
}
|
|
|
|
void putByte(ubyte b) {
|
|
if (inOpcode) {
|
|
if (instructionBegin) {
|
|
value = b;
|
|
instructionBegin = false;
|
|
}
|
|
return;
|
|
}
|
|
if (willSkip) {
|
|
assert(!pass2);
|
|
willSkip = false;
|
|
skipping = true;
|
|
}
|
|
if (skipping) {
|
|
assert(!pass2);
|
|
skipOffsets[$ - 1]++;
|
|
}
|
|
if (instructionBegin) {
|
|
repeatOffset = -2;
|
|
instructionBegin = false;
|
|
}
|
|
repeatOffset--;
|
|
if (optionFill && loadingOrigin >= 0 && loadingOrigin != loadOrigin) {
|
|
if (loadingOrigin > loadOrigin)
|
|
throw new AssemblyError("Can't fill from higher to lower memory location");
|
|
if (pass2) {
|
|
while (loadingOrigin < loadOrigin) {
|
|
objectByte(0xff);
|
|
loadingOrigin++;
|
|
}
|
|
}
|
|
}
|
|
debug {
|
|
objectByte(b);
|
|
}
|
|
if (pass2) {
|
|
debug {
|
|
}else {
|
|
objectByte(b);
|
|
}
|
|
if (listingColumn < 29) {
|
|
listByte(b);
|
|
listingLine[listingColumn++] = ' ';
|
|
} else {
|
|
listingLine[29] = '+';
|
|
listingColumn = 30;
|
|
}
|
|
}
|
|
if (optionHeaders) {
|
|
if (origin < 0)
|
|
throw new AssemblyError("No ORG specified");
|
|
assert(blockIndex >= 0);
|
|
if (!pass2) {
|
|
blockEnds[blockIndex] = cast(ushort) loadOrigin;
|
|
}
|
|
}
|
|
if (origin >= 0) {
|
|
origin++;
|
|
loadingOrigin = ++loadOrigin;
|
|
}
|
|
}
|
|
|
|
void putWord(ushort w) {
|
|
putByte(cast(ubyte) w);
|
|
putByte(cast(ubyte) (w >> 8));
|
|
}
|
|
|
|
void putCommand(ubyte b) {
|
|
putByte(b);
|
|
if (inOpcode) return;
|
|
switch (addrMode & AddrMode.STANDARD_MASK) {
|
|
case AddrMode.ACCUMULATOR:
|
|
break;
|
|
case AddrMode.IMMEDIATE:
|
|
case AddrMode.ZEROPAGE:
|
|
case AddrMode.ZEROPAGE_X:
|
|
case AddrMode.ZEROPAGE_Y:
|
|
case AddrMode.INDIRECT_X:
|
|
case AddrMode.INDIRECT_Y:
|
|
if (pass2 && (value < -0xff || value > 0xff))
|
|
throw new AssemblyError("Value out of range");
|
|
putByte(cast(ubyte) value);
|
|
break;
|
|
case AddrMode.ABSOLUTE:
|
|
case AddrMode.ABSOLUTE_X:
|
|
case AddrMode.ABSOLUTE_Y:
|
|
case AddrMode.INDIRECT:
|
|
putWord(cast(ushort) value);
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
switch (addrMode) {
|
|
case cast(AddrMode) (AddrMode.ABSOLUTE_X + AddrMode.INCREMENT):
|
|
case cast(AddrMode) (AddrMode.ZEROPAGE_X + AddrMode.INCREMENT):
|
|
putByte(0xe8);
|
|
break;
|
|
case cast(AddrMode) (AddrMode.ABSOLUTE_X + AddrMode.DECREMENT):
|
|
case cast(AddrMode) (AddrMode.ZEROPAGE_X + AddrMode.DECREMENT):
|
|
putByte(0xca);
|
|
break;
|
|
case cast(AddrMode) (AddrMode.ABSOLUTE_Y + AddrMode.INCREMENT):
|
|
case cast(AddrMode) (AddrMode.ZEROPAGE_Y + AddrMode.INCREMENT):
|
|
case cast(AddrMode) (AddrMode.INDIRECT_Y + AddrMode.INCREMENT):
|
|
case cast(AddrMode) (AddrMode.INDIRECT_Y + AddrMode.INCREMENT + AddrMode.ZERO):
|
|
putByte(0xc8);
|
|
break;
|
|
case cast(AddrMode) (AddrMode.ABSOLUTE_Y + AddrMode.DECREMENT):
|
|
case cast(AddrMode) (AddrMode.ZEROPAGE_Y + AddrMode.DECREMENT):
|
|
case cast(AddrMode) (AddrMode.INDIRECT_Y + AddrMode.DECREMENT):
|
|
case cast(AddrMode) (AddrMode.INDIRECT_Y + AddrMode.DECREMENT + AddrMode.ZERO):
|
|
putByte(0x88);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void noOpcode() {
|
|
if (inOpcode)
|
|
throw new AssemblyError("Can't get opcode of this");
|
|
}
|
|
|
|
void directive() {
|
|
noOpcode();
|
|
if (repeating)
|
|
throw new AssemblyError("Can't repeat this directive");
|
|
if (pairing)
|
|
throw new AssemblyError("Can't pair this directive");
|
|
}
|
|
|
|
void noRepeatSkipDirective() {
|
|
directive();
|
|
if (willSkip)
|
|
throw new AssemblyError("Can't skip over this");
|
|
repeatOffset = 0;
|
|
}
|
|
|
|
void illegalAddrMode() {
|
|
throw new AssemblyError("Illegal addressing mode");
|
|
}
|
|
|
|
void addrModeForMove(int move) {
|
|
final switch (move) {
|
|
case 0:
|
|
readAddrMode();
|
|
break;
|
|
case 1:
|
|
value = value1;
|
|
addrMode = addrMode1;
|
|
break;
|
|
case 2:
|
|
value = value2;
|
|
addrMode = addrMode2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void assemblyAccumulator(ubyte b, ubyte prefix, int move) {
|
|
addrModeForMove(move);
|
|
if (prefix != 0)
|
|
putByte(prefix);
|
|
switch (addrMode & AddrMode.STANDARD_MASK) {
|
|
case AddrMode.ACCUMULATOR:
|
|
case AddrMode.INDIRECT:
|
|
illegalAddrMode();
|
|
case AddrMode.IMMEDIATE:
|
|
if (b == 0x80) {
|
|
// STA #
|
|
illegalAddrMode();
|
|
}
|
|
putCommand(cast(ubyte) (b + 9));
|
|
break;
|
|
case AddrMode.ABSOLUTE:
|
|
putCommand(cast(ubyte) (b + 0xd));
|
|
break;
|
|
case AddrMode.ZEROPAGE:
|
|
putCommand(cast(ubyte) (b + 5));
|
|
break;
|
|
case AddrMode.ABSOLUTE_X:
|
|
putCommand(cast(ubyte) (b + 0x1d));
|
|
break;
|
|
case AddrMode.ZEROPAGE_X:
|
|
putCommand(cast(ubyte) (b + 0x15));
|
|
break;
|
|
case AddrMode.ZEROPAGE_Y:
|
|
addrMode -= 1;
|
|
goto case AddrMode.ABSOLUTE_Y;
|
|
case AddrMode.ABSOLUTE_Y:
|
|
putCommand(cast(ubyte) (b + 0x19));
|
|
break;
|
|
case AddrMode.INDIRECT_X:
|
|
if ((addrMode & AddrMode.ZERO) != 0)
|
|
putWord(0x00a2);
|
|
putCommand(cast(ubyte) (b + 1));
|
|
break;
|
|
case AddrMode.INDIRECT_Y:
|
|
if ((addrMode & AddrMode.ZERO) != 0)
|
|
putWord(0x00a0);
|
|
putCommand(cast(ubyte) (b + 0x11));
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
void assemblyShift(ubyte b) {
|
|
readAddrMode();
|
|
switch (addrMode & AddrMode.STANDARD_MASK) {
|
|
case AddrMode.ACCUMULATOR:
|
|
if (b == 0xc0 || b == 0xe0) {
|
|
// INC @, DEC @
|
|
illegalAddrMode();
|
|
}
|
|
putByte(cast(ubyte) (b + 0xa));
|
|
break;
|
|
case AddrMode.ABSOLUTE:
|
|
putCommand(cast(ubyte) (b + 0xe));
|
|
break;
|
|
case AddrMode.ZEROPAGE:
|
|
putCommand(cast(ubyte) (b + 6));
|
|
break;
|
|
case AddrMode.ABSOLUTE_X:
|
|
putCommand(cast(ubyte) (b + 0x1e));
|
|
break;
|
|
case AddrMode.ZEROPAGE_X:
|
|
putCommand(cast(ubyte) (b + 0x16));
|
|
break;
|
|
default:
|
|
illegalAddrMode();
|
|
}
|
|
}
|
|
|
|
void assemblyCompareIndex(ubyte b) {
|
|
readAddrMode();
|
|
switch (addrMode) {
|
|
case AddrMode.IMMEDIATE:
|
|
putCommand(b);
|
|
break;
|
|
case AddrMode.ABSOLUTE:
|
|
putCommand(cast(ubyte) (b + 0xc));
|
|
break;
|
|
case AddrMode.ZEROPAGE:
|
|
putCommand(cast(ubyte) (b + 4));
|
|
break;
|
|
default:
|
|
illegalAddrMode();
|
|
}
|
|
}
|
|
|
|
void assemblyLda(int move) {
|
|
assemblyAccumulator(0xa0, 0, move);
|
|
}
|
|
|
|
void assemblyLdx(int move) {
|
|
addrModeForMove(move);
|
|
switch (addrMode & AddrMode.STANDARD_MASK) {
|
|
case AddrMode.IMMEDIATE:
|
|
putCommand(0xa2);
|
|
break;
|
|
case AddrMode.ABSOLUTE:
|
|
putCommand(0xae);
|
|
break;
|
|
case AddrMode.ZEROPAGE:
|
|
putCommand(0xa6);
|
|
break;
|
|
case AddrMode.ABSOLUTE_Y:
|
|
putCommand(0xbe);
|
|
break;
|
|
case AddrMode.ZEROPAGE_Y:
|
|
putCommand(0xb6);
|
|
break;
|
|
default:
|
|
illegalAddrMode();
|
|
}
|
|
}
|
|
|
|
void assemblyLdy(int move) {
|
|
addrModeForMove(move);
|
|
switch (addrMode & AddrMode.STANDARD_MASK) {
|
|
case AddrMode.IMMEDIATE:
|
|
putCommand(0xa0);
|
|
break;
|
|
case AddrMode.ABSOLUTE:
|
|
putCommand(0xac);
|
|
break;
|
|
case AddrMode.ZEROPAGE:
|
|
putCommand(0xa4);
|
|
break;
|
|
case AddrMode.ABSOLUTE_X:
|
|
putCommand(0xbc);
|
|
break;
|
|
case AddrMode.ZEROPAGE_X:
|
|
putCommand(0xb4);
|
|
break;
|
|
default:
|
|
illegalAddrMode();
|
|
}
|
|
}
|
|
|
|
void assemblySta(int move) {
|
|
assemblyAccumulator(0x80, 0, move);
|
|
}
|
|
|
|
void assemblyStx(int move) {
|
|
addrModeForMove(move);
|
|
switch (addrMode & AddrMode.STANDARD_MASK) {
|
|
case AddrMode.ABSOLUTE:
|
|
putCommand(0x8e);
|
|
break;
|
|
case AddrMode.ZEROPAGE:
|
|
putCommand(0x86);
|
|
break;
|
|
case AddrMode.ABSOLUTE_Y:
|
|
addrMode += 1;
|
|
goto case AddrMode.ZEROPAGE_Y;
|
|
case AddrMode.ZEROPAGE_Y:
|
|
putCommand(0x96);
|
|
break;
|
|
default:
|
|
illegalAddrMode();
|
|
}
|
|
}
|
|
|
|
void assemblySty(int move) {
|
|
addrModeForMove(move);
|
|
switch (addrMode & AddrMode.STANDARD_MASK) {
|
|
case AddrMode.ABSOLUTE:
|
|
putCommand(0x8c);
|
|
break;
|
|
case AddrMode.ZEROPAGE:
|
|
putCommand(0x84);
|
|
break;
|
|
case AddrMode.ABSOLUTE_X:
|
|
addrMode += 1;
|
|
goto case AddrMode.ZEROPAGE_X;
|
|
case AddrMode.ZEROPAGE_X:
|
|
putCommand(0x94);
|
|
break;
|
|
default:
|
|
illegalAddrMode();
|
|
}
|
|
}
|
|
|
|
void assemblyBit() {
|
|
readAddrMode();
|
|
switch (addrMode) {
|
|
case AddrMode.ABSOLUTE:
|
|
putCommand(0x2c);
|
|
break;
|
|
case AddrMode.ZEROPAGE:
|
|
putCommand(0x24);
|
|
break;
|
|
default:
|
|
illegalAddrMode();
|
|
}
|
|
}
|
|
|
|
void putJump() {
|
|
switch (addrMode) {
|
|
case AddrMode.ZEROPAGE:
|
|
addrMode = AddrMode.ABSOLUTE;
|
|
goto case AddrMode.ABSOLUTE;
|
|
case AddrMode.ABSOLUTE:
|
|
putCommand(0x4c);
|
|
break;
|
|
case AddrMode.INDIRECT:
|
|
if (pass2 && (value & 0xff) == 0xff)
|
|
warning("Buggy indirect jump");
|
|
putCommand(0x6c);
|
|
break;
|
|
default:
|
|
illegalAddrMode();
|
|
}
|
|
}
|
|
|
|
void assemblyJmp() {
|
|
readAddrMode();
|
|
putJump();
|
|
}
|
|
|
|
void assemblyConditionalJump(ubyte b) {
|
|
noOpcode();
|
|
readAddrMode();
|
|
if ((addrMode == AddrMode.ABSOLUTE || addrMode == AddrMode.ZEROPAGE)
|
|
&& pass2 && origin >= 0 && value - origin - 2 >= -0x80 && value - origin - 2 <= 0x7f) {
|
|
warning("Plain branch instruction would be sufficient");
|
|
}
|
|
putByte(b);
|
|
putByte(3);
|
|
putJump();
|
|
}
|
|
|
|
void assemblyJsr() {
|
|
readAbsoluteAddrMode();
|
|
putCommand(0x20);
|
|
}
|
|
|
|
ubyte calculateBranch(int offset) {
|
|
if (offset < -0x80 || offset > 0x7f) {
|
|
int dist = offset < 0 ? -offset - 0x80 : offset - 0x7f;
|
|
throw new AssemblyError("Branch out of range by " ~ to!string(dist) ~ " bytes");
|
|
}
|
|
return cast(ubyte) offset;
|
|
}
|
|
|
|
void assemblyBranch(ubyte b) {
|
|
readAbsoluteAddrMode();
|
|
if (inOpcode) {
|
|
putByte(b);
|
|
return;
|
|
}
|
|
checkOriginDefined();
|
|
putByte(b);
|
|
putByte(pass2 ? calculateBranch(value - origin - 1) : 0);
|
|
}
|
|
|
|
void assemblyRepeat(ubyte b) {
|
|
noOpcode();
|
|
int offset = repeatOffset;
|
|
if (offset >= 0)
|
|
throw new AssemblyError("No instruction to repeat");
|
|
if (pass2 && wereManyInstructions)
|
|
warning("Repeating only the last instruction");
|
|
putByte(b);
|
|
putByte(calculateBranch(offset));
|
|
}
|
|
|
|
void assemblySkip(ubyte b) {
|
|
noOpcode();
|
|
if (willSkip) {
|
|
skipOffsets[$ - 1] = 2;
|
|
willSkip = false;
|
|
}
|
|
putByte(b);
|
|
if (pass2)
|
|
putByte(calculateBranch(skipOffsets[skipOffsetsIndex++]));
|
|
else {
|
|
putByte(0);
|
|
skipOffsets ~= 0;
|
|
willSkip = true;
|
|
}
|
|
}
|
|
|
|
void assemblyInw() {
|
|
noOpcode();
|
|
readAddrMode();
|
|
switch (addrMode) {
|
|
case AddrMode.ABSOLUTE:
|
|
putCommand(0xee);
|
|
putWord(0x03d0);
|
|
value++;
|
|
putCommand(0xee);
|
|
break;
|
|
case AddrMode.ZEROPAGE:
|
|
putCommand(0xe6);
|
|
putWord(0x02d0);
|
|
value++;
|
|
putCommand(0xe6);
|
|
break;
|
|
case AddrMode.ABSOLUTE_X:
|
|
putCommand(0xfe);
|
|
putWord(0x03d0);
|
|
value++;
|
|
putCommand(0xfe);
|
|
break;
|
|
case AddrMode.ZEROPAGE_X:
|
|
putCommand(0xf6);
|
|
putWord(0x02d0);
|
|
value++;
|
|
putCommand(0xf6);
|
|
break;
|
|
default:
|
|
illegalAddrMode();
|
|
}
|
|
}
|
|
|
|
void assemblyMove() {
|
|
noOpcode();
|
|
readAddrMode();
|
|
value1 = value;
|
|
addrMode1 = addrMode;
|
|
bool unknown1 = unknownInPass1;
|
|
readAddrMode();
|
|
value2 = value;
|
|
addrMode2 = addrMode;
|
|
unknownInPass1 = unknown1;
|
|
}
|
|
|
|
void assemblyMoveByte(MoveFunction load, MoveFunction store) {
|
|
assemblyMove();
|
|
load(1);
|
|
store(2);
|
|
}
|
|
|
|
void assemblyMoveWord(MoveFunction load, MoveFunction store, ubyte inc, ubyte dec) {
|
|
assemblyMove();
|
|
switch (addrMode2) {
|
|
case AddrMode.ABSOLUTE:
|
|
case AddrMode.ZEROPAGE:
|
|
case AddrMode.ABSOLUTE_X:
|
|
case AddrMode.ZEROPAGE_X:
|
|
case AddrMode.ABSOLUTE_Y:
|
|
case AddrMode.ZEROPAGE_Y:
|
|
break;
|
|
default:
|
|
illegalAddrMode();
|
|
}
|
|
switch (addrMode1) {
|
|
case AddrMode.IMMEDIATE:
|
|
int high = value1 >> 8;
|
|
value1 &= 0xff;
|
|
load(1);
|
|
store(2);
|
|
value2++;
|
|
if (unknownInPass1) {
|
|
value1 = high;
|
|
load(1);
|
|
} else {
|
|
if (inc != 0 && cast(ubyte) (value1 + 1) == high)
|
|
putByte(inc);
|
|
else if (dec != 0 && cast(ubyte) (value1 - 1) == high)
|
|
putByte(dec);
|
|
else if (value1 != high) {
|
|
value1 = high;
|
|
load(1);
|
|
}
|
|
}
|
|
store(2);
|
|
break;
|
|
case AddrMode.ABSOLUTE:
|
|
case AddrMode.ZEROPAGE:
|
|
case AddrMode.ABSOLUTE_X:
|
|
case AddrMode.ZEROPAGE_X:
|
|
case AddrMode.ABSOLUTE_Y:
|
|
case AddrMode.ZEROPAGE_Y:
|
|
load(1);
|
|
store(2);
|
|
value1++;
|
|
value2++;
|
|
load(1);
|
|
store(2);
|
|
break;
|
|
default:
|
|
illegalAddrMode();
|
|
}
|
|
}
|
|
|
|
void storeDtaNumber(int val, char letter) {
|
|
int limit = 0xffff;
|
|
if (letter == 'b') limit = 0xff;
|
|
if ((!unknownInPass1 || pass2) && (val < -limit || val > limit))
|
|
throw new AssemblyError("Value out of range");
|
|
final switch (letter) {
|
|
case 'a':
|
|
putWord(cast(ushort) val);
|
|
break;
|
|
case 'b':
|
|
case 'l':
|
|
putByte(cast(ubyte) val);
|
|
break;
|
|
case 'h':
|
|
putByte(cast(ubyte) (val >> 8));
|
|
break;
|
|
}
|
|
}
|
|
|
|
void assemblyDtaInteger(char letter) {
|
|
if (readFunction() == "SIN") {
|
|
readWord();
|
|
int sinCenter = value;
|
|
readComma();
|
|
readWord();
|
|
int sinAmp = value;
|
|
readComma();
|
|
readKnownPositive();
|
|
int sinPeriod = value;
|
|
int sinMin = 0;
|
|
int sinMax = sinPeriod - 1;
|
|
switch (readChar()) {
|
|
case ')':
|
|
break;
|
|
case ',':
|
|
readUnsignedWord();
|
|
mustBeKnownInPass1();
|
|
sinMin = value;
|
|
readComma();
|
|
readUnsignedWord();
|
|
mustBeKnownInPass1();
|
|
sinMax = value;
|
|
if (readChar() != ')') {
|
|
illegalCharacter();
|
|
}
|
|
break;
|
|
default:
|
|
illegalCharacter();
|
|
}
|
|
while (sinMin <= sinMax) {
|
|
int val = sinCenter + cast(int) rint(sinAmp * sin(sinMin * 2 * PI / sinPeriod));
|
|
storeDtaNumber(val, letter);
|
|
sinMin++;
|
|
}
|
|
return;
|
|
}
|
|
readWord();
|
|
storeDtaNumber(value, letter);
|
|
}
|
|
|
|
bool realSign;
|
|
|
|
int realExponent;
|
|
|
|
long realMantissa;
|
|
|
|
void putReal() {
|
|
if (realMantissa == 0) {
|
|
putWord(0);
|
|
putWord(0);
|
|
putWord(0);
|
|
return;
|
|
}
|
|
while (realMantissa < 0x1000000000L) {
|
|
realMantissa <<= 4;
|
|
realExponent--;
|
|
}
|
|
if ((realExponent & 1) != 0) {
|
|
if (realMantissa & 0xf)
|
|
throw new AssemblyError("Out of precision");
|
|
realMantissa >>= 4;
|
|
}
|
|
realExponent = (realExponent + 0x89) >> 1;
|
|
if (realExponent < 64 - 49)
|
|
throw new AssemblyError("Out of precision");
|
|
if (realExponent > 64 + 49)
|
|
throw new AssemblyError("Number too big");
|
|
putByte(cast(ubyte) (realSign ? realExponent + 0x80 : realExponent));
|
|
putByte(cast(ubyte) (realMantissa >> 32));
|
|
putByte(cast(ubyte) (realMantissa >> 24));
|
|
putByte(cast(ubyte) (realMantissa >> 16));
|
|
putByte(cast(ubyte) (realMantissa >> 8));
|
|
putByte(cast(ubyte) realMantissa);
|
|
}
|
|
|
|
bool readSign() {
|
|
switch (readChar()) {
|
|
case '+':
|
|
return false;
|
|
case '-':
|
|
return true;
|
|
default:
|
|
column--;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void readExponent() {
|
|
bool sign = readSign();
|
|
char c = readChar();
|
|
if (c < '0' || c > '9')
|
|
illegalCharacter();
|
|
int e = c - '0';
|
|
c = readChar();
|
|
if (c >= '0' && c <= '9')
|
|
e = 10 * e + c - '0';
|
|
else
|
|
column--;
|
|
realExponent += sign ? -e : e;
|
|
putReal();
|
|
}
|
|
|
|
void readFraction() {
|
|
for (;;) {
|
|
char c = readChar();
|
|
if (c >= '0' && c <= '9') {
|
|
if (c != '0' && realMantissa >= 0x1000000000L)
|
|
throw new AssemblyError("Out of precision");
|
|
realMantissa <<= 4;
|
|
realMantissa += c - '0';
|
|
realExponent--;
|
|
continue;
|
|
}
|
|
if (c == 'E' || c == 'e') {
|
|
readExponent();
|
|
return;
|
|
}
|
|
column--;
|
|
putReal();
|
|
return;
|
|
}
|
|
}
|
|
|
|
void assemblyDtaReal() {
|
|
realSign = readSign();
|
|
realExponent = 0;
|
|
realMantissa = 0;
|
|
char c = readChar();
|
|
if (c == '.') {
|
|
readFraction();
|
|
return;
|
|
}
|
|
if (c < '0' || c > '9')
|
|
illegalCharacter();
|
|
do {
|
|
if (realMantissa < 0x1000000000L) {
|
|
realMantissa <<= 4;
|
|
realMantissa += c - '0';
|
|
} else {
|
|
if (c != '0')
|
|
throw new AssemblyError("Out of precision");
|
|
realExponent++;
|
|
}
|
|
c = readChar();
|
|
} while (c >= '0' && c <= '9');
|
|
switch (c) {
|
|
case '.':
|
|
readFraction();
|
|
break;
|
|
case 'E':
|
|
case 'e':
|
|
readExponent();
|
|
break;
|
|
default:
|
|
column--;
|
|
putReal();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void assemblyDtaNumbers(char letter) {
|
|
if (eol() || line[column] != '(') {
|
|
column--;
|
|
assemblyDtaInteger('b');
|
|
return;
|
|
}
|
|
column++;
|
|
for (;;) {
|
|
final switch (letter) {
|
|
case 'a':
|
|
case 'b':
|
|
case 'h':
|
|
case 'l':
|
|
assemblyDtaInteger(letter);
|
|
break;
|
|
case 'r':
|
|
assemblyDtaReal();
|
|
break;
|
|
}
|
|
switch (readChar()) {
|
|
case ')':
|
|
return;
|
|
case ',':
|
|
break;
|
|
default:
|
|
illegalCharacter();
|
|
}
|
|
}
|
|
}
|
|
|
|
void assemblyDta() {
|
|
noOpcode();
|
|
readSpaces();
|
|
for (;;) {
|
|
switch (readChar()) {
|
|
case 'A':
|
|
case 'a':
|
|
assemblyDtaNumbers('a');
|
|
break;
|
|
case 'B':
|
|
case 'b':
|
|
assemblyDtaNumbers('b');
|
|
break;
|
|
case 'C':
|
|
case 'c':
|
|
ubyte[] s = readString();
|
|
if (s is null) {
|
|
column--;
|
|
assemblyDtaInteger('b');
|
|
break;
|
|
}
|
|
foreach (ubyte b; s) {
|
|
putByte(b);
|
|
}
|
|
break;
|
|
case 'D':
|
|
case 'd':
|
|
ubyte[] s = readString();
|
|
if (s is null) {
|
|
column--;
|
|
assemblyDtaInteger('b');
|
|
break;
|
|
}
|
|
foreach (ubyte b; s) {
|
|
final switch (b & 0x60) {
|
|
case 0x00:
|
|
putByte(cast(ubyte) (b + 0x40));
|
|
break;
|
|
case 0x20:
|
|
case 0x40:
|
|
putByte(cast(ubyte) (b - 0x20));
|
|
break;
|
|
case 0x60:
|
|
putByte(b);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case 'H':
|
|
case 'h':
|
|
assemblyDtaNumbers('h');
|
|
break;
|
|
case 'L':
|
|
case 'l':
|
|
assemblyDtaNumbers('l');
|
|
break;
|
|
case 'R':
|
|
case 'r':
|
|
assemblyDtaNumbers('r');
|
|
break;
|
|
default:
|
|
column--;
|
|
assemblyDtaInteger('b');
|
|
break;
|
|
}
|
|
if (eol() || line[column] != ',')
|
|
break;
|
|
column++;
|
|
}
|
|
}
|
|
|
|
void assemblyEqu() {
|
|
directive();
|
|
if (currentLabel is null)
|
|
throw new AssemblyError("Label name required");
|
|
currentLabel.value = 0;
|
|
readSpaces();
|
|
readValue();
|
|
currentLabel.value = value;
|
|
currentLabel.unknownInPass1 = unknownInPass1;
|
|
if (optionListing) {
|
|
listingLine[6] = '=';
|
|
int val = value;
|
|
listingLine[7] = ' ';
|
|
if (val < 0) {
|
|
listingLine[7] = '-';
|
|
val = -val;
|
|
}
|
|
listingColumn = 8;
|
|
if ((val & 0xffff0000) != 0)
|
|
listWord(cast(ushort) (val >> 16));
|
|
else {
|
|
while (listingColumn < 12)
|
|
listingLine[listingColumn++] = ' ';
|
|
}
|
|
listWord(cast(ushort) val);
|
|
}
|
|
}
|
|
|
|
void assemblyEnd() {
|
|
directive();
|
|
assert(!foundEnd);
|
|
foundEnd = true;
|
|
}
|
|
|
|
void assemblyIftEli() {
|
|
ifContexts[$ - 1].condition = true;
|
|
if (!inFalseCondition()) {
|
|
readSpaces();
|
|
readValue();
|
|
mustBeKnownInPass1();
|
|
if (value != 0)
|
|
ifContexts[$ - 1].aConditionMatched = true;
|
|
else
|
|
listLine();
|
|
ifContexts[$ - 1].condition = value != 0;
|
|
}
|
|
}
|
|
|
|
void checkMissingIft() {
|
|
if (ifContexts.length == 0)
|
|
throw new AssemblyError("Missing IFT");
|
|
}
|
|
|
|
void assemblyIft() {
|
|
directive();
|
|
ifContexts.length++;
|
|
assemblyIftEli();
|
|
}
|
|
|
|
void assemblyEliEls() {
|
|
directive();
|
|
checkMissingIft();
|
|
if (ifContexts[$ - 1].wasElse)
|
|
throw new AssemblyError("EIF expected");
|
|
}
|
|
|
|
void assemblyEli() {
|
|
assemblyEliEls();
|
|
if (ifContexts[$ - 1].aConditionMatched) {
|
|
ifContexts[$ - 1].condition = false;
|
|
return;
|
|
}
|
|
assemblyIftEli();
|
|
}
|
|
|
|
void assemblyEls() {
|
|
assemblyEliEls();
|
|
with (ifContexts[$ - 1]) {
|
|
if (condition && aConditionMatched)
|
|
listLine();
|
|
wasElse = true;
|
|
condition = !aConditionMatched;
|
|
}
|
|
}
|
|
|
|
void assemblyEif() {
|
|
directive();
|
|
checkMissingIft();
|
|
ifContexts.length--;
|
|
}
|
|
|
|
void assemblyErt() {
|
|
directive();
|
|
readSpaces();
|
|
readValue();
|
|
if (pass2 && value != 0)
|
|
throw new AssemblyError("User-defined error");
|
|
}
|
|
|
|
bool readOption() {
|
|
switch (readChar()) {
|
|
case '-':
|
|
return false;
|
|
case '+':
|
|
return true;
|
|
default:
|
|
illegalCharacter();
|
|
}
|
|
assert(0);
|
|
}
|
|
|
|
void assemblyOpt() {
|
|
directive();
|
|
readSpaces();
|
|
while (!eol()) {
|
|
switch (line[column++]) {
|
|
case 'F':
|
|
case 'f':
|
|
optionFill = readOption();
|
|
break;
|
|
case 'G':
|
|
case 'g':
|
|
option5200 = readOption();
|
|
break;
|
|
case 'H':
|
|
case 'h':
|
|
optionHeaders = readOption();
|
|
break;
|
|
case 'L':
|
|
case 'l':
|
|
optionListing = readOption() && !pass2;
|
|
break;
|
|
case 'O':
|
|
case 'o':
|
|
optionObject = readOption();
|
|
break;
|
|
case 'U':
|
|
case 'u':
|
|
optionUnusedLabels = readOption();
|
|
break;
|
|
default:
|
|
column--;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void originWord(ushort value, char listingChar) {
|
|
objectWord(value);
|
|
listWord(value);
|
|
listingLine[listingColumn++] = listingChar;
|
|
}
|
|
|
|
OrgModifier readOrgModifier() {
|
|
readSpaces();
|
|
if (column + 2 < line.length && line[column + 1] == ':') {
|
|
switch (line[column]) {
|
|
case 'F':
|
|
case 'f':
|
|
checkHeadersOn();
|
|
column += 2;
|
|
return OrgModifier.FORCE_FFFF;
|
|
case 'A':
|
|
case 'a':
|
|
checkHeadersOn();
|
|
column += 2;
|
|
return OrgModifier.FORCE_HEADER;
|
|
case 'R':
|
|
case 'r':
|
|
column += 2;
|
|
return OrgModifier.RELOCATE;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return OrgModifier.NONE;
|
|
}
|
|
|
|
void setOrigin(int addr, OrgModifier modifier) {
|
|
origin = loadOrigin = addr;
|
|
bool requestedHeader = modifier != OrgModifier.NONE;
|
|
if (requestedHeader || loadingOrigin < 0 || (addr != loadingOrigin && !optionFill)) {
|
|
blockIndex++;
|
|
if (!pass2) {
|
|
assert(blockIndex == blockEnds.length);
|
|
blockEnds ~= cast(ushort) (addr - 1);
|
|
}
|
|
if (pass2 && optionHeaders) {
|
|
if (addr - 1 == blockEnds[blockIndex]) {
|
|
if (requestedHeader)
|
|
throw new AssemblyError("Cannot generate an empty block");
|
|
return;
|
|
}
|
|
if (modifier == OrgModifier.FORCE_FFFF || objectBytes == 0) {
|
|
assert(requestedHeader || addr != loadingOrigin);
|
|
originWord(0xffff, '>');
|
|
listingLine[listingColumn++] = ' ';
|
|
}
|
|
if (requestedHeader || addr != loadingOrigin) {
|
|
originWord(cast(ushort) addr, '-');
|
|
originWord(blockEnds[blockIndex], '>');
|
|
listingLine[listingColumn++] = ' ';
|
|
loadingOrigin = -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void checkHeadersOn() {
|
|
if (!optionHeaders)
|
|
throw new AssemblyError("Illegal when Atari file headers disabled");
|
|
}
|
|
|
|
void assemblyOrg() {
|
|
noRepeatSkipDirective();
|
|
OrgModifier modifier = readOrgModifier();
|
|
readUnsignedWord();
|
|
mustBeKnownInPass1();
|
|
if (modifier == OrgModifier.RELOCATE) {
|
|
checkOriginDefined();
|
|
origin = value;
|
|
}
|
|
else {
|
|
setOrigin(value, modifier);
|
|
}
|
|
}
|
|
|
|
void assemblyRunIni(ushort addr) {
|
|
noRepeatSkipDirective();
|
|
checkHeadersOn();
|
|
loadingOrigin = -1; // don't fill
|
|
OrgModifier modifier = readOrgModifier();
|
|
if (modifier == OrgModifier.RELOCATE)
|
|
throw new AssemblyError("r: invalid here");
|
|
setOrigin(addr, modifier);
|
|
readUnsignedWord();
|
|
putWord(cast(ushort) (value));
|
|
loadingOrigin = -1; // don't fill
|
|
}
|
|
|
|
void assemblyIcl() {
|
|
directive();
|
|
string filename = readFilename();
|
|
checkNoExtraCharacters();
|
|
listLine();
|
|
includeLevel++;
|
|
assemblyFile(filename);
|
|
includeLevel--;
|
|
line = null;
|
|
}
|
|
|
|
void assemblyIns() {
|
|
string filename = readFilename();
|
|
int offset = 0;
|
|
int length = -1;
|
|
if (!eol() && line[column] == ',') {
|
|
column++;
|
|
readValue();
|
|
mustBeKnownInPass1();
|
|
offset = value;
|
|
if (!eol() && line[column] == ',') {
|
|
column++;
|
|
readKnownPositive();
|
|
length = value;
|
|
}
|
|
}
|
|
File stream = openInputFile(filename);
|
|
scope (exit) stream.close();
|
|
try {
|
|
stream.seek(offset, offset >= 0 ? SEEK_SET : SEEK_END);
|
|
} catch (Exception e) {
|
|
throw new AssemblyError("Error seeking file");
|
|
}
|
|
if (inOpcode)
|
|
length = 1;
|
|
while (length != 0) {
|
|
int b = readByte(&stream);
|
|
if (b < 0) {
|
|
if (length > 0)
|
|
throw new AssemblyError("File is too short");
|
|
break;
|
|
}
|
|
putByte(cast(ubyte) b);
|
|
if (length > 0) length--;
|
|
}
|
|
}
|
|
|
|
void assemblyInstruction(string instruction) {
|
|
if (!inOpcode && origin < 0 && currentLabel !is null && instruction != "EQU")
|
|
throw new AssemblyError("No ORG specified");
|
|
instructionBegin = true;
|
|
switch (instruction) {
|
|
case "ADC":
|
|
assemblyAccumulator(0x60, 0, 0);
|
|
break;
|
|
case "ADD":
|
|
assemblyAccumulator(0x60, 0x18, 0);
|
|
break;
|
|
case "AND":
|
|
assemblyAccumulator(0x20, 0, 0);
|
|
break;
|
|
case "ASL":
|
|
assemblyShift(0x00);
|
|
break;
|
|
case "BCC":
|
|
assemblyBranch(0x90);
|
|
break;
|
|
case "BCS":
|
|
assemblyBranch(0xb0);
|
|
break;
|
|
case "BEQ":
|
|
assemblyBranch(0xf0);
|
|
break;
|
|
case "BIT":
|
|
assemblyBit();
|
|
break;
|
|
case "BMI":
|
|
assemblyBranch(0x30);
|
|
break;
|
|
case "BNE":
|
|
assemblyBranch(0xd0);
|
|
break;
|
|
case "BPL":
|
|
assemblyBranch(0x10);
|
|
break;
|
|
case "BRK":
|
|
putByte(0x00);
|
|
break;
|
|
case "BVC":
|
|
assemblyBranch(0x50);
|
|
break;
|
|
case "BVS":
|
|
assemblyBranch(0x70);
|
|
break;
|
|
case "CLC":
|
|
putByte(0x18);
|
|
break;
|
|
case "CLD":
|
|
putByte(0xd8);
|
|
break;
|
|
case "CLI":
|
|
putByte(0x58);
|
|
break;
|
|
case "CLV":
|
|
putByte(0xb8);
|
|
break;
|
|
case "CMP":
|
|
assemblyAccumulator(0xc0, 0, 0);
|
|
break;
|
|
case "CPX":
|
|
assemblyCompareIndex(0xe0);
|
|
break;
|
|
case "CPY":
|
|
assemblyCompareIndex(0xc0);
|
|
break;
|
|
case "DEC":
|
|
assemblyShift(0xc0);
|
|
break;
|
|
case "DEX":
|
|
putByte(0xca);
|
|
break;
|
|
case "DEY":
|
|
putByte(0x88);
|
|
break;
|
|
case "DTA":
|
|
assemblyDta();
|
|
break;
|
|
case "EIF":
|
|
assemblyEif();
|
|
break;
|
|
case "ELI":
|
|
assemblyEli();
|
|
break;
|
|
case "ELS":
|
|
assemblyEls();
|
|
break;
|
|
case "END":
|
|
assemblyEnd();
|
|
break;
|
|
case "EOR":
|
|
assemblyAccumulator(0x40, 0, 0);
|
|
break;
|
|
case "EQU":
|
|
assemblyEqu();
|
|
break;
|
|
case "ERT":
|
|
assemblyErt();
|
|
break;
|
|
case "ICL":
|
|
assemblyIcl();
|
|
break;
|
|
case "IFT":
|
|
assemblyIft();
|
|
break;
|
|
case "INC":
|
|
assemblyShift(0xe0);
|
|
break;
|
|
case "INI":
|
|
assemblyRunIni(0x2e2);
|
|
break;
|
|
case "INS":
|
|
assemblyIns();
|
|
break;
|
|
case "INX":
|
|
putByte(0xe8);
|
|
break;
|
|
case "INY":
|
|
putByte(0xc8);
|
|
break;
|
|
case "INW":
|
|
assemblyInw();
|
|
break;
|
|
case "JCC":
|
|
assemblyConditionalJump(0xb0);
|
|
break;
|
|
case "JCS":
|
|
assemblyConditionalJump(0x90);
|
|
break;
|
|
case "JEQ":
|
|
assemblyConditionalJump(0xd0);
|
|
break;
|
|
case "JMI":
|
|
assemblyConditionalJump(0x10);
|
|
break;
|
|
case "JMP":
|
|
assemblyJmp();
|
|
break;
|
|
case "JNE":
|
|
assemblyConditionalJump(0xf0);
|
|
break;
|
|
case "JPL":
|
|
assemblyConditionalJump(0x30);
|
|
break;
|
|
case "JSR":
|
|
assemblyJsr();
|
|
break;
|
|
case "JVC":
|
|
assemblyConditionalJump(0x70);
|
|
break;
|
|
case "JVS":
|
|
assemblyConditionalJump(0x50);
|
|
break;
|
|
case "LDA":
|
|
assemblyAccumulator(0xa0, 0, 0);
|
|
break;
|
|
case "LDX":
|
|
assemblyLdx(0);
|
|
break;
|
|
case "LDY":
|
|
assemblyLdy(0);
|
|
break;
|
|
case "LSR":
|
|
assemblyShift(0x40);
|
|
break;
|
|
case "MVA":
|
|
assemblyMoveByte(&assemblyLda, &assemblySta);
|
|
break;
|
|
case "MVX":
|
|
assemblyMoveByte(&assemblyLdx, &assemblyStx);
|
|
break;
|
|
case "MVY":
|
|
assemblyMoveByte(&assemblyLdy, &assemblySty);
|
|
break;
|
|
case "MWA":
|
|
assemblyMoveWord(&assemblyLda, &assemblySta, 0, 0);
|
|
break;
|
|
case "MWX":
|
|
assemblyMoveWord(&assemblyLdx, &assemblyStx, 0xe8, 0xca);
|
|
break;
|
|
case "MWY":
|
|
assemblyMoveWord(&assemblyLdy, &assemblySty, 0xc8, 0x88);
|
|
break;
|
|
case "NOP":
|
|
putByte(0xea);
|
|
break;
|
|
case "OPT":
|
|
assemblyOpt();
|
|
break;
|
|
case "ORA":
|
|
assemblyAccumulator(0x00, 0, 0);
|
|
break;
|
|
case "ORG":
|
|
assemblyOrg();
|
|
break;
|
|
case "PHA":
|
|
putByte(0x48);
|
|
break;
|
|
case "PHP":
|
|
putByte(0x08);
|
|
break;
|
|
case "PLA":
|
|
putByte(0x68);
|
|
break;
|
|
case "PLP":
|
|
putByte(0x28);
|
|
break;
|
|
case "RCC":
|
|
assemblyRepeat(0x90);
|
|
break;
|
|
case "RCS":
|
|
assemblyRepeat(0xb0);
|
|
break;
|
|
case "REQ":
|
|
assemblyRepeat(0xf0);
|
|
break;
|
|
case "RMI":
|
|
assemblyRepeat(0x30);
|
|
break;
|
|
case "RNE":
|
|
assemblyRepeat(0xd0);
|
|
break;
|
|
case "ROL":
|
|
assemblyShift(0x20);
|
|
break;
|
|
case "ROR":
|
|
assemblyShift(0x60);
|
|
break;
|
|
case "RPL":
|
|
assemblyRepeat(0x10);
|
|
break;
|
|
case "RTI":
|
|
putByte(0x40);
|
|
break;
|
|
case "RTS":
|
|
putByte(0x60);
|
|
break;
|
|
case "RUN":
|
|
assemblyRunIni(0x2e0);
|
|
break;
|
|
case "RVC":
|
|
assemblyRepeat(0x50);
|
|
break;
|
|
case "RVS":
|
|
assemblyRepeat(0x70);
|
|
break;
|
|
case "SBC":
|
|
assemblyAccumulator(0xe0, 0, 0);
|
|
break;
|
|
case "SCC":
|
|
assemblySkip(0x90);
|
|
break;
|
|
case "SCS":
|
|
assemblySkip(0xb0);
|
|
break;
|
|
case "SEC":
|
|
putByte(0x38);
|
|
break;
|
|
case "SED":
|
|
putByte(0xf8);
|
|
break;
|
|
case "SEI":
|
|
putByte(0x78);
|
|
break;
|
|
case "SEQ":
|
|
assemblySkip(0xf0);
|
|
break;
|
|
case "SMI":
|
|
assemblySkip(0x30);
|
|
break;
|
|
case "SNE":
|
|
assemblySkip(0xd0);
|
|
break;
|
|
case "SPL":
|
|
assemblySkip(0x10);
|
|
break;
|
|
case "STA":
|
|
assemblyAccumulator(0x80, 0, 0);
|
|
break;
|
|
case "STX":
|
|
assemblyStx(0);
|
|
break;
|
|
case "STY":
|
|
assemblySty(0);
|
|
break;
|
|
case "SUB":
|
|
assemblyAccumulator(0xe0, 0x38, 0);
|
|
break;
|
|
case "SVC":
|
|
assemblySkip(0x50);
|
|
break;
|
|
case "SVS":
|
|
assemblySkip(0x70);
|
|
break;
|
|
case "TAX":
|
|
putByte(0xaa);
|
|
break;
|
|
case "TAY":
|
|
putByte(0xa8);
|
|
break;
|
|
case "TSX":
|
|
putByte(0xba);
|
|
break;
|
|
case "TXA":
|
|
putByte(0x8a);
|
|
break;
|
|
case "TXS":
|
|
putByte(0x9a);
|
|
break;
|
|
case "TYA":
|
|
putByte(0x98);
|
|
break;
|
|
default:
|
|
throw new AssemblyError("Illegal instruction");
|
|
}
|
|
skipping = false;
|
|
}
|
|
|
|
debug ubyte[] testInstruction(string l) {
|
|
objectBuffer.length = 0;
|
|
line = l;
|
|
column = 0;
|
|
assemblyInstruction(readInstruction());
|
|
write(line, " assembles to");
|
|
foreach (ubyte b; objectBuffer)
|
|
writef(" %02x", b);
|
|
writeln();
|
|
return objectBuffer;
|
|
}
|
|
|
|
unittest {
|
|
assert(testInstruction("nop") == cast(ubyte[]) x"ea");
|
|
assert(testInstruction("add (5,0)") == cast(ubyte[]) x"18a2006105");
|
|
assert(testInstruction("mwa #$abcd $1234") == cast(ubyte[]) x"a9cd8d3412a9ab8d3512");
|
|
assert(testInstruction("dta 5,d'Foo'*,a($4589)") == cast(ubyte[]) x"05a6efef8945");
|
|
assert(testInstruction("dta r(1,12,123,1234567890,12345678900000,.5,.03,000.1664534589,1e97)")
|
|
== cast(ubyte[]) x"400100000000 401200000000 410123000000 441234567890 461234567890 3f5000000000 3f0300000000 3f1664534589 701000000000");
|
|
}
|
|
|
|
void assemblyPair() {
|
|
assert(!inOpcode);
|
|
string instruction = readInstruction();
|
|
if (!eol() && line[column] == ':') {
|
|
pairing = true;
|
|
column++;
|
|
string instruction2 = readInstruction();
|
|
int savedColumn = column;
|
|
if (willSkip)
|
|
warning("Skipping only the first instruction");
|
|
assemblyInstruction(instruction);
|
|
checkNoExtraCharacters();
|
|
column = savedColumn;
|
|
wereManyInstructions = false;
|
|
assemblyInstruction(instruction2);
|
|
wereManyInstructions = true;
|
|
} else {
|
|
pairing = false;
|
|
assemblyInstruction(instruction);
|
|
wereManyInstructions = false;
|
|
}
|
|
}
|
|
|
|
void assemblyLine() {
|
|
debug {
|
|
writeln(line);
|
|
}
|
|
lineNo++;
|
|
totalLines++;
|
|
column = 0;
|
|
listingColumn = 6;
|
|
if (origin >= 0) {
|
|
listWord(cast(ushort) origin);
|
|
listingLine[listingColumn++] = ' ';
|
|
}
|
|
string label = readLabel();
|
|
currentLabel = null;
|
|
if (label !is null) {
|
|
if (!inFalseCondition()) {
|
|
if (!pass2) {
|
|
if (label in labelTable)
|
|
throw new AssemblyError("Label declared twice");
|
|
currentLabel = new Label(origin);
|
|
labelTable[label] = currentLabel;
|
|
} else {
|
|
assert(label in labelTable);
|
|
currentLabel = labelTable[label];
|
|
currentLabel.passed = true;
|
|
if (currentLabel.unused && getOption('u') && optionUnusedLabels)
|
|
warning("Unused label: " ~ label);
|
|
}
|
|
}
|
|
if (eol()) {
|
|
listCommentLine();
|
|
return;
|
|
}
|
|
readSpaces();
|
|
}
|
|
commentOrRep: for (;;) {
|
|
if (eol()) {
|
|
listCommentLine();
|
|
return;
|
|
}
|
|
switch (line[column]) {
|
|
case '\t':
|
|
case ' ':
|
|
column++;
|
|
continue;
|
|
case '*':
|
|
case ';':
|
|
case '|':
|
|
listCommentLine();
|
|
return;
|
|
case ':':
|
|
if (inFalseCondition()) {
|
|
listCommentLine();
|
|
return;
|
|
}
|
|
column++;
|
|
readUnsignedWord();
|
|
mustBeKnownInPass1();
|
|
int repeatLimit = value;
|
|
if (repeatLimit == 0) {
|
|
listCommentLine();
|
|
return;
|
|
}
|
|
readSpaces();
|
|
repeating = true;
|
|
if (repeatLimit == 1)
|
|
break;
|
|
if (willSkip)
|
|
warning("Skipping only the first instruction");
|
|
int savedColumn = column;
|
|
for (repeatCounter = 0; repeatCounter < repeatLimit; repeatCounter++) {
|
|
column = savedColumn;
|
|
assemblyPair();
|
|
}
|
|
checkNoExtraCharacters();
|
|
listLine();
|
|
wereManyInstructions = true;
|
|
return;
|
|
default:
|
|
repeating = false;
|
|
break commentOrRep;
|
|
}
|
|
}
|
|
if (inFalseCondition()) {
|
|
switch (readInstruction()) {
|
|
case "END":
|
|
assemblyEnd();
|
|
break;
|
|
case "IFT":
|
|
assemblyIft();
|
|
break;
|
|
case "ELI":
|
|
assemblyEli();
|
|
break;
|
|
case "ELS":
|
|
assemblyEls();
|
|
break;
|
|
case "EIF":
|
|
assemblyEif();
|
|
break;
|
|
default:
|
|
listCommentLine();
|
|
return;
|
|
}
|
|
checkNoExtraCharacters();
|
|
listCommentLine();
|
|
return;
|
|
}
|
|
assemblyPair();
|
|
checkNoExtraCharacters();
|
|
listLine();
|
|
}
|
|
|
|
void assemblyFile(string filename) {
|
|
filename = filename.defaultExtension("asx");
|
|
if (getOption('p'))
|
|
filename = absolutePath(filename);
|
|
File stream = openInputFile(filename);
|
|
scope (exit) stream.close();
|
|
string oldFilename = currentFilename;
|
|
int oldLineNo = lineNo;
|
|
currentFilename = filename;
|
|
lineNo = 0;
|
|
foundEnd = false;
|
|
line = "";
|
|
readChar: while (!foundEnd) {
|
|
int b = readByte(&stream);
|
|
switch (b) {
|
|
case -1:
|
|
break readChar;
|
|
case '\r':
|
|
assemblyLine();
|
|
line = "";
|
|
b = readByte(&stream);
|
|
if (b < 0)
|
|
break readChar;
|
|
if (b != '\n')
|
|
line ~= cast(char) b;
|
|
break;
|
|
case '\n':
|
|
case 0x9b:
|
|
assemblyLine();
|
|
line = "";
|
|
break;
|
|
default:
|
|
line ~= cast(char) b;
|
|
break;
|
|
}
|
|
}
|
|
if (!foundEnd)
|
|
assemblyLine();
|
|
foundEnd = false;
|
|
currentFilename = oldFilename;
|
|
lineNo = oldLineNo;
|
|
}
|
|
|
|
void assemblyPass() {
|
|
origin = -1;
|
|
loadOrigin = -1;
|
|
loadingOrigin = -1;
|
|
blockIndex = -1;
|
|
optionFill = false;
|
|
option5200 = false;
|
|
optionHeaders = true;
|
|
optionListing = pass2;
|
|
optionObject = true;
|
|
optionUnusedLabels = true;
|
|
willSkip = false;
|
|
skipping = false;
|
|
repeatOffset = 0;
|
|
wereManyInstructions = false;
|
|
currentFilename = "command line";
|
|
lineNo = 0;
|
|
foreach (definition; commandLineDefinitions) {
|
|
line = replaceFirst(definition, "=", " equ ");
|
|
assemblyLine();
|
|
}
|
|
line = null;
|
|
totalLines = 0;
|
|
assemblyFile(sourceFilename);
|
|
if (ifContexts.length != 0)
|
|
throw new AssemblyError("Missing EIF");
|
|
if (willSkip)
|
|
throw new AssemblyError("Can't skip over this");
|
|
}
|
|
|
|
pure bool isOption(string arg) {
|
|
if (arg.length < 2) return false;
|
|
if (arg[0] == '-') return true;
|
|
if (arg[0] != '/') return false;
|
|
if (arg.length == 2) return true;
|
|
if (arg[2] == ':') return true;
|
|
return false;
|
|
}
|
|
|
|
void setOption(char letter) {
|
|
assert(letter >= 'a' && letter <= 'z');
|
|
if (options[letter - 'a']) {
|
|
exitCode = 3;
|
|
return;
|
|
}
|
|
options[letter - 'a'] = true;
|
|
}
|
|
|
|
int main(string[] args) {
|
|
for (int i = 1; i < args.length; i++) {
|
|
string arg = args[i];
|
|
if (isOption(arg)) {
|
|
char letter = arg[1];
|
|
if (letter >= 'A' && letter <= 'Z')
|
|
letter += 'a' - 'A';
|
|
switch (letter) {
|
|
case 'c':
|
|
case 'i':
|
|
case 'm':
|
|
case 'p':
|
|
case 'q':
|
|
case 'u':
|
|
if (arg.length != 2)
|
|
exitCode = 3;
|
|
setOption(letter);
|
|
break;
|
|
case 'd':
|
|
string definition = null;
|
|
if (arg[0] == '/') {
|
|
if (arg.length >= 3 && arg[2] == ':')
|
|
definition = arg[3 .. $];
|
|
} else if (i + 1 < args.length && !isOption(args[i + 1]))
|
|
definition = args[++i];
|
|
if (definition is null || find(definition, '=').empty)
|
|
exitCode = 3;
|
|
commandLineDefinitions ~= definition;
|
|
break;
|
|
case 'l':
|
|
case 't':
|
|
case 'o':
|
|
setOption(letter);
|
|
string filename = null;
|
|
if (arg[0] == '/') {
|
|
if (arg.length >= 3 && arg[2] == ':')
|
|
filename = arg[3 .. $];
|
|
} else if (i + 1 < args.length && !isOption(args[i + 1]))
|
|
filename = args[++i];
|
|
if (filename is null && (letter == 'o' || arg.length != 2))
|
|
exitCode = 3;
|
|
optionParameters[letter - 'a'] = filename;
|
|
break;
|
|
default:
|
|
exitCode = 3;
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
if (sourceFilename !is null)
|
|
exitCode = 3;
|
|
sourceFilename = arg;
|
|
}
|
|
if (sourceFilename is null)
|
|
exitCode = 3;
|
|
if (!getOption('q'))
|
|
writeln(TITLE);
|
|
if (exitCode != 0) {
|
|
write(
|
|
"Syntax: xasm source [options]\n"
|
|
"/c Include false conditionals in listing\n"
|
|
"/d:label=value Define a label\n"
|
|
"/i Don't list included files\n"
|
|
"/l[:filename] Generate listing\n"
|
|
"/o:filename Set object file name\n"
|
|
"/M Print Makefile rule\n"
|
|
"/p Print absolute paths in listing and error messages\n"
|
|
"/q Suppress info messages\n"
|
|
"/t[:filename] List label table\n"
|
|
"/u Warn of unused labels\n");
|
|
return exitCode;
|
|
}
|
|
try {
|
|
assemblyPass();
|
|
pass2 = true;
|
|
assemblyPass();
|
|
if (getOption('t') && labelTable.length > 0)
|
|
listLabelTable();
|
|
} catch (AssemblyError e) {
|
|
warning(e.msg, true);
|
|
exitCode = 2;
|
|
}
|
|
listingStream.close();
|
|
objectStream.close();
|
|
if (exitCode <= 1) {
|
|
if (!getOption('q')) {
|
|
writefln("%d lines of source assembled", totalLines);
|
|
if (objectBytes > 0)
|
|
writefln("%d bytes written to the object file", objectBytes);
|
|
}
|
|
if (getOption('m')) {
|
|
writef("%s:", makeTarget);
|
|
foreach (filename; makeSources)
|
|
writef(" %s", makeEscape(filename));
|
|
write("\n\txasm");
|
|
for (int i = 1; i < args.length; i++) {
|
|
string arg = args[i];
|
|
if (isOption(arg)) {
|
|
char letter = arg[1];
|
|
if (letter >= 'A' && letter <= 'Z')
|
|
letter += 'a' - 'A';
|
|
switch (letter) {
|
|
case 'm':
|
|
break;
|
|
case 'o':
|
|
if (arg[0] == '/')
|
|
writef(" /%c:$@", arg[1]);
|
|
else {
|
|
writef(" -%c $@", arg[1]);
|
|
++i;
|
|
}
|
|
break;
|
|
default:
|
|
if (arg[0] == '-'
|
|
&& (letter == 'd' || letter == 'l' || letter == 't')
|
|
&& i + 1 < args.length && !isOption(args[i + 1])) {
|
|
writef(" %s %s", arg, makeEscape(args[++i]));
|
|
}
|
|
else {
|
|
writef(" %s", makeEscape(arg));
|
|
}
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
write(" $<");
|
|
}
|
|
writeln();
|
|
}
|
|
}
|
|
return exitCode;
|
|
}
|