668 lines
18 KiB
D
668 lines
18 KiB
D
/*******************************************************************************
|
|
* Authors: Brian Schott
|
|
* Copyright: Brian Schott
|
|
* Date: Sep 21 2013
|
|
*
|
|
* License:
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
******************************************************************************/
|
|
|
|
/**
|
|
* AST conversion takes place in several steps
|
|
* 1. AST is converted to a tree of SemanicSymbols, a tree of ACSymbols, and a
|
|
* tree of scopes. The following fields are set on the symbols:
|
|
* * name
|
|
* * location
|
|
* * alias this
|
|
* * base class names
|
|
* * protection level
|
|
* * symbol kind
|
|
* * function call tip
|
|
* * symbol file path
|
|
* Import statements are recorded in the scope tree.
|
|
* 2. Scope tree is traversed and all imports are resolved by adding appropriate
|
|
* ACSymbol instances.
|
|
* 3. Semantic symbol tree is traversed
|
|
* * types are resolved
|
|
* * base classes are resolved
|
|
* * mixin templates are resolved
|
|
* * alias this is resolved
|
|
*/
|
|
|
|
module astconverter;
|
|
|
|
import std.array;
|
|
import std.conv;
|
|
import std.range;
|
|
import std.algorithm;
|
|
|
|
import stdx.d.ast;
|
|
import stdx.d.lexer;
|
|
import stdx.d.parser;
|
|
|
|
import actypes;
|
|
import messages;
|
|
import semantic;
|
|
import stupidlog;
|
|
|
|
class FirstPass : ASTVisitor
|
|
{
|
|
override void visit(Constructor con)
|
|
{
|
|
// Log.trace(__FUNCTION__, " ", typeof(con).stringof);
|
|
visitFunctionDeclaration(con);
|
|
}
|
|
|
|
override void visit(SharedStaticConstructor con)
|
|
{
|
|
// Log.trace(__FUNCTION__, " ", typeof(con).stringof);
|
|
visitFunctionDeclaration(con);
|
|
}
|
|
|
|
override void visit(StaticConstructor con)
|
|
{
|
|
// Log.trace(__FUNCTION__, " ", typeof(con).stringof);
|
|
visitFunctionDeclaration(con);
|
|
}
|
|
|
|
override void visit(Destructor des)
|
|
{
|
|
// Log.trace(__FUNCTION__, " ", typeof(des).stringof);
|
|
visitFunctionDeclaration(des);
|
|
}
|
|
|
|
override void visit(SharedStaticDestructor des)
|
|
{
|
|
// Log.trace(__FUNCTION__, " ", typeof(des).stringof);
|
|
visitFunctionDeclaration(des);
|
|
}
|
|
|
|
override void visit(StaticDestructor des)
|
|
{
|
|
// Log.trace(__FUNCTION__, " ", typeof(des).stringof);
|
|
visitFunctionDeclaration(des);
|
|
}
|
|
|
|
override void visit(FunctionDeclaration dec)
|
|
{
|
|
// Log.trace(__FUNCTION__, " ", typeof(dec).stringof);
|
|
visitFunctionDeclaration(dec);
|
|
}
|
|
|
|
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;
|
|
symbol.acSymbol.type = t;
|
|
symbol.kind = CompletionKind.variableName;
|
|
symbol.name = declarator.name.value.dup;
|
|
symbol.location = declarator.name.startIndex;
|
|
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))
|
|
p = attr.attribute;
|
|
}
|
|
dec.accept(this);
|
|
protection = p;
|
|
}
|
|
|
|
override void visit(Module mod)
|
|
{
|
|
// Log.trace(__FUNCTION__, " ", typeof(mod).stringof);
|
|
rootSymbol = new SemanticSymbol;
|
|
rootSymbol.kind = CompletionKind.moduleName;
|
|
rootSymbol.startLocation = 0;
|
|
rootSymbol.endLocation = size_t.max;
|
|
currentSymbol = rootSymbol;
|
|
currentScope = new Scope();
|
|
currentScope.startLocation = 0;
|
|
currentScope.endLocation = size_t.max;
|
|
mod.accept(this);
|
|
}
|
|
|
|
override void visit(EnumDeclaration dec)
|
|
{
|
|
assert (currentSymbol);
|
|
// Log.trace(__FUNCTION__, " ", typeof(dec).stringof);
|
|
SemanticSymbol* symbol = new SemanticSymbol;
|
|
symbol.name = dec.name.value.dup;
|
|
symbol.location = dec.name.startIndex;
|
|
symbol.kind = CompletionKind.enumName;
|
|
symbol.type = dec.type;
|
|
symbol.parent = currentSymbol;
|
|
currentSymbol = symbol;
|
|
if (dec.enumBody !is null)
|
|
dec.enumBody.accept(this);
|
|
currentSymbol = symbol.parent;
|
|
currentSymbol.children ~= symbol;
|
|
}
|
|
|
|
override void visit(EnumMember member)
|
|
{
|
|
// Log.trace(__FUNCTION__, " ", typeof(member).stringof);
|
|
SemanticSymbol* symbol = new SemanticSymbol;
|
|
symbol.kind = CompletionKind.enumMember;
|
|
symbol.name = member.name.value.dup;
|
|
symbol.location = member.name.startIndex;
|
|
symbol.type = member.type;
|
|
symbol.parent = currentSymbol;
|
|
currentSymbol.children ~= 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);
|
|
s.parent = currentScope;
|
|
currentScope = s;
|
|
foreach (dec; structBody.declarations)
|
|
visit(dec);
|
|
currentScope = s.parent;
|
|
currentScope.children ~= s;
|
|
}
|
|
|
|
// Create scope for block statements
|
|
override void visit(BlockStatement blockStatement)
|
|
{
|
|
// Log.trace(__FUNCTION__, " ", typeof(blockStatement).stringof);
|
|
Scope* s = new Scope;
|
|
s.startLocation = blockStatement.startLocation;
|
|
s.endLocation = blockStatement.endLocation;
|
|
|
|
if (currentSymbol.kind == CompletionKind.functionName)
|
|
{
|
|
foreach (child; currentSymbol.children)
|
|
{
|
|
// Log.trace("Setting ", child.name, " location");
|
|
child.location = s.startLocation + 1;
|
|
}
|
|
}
|
|
// Log.trace("Added scope ", s.startLocation, " ", s.endLocation);
|
|
s.parent = currentScope;
|
|
if (blockStatement.declarationsAndStatements !is null)
|
|
{
|
|
currentScope = s;
|
|
visit (blockStatement.declarationsAndStatements);
|
|
currentScope = s.parent;
|
|
}
|
|
currentScope.children ~= s;
|
|
}
|
|
|
|
alias ASTVisitor.visit visit;
|
|
|
|
private:
|
|
|
|
void visitAggregateDeclaration(AggType)(AggType dec, CompletionKind kind)
|
|
{
|
|
SemanticSymbol* symbol = new SemanticSymbol;
|
|
// Log.trace("visiting aggregate declaration ", dec.name.value);
|
|
symbol.name = dec.name.value.dup;
|
|
symbol.location = dec.name.startIndex;
|
|
symbol.kind = kind;
|
|
symbol.parent = currentSymbol;
|
|
symbol.protection = protection;
|
|
currentSymbol = symbol;
|
|
dec.accept(this);
|
|
currentSymbol = symbol.parent;
|
|
currentSymbol.children ~= symbol;
|
|
}
|
|
|
|
void visitFunctionDeclaration(DeclarationType)(DeclarationType dec)
|
|
{
|
|
SemanticSymbol* symbol = new SemanticSymbol;
|
|
|
|
static if (is (DeclarationType == FunctionDeclaration))
|
|
{
|
|
symbol.name = dec.name.value.dup;
|
|
symbol.location = dec.name.startIndex;
|
|
}
|
|
else static if (is (DeclarationType == Destructor)
|
|
|| is (DeclarationType == StaticDestructor)
|
|
|| is (DeclarationType == SharedStaticDestructor))
|
|
{
|
|
symbol.name = "*destructor*";
|
|
symbol.location = dec.location;
|
|
}
|
|
else
|
|
{
|
|
symbol.name = "*constructor*";
|
|
symbol.location = dec.location;
|
|
}
|
|
|
|
static if (is (DeclarationType == Destructor)
|
|
|| is (DeclarationType == StaticDestructor)
|
|
|| is (DeclarationType == SharedStaticDestructor))
|
|
{
|
|
symbol.callTip = "~this()";
|
|
}
|
|
else static if (is (DeclarationType == StaticConstructor)
|
|
|| is (DeclarationType == SharedStaticConstructor))
|
|
{
|
|
symbol.callTip = "this()";
|
|
}
|
|
else
|
|
{
|
|
string parameterString;
|
|
if (dec.parameters !is null)
|
|
{
|
|
parameterString = formatNode(dec.parameters);
|
|
foreach (Parameter p; dec.parameters.parameters)
|
|
{
|
|
SemanticSymbol* parameter = new SemanticSymbol;
|
|
parameter.name = p.name.value.dup;
|
|
parameter.type = p.type;
|
|
parameter.kind = CompletionKind.variableName;
|
|
parameter.startLocation = p.name.startIndex;
|
|
symbol.children ~= parameter;
|
|
// Log.trace("Parameter ", parameter.name, " added to ", symbol.name);
|
|
}
|
|
}
|
|
else
|
|
parameterString = "()";
|
|
|
|
static if (is (DeclarationType == FunctionDeclaration))
|
|
symbol.callTip = "%s %s%s".format(formatNode(dec.returnType),
|
|
dec.name.value, parameterString);
|
|
else
|
|
symbol.callTip = "this%s".format(parameterString);
|
|
}
|
|
|
|
symbol.protection = protection;
|
|
symbol.kind = CompletionKind.functionName;
|
|
symbol.parent = currentSymbol;
|
|
currentSymbol = symbol;
|
|
|
|
if (dec.functionBody !is null)
|
|
{
|
|
dec.functionBody.accept(this);
|
|
}
|
|
currentSymbol = symbol.parent;
|
|
currentSymbol.children ~= symbol;
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/// Current protection type
|
|
TokenType protection;
|
|
|
|
/// Current symbol
|
|
SemanticSymbol* currentSymbol;
|
|
|
|
/// The module
|
|
SemanticSymbol* rootSymbol;
|
|
|
|
/// Package and module name
|
|
string[] moduleName;
|
|
|
|
/// Current scope
|
|
Scope* currentScope;
|
|
}
|
|
|
|
|
|
struct SemanticConverter
|
|
{
|
|
public:
|
|
|
|
this(const(SemanticSymbol)* symbol, Scope* scopes)
|
|
{
|
|
this.sc = scopes;
|
|
this.symbol = symbol;
|
|
}
|
|
|
|
void convertModule()
|
|
{
|
|
convertSemanticSymbol(symbol);
|
|
assert (current !is null);
|
|
resolveTypes(current);
|
|
}
|
|
|
|
void resolveTypes(const(ACSymbol*) symbol)
|
|
{
|
|
}
|
|
|
|
ACSymbol* convertSemanticSymbol(const(SemanticSymbol)* symbol)
|
|
{
|
|
ACSymbol* s = null;
|
|
with (CompletionKind) final switch (symbol.kind)
|
|
{
|
|
case moduleName:
|
|
s = convertAggregateDeclaration(symbol);
|
|
current = s;
|
|
break;
|
|
case className:
|
|
case interfaceName:
|
|
case structName:
|
|
case unionName:
|
|
case enumName:
|
|
s = convertAggregateDeclaration(symbol);
|
|
break;
|
|
case enumMember:
|
|
s = convertEnumMember(symbol);
|
|
break;
|
|
case variableName:
|
|
case memberVariableName:
|
|
s = convertVariableDeclaration(symbol);
|
|
break;
|
|
case functionName:
|
|
s = convertFunctionDeclaration(symbol);
|
|
break;
|
|
case aliasName:
|
|
s = convertAliasDeclaration(symbol);
|
|
break;
|
|
case packageName:
|
|
assert (false, "Not implemented");
|
|
case keyword:
|
|
case array:
|
|
case assocArray:
|
|
case dummy:
|
|
assert (false, "This should never be reached");
|
|
}
|
|
if (sc !is null && symbol.kind != CompletionKind.moduleName)
|
|
{
|
|
sc.getScopeByCursor(s.location).symbols ~= s;
|
|
// Log.trace("Set scope location");
|
|
}
|
|
return s;
|
|
}
|
|
|
|
ACSymbol* convertAliasDeclaration(const(SemanticSymbol)* symbol)
|
|
{
|
|
ACSymbol* ac = new ACSymbol;
|
|
ac.name = symbol.name;
|
|
ac.kind = symbol.kind;
|
|
ac.location = symbol.location;
|
|
// TODO: Set type
|
|
return ac;
|
|
}
|
|
|
|
ACSymbol* convertVariableDeclaration(const(SemanticSymbol)* symbol)
|
|
{
|
|
ACSymbol* ac = new ACSymbol;
|
|
ac.name = symbol.name;
|
|
ac.kind = CompletionKind.variableName;
|
|
ac.location = symbol.location;
|
|
if (symbol.type !is null)
|
|
ac.type = resolveType(symbol.type);
|
|
// TODO: Handle auto
|
|
return ac;
|
|
}
|
|
|
|
ACSymbol* convertEnumMember(const(SemanticSymbol)* symbol)
|
|
{
|
|
ACSymbol* ac = new ACSymbol;
|
|
ac.name = symbol.name;
|
|
ac.kind = symbol.kind;
|
|
ac.location = symbol.location;
|
|
// TODO: type
|
|
return ac;
|
|
}
|
|
|
|
ACSymbol* convertFunctionDeclaration(const(SemanticSymbol)* symbol)
|
|
{
|
|
// Log.trace("Converted ", symbol.name, " ", symbol.kind, " ", symbol.location);
|
|
ACSymbol* ac = new ACSymbol;
|
|
ac.name = symbol.name;
|
|
ac.kind = symbol.kind;
|
|
ac.location = symbol.location;
|
|
ac.callTip = symbol.callTip;
|
|
if (symbol.type !is null)
|
|
ac.type = resolveType(symbol.type);
|
|
return ac;
|
|
}
|
|
|
|
ACSymbol* convertAggregateDeclaration(const(SemanticSymbol)* symbol)
|
|
{
|
|
// Log.trace("Converted ", symbol.name, " ", symbol.kind, " ", symbol.location);
|
|
ACSymbol* ac = new ACSymbol;
|
|
ac.name = symbol.name;
|
|
ac.kind = symbol.kind;
|
|
ac.location = symbol.location;
|
|
if (symbol.kind == CompletionKind.className
|
|
|| symbol.kind == CompletionKind.structName)
|
|
{
|
|
ACSymbol* thisSymbol = new ACSymbol("this",
|
|
CompletionKind.variableName, ac);
|
|
ac.parts ~= thisSymbol;
|
|
}
|
|
auto temp = current;
|
|
current = ac;
|
|
foreach (child; symbol.children)
|
|
current.parts ~= convertSemanticSymbol(child);
|
|
current = temp;
|
|
return ac;
|
|
}
|
|
|
|
private:
|
|
|
|
|
|
ACSymbol* resolveType(const Type t)
|
|
in
|
|
{
|
|
assert (t !is null);
|
|
assert (t.type2 !is null);
|
|
}
|
|
body
|
|
{
|
|
ACSymbol* s;
|
|
if (t.type2.builtinType != TokenType.invalid)
|
|
s = convertBuiltinType(t.type2);
|
|
else if (t.type2.typeConstructor != TokenType.invalid)
|
|
s = resolveType(t.type2.type);
|
|
else if (t.type2.symbol !is null)
|
|
{
|
|
if (t.type2.symbol.dot)
|
|
Log.error("TODO: global scoped symbol handling");
|
|
string[] symbolParts = expandSymbol(
|
|
t.type2.symbol.identifierOrTemplateChain);
|
|
|
|
}
|
|
foreach (suffix; t.typeSuffixes)
|
|
s = processSuffix(s, suffix);
|
|
return null;
|
|
}
|
|
|
|
static string[] expandSymbol(const IdentifierOrTemplateChain chain)
|
|
{
|
|
string[] strings = new string[chain.identifiersOrTemplateInstances.length];
|
|
for (size_t i = 0; i != chain.identifiersOrTemplateInstances.length; ++i)
|
|
{
|
|
auto identOrTemplate = chain.identifiersOrTemplateInstances[i];
|
|
strings[i] = identOrTemplate.templateInstance is null ?
|
|
identOrTemplate.identifier.value.dup
|
|
: identOrTemplate.identifier.value.dup;
|
|
}
|
|
return strings;
|
|
}
|
|
|
|
static ACSymbol* processSuffix(ACSymbol* symbol, const TypeSuffix suffix)
|
|
{
|
|
if (suffix.star)
|
|
return symbol;
|
|
if (suffix.array || suffix.type)
|
|
{
|
|
ACSymbol* s = new ACSymbol;
|
|
s.parts = arraySymbols;
|
|
s.type = symbol;
|
|
s.qualifier = suffix.array ? SymbolQualifier.array : SymbolQualifier.assocArray;
|
|
return s;
|
|
}
|
|
if (suffix.parameters)
|
|
{
|
|
Log.error("TODO: Function type suffix");
|
|
return null;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
ACSymbol* current;
|
|
Scope* sc;
|
|
const(SemanticSymbol)* symbol;
|
|
}
|
|
|
|
const(ACSymbol)*[] convertAstToSymbols(Module m)
|
|
{
|
|
SemanticVisitor visitor = new SemanticVisitor(SemanticType.partial);
|
|
visitor.visit(m);
|
|
SemanticConverter converter = SemanticConverter(visitor.rootSymbol, null);
|
|
converter.convertModule();
|
|
return cast(typeof(return)) converter.current.parts;
|
|
}
|
|
|
|
const(Scope)* generateAutocompleteTrees(const(Token)[] tokens)
|
|
{
|
|
Module m = parseModule(tokens, null);
|
|
SemanticVisitor visitor = new SemanticVisitor(SemanticType.full);
|
|
visitor.visit(m);
|
|
SemanticConverter converter = SemanticConverter(visitor.rootSymbol,
|
|
visitor.currentScope);
|
|
converter.convertModule();
|
|
return converter.sc;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
unittest
|
|
{
|
|
auto source = q{
|
|
module foo;
|
|
|
|
struct Bar { int x; int y;}
|
|
}c;
|
|
Module m = parseTestCode(source);
|
|
BasicSemanticVisitor visitor = new BasicSemanticVisitor;
|
|
visitor.visit(m);
|
|
assert (visitor.rootSymbol !is null);
|
|
assert (visitor.rootSymbol.name == "foo");
|
|
SemanticSymbol* bar = visitor.root.getPartByName("Bar");
|
|
assert (bar !is null);
|
|
assert (bar.getPartByName("x") !is null);
|
|
assert (bar.getPartByName("y") !is null);
|
|
}
|