Files

1481 lines
31 KiB
D

/*
* Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016 Vladimir Panteleev <vladimir@thecybershadow.net>
* This file is part of RABCDAsm.
*
* RABCDAsm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* RABCDAsm is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with RABCDAsm. If not, see <http://www.gnu.org/licenses/>.
*/
module assembler;
import std.conv;
import std.exception;
import std.file;
import std.path;
import std.range;
import std.stdio;
import std.string;
import std.typecons;
import abcfile;
import asprogram;
import common;
final class Assembler
{
static struct Position
{
SourceFile file;
ulong offset;
SourceFile load()
{
assert(file.ptr == file.end);
file.filePosition = offset;
return file;
}
}
static final class SourceFile
{
string name;
string[] arguments;
string data;
@property bool isVirtual() { return data ! is null; }
SourceFile parent;
const(char)* ptr, end;
enum BUF_SIZE = 256*1024;
enum PADDING = 16;
private char[] buffer;
static char[][] usedBuffers;
ulong filePosition; // File position of buffer.ptr
sizediff_t shift; // Error location adjustment
File f;
this(string name, string data = null, string[] arguments = null)
{
this.name = name;
this.arguments = arguments;
this.data = data;
if (data)
{
createBuffer(data.length);
buffer[0..data.length] = data[];
ptr = buffer.ptr;
end = ptr + data.length;
}
else
{
createBuffer(BUF_SIZE);
f = openFile(name, "rb");
}
}
~this()
{
if (f.isOpen)
f.close();
}
bool loadNextChunk()
{
if (!f.isOpen)
return false;
filePosition += end - buffer.ptr;
assert(filePosition == f.tell, "%s %s".format(filePosition, f.tell));
auto result = f.rawRead(buffer[0..BUF_SIZE]);
if (result.length)
{
ptr = result.ptr;
end = ptr + result.length;
if (result.length < BUF_SIZE)
buffer[result.length..result.length+PADDING] = 0;
return true;
}
else
{
f.close();
ptr = end = buffer.ptr + buffer.length - PADDING;
usedBuffers ~= buffer;
buffer = null;
return false;
}
}
void createBuffer(size_t size)
{
// guarantee that an out-of-bounds access (within a few bytes)
// will result in a null character, as an optimization
auto bufferSize = size + PADDING;
if (usedBuffers.length && usedBuffers[0].length >= bufferSize)
{
buffer = usedBuffers[0];
usedBuffers = usedBuffers[1..$];
buffer[size..bufferSize] = 0;
}
else
buffer = new char[bufferSize];
ptr = end = buffer.ptr;
buffer[] = 0;
}
@property Position position()
{
return Position(this, filePosition + (buffer ? ptr - buffer.ptr : 0) + shift);
}
@property string positionStr()
{
auto offset = position.offset;
ulong p = 0;
ulong line = 1;
ulong lineStart = 0;
InputRange!(ubyte[]) dataSource;
if (isVirtual)
dataSource = (cast(ubyte[])data).only.inputRangeObject;
else
{
f = openFile(name, "rb");
dataSource = f.byChunk(BUF_SIZE).inputRangeObject;
}
scope(exit) if (!isVirtual) f.close();
foreach (ubyte[] buffer; dataSource)
foreach (b; buffer)
{
if (p == offset)
return "%s(%d,%d)".format(name, line, p-lineStart+1);
p++;
if (b == 10)
{
line++;
lineStart = p;
}
}
return "%s(???)".format(name);
}
@property char front()
{
char c;
return (c = *ptr) != 0 ? c : { loadNextChunk(); return *ptr; }();
}
void popFront()
{
ptr++;
}
}
SourceFile currentFile;
string getBasePath()
{
for (auto f = currentFile; f; f = f.parent)
if (!f.isVirtual)
return f.name.dirName();
return null;
}
string convertFilename(in char[] filename)
{
if (filename.length == 0)
throw new Exception("Empty filename");
auto buf = filename.dup;
foreach (ref c; buf)
if (c == '\\')
c = '/';
return buildPath(getBasePath(), buf);
}
void skipWhitespace()
{
while (true)
{
char c;
while ((c = peekChar())==0)
popFile();
if (c == ' ' || c == '\r' || c == '\n' || c == '\t')
skipChar();
else
if (c == '#')
handlePreprocessor();
else
if (c == '$')
handleVar();
else
if (c == ';')
do
skipChar();
while (peekChar() != '\n');
else
return;
}
}
string[string] vars;
uint[string] namespaceLabels; // for homonym namespaces
uint sourceVersion = 1;
void handlePreprocessor()
{
skipChar(); // #
auto word = readWord();
switch (word)
{
case "mixin":
pushFile(new SourceFile("#mixin", readImmString()));
break;
case "call": // #mixin with arguments
pushFile(new SourceFile("#call", readImmString(), readList!('(', ')', readImmString, false)()));
break;
case "include":
pushFile(new SourceFile(convertFilename(readString())));
break;
case "get":
auto filename = convertFilename(readString());
pushFile(new SourceFile(filename, toStringLiteral(cast(string)read(longPath(filename)))));
break;
case "set":
vars[readWord()] = readImmString();
break;
case "unset":
vars.remove(readWord().idup);
break;
case "privatens":
enforce(sourceVersion < 3, "#privatens is deprecated");
readUInt();
readString();
break;
case "version":
sourceVersion = readUInt().to!uint();
enforce(sourceVersion >= 1 && sourceVersion <= 4, "Invalid/unknown #version");
break;
default:
backpedal(word.length);
throw new Exception("Unknown preprocessor declaration: " ~ word.idup);
}
}
void handleVar()
{
skipChar(); // $
skipWhitespace();
const(char)[] name;
bool asStringLiteral;
if (peekChar() == '"')
{
name = readString();
asStringLiteral = true;
}
else
name = readWord();
if (name.length == 0)
throw new Exception("Empty var name");
if (name[0] >= '1' && name[0] <= '9')
{
for (auto f = currentFile; f; f = f.parent)
if (f.arguments.length)
{
uint index = .to!uint(name)-1;
if (index >= f.arguments.length)
throw new Exception("Argument index out-of-bounds");
string value = f.arguments[index];
pushFile(new SourceFile('$' ~ name.assumeUnique(), asStringLiteral ? toStringLiteral(value) : value));
return;
}
throw new Exception("No arguments in context");
}
else
{
auto pvalue = name in vars;
if (pvalue is null)
throw new Exception("variable %s is not defined".format(name));
string value = *pvalue;
pushFile(new SourceFile('$' ~ name.assumeUnique(), asStringLiteral ? toStringLiteral(value) : value));
}
}
static bool isWordChar(char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '+' || c == '.';
}
const(char)[] readWord()
{
skipWhitespace();
if (!isWordChar(currentFile.front))
//throw new Exception("Word character expected");
return null;
auto b = getBuf();
while (true)
{
auto c = currentFile.front;
if (!isWordChar(c))
break;
b.put(c);
currentFile.popFront();
}
return b.get();
}
string readImmWord() { return readWord().idup; }
ubyte fromHex(char x)
{
switch (x)
{
case '0': return 0;
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
case 'a': case 'A': return 10;
case 'b': case 'B': return 11;
case 'c': case 'C': return 12;
case 'd': case 'D': return 13;
case 'e': case 'E': return 14;
case 'f': case 'F': return 15;
default:
throw new Exception("Malformed hex digit " ~ x);
}
}
void pushFile(SourceFile file)
{
file.parent = currentFile;
currentFile = file;
}
/// For restoring the position of an error
void setFile(SourceFile file)
{
currentFile = file;
}
void popFile()
{
if (!currentFile || !currentFile.parent)
throw new Exception("Unexpected end of file");
currentFile = currentFile.parent;
}
void expectWord(string expected)
{
auto word = readWord();
if (word != expected)
{
backpedal(word.length);
throw new Exception("Expected " ~ expected);
}
}
char peekChar()
{
return currentFile.front;
}
void skipChar()
{
currentFile.popFront();
}
void backpedal(size_t amount=1)
{
currentFile.shift -= amount;
}
char readChar()
{
auto c = currentFile.front;
if (c)
currentFile.popFront;
return c;
}
char readSymbol()
{
skipWhitespace();
return readChar();
}
void expectSymbol(char c)
{
if (readSymbol() != c)
{
backpedal();
throw new Exception("Expected " ~ c);
}
}
// **************************************************
static void mustBeNull(T)(T obj)
{
if (obj !is null)
throw new Exception("Repeating field declaration");
}
static void mustBeSet(string name, T)(T obj)
{
if (obj is null)
throw new Exception(name ~ " not set");
}
static ASType toASType(in char[] name)
{
auto t = name in ASTypeByName;
if (t)
return *t;
throw new Exception("Unknown ASType %s".format(name));
}
static void addUnique(string name, K, V)(ref V[K] aa, K k, V v)
{
if (k in aa)
throw new Exception("Duplicate " ~ name);
aa[k] = v;
}
static string toStringLiteral(string str)
{
if (str is null)
return "null";
else
{
static const char[16] hexDigits = "0123456789ABCDEF";
// TODO: optimize
string s = "\"";
foreach (c; str)
if (c == 0x0A)
s ~= `\n`;
else
if (c == 0x0D)
s ~= `\r`;
else
if (c == '\\')
s ~= `\\`;
else
if (c == '"')
s ~= `\"`;
else
if (c < 0x20)
s ~= ['\\', 'x', hexDigits[c / 0x10], hexDigits[c % 0x10]];
else
s ~= c;
s ~= '"';
return s;
}
}
// **************************************************
ASProgram.Value readValue()
{
ASProgram.Value v;
v.vkind = toASType(readWord());
expectSymbol('(');
switch (v.vkind)
{
case ASType.Integer:
v.vint = readInt();
break;
case ASType.UInteger:
v.vuint = readUInt();
break;
case ASType.Double:
v.vdouble = readDouble();
break;
case ASType.Utf8:
v.vstring = readImmString();
break;
case ASType.Namespace:
case ASType.PackageNamespace:
case ASType.PackageInternalNs:
case ASType.ProtectedNamespace:
case ASType.ExplicitNamespace:
case ASType.StaticProtectedNs:
case ASType.PrivateNamespace:
v.vnamespace = readNamespace();
break;
case ASType.True:
case ASType.False:
case ASType.Null:
case ASType.Undefined:
break;
default:
throw new Exception("Unknown type");
}
expectSymbol(')');
return v;
}
ubyte readFlag(const string[] names)
{
auto word = readWord();
ubyte f = 1;
for (int i=0; f; i++, f<<=1)
if (word == names[i])
return f;
backpedal(word.length);
throw new Exception("Unknown flag %s".format(word));
}
T[] readList(char OPEN, char CLOSE, alias READER, bool ALLOW_NULL, T=typeof(READER()))()
{
static if (ALLOW_NULL)
{
skipWhitespace();
if (peekChar() != OPEN)
{
auto word = readWord();
if (word != "null")
{
backpedal(word.length);
throw new Exception("Expected " ~ OPEN ~ " or null");
}
return null;
}
}
expectSymbol(OPEN);
auto a = appender!(T[]);
skipWhitespace();
if (peekChar() == CLOSE)
{
skipChar(); // CLOSE
static if (ALLOW_NULL)
{
static T[1] sr;
// HACK: give r a .ptr so (r is null) is false, to distinguish it from "null"
auto r = sr[0..0];
assert(r !is null);
return r;
}
else
return null;
}
while (true)
{
a.put(READER());
char c = readSymbol();
if (c == CLOSE)
break;
if (c != ',')
{
backpedal();
throw new Exception("Expected " ~ CLOSE ~ " or ,");
}
}
return a.data;
}
// **************************************************
long readInt()
{
auto w = readWord();
if (w == "null")
return ABCFile.NULL_INT;
auto v = to!long(w);
enforce(v >= ABCFile.MIN_INT && v <= ABCFile.MAX_INT, "Int out of bounds");
return v;
}
ulong readUInt()
{
auto w = readWord();
if (w == "null")
return ABCFile.NULL_UINT;
auto v = to!ulong(w);
enforce(v <= ABCFile.MAX_UINT, "UInt out of bounds");
return v;
}
double readDouble()
{
auto w = readWord();
if (w == "null")
return ABCFile.NULL_DOUBLE;
return to!double(w);
}
const(char)[] readString()
{
skipWhitespace();
char c = readSymbol();
if (c != '"')
{
auto word = readWord();
if (c == 'n' && word == "ull")
return null;
else
{
backpedal(1 + word.length);
throw new Exception("String literal expected");
}
}
auto buf = getBuf();
while (true)
switch (c = readChar())
{
case '"':
return buf.get();
case '\\':
switch (c = readChar())
{
case 'n': buf.put('\n'); break;
case 'r': buf.put('\r'); break;
case 'x':
{
char c0 = readChar();
char c1 = readChar();
buf.put(cast(char)((fromHex(c0) << 4) | fromHex(c1)));
break;
}
default : buf.put(c);
}
break;
case 0:
throw new Exception("Unexpected null/terminator");
default:
buf.put(c);
}
}
StringPool stringPool;
string readImmString() { return stringPool.get(readString()); }
Pool!(ASProgram.Namespace, ASType, string, uint) namespacePool;
ASProgram.Namespace readNamespace()
{
auto word = readWord();
if (word == "null")
return null;
auto kind = toASType(word);
expectSymbol('(');
auto name = readImmString();
uint id;
if (peekChar() == ',')
{
skipChar();
string s = readImmString();
auto pindex = s in namespaceLabels;
if (pindex)
id = *pindex;
else
id = namespaceLabels[s] = cast(uint)namespaceLabels.length+1;
}
expectSymbol(')');
static ASProgram.Namespace createNamespace(ASType kind, string name, uint id)
{
ASProgram.Namespace n = new ASProgram.Namespace;
n.kind = kind;
n.name = name;
n.id = id;
return n;
}
return namespacePool.get(kind, name, id, &createNamespace);
}
ASProgram.Namespace[] readNamespaceSet()
{
return readList!('[', ']', readNamespace, true)();
}
ASProgram.Multiname readMultiname()
{
auto word = readWord();
if (word == "null")
return null;
ASProgram.Multiname m = new ASProgram.Multiname;
m.kind = toASType(word);
expectSymbol('(');
switch (m.kind)
{
case ASType.QName:
case ASType.QNameA:
m.vQName.ns = readNamespace();
expectSymbol(',');
m.vQName.name = readImmString();
break;
case ASType.RTQName:
case ASType.RTQNameA:
m.vRTQName.name = readImmString();
break;
case ASType.RTQNameL:
case ASType.RTQNameLA:
break;
case ASType.Multiname:
case ASType.MultinameA:
m.vMultiname.name = readImmString();
expectSymbol(',');
m.vMultiname.nsSet = readNamespaceSet();
break;
case ASType.MultinameL:
case ASType.MultinameLA:
m.vMultinameL.nsSet = readNamespaceSet();
break;
case ASType.TypeName:
m.vTypeName.name = readMultiname();
m.vTypeName.params = readList!('<', '>', readMultiname, false)();
break;
default:
throw new Exception("Unknown Multiname kind");
}
expectSymbol(')');
return m;
}
ASProgram.Class[string] classesByID;
ASProgram.Method[string] methodsByID;
struct Fixup(T) { Position where; T* ptr; string name; }
Fixup!(ASProgram.Class)[] classFixups;
Fixup!(ASProgram.Method)[] methodFixups;
ASProgram.Trait readTrait()
{
ASProgram.Trait t;
auto kind = readWord();
auto pkind = kind in TraitKindByName;
if (pkind is null)
{
backpedal(kind.length);
throw new Exception("Unknown trait kind");
}
t.kind = *pkind;
kind = TraitKindNames[t.kind];
t.name = readMultiname();
switch (t.kind)
{
case TraitKind.Slot:
case TraitKind.Const:
while (true)
{
auto word = readWord();
switch (word)
{
case "flag":
t.attr |= readFlag(TraitAttributeNames);
break;
case "slotid":
t.vSlot.slotId = readUInt().to!uint();
break;
case "type":
mustBeNull(t.vSlot.typeName);
t.vSlot.typeName = readMultiname();
break;
case "value":
t.vSlot.value = readValue();
break;
case "metadata":
t.metadata ~= readMetadata();
break;
case "end":
return t;
default:
throw new Exception("Unknown %s trait field %s".format(kind, word));
}
}
case TraitKind.Class:
while (true)
{
auto word = readWord();
switch (word)
{
case "flag":
t.attr |= readFlag(TraitAttributeNames);
break;
case "slotid":
t.vClass.slotId = readUInt().to!uint();
break;
case "class":
mustBeNull(t.vClass.vclass);
t.vClass.vclass = readClass();
break;
case "metadata":
t.metadata ~= readMetadata();
break;
case "end":
return t;
default:
throw new Exception("Unknown %s trait field %s".format(kind, word));
}
}
case TraitKind.Function:
while (true)
{
auto word = readWord();
switch (word)
{
case "flag":
t.attr |= readFlag(TraitAttributeNames);
break;
case "slotid":
t.vFunction.slotId = readUInt().to!uint();
break;
case "method":
mustBeNull(t.vFunction.vfunction);
t.vFunction.vfunction = readMethod();
break;
case "metadata":
t.metadata ~= readMetadata();
break;
case "end":
return t;
default:
throw new Exception("Unknown %s trait field %s".format(kind, word));
}
}
case TraitKind.Method:
case TraitKind.Getter:
case TraitKind.Setter:
while (true)
{
auto word = readWord();
switch (word)
{
case "flag":
t.attr |= readFlag(TraitAttributeNames);
break;
case "dispid":
t.vMethod.dispId = readUInt().to!uint();
break;
case "method":
mustBeNull(t.vMethod.vmethod);
t.vMethod.vmethod = readMethod();
break;
case "metadata":
t.metadata ~= readMetadata();
break;
case "end":
return t;
default:
throw new Exception("Unknown %s trait field %s".format(kind, word));
}
}
default:
throw new Exception("Unknown trait kind");
}
}
ASProgram.Metadata readMetadata()
{
auto metadata = new ASProgram.Metadata;
metadata.name = readImmString();
string[] items;
while (true)
switch (readWord())
{
case "item":
items ~= readImmString();
items ~= readImmString();
break;
case "end":
if (sourceVersion < 2)
{
metadata.keys = items[0..$/2];
metadata.values = items[$/2..$];
}
else
{
metadata.keys .length = items.length/2;
metadata.values.length = items.length/2;
foreach (i; 0..items.length/2)
{
metadata.keys [i] = items[i*2 ];
metadata.values[i] = items[i*2+1];
}
}
return metadata;
default:
throw new Exception("Expected item or end");
}
}
ASProgram.Method readMethod()
{
ASProgram.Method m = new ASProgram.Method;
while (true)
{
auto word = readWord();
switch (word)
{
case "name":
mustBeNull(m.name);
m.name = readImmString();
break;
case "refid":
addUnique!("method")(methodsByID, readImmString(), m);
break;
case "param":
m.paramTypes ~= readMultiname();
break;
case "returns":
mustBeNull(m.returnType);
m.returnType = readMultiname();
break;
case "flag":
m.flags |= readFlag(MethodFlagNames);
break;
case "optional":
m.options ~= readValue();
break;
case "paramname":
m.paramNames ~= readImmString();
break;
case "body":
m.vbody = readMethodBody();
m.vbody.method = m;
break;
case "end":
return m;
default:
throw new Exception("Unknown method field %s".format(word));
}
}
}
ASProgram.Instance readInstance()
{
ASProgram.Instance i = new ASProgram.Instance;
i.name = readMultiname();
while (true)
{
auto word = readWord();
switch (word)
{
case "extends":
mustBeNull(i.superName);
i.superName = readMultiname();
break;
case "implements":
i.interfaces ~= readMultiname();
break;
case "flag":
i.flags |= readFlag(InstanceFlagNames);
break;
case "protectedns":
mustBeNull(i.protectedNs);
i.protectedNs = readNamespace();
break;
case "iinit":
mustBeNull(i.iinit);
i.iinit = readMethod();
break;
case "trait":
i.traits ~= readTrait();
break;
case "end":
mustBeSet!("iinit")(i.iinit);
return i;
default:
throw new Exception("Unknown instance field %s".format(word));
}
}
}
ASProgram.Class readClass()
{
ASProgram.Class c = new ASProgram.Class;
while (true)
{
auto word = readWord();
switch (word)
{
case "refid":
addUnique!("class")(classesByID, readImmString(), c);
break;
case "instance":
mustBeNull(c.instance);
c.instance = readInstance();
break;
case "cinit":
mustBeNull(c.cinit);
c.cinit = readMethod();
break;
case "trait":
c.traits ~= readTrait();
break;
case "end":
mustBeSet!("cinit")(c.cinit);
mustBeSet!("instance")(c.instance);
return c;
default:
throw new Exception("Unknown class field %s".format(word));
}
}
}
/+
ASProgram.Script readScript()
{
ASProgram.Script s = new ASProgram.Script;
while (true)
{
auto word = readWord();
switch (word)
{
case "end":
return s;
default:
throw new Exception("Unknown script field " ~ word);
}
}
}
+/
ASProgram.Script readScript()
{
ASProgram.Script s = new ASProgram.Script;
while (true)
{
auto word = readWord();
switch (word)
{
case "sinit":
mustBeNull(s.sinit);
s.sinit = readMethod();
break;
case "trait":
s.traits ~= readTrait();
break;
case "end":
mustBeSet!("sinit")(s.sinit);
return s;
default:
throw new Exception("Unknown script field %s".format(word));
}
}
}
ASProgram.MethodBody readMethodBody()
{
uint[string] labels;
ASProgram.MethodBody m = new ASProgram.MethodBody;
while (true)
{
auto word = readWord();
switch (word)
{
case "maxstack":
m.maxStack = readUInt().to!uint();
break;
case "localcount":
m.localCount = readUInt().to!uint();
break;
case "initscopedepth":
m.initScopeDepth = readUInt().to!uint();
break;
case "maxscopedepth":
m.maxScopeDepth = readUInt().to!uint();
break;
case "code":
m.instructions = readInstructions(labels);
break;
case "try":
m.exceptions ~= readException(labels);
break;
case "trait":
m.traits ~= readTrait();
break;
case "end":
return m;
default:
throw new Exception("Unknown body field %s".format(word));
}
}
}
ABCFile.Label parseLabel(const(char)[] label, uint[string] labels)
{
auto name = label;
int offset = 0;
foreach (i, c; label)
if (c=='-' || c=='+')
{
name = label[0..i];
offset = to!int(label[i..$]);
break;
}
auto lp = name in labels;
if (lp is null)
throw new Exception("Unknown label %s".format(name));
return ABCFile.Label(*lp, offset);
}
ASProgram.Instruction[] readInstructions(ref uint[string] _labels)
{
ASProgram.Instruction[] instructions;
struct LocalFixup { Position where; uint ii, ai; string name; uint si; } // BUG: "pos" won't save correctly in #includes
LocalFixup[] jumpFixups, switchFixups, localClassFixups, localMethodFixups;
uint[string] labels;
while (true)
{
auto word = readWord();
if (word == "end")
break;
if (peekChar() == ':')
{
addUnique!("label")(labels, word.idup, to!uint(instructions.length));
skipChar(); // :
continue;
}
auto popcode = word in OpcodeByName;
if (popcode is null)
{
backpedal(word.length);
throw new Exception("Unknown opcode %s".format(word));
}
ASProgram.Instruction instruction;
instruction.opcode = *popcode;
auto argTypes = opcodeInfo[instruction.opcode].argumentTypes;
instruction.arguments.length = argTypes.length;
foreach (uint i, type; argTypes)
{
final switch (type)
{
case OpcodeArgumentType.Unknown:
throw new Exception("Don't know how to assemble OP_" ~ opcodeInfo[instruction.opcode].name);
case OpcodeArgumentType.ByteLiteral:
if (sourceVersion < 4)
instruction.arguments[i].bytev = to!ubyte(readInt());
else
instruction.arguments[i].bytev = to!byte(readInt());
break;
case OpcodeArgumentType.UByteLiteral:
instruction.arguments[i].ubytev = to!ubyte(readUInt());
break;
case OpcodeArgumentType.IntLiteral:
instruction.arguments[i].intv = readInt();
break;
case OpcodeArgumentType.UIntLiteral:
instruction.arguments[i].uintv = readUInt();
break;
case OpcodeArgumentType.Int:
instruction.arguments[i].intv = readInt();
break;
case OpcodeArgumentType.UInt:
instruction.arguments[i].uintv = readUInt();
break;
case OpcodeArgumentType.Double:
instruction.arguments[i].doublev = readDouble();
break;
case OpcodeArgumentType.String:
instruction.arguments[i].stringv = readImmString();
break;
case OpcodeArgumentType.Namespace:
instruction.arguments[i].namespacev = readNamespace();
break;
case OpcodeArgumentType.Multiname:
instruction.arguments[i].multinamev = readMultiname();
break;
case OpcodeArgumentType.Class:
localClassFixups ~= LocalFixup(currentFile.position, to!uint(instructions.length), i, readImmString());
break;
case OpcodeArgumentType.Method:
localMethodFixups ~= LocalFixup(currentFile.position, to!uint(instructions.length), i, readImmString());
break;
case OpcodeArgumentType.JumpTarget:
case OpcodeArgumentType.SwitchDefaultTarget:
jumpFixups ~= LocalFixup(currentFile.position, to!uint(instructions.length), i, readWord().idup);
break;
case OpcodeArgumentType.SwitchTargets:
string[] switchTargetLabels = readList!('[', ']', readImmWord, false)();
instruction.arguments[i].switchTargets.length = switchTargetLabels.length;
foreach (uint li, s; switchTargetLabels)
switchFixups ~= LocalFixup(currentFile.position, to!uint(instructions.length), i, s, li);
break;
}
if (i < argTypes.length-1)
expectSymbol(',');
}
instructions ~= instruction;
}
foreach (ref f; jumpFixups)
{
scope(failure) setFile(f.where.load());
instructions[f.ii].arguments[f.ai].jumpTarget = parseLabel(f.name, labels);
}
foreach (ref f; switchFixups)
{
scope(failure) setFile(f.where.load());
instructions[f.ii].arguments[f.ai].switchTargets[f.si] = parseLabel(f.name, labels);
}
foreach (ref f; localClassFixups)
classFixups ~= Fixup!(ASProgram.Class)(f.where, &instructions[f.ii].arguments[f.ai].classv, f.name);
foreach (ref f; localMethodFixups)
methodFixups ~= Fixup!(ASProgram.Method)(f.where, &instructions[f.ii].arguments[f.ai].methodv, f.name);
_labels = labels;
return instructions;
}
ASProgram.Exception readException(uint[string] labels)
{
ABCFile.Label readLabel()
{
auto word = readWord();
scope(failure) backpedal(word.length);
return parseLabel(word, labels);
}
ASProgram.Exception e;
while (true)
{
auto word = readWord();
switch (word)
{
case "from":
e.from = readLabel();
break;
case "to":
e.to = readLabel();
break;
case "target":
e.target = readLabel();
break;
case "type":
e.excType = readMultiname();
break;
case "name":
e.varName = readMultiname();
break;
case "end":
return e;
default:
throw new Exception("Unknown exception field %s".format(word));
}
}
}
ASProgram as;
void readProgram()
{
expectWord("program");
while (true)
{
auto word = readWord();
switch (word)
{
case "minorversion":
as.minorVersion = readUInt().to!ushort();
break;
case "majorversion":
as.majorVersion = readUInt().to!ushort();
break;
case "script":
as.scripts ~= readScript();
break;
case "class":
as.orphanClasses ~= readClass();
break;
case "method":
as.orphanMethods ~= readMethod();
break;
case "end":
return;
default:
throw new Exception("Unknown program field %s".format(word));
}
}
}
this(ASProgram as)
{
this.as = as;
}
string context()
{
string s = currentFile.positionStr ~ ": ";
for (auto f = currentFile.parent; f; f = f.parent)
s ~= "\n\t(included from %s)".format(f.positionStr);
return s;
}
void assemble(string mainFilename)
{
auto mainFile = scoped!SourceFile(mainFilename);
pushFile(mainFile);
try
{
readProgram();
applyFixups();
}
catch (Exception e)
{
e.msg = "\n%s\n%s".format(context(), e.msg);
throw e;
}
}
void applyFixups()
{
foreach (ref f; classFixups)
if (f.name is null)
*f.ptr = null;
else
{
auto cp = f.name in classesByID;
if (cp is null)
{
setFile(f.where.load());
throw new Exception("Unknown class refid: " ~ f.name);
}
*f.ptr = *cp;
}
foreach (ref f; methodFixups)
if (f.name is null)
*f.ptr = null;
else
{
auto mp = f.name in methodsByID;
if (mp is null)
{
setFile(f.where.load());
throw new Exception("Unknown method refid: " ~ f.name);
}
*f.ptr = *mp;
}
classFixups = null;
methodFixups = null;
classesByID = null;
methodsByID = null;
}
}
struct StackBuf
{
enum BUF_COUNT = 16;
enum BUF_SIZE = 1024;
static char[BUF_SIZE][BUF_COUNT] bufStorage;
static char[][BUF_COUNT] buffers;
static uint counter;
static this()
{
foreach (n; 0..BUF_COUNT)
buffers[n] = bufStorage[n][];
}
static StackBuf create()
{
StackBuf b;
b.buffer = &buffers[counter++ % BUF_COUNT];
assert((*b.buffer).length);
b.setBuffer();
return b;
}
void setBuffer()
{
ptr = (*buffer).ptr;
end = ptr + (*buffer).length;
}
char[]* buffer;
char* ptr, end;
void put(char c)
{
if (ptr == end)
{
auto len = (*buffer).length;
buffer.length = len * 2;
setBuffer();
ptr += len;
}
*ptr++ = c;
}
char[] get()
{
return (*buffer)[0..ptr-(*buffer).ptr];
}
}
alias StackBuf.create getBuf;
struct StringPool
{
string[string] pool;
string get(in char[] s)
{
if (s is null)
return null;
auto p = s in pool;
if (p)
return *p;
auto i = s.idup;
if (i is null)
i = ""[0..0];
assert(i !is null);
return pool[i] = i;
}
}
struct Pool(T, IndexTypes...)
{
struct Data
{
IndexTypes indices;
}
T[Data] pool;
T get(IndexTypes indices, T function(IndexTypes) ctor)
{
auto data = Data(indices);
auto p = data in pool;
if (p)
return *p;
return pool[data] = ctor(indices);
}
}