module parser.linkage_location;

import dmd.frontend : parseModule;
import support : afterEach, beforeEach;
import dmd.attrib : CPPMangleDeclaration, CPPNamespaceDeclaration, LinkDeclaration;
import dmd.globals : Loc;
import dmd.visitor : SemanticTimeTransitiveVisitor;

@beforeEach initializeFrontend()
{
    import dmd.frontend : initDMD;

    initDMD();
}

@afterEach deinitializeFrontend()
{
    import dmd.frontend : deinitializeDMD;
    deinitializeDMD();
}

extern (C++) static class Visitor : SemanticTimeTransitiveVisitor
{
    alias visit = typeof(super).visit;
    Loc l;

    override void visit(CPPMangleDeclaration md)
    {
        l = md.loc;
    }

    override void visit(CPPNamespaceDeclaration nd)
    {
        l = nd.loc;
    }
}

// Testing cpp mangling

@("extern (C++, struct) class")
unittest
{
    auto t = parseModule("test.d", "extern (C++, struct) class C {}");

    scope visitor = new Visitor;
    t.module_.accept(visitor);

    assert(visitor.l.linnum == 1);
    assert(visitor.l.charnum == 1);
}

@("extern (C++, struct) struct")
unittest
{
    auto t = parseModule("test.d", "extern (C++, struct) struct S {}");

    scope visitor = new Visitor;
    t.module_.accept(visitor);

    assert(visitor.l.linnum == 1);
    assert(visitor.l.charnum == 1);
}

@("extern (C++, class) class")
unittest
{
    auto t = parseModule("test.d", "extern (C++, class) class C {}");

    scope visitor = new Visitor;
    t.module_.accept(visitor);

    assert(visitor.l.linnum == 1);
    assert(visitor.l.charnum == 1);
}

@("extern (C++, class) struct")
unittest
{
    auto t = parseModule("test.d", "extern (C++, class) struct C {}");

    scope visitor = new Visitor;
    t.module_.accept(visitor);

    assert(visitor.l.linnum == 1);
    assert(visitor.l.charnum == 1);
}

@("extern (C++, struct) {}")
unittest
{
    auto t = parseModule("test.d", "extern (C++, struct) {}");

    scope visitor = new Visitor;
    t.module_.accept(visitor);

    assert(visitor.l.linnum == 1);
    assert(visitor.l.charnum == 1);
}

@("extern (C++, class) {}")
unittest
{
    auto t = parseModule("test.d", "extern (C++, class) {}");

    scope visitor = new Visitor;
    t.module_.accept(visitor);

    assert(visitor.l.linnum == 1);
    assert(visitor.l.charnum == 1);
}

// Testing namespaces

@(`"namespace"`)
unittest
{
    auto t = parseModule("test.d", `extern (C++, "namespace") {}`);

    scope visitor = new Visitor;
    t.module_.accept(visitor);

    assert(visitor.l.linnum == 1);
    assert(visitor.l.charnum == 1);
}

@(`"name"~"space"`)
unittest
{
    auto t = parseModule("test.d", `extern (C++, "name"~"space") {}`);

    scope visitor = new Visitor;
    t.module_.accept(visitor);

    assert(visitor.l.linnum == 1);
    assert(visitor.l.charnum == 1);
}

extern (C++) static class NamespaceVisitor : SemanticTimeTransitiveVisitor
{
    alias visit = typeof(super).visit;
    Loc[] l;

    override void visit(CPPNamespaceDeclaration nd)
    {
        l ~= nd.loc;
        if (nd.decl.length) {
            CPPNamespaceDeclaration next = cast(CPPNamespaceDeclaration)nd.decl.pop();
            next.accept(this);
        }
    }
}

@("multiple namespaces - comma separated")
unittest
{
    auto t = parseModule("test.d", `extern (C++, "ns1", "ns2", "ns3") {}`);

    scope visitor = new NamespaceVisitor;
    t.module_.accept(visitor);

    assert(visitor.l.length == 3);
    foreach (Loc l; visitor.l) {
        assert(l.linnum == 1);
        assert(l.charnum == 1);
    }
}

@("multiple namespaces - same line")
unittest
{
    auto t = parseModule("test.d", `extern (C++, "ns1") extern(C++, "ns2") {}`);

    scope visitor = new NamespaceVisitor;
    t.module_.accept(visitor);
    int charnum = 1;

    assert(visitor.l.length == 2);
    foreach (Loc l; visitor.l) {
        assert(l.linnum == 1);
        assert(l.charnum == charnum);
        charnum += 20;
    }
}

@("multiple namespaces - different lines")
unittest
{
    auto t = parseModule("test.d", "extern (C++, 'ns1')\nextern(C++, 'ns2') {}");

    scope visitor = new NamespaceVisitor;
    t.module_.accept(visitor);
    int linnum = 1;

    assert(visitor.l.length == 2);
    foreach (Loc l; visitor.l) {
        assert(l.linnum == linnum);
        assert(l.charnum == 1);
        linnum++;
    }
}

// Testing linkage

extern (C++) static class LinkVisitor : SemanticTimeTransitiveVisitor
{
    alias visit = typeof(super).visit;
    Loc l;

    override void visit(LinkDeclaration ld)
    {
        l = ld.loc;
    }
}

enum Links = [
    "C",
    "C++",
    "D",
    "Objective-C",
    "Windows",
    "System"];

static foreach (link; Links)
{
    @("extern("~link~")")
    unittest
    {
        auto t = parseModule("test.d", "first_token extern("~link~")");

        scope visitor = new LinkVisitor;
        t.module_.accept(visitor);

        assert(visitor.l.linnum == 1);
        assert(visitor.l.charnum == 13);
    }
}

@("extern(C++) variable")
unittest
{
    auto t = parseModule("test.d", "first_token extern(C++) int a;");

    scope visitor = new LinkVisitor;
    t.module_.accept(visitor);

    assert(visitor.l.linnum == 1);
    assert(visitor.l.charnum == 13);
}

@("extern(C++) function")
unittest
{
    auto t = parseModule("test.d", "first_token extern(C++) void f();");

    scope visitor = new LinkVisitor;
    t.module_.accept(visitor);

    assert(visitor.l.linnum == 1);
    assert(visitor.l.charnum == 13);
}

@("extern(C++) struct")
unittest
{
    auto t = parseModule("test.d", "first_token extern(C++) struct C {}");

    scope visitor = new LinkVisitor;
    t.module_.accept(visitor);

    assert(visitor.l.linnum == 1);
    assert(visitor.l.charnum == 13);
}

@("extern(C++) class")
unittest
{
    auto t = parseModule("test.d", "first_token extern(C++) class C {}");

    scope visitor = new LinkVisitor;
    t.module_.accept(visitor);

    assert(visitor.l.linnum == 1);
    assert(visitor.l.charnum == 13);
}

@("alias t = extern(C++) void f();")
unittest
{
    auto t = parseModule("test.d", "alias t = extern(C++) void f();");

    scope visitor = new LinkVisitor;
    t.module_.accept(visitor);

    assert(visitor.l.linnum == 1);
    assert(visitor.l.charnum == 11);
}
