module cesyms;

import std.stdio, std.path, std.file, std.array, std.string;
import std.d.lexer, std.d.ast, std.d.parser;
import std.traits;

void main(string[] args)
{
    if (args.length < 2) return;
    auto fname = args[1];
    if (!fname.exists) return;

    // load and parse the file
    auto config = LexerConfig(fname, StringBehavior.source, WhitespaceBehavior.skip);
    auto source = cast(ubyte[]) read(fname, size_t.max);
    auto scache = StringCache(StringCache.defaultBucketCount);
    
    auto ast = parseModule(getTokensForParser(source, config, &scache), fname, null, &(SymbolListBuilder.astError));

    // visit each root member
    auto slb = construct!SymbolListBuilder; 
    foreach(Declaration decl; ast.declarations)
    {
        slb.resetRoot;
        slb.visit(decl);
    }
    
    version(runnable_module)
    {
        int level = -1;
        void print(Symbol * s)
        {
            foreach(i; 0 .. level) write(".");
            level++;
            write(s.name, '\r');
            foreach(ss; s.subs)
                print(ss);
            
            level--;
        }
        print(slb.root);
    } 
    else 
    {
        write(slb.serialize);
    }
    
    slb.destruct;
}

// libdparse warnings includes some "'", which in Pascal are string delim
string patchPasStringLitteral(const ref string p)
{
    string result;
    for (auto i = 0; i < p.length; i++)
    {    
        auto curr = p[i];
        if (curr == 0)
            break;
        else if (curr == 13 || curr == 10)
            result ~= ' ';
        else if (curr == '\'') 
            result ~= "'#39'";
        else  
            result ~= curr; 
    }
    return result;
}

// Memory utils ---------------------------------------------------------------+
void * getMem(size_t size) nothrow
{
    import std.c.stdlib;
    auto result = malloc(size);
    assert(result, "Out of memory");
    return result;
}

CT construct(CT, A...)(A a) 
if (is(CT == class))
{
    import std.conv : emplace;
    auto size = __traits(classInstanceSize, CT);
    auto memory = getMem(size)[0 .. size];
    return emplace!(CT, A)(memory, a);
}

ST * construct(ST, A...)(A a)
if(is(ST==struct))
{
    import std.conv : emplace;
    auto size = ST.sizeof;
    auto memory = getMem(size)[0 .. size];
    return emplace!(ST, A)(memory, a);
}

static void destruct(T)(ref T instance) 
if (is(T == class) || (isPointer!T && is(PointerTarget!T == struct)))
{
    if (!instance) return;
    destroy(instance);
    instance = null;
}
//----

// Serializable Symbol --------------------------------------------------------+
enum SymbolType
{
    _alias,
    _class, 
    _enum, 
    _error,   
    _function, 
    _interface, 
    _import,   
    _mixin,   // (template decl)
    _struct, 
    _template,
    _union,
    _variable,
    _warning
}

struct Symbol
{
    size_t line;
    size_t col;
    string name;
    SymbolType type; 
    Symbol * [] subs;
    
    ~this()
    {
        foreach_reverse(i; 0..subs.length)
            subs[i].destruct;
    }
    
    void serialize(ref Appender!string lfmApp)
    {
        lfmApp.put("\ritem\r");
        
        lfmApp.put(format("line = %d\r", line));
        lfmApp.put(format("col = %d\r", col));
        lfmApp.put(format("name = '%s'\r", name));
        lfmApp.put(format("symType = %s\r", type));
         
        lfmApp.put("subs = <");
        if (subs.length) foreach(Symbol * sub; subs)
            sub.serialize(lfmApp);
        lfmApp.put(">\r");
        lfmApp.put("end");
    }
}
//----

// AST visitor/Symbol list ----------------------------------------------------+
class SymbolListBuilder : ASTVisitor
{
    Symbol * root;
    Symbol * parent;
    
    // for some reason (?) the .name of a (static Symbol* []) item was lost 
    __gshared static Symbol[] illFormed;
    
    size_t count;
    
    alias visit = ASTVisitor.visit;
    
    this()
    {
        root = construct!Symbol;
        resetRoot;
    }
    
    ~this()
    {
        root.destruct;  
    }
    
    static void astError(string fname, size_t line, size_t col, string msg, bool isErr)
    {
        Symbol * newSym = construct!Symbol;
        newSym.col = col;
        newSym.line = line;
        newSym.name = patchPasStringLitteral(msg);
        newSym.type = isErr ? SymbolType._error : SymbolType._warning; 
        illFormed ~= * newSym;
    }
    
    final void resetRoot(){parent = root;}
    
    final string serialize()
    {
        Appender!string lfmApp;
        lfmApp.reserve(count * 64);
        
        lfmApp.put("object TSymbolList\rsymbols = <");
        foreach(sym; illFormed) sym.serialize(lfmApp);
        foreach(sym; root.subs) sym.serialize(lfmApp);
        lfmApp.put(">\rend\r\n");
        
        return lfmApp.data; 
    }
    
    /// returns a new symbol if the declarator is based on a Token named "name".
    final Symbol * addDeclaration(DT)(DT adt)
    {
        static if
        (
            is(DT == const(EponymousTemplateDeclaration)) ||
            is(DT == const(AnonymousEnumMember))    ||
            is(DT == const(AliasInitializer))       ||
            is(DT == const(ClassDeclaration))       ||
            is(DT == const(Declarator))             ||
            is(DT == const(EnumDeclaration))        ||
            is(DT == const(FunctionDeclaration))    ||
            is(DT == const(InterfaceDeclaration))   ||
            is(DT == const(StructDeclaration))      ||
            is(DT == const(TemplateDeclaration))    ||
            is(DT == const(UnionDeclaration))
            
        )
        {
            count++;
            auto result = construct!Symbol;
            result.name = adt.name.text;
            result.line = adt.name.line;
            result.col  = adt.name.column;
            parent.subs ~= result;
            return result;
        }
        
        version(none) assert(0, "addDeclaration no implemented for " ~ DT.stringof);
    }
    
    /// visitor implementation if the declarator is based on a Token named "name".
    final void namedVisitorImpl(DT, SymbolType st, bool dig = true)(const(DT) dt)
    {
        auto newSymbol = addDeclaration(dt);
        newSymbol.type = st;
        //
        static if (dig)
        {
            auto previousParent = parent;
            scope(exit) parent = previousParent;
            parent = newSymbol;
            dt.accept(this);
        }
    }

    /// visitor implementation for special cases.
    final void otherVisitorImpl(SymbolType st, string name, size_t line, size_t col)
    {
        count++;
        auto result = construct!Symbol;
        result.name = name;
        result.line = line;
        result.col  = col; 
        result.type = st;            
        parent.subs ~= result;     
    }

    final override void visit(const AliasDeclaration decl) 
    { 
        // why is initializers an array ?
        if (decl.initializers.length > 0)
            namedVisitorImpl!(AliasInitializer, SymbolType._alias)(decl.initializers[0]);  
    }   
    
    final override void visit(const AnonymousEnumDeclaration decl) 
    {
        if (decl.members.length > 0)
            namedVisitorImpl!(AnonymousEnumMember, SymbolType._enum)(decl.members[0]);
    }
     
    final override void visit(const ClassDeclaration decl) 
    {
        namedVisitorImpl!(ClassDeclaration, SymbolType._class)(decl);
    }
    
    final override void visit(const Constructor decl) 
    {
        otherVisitorImpl(SymbolType._function, "this", decl.line, decl.column); 
    }
    
    final override void visit(const Destructor decl) 
    {
        otherVisitorImpl(SymbolType._function, "~this", decl.line, decl.column); 
    }
    
    final override void visit(const EnumDeclaration decl) 
    {
        namedVisitorImpl!(EnumDeclaration, SymbolType._enum)(decl);
    }
    
    final override void visit(const EponymousTemplateDeclaration decl)
    {
        namedVisitorImpl!(EponymousTemplateDeclaration, SymbolType._template)(decl);
    }
    
    final override void visit(const FunctionDeclaration decl) 
    {
        namedVisitorImpl!(FunctionDeclaration, SymbolType._function)(decl);    
    }
    
    final override void visit(const InterfaceDeclaration decl) 
    {
        namedVisitorImpl!(InterfaceDeclaration, SymbolType._interface)(decl);    
    }
    
    final override void visit(const ImportDeclaration decl) 
    {
        foreach(const(SingleImport) si; decl.singleImports)
        { 
            if (!si.identifierChain.identifiers.length)
                continue;
            //
            string[] modules;
            foreach(ident; si.identifierChain.identifiers)
            {
                modules ~= ident.text;
                modules ~= ".";
            }
            //
            otherVisitorImpl(SymbolType._import, modules[0..$-1].join, 
                si.identifierChain.identifiers[0].line,
                si.identifierChain.identifiers[0].column
            ); 
        }   
    }
    
    final override void visit(const MixinTemplateDeclaration decl) 
    {
        namedVisitorImpl!(TemplateDeclaration, SymbolType._mixin)(decl.templateDeclaration);  
    }   
    
    final override void visit(const StructDeclaration decl) 
    {
        namedVisitorImpl!(StructDeclaration, SymbolType._struct)(decl);   
    }
    
    final override void visit(const TemplateDeclaration decl) 
    {
        namedVisitorImpl!(TemplateDeclaration, SymbolType._template)(decl);    
    }
    
    final override void visit(const UnionDeclaration decl) 
    {
        namedVisitorImpl!(UnionDeclaration, SymbolType._union)(decl);    
    }
    
    final override void visit(const VariableDeclaration decl) 
    {
        foreach(elem; decl.declarators)
            namedVisitorImpl!(Declarator, SymbolType._variable, false)(elem);  
    }
    
    final override void visit(const StaticConstructor decl) 
    {
        otherVisitorImpl(SymbolType._function, "static this", decl.line, decl.column); 
    }
    
    final override void visit(const StaticDestructor decl) 
    {
        otherVisitorImpl(SymbolType._function, "static ~this", decl.line, decl.column); 
    }
}
//----
