/* -*- 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 oqr * 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 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 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(char16Value(ch)), 2); } else { f << 'u'; printHex(f, static_cast(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 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 printChars(FILE *file, const char *begin, const char *end) { ASSERT(end >= begin); size_t n = static_cast(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(c); if (buffer == '\n') buffer = '\r'; return static_cast(fwrite(&buffer, 1, 1, file)); } int std::fputs(const char *s, FILE *file) { return static_cast(printChars(file, s, s + strlen(s))); } int std::fprintf(FILE* file, const char *format, ...) { Buffer 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(fwrite(b.buffer, 1, static_cast(n), file)); } b.expand(b.size*2); } } #endif // XP_MAC_MPW // Write ch. void Formatter::printChar8(char ch) { printStr8(&ch, &ch + 1); } // Write ch. void Formatter::printChar16(char16 ch) { printStr16(&ch, &ch + 1); } // Write the null-terminated string str. void Formatter::printZStr8(const char *str) { printStr8(str, str + strlen(str)); } // Write the String s. void Formatter::printString16(const String &s) { const char16 *begin = s.data(); printStr16(begin, begin + s.size()); } // Write the printf format using the supplied args. void Formatter::printVFormat8(const char *format, va_list args) { Buffer b; while (true) { int n = vsnprintf(b.buffer, b.size, format, args); if (n >= 0 && static_cast(n) < b.size) { printStr8(b.buffer, b.buffer + n); return; } b.expand(b.size*2); } } static const int printCharBufferSize = 64; // Print ch count times. void 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, static_cast(c)); printString(f, str, str+c); } } // Print ch count times. void 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 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 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 }; BitSet<256> AsciiFileFormatter::defaultFilter(defaultFilterRanges); // Construct an AsciiFileFormatter using the given file and filter f. // If f is nil, use the default filter. AsciiFileFormatter::AsciiFileFormatter(FILE *file, BitSet<256> *f): file(file) #ifndef _WIN32 // Microsoft Visual C++ 6.0 bug , filter(f ? *f : defaultFilter) #endif { #ifdef _WIN32 // Microsoft Visual C++ 6.0 bug if (f) filter = *f; else filter = defaultFilter; #endif filterEmpty = filter.none(); } // Write ch, escaping non-ASCII characters. void AsciiFileFormatter::printChar8(char ch) { if (filterChar(ch)) fprintf(file, unprintableFormat, static_cast(ch)); else fputc(ch, file); } // Write ch, escaping non-ASCII characters. void AsciiFileFormatter::printChar16(char16 ch) { if (filterChar(ch)) fprintf(file, unprintableFormat, char16Value(ch)); else fputc(static_cast(ch), file); } // Write the null-terminated string str, escaping non-ASCII characters. void 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 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(ch)); } ++strBegin; } if (p != strBegin) printChars(file, p, strBegin); } } // Write the string between strBegin and strEnd, escaping non-ASCII characters. void 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(ch); if (q == buffer + sizeof buffer) { printChars(file, buffer, buffer + sizeof buffer); q = buffer; } } } if (q != buffer) printChars(file, buffer, q); } AsciiFileFormatter stdOut(stdout); AsciiFileFormatter stdErr(stderr); // Write ch. void StringFormatter::printChar8(char ch) { s += ch; } // Write ch. void StringFormatter::printChar16(char16 ch) { s += ch; } // Write the null-terminated string str. void StringFormatter::printZStr8(const char *str) { s += str; } // Write the string between strBegin and strEnd. void StringFormatter::printStr8(const char *strBegin, const char *strEnd) { appendChars(s, strBegin, strEnd); } // Write the string between strBegin and strEnd. void StringFormatter::printStr16(const char16 *strBegin, const char16 *strEnd) { s.append(strBegin, strEnd); } // Write the String str. void 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 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. PrettyPrinter::PrettyPrinter(Formatter &f, uint32 lineWidth): lineWidth(min(lineWidth, static_cast(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. 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 PrettyPrinter::outputBreak(bool sameLine, uint32 length) { leftSerialPos += length; if (sameLine) { outputPos += length; // Exceptions may be thrown below. printChar(outputFormatter, ' ', static_cast(length)); } else { lastBreak = ++lineNum; outputPos = margin; // Exceptions may be thrown below. outputFormatter << '\n'; printChar(outputFormatter, ' ', static_cast(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 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(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 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. PrettyPrinter::Item & PrettyPrinter::beginIndent(int32 offset) { Item *unindent = new(itemPool) Item(Item::indent, static_cast(-offset)); if (activeItems) { try { activeItems.push_back(*new(itemPool) Item(Item::indent, static_cast(offset))); } catch (...) { itemPool.destroy(unindent); throw; } } else { margin += offset; ASSERT(static_cast(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 PrettyPrinter::endIndent(Item &i) { if (activeItems) activeItems.push_back(i); else { margin += i.length; ASSERT(static_cast(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. PrettyPrinter::Item & 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(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 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 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 PrettyPrinter::printStr8(const char *strBegin, const char *strEnd) { while (strBegin != strEnd) { const char *sectionEnd = findValue(strBegin, strEnd, '\n'); uint32 sectionLength = static_cast(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(strBegin), reinterpret_cast(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 PrettyPrinter::printStr16(const char16 *strBegin, const char16 *strEnd) { while (strBegin != strEnd) { const char16 *sectionEnd = findValue(strBegin, strEnd, uni::lf); uint32 sectionLength = static_cast(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 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 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 PrettyPrinter::end() { ASSERT(!topRegion); reduceRightActiveItems(); reduceLeftActiveItems(infiniteLength); ASSERT(!savedBlocks && !itemStack && !activeItems && !itemText && rightSerialPos == leftSerialPos && !margin); } }