diff --git a/README.md b/README.md
index 5fd34c1..1045ca6 100644
--- a/README.md
+++ b/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
diff --git a/actypes.d b/actypes.d
index ef15612..3164dfe 100644
--- a/actypes.d
+++ b/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;
diff --git a/acvisitor.d b/acvisitor.d
deleted file mode 100644
index ab471d5..0000000
--- a/acvisitor.d
+++ /dev/null
@@ -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 .
- */
-
-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;
-}
diff --git a/astconverter.d b/astconverter.d
new file mode 100644
index 0000000..9db1716
--- /dev/null
+++ b/astconverter.d
@@ -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 .
+ ******************************************************************************/
+
+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) {}
diff --git a/autocomplete.d b/autocomplete.d
index bce918f..f7488e1 100644
--- a/autocomplete.d
+++ b/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;
diff --git a/build.sh b/build.sh
index 67ca672..a047116 100755
--- a/build.sh
+++ b/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
diff --git a/client.d b/client.d
index 38b4233..8aab9ec 100644
--- a/client.d
+++ b/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);
+ }
+}
diff --git a/constants.d b/constants.d
index 9c78779..d7b4f37 100644
--- a/constants.d
+++ b/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";
+}
diff --git a/dscanner b/dscanner
index e381964..87ed0bd 160000
--- a/dscanner
+++ b/dscanner
@@ -1 +1 @@
-Subproject commit e3819643bbec121e16abda6f980cc096d2e7f4f1
+Subproject commit 87ed0bd3b42758488ad59fb26f2180a01740dfa9
diff --git a/editors/textadept/modules/dmd/dcd.lua b/editors/textadept/modules/dmd/dcd.lua
index 0ead9fb..3849dc9 100644
--- a/editors/textadept/modules/dmd/dcd.lua
+++ b/editors/textadept/modules/dmd/dcd.lua
@@ -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
diff --git a/editors/textadept/modules/dmd/init.lua b/editors/textadept/modules/dmd/init.lua
index 6978bf2..894c5f3 100644
--- a/editors/textadept/modules/dmd/init.lua
+++ b/editors/textadept/modules/dmd/init.lua
@@ -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},
}
diff --git a/messages.d b/messages.d
index 613eeec..7649e57 100644
--- a/messages.d
+++ b/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
*/
diff --git a/modulecache.d b/modulecache.d
index 95c53d0..01f249d 100644
--- a/modulecache.d
+++ b/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;
}
diff --git a/msgpack-d b/msgpack-d
index 40c797c..30a7d3f 160000
--- a/msgpack-d
+++ b/msgpack-d
@@ -1 +1 @@
-Subproject commit 40c797cb8ae3eb56cf88399ef3532fc29abd238a
+Subproject commit 30a7d3fb38b43dccef3be7cea1f40b4dc61a3474
diff --git a/semantic.d b/semantic.d
new file mode 100644
index 0000000..b9796e5
--- /dev/null
+++ b/semantic.d
@@ -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 .
+ */
+
+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;
+}
diff --git a/server.d b/server.d
index 6546cbe..9341ac2 100644
--- a/server.d
+++ b/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();
}
diff --git a/stupidlog.d b/stupidlog.d
new file mode 100644
index 0000000..dc78a57
--- /dev/null
+++ b/stupidlog.d
@@ -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 .
+ ******************************************************************************/
+
+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;
+}