1
0
mirror of https://github.com/pfusik/xasm.git synced 2024-10-14 19:24:15 +00:00
xasm/xasm.d

3023 lines
59 KiB
D
Raw Normal View History

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() {