From 46fcd0f2a047a6852c8b55533c884da5e961202f Mon Sep 17 00:00:00 2001 From: Jacob Carlborg Date: Fri, 10 Aug 2018 19:22:29 +0200 Subject: [PATCH] Initial commit --- .gitignore | 17 ++ .gitmodules | 3 + .travis.yml | 52 ++++ appveyor.yml | 58 ++++ dub.sdl | 31 ++ dub.selections.json | 8 + readme.md | 54 ++++ source/dlp/core/algorithm.d | 25 ++ source/dlp/core/optional.d | 308 ++++++++++++++++++++ source/dlp/core/set.d | 169 +++++++++++ source/dlp/core/test.d | 8 + source/dlp/core/traits.d | 181 ++++++++++++ source/dlp/driver/application.d | 245 ++++++++++++++++ source/dlp/driver/cli.d | 8 + source/dlp/driver/command.d | 17 ++ source/dlp/driver/commands/leaf_functions.d | 75 +++++ source/dlp/driver/main.d | 8 + source/dlp/visitors/leaf_functions.d | 284 ++++++++++++++++++ source/dlp/visitors/utility.d | 55 ++++ tools/build_release.ps1 | 35 +++ tools/build_release.sh | 42 +++ tools/generate_version.bat | 7 + tools/generate_version.sh | 11 + tools/install_dc.ps1 | 94 ++++++ vendor/dmd | 1 + 25 files changed, 1796 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .travis.yml create mode 100644 appveyor.yml create mode 100644 dub.sdl create mode 100644 dub.selections.json create mode 100644 readme.md create mode 100644 source/dlp/core/algorithm.d create mode 100644 source/dlp/core/optional.d create mode 100644 source/dlp/core/set.d create mode 100644 source/dlp/core/test.d create mode 100644 source/dlp/core/traits.d create mode 100644 source/dlp/driver/application.d create mode 100644 source/dlp/driver/cli.d create mode 100644 source/dlp/driver/command.d create mode 100644 source/dlp/driver/commands/leaf_functions.d create mode 100644 source/dlp/driver/main.d create mode 100644 source/dlp/visitors/leaf_functions.d create mode 100644 source/dlp/visitors/utility.d create mode 100644 tools/build_release.ps1 create mode 100755 tools/build_release.sh create mode 100644 tools/generate_version.bat create mode 100755 tools/generate_version.sh create mode 100644 tools/install_dc.ps1 create mode 160000 vendor/dmd 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