From d42cf075f54e710dcbf9249decdae376b230b656 Mon Sep 17 00:00:00 2001 From: Hackerpilot Date: Sun, 22 Apr 2012 13:49:37 -0700 Subject: [PATCH 1/7] Fixed bug in handling of extern (x) declarations --- parser.d | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/parser.d b/parser.d index 482ea1e..5a99ffb 100644 --- a/parser.d +++ b/parser.d @@ -218,6 +218,7 @@ string parseTypeDeclaration(const Token[] tokens, ref size_t index) break buildingType; } } + stderr.writeln("type = ", type); return type; } @@ -275,25 +276,26 @@ Module parseModule(const Token[] tokens, string protection = "public", string[] else if (tokens[index] == TokenType.assign) tokens.skipBlockStatement(index); break; + case TokenType.tDeprecated: + case TokenType.tNothrow: + case TokenType.tOverride: + case TokenType.tSynchronized: 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.tStatic: + localAttributes ~= tokens[index++].value; + break; + case TokenType.tConst: 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); @@ -302,10 +304,10 @@ Module parseModule(const Token[] tokens, string protection = "public", string[] index++; attributes ~= tmp; } - else - localAttributes ~= tmp; + localAttributes ~= tmp; break; case TokenType.tAlign: + case TokenType.tExtern: string attribute = tokens[index++].value; if (tokens[index] == TokenType.lParen) attribute ~= parenContent(tokens, index); From e7c198cbc7eb5c0feecb927bdb50d08f7149f7c2 Mon Sep 17 00:00:00 2001 From: Hackerpilot Date: Sun, 22 Apr 2012 18:58:35 -0700 Subject: [PATCH 2/7] Lex various number literals differently. Fix a few bugs --- README.md | 2 + autocomplete.d | 173 ++++++++----------------------- build.sh | 2 +- highlighter.d | 4 +- langutils.d | 14 ++- parser.d | 1 - tokenizer.d | 271 +++++++++++++++++++++++++++++++++++++++---------- 7 files changed, 273 insertions(+), 194 deletions(-) diff --git a/README.md b/README.md index 2646c24..46e5093 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,10 @@ well as any paths specified in /etc/dmd.conf. code file. # Dot Completion +This is currently under development. # Paren Completion +This is currently under development. # JSON output Generates a JSON summary of the input file. diff --git a/autocomplete.d b/autocomplete.d index 18e08ec..82b58af 100644 --- a/autocomplete.d +++ b/autocomplete.d @@ -1,4 +1,3 @@ - // 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 @@ -28,100 +27,19 @@ immutable string[] versions = ["AIX", "all", "Alpha", "ARM", "BigEndian", "BSD", "Win64", "Windows", "X86", "X86_64" ]; -/+/** +/** * Returns: indicies into the token array */ -Tuple!(size_t, size_t) findEndOfStatement(const Token[] tokens, size_t index, out size_t) +size_t findEndOfExpression(const Token[] tokens, size_t index) { - -}+/ - -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; + return index; } - -string[] callChainForwards(const Token[] tokens, size_t index) +size_t findBeginningOfExpression(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; + return index; } - struct AutoComplete { this(const (Token)[] tokens, CompletionContext context) @@ -130,39 +48,42 @@ struct AutoComplete this.context = context; } - string getTypeOfExpression(string[] chain, const Token[] tokens, size_t cursor) + string getTypeOfExpression(const(Token)[] expression, 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; + return "void"; } /** * This is where the magic happens */ - string typeOfVariable(string symbol, size_t cursor) + string typeOfVariable(Token symbol, size_t cursor) { // int is of type int, double of type double, and so on - if (symbol in typeProperties) - return symbol; + if (symbol.value in typeProperties) + return symbol.value; - if (context.getMembersOfType(symbol)) - return symbol; + switch (symbol.type) + { + case TokenType.floatLiteral: + return "float"; + case TokenType.doubleLiteral: + return "double"; + case TokenType.realLiteral: + return "real"; + case TokenType.intLiteral: + return "int"; + case TokenType.unsignedIntLiteral: + return "uint"; + case TokenType.longLiteral: + return "long"; + case TokenType.unsignedLongLiteral: + return "ulong"; + default: + break; + } + + if (context.getMembersOfType(symbol.value)) + return symbol.value; // Arbitrarily define the depth of the cursor position as zero // iterate backwards through the code to try to find the variable @@ -183,14 +104,13 @@ struct AutoComplete || p == TokenType.tConst) && preceedingTokens[index + 1] == TokenType.assign) { - auto chain = callChainForwards(tokens, index + 2); - return getTypeOfExpression(chain, tokens, cursor); + return null; } - if (p == TokenType.identifier + else if (p == TokenType.identifier || (p.type > TokenType.TYPES_BEGIN && p.type < TokenType.TYPES_END)) { - return preceedingTokens[index - 1].value; + return p.value; } } if (index == 0) @@ -207,7 +127,7 @@ struct AutoComplete return minCount!("a.bodyStart > b.bodyStart")(structs)[0].name; foreach (s; structs) { - auto t = s.getMemberType(symbol); + auto t = s.getMemberType(symbol.value); if (t !is null) return t; } @@ -225,14 +145,16 @@ struct AutoComplete string parenComplete(size_t cursor) { + stderr.writeln("parenComplete"); auto index = assumeSorted(tokens).lowerBound(cursor).length - 2; Token t = tokens[index]; + stderr.writeln(t); if (t.startIndex + t.value.length + 1 != cursor) return ""; switch (tokens[index].type) { case TokenType.tVersion: - return to!string(array(join(map!`a ~ "?1"`(versions), " "))); + return to!string(join(map!`a ~ "?1"`(versions), " ").array()); case TokenType.tIf: case TokenType.tCast: case TokenType.tWhile: @@ -251,20 +173,7 @@ struct AutoComplete Token t = tokens[index]; if (t.startIndex + t.value.length + 1 != cursor) return ""; - stderr.writeln(t); - 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; - } + auto type = typeOfVariable(t, cursor); const Tuple!(string, string)[string] typeMap = context.getMembersOfType(type); if (typeMap is null) @@ -272,7 +181,7 @@ struct AutoComplete auto app = appender!(string[])(); foreach (k, t; typeMap) app.put(k ~ t[1]); - return to!string(array(join(sort(app.data), " "))); + return to!string(array(join(sort!"a.toLower() < b.toLower()"(app.data), " "))); } const(Token)[] tokens; diff --git a/build.sh b/build.sh index 41bddc5..ac9688e 100755 --- a/build.sh +++ b/build.sh @@ -1,2 +1,2 @@ dmd *.d -release -noboundscheck -O -w -wi -m64 -property -ofdscanner -#dmd *.d -g -unittest -m64 -w -wi -property -oftokenizer +#dmd *.d -g -unittest -m64 -w -wi -property -ofdscanner diff --git a/highlighter.d b/highlighter.d index cb25238..9a9e557 100644 --- a/highlighter.d +++ b/highlighter.d @@ -30,7 +30,7 @@ html { background-color: #111; color: #ccc; } .string { color: Tomato; font-style: italic; } .property { color: HotPink; font-weight: bold;} .operator { color: tan; font-weight: bold; } -.type { color: cyan; } +.type { color: cyan; font-weight: bold; }
]");
 
@@ -50,7 +50,7 @@ html { background-color: #111; color: #ccc; }
 		case TokenType.stringLiteral:
 			writeSpan("string", t.value);
 			break;
-		case TokenType.numberLiteral:
+		case TokenType.NUMBERS_BEGIN: .. case TokenType.NUMBERS_END:
 			writeSpan("number", t.value);
 			break;
 		case TokenType.OPERATORS_BEGIN: .. case TokenType.OPERATORS_END:
diff --git a/langutils.d b/langutils.d
index 138b0b2..c9c4818 100644
--- a/langutils.d
+++ b/langutils.d
@@ -254,11 +254,19 @@ enum TokenType: uint
 // Misc
 	MISC_BEGIN,
 	comment, /// /** comment */ or // comment or ///comment
+	NUMBERS_BEGIN,
+	floatLiteral, /// 123.456f or 0x123_45p-af
+	doubleLiteral, /// 123.456
+	realLiteral, /// 123.456L
+	intLiteral, /// 123 or 0b1101010101
+	unsignedIntLiteral, /// 123u
+	longLiteral, /// 123L
+	unsignedLongLiteral, /// 123uL
+	NUMBERS_END,
 	stringLiteral, /// "a string"
-	numberLiteral, /// int, float, etc...
-	identifier,
+	identifier, /// anything else
 	whitespace, /// whitespace
-	blank,
+	blank, /// unknown token type
 	MISC_END,
 }
 
diff --git a/parser.d b/parser.d
index 5a99ffb..71d061b 100644
--- a/parser.d
+++ b/parser.d
@@ -218,7 +218,6 @@ string parseTypeDeclaration(const Token[] tokens, ref size_t index)
 			break buildingType;
 		}
 	}
-	stderr.writeln("type = ", type);
 	return type;
 }
 
diff --git a/tokenizer.d b/tokenizer.d
index a9c1fbd..47528a8 100644
--- a/tokenizer.d
+++ b/tokenizer.d
@@ -141,11 +141,15 @@ body
 		++endIndex;
 	}
 	++endIndex;
+	if (endIndex < inputString.length && (inputString[endIndex] == 'w'
+		|| inputString[endIndex] == 'd' || inputString[endIndex] == 'c'))
+	{
+		++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"<>".
@@ -218,79 +222,230 @@ string lexTokenString(S)(ref S inputString, ref size_t endIndex, ref uint lineNu
 	return "";
 }
 
-/**
- *
- */
-pure nothrow string lexNumber(S)(ref S inputString, ref size_t endIndex) if (isSomeString!S)
+pure nothrow Token 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)
+	Token token;
+	size_t startIndex = endIndex;
+	if (inputString[endIndex] == '0')
+	{
+		endIndex++;
+		if (endIndex >= inputString.length)
+		{
+			token.type = TokenType.intLiteral;
+			token.value = inputString[startIndex .. endIndex];
+			return token;
+		}
+		switch (inputString[endIndex])
+		{
+		case '0': .. case '9':
+			// The current language spec doesn't cover octal literals, so this
+			// is decimal.
+			lexDecimal(inputString, startIndex, endIndex, token);
+			return token;
+		case 'b':
+		case 'B':
+			lexBinary(inputString, startIndex, ++endIndex, token);
+			return token;
+		case 'x':
+		case 'X':
+			lexHex(inputString, startIndex, ++endIndex, token);
+			return token;
+		default:
+			token.type = TokenType.intLiteral;
+			token.value = inputString[startIndex .. endIndex];
+			return token;
+		}
+	}
+	else
+	{
+		lexDecimal(inputString, startIndex, endIndex, token);
+		return token;
+	}
+}
+
+pure nothrow void lexBinary(S)(ref S inputString, size_t startIndex,
+	ref size_t endIndex, ref Token token) if (isSomeString!S)
+{
+	bool lexingSuffix = false;
+	bool isLong = false;
+	bool isUnsigned = false;
+	token.type = TokenType.intLiteral;
+	binaryLoop: 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;
-				}
-			}
+		case '1':
+		case '_':
+			++endIndex;
+			if (lexingSuffix)
+				break binaryLoop;
+			break;
+		case 'u':
+		case 'U':
+			++endIndex;
+			lexingSuffix = true;
+			if (isLong)
+				token.type = TokenType.unsignedLongLiteral;
 			else
-				++endIndex;
+				token.type = TokenType.unsignedIntLiteral;
 			break;
-		case 'b':
-			if (foundB)
-				break numberLoop;
-			foundB = true;
+		case 'L':
 			++endIndex;
+			if (isLong)
+				break binaryLoop;
+			if (isUnsigned)
+				token.type = TokenType.unsignedLongLiteral;
+			else
+				token.type = TokenType.longLiteral;
+			isLong = true;
 			break;
-		case '.':
-			if (foundDot || foundX || foundE)
-				break numberLoop;
-			foundDot = true;
+		default:
+			break binaryLoop;
+		}
+	}
+
+	token.value = inputString[startIndex .. endIndex];
+}
+
+pure nothrow void lexDecimal(S)(ref S inputString, size_t startIndex,
+	ref size_t endIndex, ref Token token) if (isSomeString!S)
+{
+	bool lexingSuffix = false;
+	bool isLong = false;
+	bool isUnsigned = false;
+	bool isFloat = false;
+	bool isReal = false;
+	bool isDouble = false;
+	bool foundDot = false;
+	bool foundE = false;
+	bool foundPlusMinus = false;
+	token.type = TokenType.intLiteral;
+	decimalLoop: while (endIndex < inputString.length)
+	{
+		switch (inputString[endIndex])
+		{
+		case '0': .. case '9':
+		case '_':
 			++endIndex;
+			if (lexingSuffix)
+				break decimalLoop;
+			break;
+		case 'e':
+		case 'E':
+			if (foundE)
+				break decimalLoop;
+			++endIndex;
+			foundE = true;
 			break;
 		case '+':
 		case '-':
-			if (!foundE)
-				break numberLoop;
+			if (foundPlusMinus || !foundE)
+				break decimalLoop;
+			foundPlusMinus = true;
 			++endIndex;
 			break;
+		case '.':
+			if (foundDot)
+				break decimalLoop;
+			++endIndex;
+			foundDot = true;
+			token.type = TokenType.doubleLiteral;
+			isDouble = true;
+			break;
+		case 'u':
+		case 'U':
+			++endIndex;
+			lexingSuffix = true;
+			if (isLong)
+				token.type = TokenType.unsignedLongLiteral;
+			else
+				token.type = TokenType.unsignedIntLiteral;
+			isUnsigned = true;
+			break;
+		case 'L':
+			++endIndex;
+			lexingSuffix = true;
+			if (isLong || isReal)
+				break decimalLoop;
+			if (isDouble)
+				token.type = TokenType.realLiteral;
+			else if (isUnsigned)
+				token.type = TokenType.unsignedLongLiteral;
+			else
+				token.type = TokenType.longLiteral;
+			isLong = true;
+			break;
+		case 'f':
+		case 'F':
+			lexingSuffix = true;
+			if (isUnsigned || isLong)
+				break decimalLoop;
+			++endIndex;
+			token.type = TokenType.floatLiteral;
+			break decimalLoop;
+		default:
+			break decimalLoop;
+		}
+	}
+
+	token.value = inputString[startIndex .. endIndex];
+}
+
+nothrow void lexHex(S)(ref S inputString, ref size_t startIndex,
+	ref size_t endIndex, ref Token token) if (isSomeString!S)
+{
+	bool lexingSuffix = false;
+	bool isLong = false;
+	bool isUnsigned = false;
+	bool isFloat = false;
+	bool isReal = false;
+	bool isDouble = false;
+	bool foundDot = false;
+	bool foundE = false;
+	bool foundPlusMinus = false;
+	token.type = TokenType.intLiteral;
+	hexLoop: while (endIndex < inputString.length)
+	{
+		switch (inputString[endIndex])
+		{
+		case '0': .. case '9':
+		case 'a': .. case 'f':
+		case 'A': .. case 'F':
+		case '_':
+			++endIndex;
+			if (lexingSuffix)
+				break hexLoop;
+			break;
 		case 'p':
 		case 'P':
-			if (!foundX)
-				break numberLoop;
+			if (foundE)
+				break hexLoop;
+			++endIndex;
 			foundE = true;
-			goto case '_';
-		case 'e':
-		case 'E':
-			if (foundE || foundX)
-				break numberLoop;
-			foundE = true;
-			goto case '_';
-		case '1': .. case '9':
-		case '_':
+			break;
+		case '+':
+		case '-':
+			if (foundPlusMinus || !foundE)
+				break hexLoop;
+			foundPlusMinus = true;
 			++endIndex;
 			break;
-		case 'F':
-		case 'f':
-		case 'L':
-		case 'i':
+		case '.':
+			if (foundDot)
+				break hexLoop;
 			++endIndex;
-			break numberLoop;
+			foundDot = true;
+			token.type = TokenType.doubleLiteral;
+			isDouble = true;
+			break;
 		default:
-			break numberLoop;
+			break hexLoop;
 		}
 	}
-	return inputString[startIndex .. endIndex];
+
+	token.value = inputString[startIndex .. endIndex];
 }
 
 
@@ -337,8 +492,10 @@ Token[] tokenize(S)(S inputString, IterationStyle iterationStyle = IterationStyl
 
 	size_t endIndex = 0;
 	uint lineNumber = 1;
+
 	while (endIndex < inputString.length)
 	{
+		size_t prevIndex = endIndex;
 		Token currentToken;
 		auto startIndex = endIndex;
 		if (isWhite(inputString[endIndex]))
@@ -421,11 +578,8 @@ Token[] tokenize(S)(S inputString, IterationStyle iterationStyle = IterationStyl
 			"^",    "TokenType.xor",
 			"^=",   "TokenType.xorEquals",
 		));
-
 		case '0': .. case '9':
-			currentToken.value = lexNumber(inputString, endIndex);
-			currentToken.type = TokenType.numberLiteral;
-			currentToken.lineNumber = lineNumber;
+			currentToken = lexNumber(inputString, endIndex);
 			break;
 		case '/':
 			++endIndex;
@@ -528,8 +682,15 @@ Token[] tokenize(S)(S inputString, IterationStyle iterationStyle = IterationStyl
 			currentToken.lineNumber = lineNumber;
 			break;
 		}
-//		writeln(currentToken);
+		//stderr.writeln(currentToken);
 		tokenAppender.put(currentToken);
+
+		// This should never happen.
+		if (endIndex <= prevIndex)
+		{
+			stderr.writeln("FAIL");
+			return [];
+		}
 	}
 	return tokenAppender.data;
 }

From ba5894b810d7c2c6caa09d9522d9bb3cfaeaed47 Mon Sep 17 00:00:00 2001
From: Hackerpilot 
Date: Sun, 22 Apr 2012 19:01:46 -0700
Subject: [PATCH 3/7] Update --ctags example

---
 README.md | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 46e5093..2973d33 100644
--- a/README.md
+++ b/README.md
@@ -223,7 +223,19 @@ is transformed into the following JSON markup:
 
 # Ctags output
 Dscanner can create a tags file from the specified file. Output is formatted as
-specified at http://ctags.sourceforge.net/FORMAT
+specified at http://ctags.sourceforge.net/FORMAT. The result of generating ctags
+on the same file used in the JSON example will produce this output:
+		{!_TAG_FILE_FORMAT 2}
+		{!_TAG_FILE_SORTED 1}
+		Iface	tmp.d	3;"	c	inherits:
+		SomeClass	tmp.d	7;"	c	inherits:IFace
+		doStuff	tmp.d	10;"	f	arity:1	struct:SomeClass
+		freeFunction	tmp.d	16;"	f	arity:1
+		interfaceMethod	tmp.d	11;"	f	arity:0	struct:SomeClass
+		interfaceMethod	tmp.d	4;"	f	arity:0	struct:Iface
+		main	tmp.d	18;"	f	arity:1
+		theTee	tmp.d	13;"	m	struct:SomeClass
+		this	tmp.d	9;"	f	arity:0	struct:SomeClass
 
 # Line of Code count
 This option counts the logical lines of code in the given source files, not

From 2ff81e8240dd3fcdecdb8cee9580ff4ceaace169 Mon Sep 17 00:00:00 2001
From: Hackerpilot 
Date: Sun, 22 Apr 2012 19:03:04 -0700
Subject: [PATCH 4/7] Markdown formatting error

---
 README.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/README.md b/README.md
index 2973d33..2a8c0be 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,7 @@ Generates a JSON summary of the input file.
 
 ### Example
 The given D code:
+
 		module example;
 
 		import std.stdio;
@@ -225,6 +226,7 @@ is transformed into the following JSON markup:
 Dscanner can create a tags file from the specified file. Output is formatted as
 specified at http://ctags.sourceforge.net/FORMAT. The result of generating ctags
 on the same file used in the JSON example will produce this output:
+
 		{!_TAG_FILE_FORMAT 2}
 		{!_TAG_FILE_SORTED 1}
 		Iface	tmp.d	3;"	c	inherits:

From 4b6ef9809aa9ab5fa332b628f6f32a25ebf4b638 Mon Sep 17 00:00:00 2001
From: Hackerpilot 
Date: Sun, 22 Apr 2012 19:03:59 -0700
Subject: [PATCH 5/7] Markdown formatting error

---
 README.md | 308 +++++++++++++++++++++++++++---------------------------
 1 file changed, 154 insertions(+), 154 deletions(-)

diff --git a/README.md b/README.md
index 2a8c0be..c2e6433 100644
--- a/README.md
+++ b/README.md
@@ -31,213 +31,213 @@ Generates a JSON summary of the input file.
 ### Example
 The given D code:
 
-		module example;
+	module example;
 
-		import std.stdio;
+	import std.stdio;
 
-		interface Iface {
-			double interfaceMethod();
-		}
+	interface Iface {
+		double interfaceMethod();
+	}
 
-		class SomeClass(T) if (isSomeString!T) : IFace {
-		public:
-			this() {}
-			void doStuff(T);
-			override double interfaceMethod() {}
-		private:
-			T theTee;
-		}
+	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; }
+	int freeFunction(int x) { return x + x; }
 
-		void main(string[] args) {
+	void main(string[] args) {
 
-		}
+	}
 
 is transformed into the following JSON markup:
 
+	{
+	  "name" : "example",
+	  "imports" : [
+		"std.stdio"
+	  ],
+	  "interfaces" : [
 		{
-		  "name" : "example",
-		  "imports" : [
-			"std.stdio"
+		  "name" : "Iface",
+		  "line" : 5,
+		  "protection" : "public",
+		  "attributes" : [
 		  ],
-		  "interfaces" : [
+		  "constraint" : "",
+		  "templateParameters" : [
+		  ],
+		  "functions" : [
 			{
-			  "name" : "Iface",
-			  "line" : 5,
-			  "protection" : "public",
+			  "name" : "interfaceMethod",
+			  "line" : 6,
+			  "protection" : "",
 			  "attributes" : [
 			  ],
 			  "constraint" : "",
 			  "templateParameters" : [
 			  ],
-			  "functions" : [
-				{
-				  "name" : "interfaceMethod",
-				  "line" : 6,
-				  "protection" : "",
-				  "attributes" : [
-				  ],
-				  "constraint" : "",
-				  "templateParameters" : [
-				  ],
-				  "parameters" : [
-				  ],
-				  "returnType" : "double"
-				}
+			  "parameters" : [
 			  ],
-			  "variables" : [
-			  ],
-			  "baseClasses" : [
-			  ]
+			  "returnType" : "double"
 			}
 		  ],
-		  "classes" : [
+		  "variables" : [
+		  ],
+		  "baseClasses" : [
+		  ]
+		}
+	  ],
+	  "classes" : [
+		{
+		  "name" : "SomeClass",
+		  "line" : 9,
+		  "protection" : "public",
+		  "attributes" : [
+		  ],
+		  "constraint" : "if (isSomeString!T)",
+		  "templateParameters" : [
+			"T"
+		  ],
+		  "functions" : [
 			{
-			  "name" : "SomeClass",
-			  "line" : 9,
-			  "protection" : "public",
+			  "name" : "this",
+			  "line" : 11,
+			  "protection" : "",
 			  "attributes" : [
 			  ],
-			  "constraint" : "if (isSomeString!T)",
+			  "constraint" : "",
 			  "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"
-				}
+			  "parameters" : [
 			  ],
-			  "variables" : [
+			  "returnType" : ""
+			},
+			{
+			  "name" : "doStuff",
+			  "line" : 12,
+			  "protection" : "",
+			  "attributes" : [
+			  ],
+			  "constraint" : "",
+			  "templateParameters" : [
+			  ],
+			  "parameters" : [
 				{
-				  "name" : "theTee",
-				  "line" : 15,
-				  "protection" : "private",
+				  "name" : "",
+				  "line" : 0,
+				  "protection" : "",
 				  "attributes" : [
 				  ],
 				  "type" : "T"
 				}
 			  ],
-			  "baseClasses" : [
-				"IFace"
-			  ]
+			  "returnType" : "void"
+			},
+			{
+			  "name" : "interfaceMethod",
+			  "line" : 13,
+			  "protection" : "",
+			  "attributes" : [
+				"override"
+			  ],
+			  "constraint" : "",
+			  "templateParameters" : [
+			  ],
+			  "parameters" : [
+			  ],
+			  "returnType" : "double"
 			}
 		  ],
-		  "structs" : [
-		  ],
-		  "structs" : [
-		  ],
-		  "functions" : [
+		  "variables" : [
 			{
-			  "name" : "freeFunction",
+			  "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" : [
 			  ],
-			  "constraint" : "",
-			  "templateParameters" : [
-			  ],
-			  "parameters" : [
-				{
-				  "name" : "x",
-				  "line" : 18,
-				  "protection" : "",
-				  "attributes" : [
-				  ],
-				  "type" : "int"
-				}
-			  ],
-			  "returnType" : "int"
-			},
+			  "type" : "int"
+			}
+		  ],
+		  "returnType" : "int"
+		},
+		{
+		  "name" : "main",
+		  "line" : 20,
+		  "protection" : "",
+		  "attributes" : [
+		  ],
+		  "constraint" : "",
+		  "templateParameters" : [
+		  ],
+		  "parameters" : [
 			{
-			  "name" : "main",
+			  "name" : "args",
 			  "line" : 20,
 			  "protection" : "",
 			  "attributes" : [
 			  ],
-			  "constraint" : "",
-			  "templateParameters" : [
-			  ],
-			  "parameters" : [
-				{
-				  "name" : "args",
-				  "line" : 20,
-				  "protection" : "",
-				  "attributes" : [
-				  ],
-				  "type" : "string[]"
-				}
-			  ],
-			  "returnType" : "void"
+			  "type" : "string[]"
 			}
 		  ],
-		  "variables" : [
-		  ],
-		  "enums" : [
-		  ]
+		  "returnType" : "void"
 		}
+	  ],
+	  "variables" : [
+	  ],
+	  "enums" : [
+	  ]
+	}
 
 # Ctags output
 Dscanner can create a tags file from the specified file. Output is formatted as
 specified at http://ctags.sourceforge.net/FORMAT. The result of generating ctags
 on the same file used in the JSON example will produce this output:
 
-		{!_TAG_FILE_FORMAT 2}
-		{!_TAG_FILE_SORTED 1}
-		Iface	tmp.d	3;"	c	inherits:
-		SomeClass	tmp.d	7;"	c	inherits:IFace
-		doStuff	tmp.d	10;"	f	arity:1	struct:SomeClass
-		freeFunction	tmp.d	16;"	f	arity:1
-		interfaceMethod	tmp.d	11;"	f	arity:0	struct:SomeClass
-		interfaceMethod	tmp.d	4;"	f	arity:0	struct:Iface
-		main	tmp.d	18;"	f	arity:1
-		theTee	tmp.d	13;"	m	struct:SomeClass
-		this	tmp.d	9;"	f	arity:0	struct:SomeClass
+	{!_TAG_FILE_FORMAT 2}
+	{!_TAG_FILE_SORTED 1}
+	Iface	tmp.d	3;"	c	inherits:
+	SomeClass	tmp.d	7;"	c	inherits:IFace
+	doStuff	tmp.d	10;"	f	arity:1	struct:SomeClass
+	freeFunction	tmp.d	16;"	f	arity:1
+	interfaceMethod	tmp.d	11;"	f	arity:0	struct:SomeClass
+	interfaceMethod	tmp.d	4;"	f	arity:0	struct:Iface
+	main	tmp.d	18;"	f	arity:1
+	theTee	tmp.d	13;"	m	struct:SomeClass
+	this	tmp.d	9;"	f	arity:0	struct:SomeClass
 
 # Line of Code count
 This option counts the logical lines of code in the given source files, not

From 7fdf090144ee5fd95ff984b410d53539c326ea2a Mon Sep 17 00:00:00 2001
From: Hackerpilot 
Date: Sun, 22 Apr 2012 22:34:07 -0700
Subject: [PATCH 6/7] Fixed CTAGS output

---
 types.d | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/types.d b/types.d
index 9f4f23f..2016620 100644
--- a/types.d
+++ b/types.d
@@ -507,8 +507,9 @@ public:
 		}
 
 		sort(tags);
-		file.writeln("{!_TAG_FILE_FORMAT 2}");
-		file.writeln("{!_TAG_FILE_SORTED 1}");
+		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);

From ba90aa20b83d9058169acdd1bca24c0112bcd7fb Mon Sep 17 00:00:00 2001
From: Hackerpilot 
Date: Mon, 23 Apr 2012 01:25:19 -0700
Subject: [PATCH 7/7] Update readme to reflect bug fix in ctags output

---
 README.md | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index c2e6433..77a1e54 100644
--- a/README.md
+++ b/README.md
@@ -227,8 +227,9 @@ Dscanner can create a tags file from the specified file. Output is formatted as
 specified at http://ctags.sourceforge.net/FORMAT. The result of generating ctags
 on the same file used in the JSON example will produce this output:
 
-	{!_TAG_FILE_FORMAT 2}
-	{!_TAG_FILE_SORTED 1}
+	!_TAG_FILE_FORMAT 2
+	!_TAG_FILE_SORTED 1
+	!_TAG_PROGRAM_URL https://github.com/Hackerpilot/Dscanner/
 	Iface	tmp.d	3;"	c	inherits:
 	SomeClass	tmp.d	7;"	c	inherits:IFace
 	doStuff	tmp.d	10;"	f	arity:1	struct:SomeClass