mirror of https://gitlab.com/basile.b/dexed.git
444 lines
10 KiB
D
444 lines
10 KiB
D
module common;
|
|
|
|
import
|
|
core.stdc.string;
|
|
import
|
|
std.array, std.traits, std.meta, std.conv, std.algorithm, std.file, std.path;
|
|
import
|
|
dparse.lexer, dparse.ast, dparse.parser, dparse.rollback_allocator;
|
|
import
|
|
iz.memory;
|
|
|
|
version(Windows)
|
|
{
|
|
import core.runtime: rt_init, rt_term;
|
|
export extern(C) int d_rt_init(){ return rt_init(); }
|
|
export extern(C) int d_rt_term(){ return rt_term(); }
|
|
}
|
|
|
|
export extern(C) void setRtOptions()
|
|
{
|
|
//import core.gc.config : config;
|
|
//config.gc = "precise";
|
|
}
|
|
|
|
export extern(C) void minimizeGcHeap()
|
|
{
|
|
import core.memory : GC;
|
|
__gshared ubyte c;
|
|
if (c++ > 31)
|
|
{
|
|
GC.collect();
|
|
GC.minimize();
|
|
c = 0;
|
|
}
|
|
}
|
|
|
|
enum ErrorType: ubyte
|
|
{
|
|
warning,
|
|
error
|
|
}
|
|
|
|
/// Stores a dparse AST error
|
|
@NoInit @NoGc struct AstError
|
|
{
|
|
///
|
|
ErrorType type;
|
|
///
|
|
@NoGc string message;
|
|
///
|
|
size_t line, column;
|
|
|
|
@disable this();
|
|
|
|
///
|
|
this(ErrorType type, string message, size_t line, size_t column) @nogc @safe
|
|
{
|
|
this.type = type;
|
|
this.message = message;
|
|
this.line = line;
|
|
this.column = column;
|
|
}
|
|
}
|
|
|
|
alias AstErrors = AstError*[];
|
|
|
|
@nogc @safe unittest
|
|
{
|
|
AstError* err = construct!(AstError)(ErrorType.warning, "warning", 0, 0);
|
|
assert(err);
|
|
assert(err.type == ErrorType.warning);
|
|
assert(err.message == "warning");
|
|
assert(err.column == 0);
|
|
assert(err.line == 0);
|
|
destruct(err);
|
|
}
|
|
|
|
/// Write function call when compiled with version "devel"
|
|
enum logCall =
|
|
q{
|
|
import std.experimental.logger: log;
|
|
version(devel) log();
|
|
else version(unittest) log();
|
|
};
|
|
|
|
|
|
/**
|
|
* Contains all the D version identifiers that are not valid
|
|
* for this operating system.
|
|
*/
|
|
ref const(bool[string]) badVersions()
|
|
{
|
|
if (!_badVersions.length)
|
|
fillBadVersions;
|
|
return _badVersions;
|
|
}
|
|
|
|
private __gshared bool[string] _badVersions;
|
|
|
|
private static immutable predefinedVersions = [
|
|
"AArch64",
|
|
"AIX",
|
|
"all",
|
|
"Alpha",
|
|
"Alpha_HardFloat",
|
|
"Alpha_SoftFloat",
|
|
"Android",
|
|
"ARM",
|
|
"ARM_HardFloat",
|
|
"ARM_SoftFloat",
|
|
"ARM_SoftFP",
|
|
"ARM_Thumb",
|
|
"assert",
|
|
"BigEndian",
|
|
"BSD",
|
|
"CRuntime_Bionic",
|
|
"CRuntime_DigitalMars",
|
|
"CRuntime_Glibc",
|
|
"CRuntime_Microsoft",
|
|
"Cygwin",
|
|
"DigitalMars",
|
|
"DragonFlyBSD",
|
|
"D_Coverage",
|
|
"D_Ddoc",
|
|
"D_HardFloat",
|
|
"D_InlineAsm_X86",
|
|
"D_InlineAsm_X86_64",
|
|
"D_LP64",
|
|
"D_NoBoundsChecks",
|
|
"D_PIC",
|
|
"D_SIMD",
|
|
"D_SoftFloat",
|
|
"D_Version2",
|
|
"D_X32",
|
|
"ELFv1",
|
|
"ELFv2",
|
|
"Epiphany",
|
|
"FreeBSD",
|
|
"FreeStanding",
|
|
"GNU",
|
|
"Haiku",
|
|
"HPPA",
|
|
"HPPA64",
|
|
"Hurd",
|
|
"IA64",
|
|
"iOS",
|
|
"LDC",
|
|
"linux",
|
|
"LittleEndian",
|
|
"MinGW",
|
|
"MIPS32",
|
|
"MIPS64",
|
|
"MIPS_EABI",
|
|
"MIPS_HardFloat",
|
|
"MIPS_N32",
|
|
"MIPS_N64",
|
|
"MIPS_O32",
|
|
"MIPS_O64",
|
|
"MIPS_SoftFloat",
|
|
"NetBSD",
|
|
"none",
|
|
"NVPTX",
|
|
"NVPTX64",
|
|
"OpenBSD",
|
|
"OSX",
|
|
"PlayStation",
|
|
"PlayStation4",
|
|
"Posix",
|
|
"PPC",
|
|
"PPC64",
|
|
"PPC_HardFloat",
|
|
"PPC_SoftFloat",
|
|
"S390",
|
|
"S390X",
|
|
"SDC",
|
|
"SH",
|
|
"SH64",
|
|
"SkyOS",
|
|
"Solaris",
|
|
"SPARC",
|
|
"SPARC64",
|
|
"SPARC_HardFloat",
|
|
"SPARC_SoftFloat",
|
|
"SPARC_V8Plus",
|
|
"SystemZ",
|
|
"SysV3",
|
|
"SysV4",
|
|
"TVOS",
|
|
"unittest",
|
|
"WatchOS",
|
|
"Win32",
|
|
"Win64",
|
|
"Windows",
|
|
"X86",
|
|
"X86_64"
|
|
];
|
|
|
|
private void fillBadVersions()
|
|
{
|
|
// note: compiler switch -m32/64 can lead to wrong results
|
|
|
|
alias addVerId = (ver) => `version(` ~ ver ~ `){}
|
|
else _badVersions["` ~ ver ~ "\"] = true;\n";
|
|
|
|
string addVerionIdentifiers()
|
|
{
|
|
import std.meta: aliasSeqOf;
|
|
import std.range: iota;
|
|
string result;
|
|
foreach(i; aliasSeqOf!(iota(0, predefinedVersions.length)))
|
|
{
|
|
result ~= addVerId(predefinedVersions[i]);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
mixin(addVerionIdentifiers);
|
|
}
|
|
|
|
/**
|
|
* Make a D string compatible with an Object Pascal string literal.
|
|
*/
|
|
string patchPascalString(size_t lenLimit = 0)(string value)
|
|
{
|
|
bool needed;
|
|
foreach(i; 0 .. value.length)
|
|
switch (value[i])
|
|
{
|
|
case '\'' :
|
|
case '\r' :
|
|
case '\n' :
|
|
needed = true;
|
|
break;
|
|
default:
|
|
}
|
|
if (!needed)
|
|
return value;
|
|
|
|
Appender!string app;
|
|
static if (lenLimit)
|
|
const size_t len = value.length > 100 ? 100 : value.length;
|
|
else
|
|
const size_t len = value.length;
|
|
app.reserve(len);
|
|
bool skip;
|
|
L: foreach (immutable i; 0..len)
|
|
{
|
|
const char c = value[i];
|
|
switch (c)
|
|
{
|
|
case 0x80: .. case 0xFF:
|
|
{
|
|
app ~= value[i];
|
|
skip = true;
|
|
break;
|
|
}
|
|
case '\'':
|
|
{
|
|
if (skip)
|
|
app ~= value[i];
|
|
else
|
|
app ~= "'#39";
|
|
app ~= "'";
|
|
skip = false;
|
|
break;
|
|
}
|
|
case '\r': case '\n':
|
|
{
|
|
if (skip)
|
|
app ~= value[i];
|
|
else
|
|
app ~= "'#10";
|
|
if (i != len-1)
|
|
app ~= "'";
|
|
skip = false;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
app ~= value[i];
|
|
skip = false;
|
|
static if (lenLimit)
|
|
if (app.data.length >= len)
|
|
break L;
|
|
}
|
|
}
|
|
}
|
|
return app.data;
|
|
}
|
|
|
|
/// Used to annotate a public field that is written in `pascalStreaming()`.
|
|
enum Pascal;
|
|
|
|
/**
|
|
* Streams a class or a struct using the Object Pascal streaming format, as defined
|
|
* in FPC's FCL or Delphi's RTL.
|
|
*/
|
|
void pascalStreaming(T, string[][] enumLuts = [[""]], bool bin = false)(auto ref T t,
|
|
ref Appender!string stream)
|
|
if (is(T == struct) || is(T == class))
|
|
{
|
|
|
|
// TODO: find speification of the Pascal binary format
|
|
static if (bin) {}
|
|
else
|
|
{
|
|
stream ~= "object ";
|
|
stream ~= T.stringof;
|
|
stream ~= "\r";
|
|
}
|
|
|
|
foreach(member; __traits(allMembers, T))
|
|
{
|
|
static if (is(typeof(__traits(getMember, t, member))) &&
|
|
hasUDA!(__traits(getMember, t, member), Pascal))
|
|
{
|
|
alias MT = typeof(__traits(getMember, t, member));
|
|
alias TestBasicTypes = templateOr!(isSomeString, isIntegral,
|
|
isFloatingPoint, isBoolean,);
|
|
|
|
static if (is(MT == class) || is(MT == struct))
|
|
{
|
|
pascalStreaming!(MT, enumLuts, bin)(__traits(getMember, t, member), stream);
|
|
}
|
|
else static if (is(MT == enum))
|
|
{
|
|
import std.range: iota;
|
|
bool done;
|
|
static if (isIntegral!(OriginalType!MT))
|
|
foreach (i; aliasSeqOf!(iota(0,enumLuts.length)))
|
|
{
|
|
static if (enumLuts[i].length == MT.max+1)
|
|
{
|
|
static if (bin) {}
|
|
else
|
|
{
|
|
stream ~= member;
|
|
stream ~= " = ";
|
|
stream ~= enumLuts[i][__traits(getMember, t, member)];
|
|
stream ~= "\r";
|
|
}
|
|
done = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!done)
|
|
{
|
|
static if (bin) {}
|
|
else
|
|
{
|
|
stream ~= member;
|
|
stream ~= " = ";
|
|
static if (isSomeString!MT)
|
|
stream ~= "'";
|
|
stream ~= to!string(__traits(getMember, t, member));
|
|
static if (isSomeString!MT)
|
|
stream ~= "'";
|
|
stream ~= "\r";
|
|
}
|
|
}
|
|
}
|
|
else static if (TestBasicTypes!MT)
|
|
{
|
|
static if (bin) {}
|
|
else
|
|
{
|
|
stream ~= member;
|
|
stream ~= " = ";
|
|
stream ~= to!string(__traits(getMember, t, member));
|
|
stream ~= "\r";
|
|
}
|
|
}
|
|
else static assert(0);
|
|
}
|
|
}
|
|
|
|
static if (bin) {}
|
|
else
|
|
{
|
|
stream ~= "end\r";
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
enum Bar{bar}
|
|
|
|
static struct TRat
|
|
{
|
|
int notaprop1 = 0;
|
|
@Pascal ubyte subProperty1 = 1;
|
|
@Pascal string subProperty2 = "pascal";
|
|
}
|
|
|
|
static class TFoo
|
|
{
|
|
int notaprop1 = 0;
|
|
@Pascal ubyte property1 = 1;
|
|
@Pascal string property2 = "pascal";
|
|
@Pascal Bar property3 = Bar.bar;
|
|
@Pascal TRat rat;
|
|
}
|
|
|
|
Appender!string stream;
|
|
pascalStreaming!(TFoo, [["bar"]])(new TFoo, stream);
|
|
assert(stream.data != "");
|
|
}
|
|
|
|
/**
|
|
* Produces and visits the AST for a source code.
|
|
*
|
|
* This function is used to handle the content of a MixinExpression in an
|
|
* ASTVisitor.
|
|
*/
|
|
T parseAndVisit(T : ASTVisitor, A...)(const(char)[] source, A a)
|
|
{
|
|
import std.functional;
|
|
|
|
RollbackAllocator allocator;
|
|
LexerConfig config = LexerConfig("", StringBehavior.source, WhitespaceBehavior.skip);
|
|
StringCache cache = StringCache(StringCache.defaultBucketCount);
|
|
const(Token)[] tokens = getTokensForParser(cast(ubyte[]) source, config, &cache);
|
|
Module mod = parseModule(tokens, "", &allocator, toDelegate(&ignoreErrors));
|
|
T result = construct!(T)(a);
|
|
result.visit(mod);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* By default libdparse outputs errors and warnings to the standard streams.
|
|
* This function prevents that.
|
|
*/
|
|
void ignoreErrors(string, size_t, size_t, string, bool) @system {}
|
|
|
|
/**
|
|
* Split a C string representing a list of filenames into an array of strings.
|
|
* The filenames are separated with the system path separator.
|
|
*/
|
|
alias joinedFilesToFiles = (const char* a) => a[0 .. a.strlen]
|
|
.splitter(pathSeparator)
|
|
.filter!exists
|
|
.filter!(b => b != "")
|
|
.array;
|