Checking in the code
This commit is contained in:
parent
d88453cf4f
commit
9d5a8ce7e6
231
README.md
231
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
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
dmd *.d -release -noboundscheck -O -w -wi -m64 -property -ofdscanner -D
|
||||||
|
#dmd *.d -g -unittest -m64 -w -wi -property -oftokenizer
|
||||||
|
|
@ -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, "");
|
||||||
|
}
|
||||||
|
|
@ -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(`<span class="`, cssClass, `">`, value.replace("<", "<"), `</span>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
void highlight(Token[] tokens)
|
||||||
|
{
|
||||||
|
stdout.writeln(q"[<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
|
||||||
|
<body>
|
||||||
|
<style type="text/css">
|
||||||
|
html { background-color: #111; color: #ccc; }
|
||||||
|
.keyword { font-weight: bold; color: DeepSkyBlue; }
|
||||||
|
.comment { color: lightgreen; font-style: italic;}
|
||||||
|
.number { color: red; font-weigth: bold; }
|
||||||
|
.string { color: Tomato; font-style: italic; }
|
||||||
|
.property { color: HotPink; font-weight: bold;}
|
||||||
|
.operator { color: tan; font-weight: bold; }
|
||||||
|
.type { color: cyan; }
|
||||||
|
</style>
|
||||||
|
<pre>]");
|
||||||
|
|
||||||
|
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("</pre>\n</body></html>");
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue