dmd/compiler/test/dub_package/retrieve_scope.d
2025-01-26 14:06:14 +01:00

215 lines
5.2 KiB
D
Executable file

#!/usr/bin/env dub
/+dub.sdl:
dependency "dmd" path="../../.."
versions "CallbackAPI"
+/
/*
* This file contains an example of how to retrieve the scope of a statement.
* First, the callback system is used. This, however, will not work for fields
* of structs or classes, which is why the visitor will cover this corner case
*/
import core.stdc.stdarg;
import core.stdc.string;
import std.conv;
import std.string;
import std.algorithm.sorting;
import std.algorithm.mutation : SwapStrategy;
import std.path : dirName;
import dmd.errors;
import dmd.frontend;
import dmd.console;
import dmd.arraytypes;
import dmd.compiler;
import dmd.dmodule;
import dmd.dsymbol;
import dmd.dsymbolsem;
import dmd.location;
import dmd.semantic2;
import dmd.semantic3;
import dmd.statement;
import dmd.visitor;
import dmd.dscope;
import dmd.denum;
import dmd.nspace;
import dmd.dstruct;
import dmd.dclass;
import dmd.globals;
import std.stdio : writeln;
private bool isBefore(Loc loc1, Loc loc2)
{
return loc1.linnum != loc2.linnum? loc1.linnum < loc2.linnum
: loc1.charnum < loc2.charnum;
}
private struct CallbackHelper {
static Loc cursorLoc;
static Scope *scp;
static extern (C++) void statementSem(Statement s, Scope *sc) {
if (s.loc.linnum == cursorLoc.linnum
&& strcmp(s.loc.filename, cursorLoc.filename) == 0) {
sc.setNoFree();
scp = sc;
}
}
};
int main()
{
auto dmdParentDir = dirName(dirName(dirName(dirName(__FILE_FULL_PATH__))));
global.path = Strings();
global.path.push((dirName(dmdParentDir) ~ "/phobos" ~ '\0').ptr);
global.path.push((dmdParentDir ~ "/druntime/import" ~ '\0').ptr);
// comment for error output in parsing & semantic
diagnosticHandler = (const ref SourceLoc location,
Color headerColor,
const(char)* header,
const(char)* messageFormat,
va_list args,
const(char)* prefix1,
const(char)* prefix2) => true;
global.gag = 1;
initDMD(diagnosticHandler);
Module m = parseModule("testfiles/correct.d").module_;
m.importedFrom = m; // m.isRoot() == true
CallbackHelper.cursorLoc = Loc(to!string(m.srcfile).ptr, 22, 10);
Compiler.onStatementSemanticStart = &CallbackHelper.statementSem;
m.importAll(null);
// semantic
m.dsymbolSemantic(null);
Module.runDeferredSemantic();
m.semantic2(null);
Module.runDeferredSemantic2();
m.semantic3(null);
Module.runDeferredSemantic3();
Dsymbol[] symbols;
// if scope could not be retrieved through the callback, then traverse AST
if (!CallbackHelper.scp) {
auto visitor = new DsymbolsScopeRetrievingVisitor(CallbackHelper.cursorLoc);
m.accept(visitor);
symbols = visitor.symbols;
}
while (CallbackHelper.scp) {
if (CallbackHelper.scp.scopesym && CallbackHelper.scp.scopesym.symtab)
foreach (x; CallbackHelper.scp.scopesym.symtab.tab.asRange()) {
symbols ~= x.value;
}
CallbackHelper.scp = CallbackHelper.scp.enclosing;
}
sort!("to!string(a.ident) < to!string(b.ident)", SwapStrategy.stable)(symbols);
foreach (sym; symbols) {
writeln(sym.ident);
}
deinitializeDMD();
return 0;
}
private extern (C++) final class DsymbolsScopeRetrievingVisitor : Visitor
{
Loc loc;
Dsymbol[] symbols;
alias visit = Visitor.visit;
public:
extern (D) this(Loc loc)
{
this.loc = loc;
}
override void visit(Dsymbol s)
{
}
override void visit(ScopeDsymbol s)
{
visitScopeDsymbol(s);
}
override void visit(EnumDeclaration d)
{
visitScopeDsymbol(d);
}
override void visit(Nspace d)
{
visitScopeDsymbol(d);
}
override void visit(StructDeclaration d)
{
visitScopeDsymbol(d);
}
override void visit(ClassDeclaration d)
{
visitScopeDsymbol(d);
}
void visitBaseClasses(ClassDeclaration d)
{
visitScopeDsymbol(d);
}
override void visit(Module m)
{
visitScopeDsymbol(m);
}
private void visitScopeDsymbol(ScopeDsymbol scopeDsym)
{
if (!scopeDsym.members)
return;
Dsymbol dsym;
foreach (i, s; *scopeDsym.members)
{
if (s is null || s.ident is null)
continue;
// if the current symbol is from another module
if (auto m = scopeDsym.isModule())
if (!(to!string(s.loc.filename).endsWith(m.ident.toString() ~ ".d")))
continue;
if (!s.isImport())
symbols ~= s;
if (!i || dsym is null) {
dsym = s;
continue;
}
// only visit a symbol which contains the cursor
// choose the symbol which is before and the closest to the cursor
if (isBefore(dsym.loc, loc)
&& isBefore(dsym.loc, s.loc)
&& isBefore(s.loc, loc)) {
dsym = s;
}
}
dsym.accept(this);
}
}