dfmt/src/dfmt/indentation.d

306 lines
8.2 KiB
D

// Copyright Brian Schott 2015.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
module dfmt.indentation;
import dparse.lexer;
import std.bitmanip : bitfields;
/**
* Returns: true if the given token type is a wrap indent type
*/
bool isWrapIndent(IdType type) pure nothrow @nogc @safe
{
return type != tok!"{" && type != tok!"case" && type != tok!"@"
&& type != tok!"]" && type != tok!"(" && type != tok!")" && isOperator(type);
}
/**
* Returns: true if the given token type is a temporary indent type
*/
bool isTempIndent(IdType type) pure nothrow @nogc @safe
{
return type != tok!")" && type != tok!"{" && type != tok!"case" && type != tok!"@";
}
/**
* Stack for managing indent levels.
*/
struct IndentStack
{
static struct Details
{
mixin(bitfields!(
// generally true for all operators except {, case, @, ], (, )
bool, "wrap", 1,
// temporary indentation which get's reverted when a block starts
// generally true for all tokens except ), {, case, @
bool, "temp", 1,
// emit minimal newlines
bool, "mini", 1,
// for associative arrays or arrays containing them, break after every item
bool, "breakEveryItem", 1,
// when an item inside an array would break mid-item, definitely break at the comma first
bool, "preferLongBreaking", 1,
uint, "", 27));
}
/**
* Get the indent size at the most recent occurrence of the given indent type
*/
int indentToMostRecent(IdType item) const
{
if (index == 0)
return -1;
size_t i = index - 1;
while (true)
{
if (arr[i] == item)
return indentSize(i);
if (i > 0)
i--;
else
return -1;
}
}
int wrapIndents() const pure nothrow @property
{
if (index == 0)
return 0;
int tempIndentCount = 0;
for (size_t i = index; i > 0; i--)
{
if (!details[i - 1].wrap && arr[i - 1] != tok!"]")
break;
tempIndentCount++;
}
return tempIndentCount;
}
/**
* Pushes the given indent type on to the stack.
*/
void push(IdType item) pure nothrow
{
Details detail;
detail.wrap = isWrapIndent(item);
detail.temp = isTempIndent(item);
push(item, detail);
}
/**
* Pushes the given indent type on to the stack.
*/
void push(IdType item, Details detail) pure nothrow
{
arr[index] = item;
details[index] = detail;
//FIXME this is actually a bad thing to do,
//we should not just override when the stack is
//at it's limit
if (index < arr.length)
{
index++;
}
}
/**
* Pops the top indent from the stack.
*/
void pop() pure nothrow
{
if (index)
index--;
}
/**
* Pops all wrapping indents from the top of the stack.
*/
void popWrapIndents() pure nothrow @safe @nogc
{
while (index > 0 && details[index - 1].wrap)
index--;
}
/**
* Pops all temporary indents from the top of the stack.
*/
void popTempIndents() pure nothrow @safe @nogc
{
while (index > 0 && details[index - 1].temp)
index--;
}
bool topAre(IdType[] types...)
{
if (types.length > index)
return false;
return arr[index - types.length .. index] == types;
}
/**
* Returns: `true` if the top of the indent stack is the given indent type.
*/
bool topIs(IdType type) const pure nothrow @safe @nogc
{
return index > 0 && index <= arr.length && arr[index - 1] == type;
}
/**
* Returns: `true` if the top of the indent stack is a temporary indent
*/
bool topIsTemp()
{
return index > 0 && index <= arr.length && details[index - 1].temp;
}
/**
* Returns: `true` if the top of the indent stack is a temporary indent with the specified token
*/
bool topIsTemp(IdType item)
{
return index > 0 && index <= arr.length && arr[index - 1] == item && details[index - 1].temp;
}
/**
* Returns: `true` if the top of the indent stack is a wrapping indent
*/
bool topIsWrap()
{
return index > 0 && index <= arr.length && details[index - 1].wrap;
}
/**
* Returns: `true` if the top of the indent stack is a temporary indent with the specified token
*/
bool topIsWrap(IdType item)
{
return index > 0 && index <= arr.length && arr[index - 1] == item && details[index - 1].wrap;
}
/**
* Returns: `true` if the top of the indent stack is one of the given token
* types.
*/
bool topIsOneOf(IdType[] types...) const pure nothrow @safe @nogc
{
if (index == 0)
return false;
immutable topType = arr[index - 1];
foreach (t; types)
if (t == topType)
return true;
return false;
}
IdType top() const pure nothrow @property @safe @nogc
{
return arr[index - 1];
}
Details topDetails() const pure nothrow @property @safe @nogc
{
return details[index - 1];
}
int indentLevel() const pure nothrow @property @safe @nogc
{
return indentSize();
}
int length() const pure nothrow @property @safe @nogc
{
return cast(int) index;
}
/**
* Dumps the current state of the indentation stack to `stderr`. Used for debugging.
*/
void dump(size_t pos = size_t.max, string file = __FILE__, uint line = __LINE__)
{
import dparse.lexer : str;
import std.algorithm.iteration : map;
import std.stdio : stderr;
if (pos == size_t.max)
stderr.writefln("\033[31m%s:%d %(%s %)\033[0m", file, line, arr[0 .. index].map!(a => str(a)));
else
stderr.writefln("\033[31m%s:%d at %d %(%s %)\033[0m", file, line, pos, arr[0 .. index].map!(a => str(a)));
}
private:
size_t index;
IdType[256] arr;
Details[arr.length] details;
int indentSize(const size_t k = size_t.max) const pure nothrow @safe @nogc
{
import std.algorithm : among;
if (index == 0 || k == 0)
return 0;
immutable size_t j = k == size_t.max ? index : k;
int size = 0;
int parenCount;
foreach (i; 0 .. j)
{
immutable int pc = (arr[i] == tok!"!" || arr[i] == tok!"(" || arr[i] == tok!")") ? parenCount + 1
: parenCount;
if ((details[i].wrap || arr[i] == tok!"(") && parenCount > 1)
{
parenCount = pc;
continue;
}
if (i + 1 < index)
{
immutable currentIsNonWrapTemp = !details[i].wrap
&& details[i].temp && arr[i] != tok!")" && arr[i] != tok!"!";
if (arr[i] == tok!"static"
&& arr[i + 1].among!(tok!"if", tok!"else", tok!"foreach", tok!"foreach_reverse")
&& (i + 2 >= index || arr[i + 2] != tok!"{"))
{
parenCount = pc;
continue;
}
if (currentIsNonWrapTemp && (arr[i + 1] == tok!"switch"
|| arr[i + 1] == tok!"{" || arr[i + 1] == tok!")"))
{
parenCount = pc;
continue;
}
}
else if (parenCount == 0 && arr[i] == tok!"(")
size++;
if (arr[i] == tok!"!")
size++;
parenCount = pc;
size++;
}
return size;
}
}
unittest
{
IndentStack stack;
stack.push(tok!"{");
assert(stack.length == 1);
assert(stack.indentLevel == 1);
stack.pop();
assert(stack.length == 0);
assert(stack.indentLevel == 0);
stack.push(tok!"if");
assert(stack.topIsTemp());
stack.popTempIndents();
assert(stack.length == 0);
}