diff --git a/autocomplete.d b/autocomplete.d new file mode 100644 index 0000000..3890865 --- /dev/null +++ b/autocomplete.d @@ -0,0 +1,22 @@ +// 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 autocomplete; + +string dotComplete(Tokens)(ref Tokens tokens, size_t cursorPosition) +{ + return ""; +} + +string parenComplete(Tokens)(ref Tokens tokens, size_t cursorPosition) +{ + return ""; +} + + +string symbolComplete(Tokens)(ref Tokens tokens, size_t cursorPosition) +{ + return ""; +} diff --git a/main.d b/main.d index 37ad276..347632e 100644 --- a/main.d +++ b/main.d @@ -19,23 +19,8 @@ import std.range; import std.d.lexer; import highlighter; - -pure nothrow bool isLineOfCode(TokenType t) -{ - switch(t) - { - case TokenType.semicolon: - case TokenType.while_: - case TokenType.if_: - case TokenType.for_: - case TokenType.foreach_: - case TokenType.foreach_reverse_: - case TokenType.case_: - return true; - default: - return false; - } -} +import autocomplete; +import stats; /** * Loads any import directories specified in /etc/dmd.conf. @@ -140,33 +125,7 @@ int main(string[] args) return 1; } - if (tokenCount || sloc) - { - LexerConfig config; - config.tokenStyle = TokenStyle.doNotReplaceSpecial; - ulong[] counts = new ulong[args.length - 1]; - foreach (i, arg; parallel(args[1..$])) - { - config.fileName = arg; - uint count; - auto f = File(arg); - import core.stdc.stdlib; - ubyte[] buffer = (cast(ubyte*)malloc(f.size))[0..f.size]; - scope(exit) free(buffer.ptr); - //uninitializedArray!(ubyte[])(f.size); - foreach (t; byToken(f.rawRead(buffer), config)) - { - if (tokenCount) - ++counts[i]; - else if (isLineOfCode(t.type)) - ++counts[i]; - } - - } - foreach(i; 0 .. counts.length) - writefln("%s: %d", args[i + 1], counts[i]); - } - else if (highlight) + if (highlight) { LexerConfig config; config.iterStyle = IterationStyle.everything; @@ -177,6 +136,36 @@ int main(string[] args) args.length == 1 ? "stdin" : args[1]); return 0; } + else + { + LexerConfig config; + + bool usingStdin = args.length == 3; + config.fileName = usingStdin ? "stdin" : args[1]; + File f = usingStdin ? stdin : File(args[1]); + auto bytes = usingStdin ? cast(ubyte[]) [] : uninitializedArray!(ubyte[])(f.size); + + f.rawRead(bytes); + + auto tokens = byToken(bytes, config); + if (sloc) + { + printLineCount(stdout, tokens); + } + else if (tokenCount) + { + printTokenCount(stdout, tokens); + } + else if (dotComplete) + { + } + else if (parenComplete) + { + } + else if (symbolComplete) + { + } + } return 0; } diff --git a/perftest.sh b/perftest.sh new file mode 100755 index 0000000..5d4c48e --- /dev/null +++ b/perftest.sh @@ -0,0 +1,9 @@ +echo -e "file\tstd.d.lexer dmd\tstd.d.lexer ldc\tstd.d.lexer gdc\tdmd" +for i in `ls ../phobos/std/*.d`; do + f=`echo $i | sed s/.*phobos\\\\///` + dmdt=`avgtime -q -r 200 ./dscanner-dmd --tokenCount $i | grep "Median" | sed "s/.*: //"` + ldct=`avgtime -q -r 200 ./dscanner-ldc --tokenCount $i | grep "Median" | sed "s/.*: //"` + gdct=`avgtime -q -r 200 ./dscanner-gdc --tokenCount $i | grep "Median" | sed "s/.*: //"` + gcct=`avgtime -q -r 200 ~/src/dmd-lexer/src/dmd $i | grep "Median" | sed "s/.*: //"` + echo -e "${f}\t${dmdt}\t${ldct}\t${gdct}\t${gcct}" +done diff --git a/stats.d b/stats.d new file mode 100644 index 0000000..6bf76a4 --- /dev/null +++ b/stats.d @@ -0,0 +1,48 @@ +// 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 stats; + +import std.stdio; +import std.d.lexer; + +pure nothrow bool isLineOfCode(TokenType t) +{ + switch(t) + { + case TokenType.semicolon: + case TokenType.while_: + case TokenType.if_: + case TokenType.for_: + case TokenType.foreach_: + case TokenType.foreach_reverse_: + case TokenType.case_: + return true; + default: + return false; + } +} + +void printTokenCount(Tokens)(File output, ref Tokens tokens) +{ + ulong count; + while(!tokens.empty) + { + tokens.popFront(); + ++count; + } + output.writefln("%d", count); +} + +void printLineCount(Tokens)(File output, ref Tokens tokens) +{ + ulong count; + foreach (t; tokens) + { + if (isLineOfCode(t.type)) + ++count; + } + output.writefln("%d", count); +} diff --git a/types.d b/types.d deleted file mode 100644 index aa09dc9..0000000 --- a/types.d +++ /dev/null @@ -1,838 +0,0 @@ -// 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:", 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:", 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 - */ - void writeCtagsTo(File file, 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); - } - - sort(tags); - file.writeln("!_TAG_FILE_FORMAT 2"); - file.writeln("!_TAG_FILE_SORTED 1"); - file.writeln("!_TAG_PROGRAM_URL https://github.com/Hackerpilot/Dscanner/"); - foreach (tag; tags) - { - file.writeln(tag); - } - } -} - - -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; - } -}