diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c949db3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +.dub +docs.json +__dummy.html +docs/ +dlp.so +dlp.dylib +dlp.dll +dlp.a +dlp.lib +dlp-test-* +*.exe +*.o +*.obj +*.lst +*.a +*.lib +/dlp diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..0f0f6c6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vendor/dmd"] + path = vendor/dmd + url = https://github.com/dlang/dmd diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..90ed549 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,52 @@ +dist: trusty +git: + depth: false + +language: d +d: + - dmd + - ldc-1.11.0 + +os: + - linux + - osx + +env: + global: MACOSX_DEPLOYMENT_TARGET=10.9 + +matrix: + include: + # beta and nightly builds for DMD and LDC + - &entry + d: dmd-beta + os: linux + - <<: *entry + d: ldc-beta + - <<: *entry + d: dmd-nightly + + # deployment + - &deploy + stage: deploy + if: tag IS present + d: ldc-beta + os: linux + script: ./tools/build_release.sh + deploy: + provider: releases + api_key: + secure: Sw5vEwezf+EzoxFeRNKk6PqsWv0HDXnC3JhVMwtaGNWAOdGc5rdZVcUIR88S5Q54eB16z1H+SHu9H/4SSdCschjjxsQmAqw/KicpjFw/puIgA96fpcQM+ixTbuXpw2YTfAn7jg9CWukDQ/3Wb70F8NV5hUPOpLPcyLloicp8Uy5LAmbvKIhegot/tzdRvKGaYjswHBFTThhiZEUFqZWMb/e/ge92Rp/Dhp+DdEb7XnPiqI/B6JRJs96KIdCj2o23GKFb83brIza8i6dsMybFLRiKhtrm1SgrFwyVdF9NRFe6/uDUJFJC8A6dWfhiYl24Pp3qCBU2SrlCEqdecBTx9SyjHfjJMvhlCYOxLNxvv86sGbmpfIIt/J0S+kAbi9tgcsGjtJhvnrHTPOy3Qd1mhg/+IrXccLSoL3zSvv7SNqPnxmA43skBdcswghjPBJyNtUk09WWnPUvcRAD6iBitQDC/ZYYC1+nkuMzGJ22tAiKlQ48tWI955kP9ClTf/pZrSMlXOuCAIaiUh7+iuR4O68XXF0AfWabGV5JV1TzNqTT4IofsdbCZbajjApVbUqvc/LB0zHzaTcchfLbUE7TS+Chc8cym+W8ebXvGN57L/7/jaaYbdtu++8bMaqDQidKo6B4krVtUxZoCTDkMHSKfxws1vThmWEZKzErNgsd//fE= + draft: true + tag_name: $TRAVIS_TAG + file_glob: true + file: dlp*.tar.xz + skip_cleanup: true + on: + repo: jacob-carlborg/dlp + tags: true + - <<: *deploy + os: osx + + allow_failures: + - <<: *entry + d: dmd-nightly diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..de58c1b --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,58 @@ +platform: + - x64 + - x86 + +environment: + DVersion: stable + + matrix: + - d: dmd + - d: ldc + +init: + - ps: | + if ($env:PLATFORM -eq 'x86') + { + $env:arch = 'x86' + $env:vcvarsall_arg = 'x86' + } + else + { + $env:arch = 'x86_64' + $env:vcvarsall_arg = 'x86_amd64' + } + +install: + - ps: git submodule -q update --init --recursive + - ps: tools/install_dc.ps1 + +build_script: + - echo dummy build script - dont remove me + +before_test: + - '"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall" %vcvarsall_arg%' + +test_script: + - ps: | + if ($env:APPVEYOR_REPO_TAG -eq 'true') + { + tools/build_release.ps1 + } + else + { + dub test --verror --arch=$env:arch --compiler=$env:DC + } + +artifacts: + - path: dlp*.7z + +deploy: + description: '' + provider: GitHub + auth_token: + secure: YuE5X5DFwdA7qB+OFY93l7sCWVBQ77cqKSsAtAoTrK9rMiJogtd4dK0hAh1kOmbk + artifact: /dlp.+\.7z/ + draft: true + on: + appveyor_repo_tag: true + d: ldc diff --git a/dub.sdl b/dub.sdl new file mode 100644 index 0000000..eb4427d --- /dev/null +++ b/dub.sdl @@ -0,0 +1,31 @@ +name "dlp" +description "Tool for processing the D language" +authors "Jacob Carlborg" +copyright "Copyright © 2018, Jacob Carlborg" +license "BSL-1.0" + +targetType "executable" +mainSourceFile "source/dlp/driver/main.d" +dependency "dmd" path="vendor/dmd" + +preGenerateCommands "$PACKAGE_DIR/tools/generate_version.sh" platform="posix" +preGenerateCommands "$PACKAGE_DIR/tools/generate_version.bat" platform="windows" + +preBuildCommands "cl /nologo /EHsc /TP /c vendor/dmd/src/dmd/backend/strtold.c /Fotmp/strtold.obj /I vendor/dmd/src/dmd/root" platform="windows-x86_64-dmd" +preBuildCommands "cl /nologo /EHsc /TP /c vendor/dmd/src/dmd/backend/strtold.c /Fotmp/strtold.obj /I vendor/dmd/src/dmd/root" platform="windows-ldc" + +stringImportPaths "tmp" + +lflags "tmp/strtold.obj" platform="windows-x86_64-dmd" +lflags "tmp/strtold.obj" platform="windows-ldc" + +buildType "release" { + buildOptions "optimize" "inline" platform="posix" + buildOptions "optimize" "inline" platform="windows-ldc" + buildOptions "optimize" platform="windows-dmd" + + dflags "-flto=full" platform="posix-ldc" + + lflags "-static" platform="linux-ldc" + lflags "-dead_strip" platform="osx" +} diff --git a/dub.selections.json b/dub.selections.json new file mode 100644 index 0000000..1d2311a --- /dev/null +++ b/dub.selections.json @@ -0,0 +1,8 @@ +{ + "fileVersion": 1, + "versions": { + "dmd": { + "path": "vendor/dmd" + } + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..64ed390 --- /dev/null +++ b/readme.md @@ -0,0 +1,54 @@ +# DLP - D Language Processing + +DLP is a tool collecting commands/tasks related to processing the +D programming language. It uses the DMD frontend as a library to process D code. + +## Requirements + +To run this tool you need to have druntime and Phobos installed. This is easiest +accomplished by installing the DMD compiler: https://dlang.org/download.html. + +## Download + +For the latest release see: [releases/latest](https://github.com/jacob-carlborg/dlp/releases/latest). +Pre-compiled binaries are available for macOS and Linux as 64 bit binaries and +Windows as 32 and 64 bit binaries. The Linux binaries are completely statically +linked and should work on all distros. The macOS binaries should work on macOS +Mavericks (10.9) and later. + +## Commands + +* **leaf-functions** - Prints all leaf functions to standard out. A leaf + function is a function that doesn't call any other functions, or doesn't have + a body. + +### Usage + +``` +$ cat test.d +void main() +{ +} +$ dlp leaf-functions test +test.d(1): test.d.main +``` + +## Building + +Building is done using Dub. + +1. Clone the repository using: + ``` + git clone --recursive https://github.com/jacob-carlborg/dlp.git + ``` +1. Run `dub build` to build the project + +## Running the Tests + +Running the tests is done using Dub. + +1. Clone the repository using: + ``` + git clone --recursive https://github.com/jacob-carlborg/dlp.git + ``` +1. Run `dub test` to run the tests diff --git a/source/dlp/core/algorithm.d b/source/dlp/core/algorithm.d new file mode 100644 index 0000000..4fb3bcc --- /dev/null +++ b/source/dlp/core/algorithm.d @@ -0,0 +1,25 @@ +module dlp.core.algorithm; + +template flatMap(func...) +if (func.length >= 1) +{ + import std.range : isInputRange; + import std.traits : Unqual; + + auto flatMap(Range)(Range range) if (isInputRange!(Unqual!Range)) + { + import std.algorithm : map, joiner; + + return range.map!func.joiner; + } +} + +/// +unittest +{ + import std.algorithm : equal; + + [1, 2, 3, 4] + .flatMap!(e => [e, -e]) + .equal([1, -1, 2, -2, 3, -3, 4, -4]); +} diff --git a/source/dlp/core/optional.d b/source/dlp/core/optional.d new file mode 100644 index 0000000..c5cc848 --- /dev/null +++ b/source/dlp/core/optional.d @@ -0,0 +1,308 @@ +module dlp.core.optional; + +import dlp.core.traits : isNullable; + +private struct None {} + +version (unittest) +{ + // Cannot put this inside the unittest block due to + // https://issues.dlang.org/show_bug.cgi?id=19157 + private struct Foo + { + int a; + Bar* b; + + int c(int a) + { + return a; + } + + Bar d(int a) + { + return Bar(a); + } + } + + private struct Bar + { + int a; + + int foo(int a) + { + return a; + } + } +} + +struct Optional(T) +{ + import std.traits : Unqual; + + // private alias OriginalType = U; + // private alias T = Unqual!U; + + private Unqual!T value; + + static if (!isNullable!T) + private bool present; + + this(T value) + { + opAssign(value); + } + + this(None) + { + + } + + void opAssign(T value) + { + this.value = value; + + static if (!isNullable!T) + present = true; + } + + void opAssign(None) + { + static if (isNullable!T) + value = null; + else + present = false; + } + + unittest + { + enum newVale = 4; + Optional!int a = 3; + a = newVale; + assert(a.get == newVale); + } + + unittest + { + Optional!int a = 3; + a = none; + assert(!a.isPresent); + } + + bool isPresent() const + { + static if (isNullable!T) + return value !is null; + + else + return present; + } + + unittest + { + Optional!int a = 3; + assert(a.isPresent); + + Optional!(int*) b = null; + assert(!b.isPresent); + } + + inout(T) get() inout + { + assert(isPresent); + return value; + } + + unittest + { + Optional!int a = 3; + assert(a.get == 3); + } + + T or(lazy T alternativeValue) + { + return isPresent ? value : alternativeValue; + } + + unittest + { + Optional!int a = 3; + assert(a.or(4) == 3); + + Optional!int b = none; + assert(b.or(4) == 4); + } + + bool empty() const + { + return !isPresent; + } + + unittest + { + Optional!int a = 3; + assert(!a.empty); + + Optional!int b = none; + assert(b.empty); + } + + T front() + { + return get; + } + + unittest + { + Optional!int a = 3; + assert(a.get == 3); + } + + void popFront() + { + static if (isNullable!T) + value = null; + else + present = false; + } + + unittest + { + Optional!int a = 3; + a.popFront(); + assert(!a.isPresent); + } + + size_t length() + { + return isPresent ? 1 : 0; + } + + unittest + { + Optional!int a = 3; + assert(a.length == 1); + + Optional!int b = none; + assert(b.length == 0); + } + + auto ref opDispatch(string name, Args...)(auto ref Args args) + { + import std.traits : PointerTarget, isPointer; + import dlp.core.traits : hasField, TypeOfMember, getMember; + + static if (isPointer!T) + alias StoredType = PointerTarget!T; + else + alias StoredType = T; + + static if (is(StoredType == class) || is(StoredType == struct)) + { + static if (hasField!(StoredType, name)) + { + alias FieldType = TypeOfMember!(StoredType, name); + + if (isPresent) + return optional(value.getMember!name); + else + return none!FieldType; + } + else + { + alias ReturnType = typeof(__traits(getMember, value, name)(args)); + + if (isPresent) + return optional(__traits(getMember, value, name)(args)); + else + return none!ReturnType; + } + } + else + { + return optional(value.getMember!name); + } + + assert(0); + } + + unittest + { + assert(Optional!Foo(Foo(3)).a.get == 3); + assert(Optional!Foo.init.a.empty); + + assert(Optional!Foo(Foo()).opDispatch!"c"(4).get == 4); + assert(Optional!Foo.init.c(4).empty); + + assert(Optional!Foo(Foo(1, new Bar(5))).b.a.get == 5); + assert(Optional!Foo(Foo(1)).b.a.empty); + } +} + +Optional!T optional(T)(T value) +{ + return Optional!T(value); +} + +unittest +{ + assert(optional(3).isPresent); +} + +unittest +{ + int i; + assert(optional(&i).isPresent); + assert(!optional!(int*)(null).isPresent); +} + +unittest +{ + import std.algorithm : map; + + enum value = 3; + assert(optional(value).map!(e => e).front == value); +} + +Optional!T some(T)(T value) +in +{ + static if (isNullable!T) + assert(value !is null); +} +do +{ + Optional!T o; + o.value = value; + + static if (!isNullable!(T)) + o.present = true; + + return o; +} + +unittest +{ + assert(some(3).isPresent); +} + +unittest +{ + int a; + int* b = &a; + assert(some(b).isPresent); +} + +None none() +{ + return None(); +} + +Optional!T none(T)() +{ + return Optional!T.init; +} + +unittest +{ + assert(!none!int.isPresent); +} diff --git a/source/dlp/core/set.d b/source/dlp/core/set.d new file mode 100644 index 0000000..af4c8d5 --- /dev/null +++ b/source/dlp/core/set.d @@ -0,0 +1,169 @@ +module dlp.core.set; + +struct Set(T) +{ + private alias Value = void[0]; + private alias Key = KeyType!T; + + private alias ByKeyRange = typeof( + delegate() const { return storage.byKey(); }() + ); + + Value[Key] storage; + + this(const T[] values ...) + { + put(values); + } + + /// + unittest + { + auto set = Set!int(1, 2, 3, 4); + } + + this(const Set set) + { + put(set); + } + + /// + unittest + { + auto a = Set!int(1, 2, 3, 4); + auto b = Set!int(a); + + assert(a == b); + } + + void put(const T value) pure nothrow + { + storage[cast(Key) value] = Value.init; + } + + /// + unittest + { + Set!int set; + set.put(3); + + assert(set.length == 1); + } + + void put(const T[] values ...) pure nothrow + { + foreach (v; values) + put(v); + } + + /// + unittest + { + Set!int set; + set.put(1, 2, 3, 4); + + assert(set.length == 4); + } + + void put(const Set set) pure nothrow + { + foreach (key; set[]) + put(key); + } + + /// + unittest + { + Set!int a; + a.put(1); + a.put(2); + + Set!int b; + b.put(a); + + assert(b == a); + } + + bool opBinaryRight(string op)(const T rhs) + if (op == "in") + { + return (cast(Key) rhs in storage) !is null; + } + + /// + unittest + { + auto set = Set!int(1, 2); + assert(2 in set); + + auto o = new Object; + auto s = Set!Object(o); + s.put(new Object); + + assert(o in s); + } + + auto opSlice() const + { + return ByKeyRangeWrapper(storage.byKey); + } + + /// + unittest + { + import std.algorithm : map, canFind; + + auto set = Set!int(1, 2); + auto range = set[].map!(e => e); + + // cannot compare "range" with another range since the order of a set is + // not guaranteed. + assert(range.canFind(1)); + assert(range.canFind(2)); + } + + size_t length() const + { + return storage.length; + } + + /// + unittest + { + auto set = Set!int(1, 2); + assert(set.length == 2); + } + + private static struct ByKeyRangeWrapper + { + private ByKeyRange range; + + T front() + { + return cast(T) range.front(); + } + + void popFront() + { + range.popFront(); + } + + bool empty() + { + return range.empty(); + } + } +} + +private template KeyType(T) +{ + static if (is(T == class) || is(T == interface)) + { + static if (__traits(getLinkage, T) == "D" ) + alias KeyType = T; + else + alias KeyType = void*; + } + else + alias KeyType = T; +} diff --git a/source/dlp/core/test.d b/source/dlp/core/test.d new file mode 100644 index 0000000..985b362 --- /dev/null +++ b/source/dlp/core/test.d @@ -0,0 +1,8 @@ +module dlp.core.test; + +version(unittest): + +struct test +{ + string name; +} diff --git a/source/dlp/core/traits.d b/source/dlp/core/traits.d new file mode 100644 index 0000000..6099ee7 --- /dev/null +++ b/source/dlp/core/traits.d @@ -0,0 +1,181 @@ +module dlp.core.traits; + +import std.traits : isAssociativeArray, isDynamicArray, isPointer; + +/// Evaluates to `true` if the given type can hold `null`. +template isNullable(T) +{ + enum isNullable = is(T == class) || is(T == interface) || + is(T == function) || is(T == delegate) || isAssociativeArray!T || + isDynamicArray!T || isPointer!T; +} + +/// +unittest +{ + assert(isNullable!Object); + assert(isNullable!(Object.Monitor)); + assert(isNullable!(void function())); + assert(isNullable!(void delegate())); + assert(isNullable!(int*)); + assert(!isNullable!int); +} + +// /** +// * Returns `true` if `T` has a field with the given name $(D_PARAM field). +// * +// * Params: +// * T = the type of the class/struct +// * field = the name of the field +// * +// * Returns: `true` if `T` has a field with the given name $(D_PARAM field) +// */ +// bool hasField(T, string field)() +// { +// static foreach (i; 0 .. T.tupleof.length) +// { +// static if (nameOfFieldAt!(T, i) == field) +// return true; +// else +// return false; +// } +// } + +template hasField (T, string field) +{ + enum hasField = hasFieldImpl!(T, field, 0); +} + +private template hasFieldImpl (T, string field, size_t i) +{ + static if (T.tupleof.length == i) + enum hasFieldImpl = false; + + else static if (nameOfFieldAt!(T, i) == field) + enum hasFieldImpl = true; + + else + enum hasFieldImpl = hasFieldImpl!(T, field, i + 1); +} + +/// +unittest +{ + static struct Foo + { + int bar; + } + + assert(hasField!(Foo, "bar")); + assert(!hasField!(Foo, "foo")); +} + +/** + * Evaluates to a string containing the name of the field at given position in the given type. + * + * Params: + * T = the type of the class/struct + * position = the position of the field in the tupleof array + */ +template nameOfFieldAt (T, size_t position) +{ + import std.format : format; + + enum errorMessage = `The given position "%s" is greater than the number ` ~ + `of fields (%s) in the type "%s"`; + + static assert (position < T.tupleof.length, format(errorMessage, position, + T.tupleof.length, T.stringof)); + + enum nameOfFieldAt = __traits(identifier, T.tupleof[position]); +} + +/// +unittest +{ + static struct Foo + { + int foo; + int bar; + } + + assert(nameOfFieldAt!(Foo, 1) == "bar"); +} + +/** + * Returns `true` if `T` has a member with the given name $(D_PARAM member). + * + * Params: + * T = the type of the class/struct + * member = the name of the member + * + * Returns: `true` if `T` has a member with the given name $(D_PARAM member) + */ +bool hasMember(T, string member)() +{ + return __traits(hasMember, T, member); +} + +/// +unittest +{ + static struct Foo + { + int bar; + } + + assert(hasMember!(Foo, "bar")); + assert(!hasMember!(Foo, "foo")); +} + +/** + * Evaluates to the type of the member with the given name + * + * Params: + * T = the type of the class/struct + * member = the name of the member + */ +template TypeOfMember (T, string member) +{ + import std.format : format; + + enum errorMessage = `The given member "%s" does not exist in the type "%s"`; + + static if (!hasMember!(T, member)) + static assert(false, format(errorMessage, member, T.stringof)); + + else + alias TypeOfMember = typeof(__traits(getMember, T, member)); +} + +/// +unittest +{ + static struct Foo + { + int foo; + } + + assert(is(TypeOfMember!(Foo, "foo") == int)); + assert(!__traits(compiles, { alias T = TypeOfMember!(Foo, "bar"); })); +} + +auto ref getMember(string member, T)(auto ref T value) +{ + enum errorMessage = `The given member "%s" does not exist in the type "%s"`; + + static if (!hasMember!(T, member)) + static assert(false, format(errorMessage, member, T.stringof)); + else + return __traits(getMember, value, member); +} + +unittest +{ + static struct Foo + { + int foo; + } + + assert(Foo(3).getMember!"foo" == 3); +} diff --git a/source/dlp/driver/application.d b/source/dlp/driver/application.d new file mode 100644 index 0000000..5696aaf --- /dev/null +++ b/source/dlp/driver/application.d @@ -0,0 +1,245 @@ +module dlp.driver.application; + +import std.array; +import std.stdio; + +import dlp.driver.cli; +import dlp.core.optional; + +enum ExitStatus +{ + success, + fail, + stop +} + +class Application +{ + import std.string : strip; + import std.range : drop; + + import dlp.driver.command : Command; + + enum version_ = import("version").strip.drop(1); + + static final class ParsedArguments : .ParsedArguments + { + bool version_; + bool help; + + Optional!string command; + string[] remainingArgs; + } + + private string[] args; + private Command[string] commands; + + static int start(string[] args) + { + return new Application(args).run(); + } + + this(string[] args) + in + { + assert(!args.empty); + } + do + { + this.args = args; + + registerCommands(); + } + +private: + + int run() + { + import std.stdio : stderr; + import std.array : empty; + + import dlp.visitors.utility : DiagnosticsException; + + try + return runImplementation() ? 0 : 1; + catch (DiagnosticsException) + { + // For DiagnosticsException the errors have already been printed + } + catch (Throwable t) + stderr.writeln(t.message); + + return 1; + } + + bool runImplementation() + { + import dlp.driver.commands.leaf_functions : LeafFunctions; + + registerCommands(); + auto parsedArguments = parseCli(); + + const result = handleArguments(parsedArguments); + + if (result == ExitStatus.fail) + return false; + + if (result == ExitStatus.success) + invokeCommand(parsedArguments.command.get, parsedArguments.remainingArgs); + + return true; + } + + void invokeCommand(const string command, const string[] args) + { + import std.format : format; + + if (auto invoker = command in commands) + (*invoker).run(args); + else + throw new CliException(format!`Unrecognized command "%s"`(command)); + } + + void registerCommands() + { + import dlp.driver.commands.leaf_functions : LeafFunctions; + + registerCommand!LeafFunctions; + } + + void registerCommand(Command)() + { + auto command = new Command(); + commands[command.name] = command; + } + + ParsedArguments parseCli() + { + import std.typecons : tuple; + + static ParsedArguments parseGlobalCLi(string[] args) + { + import std.getopt : getopt, defaultGetoptPrinter; + + auto parsedArguments = new ParsedArguments; + + auto result = getopt(args, + "version|V", "Print the version of DLP and exit.", &parsedArguments.version_ + ); + + parsedArguments.remainingArgs = args; + parsedArguments.help = result.helpWanted; + parsedArguments.getoptResult = result; + + return parsedArguments; + } + + static auto parseCommand(string[] args) pure + { + import std.algorithm : startsWith; + import std.array : empty, front; + import std.typecons : tuple; + + if (args.empty || args.front.startsWith("-")) + return tuple(none!string, args); + + return tuple(some(args.front), args[1 .. $]); + } + + auto parsedArguments = parseGlobalCLi(args); + auto t = parseCommand(parsedArguments.remainingArgs[1 .. $]); + parsedArguments.command = t[0]; + parsedArguments.remainingArgs = t[1]; + + return parsedArguments; + } + + ExitStatus handleArguments(ParsedArguments parsedArguments) + { + import std.stdio : stderr; + + if (parsedArguments.help) + { + printHelp(parsedArguments); + return ExitStatus.stop; + } + + if (parsedArguments.version_) + { + printVersion(); + return ExitStatus.stop; + } + + if (parsedArguments.command.empty) + { + stderr.writeln("No command specified"); + printHelp(parsedArguments); + return ExitStatus.fail; + } + + return ExitStatus.success; + } + + void printVersion() + { + import std.stdio : writeln; + writeln(version_); + } + + void printHelp(ParsedArguments parsedArguments) + { + import std.format : format; + import std.getopt : defaultGetoptPrinter; + import std.stdio : writef; + + string generateCommandsHelp() + { + import std.algorithm : joiner, map, reduce, max; + import std.conv : to; + import std.format : format; + import std.range : repeat; + + const maxLength = commands + .byValue + .map!(e => e.name.length) + .array + .reduce!max; + + alias formatCommand = e => format( + " %-*s %s\n", maxLength + 1, e.name, e.shortHelp + ); + + return commands + .byValue + .map!formatCommand + .joiner("\n") + .to!string; + } + + enum helpBanner = q"BANNER +Usage: dlp [options] +Version: %s + +Options: +BANNER".format(version_); + + defaultGetoptPrinter(helpBanner, parsedArguments.getoptResult.options); + + writef("\nCommands:\n%s", generateCommandsHelp()); + } +} + +private class CliException : Exception +{ + @nogc @safe pure nothrow this(string msg, string file = __FILE__, + size_t line = __LINE__, Throwable nextInChain = null) + { + super(msg, file, line, nextInChain); + } + + @nogc @safe pure nothrow this(string msg, Throwable nextInChain, + string file = __FILE__, size_t line = __LINE__) + { + super(msg, file, line, nextInChain); + } +} diff --git a/source/dlp/driver/cli.d b/source/dlp/driver/cli.d new file mode 100644 index 0000000..47e01a5 --- /dev/null +++ b/source/dlp/driver/cli.d @@ -0,0 +1,8 @@ +module dlp.driver.cli; + +import std.getopt : GetoptResult; + +abstract class ParsedArguments +{ + GetoptResult getoptResult; +} diff --git a/source/dlp/driver/command.d b/source/dlp/driver/command.d new file mode 100644 index 0000000..7167ffd --- /dev/null +++ b/source/dlp/driver/command.d @@ -0,0 +1,17 @@ +module dlp.driver.command; + +import dlp.core.optional : Optional; + +abstract class Command +{ + abstract string name() const; + abstract string shortHelp() const; + abstract void run(const string[] args) const; + + Optional!string longHelp() const + { + import dlp.core.optional : none; + + return none!string; + } +} diff --git a/source/dlp/driver/commands/leaf_functions.d b/source/dlp/driver/commands/leaf_functions.d new file mode 100644 index 0000000..c8b69f9 --- /dev/null +++ b/source/dlp/driver/commands/leaf_functions.d @@ -0,0 +1,75 @@ +module dlp.driver.commands.leaf_functions; + +import dlp.driver.command : Command; + +class LeafFunctions : Command +{ + import dmd.func : FuncDeclaration; + + import dlp.core.optional : Optional, some; + + override string name() const + { + return "leaf-functions"; + } + + override string shortHelp() const + { + return "Prints all leaf functions."; + } + + override Optional!string longHelp() const + { + return some("Prints all leaf functions to standard out. A leaf function " ~ + "is a function that doesn't call any other functions, or doesn't " ~ + "have a body."); + } + + override void run(const string[] args) const + { + import std.algorithm : each, map, sort; + import std.array : array; + import std.file : readText; + import std.typecons : tuple; + + import dlp.core.algorithm : flatMap; + import dlp.visitors.leaf_functions : leafFunctions; + + alias sortByLine = (a, b) => a.location.linnum < b.location.linnum; + alias sortByColumn = (a, b) => a.location.charnum < b.location.charnum; + + args + .map!(e => tuple(e, readText(e))) + .flatMap!(e => leafFunctions(e.expand)[]) + .map!toLeafFunction + .array + .sort!sortByLine + .release + .sort!sortByColumn // we sort by column since there can be multiple functions on the same line + .each!printResult; + } + + static LeafFunction toLeafFunction(FuncDeclaration func) + { + import dlp.visitors.utility : fullyQualifiedName; + + return LeafFunction(func.loc, func.fullyQualifiedName); + } + + static void printResult(LeafFunction leafFunction) + { + import std.string : fromStringz; + import std.stdio : writefln; + + const location = leafFunction.location.toChars.fromStringz; + writefln("%s: %s", location, leafFunction.fullyQualifiedName); + } +} + +private struct LeafFunction +{ + import dmd.globals : Loc; + + Loc location; + string fullyQualifiedName; +} diff --git a/source/dlp/driver/main.d b/source/dlp/driver/main.d new file mode 100644 index 0000000..6743293 --- /dev/null +++ b/source/dlp/driver/main.d @@ -0,0 +1,8 @@ +module dlp.driver.main; + +import dlp.driver.application; + +int main(string[] args) +{ + return Application.start(args); +} diff --git a/source/dlp/visitors/leaf_functions.d b/source/dlp/visitors/leaf_functions.d new file mode 100644 index 0000000..618f6b9 --- /dev/null +++ b/source/dlp/visitors/leaf_functions.d @@ -0,0 +1,284 @@ +module dlp.visitors.leaf_functions; + +import dmd.func : FuncDeclaration; +import dmd.dmodule : Module; +import dmd.visitor : SemanticTimeTransitiveVisitor; + +import dlp.core.set; +import dlp.visitors.utility; + +Set!FuncDeclaration leafFunctions(const string filename, const string content) +{ + return runFullFrontend(filename, content) + .leafFunctions(); +} + +private: + +Module runFullFrontend(const string filename, const string content) +{ + import std.algorithm : each; + + import dmd.frontend : addImport, initDMD, findImportPaths, fullSemantic, + parseModule, parseImportPathsFromConfig; + import dmd.globals : global; + + global.params.mscoff = global.params.is64bit; + initDMD(); + // import std.path : expandTilde; + // const binPath = "~/.dvm/compilers/dmd-2.081.1/osx/bin".expandTilde; + // parseImportPathsFromConfig(binPath ~ "/dmd.conf", binPath).each!addImport; + findImportPaths.each!addImport; + + auto t = parseModule(filename, content); + + if (t.diagnostics.hasErrors) + throw new DiagnosticsException(t.diagnostics); + + fullSemantic(t.module_); + handleDiagnosticErrors(); + + return t.module_; +} + +Set!FuncDeclaration leafFunctions(Module module_) +{ + extern (C++) static class Visitor : SemanticTimeTransitiveVisitor + { + import dmd.expression : CallExp; + import dlp.core.optional : Optional, none; + + alias visit = typeof(super).visit; + + Optional!FuncDeclaration lastVisitedFuncDeclaration; + private Set!FuncDeclaration leafFunctions; + private Set!FuncDeclaration visitedFunctions; + + override void visit(CallExp e) + { + lastVisitedFuncDeclaration = none; + super.visit(e); + } + + override void visit(FuncDeclaration func) + { + import std.algorithm : each; + + if (func in visitedFunctions) return; + + visitedFunctions.put(func); + lastVisitedFuncDeclaration = func; + super.visit(func); + lastVisitedFuncDeclaration.each!(f => leafFunctions.put(f)); + } + } + + scope visitor = new Visitor; + module_.accept(visitor); + + return visitor.leafFunctions; +} + +version(unittest): + +import dmd.globals : Loc; + +import dlp.core.test; + +string setup() +{ + return q{ + { + import dmd.globals : global; + global.params.showColumns = true; + } + + scope (exit) + { + import dmd.globals : global; + global.params.showColumns = false; + } + }; +} + +bool leafFunctionEqualsLoc(const string content, int line, int column) +{ + enum filename = "test.d"; + const expected = Loc(filename, line, column); + + const leafFunctions = leafFunctions(filename, content); + + if (leafFunctions.length == 0) + return false; + + const actual = leafFunctions[].front.loc; + + return actual.equals(expected); +} + +@test("leaf function") unittest +{ + mixin(setup); + + enum content = q{ + #line 10 + void foo() {} + }; + + assert(leafFunctionEqualsLoc(content, 10, 14)); +} + +@test("non leaf function") unittest +{ + mixin(setup); + + enum content = q{ + import std.stdio; + void foo() + { + write(); + } + }; + + assert(leafFunctions("test.d", content)[].empty); +} + +@test("circular calls - non leaf functions") unittest +{ + mixin(setup); + + enum content = q{ + void a() + { + b(); + } + + void b() + { + a(); + } + }; + + assert(leafFunctions("test.d", content)[].empty); +} + +@test("leaf function inside template mixin") unittest +{ + mixin(setup); + + enum content = q{ + mixin template Foo() + { + #line 10 + void foo() {} + } + + mixin Foo; + }; + + assert(leafFunctionEqualsLoc(content, 10, 18)); +} + +@test("multiple functions on the same line") unittest +{ + mixin(setup); + + enum content = q{ + #line 10 + void foo() { bar(); } void bar() {} + }; + + assert(leafFunctionEqualsLoc(content, 10, 36)); +} + +@test("function without body") unittest +{ + mixin(setup); + + enum content = q{ + #line 10 + void foo(); + }; + + assert(leafFunctionEqualsLoc(content, 10, 14)); +} + +@test("with imports") unittest +{ + mixin(setup); + + enum content = q{ + import std.stdio; + #line 10 + void foo() {} + }; + + assert(leafFunctions("test.d", content).length == 1); +} + +@test("nested function call") unittest +{ + mixin(setup); + + enum content = q{ + import std.stdio; + void foo() + { + if (true) + write(); + } + }; + + assert(leafFunctions("test.d", content)[].empty); +} + +@test("nested function") unittest +{ + mixin(setup); + + enum content = q{ + void foo() + { + #line 10 + void bar() {} + bar(); + } + }; + + assert(leafFunctionEqualsLoc(content, 10, 18)); +} + +@test("method") unittest +{ + mixin(setup); + + enum content = q{ + class Foo + { + #line 10 + void foo() {} + } + }; + + assert(leafFunctionEqualsLoc(content, 10, 18)); +} + +@test("property function call") unittest +{ + mixin(setup); + + enum content = q{ + #line 10 + void a() + { + b; + } + + void b() + { + a; + } + }; + + assert(leafFunctions("test.d", content)[].empty); +} diff --git a/source/dlp/visitors/utility.d b/source/dlp/visitors/utility.d new file mode 100644 index 0000000..caa82af --- /dev/null +++ b/source/dlp/visitors/utility.d @@ -0,0 +1,55 @@ +module dlp.visitors.utility; + +import dmd.dsymbol : Dsymbol; +import dmd.globals : Global; +import dmd.root.outbuffer : OutBuffer; + +class DiagnosticsException : Exception +{ + import dmd.frontend : Diagnostics; + + Diagnostics diagnostics; + + @nogc @safe pure nothrow this(Diagnostics diagnostics, + string file = __FILE__, size_t line = __LINE__, + Throwable nextInChain = null) + { + this.diagnostics = diagnostics; + super(null, file, line, nextInChain); + } +} + +string fullyQualifiedName(Dsymbol symbol) +{ + OutBuffer buf; + buf.writestring(symbol.ident.toString()); + + for (auto package_ = symbol.parent; package_ !is null; package_ = package_.parent) + { + buf.prependstring("."); + buf.prependstring(package_.ident.toChars()); + } + + return buf.peekSlice.idup; +} + +void handleDiagnosticErrors() +{ + import dmd.frontend : Diagnostics; + import dmd.globals : global; + + if (!global.hasErrors) + return; + + Diagnostics diagnostics = { + errors: global.errors, + warnings: global.warnings + }; + + throw new DiagnosticsException(diagnostics); +} + +bool hasErrors(const ref Global global) +{ + return global.errors > 0; +} diff --git a/tools/build_release.ps1 b/tools/build_release.ps1 new file mode 100644 index 0000000..4df7c60 --- /dev/null +++ b/tools/build_release.ps1 @@ -0,0 +1,35 @@ +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +$PSDefaultParameterValues['*:ErrorAction'] = 'Stop' + +$appName="dlp" +$targetDir="." +$targetPath="$targetDir/$appName.exe" + +function Build +{ + dub build --verror -b release +} + +function Version +{ + Invoke-Expression "$targetPath --version" +} + +function Arch +{ + if ($env:PLATFORM -eq 'x86') { '32' } else { '64' } +} + +function ReleaseName +{ + "$appName-$(Version)-win$(Arch)" +} + +function Archive +{ + 7z a "$(ReleaseName).7z" "$targetPath" +} + +Build +Archive diff --git a/tools/build_release.sh b/tools/build_release.sh new file mode 100755 index 0000000..1ab8af2 --- /dev/null +++ b/tools/build_release.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +set -ex + +function build { + dub build --verror -b release + strip "$target_path" +} + +function version { + "$target_path" --version +} + +function arch { + uname -m +} + +function os { + local os=$(uname | tr '[:upper:]' '[:lower:]') + [ "$os" = 'darwin' ] && echo 'macos' || echo "$os" +} + +function release_name { + local release_name="$app_name-$(version)-$(os)" + + if [ "$(os)" = 'macos' ]; then + echo "$release_name" + else + echo "$release_name-$(arch)" + fi +} + +function archive { + tar Jcf "$(release_name).tar.xz" -C "$target_dir" "$app_name" +} + +app_name="dlp" +target_dir="." +target_path="$target_dir/$app_name" + +build +archive diff --git a/tools/generate_version.bat b/tools/generate_version.bat new file mode 100644 index 0000000..0e04dc7 --- /dev/null +++ b/tools/generate_version.bat @@ -0,0 +1,7 @@ +if not exist "%DUB_PACKAGE_DIR%\tmp" mkdir "%DUB_PACKAGE_DIR%\tmp" + +if exist "%DUB_PACKAGE_DIR%\.git" ( + git describe --tags --always > "%DUB_PACKAGE_DIR%\tmp\version" +) else ( + echo unknown > "%DUB_PACKAGE_DIR%\tmp\version" +) diff --git a/tools/generate_version.sh b/tools/generate_version.sh new file mode 100755 index 0000000..1c4ecb5 --- /dev/null +++ b/tools/generate_version.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +set -e + +mkdir -p "$DUB_PACKAGE_DIR/tmp" + +if [ -d "$DUB_PACKAGE_DIR/.git" ]; then + git describe --tags --always > "$DUB_PACKAGE_DIR/tmp/version" +else + echo 'unknown' > "$DUB_PACKAGE_DIR/tmp/version" +fi diff --git a/tools/install_dc.ps1 b/tools/install_dc.ps1 new file mode 100644 index 0000000..a28bda5 --- /dev/null +++ b/tools/install_dc.ps1 @@ -0,0 +1,94 @@ +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +$PSDefaultParameterValues['*:ErrorAction'] = 'Stop' + +function Get-LatestVersion($url) +{ + (Invoke-WebRequest $url).toString().replace("`n","").replace("`r","") +} + +function ResolveLatestDMD +{ + $version = $env:DVersion + + if ($version -eq 'stable') + { + $version = Get-LatestVersion('http://downloads.dlang.org/releases/LATEST') + $url = "http://downloads.dlang.org/releases/2.x/$version/dmd.$version.windows.7z" + } + elseif ($version -eq 'beta') + { + $version = Get-LatestVersion('http://downloads.dlang.org/pre-releases/LATEST') + $latestVersion = $latest.split("-")[0].split("~")[0] + $url = "http://downloads.dlang.org/pre-releases/2.x/$latestVersion/dmd.$version.windows.7z" + } + elseif ($version -eq 'nightly') + { + $url = 'http://nightlies.dlang.org/dmd-master-2017-05-20/dmd.master.windows.7z' + } + else + { + $url = "http://downloads.dlang.org/releases/2.x/$version/dmd.$version.windows.7z" + } + + $bin_path = "/dmd2/windows/bin" + $env:PATH += ";$bin_path" + + $url, $bin_path +} + +function Get-LatestLDCVersion($latest) +{ + Get-LatestVersion("https://ldc-developers.github.io/$latest") +} + +function ResolveLatestLDC +{ + $version = $env:DVersion + + if ($version -eq 'stable') + { + $version = Get-LatestLDCVersion('LATEST') + } + elseif ($version -eq 'beta') + { + $version = Get-LatestLDCVersion('LATEST_BETA') + } + + $bin_path = "/ldc2-$version-windows-$env:PLATFORM/bin" + $url = 'https://github.com/ldc-developers/ldc/releases/download/' ` + + "v$version/ldc2-$version-windows-$env:PLATFORM.7z" + + $env:PATH += ";$bin_path" + + $url, $bin_path +} + +function SetUpDCompiler +{ + if ($env:d -eq 'dmd') + { + $url, $bin_path = ResolveLatestDMD + $env:DC = 'dmd' + } + elseif ($env:d -eq 'ldc') + { + $url, $bin_path = ResolveLatestLDC + $env:DC = 'ldc2' + } + else + { + echo "Unrecognized compiler $env:d" + $host.SetShouldExit(-1) + return + } + + echo "Downloading ..." + echo "$url" + Invoke-WebRequest "$url" -OutFile /dc.7z + echo 'Finished' + 7z x /dc.7z -o/ > $null + cp "$bin_path/libcurl.dll" . +} + +SetUpDCompiler diff --git a/vendor/dmd b/vendor/dmd new file mode 160000 index 0000000..1d8324c --- /dev/null +++ b/vendor/dmd @@ -0,0 +1 @@ +Subproject commit 1d8324cb0bfbf901e5607aedae982065a70d4787