// Written in the D programming language

/**
 * This module contains a parser for D source code.
 */

module std.d.parser;

import std.d.lexer;
import std.d.ast;
version(unittest) import std.stdio;

struct Parser
{
public:

    Module parseModule()
    {
        Module m = new Module;
        while (index < tokens.length)
        {
            switch (tokens[index].type)
            {
            case TokenType.module_:
                if (m.declaration !is null)
                    m.declaration = parseModuleDeclaration();
                else
                    error("Only one module declaration is allowed per module");
                break;
            default:
                m.declDefs.insert(parseDeclDef());
            }
        }
        return m;
    }

private:

    ModuleDeclaration parseModuleDeclaration()
    in
    {
        assert (expect(TokenType.module_));
    }
    body
    {
        ModuleDeclaration declaration = new ModuleDeclaration;
        string recent;
        loop: while (index < tokens.length)
        {
            if (tokens[index].type == TokenType.identifier)
            {
                recent = tokens[index++].value;
                switch (tokens[index].type)
                {
                case TokenType.dot:
                    declaration.packageName ~= recent;
                    index++;
                    break;
                case TokenType.semicolon:
                    declaration.moduleName = recent;
                    index++;
                    break loop;
                default:
                    break;
                }
            }
            else
                error("Identifier expected");
        }
        return declaration;
    }

    DeclDef parseDeclDef()
    {
        switch (tokens[index].type)
        {
//			case TokenType.identifier:
//				if (nextIs(TokenType.colon))
//					return parseLabeledStatement();
//				break;
//			case TokenType.this_:
//				return parseConstructor();
//			case TokenType.tilde:
//				if (nextIs(TokenType.this_))
//					return parseDestructor();
//				break;
            default:
                return null;
        }
    }

//	LabeledStatement parseLabeledStatement()
//	in
//	{
//		assert (tokens[index].type == TokenType.identifier);
//	}
//	body
//	{
//		auto ls = new LabeledStatement;
//		ls.label = tokens[index++].value;
//		ls.statement = parseNoScopeStatement();
//		return ls;
//	}

//	NoScopeStatement parseNoScopeStatement()
//	{
//		switch (tokens[index].type)
//		{
//		case TokenType.semicolon:
//			return new EmptyStatement;
//		case TokenType.lBrace:
//			return parseBlockStatement();
//		default:
//			return parseNonEmptyStatement();
//		}
//	}

    void error(string message)
    {
        import std.stdio;
        stderr.writefln("%s(%d:%d): %s", fileName, tokens[index].line,
            tokens[index].column, message);
        while (index < tokens.length)
        {
            if (tokens[++index].type == TokenType.semicolon)
                break;
        }
    }

    Token* peekPast(alias O, alias C)()
    in
    {
        assert (tokens[index].type == O);
    }
    body
    {
        int depth = 1;
        auto i = index;
        ++i;
        while (index < tokens.length)
        {
            if (tokens[i] == O)
                ++depth;
            else if (tokens[i] == C)
            {
                --depth;
                ++i;
                if (depth <= 0)
                    break;
            }
            ++i;
        }
        return depth == 0 ? &tokens[i] : null;
    }

    Token* peekPastParens()
    {
        return peekPast!(TokenType.lParen, TokenType.rParen)();
    }

    Token* peekPastBrackets()
    {
        return peekPast!(TokenType.lBracket, TokenType.rBracket)();
    }

    Token* peekPastBraces()
    {
        return peekPast!(TokenType.lBrace, TokenType.rBrace)();
    }

    Token* expect(TokenType type)
    {
        if (tokens[index].type == type)
            return &tokens[index++];
        else
            return null;
    }

    Token* peek()
    {
        return index + 1 < tokens.length ? &tokens[index + 1] : null;
    }

    bool nextIs(TokenType t)
    {
        return peek() && peek().type == t;
    }

    bool moreTokens()
    {
        return index < tokens.length;
    }

    Token[] tokens;
    size_t index;
    string fileName;
}

//
//unittest
//{
//	auto a = cast(ubyte[]) q{/** */ module a.b.c;};
//	LexerConfig config;
//	auto ta = byToken(a, config);
//	auto moda = parseModuleDeclaration(ta);
//	assert (moda.packageName == ["a", "b"]);
//	assert (moda.moduleName == "c");
//
//	auto b = cast(ubyte[]) q{module a;};
//	auto tb = byToken(b, config);
//	auto modb = parseModuleDeclaration(tb);
//	assert (modb.packageName.length == 0);
//	assert (modb.moduleName == "a");
//}
//
//NonEmptyStatement parseNonEmptyStatement(Token[] tokens)
//{
//	switch (tokens[i].type)
//	{
//		case TokenType.case_:
//			return null;
//		case TokenType.default_:
//			return parseDefaultStatement(tokens);
//		default:
//			return parseNonEmptyStatementNoCaseNoDefault(tokens);
//	}
//}
//
//NonEmptyStatementNoCaseNoDefault parseNonEmptyStatementNoCaseNoDefault(Token[] tokens)
//{
//	switch (tokens[i].type)
//	{
//	case TokenType.identifier:
//	case TokenType.if_:
//		return parseIfStatement(tokens);
//	case TokenType.while_:
//		return parseWhileStatement(tokens);
//	case TokenType.do_:
//		return parseDoStatement(tokens);
//	case TokenType.for_:
//		return parseForStatement(tokens);
//	case TokenType.foreach_:
//		return parseForeachStatement(tokens);
//	case TokenType.switch_:
//		return parseSwitchStatement(tokens);
//	case TokenType.final_:
//		if (tokens.peek(1).type == TokenType.switch_)
//			return parseFinalSwitchStatement(tokens);
//		else
//			goto default;
//	case TokenType.continue_:
//		return parseContinueStatement(tokens);
//	case TokenType.break_:
//		return parseBreakStatement(tokens);
//	case TokenType.return_:
//		return parseReturnStatement(tokens);
//	case TokenType.goto_:
//		return parseGotoStatement(tokens);
//	case TokenType.with_:
//		return parseWithStatement(tokens);
//	case TokenType.synchronized_:
//		return parseSynchronizedStatement(tokens);
//	case TokenType.try_:
//		return parseTryStatement(tokens);
//	case TokenType.scope_:
//		return parseScopeGuardStatement(tokens);
//	case TokenType.throw_:
//		return parseThrowStatement(tokens);
//	case TokenType.asm_:
//		return parseAsmStatement(tokens);
//	case TokenType.pragma_:
//		return parsePragmaStatement(tokens);
//	case TokenType.mixin_:
//		if (tokens.peek(1).type == TokenType.lParen)
//			return parseMixinStatement(tokens);
//		else if (tokens.peek(1).type == TokenType.identifier)
//			return parseTemplateMixinStatement(tokens);
//		else
//		{
//			error(tokens, "Expected identifier or ( following \"mixin\"");
//			return null;
//		}
//	case TokenType.version_:
//		if (tokens.peek(1).type == TokenType.lParen)
//			return parseConditionalStatement(tokens);
//		else
//		{
//			error(tokens, "Expected ( following \"version\"");
//			return null;
//		}
//	case TokenType.debug_:
//		return parseConditionalStatement(tokens);
//	case TokenType.static_:
//		if (tokens.peek(1).type == TokenType.if_)
//			return parseConditionalStatement(tokens);
//		else if (tokens.peek(1).type == TokenType.assert_)
//			return parseStaticAssert(tokens);
//		else
//		{
//			error(tokens, "Expected \"if\" or \"assert\" following \"static\"");
//			return null;
//		}
//	case TokenType.import_:
//		return parseImportDeclaration(tokens);
//	default:
//		auto d = parseDeclarationStatement(tokens);
//		if (d is null)
//		{
//			auto e = parseExpressionStatement(tokens);
//			if (e is null)
//			{
//				error(tokens, "OMGWTF");
//				return null;
//			}
//			else
//				return e;
//		}
//		else
//			return d;
//	}
//}
//
//GotoStatement parseGotoStatement(Token[] tokens)
//in
//{
//	assert (tokens[i] == TokenType.goto_);
//}
//body
//{
//	tokens.popFront();
//	auto g = new GotoExpression;
//	switch (tokens[i].type)
//	{
//	case TokenType.identifier:
//		g.type = GotoStatement.GotoType.identifier;
//		g.identifier = tokens.moveFront().value;
//		break;
//	case TokenType.default_:
//		tokens.popFront();
//		g.type = GotoStatement.GotoType.break_;
//	case TokenType.case_:
//		g.type = GotoStatement.GotoType.case_;
//		tokens.popFront();
//	default:
//		error(tokens, "Expected an identifier, \"default\", or \"case\" following \"goto\"");
//		return null;
//	}
//}
//
//ContinueStatement parseContinueStatement(Token[] tokens)
//in
//{
//	assert (tokens[i] == TokenType.continue_);
//}
//body
//{
//	return parseContinueBreakStatement!(R, ContinueStatement)(tokens);
//}
//
//BreakStatement parseBreakStatement(Token[] tokens)
//in
//{
//	assert (tokens[i] == TokenType.break_);
//}
//body
//{
//	return parseBreakStatement!(R, BreakStatement)(tokens);
//}
//
//statementType parseContinueBreakStatement(R, alias statementType)(ref R tokens)
//{
//	tokens.popFront();
//	auto c = new statementType;
//	switch (tokens[i].type)
//	{
//		case TokenType.identifier:
//			c.identifier = tokens.moveFront().value;
//			goto case;
//		case TokenType.semicolon:
//			return c;
//		default:
//			error(tokens, "Identifier or semicolon expected");
//			return null;
//	}
//
//}
//
//
//T parseSingleTokenExpression(TokType, AstType, R)(ref R range)
//{
//	auto node = new AstType;
//	node.token = range.moveFront();
//	return node;
//}
//
//AssignExpression parseAssignExpression(Tokens)(ref Tokens tokens)
//{
//	auto expr = new AssignExpression;
//	expr.left = parseConditionalExpression(tokens);
//	switch (tokens[i].type)
//	{
//		case TokenType.assign:
//		case TokenType.plusEqual:
//		case TokenType.minusEqual:
//		case TokenType.mulEqual:
//		case TokenType.divEqual:
//		case TokenType.modEqual:
//		case TokenType.bitAndEqual:
//		case TokenType.bitOrEqual:
//		case TokenType.xorEqual:
//		case TokenType.catEqual:
//		case TokenType.shiftLeftEqual:
//		case TokenType.shiftRightEqual:
//		case TokenType.unsignedShiftRightEqual:
//		case TokenType.powEqual:
//			expr.operator = tokens.moveFront().type;
//			expr.right = parseAssignExpression();
//		default:
//			break;
//	}
//	return expr;
//}

//void main(string[] args) {}

/+
//          Copyright Brian Schott (Sir Alaran) 2012.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

module parser;

import std.d.lexer;
import std.range;
import basicast;
version (unittest) import std.stdio;
version (unittest) import std.string;

void skipPastSemicolon(Tokens)(ref Tokens tokens)
{
    while (!tokens.empty)
    {
        if (tokens[i].type == TokenType.semicolon)
        {
            tokens.popFront();
            return;
        }
        else
            tokens.popFront();
    }
}

void skipDelimited(Tokens, alias O, alias C)(ref Tokens tokens)
in
{
    assert (tokens[i].type == O);
}
body
{
    tokens.popFront();
    int depth = 1;
    while (!tokens.empty)
    {
        switch (tokens[i].type)
        {
        case C:
            --depth;
            if (depth > 0)
                goto default;
            tokens.popFront();
            return;
        case O:
            ++depth;
            goto default;
        default:
            tokens.popFront();
            break;
        }
    }
}

void skipBraces(Tokens)(ref Tokens tokens)
{
    return skipDelimited!(Tokens, TokenType.lBrace, TokenType.rBrace)(tokens);
}

void skipParens(Tokens)(ref Tokens tokens)
{
    return skipDelimited!(Tokens, TokenType.lParen, TokenType.rParen)(tokens);
}

void skipBrackets(Tokens)(ref Tokens tokens)
{
    return skipDelimited!(Tokens, TokenType.lBracket, TokenType.rBracket)(tokens);
}

string delimiterContent(Tokens, alias O, alias C)(ref Tokens tokens)
{
    tokens.popFront();
    int depth = 1;
    auto app = appender!(char[])();
    loop: while (!tokens.empty)
    {
        switch (tokens[i].type)
        {
        case C:
            --depth;
            if (depth > 0)
                goto default;
            tokens.popFront();
            break loop;
        case O:
            ++depth;
            goto default;
        default:
            app.put(tokens.moveFront().value);
            break;
        }
    }
    return cast(string) app.data;
}

string bracketContent(Tokens)(ref Tokens tokens)
{
    return "[" ~ delimiterContent!(Tokens, TokenType.lBracket, TokenType.rBracket)(tokens) ~ "]";
}

string parenContent(Tokens)(ref Tokens tokens)
{
    return "(" ~ delimiterContent!(Tokens, TokenType.lParen, TokenType.rParen)(tokens) ~ ")";
}

string braceContent(Tokens)(ref Tokens tokens)
{
    return "{" ~ delimiterContent!(Tokens, TokenType.lBrace, TokenType.rBrace)(tokens) ~ "}";
}

bool isIdentifierOrBasicType(const TokenType type)
{
    return isType(type) || type == TokenType.identifier;
}

ImportDeclaration parseImportDeclaration(Tokens)(ref Tokens tokens)
in
{
    assert(tokens[i] == TokenType.import_);
}
body
{
    auto declaration = new ImportDeclaration;
    tokens.popFront();
    Import im;

    if (tokens[i].type != TokenType.identifier)
    {
        tokens.skipPastSemicolon();
        return declaration;
    }

    void completeImport()
    {
        im.moduleName = tokens.moveFront().value;
        tokens.popFront();
        declaration.imports ~= im;
    }

    void parseImportBindings()
    {
        loop: while (!tokens.empty)
        {
            if (tokens[i].type != TokenType.identifier)
                break;
            switch (tokens.peek().type)
            {
            case TokenType.assign:
                Import.ImportSymbol s;
                s.alias_ = tokens.moveFront().value;
                tokens.popFront();
                if (tokens.empty || tokens[i].type != TokenType.identifier)
                    break loop;
                s.symbolName = tokens.moveFront().value;
                im.symbols ~= s;
                if (!tokens.empty())
                {
                    if (tokens[i].type == TokenType.comma)
                        tokens.popFront();
                    if (tokens[i].type == TokenType.semicolon)
                    {
                        tokens.popFront();
                        declaration.imports ~= im;
                        break loop;
                    }
                }
                break;
            case TokenType.comma:
                Import.ImportSymbol s;
                s.symbolName = tokens.moveFront().value;
                tokens.popFront();
                im.symbols ~= s;
                break;
            case TokenType.semicolon:
                Import.ImportSymbol s;
                s.symbolName = tokens.moveFront().value;
                tokens.popFront();
                im.symbols ~= s;
                declaration.imports ~= im;
                break loop;
            default:
                break loop;
            }
        }
    }

    loop: while (!tokens.empty)
    {
        switch  (tokens.peek().type)
        {
        case TokenType.dot:
            im.packageParts ~= tokens.moveFront().value;
            tokens.popFront();
            break;
        case TokenType.comma:
            completeImport();
            im = Import.init;
            break;
        case TokenType.semicolon:
            completeImport();
            break loop;
        case TokenType.colon:
            im.moduleName = tokens.moveFront().value;
            tokens.popFront();
            parseImportBindings();
            break loop;
        case TokenType.assign:
            im.alias_ = tokens.moveFront().value;
            tokens.popFront();
            break;
        default:
            tokens.popFront();
            break;
        }
    }

    return declaration;
}

unittest
{
    auto source = cast(ubyte[]) q{import std.stdio;
        import std.ascii: hexDigits;
        import r = std.range;
        import foo, bar;
        import std.stdio : writefln, foo = writef;}c;

    LexerConfig config;
    auto tokens = source.byToken(config).circularBuffer(4);
    assert (tokens[i] == "import");

    auto decl = parseImportDeclaration(tokens);
    assert (decl.imports.length == 1);
    assert (decl.imports[0].packageParts == ["std"]);
    assert (decl.imports[0].moduleName == "stdio");
    assert (tokens[i].value == "import", tokens.front.value);
    assert (tokens.peek(3).value == "ascii", tokens.front.value);

    decl = parseImportDeclaration(tokens);
    assert (decl.imports.length == 1, "%d".format(decl.imports.length));
    assert (decl.imports[0].packageParts == ["std"]);
    assert (decl.imports[0].moduleName == "ascii", decl.imports[0].moduleName);
    assert (decl.imports[0].symbols[0].symbolName == "hexDigits", decl.imports[0].symbols[0].symbolName);
    assert (decl.imports[0].symbols[0].alias_.length == 0);

    decl = parseImportDeclaration(tokens);
    assert (decl.imports.length == 1, "%s".format(decl.imports.length));
    assert (decl.imports[0].moduleName == "range");
    assert (decl.imports[0].packageParts == ["std"]);
    assert (decl.imports[0].alias_ == "r");

    decl = parseImportDeclaration(tokens);
    assert (decl.imports.length == 2);
    assert (decl.imports[0].packageParts.length == 0);
    assert (decl.imports[0].moduleName == "foo");
    assert (decl.imports[1].packageParts.length == 0);
    assert (decl.imports[1].moduleName == "bar");

    decl = parseImportDeclaration(tokens);
    assert (decl.imports.length == 1, "%s".format(decl.imports.length));
    assert (decl.imports[0].packageParts == ["std"]);
    assert (decl.imports[0].moduleName == "stdio");
    assert (decl.imports[0].symbols.length == 2);
    assert (decl.imports[0].symbols[0].symbolName == "writefln");
    assert (decl.imports[0].symbols[1].symbolName == "writef");
    assert (decl.imports[0].symbols[1].alias_ == "foo");
}

string parseType(Tokens)(ref Tokens tokens)
{
    if (tokens.front != TokenType.identifier && !isType(tokens.front.type))
        return null;
    auto app = appender!(ubyte[])();
    switch (tokens.front.type)
    {
    case TokenType.const_:
    case TokenType.immutable_:
    case TokenType.shared_:
    case TokenType.inout_:
    case TokenType.typeof_:
        app.put(cast(ubyte[]) tokens.moveFront().value);
        if (tokens.empty) goto ret;
        if (tokens.front.type == TokenType.lParen)
            app.put(cast(ubyte[]) parenContent(tokens));
        break;
    case TokenType.bool_: .. case TokenType.wchar_:
    case TokenType.identifier:
        app.put(cast(ubyte[]) tokens.moveFront().value);
        break;
    default:
        return null;
    }

    if (tokens.empty) goto ret;

    if (tokens.front.type == TokenType.not)
    {
        app.put('!');
        tokens.popFront();
        if (tokens.empty) goto ret;
        if (tokens.front.type == TokenType.lParen)
            app.put(cast(ubyte[]) parenContent(tokens));
        else if (isIdentifierOrBasicType(tokens.front.type))
            app.put(cast(ubyte[]) tokens.moveFront().value);
        else
            goto ret;
    }
    else if (tokens.front.type == TokenType.function_ || tokens.front.type == TokenType.delegate_)
    {
        app.put(' ');
        app.put(cast(ubyte[]) tokens.moveFront().value);
        if (tokens.empty) goto ret;
        if (tokens.front.type == TokenType.lParen)
        {
            app.put(cast(ubyte[]) parenContent(tokens));
            goto ret;
        }
    }

    loop: while (!tokens.empty)
    {
        switch (tokens.front.type)
        {
        case TokenType.star:
            app.put('*');
            tokens.popFront();
            break;
        case TokenType.lBracket:
            app.put(cast(ubyte[]) bracketContent(tokens));
            break;
        default:
            break loop;
        }
    }
ret:
    return cast(string) app.data;
}

unittest
{
    auto sources = [
        q{int}c,
        q{int function(int,int)}c,
        q{void}c,
        q{char*}c,
        q{char*[]*[]}c,
        q{Stuff!(int,double)*}c,
        q{Template!a[]}c,
        q{Template!(a)[]}c,
    ];
    LexerConfig config;
    foreach (source; sources)
    {
        auto tokens = (cast(ubyte[]) source).byToken(config).circularBuffer(4);
        auto t = parseType(tokens);
        assert (t == source, t);
    }
}

Parameter parseParameter(Tokens)(ref Tokens tokens)
{
    Parameter p;
    p.type = parseType(tokens);
    if (!tokens.empty && (tokens.front.type == TokenType.delegate_ || tokens.front.type == TokenType.function_))
    {
        p.type ~= " " ~ tokens.moveFront().value;
        if (tokens.front.type == TokenType.lParen)
            p.type ~= "(" ~ parenContent(tokens) ~")";
    }
    if (tokens.empty || tokens.front.type == TokenType.comma || tokens.front.type == TokenType.rParen)
    {
        p.name = p.type;
        p.type = "";
    }
    else
        p.name = tokens.moveFront().value;
    return p;
}

unittest
{
    auto source = cast(ubyte[]) q{int[] a}c;
    LexerConfig config;
    auto tokens = source.byToken(config).circularBuffer(4);
    auto p = parseParameter(tokens);
    assert (p.name == "a", p.name);
    assert (p.type == "int[]", p.type);
}

Parameter[] parseParameters(Tokens)(ref Tokens tokens)
in
{
    assert (tokens.front == TokenType.lParen);
}
body
{
    tokens.popFront();
    if (tokens.front.type == TokenType.rParen)
    {
        tokens.popFront();
        return [];
    }
    auto app = appender!(Parameter[])();
    while (!tokens.empty)
    {
        app.put(parseParameter(tokens));
        if (tokens.empty)
            break;
        else if (tokens.front.type == TokenType.rParen)
        {
            tokens.popFront();
            break;
        }
        else if (tokens.front == TokenType.comma)
            tokens.popFront();
        else
        {
            tokens.popFront();
            break;
        }
    }
    return app.data;
}

unittest
{
    auto source = cast(ubyte[]) q{(int[] a, double d, void*[] voids,
        void function(int**, double*[][string]) vf, R);}c;
    LexerConfig config;
    auto tokens = source.byToken(config).circularBuffer(4);
    auto p = parseParameters(tokens);
    assert (p.length == 5, "%d".format(p.length));
    assert (p[0].name == "a", p[0].name);
    assert (p[0].type == "int[]", p[0].type);
    assert (p[1].name == "d", p[1].name);
    assert (p[1].type == "double", p[1].type);
    assert (p[2].name == "voids", p[2].name);
    assert (p[2].type == "void*[]", p[2].type);
    assert (p[3].name == "vf", p[3].name);
    assert (p[3].type == "void function(int**,double*[][string])", p[3].type);
    assert (p[4].name == "R", p[4].name);
    assert (p[4].type == "", p[4].type);
    assert (tokens.front.type == TokenType.semicolon, tokens.front.value);
}

unittest
{
    auto source = cast(ubyte[]) q{()}c;
    LexerConfig config;
    auto tokens = source.byToken(config).circularBuffer(2);
    auto p = parseParameters(tokens);
    assert (p.length == 0, "%s".format(p.length));
}

FunctionDeclaration parseFunctionDeclaration(Tokens)(ref Tokens tokens, string type)
{
    FunctionDeclaration fun;
    fun.returnType = type;
    fun.name = tokens.moveFront().value;
    fun.rtParameters = parseParameters(tokens);
    if (tokens.front.type == TokenType.lParen)
    {
        fun.ctParameters = fun.rtParameters;
        fun.rtParameters = parseParameters(tokens);
    }
    while (!tokens.empty && isAttribute(tokens.front.type))
        fun.attributes.set(tokens.moveFront().type);
    if (tokens.front.type == TokenType.lBrace)
        skipBraces(tokens);
    else if (tokens.front.type == TokenType.semicolon)
        tokens.popFront();
    return fun;
}

unittest
{
    auto source = cast(ubyte[]) q{
        void doStuff();
        T calcSomething(T)(T input) { return input * 2; }
        const(string)[] getStrings() const {}
    }c;
    LexerConfig config;
    auto tokens = source.byToken(config).circularBuffer(4);

    auto decl = parseFunctionDeclaration(tokens, parseType(tokens));
    assert (decl.name == "doStuff");
    assert (decl.returnType == "void");
    assert (decl.ctParameters.length == 0);
    assert (decl.rtParameters.length == 0);

    decl = parseFunctionDeclaration(tokens, parseType(tokens));
    assert (decl.name == "calcSomething");
    assert (decl.returnType == "T");
    assert (decl.ctParameters[0].name == "T");
    assert (decl.rtParameters[0].type == "T");
    assert (decl.rtParameters[0].name == "input");

    decl = parseFunctionDeclaration(tokens, parseType(tokens));
    assert (decl.returnType == "const(string)[]", decl.returnType);
    assert (decl.name == "getStrings", decl.name);
    assert (decl.ctParameters.length == 0);
    assert (decl.rtParameters.length == 0);
    assert (decl.attributes.get() == [TokenType.const_]);
    assert (tokens.empty);
}

VariableDeclaration parseVariableDeclaration(Tokens)(ref Tokens tokens, string type)
{
    VariableDeclaration v;
    v.line = tokens.front.line;
    v.type = type;
    v.name = tokens.front.value;
    tokens.popFront();
    if (tokens.front.type == TokenType.semicolon)
        tokens.popFront();
    else
        skipPastSemicolon(tokens);
    return v;
}

unittest
{
    auto source = cast(ubyte[]) q{int c;}c;
    LexerConfig config;
    auto tokens = source.byToken(config).circularBuffer(2);
    auto decl = parseVariableDeclaration(tokens, parseType(tokens));
    assert (decl.name == "c");
    assert (decl.type == "int", decl.type);
    assert (tokens.empty);
}

ModuleDeclaration parseModuleDeclaration(Tokens)(ref Tokens tokens)
in
{
    assert (tokens.front.type == TokenType.module_);
}
body
{
    tokens.popFront();
    ModuleDeclaration declaration;
    string recent;
    loop: while (!tokens.empty)
    {
        if (tokens.front.type == TokenType.identifier)
        {
            recent = tokens.moveFront().value;
            switch (tokens.front.type)
            {
            case TokenType.dot:
                declaration.package_ ~= recent;
                tokens.popFront();
                break;
            case TokenType.semicolon:
                declaration.name = recent;
                tokens.popFront();
                break loop;
            default:
                break;
            }
        }
        else
            skipPastSemicolon(tokens);
    }
    return declaration;
}

unittest
{
    auto a = cast(ubyte[]) q{/** */ module a.b.c;};
    LexerConfig config;
    auto ta = byToken(a, config).circularBuffer(2);
    auto moda = parseModuleDeclaration(ta);
    assert (moda.package_ == ["a", "b"]);
    assert (moda.name == "c");

    auto b = cast(ubyte[]) q{module a;};
    auto tb = byToken(b, config).circularBuffer(2);
    auto modb = parseModuleDeclaration(tb);
    assert (modb.package_.length == 0);
    assert (modb.name == "a");
}

Module parseMod(Tokens)(ref Tokens tokens)
{
    Module mod;
    while (!tokens.empty)
    {
        switch (tokens.front.type)
        {
//			case TokenType.const_:
//			case TokenType.immutable_:
//			case TokenType.shared_:
//			case TokenType.inout_:
//				if (tokens.peek.type == TokenType.lParen)
//				{
//					auto
//				}
//				break;
            case TokenType.rBrace:
                return mod;
            case TokenType.identifier:
            case TokenType.bool_: .. case TokenType.wchar_:
                auto type = parseType(tokens);
                if (tokens.front.type == TokenType.identifier)
                {
                    if (tokens.peek.type == TokenType.lParen)
                        mod.functions ~= parseFunctionDeclaration(tokens, type);
                    else
                        mod.variables ~= parseVariableDeclaration(tokens, type);
                }
                else
                    skipPastSemicolon(tokens);
                break;
            case TokenType.module_:
                mod.moduleDeclaration = parseModuleDeclaration(tokens);
                break;
            case TokenType.class_:
                mod.classes ~= parseClassDeclaration(tokens);
                break;
//			case TokenType.align_:
//			case TokenType.deprecated_:
//			case TokenType.extern_:
            default:
                tokens.popFront();
        }
    }
    return mod;
}

Module parseModule(ref const(ubyte)[] source)
{
    LexerConfig config;
    auto tokens = source.byToken(config).circularBuffer(2);
    AttributeList attributes;
    return parseMod(tokens);
}

unittest
{
    auto source = cast(const(ubyte)[]) q{
        module a.b.c;

        int x;
        int doStuff();
        int doOtherStuff() {}

        class Point { int x; int y; }
    }c;
    auto mod = parseModule(source);
    assert (mod.moduleDeclaration.name == "c");
    assert (mod.moduleDeclaration.package_ == ["a", "b"]);
    assert (mod.functions.length == 2);
    assert (mod.variables.length == 1);
    assert (mod.classes.length == 1);
}

ClassDeclaration parseClassDeclaration(Tokens)(ref Tokens tokens)
in
{
    assert (tokens.front.type == TokenType.class_);
}
body
{
    tokens.popFront();
    ClassDeclaration decl;
    if (tokens.front.type != TokenType.identifier)


    return decl;
}

void main(string[] args)
{
}

+/