How to create compile-time container?

Steven Schveighoffer schveiguy at gmail.com
Wed Sep 2 14:38:30 UTC 2020


On 9/2/20 5:56 AM, Andrey Zherikov wrote:

> ==============
> Everything works well until I have included scripts in subdirectories:
> ├── dir1
> │   ├── dir2
> │   │   └── script
> │   └── script
> └── script
> Content:
> ============== script
> msg hello
> include dir1/script
> ============== dir1/script
> msg hello from dir1
> include dir2/script
> ============== dir1/dir2/script
> msg hello from dir1/dir2
> ==============
> 
> Compilation fails with "Error: file `"dir2/script"` cannot be found or 
> not in a path specified with `-J`" (I used simple dmd -J. -run parser.d) 
> which is expected because parse* functions do not track the directory 
> where the script is located.
> 
> In this simple example the issue can be fixed by passing path to script 
> as a parameter to parseScript function. But this doesn't seem to be 
> flexible and extendable solution because there can be other commands 
> that might call parseFile indirectly (they can even be in other modules).
> 
> Theoretically this can be solved by doing something like this but it 
> doesn't work because "static variable `paths` cannot be read at compile 
> time":
> ==============
> string[] paths;
> void parseFile(string file)()
> {
>      enum path = paths.length > 0 ? buildPath(paths[$-1], 
> file.dirName()) : file.dirName();
> 
>      paths ~= path;
>      scope(exit) paths = paths[0..$-1];
> 
>      enum script = import(buildPath(path, file));
>      mixin(parseScript(script));
> }
> ==============
> Note that the whole point is to do this parsing at compile time.


OK, NOW I see where your code is coming from. The issue is that you need 
the directory of the script imported to be the "local directory". Your 
solution will not work -- you can't mixin code that is not available at 
compile time.

Here is what I would do instead:

string parseScript(string filename, string script)
{
     string code;
     string base = dirName(filename);
     if(base[$-1] != '/') base ~= '/';
     foreach(line; script.lineSplitter())
     {
         auto idx = line.indexOf(' ');

         switch(line[0..idx])
         {
             case "msg":
                 code ~= "writeln(\"" ~ line[idx+1..$] ~ "\");";
                 break;
             case "include":
             {
                 code ~= `parseFile!"`;
                 string importfile = line[idx+1 .. $];
                 if(!importfile.startsWith('/')) // relative path
                      code ~= base;
                 code ~= importfile ~ `";`;
                 break;
             }
             default: break;
         }
     }
     return code;
}

And pass the filename to this function in addition to the script source.

-Steve


More information about the Digitalmars-d-learn mailing list