Merge branch '0.2.0-dev'
Conflicts: acvisitor.d autocomplete.d dscanner server.d
This commit is contained in:
commit
07c93758f7
15
README.md
15
README.md
|
@ -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
502
actypes.d
|
@ -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;
|
||||
|
|
573
acvisitor.d
573
acvisitor.d
|
@ -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;
|
||||
}
|
|
@ -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) {}
|
539
autocomplete.d
539
autocomplete.d
|
@ -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;
|
||||
|
|
33
build.sh
33
build.sh
|
@ -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
|
||||
|
|
65
client.d
65
client.d
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
87
constants.d
87
constants.d
|
@ -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";
|
||||
}
|
||||
|
|
2
dscanner
2
dscanner
|
@ -1 +1 @@
|
|||
Subproject commit e3819643bbec121e16abda6f980cc096d2e7f4f1
|
||||
Subproject commit 87ed0bd3b42758488ad59fb26f2180a01740dfa9
|
|
@ -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
|
||||
|
|
|
@ -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},
|
||||
}
|
||||
|
|
40
messages.d
40
messages.d
|
@ -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
|
||||
*/
|
||||
|
|
126
modulecache.d
126
modulecache.d
|
@ -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
|
|
@ -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;
|
||||
}
|
64
server.d
64
server.d
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue