Merge branch '0.2.0-dev'

Conflicts:
	acvisitor.d
	autocomplete.d
	dscanner
	server.d
This commit is contained in:
Hackerpilot 2013-11-06 16:13:25 -08:00
commit 07c93758f7
17 changed files with 1915 additions and 1197 deletions

View File

@ -17,6 +17,8 @@ back to the client.
* Autocompletion of class, struct, and interface instances.
* Display of call tips for functions, constructors, and variables of function type
* alias declarations
* Public imports
* Finding the declaration location of a symbol at the cursor
* *import* statement completions
* Not working:
* Automatic starting of the server by the client
@ -25,7 +27,6 @@ back to the client.
* *auto* declarations
* *alias this*
* Determining the type of an enum member when no base type is specified, but the first member has an initialaizer
* Public imports
* That one feature that you *REALLY* needed
#Setup
@ -105,6 +106,18 @@ this, run the client with the -I option:
dcd-client -Ipath/to/imports
##Find declaration of symbol at cursor
```dcd-client --symbolLocation -c 123```
The "--symbolLocation" or "-l" flags cause the client to instruct the server
to return the path to the file and the byte offset of the declaration of the
symbol at the given cursor position.
The output consists of the absolute path to the file followed by a tab character
followed by the byte offset, folled by a newline character. For example
/home/example/src/project/bar.d 3482
#Server
The server must be running for the DCD client to provide autocomplete information.
In future versions the client may start the server if it is not running, but for

502
actypes.d
View File

@ -24,13 +24,13 @@ import std.algorithm;
import std.stdio;
import std.array;
import messages;
import autocomplete;
import std.array;
import std.typecons;
/**
* Any special information about a variable declaration symbol.
*/
enum SymbolQualifier
enum SymbolQualifier : ubyte
{
/// _none
none,
@ -45,12 +45,10 @@ enum SymbolQualifier
/**
* Autocompletion symbol
*/
class ACSymbol
struct ACSymbol
{
public:
this() {}
/**
* Params:
* name = the symbol's name
@ -77,23 +75,51 @@ public:
* kind = the symbol's completion kind
* resolvedType = the resolved type of the symbol
*/
this(string name, CompletionKind kind, ACSymbol resolvedType)
this(string name, CompletionKind kind, const(ACSymbol)* type)
{
this.name = name;
this.kind = kind;
this.resolvedType = resolvedType;
this.type = type;
}
/**
* Comparison operator sorts based on the name field
*/
int opCmp(string str) const
{
if (str < this.name) return -1;
if (str > this.name) return 1;
return 0;
}
/// ditto
int opCmp(const(ACSymbol)* other) const
{
return this.opCmp(other.name);
}
/**
* Gets all parts whose name matches the given string.
*/
const(ACSymbol)*[] getPartsByName(string name) const
{
return cast(typeof(return)) parts.filter!(a => a.name == name).array;
}
size_t estimateMemory(size_t runningTotal) const
{
runningTotal = runningTotal + name.length + callTip.length
+ ACSymbol.sizeof;
foreach (part; parts)
runningTotal = part.estimateMemory(runningTotal);
return runningTotal;
}
/**
* Symbols that compose this symbol, such as enum members, class variables,
* methods, etc.
*/
ACSymbol[] parts;
/**
* Listing of superclasses
*/
string[] superClasses;
const(ACSymbol)*[] parts;
/**
* Symbol's name
@ -101,14 +127,24 @@ public:
string name;
/**
* Symbol's location in bytes
* Calltip to display if this is a function
*/
size_t location;
string callTip;
/**
* Any special information about this symbol
* Module containing the symbol.
*/
SymbolQualifier qualifier;
string symbolFile;
/**
* The symbol that represents the type.
*/
const(ACSymbol)* type;
/**
* Symbol location
*/
size_t location;
/**
* The kind of symbol
@ -116,250 +152,226 @@ public:
CompletionKind kind;
/**
* The return type if this is a function, or the element type if this is an
* array or associative array, or the variable type if this is a variable.
* This field is null if this symbol is a class
* Symbol qualifier
*/
Type type;
/**
* The symbol that represents the type. _resolvedType is an autocomplete
* class, type is an AST class, so after a module is parsed the symbols
* need to be post-processed to tie variable declarations to the symbols
* that actually contain the correct autocomplete information.
*/
ACSymbol resolvedType;
/**
* Calltip to display if this is a function
*/
string calltip;
/**
* Finds symbol parts by name
*/
ACSymbol[] getPartsByName(string name)
{
return parts.filter!(a => a.name == name).array;
}
SymbolQualifier qualifier;
}
struct Scope
{
Scope* getScopeByCursor(size_t cursorPosition) const
{
if (cursorPosition < startLocation) return null;
if (cursorPosition > endLocation) return null;
foreach (child; children)
{
auto childScope = child.getScopeByCursor(cursorPosition);
if (childScope !is null)
return childScope;
}
return cast(typeof(return)) &this;
}
const(ACSymbol)*[] getSymbolsInCursorScope(size_t cursorPosition) const
{
auto s = getScopeByCursor(cursorPosition);
if (s is null)
return [];
const(ACSymbol)*[] symbols = cast(typeof(return)) s.symbols;
Scope* sc = s.parent;
while (sc !is null)
{
symbols ~= sc.symbols;
sc = sc.parent;
}
return symbols;
}
const(ACSymbol)*[] getSymbolsByName(string name) const
{
const(ACSymbol)*[] retVal = cast(typeof(return)) symbols.filter!(a => a.name == name).array();
if (retVal.length > 0)
return retVal;
if (parent is null)
return [];
return parent.getSymbolsByName(name);
}
const(ACSymbol)*[] getSymbolsByNameAndCursor(string name, size_t cursorPosition) const
{
auto s = getScopeByCursor(cursorPosition);
if (s is null)
return [];
return s.getSymbolsByName(name);
}
const(ACSymbol)*[] symbols;
ImportInformation[] importInformation;
Scope* parent;
Scope*[] children;
size_t startLocation;
size_t endLocation;
}
struct ImportInformation
{
/// module relative path
string modulePath;
/// symbols to import from this module
Tuple!(string, string)[] importedSymbols;
/// true if the import is public
bool isPublic;
}
/**
* Scope such as a block statement, struct body, etc.
* Initializes builtin types and the various properties of builtin types
*/
class Scope
static this()
{
public:
auto bool_ = new ACSymbol("bool", CompletionKind.keyword);
auto int_ = new ACSymbol("int", CompletionKind.keyword);
auto long_ = new ACSymbol("long", CompletionKind.keyword);
auto byte_ = new ACSymbol("byte", CompletionKind.keyword);
auto char_ = new ACSymbol("char", CompletionKind.keyword);
auto dchar_ = new ACSymbol("dchar", CompletionKind.keyword);
auto short_ = new ACSymbol("short", CompletionKind.keyword);
auto ubyte_ = new ACSymbol("ubyte", CompletionKind.keyword);
auto uint_ = new ACSymbol("uint", CompletionKind.keyword);
auto ulong_ = new ACSymbol("ulong", CompletionKind.keyword);
auto ushort_ = new ACSymbol("ushort", CompletionKind.keyword);
auto wchar_ = new ACSymbol("wchar", CompletionKind.keyword);
/**
* Params:
* start = the index of the opening brace
* end = the index of the closing brace
*/
this(size_t start, size_t end)
auto alignof_ = new ACSymbol("alignof", CompletionKind.keyword, ulong_);
auto mangleof_ = new ACSymbol("mangleof", CompletionKind.keyword);
auto sizeof_ = new ACSymbol("sizeof", CompletionKind.keyword, ulong_);
auto stringof_ = new ACSymbol("init", CompletionKind.keyword);
auto init = new ACSymbol("stringof", CompletionKind.keyword);
arraySymbols ~= alignof_;
arraySymbols ~= new ACSymbol("dup", CompletionKind.keyword);
arraySymbols ~= new ACSymbol("idup", CompletionKind.keyword);
arraySymbols ~= init;
arraySymbols ~= new ACSymbol("length", CompletionKind.keyword, ulong_);
arraySymbols ~= mangleof_;
arraySymbols ~= new ACSymbol("ptr", CompletionKind.keyword);
arraySymbols ~= new ACSymbol("reverse", CompletionKind.keyword);
arraySymbols ~= sizeof_;
arraySymbols ~= new ACSymbol("sort", CompletionKind.keyword);
arraySymbols ~= stringof_;
arraySymbols.sort();
assocArraySymbols ~= alignof_;
assocArraySymbols ~= new ACSymbol("byKey", CompletionKind.keyword);
assocArraySymbols ~= new ACSymbol("byValue", CompletionKind.keyword);
assocArraySymbols ~= new ACSymbol("dup", CompletionKind.keyword);
assocArraySymbols ~= new ACSymbol("get", CompletionKind.keyword);
assocArraySymbols ~= new ACSymbol("init", CompletionKind.keyword);
assocArraySymbols ~= new ACSymbol("keys", CompletionKind.keyword);
assocArraySymbols ~= new ACSymbol("length", CompletionKind.keyword, ulong_);
assocArraySymbols ~= mangleof_;
assocArraySymbols ~= new ACSymbol("rehash", CompletionKind.keyword);
assocArraySymbols ~= sizeof_;
assocArraySymbols ~= stringof_;
assocArraySymbols ~= init;
assocArraySymbols ~= new ACSymbol("values", CompletionKind.keyword);
assocArraySymbols.sort();
foreach (s; [bool_, int_, long_, byte_, char_, dchar_, short_, ubyte_, uint_,
ulong_, ushort_, wchar_])
{
this.start = start;
this.end = end;
s.parts ~= new ACSymbol("init", CompletionKind.keyword, s);
s.parts ~= new ACSymbol("min", CompletionKind.keyword, s);
s.parts ~= new ACSymbol("max", CompletionKind.keyword, s);
s.parts ~= alignof_;
s.parts ~= sizeof_;
s.parts ~= stringof_;
s.parts ~= mangleof_;
s.parts ~= init;
}
/**
* Gets all symbols in the scope that contains the cursor as well as its
* parent scopes.
*/
ACSymbol[] getSymbolsInCurrentScope(size_t cursorPosition)
auto cdouble_ = new ACSymbol("cdouble", CompletionKind.keyword);
auto cent_ = new ACSymbol("cent", CompletionKind.keyword);
auto cfloat_ = new ACSymbol("cfloat", CompletionKind.keyword);
auto creal_ = new ACSymbol("creal", CompletionKind.keyword);
auto double_ = new ACSymbol("double", CompletionKind.keyword);
auto float_ = new ACSymbol("float", CompletionKind.keyword);
auto idouble_ = new ACSymbol("idouble", CompletionKind.keyword);
auto ifloat_ = new ACSymbol("ifloat", CompletionKind.keyword);
auto ireal_ = new ACSymbol("ireal", CompletionKind.keyword);
auto real_ = new ACSymbol("real", CompletionKind.keyword);
auto ucent_ = new ACSymbol("ucent", CompletionKind.keyword);
foreach (s; [cdouble_, cent_, cfloat_, creal_, double_, float_,
idouble_, ifloat_, ireal_, real_, ucent_])
{
Scope s = findCurrentScope(cursorPosition);
if (s is null)
return [];
else
return s.getSymbols();
s.parts ~= alignof_;
s.parts ~= new ACSymbol("dig", CompletionKind.keyword, s);
s.parts ~= new ACSymbol("epsilon", CompletionKind.keyword, s);
s.parts ~= new ACSymbol("infinity", CompletionKind.keyword, s);
s.parts ~= new ACSymbol("init", CompletionKind.keyword, s);
s.parts ~= mangleof_;
s.parts ~= new ACSymbol("mant_dig", CompletionKind.keyword, int_);
s.parts ~= new ACSymbol("max", CompletionKind.keyword, s);
s.parts ~= new ACSymbol("max_10_exp", CompletionKind.keyword, int_);
s.parts ~= new ACSymbol("max_exp", CompletionKind.keyword, int_);
s.parts ~= new ACSymbol("min", CompletionKind.keyword, s);
s.parts ~= new ACSymbol("min_exp", CompletionKind.keyword, int_);
s.parts ~= new ACSymbol("min_10_exp", CompletionKind.keyword, int_);
s.parts ~= new ACSymbol("min_normal", CompletionKind.keyword, s);
s.parts ~= new ACSymbol("nan", CompletionKind.keyword, s);
s.parts ~= sizeof_;
s.parts ~= stringof_;
}
/**
* Gets all symbols in this scope and its parent scopes.
*/
ACSymbol[] getSymbols()
{
ACSymbol[] rVal;
rVal ~= symbols;
if (parent !is null)
rVal ~= parent.getSymbols();
return rVal;
}
classSymbols ~= new ACSymbol("classInfo", CompletionKind.variableName);
classSymbols ~= new ACSymbol("tupleof", CompletionKind.variableName);
classSymbols ~= new ACSymbol("__vptr", CompletionKind.variableName);
classSymbols ~= new ACSymbol("__monitor", CompletionKind.variableName);
classSymbols ~= mangleof_;
classSymbols ~= alignof_;
classSymbols ~= sizeof_;
classSymbols ~= init;
/**
* Finds the scope containing the cursor position, then searches for a
* symbol with the given name.
*/
ACSymbol[] findSymbolsInCurrentScope(size_t cursorPosition, string name)
{
auto s = findCurrentScope(cursorPosition);
if (s is null)
{
writeln("Could not find scope");
return [];
}
else
return s.findSymbolsInScope(name);
}
ireal_.parts ~= new ACSymbol("im", CompletionKind.keyword, real_);
ifloat_.parts ~= new ACSymbol("im", CompletionKind.keyword, float_);
idouble_.parts ~= new ACSymbol("im", CompletionKind.keyword, double_);
ireal_.parts ~= new ACSymbol("re", CompletionKind.keyword, real_);
ifloat_.parts ~= new ACSymbol("re", CompletionKind.keyword, float_);
idouble_.parts ~= new ACSymbol("re", CompletionKind.keyword, double_);
/**
* Returns: the innermost Scope that contains the given cursor position.
*/
Scope findCurrentScope(size_t cursorPosition)
{
if (start != size_t.max && (cursorPosition < start || cursorPosition > end))
return null;
foreach (sc; children)
{
auto s = sc.findCurrentScope(cursorPosition);
if (s is null)
continue;
else
return s;
}
return this;
}
auto void_ = new ACSymbol("void", CompletionKind.keyword);
/**
* Finds a symbol with the given name in this scope or one of its parent
* scopes.
*/
ACSymbol[] findSymbolsInScope(string name)
{
ACSymbol[] currentMatches = symbols.filter!(a => a.name == name)().array();
if (currentMatches.length == 0 && parent !is null)
return parent.findSymbolsInScope(name);
return currentMatches;
}
builtinSymbols = [bool_, int_, long_, byte_, char_, dchar_, short_, ubyte_, uint_,
ulong_, ushort_, wchar_, cdouble_, cent_, cfloat_, creal_, double_,
float_, idouble_, ifloat_, ireal_, real_, ucent_, void_];
/**
* Fills in the $(D resolvedType) fields of the symbols in this scope and
* all child scopes.
*/
void resolveSymbolTypes()
{
// We only care about resolving types of variables, all other symbols
// don't have any indirection
foreach (ref s; symbols.filter!(a => (a.kind == CompletionKind.variableName
|| a.kind == CompletionKind.functionName || a.kind == CompletionKind.memberVariableName
|| a.kind == CompletionKind.enumMember || a.kind == CompletionKind.aliasName)
&& a.resolvedType is null)())
{
//writeln("Resolving type of symbol ", s.name);
Type type = s.type;
if (type is null)
{
//writeln("Could not find it due to null type");
continue;
}
if (type.type2.builtinType != TokenType.invalid)
{
// This part is easy. Autocomplete properties of built-in types
auto foundSymbols = findSymbolsInScope(getTokenValue(type.type2.builtinType));
s.resolvedType = foundSymbols[0];
}
else if (type.type2.symbol !is null)
{
// Look up a type by its name for cases like class, enum,
// interface, struct, or union members.
// _argptr has type void*
argptrType = new Type;
argptrType.type2 = new Type2;
argptrType.type2.builtinType = TokenType.void_;
TypeSuffix argptrTypeSuffix = new TypeSuffix;
argptrTypeSuffix.star = true;
argptrType.typeSuffixes ~= argptrTypeSuffix;
// TODO: Does not work with qualified names or template instances
Symbol sym = type.type2.symbol;
if (sym.identifierOrTemplateChain.identifiersOrTemplateInstances.length != 1)
{
writeln("Could not resolve type");
continue;
}
ACSymbol[] resolvedType = findSymbolsInCurrentScope(s.location,
sym.identifierOrTemplateChain.identifiersOrTemplateInstances[0].identifier.value);
if (resolvedType.length > 0 && (resolvedType[0].kind == CompletionKind.interfaceName
|| resolvedType[0].kind == CompletionKind.className
|| resolvedType[0].kind == CompletionKind.aliasName
|| resolvedType[0].kind == CompletionKind.unionName
|| resolvedType[0].kind == CompletionKind.structName))
{
// writeln("Type resolved to ", resolvedType[0].name, " which has kind ",
// resolvedType[0].kind, " and call tip ", resolvedType[0].calltip);
s.resolvedType = resolvedType[0];
}
}
foreach (suffix; type.typeSuffixes)
{
//writeln("Handling type suffix");
// Handle type suffixes for declarations, e.g.:
// int[] a;
// SomeClass[string] b;
// double function(double, double) c;
auto sym = s.resolvedType;
s.resolvedType = new ACSymbol;
s.resolvedType.resolvedType = sym;
if (suffix.array)
{
if (suffix.type !is null)
{
// assocative array
s.resolvedType.qualifier = SymbolQualifier.assocArray;
s.resolvedType.parts ~= assocArraySymbols;
}
else
{
// normal array
s.resolvedType.qualifier = SymbolQualifier.array;
s.resolvedType.parts ~= arraySymbols;
}
}
else if (suffix.delegateOrFunction.type != TokenType.invalid)
{
s.resolvedType.qualifier = SymbolQualifier.func;
}
}
}
foreach (c; children)
{
c.resolveSymbolTypes();
}
foreach (ref ACSymbol c; symbols.filter!(a => a.kind == CompletionKind.className
|| a.kind == CompletionKind.interfaceName))
{
foreach (string sc; c.superClasses)
{
//writeln("Adding inherited fields from ", sc);
ACSymbol[] s = findSymbolsInScope(sc);
if (s.length > 0)
{
foreach (part; s[0].parts)
c.parts ~= part;
}
}
}
}
/**
* Index of the opening brace
*/
size_t start = size_t.max;
/**
* Index of the closing brace
*/
size_t end = size_t.max;
/**
* Symbols contained in this scope
*/
ACSymbol[] symbols;
/**
* The parent scope
*/
Scope parent;
/**
* Child scopes
*/
Scope[] children;
// _arguments has type TypeInfo[]
argumentsType = new Type;
argumentsType = new Type;
argumentsType.type2 = new Type2;
argumentsType.type2.symbol = new Symbol;
argumentsType.type2.symbol.identifierOrTemplateChain = new IdentifierOrTemplateChain;
IdentifierOrTemplateInstance i = new IdentifierOrTemplateInstance;
i.identifier.value = "TypeInfo";
i.identifier.type = TokenType.identifier;
argumentsType.type2.symbol.identifierOrTemplateChain.identifiersOrTemplateInstances ~= i;
TypeSuffix argumentsTypeSuffix = new TypeSuffix;
argumentsTypeSuffix.array = true;
argumentsType.typeSuffixes ~= argptrTypeSuffix;
}
const(ACSymbol)*[] builtinSymbols;
const(ACSymbol)*[] arraySymbols;
const(ACSymbol)*[] assocArraySymbols;
const(ACSymbol)*[] classSymbols;
Type argptrType;
Type argumentsType;

View File

@ -1,573 +0,0 @@
/**
* This file is part of DCD, a development tool for the D programming language.
* Copyright (C) 2013 Brian Schott
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
module acvisitor;
import std.file;
import stdx.d.parser;
import stdx.d.ast;
import stdx.d.lexer;
import std.stdio;
import std.algorithm;
import std.path;
import std.range;
import std.conv;
import actypes;
import messages;
import modulecache;
import autocomplete;
/**
* Converts an AST into a simple symbol and scope heirarchy so that the
* autocompletion coed can do its job more easily.
*/
class AutocompleteVisitor : ASTVisitor
{
alias ASTVisitor.visit visit;
override void visit(Unittest dec)
{
// writeln("Unitttest visit");
auto symbol = new ACSymbol("*unittest*");
auto p = parentSymbol;
parentSymbol = symbol;
dec.accept(this);
parentSymbol = p;
}
override void visit(StructDeclaration dec)
{
// writeln("StructDeclaration visit");
auto symbol = new ACSymbol;
symbol.name = dec.name.value;
symbol.location = dec.name.startIndex;
symbol.kind = CompletionKind.structName;
mixin (visitAndAdd);
}
override void visit(ClassDeclaration dec)
{
// writeln("ClassDeclaration visit");
auto symbol = new ACSymbol;
symbol.name = dec.name.value;
symbol.location = dec.name.startIndex;
symbol.kind = CompletionKind.className;
mixin (visitAndAdd);
}
override void visit(ForStatement forStatement)
{
if (forStatement.declarationOrStatement is null) goto visitBody;
if (forStatement.declarationOrStatement.declaration is null) goto visitBody;
if (forStatement.declarationOrStatement.declaration.variableDeclaration is null) goto visitBody;
if (forStatement.statementNoCaseNoDefault is null) goto visitBody;
if (forStatement.statementNoCaseNoDefault.blockStatement is null) goto visitBody;
// writeln("Visiting for statement");
ACSymbol[] symbols;
VariableDeclaration varDec = forStatement.declarationOrStatement.declaration.variableDeclaration;
Type t = varDec.type;
foreach (Declarator declarator; varDec.declarators)
{
ACSymbol symbol = new ACSymbol();
symbol.name = declarator.name.value;
symbol.type = t;
symbol.kind = CompletionKind.variableName;
symbols ~= symbol;
//writeln("For statement variable ", symbol.name, " of type ", symbol.type, " added.");
}
BlockStatement block = forStatement.statementNoCaseNoDefault.blockStatement;
auto s = new Scope(forStatement.startIndex,
block.endLocation);
s.parent = scope_;
scope_.children ~= s;
auto p = scope_;
scope_ = s;
foreach (symbol; symbols)
{
//writeln("added ", symbol.name, " to scope");
symbol.location = scope_.start;
scope_.symbols ~= symbol;
}
if (block.declarationsAndStatements !is null)
{
//writeln("visiting body");
visit(block.declarationsAndStatements);
}
scope_ = p;
return;
visitBody:
// writeln("visiting body");
if (forStatement.statementNoCaseNoDefault !is null)
visit(forStatement.statementNoCaseNoDefault);
}
override void visit(ForeachStatement statement)
{
ACSymbol[] symbols;
if (statement.foreachTypeList is null)
{
statement.declarationOrStatement.accept(this);
}
else if (statement.foreachType !is null)
{
ACSymbol loopVariable = new ACSymbol(statement.foreachType.identifier.value);
loopVariable.type = statement.foreachType.type;
loopVariable.kind = CompletionKind.variableName;
symbols ~= loopVariable;
}
else foreach (ForeachType feType; statement.foreachTypeList.items.filter!(a => a.type !is null))
{
ACSymbol loopVariable = new ACSymbol(feType.identifier.value);
loopVariable.type = feType.type;
loopVariable.kind = CompletionKind.variableName;
symbols ~= loopVariable;
}
// This is much prettier on the 0.2.0-dev branch
if (statement.declarationOrStatement !is null
&& statement.declarationOrStatement.statement !is null
&& statement.declarationOrStatement.statement.statementNoCaseNoDefault !is null
&& statement.declarationOrStatement.statement.statementNoCaseNoDefault.blockStatement !is null)
{
BlockStatement block = statement.declarationOrStatement.statement.statementNoCaseNoDefault.blockStatement;
auto s = scope_;
scope_ = new Scope(statement.startIndex,
block.endLocation);
scope_.parent = s;
foreach (symbol; symbols)
{
symbol.location = block.startLocation;
scope_.symbols ~= symbol;
}
if (block.declarationsAndStatements !is null)
block.declarationsAndStatements.accept(this);
s.children ~= scope_;
scope_ = s;
}
}
override void visit(InterfaceDeclaration dec)
{
// writeln("InterfaceDeclaration visit");
auto symbol = new ACSymbol;
symbol.name = dec.name.value;
symbol.location = dec.name.startIndex;
symbol.kind = CompletionKind.interfaceName;
mixin (visitAndAdd);
}
override void visit(StructBody structBody)
{
// writeln("StructBody visit");
auto s = scope_;
scope_ = new Scope(structBody.startLocation, structBody.endLocation);
scope_.symbols ~= new ACSymbol("this", CompletionKind.variableName,
parentSymbol);
scope_.parent = s;
s.children ~= scope_;
structBody.accept(this);
scope_ = s;
}
override void visit(EnumDeclaration dec)
{
// TODO: Set enum type based on initializer of first member
// writeln("EnumDeclaration visit");
auto symbol = new ACSymbol;
symbol.name = dec.name.value;
symbol.location = dec.name.startIndex;
symbol.kind = CompletionKind.enumName;
auto type = dec.type;
auto p = parentSymbol;
parentSymbol = symbol;
if (dec.enumBody !is null)
{
Scope enumBodyScope = new Scope(dec.enumBody.startLocation,
dec.enumBody.endLocation);
foreach (member; dec.enumBody.enumMembers)
{
auto s = new ACSymbol;
s.kind = CompletionKind.enumMember;
s.name = member.name.value;
s.location = member.name.startIndex;
if (type is null)
s.resolvedType = scope_.findSymbolsInScope("int")[0];
else
s.type = type;
if (parentSymbol !is null)
parentSymbol.parts ~= s;
enumBodyScope.symbols ~= s;
}
enumBodyScope.parent = scope_;
scope_.children ~= enumBodyScope;
}
parentSymbol = p;
if (parentSymbol is null)
symbols ~= symbol;
else
parentSymbol.parts ~= symbol;
scope_.symbols ~= symbol;
}
override void visit(Constructor dec)
{
ACSymbol symbol = new ACSymbol("*constructor*");
symbol.location = dec.location;
symbol.kind = CompletionKind.functionName;
//symbol.type = dec.returnType;
ACSymbol[] parameterSymbols;
if (dec.parameters !is null)
{
foreach (parameter; dec.parameters.parameters)
{
// writeln("Adding parameter ", parameter.name.value);
ACSymbol paramSymbol = new ACSymbol;
paramSymbol.name = parameter.name.value;
paramSymbol.type = parameter.type;
paramSymbol.kind = CompletionKind.variableName;
paramSymbol.location = parameter.name.startIndex;
parameterSymbols ~= paramSymbol;
}
}
if (dec.parameters !is null && parentSymbol !is null)
{
symbol.calltip = format("%s this%s", parentSymbol.name,
formatNode(dec.parameters));
}
auto p = parentSymbol;
parentSymbol = symbol;
BlockStatement functionBody = dec.functionBody is null ? null
: (dec.functionBody.bodyStatement !is null
? dec.functionBody.bodyStatement.blockStatement : dec.functionBody.blockStatement);
if (functionBody !is null)
{
auto s = scope_;
scope_ = new Scope(functionBody.startLocation,
functionBody.endLocation);
scope_.parent = s;
foreach (parameterSymbol; parameterSymbols)
{
parameterSymbol.location = functionBody.startLocation;
scope_.symbols ~= parameterSymbol;
}
if (functionBody.declarationsAndStatements !is null)
functionBody.declarationsAndStatements.accept(this);
s.children ~= scope_;
scope_ = s;
}
parentSymbol = p;
if (parentSymbol is null)
symbols ~= symbol;
else
parentSymbol.parts ~= symbol;
scope_.symbols ~= symbol;
}
override void visit(FunctionDeclaration dec)
{
ACSymbol symbol = new ACSymbol;
symbol.name = dec.name.value;
symbol.location = dec.name.startIndex;
symbol.kind = CompletionKind.functionName;
symbol.type = dec.returnType;
ACSymbol[] parameterSymbols;
if (dec.parameters !is null)
{
foreach (parameter; dec.parameters.parameters)
{
// writeln("Adding parameter ", parameter.name.value);
ACSymbol paramSymbol = new ACSymbol;
paramSymbol.name = parameter.name.value;
paramSymbol.type = parameter.type;
paramSymbol.kind = CompletionKind.variableName;
paramSymbol.location = parameter.name.startIndex;
parameterSymbols ~= paramSymbol;
}
}
if (dec.parameters !is null)
{
string returnType;
if (dec.returnType !is null)
returnType = formatNode(dec.returnType);
else
{
if (dec.hasAuto)
{
returnType = "auto";
if (dec.hasRef)
returnType = "auto ref";
}
else if (dec.hasRef)
returnType = "ref";
}
symbol.calltip = formatCalltip(dec.returnType, dec.name.value, dec.parameters);
}
auto p = parentSymbol;
parentSymbol = symbol;
BlockStatement functionBody = dec.functionBody is null ? null
: (dec.functionBody.bodyStatement !is null
? dec.functionBody.bodyStatement.blockStatement : dec.functionBody.blockStatement);
if (functionBody !is null)
{
auto s = scope_;
scope_ = new Scope(functionBody.startLocation,
functionBody.endLocation);
scope_.parent = s;
foreach (parameterSymbol; parameterSymbols)
{
parameterSymbol.location = functionBody.startLocation;
scope_.symbols ~= parameterSymbol;
}
if (functionBody.declarationsAndStatements !is null)
functionBody.declarationsAndStatements.accept(this);
s.children ~= scope_;
scope_ = s;
}
parentSymbol = p;
if (parentSymbol is null)
symbols ~= symbol;
else
parentSymbol.parts ~= symbol;
scope_.symbols ~= symbol;
}
override void visit(VariableDeclaration dec)
{
// writeln("VariableDeclaration visit");
foreach (d; dec.declarators)
{
ACSymbol symbol = new ACSymbol;
if (dec.type.typeSuffixes.length > 0
&& dec.type.typeSuffixes[$-1].delegateOrFunction != TokenType.invalid)
{
TypeSuffix suffix = dec.type.typeSuffixes[$ - 1];
dec.type.typeSuffixes = dec.type.typeSuffixes[0 .. $ - 1];
symbol.calltip = formatCalltip(dec.type,
suffix.delegateOrFunction.value, suffix.parameters);
}
symbol.kind = CompletionKind.variableName;
symbol.type = dec.type;
symbol.name = d.name.value;
symbol.location = d.name.startIndex;
if (parentSymbol is null)
symbols ~= symbol;
else
parentSymbol.parts ~= symbol;
scope_.symbols ~= symbol;
}
}
override void visit(AliasDeclaration dec)
{
if (dec.type is null) foreach (aliasPart; dec.initializers)
{
ACSymbol aliasSymbol = new ACSymbol;
aliasSymbol.kind = CompletionKind.aliasName;
aliasSymbol.location = aliasPart.name.startIndex;
aliasSymbol.type = aliasPart.type;
if (aliasPart.type.typeSuffixes.length > 0
&& aliasPart.type.typeSuffixes[$-1].delegateOrFunction != TokenType.invalid)
{
TypeSuffix suffix = aliasPart.type.typeSuffixes[$ - 1];
aliasPart.type.typeSuffixes = aliasPart.type.typeSuffixes[0 .. $ - 1];
aliasSymbol.calltip = formatCalltip(dec.type, suffix.delegateOrFunction.value, suffix.parameters);
}
if (parentSymbol is null)
symbols ~= aliasSymbol;
else
parentSymbol.parts ~= aliasSymbol;
scope_.symbols ~= aliasSymbol;
}
else
{
// writeln("Visiting alias declaration ", dec.name.value);
ACSymbol aliasSymbol = new ACSymbol;
aliasSymbol.kind = CompletionKind.aliasName;
aliasSymbol.name = dec.name.value;
aliasSymbol.type = dec.type;
if (dec.type.typeSuffixes.length > 0
&& dec.type.typeSuffixes[$-1].delegateOrFunction != TokenType.invalid)
{
TypeSuffix suffix = dec.type.typeSuffixes[$ - 1];
dec.type.typeSuffixes = dec.type.typeSuffixes[0 .. $ - 1];
aliasSymbol.calltip = "%s %s%s".format(formatNode(dec.type),
suffix.delegateOrFunction.value,
formatNode(suffix.parameters));
}
aliasSymbol.location = dec.name.startIndex;
if (parentSymbol is null)
symbols ~= aliasSymbol;
else
parentSymbol.parts ~= aliasSymbol;
scope_.symbols ~= aliasSymbol;
}
}
override void visit(ImportDeclaration dec)
{
// TODO: handle public imports
if (!currentFile) return;
foreach (singleImport; dec.singleImports.filter!(a => a !is null
&& a.identifierChain !is null))
{
scope_.symbols ~= ModuleCache.getSymbolsInModule(
convertChainToImportPath(singleImport.identifierChain));
}
if (dec.importBindings !is null
&& dec.importBindings.singleImport.identifierChain !is null)
{
ACSymbol[] importedSymbols = ModuleCache.getSymbolsInModule(
convertChainToImportPath(dec.importBindings.singleImport.identifierChain));
foreach (ImportBind b; dec.importBindings.importBinds)
{
if (b.right == TokenType.invalid)
{
// Selecive import
foreach (ACSymbol symbol; importedSymbols.filter!(a => a.name == b.left))
scope_.symbols ~= symbol;
}
else
{
// renamed selective import
foreach (ACSymbol symbol; importedSymbols.filter!(a => a.name == b.right))
{
ACSymbol s = new ACSymbol;
s.kind = symbol.kind;
s.location = symbol.location;
s.name = b.left.value;
s.parts = symbol.parts;
s.qualifier = symbol.qualifier;
s.resolvedType = symbol.resolvedType;
s.superClasses = symbol.superClasses;
s.type = symbol.type;
scope_.symbols ~= s;
}
}
}
}
}
override void visit(BaseClassList classList)
{
if (parentSymbol is null)
return;
foreach (BaseClass bc; classList.items)
{
if (bc.identifierOrTemplateChain is null)
continue;
if (bc.identifierOrTemplateChain.identifiersOrTemplateInstances.length != 1)
continue;
IdentifierOrTemplateInstance i = bc.identifierOrTemplateChain.identifiersOrTemplateInstances[0];
if (i is null || i.identifier == TokenType.invalid)
continue;
parentSymbol.superClasses ~= i.identifier.value;
}
}
override void visit(BlockStatement blockStatement)
{
auto s = scope_;
scope_ = new Scope(blockStatement.startLocation,
blockStatement.endLocation);
scope_.parent = s;
blockStatement.accept(this);
s.children ~= scope_;
scope_ = s;
}
override void visit(Module mod)
{
scope_ = new Scope(0, size_t.max);
scope_.symbols ~= builtinSymbols;
mod.accept(this);
}
private static string convertChainToImportPath(IdentifierChain chain)
{
return to!string(chain.identifiers.map!(a => a.value).join(dirSeparator).array) ~ ".d";
}
ACSymbol[] symbols;
ACSymbol parentSymbol;
Scope scope_;
string[] imports = ["object"];
bool currentFile = false;
private:
static string formatCalltip(Type returnType, string name, Parameters parameters,
string doc = null)
{
return "%s %s%s".format(formatNode(returnType), name, formatNode(parameters));
}
static string formatNode(T)(T node)
{
if (node is null) return "";
import formatter;
auto app = appender!(char[])();
auto f = new Formatter!(typeof(app))(app);
f.format(node);
return to!string(app.data);
}
static enum string visitAndAdd = q{
auto p = parentSymbol;
parentSymbol = symbol;
dec.accept(this);
parentSymbol = p;
if (parentSymbol is null)
symbols ~= symbol;
else
parentSymbol.parts ~= symbol;
scope_.symbols ~= symbol;
};
}
void doesNothing(string, int, int, string) {}
AutocompleteVisitor processModule(const(Token)[] tokens)
{
Module mod = parseModule(tokens, "", &doesNothing);
auto visitor = new AutocompleteVisitor;
visitor.currentFile = true;
visitor.visit(mod);
return visitor;
}

893
astconverter.d Normal file
View File

@ -0,0 +1,893 @@
/*******************************************************************************
* Authors: Brian Schott
* Copyright: Brian Schott
* Date: Sep 21 2013
*
* License:
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
module astconverter;
import std.algorithm;
import std.array;
import std.conv;
import std.path;
import std.range;
import std.typecons;
import stdx.d.ast;
import stdx.d.lexer;
import stdx.d.parser;
import actypes;
import constants;
import messages;
import semantic;
import stupidlog;
import modulecache; // circular import
/**
* First Pass handles the following:
* $(UL
* $(LI symbol name)
* $(LI symbol location)
* $(LI alias this locations)
* $(LI base class names)
* $(LI protection level)
* $(LI symbol kind)
* $(LI function call tip)
* $(LI symbol file path)
* )
*/
final class FirstPass : ASTVisitor
{
this(Module mod, string symbolFile)
{
this.symbolFile = symbolFile;
this.mod = mod;
}
void run()
{
visit(mod);
mod = null;
}
override void visit(Unittest u)
{
// Create a dummy symbol because we don't want unit test symbols leaking
// into the symbol they're declared in.
SemanticSymbol* s = new SemanticSymbol("*unittest*",
CompletionKind.dummy, null, 0);
s.parent = currentSymbol;
currentSymbol = s;
u.accept(this);
currentSymbol = s.parent;
}
override void visit(Constructor con)
{
// Log.trace(__FUNCTION__, " ", typeof(con).stringof);
visitConstructor(con.location, con.parameters, con.functionBody);
}
override void visit(SharedStaticConstructor con)
{
// Log.trace(__FUNCTION__, " ", typeof(con).stringof);
visitConstructor(con.location, null, con.functionBody);
}
override void visit(StaticConstructor con)
{
// Log.trace(__FUNCTION__, " ", typeof(con).stringof);
visitConstructor(con.location, null, con.functionBody);
}
override void visit(Destructor des)
{
// Log.trace(__FUNCTION__, " ", typeof(des).stringof);
visitDestructor(des.location, des.functionBody);
}
override void visit(SharedStaticDestructor des)
{
// Log.trace(__FUNCTION__, " ", typeof(des).stringof);
visitDestructor(des.location, des.functionBody);
}
override void visit(StaticDestructor des)
{
// Log.trace(__FUNCTION__, " ", typeof(des).stringof);
visitDestructor(des.location, des.functionBody);
}
override void visit(FunctionDeclaration dec)
{
// Log.trace(__FUNCTION__, " ", typeof(dec).stringof);
SemanticSymbol* symbol = new SemanticSymbol(dec.name.value.dup,
CompletionKind.functionName, symbolFile, dec.name.startIndex);
processParameters(symbol, dec.returnType, symbol.acSymbol.name,
dec.parameters);
symbol.protection = protection;
symbol.parent = currentSymbol;
currentSymbol.addChild(symbol);
if (dec.functionBody !is null)
{
currentSymbol = symbol;
dec.functionBody.accept(this);
currentSymbol = symbol.parent;
}
}
override void visit(ClassDeclaration dec)
{
// Log.trace(__FUNCTION__, " ", typeof(dec).stringof);
visitAggregateDeclaration(dec, CompletionKind.className);
}
override void visit(InterfaceDeclaration dec)
{
// Log.trace(__FUNCTION__, " ", typeof(dec).stringof);
visitAggregateDeclaration(dec, CompletionKind.interfaceName);
}
override void visit(UnionDeclaration dec)
{
// Log.trace(__FUNCTION__, " ", typeof(dec).stringof);
visitAggregateDeclaration(dec, CompletionKind.unionName);
}
override void visit(StructDeclaration dec)
{
// Log.trace(__FUNCTION__, " ", typeof(dec).stringof);
visitAggregateDeclaration(dec, CompletionKind.structName);
}
override void visit(BaseClass bc)
{
// Log.trace(__FUNCTION__, " ", typeof(bc).stringof);
currentSymbol.baseClasses ~= iotcToStringArray(bc.identifierOrTemplateChain);
}
override void visit(VariableDeclaration dec)
{
assert (currentSymbol);
// Log.trace(__FUNCTION__, " ", typeof(dec).stringof);
Type t = dec.type;
foreach (declarator; dec.declarators)
{
SemanticSymbol* symbol = new SemanticSymbol(
declarator.name.value.dup,
CompletionKind.variableName,
symbolFile,
declarator.name.startIndex);
symbol.type = t;
symbol.protection = protection;
symbol.parent = currentSymbol;
currentSymbol.addChild(symbol);
}
}
override void visit(AliasDeclaration aliasDeclaration)
{
if (aliasDeclaration.initializers.length == 0)
{
SemanticSymbol* symbol = new SemanticSymbol(
aliasDeclaration.name.value.dup,
CompletionKind.aliasName,
symbolFile,
aliasDeclaration.name.startIndex);
symbol.type = aliasDeclaration.type;
symbol.protection = protection;
symbol.parent = currentSymbol;
currentSymbol.addChild(symbol);
}
else
{
foreach (initializer; aliasDeclaration.initializers)
{
SemanticSymbol* symbol = new SemanticSymbol(
initializer.name.value.dup,
CompletionKind.aliasName,
symbolFile,
initializer.name.startIndex);
symbol.type = initializer.type;
symbol.protection = protection;
symbol.parent = currentSymbol;
currentSymbol.addChild(symbol);
}
}
}
override void visit(AliasThisDeclaration dec)
{
// Log.trace(__FUNCTION__, " ", typeof(dec).stringof);
currentSymbol.aliasThis ~= dec.identifier.value.dup;
}
override void visit(Declaration dec)
{
// Log.trace(__FUNCTION__, " ", typeof(dec).stringof);
if (dec.attributeDeclaration !is null
&& isProtection(dec.attributeDeclaration.attribute.attribute))
{
protection = dec.attributeDeclaration.attribute.attribute;
return;
}
TokenType p = protection;
foreach (Attribute attr; dec.attributes)
{
if (isProtection(attr.attribute))
protection = attr.attribute;
}
dec.accept(this);
protection = p;
}
override void visit(Module mod)
{
// Log.trace(__FUNCTION__, " ", typeof(mod).stringof);
//
currentSymbol = new SemanticSymbol(null, CompletionKind.moduleName,
symbolFile);
rootSymbol = currentSymbol;
currentScope = new Scope();
currentScope.startLocation = 0;
currentScope.endLocation = size_t.max;
moduleScope = currentScope;
mod.accept(this);
}
override void visit(EnumDeclaration dec)
{
assert (currentSymbol);
// Log.trace(__FUNCTION__, " ", typeof(dec).stringof);
SemanticSymbol* symbol = new SemanticSymbol(dec.name.value.dup,
CompletionKind.enumName, symbolFile, dec.name.startIndex);
symbol.type = dec.type;
symbol.parent = currentSymbol;
currentSymbol = symbol;
if (dec.enumBody !is null)
dec.enumBody.accept(this);
currentSymbol = symbol.parent;
currentSymbol.addChild(symbol);
}
override void visit(EnumMember member)
{
// Log.trace(__FUNCTION__, " ", typeof(member).stringof);
SemanticSymbol* symbol = new SemanticSymbol(member.name.value.dup,
CompletionKind.enumMember, symbolFile, member.name.startIndex);
symbol.type = member.type;
symbol.parent = currentSymbol;
currentSymbol.addChild(symbol);
}
override void visit(ModuleDeclaration dec)
{
// Log.trace(__FUNCTION__, " ", typeof(dec).stringof);
foreach (Token t; dec.moduleName.identifiers)
moduleName ~= t.value.dup;
}
// creates scopes for
override void visit(StructBody structBody)
{
// Log.trace(__FUNCTION__, " ", typeof(structBody).stringof);
Scope* s = new Scope;
s.startLocation = structBody.startLocation;
s.endLocation = structBody.endLocation;
// Log.trace("Added scope ", s.startLocation, " ", s.endLocation);
ACSymbol* thisSymbol = new ACSymbol("this", CompletionKind.variableName,
currentSymbol.acSymbol);
thisSymbol.location = s.startLocation;
thisSymbol.symbolFile = symbolFile;
currentSymbol.acSymbol.parts ~= thisSymbol;
s.parent = currentScope;
currentScope = s;
foreach (dec; structBody.declarations)
visit(dec);
currentScope = s.parent;
currentScope.children ~= s;
}
override void visit(ImportDeclaration importDeclaration)
{
// Log.trace(__FUNCTION__, " ImportDeclaration");
foreach (single; importDeclaration.singleImports.filter!(
a => a !is null && a.identifierChain !is null))
{
ImportInformation info;
info.modulePath = convertChainToImportPath(single.identifierChain);
info.isPublic = protection == TokenType.public_;
currentScope.importInformation ~= info;
}
if (importDeclaration.importBindings is null) return;
if (importDeclaration.importBindings.singleImport.identifierChain is null) return;
ImportInformation info;
info.modulePath = convertChainToImportPath(
importDeclaration.importBindings.singleImport.identifierChain);
foreach (bind; importDeclaration.importBindings.importBinds)
{
Tuple!(string, string) bindTuple;
bindTuple[0] = bind.left.value.dup;
bindTuple[1] = bind.right == TokenType.invalid ? null : bind.right.value.dup;
info.importedSymbols ~= bindTuple;
}
info.isPublic = protection == TokenType.public_;
currentScope.importInformation ~= info;
}
// Create scope for block statements
override void visit(BlockStatement blockStatement)
{
// Log.trace(__FUNCTION__, " ", typeof(blockStatement).stringof);
Scope* s = new Scope;
s.parent = currentScope;
currentScope.children ~= s;
s.startLocation = blockStatement.startLocation;
s.endLocation = blockStatement.endLocation;
if (currentSymbol.acSymbol.kind == CompletionKind.functionName)
{
foreach (child; currentSymbol.children)
{
child.acSymbol.location = s.startLocation + 1;
}
}
if (blockStatement.declarationsAndStatements !is null)
{
currentScope = s;
visit (blockStatement.declarationsAndStatements);
currentScope = s.parent;
}
}
override void visit(VersionCondition versionCondition)
{
// TODO: This is a bit of a hack
if (predefinedVersions.canFind(versionCondition.token.value))
versionCondition.accept(this);
}
alias ASTVisitor.visit visit;
private:
void visitAggregateDeclaration(AggType)(AggType dec, CompletionKind kind)
{
// Log.trace("visiting aggregate declaration ", dec.name.value);
SemanticSymbol* symbol = new SemanticSymbol(dec.name.value.dup,
kind, symbolFile, dec.name.startIndex);
symbol.acSymbol.parts ~= classSymbols;
symbol.parent = currentSymbol;
symbol.protection = protection;
currentSymbol = symbol;
dec.accept(this);
currentSymbol = symbol.parent;
currentSymbol.addChild(symbol);
}
void visitConstructor(size_t location, Parameters parameters,
FunctionBody functionBody)
{
SemanticSymbol* symbol = new SemanticSymbol("*constructor*",
CompletionKind.functionName, symbolFile, location);
processParameters(symbol, null, "this", parameters);
symbol.protection = protection;
symbol.parent = currentSymbol;
currentSymbol.addChild(symbol);
if (functionBody !is null)
{
currentSymbol = symbol;
functionBody.accept(this);
currentSymbol = symbol.parent;
}
}
void visitDestructor(size_t location, FunctionBody functionBody)
{
SemanticSymbol* symbol = new SemanticSymbol("~this",
CompletionKind.functionName, symbolFile, location);
symbol.acSymbol.callTip = "~this()";
symbol.protection = protection;
symbol.parent = currentSymbol;
currentSymbol.addChild(symbol);
if (functionBody !is null)
{
currentSymbol = symbol;
functionBody.accept(this);
currentSymbol = symbol.parent;
}
}
void processParameters(SemanticSymbol* symbol, Type returnType,
string functionName, Parameters parameters) const
{
if (parameters !is null)
{
foreach (Parameter p; parameters.parameters)
{
SemanticSymbol* parameter = new SemanticSymbol(p.name.value.dup,
CompletionKind.variableName, symbolFile, p.name.startIndex);
parameter.type = p.type;
symbol.addChild(parameter);
parameter.parent = symbol;
}
if (parameters.hasVarargs)
{
SemanticSymbol* argptr = new SemanticSymbol("_argptr",
CompletionKind.variableName, null, 0);
argptr.type = argptrType;
argptr.parent = symbol;
symbol.addChild(argptr);
SemanticSymbol* arguments = new SemanticSymbol("_arguments",
CompletionKind.variableName, null, 0);
arguments.type = argumentsType;
arguments.parent = symbol;
symbol.addChild(arguments);
}
}
symbol.acSymbol.callTip = formatCallTip(returnType, functionName,
parameters);
symbol.type = returnType;
}
static string formatCallTip(Type returnType, string name, Parameters parameters,
string doc = null)
{
string parameterString = parameters is null ? "()"
: formatNode(parameters);
if (returnType is null)
return "%s%s".format(name, parameterString);
return "%s %s%s".format(formatNode(returnType), name, parameterString);
}
/// Current protection type
TokenType protection;
/// Current symbol
SemanticSymbol* currentSymbol;
/// The module
SemanticSymbol* rootSymbol;
/// Package and module name
string[] moduleName;
/// Current scope
Scope* currentScope;
/// Module scope
Scope* moduleScope;
/// Path to the file being converted
string symbolFile;
Module mod;
}
/**
* Second pass handles the following:
* $(UL
* $(LI Import statements)
* $(LI assigning symbols to scopes)
* )
*/
struct SecondPass
{
public:
this(SemanticSymbol* rootSymbol, Scope* moduleScope)
{
this.rootSymbol = rootSymbol;
this.moduleScope = moduleScope;
}
void run()
{
assignToScopes(rootSymbol.acSymbol);
resolveImports(moduleScope);
}
private:
void assignToScopes(const(ACSymbol)* currentSymbol)
{
moduleScope.getScopeByCursor(currentSymbol.location).symbols
~= currentSymbol;
foreach (part; currentSymbol.parts)
assignToScopes(part);
}
void resolveImports(Scope* currentScope)
{
foreach (importInfo; currentScope.importInformation)
{
auto symbols = ModuleCache.getSymbolsInModule(
ModuleCache.resolveImportLoctation(importInfo.modulePath));
if (importInfo.importedSymbols.length == 0)
{
currentScope.symbols ~= symbols;
if (importInfo.isPublic && currentScope.parent is null)
{
rootSymbol.acSymbol.parts ~= symbols;
}
continue;
}
symbolLoop: foreach (symbol; symbols)
{
foreach (tup; importInfo.importedSymbols)
{
if (tup[0] != symbol.name)
continue symbolLoop;
if (tup[1] !is null)
{
ACSymbol* s = new ACSymbol(tup[1],
symbol.kind, symbol.type);
// TODO: Compiler gets confused here, so cast the types.
s.parts = cast(typeof(s.parts)) symbol.parts;
// TODO: Re-format callTip with new name?
s.callTip = symbol.callTip;
s.qualifier = symbol.qualifier;
s.location = symbol.location;
s.symbolFile = symbol.symbolFile;
currentScope.symbols ~= s;
if (importInfo.isPublic && currentScope.parent is null)
rootSymbol.acSymbol.parts ~= s;
}
else
{
currentScope.symbols ~= symbol;
if (importInfo.isPublic && currentScope.parent is null)
rootSymbol.acSymbol.parts ~= symbol;
}
}
}
}
foreach (childScope; currentScope.children)
resolveImports(childScope);
}
SemanticSymbol* rootSymbol;
Scope* moduleScope;
}
/**
* Third pass handles the following:
* $(UL
* $(LI types)
* $(LI base classes)
* $(LI mixin templates)
* $(LI alias this)
* $(LI alias declarations)
* )
*/
struct ThirdPass
{
public:
this(SemanticSymbol* rootSymbol, Scope* moduleScope) pure
{
this.rootSymbol = rootSymbol;
this.moduleScope = moduleScope;
}
void run()
{
thirdPass(rootSymbol);
}
private:
void thirdPass(SemanticSymbol* currentSymbol)
{
// Log.trace("third pass on ", currentSymbol.acSymbol.name);
with (CompletionKind) final switch (currentSymbol.acSymbol.kind)
{
case className:
case interfaceName:
resolveInheritance(currentSymbol);
goto case structName;
case structName:
case unionName:
resolveAliasThis(currentSymbol);
resolveMixinTemplates(currentSymbol);
break;
case variableName:
case memberVariableName:
case functionName:
case aliasName:
const(ACSymbol)* t = resolveType(currentSymbol.type,
currentSymbol.acSymbol.location);
while (t !is null && t.kind == CompletionKind.aliasName)
t = t.type;
currentSymbol.acSymbol.type = t;
break;
case enumName:
case keyword:
case enumMember:
case packageName:
case moduleName:
case dummy:
case array:
case assocArray:
case templateName:
case mixinTemplateName:
break;
}
foreach (child; currentSymbol.children)
thirdPass(child);
}
void resolveInheritance(SemanticSymbol* currentSymbol)
{
// Log.trace("Resolving inheritance for ", currentSymbol.acSymbol.name);
outer: foreach (string[] base; currentSymbol.baseClasses)
{
const(ACSymbol)* baseClass;
if (base.length == 0)
continue;
auto symbols = moduleScope.getSymbolsByNameAndCursor(
base[0], currentSymbol.acSymbol.location);
if (symbols.length == 0)
continue;
baseClass = symbols[0];
foreach (part; base[1..$])
{
symbols = baseClass.getPartsByName(part);
if (symbols.length == 0)
continue outer;
baseClass = symbols[0];
}
currentSymbol.acSymbol.parts ~= baseClass.parts;
}
}
void resolveAliasThis(SemanticSymbol* currentSymbol)
{
// TODO:
}
void resolveMixinTemplates(SemanticSymbol* currentSymbol)
{
// TODO:
}
const(ACSymbol)* resolveType(Type t, size_t location)
{
if (t is null) return null;
if (t.type2 is null) return null;
const(ACSymbol)* s;
if (t.type2.builtinType != TokenType.invalid)
s = convertBuiltinType(t.type2);
else if (t.type2.typeConstructor != TokenType.invalid)
s = resolveType(t.type2.type, location);
else if (t.type2.symbol !is null)
{
// TODO: global scoped symbol handling
string[] symbolParts = expandSymbol(
t.type2.symbol.identifierOrTemplateChain);
auto symbols = moduleScope.getSymbolsByNameAndCursor(
symbolParts[0], location);
if (symbols.length == 0)
goto resolveSuffixes;
s = symbols[0];
foreach (symbolPart; symbolParts[1..$])
{
auto parts = s.getPartsByName(symbolPart);
if (parts.length == 0)
goto resolveSuffixes;
s = parts[0];
}
}
resolveSuffixes:
foreach (suffix; t.typeSuffixes)
s = processSuffix(s, suffix);
return s;
}
static string[] expandSymbol(const IdentifierOrTemplateChain chain) pure
{
string[] strings = new string[chain.identifiersOrTemplateInstances.length];
for (size_t i = 0; i != chain.identifiersOrTemplateInstances.length; ++i)
{
auto identOrTemplate = chain.identifiersOrTemplateInstances[i];
if (identOrTemplate is null)
continue;
strings[i] = identOrTemplate.templateInstance is null ?
identOrTemplate.identifier.value.dup
: identOrTemplate.identifier.value.dup;
}
return strings;
}
static const(ACSymbol)* processSuffix(const(ACSymbol)* symbol, const TypeSuffix suffix)
{
if (suffix.star)
return symbol;
if (suffix.array || suffix.type)
{
ACSymbol* s = new ACSymbol;
s.parts = suffix.array ? arraySymbols : assocArraySymbols;
s.type = symbol;
s.qualifier = suffix.array ? SymbolQualifier.array : SymbolQualifier.assocArray;
return s;
}
if (suffix.parameters)
{
ACSymbol* s = new ACSymbol;
s.type = symbol;
s.qualifier = SymbolQualifier.func;
s.callTip = suffix.delegateOrFunction.value ~ formatNode(suffix.parameters);
return s;
}
return null;
}
static const(ACSymbol)* convertBuiltinType(const Type2 type2)
{
string stringRepresentation = getTokenValue(type2.builtinType);
if (stringRepresentation is null) return null;
// TODO: Make this use binary search instead
foreach (s; builtinSymbols)
if (s.name == stringRepresentation)
return s;
return null;
}
SemanticSymbol* rootSymbol;
Scope* moduleScope;
}
const(ACSymbol)*[] convertAstToSymbols(const(Token)[] tokens, string symbolFile)
{
Module m = parseModuleSimple(tokens, symbolFile);
FirstPass first = new FirstPass(m, symbolFile);
first.run();
SecondPass second = SecondPass(first.rootSymbol, first.moduleScope);
second.run();
ThirdPass third = ThirdPass(second.rootSymbol, second.moduleScope);
third.run();
return cast(typeof(return)) third.rootSymbol.acSymbol.parts;
}
const(Scope)* generateAutocompleteTrees(const(Token)[] tokens, string symbolFile)
{
Module m = parseModule(tokens, "editor buffer", &doesNothing);
FirstPass first = new FirstPass(m, symbolFile);
first.run();
SecondPass second = SecondPass(first.rootSymbol, first.currentScope);
second.run();
ThirdPass third = ThirdPass(second.rootSymbol, second.moduleScope);
third.run();
return cast(typeof(return)) third.moduleScope;
}
private:
Module parseModuleSimple(const(Token)[] tokens, string fileName)
{
auto parser = new SimpleParser();
parser.fileName = fileName;
parser.tokens = tokens;
parser.messageFunction = &doesNothing;
auto mod = parser.parseModule();
return mod;
}
class SimpleParser : Parser
{
override Unittest parseUnittest()
{
expect(TokenType.unittest_);
skipBraces();
return null;
}
override FunctionBody parseFunctionBody()
{
if (currentIs(TokenType.semicolon))
advance();
else if (currentIs(TokenType.lBrace))
skipBraces();
else
{
if (currentIs(TokenType.in_))
{
advance();
skipBraces();
if (currentIs(TokenType.out_))
{
advance();
if (currentIs(TokenType.lParen))
skipParens();
skipBraces();
}
}
else if (currentIs(TokenType.out_))
{
advance();
if (currentIs(TokenType.lParen))
skipParens();
skipBraces();
if (currentIs(TokenType.in_))
{
advance();
skipBraces();
}
}
expect(TokenType.body_);
skipBraces();
}
return null;
}
}
string[] iotcToStringArray(const IdentifierOrTemplateChain iotc)
{
string[] parts;
foreach (ioti; iotc.identifiersOrTemplateInstances)
{
if (ioti.identifier != TokenType.invalid)
parts ~= ioti.identifier.value.dup;
else
parts ~= ioti.templateInstance.identifier.value.dup;
}
return parts;
}
private static string convertChainToImportPath(IdentifierChain chain)
{
return to!string(chain.identifiers.map!(a => a.value).join(dirSeparator).array);
}
version(unittest) Module parseTestCode(string code)
{
LexerConfig config;
const(Token)[] tokens = byToken(cast(ubyte[]) code, config);
Parser p = new Parser;
p.fileName = "unittest";
p.tokens = tokens;
Module m = p.parseModule();
assert (p.errorCount == 0);
assert (p.warningCount == 0);
return m;
}
string formatNode(T)(T node)
{
if (node is null) return "";
import formatter;
auto app = appender!(char[])();
auto f = new Formatter!(typeof(app))(app);
f.format(node);
return to!string(app.data);
}
private void doesNothing(string a, int b, int c, string d) {}

View File

@ -34,19 +34,229 @@ import stdx.d.parser;
import std.string;
import messages;
import acvisitor;
import actypes;
import constants;
import modulecache;
import astconverter;
import stupidlog;
AutocompleteResponse complete(AutocompleteRequest request, string[] importPaths)
AutocompleteResponse findDeclaration(const AutocompleteRequest request)
{
writeln("Got a completion request");
Log.info("Finding declaration");
AutocompleteResponse response;
LexerConfig config;
config.fileName = "stdin";
auto tokens = byToken(cast(ubyte[]) request.sourceCode, config);
const(Token)[] tokenArray = void;
try {
tokenArray = tokens.array();
} catch (Exception e) {
Log.error("Could not provide autocomplete due to lexing exception: ", e.msg);
return response;
}
auto sortedTokens = assumeSorted(tokenArray);
string partial;
auto beforeTokens = sortedTokens.lowerBound(cast(size_t) request.cursorPosition);
Log.info("Token at cursor: ", beforeTokens[$ - 1]);
const(Scope)* completionScope = generateAutocompleteTrees(tokenArray, "stdin");
auto expression = getExpression(beforeTokens);
const(ACSymbol)*[] symbols = getSymbolsByTokenChain(completionScope, expression,
request.cursorPosition, CompletionType.identifiers);
if (symbols.length > 0)
{
response.symbolLocation = symbols[0].location;
response.symbolFilePath = symbols[0].symbolFile;
Log.info(beforeTokens[$ - 1].value, " declared in ",
response.symbolFilePath, " at ", response.symbolLocation);
}
else
{
Log.error("Could not find symbol");
}
return response;
}
const(ACSymbol)*[] getSymbolsByTokenChain(T)(const(Scope)* completionScope,
T tokens, size_t cursorPosition, CompletionType completionType)
{
// Find the symbol corresponding to the beginning of the chain
const(ACSymbol)*[] symbols = completionScope.getSymbolsByNameAndCursor(
tokens[0].value, cursorPosition);
if (symbols.length == 0)
{
Log.trace("Could not find declaration of ", tokens[0].value);
return [];
}
if (completionType == CompletionType.identifiers
&& symbols[0].kind == CompletionKind.memberVariableName
|| symbols[0].kind == CompletionKind.variableName
|| symbols[0].kind == CompletionKind.aliasName
|| symbols[0].kind == CompletionKind.enumMember)
{
symbols = symbols[0].type is null ? [] : [symbols[0].type];
if (symbols.length == 0)
return symbols;
}
loop: for (size_t i = 1; i < tokens.length; i++)
{
TokenType open;
TokenType close;
void skip()
{
i++;
for (int depth = 1; depth > 0 && i < tokens.length; i++)
{
if (tokens[i].type == open)
depth++;
else if (tokens[i].type == close)
{
depth--;
if (depth == 0) break;
}
}
}
with (TokenType) switch (tokens[i].type)
{
case int_:
case uint_:
case long_:
case ulong_:
case char_:
case wchar_:
case dchar_:
case bool_:
case byte_:
case ubyte_:
case short_:
case ushort_:
case cent_:
case ucent_:
case float_:
case ifloat_:
case cfloat_:
case idouble_:
case cdouble_:
case double_:
case real_:
case ireal_:
case creal_:
case this_:
symbols = symbols[0].getPartsByName(getTokenValue(tokens[i].type));
if (symbols.length == 0)
break loop;
break;
case identifier:
// Use function return type instead of the function itself
if (symbols[0].qualifier == SymbolQualifier.func
|| symbols[0].kind == CompletionKind.functionName)
{
symbols = symbols[0].type is null ? [] :[symbols[0].type];
if (symbols.length == 0)
break loop;
}
Log.trace("looking for ", tokens[i].value, " in ", symbols[0].name);
symbols = symbols[0].getPartsByName(tokens[i].value);
if (symbols.length == 0)
{
Log.trace("Couldn't find it.");
break loop;
}
if (symbols[0].kind == CompletionKind.variableName
|| symbols[0].kind == CompletionKind.memberVariableName
|| symbols[0].kind == CompletionKind.enumMember
|| (symbols[0].kind == CompletionKind.functionName
&& (completionType == CompletionType.identifiers
|| i + 1 < tokens.length)))
{
symbols = symbols[0].type is null ? [] : [symbols[0].type];
}
if (symbols.length == 0)
break loop;
if (symbols[0].kind == CompletionKind.aliasName
&& (completionType == CompletionType.identifiers
|| i + 1 < tokens.length))
{
symbols = symbols[0].type is null ? [] : [symbols[0].type];
}
if (symbols.length == 0)
break loop;
break;
case lParen:
open = TokenType.lParen;
close = TokenType.rParen;
skip();
break;
case lBracket:
open = TokenType.lBracket;
close = TokenType.rBracket;
if (symbols[0].qualifier == SymbolQualifier.array)
{
auto h = i;
skip();
Parser p = new Parser();
p.setTokens(tokens[h .. i].array());
if (!p.isSliceExpression())
{
symbols = symbols[0].type is null ? [] : [symbols[0].type];
}
}
else if (symbols[0].qualifier == SymbolQualifier.assocArray)
{
symbols = symbols[0].type is null ? [] :[symbols[0].type];
skip();
}
else
{
auto h = i;
skip();
Parser p = new Parser();
p.setTokens(tokens[h .. i].array());
const(ACSymbol)*[] overloads;
if (p.isSliceExpression())
overloads = symbols[0].getPartsByName("opSlice");
else
overloads = symbols[0].getPartsByName("opIndex");
if (overloads.length > 0)
{
symbols = overloads[0].type is null ? [] : [overloads[0].type];
}
else
return [];
}
break;
case dot:
break;
default:
break loop;
}
}
return symbols;
}
AutocompleteResponse complete(const AutocompleteRequest request)
{
Log.info("Got a completion request");
AutocompleteResponse response;
LexerConfig config;
auto tokens = request.sourceCode.byToken(config);
auto tokenArray = tokens.array();
config.fileName = "stdin";
auto tokens = byToken(cast(ubyte[]) request.sourceCode, config);
const(Token)[] tokenArray = void;
try {
tokenArray = tokens.array();
} catch (Exception e) {
Log.error("Could not provide autocomplete due to lexing exception: ", e.msg);
return response;
}
auto sortedTokens = assumeSorted(tokenArray);
string partial;
@ -56,7 +266,7 @@ AutocompleteResponse complete(AutocompleteRequest request, string[] importPaths)
if (beforeTokens.length >= 1 && beforeTokens[$ - 1] == TokenType.identifier)
{
//writeln("partial completion");
Log.trace("partial completion");
partial = beforeTokens[$ - 1].value;
tokenType = beforeTokens[$ - 1].type;
beforeTokens = beforeTokens[0 .. $ - 1];
@ -92,10 +302,11 @@ AutocompleteResponse complete(AutocompleteRequest request, string[] importPaths)
case TokenType.identifier:
case TokenType.rParen:
case TokenType.rBracket:
auto visitor = processModule(tokenArray);
const(Scope)* completionScope = generateAutocompleteTrees(tokenArray,
"stdin");
auto expression = getExpression(beforeTokens[0 .. $ - 1]);
response.setCompletions(visitor, expression, request.cursorPosition,
CompletionType.calltips);
response.setCompletions(completionScope, expression,
request.cursorPosition, CompletionType.calltips);
break;
default:
break;
@ -144,10 +355,11 @@ dotCompletion:
case TokenType.rParen:
case TokenType.rBracket:
case TokenType.this_:
auto visitor = processModule(tokenArray);
const(Scope)* completionScope = generateAutocompleteTrees(tokenArray,
"stdin");
auto expression = getExpression(beforeTokens);
response.setCompletions(visitor, expression, request.cursorPosition,
CompletionType.identifiers, partial);
response.setCompletions(completionScope, expression,
request.cursorPosition, CompletionType.identifiers, partial);
break;
case TokenType.lParen:
case TokenType.lBrace:
@ -164,7 +376,7 @@ dotCompletion:
}
void setCompletions(T)(ref AutocompleteResponse response,
AutocompleteVisitor visitor, T tokens, size_t cursorPosition,
const(Scope)* completionScope, T tokens, size_t cursorPosition,
CompletionType completionType, string partial = null)
{
// Autocomplete module imports instead of symbols
@ -175,14 +387,11 @@ void setCompletions(T)(ref AutocompleteResponse response,
return;
}
visitor.scope_.resolveSymbolTypes();
// Handle the simple case where we get all symbols in scope and filter it
// based on the currently entered text.
if (partial !is null && tokens.length == 0)
{
// writeln("Showing all symbols in current scope that start with ", partial);
foreach (s; visitor.scope_.getSymbolsInCurrentScope(cursorPosition)
foreach (s; completionScope.getSymbolsInCursorScope(cursorPosition)
.filter!(a => a.name.toUpper().startsWith(partial.toUpper())))
{
response.completionKinds ~= s.kind;
@ -195,166 +404,28 @@ void setCompletions(T)(ref AutocompleteResponse response,
if (tokens.length == 0)
return;
// Find the symbol corresponding to the beginning of the chain
ACSymbol[] symbols = visitor.scope_.findSymbolsInCurrentScope(cursorPosition, tokens[0].value);
if (symbols.length == 0)
{
writeln("Could not find declaration of ", tokens[0].value);
return;
}
if (completionType == CompletionType.identifiers
&& symbols[0].kind == CompletionKind.memberVariableName
|| symbols[0].kind == CompletionKind.variableName
|| symbols[0].kind == CompletionKind.aliasName
|| symbols[0].kind == CompletionKind.enumMember)
{
symbols = symbols[0].resolvedType is null ? [] : [symbols[0].resolvedType];
if (symbols.length == 0)
{
return;
}
}
loop: for (size_t i = 1; i < tokens.length; i++)
{
TokenType open;
TokenType close;
void skip()
{
i++;
for (int depth = 1; depth > 0 && i < tokens.length; i++)
{
if (tokens[i].type == open)
depth++;
else if (tokens[i].type == close)
{
depth--;
if (depth == 0) break;
}
}
}
with (TokenType) switch (tokens[i].type)
{
case TokenType.int_:
case TokenType.uint_:
case TokenType.long_:
case TokenType.ulong_:
case TokenType.char_:
case TokenType.wchar_:
case TokenType.dchar_:
case TokenType.bool_:
case TokenType.byte_:
case TokenType.ubyte_:
case TokenType.short_:
case TokenType.ushort_:
case TokenType.cent_:
case TokenType.ucent_:
case TokenType.float_:
case TokenType.ifloat_:
case TokenType.cfloat_:
case TokenType.idouble_:
case TokenType.cdouble_:
case TokenType.double_:
case TokenType.real_:
case TokenType.ireal_:
case TokenType.creal_:
case this_:
symbols = symbols[0].getPartsByName(getTokenValue(tokens[i].type));
if (symbols.length == 0)
break loop;
break;
case identifier:
// writeln("looking for ", tokens[i].value, " in ", symbols[0].name);
symbols = symbols[0].getPartsByName(tokens[i].value);
if (symbols.length == 0)
{
// writeln("Couldn't find it.");
break loop;
}
if (symbols[0].kind == CompletionKind.variableName
|| symbols[0].kind == CompletionKind.memberVariableName
|| symbols[0].kind == CompletionKind.enumMember
|| (symbols[0].kind == CompletionKind.functionName
&& (completionType == CompletionType.identifiers
|| i + 1 < tokens.length)))
{
symbols = symbols[0].resolvedType is null ? [] : [symbols[0].resolvedType];
}
if (symbols.length == 0)
break loop;
if (symbols[0].kind == CompletionKind.aliasName
&& (completionType == CompletionType.identifiers
|| i + 1 < tokens.length))
{
symbols = symbols[0].resolvedType is null ? [] : [symbols[0].resolvedType];
}
if (symbols.length == 0)
break loop;
break;
case lParen:
open = TokenType.lParen;
close = TokenType.rParen;
skip();
break;
case lBracket:
open = TokenType.lBracket;
close = TokenType.rBracket;
if (symbols[0].qualifier == SymbolQualifier.array)
{
auto h = i;
skip();
Parser p = new Parser();
p.setTokens(tokens[h .. i].array());
if (!p.isSliceExpression())
{
symbols = symbols[0].resolvedType is null ? [] : [symbols[0].resolvedType];
}
}
else if (symbols[0].qualifier == SymbolQualifier.assocArray)
{
symbols = symbols[0].resolvedType is null ? [] :[symbols[0].resolvedType];
skip();
}
else
{
auto h = i;
skip();
Parser p = new Parser();
p.setTokens(tokens[h .. i].array());
ACSymbol[] overloads;
if (p.isSliceExpression())
overloads = symbols[0].getPartsByName("opSlice");
else
overloads = symbols[0].getPartsByName("opIndex");
if (overloads.length > 0)
{
symbols = overloads[0].resolvedType is null ? [] : [overloads[0].resolvedType];
}
else
return;
}
break;
case dot:
break;
default:
break loop;
}
}
const(ACSymbol)*[] symbols = getSymbolsByTokenChain(completionScope, tokens,
cursorPosition, completionType);
if (symbols.length == 0)
{
writeln("Could not get completions");
return;
}
if (completionType == CompletionType.identifiers)
{
if (symbols[0].qualifier == SymbolQualifier.func
|| symbols[0].kind == CompletionKind.functionName)
{
Log.trace("Completion list for return type of function ", symbols[0].name);
symbols = symbols[0].type is null ? [] : [symbols[0].type];
if (symbols.length == 0)
return;
}
foreach (s; symbols[0].parts.filter!(a => a.name !is null
&& a.name[0] != '*'
&& (partial is null ? true : a.name.toUpper().startsWith(partial.toUpper()))
&& !response.completions.canFind(a.name)))
{
//writeln("Adding ", s.name, " to the completion list");
Log.trace("Adding ", s.name, " to the completion list");
response.completionKinds ~= s.kind;
response.completions ~= s.name;
}
@ -362,12 +433,12 @@ void setCompletions(T)(ref AutocompleteResponse response,
}
else if (completionType == CompletionType.calltips)
{
//writeln("Showing call tips for ", symbols[0].name, " of type ", symbols[0].kind);
Log.trace("Showing call tips for ", symbols[0].name, " of type ", symbols[0].kind);
if (symbols[0].kind != CompletionKind.functionName
&& symbols[0].calltip is null)
&& symbols[0].callTip is null)
{
auto call = symbols[0].getPartsByName("opCall");
if (call.length == 0)
if (call.length > 0)
{
symbols = call;
goto setCallTips;
@ -377,6 +448,7 @@ void setCompletions(T)(ref AutocompleteResponse response,
return;
else
{
Log.trace("Not a function, but it has a constructor");
symbols = constructor;
goto setCallTips;
}
@ -385,11 +457,11 @@ void setCompletions(T)(ref AutocompleteResponse response,
response.completionType = CompletionType.calltips;
foreach (symbol; symbols)
{
// writeln("Adding calltip ", symbol.calltip);
response.completions ~= symbol.calltip;
Log.trace("Adding calltip ", symbol.callTip);
if (symbol.kind != CompletionKind.aliasName)
response.completions ~= symbol.callTip;
}
}
}
T getExpression(T)(T beforeTokens)
@ -504,7 +576,7 @@ void setImportCompletions(T)(T tokens, ref AutocompleteResponse response)
foreach (importDirectory; ModuleCache.getImportPaths())
{
string p = format("%s%s%s", importDirectory, dirSeparator, path);
writeln("Checking for ", p);
Log.trace("Checking for ", p);
if (!exists(p))
continue;
foreach (string name; dirEntries(p, SpanMode.shallow))
@ -517,123 +589,10 @@ void setImportCompletions(T)(T tokens, ref AutocompleteResponse response)
else if (isDir(name))
{
response.completions ~= name.baseName();
response.completionKinds ~= CompletionKind.packageName;
response.completionKinds ~=
exists(buildPath(name, "package.d")) || exists(buildPath(name, "package.di"))
? CompletionKind.packageName : CompletionKind.moduleName;
}
}
}
}
/**
* Initializes builtin types and the various properties of builtin types
*/
static this()
{
auto bool_ = new ACSymbol("bool", CompletionKind.keyword);
auto int_ = new ACSymbol("int", CompletionKind.keyword);
auto long_ = new ACSymbol("long", CompletionKind.keyword);
auto byte_ = new ACSymbol("byte", CompletionKind.keyword);
auto char_ = new ACSymbol("char", CompletionKind.keyword);
auto dchar_ = new ACSymbol("dchar", CompletionKind.keyword);
auto short_ = new ACSymbol("short", CompletionKind.keyword);
auto ubyte_ = new ACSymbol("ubyte", CompletionKind.keyword);
auto uint_ = new ACSymbol("uint", CompletionKind.keyword);
auto ulong_ = new ACSymbol("ulong", CompletionKind.keyword);
auto ushort_ = new ACSymbol("ushort", CompletionKind.keyword);
auto wchar_ = new ACSymbol("wchar", CompletionKind.keyword);
auto alignof_ = new ACSymbol("alignof", CompletionKind.keyword, ulong_);
auto mangleof_ = new ACSymbol("mangleof", CompletionKind.keyword);
auto sizeof_ = new ACSymbol("sizeof", CompletionKind.keyword, ulong_);
auto stringof_ = new ACSymbol("stringof", CompletionKind.keyword);
arraySymbols ~= alignof_;
arraySymbols ~= new ACSymbol("dup", CompletionKind.keyword);
arraySymbols ~= new ACSymbol("idup", CompletionKind.keyword);
arraySymbols ~= new ACSymbol("init", CompletionKind.keyword);
arraySymbols ~= new ACSymbol("length", CompletionKind.keyword, ulong_);
arraySymbols ~= mangleof_;
arraySymbols ~= new ACSymbol("ptr", CompletionKind.keyword);
arraySymbols ~= new ACSymbol("reverse", CompletionKind.keyword);
arraySymbols ~= sizeof_;
arraySymbols ~= new ACSymbol("sort", CompletionKind.keyword);
arraySymbols ~= stringof_;
assocArraySymbols ~= alignof_;
assocArraySymbols ~= new ACSymbol("byKey", CompletionKind.keyword);
assocArraySymbols ~= new ACSymbol("byValue", CompletionKind.keyword);
assocArraySymbols ~= new ACSymbol("dup", CompletionKind.keyword);
assocArraySymbols ~= new ACSymbol("get", CompletionKind.keyword);
assocArraySymbols ~= new ACSymbol("init", CompletionKind.keyword);
assocArraySymbols ~= new ACSymbol("keys", CompletionKind.keyword);
assocArraySymbols ~= new ACSymbol("length", CompletionKind.keyword, ulong_);
assocArraySymbols ~= mangleof_;
assocArraySymbols ~= new ACSymbol("rehash", CompletionKind.keyword);
assocArraySymbols ~= sizeof_;
assocArraySymbols ~= stringof_;
assocArraySymbols ~= new ACSymbol("values", CompletionKind.keyword);
foreach (s; [bool_, int_, long_, byte_, char_, dchar_, short_, ubyte_, uint_,
ulong_, ushort_, wchar_])
{
s.parts ~= new ACSymbol("init", CompletionKind.keyword, s);
s.parts ~= new ACSymbol("min", CompletionKind.keyword, s);
s.parts ~= new ACSymbol("max", CompletionKind.keyword, s);
s.parts ~= alignof_;
s.parts ~= sizeof_;
s.parts ~= stringof_;
s.parts ~= mangleof_;
}
auto cdouble_ = new ACSymbol("cdouble", CompletionKind.keyword);
auto cent_ = new ACSymbol("cent", CompletionKind.keyword);
auto cfloat_ = new ACSymbol("cfloat", CompletionKind.keyword);
auto creal_ = new ACSymbol("creal", CompletionKind.keyword);
auto double_ = new ACSymbol("double", CompletionKind.keyword);
auto float_ = new ACSymbol("float", CompletionKind.keyword);
auto idouble_ = new ACSymbol("idouble", CompletionKind.keyword);
auto ifloat_ = new ACSymbol("ifloat", CompletionKind.keyword);
auto ireal_ = new ACSymbol("ireal", CompletionKind.keyword);
auto real_ = new ACSymbol("real", CompletionKind.keyword);
auto ucent_ = new ACSymbol("ucent", CompletionKind.keyword);
foreach (s; [cdouble_, cent_, cfloat_, creal_, double_, float_,
idouble_, ifloat_, ireal_, real_, ucent_])
{
s.parts ~= alignof_;
s.parts ~= new ACSymbol("dig", CompletionKind.keyword, s);
s.parts ~= new ACSymbol("epsilon", CompletionKind.keyword, s);
s.parts ~= new ACSymbol("infinity", CompletionKind.keyword, s);
s.parts ~= new ACSymbol("init", CompletionKind.keyword, s);
s.parts ~= mangleof_;
s.parts ~= new ACSymbol("mant_dig", CompletionKind.keyword, int_);
s.parts ~= new ACSymbol("max", CompletionKind.keyword, s);
s.parts ~= new ACSymbol("max_10_exp", CompletionKind.keyword, int_);
s.parts ~= new ACSymbol("max_exp", CompletionKind.keyword, int_);
s.parts ~= new ACSymbol("min", CompletionKind.keyword, s);
s.parts ~= new ACSymbol("min_exp", CompletionKind.keyword, int_);
s.parts ~= new ACSymbol("min_10_exp", CompletionKind.keyword, int_);
s.parts ~= new ACSymbol("min_normal", CompletionKind.keyword, s);
s.parts ~= new ACSymbol("nan", CompletionKind.keyword, s);
s.parts ~= sizeof_;
s.parts ~= stringof_;
}
ireal_.parts ~= new ACSymbol("im", CompletionKind.keyword, real_);
ifloat_.parts ~= new ACSymbol("im", CompletionKind.keyword, float_);
idouble_.parts ~= new ACSymbol("im", CompletionKind.keyword, double_);
ireal_.parts ~= new ACSymbol("re", CompletionKind.keyword, real_);
ifloat_.parts ~= new ACSymbol("re", CompletionKind.keyword, float_);
idouble_.parts ~= new ACSymbol("re", CompletionKind.keyword, double_);
auto void_ = new ACSymbol("void", CompletionKind.keyword);
builtinSymbols = [bool_, int_, long_, byte_, char_, dchar_, short_, ubyte_, uint_,
ulong_, ushort_, wchar_, cdouble_, cent_, cfloat_, creal_, double_,
float_, idouble_, ifloat_, ireal_, real_, ucent_, void_];
}
ACSymbol[] builtinSymbols;
ACSymbol[] arraySymbols;
ACSymbol[] assocArraySymbols;
ACSymbol[] classSymbols;
ACSymbol[] structSymbols;

View File

@ -1,2 +1,31 @@
dmd -wi client.d messages.d msgpack-d/src/msgpack.d -Imsgpack-d/src -ofdcd-client
dmd -wi -g server.d modulecache.d actypes.d messages.d constants.d acvisitor.d autocomplete.d dscanner/stdx/d/ast.d dscanner/stdx/d/parser.d dscanner/stdx/d/lexer.d dscanner/stdx/d/entities.d dscanner/formatter.d msgpack-d/src/msgpack.d -Imsgpack-d/src -Idscanner/ -ofdcd-server
dmd -wi client.d\
messages.d\
msgpack-d/src/msgpack.d\
-Imsgpack-d/src\
-release\
-inline\
-noboundscheck\
-O\
-ofdcd-client
dmd \
actypes.d\
astconverter.d\
autocomplete.d\
constants.d\
messages.d\
modulecache.d\
semantic.d\
server.d\
stupidlog.d\
dscanner/stdx/d/ast.d\
dscanner/stdx/d/parser.d\
dscanner/stdx/d/lexer.d\
dscanner/stdx/d/entities.d\
dscanner/formatter.d\
msgpack-d/src/msgpack.d\
-Imsgpack-d/src\
-Idscanner\
-wi\
-g\
-ofdcd-server

View File

@ -40,12 +40,13 @@ int main(string[] args)
bool help;
bool shutdown;
bool clearCache;
bool symbolLocation;
try
{
getopt(args, "cursorPos|c", &cursorPos, "I", &importPaths,
"port|p", &port, "help|h", &help, "shutdown", &shutdown,
"clearCache", &clearCache);
"clearCache", &clearCache, "symbolLocation|l", &symbolLocation);
}
catch (Exception e)
{
@ -118,6 +119,7 @@ int main(string[] args)
request.importPaths = importPaths;
request.sourceCode = sourceCode;
request.cursorPosition = cursorPos;
request.kind = symbolLocation ? RequestKind.symbolLocation : RequestKind.autocomplete;
// Send message to server
TcpSocket socket = createSocket(port);
@ -127,26 +129,11 @@ int main(string[] args)
AutocompleteResponse response = getResponse(socket);
if (response.completions.length > 0)
{
writeln(response.completionType);
auto app = appender!(string[])();
if (response.completionType == CompletionType.identifiers)
{
for (size_t i = 0; i < response.completions.length; i++)
app.put(format("%s\t%s", response.completions[i], response.completionKinds[i]));
}
else
{
foreach (completion; response.completions)
{
app.put(completion);
}
}
// Deduplicate overloaded methods
foreach (line; app.data.sort.uniq)
writeln(line);
}
if (symbolLocation)
printLocationResponse(response);
else
printCompletionResponse(response);
return 0;
}
@ -176,6 +163,10 @@ Options:
--shutdown
Instructs the server to shut down.
--symbolLocation | -l
Get the file name and position that the symbol at the cursor location
was defined.
-IPATH
Instructs the server to add PATH to its list of paths searced for
imported modules.
@ -220,3 +211,35 @@ AutocompleteResponse getResponse(TcpSocket socket)
msgpack.unpack(buffer[0..bytesReceived], response);
return response;
}
void printLocationResponse(AutocompleteResponse response)
{
if (response.symbolFilePath is null)
writeln("Not found");
else
writefln("%s\t%d", response.symbolFilePath, response.symbolLocation);
}
void printCompletionResponse(AutocompleteResponse response)
{
if (response.completions.length > 0)
{
writeln(response.completionType);
auto app = appender!(string[])();
if (response.completionType == CompletionType.identifiers)
{
for (size_t i = 0; i < response.completions.length; i++)
app.put(format("%s\t%s", response.completions[i], response.completionKinds[i]));
}
else
{
foreach (completion; response.completions)
{
app.put(completion);
}
}
// Deduplicate overloaded methods
foreach (line; app.data.sort.uniq)
writeln(line);
}
}

View File

@ -53,7 +53,9 @@ immutable string[] traits = [
"getMember",
"getOverloads",
"getProtection",
"getUnitTests",
"getVirtualFunctions",
"getVirtualIndex",
"getVirtualMethods",
"hasMember",
"identifier",
@ -68,6 +70,7 @@ immutable string[] traits = [
"isLazy",
"isNested",
"isOut",
"isOverrideFunction",
"isPOD",
"isRef",
"isSame",
@ -199,3 +202,87 @@ immutable string[] structProperties = [
"sizeof",
"stringof"
];
immutable string[] predefinedVersions;
static this()
{
version(AArch64) predefinedVersions ~= "AArch64";
version(AIX) predefinedVersions ~= "AIX";
version(all) predefinedVersions ~= "all";
version(Alpha) predefinedVersions ~= "Alpha";
version(Alpha_HardFloat) predefinedVersions ~= "Alpha_HardFloat";
version(Alpha_SoftFloat) predefinedVersions ~= "Alpha_SoftFloat";
version(Android) predefinedVersions ~= "Android";
version(ARM) predefinedVersions ~= "ARM";
version(ARM_HardFloat) predefinedVersions ~= "ARM_HardFloat";
version(ARM_SoftFloat) predefinedVersions ~= "ARM_SoftFloat";
version(ARM_SoftFP) predefinedVersions ~= "ARM_SoftFP";
version(ARM_Thumb) predefinedVersions ~= "ARM_Thumb";
version(assert) predefinedVersions ~= "assert";
version(BigEndian) predefinedVersions ~= "BigEndian";
version(BSD) predefinedVersions ~= "BSD";
version(Cygwin) predefinedVersions ~= "Cygwin";
version(D_Coverage) predefinedVersions ~= "D_Coverage";
version(D_Ddoc) predefinedVersions ~= "D_Ddoc";
version(D_HardFloat) predefinedVersions ~= "D_HardFloat";
version(DigitalMars) predefinedVersions ~= "DigitalMars";
version(D_InlineAsm_X86) predefinedVersions ~= "D_InlineAsm_X86";
version(D_InlineAsm_X86_64) predefinedVersions ~= "D_InlineAsm_X86_64";
version(D_LP64) predefinedVersions ~= "D_LP64";
version(D_NoBoundsChecks) predefinedVersions ~= "D_NoBoundsChecks";
version(D_PIC) predefinedVersions ~= "D_PIC";
version(DragonFlyBSD) predefinedVersions ~= "DragonFlyBSD";
version(D_SIMD) predefinedVersions ~= "D_SIMD";
version(D_SoftFloat) predefinedVersions ~= "D_SoftFloat";
version(D_Version2) predefinedVersions ~= "D_Version2";
version(D_X32) predefinedVersions ~= "D_X32";
version(FreeBSD) predefinedVersions ~= "FreeBSD";
version(GNU) predefinedVersions ~= "GNU";
version(Haiku) predefinedVersions ~= "Haiku";
version(HPPA) predefinedVersions ~= "HPPA";
version(HPPA64) predefinedVersions ~= "HPPA64";
version(Hurd) predefinedVersions ~= "Hurd";
version(IA64) predefinedVersions ~= "IA64";
version(LDC) predefinedVersions ~= "LDC";
version(linux) predefinedVersions ~= "linux";
version(LittleEndian) predefinedVersions ~= "LittleEndian";
version(MIPS32) predefinedVersions ~= "MIPS32";
version(MIPS64) predefinedVersions ~= "MIPS64";
version(MIPS_EABI) predefinedVersions ~= "MIPS_EABI";
version(MIPS_HardFloat) predefinedVersions ~= "MIPS_HardFloat";
version(MIPS_N32) predefinedVersions ~= "MIPS_N32";
version(MIPS_N64) predefinedVersions ~= "MIPS_N64";
version(MIPS_O32) predefinedVersions ~= "MIPS_O32";
version(MIPS_O64) predefinedVersions ~= "MIPS_O64";
version(MIPS_SoftFloat) predefinedVersions ~= "MIPS_SoftFloat";
version(NetBSD) predefinedVersions ~= "NetBSD";
version(none) predefinedVersions ~= "none";
version(OpenBSD) predefinedVersions ~= "OpenBSD";
version(OSX) predefinedVersions ~= "OSX";
version(Posix) predefinedVersions ~= "Posix";
version(PPC) predefinedVersions ~= "PPC";
version(PPC64) predefinedVersions ~= "PPC64";
version(PPC_HardFloat) predefinedVersions ~= "PPC_HardFloat";
version(PPC_SoftFloat) predefinedVersions ~= "PPC_SoftFloat";
version(S390) predefinedVersions ~= "S390";
version(S390X) predefinedVersions ~= "S390X";
version(SDC) predefinedVersions ~= "SDC";
version(SH) predefinedVersions ~= "SH";
version(SH64) predefinedVersions ~= "SH64";
version(SkyOS) predefinedVersions ~= "SkyOS";
version(Solaris) predefinedVersions ~= "Solaris";
version(SPARC) predefinedVersions ~= "SPARC";
version(SPARC64) predefinedVersions ~= "SPARC64";
version(SPARC_HardFloat) predefinedVersions ~= "SPARC_HardFloat";
version(SPARC_SoftFloat) predefinedVersions ~= "SPARC_SoftFloat";
version(SPARC_V8Plus) predefinedVersions ~= "SPARC_V8Plus";
version(SysV3) predefinedVersions ~= "SysV3";
version(SysV4) predefinedVersions ~= "SysV4";
version(unittest) predefinedVersions ~= "unittest";
version(Win32) predefinedVersions ~= "Win32";
version(Win64) predefinedVersions ~= "Win64";
version(Windows) predefinedVersions ~= "Windows";
version(X86) predefinedVersions ~= "X86";
version(X86_64) predefinedVersions ~= "X86_64";
}

@ -1 +1 @@
Subproject commit e3819643bbec121e16abda6f980cc096d2e7f4f1
Subproject commit 87ed0bd3b42758488ad59fb26f2180a01740dfa9

View File

@ -100,6 +100,32 @@ function M.cycleCalltips(delta)
showCurrentCallTip()
end
function M.gotoDeclaration()
local fileName = os.tmpname()
local command = M.PATH_TO_DCD_CLIENT .. " -l -c" .. buffer.current_pos .. " > " .. fileName
local mode = "w"
if _G.WIN32 then
mode = "wb"
end
local p = io.popen(command, mode)
p:write(buffer:get_text())
p:flush()
p:close()
local tmpFile = io.open(fileName, "r")
local r = tmpFile:read("*a")
if r ~= "Not found\n" then
path, position = r:match("^(.-)\t(%d+)")
if (path ~= nil and position ~= nil) then
if (path ~= "stdin") then
io.open_file(path)
end
buffer:goto_pos(tonumber(position))
buffer:word_right_end_extend()
end
end
os.remove(fileName)
end
events.connect(events.CALL_TIP_CLICK, function(arrow)
if buffer:get_lexer() ~= "dmd" then return end
if arrow == 1 then

View File

@ -31,6 +31,7 @@ keys.dmd = {
(_USERHOME..'/modules/dmd/init.lua'):iconv('UTF-8', _CHARSET) },
},
['c\n'] = {autocomplete},
['cG'] = {_M.dcd.gotoDeclaration},
['down'] = {_M.dcd.cycleCalltips, 1},
['up'] = {_M.dcd.cycleCalltips, -1},
}

View File

@ -23,6 +23,10 @@ module messages;
*/
enum CompletionKind : char
{
/// Invalid completion kind. This is used internally and will never
/// be returned in a completion response.
dummy = '?',
/// class names
className = 'c',
@ -67,6 +71,12 @@ enum CompletionKind : char
/// alias name
aliasName = 'l',
/// template name
templateName = 't',
/// mixin template name
mixinTemplateName = 'T'
}
/**
@ -83,15 +93,29 @@ enum CompletionType : string
* The auto-completion list consists of a listing of functions and their
* parameters.
*/
calltips = "calltips"
calltips = "calltips",
/**
* The response contains the location of a symbol declaration.
*/
location = "location"
}
enum RequestKind
/**
* Request kind
*/
enum RequestKind : ubyte
{
/// Autocompletion
autocomplete,
/// Clear the completion cache
clearCache,
/// Add import directory to server
addImport,
shutdown
/// Shut down the server
shutdown,
/// Get declaration location of given symbol
symbolLocation
}
/**
@ -135,6 +159,16 @@ struct AutocompleteResponse
*/
string completionType;
/**
* The path to the file that contains the symbol.
*/
string symbolFilePath;
/**
* The byte offset at which the symbol is located.
*/
size_t symbolLocation;
/**
* The completions
*/

View File

@ -29,14 +29,20 @@ import std.path;
import std.algorithm;
import std.conv;
import acvisitor;
import actypes;
import autocomplete;
import semantic;
import astconverter;
import stupidlog;
struct CacheEntry
{
ACSymbol[] symbols;
const(ACSymbol)*[] symbols;
SysTime modificationTime;
void opAssign(ref const CacheEntry other)
{
this.symbols = cast(typeof(symbols)) other.symbols;
this.modificationTime = other.modificationTime;
}
}
/**
@ -54,37 +60,64 @@ struct ModuleCache
cache = cache.init;
}
static void estimateMemory()
{
size_t estimate = 0;
foreach (c; cache)
{
foreach (symbol; c.symbols)
estimate = symbol.estimateMemory(estimate);
}
double megabytes = estimate / (1024.0F * 1024.0F);
Log.trace("Memory use estimated at ", megabytes, " megabytes");
}
/**
* Adds the given path to the list of directories checked for imports
*/
static void addImportPath(string path)
static void addImportPaths(string[] paths)
{
if (!exists(path))
return;
importPaths ~= path;
foreach (fileName; dirEntries(path, "*.{d,di}", SpanMode.depth))
foreach (path; paths)
{
writeln("Loading and caching completions for ", fileName);
getSymbolsInModule(fileName);
if (!exists(path))
{
Log.error("Cannot cache modules in ", path, " because it does not exist");
continue;
}
importPaths ~= path;
}
foreach (path; paths)
{
foreach (fileName; dirEntries(path, "*.{d,di}", SpanMode.depth))
{
getSymbolsInModule(fileName);
}
}
}
/**
* Params:
* moduleName = the name of the module in "a/b.d" form
* moduleName = the name of the module in "a/b/c" form
* Returns:
* The symbols defined in the given module
*/
static ACSymbol[] getSymbolsInModule(string moduleName)
static const(ACSymbol)*[] getSymbolsInModule(string location)
{
writeln("Getting symbols for module ", moduleName);
string location = resolveImportLoctation(moduleName);
if (location is null)
return [];
if (!needsReparsing(location))
return cache[location].symbols;
auto visitor = new AutocompleteVisitor;
if (!needsReparsing(location))
{
if (location in cache)
return cache[location].symbols;
return [];
}
Log.info("Getting symbols for ", location);
recursionGuard[location] = true;
const(ACSymbol)*[] symbols;
try
{
File f = File(location);
@ -92,51 +125,64 @@ struct ModuleCache
f.rawRead(source);
LexerConfig config;
config.fileName = location;
auto tokens = source.byToken(config).array();
Module mod = parseModule(tokens, location, &doesNothing);
symbols = convertAstToSymbols(tokens, location);
visitor.visit(mod);
visitor.scope_.resolveSymbolTypes();
// Parsing allocates a lot of AST nodes. We can greatly reduce the
// program's idle memory use by running the GC here.
// TODO: Re-visit this when D gets a precise GC.
import core.memory;
GC.collect();
GC.minimize();
}
catch (Exception ex)
{
writeln("Couln't parse ", location, " due to exception: ", ex.msg);
Log.error("Couln't parse ", location, " due to exception: ", ex.msg);
return [];
}
SysTime access;
SysTime modification;
getTimes(location, access, modification);
if (location !in cache)
cache[location] = CacheEntry.init;
cache[location].modificationTime = modification;
cache[location].symbols = visitor.symbols;
return cache[location].symbols;
CacheEntry c = CacheEntry(symbols, modification);
cache[location] = c;
recursionGuard[location] = false;
return symbols;
}
/**
* Params:
* moduleName the name of the module being imported, in "a/b/c.d" style
* moduleName the name of the module being imported, in "a/b/c" style
* Returns:
* The absolute path to the file that contains the module, or null if
* not found.
*/
static string resolveImportLoctation(string moduleName)
{
// writeln("Resolving location of ", moduleName);
if (isRooted(moduleName))
return moduleName;
string[] alternatives;
foreach (path; importPaths)
{
string filePath = path ~ "/" ~ moduleName;
if (filePath.exists())
return filePath;
filePath ~= "i"; // check for x.di if x.d isn't found
if (filePath.exists())
return filePath;
string filePath = buildPath(path, moduleName);
if (exists(filePath ~ ".d") && isFile(filePath ~ ".d"))
alternatives = (filePath ~ ".d") ~ alternatives;
else if (exists(filePath ~ ".di") && isFile(filePath ~ ".di"))
alternatives = (filePath ~ ".di") ~ alternatives;
else if (exists(filePath) && isDir(filePath))
{
string packagePath = buildPath(filePath, "package.d");
if (exists(packagePath) && isFile(packagePath))
{
alternatives ~= packagePath;
continue;
}
packagePath ~= "i";
if (exists(packagePath) && isFile(packagePath))
alternatives ~= packagePath;
}
}
writeln("Could not find ", moduleName);
return null;
return alternatives.length > 0 ? alternatives[0] : null;
}
static const(string[]) getImportPaths()
@ -154,6 +200,10 @@ private:
*/
static bool needsReparsing(string mod)
{
if (mod !in recursionGuard)
return true;
if (recursionGuard[mod])
return false;
if (!exists(mod) || mod !in cache)
return true;
SysTime access;
@ -165,6 +215,8 @@ private:
// Mapping of file paths to their cached symbols.
static CacheEntry[string] cache;
static bool[string] recursionGuard;
// Listing of paths to check for imports
static string[] importPaths;
}

@ -1 +1 @@
Subproject commit 40c797cb8ae3eb56cf88399ef3532fc29abd238a
Subproject commit 30a7d3fb38b43dccef3be7cea1f40b4dc61a3474

72
semantic.d Normal file
View File

@ -0,0 +1,72 @@
/**
* This file is part of DCD, a development tool for the D programming language.
* Copyright (C) 2013 Brian Schott
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
module semantic;
import messages;
import actypes;
import stdx.d.ast;
import stdx.d.lexer;
import stupidlog;
/**
* Intermediate form between ACSymbol and the AST classes. Stores enough
* information to resolve things like base classes and alias this.
*/
struct SemanticSymbol
{
public:
@disable this();
this(string name, CompletionKind kind, string symbolFile,
size_t location = size_t.max)
{
acSymbol = new ACSymbol(name, kind);
acSymbol.location = location;
acSymbol.symbolFile = symbolFile;
}
void addChild(SemanticSymbol* child)
{
children ~= child;
acSymbol.parts ~= child.acSymbol;
}
/// Autocompletion symbol
ACSymbol* acSymbol;
/// Base classes
string[][] baseClasses;
/// Variable type or function return type
Type type;
/// Alias this symbols
string[] aliasThis;
/// MixinTemplates
string[] mixinTemplates;
/// Protection level for this symobol
TokenType protection;
SemanticSymbol* parent;
SemanticSymbol*[] children;
}

View File

@ -26,12 +26,16 @@ import std.path;
import std.file;
import std.array;
import std.process;
import std.datetime;
import msgpack;
import messages;
import autocomplete;
import modulecache;
import stupidlog;
import actypes;
import core.memory;
enum CONFIG_FILE_NAME = "dcd.conf";
@ -42,6 +46,13 @@ version(OSX) version = useXDG;
int main(string[] args)
{
Log.info("Starting up...");
StopWatch sw = StopWatch(AutoStart.yes);
Log.output = stdout;
Log.level = LogLevel.trace;
ushort port = 9166;
bool help;
string[] importPaths;
@ -72,33 +83,40 @@ int main(string[] args)
socket.listen(0);
scope (exit)
{
writeln("Shutting down sockets...");
Log.info("Shutting down sockets...");
socket.shutdown(SocketShutdown.BOTH);
socket.close();
writeln("Sockets shut down.");
Log.info("Sockets shut down.");
}
foreach (path; importPaths)
ModuleCache.addImportPath(path);
writeln("Import directories: ", ModuleCache.getImportPaths());
ModuleCache.addImportPaths(importPaths);
Log.info("Import directories: ", ModuleCache.getImportPaths());
ubyte[] buffer = new ubyte[1024 * 1024 * 4]; // 4 megabytes should be enough for anybody...
sw.stop();
Log.info("Startup completed in ", sw.peek().to!("msecs", float), " milliseconds");
ModuleCache.estimateMemory();
// No relative paths
version (Posix) chdir("/");
writeln("Startup complete");
while (true)
{
auto s = socket.accept();
s.blocking = true;
// TODO: Restrict connections to localhost
scope (exit)
{
s.shutdown(SocketShutdown.BOTH);
s.close();
}
ptrdiff_t bytesReceived = s.receive(buffer);
auto requestWatch = StopWatch(AutoStart.yes);
size_t messageLength;
// bit magic!
(cast(ubyte*) &messageLength)[0..size_t.sizeof] = buffer[0..size_t.sizeof];
@ -115,7 +133,7 @@ int main(string[] args)
if (bytesReceived == Socket.ERROR)
{
writeln("Socket recieve failed");
Log.error("Socket recieve failed");
break;
}
@ -123,28 +141,28 @@ int main(string[] args)
msgpack.unpack(buffer[size_t.sizeof .. bytesReceived], request);
if (request.kind == RequestKind.addImport)
{
foreach (path; request.importPaths)
{
ModuleCache.addImportPath(path);
}
ModuleCache.addImportPaths(request.importPaths);
}
else if (request.kind == RequestKind.clearCache)
{
writeln("Clearing cache.");
Log.info("Clearing cache.");
ModuleCache.clear();
}
else if (request.kind == RequestKind.shutdown)
{
writeln("Shutting down.");
Log.info("Shutting down.");
break;
}
else
{
AutocompleteResponse response = complete(request, importPaths);
AutocompleteResponse response =
request.kind == RequestKind.autocomplete
? complete(request)
: findDeclaration(request);
ubyte[] responseBytes = msgpack.pack(response);
assert(s.send(responseBytes) == responseBytes.length);
s.send(responseBytes);
}
Log.info("Request processed in ", requestWatch.peek().to!("msecs", float), " milliseconds");
}
return 0;
}
@ -178,11 +196,11 @@ void warnAboutOldConfigLocation()
version (linux) if ("~/.config/dcd".expandTilde().exists()
&& "~/.config/dcd".expandTilde().isFile())
{
writeln("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
writeln("!! Upgrade warning:");
writeln("!! '~/.config/dcd' should be moved to '$XDG_CONFIG_HOME/dcd/dcd.conf'");
writeln("!! or '$HOME/.config/dcd/dcd.conf'");
writeln("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
Log.error("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
Log.error("!! Upgrade warning:");
Log.error("!! '~/.config/dcd' should be moved to '$XDG_CONFIG_HOME/dcd/dcd.conf'");
Log.error("!! or '$HOME/.config/dcd/dcd.conf'");
Log.error("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
}
}
@ -192,7 +210,7 @@ string[] loadConfiguredImportDirs()
immutable string configLocation = getConfigurationLocation();
if (!configLocation.exists())
return [];
writeln("Loading configuration from ", configLocation);
Log.info("Loading configuration from ", configLocation);
File f = File(configLocation, "rt");
return f.byLine(KeepTerminator.no).map!(a => a.idup).filter!(a => a.exists()).array();
}

72
stupidlog.d Normal file
View File

@ -0,0 +1,72 @@
/*******************************************************************************
* Authors: Brian Schott
* Copyright: Brian Schott
* Date: Oct 5 2013
*
* License:
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
module stupidlog;
import std.stdio;
import core.vararg;
enum LogLevel : uint
{
fatal = 0,
error,
info,
trace
}
struct Log
{
static void trace(T...)(T args)
{
if (level < LogLevel.trace) return;
if (output is stdout)
output.writeln("[\033[01;36mtrace\033[0m] ", args);
else
output.writeln("[trace] ", args);
}
static void info(T...)(T args)
{
if (level < LogLevel.info) return;
if (output is stdout)
output.writeln("[\033[01;32minfo\033[0m ] ", args);
else
output.writeln("[info ] ", args);
}
static void error(T...)(T args)
{
if (level < LogLevel.error) return;
if (output is stdout)
output.writeln("[\033[01;31merror\033[0m] ", args);
else
output.writeln("[error] ", args);
}
static void fatal(T...)(T args)
{
if (output is stdout)
output.writeln("[\033[01;35mfatal\033[0m] ", args);
else
output.writeln("[fatal] ", args);
}
static LogLevel level;
static File output;
}