diff --git a/README.markdown b/README.markdown index c26b780d..038064e6 100644 --- a/README.markdown +++ b/README.markdown @@ -49,7 +49,7 @@ Use `-h` for usage information. Any flags recognized by Clang can be used. ## Limitations/Known issues * Doesn't translate preprocessor macros of any kind -* Doesn't translate `#include` to `import`. A few standard C headers are translated +* Only very simplistic translation of `#include` to `import`. A few standard C headers are translated * Doesn't translate C++ at all * Umbrella headers. Some headers just serve to include other headers. If these other headers contain some form of protection, like `#error`, to be included directly this can cause problems for DStep * Some headers are designed to always be included together with other header files. These headers may very well use symbols from other header files without including them itself. Since DStep is designed to convert header files one-by-one this doesn't work. There are two workarounds for this: diff --git a/dstep/driver/Application.d b/dstep/driver/Application.d index ab994e45..c1ead943 100644 --- a/dstep/driver/Application.d +++ b/dstep/driver/Application.d @@ -25,6 +25,7 @@ import clang.Util; import dstep.core.Exceptions; import dstep.translator.Translator; +import dstep.translator.IncludeHandler; class Application : DStack.Application { @@ -77,6 +78,14 @@ class Application : DStack.Application .on(&handleLanguage); arguments("objective-c", "Treat source input file as Objective-C input."); + + arguments('f',"import-filter", "A regex to filter includes that will be auto converted.") + .params(1) + .defaults(".*"); + + arguments('p',"import-prefix", "A prefix to add to any custom generated import") + .params(1) + .defaults(""); } private: @@ -131,6 +140,12 @@ private: // FIXME: Cannot use type inference here, probably a bug. Results in segfault. if (arguments.rawArgs.any!((string e) => e == "-ObjC")) handleObjectiveC(); + + if (arguments["import-prefix"].hasValue) + handleAutoImportPrefix(arguments["import-prefix"].value); + + if (arguments["import-filter"].hasValue) + handleAutoImportFilter(arguments["import-filter"].value); } void handleObjectiveC () @@ -168,6 +183,16 @@ private: argsToRestore ~= language; } + void handleAutoImportPrefix (string prefix) + { + includeHandler.autoImportPrefix = prefix; + } + + void handleAutoImportFilter (string filter) + { + includeHandler.autoImportFilter = filter; + } + @property string[] remainingArgs () { return arguments.rawArgs[1 .. $] ~ argsToRestore; @@ -213,6 +238,8 @@ private: println(" -ObjC, --objective-c Treat source input file as Objective-C input."); println(" -x, --language Treat subsequent input files as having type ."); println(" -h, --help Show this message and exit."); + println(" -f, --import-filter A regex to filter includes that will be auto converted to imports."); + println(" -p, --import-prefix A prefix to add to any import generated from an include."); println(); println("All options that Clang accepts can be used as well."); println(); diff --git a/dstep/translator/IncludeHandler.d b/dstep/translator/IncludeHandler.d index 62be7441..a0a2341a 100644 --- a/dstep/translator/IncludeHandler.d +++ b/dstep/translator/IncludeHandler.d @@ -7,8 +7,10 @@ module dstep.translator.IncludeHandler; import Path = std.path; +import std.regex; +import std.conv; -import mambo.core._; +import mambo.core.Array; private IncludeHandler includeHandler_; @@ -26,6 +28,16 @@ class IncludeHandler { private string[] rawIncludes; private string[] imports; + + // True if includes should be converted to imports. + private bool convertIncludes = false; + + // Includes matching this will be converted to imports. + private Regex!char convertableIncludePattern = regex(".*"); + + // Prefix for auto generated imports. + private string importPrefix = ""; + static string[string] knownIncludes; static this () @@ -109,12 +121,33 @@ class IncludeHandler imports ~= "core.stdc.config"; } + + /// Makes includes that match regex filter be converted to import with prefix. + @property void autoImportPrefix (string prefix) + { + convertIncludes = true; + importPrefix = prefix; + } + + @property string autoImportPrefix () + { + return this.importPrefix; + } + + /// Makes includes that match regex filter be converted to import with prefix. + @property void autoImportFilter (string filter) + { + convertIncludes = true; + convertableIncludePattern = regex(filter); + } + string[] toImports () { - auto r = rawIncludes.map!((e) { + auto r = rawIncludes.map!((e) { if (auto i = isKnownInclude(e)) return toImport(i); - + else if (convertIncludes && isConvertableInclude(e) ) + return toImport(autoConvertInclude(e)); else return ""; }); @@ -124,6 +157,12 @@ class IncludeHandler return r.append(imps).filter!(e => e.any).unique.toArray; } + /// Returns the base name (last component without extension) of a file path. + static string bareName (string path) + { + return Path.stripExtension(Path.baseName(path)); + } + private: string toImport (string str) @@ -140,4 +179,26 @@ private: return null; } -} \ No newline at end of file + + /// Checks if the given include file name should be converted to an import declaration. + bool isConvertableInclude (string include) + { + // Do not try to convert empty strings, no matter what the pattern says. + return include != "" && matchFirst(include, convertableIncludePattern); + } + + + /// Generates an importable module name from an include file name. + string autoConvertInclude (string include) + { + string prefix = importPrefix; + + // If we have a prefix, append a dot to it if it doesn't already have one at the end. + if (prefix != "" && !prefix.endsWith(".")) + { + prefix ~= "."; + } + + return prefix ~ bareName(include); + } +} diff --git a/features/include_conversion.feature b/features/include_conversion.feature new file mode 100644 index 00000000..8126a462 --- /dev/null +++ b/features/include_conversion.feature @@ -0,0 +1,5 @@ +Feature: Include Conversion + + Scenario: Convert includes + Then I test the file "includes" with filter "dummy_includes/transform.*" and prefix "transformed" + diff --git a/features/step_definitions/common.rb b/features/step_definitions/common.rb index ed709941..dd76317e 100644 --- a/features/step_definitions/common.rb +++ b/features/step_definitions/common.rb @@ -50,6 +50,13 @@ step %{the files "#{file}.d" and "test_files/#{path}/#{file}.d" should be equal} end +Then /^I test the file "([^"]*)" with filter "([^"]*)" and prefix "([^"]*)"$/ do |file, filter, prefix| + step %{a test file named "#{file}"} + step %{an expected file named "#{file}"} + step %{I successfully convert the test file "#{file}" in "" with the flags "--import-filter #{filter} --import-prefix #{prefix}"} + step %{the files "#{file}.d" and "test_files/#{file}.d" should be equal} +end + if OSX Then /^I test the Objective\-C file "([^"]*)" in "([^"]*)"$/ do |file, path| step %{a test file named "#{file}" in "#{path}"} diff --git a/test_files/dummy_includes/README.md b/test_files/dummy_includes/README.md new file mode 100644 index 00000000..1d02fc6a --- /dev/null +++ b/test_files/dummy_includes/README.md @@ -0,0 +1,8 @@ +## Directory dummy_includes + +The support for import generation works based on the use of types +haling from specific includes. That is why to test it +we need a few dummy includes that define types. + +(Other test files cannot be used because they need +to be free to contain name conflicts.) diff --git a/test_files/dummy_includes/ignored.h b/test_files/dummy_includes/ignored.h new file mode 100644 index 00000000..0714ebc7 --- /dev/null +++ b/test_files/dummy_includes/ignored.h @@ -0,0 +1 @@ +struct Ignored{}; diff --git a/test_files/dummy_includes/transform.h b/test_files/dummy_includes/transform.h new file mode 100644 index 00000000..8674f342 --- /dev/null +++ b/test_files/dummy_includes/transform.h @@ -0,0 +1 @@ +struct Transform{}; diff --git a/test_files/dummy_includes/transform_also.h b/test_files/dummy_includes/transform_also.h new file mode 100644 index 00000000..97ab124c --- /dev/null +++ b/test_files/dummy_includes/transform_also.h @@ -0,0 +1 @@ +struct TransformAlso{}; diff --git a/test_files/includes.d b/test_files/includes.d new file mode 100644 index 00000000..c9a0da00 --- /dev/null +++ b/test_files/includes.d @@ -0,0 +1,5 @@ +import transformed.transform; +import transformed.transform_also; + +extern (C): + diff --git a/test_files/includes.h b/test_files/includes.h new file mode 100644 index 00000000..aeaa714e --- /dev/null +++ b/test_files/includes.h @@ -0,0 +1,10 @@ +#include "dummy_includes/transform.h" +#include "dummy_includes/ignored.h" +#include "dummy_includes/transform_also.h" + +// Dstep only pays attention to includes whose types are used so we +// use a type from each here. +struct Transform transform; +struct Ignored ignored; +struct TransformAlso transform_also; +