// 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 autocomplete; import std.range; import std.algorithm; import std.array; import std.conv; import std.stdio; import std.typecons; import std.path; import std.file; import std.d.lexer; import parser; import langutils; import types; immutable string[] versions = ["AIX", "all", "Alpha", "ARM", "BigEndian", "BSD", "Cygwin", "D_Coverage", "D_Ddoc", "DigitalMars", "D_InlineAsm_X86", "D_InlineAsm_X86_64", "D_LP64", "D_NET", "D_PIC", "D_Version2", "FreeBSD", "GNU", "HPPA", "HPPA64", "Hurd", "IA64", "LDC", "linux", "LittleEndian", "MinGW", "MIPS", "MIPS64", "none", "OpenBSD", "OSX", "Posix", "PPC", "PPC64", "S390", "S390X", "SDC", "SH", "SH64", "SkyOS", "Solaris", "SPARC", "SPARC64", "SysV3", "SysV4", "unittest", "Win32", "Win64", "Windows", "X86", "X86_64" ]; immutable string[] scopes = ["exit", "failure", "success"]; ///** // * Returns: indicies into the token array // */ //size_t findEndOfExpression(const Token[] tokens, const size_t index) //out (result) //{ // assert (result < tokens.length); // assert (result >= index); //} //body //{ // size_t i = index; // loop: while (i < tokens.length) // { // switch (tokens[i].type) // { // case TokenType.Return: // case TokenType.New: // case TokenType.Delete: // case TokenType.Comma: // case TokenType.RBrace: // case TokenType.RParen: // case TokenType.RBracket: // case TokenType.Semicolon: // break loop; // case TokenType.LParen: // skipParens(tokens, i); // break; // case TokenType.LBrace: // skipBraces(tokens, i); // break; // case TokenType.LBracket: // skipBrackets(tokens, i); // break; // default: // ++i; // break; // } // } // return i; //} // //size_t findBeginningOfExpression(const Token[] tokens, const size_t index) //in //{ // assert (index < tokens.length); // assert (tokens.length > 0); //} //out (result) //{ // import std.string; // assert (result < tokens.length); // assert (result <= index, format("findBeginningOfExpression %d, %d", result, index)); //} //body //{ // size_t i = index; // loop: while (i < tokens.length) // { // switch (tokens[i].type) // { // case TokenType.Assign: case TokenType.BitAnd: case TokenType.BitAndEquals: // case TokenType.BitOr: case TokenType.BitOrEquals: case TokenType.CatEquals: // case TokenType.Colon: case TokenType.Comma: case TokenType.Decrement: // case TokenType.Div: case TokenType.DivEquals: case TokenType.Dollar: // case TokenType.Equals: case TokenType.GoesTo: // case TokenType.Greater: case TokenType.GreaterEqual: case TokenType.Hash: // case TokenType.Increment: case TokenType.LBrace: case TokenType.LBracket: // case TokenType.Less: case TokenType.LessEqual: case TokenType.LessEqualGreater: // case TokenType.LessOrGreater: case TokenType.LogicAnd: case TokenType.LogicOr: // case TokenType.LParen: case TokenType.Minus: case TokenType.MinusEquals: // case TokenType.Mod: case TokenType.ModEquals: case TokenType.MulEquals: // case TokenType.Not: case TokenType.NotEquals: case TokenType.NotGreater: // case TokenType.NotGreaterEqual: case TokenType.NotLess: case TokenType.NotLessEqual: // case TokenType.NotLessEqualGreater: case TokenType.Plus: case TokenType.PlusEquals: // case TokenType.Pow: case TokenType.PowEquals: case TokenType.RBrace: // case TokenType.Semicolon: case TokenType.ShiftLeft: case TokenType.ShiftLeftEqual: // case TokenType.ShiftRight: case TokenType.ShiftRightEqual: case TokenType.Slice: // case TokenType.Star: case TokenType.Ternary: case TokenType.Tilde: // case TokenType.Unordered: case TokenType.UnsignedShiftRight: case TokenType.UnsignedShiftRightEqual: // case TokenType.Vararg: case TokenType.Xor: case TokenType.XorEquals: // case TokenType.KEYWORDS_BEGIN: .. case TokenType.KEYWORDS_END: // return i + 1; // case TokenType.RParen: // if (i == 0) // break loop; // skipParens(tokens, i); // break; // case TokenType.RBracket: // if (i == 0) // break loop; // skipBrackets(tokens, i); // break; // default: // if (i == 0) // break loop; // i--; // break; // } // } // return i + 1; //} // //const(Token)[] splitCallChain(const(Token)[] tokens) //{ // auto app = appender!(Token[])(); // size_t i = 0; // while (i < tokens.length) // { // app.put(tokens[i++]); // while (i < tokens.length && tokens[i] == TokenType.LParen) skipParens(tokens, i); // while (i < tokens.length && tokens[i] == TokenType.LBracket) skipBrackets(tokens, i); // while (i < tokens.length && tokens[i] == TokenType.Dot) ++i; // } // return app.data; //} // //unittest //{ // auto code = `a.b[10].c("grcl").x`; // auto tokens = tokenize(code); // assert (splitCallChain(tokens) == ["a", "b", "c", "x"]); //} // //struct AutoComplete //{ // this(const (Token)[] tokens, CompletionContext context) // { // this.tokens = tokens; // this.context = context; // } // // string getTypeOfExpression(const(Token)[] expression, const Token[] tokens, size_t cursor) // { // stderr.writeln("getting type of ", expression); // if (expression.length == 0) // return "void"; // auto type = typeOfVariable(expression[0], cursor); // if (type is null) // return "void"; // size_t index = 1; // while (index < expression.length) // { // const Tuple!(string, string)[string] typeMap = context.getMembersOfType( // type); // const Tuple!(string, string)* memberType = expression[index].value in typeMap; // if (memberType is null) // return "void"; // else // type = (*memberType)[0]; // index++; // } // return type; // } // // string typeOfVariable(Token symbol, size_t cursor) // { // // int is of type int, double of type double, and so on // if (symbol.value in typeProperties) // return symbol.value; // // string tokenType = getTypeFromToken(symbol); // if (tokenType !is null) // return tokenType; // // if (context.getMembersOfType(symbol.value)) // return symbol.value; // // // Arbitrarily define the depth of the cursor position as zero // // iterate backwards through the code to try to find the variable // int depth = 0; // auto preceedingTokens = assumeSorted(tokens).lowerBound(cursor); // auto index = preceedingTokens.length - 1; // while (true) // { // if (preceedingTokens[index] == TokenType.LBrace) // --depth; // else if (preceedingTokens[index] == TokenType.RBrace) // ++depth; // else if (depth <= 0 && preceedingTokens[index].value == symbol) // { // // Found the symbol, now determine if it was declared here. // auto p = preceedingTokens[index - 1]; // // // if ((p == TokenType.Auto || p == TokenType.Immutable // || p == TokenType.Const) // && preceedingTokens[index + 1] == TokenType.Assign) // { // // Try to determine the type of a variable declared as "auto" // return getTypeOfExpression( // tokens[index + 2 .. findEndOfExpression(tokens, index + 2)], // tokens, cursor); // } // else if (p == TokenType.Identifier // || (p.type > TokenType.TYPES_BEGIN // && p.type < TokenType.TYPES_END)) // { // // Handle simple cases like "int a;" or "Someclass instance;" // return p.value; // } // else if (p == TokenType.RBracket || p == TokenType.RParen) // { // return combineTokens(tokens[findBeginningOfExpression(tokens, index) .. index]); // } // } // if (index == 0) // break; // else // --index; // } // // // Find all struct or class bodies that we're in. // // Check for the symbol in those class/struct/interface bodies // // if match is found, return it // auto structs = context.getStructsContaining(cursor); // if (symbol == "this" && structs.length > 0) // { // return minCount!("a.bodyStart > b.bodyStart")(structs)[0].name; // } // // foreach (s; structs) // { // auto t = s.getMemberType(symbol.value); // if (t !is null) // return t; // } // return "void"; // } // // string symbolAt(size_t cursor) const // { // auto r = assumeSorted(tokens).lowerBound(cursor)[$ - 1]; // if (r.value.length + r.startIndex > cursor) // return r.value; // else // return null; // } // // string parenComplete(size_t cursor) // { // auto index = assumeSorted(tokens).lowerBound(cursor).length - 2; // Token t = tokens[index]; // switch (tokens[index].type) // { // case TokenType.Version: // return "completions\n" ~ to!string(join(map!`a ~ " k"`(versions), "\n").array()); // case TokenType.Scope: // return "completions\n" ~ to!string(join(map!`a ~ " k"`(scopes), "\n").array()); // case TokenType.If: // case TokenType.Cast: // case TokenType.While: // case TokenType.For: // case TokenType.Foreach: // case TokenType.Switch: // return ""; // default: // size_t startIndex = findBeginningOfExpression(tokens, index); // auto callChain = splitCallChain(tokens[startIndex .. index + 1]); // auto expressionType = getTypeOfExpression( // callChain[0 .. $ - 1], tokens, cursor); // return "calltips\n" ~ to!string(context.getCallTipsFor(expressionType, // callChain[$ - 1].value, cursor).join("\n").array()); // } // } // // string dotComplete(size_t cursor) // { // stderr.writeln("dotComplete"); // auto index = assumeSorted(tokens).lowerBound(cursor).length - 1; // Token t = tokens[index]; // // // If the last character entered before the cursor isn't a dot, give up. // // The user was probably in the middle of typing the slice or vararg // // operators // if (t != TokenType.Dot) // return null; // // size_t startIndex = findBeginningOfExpression(tokens, index); // if (startIndex - 1 < tokens.length && tokens[startIndex - 1] == TokenType.Import) // { // return importComplete(splitCallChain(tokens[startIndex .. index])); // } // // auto expressionType = getTypeOfExpression( // splitCallChain(tokens[startIndex .. index]), tokens, cursor); // // stderr.writeln("expression type is ", expressionType); // // // Complete pointers and references the same way // if (expressionType[$ - 1] == '*') // expressionType = expressionType[0 .. $ - 1]; // // const Tuple!(string, string)[string] typeMap = context.getMembersOfType( // expressionType); // if (typeMap is null) // return ""; // auto app = appender!(string[])(); // foreach (k, t; typeMap) // app.put(k ~ " " ~ t[1]); // return to!string(array(join(sort!("a.toLower() < b.toLower()")(app.data), "\n"))); // } // // string importComplete(const(Token)[] tokens) // { // stderr.writeln("importComplete"); // auto app = appender!(string[])(); // string part = to!string(map!"a.value.dup"(tokens).join("/").array()); // foreach (path; context.importDirectories) // { // stderr.writeln("Searching for ", path, "/", part); // if (!exists(buildPath(path, part))) // continue; // stderr.writeln("found it"); // foreach (DirEntry dirEntry; dirEntries(buildPath(path, part), // SpanMode.shallow)) // { // if (dirEntry.isDir) // app.put(baseName(dirEntry.name) ~ " P"); // else if (dirEntry.name.endsWith(".d", ".di")) // app.put(stripExtension(baseName(dirEntry.name)) ~ " M"); // } // } // return to!string(sort!("a.toLower() < b.toLower()")(app.data).join("\n").array()); // } // // const(Token)[] tokens; // CompletionContext context; //} // //unittest //{ // auto code = q{ //struct TestStruct { int a; int b; } //TestStruct ts; //ts.a. // }; // // auto tokens = tokenize(code); // auto mod = parseModule(tokens); // auto context = new CompletionContext(mod); // auto completion = AutoComplete(tokens, context); // assert (completion.getTypeOfExpression(splitCallChain(tokens[13 .. 16]), // tokens, 56) == "int"); //}