dexed/dexed-d/src/common.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;