1/* 2 * Copyright (C) 2013 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26function FormatterContentBuilder(mapping, originalLineEndings, formattedLineEndings, originalOffset, formattedOffset, indentString) 27{ 28 this._originalContent = null; 29 this._formattedContent = []; 30 this._formattedContentLength = 0; 31 32 this._startOfLine = true; 33 this.lastTokenWasNewline = false; 34 this.lastTokenWasWhitespace = false; 35 this.lastNewlineAppendWasMultiple = false; 36 37 this._indent = 0; 38 this._indentString = indentString; 39 this._indentCache = ["", this._indentString]; 40 41 this._mapping = mapping; 42 this._originalLineEndings = originalLineEndings || []; 43 this._formattedLineEndings = formattedLineEndings || []; 44 this._originalOffset = originalOffset || 0; 45 this._formattedOffset = formattedOffset || 0; 46 47 this._lastOriginalPosition = 0; 48 this._lastFormattedPosition = 0; 49} 50 51FormatterContentBuilder.prototype = { 52 constructor: FormatterContentBuilder, 53 54 // Public 55 56 get originalContent() 57 { 58 return this._originalContent; 59 }, 60 61 get formattedContent() 62 { 63 var formatted = this._formattedContent.join(""); 64 console.assert(formatted.length === this._formattedContentLength); 65 return formatted; 66 }, 67 68 get mapping() 69 { 70 return this._mapping; 71 }, 72 73 get originalLineEndings() 74 { 75 return this._originalLineEndings; 76 }, 77 78 get formattedLineEndings() 79 { 80 return this._formattedLineEndings; 81 }, 82 83 setOriginalContent: function(originalContent) 84 { 85 console.assert(!this._originalContent); 86 this._originalContent = originalContent; 87 }, 88 89 appendToken: function(string, originalPosition) 90 { 91 if (this._startOfLine) 92 this._appendIndent(); 93 94 this._addMappingIfNeeded(originalPosition); 95 96 this._append(string); 97 this._startOfLine = false; 98 this.lastTokenWasNewline = false; 99 this.lastTokenWasWhitespace = false; 100 }, 101 102 appendSpace: function() 103 { 104 if (!this._startOfLine) { 105 this._append(" "); 106 this.lastTokenWasNewline = false; 107 this.lastTokenWasWhitespace = true; 108 } 109 }, 110 111 appendNewline: function(force) 112 { 113 if ((!this.lastTokenWasNewline && !this._startOfLine) || force) { 114 this._append("\n"); 115 this._addFormattedLineEnding(); 116 this._startOfLine = true; 117 this.lastTokenWasNewline = true; 118 this.lastTokenWasWhitespace = false; 119 this.lastNewlineAppendWasMultiple = false; 120 } 121 }, 122 123 appendMultipleNewlines: function(newlines) 124 { 125 console.assert(newlines > 0); 126 127 var wasMultiple = newlines > 1; 128 129 while (newlines-- > 0) 130 this.appendNewline(true); 131 132 if (wasMultiple) 133 this.lastNewlineAppendWasMultiple = true; 134 }, 135 136 removeLastNewline: function() 137 { 138 console.assert(this.lastTokenWasNewline); 139 console.assert(this._formattedContent.lastValue === "\n"); 140 if (this.lastTokenWasNewline) { 141 this._popNewLine(); 142 this._startOfLine = false; 143 this.lastTokenWasNewline = false; 144 this.lastTokenWasWhitespace = false; 145 } 146 }, 147 148 indent: function() 149 { 150 ++this._indent; 151 }, 152 153 dedent: function() 154 { 155 --this._indent; 156 157 console.assert(this._indent >= 0); 158 if (this._indent < 0) 159 this._indent = 0; 160 }, 161 162 addOriginalLineEnding: function(originalPosition) 163 { 164 this._originalLineEndings.push(originalPosition); 165 }, 166 167 finish: function() 168 { 169 this.appendNewline(); 170 }, 171 172 // Private 173 174 _popNewLine: function() 175 { 176 var removed = this._formattedContent.pop(); 177 this._formattedContentLength -= removed.length; 178 this._formattedLineEndings.pop(); 179 }, 180 181 _append: function(str) 182 { 183 this._formattedContent.push(str); 184 this._formattedContentLength += str.length; 185 }, 186 187 _appendIndent: function() 188 { 189 // Indent is already in the cache. 190 if (this._indent < this._indentCache.length) { 191 this._append(this._indentCache[this._indent]); 192 return; 193 } 194 195 // Indent was not in the cache, fill up the cache up with what was needed. 196 const maxCacheIndent = 20; 197 var max = Math.min(this._indent, maxCacheIndent); 198 for (var i = this._indentCache.length; i <= max; ++i) 199 this._indentCache[i] = this._indentCache[i-1] + this._indentString; 200 201 // Append indents as needed. 202 var indent = this._indent; 203 do { 204 if (indent >= maxCacheIndent) 205 this._append(this._indentCache[maxCacheIndent]); 206 else 207 this._append(this._indentCache[indent]); 208 indent -= maxCacheIndent; 209 } while (indent > 0); 210 }, 211 212 _addMappingIfNeeded: function(originalPosition) 213 { 214 if (originalPosition - this._lastOriginalPosition === this._formattedContentLength - this._lastFormattedPosition) 215 return; 216 217 this._mapping.original.push(this._originalOffset + originalPosition); 218 this._mapping.formatted.push(this._formattedOffset + this._formattedContentLength); 219 220 this._lastOriginalPosition = originalPosition; 221 this._lastFormattedPosition = this._formattedContentLength; 222 }, 223 224 _addFormattedLineEnding: function() 225 { 226 console.assert(this._formattedContent.lastValue === "\n"); 227 this._formattedLineEndings.push(this._formattedContentLength - 1); 228 } 229} 230