// Copyright Brian Schott (Sir Alaran) 2012. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) module types; import std.stdio; import std.array; import std.range; import std.algorithm; import std.typecons; import std.string; /** * Returns: s with any quote characters backslash-escaped */ string escapeJSON(string s) { return s.replace("\"", "\\\""); } unittest { assert(escapeJSON("abc\"def") == "abc\\\"def"); } /** * Writes a string in JSON fromat to the given file * Params: * f = the file to write to * name = the name of the json attribute * value = the value of the json attribute * indent = the indent level */ void writeJSONString(File f, const string name, const string value, uint indent = 0) { if (value is null) f.write(std.array.replicate(" ", indent), "\"", name, "\" : null"); else f.write(std.array.replicate(" ", indent), "\"", name, "\" : \"", escapeJSON(value), "\""); } /** * Writes a string array in JSON format to the given file * f = the file to write to * name = the name of the json attribute * values = the strings that should be written * indent = the indent level */ void writeJSONString(File f, const string name, const string[] values, uint indent = 0) { f.writeln(std.array.replicate(" ", indent), "\"", name, "\" : ["); foreach(i, v; values) { f.write(std.array.replicate(" ", indent + 1), "\"", escapeJSON(v), "\""); if (i + 1 < values.length) f.writeln(","); else f.writeln(); } f.write(std.array.replicate(" ", indent), "]"); } /** * Attributes common to everything interesting */ abstract class Base { public: /// Sybol name string name; /// Line number of declaration uint line; /// Attributes such as "ref", "const", etc. string[] attributes; /// Protection level such as "public", protected, etc. string protection; /// See_also: writeJSONString void writeJSONTo(File f, uint indent) const { f.writeln(std.array.replicate(" ", indent + 1), "{"); printMembers(f, indent + 2); f.write("\n", std.array.replicate(" ", indent + 1), "}"); } protected: void printMembers(File f, uint indent = 0) const { writeJSONString(f, "name", name == null ? "<>" : name, indent); f.writeln(","); f.write(std.array.replicate(" ", indent), "\"line\" : ", line); f.writeln(","); writeJSONString(f, "protection", protection, indent); f.writeln(","); writeJSONString(f, "attributes", attributes, indent); } } /** * Alias Declaration */ class Alias : Base { public: string aliasedType; protected: override void printMembers(File f, uint indent = 0) const { super.printMembers(f, indent); f.writeln(","); writeJSONString(f, "aliasedType", aliasedType, indent); } } /** * Varible declaration */ class Variable : Base { public: /// Variable type string type; protected: override void printMembers(File f, uint indent = 0) const { super.printMembers(f, indent); f.writeln(","); writeJSONString(f, "type", type, indent); } } /** * Base class for any type that can be a template */ abstract class Templateable : Base { public: /// Template constraint, which may be null string constraint; /// Template parameters, may be empty string[] templateParameters; protected: override void printMembers(File f, uint indent = 0) const { super.printMembers(f, indent); f.writeln(","); writeJSONString(f, "constraint", constraint, indent); f.writeln(","); writeJSONString(f, "templateParameters", templateParameters, indent); } } /** * Stuff common to struct, interface, and class. */ class Struct : Templateable { public: /// List of methods Function[] functions; /// List of member variables; may be empty Variable[] variables; /// List of aliases defined Alias[] aliases; /// Source code character position of the beginning of the struct body size_t bodyStart; /// Source code character position of the end of the struct body size_t bodyEnd; string getMemberType(string name) const { foreach (f; functions) if (f.name == name) return f.returnType; foreach (v; variables) if (v.name == name) return v.type; return null; } string[] getFunctionDocs(string functionName) { auto app = appender!(string[])(); foreach (fun; functions) { if (fun.name != functionName) continue; app.put(fun.documentString()); } return app.data; // TODO: Try this again with a newer DMD //return array(map!(a => a.documentString())( // filter!(a => a.name == functionName)(functions))); } string[] getCtags(string fileName) { auto app = appender!(string[])(); app.put(format("%s\t%s\t%d;\"\ts", name, fileName, line)); foreach (Function f; functions) { app.put(format("%s\t%s\t%d;\"\tf\tarity:%d\tstruct:%s", f.name, fileName, f.line, f.parameters.length, name)); } foreach (Variable v; variables) { app.put(format("%s\t%s\t%d;\"\tm\tstruct:%s", v.name, fileName, v.line, name)); } return app.data(); } protected: override void printMembers(File f, uint indent = 0) const { super.printMembers(f, indent); f.writeln(",\n", std.array.replicate(" ", indent), "\"functions\" : ["); foreach(i, fun; functions) { fun.writeJSONTo(f, indent); if (i + 1 < functions.length) f.writeln(","); else f.writeln(); } f.writeln(std.array.replicate(" ", indent), "],\n", std.array.replicate(" ", indent), "\"variables\" : ["); foreach(i, var; variables) { var.writeJSONTo(f, indent); if (i + 1 < variables.length) f.writeln(","); else f.writeln(); } f.writeln(std.array.replicate(" ", indent), "],\n", std.array.replicate(" ", indent), "\"aliases\" : ["); foreach(i, al; aliases) { al.writeJSONTo(f, indent); if (i + 1 < aliases.length) f.writeln(","); else f.writeln(); } f.write(std.array.replicate(" ", indent), "]"); } } /** * Functions and delegates */ class Function : Templateable { public: /// Function return type string returnType; /// Parameter list; may be empty Variable[] parameters; string documentString() { string r = returnType ~ " " ~ name ~ "("; foreach (i, param; parameters) { r ~= param.type ~ " " ~ param.name; if (i + 1 < parameters.length) r ~= ",\\n\\t"; } r ~= ")"; return r; } protected: override void printMembers(File f, uint indent) const { super.printMembers(f, indent); f.write(",\n"); f.writeln(std.array.replicate(" ", indent), "\"parameters\" : ["); foreach(i, params; parameters) { params.writeJSONTo(f, indent); if (i + 1 < parameters.length) f.writeln(","); else f.writeln(); } f.write(std.array.replicate(" ", indent), "],\n"); writeJSONString(f, "returnType", returnType, indent); } } /** * class and interface */ class Inherits : Struct { public: /** * List of interfaces and classes that this inherits or implements; may * be empty */ string[] baseClasses; override string[] getCtags(string fileName) { auto app = appender!(string[])(); app.put(format("%s\t%s\t%d;\"\tc\tinherits:%s", name, fileName, line, array(baseClasses.joiner(",")))); foreach (Function f; functions) { app.put(format("%s\t%s\t%d;\"\tf\tarity:%d\tstruct:%s", f.name, fileName, f.line, f.parameters.length, name)); } foreach (Variable v; variables) { app.put(format("%s\t%s\t%d;\"\tm\tstruct:%s", v.name, fileName, v.line, name)); } return app.data(); } protected: override void printMembers(File f, uint indent = 0) const { super.printMembers(f, indent); f.writeln(","); writeJSONString(f, "baseClasses", baseClasses, indent); } } /** * enum member */ struct EnumMember { uint line; string name; string type; } /** * enum */ class Enum : Base { public: /// True in the case of "enum a {b, c}" or /// False in the case of "enum x = 5" or "enum :double {x = 12.9, y = 8.3}" bool hasMembers; /// Enum members; may be empty EnumMember[] members; string[] getCtags(string fileName) const { auto app = appender!(string[])(); app.put(format("%s\t%s\t%d;\"\tg", name, fileName, line)); if (hasMembers) { foreach (EnumMember member; members) { app.put(format("%s\t%s\t%d;\"\te\tenum:%s", member.name, fileName, member.line, name)); } } return app.data; } protected: override void printMembers(File f, uint indent = 0) const { super.printMembers(f, indent); f.writeln(",\n", std.array.replicate(" ", indent), "\"members\" : ["); foreach(i, member; members) { f.writeln(std.array.replicate(" ", indent + 1), "{"); writeJSONString(f, "name", member.name, indent + 2); f.writeln(","); writeJSONString(f, "type", member.type, indent + 2); f.writeln(","); f.writeln(std.array.replicate(" ", indent + 2), "\"line\" : ", member.line); f.write(std.array.replicate(" ", indent + 1), "}"); if (i + 1 < members.length) f.writeln(","); else f.writeln(); } f.write(std.array.replicate(" ", indent), "]"); } } class HasDeclarations { public: /// List of interfaces declared in this module Inherits[] interfaces; /// List of classes declared in this module Inherits[] classes; /// List of functions declared in this module Function[] functions; /// List of unions declared in this module Struct[] unions; /// List of variables declared in this module Variable[] variables; /// List of structs declared in this module Struct[] structs; /// List of enums declared in this module Enum[] enums; /// List of aliases declared in this module Alias[] aliases; } /** * Module is a container class for the other classes */ class Module : HasDeclarations { public: /// Module name. Will be blank if there is no module statement string name; /// List of other modules that are imported by this one string[] imports; /// Combine this module with another one void merge(Module other) { interfaces.insertInPlace(interfaces.length, other.interfaces); classes.insertInPlace(classes.length, other.classes); functions.insertInPlace(functions.length, other.functions); unions.insertInPlace(unions.length, other.unions); variables.insertInPlace(variables.length, other.variables); structs.insertInPlace(structs.length, other.structs); enums.insertInPlace(enums.length, other.enums); imports.insertInPlace(imports.length, other.imports); } /** * Prints a JSON representation of this module to the given file */ void writeJSONTo(File f) const { uint indent = 0; f.writeln("{"); writeJSONString(f, "name", name, indent + 1); f.writeln(","); writeJSONString(f, "imports", imports, indent + 1); f.writeln(",\n \"interfaces\" : ["); foreach(i, inter; interfaces) { inter.writeJSONTo(f, indent + 1); if (i + 1 < interfaces.length) f.writeln(","); else f.writeln(); } f.writeln(" ],\n \"classes\" : ["); foreach(i, cl; classes) { cl.writeJSONTo(f, indent + 1); if (i + 1 < classes.length) f.writeln(","); else f.writeln(); } f.writeln(" ],\n \"structs\" : ["); foreach(i, str; structs) { str.writeJSONTo(f, indent + 1); if (i + 1 < structs.length) f.writeln(","); else f.writeln(); } f.writeln(" ],\n \"unions\" : ["); foreach(i, un; unions) { un.writeJSONTo(f, indent + 1); if (i + 1 < unions.length) f.writeln(","); else f.writeln(); } f.writeln(" ],\n \"functions\" : ["); foreach(i, fun; functions) { fun.writeJSONTo(f, indent + 1); if (i + 1 < functions.length) f.writeln(","); else f.writeln(); } f.writeln(" ],\n \"variables\" : ["); foreach(i, var; variables) { var.writeJSONTo(f, indent + 1); if (i + 1 < variables.length) f.writeln(","); else f.writeln(); } f.writeln(" ],\n \"enums\" : ["); foreach(i, en; enums) { en.writeJSONTo(f, indent + 1); if (i + 1 < enums.length) f.writeln(","); else f.writeln(); } f.writeln(" ],\n \"aliases\" : ["); foreach(i, a; aliases) { a.writeJSONTo(f, indent + 1); if (i + 1 < aliases.length) f.writeln(","); else f.writeln(); } f.writeln(" ]\n}"); } /** * Standards: http://ctags.sourceforge.net/FORMAT */ string[] getCtags(string fileName) { string[] tags; foreach (Enum e; enums) { tags ~= e.getCtags(fileName); } foreach (Variable v; variables) { tags ~= format("%s\t%s\t%d;\"\tv", v.name, fileName, v.line); } foreach (Function f; functions) { tags ~= format("%s\t%s\t%d;\"\tf\tarity:%d", f.name, fileName, f.line, f.parameters.length); } foreach (Inherits c; classes) { tags ~= c.getCtags(fileName); } foreach (Inherits i; interfaces) { tags ~= i.getCtags(fileName); } foreach (Struct s; structs) { tags ~= s.getCtags(fileName); } return tags; } } immutable(string[string][string]) typeProperties; // Yo dawg I heard you like maps... immutable(string[string]) floatProperties; immutable(string[string]) integralProperties; immutable(string[string]) commonProperties; immutable(string[string]) arrayProperties; static this() { // <#> means "its own type" // for example float.max is of type float floatProperties = [ "alignof" : "int", "dig" : "<#>", "epsilon" : "<#>", "im" : "<#>", "infinity" : "<#>", "init" : "<#>", "mangleof" : "string", "mant_dig" : "int", "max" : "<#>", "max_10_exp" : "int", "max_­exp" : "int", "min_10_­exp" : "int", "min_­exp" : "int", "min_nor­mal" : "<#>", "nan" : "<#>", "re" : "<#>", "sizeof" : "size_t" ]; integralProperties = [ "alignof" : "int", "init" : "<#>", "mangleof" : "string", "max" : "<#>", "min" : "<#>", "sizeof" : "size_t", "stringof" : "string" ]; commonProperties = [ "alignof" : "int", "init" : "<#>", "mangleof" : "string", "stringof" : "string" ]; arrayProperties = [ "alignof" : "int", "init" : "<#>", "length" : "size_t", "mangleof" : "string", "ptr" : "<#>*", "stringof" : "string", ]; typeProperties = [ "bool" : commonProperties, "byte" : integralProperties, "ubyte" : integralProperties, "short" : integralProperties, "ushort" : integralProperties, "int" : integralProperties, "uint" : integralProperties, "long" : integralProperties, "ulong" : integralProperties, "cent" : integralProperties, "ucent" : integralProperties, "float" : floatProperties, "double" : floatProperties, "real" : floatProperties, "ifloat" : floatProperties, "idouble" : floatProperties, "ireal" : floatProperties, "cfloat" : floatProperties, "cdouble" : floatProperties, "creal" : floatProperties, "char" : commonProperties, "wchar" : commonProperties, "dchar" : commonProperties, "ptrdiff_t" : integralProperties, "size_t" : integralProperties, "string" : arrayProperties, "wstring" : arrayProperties, "dstring" : arrayProperties ]; } class CompletionContext { public: this(Module mod) { this.currentModule = mod; } Tuple!(string, string)[string] getMembersOfType(string name) { // Arrays if (name.length > 2 && name[$ - 2 .. $] == "[]") { Tuple!(string, string)[string] typeMap; foreach(k, v; arrayProperties) typeMap[k] = Tuple!(string, string)(v, "m"); return typeMap; } // Basic types auto tp = name in typeProperties; if (tp !is null) { Tuple!(string, string)[string] typeMap; foreach (k, v; *tp) typeMap[k] = Tuple!(string, string)(v.replace("<#>", name), "m"); return typeMap; } // User-defined types foreach (m; chain(modules, [currentModule])) { foreach (inherits; chain(m.interfaces, m.classes)) { if (inherits.name != name) continue; Tuple!(string, string)[string] typeMap; foreach (var; inherits.variables) typeMap[var.name] = Tuple!(string, string)(var.type, "m"); foreach (fun; inherits.functions) typeMap[fun.name] = Tuple!(string, string)(fun.returnType, "f"); foreach (parent; inherits.baseClasses) { foreach (k, v; getMembersOfType(parent)) { typeMap[k] = v; } } typeMap["classInfo"] = Tuple!(string, string)("TypeInfo_Class", "m"); return typeMap; } foreach (s; chain(m.structs, m.unions)) { if (s.name != name) continue; Tuple!(string, string)[string] typeMap; foreach (var; s.variables) typeMap[var.name] = Tuple!(string, string)(var.type, "m"); foreach (fun; s.functions) typeMap[fun.name] = Tuple!(string, string)(fun.returnType, "f"); return typeMap; } foreach (Enum e; m.enums) { if (e.name != name) continue; Tuple!(string, string)[string] typeMap; foreach (member; e.members) typeMap[member.name] = Tuple!(string, string)(member.type, "e"); return typeMap; } } return null; } Struct[] getStructsContaining(size_t cursorPosition) { auto app = appender!(Struct[])(); foreach(s; chain(currentModule.structs, currentModule.interfaces, currentModule.classes, currentModule.unions)) { if (s.bodyStart <= cursorPosition && s.bodyEnd >= cursorPosition) app.put(s); } return app.data(); } string[] getCallTipsFor(string container, string functionName, size_t cursorPosition) { if (container == null || container.length == 0 || container == "void") { // Try member functions first if the cursor is inside of a class // or structure definiton Struct[] structs = getStructsContaining(cursorPosition); foreach (s; structs) { auto docs = s.getFunctionDocs(functionName); if (docs.length > 0) return docs; } // Try global functions if the above failed. return getCallTipsFor(functionName); } foreach (m; chain(modules, [currentModule])) { foreach (s; chain(m.structs, m.interfaces, m.classes, m.unions)) { if (s.name != container) continue; return s.getFunctionDocs(functionName); } } return []; } void addModule(Module mod) { modules ~= mod; } Module currentModule; Module[] modules; string[] importDirectories; private: string[] getCallTipsFor(string functionName) { stderr.writeln("Getting call tips for ", functionName); auto app = appender!(string[])(); foreach (m; chain(modules, [currentModule])) { foreach (fun; m.functions) { if (fun.name == functionName) app.put(fun.documentString()); } } return app.data; } }