Mozilla/mozilla/js2/src/formatter.cpp
waldemar%netscape.com 565e137cb7 Eliminated unnecessary VC6 workaround
git-svn-id: svn://10.0.0.236/trunk@102976 18797224-902f-48f8-a5cc-f745e15eee43
2001-09-15 05:17:51 +00:00

873 lines
27 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* The contents of this file are subject to the Netscape Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is the JavaScript 2 Prototype.
*
* The Initial Developer of the Original Code is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the
* terms of the GNU Public License (the "GPL"), in which case the
* provisions of the GPL are applicable instead of those above.
* If you wish to allow use of your version of this file only
* under the terms of the GPL and not to allow others to use your
* version of this file under the NPL, indicate your decision by
* deleting the provisions above and replace them with the notice
* and other provisions required by the GPL. If you do not delete
* the provisions above, a recipient may use your version of this
* file under either the NPL or the GPL.
*/
#include "algo.h"
#include "formatter.h"
namespace JS = JavaScript;
static const char controlCharNames[6] = {'b', 't', 'n', 'v', 'f', 'r'};
// Print the characters from begin to end, escaping them as necessary to make
// the resulting string be readable if placed between two quotes specified by
// quote (which should be either '\'' or '"').
void JS::escapeString(Formatter &f, const char16 *begin, const char16 *end, char16 quote)
{
ASSERT(begin <= end);
const char16 *chunk = begin;
while (begin != end) {
char16 ch = *begin++;
CharInfo ci(ch);
if (char16Value(ch) < 0x20 || isLineBreak(ci) || isFormat(ci) || ch == '\\' || ch == quote) {
if (begin-1 != chunk)
printString(f, chunk, begin-1);
chunk = begin;
f << '\\';
switch (ch) {
case 0x0008:
case 0x0009:
case 0x000A:
case 0x000B:
case 0x000C:
case 0x000D:
f << controlCharNames[ch - 0x0008];
break;
case '\'':
case '"':
case '\\':
f << ch;
break;
case 0x0000:
if (begin == end || char16Value(*begin) < '0' || char16Value(*begin) > '9') {
f << '0';
break;
}
default:
if (char16Value(ch) <= 0xFF) {
f << 'x';
printHex(f, static_cast<uint32>(char16Value(ch)), 2);
} else {
f << 'u';
printHex(f, static_cast<uint32>(char16Value(ch)), 4);
}
}
}
}
if (begin != chunk)
printString(f, chunk, begin);
}
// Print s as a quoted string using the given quotes (which should be
// either '\'' or '"').
void JS::quoteString(Formatter &f, const String &s, char16 quote)
{
f << quote;
const char16 *begin = s.data();
escapeString(f, begin, begin + s.size(), quote);
f << quote;
}
#ifdef XP_MAC_MPW
// Macintosh MPW replacements for the ANSI routines. These translate LF's to
// CR's because the MPW libraries supplied by Metrowerks don't do that for some
// reason.
static void translateLFtoCR(char *begin, char *end)
{
while (begin != end) {
if (*begin == '\n')
*begin = '\r';
++begin;
}
}
size_t JS::printChars(FILE *file, const char *begin, const char *end)
{
ASSERT(end >= begin);
size_t n = toSize_t(end - begin);
size_t extra = 0;
char buffer[1024];
while (n > sizeof buffer) {
std::memcpy(buffer, begin, sizeof buffer);
translateLFtoCR(buffer, buffer + sizeof buffer);
extra += fwrite(buffer, 1, sizeof buffer, file);
n -= sizeof buffer;
begin += sizeof buffer;
}
std::memcpy(buffer, begin, n);
translateLFtoCR(buffer, buffer + n);
return extra + fwrite(buffer, 1, n, file);
}
int std::fputc(int c, FILE *file)
{
char buffer = static_cast<char>(c);
if (buffer == '\n')
buffer = '\r';
return static_cast<int>(fwrite(&buffer, 1, 1, file));
}
int std::fputs(const char *s, FILE *file)
{
return static_cast<int>(JS::printChars(file, s, s + strlen(s)));
}
int std::fprintf(FILE* file, const char *format, ...)
{
JS::Buffer<char, 1024> b;
while (true) {
va_list args;
va_start(args, format);
int n = vsnprintf(b.buffer, b.size, format, args);
va_end(args);
if (n >= 0 && n < b.size) {
translateLFtoCR(b.buffer, b.buffer + n);
return static_cast<int>(fwrite(b.buffer, 1, JS::toSize_t(n), file));
}
b.expand(b.size*2);
}
}
#endif // XP_MAC_MPW
// Write ch.
void JS::Formatter::printChar8(char ch)
{
printStr8(&ch, &ch + 1);
}
// Write ch.
void JS::Formatter::printChar16(char16 ch)
{
printStr16(&ch, &ch + 1);
}
// Write the null-terminated string str.
void JS::Formatter::printZStr8(const char *str)
{
printStr8(str, str + strlen(str));
}
// Write the String s.
void JS::Formatter::printString16(const String &s)
{
const char16 *begin = s.data();
printStr16(begin, begin + s.size());
}
// Write the printf format using the supplied args.
void JS::Formatter::printVFormat8(const char *format, va_list args)
{
Buffer<char, 1024> b;
while (true) {
int n = vsnprintf(b.buffer, b.size, format, args);
if (n >= 0 && static_cast<uint>(n) < b.size) {
printStr8(b.buffer, b.buffer + n);
return;
}
b.expand(b.size*2);
}
}
// Write either "true" or "false".
JS::Formatter &JS::Formatter::operator<<(bool b)
{
printZStr8(b ? "true" : "false");
return *this;
}
// Write the printf format using the supplied args.
void JS::printFormat(Formatter &f, const char *format, ...)
{
va_list args;
va_start(args, format);
f.printVFormat8(format, args);
va_end(args);
}
static const int printCharBufferSize = 64;
// Print ch count times.
void JS::printChar(Formatter &f, char ch, int count)
{
char str[printCharBufferSize];
while (count > 0) {
int c = count;
if (c > printCharBufferSize)
c = printCharBufferSize;
count -= c;
STD::memset(str, ch, toSize_t(c));
printString(f, str, str+c);
}
}
// Print ch count times.
void JS::printChar(Formatter &f, char16 ch, int count)
{
char16 str[printCharBufferSize];
while (count > 0) {
int c = count;
if (c > printCharBufferSize)
c = printCharBufferSize;
count -= c;
char16 *strEnd = str + c;
std::fill(str, strEnd, ch);
printString(f, str, strEnd);
}
}
// Print i using the given formatting string, padding on the left with pad
// characters to use at least nDigits characters.
void JS::printNum(Formatter &f, uint32 i, int nDigits, char pad, const char *format)
{
char str[20];
int n = sprintf(str, format, i);
if (n < nDigits)
printChar(f, pad, nDigits - n);
printString(f, str, str+n);
}
// Print p as a pointer.
void JS::printPtr(Formatter &f, void *p)
{
char str[20];
int n = sprintf(str, "%p", p);
printString(f, str, str+n);
}
// printf formats for printing non-ASCII characters on an ASCII stream
#ifdef XP_MAC
static const char unprintableFormat[] = "\xC7%.4X\xC8"; // Use angle quotes
#elif defined _WIN32
static const char unprintableFormat[] = "\xAB%.4X\xBB"; // Use angle quotes
#else
static const char unprintableFormat[] = "<%.4X>";
#endif
static const uint16 defaultFilterRanges[] = {
0x00, 0x09, // Filter all control characters except \t and \n
0x0B, 0x20,
0x7F, 0x100, // Filter all non-ASCII characters
0, 0
};
JS::BitSet<256> JS::AsciiFileFormatter::defaultFilter(defaultFilterRanges);
// Construct an AsciiFileFormatter using the given file and filter f.
// If f is nil, use the default filter.
JS::AsciiFileFormatter::AsciiFileFormatter(FILE *file, BitSet<256> *f):
file(file), filter(f ? *f : defaultFilter)
{
filterEmpty = filter.none();
}
// Write ch, escaping non-ASCII characters.
void JS::AsciiFileFormatter::printChar8(char ch)
{
if (filterChar(ch))
fprintf(file, unprintableFormat, static_cast<uchar>(ch));
else
fputc(ch, file);
}
// Write ch, escaping non-ASCII characters.
void JS::AsciiFileFormatter::printChar16(char16 ch)
{
if (filterChar(ch))
fprintf(file, unprintableFormat, char16Value(ch));
else
fputc(static_cast<char>(ch), file);
}
// Write the null-terminated string str, escaping non-ASCII characters.
void JS::AsciiFileFormatter::printZStr8(const char *str)
{
if (filterEmpty)
fputs(str, file);
else
printStr8(str, str + strlen(str));
}
// Write the string between strBegin and strEnd, escaping non-ASCII characters.
void JS::AsciiFileFormatter::printStr8(const char *strBegin, const char *strEnd)
{
if (filterEmpty)
printChars(file, strBegin, strEnd);
else {
ASSERT(strEnd >= strBegin);
const char *p = strBegin;
while (strBegin != strEnd) {
char ch = *strBegin;
if (filterChar(ch)) {
if (p != strBegin) {
printChars(file, p, strBegin);
p = strBegin;
}
fprintf(file, unprintableFormat, static_cast<uchar>(ch));
}
++strBegin;
}
if (p != strBegin)
printChars(file, p, strBegin);
}
}
// Write the string between strBegin and strEnd, escaping non-ASCII characters.
void JS::AsciiFileFormatter::printStr16(const char16 *strBegin, const char16 *strEnd)
{
char buffer[512];
ASSERT(strEnd >= strBegin);
char *q = buffer;
while (strBegin != strEnd) {
char16 ch = *strBegin++;
if (filterChar(ch)) {
if (q != buffer) {
printChars(file, buffer, q);
q = buffer;
}
fprintf(file, unprintableFormat, char16Value(ch));
} else {
*q++ = static_cast<char>(ch);
if (q == buffer + sizeof buffer) {
printChars(file, buffer, buffer + sizeof buffer);
q = buffer;
}
}
}
if (q != buffer)
printChars(file, buffer, q);
}
JS::AsciiFileFormatter JS::stdOut(stdout);
JS::AsciiFileFormatter JS::stdErr(stderr);
// Write ch.
void JS::StringFormatter::printChar8(char ch)
{
s += ch;
}
// Write ch.
void JS::StringFormatter::printChar16(char16 ch)
{
s += ch;
}
// Write the null-terminated string str.
void JS::StringFormatter::printZStr8(const char *str)
{
s += str;
}
// Write the string between strBegin and strEnd.
void JS::StringFormatter::printStr8(const char *strBegin, const char *strEnd)
{
appendChars(s, strBegin, strEnd);
}
// Write the string between strBegin and strEnd.
void JS::StringFormatter::printStr16(const char16 *strBegin, const char16 *strEnd)
{
s.append(strBegin, strEnd);
}
// Write the String str.
void JS::StringFormatter::printString16(const String &str)
{
s += str;
}
//
// Formatted Output
//
// See "Prettyprinting" by Derek Oppen in ACM Transactions on Programming
// Languages and Systems 2:4, October 1980, pages 477-482 for the algorithm.
// The default line width for pretty printing
uint32 JS::PrettyPrinter::defaultLineWidth = 20;
// Create a PrettyPrinter that outputs to Formatter f. The PrettyPrinter
// breaks lines at optional breaks so as to try not to exceed lines of width
// lineWidth, although it may not always be able to do so. Formatter f should
// be at the beginning of a line. Call end before destroying the Formatter;
// otherwise the last line may not be output to f.
JS::PrettyPrinter::PrettyPrinter(Formatter &f, uint32 lineWidth):
lineWidth(min(lineWidth, static_cast<uint32>(unlimitedLineWidth))),
outputFormatter(f),
outputPos(0),
lineNum(0),
lastBreak(0),
margin(0),
nNestedBlocks(0),
leftSerialPos(0),
rightSerialPos(0),
itemPool(20)
{
#ifdef DEBUG
topRegion = 0;
#endif
}
// Destroy the PrettyPrinter. Because it's a very bad idea for a destructor to
// throw exceptions, this destructor does not flush any buffered output. Call
// end just before destroying the PrettyPrinter to do that.
JS::PrettyPrinter::~PrettyPrinter()
{
ASSERT(!topRegion && !nNestedBlocks);
}
// Output either a line break (if sameLine is false) or length spaces (if
// sameLine is true). Also advance leftSerialPos by length.
//
// If this method throws an exception, it is guaranteed to already have updated
// all of the PrettyPrinter state; all that might be missing would be some
// output to outputFormatter.
void JS::PrettyPrinter::outputBreak(bool sameLine, uint32 length)
{
leftSerialPos += length;
if (sameLine) {
outputPos += length;
// Exceptions may be thrown below.
printChar(outputFormatter, ' ', static_cast<int>(length));
} else {
lastBreak = ++lineNum;
outputPos = margin;
// Exceptions may be thrown below.
outputFormatter << '\n';
printChar(outputFormatter, ' ', static_cast<int>(margin));
}
}
// Check to see whether (rightSerialPos+rightOffset)-leftSerialPos has gotten so large that we may pop items
// off the left end of activeItems because their totalLengths are known to be larger than the
// amount of space left on the current line.
// Return true if there are any items left on activeItems.
//
// If this method throws an exception, it leaves the PrettyPrinter in a consistent state, having
// atomically popped off one or more items from the left end of activeItems.
bool JS::PrettyPrinter::reduceLeftActiveItems(uint32 rightOffset)
{
uint32 newRightSerialPos = rightSerialPos + rightOffset;
while (activeItems) {
Item *leftItem = &activeItems.front();
if (itemStack && leftItem == itemStack.front()) {
if (outputPos + newRightSerialPos - leftSerialPos > lineWidth) {
itemStack.pop_front();
leftItem->lengthKnown = true;
leftItem->totalLength = infiniteLength;
} else if (leftItem->lengthKnown)
itemStack.pop_front();
}
if (!leftItem->lengthKnown)
return true;
activeItems.pop_front();
try {
uint32 length = leftItem->length;
switch (leftItem->kind) {
case Item::text:
{
outputPos += length;
leftSerialPos += length;
// Exceptions may be thrown below.
char16 *textBegin;
char16 *textEnd;
do {
length -= itemText.pop_front(length, textBegin, textEnd);
printString(outputFormatter, textBegin, textEnd);
} while (length);
}
break;
case Item::blockBegin:
case Item::indentBlockBegin:
{
BlockInfo *b = savedBlocks.advance_back();
b->margin = margin;
b->lastBreak = lastBreak;
b->fits = outputPos + leftItem->totalLength <= lineWidth;
if (leftItem->hasKind(Item::blockBegin))
margin = outputPos;
else
margin += length;
}
break;
case Item::blockEnd:
{
BlockInfo &b = savedBlocks.pop_back();
margin = b.margin;
lastBreak = b.lastBreak;
}
break;
case Item::indent:
margin += length;
ASSERT(static_cast<int32>(margin) >= 0);
break;
case Item::linearBreak:
// Exceptions may be thrown below, but only after updating the PrettyPrinter.
outputBreak(savedBlocks.back().fits, length);
break;
case Item::fillBreak:
// Exceptions may be thrown below, but only after updating the PrettyPrinter.
outputBreak(lastBreak == lineNum && outputPos + leftItem->totalLength <= lineWidth, length);
break;
}
} catch (...) {
itemPool.destroy(leftItem);
throw;
}
itemPool.destroy(leftItem);
}
return false;
}
// A break or end of input is about to be processed. Check whether there are
// any complete blocks or clumps on the itemStack whose lengths we can now
// compute; if so, compute these and pop them off the itemStack.
// The current rightSerialPos must be the beginning of the break or end of input.
//
// This method can't throw exceptions.
void JS::PrettyPrinter::reduceRightActiveItems()
{
uint32 nUnmatchedBlockEnds = 0;
while (itemStack) {
Item *rightItem = itemStack.pop_back();
switch (rightItem->kind) {
case Item::blockBegin:
case Item::indentBlockBegin:
if (!nUnmatchedBlockEnds) {
itemStack.fast_push_back(rightItem);
return;
}
rightItem->computeTotalLength(rightSerialPos);
--nUnmatchedBlockEnds;
break;
case Item::blockEnd:
++nUnmatchedBlockEnds;
break;
case Item::linearBreak:
case Item::fillBreak:
rightItem->computeTotalLength(rightSerialPos);
if (!nUnmatchedBlockEnds)
// There can be at most one consecutive break posted on the itemStack.
return;
break;
default:
ASSERT(false); // Other kinds can't be pushed onto the itemStack.
}
}
}
// Indent the beginning of every new line after this one by offset until the
// corresponding endIndent call. Return an Item to pass to endIndent that will
// end this indentation. This method may throw an exception, in which case the
// PrettyPrinter is left unchanged.
JS::PrettyPrinter::Item &JS::PrettyPrinter::beginIndent(int32 offset)
{
Item *unindent = new(itemPool) Item(Item::indent, static_cast<uint32>(-offset));
if (activeItems) {
try {
activeItems.push_back(*new(itemPool) Item(Item::indent, static_cast<uint32>(offset)));
} catch (...) {
itemPool.destroy(unindent);
throw;
}
} else {
margin += offset;
ASSERT(static_cast<int32>(margin) >= 0);
}
return *unindent;
}
// End an indent began by beginIndent. i should be the result of a beginIndent.
// This method can't throw exceptions (it's called by the Indent destructor).
void JS::PrettyPrinter::endIndent(Item &i)
{
if (activeItems)
activeItems.push_back(i);
else {
margin += i.length;
ASSERT(static_cast<int32>(margin) >= 0);
itemPool.destroy(&i);
}
}
// Begin a logical block. If kind is Item::indentBlockBegin, offset is the
// indent to use for the second and subsequent lines of this block.
// Return an Item to pass to endBlock that will end this block.
// This method may throw an exception, in which case the PrettyPrinter is left
// unchanged.
JS::PrettyPrinter::Item &JS::PrettyPrinter::beginBlock(Item::Kind kind, int32 offset)
{
uint32 newNNestedBlocks = nNestedBlocks + 1;
savedBlocks.reserve(newNNestedBlocks);
itemStack.reserve_back(1 + newNNestedBlocks);
Item *endItem = new(itemPool) Item(Item::blockEnd);
Item *beginItem;
try {
beginItem = new(itemPool) Item(kind, static_cast<uint32>(offset), rightSerialPos);
} catch (...) {
itemPool.destroy(endItem);
throw;
}
// No state modifications before this point.
// No exceptions after this point.
activeItems.push_back(*beginItem);
itemStack.fast_push_back(beginItem);
nNestedBlocks = newNNestedBlocks;
return *endItem;
}
// End a logical block began by beginBlock. i should be the result of a
// beginBlock.
// This method can't throw exceptions (it's called by the Block destructor).
void JS::PrettyPrinter::endBlock(Item &i)
{
activeItems.push_back(i);
itemStack.fast_push_back(&i);
--nNestedBlocks;
}
// Write a conditional line break. This kind of a line break can only be
// emitted inside a block.
// A linear line break starts a new line if the containing block cannot be put
// all one one line; otherwise the line break is replaced by nSpaces spaces.
// Typically a block contains several linear breaks; either they all start new
// lines or none of them do.
// Moreover, if a block directly contains a required break then linear breaks
// become required breaks.
//
// A fill line break starts a new line if either the preceding clump or the
// following clump cannot be placed entirely on one line or if the following
// clump would not fit on the current line. A clump is a consecutive sequence
// of strings and nested blocks delimited by either a break or the beginning or
// end of the currently enclosing block.
//
// If this method throws an exception, it leaves the PrettyPrinter in a
// consistent state.
void JS::PrettyPrinter::conditionalBreak(uint32 nSpaces, Item::Kind kind)
{
ASSERT(nSpaces <= unlimitedLineWidth && nNestedBlocks);
reduceRightActiveItems();
itemStack.reserve_back(1 + nNestedBlocks);
// Begin of exception-atomic stack update. Only new(itemPool) can throw
// an exception here, in which case nothing is updated.
Item *i = new(itemPool) Item(kind, nSpaces, rightSerialPos);
activeItems.push_back(*i);
itemStack.fast_push_back(i);
rightSerialPos += nSpaces;
// End of exception-atomic stack update.
reduceLeftActiveItems(0);
}
// Write the string between strBegin and strEnd. Any embedded newlines ('\n'
// only) become required line breaks.
//
// If this method throws an exception, it may have partially formatted the
// string but leaves the PrettyPrinter in a consistent state.
void JS::PrettyPrinter::printStr8(const char *strBegin, const char *strEnd)
{
while (strBegin != strEnd) {
const char *sectionEnd = findValue(strBegin, strEnd, '\n');
uint32 sectionLength = static_cast<uint32>(sectionEnd - strBegin);
if (sectionLength) {
if (reduceLeftActiveItems(sectionLength)) {
itemText.reserve_back(sectionLength);
Item &backItem = activeItems.back();
// Begin of exception-atomic update. Only new(itemPool) can throw an exception here,
// in which case nothing is updated.
if (backItem.hasKind(Item::text))
backItem.length += sectionLength;
else
activeItems.push_back(*new(itemPool) Item(Item::text, sectionLength));
rightSerialPos += sectionLength;
itemText.fast_append(reinterpret_cast<const uchar *>(strBegin), reinterpret_cast<const uchar *>(sectionEnd));
// End of exception-atomic update.
} else {
ASSERT(!itemStack && !activeItems && !itemText && leftSerialPos == rightSerialPos);
outputPos += sectionLength;
printString(outputFormatter, strBegin, sectionEnd);
}
strBegin = sectionEnd;
if (strBegin == strEnd)
break;
}
requiredBreak();
++strBegin;
}
}
// Write the string between strBegin and strEnd. Any embedded newlines ('\n'
// only) become required line breaks.
//
// If this method throws an exception, it may have partially formatted the
// string but leaves the PrettyPrinter in a consistent state.
void JS::PrettyPrinter::printStr16(const char16 *strBegin, const char16 *strEnd)
{
while (strBegin != strEnd) {
const char16 *sectionEnd = findValue(strBegin, strEnd, uni::lf);
uint32 sectionLength = static_cast<uint32>(sectionEnd - strBegin);
if (sectionLength) {
if (reduceLeftActiveItems(sectionLength)) {
itemText.reserve_back(sectionLength);
Item &backItem = activeItems.back();
// Begin of exception-atomic update. Only new(itemPool) can throw an exception here,
// in which case nothing is updated.
if (backItem.hasKind(Item::text))
backItem.length += sectionLength;
else
activeItems.push_back(*new(itemPool) Item(Item::text, sectionLength));
rightSerialPos += sectionLength;
itemText.fast_append(strBegin, sectionEnd);
// End of exception-atomic update.
} else {
ASSERT(!itemStack && !activeItems && !itemText && leftSerialPos == rightSerialPos);
outputPos += sectionLength;
printString(outputFormatter, strBegin, sectionEnd);
}
strBegin = sectionEnd;
if (strBegin == strEnd)
break;
}
requiredBreak();
++strBegin;
}
}
// Write a required line break.
//
// If this method throws an exception, it may have emitted partial output but
// leaves the PrettyPrinter in a consistent state.
void JS::PrettyPrinter::requiredBreak()
{
reduceRightActiveItems();
reduceLeftActiveItems(infiniteLength);
ASSERT(!itemStack && !activeItems && !itemText && leftSerialPos == rightSerialPos);
outputBreak(false, 0);
}
// If required is true, write a required line break; otherwise write a linear
// line break of the given width.
//
// If this method throws an exception, it may have emitted partial output but
// leaves the PrettyPrinter in a consistent state.
void JS::PrettyPrinter::linearBreak(uint32 nSpaces, bool required)
{
if (required)
requiredBreak();
else
linearBreak(nSpaces);
}
// Flush any saved output in the PrettyPrinter to the output. Call this just
// before destroying the PrettyPrinter. All Indent and Block objects must have
// been exited already.
//
// If this method throws an exception, it may have emitted partial output but
// leaves the PrettyPrinter in a consistent state.
void JS::PrettyPrinter::end()
{
ASSERT(!topRegion);
reduceRightActiveItems();
reduceLeftActiveItems(infiniteLength);
ASSERT(!savedBlocks && !itemStack && !activeItems && !itemText && rightSerialPos == leftSerialPos && !margin);
}