1481 lines
31 KiB
D
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);
|
|
}
|
|
}
|