diff --git a/README.md b/README.md index f1191e8..dc140d5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,229 @@ -Dscanner -======== +# Overview +Dscanner is a tool used to analyze D source code. + +### Options +* **--dotComplete** _sourceFile_ _cursorPosition_ - Provide autocompletion for the +insertion of the dot operator. The cursor position is the character position in +the **file**, not the position in the line. +* **--sloc** _sourceFiles_ - count the number of logical lines of code in the given +source files. +* **--json** _sourceFile_ - Generate a JSON summary of the given source file +* **--parenComplete** _sourceFile_ _cursorPosition_ - Provides a listing of function +parameters or pre-defined version identifiers at the cursor position. The cursor +position is the character position in the **file**, not the line. +* **--highlight** _sourceFile_ - Syntax-highlight the given source file. The +resulting HTML will be written to standard output. +* **-I** _includePath_ - Include _includePath_ in the list of paths used to search +for imports. By default dscanner will search in the current working directory as +well as any paths specified in /etc/dmd.conf. + +# Dot Completion + +# Paren Completion + +# JSON output +Generates a JSON summary of the input file. + +### Example +The given D code: + module example; + + import std.stdio; + + interface Iface { + double interfaceMethod(); + } + + class SomeClass(T) if (isSomeString!T) : IFace { + public: + this() {} + void doStuff(T); + override double interfaceMethod() {} + private: + T theTee; + } + + int freeFunction(int x) { return x + x; } + + void main(string[] args) { + + } + +is transformed into the following JSON markup: + + { + "name" : "example", + "imports" : [ + "std.stdio" + ], + "interfaces" : [ + { + "name" : "Iface", + "line" : 5, + "protection" : "public", + "attributes" : [ + ], + "constraint" : "", + "templateParameters" : [ + ], + "functions" : [ + { + "name" : "interfaceMethod", + "line" : 6, + "protection" : "", + "attributes" : [ + ], + "constraint" : "", + "templateParameters" : [ + ], + "parameters" : [ + ], + "returnType" : "double" + } + ], + "variables" : [ + ], + "baseClasses" : [ + ] + } + ], + "classes" : [ + { + "name" : "SomeClass", + "line" : 9, + "protection" : "public", + "attributes" : [ + ], + "constraint" : "if (isSomeString!T)", + "templateParameters" : [ + "T" + ], + "functions" : [ + { + "name" : "this", + "line" : 11, + "protection" : "", + "attributes" : [ + ], + "constraint" : "", + "templateParameters" : [ + ], + "parameters" : [ + ], + "returnType" : "" + }, + { + "name" : "doStuff", + "line" : 12, + "protection" : "", + "attributes" : [ + ], + "constraint" : "", + "templateParameters" : [ + ], + "parameters" : [ + { + "name" : "", + "line" : 0, + "protection" : "", + "attributes" : [ + ], + "type" : "T" + } + ], + "returnType" : "void" + }, + { + "name" : "interfaceMethod", + "line" : 13, + "protection" : "", + "attributes" : [ + "override" + ], + "constraint" : "", + "templateParameters" : [ + ], + "parameters" : [ + ], + "returnType" : "double" + } + ], + "variables" : [ + { + "name" : "theTee", + "line" : 15, + "protection" : "private", + "attributes" : [ + ], + "type" : "T" + } + ], + "baseClasses" : [ + "IFace" + ] + } + ], + "structs" : [ + ], + "structs" : [ + ], + "functions" : [ + { + "name" : "freeFunction", + "line" : 18, + "protection" : "", + "attributes" : [ + ], + "constraint" : "", + "templateParameters" : [ + ], + "parameters" : [ + { + "name" : "x", + "line" : 18, + "protection" : "", + "attributes" : [ + ], + "type" : "int" + } + ], + "returnType" : "int" + }, + { + "name" : "main", + "line" : 20, + "protection" : "", + "attributes" : [ + ], + "constraint" : "", + "templateParameters" : [ + ], + "parameters" : [ + { + "name" : "args", + "line" : 20, + "protection" : "", + "attributes" : [ + ], + "type" : "string[]" + } + ], + "returnType" : "void" + } + ], + "variables" : [ + ], + "enums" : [ + ] + } + +# Line of Code count +This option counts the logical lines of code in the given source files, not +simply the physical lines. More specifically, it counts the number of +semicolons, **if**, **while**, **case**, **foreach**, and **for** tokens in the +given files. + +# Highlighting +Syntax highlights the given file in HTML format. Output is written to _stdout_. +The CSS styling information is currently hard-coded. -Swiss-army knife for D source code \ No newline at end of file diff --git a/autocomplete.d b/autocomplete.d new file mode 100644 index 0000000..d601c1a --- /dev/null +++ b/autocomplete.d @@ -0,0 +1,281 @@ +/******************************************************************************* + * The MIT License + * + * Copyright (c) 2012 Brian Schott (Sir Alaran) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +import std.range; +import std.algorithm; +import std.array; +import std.conv; +import std.stdio; +import std.typecons; + +import parser; +import langutils; +import types; +import tokenizer; + +immutable string[] versions = ["AIX", "all", "Alpha", "ARM", "BigEndian", "BSD", + "Cygwin", "D_Coverage", "D_Ddoc", "DigitalMars", "D_InlineAsm_X86", + "D_InlineAsm_X86_64", "D_LP64", "D_NET", "D_PIC", "D_Version2", + "FreeBSD", "GNU", "HPPA", "HPPA64", "Hurd", "IA64", "LDC", "linux", + "LittleEndian", "MinGW", "MIPS", "MIPS64", "none", "OpenBSD", "OSX", + "Posix", "PPC", "PPC64", "S390", "S390X", "SDC", "SH", "SH64", "SkyOS", + "Solaris", "SPARC", "SPARC64", "SysV3", "SysV4", "unittest", "Win32", + "Win64", "Windows", "X86", "X86_64" +]; + +string[] callChainBackwards(const Token[] tokens, size_t index) +{ + if (index == 0) + return [tokens[index].value]; + string[] callChain; + string current; + loop: while(true) + { + switch(tokens[index].type) + { + case TokenType.tThis: + case TokenType.identifier: + case TokenType.TYPES_BEGIN: .. case TokenType.TYPES_END: + current = tokens[index].value ~ current; + callChain = current ~ callChain; + current = ""; + if (index == 0) + break loop; + else + --index; + if (tokens[index] == TokenType.not) + callChain = callChain[1 .. $]; + break; + case TokenType.rBracket: + tokens.skipBrackets(index); + current ~= "[]"; + break; + case TokenType.rParen: + tokens.skipParens(index); + break; + case TokenType.not: + case TokenType.dot: + if (index == 0) + break loop; + else + --index; + break; + default: + break loop; + } + } + return callChain; +} + + +string[] callChainForwards(const Token[] tokens, size_t index) +{ + string[] callChain; + while (index < tokens.length) + { + switch(tokens[index].type) + { + case TokenType.tNew: + ++index; + break; + case TokenType.tThis: + case TokenType.identifier: + case TokenType.TYPES_BEGIN: .. case TokenType.TYPES_END: + callChain ~= tokens[index++].value; + break; + case TokenType.lParen: + tokens.skipParens(index); + break; + case TokenType.lBracket: + tokens.skipBrackets(index); + callChain[$ - 1] ~= "[i]"; + break; + case TokenType.not: + ++index; + if (tokens.startsWith(TokenType.lParen)) + tokens.skipParens(index); + else + ++index; + break; + default: + break; + } + if (index >= tokens.length || tokens[index] != TokenType.dot) + break; + else + ++index; + } + return callChain; +} + + +struct AutoComplete +{ + this(const (Token)[] tokens, CompletionContext context) + { + this.tokens = tokens; + this.context = context; + } + + string getTypeOfExpression(string[] chain, const Token[] tokens, size_t cursor) + { + if (chain.length == 0) + return "void"; + auto type = typeOfVariable(chain[0], cursor); + if (type == "void") + return type; + chain = chain[1 .. $]; + while (chain.length >= 1) + { + auto typeMap = context.getMembersOfType(type); + if (typeMap is null) + return "void"; + auto memberType = typeMap[chain[0]][0]; + if (memberType is null) + return "void"; + type = memberType; + chain = chain[1 .. $]; + } + return type; + } + + /** + * This is where the magic happens + */ + string typeOfVariable(string symbol, size_t cursor) + { + // int is of type int, double of type double, and so on + if (symbol in typeProperties) + return symbol; + + if (context.getMembersOfType(symbol)) + return symbol; + + // Arbitrarily define the depth of the cursor position as zero + // iterate backwards through the code to try to find the variable + int depth = 0; + auto preceedingTokens = assumeSorted(tokens).lowerBound(cursor); + auto index = preceedingTokens.length - 1; + while (true) + { + if (preceedingTokens[index] == TokenType.lBrace) + --depth; + else if (preceedingTokens[index] == TokenType.rBrace) + ++depth; + else if (depth <= 0 && preceedingTokens[index].value == symbol) + { + // Found the symbol, now determine if it was declared here. + auto p = preceedingTokens[index - 1]; + if ((p == TokenType.tAuto || p == TokenType.tImmutable + || p == TokenType.tConst) + && preceedingTokens[index + 1] == TokenType.assign) + { + auto chain = callChainForwards(tokens, index + 2); + return getTypeOfExpression(chain, tokens, cursor); + } + if (p == TokenType.identifier + || (p.type > TokenType.TYPES_BEGIN + && p.type < TokenType.TYPES_END)) + { + return preceedingTokens[index - 1].value; + } + } + if (index == 0) + break; + else + --index; + } + + // Find all struct or class bodies that we're in. + // Check for the symbol in those class/struct/interface bodies + // if match is found, return it + auto structs = context.getStructsContaining(cursor); + if (symbol == "this" && structs.length > 0) + return minCount!("a.bodyStart > b.bodyStart")(structs)[0].name; + foreach (s; structs) + { + auto t = s.getMemberType(symbol); + if (t !is null) + return t; + } + return "void"; + } + + string symbolAt(size_t cursor) const + { + auto r = assumeSorted(tokens).lowerBound(cursor)[$ - 1]; + if (r.value.length + r.startIndex > cursor) + return r.value; + else + return null; + } + + string parenComplete(size_t cursor) + { + auto index = assumeSorted(tokens).lowerBound(cursor).length; + if (index > 2) + index -= 2; + else + return []; + if (tokens[index] == TokenType.tVersion) + { + return to!string(array(join(map!`a ~ "?1"`(versions), " "))); + } + return ""; + } + + string dotComplete(size_t cursor) + { + auto index = assumeSorted(tokens).lowerBound(cursor).length; + if (index > 2) + index -= 2; + else + return ""; + auto t = tokens[index]; + string[] chain = callChainBackwards(tokens, index); + auto type = getTypeOfExpression(chain, tokens, cursor); + + if (type && type in typeProperties) + { + string r; + foreach (i, prop; typeProperties[type]) + if (i == typeProperties.length) + r = r ~ prop; + else + r = r ~ prop ~ " "; + return r; + } + + const Tuple!(string, string)[string] typeMap = context.getMembersOfType(type); + if (typeMap is null) + return ""; + auto app = appender!(string[])(); + foreach (k, t; typeMap) + app.put(k ~ t[1]); + return to!string(array(join(sort(app.data), " "))); + } + + const(Token)[] tokens; + CompletionContext context; +} diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..8c2fc91 --- /dev/null +++ b/build.sh @@ -0,0 +1,2 @@ +dmd *.d -release -noboundscheck -O -w -wi -m64 -property -ofdscanner -D +#dmd *.d -g -unittest -m64 -w -wi -property -oftokenizer diff --git a/codegen.d b/codegen.d new file mode 100644 index 0000000..a8d2df3 --- /dev/null +++ b/codegen.d @@ -0,0 +1,104 @@ +module codegen; + +import std.range; + + +class Trie(K, V) if (isInputRange!K): TrieNode!(K, V) +{ + /** + * Adds the given value to the trie with the given key + */ + void add(K key, V value) pure + { + TrieNode!(K,V) current = this; + foreach(keyPart; key) + { + if ((keyPart in current.children) is null) + { + auto node = new TrieNode!(K, V); + current.children[keyPart] = node; + current = node; + } + else + current = current.children[keyPart]; + } + current.value = value; + } +} + +class TrieNode(K, V) if (isInputRange!K) +{ + V value; + TrieNode!(K,V)[ElementType!K] children; +} + +string printCaseStatements(K, V)(TrieNode!(K,V) node, string indentString) +{ + string caseStatement = ""; + foreach(dchar k, TrieNode!(K,V) v; node.children) + { + caseStatement ~= indentString; + caseStatement ~= "case '"; + caseStatement ~= k; + caseStatement ~= "':\n"; + caseStatement ~= indentString; + caseStatement ~= "\tcurrentToken.value ~= '"; + caseStatement ~= k; + caseStatement ~= "';\n"; + caseStatement ~= indentString; + caseStatement ~= "\tcurrentToken.lineNumber = lineNumber;"; + caseStatement ~= indentString; + caseStatement ~= "\t++endIndex;\n"; + if (v.children.length > 0) + { + caseStatement ~= indentString; + caseStatement ~= "\tif (endIndex >= inputString.length)\n"; + caseStatement ~= indentString; + caseStatement ~= "\t{\n"; + caseStatement ~= indentString; + caseStatement ~= "\t\tcurrentToken.type = " ~ node.children[k].value; + caseStatement ~= ";\n"; + caseStatement ~= indentString; + caseStatement ~= "\t\tbreak;\n"; + caseStatement ~= indentString; + caseStatement ~= "\t}\n"; + caseStatement ~= indentString; + caseStatement ~= "\tswitch (inputString[endIndex])\n"; + caseStatement ~= indentString; + caseStatement ~= "\t{\n"; + caseStatement ~= printCaseStatements(v, indentString ~ "\t"); + caseStatement ~= indentString; + caseStatement ~= "\tdefault:\n"; + caseStatement ~= indentString; + caseStatement ~= "\t\tcurrentToken.type = "; + caseStatement ~= v.value; + caseStatement ~= ";\n"; + caseStatement ~= indentString; + caseStatement ~= "\t\tbreak;\n"; + caseStatement ~= indentString; + caseStatement ~= "\t}\n"; + caseStatement ~= indentString; + caseStatement ~= "\tbreak;\n"; + } + else + { + caseStatement ~= indentString; + caseStatement ~= "\tcurrentToken.type = "; + caseStatement ~= v.value; + caseStatement ~= ";\n"; + caseStatement ~= indentString; + caseStatement ~= "\tbreak;\n"; + } + } + return caseStatement; +} + +string generateCaseTrie(string[] args ...) +{ + auto t = new Trie!(string, string); + for(int i = 0; i < args.length; i+=2) + { + t.add(args[i], args[i+1]); + } + return printCaseStatements(t, ""); +} diff --git a/highlighter.d b/highlighter.d new file mode 100644 index 0000000..872aa0c --- /dev/null +++ b/highlighter.d @@ -0,0 +1,84 @@ +/******************************************************************************* + * The MIT License + * + * Copyright (c) 2012 Brian Schott (Sir Alaran) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +import std.stdio; +import langutils; +import std.array; + +void writeSpan(string cssClass, string value) +{ + stdout.write(``, value.replace("<", "<"), ``); +} + +void highlight(Token[] tokens) +{ + stdout.writeln(q"[ + +
+ + + +]");
+
+ foreach (Token t; tokens)
+ {
+ switch (t.type)
+ {
+ case TokenType.KEYWORDS_BEGIN: .. case TokenType.KEYWORDS_END:
+ writeSpan("keyword", t.value);
+ break;
+ case TokenType.TYPES_BEGIN: .. case TokenType.TYPES_END:
+ writeSpan("type", t.value);
+ break;
+ case TokenType.comment:
+ writeSpan("comment", t.value);
+ break;
+ case TokenType.stringLiteral:
+ writeSpan("string", t.value);
+ break;
+ case TokenType.numberLiteral:
+ writeSpan("number", t.value);
+ break;
+ case TokenType.OPERATORS_BEGIN: .. case TokenType.OPERATORS_END:
+ writeSpan("operator", t.value);
+ break;
+ case TokenType.PROPERTIES_BEGIN: .. case TokenType.PROPERTIES_END:
+ writeSpan("property", t.value);
+ break;
+ default:
+ stdout.write(t.value.replace("<", "<"));
+ break;
+ }
+ }
+ stdout.writeln("\n");
+}
diff --git a/langutils.d b/langutils.d
new file mode 100644
index 0000000..44657c8
--- /dev/null
+++ b/langutils.d
@@ -0,0 +1,427 @@
+/*******************************************************************************
+ * The MIT License
+ *
+ * Copyright (c) 2012 Brian Schott (Sir Alaran)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modif y, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ******************************************************************************/
+
+module langutils;
+
+/**
+ * Returns: true if input is a access attribute
+ */
+pure nothrow bool isAccessAttribute(TokenType input)
+{
+ return input > TokenType.PROTECTION_BEGIN && input < TokenType.PROTECTION_END;
+}
+
+/**
+ * See_also: isAttribute(TokenType)
+ */
+pure nothrow bool isAttribute(ref const Token token)
+{
+ return isAttribute(token.type);
+}
+
+/**
+ * Returns: true if the given token type is an attribute, false otherwise
+ */
+pure nothrow bool isAttribute(TokenType input)
+{
+ if (isAccessAttribute(input))
+ return true;
+ return input > TokenType.ATTRIBUTES_BEGIN && input < TokenType.ATTRIBUTES_END;
+}
+
+/**
+ * Returns: the token type for the given string. Defaults to "identifier"
+ */
+pure nothrow TokenType lookupTokenType(const string input)
+{
+ immutable(TokenType)* type = input in tokenLookup;
+ if (type !is null)
+ return *type;
+ else
+ return TokenType.identifier;
+}
+
+
+/**
+ * Listing of all the tokens in the D language
+ */
+enum TokenType: uint
+{
+// Operators
+ OPERATORS_BEGIN,
+ div, /// /
+ divEquals, /// /=
+ dot, /// .
+ slice, // ..
+ vararg, /// ...
+ bitAnd, /// &
+ bitAndEquals, /// &=
+ lAnd, /// &&
+ bitOr, /// |
+ bitOrEquals, /// |=
+ lOr, /// ||
+ minus, /// -
+ minusEquals, /// -=
+ uMinus, /// --
+ plus, /// +
+ plusEquals, /// +=
+ uPlus, /// ++
+ less, /// <
+ lessEqual, /// <=
+ shiftLeft, /// <<
+ shiftLeftEqual, /// <<=
+ lessOrGreater, /// <>
+ lessEqualGreater, // <>=
+ greater, /// >
+ greaterEqual, /// >=
+ shiftRightEqual, /// >>=
+ unsignedShiftRightEqual, /// >>>=
+ shiftRight, /// >>
+ unsignedShiftRight, /// >>>
+ not, /// !
+ notEquals, /// !=
+ notLessEqualGreater, /// !<>
+ unordered, /// !<>=
+ notLess, /// !<
+ notLessEqual, /// !<=
+ notGreater, /// !>
+ notGreaterEqual, /// !>=
+ lParen, /// $(LPAREN)
+ rParen, /// $(RPAREN)
+ lBracket, /// [
+ rBracket, /// ]
+ lBrace, /// {
+ rBrace, /// }
+ ternary, /// ?
+ comma, /// ,
+ semicolon, /// ;
+ colon, /// :
+ dollar, /// $
+ assign, /// =
+ equals, /// ==
+ star, /// *
+ mulEquals, /// *=
+ mod, /// %
+ modEquals, /// %=
+ xor, /// ^
+ xorEquals, /// ^=
+ pow, /// ^^
+ powEquals, /// ^^=
+ tilde, /// ~
+ catEquals, /// ~=
+ hash, // #
+ goesTo, // =>
+ OPERATORS_END,
+
+// Types
+ TYPES_BEGIN,
+ tString, /// string
+ tBool, /// bool,
+ tByte, /// byte,
+ tCdouble, /// cdouble,
+ tCent, /// cent,
+ tCfloat, /// cfloat,
+ tChar, /// char,
+ tCreal, /// creal,
+ tDchar, /// dchar,
+ tDouble, /// double,
+ tFloat, /// float,
+ tUbyte, /// ubyte,
+ tUcent, /// ucent,
+ tUint, /// uint,
+ tUlong, /// ulong,
+ tShort, /// short,
+ tReal, /// real,
+ tLong, /// long,
+ tInt, /// int,
+ tFunction, /// function,
+ tIdouble, /// idouble,
+ tIreal, /// ireal,
+ tWchar, /// wchar,
+ tVoid, /// void,
+ tUshort, /// ushort,
+ tIfloat, /// if loat,
+ TYPES_END,
+ tTemplate, /// template,
+
+// Keywords
+ KEYWORDS_BEGIN,
+ ATTRIBUTES_BEGIN,
+ tExtern, /// extern,
+ tAlign, /// align,
+ tPragma, /// pragma,
+ tDeprecated, /// deprecated,
+ PROTECTION_BEGIN,
+ tPackage, /// package,
+ tPrivate, /// private,
+ tProtected, /// protected,
+ tPublic, /// public,
+ tExport, /// export,
+ PROTECTION_END,
+ tStatic, /// static,
+ tSynchronized, /// synchronized,
+ tFinal, /// final
+ tAbstract, /// abstract,
+ tConst, /// const,
+ tAuto, /// auto,
+ tScope, /// scope,
+ t__gshared, /// __gshared,
+ tShared, // shared,
+ tImmutable, // immutable,
+ tInout, // inout,
+ atDisable, /// @disable
+ ATTRIBUTES_END,
+ tAlias, /// alias,
+ tAsm, /// asm,
+ tAssert, /// assert,
+ tBody, /// body,
+ tBreak, /// break,
+ tCase, /// case,
+ tCast, /// cast,
+ tCatch, /// catch,
+ tClass, /// class,
+ tContinue, /// continue,
+ tDebug, /// debug,
+ tDefault, /// default,
+ tDelegate, /// delegate,
+ tDelete, /// delete,
+ tDo, /// do,
+ tElse, /// else,
+ tEnum, /// enum,
+ tFalse, /// false,
+ tFinally, /// finally,
+ tFor, /// for,
+ tForeach, /// foreach,
+ tForeach_reverse, /// foreach_reverse,
+ tGoto, /// goto,
+ tIf, /// if ,
+ tImport, /// import,
+ tIn, /// in,
+ tInterface, /// interface,
+ tInvariant, /// invariant,
+ tIs, /// is,
+ tLazy, /// lazy,
+ tMacro, /// macro,
+ tMixin, /// mixin,
+ tModule, /// module,
+ tNew, /// new,
+ tNothrow, /// nothrow,
+ tNull, /// null,
+ tOut, /// out,
+ tOverride, /// override,
+ tPure, /// pure,
+ tRef, /// ref,
+ tReturn, /// return,
+ tStruct, /// struct,
+ tSuper, /// super,
+ tSwitch, /// switch ,
+ tThis, /// this,
+ tThrow, /// throw,
+ tTrue, /// true,
+ tTry, /// try,
+ tTypedef, /// typedef,
+ tTypeid, /// typeid,
+ tTypeof, /// typeof,
+ tUnion, /// union,
+ tUnittest, /// unittest,
+ tVersion, /// version,
+ tVolatile, /// volatile,
+ tWhile, /// while ,
+ tWith, /// with,
+ KEYWORDS_END,
+
+// Constants
+ CONSTANTS_BEGIN,
+ t__FILE__, /// __FILE__,
+ t__LINE__, /// __LINE__,
+
+ t__thread, /// __thread,
+ t__traits, /// __traits,
+ CONSTANTS_END,
+
+// Properties
+ PROPERTIES_BEGIN,
+
+ atProperty, /// @property
+ atSafe, /// @safe
+ atSystem, /// @system
+ atTrusted, /// @trusted
+ PROPERTIES_END,
+
+// Misc
+ MISC_BEGIN,
+ comment, /// /** comment */ or // comment or ///comment
+ stringLiteral, /// "a string"
+ numberLiteral, /// int, float, etc...
+ identifier,
+ whitespace, /// whitespace
+ blank,
+ MISC_END,
+}
+
+
+/**
+ * lookup table for converting strings to tokens
+ */
+immutable TokenType[string] tokenLookup;
+
+
+static this()
+{
+ tokenLookup = [
+ "abstract" : TokenType.tAbstract,
+ "alias" : TokenType.tAlias,
+ "align" : TokenType.tAlign,
+ "asm" : TokenType.tAsm,
+ "assert" : TokenType.tAssert,
+ "auto" : TokenType.tAuto,
+ "body" : TokenType.tBody,
+ "bool" : TokenType.tBool,
+ "break" : TokenType.tBreak,
+ "byte" : TokenType.tByte,
+ "case" : TokenType.tCase,
+ "cast" : TokenType.tCast,
+ "catch" : TokenType.tCatch,
+ "cdouble" : TokenType.tCdouble,
+ "cent" : TokenType.tCent,
+ "cfloat" : TokenType.tCfloat,
+ "char" : TokenType.tChar,
+ "class" : TokenType.tClass,
+ "const" : TokenType.tConst,
+ "continue" : TokenType.tContinue,
+ "creal" : TokenType.tCreal,
+ "dchar" : TokenType.tDchar,
+ "debug" : TokenType.tDebug,
+ "default" : TokenType.tDefault,
+ "delegate" : TokenType.tDelegate,
+ "delete" : TokenType.tDelete,
+ "deprecated" : TokenType.tDeprecated,
+ "do" : TokenType.tDo,
+ "double" : TokenType.tDouble,
+ "else" : TokenType.tElse,
+ "enum" : TokenType.tEnum,
+ "export" : TokenType.tExport,
+ "extern" : TokenType.tExtern,
+ "false" : TokenType.tFalse,
+ "final" : TokenType.tFinal,
+ "finally" : TokenType.tFinally,
+ "float" : TokenType.tFloat,
+ "for" : TokenType.tFor,
+ "foreach" : TokenType.tForeach,
+ "foreach_reverse" : TokenType.tForeach_reverse,
+ "function" : TokenType.tFunction,
+ "goto" : TokenType.tGoto,
+ "idouble" : TokenType.tIdouble,
+ "if" : TokenType.tIf,
+ "ifloat" : TokenType.tIfloat,
+ "immutable" : TokenType.tImmutable,
+ "import" : TokenType.tImport,
+ "in" : TokenType.tIn,
+ "inout" : TokenType.tInout,
+ "int" : TokenType.tInt,
+ "interface" : TokenType.tInterface,
+ "invariant" : TokenType.tInvariant,
+ "ireal" : TokenType.tIreal,
+ "is" : TokenType.tIs,
+ "lazy" : TokenType.tLazy,
+ "long" : TokenType.tLong,
+ "macro" : TokenType.tMacro,
+ "mixin" : TokenType.tMixin,
+ "module" : TokenType.tModule,
+ "new" : TokenType.tNew,
+ "nothrow" : TokenType.tNothrow,
+ "null" : TokenType.tNull,
+ "out" : TokenType.tOut,
+ "override" : TokenType.tOverride,
+ "package" : TokenType.tPackage,
+ "pragma" : TokenType.tPragma,
+ "private" : TokenType.tPrivate,
+ "protected" : TokenType.tProtected,
+ "public" : TokenType.tPublic,
+ "pure" : TokenType.tPure,
+ "real" : TokenType.tReal,
+ "ref" : TokenType.tRef,
+ "return" : TokenType.tReturn,
+ "scope" : TokenType.tScope,
+ "shared" : TokenType.tShared,
+ "short" : TokenType.tShort,
+ "static" : TokenType.tStatic,
+ "struct" : TokenType.tStruct,
+ "string" : TokenType.tString,
+ "super" : TokenType.tSuper,
+ "switch" : TokenType.tSwitch,
+ "synchronized" : TokenType.tSynchronized,
+ "template" : TokenType.tTemplate,
+ "this" : TokenType.tThis,
+ "throw" : TokenType.tThrow,
+ "true" : TokenType.tTrue,
+ "try" : TokenType.tTry,
+ "typedef" : TokenType.tTypedef,
+ "typeid" : TokenType.tTypeid,
+ "typeof" : TokenType.tTypeof,
+ "ubyte" : TokenType.tUbyte,
+ "ucent" : TokenType.tUcent,
+ "uint" : TokenType.tUint,
+ "ulong" : TokenType.tUlong,
+ "union" : TokenType.tUnion,
+ "unittest" : TokenType.tUnittest,
+ "ushort" : TokenType.tUshort,
+ "version" : TokenType.tVersion,
+ "void" : TokenType.tVoid,
+ "volatile" : TokenType.tVolatile,
+ "wchar" : TokenType.tWchar,
+ "while" : TokenType.tWhile,
+ "with" : TokenType.tWith,
+ "__FILE__" : TokenType.t__FILE__,
+ "__LINE__" : TokenType.t__LINE__,
+ "__gshared" : TokenType.t__gshared,
+ "__thread" : TokenType.t__thread,
+ "__traits" : TokenType.t__traits,
+ "@disable" : TokenType.atDisable,
+ "@property" : TokenType.atProperty,
+ "@safe" : TokenType.atSafe,
+ "@system" : TokenType.atSystem,
+ "@trusted" : TokenType.atTrusted,
+ ];
+}
+
+struct Token
+{
+ TokenType type;
+ string value;
+ uint lineNumber;
+ size_t startIndex;
+ bool opEquals(ref const(Token) other) const
+ {
+ return other.type == type && other.value == value;
+ }
+ bool opEquals(string range) const { return range == value; }
+ bool opEquals(TokenType t) const { return type == t; }
+ int opCmp(size_t i) const
+ {
+ if (i > startIndex) return -1;
+ if (i < startIndex) return 1;
+ return 0;
+ }
+}
diff --git a/main.d b/main.d
new file mode 100644
index 0000000..24aef01
--- /dev/null
+++ b/main.d
@@ -0,0 +1,199 @@
+/*******************************************************************************
+ * The MIT License
+ *
+ * Copyright (c) 2012 Brian Schott (Sir Alaran)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ******************************************************************************/
+
+import std.file;
+import std.stdio;
+import std.algorithm;
+import std.conv;
+import std.array;
+import std.path;
+import std.regex;
+import std.getopt;
+import std.parallelism;
+import types;
+import tokenizer;
+import parser;
+import langutils;
+import autocomplete;
+import highlighter;
+
+pure bool isLineOfCode(TokenType t)
+{
+ switch(t)
+ {
+ case TokenType.semicolon:
+ case TokenType.tWhile:
+ case TokenType.tIf:
+ case TokenType.tFor:
+ case TokenType.tForeach:
+ case TokenType.tCase:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/**
+ * Loads any import directories specified in /etc/dmd.conf.
+ * Bugs: Only works on Linux
+ * Returns: the paths specified as -I options in /etc/dmd.conf
+ */
+string[] loadDefaultImports()
+{
+version(linux)
+{
+ string path = "/etc/dmd.conf";
+ if (!exists(path))
+ return [];
+ string[] rVal;
+ auto file = File(path, "r");
+ foreach(char[] line; file.byLine())
+ {
+ if (!line.startsWith("DFLAGS"))
+ continue;
+ while ((line = line.find("-I")).length > 0)
+ {
+ auto end = std.string.indexOf(line, " ");
+ auto importDir = line[2 .. end].idup;
+ rVal ~= importDir;
+ line = line[end .. $];
+ }
+ }
+ return rVal;
+}
+else
+{
+ return [];
+}
+}
+
+/**
+ * Returns: the absolute path of the given module, or null if it could not be
+ * found.
+ */
+string findAbsPath(string[] dirs, string moduleName)
+{
+ // For file names
+ if (endsWith(moduleName, ".d") || endsWith(moduleName, ".di"))
+ {
+ if (startsWith(moduleName, "/"))
+ return moduleName;
+ else
+ return getcwd() ~ "/" ~ moduleName;
+ }
+
+ // Try to find the file name from a module name like "std.stdio"
+ foreach(dir; dirs)
+ {
+ string fileLocation = dir ~ "/" ~ replace(moduleName, ".", "/");
+ string dfile = fileLocation ~ ".d";
+ if (exists(dfile) && isFile(dfile))
+ {
+ return dfile;
+ }
+ if (exists(fileLocation ~ ".di") && isFile(fileLocation ~ ".di"))
+ {
+ return fileLocation ~ ".di";
+ }
+ }
+ stderr.writeln("Could not locate import ", moduleName, " in ", dirs);
+ return null;
+}
+
+string[] loadConfig()
+{
+ string path = expandTilde("~/.dscanner");
+ string[] dirs;
+ if (exists(path))
+ {
+ auto f = File(path, "r");
+ scope(exit) f.close();
+
+ auto trimRegex = ctRegex!("\\s*$");
+ foreach(string line; lines(f))
+ {
+ dirs ~= replace(line, trimRegex, "");
+ }
+ }
+ foreach(string importDir; loadDefaultImports()) {
+ dirs ~= importDir;
+ }
+ return dirs;
+}
+
+
+void main(string[] args)
+{
+ string[] importDirs;
+ bool sloc;
+ bool dotComplete;
+ bool json;
+ bool parenComplete;
+ bool highlight;
+ getopt(args, "I", &importDirs, "dotComplete", &dotComplete, "sloc", &sloc,
+ "json", &json, "parenComplete", &parenComplete, "highlight", &highlight);
+
+ importDirs ~= loadConfig();
+
+ if (sloc)
+ {
+ writeln(args[1..$].map!(a => a.readText().tokenize())().joiner()
+ .count!(a => isLineOfCode(a.type))());
+ return;
+ }
+
+ if (highlight)
+ {
+ highlighter.highlight(args[1].readText().tokenize(IterationStyle.EVERYTHING));
+ return;
+ }
+
+ if (dotComplete || parenComplete)
+ {
+ auto tokens = args[1].readText().tokenize();
+ auto mod = parseModule(tokens);
+ auto context = new CompletionContext(mod);
+ foreach (im; parallel(mod.imports))
+ {
+ auto p = findAbsPath(importDirs, im);
+ if (p is null || !p.exists())
+ continue;
+ context.addModule(p.readText().tokenize().parseModule());
+ }
+ auto complete = AutoComplete(tokens, context);
+ if (parenComplete)
+ writeln(complete.parenComplete(to!size_t(args[2])));
+ else if (dotComplete)
+ writeln(complete.dotComplete(to!size_t(args[2])));
+ return;
+ }
+
+ if (json)
+ {
+ auto tokens = tokenize(readText(args[1]));
+ auto mod = parseModule(tokens);
+ mod.writeJSONTo(stdout);
+ }
+}
+
diff --git a/parser.d b/parser.d
new file mode 100644
index 0000000..e9109f0
--- /dev/null
+++ b/parser.d
@@ -0,0 +1,830 @@
+/*******************************************************************************
+ * The MIT License
+ *
+ * Copyright (c) 2012 Brian Schott (Sir Alaran)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ******************************************************************************/
+
+
+module parser;
+
+import std.stream;
+import std.array;
+import std.stdio;
+import std.algorithm;
+
+import types, tokenizer;
+import langutils;
+
+
+/**
+ * Params:
+ * tokens = the array of tokens
+ * index = an index into tokens such that tokens[index].type == open
+ * open = the opening delimiter
+ * close = the closing delimiter
+ * Returns: all tokens that are between the balanced delimiters that start at
+ * tokens[index], not including the delimiters. If the delimiters in tokens
+ * are not balanced, this function will return tokens[index + 1 .. $];
+ */
+const(Token)[] betweenBalanced(const Token[] tokens, ref size_t index, TokenType open,
+ TokenType close)
+in
+{
+ assert (tokens[index] == open);
+}
+body
+{
+ ++index;
+ size_t start = index;
+ int depth = 1;
+ while (depth > 0 && index < tokens.length)
+ {
+ if (tokens[index] == open) ++depth;
+ else if (tokens[index] == close) --depth;
+ ++index;
+ }
+ return tokens[start .. index - 1];
+}
+
+
+/**
+ * See_also: betweenBalanced
+ */
+const(Token)[] betweenBalancedBraces(const Token[] tokens, ref size_t index)
+{
+ return betweenBalanced(tokens, index, TokenType.lBrace, TokenType.rBrace);
+}
+
+
+/**
+ * See_also: betweenBalanced
+ */
+const(Token)[] betweenBalancedParens(const Token[] tokens, ref size_t index)
+{
+ return betweenBalanced(tokens, index, TokenType.lParen, TokenType.rParen);
+}
+
+
+/**
+ * See_also: betweenBalanced
+ */
+const(Token)[] betweenBalancedBrackets(const Token[] tokens, ref size_t index)
+{
+ return betweenBalanced(tokens, index, TokenType.lBracket, TokenType.rBracket);
+}
+
+void skipBalanced(alias Op, alias Cl)(const Token[] tokens, ref size_t index)
+{
+ int depth = tokens[index] == Op ? 1 : -1;
+ int deltaIndex = depth;
+ index += deltaIndex;
+ for (; index < tokens.length && index > 0 && depth != 0; index += deltaIndex)
+ {
+ switch (tokens[index].type)
+ {
+ case Op: ++depth; break;
+ case Cl: --depth; break;
+ default: break;
+ }
+ }
+}
+
+void skipParens(const Token[] tokens, ref size_t index)
+{
+ skipBalanced!(TokenType.lParen, TokenType.rParen)(tokens, index);
+}
+
+void skipBrackets(const Token[] tokens, ref size_t index)
+{
+ skipBalanced!(TokenType.lBracket, TokenType.rBracket)(tokens, index);
+}
+
+/**
+ * Params:
+ * tokens = the token array to examine
+ * index = an indext into tokens such that tokens[index].type == open
+ * open = the opening delimiter
+ * close = the closing delimiter
+ * Returns: a string representing the contents of the two delimiters. This will
+ * not preserve whitespace, but it will place a single space character after
+ * a comma and between identifiers.
+ */
+string content(const Token[] tokens, ref size_t index, TokenType open, TokenType close)
+in
+{
+ assert (tokens[index] == open);
+}
+body
+{
+ index++;
+ auto app = appender!string();
+ int depth = 1;
+ while (depth > 0 && index < tokens.length)
+ {
+ if (tokens[index] == open) ++depth;
+ else if (tokens[index] == close) --depth;
+ else if (tokens[index] == TokenType.comma)
+ {
+ app.put(", ");
+ }
+ else
+ app.put(tokens[index].value);
+ ++index;
+ }
+ return app.data;
+}
+
+
+/**
+ * See_also: content
+ */
+string parenContent(const Token[]tokens, ref size_t index)
+{
+ return "(" ~ content(tokens, index, TokenType.lParen, TokenType.rParen) ~ ")";
+}
+
+
+/**
+ * See_also: content
+ */
+string bracketContent(const Token[]tokens, ref size_t index)
+{
+ return "[" ~ content(tokens, index, TokenType.lBracket, TokenType.rBracket) ~ "]";
+}
+
+
+/**
+ * Advances index until it indexes a character in tokens after a right brace if
+ * index initially indexed a right brace, or advances index until it indexes a
+ * character after a simicolon otherwise.
+ */
+void skipBlockStatement(const Token[] tokens, ref size_t index)
+{
+ if (tokens[index] == TokenType.lBrace)
+ betweenBalancedBraces(tokens, index);
+ else
+ {
+ skipPastNext(tokens, TokenType.semicolon, index);
+ }
+}
+
+
+/**
+ * Advances index until it indexes a character in tokens directly after a token
+ * of type type. This function handles nesting of braces, brackets, and
+ * parenthesis
+ */
+void skipPastNext(const Token[] tokens, TokenType type, ref size_t index)
+{
+ while (index < tokens.length)
+ {
+ if (tokens[index].type == TokenType.lBrace)
+ betweenBalancedBraces(tokens, index);
+ else if (tokens[index].type == TokenType.lParen)
+ betweenBalancedParens(tokens, index);
+ else if (tokens[index].type == TokenType.lBracket)
+ betweenBalancedBrackets(tokens, index);
+ else if (tokens[index].type == type)
+ {
+ ++index;
+ return;
+ }
+ else
+ ++index;
+ }
+}
+
+string parseTypeDeclaration(const Token[] tokens, ref size_t index)
+{
+ auto type = tokens[index++].value.idup;
+ buildingType: while (index < tokens.length)
+ {
+ switch (tokens[index].type)
+ {
+ case TokenType.lBracket:
+ type ~= bracketContent(tokens, index);
+ break;
+ case TokenType.not:
+ type ~= tokens[index++].value;
+ if (tokens[index] == TokenType.lParen)
+ type ~= parenContent(tokens, index);
+ else
+ type ~= tokens[index++].value;
+ break;
+ case TokenType.star:
+ case TokenType.bitAnd:
+ type ~= tokens[index++].value;
+ break;
+ default:
+ break buildingType;
+ }
+ }
+ return type;
+}
+
+/**
+ * Parses a module from a token array.
+ * Params:
+ * protection = the default protection level for a block statement
+ * attributes = the default attributes for a block statement
+ * Returns: the parsed module
+ */
+Module parseModule(const Token[] tokens, string protection = "public", string[] attributes = [])
+{
+ string type;
+ string name;
+ string localProtection = "";
+ string[] localAttributes = [];
+
+ void resetLocals()
+ {
+ type = "";
+ name = "";
+ localProtection = "";
+ localAttributes = [];
+ }
+
+ Module mod = new Module;
+ size_t index = 0;
+ while(index < tokens.length)
+ {
+ switch(tokens[index].type)
+ {
+ case TokenType.tElse:
+ case TokenType.tMixin:
+ case TokenType.tAssert:
+ ++index;
+ tokens.skipBlockStatement(index);
+ break;
+ case TokenType.tAlias:
+ tokens.skipBlockStatement(index);
+ break;
+ case TokenType.tImport:
+ mod.imports ~= parseImports(tokens, index);
+ resetLocals();
+ break;
+ case TokenType.tVersion:
+ ++index;
+ if (tokens[index] == TokenType.lParen)
+ {
+ tokens.betweenBalancedParens(index);
+ if (tokens[index] == TokenType.lBrace)
+ mod.merge(parseModule(betweenBalancedBraces(tokens, index),
+ localProtection.empty() ? protection : localProtection,
+ attributes));
+ }
+ else if (tokens[index] == TokenType.assign)
+ tokens.skipBlockStatement(index);
+ break;
+ case TokenType.atDisable:
+ case TokenType.atProperty:
+ case TokenType.atSafe:
+ case TokenType.atSystem:
+ case TokenType.tAbstract:
+ case TokenType.tConst:
+ case TokenType.tDeprecated:
+ case TokenType.tExtern:
+ case TokenType.tFinal:
+ case TokenType.t__gshared:
+ case TokenType.tImmutable:
+ case TokenType.tInout:
+ case TokenType.tNothrow:
+ case TokenType.tOverride:
+ case TokenType.tPure:
+ case TokenType.tScope:
+ case TokenType.tShared:
+ case TokenType.tStatic:
+ case TokenType.tSynchronized:
+ auto tmp = tokens[index++].value;
+ if (tokens[index] == TokenType.lParen)
+ type = tmp ~ parenContent(tokens, index);
+ else if (tokens[index] == TokenType.colon)
+ {
+ index++;
+ attributes ~= tmp;
+ }
+ else
+ localAttributes ~= tmp;
+ break;
+ case TokenType.tAlign:
+ string attribute = tokens[index++].value;
+ if (tokens[index] == TokenType.lParen)
+ attribute ~= parenContent(tokens, index);
+ if (tokens[index] == TokenType.lBrace)
+ mod.merge(parseModule(betweenBalancedBraces(tokens, index),
+ localProtection.empty() ? protection : localProtection,
+ attributes ~ attribute));
+ else if (tokens[index] == TokenType.colon)
+ {
+ ++index;
+ attributes ~= attribute;
+ }
+ else
+ localAttributes ~= attribute;
+ break;
+ case TokenType.PROTECTION_BEGIN: .. case TokenType.PROTECTION_END:
+ string p = tokens[index++].value;
+ if (tokens[index] == TokenType.colon)
+ {
+ protection = p;
+ ++index;
+ }
+ else if (tokens[index] == TokenType.lBrace)
+ mod.merge(parseModule(betweenBalancedBraces(tokens, index),
+ p, attributes ~ localAttributes));
+ else
+ localProtection = p;
+ break;
+ case TokenType.tModule:
+ ++index;
+ while (index < tokens.length && tokens[index] != TokenType.semicolon)
+ mod.name ~= tokens[index++].value;
+ ++index;
+ resetLocals();
+ break;
+ case TokenType.tUnion:
+ mod.unions ~= parseUnion(tokens, index,
+ localProtection.empty() ? protection : localProtection,
+ localAttributes ~ attributes);
+ resetLocals();
+ break;
+ case TokenType.tClass:
+ mod.classes ~= parseClass(tokens, index,
+ localProtection.empty() ? protection : localProtection,
+ localAttributes ~ attributes);
+ resetLocals();
+ break;
+ case TokenType.tInterface:
+ mod.interfaces ~= parseInterface(tokens, index,
+ localProtection.empty() ? protection : localProtection,
+ localAttributes ~ attributes);
+ resetLocals();
+ break;
+ case TokenType.tStruct:
+ mod.structs ~= parseStruct(tokens, index,
+ localProtection.empty() ? protection : localProtection,
+ localAttributes ~ attributes);
+ resetLocals();
+ break;
+ case TokenType.tEnum:
+ mod.enums ~= parseEnum(tokens, index,
+ localProtection.empty() ? protection : localProtection,
+ localAttributes ~ attributes);
+ resetLocals();
+ break;
+ case TokenType.tTemplate:
+ ++index; // template
+ ++index; // name
+ if (tokens[index] == TokenType.lParen)
+ tokens.betweenBalancedParens(index); // params
+ if (tokens[index] == TokenType.lBrace)
+ tokens.betweenBalancedBraces(index); // body
+ resetLocals();
+ break;
+ case TokenType.TYPES_BEGIN: .. case TokenType.TYPES_END:
+ case TokenType.tAuto:
+ case TokenType.identifier:
+ if (type.empty())
+ {
+ type = tokens.parseTypeDeclaration(index);
+ }
+ else
+ {
+ name = tokens[index++].value;
+ if (index >= tokens.length) break;
+ if (tokens[index] == TokenType.lParen)
+ {
+ mod.functions ~= parseFunction(tokens, index, type, name,
+ tokens[index].lineNumber,
+ localProtection.empty() ? protection : localProtection,
+ attributes ~ localAttributes);
+ }
+ else
+ {
+ Variable v = new Variable;
+ v.name = name;
+ v.type = type;
+ v.attributes = localAttributes ~ attributes;
+ v.protection = localProtection.empty() ? protection : localProtection;
+ v.line = tokens[index].lineNumber;
+ mod.variables ~= v;
+ }
+ resetLocals();
+ }
+ break;
+ case TokenType.tUnittest:
+ ++index;
+ if (!tokens.empty() && tokens[index] == TokenType.lBrace)
+ tokens.skipBlockStatement(index);
+ resetLocals();
+ break;
+ case TokenType.tilde:
+ ++index;
+ if (tokens[index] == TokenType.tThis)
+ {
+ name = "~";
+ goto case;
+ }
+ break;
+ case TokenType.tThis:
+ name ~= tokens[index++].value;
+ if (tokens[index] == TokenType.lParen)
+ {
+ mod.functions ~= parseFunction(tokens, index, "", name,
+ tokens[index - 1].lineNumber,
+ localProtection.empty() ? protection : localProtection,
+ localAttributes ~ attributes);
+ }
+ resetLocals();
+ break;
+ default:
+ ++index;
+ break;
+ }
+ }
+ return mod;
+}
+
+
+/**
+ * Parses an import statement
+ * Returns: only the module names that were imported, not which symbols were
+ * selectively improted.
+ */
+string[] parseImports(const Token[] tokens, ref size_t index)
+{
+ assert(tokens[index] == TokenType.tImport);
+ ++index;
+ auto app = appender!(string[])();
+ string im;
+ while (index < tokens.length)
+ {
+ switch(tokens[index].type)
+ {
+ case TokenType.comma:
+ ++index;
+ app.put(im);
+ im = "";
+ break;
+ case TokenType.assign:
+ case TokenType.semicolon:
+ app.put(im);
+ ++index;
+ return app.data;
+ case TokenType.colon:
+ app.put(im);
+ tokens.skipBlockStatement(index);
+ return app.data;
+ default:
+ im ~= tokens[index++].value;
+ break;
+ }
+ }
+ return app.data;
+}
+
+
+/**
+ * Parses an enum declaration
+ */
+Enum parseEnum(const Token[] tokens, ref size_t index, string protection,
+ string[] attributes)
+in
+{
+ assert (tokens[index] == TokenType.tEnum);
+}
+body
+{
+ ++index;
+ Enum e = new Enum;
+ e.line = tokens[index].lineNumber;
+ e.name = tokens[index++].value;
+
+ if (tokens[index] == TokenType.colon)
+ {
+ ++index;
+ e.type = tokens[index++].value;
+ }
+ else
+ e.type = "uint";
+
+ if (tokens[index] != TokenType.lBrace)
+ {
+ tokens.skipBlockStatement(index);
+ return e;
+ }
+
+ auto r = betweenBalancedBraces(tokens, index);
+ for (size_t i = 0; i < r.length;)
+ {
+ if (r[i].type == TokenType.identifier)
+ {
+ EnumMember member;
+ member.line = r[i].lineNumber;
+ member.name = r[i].value;
+ e.members ~= member;
+ r.skipPastNext(TokenType.comma, i);
+ }
+ else
+ ++i;
+ }
+ return e;
+}
+
+
+/**
+ * Parses a function declaration
+ */
+Function parseFunction(const Token[] tokens, ref size_t index, string type,
+ string name, uint line, string protection, string[] attributes)
+in
+{
+ assert (tokens[index] == TokenType.lParen);
+}
+body
+{
+ Function f = new Function;
+ f.name = name;
+ f.returnType = type;
+ f.line = line;
+ f.attributes.insertInPlace(f.attributes.length, attributes);
+
+ Variable[] vars1 = parseParameters(tokens, index);
+ if (tokens[index] == TokenType.lParen)
+ {
+ f.templateParameters.insertInPlace(f.templateParameters.length,
+ map!("a.type")(vars1));
+ f.parameters.insertInPlace(f.parameters.length,
+ parseParameters(tokens, index));
+ }
+ else
+ f.parameters.insertInPlace(f.parameters.length, vars1);
+
+ attributeLoop: while(index < tokens.length)
+ {
+ switch (tokens[index].type)
+ {
+ case TokenType.tImmutable:
+ case TokenType.tConst:
+ case TokenType.tPure:
+ case TokenType.atTrusted:
+ case TokenType.atProperty:
+ case TokenType.tNothrow:
+ case TokenType.tFinal:
+ case TokenType.tOverride:
+ f.attributes ~= tokens[index++].value;
+ break;
+ default:
+ break attributeLoop;
+ }
+ }
+
+ if (tokens[index] == TokenType.tIf)
+ f.constraint = parseConstraint(tokens, index);
+ while (index < tokens.length &&
+ (tokens[index] == TokenType.tIn || tokens[index] == TokenType.tOut
+ || tokens[index] == TokenType.tBody))
+ {
+ ++index;
+ if (index < tokens.length && tokens[index] == TokenType.lBrace)
+ tokens.skipBlockStatement(index);
+ }
+ if (index >= tokens.length)
+ return f;
+ if (tokens[index] == TokenType.lBrace)
+ tokens.skipBlockStatement(index);
+ else if (tokens[index] == TokenType.semicolon)
+ ++index;
+ return f;
+}
+
+string parseConstraint(const Token[] tokens, ref size_t index)
+{
+ auto appender = appender!(string)();
+ assert(tokens[index] == TokenType.tIf);
+ appender.put(tokens[index++].value);
+ assert(tokens[index] == TokenType.lParen);
+ return "if " ~ parenContent(tokens, index);
+}
+
+Variable[] parseParameters(const Token[] tokens, ref size_t index)
+in
+{
+ assert (tokens[index] == TokenType.lParen);
+}
+body
+{
+ auto appender = appender!(Variable[])();
+ Variable v = new Variable;
+ auto r = betweenBalancedParens(tokens, index);
+ size_t i = 0;
+ while (i < r.length)
+ {
+ switch(r[i].type)
+ {
+ case TokenType.tIn:
+ case TokenType.tOut:
+ case TokenType.tRef:
+ case TokenType.tScope:
+ case TokenType.tLazy:
+ case TokenType.tConst:
+ case TokenType.tImmutable:
+ case TokenType.tShared:
+ case TokenType.tInout:
+ auto tmp = r[i++].value;
+ if (r[i] == TokenType.lParen)
+ v.type ~= tmp ~ parenContent(r, i);
+ else
+ v.attributes ~= tmp;
+ break;
+ case TokenType.colon:
+ i++;
+ r.skipPastNext(TokenType.comma, i);
+ appender.put(v);
+ v = new Variable;
+ break;
+ case TokenType.comma:
+ ++i;
+ appender.put(v);
+ v = new Variable;
+ break;
+ default:
+ if (v.type.empty())
+ {
+ v.type = r.parseTypeDeclaration(i);
+ if (i >= r.length)
+ appender.put(v);
+ }
+ else
+ {
+ v.line = r[i].lineNumber;
+ v.name = r[i++].value;
+ appender.put(v);
+ if (i < r.length && r[i] == TokenType.vararg)
+ {
+ v.type ~= " ...";
+ }
+ v = new Variable;
+ r.skipPastNext(TokenType.comma, i);
+ }
+ break;
+ }
+ }
+ return appender.data;
+}
+
+string[] parseBaseClassList(const Token[] tokens, ref size_t index)
+in
+{
+ assert(tokens[index] == TokenType.colon);
+}
+body
+{
+ auto appender = appender!(string[])();
+ ++index;
+ while (index < tokens.length)
+ {
+ if (tokens[index] == TokenType.identifier)
+ {
+ string base = parseTypeDeclaration(tokens, index);
+ appender.put(base);
+ if (tokens[index] == TokenType.comma)
+ ++index;
+ else
+ break;
+ }
+ else
+ break;
+ }
+ return appender.data;
+}
+
+void parseStructBody(const Token[] tokens, ref size_t index, Struct st)
+{
+ st.bodyStart = tokens[index].startIndex;
+ Module m = parseModule(betweenBalancedBraces(tokens, index));
+ st.bodyEnd = tokens[index - 1].startIndex;
+ st.functions.insertInPlace(0, m.functions);
+ st.variables.insertInPlace(0, m.variables);
+}
+
+
+Struct parseStructOrUnion(const Token[] tokens, ref size_t index, string protection,
+ string[] attributes)
+{
+ Struct s = new Struct;
+ s.line = tokens[index].lineNumber;
+ s.attributes = attributes;
+ s.protection = protection;
+ s.name = tokens[index++].value;
+ if (tokens[index] == TokenType.lParen)
+ s.templateParameters.insertInPlace(s.templateParameters.length,
+ map!("a.type")(parseParameters(tokens, index)));
+
+ if (index >= tokens.length) return s;
+
+ if (tokens[index] == TokenType.tIf)
+ s.constraint = parseConstraint(tokens, index);
+
+ if (index >= tokens.length) return s;
+
+ if (tokens[index] == TokenType.lBrace)
+ parseStructBody(tokens, index, s);
+ else
+ tokens.skipBlockStatement(index);
+ return s;
+}
+
+Struct parseStruct(const Token[] tokens, ref size_t index, string protection,
+ string[] attributes)
+in
+{
+ assert(tokens[index] == TokenType.tStruct);
+}
+body
+{
+ return parseStructOrUnion(tokens, ++index, protection, attributes);
+}
+
+Struct parseUnion(const Token[] tokens, ref size_t index, string protection,
+ string[] attributes)
+in
+{
+ assert(tokens[index] == TokenType.tUnion);
+}
+body
+{
+ return parseStructOrUnion(tokens, ++index, protection, attributes);
+}
+
+Inherits parseInherits(const Token[] tokens, ref size_t index, string protection,
+ string[] attributes)
+{
+ auto i = new Inherits;
+ i.line = tokens[index].lineNumber;
+ i.name = tokens[index++].value;
+ i.protection = protection;
+ i.attributes.insertInPlace(i.attributes.length, attributes);
+ if (tokens[index] == TokenType.lParen)
+ i.templateParameters.insertInPlace(i.templateParameters.length,
+ map!("a.type")(parseParameters(tokens, index)));
+
+ if (index >= tokens.length) return i;
+
+ if (tokens[index] == TokenType.tIf)
+ i.constraint = parseConstraint(tokens, index);
+
+ if (index >= tokens.length) return i;
+
+ if (tokens[index] == TokenType.colon)
+ i.baseClasses = parseBaseClassList(tokens, index);
+
+ if (index >= tokens.length) return i;
+
+ if (tokens[index] == TokenType.lBrace)
+ parseStructBody(tokens, index, i);
+ else
+ tokens.skipBlockStatement(index);
+ return i;
+}
+
+Inherits parseInterface(const Token[] tokens, ref size_t index, string protection,
+ string[] attributes)
+in
+{
+ assert (tokens[index] == TokenType.tInterface);
+}
+body
+{
+ return parseInherits(tokens, ++index, protection, attributes);
+}
+
+
+Inherits parseClass(const Token[] tokens, ref size_t index, string protection,
+ string[] attributes)
+in
+{
+ assert(tokens[index] == TokenType.tClass);
+}
+body
+{
+ return parseInherits(tokens, ++index, protection, attributes);
+}
diff --git a/tokenizer.d b/tokenizer.d
new file mode 100644
index 0000000..4049b61
--- /dev/null
+++ b/tokenizer.d
@@ -0,0 +1,552 @@
+/*******************************************************************************
+ * The MIT License
+ *
+ * Copyright (c) 2012 Brian Schott (Sir Alaran / Hackerpilot)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ******************************************************************************/
+
+
+import std.range;
+import std.file;
+import std.traits;
+import std.algorithm;
+import std.conv;
+import std.uni;
+import std.stdio;
+
+import langutils;
+import codegen;
+
+
+/**
+ * Increments endIndex until it indexes a non-whitespace character in
+ * inputString.
+ * Params:
+ * inputString = the source code to examine
+ * endIndex = an index into inputString
+ * lineNumber = the line number that corresponds to endIndex
+ * style = the code iteration style
+ * Returns: The whitespace, or null if style was CODE_ONLY
+ */
+pure nothrow string lexWhitespace(S)(S inputString, ref size_t endIndex,
+ ref uint lineNumber, IterationStyle style = IterationStyle.CODE_ONLY)
+ if (isSomeString!S)
+{
+ immutable startIndex = endIndex;
+ while (endIndex < inputString.length && isWhite(inputString[endIndex]))
+ {
+ if (inputString[endIndex] == '\n')
+ lineNumber++;
+ ++endIndex;
+ }
+ final switch (style)
+ {
+ case IterationStyle.EVERYTHING:
+ return inputString[startIndex .. endIndex];
+ case IterationStyle.CODE_ONLY:
+ return null;
+ }
+}
+
+
+/**
+ * Increments endIndex until it indexes a character directly after a comment
+ * Params:
+ * inputString = the source code to examine
+ * endIndex = an index into inputString at the second character of a
+ * comment, i.e. points at the second slash in a // comment.
+ * lineNumber = the line number that corresponds to endIndex
+ * Returns: The comment
+ */
+pure nothrow string lexComment(S)(ref S inputString, ref size_t endIndex,
+ ref uint lineNumber) if (isSomeString!S)
+{
+ if (inputString.length == 0)
+ return "";
+ auto startIndex = endIndex - 1;
+ switch(inputString[endIndex])
+ {
+ case '/':
+ while (endIndex < inputString.length && inputString[endIndex] != '\n')
+ {
+ if (inputString[endIndex] == '\n')
+ ++lineNumber;
+ ++endIndex;
+ }
+ break;
+ case '*':
+ while (endIndex < inputString.length
+ && !inputString[endIndex..$].startsWith("*/"))
+ {
+ if (inputString[endIndex] == '\n')
+ ++lineNumber;
+ ++endIndex;
+ }
+ endIndex += 2;
+ break;
+ case '+':
+ ++endIndex;
+ int depth = 1;
+ while (depth > 0 && endIndex + 1 < inputString.length)
+ {
+ if (inputString[endIndex] == '\n')
+ lineNumber++;
+ else if (inputString[endIndex..$].startsWith("+/"))
+ depth--;
+ else if (inputString[endIndex..$].startsWith("/+"))
+ depth++;
+ ++endIndex;
+ }
+ ++endIndex;
+ break;
+ default:
+ break;
+ }
+ return inputString[startIndex..endIndex];
+}
+
+
+/**
+ * Params:
+ * inputString = the source code to examine
+ * endIndex = an index into inputString at the opening quote
+ * lineNumber = the line number that corresponds to endIndex
+ * quote = the opening (and closing) quote character for the string to be
+ * lexed
+ * Returns: a string literal, including its opening and closing quote characters
+ * Bugs: Does not handle string suffixes
+ */
+pure nothrow string lexString(S, C)(S inputString, ref size_t endIndex, ref uint lineNumber,
+ C quote, bool canEscape = true) if (isSomeString!S && isSomeChar!C)
+in
+{
+ assert (inputString[endIndex] == quote);
+ assert (quote == '\'' || quote == '\"' || quote == '`');
+}
+body
+{
+ if (inputString[endIndex] != quote)
+ return "";
+ auto startIndex = endIndex;
+ ++endIndex;
+ bool escape = false;
+ while (endIndex < inputString.length && (inputString[endIndex] != quote || escape))
+ {
+ if (escape)
+ escape = false;
+ else
+ escape = (canEscape && inputString[endIndex] == '\\');
+ if (inputString[endIndex] == '\n')
+ lineNumber++;
+ ++endIndex;
+ }
+ ++endIndex;
+ endIndex = min(endIndex, inputString.length);
+ return inputString[startIndex .. endIndex];
+}
+
+
+/**
+ * Lexes the various crazy D string literals such as q{}, q"WTF is this? WTF",
+ * and q"<>".
+ * Params:
+ * inputString = the source code to examine
+ * endIndex = an index into inputString at the opening quote
+ * lineNumber = the line number that corresponds to endIndex
+ * Returns: a string literal, including its opening and closing quote characters
+ */
+string lexDelimitedString(S)(ref S inputString, ref size_t endIndex,
+ ref uint lineNumber) if (isSomeString!S)
+{
+ auto startIndex = endIndex;
+ ++endIndex;
+ string open = to!string(inputString[endIndex]);
+ string close;
+ bool nesting = false;
+ switch (open)
+ {
+ case "[": close = "]"; ++endIndex; nesting = true; break;
+ case "<": close = ">"; ++endIndex; nesting = true; break;
+ case "{": close = "}"; ++endIndex; nesting = true; break;
+ case "(": close = ")"; ++endIndex; nesting = true; break;
+ default:
+ while(!isWhite(inputString[endIndex])) endIndex++;
+ close = open = inputString[startIndex + 1 .. endIndex];
+ break;
+ }
+ int depth = 1;
+ while (endIndex < inputString.length && depth > 0)
+ {
+ if (inputString[endIndex] == '\n')
+ {
+ lineNumber++;
+ endIndex++;
+ }
+ else if (inputString[endIndex..$].startsWith(open))
+ {
+ endIndex += open.length;
+ if (!nesting)
+ {
+ if (inputString[endIndex] == '\"')
+ ++endIndex;
+ break;
+ }
+ depth++;
+ }
+ else if (inputString[endIndex..$].startsWith(close))
+ {
+ endIndex += close.length;
+ depth--;
+ if (depth <= 0)
+ break;
+ }
+ else
+ ++endIndex;
+ }
+ if (endIndex < inputString.length && inputString[endIndex] == '\"')
+ ++endIndex;
+ return inputString[startIndex .. endIndex];
+}
+
+
+string lexTokenString(S)(ref S inputString, ref size_t endIndex, ref uint lineNumber)
+{
+ /+auto r = byDToken(range, IterationStyle.EVERYTHING);
+ string s = getBraceContent(r);
+ range.popFrontN(s.length);
+ return s;+/
+ return "";
+}
+
+/**
+ *
+ */
+pure nothrow string lexNumber(S)(ref S inputString, ref size_t endIndex) if (isSomeString!S)
+{
+ auto startIndex = endIndex;
+ bool foundDot = false;
+ bool foundX = false;
+ bool foundB = false;
+ bool foundE = false;
+ numberLoop: while (endIndex < inputString.length)
+ {
+ switch (inputString[endIndex])
+ {
+ case '0':
+ if (!foundX)
+ {
+ ++endIndex;
+ if (endIndex < inputString.length
+ && (inputString[endIndex] == 'x' || inputString[endIndex] == 'X'))
+ {
+ ++endIndex;
+ foundX = true;
+ }
+ }
+ else
+ ++endIndex;
+ break;
+ case 'b':
+ if (foundB)
+ break numberLoop;
+ foundB = true;
+ ++endIndex;
+ break;
+ case '.':
+ if (foundDot || foundX || foundE)
+ break numberLoop;
+ foundDot = true;
+ ++endIndex;
+ break;
+ case '+':
+ case '-':
+ if (!foundE)
+ break numberLoop;
+ ++endIndex;
+ break;
+ case 'p':
+ case 'P':
+ if (!foundX)
+ break numberLoop;
+ foundE = true;
+ goto case '_';
+ case 'e':
+ case 'E':
+ if (foundE || foundX)
+ break numberLoop;
+ foundE = true;
+ goto case '_';
+ case '1': .. case '9':
+ case '_':
+ ++endIndex;
+ break;
+ case 'F':
+ case 'f':
+ case 'L':
+ case 'i':
+ ++endIndex;
+ break numberLoop;
+ default:
+ break numberLoop;
+ }
+ }
+ return inputString[startIndex .. endIndex];
+}
+
+
+/**
+ * Returns: true if ch marks the ending of one token and the beginning of
+ * another, false otherwise
+ */
+pure nothrow bool isSeparating(C)(C ch) if (isSomeChar!C)
+{
+ switch (ch)
+ {
+ case '!': .. case '/':
+ case ':': .. case '@':
+ case '[': .. case '^':
+ case '{': .. case '~':
+ case 0x20: // space
+ case 0x09: // tab
+ case 0x0a: .. case 0x0d: // newline, vertical tab, form feed, carriage return
+ return true;
+ default:
+ return false;
+ }
+}
+
+/**
+ * Configure the tokenize() function
+ */
+enum IterationStyle
+{
+ /// Only include code, not whitespace or comments
+ CODE_ONLY,
+ /// Include everything
+ EVERYTHING
+}
+
+Token[] tokenize(S)(S inputString, IterationStyle iterationStyle = IterationStyle.CODE_ONLY)
+ if (isSomeString!S)
+{
+ auto tokenAppender = appender!(Token[])();
+
+ // This is very likely a local maximum, but it does seem to take a few
+ // milliseconds off of the run time
+ tokenAppender.reserve(inputString.length / 4);
+
+ size_t endIndex = 0;
+ uint lineNumber = 1;
+ while (endIndex < inputString.length)
+ {
+ Token currentToken;
+ auto startIndex = endIndex;
+ if (isWhite(inputString[endIndex]))
+ {
+ if (iterationStyle == IterationStyle.EVERYTHING)
+ {
+ currentToken.lineNumber = lineNumber;
+ currentToken.value = lexWhitespace(inputString, endIndex,
+ lineNumber, IterationStyle.EVERYTHING);
+ currentToken.type = TokenType.whitespace;
+ tokenAppender.put(currentToken);
+ }
+ else
+ lexWhitespace(inputString, endIndex, lineNumber);
+ continue;
+ }
+ currentToken.startIndex = endIndex;
+
+ outerSwitch: switch(inputString[endIndex])
+ {
+ mixin(generateCaseTrie(
+ "=", "TokenType.assign",
+ "&", "TokenType.bitAnd",
+ "&=", "TokenType.bitAndEquals",
+ "|", "TokenType.bitOr",
+ "|=", "TokenType.bitOrEquals",
+ "~=", "TokenType.catEquals",
+ ":", "TokenType.colon",
+ ",", "TokenType.comma",
+ "$", "TokenType.dollar",
+ ".", "TokenType.dot",
+ "==", "TokenType.equals",
+ "=>", "TokenType.goesTo",
+ ">", "TokenType.greater",
+ ">=", "TokenType.greaterEqual",
+ "#", "TokenType.hash",
+ "&&", "TokenType.lAnd",
+ "{", "TokenType.lBrace",
+ "[", "TokenType.lBracket",
+ "<", "TokenType.less",
+ "<=", "TokenType.lessEqual",
+ "<>=", "TokenType.lessEqualGreater",
+ "<>", "TokenType.lessOrGreater",
+ "||", "TokenType.lOr",
+ "(", "TokenType.lParen",
+ "-", "TokenType.minus",
+ "-=", "TokenType.minusEquals",
+ "%", "TokenType.mod",
+ "%=", "TokenType.modEquals",
+ "*=", "TokenType.mulEquals",
+ "!", "TokenType.not",
+ "!=", "TokenType.notEquals",
+ "!>", "TokenType.notGreater",
+ "!>=", "TokenType.notGreaterEqual",
+ "!<", "TokenType.notLess",
+ "!<=", "TokenType.notLessEqual",
+ "!<>", "TokenType.notLessEqualGreater",
+ "+", "TokenType.plus",
+ "+=", "TokenType.plusEquals",
+ "^^", "TokenType.pow",
+ "^^=", "TokenType.powEquals",
+ "}", "TokenType.rBrace",
+ "]", "TokenType.rBracket",
+ ")", "TokenType.rParen",
+ ";", "TokenType.semicolon",
+ "<<", "TokenType.shiftLeft",
+ "<<=", "TokenType.shiftLeftEqual",
+ ">>", "TokenType.shiftRight",
+ ">>=", "TokenType.shiftRightEqual",
+ "..", "TokenType.slice",
+ "*", "TokenType.star",
+ "?", "TokenType.ternary",
+ "~", "TokenType.tilde",
+ "--", "TokenType.uMinus",
+ "!<>=", "TokenType.unordered",
+ ">>>", "TokenType.unsignedShiftRight",
+ ">>>=", "TokenType.unsignedShiftRightEqual",
+ "++", "TokenType.uPlus",
+ "...", "TokenType.vararg",
+ "^", "TokenType.xor",
+ "^=", "TokenType.xorEquals",
+ ));
+
+ case '0': .. case '9':
+ currentToken.value = lexNumber(inputString, endIndex);
+ currentToken.type = TokenType.numberLiteral;
+ currentToken.lineNumber = lineNumber;
+ break;
+ case '/':
+ ++endIndex;
+ if (endIndex >= inputString.length)
+ {
+ currentToken.value = "/";
+ currentToken.type = TokenType.div;
+ currentToken.lineNumber = lineNumber;
+ break;
+ }
+ currentToken.lineNumber = lineNumber;
+ switch (inputString[endIndex])
+ {
+ case '/':
+ case '+':
+ case '*':
+ if (iterationStyle == IterationStyle.CODE_ONLY)
+ {
+ lexComment(inputString, endIndex, lineNumber);
+ continue;
+ }
+ else
+ {
+ currentToken.value = lexComment(inputString, endIndex, lineNumber);
+ currentToken.type = TokenType.comment;
+ break;
+ }
+ case '=':
+ currentToken.value = "/=";
+ currentToken.type = TokenType.divEquals;
+ ++endIndex;
+ break;
+ default:
+ currentToken.value = "/";
+ currentToken.type = TokenType.div;
+ break;
+ }
+ break;
+ case 'r':
+ currentToken.value = "r";
+ ++endIndex;
+ if (inputString[endIndex] == '\"')
+ {
+ currentToken.lineNumber = lineNumber;
+ currentToken.value = lexString(inputString, endIndex,
+ lineNumber, inputString[endIndex], false);
+ currentToken.type = TokenType.stringLiteral;
+ break;
+ }
+ else
+ goto default;
+ case '`':
+ currentToken.lineNumber = lineNumber;
+ currentToken.value = lexString(inputString, endIndex, lineNumber,
+ inputString[endIndex], false);
+ currentToken.type = TokenType.stringLiteral;
+ break;
+ case 'x':
+ currentToken.value = "x";
+ ++endIndex;
+ if (inputString[endIndex] == '\"')
+ goto case '\"';
+ else
+ goto default;
+ case '\'':
+ case '"':
+ currentToken.lineNumber = lineNumber;
+ currentToken.value = lexString(inputString, endIndex, lineNumber,
+ inputString[endIndex]);
+ currentToken.type = TokenType.stringLiteral;
+ break;
+ case 'q':
+ ++endIndex;
+ switch (inputString[endIndex])
+ {
+ case '\"':
+ currentToken.lineNumber = lineNumber;
+ currentToken.value ~= "q" ~ lexDelimitedString(inputString,
+ endIndex, lineNumber);
+ currentToken.type = TokenType.stringLiteral;
+ break outerSwitch;
+ case '{':
+ currentToken.lineNumber = lineNumber;
+ currentToken.value ~= "q" ~ lexTokenString(inputString,
+ endIndex, lineNumber);
+ currentToken.type = TokenType.stringLiteral;
+ break outerSwitch;
+ default:
+ break;
+ }
+ goto default;
+ case '@':
+ ++endIndex;
+ goto default;
+ default:
+ while(endIndex < inputString.length && !isSeparating(inputString[endIndex]))
+ ++endIndex;
+ currentToken.value = inputString[startIndex .. endIndex];
+ currentToken.type = lookupTokenType(currentToken.value);
+ currentToken.lineNumber = lineNumber;
+ break;
+ }
+// writeln(currentToken);
+ tokenAppender.put(currentToken);
+ }
+ return tokenAppender.data;
+}
diff --git a/types.d b/types.d
new file mode 100644
index 0000000..6a54b2d
--- /dev/null
+++ b/types.d
@@ -0,0 +1,567 @@
+/*******************************************************************************
+ * The MIT License
+ *
+ * Copyright (c) 2012 Brian Schott (Sir Alaran)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modif y, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ******************************************************************************/
+
+module types;
+
+import std.stdio;
+import std.array;
+import std.range;
+import std.algorithm;
+import std.typecons;
+
+/**
+ * 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)
+{
+ 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, 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);
+ }
+}
+
+/**
+ * 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;
+
+ /// 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;
+ }
+
+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.write(std.array.replicate(" ", indent), "]");
+ }
+}
+
+/**
+ * Functions and delegates
+ */
+class Function : Templateable
+{
+public:
+
+ /// Function return type
+ string returnType;
+
+ /// Parameter list; may be empty
+ Variable[] parameters;
+
+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;
+
+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;
+}
+
+/**
+ * enum
+ */
+class Enum : Base
+{
+public:
+
+ /// Base type for this enum
+ string type;
+
+ /// Enum members; may be empty
+ EnumMember[] members;
+
+protected:
+
+ override void printMembers(File f, uint indent = 0) const
+ {
+ super.printMembers(f, indent);
+ f.writeln(",");
+ writeJSONString(f, "type", type, 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(",");
+ 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), "]");
+ }
+}
+
+/**
+ * Module is a container class for the other classes
+ */
+class Module
+{
+public:
+
+ /// Module name. Will be blank if there is no module statement
+ string name;
+
+ /// 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 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 \"structs\" : [");
+ 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}");
+ }
+}
+
+immutable(string[][string]) typeProperties;
+immutable(string[]) floatProperties;
+immutable(string[]) integralProperties;
+immutable(string[]) commonProperties;
+immutable(string[]) arrayProperties;
+
+static this()
+{
+ floatProperties = ["alignof", "dig", "epsilon", "im", "infinity", "init",
+ "mangleof", "mant_dig", "max", "max_10_exp", ".max_exp", "min_10_exp",
+ "min_exp", "min_normal", "nan", "re", "sizeof"
+ ];
+
+ integralProperties = ["alignof", "init", "mangleof", "max",
+ "min", "sizeof", "stringof"
+ ];
+
+ commonProperties = [
+ "alignof",
+ "init",
+ "mangleof",
+ "stringof"
+ ];
+
+ arrayProperties = [
+ "alignof",
+ "init",
+ "length",
+ "mangleof",
+ "ptr",
+ "stringof",
+ ];
+
+ 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)
+ {
+ foreach (m; chain(modules, [currentModule]))
+ {
+ foreach (s; chain(m.structs, m.interfaces, m.classes, m.unions))
+ {
+ if (s.name != name)
+ continue;
+ Tuple!(string, string)[string] typeMap;
+ foreach(var; s.variables)
+ typeMap[var.name] = Tuple!(string, string)(var.type, "?1");
+ foreach(fun; s.functions)
+ typeMap[fun.name] = Tuple!(string, string)(fun.returnType, "?2");
+ 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)(e.type, "?1");
+ 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();
+ }
+
+ void addModule(Module mod)
+ {
+ modules ~= mod;
+ }
+
+ Module currentModule;
+ Module[] modules;
+}