www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Alias template param - Undefined Identifier

reply "Chris Williams" <yoreanon-chrisw yahoo.co.jp> writes:
I have a series of functions that each needs to change directory, 
perform an action, and then revert to the original working 
directory.

I could put a wrapper around the calls to these functions that 
performs this action, but I figured a little macro would be 
sufficient for my needs (it's just a little script.)


import fs = std.file;
import std.stdio;

template gotoPath(alias path) {
     enum gotoPath = q{
         string currPath = fs.getcwd();
         scope (exit) fs.chdir(currPath);
         fs.chdir(path);
     };
}

void doStuff(string targetDirectory) {
     mixin(gotoPath!(targetDirectory));
     writefln("b %s", fs.getcwd());
}

void main() {
     writefln("a %s", fs.getcwd());
     doStuff("/etc");
     writefln("c %s", fs.getcwd());
}


When I try to compile (DMD 2.064), I get the following error:

test.d(16): Error: undefined identifier path

I can fix the issue by removing the alias parameter and 
hardcoding the name of the variable in the template:


import fs = std.file;
import std.stdio;

template gotoPath() {
     enum gotoPath = q{
         string currPath = fs.getcwd();
         scope (exit) fs.chdir(currPath);
         fs.chdir(targetDirectory);
     };
}

void doStuff(string targetDirectory) {
     mixin(gotoPath!());
     writefln("b %s", fs.getcwd());
}

void main() {
     writefln("a %s", fs.getcwd());
     doStuff("/etc");
     writefln("c %s", fs.getcwd());
}


However, it seems like that shouldn't be necessary. The alias 
parameter should create a local alias for the external variable 
which can be utilized by the generated code. Is there something 
wrong with my syntax?
Mar 18 2014
next sibling parent reply "anonymous" <anonymous example.com> writes:
On Tuesday, 18 March 2014 at 21:56:32 UTC, Chris Williams wrote:
 import fs = std.file;
 import std.stdio;

 template gotoPath(alias path) {
     enum gotoPath = q{
         string currPath = fs.getcwd();
         scope (exit) fs.chdir(currPath);
         fs.chdir(path);
     };
This is just a string. The occurence of "path" in it has no relation to the template parameter path. "path" is mixed in literally. The template parameter is unused.
 }

 void doStuff(string targetDirectory) {
     mixin(gotoPath!(targetDirectory));
     writefln("b %s", fs.getcwd());
 }

 void main() {
     writefln("a %s", fs.getcwd());
     doStuff("/etc");
     writefln("c %s", fs.getcwd());
 }


 When I try to compile (DMD 2.064), I get the following error:

 test.d(16): Error: undefined identifier path
You can pass the variable name as a string: import std.string: format; template gotoPath(string pathVar) { enum gotoPath = format(q{ ... fs.chdir(%s); }, pathVar); } ... mixin(gotoPath!"targetDirectory"); Or you can get the variable name from the alias parameter: import std.string: format; template gotoPath(alias path) { enum gotoPath = format(q{ ... fs.chdir(%s); }, path.stringof); } I think I've read that .stringof shouldn't be used for code generation, but I can't think of anything better. Then there's the struct destructor version: struct GotoPath { private string currPath; this(string path) { currPath = fs.getcwd(); fs.chdir(path); } ~this() { fs.chdir(currPath); } } Ideally, it would be used like this: with(GotoPath(targetDirectory)) writefln("b %s", fs.getcwd()); which would be beautiful, if you ask me. But a compiler bug [1] ruins that. You can still use it with a dummy variable: auto dummy = GotoPath(targetDirectory); writefln("b %s", fs.getcwd()); [1] https://d.puremagic.com/issues/show_bug.cgi?id=8269
Mar 18 2014
parent reply "Chris Williams" <yoreanon-chrisw yahoo.co.jp> writes:
On Tuesday, 18 March 2014 at 22:42:20 UTC, anonymous wrote:
 You can pass the variable name as a string:
 Or you can get the variable name from the alias parameter:

      import std.string: format;
      template gotoPath(alias path) {
          enum gotoPath = format(q{
              ...
              fs.chdir(%s);
          }, path.stringof);
      }

 I think I've read that .stringof shouldn't be used for code
 generation, but I can't think of anything better.
Your memory seems to be correct: http://forum.dlang.org/thread/bug-11036-3 http.d.puremagic.com/issues/ It looks like the issue is that .stringOf is something generated according to AST information and is thus intrinsically compiler-specific. The ticket it links to suggest using __traits(identifier, X) or 'one of the Phobos helper functions such as "fullyQualifiedName".' Annoying that formatting the string is required. I think you're right that it's ignoring my alias parameter because I'm not using it. Formatting doesn't accomplish anything except to use the parameter. But in exchange, you have to relate your format arguments to the %s entries, instead of the q{} contents just being code. import fs = std.file; import std.stdio; import std.string: format; template gotoPath(alias path) { enum gotoPath = format(q{ string currPath = fs.getcwd(); scope (exit) fs.chdir(currPath); fs.chdir(%s); }, __traits(identifier, path)); } void doStuff(string targetDirectory) { mixin(gotoPath!(targetDirectory)); writefln("b %s", fs.getcwd()); } void main() { writefln("a %s", fs.getcwd()); doStuff("/etc"); writefln("c %s", fs.getcwd()); }
Mar 18 2014
parent "anonymous" <anonymous example.com> writes:
On Tuesday, 18 March 2014 at 23:10:05 UTC, Chris Williams wrote:
 Annoying that formatting the string is required. I think you're 
 right that it's ignoring my alias parameter because I'm not 
 using it. Formatting doesn't accomplish anything except to use 
 the parameter. But in exchange, you have to relate your format 
 arguments to the %s entries, instead of the q{} contents just 
 being code.


 import fs = std.file;
 import std.stdio;
 import std.string: format;

 template gotoPath(alias path) {
     enum gotoPath = format(q{
         string currPath = fs.getcwd();
         scope (exit) fs.chdir(currPath);
         fs.chdir(%s);
     }, __traits(identifier, path));
 }

 void doStuff(string targetDirectory) {
     mixin(gotoPath!(targetDirectory));
     writefln("b %s", fs.getcwd());
 }

 void main() {
     writefln("a %s", fs.getcwd());
     doStuff("/etc");
     writefln("c %s", fs.getcwd());
 }
I feel like there might be a misunderstanding. It's not that the parameter was ignored because it wasn't used. You're not giving the compiler a little extra help. You're building a string from the parameter. __traits(identifier, path) isn't "path", it's "targetDirectory" (in this specific instantiation).
Mar 18 2014
prev sibling parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Chris Williams:

 I could put a wrapper around the calls to these functions that 
 performs this action, but I figured a little macro would be 
 sufficient for my needs (it's just a little script.)
I suggest to keep the code simpler. Bye, bearophile
Mar 18 2014
parent reply "Chris Williams" <yoreanon-chrisw yahoo.co.jp> writes:
On Tuesday, 18 March 2014 at 23:05:51 UTC, bearophile wrote:
 Chris Williams:

 I could put a wrapper around the calls to these functions that 
 performs this action, but I figured a little macro would be 
 sufficient for my needs (it's just a little script.)
I suggest to keep the code simpler. Bye, bearophile
I could. I could simply allow the current working directory to drift as the application performs operations, but I prefer the consistency of knowing that methods I call restore state before I continue to the next method. I sense that in the long run it will make life easier. But I also like to know how most-effectively to write a C-style macro in D, so it seemed worth checking what the state of the art is.
Mar 18 2014
parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Chris Williams:

 But I also like to know how most-effectively to write a C-style 
 macro in D, so it seemed worth checking what the state of the 
 art is.
This is an antipattern :-) Bye, bearophile
Mar 18 2014
parent reply "Chris Williams" <yoreanon-chrisw yahoo.co.jp> writes:
On Tuesday, 18 March 2014 at 23:33:12 UTC, bearophile wrote:
 Chris Williams:

 But I also like to know how most-effectively to write a 
 C-style macro in D, so it seemed worth checking what the state 
 of the art is.
This is an antipattern :-) Bye, bearophile
As are goto, pointers, and conditional compilation. That doesn't mean that there aren't cases where those are the best solution, because other solutions would be longer and more convoluted or too inefficient. In Python, I could write a wrapping function that I could apply (using decorators) to each function with this behavior, based on the presence of a UDA. I could roughly build something like that for D, but the whole framework for it would be far more convoluted and harder to maintain than a simple three line macro. ...probably something along the lines of making all of my functions a static function in a struct, which I then pass into a template which processes UDAs to generate functions at the top with the same names as the originals in the struct, which call the struct variants. It's also a longer to write and debug. Unfortunately, at the moment, I don't believe that there's any way to use UDAs to process a file and generate interceptors -- which would be the sort of pattern that we'd like.
Mar 18 2014
parent "Chris Williams" <yoreanon-chrisw yahoo.co.jp> writes:
On Wednesday, 19 March 2014 at 00:07:04 UTC, Chris Williams wrote:
                                           ...probably something 
 along the lines of making all of my functions a static function 
 in a struct, which I then pass into a template which processes 
 UDAs to generate functions at the top with the same names as 
 the originals in the struct, which call the struct variants. 
 It's also a longer to write and debug.
Here's a simple (unsafe and inflexible) implementation, should anyone want to build it out into something that can accept multiple arguments and return types. import std.stdio; string wrap(string wrapperName, string structName, string innerName) { return "void " ~ innerName ~ "() {" ~wrapperName~ "( &"~structName~"."~innerName~" );}"; } template decorate(T) { template ForeachMember(Mbr...) { static if (__traits(getAttributes, __traits(getMember, T, Mbr[0])).length > 0) { static if (Mbr.length > 1) { enum ForeachMember = wrap( __traits(identifier, __traits(getAttributes, __traits(getMember, T, Mbr[0]))[0]), __traits(identifier, T), Mbr[0] ) ~ ForeachMember!(Mbr[1..$]) ; } else { enum ForeachMember = wrap( __traits(identifier, __traits(getAttributes, __traits(getMember, T, Mbr[0]))[0]), __traits(identifier, T), Mbr[0] ) ; } } else { static if (Mbr.length > 1) { enum ForeachMember = "" ~ ForeachMember!(Mbr[1..$]); } else { enum ForeachMember = ""; } } } enum decorate = ForeachMember!(__traits(allMembers, T)); } void BeforeAfter(void function() fun) { writeln("Before"); write("\t"); fun(); writeln("After"); } void Llama(void function() fun) { writeln("Llama"); write("\t"); fun(); writeln("Llama"); } struct Foo { BeforeAfter static void hello() { writeln("Hello"); } Llama static void and() { writeln("and"); } BeforeAfter static void goodbye() { writeln("Goodbye"); } } mixin(decorate!(Foo)); void main() { hello(); and(); goodbye(); }
Mar 21 2014